Skip to content

Commit 40c5a5c

Browse files
committed
fix(library): compare decoded pixels in the cover sync check
Byte-equality on the mozjpeg output assumed portable encoder bytes. libvips/mozjpeg does not guarantee that across OS and CPU, so identical input can encode differently on a contributor's machine or a Linux CI runner and fail the check for no real reason — exactly where the check was meant to run. Decodes both images to greyscale and compares mean absolute difference instead, which discards encoder variance while still testing what the check is about. Measured on this cover set: re-encoding an identical render with a deliberately different encoder moves it ~0.26, a one-word title change moves it ~12; the threshold of 2 sits between them with ~8x margin.
1 parent 3e1bc56 commit 40c5a5c

1 file changed

Lines changed: 42 additions & 4 deletions

File tree

scripts/generate-library-covers.tsx

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ const COVER_HEIGHT = 675
4949
* the encoder has to preserve is glyph edges.
5050
*/
5151
const JPEG_QUALITY = 82
52+
/**
53+
* Greyscale mean-absolute-difference above which `--check` calls a committed
54+
* cover stale. Measured on this cover set: re-encoding an identical render
55+
* with a deliberately different encoder (quality 70, mozjpeg off) moves it
56+
* ~0.26, while a one-word title change moves it ~12. A threshold of 2 clears
57+
* encoder and platform noise by ~8x and still catches any real drift.
58+
*/
59+
const MAX_PIXEL_DIFFERENCE = 2
5260

5361
/** Exact hex from a vector trace of the reference cover template, not an estimate off compressed JPEG pixels. */
5462
const INK_COLOR = '#515151'
@@ -206,6 +214,30 @@ async function renderCover(
206214
return await sharp(png).jpeg({ quality: JPEG_QUALITY, mozjpeg: true }).toBuffer()
207215
}
208216

217+
/**
218+
* Mean absolute difference between the committed cover and a freshly rendered
219+
* one, over decoded greyscale pixels — `null` if the committed file is not the
220+
* expected size.
221+
*
222+
* Deliberately not a byte comparison of the JPEGs. libvips/mozjpeg output is
223+
* not portable across OS and CPU, so identical input can encode to different
224+
* bytes on a contributor's machine or a Linux CI runner and fail a byte-equal
225+
* check for no real reason. Decoding first discards that encoder variance
226+
* while preserving what the check is actually about: whether the committed
227+
* image still renders the current title.
228+
*/
229+
async function compareCovers(committedPath: string, rendered: Buffer): Promise<number | null> {
230+
const decode = (input: string | Buffer) =>
231+
sharp(input).greyscale().raw().toBuffer({ resolveWithObject: true })
232+
233+
const [a, b] = await Promise.all([decode(committedPath), decode(rendered)])
234+
if (a.info.width !== b.info.width || a.info.height !== b.info.height) return null
235+
236+
let total = 0
237+
for (let i = 0; i < a.data.length; i++) total += Math.abs(a.data[i] - b.data[i])
238+
return total / a.data.length
239+
}
240+
209241
/**
210242
* Reads the `title` out of an MDX file's YAML frontmatter without pulling in a
211243
* YAML parser — the field is a single quoted or bare scalar on one line.
@@ -262,11 +294,17 @@ async function main() {
262294
const cover = await renderCover(title, fontData, measure)
263295

264296
if (check) {
265-
const committed = existsSync(outputPath) ? await readFile(outputPath) : null
266-
if (committed === null) {
297+
if (!existsSync(outputPath)) {
267298
stale.push(`${slug} — no cover committed`)
268-
} else if (!committed.equals(cover)) {
269-
stale.push(`${slug} — committed cover does not match "${title}"`)
299+
continue
300+
}
301+
const difference = await compareCovers(outputPath, cover)
302+
if (difference === null) {
303+
stale.push(`${slug} — committed cover has unexpected dimensions`)
304+
} else if (difference > MAX_PIXEL_DIFFERENCE) {
305+
stale.push(
306+
`${slug} — committed cover does not render "${title}" (difference ${difference.toFixed(2)})`
307+
)
270308
}
271309
continue
272310
}

0 commit comments

Comments
 (0)