Skip to content

whiteboard: keep the highlighter translucent while drawing, and give it a chisel nib - #294

Merged
williamstein merged 1 commit into
mainfrom
whiteboard-highlighter-live-opacity-20260825
Aug 27, 2026
Merged

whiteboard: keep the highlighter translucent while drawing, and give it a chisel nib#294
williamstein merged 1 commit into
mainfrom
whiteboard-highlighter-live-opacity-20260825

Conversation

@haraldschilly

@haraldschilly haraldschilly commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

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

  • Preview redraws the whole path in one operation, coalesced through requestAnimationFrame. drawCurve now always paints a curve with a single stroke()/fill(), so a translucent color never composites with itself.
  • Point thinning: preview points closer than 1px to the previous one are dropped, so a high-frequency pointer doesn't blow up the path. (The existing thinning when the element is committed is unchanged.)
  • Chisel nib for the highlighter (nib: "chisel"): an upright rectangle 2*radius tall 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 without nib keep the old round rendering, so existing drawings are unaffected.

Two related bugs found while verifying:

  • Pen cleared its canvas after translating by pad, leaving a pad-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).
  • 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. 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 devicePixelRatio 1 and 2:

during drag after release
before maxAlpha 255 (avg 244) 102
after maxAlpha 102 102

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.ts covering the single-composite property and the chisel geometry.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…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
haraldschilly marked this pull request as ready for review August 25, 2026 18:18
@blacksmith-sh

blacksmith-sh Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Found 10 test failures on Blacksmith runners:

Failures

Test View Logs
account/tests/membership-package-manager.test.tsx/
lets admins add site-license delegates with admin user search
View Logs
membership package managers/
lets admins add site-license delegates with admin user search
View Logs
membership package managers/
lets admins add site-license delegates with admin user search
View Logs
membership package managers/
lets admins add site-license delegates with admin user search
View Logs
membership package managers/
lets admins add site-license delegates with admin user search
View Logs
membership package managers/
lets admins add site-license delegates with admin user search
View Logs
membership package managers/
lets admins add site-license delegates with admin user search
View Logs
membership package managers/
lets admins add site-license delegates with admin user search
View Logs
membership package managers/
lets admins add site-license delegates with admin user search
View Logs
membership package managers/
lets admins add site-license delegates with admin user search
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@williamstein
williamstein merged commit d94c5ab into main Aug 27, 2026
5 of 6 checks passed
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.

2 participants