Skip to content

fix(smudge): drag instead of blur: pull by distance, crisp shape and grain, an even strength curve, pigment mixing - #96

Open
zednaked wants to merge 8 commits into
artcraft-android:mainfrom
zednaked:fix/smudge-keeps-grain
Open

zednaked wants to merge 8 commits into
artcraft-android:mainfrom
zednaked:fix/smudge-keeps-grain

Conversation

@zednaked

@zednaked zednaked commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Problem

With dense brushes, like most imported Procreate smudges (spacing around 0.04), the smudge tool blurs in place instead of dragging paint. The result is a soft, even-coloured patch with no trace of the brush's shape or grain. Measured offscreen over thin colour stripes with a commercial "Gouache Flow" smudge: the stroke left one flat average colour, and broad colour bands showed only a few pixels of drag.

Three causes, each measured:

  1. Pull per stamp. What the brush carries keeps DEFAULT_PULL (0.93) at every stamp. A dense brush stamps many times per pixel, so it lets go of the paint within a fraction of its own size.
  2. Deposit saturates. Each stamp lays mix(w, carried, shape·alpha·grain). Hundreds of overlapping stamps take every pixel to 100% whatever the grain, so the texture evens out to a flat colour.
  3. RGB mixing. Colours mix as light. Blue dragged into yellow goes grey rather than green.

Changes

  • Pull by distance (canvas.rs): pull^(distance / (0.1 · diameter)). Any spacing drags as far as a brush spaced at 10% of its size, the common case, which behaves as before.
  • Strongest stamp, not a sum (smudge.wgsl, the amount buffer now always made): a pixel takes the newest carried colour at the strength of the strongest stamp that passed over it. That strength is a crisp cut of shape × grain, so each fibre drags all the way or not at all. The pixel as the stroke found it is kept in amount's second half.
  • Opacity curve: the strength sets how much is laid and how far it drags, both at alpha^0.15. Measured over the slider, the effect now rises evenly from 10% to 100%: 0.40, 0.53, 0.68, 0.84, 1.03, 1.25, 1.52, 1.86, 2.30, 2.91. Before, almost nothing happened up to 50%, the effect jumped 10× between 50 and 70%, and it was flat above.
  • Pigment mixing in the carry and the deposit: Kubelka–Munk over 38 spectral bands. The tables are generated from spectral.js 3.0.0 (MIT, © 2025 Ronald van Wijnen), credited in the shader. Shares are linear rather than squared as in spectral.js, because squared shares also lengthened the drag. The inverse 1 + ks − √(ks² + 2ks) is written as 1 / (1 + ks + √(ks² + 2ks)), so it doesn't lose precision in 32 bits.

Checked

  • New test a_smudge_mixes_blue_into_yellow_as_paint_does: it fails on main (the greenest pixel is −3, an even grey) and passes here.
  • cargo test -p procraft-gpu: 174 passed, 0 failed, including a_smudge_goes_by_a_shape_at_its_own_size and a_stroke_reads_a_brush_file_s_grain_at_its_own_size. Both need the crisp cut's floor of 0.12, without which a zero in the shape or grain passed at half strength.
  • Used for painting on desktop Linux (Wayland, Kamvas pen) by an artist who uses Procreate daily.

Open questions

  • At full strength the smudge picks up what it has just laid, so a strong drag runs to the end of the stroke. Reading the pick-up from the layer as it was before the stroke (source_before) didn't make it finite in my test, and I couldn't explain why, so I left it out.
  • I couldn't compare against Procreate directly. The calibration (0.1 of the diameter, the 0.12 cut, the 0.15 exponent) was tuned by feel with the artist.

🤖 Generated with Claude Code

zednaked and others added 8 commits November 7, 2026 14:00
…the stamp, so a dense brush drags as a spaced one does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mp there, over the pixel as found, so the shape and grain show in the drag

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… all the way or not, its strength how much passes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r grain leaves out stays out at full strength

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…udge barely moves the paint

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al.js, MIT), so blue dragged into yellow goes green, not grey

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the strength taking how much and how far at a gentle curve

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e pigment mix's inverse written without cancellation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant