You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
gridaco/grida#867 fixed five render-fidelity issues, split across two layers:
Converter (TypeScript) — Crop image fills (#897) + gradient/crop-image flip. Already shipped in @grida/refig@0.0.6 (#899); no wasm rebuild.
Rust painter (crates/grida, commit 3f03926fa) — layer opacity, image sampling, text line-height. Not yet on npm; reach @grida/refig only through a rebuilt @grida/canvas-wasm.
This issue tracks the Rust half. Opacity and line-height are clean correctness fixes; image sampling carries an open design question (below) and should NOT be shipped as the current hardcode.
Clean fixes — release with the wasm rebuild (correctness, no tradeoff)
Solid opacity (paint.rs) — per-fill alpha was squared (color.a * opacity); now uses the color alpha directly.
Line-height leading (text_style.rs) — switched to half-leading (CSS/Figma model).
Image sampling — drive it from the render client, not a hardcode
gridaco/grida#867 hardcoded SamplingOptions::from(CubicResampler::mitchell()) at crates/grida/src/painter/image.rs:48. image_shader (called from painter/paint.rs:150) takes no context, so every path gets identical sampling. The fix is to make sampling a function of which of two clients is rendering:
Client A — design (interactive canvas). The designer is inspecting the artwork and wants to see the actual image pixels — pixel-faithful, cheap sampling (Nearest). Truthful to the source texels, and fast for live pan/zoom.
Client B — render (output / best). Produce the best possible image — high-quality resampling (Mitchell cubic).
Which client runs when:
The design canvas renders as design while editing, and switches to render when the user exports.
refig is always render — headless rendering is output by definition.
So the same scene samples differently depending on the client carried in the render context. Exact enum naming TBD (e.g. RenderMode { Design, Render } / RenderIntent { Edit, Export }).
Orthogonal sub-axis: a per-image content hint (smooth vs crisp / pixel-art, à la CSS image-rendering) can layer on top within a client — precedent in the htmlcss path: sampling_for(ImageRendering) at crates/grida/src/htmlcss/paint.rs:904 (Auto→Linear, pixelated→Nearest).
Prior art — gridaco/grida#509 Pixel Preview (build on this, don't reinvent)
gridaco/grida#509 already introduced the "design client sees pixels" mode and the infrastructure to drive it:
WASM API runtime_renderer_set_pixel_preview_scale(0|1|2) + runtime_renderer_set_pixel_preview_stable(bool) (crates/grida-canvas-wasm/src/wasm_application.rs).
Backed by FrameRenderStrategy (crates/grida/src/runtime/frame_strategy.rs) — the centralized per-frame render-policy struct, computed once per frame and consulted at each decision point. It already encodes a quality-vs-perf tradeoff (the "blurry during gesture, sharp on settle" cache gating, EAGER_RENDER_ZOOM_THRESHOLD).
FrameRenderStrategy::compute() is the natural home for the render-client / sampling decision — add it as a policy field there and have image_shader read it, instead of the bare constant at image.rs:48. The design ↔ render client distinction generalizes the existing pixel-preview stable/scale knobs.
The test image_shader_samples_smoothly_not_nearest (painter/image.rs) asserts today's blanket smooth default; it moves onto the new surface (assert render smooths, design shows pixels).
Until this exists, shipping the gridaco/grida#867 Mitchell hardcode to the interactive canvas both costs pan/zoom perf and hides the real pixels from the designer.
Resolve the render-client / sampling design above — or knowingly ship the Mitchell hardcode as an interim (both clients smooth) and keep this issue open for the client work.
Rebuild + republish @grida/canvas-wasm as a new canary (e.g. canary.22).
Repin @grida/refig → new canary; version + publish (CHANGELOG adds opacity / sampling / line-height).
Acceptance
A render-client / quality mode exists in the render context: design (see pixels / cheap) vs render (best), threaded via FrameRenderStrategy
design canvas renders as design interactively, switches to render on export; refig is always render
per-image smooth/crisp content hint honored within a client (pixel-art opt-in)
Context
gridaco/grida#867 fixed five render-fidelity issues, split across two layers:
@grida/refig@0.0.6(#899); no wasm rebuild.crates/grida, commit 3f03926fa) — layer opacity, image sampling, text line-height. Not yet on npm; reach@grida/refigonly through a rebuilt@grida/canvas-wasm.This issue tracks the Rust half. Opacity and line-height are clean correctness fixes; image sampling carries an open design question (below) and should NOT be shipped as the current hardcode.
Clean fixes — release with the wasm rebuild (correctness, no tradeoff)
paint.rs) — per-fill alpha was squared (color.a * opacity); now uses the color alpha directly.text_style.rs) — switched to half-leading (CSS/Figma model).Image sampling — drive it from the render client, not a hardcode
gridaco/grida#867 hardcoded
SamplingOptions::from(CubicResampler::mitchell())atcrates/grida/src/painter/image.rs:48.image_shader(called frompainter/paint.rs:150) takes no context, so every path gets identical sampling. The fix is to make sampling a function of which of two clients is rendering:Client A —
design(interactive canvas). The designer is inspecting the artwork and wants to see the actual image pixels — pixel-faithful, cheap sampling (Nearest). Truthful to the source texels, and fast for live pan/zoom.Client B —
render(output / best). Produce the best possible image — high-quality resampling (Mitchell cubic).Which client runs when:
designwhile editing, and switches torenderwhen the user exports.render— headless rendering is output by definition.So the same scene samples differently depending on the client carried in the render context. Exact enum naming TBD (e.g.
RenderMode { Design, Render }/RenderIntent { Edit, Export }).Orthogonal sub-axis: a per-image content hint (smooth vs crisp / pixel-art, à la CSS
image-rendering) can layer on top within a client — precedent in the htmlcss path:sampling_for(ImageRendering)atcrates/grida/src/htmlcss/paint.rs:904(Auto→Linear, pixelated→Nearest).Prior art — gridaco/grida#509 Pixel Preview (build on this, don't reinvent)
gridaco/grida#509 already introduced the "design client sees pixels" mode and the infrastructure to drive it:
runtime_renderer_set_pixel_preview_scale(0|1|2)+runtime_renderer_set_pixel_preview_stable(bool)(crates/grida-canvas-wasm/src/wasm_application.rs).FrameRenderStrategy(crates/grida/src/runtime/frame_strategy.rs) — the centralized per-frame render-policy struct, computed once per frame and consulted at each decision point. It already encodes a quality-vs-perf tradeoff (the "blurry during gesture, sharp on settle" cache gating,EAGER_RENDER_ZOOM_THRESHOLD).FrameRenderStrategy::compute()is the natural home for the render-client / sampling decision — add it as a policy field there and haveimage_shaderread it, instead of the bare constant atimage.rs:48. Thedesign↔renderclient distinction generalizes the existing pixel-previewstable/scale knobs.The test
image_shader_samples_smoothly_not_nearest(painter/image.rs) asserts today's blanket smooth default; it moves onto the new surface (assertrendersmooths,designshows pixels).Until this exists, shipping the gridaco/grida#867 Mitchell hardcode to the interactive canvas both costs pan/zoom perf and hides the real pixels from the designer.
Release order (maintainer, npm publish rights)
main → canary, or build wasm locally frommain.@grida/canvas-wasmas a new canary (e.g.canary.22).@grida/refig→ new canary; version + publish (CHANGELOG adds opacity / sampling / line-height).Acceptance
design(see pixels / cheap) vsrender(best), threaded viaFrameRenderStrategydesigninteractively, switches torenderon export; refig is alwaysrender@grida/canvas-wasmcanary rebuilt with fix(refig): Figma REST render fidelity (opacity, image CROP, gradient flip, sampling, line-height) grida#867 painter changes@grida/refigrepinned + republished; opacity / sampling / line-height verified against a Figma oracleRefs: gridaco/grida#867, gridaco/grida#897, gridaco/grida#899, gridaco/grida#509 (Pixel Preview — prior art), gridaco/grida#898 (publish CI)