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 }