Path shrink#21370
Open
masterpiga wants to merge 1 commit into
Open
Conversation
Member
|
Windows compile error: |
Collaborator
Author
|
Thanks, done, there was a name conflict with some transitive import. |
Grow/shrink a path mask by rasterizing it to a bitmap, applying an exact Euclidean distance transform (Meijster 2000) to offset the boundary, then re-vectorizing with potrace. Includes a LIFO per-form cache to avoid recomputing at previously visited steps. Exposes user preferences for step amount, unit (px / % of path size), tracing resolution, and curve smoothing. Adds options to the mask manager to grow/shrink a mask by a specific amount for fine grained control. Re-routes the pre-existent resize algo (distance from centroid) to use CTRL+SHIFT+scroll, so that the two algorithms can co-exist.
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.
Summary
This adds a true grow/shrink (outset/inset) operation to path masks, complementing the existing centroid-based "size" scaling. Instead of scaling node positions about the centroid — which only enlarges the existing outline proportionally — this offsets the actual boundary by a constant distance, the way a morphological dilation/erosion would. Concave bays, thin necks and corners behave correctly: a grow rounds outer corners and fills narrow gaps, a shrink can pinch a thin shape into two, etc.
It is exposed both as a scroll-wheel gesture in the darkroom and as a new "shrink or grow" slider in the mask manager, with a px / % unit toggle.
Screen.Recording.2026-06-20.at.07.34.45.mp4
How it works
A grow/shrink can't be done analytically on Bézier nodes, so each operation runs a three-stage pipeline (
_path_morph_coreinpath.c):_path_rasterize), at a configurable internal resolution._edt_compute/_edt_row. Grow keeps every pixel withinrof the inside; shrink keeps inside pixels farther thanrfrom the outside. The threshold is sub-pixel (compared in squared distance), so the offset is smooth rather than blocky.ras2forms), pick the largest true outer ('+') contour, simplify the dense boundary with Ramer–Douglas–Peucker (_path_simplify_rdp), normalize coordinates and regenerate Catmull-Rom handles.Key design choices
Baseline + offset-keyed cache (lossless, non-compounding). Every grow/shrink is lossy and expensive, so applying them in sequence would both compound error and recompute needlessly. Instead each form gets a
_resize_state_tholding a baseline (a deep copy of the points when resizing first started) plus aGHashTableof results keyed by the signed pixel offset. Every operation is computed from the baseline, so:The baseline is captured lazily on the first resize and invalidated whenever the shape changes by other means (node move/add/delete, segment drag, deletion), so the next resize re-baselines from the edited shape.
Corner preservation. RDP keeps potrace's per-node Bézier handles, including the zero-length handles at genuine corners, so sharp features survive a resize instead of being rounded away. A
"sharp" / "balanced" / "smooth"preference trades node count against smoothness (it tunes both the potracealphamaxand the RDP tolerance, the latter expressed in image pixels so it means the same at any tracing resolution).Self-intersection guard. Re-vectorized nodes can be packed tightly enough that auto-generated Catmull-Rom handles exceed their edge chord and create visible loops.
_path_clamp_ctrl_pointsclamps handles to the chord length — but only after re-vectorization, never on hand-drawn masks.Robustness. Large EDT buffers use
g_try_malloc_nso an allocation failure no-ops the resize instead of aborting darktable. An over-shrink that erases the shape rolls back to the displayed shape and offset rather than snapping to the baseline. Clone/retouch sources are re-anchored so they don't drift when the path's first node moves.Shortcut / gesture remapping
The plain scroll wheel over a selected path previously did centroid "size" scaling. That gesture is preserved but moved to a modifier, freeing plain scroll for the new operation:
Opacity and feather shortcuts are unchanged.
The on-canvas hint message and the mouse-action help list are updated to match.
User-visible changes
darktableconfig.xml.in:masks/path_resize_amount— scroll step size;masks/path_resize_unit— px or % of path size;masks/path_resize_resolution— internal tracing resolution (512–4096), trading detail for speed;masks/path_resize_curve_smoothing— sharp / balanced / smooth.masks_scroll_down_increasesandshow_mask_indicatorkeys are also relocated into the new masking section.Main code changes
src/develop/masks/path.c— the whole pipeline: rasterize / EDT / re-vectorize / RDP, the per-form baseline + result cache, the scroll (_path_resize_morph) and slider (_path_resize_amount) entry points,_path_resize_getfor slider mirroring, cache invalidation at every editing call site, and the extracted legacy_path_resize_centroid.src/develop/masks.h— two new optional vtable entries ondt_masks_functions_t:resize(absolute, baseline-relative, cached) andresize_get(report current offset). Implemented only by path masks for now.src/libs/masks.c— the masks-manager slider, its px/% quad (custom paint), debounced commit, selection-aware visibility (_selected_single_path), and offset mirroring. The path mask owns the baseline/cache, so the lib side just sets and reads the current offset.Co-authored with Claude.