image_test.go (9208B)
1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package hugolib 15 16 import ( 17 "io" 18 "os" 19 "path/filepath" 20 "runtime" 21 "strings" 22 "testing" 23 24 "github.com/gohugoio/hugo/config" 25 "github.com/gohugoio/hugo/htesting" 26 27 qt "github.com/frankban/quicktest" 28 "github.com/gohugoio/hugo/hugofs" 29 ) 30 31 // We have many tests for the different resize operations etc. in the resource package, 32 // this is an integration test. 33 func TestImageOps(t *testing.T) { 34 c := qt.New(t) 35 // Make this a real as possible. 36 workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "image-resize") 37 c.Assert(err, qt.IsNil) 38 defer clean() 39 40 newBuilder := func(timeout any) *sitesBuilder { 41 v := config.NewWithTestDefaults() 42 v.Set("workingDir", workDir) 43 v.Set("baseURL", "https://example.org") 44 v.Set("timeout", timeout) 45 46 b := newTestSitesBuilder(t).WithWorkingDir(workDir) 47 b.Fs = hugofs.NewDefault(v) 48 b.WithViper(v) 49 b.WithContent("mybundle/index.md", ` 50 --- 51 title: "My bundle" 52 --- 53 54 {{< imgproc >}} 55 56 `) 57 58 b.WithTemplatesAdded( 59 "shortcodes/imgproc.html", ` 60 {{ $img := resources.Get "images/sunset.jpg" }} 61 {{ $r := $img.Resize "129x239" }} 62 IMG SHORTCODE: {{ $r.RelPermalink }}/{{ $r.Width }} 63 `, 64 "index.html", ` 65 {{ $p := .Site.GetPage "mybundle" }} 66 {{ $img1 := resources.Get "images/sunset.jpg" }} 67 {{ $img2 := $p.Resources.GetMatch "sunset.jpg" }} 68 {{ $img3 := resources.GetMatch "images/*.jpg" }} 69 {{ $r := $img1.Resize "123x234" }} 70 {{ $r2 := $r.Resize "12x23" }} 71 {{ $b := $img2.Resize "345x678" }} 72 {{ $b2 := $b.Resize "34x67" }} 73 {{ $c := $img3.Resize "456x789" }} 74 {{ $fingerprinted := $img1.Resize "350x" | fingerprint }} 75 76 {{ $images := slice $r $r2 $b $b2 $c $fingerprinted }} 77 78 {{ range $i, $r := $images }} 79 {{ printf "Resized%d:" (add $i 1) }} {{ $r.Name }}|{{ $r.Width }}|{{ $r.Height }}|{{ $r.MediaType }}|{{ $r.RelPermalink }}| 80 {{ end }} 81 82 {{ $blurryGrayscale1 := $r | images.Filter images.Grayscale (images.GaussianBlur 8) }} 83 BG1: {{ $blurryGrayscale1.RelPermalink }}/{{ $blurryGrayscale1.Width }} 84 {{ $blurryGrayscale2 := $r.Filter images.Grayscale (images.GaussianBlur 8) }} 85 BG2: {{ $blurryGrayscale2.RelPermalink }}/{{ $blurryGrayscale2.Width }} 86 {{ $blurryGrayscale2_2 := $r.Filter images.Grayscale (images.GaussianBlur 8) }} 87 BG2_2: {{ $blurryGrayscale2_2.RelPermalink }}/{{ $blurryGrayscale2_2.Width }} 88 89 {{ $filters := slice images.Grayscale (images.GaussianBlur 9) }} 90 {{ $blurryGrayscale3 := $r | images.Filter $filters }} 91 BG3: {{ $blurryGrayscale3.RelPermalink }}/{{ $blurryGrayscale3.Width }} 92 93 {{ $blurryGrayscale4 := $r.Filter $filters }} 94 BG4: {{ $blurryGrayscale4.RelPermalink }}/{{ $blurryGrayscale4.Width }} 95 96 {{ $p.Content }} 97 98 `) 99 100 return b 101 } 102 103 imageDir := filepath.Join(workDir, "assets", "images") 104 bundleDir := filepath.Join(workDir, "content", "mybundle") 105 106 c.Assert(os.MkdirAll(imageDir, 0777), qt.IsNil) 107 c.Assert(os.MkdirAll(bundleDir, 0777), qt.IsNil) 108 src, err := os.Open("testdata/sunset.jpg") 109 c.Assert(err, qt.IsNil) 110 out, err := os.Create(filepath.Join(imageDir, "sunset.jpg")) 111 c.Assert(err, qt.IsNil) 112 _, err = io.Copy(out, src) 113 c.Assert(err, qt.IsNil) 114 out.Close() 115 116 src.Seek(0, 0) 117 118 out, err = os.Create(filepath.Join(bundleDir, "sunset.jpg")) 119 c.Assert(err, qt.IsNil) 120 _, err = io.Copy(out, src) 121 c.Assert(err, qt.IsNil) 122 out.Close() 123 src.Close() 124 125 // First build it with a very short timeout to trigger errors. 126 b := newBuilder("10ns") 127 128 imgExpect := ` 129 Resized1: images/sunset.jpg|123|234|image/jpeg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg| 130 Resized2: images/sunset.jpg|12|23|image/jpeg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ada4bb1a57f77a63306e3bd67286248e.jpg| 131 Resized3: sunset.jpg|345|678|image/jpeg|/mybundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_345x678_resize_q75_box.jpg| 132 Resized4: sunset.jpg|34|67|image/jpeg|/mybundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_44d8c928664d7c5a67377c6ec58425ce.jpg| 133 Resized5: images/sunset.jpg|456|789|image/jpeg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_456x789_resize_q75_box.jpg| 134 Resized6: images/sunset.jpg|350|219|image/jpeg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_350x0_resize_q75_box.a86fe88d894e5db613f6aa8a80538fefc25b20fa24ba0d782c057adcef616f56.jpg| 135 BG1: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_2ae8bb993431ec1aec40fe59927b46b4.jpg/123 136 BG2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_2ae8bb993431ec1aec40fe59927b46b4.jpg/123 137 BG3: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ed7740a90b82802261c2fbdb98bc8082.jpg/123 138 BG4: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ed7740a90b82802261c2fbdb98bc8082.jpg/123 139 IMG SHORTCODE: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_129x239_resize_q75_box.jpg/129 140 ` 141 142 assertImages := func() { 143 b.Helper() 144 b.AssertFileContent("public/index.html", imgExpect) 145 b.AssertImage(350, 219, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_350x0_resize_q75_box.a86fe88d894e5db613f6aa8a80538fefc25b20fa24ba0d782c057adcef616f56.jpg") 146 b.AssertImage(129, 239, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_129x239_resize_q75_box.jpg") 147 } 148 149 err = b.BuildE(BuildCfg{}) 150 if runtime.GOOS != "windows" && !strings.Contains(runtime.GOARCH, "arm") && !htesting.IsGitHubAction() { 151 // TODO(bep) 152 c.Assert(err, qt.Not(qt.IsNil)) 153 } 154 155 b = newBuilder(29000) 156 b.Build(BuildCfg{}) 157 158 assertImages() 159 160 // Truncate one image. 161 imgInCache := filepath.Join(workDir, "resources/_gen/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ed7740a90b82802261c2fbdb98bc8082.jpg") 162 f, err := os.Create(imgInCache) 163 c.Assert(err, qt.IsNil) 164 f.Close() 165 166 // Build it again to make sure we read images from file cache. 167 b = newBuilder("30s") 168 b.Build(BuildCfg{}) 169 170 assertImages() 171 } 172 173 func TestImageResizeMultilingual(t *testing.T) { 174 b := newTestSitesBuilder(t).WithConfigFile("toml", ` 175 baseURL="https://example.org" 176 defaultContentLanguage = "en" 177 178 [languages] 179 [languages.en] 180 title = "Title in English" 181 languageName = "English" 182 weight = 1 183 [languages.nn] 184 languageName = "Nynorsk" 185 weight = 2 186 title = "Tittel på nynorsk" 187 [languages.nb] 188 languageName = "Bokmål" 189 weight = 3 190 title = "Tittel på bokmål" 191 [languages.fr] 192 languageName = "French" 193 weight = 4 194 title = "French Title" 195 196 `) 197 198 pageContent := `--- 199 title: "Page" 200 --- 201 ` 202 203 b.WithContent("bundle/index.md", pageContent) 204 b.WithContent("bundle/index.nn.md", pageContent) 205 b.WithContent("bundle/index.fr.md", pageContent) 206 b.WithSunset("content/bundle/sunset.jpg") 207 b.WithSunset("assets/images/sunset.jpg") 208 b.WithTemplates("index.html", ` 209 {{ with (.Site.GetPage "bundle" ) }} 210 {{ $sunset := .Resources.GetMatch "sunset*" }} 211 {{ if $sunset }} 212 {{ $resized := $sunset.Resize "200x200" }} 213 SUNSET FOR: {{ $.Site.Language.Lang }}: {{ $resized.RelPermalink }}/{{ $resized.Width }}/Lat: {{ $resized.Exif.Lat }} 214 {{ end }} 215 {{ else }} 216 No bundle for {{ $.Site.Language.Lang }} 217 {{ end }} 218 219 {{ $sunset2 := resources.Get "images/sunset.jpg" }} 220 {{ $resized2 := $sunset2.Resize "123x234" }} 221 SUNSET2: {{ $resized2.RelPermalink }}/{{ $resized2.Width }}/Lat: {{ $resized2.Exif.Lat }} 222 223 224 `) 225 226 b.Build(BuildCfg{}) 227 228 b.AssertFileContent("public/index.html", "SUNSET FOR: en: /bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg/200/Lat: 36.59744166666667") 229 b.AssertFileContent("public/fr/index.html", "SUNSET FOR: fr: /fr/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg/200/Lat: 36.59744166666667") 230 b.AssertFileContent("public/index.html", " SUNSET2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg/123/Lat: 36.59744166666667") 231 b.AssertFileContent("public/nn/index.html", " SUNSET2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg/123/Lat: 36.59744166666667") 232 233 b.AssertImage(200, 200, "public/fr/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg") 234 b.AssertImage(200, 200, "public/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg") 235 236 // Check the file cache 237 b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg") 238 239 b.AssertFileContent("resources/_gen/images/bundle/sunset_3166614710256882113.json", 240 "DateTimeDigitized|time.Time", "PENTAX") 241 b.AssertImage(123, 234, "resources/_gen/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg") 242 b.AssertFileContent("resources/_gen/images/sunset_3166614710256882113.json", 243 "DateTimeDigitized|time.Time", "PENTAX") 244 245 // TODO(bep) add this as a default assertion after Build()? 246 b.AssertNoDuplicateWrites() 247 }