Mark and round the uploaded icon's favicon, enlarge the square icon's mark
An uploaded image's favicon now carries the stone mark like the PWA icons and is cut to a circle. The mark is larger on the favicon and on the regular (rounded-square) icons, and the settings preview matches. A render revision in the set's version gives re-saved icons a new URL past the immutable cache. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
||||
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {
|
||||
PwaInstallIconRenderRevision,
|
||||
PwaInstallIconSettingKey,
|
||||
type PwaInstallIcon,
|
||||
type PwaInstallIconMode,
|
||||
@@ -57,7 +58,7 @@ export default async function saveInstallIcon({ mode, stone, mark, image }: Para
|
||||
const version = is_stock
|
||||
? null
|
||||
: createHash("sha256")
|
||||
.update(JSON.stringify({ mode, stone, mark }))
|
||||
.update(JSON.stringify({ mode, stone, mark, render: PwaInstallIconRenderRevision }))
|
||||
.update(source || "")
|
||||
.digest("hex")
|
||||
.slice(0, 12);
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import sharp from "sharp";
|
||||
import sharp, { type OverlayOptions } from "sharp";
|
||||
import {
|
||||
PwaAppIconMarkScale,
|
||||
PwaCornerMarkInset,
|
||||
PwaCornerMarkScale,
|
||||
PwaFaviconCornerMarkInset,
|
||||
PwaFaviconCornerMarkScale,
|
||||
PwaInstallIconFiles,
|
||||
PwaMaskableMarkScale,
|
||||
PwaSquareCornerMarkInset,
|
||||
PwaSquareCornerMarkScale,
|
||||
type PwaInstallIcon,
|
||||
type PwaInstallIconFile,
|
||||
} from "@/src/data/pwa-data";
|
||||
@@ -26,7 +30,8 @@ type Params = {
|
||||
/**
|
||||
* Every file of an install's icon set (`PwaInstallIconFiles`), by name.
|
||||
* A stone icon is the stone on paper, like the stock one. An image fills
|
||||
* the icon, with the stone in the top-left corner when `mark` is on.
|
||||
* the icon, with the stone in the top-left corner when `mark` is on; its
|
||||
* favicon is cut to a circle, the mark kept inside it.
|
||||
*/
|
||||
export default async function renderInstallIcons({ icon, stone_svg, image, background }: Params) {
|
||||
const files: Record<string, Buffer> = {};
|
||||
@@ -79,19 +84,41 @@ async function renderImageFile({
|
||||
.resize(file.size, file.size, { fit: "cover" })
|
||||
.flatten({ background });
|
||||
|
||||
if (!mark || file.kind == "favicon" || file.kind == "plain") {
|
||||
return base.png().toBuffer();
|
||||
const layers: OverlayOptions[] = [];
|
||||
|
||||
if (mark && file.kind != "plain") {
|
||||
const { scale, inset_scale } = markPlacement({ kind: file.kind });
|
||||
const mark_size = Math.max(1, Math.round(file.size * scale));
|
||||
const inset = Math.round(file.size * inset_scale);
|
||||
const stone = await sharp(stone_svg)
|
||||
.resize(mark_size, mark_size, { fit: "contain" })
|
||||
.png()
|
||||
.toBuffer();
|
||||
|
||||
layers.push({ input: stone, left: inset, top: inset });
|
||||
}
|
||||
|
||||
const mark_size = Math.max(1, Math.round(file.size * PwaCornerMarkScale));
|
||||
const inset = Math.round(file.size * PwaCornerMarkInset);
|
||||
const stone = await sharp(stone_svg)
|
||||
.resize(mark_size, mark_size, { fit: "contain" })
|
||||
.png()
|
||||
.toBuffer();
|
||||
if (file.kind == "favicon") {
|
||||
const half = file.size / 2;
|
||||
const circle = Buffer.from(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" width="${file.size}" height="${file.size}"><circle cx="${half}" cy="${half}" r="${half}"/></svg>`,
|
||||
);
|
||||
|
||||
return base
|
||||
.composite([{ input: stone, left: inset, top: inset }])
|
||||
.png()
|
||||
.toBuffer();
|
||||
layers.push({ input: circle, blend: "dest-in" });
|
||||
}
|
||||
|
||||
// Layers apply in order, so the circle cuts the mark too.
|
||||
return base.ensureAlpha().composite(layers).png().toBuffer();
|
||||
}
|
||||
|
||||
function markPlacement({ kind }: { kind: PwaInstallIconFile["kind"] }) {
|
||||
if (kind == "favicon") {
|
||||
return { scale: PwaFaviconCornerMarkScale, inset_scale: PwaFaviconCornerMarkInset };
|
||||
}
|
||||
|
||||
if (kind == "any") {
|
||||
return { scale: PwaSquareCornerMarkScale, inset_scale: PwaSquareCornerMarkInset };
|
||||
}
|
||||
|
||||
return { scale: PwaCornerMarkScale, inset_scale: PwaCornerMarkInset };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user