Skip to content

feat(tools): Add background tool - #1081

Merged
KnifeLemon merged 1 commit into
KnifeLemon:mainfrom
lightscape-jm:feat/add-background
Nov 4, 2026
Merged

KnifeLemon merged 1 commit into
KnifeLemon:mainfrom
lightscape-jm:feat/add-background

Conversation

@lightscape-jm

@lightscape-jm lightscape-jm commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

What does this change?

Closes #876. Adds Add background, a palette tool that sets pictures on a gradient, a colour or a picture of one's own, with padding, rounded corners, a soft shadow and a canvas shape, as planned in #856 (step 5, after Annotate). It is in the palette's Edit tab for pictures, not on a default donut.

  • Window: the same full-picture stage as Pixelate / blur and Annotate: the picture large on the left, set on its background as the file will be, and the look on a card on the right.
    • Background: Gradient (six named presets and two colour pickers), Colour (six named swatches and a picker) or Picture (one of one's own, covering the canvas).
    • Padding, Corners, Shadow: sliders. Sizes are parts of the picture, so a look suits a screenshot and a photo alike.
    • Canvas: Auto, Square, 16:9 or 4:5. The canvas grows round the picture to the shape and never crops it.
    • The finished size of the file under the preview; one look for all pictures or one each; Skip and Save & next.
  • Saving: each picture is an ordinary conversion in the toast, "{name} With Background.png". The look used last is kept in the tool.

What the window shows is what the file gets. This was the bar for the whole tool, so it is built and tested around it:

  • Engines/Magick/BackdropComposer draws the file with Skia, with the same things the window draws with: Avalonia's linear gradient brush and drop shadow are Skia's. The layout and the shadow come from the same formulas on both sides.
  • The picture is brought to sRGB as it is seen before anything is laid on it: through its profile (Display P3, Adobe RGB, a print CMYK, a scanner's grey), or by formula for CMYK and grey without one. The window's preview goes through the same function. Without it, a greyscale picture came out grey, background included, and a CMYK or wide-gamut one shifted the background's colours.
  • Skia draws within limits of size: its clip loses its smoothing on a canvas some 8,000 px wide, and its blur isn't Gaussian past a sigma of about 100. So the canvas is drawn in tiles of at most 2048 px, a band at a time; the corners are cut in the picture's own pixels; and a wide shadow is blurred smaller and scaled up.
  • A picture of one's own behind is read as large as covering the canvas takes, cropped to what shows, white where it is see-through. Its size is checked before it is decoded, it is read as what its first bytes say it is (among a few raster kinds only), and one that can't be read refuses the file rather than writing another look.
  • The window reads a picture behind when it is chosen, and again when it is picked again. At Save, one whose file changed since it was read (edited in another app) is read again and shown first. The conversion carries the stamp (time written and length) of the picture behind as the window showed it, and the file is refused ("The picture to put behind changed after it was shown") when it changed since, rather than written with a picture the window didn't show; the toast offers no Retry for that, as retrying can't help.

How it's built (also in docs/ARCHITECTURE.md → Add background tool):

  • ImageOptions.Backdrop (BackdropOptions): kind, colours, picture, padding, corners, shadow and canvas. Enums are read leniently, numbers clamped, colours checked. It counts in ReworksPixels, so it is applied once, in the first transform step; a route that can't apply it fails with error.images_only; History offers no Retry, as for hidden parts and marks.
  • The tool keeps the last look apart, in ImageOptions.BackdropLook. An export or import leaves out the path of a picture of one's own.
  • BackgroundViewModel and BackgroundWindow follow Pixelate / blur and Annotate: the key handling (Enter saves unless a control reached with the keyboard takes it, Esc closes, arrows along a row of choices), radio buttons with names a screen reader says, a summary when a picture can't be opened or saved, a failed save that keeps the picture, the look kept however the window closes, texts that follow a language change, and a preview that isn't mirrored in a right-to-left UI.

Compatibility: the built-in tool-background comes with settings schema 64, after Annotate's 60 and the steps that landed since (UserDataStore adds it to existing libraries after Annotate, in the palette only). I can renumber it if another change takes 64 first. An older Filee doesn't know the tool and opens nothing for it. SkiaSharp and Magick.NET are already dependencies, so nothing new is added.

Type of change

  • Bug fix
  • New feature or improvement
  • New format or engine
  • Translation
  • Documentation
  • Build, CI or dependencies

How was it tested?

  • dotnet format --verify-no-changes
  • dotnet test: Core, Cli and Platform pass, and all the Add background tests in App, Core and Engines pass on macOS, in an Ubuntu 24.04 container and on Windows 11 ARM64. On my Mac, the Engines and App tests that fail on unmodified main fail the same way (FFmpeg without some encoders, the engine-page renders, A_second_engine_can_be_installed…, and the flaky A_lost_animation_frame…). I didn't run the Windows installer (Setup) tests.
  • Tried it in the app (macOS, a build of this branch on current main, light and dark): put on the Images donut from the palette's Edit tab; three screenshots opened from the donut; each background (gradient presets, colour swatches, a photo of my own behind), each canvas shape (the size shown under the preview was the file's each time), Save & next over the three, then the files checked (sizes as shown, opaque). The look was kept on disk when the window closed, and a photo behind kept in the look was read again when the tool next opened.

New tests:

Test Checks
Engines BackdropTests (77) each canvas shape round the picture, never cropping it, for 2,000 random sizes; the size the window tells is the file's, the pixel limit included; colours kept on greyscale (also with its own profile), CMYK, Adobe RGB and SWOP pictures, as a colour-managed viewer shows them; a picture behind in other colour spaces, see-through, cropped to what shows, read as large as covering takes, a JPEG never decoded larger, one named as another kind, one over the limit refused, a missing or changed one refusing the file; the shadow against Skia's drop shadow for pictures of any shape, and Gaussian at any width; smooth corners on wide pictures; the same file however it is split into tiles and bands; the EXIF thumbnail dropped; a GIF with see-through parts written opaque; Windows device paths refused
Core BackgroundToolTests (14) schema 64 adds the tool once, after Annotate; the look kept apart from the conversion's and read leniently (unknown kinds, bad colours); applied once, refused on a route that can't; no History retry; picture paths left out of exports and imports; the picture behind's stamp on the conversion only
App BackgroundWindowTests (62) what the window draws is what the file gets, for 12 pictures and looks (CMYK, grey, Adobe RGB, a cut-out, a see-through GIF, tiny and thin pictures, pictures behind, right to left), part by part and opaque; every text whole in all 20 languages; choices as named radio buttons; keys, Tab order and Space; the summary and failed saves; the look kept on disk however the window closes; a picture behind that can't be read, is picked again, or is edited before Save or while it is read (shown again first); pictures let go of disposed; no toast Retry for a changed one

Screenshots (UI changes)

The window, dark, three pictures on one look:

The Add background window, dark, on a screenshot: Sky gradient, Auto canvas, 1 of 3

The window, light, one picture:

The Add background window, light, on one picture: Sunset gradient

A saved file ("screenshot-3 With Background.png", 1340 × 1080):

The saved file, "screenshot-3 With Background.png"

Checklist

  • New UI strings are in all src/Filee.App/Assets/i18n/*.json files (40 strings, translated in every language)
  • New third-party code or binaries are listed in THIRD-PARTY-NOTICES.md (none: SkiaSharp and Magick.NET are listed already)
  • The PR does one thing (unrelated changes go in a separate PR)

🤖 Generated with Claude Code

A palette tool that sets pictures on a gradient, a colour or a picture of
one's own, with padding, rounded corners, a soft shadow and a canvas shape
(Auto, Square, 16:9, 4:5). The file is drawn with Skia, as the window's
preview is, so what the window shows is what the file gets. Settings
schema 64 adds the built-in tool-background to the palette (KnifeLemon#876).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @lightscape-jm! This follows #876 and the #856 plan closely:

  • palette only, the same stage as Pixelate / blur and Annotate;
  • Backdrop counted in ReworksPixels, no History retry;
  • the picture path left out of exports and imports;
  • the tool inserted after tool-annotate for existing libraries too.

What I checked

  • Build: on current main; dotnet format --verify-no-changes is clean.
  • Tests: Core all pass (970), Engines Backdrop / ImageEncoder (86), App Background, Localization and palette (334). CI is green everywhere.
  • Strings: all 40 keys are in all 20 language files, Vietnamese included.
  • Real files: I ran it as a conversion on generated pictures with four looks: a screenshot, a greyscale picture, a CMYK JPEG without a profile, a transparent cut-out and a very thin strip (also a transparent GIF, an EXIF-rotated JPEG and a 16-bit grey PNG). Colours, shadow, rotation, canvas shapes and the cover crop of a picture behind all came out right, and a missing picture behind fails the file as it should.

Inputs (left) and the default, square sunset, solid colour 16:9 and picture-behind 4:5 looks

Must fix

Nothing.

One thing to watch

#1064 (upscaling) also wants settings schema 64. This one lands first, so 64 is yours; we'll move the other one.

Merging now.

@KnifeLemon
KnifeLemon merged commit 2a23e5a into KnifeLemon:main Nov 4, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Add background tool: set pictures on a gradient, a colour or your own picture

2 participants