fix(frame): give OLD FILM PORTRAIT a real portrait output
The sheet was stretched over the visitor's frame, so the portrait variant only turned the paper inside whatever shape the photo had: a landscape photo stayed landscape. The film now has its own opening like the walls do — the frame is scaled up to the photo, the photo is cover-cropped into the whole sheet, and `old-film-portrait` is the PNG turned 90° CW, so the pair is one frame standing and one lying. The torn edge has no straight sides, so the photo runs under all of it instead of being cut against a measured window.
This commit is contained in:
@@ -765,11 +765,13 @@ function loadWallframe(assets: RenderAssets) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Both films are the same sheet of distressed paper: `old-film` lays it down as
|
// Both films are the same sheet of distressed paper: `old-film` lays it down as
|
||||||
// it is, `old-film-portrait` turns it 90° CW so the torn edge and the dust lie
|
// it is, `old-film-portrait` turns it 90° CW, so the pair is one frame standing
|
||||||
// the other way round. Unlike the walls the sheet has no mat of its own — its
|
// and one lying — the same two shapes the walls offer, in a sheet whose middle
|
||||||
// middle is a transparent window and its opaque part is a border a couple of
|
// is a transparent window a couple of percent of paper wide around it.
|
||||||
// percent wide — so it is drawn OVER the photo and never crops it.
|
|
||||||
const isOldFilm = (id: FrameId) => id === 'old-film' || id === 'old-film-portrait';
|
const isOldFilm = (id: FrameId) => id === 'old-film' || id === 'old-film-portrait';
|
||||||
|
// A frame that carries its own opening: the aspect chip has nothing to say to
|
||||||
|
// it (both walls, both films).
|
||||||
|
const hasOwnAspect = (id: FrameId) => isWallframe(id) || isOldFilm(id);
|
||||||
|
|
||||||
function loadOldFilm(assets: RenderAssets) {
|
function loadOldFilm(assets: RenderAssets) {
|
||||||
if (oldFilmImage) return oldFilmImage;
|
if (oldFilmImage) return oldFilmImage;
|
||||||
@@ -812,10 +814,10 @@ export async function renderPhoto(input: RenderInput): Promise<RenderResult | nu
|
|||||||
const aspectWH = options?.aspect && options.aspect !== 'full' ? ASPECT_RATIO_W_H[options.aspect] : null;
|
const aspectWH = options?.aspect && options.aspect !== 'full' ? ASPECT_RATIO_W_H[options.aspect] : null;
|
||||||
const cropRect = options?.cropRect ?? null;
|
const cropRect = options?.cropRect ?? null;
|
||||||
const ratioWH = cropRect ? null : options?.cropWH ?? aspectWH;
|
const ratioWH = cropRect ? null : options?.cropWH ?? aspectWH;
|
||||||
// A wall is an artwork with its own opening, so the aspect chip (ratioWH)
|
// A wall or a film is an artwork with its own opening, so the aspect chip
|
||||||
// has nothing to say there — but the visitor's own crop does, or a photo
|
// (ratioWH) has nothing to say there — but the visitor's own crop does, or a
|
||||||
// that was cropped and applied comes back whole behind the frame.
|
// photo that was cropped and applied comes back whole behind the frame.
|
||||||
if (cropRect || (ratioWH && !isWallframe(frameId))) {
|
if (cropRect || (ratioWH && !hasOwnAspect(frameId))) {
|
||||||
const curAspect = width / height;
|
const curAspect = width / height;
|
||||||
let cropW = width;
|
let cropW = width;
|
||||||
let cropH = height;
|
let cropH = height;
|
||||||
@@ -1460,36 +1462,65 @@ export async function renderPhoto(input: RenderInput): Promise<RenderResult | nu
|
|||||||
stampW = outW;
|
stampW = outW;
|
||||||
stampH = outH;
|
stampH = outH;
|
||||||
} else if (isOldFilm(frameId)) {
|
} else if (isOldFilm(frameId)) {
|
||||||
// The sheet is stretched over the whole frame and drawn on top of the
|
// The sheet has the frame's SHAPE, like the walls: `old-film-portrait` is
|
||||||
// photo, so the paper eats the outer percent of the picture instead of
|
// the PNG turned 90° CW, so the pair is one standing frame and one lying.
|
||||||
// cropping it — the inverse of the walls above, whose mat window crops.
|
// The sheet's own 914x609 is far too small to hold a photo, so the frame
|
||||||
// The stretch is deliberately non-uniform: a cover-scale of a 3:2 sheet
|
// is scaled UP to the photo instead of the photo down to the frame: the
|
||||||
// onto a 4:3 frame would cut the torn edge off two sides, and the deckle
|
// deckle stays a couple of percent of the print at any export size, which
|
||||||
// is the whole point of the frame.
|
// is also why the photo is cover-cropped into the whole sheet and not
|
||||||
|
// into a measured window — the sheet's edge is torn, so it has no straight
|
||||||
|
// sides to cut against; the photo simply runs under all of it.
|
||||||
|
flush(surface);
|
||||||
|
const photoImg = own(surface.makeImageSnapshot());
|
||||||
|
cardSource = photoImg;
|
||||||
const film = loadOldFilm(input.assets);
|
const film = loadOldFilm(input.assets);
|
||||||
if (!film) {
|
if (!film) {
|
||||||
console.error('Failed to decode old_film.png');
|
console.error('Failed to decode old_film.png');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const portrait = frameId === 'old-film-portrait';
|
const portrait = frameId === 'old-film-portrait';
|
||||||
const filmPaint = own(Skia.Paint());
|
const sheetW = portrait ? film.height() : film.width();
|
||||||
canvas.save();
|
const sheetH = portrait ? film.width() : film.height();
|
||||||
|
const scale = Math.max(width / sheetW, height / sheetH);
|
||||||
|
const outW = Math.round(sheetW * scale);
|
||||||
|
const outH = Math.round(sheetH * scale);
|
||||||
|
const cardSurface = createSurface(outW, outH);
|
||||||
|
if (!cardSurface) return null;
|
||||||
|
own(cardSurface);
|
||||||
|
const cardCanvas = cardSurface.getCanvas();
|
||||||
|
const plainPaint = own(Skia.Paint());
|
||||||
|
const cover = Math.max(outW / width, outH / height);
|
||||||
|
const coverW = outW / cover;
|
||||||
|
const coverH = outH / cover;
|
||||||
|
cardCanvas.drawImageRectOptions(
|
||||||
|
photoImg,
|
||||||
|
Skia.XYWHRect((width - coverW) / 2, (height - coverH) / 2, coverW, coverH),
|
||||||
|
Skia.XYWHRect(0, 0, outW, outH),
|
||||||
|
Skia.FilterMode.Linear,
|
||||||
|
Skia.MipmapMode.None,
|
||||||
|
plainPaint
|
||||||
|
);
|
||||||
|
cardCanvas.save();
|
||||||
if (portrait) {
|
if (portrait) {
|
||||||
// The wall-frame portrait's own turn: origin to the top-right corner,
|
// The wall-frame portrait's own turn: origin to the top-right corner,
|
||||||
// then 90° CW, so the sheet's width runs down the frame and the drawn
|
// then 90° CW, so the sheet's width runs down the frame and the drawn
|
||||||
// rect is the frame's height by its width.
|
// rect is the frame's height by its width.
|
||||||
canvas.translate(width, 0);
|
cardCanvas.translate(outW, 0);
|
||||||
canvas.rotate(90, 0, 0);
|
cardCanvas.rotate(90, 0, 0);
|
||||||
}
|
}
|
||||||
canvas.drawImageRectOptions(
|
cardCanvas.drawImageRectOptions(
|
||||||
film,
|
film,
|
||||||
Skia.XYWHRect(0, 0, film.width(), film.height()),
|
Skia.XYWHRect(0, 0, film.width(), film.height()),
|
||||||
portrait ? Skia.XYWHRect(0, 0, height, width) : Skia.XYWHRect(0, 0, width, height),
|
portrait ? Skia.XYWHRect(0, 0, outH, outW) : Skia.XYWHRect(0, 0, outW, outH),
|
||||||
Skia.FilterMode.Linear,
|
Skia.FilterMode.Linear,
|
||||||
Skia.MipmapMode.None,
|
Skia.MipmapMode.None,
|
||||||
filmPaint
|
plainPaint
|
||||||
);
|
);
|
||||||
canvas.restore();
|
cardCanvas.restore();
|
||||||
|
outputSurface = cardSurface;
|
||||||
|
stampCanvas = cardCanvas;
|
||||||
|
stampW = outW;
|
||||||
|
stampH = outH;
|
||||||
} else {
|
} else {
|
||||||
drawFrameOnCanvas(canvas, width, height, frameId);
|
drawFrameOnCanvas(canvas, width, height, frameId);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user