Repository navigation
feat(images): Merge images, pictures one under another or side by side (#645) - #682
Merged
Merged
Conversation
#645) A new preset on the back of the images donut, before Merge PDF (in place of GIF, which stays in the library). Dropping several pictures on it opens the merge order window, which for pictures also offers the layout: top to bottom or side by side, the same width / height or their own sizes centred, a gap in pixels and a background colour or transparency. The choices are remembered in the preset. The ImageMagick engine merges them (IImageJoiner) into one image named after the first picture ({name}_merged), in its format (PNG when Filee can't write it). Pictures ImageMagick doesn't read become PNG first. A merged image over 200 megapixels, the size of the longest Long image, is refused with its size. Settings schema 53 adds the preset to existing libraries and the images donut's back face. The new layout fields are separate fields with lenient enum converters, so older presets files and Filee 2.1.0 stay readable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A "Pixelate / blur" tool on the images donut opens the pictures one at a time in a window drawn like the crop overlay: drag frames over names, faces or numbers, each one showing the picture already pixelated or blurred, move and size them, pick Pixelate or Blur and a strength, then Hide & Save (or Hide & Next). Each picture is written by an ordinary conversion in its own format (PNG when it can't be written), so the toast, the output rules and the history work as usual; the frames carry on to the next picture of a batch. Pixelate averages squares on a grid laid from the picture's corner; blur averages into smaller squares first and then blurs about a line of text wide, so a light, reversible blur is never used. Both are sized from the picture's shorter side. The job queue covers the parts once, in the first step whose engine applies image options, refuses a route that can't, never puts the original back for "only if smaller", and keeps no Retry for them. The previews of the uncovered picture a file carries (EXIF, Photoshop, XMP thumbnails) are dropped. Settings schema 52 adds the tool after Crop and on the images donut after Half size (or on the back face when the front is full). Pixelate or blur and the strength are remembered in the tool, in fields of their own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The toolbar gave up its names once they didn't fit, judged against widths that happened to fit at the window's default size with Windows' fonts (German with Skip: 978 of 982 px). With CI's Noto fonts they didn't, so the test that expected the names back at 1040 px failed there. The toolbar now measures each level with the fonts in use and takes the first that fits: all names; then without the names of Clear all and Skip and the keycaps; then icons only. The test checks that rule against the measured widths instead of assuming where names fit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te / blur (52) side by side Both tools sit after "Half size" on the images donut and after "Crop" in the library, Edit pictures first; the v52 step puts Pixelate / blur after Edit pictures when that is there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…late / blur (52), Merge images (53) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 of 8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this change?
Closes #645.
A new preset, Merge images, puts several pictures into one image, one under another or side by side. The front and back of an ID card or a driving licence become one JPG to upload, in one step instead of Merge PDF and then Long image.
It works like Merge videos: dropping two or more pictures on the slice opens the merge order window, where the pictures are sorted (by name, as people count, then by hand with the arrows or Alt+↑ / Alt+↓). For pictures the window has a layout card below the list:
The button is named after the preset ("Merge images"; for the existing merges it now says "Merge videos" / "Merge audio" instead of always "Merge PDF"). The choices go into the merge and are remembered in the preset for next time; Cancel starts nothing and remembers nothing.
The result is one image named after the first picture,
{name}_merged, as Merge videos names its file, in the first picture's format (photo_merged.jpg). A first picture Filee can only read (HEIC, RAW, SVG, ...) gives a PNG. The preset's other picture options (resize, quality, grayscale, size limit) apply to the merged image; rotation and EXIF orientation apply to each picture before it is placed.How it is built
ImageOptionsgetsJoinIntoOne,JoinDirection,JoinFit,JoinGapandJoinBackground;Preset.JoinsImagessays a preset merges pictures. The two enums are new fields withLenientEnumJsonConverter, so a value from a newer Filee reads as the default. Filee 2.1.0 reads such a presets.json: it skips the fields it doesn't know (there the slice converts the pictures one by one).IImageJoiner(Core), implemented by the ImageMagick engine (Magick/ImageJoiner.cs). The layout is worked out from the pictures' sizes first, then each picture is read, scaled and drawn onto the merged image, so only one picture at a time and the result are in memory (a few small pictures are kept from the first read instead of being read twice). Pictures are turned by their EXIF orientation and put into sRGB, so photos with a Display P3 or CMYK profile match.JobQueue.RunMergedImagesAsync: pictures ImageMagick doesn't read as they are (ICNS, JPEG XR, SVG and other drawings) become a plain PNG in the work folder first, without the preset's options, which apply once to the merged image. Writing the one output is shared with Merge videos (WriteJoinedAsync), so naming, progress, cancelling and undoing a failed write are the same.PdfiumConverter.MaxStackedPixels, about 800 MB while the image is put together). Long image scales a longer stack down; a merged picture is meant to be seen at its size, so a merge beyond that is refused with a translated message and its size, e.g. "The merged picture would be too large, merge fewer or smaller pictures (72000 × 4000 px)". Twelve 24 MP photos side by side are refused, eight fit. The format's own limits (JPG 65,500 px, WebP 16,383 px) scale the result as for every other picture.Donut placement
Multi-file tools live on back faces, so Merge images goes on the back of the images donut, right before Merge PDF. That face was full (12 slices), so GIF gives its slot: a still picture saved as GIF (256 colours) is the least useful slice there. GIF stays in the library and on the video donut's front, and can be put back in Settings → Toolbar.
Settings schema 53
Main is at 50; #617 takes 51 and another PR 52, so this uses 53. It may need renumbering at merge time depending on what lands first. The step:
Type of change
How was it tested?
dotnet format --verify-no-changesdotnet build Filee.slnx -c Releasedotnet test: Core 664, App 1449, Setup 196, Cli 39 and Platform 86 pass; Engines 1837 pass and 2 failed while every project ran at once (HwpxReaderTests.A_hundred_pages_are_read_in_well_under_a_second, a timing test, andAppLocalRuntimeTests.ProcessRunner_reports_a_missing_DLL_instead_of_the_exit_code); both pass when run again alone and touch nothing here.MergeImagesTests(Core): the preset and its place on the donut, the schema 50 → 53 step (full face, free slot, a face the user laid out, the option put back), the presets file with the new fields and a newer Filee's values, and the queue with a fake joiner (order, first picture's format, PNG fallback, a picture that becomes PNG first without the preset's options, refusals).MergeImagesEngineTests(Engines, real ImageMagick on generated pictures): top to bottom at the same width with a white gap; side by side at their own sizes, centred on a transparent background; a greyscale picture next to a JPG photo with a navy gap; each picture turned before the merge; an SVG merged with a photo; the preset's resize on the merged image; the layout maths; the 200 MP limit; background colours.MergeOrderTests(App, headless): from the donut, the window shows the layout, the choices (side by side, own sizes, gap 6, transparent) give a 156 × 80 PNG and are remembered in presets.json; Cancel starts nothing and remembers nothing. The layout fits at the window's smallest size in en, de, ar (mirrored), ko, ru, hi, ne and th, in light and dark.Screenshots (UI changes)
Headless renders (
MergeOrderTests.Merge_images_layout_fits_at_its_minimum_size, written totests/Filee.App.Tests/bin/<config>/net10.0/screenshots/merge-images-*.png) at the smallest size the window opens with for pictures (440 × 540): en, de, ar, ko, ru, hi, ne and th, light and dark, each also side by side with a transparent background. Reviewed: every choice whole and inside the card, Arabic mirrored, the list keeps two rows in German and Russian where the layout takes two lines.Checklist
src/Filee.App/Assets/i18n/*.jsonfiles (12 keys, 17 languages)THIRD-PARTY-NOTICES.md(none)🤖 Generated with Claude Code