image_test.go - hugo - [fork] hugo port for 9front
(HTM) git clone https://git.drkhsh.at/hugo.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Submodules
(DIR) README
(DIR) LICENSE
---
image_test.go (15523B)
---
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 resources_test
15
16 import (
17 "context"
18 "fmt"
19 "io/fs"
20 "math/rand"
21 "os"
22 "strconv"
23 "sync"
24 "testing"
25 "time"
26
27 "github.com/bep/imagemeta"
28
29 "github.com/gohugoio/hugo/common/paths"
30
31 "github.com/spf13/afero"
32
33 "github.com/gohugoio/hugo/media"
34 "github.com/gohugoio/hugo/resources/images"
35 "github.com/google/go-cmp/cmp"
36
37 "github.com/gohugoio/hugo/htesting/hqt"
38
39 qt "github.com/frankban/quicktest"
40 )
41
42 var eq = qt.CmpEquals(
43 cmp.Comparer(func(p1, p2 os.FileInfo) bool {
44 return p1.Name() == p2.Name() && p1.Size() == p2.Size() && p1.IsDir() == p2.IsDir()
45 }),
46 cmp.Comparer(func(d1, d2 fs.DirEntry) bool {
47 p1, err1 := d1.Info()
48 p2, err2 := d2.Info()
49 if err1 != nil || err2 != nil {
50 return false
51 }
52 return p1.Name() == p2.Name() && p1.Size() == p2.Size() && p1.IsDir() == p2.IsDir()
53 }),
54 // cmp.Comparer(func(p1, p2 *genericResource) bool { return p1 == p2 }),
55 cmp.Comparer(func(m1, m2 media.Type) bool {
56 return m1.Type == m2.Type
57 }),
58 cmp.Comparer(
59 func(v1, v2 imagemeta.Rat[uint32]) bool {
60 return v1.String() == v2.String()
61 },
62 ),
63 cmp.Comparer(
64 func(v1, v2 imagemeta.Rat[int32]) bool {
65 return v1.String() == v2.String()
66 },
67 ),
68 cmp.Comparer(func(v1, v2 time.Time) bool {
69 return v1.Unix() == v2.Unix()
70 }),
71 )
72
73 func TestImageTransformBasic(t *testing.T) {
74 c := qt.New(t)
75
76 _, image := fetchSunset(c)
77
78 assertWidthHeight := func(img images.ImageResource, w, h int) {
79 assertWidthHeight(c, img, w, h)
80 }
81
82 gotColors, err := image.Colors()
83 c.Assert(err, qt.IsNil)
84 expectedColors := images.HexStringsToColors("#2d2f33", "#a49e93", "#d39e59", "#a76936", "#737a84", "#7c838b")
85 c.Assert(len(gotColors), qt.Equals, len(expectedColors))
86 for i := range gotColors {
87 c1, c2 := gotColors[i], expectedColors[i]
88 c.Assert(c1.ColorHex(), qt.Equals, c2.ColorHex())
89 c.Assert(c1.ColorGo(), qt.DeepEquals, c2.ColorGo())
90 c.Assert(c1.Luminance(), qt.Equals, c2.Luminance())
91 }
92
93 c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.jpg")
94 c.Assert(image.ResourceType(), qt.Equals, "image")
95 assertWidthHeight(image, 900, 562)
96
97 resized, err := image.Resize("300x200")
98 c.Assert(err, qt.IsNil)
99 c.Assert(image != resized, qt.Equals, true)
100 assertWidthHeight(resized, 300, 200)
101 assertWidthHeight(image, 900, 562)
102
103 resized0x, err := image.Resize("x200")
104 c.Assert(err, qt.IsNil)
105 assertWidthHeight(resized0x, 320, 200)
106
107 resizedx0, err := image.Resize("200x")
108 c.Assert(err, qt.IsNil)
109 assertWidthHeight(resizedx0, 200, 125)
110
111 resizedAndRotated, err := image.Resize("x200 r90")
112 c.Assert(err, qt.IsNil)
113 assertWidthHeight(resizedAndRotated, 125, 200)
114
115 assertWidthHeight(resized, 300, 200)
116 c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu_d2115125d9324a79.jpg")
117
118 fitted, err := resized.Fit("50x50")
119 c.Assert(err, qt.IsNil)
120 c.Assert(fitted.RelPermalink(), qt.Equals, "/a/sunset_hu_c2c98e06123b048e.jpg")
121 assertWidthHeight(fitted, 50, 33)
122
123 // Check the MD5 key threshold
124 fittedAgain, _ := fitted.Fit("10x20")
125 fittedAgain, err = fittedAgain.Fit("10x20")
126 c.Assert(err, qt.IsNil)
127 c.Assert(fittedAgain.RelPermalink(), qt.Equals, "/a/sunset_hu_dc9e89c10109de72.jpg")
128 assertWidthHeight(fittedAgain, 10, 7)
129
130 filled, err := image.Fill("200x100 bottomLeft")
131 c.Assert(err, qt.IsNil)
132 c.Assert(filled.RelPermalink(), qt.Equals, "/a/sunset_hu_b9f6d350738928fe.jpg")
133 assertWidthHeight(filled, 200, 100)
134
135 smart, err := image.Fill("200x100 smart")
136 c.Assert(err, qt.IsNil)
137 c.Assert(smart.RelPermalink(), qt.Equals, "/a/sunset_hu_6fd390e7b0d26f0b.jpg")
138 assertWidthHeight(smart, 200, 100)
139
140 // Check cache
141 filledAgain, err := image.Fill("200x100 bottomLeft")
142 c.Assert(err, qt.IsNil)
143 c.Assert(filled, qt.Equals, filledAgain)
144
145 cropped, err := image.Crop("300x300 topRight")
146 c.Assert(err, qt.IsNil)
147 c.Assert(cropped.RelPermalink(), qt.Equals, "/a/sunset_hu_3df036e11f4ddd43.jpg")
148 assertWidthHeight(cropped, 300, 300)
149
150 smartcropped, err := image.Crop("200x200 smart")
151 c.Assert(err, qt.IsNil)
152 c.Assert(smartcropped.RelPermalink(), qt.Equals, "/a/sunset_hu_12e2d26de89b464b.jpg")
153 assertWidthHeight(smartcropped, 200, 200)
154
155 // Check cache
156 croppedAgain, err := image.Crop("300x300 topRight")
157 c.Assert(err, qt.IsNil)
158 c.Assert(cropped, qt.Equals, croppedAgain)
159 }
160
161 func TestImageProcess(t *testing.T) {
162 c := qt.New(t)
163 _, img := fetchSunset(c)
164 resized, err := img.Process("resiZe 300x200")
165 c.Assert(err, qt.IsNil)
166 assertWidthHeight(c, resized, 300, 200)
167 rotated, err := resized.Process("R90")
168 c.Assert(err, qt.IsNil)
169 assertWidthHeight(c, rotated, 200, 300)
170 converted, err := img.Process("png")
171 c.Assert(err, qt.IsNil)
172 c.Assert(converted.MediaType().Type, qt.Equals, "image/png")
173
174 checkProcessVsMethod := func(action, spec string) {
175 var expect images.ImageResource
176 var err error
177 switch action {
178 case images.ActionCrop:
179 expect, err = img.Crop(spec)
180 case images.ActionFill:
181 expect, err = img.Fill(spec)
182 case images.ActionFit:
183 expect, err = img.Fit(spec)
184 case images.ActionResize:
185 expect, err = img.Resize(spec)
186 }
187 c.Assert(err, qt.IsNil)
188 got, err := img.Process(spec + " " + action)
189 c.Assert(err, qt.IsNil)
190 assertWidthHeight(c, got, expect.Width(), expect.Height())
191 c.Assert(got.MediaType(), qt.Equals, expect.MediaType())
192 }
193
194 checkProcessVsMethod(images.ActionCrop, "300x200 topleFt")
195 checkProcessVsMethod(images.ActionFill, "300x200 topleft")
196 checkProcessVsMethod(images.ActionFit, "300x200 png")
197 checkProcessVsMethod(images.ActionResize, "300x R90")
198 }
199
200 func TestImageTransformFormat(t *testing.T) {
201 c := qt.New(t)
202
203 _, image := fetchSunset(c)
204
205 assertExtWidthHeight := func(img images.ImageResource, ext string, w, h int) {
206 c.Helper()
207 c.Assert(img, qt.Not(qt.IsNil))
208 c.Assert(paths.Ext(img.RelPermalink()), qt.Equals, ext)
209 c.Assert(img.Width(), qt.Equals, w)
210 c.Assert(img.Height(), qt.Equals, h)
211 }
212
213 c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.jpg")
214 c.Assert(image.ResourceType(), qt.Equals, "image")
215 assertExtWidthHeight(image, ".jpg", 900, 562)
216
217 imagePng, err := image.Resize("450x png")
218 c.Assert(err, qt.IsNil)
219 c.Assert(imagePng.RelPermalink(), qt.Equals, "/a/sunset_hu_e8b9444dcf2e75ef.png")
220 c.Assert(imagePng.ResourceType(), qt.Equals, "image")
221 assertExtWidthHeight(imagePng, ".png", 450, 281)
222 c.Assert(imagePng.Name(), qt.Equals, "sunset.jpg")
223 c.Assert(imagePng.MediaType().String(), qt.Equals, "image/png")
224
225 imageGif, err := image.Resize("225x gif")
226 c.Assert(err, qt.IsNil)
227 c.Assert(imageGif.RelPermalink(), qt.Equals, "/a/sunset_hu_f80842d4c3789345.gif")
228 c.Assert(imageGif.ResourceType(), qt.Equals, "image")
229 assertExtWidthHeight(imageGif, ".gif", 225, 141)
230 c.Assert(imageGif.Name(), qt.Equals, "sunset.jpg")
231 c.Assert(imageGif.MediaType().String(), qt.Equals, "image/gif")
232 }
233
234 // https://github.com/gohugoio/hugo/issues/5730
235 func TestImagePermalinkPublishOrder(t *testing.T) {
236 for _, checkOriginalFirst := range []bool{true, false} {
237 name := "OriginalFirst"
238 if !checkOriginalFirst {
239 name = "ResizedFirst"
240 }
241
242 t.Run(name, func(t *testing.T) {
243 c := qt.New(t)
244 spec, workDir := newTestResourceOsFs(c)
245 defer func() {
246 os.Remove(workDir)
247 }()
248
249 check1 := func(img images.ImageResource) {
250 resizedLink := "/a/sunset_hu_3910bca82e28c9d6.jpg"
251 c.Assert(img.RelPermalink(), qt.Equals, resizedLink)
252 assertImageFile(c, spec.PublishFs, resizedLink, 100, 50)
253 }
254
255 check2 := func(img images.ImageResource) {
256 c.Assert(img.RelPermalink(), qt.Equals, "/a/sunset.jpg")
257 assertImageFile(c, spec.PublishFs, "a/sunset.jpg", 900, 562)
258 }
259
260 original := fetchImageForSpec(spec, c, "sunset.jpg")
261 c.Assert(original, qt.Not(qt.IsNil))
262
263 if checkOriginalFirst {
264 check2(original)
265 }
266
267 resized, err := original.Resize("100x50")
268 c.Assert(err, qt.IsNil)
269
270 check1(resized)
271
272 if !checkOriginalFirst {
273 check2(original)
274 }
275 })
276 }
277 }
278
279 func TestImageBugs(t *testing.T) {
280 c := qt.New(t)
281
282 // Issue #4261
283 c.Run("Transform long filename", func(c *qt.C) {
284 _, image := fetchImage(c, "1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph.jpg")
285 c.Assert(image, qt.Not(qt.IsNil))
286
287 resized, err := image.Resize("200x")
288 c.Assert(err, qt.IsNil)
289 c.Assert(resized, qt.Not(qt.IsNil))
290 c.Assert(resized.Width(), qt.Equals, 200)
291 c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu_951d3980b18c52a9.jpg")
292 resized, err = resized.Resize("100x")
293 c.Assert(err, qt.IsNil)
294 c.Assert(resized, qt.Not(qt.IsNil))
295 c.Assert(resized.Width(), qt.Equals, 100)
296 c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu_1daa203572ecd6ec.jpg")
297 })
298
299 // Issue #6137
300 c.Run("Transform upper case extension", func(c *qt.C) {
301 _, image := fetchImage(c, "sunrise.JPG")
302
303 resized, err := image.Resize("200x")
304 c.Assert(err, qt.IsNil)
305 c.Assert(resized, qt.Not(qt.IsNil))
306 c.Assert(resized.Width(), qt.Equals, 200)
307 })
308
309 // Issue #7955
310 c.Run("Fill with smartcrop", func(c *qt.C) {
311 _, sunset := fetchImage(c, "sunset.jpg")
312
313 for _, test := range []struct {
314 originalDimensions string
315 targetWH int
316 }{
317 {"408x403", 400},
318 {"425x403", 400},
319 {"459x429", 400},
320 {"476x442", 400},
321 {"544x403", 400},
322 {"476x468", 400},
323 {"578x585", 550},
324 {"578x598", 550},
325 } {
326 c.Run(test.originalDimensions, func(c *qt.C) {
327 image, err := sunset.Resize(test.originalDimensions)
328 c.Assert(err, qt.IsNil)
329 resized, err := image.Fill(fmt.Sprintf("%dx%d smart", test.targetWH, test.targetWH))
330 c.Assert(err, qt.IsNil)
331 c.Assert(resized, qt.Not(qt.IsNil))
332 c.Assert(resized.Width(), qt.Equals, test.targetWH)
333 c.Assert(resized.Height(), qt.Equals, test.targetWH)
334 })
335 }
336 })
337 }
338
339 func TestImageTransformConcurrent(t *testing.T) {
340 var wg sync.WaitGroup
341
342 c := qt.New(t)
343
344 spec, workDir := newTestResourceOsFs(c)
345 defer func() {
346 os.Remove(workDir)
347 }()
348
349 image := fetchImageForSpec(spec, c, "sunset.jpg")
350
351 for i := range 4 {
352 wg.Add(1)
353 go func(id int) {
354 defer wg.Done()
355 for j := range 5 {
356 img := image
357 for k := range 2 {
358 r1, err := img.Resize(fmt.Sprintf("%dx", id-k))
359 if err != nil {
360 t.Error(err)
361 }
362
363 if r1.Width() != id-k {
364 t.Errorf("Width: %d:%d", r1.Width(), j)
365 }
366
367 r2, err := r1.Resize(fmt.Sprintf("%dx", id-k-1))
368 if err != nil {
369 t.Error(err)
370 }
371
372 img = r2
373 }
374 }
375 }(i + 20)
376 }
377
378 wg.Wait()
379 }
380
381 func TestImageResize8BitPNG(t *testing.T) {
382 c := qt.New(t)
383
384 _, image := fetchImage(c, "gohugoio.png")
385
386 c.Assert(image.MediaType().Type, qt.Equals, "image/png")
387 c.Assert(image.RelPermalink(), qt.Equals, "/a/gohugoio.png")
388 c.Assert(image.ResourceType(), qt.Equals, "image")
389 c.Assert(image.Exif(), qt.IsNotNil)
390
391 resized, err := image.Resize("800x")
392 c.Assert(err, qt.IsNil)
393 c.Assert(resized.MediaType().Type, qt.Equals, "image/png")
394 c.Assert(resized.RelPermalink(), qt.Equals, "/a/gohugoio_hu_fe2b762e9cac406c.png")
395 c.Assert(resized.Width(), qt.Equals, 800)
396 }
397
398 func TestSVGImage(t *testing.T) {
399 c := qt.New(t)
400 spec := newTestResourceSpec(specDescriptor{c: c})
401 svg := fetchResourceForSpec(spec, c, "circle.svg")
402 c.Assert(svg, qt.Not(qt.IsNil))
403 }
404
405 func TestSVGImageContent(t *testing.T) {
406 c := qt.New(t)
407 spec := newTestResourceSpec(specDescriptor{c: c})
408 svg := fetchResourceForSpec(spec, c, "circle.svg")
409 c.Assert(svg, qt.Not(qt.IsNil))
410
411 content, err := svg.Content(context.Background())
412 c.Assert(err, qt.IsNil)
413 c.Assert(content, hqt.IsSameType, "")
414 c.Assert(content.(string), qt.Contains, `<svg height="100" width="100">`)
415 }
416
417 func TestImageExif(t *testing.T) {
418 c := qt.New(t)
419 fs := afero.NewMemMapFs()
420 spec := newTestResourceSpec(specDescriptor{fs: fs, c: c})
421 image := fetchResourceForSpec(spec, c, "sunset.jpg").(images.ImageResource)
422
423 getAndCheckExif := func(c *qt.C, image images.ImageResource) {
424 x := image.Exif()
425 c.Assert(x, qt.Not(qt.IsNil))
426
427 c.Assert(x.Date.Format("2006-01-02"), qt.Equals, "2017-10-27")
428
429 // Malaga: https://goo.gl/taazZy
430 c.Assert(x.Lat, qt.Equals, float64(36.59744166666667))
431 c.Assert(x.Long, qt.Equals, float64(-4.50846))
432
433 v, found := x.Tags["LensModel"]
434 c.Assert(found, qt.Equals, true)
435 lensModel, ok := v.(string)
436 c.Assert(ok, qt.Equals, true)
437 c.Assert(lensModel, qt.Equals, "smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM")
438 resized, _ := image.Resize("300x200")
439 x2 := resized.Exif()
440
441 c.Assert(x2, eq, x)
442 }
443
444 getAndCheckExif(c, image)
445 image = fetchResourceForSpec(spec, c, "sunset.jpg").(images.ImageResource)
446 // This will read from file cache.
447 getAndCheckExif(c, image)
448 }
449
450 func TestImageColorsLuminance(t *testing.T) {
451 c := qt.New(t)
452
453 _, image := fetchSunset(c)
454 c.Assert(image, qt.Not(qt.IsNil))
455 colors, err := image.Colors()
456 c.Assert(err, qt.IsNil)
457 c.Assert(len(colors), qt.Equals, 6)
458 var prevLuminance float64
459 for i, color := range colors {
460 luminance := color.Luminance()
461 c.Assert(err, qt.IsNil)
462 c.Assert(luminance > 0, qt.IsTrue)
463 c.Assert(luminance, qt.Not(qt.Equals), prevLuminance, qt.Commentf("i=%d", i))
464 prevLuminance = luminance
465 }
466 }
467
468 func BenchmarkImageExif(b *testing.B) {
469 getImages := func(c *qt.C, b *testing.B, fs afero.Fs) []images.ImageResource {
470 spec := newTestResourceSpec(specDescriptor{fs: fs, c: c})
471 imgs := make([]images.ImageResource, b.N)
472 for i := 0; i < b.N; i++ {
473 imgs[i] = fetchResourceForSpec(spec, c, "sunset.jpg", strconv.Itoa(i)).(images.ImageResource)
474 }
475 return imgs
476 }
477
478 getAndCheckExif := func(c *qt.C, image images.ImageResource) {
479 x := image.Exif()
480 c.Assert(x, qt.Not(qt.IsNil))
481 c.Assert(x.Long, qt.Equals, float64(-4.50846))
482 }
483
484 b.Run("Cold cache", func(b *testing.B) {
485 b.StopTimer()
486 c := qt.New(b)
487 images := getImages(c, b, afero.NewMemMapFs())
488
489 b.StartTimer()
490 for i := 0; i < b.N; i++ {
491 getAndCheckExif(c, images[i])
492 }
493 })
494
495 b.Run("Cold cache, 10", func(b *testing.B) {
496 b.StopTimer()
497 c := qt.New(b)
498 images := getImages(c, b, afero.NewMemMapFs())
499
500 b.StartTimer()
501 for i := 0; i < b.N; i++ {
502 for range 10 {
503 getAndCheckExif(c, images[i])
504 }
505 }
506 })
507
508 b.Run("Warm cache", func(b *testing.B) {
509 b.StopTimer()
510 c := qt.New(b)
511 fs := afero.NewMemMapFs()
512 images := getImages(c, b, fs)
513 for i := 0; i < b.N; i++ {
514 getAndCheckExif(c, images[i])
515 }
516
517 images = getImages(c, b, fs)
518
519 b.StartTimer()
520 for i := 0; i < b.N; i++ {
521 getAndCheckExif(c, images[i])
522 }
523 })
524 }
525
526 func BenchmarkResizeParallel(b *testing.B) {
527 c := qt.New(b)
528 _, img := fetchSunset(c)
529
530 b.RunParallel(func(pb *testing.PB) {
531 for pb.Next() {
532 w := rand.Intn(10) + 10
533 resized, err := img.Resize(strconv.Itoa(w) + "x")
534 if err != nil {
535 b.Fatal(err)
536 }
537 _, err = resized.Resize(strconv.Itoa(w-1) + "x")
538 if err != nil {
539 b.Fatal(err)
540 }
541 }
542 })
543 }
544
545 func assertWidthHeight(c *qt.C, img images.ImageResource, w, h int) {
546 c.Helper()
547 c.Assert(img, qt.Not(qt.IsNil))
548 c.Assert(img.Width(), qt.Equals, w)
549 c.Assert(img.Height(), qt.Equals, h)
550 }