Repository: gohugoio/hugo (language: go)

Issue:
Animated GIF images: the .Width and .Height properties are incorrect when image cache is empty (first run)
Reference: <https://discourse.gohugo.io/t/44751>

Tested with v0.101.0 and v0.113.0. This is not a new bug.

If you have an empty image cache (`resources/_gen/images`), the `.Width` and `.Height` methods return the original dimensions instead of the resized dimensions when you run `hugo` or `hugo server` for the first time. Note that the images in the cache (and the published images) are the correct size (i.e., resizing was successful).

If you run `hugo` or `hugo server` again (the image cache is not empty) the `.Width` and `.Height` methods return the correct (resized) dimensions.

This behavior is limited to animated GIF images. Non-animated GIF images work as expected, as do JPEG, WebP, etc.

---

Reproducible example (includes an animated GIF and a non-animated GIF):

```text
git clone --single-branch -b hugo-forum-topic-44751 https://github.com/jmooring/hugo-testing hugo-forum-topic-44751
cd hugo-forum-topic-44751
rm -rf resources/_gen/images/ && hugo server
```

Then stop the server, and restart without clearing the image cache:

```text
hugo server
```


Discussion / hints:

Fix patch:
diff --git a/resources/images/image.go b/resources/images/image.go
index 530057d800e..672e8578f21 100644
--- a/resources/images/image.go
+++ b/resources/images/image.go
@@ -377,6 +377,9 @@ type imageConfig struct {
 }
 
 func imageConfigFromImage(img image.Image) image.Config {
+	if giphy, ok := img.(Giphy); ok {
+		return giphy.GIF().Config
+	}
 	b := img.Bounds()
 	return image.Config{Width: b.Max.X, Height: b.Max.Y}
 }


Test patch:
diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
index 4993d922a54..0daa766ac96 100644
--- a/hugolib/integrationtest_builder.go
+++ b/hugolib/integrationtest_builder.go
@@ -299,10 +299,20 @@ func (s *IntegrationTestBuilder) initBuilder() error {
 
 		isBinaryRe := regexp.MustCompile(`^(.*)(\.png|\.jpg)$`)
 
+		const dataSourceFilenamePrefix = "sourcefilename:"
+
 		for _, f := range s.data.Files {
 			filename := filepath.Join(s.Cfg.WorkingDir, f.Name)
 			data := bytes.TrimSuffix(f.Data, []byte("\n"))
-			if isBinaryRe.MatchString(filename) {
+			datastr := strings.TrimSpace(string(data))
+			if strings.HasPrefix(datastr, dataSourceFilenamePrefix) {
+				// Read from file relative to tue current dir.
+				var err error
+				wd, _ := os.Getwd()
+				filename := filepath.Join(wd, strings.TrimSpace(strings.TrimPrefix(datastr, dataSourceFilenamePrefix)))
+				data, err = os.ReadFile(filename)
+				s.Assert(err, qt.IsNil)
+			} else if isBinaryRe.MatchString(filename) {
 				var err error
 				data, err = base64.StdEncoding.DecodeString(string(data))
 				s.Assert(err, qt.IsNil)
diff --git a/resources/image_test.go b/resources/image_test.go
index 779bacf716c..de32f06282c 100644
--- a/resources/image_test.go
+++ b/resources/image_test.go
@@ -658,11 +658,12 @@ func TestImageOperationsGolden(t *testing.T) {
 
 	// A simple Gif file (no animation).
 	orig := fetchImageForSpec(spec, c, "gohugoio-card.gif")
-	for _, resizeSpec := range []string{"100x", "220x"} {
-		resized, err := orig.Resize(resizeSpec)
+	for _, width := range []int{100, 220} {
+		resized, err := orig.Resize(fmt.Sprintf("%dx", width))
 		c.Assert(err, qt.IsNil)
 		rel := resized.RelPermalink()
 		c.Assert(rel, qt.Not(qt.Equals), "")
+		c.Assert(resized.Width(), qt.Equals, width)
 	}
 
 	// Animated GIF
diff --git a/resources/integration_test.go b/resources/integration_test.go
index 92abcb612c6..543c264dcf1 100644
--- a/resources/integration_test.go
+++ b/resources/integration_test.go
@@ -34,15 +34,21 @@ title: "My Bundle"
 ---
 -- content/mybundle/pixel.png --
 iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
+-- content/mybundle/giphy.gif --
+sourcefilename: testdata/giphy.gif
 -- layouts/foo.html --
 -- layouts/index.html --
 {{ $p := site.GetPage "mybundle"}}
 {{ $img := $p.Resources.Get "pixel.png" }}
-{{ $gif := $img.Resize "1x1 gif" }}
-{{ $bmp := $img.Resize "1x1 bmp" }}
+{{ $giphy := $p.Resources.Get "giphy.gif" }}
+{{ $gif := $img.Resize "1x2 gif" }}
+{{ $bmp := $img.Resize "2x3 bmp" }}
+{{ $anigif := $giphy.Resize "4x5" }}
 
-gif: {{ $gif.RelPermalink }}|{{ $gif.MediaType }}|
-bmp: {{ $bmp.RelPermalink }}|{{ $bmp.MediaType }}|	
+
+gif: {{ $gif.RelPermalink }}|}|{{ $gif.Width }}|{{ $gif.Height }}|{{ $gif.MediaType }}|
+bmp: {{ $bmp.RelPermalink }}|}|{{ $bmp.Width }}|{{ $bmp.Height }}|{{ $bmp.MediaType }}|
+anigif: {{ $anigif.RelPermalink }}|{{ $anigif.Width }}|{{ $anigif.Height }}|{{ $anigif.MediaType }}|
 `
 
 	b := hugolib.NewIntegrationTestBuilder(
@@ -55,9 +61,9 @@ bmp: {{ $bmp.RelPermalink }}|{{ $bmp.MediaType }}|
 
 	assertImages := func() {
 		b.AssertFileContent("public/index.html", `
-		gif: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_1x1_resize_box_3.gif|image/gif|
-		bmp: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_1x1_resize_box_3.bmp|image/bmp|
-		
+		gif: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_1x2_resize_box_3.gif|}|1|2|image/gif|
+		bmp: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x3_resize_box_3.bmp|}|2|3|image/bmp|
+		anigif: /mybundle/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_4x5_resize_box_1.gif|4|5|image/gif|
 		`)
 	}