whiteboard: keep the highlighter translucent while drawing, and give it a chisel nib - #294
Merged
Merged
Conversation
…give it a chisel nib The live preview of a pen stroke was drawn incrementally: on every mousemove the last two points were stroked onto the preview canvas. With a translucent pen (the highlighter, opacity 0.4) the overlapping round ends of consecutive segments each composited separately, so the stroke built up to fully opaque while drawing and then snapped back to translucent the moment the mouse was released and the real element rendered. The preview now redraws the whole path in one stroke/fill, coalesced through `requestAnimationFrame`, and `drawCurve` always paints the curve in a single canvas operation. Points closer than one pixel to the previous one are dropped, so a high frequency pointing device does not blow up the path. Also fixed along the way: - `Pen` cleared its canvas *after* translating by `pad`, so a `pad` wide strip along the top and left was never cleared. That strip holds the top half of a wide stroke, so every re-render of a translucent stroke composited it onto itself and the stroke got darker (0.4 -> 0.64 -> 0.78 ...) as the board was edited. Clearing now happens with the identity transform. - The preview canvas had no explicit CSS size, so it was laid out at its bitmap size; the old code compensated by drawing at 1/devicePixelRatio. The canvas is now sized in CSS pixels and drawn at full device resolution, which also makes the preview line up with the finished element on HiDPI displays. - `drawCurve` sets `globalAlpha` unconditionally instead of only when an opacity is given, so a translucent stroke can no longer leak its alpha into whatever is drawn next on the same context. The highlighter preset now uses `nib: "chisel"`: an upright rectangle 2*radius tall and a fraction of that wide, swept along the path, which is how a real highlighter behaves -- a wide band when swept horizontally, a thin line when moved vertically. The stroke is the union of the nib's positions, emitted as one path with consistently oriented subpaths and filled once. Elements without `nib` keep the old round pen rendering, so existing drawings are unchanged.
haraldschilly
marked this pull request as ready for review
August 25, 2026 18:18
Contributor
|
Found 10 test failures on Blacksmith runners: Failures
|
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.
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light.png)
While drawing with the highlighter the stroke looked opaque, and only turned translucent once the mouse button was released. Cause: the live preview was drawn incrementally — each mousemove stroked the last two points onto the preview canvas, so the overlapping round ends of consecutive segments composited on top of each other and built up to full opacity.
What changed
requestAnimationFrame.drawCurvenow always paints a curve with a singlestroke()/fill(), so a translucent color never composites with itself.nib: "chisel"): an upright rectangle2*radiustall and a fraction of that wide, swept along the path — a wide band when swept horizontally, a thin line when moved vertically, like a real highlighter. Elements withoutnibkeep the old round rendering, so existing drawings are unaffected.Two related bugs found while verifying:
Pencleared its canvas after translating bypad, leaving apad-wide strip along the top/left uncleared. That strip holds the top half of a wide stroke, so every re-render of a translucent stroke composited it onto itself and it got darker over time (measured 0.4 → 0.64 → 0.78 as more elements were added).1/devicePixelRatio. It is now sized in CSS pixels and drawn at full device resolution, so the preview also lines up with the finished element on HiDPI displays.Verification
Headless Chrome against local Lite, sampling canvas pixels during the drag and after release, at
devicePixelRatio1 and 2:Preview and finished element also agree on position/size to within 0.5px at both DPRs. Frame timings stay at 16.7ms median/p95 for a 2000-point stroke. Added
elements/pen.test.tscovering the single-composite property and the chisel geometry.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.