Oval and card-edge dodge/burn masks, plus per-mask invert - #776
Merged
Conversation
Trim the Style section to the two rules that govern how prose gets written.
Dodge & Burn had one shape, a clicked polygon. Two common darkroom moves did not fit it: burning through a hole in the card (a smooth oval took a dozen vertices) and the graduated card-edge burn (an unbounded ramp that a closed polygon and a Gaussian cannot express). Add a `shape` field to the mask, renamed `PolygonMask` -> `LocalMask`. The vertices stay the universal store and `shape` says how to read them: a polygon keeps N control points, an oval takes 3 (centre and one end of each axis, an affine frame that permits oblique axes), a gradient takes 2. Geometry mapping stays shape-blind, because every control point still goes through `map_coords_to_geometry`. An oval outline is generated, so the existing `fillPoly` and feather serve it unchanged. The gradient is a smoothstep ramp along its axis, and ignores Feather because the distance between its handles sets the softness. `invert` flips the alpha, which applies a mask outside its own shape. One rasteriser (`local/logic.rasterise`) now serves the render, the canvas tint and the printing-notes map. The GPU consumes the same CPU-rasterised map, so no shader work and no parity surface. Old saves load as polygons.
A tilted card edge could not burn a full corner. The ramp is already an unbounded half-plane, but the handles were held inside the frame, so the line through the start point always cut one corner off the full-exposure side as soon as you tilted the axis. To hold the whole top edge, the start must sit past the top-right corner, which is off the frame. The render path was ready for this: `map_coords_to_geometry` is analytic and takes coords outside [0,1]. Only the view path clamped. Both uv-grid lookups now continue past the boundary with an affine model of the grid, taken from central differences in the middle of the grid. The samples avoid the border, because a fine rotation fills it with zeros and those are not coordinates. Drop the clamps on the shape drag, on the handle drag and on the emitted points. This also permits an oval whose centre is off the frame, and a polygon vertex outside the picture.
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.
Dodge & Burn had one shape: a clicked polygon. Two common darkroom moves did not fit it.
Two more gaps came out of the same model: no way to act on everything except a shape, and
smooth_polylinerounds every corner.What changed
PolygonMaskbecomesLocalMaskand gainsshapeandinvert. The vertices stay the universal store andshapesays how to read them:[u v], 64 samplesStoring an oval as points, not as a centre plus radii plus an angle, keeps geometry mapping shape-blind: every control point still goes through
map_coords_to_geometry, so rotation, flips and distortion follow as they did. The points map to pixels before the outline is generated, so the raw-image aspect needs no special case. The axes need not be perpendicular, which makes a tilted oval the same expression as a round one.An oval outline is generated, so the existing
fillPolyand Gaussian feather serve it with no new fill path. A gradient ignores Feather, because the distance between its two handles is the softness.invertis1 - alphaafter the feather.One function (
local/logic.rasterise) now serves the render, the canvas tint and the printing-notes map, so the three cannot describe different shapes. The GPU consumes the same CPU-rasterised map, so there is no shader work and no parity surface.Canvas
Both new tools drag out their shape, and one
local_mask_created(shape, points)signal replaceslasso_completed. Outlines and control points are now tracked separately: the old code hit-tested the control polygon, which would have hit-tested an oval's triangle. A card edge draws a solid line at full exposure and a dashed line at zero, and hits near its axis. The oval centre handle moves the axes with it. Midpoint+and vertex delete stay polygon-only, because only a polygon can change its point count.UI
Three tool buttons, an Invert toggle, a shape icon per list row, and Feather disabled on a card edge. Two shortcut-registry entries with empty default keys — no free key was worth a collision.
Compatibility
shapeandinvertdefault in_build_local, so a mask saved before this change loads as a polygon. Nothing goes throughmigrations.py.Verification
make allgreen: 3499 passed, 2 skipped.Left out
A freehand brush mask, a hard-edge toggle, and tonal-range gating. The first two fit the same
shapeslot. The third needs the normalised density threaded intocompute_local_maps, which the GPU rasterises per tile before the print stage, so it is separate work.