github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/resources/image_test.go (about) 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 15 16 import ( 17 "fmt" 18 "image" 19 "io/ioutil" 20 "math/big" 21 "math/rand" 22 "os" 23 "path" 24 "path/filepath" 25 "runtime" 26 "strconv" 27 "sync" 28 "testing" 29 "time" 30 31 "github.com/gohugoio/hugo/resources/images/webp" 32 33 "github.com/gohugoio/hugo/common/paths" 34 35 "github.com/spf13/afero" 36 37 "github.com/disintegration/gift" 38 39 "github.com/gohugoio/hugo/helpers" 40 41 "github.com/gohugoio/hugo/media" 42 "github.com/gohugoio/hugo/resources/images" 43 "github.com/gohugoio/hugo/resources/resource" 44 "github.com/google/go-cmp/cmp" 45 46 "github.com/gohugoio/hugo/htesting/hqt" 47 48 qt "github.com/frankban/quicktest" 49 ) 50 51 var eq = qt.CmpEquals( 52 cmp.Comparer(func(p1, p2 *resourceAdapter) bool { 53 return p1.resourceAdapterInner == p2.resourceAdapterInner 54 }), 55 cmp.Comparer(func(p1, p2 os.FileInfo) bool { 56 return p1.Name() == p2.Name() && p1.Size() == p2.Size() && p1.IsDir() == p2.IsDir() 57 }), 58 cmp.Comparer(func(p1, p2 *genericResource) bool { return p1 == p2 }), 59 cmp.Comparer(func(m1, m2 media.Type) bool { 60 return m1.Type() == m2.Type() 61 }), 62 cmp.Comparer( 63 func(v1, v2 *big.Rat) bool { 64 return v1.RatString() == v2.RatString() 65 }, 66 ), 67 cmp.Comparer(func(v1, v2 time.Time) bool { 68 return v1.Unix() == v2.Unix() 69 }), 70 ) 71 72 func TestImageTransformBasic(t *testing.T) { 73 c := qt.New(t) 74 75 image := fetchSunset(c) 76 77 fileCache := image.(specProvider).getSpec().FileCaches.ImageCache().Fs 78 79 assertWidthHeight := func(img resource.Image, w, h int) { 80 c.Helper() 81 c.Assert(img, qt.Not(qt.IsNil)) 82 c.Assert(img.Width(), qt.Equals, w) 83 c.Assert(img.Height(), qt.Equals, h) 84 } 85 86 c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.jpg") 87 c.Assert(image.ResourceType(), qt.Equals, "image") 88 assertWidthHeight(image, 900, 562) 89 90 resized, err := image.Resize("300x200") 91 c.Assert(err, qt.IsNil) 92 c.Assert(image != resized, qt.Equals, true) 93 c.Assert(image, qt.Not(eq), resized) 94 assertWidthHeight(resized, 300, 200) 95 assertWidthHeight(image, 900, 562) 96 97 resized0x, err := image.Resize("x200") 98 c.Assert(err, qt.IsNil) 99 assertWidthHeight(resized0x, 320, 200) 100 assertFileCache(c, fileCache, path.Base(resized0x.RelPermalink()), 320, 200) 101 102 resizedx0, err := image.Resize("200x") 103 c.Assert(err, qt.IsNil) 104 assertWidthHeight(resizedx0, 200, 125) 105 assertFileCache(c, fileCache, path.Base(resizedx0.RelPermalink()), 200, 125) 106 107 resizedAndRotated, err := image.Resize("x200 r90") 108 c.Assert(err, qt.IsNil) 109 assertWidthHeight(resizedAndRotated, 125, 200) 110 111 assertWidthHeight(resized, 300, 200) 112 c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_resize_q68_linear.jpg") 113 114 fitted, err := resized.Fit("50x50") 115 c.Assert(err, qt.IsNil) 116 c.Assert(fitted.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_625708021e2bb281c9f1002f88e4753f.jpg") 117 assertWidthHeight(fitted, 50, 33) 118 119 // Check the MD5 key threshold 120 fittedAgain, _ := fitted.Fit("10x20") 121 fittedAgain, err = fittedAgain.Fit("10x20") 122 c.Assert(err, qt.IsNil) 123 c.Assert(fittedAgain.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f65ba24dc2b7fba0f56d7f104519157.jpg") 124 assertWidthHeight(fittedAgain, 10, 7) 125 126 filled, err := image.Fill("200x100 bottomLeft") 127 c.Assert(err, qt.IsNil) 128 c.Assert(filled.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_fill_q68_linear_bottomleft.jpg") 129 assertWidthHeight(filled, 200, 100) 130 131 smart, err := image.Fill("200x100 smart") 132 c.Assert(err, qt.IsNil) 133 c.Assert(smart.RelPermalink(), qt.Equals, fmt.Sprintf("/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_fill_q68_linear_smart%d.jpg", 1)) 134 assertWidthHeight(smart, 200, 100) 135 136 // Check cache 137 filledAgain, err := image.Fill("200x100 bottomLeft") 138 c.Assert(err, qt.IsNil) 139 c.Assert(filled, eq, filledAgain) 140 } 141 142 func TestImageTransformFormat(t *testing.T) { 143 c := qt.New(t) 144 145 image := fetchSunset(c) 146 147 fileCache := image.(specProvider).getSpec().FileCaches.ImageCache().Fs 148 149 assertExtWidthHeight := func(img resource.Image, ext string, w, h int) { 150 c.Helper() 151 c.Assert(img, qt.Not(qt.IsNil)) 152 c.Assert(paths.Ext(img.RelPermalink()), qt.Equals, ext) 153 c.Assert(img.Width(), qt.Equals, w) 154 c.Assert(img.Height(), qt.Equals, h) 155 } 156 157 c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.jpg") 158 c.Assert(image.ResourceType(), qt.Equals, "image") 159 assertExtWidthHeight(image, ".jpg", 900, 562) 160 161 imagePng, err := image.Resize("450x png") 162 c.Assert(err, qt.IsNil) 163 c.Assert(imagePng.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_450x0_resize_linear.png") 164 c.Assert(imagePng.ResourceType(), qt.Equals, "image") 165 assertExtWidthHeight(imagePng, ".png", 450, 281) 166 c.Assert(imagePng.Name(), qt.Equals, "sunset.jpg") 167 c.Assert(imagePng.MediaType().String(), qt.Equals, "image/png") 168 169 assertFileCache(c, fileCache, path.Base(imagePng.RelPermalink()), 450, 281) 170 171 imageGif, err := image.Resize("225x gif") 172 c.Assert(err, qt.IsNil) 173 c.Assert(imageGif.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_225x0_resize_linear.gif") 174 c.Assert(imageGif.ResourceType(), qt.Equals, "image") 175 assertExtWidthHeight(imageGif, ".gif", 225, 141) 176 c.Assert(imageGif.Name(), qt.Equals, "sunset.jpg") 177 c.Assert(imageGif.MediaType().String(), qt.Equals, "image/gif") 178 179 assertFileCache(c, fileCache, path.Base(imageGif.RelPermalink()), 225, 141) 180 } 181 182 // https://github.com/gohugoio/hugo/issues/5730 183 func TestImagePermalinkPublishOrder(t *testing.T) { 184 for _, checkOriginalFirst := range []bool{true, false} { 185 name := "OriginalFirst" 186 if !checkOriginalFirst { 187 name = "ResizedFirst" 188 } 189 190 t.Run(name, func(t *testing.T) { 191 c := qt.New(t) 192 spec, workDir := newTestResourceOsFs(c) 193 defer func() { 194 os.Remove(workDir) 195 }() 196 197 check1 := func(img resource.Image) { 198 resizedLink := "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x50_resize_q75_box.jpg" 199 c.Assert(img.RelPermalink(), qt.Equals, resizedLink) 200 assertImageFile(c, spec.PublishFs, resizedLink, 100, 50) 201 } 202 203 check2 := func(img resource.Image) { 204 c.Assert(img.RelPermalink(), qt.Equals, "/a/sunset.jpg") 205 assertImageFile(c, spec.PublishFs, "a/sunset.jpg", 900, 562) 206 } 207 208 orignal := fetchImageForSpec(spec, c, "sunset.jpg") 209 c.Assert(orignal, qt.Not(qt.IsNil)) 210 211 if checkOriginalFirst { 212 check2(orignal) 213 } 214 215 resized, err := orignal.Resize("100x50") 216 c.Assert(err, qt.IsNil) 217 218 check1(resized.(resource.Image)) 219 220 if !checkOriginalFirst { 221 check2(orignal) 222 } 223 }) 224 } 225 } 226 227 func TestImageBugs(t *testing.T) { 228 c := qt.New(t) 229 230 // Issue #4261 231 c.Run("Transform long filename", func(c *qt.C) { 232 image := fetchImage(c, "1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph.jpg") 233 c.Assert(image, qt.Not(qt.IsNil)) 234 235 resized, err := image.Resize("200x") 236 c.Assert(err, qt.IsNil) 237 c.Assert(resized, qt.Not(qt.IsNil)) 238 c.Assert(resized.Width(), qt.Equals, 200) 239 c.Assert(resized.RelPermalink(), qt.Equals, "/a/_hu59e56ffff1bc1d8d122b1403d34e039f_90587_65b757a6e14debeae720fe8831f0a9bc.jpg") 240 resized, err = resized.Resize("100x") 241 c.Assert(err, qt.IsNil) 242 c.Assert(resized, qt.Not(qt.IsNil)) 243 c.Assert(resized.Width(), qt.Equals, 100) 244 c.Assert(resized.RelPermalink(), qt.Equals, "/a/_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c876768085288f41211f768147ba2647.jpg") 245 246 }) 247 248 // Issue #6137 249 c.Run("Transform upper case extension", func(c *qt.C) { 250 image := fetchImage(c, "sunrise.JPG") 251 252 resized, err := image.Resize("200x") 253 c.Assert(err, qt.IsNil) 254 c.Assert(resized, qt.Not(qt.IsNil)) 255 c.Assert(resized.Width(), qt.Equals, 200) 256 257 }) 258 259 // Issue #7955 260 c.Run("Fill with smartcrop", func(c *qt.C) { 261 sunset := fetchImage(c, "sunset.jpg") 262 263 for _, test := range []struct { 264 originalDimensions string 265 targetWH int 266 }{ 267 {"408x403", 400}, 268 {"425x403", 400}, 269 {"459x429", 400}, 270 {"476x442", 400}, 271 {"544x403", 400}, 272 {"476x468", 400}, 273 {"578x585", 550}, 274 {"578x598", 550}, 275 } { 276 c.Run(test.originalDimensions, func(c *qt.C) { 277 image, err := sunset.Resize(test.originalDimensions) 278 c.Assert(err, qt.IsNil) 279 resized, err := image.Fill(fmt.Sprintf("%dx%d smart", test.targetWH, test.targetWH)) 280 c.Assert(err, qt.IsNil) 281 c.Assert(resized, qt.Not(qt.IsNil)) 282 c.Assert(resized.Width(), qt.Equals, test.targetWH) 283 c.Assert(resized.Height(), qt.Equals, test.targetWH) 284 }) 285 286 } 287 288 }) 289 } 290 291 func TestImageTransformConcurrent(t *testing.T) { 292 var wg sync.WaitGroup 293 294 c := qt.New(t) 295 296 spec, workDir := newTestResourceOsFs(c) 297 defer func() { 298 os.Remove(workDir) 299 }() 300 301 image := fetchImageForSpec(spec, c, "sunset.jpg") 302 303 for i := 0; i < 4; i++ { 304 wg.Add(1) 305 go func(id int) { 306 defer wg.Done() 307 for j := 0; j < 5; j++ { 308 img := image 309 for k := 0; k < 2; k++ { 310 r1, err := img.Resize(fmt.Sprintf("%dx", id-k)) 311 if err != nil { 312 t.Error(err) 313 } 314 315 if r1.Width() != id-k { 316 t.Errorf("Width: %d:%d", r1.Width(), j) 317 } 318 319 r2, err := r1.Resize(fmt.Sprintf("%dx", id-k-1)) 320 if err != nil { 321 t.Error(err) 322 } 323 324 img = r2 325 } 326 } 327 }(i + 20) 328 } 329 330 wg.Wait() 331 } 332 333 func TestImageWithMetadata(t *testing.T) { 334 c := qt.New(t) 335 336 image := fetchSunset(c) 337 338 meta := []map[string]interface{}{ 339 { 340 "title": "My Sunset", 341 "name": "Sunset #:counter", 342 "src": "*.jpg", 343 }, 344 } 345 346 c.Assert(AssignMetadata(meta, image), qt.IsNil) 347 c.Assert(image.Name(), qt.Equals, "Sunset #1") 348 349 resized, err := image.Resize("200x") 350 c.Assert(err, qt.IsNil) 351 c.Assert(resized.Name(), qt.Equals, "Sunset #1") 352 } 353 354 func TestImageResize8BitPNG(t *testing.T) { 355 c := qt.New(t) 356 357 image := fetchImage(c, "gohugoio.png") 358 359 c.Assert(image.MediaType().Type(), qt.Equals, "image/png") 360 c.Assert(image.RelPermalink(), qt.Equals, "/a/gohugoio.png") 361 c.Assert(image.ResourceType(), qt.Equals, "image") 362 c.Assert(image.Exif(), qt.IsNil) 363 364 resized, err := image.Resize("800x") 365 c.Assert(err, qt.IsNil) 366 c.Assert(resized.MediaType().Type(), qt.Equals, "image/png") 367 c.Assert(resized.RelPermalink(), qt.Equals, "/a/gohugoio_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_800x0_resize_linear_3.png") 368 c.Assert(resized.Width(), qt.Equals, 800) 369 } 370 371 func TestImageResizeInSubPath(t *testing.T) { 372 c := qt.New(t) 373 374 image := fetchImage(c, "sub/gohugoio2.png") 375 376 c.Assert(image.MediaType(), eq, media.PNGType) 377 c.Assert(image.RelPermalink(), qt.Equals, "/a/sub/gohugoio2.png") 378 c.Assert(image.ResourceType(), qt.Equals, "image") 379 c.Assert(image.Exif(), qt.IsNil) 380 381 resized, err := image.Resize("101x101") 382 c.Assert(err, qt.IsNil) 383 c.Assert(resized.MediaType().Type(), qt.Equals, "image/png") 384 c.Assert(resized.RelPermalink(), qt.Equals, "/a/sub/gohugoio2_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_101x101_resize_linear_3.png") 385 c.Assert(resized.Width(), qt.Equals, 101) 386 c.Assert(resized.Exif(), qt.IsNil) 387 388 publishedImageFilename := filepath.Clean(resized.RelPermalink()) 389 390 spec := image.(specProvider).getSpec() 391 392 assertImageFile(c, spec.BaseFs.PublishFs, publishedImageFilename, 101, 101) 393 c.Assert(spec.BaseFs.PublishFs.Remove(publishedImageFilename), qt.IsNil) 394 395 // Clear mem cache to simulate reading from the file cache. 396 spec.imageCache.clear() 397 398 resizedAgain, err := image.Resize("101x101") 399 c.Assert(err, qt.IsNil) 400 c.Assert(resizedAgain.RelPermalink(), qt.Equals, "/a/sub/gohugoio2_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_101x101_resize_linear_3.png") 401 c.Assert(resizedAgain.Width(), qt.Equals, 101) 402 assertImageFile(c, image.(specProvider).getSpec().BaseFs.PublishFs, publishedImageFilename, 101, 101) 403 } 404 405 func TestSVGImage(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 412 func TestSVGImageContent(t *testing.T) { 413 c := qt.New(t) 414 spec := newTestResourceSpec(specDescriptor{c: c}) 415 svg := fetchResourceForSpec(spec, c, "circle.svg") 416 c.Assert(svg, qt.Not(qt.IsNil)) 417 418 content, err := svg.Content() 419 c.Assert(err, qt.IsNil) 420 c.Assert(content, hqt.IsSameType, "") 421 c.Assert(content.(string), qt.Contains, `<svg height="100" width="100">`) 422 } 423 424 func TestImageExif(t *testing.T) { 425 c := qt.New(t) 426 fs := afero.NewMemMapFs() 427 spec := newTestResourceSpec(specDescriptor{fs: fs, c: c}) 428 image := fetchResourceForSpec(spec, c, "sunset.jpg").(resource.Image) 429 430 getAndCheckExif := func(c *qt.C, image resource.Image) { 431 x := image.Exif() 432 c.Assert(x, qt.Not(qt.IsNil)) 433 434 c.Assert(x.Date.Format("2006-01-02"), qt.Equals, "2017-10-27") 435 436 // Malaga: https://goo.gl/taazZy 437 c.Assert(x.Lat, qt.Equals, float64(36.59744166666667)) 438 c.Assert(x.Long, qt.Equals, float64(-4.50846)) 439 440 v, found := x.Tags["LensModel"] 441 c.Assert(found, qt.Equals, true) 442 lensModel, ok := v.(string) 443 c.Assert(ok, qt.Equals, true) 444 c.Assert(lensModel, qt.Equals, "smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM") 445 resized, _ := image.Resize("300x200") 446 x2 := resized.Exif() 447 c.Assert(x2, eq, x) 448 } 449 450 getAndCheckExif(c, image) 451 image = fetchResourceForSpec(spec, c, "sunset.jpg").(resource.Image) 452 // This will read from file cache. 453 getAndCheckExif(c, image) 454 } 455 456 func BenchmarkImageExif(b *testing.B) { 457 getImages := func(c *qt.C, b *testing.B, fs afero.Fs) []resource.Image { 458 spec := newTestResourceSpec(specDescriptor{fs: fs, c: c}) 459 images := make([]resource.Image, b.N) 460 for i := 0; i < b.N; i++ { 461 images[i] = fetchResourceForSpec(spec, c, "sunset.jpg", strconv.Itoa(i)).(resource.Image) 462 } 463 return images 464 } 465 466 getAndCheckExif := func(c *qt.C, image resource.Image) { 467 x := image.Exif() 468 c.Assert(x, qt.Not(qt.IsNil)) 469 c.Assert(x.Long, qt.Equals, float64(-4.50846)) 470 } 471 472 b.Run("Cold cache", func(b *testing.B) { 473 b.StopTimer() 474 c := qt.New(b) 475 images := getImages(c, b, afero.NewMemMapFs()) 476 477 b.StartTimer() 478 for i := 0; i < b.N; i++ { 479 getAndCheckExif(c, images[i]) 480 } 481 }) 482 483 b.Run("Cold cache, 10", func(b *testing.B) { 484 b.StopTimer() 485 c := qt.New(b) 486 images := getImages(c, b, afero.NewMemMapFs()) 487 488 b.StartTimer() 489 for i := 0; i < b.N; i++ { 490 for j := 0; j < 10; j++ { 491 getAndCheckExif(c, images[i]) 492 } 493 } 494 }) 495 496 b.Run("Warm cache", func(b *testing.B) { 497 b.StopTimer() 498 c := qt.New(b) 499 fs := afero.NewMemMapFs() 500 images := getImages(c, b, fs) 501 for i := 0; i < b.N; i++ { 502 getAndCheckExif(c, images[i]) 503 } 504 505 images = getImages(c, b, fs) 506 507 b.StartTimer() 508 for i := 0; i < b.N; i++ { 509 getAndCheckExif(c, images[i]) 510 } 511 }) 512 } 513 514 // usesFMA indicates whether "fused multiply and add" (FMA) instruction is 515 // used. The command "grep FMADD go/test/codegen/floats.go" can help keep 516 // the FMA-using architecture list updated. 517 var usesFMA = runtime.GOARCH == "s390x" || 518 runtime.GOARCH == "ppc64" || 519 runtime.GOARCH == "ppc64le" || 520 runtime.GOARCH == "arm64" 521 522 // goldenEqual compares two NRGBA images. It is used in golden tests only. 523 // A small tolerance is allowed on architectures using "fused multiply and add" 524 // (FMA) instruction to accommodate for floating-point rounding differences 525 // with control golden images that were generated on amd64 architecture. 526 // See https://golang.org/ref/spec#Floating_point_operators 527 // and https://github.com/gohugoio/hugo/issues/6387 for more information. 528 // 529 // Borrowed from https://github.com/disintegration/gift/blob/a999ff8d5226e5ab14b64a94fca07c4ac3f357cf/gift_test.go#L598-L625 530 // Copyright (c) 2014-2019 Grigory Dryapak 531 // Licensed under the MIT License. 532 func goldenEqual(img1, img2 *image.NRGBA) bool { 533 maxDiff := 0 534 if usesFMA { 535 maxDiff = 1 536 } 537 if !img1.Rect.Eq(img2.Rect) { 538 return false 539 } 540 if len(img1.Pix) != len(img2.Pix) { 541 return false 542 } 543 for i := 0; i < len(img1.Pix); i++ { 544 diff := int(img1.Pix[i]) - int(img2.Pix[i]) 545 if diff < 0 { 546 diff = -diff 547 } 548 if diff > maxDiff { 549 return false 550 } 551 } 552 return true 553 } 554 555 // Issue #8729 556 func TestImageOperationsGoldenWebp(t *testing.T) { 557 if !webp.Supports() { 558 t.Skip("skip webp test") 559 } 560 c := qt.New(t) 561 c.Parallel() 562 563 devMode := false 564 565 testImages := []string{"fuzzy-cirlcle.png"} 566 567 spec, workDir := newTestResourceOsFs(c) 568 defer func() { 569 if !devMode { 570 os.Remove(workDir) 571 } 572 }() 573 574 if devMode { 575 fmt.Println(workDir) 576 } 577 578 for _, imageName := range testImages { 579 image := fetchImageForSpec(spec, c, imageName) 580 imageWebp, err := image.Resize("200x webp") 581 c.Assert(err, qt.IsNil) 582 c.Assert(imageWebp.Width(), qt.Equals, 200) 583 } 584 585 if devMode { 586 return 587 } 588 589 dir1 := filepath.Join(workDir, "resources/_gen/images") 590 dir2 := filepath.FromSlash("testdata/golden_webp") 591 592 assetGoldenDirs(c, dir1, dir2) 593 594 } 595 596 func TestImageOperationsGolden(t *testing.T) { 597 c := qt.New(t) 598 c.Parallel() 599 600 devMode := false 601 602 testImages := []string{"sunset.jpg", "gohugoio8.png", "gohugoio24.png"} 603 604 spec, workDir := newTestResourceOsFs(c) 605 defer func() { 606 if !devMode { 607 os.Remove(workDir) 608 } 609 }() 610 611 if devMode { 612 fmt.Println(workDir) 613 } 614 615 gopher := fetchImageForSpec(spec, c, "gopher-hero8.png") 616 var err error 617 gopher, err = gopher.Resize("30x") 618 c.Assert(err, qt.IsNil) 619 620 // Test PNGs with alpha channel. 621 for _, img := range []string{"gopher-hero8.png", "gradient-circle.png"} { 622 orig := fetchImageForSpec(spec, c, img) 623 for _, resizeSpec := range []string{"200x #e3e615", "200x jpg #e3e615"} { 624 resized, err := orig.Resize(resizeSpec) 625 c.Assert(err, qt.IsNil) 626 rel := resized.RelPermalink() 627 c.Log("resize", rel) 628 c.Assert(rel, qt.Not(qt.Equals), "") 629 } 630 } 631 632 for _, img := range testImages { 633 634 orig := fetchImageForSpec(spec, c, img) 635 for _, resizeSpec := range []string{"200x100", "600x", "200x r90 q50 Box"} { 636 resized, err := orig.Resize(resizeSpec) 637 c.Assert(err, qt.IsNil) 638 rel := resized.RelPermalink() 639 c.Log("resize", rel) 640 c.Assert(rel, qt.Not(qt.Equals), "") 641 } 642 643 for _, fillSpec := range []string{"300x200 Gaussian Smart", "100x100 Center", "300x100 TopLeft NearestNeighbor", "400x200 BottomLeft"} { 644 resized, err := orig.Fill(fillSpec) 645 c.Assert(err, qt.IsNil) 646 rel := resized.RelPermalink() 647 c.Log("fill", rel) 648 c.Assert(rel, qt.Not(qt.Equals), "") 649 } 650 651 for _, fitSpec := range []string{"300x200 Linear"} { 652 resized, err := orig.Fit(fitSpec) 653 c.Assert(err, qt.IsNil) 654 rel := resized.RelPermalink() 655 c.Log("fit", rel) 656 c.Assert(rel, qt.Not(qt.Equals), "") 657 } 658 659 f := &images.Filters{} 660 661 filters := []gift.Filter{ 662 f.Grayscale(), 663 f.GaussianBlur(6), 664 f.Saturation(50), 665 f.Sepia(100), 666 f.Brightness(30), 667 f.ColorBalance(10, -10, -10), 668 f.Colorize(240, 50, 100), 669 f.Gamma(1.5), 670 f.UnsharpMask(1, 1, 0), 671 f.Sigmoid(0.5, 7), 672 f.Pixelate(5), 673 f.Invert(), 674 f.Hue(22), 675 f.Contrast(32.5), 676 f.Overlay(gopher.(images.ImageSource), 20, 30), 677 } 678 679 resized, err := orig.Fill("400x200 center") 680 c.Assert(err, qt.IsNil) 681 682 for _, filter := range filters { 683 resized, err := resized.Filter(filter) 684 c.Assert(err, qt.IsNil) 685 rel := resized.RelPermalink() 686 c.Logf("filter: %v %s", filter, rel) 687 c.Assert(rel, qt.Not(qt.Equals), "") 688 } 689 690 resized, err = resized.Filter(filters[0:4]) 691 c.Assert(err, qt.IsNil) 692 rel := resized.RelPermalink() 693 c.Log("filter all", rel) 694 c.Assert(rel, qt.Not(qt.Equals), "") 695 } 696 697 if devMode { 698 return 699 } 700 701 dir1 := filepath.Join(workDir, "resources/_gen/images") 702 dir2 := filepath.FromSlash("testdata/golden") 703 704 assetGoldenDirs(c, dir1, dir2) 705 706 } 707 708 func assetGoldenDirs(c *qt.C, dir1, dir2 string) { 709 710 // The two dirs above should now be the same. 711 dirinfos1, err := ioutil.ReadDir(dir1) 712 c.Assert(err, qt.IsNil) 713 dirinfos2, err := ioutil.ReadDir(dir2) 714 c.Assert(err, qt.IsNil) 715 c.Assert(len(dirinfos1), qt.Equals, len(dirinfos2)) 716 717 for i, fi1 := range dirinfos1 { 718 fi2 := dirinfos2[i] 719 c.Assert(fi1.Name(), qt.Equals, fi2.Name()) 720 721 f1, err := os.Open(filepath.Join(dir1, fi1.Name())) 722 c.Assert(err, qt.IsNil) 723 f2, err := os.Open(filepath.Join(dir2, fi2.Name())) 724 c.Assert(err, qt.IsNil) 725 726 img1, _, err := image.Decode(f1) 727 c.Assert(err, qt.IsNil) 728 img2, _, err := image.Decode(f2) 729 c.Assert(err, qt.IsNil) 730 731 nrgba1 := image.NewNRGBA(img1.Bounds()) 732 gift.New().Draw(nrgba1, img1) 733 nrgba2 := image.NewNRGBA(img2.Bounds()) 734 gift.New().Draw(nrgba2, img2) 735 736 if !goldenEqual(nrgba1, nrgba2) { 737 switch fi1.Name() { 738 case "gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_73c19c5f80881858a85aa23cd0ca400d.png", 739 "gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_ae631e5252bb5d7b92bc766ad1a89069.png", 740 "gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_d1bbfa2629bffb90118cacce3fcfb924.png": 741 c.Log("expectedly differs from golden due to dithering:", fi1.Name()) 742 default: 743 c.Errorf("resulting image differs from golden: %s", fi1.Name()) 744 } 745 } 746 747 if !usesFMA { 748 c.Assert(fi1, eq, fi2) 749 750 _, err = f1.Seek(0, 0) 751 c.Assert(err, qt.IsNil) 752 _, err = f2.Seek(0, 0) 753 c.Assert(err, qt.IsNil) 754 755 hash1, err := helpers.MD5FromReader(f1) 756 c.Assert(err, qt.IsNil) 757 hash2, err := helpers.MD5FromReader(f2) 758 c.Assert(err, qt.IsNil) 759 760 c.Assert(hash1, qt.Equals, hash2) 761 } 762 763 f1.Close() 764 f2.Close() 765 } 766 } 767 768 func BenchmarkResizeParallel(b *testing.B) { 769 c := qt.New(b) 770 img := fetchSunset(c) 771 772 b.RunParallel(func(pb *testing.PB) { 773 for pb.Next() { 774 w := rand.Intn(10) + 10 775 resized, err := img.Resize(strconv.Itoa(w) + "x") 776 if err != nil { 777 b.Fatal(err) 778 } 779 _, err = resized.Resize(strconv.Itoa(w-1) + "x") 780 if err != nil { 781 b.Fatal(err) 782 } 783 } 784 }) 785 }