From a8c5d0d00d0dbe5c4bec19823146874cadc839cf Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 22 Dec 2025 11:27:06 +0100 Subject: [PATCH] Fix "image: unknown format" error Fixes #14295 --- hugolib/hugo_sites_multihost_test.go | 4 +-- hugolib/integrationtest_builder.go | 10 ++++++ .../pagesfromgotmpl_integration_test.go | 2 +- internal/warpc/webp_integration_test.go | 8 ++--- resources/image_test.go | 26 +++++++-------- resources/images/config.go | 16 ++-------- resources/images/config_test.go | 3 +- resources/images/image.go | 19 +++++++++++ resources/images/imagetesting/testing.go | 2 +- ...17.webp => giphy_hu_bb052284cc220165.webp} | Bin ...23b4.gif => giphy_hu_c6b8060edf0363b1.gif} | Bin resources/resources_integration_test.go | 6 ++-- resources/transform_test.go | 8 ++--- tpl/images/images_integration_test.go | 30 ++++++++++++++++++ tpl/resources/resources_integration_test.go | 5 ++- 15 files changed, 92 insertions(+), 47 deletions(-) rename resources/images/testdata/images_golden/webp/animation/{giphy_hu_e4a5984f8835d617.webp => giphy_hu_bb052284cc220165.webp} (100%) rename resources/images/testdata/images_golden/webp/animation/{giphy_hu_87010e943ffb23b4.gif => giphy_hu_c6b8060edf0363b1.gif} (100%) diff --git a/hugolib/hugo_sites_multihost_test.go b/hugolib/hugo_sites_multihost_test.go index e090e03cc..875fcfd5a 100644 --- a/hugolib/hugo_sites_multihost_test.go +++ b/hugolib/hugo_sites_multihost_test.go @@ -207,9 +207,9 @@ title: mybundle-en b.AssertFileExists("public/de/mybundle/pixel.png", true) b.AssertFileExists("public/en/mybundle/pixel.png", true) - b.AssertFileExists("public/de/mybundle/pixel_hu_58204cbc58507d74.png", true) + b.AssertFileExists("public/de/mybundle/pixel_hu_c8522c65d73a0421.png", true) // failing test below - b.AssertFileExists("public/en/mybundle/pixel_hu_58204cbc58507d74.png", true) + b.AssertFileExists("public/en/mybundle/pixel_hu_c8522c65d73a0421.png", true) } func TestMultihostResourceOneBaseURLWithSubPath(t *testing.T) { diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go index 76271fdbb..b9aec3705 100644 --- a/hugolib/integrationtest_builder.go +++ b/hugolib/integrationtest_builder.go @@ -720,6 +720,16 @@ func (s *IntegrationTestBuilder) EditFileReplaceFunc(filename string, replacemen return s } +func (s *IntegrationTestBuilder) EditFileAppend(filename, contnt string) *IntegrationTestBuilder { + absFilename := s.absFilename(filename) + b, err := afero.ReadFile(s.fs.Source, absFilename) + s.Assert(err, qt.IsNil) + s.changedFiles = append(s.changedFiles, absFilename) + oldContent := string(b) + s.writeSource(absFilename, oldContent+contnt) + return s +} + func (s *IntegrationTestBuilder) EditFiles(filenameContent ...string) *IntegrationTestBuilder { for i := 0; i < len(filenameContent); i += 2 { filename, content := filepath.FromSlash(filenameContent[i]), filenameContent[i+1] diff --git a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go index cb42e46f9..6c8c376b0 100644 --- a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go +++ b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go @@ -122,7 +122,7 @@ docs/p1/sub/mymixcasetext2.txt "RelPermalink: /docs/p1/sub/mymixcasetext2.txt|Name: sub/mymixcasetext2.txt|", "RelPermalink: /mydata.yaml|Name: sub/data1.yaml|Title: Sub data|Params: map[]|", "Featured Image: /a/pixel.png|featured.png|", - "Resized Featured Image: /a/pixel_hu_a32b3e361d55df1.png|10|", + "Resized Featured Image: /a/pixel_hu_a354833fd576551d.png|10|", // Resource from string "RelPermalink: /docs/p1/mytext.txt|Name: textresource|Title: My Text Resource|Params: map[param1:param1v]|", // Dates diff --git a/internal/warpc/webp_integration_test.go b/internal/warpc/webp_integration_test.go index 8a98541b2..544c45e24 100644 --- a/internal/warpc/webp_integration_test.go +++ b/internal/warpc/webp_integration_test.go @@ -60,7 +60,7 @@ Resized RelPermalink: {{ $resized.RelPermalink }}| b := hugolib.Test(t, files) - b.ImageHelper("public/gopher_hu_f8d20fe200599f16.webp").AssertFormat("webp") + b.ImageHelper("public/gopher_hu_cc98ebaf742cba8e.webp").AssertFormat("webp") } func TestWebPInvalid(t *testing.T) { @@ -111,9 +111,9 @@ sourcefilename: ../../resources/testdata/giphy.gif // Source animated gif: // Frame durations in ms. giphyFrameDurations := []int{200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200} - b.ImageHelper("public/giphy_hu_e4a5984f8835d617.webp").AssertFormat("webp").AssertIsAnimated(true).AssertLoopCount(0).AssertFrameDurations(giphyFrameDurations) - b.ImageHelper("public/giphy_hu_87010e943ffb23b4.gif").AssertFormat("gif").AssertIsAnimated(true).AssertLoopCount(0).AssertFrameDurations(giphyFrameDurations) - b.ImageHelper("public/giphy_hu_e4a5984f8835d617.webp").AssertFormat("webp").AssertIsAnimated(true).AssertLoopCount(0).AssertFrameDurations(giphyFrameDurations) + + b.ImageHelper("public/giphy_hu_bb052284cc220165.webp").AssertFormat("webp").AssertIsAnimated(true).AssertLoopCount(0).AssertFrameDurations(giphyFrameDurations) + b.ImageHelper("public/giphy_hu_c6b8060edf0363b1.gif").AssertFormat("gif").AssertIsAnimated(true).AssertLoopCount(0).AssertFrameDurations(giphyFrameDurations) // Source animated webp: animFrameDurations := []int{80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80} diff --git a/resources/image_test.go b/resources/image_test.go index 19d2f5a8f..b4153f834 100644 --- a/resources/image_test.go +++ b/resources/image_test.go @@ -113,28 +113,28 @@ func TestImageTransformBasic(t *testing.T) { assertWidthHeight(resizedAndRotated, 125, 200) assertWidthHeight(resized, 300, 200) - c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu_d2115125d9324a79.jpg") + c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu_f4f15cdbaaca3b2d.jpg") fitted, err := resized.Fit("50x50") c.Assert(err, qt.IsNil) - c.Assert(fitted.RelPermalink(), qt.Equals, "/a/sunset_hu_c2c98e06123b048e.jpg") + c.Assert(fitted.RelPermalink(), qt.Equals, "/a/sunset_hu_c9781e950a09210.jpg") assertWidthHeight(fitted, 50, 33) // Check the MD5 key threshold fittedAgain, _ := fitted.Fit("10x20") fittedAgain, err = fittedAgain.Fit("10x20") c.Assert(err, qt.IsNil) - c.Assert(fittedAgain.RelPermalink(), qt.Equals, "/a/sunset_hu_dc9e89c10109de72.jpg") + c.Assert(fittedAgain.RelPermalink(), qt.Equals, "/a/sunset_hu_78c1665fdce5ec4d.jpg") assertWidthHeight(fittedAgain, 10, 7) filled, err := image.Fill("200x100 bottomLeft") c.Assert(err, qt.IsNil) - c.Assert(filled.RelPermalink(), qt.Equals, "/a/sunset_hu_b9f6d350738928fe.jpg") + c.Assert(filled.RelPermalink(), qt.Equals, "/a/sunset_hu_b3e01daae854e587.jpg") assertWidthHeight(filled, 200, 100) smart, err := image.Fill("200x100 smart") c.Assert(err, qt.IsNil) - c.Assert(smart.RelPermalink(), qt.Equals, "/a/sunset_hu_6fd390e7b0d26f0b.jpg") + c.Assert(smart.RelPermalink(), qt.Equals, "/a/sunset_hu_622a1375d91d9312.jpg") assertWidthHeight(smart, 200, 100) // Check cache @@ -144,12 +144,12 @@ func TestImageTransformBasic(t *testing.T) { cropped, err := image.Crop("300x300 topRight") c.Assert(err, qt.IsNil) - c.Assert(cropped.RelPermalink(), qt.Equals, "/a/sunset_hu_3df036e11f4ddd43.jpg") + c.Assert(cropped.RelPermalink(), qt.Equals, "/a/sunset_hu_9016246670a22728.jpg") assertWidthHeight(cropped, 300, 300) smartcropped, err := image.Crop("200x200 smart") c.Assert(err, qt.IsNil) - c.Assert(smartcropped.RelPermalink(), qt.Equals, "/a/sunset_hu_12e2d26de89b464b.jpg") + c.Assert(smartcropped.RelPermalink(), qt.Equals, "/a/sunset_hu_78e9677f68b821ed.jpg") assertWidthHeight(smartcropped, 200, 200) // Check cache @@ -216,7 +216,7 @@ func TestImageTransformFormat(t *testing.T) { imagePng, err := image.Resize("450x png") c.Assert(err, qt.IsNil) - c.Assert(imagePng.RelPermalink(), qt.Equals, "/a/sunset_hu_e8b9444dcf2e75ef.png") + c.Assert(imagePng.RelPermalink(), qt.Equals, "/a/sunset_hu_63ccccb11ff4e285.png") c.Assert(imagePng.ResourceType(), qt.Equals, "image") assertExtWidthHeight(imagePng, ".png", 450, 281) c.Assert(imagePng.Name(), qt.Equals, "sunset.jpg") @@ -224,7 +224,7 @@ func TestImageTransformFormat(t *testing.T) { imageGif, err := image.Resize("225x gif") c.Assert(err, qt.IsNil) - c.Assert(imageGif.RelPermalink(), qt.Equals, "/a/sunset_hu_f80842d4c3789345.gif") + c.Assert(imageGif.RelPermalink(), qt.Equals, "/a/sunset_hu_6d1f23c09eddc748.gif") c.Assert(imageGif.ResourceType(), qt.Equals, "image") assertExtWidthHeight(imageGif, ".gif", 225, 141) c.Assert(imageGif.Name(), qt.Equals, "sunset.jpg") @@ -247,7 +247,7 @@ func TestImagePermalinkPublishOrder(t *testing.T) { }() check1 := func(img images.ImageResource) { - resizedLink := "/a/sunset_hu_3910bca82e28c9d6.jpg" + resizedLink := "/a/sunset_hu_3a097ae28aebc166.jpg" c.Assert(img.RelPermalink(), qt.Equals, resizedLink) assertImageFile(c, spec.PublishFs, resizedLink, 100, 50) } @@ -288,12 +288,12 @@ func TestImageBugs(t *testing.T) { c.Assert(err, qt.IsNil) c.Assert(resized, qt.Not(qt.IsNil)) c.Assert(resized.Width(), qt.Equals, 200) - c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu_951d3980b18c52a9.jpg") + c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu_a6f31c42e1afef07.jpg") resized, err = resized.Resize("100x") c.Assert(err, qt.IsNil) c.Assert(resized, qt.Not(qt.IsNil)) c.Assert(resized.Width(), qt.Equals, 100) - c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu_1daa203572ecd6ec.jpg") + c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu_14e106419fe28039.jpg") }) // Issue #6137 @@ -391,7 +391,7 @@ func TestImageResize8BitPNG(t *testing.T) { resized, err := image.Resize("800x") c.Assert(err, qt.IsNil) c.Assert(resized.MediaType().Type, qt.Equals, "image/png") - c.Assert(resized.RelPermalink(), qt.Equals, "/a/gohugoio_hu_fe2b762e9cac406c.png") + c.Assert(resized.RelPermalink(), qt.Equals, "/a/gohugoio_hu_626cfc4db4222bfe.png") c.Assert(resized.Width(), qt.Equals, 800) } diff --git a/resources/images/config.go b/resources/images/config.go index e2f0d5211..f09805307 100644 --- a/resources/images/config.go +++ b/resources/images/config.go @@ -67,17 +67,9 @@ var ( media.Builtin.WEBPType.SubType: WEBP, } - // Add or increment if changes to an image format's processing requires - // re-generation. - imageFormatsVersions = map[Format]int{ - PNG: 0, - WEBP: 1, // Moved to WASM-based WebP encoder and decoder. - GIF: 0, - } - // Increment to mark all processed images as stale. Only use when absolutely needed. - // See the finer grained smartCropVersionNumber and imageFormatsVersions. - mainImageVersionNumber = 0 + // See the finer grained smartCropVersionNumber. + mainImageVersionNumber = 1 ) var anchorPositions = map[string]gift.Anchor{ @@ -328,10 +320,6 @@ func DecodeImageConfig(options []string, defaults *config.ConfigNamespace[Imagin options = append(options, strconv.Itoa(mainImageVersionNumber)) } - if v, ok := imageFormatsVersions[sourceFormat]; ok && v > 0 { - options = append(options, strconv.Itoa(v)) - } - if smartCropVersionNumber > 0 && c.Anchor == SmartCropAnchor { options = append(options, strconv.Itoa(smartCropVersionNumber)) } diff --git a/resources/images/config_test.go b/resources/images/config_test.go index 09aadeb62..907bb10cb 100644 --- a/resources/images/config_test.go +++ b/resources/images/config_test.go @@ -19,7 +19,6 @@ import ( "testing" qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/common/hashing" ) func TestDecodeConfig(t *testing.T) { @@ -118,7 +117,7 @@ func TestDecodeImageConfig(t *testing.T) { t.Fatalf("[%d] err: %s", i, err) } expect := this.expect.(ImageConfig) - expect.Key = hashing.HashStringHex(options) + result.Key = "" if fmt.Sprint(result) != fmt.Sprint(expect) { t.Fatalf("[%d] got\n%v\n but expected\n%v", i, result, expect) diff --git a/resources/images/image.go b/resources/images/image.go index ce9d5f4d5..4d8f4961c 100644 --- a/resources/images/image.go +++ b/resources/images/image.go @@ -380,6 +380,25 @@ func (f Format) MediaType() media.Type { } } +func (f Format) String() string { + switch f { + case JPEG: + return "JPEG" + case PNG: + return "PNG" + case GIF: + return "GIF" + case TIFF: + return "TIFF" + case BMP: + return "BMP" + case WEBP: + return "WEBP" + default: + return "Unknown" + } +} + type imageConfig struct { config image.Config configInit sync.Once diff --git a/resources/images/imagetesting/testing.go b/resources/images/imagetesting/testing.go index 54365f867..979ca2fc5 100644 --- a/resources/images/imagetesting/testing.go +++ b/resources/images/imagetesting/testing.go @@ -125,7 +125,7 @@ func RunGolden(opts GoldenImageTestOpts) *hugolib.IntegrationTestBuilder { shouldSkip := func(d fs.DirEntry) bool { if runtime.GOARCH == "arm64" { // TODO(bep) figure out why this fails on arm64. I have inspected the images, and they look identical. - if d.Name() == "giphy_hu_e4a5984f8835d617.webp" { + if d.Name() == "giphy_hu_bb052284cc220165.webp" { c.Logf("skipping %s on %s", d.Name(), runtime.GOARCH) return true } diff --git a/resources/images/testdata/images_golden/webp/animation/giphy_hu_e4a5984f8835d617.webp b/resources/images/testdata/images_golden/webp/animation/giphy_hu_bb052284cc220165.webp similarity index 100% rename from resources/images/testdata/images_golden/webp/animation/giphy_hu_e4a5984f8835d617.webp rename to resources/images/testdata/images_golden/webp/animation/giphy_hu_bb052284cc220165.webp diff --git a/resources/images/testdata/images_golden/webp/animation/giphy_hu_87010e943ffb23b4.gif b/resources/images/testdata/images_golden/webp/animation/giphy_hu_c6b8060edf0363b1.gif similarity index 100% rename from resources/images/testdata/images_golden/webp/animation/giphy_hu_87010e943ffb23b4.gif rename to resources/images/testdata/images_golden/webp/animation/giphy_hu_c6b8060edf0363b1.gif diff --git a/resources/resources_integration_test.go b/resources/resources_integration_test.go index 54c6122cb..1cc870f80 100644 --- a/resources/resources_integration_test.go +++ b/resources/resources_integration_test.go @@ -62,9 +62,9 @@ anigif: {{ $anigif.RelPermalink }}|{{ $anigif.Width }}|{{ $anigif.Height }}|{{ $ assertImages := func() { b.AssertFileContent("public/index.html", ` - gif: /mybundle/pixel_hu_93429543fc146fce.gif|}|1|2|image/gif| -bmp: /mybundle/pixel_hu_f9bf2acd6578e2c6.bmp|}|2|3|image/bmp| -anigif: /mybundle/giphy_hu_652d28653068b48f.gif|4|5|image/gif| +gif: /mybundle/pixel_hu_d6bad5e71f783c98.gif|}|1|2|image/gif| +bmp: /mybundle/pixel_hu_a8812c9bf8812b53.bmp|}|2|3|image/bmp| +anigif: /mybundle/giphy_hu_7f64f85f904209d4.gif|4|5|image/gif| `) } diff --git a/resources/transform_test.go b/resources/transform_test.go index eac85ada9..feae43e41 100644 --- a/resources/transform_test.go +++ b/resources/transform_test.go @@ -386,15 +386,15 @@ func TestTransform(t *testing.T) { resizedPublished1, err := img.Resize("40x40") c.Assert(err, qt.IsNil) c.Assert(resizedPublished1.Height(), qt.Equals, 40) - c.Assert(resizedPublished1.RelPermalink(), qt.Equals, "/gopher.changed_hu_85920388a7ff96fa.png") - assertShouldExist(c, spec, "public/gopher.changed_hu_85920388a7ff96fa.png", true) + c.Assert(resizedPublished1.RelPermalink(), qt.Equals, "/gopher.changed_hu_6347c67500afc377.png") + assertShouldExist(c, spec, "public/gopher.changed_hu_6347c67500afc377.png", true) // Permalink called. resizedPublished2, err := img.Resize("30x30") c.Assert(err, qt.IsNil) c.Assert(resizedPublished2.Height(), qt.Equals, 30) - c.Assert(resizedPublished2.Permalink(), qt.Equals, "https://example.com/gopher.changed_hu_c8d8163c08643a7f.png") - assertShouldExist(c, spec, "public/gopher.changed_hu_c8d8163c08643a7f.png", true) + c.Assert(resizedPublished2.Permalink(), qt.Equals, "https://example.com/gopher.changed_hu_2d293650135f63d6.png") + assertShouldExist(c, spec, "public/gopher.changed_hu_2d293650135f63d6.png", true) assertNoDuplicateWrites(c, spec) }) diff --git a/tpl/images/images_integration_test.go b/tpl/images/images_integration_test.go index b8edb22f3..55b541ff1 100644 --- a/tpl/images/images_integration_test.go +++ b/tpl/images/images_integration_test.go @@ -188,3 +188,33 @@ Brightnes method: /qr_hu_d5f06fd7594d0594.webp Brightnes func: /qr_hu_d5f06fd7594d0594.webp `) } + +// Note that this test doesn't really reproduce the original issue, +// but keep it as a regression test for WebP decoding in general. +// TODO(bep) I fixed the failing site, but I don't really understand why it failed. But now it's Christmas. +func TestPNGIssue14295(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableLiveReload = true +-- assets/qr.png -- +sourcefilename: testdata/images_golden/funcs/qr-level-high_scale-6.png +-- layouts/home.html -- +{{ $img := resources.Get "qr.png" }} +{{ $img = $img.Resize "200x webp" | images.Filter (images.GaussianBlur 5) }} +Image: {{ $img.RelPermalink }} + +` + + tempDir := t.TempDir() + + for range 2 { + b := hugolib.Test(t, files, hugolib.TestOptWithConfig(func(cfg *hugolib.IntegrationTestConfig) { + cfg.NeedsOsFS = true + cfg.WorkingDir = tempDir + })) + b.AssertFileContent("public/index.html", `Image: /qr_hu_6b5f59be9c4d3b3a.webp`) + + } +} diff --git a/tpl/resources/resources_integration_test.go b/tpl/resources/resources_integration_test.go index 919afe499..60889cb07 100644 --- a/tpl/resources/resources_integration_test.go +++ b/tpl/resources/resources_integration_test.go @@ -60,14 +60,13 @@ Copy3: {{ $copy3.RelPermalink}}|{{ $copy3.MediaType }}|{{ $copy3.Content | safeJ b.AssertFileContent("public/index.html", ` Image Orig: /blog/images/pixel.png|image/png|1|1| -Image Copy1: /blog/images/copy_hu_1d9addfff177f388.png|image/png|3|4| -Image Copy2: /blog/images/copy2.png|image/png|3|4 +Image Copy1: /blog/images/copy_hu_ef85c36d9b9eff0.png|image/png|3|4| +Image Copy2: /blog/images/copy2.png|image/png|3|4| Image Copy3: image/png|3|4| Orig: /blog/js/foo.js|text/javascript|let foo;| Copy1: /blog/js/copies/bar.js|text/javascript|let foo;| Copy2: /blog/js/copies/baz.a677329fc6c4ad947e0c7116d91f37a2.js|text/javascript|let foo;| Copy3: /blog/js/copies/moo.a677329fc6c4ad947e0c7116d91f37a2.min.js|text/javascript|let foo| - `) b.AssertFileExists("public/images/copy2.png", true) -- 2.39.5