web: filter the framed print draws so a scaled photo stops staircasing

The polaroid and the wall frame both draw the photo into a window that is not
the size of the photo — the instant print shrinks it to 0.898x, the wall frame
cover-scales it by max(winW/pw, winH/ph), which is 1.47x up for a 2400x1800
source. A plain drawImageRect is nearest on CanvasKit, so that resize dropped
the edge back onto the output pixel grid: a 5 deg straighten inside a frame
exported 59.8% of its rows with the crossing pinned to the same pixel as the
row above (61.1% in the wall frame), while the same photo without a frame came
out at 55.8%.

Both draws now go through drawImageRectOptions with FilterMode.Linear, the same
call shape the straighten draw already uses. Measured on a 2400x1800 hard-edge
fixture at 5 deg, exported at the photo's own size: polaroid 59.8% -> 37.5%
flat rows, fracStd 0.336 -> 0.184; wall frame 61.1% -> 25.7%, fracStd 0.467 ->
0.210 — and the residual matches the 0.202 of the unframed export, so the
window costs nothing beyond the resample underneath it. A 45 deg edge printed
through the instant frame at 0 deg is unchanged at 0.0% flat rows.
This commit is contained in:
2026-09-23 07:46:19 +07:00
parent 3a09674831
commit d9bababfd7
+12 -2
View File
@@ -553,10 +553,16 @@ export async function renderPhoto(input: RenderInput): Promise<RenderResult | nu
dstH = win.h;
dstW = dstH * srcAspect;
}
cardCanvas.drawImageRect(
// The window is not the size of the photo (0.898x here, more when a frame
// covers), and a plain drawImageRect is nearest on CanvasKit: the print
// would come back staircased for the same reason the straighten draw did.
// Only the *Options/*Cubic call shapes take a sampling option.
cardCanvas.drawImageRectOptions(
photoImg,
Skia.XYWHRect(pSx, pSy, pVisW, pVisH),
Skia.XYWHRect(win.x + (win.w - dstW) / 2, win.y + (win.h - dstH) / 2, dstW, dstH),
Skia.FilterMode.Linear,
Skia.MipmapMode.None,
plainPaint
);
const seamPaint = own(Skia.Paint());
@@ -604,10 +610,14 @@ export async function renderPhoto(input: RenderInput): Promise<RenderResult | nu
const wSy = wZoom
? Math.min(Math.max(planeY + srcH * wZoom.v - wVisH / 2, planeY), planeY + srcH - wVisH)
: planeY;
cardCanvas.drawImageRect(
// Filtered for the same reason as the polaroid window above: cover-scaling
// a photo into the opening is a resize, and nearest shows it.
cardCanvas.drawImageRectOptions(
photoImg,
Skia.XYWHRect(wSx, wSy, wVisW, wVisH),
Skia.XYWHRect(win.x, win.y, win.w, win.h),
Skia.FilterMode.Linear,
Skia.MipmapMode.None,
plainPaint
);
cardCanvas.save();