* Always include the content hash in the cache key for unprocessed images. * Always include the image config hash in the cache key. This is also a major cleanup/simplification of the implementation in this area. Note that this, unfortunately, forces new hashes/filenames for generated images. Fixes #13273 Fixes #13272
550 lines
15 KiB
Go
550 lines
15 KiB
Go
// Copyright 2019 The Hugo Authors. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package resources_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/fs"
|
|
"math/rand"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/bep/imagemeta"
|
|
|
|
"github.com/gohugoio/hugo/common/paths"
|
|
|
|
"github.com/spf13/afero"
|
|
|
|
"github.com/gohugoio/hugo/media"
|
|
"github.com/gohugoio/hugo/resources/images"
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"github.com/gohugoio/hugo/htesting/hqt"
|
|
|
|
qt "github.com/frankban/quicktest"
|
|
)
|
|
|
|
var eq = qt.CmpEquals(
|
|
cmp.Comparer(func(p1, p2 os.FileInfo) bool {
|
|
return p1.Name() == p2.Name() && p1.Size() == p2.Size() && p1.IsDir() == p2.IsDir()
|
|
}),
|
|
cmp.Comparer(func(d1, d2 fs.DirEntry) bool {
|
|
p1, err1 := d1.Info()
|
|
p2, err2 := d2.Info()
|
|
if err1 != nil || err2 != nil {
|
|
return false
|
|
}
|
|
return p1.Name() == p2.Name() && p1.Size() == p2.Size() && p1.IsDir() == p2.IsDir()
|
|
}),
|
|
// cmp.Comparer(func(p1, p2 *genericResource) bool { return p1 == p2 }),
|
|
cmp.Comparer(func(m1, m2 media.Type) bool {
|
|
return m1.Type == m2.Type
|
|
}),
|
|
cmp.Comparer(
|
|
func(v1, v2 imagemeta.Rat[uint32]) bool {
|
|
return v1.String() == v2.String()
|
|
},
|
|
),
|
|
cmp.Comparer(
|
|
func(v1, v2 imagemeta.Rat[int32]) bool {
|
|
return v1.String() == v2.String()
|
|
},
|
|
),
|
|
cmp.Comparer(func(v1, v2 time.Time) bool {
|
|
return v1.Unix() == v2.Unix()
|
|
}),
|
|
)
|
|
|
|
func TestImageTransformBasic(t *testing.T) {
|
|
c := qt.New(t)
|
|
|
|
_, image := fetchSunset(c)
|
|
|
|
assertWidthHeight := func(img images.ImageResource, w, h int) {
|
|
assertWidthHeight(c, img, w, h)
|
|
}
|
|
|
|
gotColors, err := image.Colors()
|
|
c.Assert(err, qt.IsNil)
|
|
expectedColors := images.HexStringsToColors("#2d2f33", "#a49e93", "#d39e59", "#a76936", "#737a84", "#7c838b")
|
|
c.Assert(len(gotColors), qt.Equals, len(expectedColors))
|
|
for i := range gotColors {
|
|
c1, c2 := gotColors[i], expectedColors[i]
|
|
c.Assert(c1.ColorHex(), qt.Equals, c2.ColorHex())
|
|
c.Assert(c1.ColorGo(), qt.DeepEquals, c2.ColorGo())
|
|
c.Assert(c1.Luminance(), qt.Equals, c2.Luminance())
|
|
}
|
|
|
|
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.jpg")
|
|
c.Assert(image.ResourceType(), qt.Equals, "image")
|
|
assertWidthHeight(image, 900, 562)
|
|
|
|
resized, err := image.Resize("300x200")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(image != resized, qt.Equals, true)
|
|
assertWidthHeight(resized, 300, 200)
|
|
assertWidthHeight(image, 900, 562)
|
|
|
|
resized0x, err := image.Resize("x200")
|
|
c.Assert(err, qt.IsNil)
|
|
assertWidthHeight(resized0x, 320, 200)
|
|
|
|
resizedx0, err := image.Resize("200x")
|
|
c.Assert(err, qt.IsNil)
|
|
assertWidthHeight(resizedx0, 200, 125)
|
|
|
|
resizedAndRotated, err := image.Resize("x200 r90")
|
|
c.Assert(err, qt.IsNil)
|
|
assertWidthHeight(resizedAndRotated, 125, 200)
|
|
|
|
assertWidthHeight(resized, 300, 200)
|
|
c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu_d2115125d9324a79.jpg")
|
|
|
|
fitted, err := resized.Fit("50x50")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(fitted.RelPermalink(), qt.Equals, "/a/sunset_hu_c2c98e06123b048e.jpg")
|
|
assertWidthHeight(fitted, 50, 33)
|
|
|
|
// Check the MD5 key threshold
|
|
fittedAgain, _ := fitted.Fit("10x20")
|
|
fittedAgain, err = fittedAgain.Fit("10x20")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(fittedAgain.RelPermalink(), qt.Equals, "/a/sunset_hu_dc9e89c10109de72.jpg")
|
|
assertWidthHeight(fittedAgain, 10, 7)
|
|
|
|
filled, err := image.Fill("200x100 bottomLeft")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(filled.RelPermalink(), qt.Equals, "/a/sunset_hu_b9f6d350738928fe.jpg")
|
|
assertWidthHeight(filled, 200, 100)
|
|
|
|
smart, err := image.Fill("200x100 smart")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(smart.RelPermalink(), qt.Equals, "/a/sunset_hu_6fd390e7b0d26f0b.jpg")
|
|
assertWidthHeight(smart, 200, 100)
|
|
|
|
// Check cache
|
|
filledAgain, err := image.Fill("200x100 bottomLeft")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(filled, qt.Equals, filledAgain)
|
|
|
|
cropped, err := image.Crop("300x300 topRight")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(cropped.RelPermalink(), qt.Equals, "/a/sunset_hu_3df036e11f4ddd43.jpg")
|
|
assertWidthHeight(cropped, 300, 300)
|
|
|
|
smartcropped, err := image.Crop("200x200 smart")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(smartcropped.RelPermalink(), qt.Equals, "/a/sunset_hu_12e2d26de89b464b.jpg")
|
|
assertWidthHeight(smartcropped, 200, 200)
|
|
|
|
// Check cache
|
|
croppedAgain, err := image.Crop("300x300 topRight")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(cropped, qt.Equals, croppedAgain)
|
|
}
|
|
|
|
func TestImageProcess(t *testing.T) {
|
|
c := qt.New(t)
|
|
_, img := fetchSunset(c)
|
|
resized, err := img.Process("resiZe 300x200")
|
|
c.Assert(err, qt.IsNil)
|
|
assertWidthHeight(c, resized, 300, 200)
|
|
rotated, err := resized.Process("R90")
|
|
c.Assert(err, qt.IsNil)
|
|
assertWidthHeight(c, rotated, 200, 300)
|
|
converted, err := img.Process("png")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(converted.MediaType().Type, qt.Equals, "image/png")
|
|
|
|
checkProcessVsMethod := func(action, spec string) {
|
|
var expect images.ImageResource
|
|
var err error
|
|
switch action {
|
|
case images.ActionCrop:
|
|
expect, err = img.Crop(spec)
|
|
case images.ActionFill:
|
|
expect, err = img.Fill(spec)
|
|
case images.ActionFit:
|
|
expect, err = img.Fit(spec)
|
|
case images.ActionResize:
|
|
expect, err = img.Resize(spec)
|
|
}
|
|
c.Assert(err, qt.IsNil)
|
|
got, err := img.Process(spec + " " + action)
|
|
c.Assert(err, qt.IsNil)
|
|
assertWidthHeight(c, got, expect.Width(), expect.Height())
|
|
c.Assert(got.MediaType(), qt.Equals, expect.MediaType())
|
|
}
|
|
|
|
checkProcessVsMethod(images.ActionCrop, "300x200 topleFt")
|
|
checkProcessVsMethod(images.ActionFill, "300x200 topleft")
|
|
checkProcessVsMethod(images.ActionFit, "300x200 png")
|
|
checkProcessVsMethod(images.ActionResize, "300x R90")
|
|
}
|
|
|
|
func TestImageTransformFormat(t *testing.T) {
|
|
c := qt.New(t)
|
|
|
|
_, image := fetchSunset(c)
|
|
|
|
assertExtWidthHeight := func(img images.ImageResource, ext string, w, h int) {
|
|
c.Helper()
|
|
c.Assert(img, qt.Not(qt.IsNil))
|
|
c.Assert(paths.Ext(img.RelPermalink()), qt.Equals, ext)
|
|
c.Assert(img.Width(), qt.Equals, w)
|
|
c.Assert(img.Height(), qt.Equals, h)
|
|
}
|
|
|
|
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.jpg")
|
|
c.Assert(image.ResourceType(), qt.Equals, "image")
|
|
assertExtWidthHeight(image, ".jpg", 900, 562)
|
|
|
|
imagePng, err := image.Resize("450x png")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(imagePng.RelPermalink(), qt.Equals, "/a/sunset_hu_e8b9444dcf2e75ef.png")
|
|
c.Assert(imagePng.ResourceType(), qt.Equals, "image")
|
|
assertExtWidthHeight(imagePng, ".png", 450, 281)
|
|
c.Assert(imagePng.Name(), qt.Equals, "sunset.jpg")
|
|
c.Assert(imagePng.MediaType().String(), qt.Equals, "image/png")
|
|
|
|
imageGif, err := image.Resize("225x gif")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(imageGif.RelPermalink(), qt.Equals, "/a/sunset_hu_f80842d4c3789345.gif")
|
|
c.Assert(imageGif.ResourceType(), qt.Equals, "image")
|
|
assertExtWidthHeight(imageGif, ".gif", 225, 141)
|
|
c.Assert(imageGif.Name(), qt.Equals, "sunset.jpg")
|
|
c.Assert(imageGif.MediaType().String(), qt.Equals, "image/gif")
|
|
}
|
|
|
|
// https://github.com/gohugoio/hugo/issues/5730
|
|
func TestImagePermalinkPublishOrder(t *testing.T) {
|
|
for _, checkOriginalFirst := range []bool{true, false} {
|
|
name := "OriginalFirst"
|
|
if !checkOriginalFirst {
|
|
name = "ResizedFirst"
|
|
}
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
c := qt.New(t)
|
|
spec, workDir := newTestResourceOsFs(c)
|
|
defer func() {
|
|
os.Remove(workDir)
|
|
}()
|
|
|
|
check1 := func(img images.ImageResource) {
|
|
resizedLink := "/a/sunset_hu_3910bca82e28c9d6.jpg"
|
|
c.Assert(img.RelPermalink(), qt.Equals, resizedLink)
|
|
assertImageFile(c, spec.PublishFs, resizedLink, 100, 50)
|
|
}
|
|
|
|
check2 := func(img images.ImageResource) {
|
|
c.Assert(img.RelPermalink(), qt.Equals, "/a/sunset.jpg")
|
|
assertImageFile(c, spec.PublishFs, "a/sunset.jpg", 900, 562)
|
|
}
|
|
|
|
original := fetchImageForSpec(spec, c, "sunset.jpg")
|
|
c.Assert(original, qt.Not(qt.IsNil))
|
|
|
|
if checkOriginalFirst {
|
|
check2(original)
|
|
}
|
|
|
|
resized, err := original.Resize("100x50")
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
check1(resized)
|
|
|
|
if !checkOriginalFirst {
|
|
check2(original)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestImageBugs(t *testing.T) {
|
|
c := qt.New(t)
|
|
|
|
// Issue #4261
|
|
c.Run("Transform long filename", func(c *qt.C) {
|
|
_, image := fetchImage(c, "1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph.jpg")
|
|
c.Assert(image, qt.Not(qt.IsNil))
|
|
|
|
resized, err := image.Resize("200x")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(resized, qt.Not(qt.IsNil))
|
|
c.Assert(resized.Width(), qt.Equals, 200)
|
|
c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu_951d3980b18c52a9.jpg")
|
|
resized, err = resized.Resize("100x")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(resized, qt.Not(qt.IsNil))
|
|
c.Assert(resized.Width(), qt.Equals, 100)
|
|
c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu_1daa203572ecd6ec.jpg")
|
|
})
|
|
|
|
// Issue #6137
|
|
c.Run("Transform upper case extension", func(c *qt.C) {
|
|
_, image := fetchImage(c, "sunrise.JPG")
|
|
|
|
resized, err := image.Resize("200x")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(resized, qt.Not(qt.IsNil))
|
|
c.Assert(resized.Width(), qt.Equals, 200)
|
|
})
|
|
|
|
// Issue #7955
|
|
c.Run("Fill with smartcrop", func(c *qt.C) {
|
|
_, sunset := fetchImage(c, "sunset.jpg")
|
|
|
|
for _, test := range []struct {
|
|
originalDimensions string
|
|
targetWH int
|
|
}{
|
|
{"408x403", 400},
|
|
{"425x403", 400},
|
|
{"459x429", 400},
|
|
{"476x442", 400},
|
|
{"544x403", 400},
|
|
{"476x468", 400},
|
|
{"578x585", 550},
|
|
{"578x598", 550},
|
|
} {
|
|
c.Run(test.originalDimensions, func(c *qt.C) {
|
|
image, err := sunset.Resize(test.originalDimensions)
|
|
c.Assert(err, qt.IsNil)
|
|
resized, err := image.Fill(fmt.Sprintf("%dx%d smart", test.targetWH, test.targetWH))
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(resized, qt.Not(qt.IsNil))
|
|
c.Assert(resized.Width(), qt.Equals, test.targetWH)
|
|
c.Assert(resized.Height(), qt.Equals, test.targetWH)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestImageTransformConcurrent(t *testing.T) {
|
|
var wg sync.WaitGroup
|
|
|
|
c := qt.New(t)
|
|
|
|
spec, workDir := newTestResourceOsFs(c)
|
|
defer func() {
|
|
os.Remove(workDir)
|
|
}()
|
|
|
|
image := fetchImageForSpec(spec, c, "sunset.jpg")
|
|
|
|
for i := 0; i < 4; i++ {
|
|
wg.Add(1)
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
for j := 0; j < 5; j++ {
|
|
img := image
|
|
for k := 0; k < 2; k++ {
|
|
r1, err := img.Resize(fmt.Sprintf("%dx", id-k))
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if r1.Width() != id-k {
|
|
t.Errorf("Width: %d:%d", r1.Width(), j)
|
|
}
|
|
|
|
r2, err := r1.Resize(fmt.Sprintf("%dx", id-k-1))
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
img = r2
|
|
}
|
|
}
|
|
}(i + 20)
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestImageResize8BitPNG(t *testing.T) {
|
|
c := qt.New(t)
|
|
|
|
_, image := fetchImage(c, "gohugoio.png")
|
|
|
|
c.Assert(image.MediaType().Type, qt.Equals, "image/png")
|
|
c.Assert(image.RelPermalink(), qt.Equals, "/a/gohugoio.png")
|
|
c.Assert(image.ResourceType(), qt.Equals, "image")
|
|
c.Assert(image.Exif(), qt.IsNotNil)
|
|
|
|
resized, err := image.Resize("800x")
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(resized.MediaType().Type, qt.Equals, "image/png")
|
|
c.Assert(resized.RelPermalink(), qt.Equals, "/a/gohugoio_hu_fe2b762e9cac406c.png")
|
|
c.Assert(resized.Width(), qt.Equals, 800)
|
|
}
|
|
|
|
func TestSVGImage(t *testing.T) {
|
|
c := qt.New(t)
|
|
spec := newTestResourceSpec(specDescriptor{c: c})
|
|
svg := fetchResourceForSpec(spec, c, "circle.svg")
|
|
c.Assert(svg, qt.Not(qt.IsNil))
|
|
}
|
|
|
|
func TestSVGImageContent(t *testing.T) {
|
|
c := qt.New(t)
|
|
spec := newTestResourceSpec(specDescriptor{c: c})
|
|
svg := fetchResourceForSpec(spec, c, "circle.svg")
|
|
c.Assert(svg, qt.Not(qt.IsNil))
|
|
|
|
content, err := svg.Content(context.Background())
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(content, hqt.IsSameType, "")
|
|
c.Assert(content.(string), qt.Contains, `<svg height="100" width="100">`)
|
|
}
|
|
|
|
func TestImageExif(t *testing.T) {
|
|
c := qt.New(t)
|
|
fs := afero.NewMemMapFs()
|
|
spec := newTestResourceSpec(specDescriptor{fs: fs, c: c})
|
|
image := fetchResourceForSpec(spec, c, "sunset.jpg").(images.ImageResource)
|
|
|
|
getAndCheckExif := func(c *qt.C, image images.ImageResource) {
|
|
x := image.Exif()
|
|
c.Assert(x, qt.Not(qt.IsNil))
|
|
|
|
c.Assert(x.Date.Format("2006-01-02"), qt.Equals, "2017-10-27")
|
|
|
|
// Malaga: https://goo.gl/taazZy
|
|
c.Assert(x.Lat, qt.Equals, float64(36.59744166666667))
|
|
c.Assert(x.Long, qt.Equals, float64(-4.50846))
|
|
|
|
v, found := x.Tags["LensModel"]
|
|
c.Assert(found, qt.Equals, true)
|
|
lensModel, ok := v.(string)
|
|
c.Assert(ok, qt.Equals, true)
|
|
c.Assert(lensModel, qt.Equals, "smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM")
|
|
resized, _ := image.Resize("300x200")
|
|
x2 := resized.Exif()
|
|
|
|
c.Assert(x2, eq, x)
|
|
}
|
|
|
|
getAndCheckExif(c, image)
|
|
image = fetchResourceForSpec(spec, c, "sunset.jpg").(images.ImageResource)
|
|
// This will read from file cache.
|
|
getAndCheckExif(c, image)
|
|
}
|
|
|
|
func TestImageColorsLuminance(t *testing.T) {
|
|
c := qt.New(t)
|
|
|
|
_, image := fetchSunset(c)
|
|
c.Assert(image, qt.Not(qt.IsNil))
|
|
colors, err := image.Colors()
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(len(colors), qt.Equals, 6)
|
|
var prevLuminance float64
|
|
for i, color := range colors {
|
|
luminance := color.Luminance()
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(luminance > 0, qt.IsTrue)
|
|
c.Assert(luminance, qt.Not(qt.Equals), prevLuminance, qt.Commentf("i=%d", i))
|
|
prevLuminance = luminance
|
|
}
|
|
}
|
|
|
|
func BenchmarkImageExif(b *testing.B) {
|
|
getImages := func(c *qt.C, b *testing.B, fs afero.Fs) []images.ImageResource {
|
|
spec := newTestResourceSpec(specDescriptor{fs: fs, c: c})
|
|
imgs := make([]images.ImageResource, b.N)
|
|
for i := 0; i < b.N; i++ {
|
|
imgs[i] = fetchResourceForSpec(spec, c, "sunset.jpg", strconv.Itoa(i)).(images.ImageResource)
|
|
}
|
|
return imgs
|
|
}
|
|
|
|
getAndCheckExif := func(c *qt.C, image images.ImageResource) {
|
|
x := image.Exif()
|
|
c.Assert(x, qt.Not(qt.IsNil))
|
|
c.Assert(x.Long, qt.Equals, float64(-4.50846))
|
|
}
|
|
|
|
b.Run("Cold cache", func(b *testing.B) {
|
|
b.StopTimer()
|
|
c := qt.New(b)
|
|
images := getImages(c, b, afero.NewMemMapFs())
|
|
|
|
b.StartTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
getAndCheckExif(c, images[i])
|
|
}
|
|
})
|
|
|
|
b.Run("Cold cache, 10", func(b *testing.B) {
|
|
b.StopTimer()
|
|
c := qt.New(b)
|
|
images := getImages(c, b, afero.NewMemMapFs())
|
|
|
|
b.StartTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
for j := 0; j < 10; j++ {
|
|
getAndCheckExif(c, images[i])
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("Warm cache", func(b *testing.B) {
|
|
b.StopTimer()
|
|
c := qt.New(b)
|
|
fs := afero.NewMemMapFs()
|
|
images := getImages(c, b, fs)
|
|
for i := 0; i < b.N; i++ {
|
|
getAndCheckExif(c, images[i])
|
|
}
|
|
|
|
images = getImages(c, b, fs)
|
|
|
|
b.StartTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
getAndCheckExif(c, images[i])
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkResizeParallel(b *testing.B) {
|
|
c := qt.New(b)
|
|
_, img := fetchSunset(c)
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
w := rand.Intn(10) + 10
|
|
resized, err := img.Resize(strconv.Itoa(w) + "x")
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
_, err = resized.Resize(strconv.Itoa(w-1) + "x")
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func assertWidthHeight(c *qt.C, img images.ImageResource, w, h int) {
|
|
c.Helper()
|
|
c.Assert(img, qt.Not(qt.IsNil))
|
|
c.Assert(img.Width(), qt.Equals, w)
|
|
c.Assert(img.Height(), qt.Equals, h)
|
|
}
|