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 }