From 02d253b2b020190dc29dfddded1192a7d59e009d Mon Sep 17 00:00:00 2001 From: Deluan Date: Sat, 25 Jul 2026 15:36:26 -0400 Subject: [PATCH] fix(thumbhash): re-condition alpha fixture and assert solid.png header alpha.png varied only alpha over a constant color, so compositing atop the average canceled L/P/Q exactly like solid.png, leaving it just as float-noise unstable and silently dropping alpha-path coverage. Vary R/G/B with position too so alpha.png carries real signal and reproduces byte-exactly, then replace the blanket solid.png skip with a precise assertion on its well-conditioned header bytes and quantized-zero scales. Also apply a range-over-int modernizer hint in reference_test.go. --- core/artwork/thumbhash/reference_test.go | 26 +++++++++++++----- core/artwork/thumbhash/testdata/alpha.png | Bin 433 -> 21876 bytes .../thumbhash/testdata/gen_fixtures.mjs | 10 ++++--- core/artwork/thumbhash/testdata/golden.json | 2 +- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/core/artwork/thumbhash/reference_test.go b/core/artwork/thumbhash/reference_test.go index b3d7453c3..a23b95526 100644 --- a/core/artwork/thumbhash/reference_test.go +++ b/core/artwork/thumbhash/reference_test.go @@ -53,14 +53,10 @@ func loadGoldens() map[string]string { } var _ = Describe("reference port", func() { - // solid.png and alpha.png are exactly uniform in color, so their true DCT AC term is 0 and the - // hash instead encodes float rounding noise from cos(), where Go and V8 disagree in the last bit. - floatUnstable := map[string]bool{"solid.png": true, "alpha.png": true} - It("reproduces every golden vector", func() { for name, want := range loadGoldens() { - if floatUnstable[name] { - continue + if name == "solid.png" { + continue // see the dedicated header-only spec below } w, h, rgba := loadFixture(name) got := base64.StdEncoding.EncodeToString(referenceEncode(w, h, rgba)) @@ -68,6 +64,22 @@ var _ = Describe("reference port", func() { } }) + // solid.png is uniform, so its true AC term is 0 and the golden's AC nibbles are just float + // rounding noise from cos(); only the header (DC terms + scales, which quantize to 0) is well-defined. + It("reproduces the well-conditioned header of a uniform image", func() { + want, err := base64.StdEncoding.DecodeString(loadGoldens()["solid.png"]) + Expect(err).ToNot(HaveOccurred()) + w, h, rgba := loadFixture("solid.png") + got := referenceEncode(w, h, rgba) + Expect(got[:5]).To(Equal(want[:5]), "header bytes") + + header24 := int(got[0]) | int(got[1])<<8 | int(got[2])<<16 + header16 := int(got[3]) | int(got[4])<<8 + Expect((header24>>18)&31).To(Equal(0), "lScale") + Expect((header16>>3)&63).To(Equal(0), "pScale") + Expect((header16>>9)&63).To(Equal(0), "qScale") + }) + It("produces 24 bytes for a square opaque image", func() { w, h, rgba := loadFixture("square.png") Expect(referenceEncode(w, h, rgba)).To(HaveLen(24)) @@ -122,7 +134,7 @@ func referenceEncode(w, h int, rgba []byte) []byte { encodeChannel := func(channel []float64, nx, ny int) (dc float64, ac []float64, scale float64) { fx := make([]float64, w) - for cy := 0; cy < ny; cy++ { + for cy := range ny { for cx := 0; cx*ny < nx*(ny-cy); cx++ { f := 0.0 for x := range w { diff --git a/core/artwork/thumbhash/testdata/alpha.png b/core/artwork/thumbhash/testdata/alpha.png index 5770c696a4b702dac15b24084cc2556e90e37f40..abcefdd5f2b2e45624ff411abd0f7d71eed0a34e 100644 GIT binary patch literal 21876 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 literal 433 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdoh8V65 { rgba[i + 2] = 180 rgba[i + 3] = 255 } -const alphaRamp = (rgba, i, x, y, w) => { - rgba[i] = 200 - rgba[i + 1] = 50 - rgba[i + 2] = 90 +// RGB must vary with position too: a constant color composited over its own average cancels to a +// flat L/P/Q (the bug this fixture exists to catch), so pair a color gradient with the alpha ramp. +const alphaRamp = (rgba, i, x, y, w, h) => { + rgba[i] = Math.floor((255 * x) / w) + rgba[i + 1] = Math.floor((255 * y) / h) + rgba[i + 2] = Math.floor((255 * (x + y)) / (w + h)) rgba[i + 3] = Math.floor((255 * x) / w) } diff --git a/core/artwork/thumbhash/testdata/golden.json b/core/artwork/thumbhash/testdata/golden.json index 83fc2fdde..e38b76638 100644 --- a/core/artwork/thumbhash/testdata/golden.json +++ b/core/artwork/thumbhash/testdata/golden.json @@ -1,5 +1,5 @@ { - "alpha.png": "HCmDBQA3j3mHeHeXiGCI94h3gHd4iIh4eA==", + "alpha.png": "JFiKBQw3s4ewiId3eBtPOfuEgHd4iIh4eA==", "landscape.png": "3wcOFJpwd3dxd3eHh3ePgAj4hw==", "portrait.png": "3/cNFBpxB4d3d3d4d3eAjwj3eA==", "solid.png": "HoUBBwB4eHeHd3hweId3h3h4B2+Ih4gA",