Skip to content

Upstream line quality improvements#425

Open
edwardhorsford wants to merge 2 commits intoPattern-Projector:betafrom
edwardhorsford:upstream-line-quality-pr
Open

Upstream line quality improvements#425
edwardhorsford wants to merge 2 commits intoPattern-Projector:betafrom
edwardhorsford:upstream-line-quality-pr

Conversation

@edwardhorsford
Copy link
Copy Markdown

Summary

This upstreams some of the work I did in my fork to improve the line quality.


Commit 1 — Push darks and contrast boost

Introduces a push-darks SVG filter (gamma=2 feComponentTransfer) and a contrast(1.5) step applied to every PDF canvas render. These are always applied, even at zero line thickness, because they improve the raw PDF output: grey anti-aliased edges are pushed back toward black, producing sharper, cleaner lines.

On Safari, where SVG filter references cannot be applied to canvas contexts, an equivalent pixel-level LUT (enhanceLineQualityFast) is used instead.


Commit 2 — feColorMatrix recolour

Replaces the Green theme's CSS filter chain (invert sepia saturate hue-rotate brightness) with a single #recolor feColorMatrix SVG filter that directly maps luminance to the target colour:

output = targetColour × (1 − luminance(input))

Black input → full target colour. White input → black. Grey inputs → proportionally dimmer shades. This gives exact, consistent colour across browsers, and makes the target colour configurable as a hex value rather than an approximation via hue-rotate. I've picked a light greenish hue similar to the brightest in the existing Pattern Projector, but you might want to tweak it.

On Chrome/Firefox, the recolour is applied via ctx.filter at canvas draw time. On Safari, pixel-level processing (recolourImageData) handles it.

After feMorphology erode, anti-aliased line edges become grey and blurry.
This adds a two-step quality pass to counteract that:

1. A push-darks SVG filter (feComponentTransfer with gamma=2) that darkens
   grey midtones toward black while leaving white backgrounds unchanged.

2. A contrast(1.5) CSS filter that further separates lines from background.

On Chrome/Firefox these run as part of the canvas draw filter chain:
  erode → url(#push-darks) → contrast(1.5)

On Safari, SVG filter references on canvas CSS aren't supported, so an
equivalent pixel-level pass (enhanceLineQualityFast) runs directly on the
ImageData after erosion. It uses a pre-built LUT for gamma + contrast,
which is 10-100× faster than per-pixel Math.pow() calls.

Both paths produce visually equivalent results.
…olorMatrix

The Green theme previously used a chain of CSS filters to approximate
green lines on black:
  invert(1) sepia(1) saturate(10000%) hue-rotate(65deg) brightness(1.5)

This had several problems (tracked in issue Pattern-Projector#418):
- Colours were never exact; hue-rotate is an approximation
- Cross-browser inconsistencies between Chrome and Safari
- Different code paths produced visually different results

This commit replaces that chain with a single feColorMatrix SVG filter
(the `#recolor` filter) that directly maps pixel luminance to the target
colour:

  output = targetColour × (1 - luminance(input))

So black input → target colour at full intensity, white input → black
(invisible on dark background), grey inputs → proportionally dimmer
shades of the target colour.

On Chrome/Firefox the filter is applied via ctx.filter at canvas draw
time. On Safari, where SVG filter references are not supported on canvas
contexts, pixel-level processing (recolourImageData) is used instead.

Safari rendering improvements:
- A back buffer canvas is used as the render target. The visible canvas
  only receives committed pixels once processing is complete, eliminating
  the grey flash that previously appeared during zoom changes.
- The visible canvas starts at 0×0 on mount to avoid a white rectangle
  appearing before the first render.
- During theme switches the canvas is hidden until the new render is
  ready; the parent div's backgroundColor shows through in the interim.
- onPageRenderSuccess is now wrapped in useCallback so its stable
  reference does not cause the render effect to re-fire on every parent
  re-render (which was causing extremely slow panning on Safari).

Changes:
- filters.tsx: adds the `#recolor` feColorMatrix filter, computed from
  the strokeColor hex passed as a prop from the page
- erode.ts: erosionFilter() always applies push-darks + contrast(1.5),
  even at 0 erosions; accepts an optional useRecolour flag to append
  url(#recolor); adds recolourImageData() for Safari pixel-level colour
- display-settings.ts: Green themeFilter returns "none"; adds
  isColourTheme() helper; Green strokeColor set to #00FFCC
- use-render-context.ts: adds recolourHex and themeFilter fields
- pdf-viewer.tsx: passes recolourHex and themeFilter to RenderContext;
  switches blend mode to "lighten" for overlapping dark-background pages;
  wraps onPageRenderSuccess in useCallback
- pdf-custom-renderer.tsx: Safari back-buffer render path; Chrome/Firefox
  ctx.filter draw path; zoom-out skip via content key; mount-zero canvas
- calibrate/page.tsx: computes recolourHex from strokeColor and wires it
  through to Filters and PdfViewer
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 13, 2026

@edwardhorsford is attempting to deploy a commit to the Courtney Pattison's projects Team on Vercel.

A member of the Team first needs to authorize it.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pattern-projector Ready Ready Preview, Comment Apr 13, 2026 11:52pm

Request Review

@courtneypattison
Copy link
Copy Markdown
Collaborator

Awesome, thank you @edwardhorsford !

@edwardhorsford
Copy link
Copy Markdown
Author

@courtneypattison no worries. Hopefully it makes sense. I had to tinker quite a bit - esp trying to reduce flashes of un-inverted styles.

I held off moving to workers in this, though Claude suggested it several times.

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