Skip to content

Better mask editing near/outside image borders.#21382

Open
masterpiga wants to merge 1 commit into
darktable-org:masterfrom
masterpiga:mask_editing
Open

Better mask editing near/outside image borders.#21382
masterpiga wants to merge 1 commit into
darktable-org:masterfrom
masterpiga:mask_editing

Conversation

@masterpiga

@masterpiga masterpiga commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

What this does

In the darkroom, mask control points (nodes, feather/border handles, clone sources) can be dragged outside the image rectangle — and a handle that ends up off the picture stays reachable. Previously the viewport could never pan past the image, so any handle outside the frame was permanently unreachable: you couldn't grab it, and at the fit zoom it wasn't even on screen. This makes off-image mask editing work at every zoom level, including below fit.

The governing constraint is preserved: at fit the image still fills the view, and the image can never be panned completely off-canvas. Only the editable canvas is allowed to grow beyond the image, and only while a mask overlay is being edited.

Screen.Recording.2026-06-21.at.08.34.05.mp4

Why

darktable clamps the viewport centre so the image edge can never cross the viewport edge (zoom_x = CLAMP(zoom_x, boxw/2 - .5, .5 - boxw/2)). That is exactly what makes a mask handle sitting outside the image impossible to reach. Lifting the clamp naively would let the user shove the whole picture off-screen, so instead the clamp is made mask-aware: the canvas grows only as much as the current overlay needs, and only while editing.

How it works

1. Mask-aware viewport clamp — src/develop/develop.c

dt_dev_zoom_move's hard clamp is replaced by _clamp_zoom_to_mask(). The editable canvas is the image, optionally enlarged by:

  • MASK_CREATION_MARGIN (0.15) on all sides while drawing, so new nodes can be dropped outside the picture;
  • the bounding box of any overlay point already outside the image (_dev_mask_overlay_bounds()), plus MASK_HANDLE_MARGIN (0.03) so an off-image handle isn't flush against the border.

On each axis the viewport centre is clamped into [home .. node], where home is the normal darktable position (image centred, or image edge pinned to viewport edge when zoomed past fit) and node is the extended off-image bound. Two design points matter here:

  • Home is not forced — the incoming (cursor-anchored or panned) centre is merely clamped into the range. A plain zoom keeps the image centred; only a deliberate pan travels out to a node.
  • The node bound is zoom-independent. Decoupling the bound from zoom keeps the framing stable while you zoom in to reach an off-image node.

2. Clip & assessment frame driven by image geometry, not the render ROI — src/views/view.c

The image clip rectangle (and the color-assessment frame) are now positioned from the viewport centre (zoom) and the image's own pixel size, intersected with the viewport — instead of from the backbuf ROI centre (offset). The render ROI is clamped to the image, so while the viewport is panned off-picture, offset lags the live zoom in discrete re-render steps. Driving the clip from the image geometry makes the clip track the content smoothly.

3. Coverage test measured against the reachable centre — src/views/view.c

The use_preview_fallback coverage check is measured against reach_* (the closest centre the clamped-to-image ROI can actually render) rather than the raw panned zoom. Off-image, offset can get no closer to zoom than the image edge, so measuring against raw zoom always reported the off-image background margin as "not covered" and re-triggered the pipe every frame. Measuring against the reachable centre stops that render loop — once offset reaches it, a re-render cannot improve coverage and the residual is harmless background.

4. Overlay alignment correction is capped — src/views/darkroom.c

Overlay points live in preview-pipe space and are shifted onto the full-pipe image by the sub-pixel difference between the two viewport centres. When panning far outside the image, the preview-pipe forward transform can extrapolate a non-linear distortion that blows up and drags the overlay off the content. The correction is now CLAMPed to ±1.5 px (its normal sub-pixel magnitude), so it can never displace the overlay.

5. Scrollbars/expose decoupled from the render centre — src/views/darkroom.c

Near fit, darktable zeroes the viewport centre it feeds to the scrollbars (to avoid a feedback resize). That zeroing is now applied only to the scrollbar values (sb_zoom_*); the real zoom_x/zoom_y are kept for rendering. Otherwise the mask overlay/guides/pickers would pin to the image centre while the image itself follows an off-canvas pan below fit, breaking alignment. expose also reads the actual stored centre when dt_dev_get_zoom_bounds reports no scrollable bounds, so a pan that exists purely to reach an off-image handle is still rendered.

6. Re-validate the canvas when the overlay is hidden — src/develop/masks/masks.c

Turning the mask overlay off (or switching it) shrinks the canvas back to the image, leaving any off-image pan stale. dt_masks_set_edit_mode issues a no-op DT_ZOOM_MOVE so the new clamp snaps the pan back in bounds immediately — the picture re-centres at once instead of lingering off-centre (which also kept the renderer churning) until the next manual pan.

Files changed

  • src/develop/develop.c — mask-aware viewport clamp + overlay bounds helper
  • src/views/view.c — clip/frame from image geometry; coverage vs reachable centre
  • src/views/darkroom.c — scrollbar/render-centre split; capped overlay correction
  • src/develop/masks/masks.c — re-validate viewport when overlay is hidden

Co-authored with Claude.

- When creating a mask, expand the canvas
  around the image to allow to position points
  outside the image area.

- When editing mask elements, the canvas is
  always expanded to make all control points
  accessible.
@masterpiga masterpiga added this to the 5.8 milestone Jun 21, 2026
@masterpiga masterpiga added feature: enhancement current features to improve difficulty: average some changes across different parts of the code base scope: UI user interface and interactions scope: image processing correcting pixels labels Jun 21, 2026
@da-phil

da-phil commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

I like it a lot, I always got frustrated from the automatic viewport centering.
The most annoying occurrence for me was when zooming in close to the corner/border area, where this auto-centering of the viewport makes it incredible hard to keep the viewport centered around the initial position where zooming in started, which I tried to explain here:
#21132 (comment)

I quickly tested the changes in this PR, but it seems that the vertical centering is still active. E.g. in the following image I want to zoom in into the waypost to read it, but once I start zooming, the content behind my curser moves to the right instead of staying behind my cursor, eventually the waypost would move out of the viewport, which requires me to use panning gestures to bring it back into the viewport:

Screencast.from.2026-06-21.13-07-04.mp4

What do you think about this? Do you think that would make sense. And if yes, whether this would be related to the functionality within this PR or rather out-of-scope.

@da-phil

da-phil commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

One regression I noticed when testing: the two-finger panning gesture stopped working and now performs zoom-scrolling, which previously needed the additional CTRL key.

@masterpiga

Copy link
Copy Markdown
Collaborator Author

Thanks for your feedback, @da-phil.

once I start zooming, the content behind my curser moves to the right instead of staying behind my cursor,

Ah, I see, you would like the image to zoom towards the cursor position, not towards the center. That makes sense, but I would say it is a different feature. Maybe you can open a feature request for tracking the feature, so that we can discuss if it's something that we want darktable to support.

One regression I noticed when testing: the two-finger panning gesture stopped working and now performs zoom-scrolling, which previously needed the additional CTRL key.

I can't reproduce, gestures work fine for me (on mac). Maybe you have deselected this option?

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

difficulty: average some changes across different parts of the code base feature: enhancement current features to improve scope: image processing correcting pixels scope: UI user interface and interactions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants