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  }