Strip PNG date chunks from magick outputs

date:create/date:modify tEXt chunks made rebuilds nondeterministic, which
would churn every content-addressed name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Christopher Monsanto
2026-08-17 00:34:30 -04:00
parent 460da484a1
commit 499651b3f8
4 changed files with 15 additions and 9 deletions

View File

@@ -4,7 +4,7 @@ import * as spritedata from '@smogon/sprite-data/index.ts';
import {gen10Modelslike, gen5Gifs} from './rules/modelslike.ts';
import {type Sprite, toPSID} from './rules/publish.ts';
import {forEachRule, memo, rule} from './tools/build/artifact.ts';
import {base, compresspng, pad, spriteglob} from './tools/build/helpers.ts';
import {PNG_DETERMINISTIC, base, compresspng, pad, spriteglob} from './tools/build/helpers.ts';
import {type DeployCtx, defineDeploy} from './tools/deploy/api.ts';
// PS spritesheets. The sheet tools readdir the minisprite dirs and resolve
@@ -49,7 +49,7 @@ rule([
], {
display: "pokemonicons-pokeball-sheet",
cmds: [
"magick convert -background transparent -gravity center -extent 40x30 %f +append %o",
`magick convert ${PNG_DETERMINISTIC} -background transparent -gravity center -extent 40x30 %f +append %o`,
compresspng({config: "SPRITESHEET"}),
],
}, ["pokemonicons-pokeball-sheet.png"]);
@@ -74,8 +74,8 @@ const paddedDex = memo(() => {
return dex.concat(forEachRule(dexMissing, {
display: "missing dex %B",
cmds: [
'magick convert "%f[0]" -trim %o',
'magick mogrify -background transparent -gravity center -resize "120x120>" -extent 120x120 %o',
`magick convert "%f[0]" ${PNG_DETERMINISTIC} -trim %o`,
`magick mogrify ${PNG_DETERMINISTIC} -background transparent -gravity center -resize "120x120>" -extent 120x120 %o`,
compresspng({config: "DEX"}),
],
}, "%B.png"));

View File

@@ -1,6 +1,6 @@
import {forEachRule, memo} from '../tools/build/artifact.ts';
import {base, compresspng, spriteglob} from '../tools/build/helpers.ts';
import {PNG_DETERMINISTIC, base, compresspng, spriteglob} from '../tools/build/helpers.ts';
// Smogdex social images: models, backfilled with gen9 species not yet in
// models (first source wins).
@@ -20,7 +20,7 @@ const socialInputs = memo(() => {
export const fbSprites = memo(() => forEachRule(socialInputs(), {
display: "fbsprite %f",
cmds: [
'magick convert "%f[0]" -trim -resize 150x150 -background white -gravity center -extent 198x198 -bordercolor black -border 1 %o',
`magick convert "%f[0]" ${PNG_DETERMINISTIC} -trim -resize 150x150 -background white -gravity center -extent 198x198 -bordercolor black -border 1 %o`,
compresspng({config: "MODELS"}),
],
}, "%B.png"));
@@ -28,7 +28,7 @@ export const fbSprites = memo(() => forEachRule(socialInputs(), {
export const twitterSprites = memo(() => forEachRule(socialInputs(), {
display: "twittersprite %f",
cmds: [
'magick convert "%f[0]" -trim -resize 115x115 -background white -gravity center -extent 120x120 %o',
`magick convert "%f[0]" ${PNG_DETERMINISTIC} -trim -resize 115x115 -background white -gravity center -extent 120x120 %o`,
compresspng({config: "MODELS"}),
],
}, "%B.png"));

View File

@@ -97,14 +97,18 @@ export function spriteglob(pats : string | string[], flagspec? : Record<string,
});
}
// PNG date/time tEXt chunks make magick output nondeterministic, which would
// churn every content-addressed name on a rebuild.
export const PNG_DETERMINISTIC = '-define png:exclude-chunks=date,time';
export function pad(opts : {w : number, h : number, input? : string, output? : string}) : string {
const input = opts.input ?? '%f';
const output = opts.output ?? '%o';
return `magick convert ${input} -background transparent -gravity center -extent ${opts.w}x${opts.h} ${output}`;
return `magick convert ${input} ${PNG_DETERMINISTIC} -background transparent -gravity center -extent ${opts.w}x${opts.h} ${output}`;
}
export function trimimg(opts : {input? : string, output? : string} = {}) : string {
return `magick convert ${opts.input ?? '%f'} -trim ${opts.output ?? '%o'}`;
return `magick convert ${opts.input ?? '%f'} ${PNG_DETERMINISTIC} -trim ${opts.output ?? '%o'}`;
}
interface CompressOpts {

View File

@@ -28,6 +28,8 @@ for (let i = 0; i < sheet.entries.length; i++) {
const proc = cp.spawn("magick", [
"montage",
"@-",
// Keep sheet bytes deterministic for the content-addressed build.
"-define", "png:exclude-chunks=date,time",
"-background", "transparent",
"-geometry", `${sheet.width}x${sheet.height}>`,
"-gravity", "center",