Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-08-26 20:12:41 +0300
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-08-28 16:59:54 +0300
commit823f53c861bb49aecc6104e0add39fc3b0729025 (patch)
tree64a55d1c41de09b67305ad69a3600f3091d4f1fc /resources
parentf9978ed16476ca6d233a89669c62c798cdf9db9d (diff)
Add a set of image filters
With this you can do variants of this: ``` {{ $img := resources.Get "images/misc/3-jenny.jpg" }} {{ $img := $img.Resize "300x" }} {{ $g1 := $img.Filter images.Grayscale }} {{ $g2 := $img | images.Filter (images.Saturate 30) (images.GaussianBlur 3) }} ``` Fixes #6255
Diffstat (limited to 'resources')
-rw-r--r--resources/image.go61
-rw-r--r--resources/image_test.go150
-rw-r--r--resources/images/config.go110
-rw-r--r--resources/images/filters.go168
-rw-r--r--resources/images/image.go90
-rw-r--r--resources/images/resampling.go214
-rw-r--r--resources/images/smartcrop.go33
-rw-r--r--resources/internal/key.go31
-rw-r--r--resources/internal/key_test.go9
-rw-r--r--resources/resource/resourcetypes.go2
-rw-r--r--resources/testdata/gohugoio24.pngbin0 -> 267952 bytes
-rw-r--r--resources/testdata/gohugoio8.pngbin0 -> 73538 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_100x100_fill_box_center_2.pngbin0 -> 11002 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_14fabac035a010e707ee3733f6590555.pngbin0 -> 59041 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x0_resize_q50_r90_box_2.pngbin0 -> 62018 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x100_resize_box_2.pngbin0 -> 20979 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x100_fill_nearestneighbor_topleft_2.pngbin0 -> 23035 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fill_gaussian_smart1_2.pngbin0 -> 46397 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fit_linear_2.pngbin0 -> 38597 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_bottomleft_2.pngbin0 -> 60099 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_center_2.pngbin0 -> 60099 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_55b828db27003cb979bac711748f4789.pngbin0 -> 44573 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_600x0_resize_box_2.pngbin0 -> 112941 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_621ae6f4010e2eb164521f54f653df1f.pngbin0 -> 62941 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_65ffdad1306cecec4d21bac1edd47c44.pngbin0 -> 8960 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_84b0614b9f84c94c0773ef49ae868d0b.pngbin0 -> 58776 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_874d58b1c4b4b538f7ade152b3e57df8.pngbin0 -> 78589 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_958fee7992cf502355355c021148638b.pngbin0 -> 60182 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9c5c204a4fc82e861344066bc8d0c7db.pngbin0 -> 58718 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a0088abf33fdbf6be1651a71e7d4dc33.pngbin0 -> 53835 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cdb3de8b01145d94ba41047655e42695.pngbin0 -> 46054 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cfc2eacca4b2748852f953954207d615.pngbin0 -> 45378 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1ad299f68cb4b3e1eba2ab7633e7857.pngbin0 -> 65067 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1f39c78ba8a0ada8233161edeed27ee.pngbin0 -> 60267 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_dd36fa3cc8ae7cf4d686caf1a171284b.pngbin0 -> 64612 bytes
-rw-r--r--resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_f5d42d1797f90edd6379e0b082fdd53b.pngbin0 -> 34370 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_100x100_fill_box_center_2.pngbin0 -> 5969 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_1bf2d9610b385893204d0a57ef8d1532.pngbin0 -> 18095 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x0_resize_q50_r90_box_2.pngbin0 -> 25346 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x100_resize_box_2.pngbin0 -> 10198 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x100_fill_nearestneighbor_topleft_2.pngbin0 -> 10210 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fill_gaussian_smart1_2.pngbin0 -> 20633 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fit_linear_2.pngbin0 -> 17575 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_bottomleft_2.pngbin0 -> 26281 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_center_2.pngbin0 -> 26281 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_41369feac467f9ecec9ef46911b04fa1.pngbin0 -> 21552 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_4c320010919da2d8b63ed24818b4d8e1.pngbin0 -> 34054 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_600x0_resize_box_2.pngbin0 -> 47492 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_7852bca7fb011b36d030e4d35d8e1d90.pngbin0 -> 23863 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_798ebb7a9e9dc7edd40e2832eb77e457.pngbin0 -> 34281 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_84a8d324276a96584446750f06d04bd4.pngbin0 -> 24422 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_8544b956dc08b714975ae52d4dcfdd78.pngbin0 -> 34524 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_888208ddeeeb3dcfe84697903ddffe30.pngbin0 -> 24546 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9660b4bf59aeb8ac8714d3e466af6197.pngbin0 -> 29412 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9a86fee686dd5973923f5ef5c3b0bc74.pngbin0 -> 26511 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9d4c2220235b3c2d9fa6506be571560f.pngbin0 -> 28414 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_bac1f274c6786fdb63dd215df2226cd9.pngbin0 -> 20267 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c1ced24877f4b1baf563997e33cadcfa.pngbin0 -> 33095 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c74bb417b961e09cf1aac2130b7b9b85.pngbin0 -> 20199 bytes
-rw-r--r--resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_de67126dc370f606d57f2c229b3accab.pngbin0 -> 24075 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_0d1b300da7a815ed567b6dadb6f2ce5e.jpgbin0 -> 6446 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x100_fill_q75_box_center.jpgbin0 -> 1805 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_17fd3c558d78ce249b5f0bcbe1ddbffb.jpgbin0 -> 7033 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x0_resize_q50_r90_box.jpgbin0 -> 4222 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_resize_q75_box.jpgbin0 -> 2698 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x100_fill_q75_nearestneighbor_topleft.jpgbin0 -> 2065 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fill_q75_gaussian_smart1.jpgbin0 -> 4667 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fit_q75_linear.jpgbin0 -> 4919 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_30fc2aab35ca0861bf396d09aebc85a4.jpgbin0 -> 7087 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_352eb0101b7c88107520ba719432bbb2.jpgbin0 -> 6435 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3efc2d0f29a8e12c5a690fc6c9288854.jpgbin0 -> 4449 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f1b1455c4a7d13c5aeb7510f9a6a581.jpgbin0 -> 6941 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_bottomleft.jpgbin0 -> 7311 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_center.jpgbin0 -> 6448 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_600x0_resize_q75_box.jpgbin0 -> 15636 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6c5c12ac79d3455ccb1993d51eec3cdf.jpgbin0 -> 6563 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_7d9bc4700565266807dc476421066137.jpgbin0 -> 6580 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_9f00027c376fe8556cc9996c47f23f78.jpgbin0 -> 6132 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_abf356affd7d70d6bec3b3498b572191.jpgbin0 -> 5908 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c36da6818db1ab630c3f87f65170003b.jpgbin0 -> 6337 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_cb45fcba865177290c89dc9f41d6ff7a.jpgbin0 -> 4464 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_d30c10468b33df9010d185a8fe8f0491.jpgbin0 -> 5858 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_de1fe6c0f40e7165355507d0f1748083.jpgbin0 -> 5469 bytes
-rw-r--r--resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_f6d8fe32ce3e83abf130e91e33456914.jpgbin0 -> 6421 bytes
-rw-r--r--resources/testhelpers_test.go18
-rw-r--r--resources/transform.go5
86 files changed, 761 insertions, 130 deletions
diff --git a/resources/image.go b/resources/image.go
index e1a816942..7113284f7 100644
--- a/resources/image.go
+++ b/resources/image.go
@@ -16,18 +16,19 @@ package resources
import (
"fmt"
"image"
- "image/color"
"image/draw"
_ "image/gif"
_ "image/png"
"os"
"strings"
+ "github.com/gohugoio/hugo/resources/internal"
+
"github.com/gohugoio/hugo/resources/resource"
_errors "github.com/pkg/errors"
- "github.com/disintegration/imaging"
+ "github.com/disintegration/gift"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/resources/images"
@@ -82,16 +83,26 @@ func (i *imageResource) cloneWithUpdates(u *transformationUpdate) (baseResource,
// filter and returns the transformed image. If one of width or height is 0, the image aspect
// ratio is preserved.
func (i *imageResource) Resize(spec string) (resource.Image, error) {
- return i.doWithImageConfig("resize", spec, func(src image.Image, conf images.ImageConfig) (image.Image, error) {
- return i.Proc.Resize(src, conf)
+ conf, err := i.decodeImageConfig("resize", spec)
+ if err != nil {
+ return nil, err
+ }
+
+ return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
+ return i.Proc.ApplyFiltersFromConfig(src, conf)
})
}
// Fit scales down the image using the specified resample filter to fit the specified
// maximum width and height.
func (i *imageResource) Fit(spec string) (resource.Image, error) {
- return i.doWithImageConfig("fit", spec, func(src image.Image, conf images.ImageConfig) (image.Image, error) {
- return i.Proc.Fit(src, conf)
+ conf, err := i.decodeImageConfig("fit", spec)
+ if err != nil {
+ return nil, err
+ }
+
+ return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
+ return i.Proc.ApplyFiltersFromConfig(src, conf)
})
}
@@ -99,8 +110,22 @@ func (i *imageResource) Fit(spec string) (resource.Image, error) {
// crops the resized image to the specified dimensions using the given anchor point.
// Space delimited config: 200x300 TopLeft
func (i *imageResource) Fill(spec string) (resource.Image, error) {
- return i.doWithImageConfig("fill", spec, func(src image.Image, conf images.ImageConfig) (image.Image, error) {
- return i.Proc.Fill(src, conf)
+ conf, err := i.decodeImageConfig("fill", spec)
+ if err != nil {
+ return nil, err
+ }
+
+ return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
+ return i.Proc.ApplyFiltersFromConfig(src, conf)
+ })
+}
+
+func (i *imageResource) Filter(filters ...gift.Filter) (resource.Image, error) {
+ conf := i.Proc.GetDefaultImageConfig("filter")
+ conf.Key = internal.HashString(filters)
+
+ return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
+ return i.Proc.Filter(src, filters...)
})
}
@@ -118,19 +143,14 @@ const imageProcWorkers = 1
var imageProcSem = make(chan bool, imageProcWorkers)
-func (i *imageResource) doWithImageConfig(action, spec string, f func(src image.Image, conf images.ImageConfig) (image.Image, error)) (resource.Image, error) {
- conf, err := i.decodeImageConfig(action, spec)
- if err != nil {
- return nil, err
- }
-
+func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src image.Image) (image.Image, error)) (resource.Image, error) {
return i.getSpec().imageCache.getOrCreate(i, conf, func() (*imageResource, image.Image, error) {
imageProcSem <- true
defer func() {
<-imageProcSem
}()
- errOp := action
+ errOp := conf.Action
errPath := i.getSourceFilename()
src, err := i.decodeSource()
@@ -138,17 +158,12 @@ func (i *imageResource) doWithImageConfig(action, spec string, f func(src image.
return nil, nil, &os.PathError{Op: errOp, Path: errPath, Err: err}
}
- if conf.Rotate != 0 {
- // Rotate it before any scaling to get the dimensions correct.
- src = imaging.Rotate(src, float64(conf.Rotate), color.Transparent)
- }
-
- converted, err := f(src, conf)
+ converted, err := f(src)
if err != nil {
return nil, nil, &os.PathError{Op: errOp, Path: errPath, Err: err}
}
- if i.Format == imaging.PNG {
+ if i.Format == images.PNG {
// Apply the colour palette from the source
if paletted, ok := src.(*image.Paletted); ok {
tmp := image.NewPaletted(converted.Bounds(), paletted.Palette)
@@ -222,7 +237,7 @@ func (i *imageResource) relTargetPathFromConfig(conf images.ImageConfig) dirFile
// Do not change for no good reason.
const md5Threshold = 100
- key := conf.Key(i.Format)
+ key := conf.GetKey(i.Format)
// It is useful to have the key in clear text, but when nesting transforms, it
// can easily be too long to read, and maybe even too long
diff --git a/resources/image_test.go b/resources/image_test.go
index 31169444d..330a3af4b 100644
--- a/resources/image_test.go
+++ b/resources/image_test.go
@@ -16,14 +16,20 @@ package resources
import (
"fmt"
"math/rand"
+ "os"
"path/filepath"
+ "regexp"
"strconv"
"sync"
"testing"
+ "github.com/disintegration/gift"
+
+ "github.com/gohugoio/hugo/helpers"
+
"github.com/gohugoio/hugo/media"
+ "github.com/gohugoio/hugo/resources/images"
"github.com/gohugoio/hugo/resources/resource"
-
"github.com/google/go-cmp/cmp"
"github.com/gohugoio/hugo/htesting/hqt"
@@ -35,6 +41,9 @@ var eq = qt.CmpEquals(
cmp.Comparer(func(p1, p2 *resourceAdapter) bool {
return p1.resourceAdapterInner == p2.resourceAdapterInner
}),
+ cmp.Comparer(func(p1, p2 os.FileInfo) bool {
+ 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()
@@ -94,7 +103,7 @@ func TestImageTransformBasic(t *testing.T) {
fittedAgain, err = fittedAgain.Fit("10x20")
c.Assert(err, qt.IsNil)
c.Assert(fittedAgain.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f65ba24dc2b7fba0f56d7f104519157.jpg")
- assertWidthHeight(fittedAgain, 10, 6)
+ assertWidthHeight(fittedAgain, 10, 7)
filled, err := image.Fill("200x100 bottomLeft")
c.Assert(err, qt.IsNil)
@@ -155,7 +164,10 @@ func TestImagePermalinkPublishOrder(t *testing.T) {
t.Run(name, func(t *testing.T) {
c := qt.New(t)
- spec := newTestResourceOsFs(c)
+ spec, workDir := newTestResourceOsFs(c)
+ defer func() {
+ os.Remove(workDir)
+ }()
check1 := func(img resource.Image) {
resizedLink := "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x50_resize_q75_box.jpg"
@@ -192,7 +204,10 @@ func TestImageTransformConcurrent(t *testing.T) {
c := qt.New(t)
- spec := newTestResourceOsFs(c)
+ spec, workDir := newTestResourceOsFs(c)
+ defer func() {
+ os.Remove(workDir)
+ }()
image := fetchImageForSpec(spec, c, "sunset.jpg")
@@ -317,6 +332,133 @@ func TestSVGImageContent(t *testing.T) {
c.Assert(content.(string), qt.Contains, `<svg height="100" width="100">`)
}
+func TestImageOperationsGolden(t *testing.T) {
+ c := qt.New(t)
+ c.Parallel()
+
+ devMode := false
+
+ testImages := []string{"sunset.jpg", "gohugoio8.png", "gohugoio24.png"}
+
+ spec, workDir := newTestResourceOsFs(c)
+ defer func() {
+ if !devMode {
+ os.Remove(workDir)
+ }
+ }()
+
+ if devMode {
+ fmt.Println(workDir)
+ }
+
+ for _, img := range testImages {
+
+ orig := fetchImageForSpec(spec, c, img)
+ for _, resizeSpec := range []string{"200x100", "600x", "200x r90 q50 Box"} {
+ resized, err := orig.Resize(resizeSpec)
+ c.Assert(err, qt.IsNil)
+ rel := resized.RelPermalink()
+ c.Log("resize", rel)
+ c.Assert(rel, qt.Not(qt.Equals), "")
+ }
+
+ for _, fillSpec := range []string{"300x200 Gaussian Smart", "100x100 Center", "300x100 TopLeft NearestNeighbor", "400x200 BottomLeft"} {
+ resized, err := orig.Fill(fillSpec)
+ c.Assert(err, qt.IsNil)
+ rel := resized.RelPermalink()
+ c.Log("fill", rel)
+ c.Assert(rel, qt.Not(qt.Equals), "")
+ }
+
+ for _, fitSpec := range []string{"300x200 Linear"} {
+ resized, err := orig.Fit(fitSpec)
+ c.Assert(err, qt.IsNil)
+ rel := resized.RelPermalink()
+ c.Log("fit", rel)
+ c.Assert(rel, qt.Not(qt.Equals), "")
+ }
+
+ f := &images.Filters{}
+
+ filters := []gift.Filter{
+ f.Grayscale(),
+ f.GaussianBlur(6),
+ f.Saturation(50),
+ f.Sepia(100),
+ f.Brightness(30),
+ f.ColorBalance(10, -10, -10),
+ f.Colorize(240, 50, 100),
+ f.Gamma(1.5),
+ f.UnsharpMask(1, 1, 0),
+ f.Sigmoid(0.5, 7),
+ f.Pixelate(5),
+ f.Invert(),
+ f.Hue(22),
+ f.Contrast(32.5),
+ }
+
+ resized, err := orig.Fill("400x200 center")
+
+ for _, filter := range filters {
+ resized, err := resized.Filter(filter)
+ c.Assert(err, qt.IsNil)
+ rel := resized.RelPermalink()
+ c.Logf("filter: %v %s", filter, rel)
+ c.Assert(rel, qt.Not(qt.Equals), "")
+ }
+
+ resized, err = resized.Filter(filters[0:4]...)
+ c.Assert(err, qt.IsNil)
+ rel := resized.RelPermalink()
+ c.Log("filter all", rel)
+ c.Assert(rel, qt.Not(qt.Equals), "")
+ }
+
+ if devMode {
+ return
+ }
+
+ dir1 := filepath.Join(workDir, "resources/_gen/images/a")
+ dir2 := filepath.FromSlash("testdata/golden")
+
+ // The two dirs above should now be the same.
+ d1, err := os.Open(dir1)
+ c.Assert(err, qt.IsNil)
+ d2, err := os.Open(dir2)
+ c.Assert(err, qt.IsNil)
+
+ dirinfos1, err := d1.Readdir(-1)
+ c.Assert(err, qt.IsNil)
+ dirinfos2, err := d2.Readdir(-1)
+
+ c.Assert(err, qt.IsNil)
+ c.Assert(len(dirinfos1), qt.Equals, len(dirinfos2))
+
+ for i, fi1 := range dirinfos1 {
+ if regexp.MustCompile("gauss").MatchString(fi1.Name()) {
+ continue
+ }
+ fi2 := dirinfos2[i]
+ c.Assert(fi1.Name(), qt.Equals, fi2.Name())
+ c.Assert(fi1, eq, fi2)
+ f1, err := os.Open(filepath.Join(dir1, fi1.Name()))
+ c.Assert(err, qt.IsNil)
+ f2, err := os.Open(filepath.Join(dir2, fi2.Name()))
+ c.Assert(err, qt.IsNil)
+
+ hash1, err := helpers.MD5FromReader(f1)
+ c.Assert(err, qt.IsNil)
+ hash2, err := helpers.MD5FromReader(f2)
+ c.Assert(err, qt.IsNil)
+
+ f1.Close()
+ f2.Close()
+
+ c.Assert(hash1, qt.Equals, hash2)
+ }
+
+}
+
func BenchmarkResizeParallel(b *testing.B) {
c := qt.New(b)
img := fetchSunset(c)
diff --git a/resources/images/config.go b/resources/images/config.go
index c4605c9cf..b6121efa5 100644
--- a/resources/images/config.go
+++ b/resources/images/config.go
@@ -19,7 +19,8 @@ import (
"strconv"
"strings"
- "github.com/disintegration/imaging"
+ "github.com/disintegration/gift"
+
"github.com/mitchellh/mapstructure"
)
@@ -29,61 +30,59 @@ const (
)
var (
- imageFormats = map[string]imaging.Format{
- ".jpg": imaging.JPEG,
- ".jpeg": imaging.JPEG,
- ".png": imaging.PNG,
- ".tif": imaging.TIFF,
- ".tiff": imaging.TIFF,
- ".bmp": imaging.BMP,
- ".gif": imaging.GIF,
+ imageFormats = map[string]Format{
+ ".jpg": JPEG,
+ ".jpeg": JPEG,
+ ".png": PNG,
+ ".tif": TIFF,
+ ".tiff": TIFF,
+ ".bmp": BMP,
+ ".gif": GIF,
}
// Add or increment if changes to an image format's processing requires
// re-generation.
- imageFormatsVersions = map[imaging.Format]int{
- imaging.PNG: 2, // Floyd Steinberg dithering
+ imageFormatsVersions = map[Format]int{
+ PNG: 2, // Floyd Steinberg dithering
}
// Increment to mark all processed images as stale. Only use when absolutely needed.
// See the finer grained smartCropVersionNumber and imageFormatsVersions.
mainImageVersionNumber = 0
-
- // Increment to mark all traced SVGs as stale.
- traceVersionNumber = 0
)
-var anchorPositions = map[string]imaging.Anchor{
- strings.ToLower("Center"): imaging.Center,
- strings.ToLower("TopLeft"): imaging.TopLeft,
- strings.ToLower("Top"): imaging.Top,
- strings.ToLower("TopRight"): imaging.TopRight,
- strings.ToLower("Left"): imaging.Left,
- strings.ToLower("Right"): imaging.Right,
- strings.ToLower("BottomLeft"): imaging.BottomLeft,
- strings.ToLower("Bottom"): imaging.Bottom,
- strings.ToLower("BottomRight"): imaging.BottomRight,
+var anchorPositions = map[string]gift.Anchor{
+ strings.ToLower("Center"): gift.CenterAnchor,
+ strings.ToLower("TopLeft"): gift.TopLeftAnchor,
+ strings.ToLower("Top"): gift.TopAnchor,
+ strings.ToLower("TopRight"): gift.TopRightAnchor,
+ strings.ToLower("Left"): gift.LeftAnchor,
+ strings.ToLower("Right"): gift.RightAnchor,
+ strings.ToLower("BottomLeft"): gift.BottomLeftAnchor,
+ strings.ToLower("Bottom"): gift.BottomAnchor,
+ strings.ToLower("BottomRight"): gift.BottomRightAnchor,
}
-var imageFilters = map[string]imaging.ResampleFilter{
- strings.ToLower("NearestNeighbor"): imaging.NearestNeighbor,
- strings.ToLower("Box"): imaging.Box,
- strings.ToLower("Linear"): imaging.Linear,
- strings.ToLower("Hermite"): imaging.Hermite,
- strings.ToLower("MitchellNetravali"): imaging.MitchellNetravali,
- strings.ToLower("CatmullRom"): imaging.CatmullRom,
- strings.ToLower("BSpline"): imaging.BSpline,
- strings.ToLower("Gaussian"): imaging.Gaussian,
- strings.ToLower("Lanczos"): imaging.Lanczos,
- strings.ToLower("Hann"): imaging.Hann,
- strings.ToLower("Hamming"): imaging.Hamming,
- strings.ToLower("Blackman"): imaging.Blackman,
- strings.ToLower("Bartlett"): imaging.Bartlett,
- strings.ToLower("Welch"): imaging.Welch,
- strings.ToLower("Cosine"): imaging.Cosine,
+var imageFilters = map[string]gift.Resampling{
+
+ strings.ToLower("NearestNeighbor"): gift.NearestNeighborResampling,
+ strings.ToLower("Box"): gift.BoxResampling,
+ strings.ToLower("Linear"): gift.LinearResampling,
+ strings.ToLower("Hermite"): hermiteResampling,
+ strings.ToLower("MitchellNetravali"): mitchellNetravaliResampling,
+ strings.ToLower("CatmullRom"): catmullRomResampling,
+ strings.ToLower("BSpline"): bSplineResampling,
+ strings.ToLower("Gaussian"): gaussianResampling,
+ strings.ToLower("Lanczos"): gift.LanczosResampling,
+ strings.ToLower("Hann"): hannResampling,
+ strings.ToLower("Hamming"): hammingResampling,
+ strings.ToLower("Blackman"): blackmanResampling,
+ strings.ToLower("Bartlett"): bartlettResampling,
+ strings.ToLower("Welch"): welchResampling,
+ strings.ToLower("Cosine"): cosineResampling,
}
-func ImageFormatFromExt(ext string) (imaging.Format, bool) {
+func ImageFormatFromExt(ext string) (Format, bool) {
f, found := imageFormats[ext]
return f, found
}
@@ -100,8 +99,8 @@ func DecodeConfig(m map[string]interface{}) (Imaging, error) {
return i, errors.New("JPEG quality must be a number between 1 and 100")
}
- if i.Anchor == "" || strings.EqualFold(i.Anchor, SmartCropIdentifier) {
- i.Anchor = SmartCropIdentifier
+ if i.Anchor == "" || strings.EqualFold(i.Anchor, smartCropIdentifier) {
+ i.Anchor = smartCropIdentifier
} else {
i.Anchor = strings.ToLower(i.Anchor)
if _, found := anchorPositions[i.Anchor]; !found {
@@ -139,8 +138,8 @@ func DecodeImageConfig(action, config string, defaults Imaging) (ImageConfig, er
for _, part := range parts {
part = strings.ToLower(part)
- if part == SmartCropIdentifier {
- c.AnchorStr = SmartCropIdentifier
+ if part == smartCropIdentifier {
+ c.AnchorStr = smartCropIdentifier
} else if pos, ok := anchorPositions[part]; ok {
c.Anchor = pos
c.AnchorStr = part
@@ -198,7 +197,7 @@ func DecodeImageConfig(action, config string, defaults Imaging) (ImageConfig, er
if c.AnchorStr == "" {
c.AnchorStr = defaults.Anchor
- if !strings.EqualFold(c.AnchorStr, SmartCropIdentifier) {
+ if !strings.EqualFold(c.AnchorStr, smartCropIdentifier) {
c.Anchor = anchorPositions[c.AnchorStr]
}
}
@@ -210,6 +209,9 @@ func DecodeImageConfig(action, config string, defaults Imaging) (ImageConfig, er
type ImageConfig struct {
Action string
+ // If set, this will be used as the key in filenames etc.
+ Key string
+
// Quality ranges from 1 to 100 inclusive, higher is better.
// This is only relevant for JPEG images.
// Default is 75.
@@ -222,14 +224,18 @@ type ImageConfig struct {
Width int
Height int
- Filter imaging.ResampleFilter
+ Filter gift.Resampling
FilterStr string
- Anchor imaging.Anchor
+ Anchor gift.Anchor
AnchorStr string
}
-func (i ImageConfig) Key(format imaging.Format) string {
+func (i ImageConfig) GetKey(format Format) string {
+ if i.Key != "" {
+ return i.Action + "_" + i.Key
+ }
+
k := strconv.Itoa(i.Width) + "x" + strconv.Itoa(i.Height)
if i.Action != "" {
k += "_" + i.Action
@@ -241,7 +247,7 @@ func (i ImageConfig) Key(format imaging.Format) string {
k += "_r" + strconv.Itoa(i.Rotate)
}
anchor := i.AnchorStr
- if anchor == SmartCropIdentifier {
+ if anchor == smartCropIdentifier {
anchor = anchor + strconv.Itoa(smartCropVersionNumber)
}
@@ -268,9 +274,9 @@ type Imaging struct {
// Default image quality setting (1-100). Only used for JPEG images.
Quality int
- // Resample filter used. See https://github.com/disintegration/imaging
+ // Resample filter to use in resize operations..
ResampleFilter string
- // The anchor used in Fill. Default is "smart", i.e. Smart Crop.
+ // The anchor to use in Fill. Default is "smart", i.e. Smart Crop.
Anchor string
}
diff --git a/resources/images/filters.go b/resources/images/filters.go
new file mode 100644
index 000000000..dd7b58345
--- /dev/null
+++ b/resources/images/filters.go
@@ -0,0 +1,168 @@
+// 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 images provides template functions for manipulating images.
+package images
+
+import (
+ "github.com/disintegration/gift"
+ "github.com/spf13/cast"
+)
+
+// Increment for re-generation of images using these filters.
+const filterAPIVersion = 0
+
+type Filters struct {
+}
+
+// Brightness creates a filter that changes the brightness of an image.
+// The percentage parameter must be in range (-100, 100).
+func (*Filters) Brightness(percentage interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(percentage),
+ Filter: gift.Brightness(cast.ToFloat32(percentage)),
+ }
+}
+
+// ColorBalance creates a filter that changes the color balance of an image.
+// The percentage parameters for each color channel (red, green, blue) must be in range (-100, 500).
+func (*Filters) ColorBalance(percentageRed, percentageGreen, percentageBlue interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(percentageRed, percentageGreen, percentageBlue),
+ Filter: gift.ColorBalance(cast.ToFloat32(percentageRed), cast.ToFloat32(percentageGreen), cast.ToFloat32(percentageBlue)),
+ }
+}
+
+// Colorize creates a filter that produces a colorized version of an image.
+// The hue parameter is the angle on the color wheel, typically in range (0, 360).
+// The saturation parameter must be in range (0, 100).
+// The percentage parameter specifies the strength of the effect, it must be in range (0, 100).
+func (*Filters) Colorize(hue, saturation, percentage interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(hue, saturation, percentage),
+ Filter: gift.Colorize(cast.ToFloat32(hue), cast.ToFloat32(saturation), cast.ToFloat32(percentage)),
+ }
+}
+
+// Contrast creates a filter that changes the contrast of an image.
+// The percentage parameter must be in range (-100, 100).
+func (*Filters) Contrast(percentage interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(percentage),
+ Filter: gift.Contrast(cast.ToFloat32(percentage)),
+ }
+}
+
+// Gamma creates a filter that performs a gamma correction on an image.
+// The gamma parameter must be positive. Gamma = 1 gives the original image.
+// Gamma less than 1 darkens the image and gamma greater than 1 lightens it.
+func (*Filters) Gamma(gamma interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(gamma),
+ Filter: gift.Gamma(cast.ToFloat32(gamma)),
+ }
+}
+
+// GaussianBlur creates a filter that applies a gaussian blur to an image.
+func (*Filters) GaussianBlur(sigma interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(sigma),
+ Filter: gift.GaussianBlur(cast.ToFloat32(sigma)),
+ }
+}
+
+// Grayscale creates a filter that produces a grayscale version of an image.
+func (*Filters) Grayscale() gift.Filter {
+ return filter{
+ Filter: gift.Grayscale(),
+ }
+}
+
+// Hue creates a filter that rotates the hue of an image.
+// The hue angle shift is typically in range -180 to 180.
+func (*Filters) Hue(shift interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(shift),
+ Filter: gift.Hue(cast.ToFloat32(shift)),
+ }
+}
+
+// Invert creates a filter that negates the colors of an image.
+func (*Filters) Invert() gift.Filter {
+ return filter{
+ Filter: gift.Invert(),
+ }
+}
+
+// Pixelate creates a filter that applies a pixelation effect to an image.
+func (*Filters) Pixelate(size interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(size),
+ Filter: gift.Pixelate(cast.ToInt(size)),
+ }
+}
+
+// Saturation creates a filter that changes the saturation of an image.
+func (*Filters) Saturation(percentage interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(percentage),
+ Filter: gift.Saturation(cast.ToFloat32(percentage)),
+ }
+}
+
+// Sepia creates a filter that produces a sepia-toned version of an image.
+func (*Filters) Sepia(percentage interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(percentage),
+ Filter: gift.Sepia(cast.ToFloat32(percentage)),
+ }
+}
+
+// Sigmoid creates a filter that changes the contrast of an image using a sigmoidal function and returns the adjusted image.
+// It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
+func (*Filters) Sigmoid(midpoint, factor interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(midpoint, factor),
+ Filter: gift.Sigmoid(cast.ToFloat32(midpoint), cast.ToFloat32(factor)),
+ }
+}
+
+// UnsharpMask creates a filter that sharpens an image.
+// The sigma parameter is used in a gaussian function and affects the radius of effect.
+// Sigma must be positive. Sharpen radius roughly equals 3 * sigma.
+// The amount parameter controls how much darker and how much lighter the edge borders become. Typically between 0.5 and 1.5.
+// The threshold parameter controls the minimum brightness change that will be sharpened. Typically between 0 and 0.05.
+func (*Filters) UnsharpMask(sigma, amount, threshold interface{}) gift.Filter {
+ return filter{
+ Options: newFilterOpts(sigma, amount, threshold),
+ Filter: gift.UnsharpMask(cast.ToFloat32(sigma), cast.ToFloat32(amount), cast.ToFloat32(threshold)),
+ }
+}
+
+type filter struct {
+ Options filterOpts
+ gift.Filter
+}
+
+// For cache-busting.
+type filterOpts struct {
+ Version int
+ Vals interface{}
+}
+
+func newFilterOpts(vals ...interface{}) filterOpts {
+ return filterOpts{
+ Version: filterAPIVersion,
+ Vals: vals,
+ }
+}
diff --git a/resources/images/image.go b/resources/images/image.go
index b39e84972..d04c1e93d 100644
--- a/resources/images/image.go
+++ b/resources/images/image.go
@@ -15,16 +15,22 @@ package images
import (
"image"
+ "image/color"
+ "image/gif"
"image/jpeg"
+ "image/png"
"io"
"sync"
- "github.com/disintegration/imaging"
+ "github.com/disintegration/gift"
+ "golang.org/x/image/bmp"
+ "golang.org/x/image/tiff"
+
"github.com/gohugoio/hugo/common/hugio"
"github.com/pkg/errors"
)
-func NewImage(f imaging.Format, proc *ImageProcessor, img image.Image, s Spec) *Image {
+func NewImage(f Format, proc *ImageProcessor, img image.Image, s Spec) *Image {
if img != nil {
return &Image{
Format: f,
@@ -40,7 +46,7 @@ func NewImage(f imaging.Format, proc *ImageProcessor, img image.Image, s Spec) *
}
type Image struct {
- Format imaging.Format
+ Format Format
Proc *ImageProcessor
@@ -51,7 +57,7 @@ type Image struct {
func (i *Image) EncodeTo(conf ImageConfig, img image.Image, w io.Writer) error {
switch i.Format {
- case imaging.JPEG:
+ case JPEG:
var rgba *image.RGBA
quality := conf.Quality
@@ -69,9 +75,23 @@ func (i *Image) EncodeTo(conf ImageConfig, img image.Image, w io.Writer) error {
return jpeg.Encode(w, rgba, &jpeg.Options{Quality: quality})
}
return jpeg.Encode(w, img, &jpeg.Options{Quality: quality})
+ case PNG:
+ encoder := png.Encoder{CompressionLevel: png.DefaultCompression}
+ return encoder.Encode(w, img)
+
+ case GIF:
+ return gif.Encode(w, img, &gif.Options{
+ NumColors: 256,
+ })
+ case TIFF:
+ return tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
+
+ case BMP:
+ return bmp.Encode(w, img)
default:
- return imaging.Encode(w, img, i.Format)
+ return errors.New("format not supported")
}
+
}
// Height returns i's height.
@@ -138,19 +158,52 @@ type ImageProcessor struct {
Cfg Imaging
}
-func (p *ImageProcessor) Fill(src image.Image, conf ImageConfig) (image.Image, error) {
- if conf.AnchorStr == SmartCropIdentifier {
- return smartCrop(src, conf.Width, conf.Height, conf.Anchor, conf.Filter)
+func (p *ImageProcessor) ApplyFiltersFromConfig(src image.Image, conf ImageConfig) (image.Image, error) {
+ var filters []gift.Filter
+
+ if conf.Rotate != 0 {
+ // Apply any rotation before any resize.
+ filters = append(filters, gift.Rotate(float32(conf.Rotate), color.Transparent, gift.NearestNeighborInterpolation))
}
- return imaging.Fill(src, conf.Width, conf.Height, conf.Anchor, conf.Filter), nil
+
+ switch conf.Action {
+ case "resize":
+ filters = append(filters, gift.Resize(conf.Width, conf.Height, conf.Filter))
+ case "fill":
+ if conf.AnchorStr == smartCropIdentifier {
+ bounds, err := p.smartCrop(src, conf.Width, conf.Height, conf.Filter)
+ if err != nil {
+ return nil, err
+ }
+
+ // First crop it, then resize it.
+ filters = append(filters, gift.Crop(bounds))
+ filters = append(filters, gift.Resize(conf.Width, conf.Height, conf.Filter))
+
+ } else {
+ filters = append(filters, gift.ResizeToFill(conf.Width, conf.Height, conf.Filter, conf.Anchor))
+ }
+ case "fit":
+ filters = append(filters, gift.ResizeToFit(conf.Width, conf.Height, conf.Filter))
+ default:
+ return nil, errors.Errorf("unsupported action: %q", conf.Action)
+ }
+
+ return p.Filter(src, filters...)
}
-func (p *ImageProcessor) Fit(src image.Image, conf ImageConfig) (image.Image, error) {
- return imaging.Fit(src, conf.Width, conf.Height, conf.Filter), nil
+func (p *ImageProcessor) Filter(src image.Image, filters ...gift.Filter) (image.Image, error) {
+ g := gift.New(filters...)
+ dst := image.NewRGBA(g.Bounds(src.Bounds()))
+ g.Draw(dst, src)
+ return dst, nil
}
-func (p *ImageProcessor) Resize(src image.Image, conf ImageConfig) (image.Image, error) {
- return imaging.Resize(src, conf.Width, conf.Height, conf.Filter), nil
+func (p *ImageProcessor) GetDefaultImageConfig(action string) ImageConfig {
+ return ImageConfig{
+ Action: action,
+ Quality: p.Cfg.Quality,
+ }
}
type Spec interface {
@@ -158,6 +211,17 @@ type Spec interface {
ReadSeekCloser() (hugio.ReadSeekCloser, error)
}
+// Format is an image file format.
+type Format int
+
+const (
+ JPEG Format = iota + 1
+ PNG
+ GIF
+ TIFF
+ BMP
+)
+
type imageConfig struct {
config image.Config
configInit sync.Once
diff --git a/resources/images/resampling.go b/resources/images/resampling.go
new file mode 100644
index 000000000..0cb267684
--- /dev/null
+++ b/resources/images/resampling.go
@@ -0,0 +1,214 @@
+// 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 images
+
+import "math"
+
+// We moved from imaging to the gift package for image processing at some point.
+// That package had more, but also less resampling filters. So we add the missing
+// ones here. They are fairly exotic, but someone may use them, so keep them here
+// for now.
+//
+// The filters below are ported from https://github.com/disintegration/imaging/blob/9aab30e6aa535fe3337b489b76759ef97dfaf362/resize.go#L369
+// MIT License.
+
+var (
+ // Hermite cubic spline filter (BC-spline; B=0; C=0).
+ hermiteResampling = resamp{
+ name: "Hermite",
+ support: 1.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 1.0 {
+ return bcspline(x, 0.0, 0.0)
+ }
+ return 0
+ },
+ }
+
+ // Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
+ mitchellNetravaliResampling = resamp{
+ name: "MitchellNetravali",
+ support: 2.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 2.0 {
+ return bcspline(x, 1.0/3.0, 1.0/3.0)
+ }
+ return 0
+ },
+ }
+
+ // Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
+ catmullRomResampling = resamp{
+ name: "CatmullRomResampling",
+ support: 2.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 2.0 {
+ return bcspline(x, 0.0, 0.5)
+ }
+ return 0
+ },
+ }
+
+ // BSpline is a smooth cubic filter (BC-spline; B=1; C=0).
+ bSplineResampling = resamp{
+ name: "BSplineResampling",
+ support: 2.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 2.0 {
+ return bcspline(x, 1.0, 0.0)
+ }
+ return 0
+ },
+ }
+
+ // Gaussian blurring filter.
+ gaussianResampling = resamp{
+ name: "GaussianResampling",
+ support: 2.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 2.0 {
+ return float32(math.Exp(float64(-2 * x * x)))
+ }
+ return 0
+ },
+ }
+
+ // Hann-windowed sinc filter (3 lobes).
+ hannResampling = resamp{
+ name: "HannResampling",
+ support: 3.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 3.0 {
+ return sinc(x) * float32(0.5+0.5*math.Cos(math.Pi*float64(x)/3.0))
+ }
+ return 0
+ },
+ }
+
+ hammingResampling = resamp{
+ name: "HammingResampling",
+ support: 3.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 3.0 {
+ return sinc(x) * float32(0.54+0.46*math.Cos(math.Pi*float64(x)/3.0))
+ }
+ return 0
+ },
+ }
+
+ // Blackman-windowed sinc filter (3 lobes).
+ blackmanResampling = resamp{
+ name: "BlackmanResampling",
+ support: 3.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 3.0 {
+ return sinc(x) * float32(0.42-0.5*math.Cos(math.Pi*float64(x)/3.0+math.Pi)+0.08*math.Cos(2.0*math.Pi*float64(x)/3.0))
+ }
+ return 0
+ },
+ }
+
+ bartlettResampling = resamp{
+ name: "BartlettResampling",
+ support: 3.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 3.0 {
+ return sinc(x) * (3.0 - x) / 3.0
+ }
+ return 0
+ },
+ }
+
+ // Welch-windowed sinc filter (parabolic window, 3 lobes).
+ welchResampling = resamp{
+ name: "WelchResampling",
+ support: 3.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 3.0 {
+ return sinc(x) * (1.0 - (x * x / 9.0))
+ }
+ return 0
+ },
+ }
+
+ // Cosine-windowed sinc filter (3 lobes).
+ cosineResampling = resamp{
+ name: "CosineResampling",
+ support: 3.0,
+ kernel: func(x float32) float32 {
+ x = absf32(x)
+ if x < 3.0 {
+ return sinc(x) * float32(math.Cos((math.Pi/2.0)*(float64(x)/3.0)))
+ }
+ return 0
+ },
+ }
+)
+
+// The following code is borrowed from https://raw.githubusercontent.com/disintegration/gift/master/resize.go
+// MIT licensed.
+type resamp struct {
+ name string
+ support float32
+ kernel func(float32) float32
+}
+
+func (r resamp) String() string {
+ return r.name
+}
+
+func (r resamp) Support() float32 {
+ return r.support
+}
+
+func (r resamp) Kernel(x float32) float32 {
+ return r.kernel(x)
+}
+
+func bcspline(x, b, c float32) float32 {
+ if x < 0 {
+ x = -x
+ }
+ if x < 1 {
+ return ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
+ }
+ if x < 2 {
+ return ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
+ }
+ return 0
+}
+
+func absf32(x float32) float32 {
+ if x < 0 {
+ return -x
+ }
+ return x
+}
+
+func sinc(x float32) float32 {
+ if x == 0 {
+ return 1
+ }
+ return float32(math.Sin(math.Pi*float64(x)) / (math.Pi * float64(x)))
+}
diff --git a/resources/images/smartcrop.go b/resources/images/smartcrop.go
index 0b35b8280..e0181b671 100644
--- a/resources/images/smartcrop.go
+++ b/resources/images/smartcrop.go
@@ -16,36 +16,38 @@ package images
import (
"image"
- "github.com/disintegration/imaging"
+ "github.com/disintegration/gift"
+
"github.com/muesli/smartcrop"
)
const (
// Do not change.
- // TODO(bep) image unexport
- SmartCropIdentifier = "smart"
+ smartCropIdentifier = "smart"
// This is just a increment, starting on 1. If Smart Crop improves its cropping, we
// need a way to trigger a re-generation of the crops in the wild, so increment this.
smartCropVersionNumber = 1
)
-func newSmartCropAnalyzer(filter imaging.ResampleFilter) smartcrop.Analyzer {
- return smartcrop.NewAnalyzer(imagingResizer{filter: filter})
+func (p *ImageProcessor) newSmartCropAnalyzer(filter gift.Resampling) smartcrop.Analyzer {
+ return smartcrop.NewAnalyzer(imagingResizer{p: p, filter: filter})
}
// Needed by smartcrop
type imagingResizer struct {
- filter imaging.ResampleFilter
+ p *ImageProcessor
+ filter gift.Resampling
}
func (r imagingResizer) Resize(img image.Image, width, height uint) image.Image {
- return imaging.Resize(img, int(width), int(height), r.filter)
+ result, _ := r.p.Filter(img, gift.Resize(int(width), int(height), r.filter))
+ return result
}
-func smartCrop(img image.Image, width, height int, anchor imaging.Anchor, filter imaging.ResampleFilter) (*image.NRGBA, error) {
+func (p *ImageProcessor) smartCrop(img image.Image, width, height int, filter gift.Resampling) (image.Rectangle, error) {
if width <= 0 || height <= 0 {
- return &image.NRGBA{}, nil
+ return image.Rectangle{}, nil
}
srcBounds := img.Bounds()
@@ -53,23 +55,20 @@ func smartCrop(img image.Image, width, height int, anchor imaging.Anchor, filter
srcH := srcBounds.Dy()
if srcW <= 0 || srcH <= 0 {
- return &image.NRGBA{}, nil
+ return image.Rectangle{}, nil
}
if srcW == width && srcH == height {
- return imaging.Clone(img), nil
+ return srcBounds, nil
}
- smart := newSmartCropAnalyzer(filter)
+ smart := p.newSmartCropAnalyzer(filter)
rect, err := smart.FindBestCrop(img, width, height)
if err != nil {
- return nil, err
+ return image.Rectangle{}, err
}
- b := img.Bounds().Intersect(rect)
-
- cropped := imaging.Crop(img, b)
+ return img.Bounds().Intersect(rect), nil
- return imaging.Resize(cropped, width, height, filter), nil
}
diff --git a/resources/internal/key.go b/resources/internal/key.go
index 3dce8b350..17543b0d4 100644
--- a/resources/internal/key.go
+++ b/resources/internal/key.go
@@ -16,8 +16,6 @@ package internal
import (
"strconv"
- bp "github.com/gohugoio/hugo/bufferpool"
-
"github.com/mitchellh/hashstructure"
)
@@ -44,18 +42,23 @@ func (k ResourceTransformationKey) Value() string {
return k.Name
}
- sb := bp.GetBuffer()
- defer bp.PutBuffer(sb)
-
- sb.WriteString(k.Name)
- for _, element := range k.elements {
- hash, err := hashstructure.Hash(element, nil)
- if err != nil {
- panic(err)
- }
- sb.WriteString("_")
- sb.WriteString(strconv.FormatUint(hash, 10))
+ return k.Name + "_" + HashString(k.elements...)
+
+}
+
+// HashString returns a hash from the given elements.
+// It will panic if the hash cannot be calculated.
+func HashString(elements ...interface{}) string {
+ var o interface{}
+ if len(elements) == 1 {
+ o = elements[0]
+ } else {
+ o = elements
}
- return sb.String()
+ hash, err := hashstructure.Hash(o, nil)
+ if err != nil {
+ panic(err)
+ }
+ return strconv.FormatUint(hash, 10)
}
diff --git a/resources/internal/key_test.go b/resources/internal/key_test.go
index 9b6a23d87..11a52f2e6 100644
--- a/resources/internal/key_test.go
+++ b/resources/internal/key_test.go
@@ -32,5 +32,12 @@ func TestResourceTransformationKey(t *testing.T) {
key := NewResourceTransformationKey("testing",
testStruct{Name: "test", V1: int64(10), V2: int32(20), V3: 30, V4: uint64(40)})
c := qt.New(t)
- c.Assert("testing_518996646957295636", qt.Equals, key.Value())
+ c.Assert(key.Value(), qt.Equals, "testing_518996646957295636")
+}
+
+func TestHashString(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(HashString("a", "b"), qt.Equals, "2712570657419664240")
+ c.Assert(HashString("ab"), qt.Equals, "590647783936702392")
}
diff --git a/resources/resource/resourcetypes.go b/resources/resource/resourcetypes.go
index 32c76fc83..4322b3c1f 100644
--- a/resources/resource/resourcetypes.go
+++ b/resources/resource/resourcetypes.go
@@ -14,6 +14,7 @@
package resource
import (
+ "github.com/disintegration/gift"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/media"
@@ -47,6 +48,7 @@ type ImageOps interface {
Fill(spec string) (Image, error)
Fit(spec string) (Image, error)
Resize(spec string) (Image, error)
+ Filter(filters ...gift.Filter) (Image, error)
}
type ResourceTypesProvider interface {
diff --git a/resources/testdata/gohugoio24.png b/resources/testdata/gohugoio24.png
new file mode 100644
index 000000000..9b004b897
--- /dev/null
+++ b/resources/testdata/gohugoio24.png
Binary files differ
diff --git a/resources/testdata/gohugoio8.png b/resources/testdata/gohugoio8.png
new file mode 100644
index 000000000..0993f90e4
--- /dev/null
+++ b/resources/testdata/gohugoio8.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_100x100_fill_box_center_2.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_100x100_fill_box_center_2.png
new file mode 100644
index 000000000..d2f0afd27
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_100x100_fill_box_center_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_14fabac035a010e707ee3733f6590555.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_14fabac035a010e707ee3733f6590555.png
new file mode 100644
index 000000000..25ac82485
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_14fabac035a010e707ee3733f6590555.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x0_resize_q50_r90_box_2.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x0_resize_q50_r90_box_2.png
new file mode 100644
index 000000000..5abf378b4
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x0_resize_q50_r90_box_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x100_resize_box_2.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x100_resize_box_2.png
new file mode 100644
index 000000000..cd56200ea
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x100_resize_box_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x100_fill_nearestneighbor_topleft_2.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x100_fill_nearestneighbor_topleft_2.png
new file mode 100644
index 000000000..dd11ce7ed
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x100_fill_nearestneighbor_topleft_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fill_gaussian_smart1_2.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fill_gaussian_smart1_2.png
new file mode 100644
index 000000000..59ac93c1c
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fill_gaussian_smart1_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fit_linear_2.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fit_linear_2.png
new file mode 100644
index 000000000..5ad74bf79
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fit_linear_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_bottomleft_2.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_bottomleft_2.png
new file mode 100644
index 000000000..76deeabc7
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_bottomleft_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_center_2.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_center_2.png
new file mode 100644
index 000000000..76deeabc7
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_center_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_55b828db27003cb979bac711748f4789.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_55b828db27003cb979bac711748f4789.png
new file mode 100644
index 000000000..362be673b
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_55b828db27003cb979bac711748f4789.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_600x0_resize_box_2.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_600x0_resize_box_2.png
new file mode 100644
index 000000000..28028b72d
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_600x0_resize_box_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_621ae6f4010e2eb164521f54f653df1f.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_621ae6f4010e2eb164521f54f653df1f.png
new file mode 100644
index 000000000..0991ca984
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_621ae6f4010e2eb164521f54f653df1f.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_65ffdad1306cecec4d21bac1edd47c44.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_65ffdad1306cecec4d21bac1edd47c44.png
new file mode 100644
index 000000000..841d369ef
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_65ffdad1306cecec4d21bac1edd47c44.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_84b0614b9f84c94c0773ef49ae868d0b.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_84b0614b9f84c94c0773ef49ae868d0b.png
new file mode 100644
index 000000000..174649232
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_84b0614b9f84c94c0773ef49ae868d0b.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_874d58b1c4b4b538f7ade152b3e57df8.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_874d58b1c4b4b538f7ade152b3e57df8.png
new file mode 100644
index 000000000..eba4b1e66
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_874d58b1c4b4b538f7ade152b3e57df8.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_958fee7992cf502355355c021148638b.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_958fee7992cf502355355c021148638b.png
new file mode 100644
index 000000000..dde14757c
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_958fee7992cf502355355c021148638b.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9c5c204a4fc82e861344066bc8d0c7db.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9c5c204a4fc82e861344066bc8d0c7db.png
new file mode 100644
index 000000000..32c5b49d8
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9c5c204a4fc82e861344066bc8d0c7db.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a0088abf33fdbf6be1651a71e7d4dc33.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a0088abf33fdbf6be1651a71e7d4dc33.png
new file mode 100644
index 000000000..93f8dfda2
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a0088abf33fdbf6be1651a71e7d4dc33.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cdb3de8b01145d94ba41047655e42695.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cdb3de8b01145d94ba41047655e42695.png
new file mode 100644
index 000000000..a48a0f25a
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cdb3de8b01145d94ba41047655e42695.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cfc2eacca4b2748852f953954207d615.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cfc2eacca4b2748852f953954207d615.png
new file mode 100644
index 000000000..0ce82e49c
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cfc2eacca4b2748852f953954207d615.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1ad299f68cb4b3e1eba2ab7633e7857.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1ad299f68cb4b3e1eba2ab7633e7857.png
new file mode 100644
index 000000000..2fece7804
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1ad299f68cb4b3e1eba2ab7633e7857.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1f39c78ba8a0ada8233161edeed27ee.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1f39c78ba8a0ada8233161edeed27ee.png
new file mode 100644
index 000000000..603b95ae0
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1f39c78ba8a0ada8233161edeed27ee.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_dd36fa3cc8ae7cf4d686caf1a171284b.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_dd36fa3cc8ae7cf4d686caf1a171284b.png
new file mode 100644
index 000000000..46fa3fd1b
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_dd36fa3cc8ae7cf4d686caf1a171284b.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_f5d42d1797f90edd6379e0b082fdd53b.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_f5d42d1797f90edd6379e0b082fdd53b.png
new file mode 100644
index 000000000..697ac914e
--- /dev/null
+++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_f5d42d1797f90edd6379e0b082fdd53b.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_100x100_fill_box_center_2.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_100x100_fill_box_center_2.png
new file mode 100644
index 000000000..0eef0aaf3
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_100x100_fill_box_center_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_1bf2d9610b385893204d0a57ef8d1532.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_1bf2d9610b385893204d0a57ef8d1532.png
new file mode 100644
index 000000000..69aa35885
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_1bf2d9610b385893204d0a57ef8d1532.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x0_resize_q50_r90_box_2.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x0_resize_q50_r90_box_2.png
new file mode 100644
index 000000000..c35f00722
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x0_resize_q50_r90_box_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x100_resize_box_2.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x100_resize_box_2.png
new file mode 100644
index 000000000..6ddb55158
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x100_resize_box_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x100_fill_nearestneighbor_topleft_2.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x100_fill_nearestneighbor_topleft_2.png
new file mode 100644
index 000000000..08eccf7cd
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x100_fill_nearestneighbor_topleft_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fill_gaussian_smart1_2.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fill_gaussian_smart1_2.png
new file mode 100644
index 000000000..f62d093a0
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fill_gaussian_smart1_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fit_linear_2.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fit_linear_2.png
new file mode 100644
index 000000000..0660c20d7
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fit_linear_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_bottomleft_2.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_bottomleft_2.png
new file mode 100644
index 000000000..acde6a0f7
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_bottomleft_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_center_2.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_center_2.png
new file mode 100644
index 000000000..acde6a0f7
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_center_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_41369feac467f9ecec9ef46911b04fa1.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_41369feac467f9ecec9ef46911b04fa1.png
new file mode 100644
index 000000000..53dd0b224
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_41369feac467f9ecec9ef46911b04fa1.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_4c320010919da2d8b63ed24818b4d8e1.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_4c320010919da2d8b63ed24818b4d8e1.png
new file mode 100644
index 000000000..c8f782598
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_4c320010919da2d8b63ed24818b4d8e1.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_600x0_resize_box_2.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_600x0_resize_box_2.png
new file mode 100644
index 000000000..40fffa23a
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_600x0_resize_box_2.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_7852bca7fb011b36d030e4d35d8e1d90.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_7852bca7fb011b36d030e4d35d8e1d90.png
new file mode 100644
index 000000000..c96e04108
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_7852bca7fb011b36d030e4d35d8e1d90.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_798ebb7a9e9dc7edd40e2832eb77e457.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_798ebb7a9e9dc7edd40e2832eb77e457.png
new file mode 100644
index 000000000..156b42f43
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_798ebb7a9e9dc7edd40e2832eb77e457.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_84a8d324276a96584446750f06d04bd4.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_84a8d324276a96584446750f06d04bd4.png
new file mode 100644
index 000000000..7134de473
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_84a8d324276a96584446750f06d04bd4.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_8544b956dc08b714975ae52d4dcfdd78.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_8544b956dc08b714975ae52d4dcfdd78.png
new file mode 100644
index 000000000..5a27e2fad
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_8544b956dc08b714975ae52d4dcfdd78.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_888208ddeeeb3dcfe84697903ddffe30.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_888208ddeeeb3dcfe84697903ddffe30.png
new file mode 100644
index 000000000..1fa2bc9de
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_888208ddeeeb3dcfe84697903ddffe30.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9660b4bf59aeb8ac8714d3e466af6197.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9660b4bf59aeb8ac8714d3e466af6197.png
new file mode 100644
index 000000000..414acff3b
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9660b4bf59aeb8ac8714d3e466af6197.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9a86fee686dd5973923f5ef5c3b0bc74.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9a86fee686dd5973923f5ef5c3b0bc74.png
new file mode 100644
index 000000000..37dc0f798
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9a86fee686dd5973923f5ef5c3b0bc74.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9d4c2220235b3c2d9fa6506be571560f.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9d4c2220235b3c2d9fa6506be571560f.png
new file mode 100644
index 000000000..2def214c8
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9d4c2220235b3c2d9fa6506be571560f.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_bac1f274c6786fdb63dd215df2226cd9.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_bac1f274c6786fdb63dd215df2226cd9.png
new file mode 100644
index 000000000..325c31acd
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_bac1f274c6786fdb63dd215df2226cd9.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c1ced24877f4b1baf563997e33cadcfa.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c1ced24877f4b1baf563997e33cadcfa.png
new file mode 100644
index 000000000..1a229a429
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c1ced24877f4b1baf563997e33cadcfa.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c74bb417b961e09cf1aac2130b7b9b85.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c74bb417b961e09cf1aac2130b7b9b85.png
new file mode 100644
index 000000000..51f6cfa7e
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c74bb417b961e09cf1aac2130b7b9b85.png
Binary files differ
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_de67126dc370f606d57f2c229b3accab.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_de67126dc370f606d57f2c229b3accab.png
new file mode 100644
index 000000000..a5852e14c
--- /dev/null
+++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_de67126dc370f606d57f2c229b3accab.png
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_0d1b300da7a815ed567b6dadb6f2ce5e.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_0d1b300da7a815ed567b6dadb6f2ce5e.jpg
new file mode 100644
index 000000000..1e2cb535b
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_0d1b300da7a815ed567b6dadb6f2ce5e.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x100_fill_q75_box_center.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x100_fill_q75_box_center.jpg
new file mode 100644
index 000000000..8e6164e32
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x100_fill_q75_box_center.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_17fd3c558d78ce249b5f0bcbe1ddbffb.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_17fd3c558d78ce249b5f0bcbe1ddbffb.jpg
new file mode 100644
index 000000000..2aa3dad2b
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_17fd3c558d78ce249b5f0bcbe1ddbffb.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x0_resize_q50_r90_box.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x0_resize_q50_r90_box.jpg
new file mode 100644
index 000000000..05d98c67a
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x0_resize_q50_r90_box.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_resize_q75_box.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_resize_q75_box.jpg
new file mode 100644
index 000000000..f12dd18fc
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_resize_q75_box.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x100_fill_q75_nearestneighbor_topleft.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x100_fill_q75_nearestneighbor_topleft.jpg
new file mode 100644
index 000000000..8ac3b2524
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x100_fill_q75_nearestneighbor_topleft.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fill_q75_gaussian_smart1.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fill_q75_gaussian_smart1.jpg
new file mode 100644
index 000000000..03de912fb
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fill_q75_gaussian_smart1.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fit_q75_linear.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fit_q75_linear.jpg
new file mode 100644
index 000000000..3801c17d9
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fit_q75_linear.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_30fc2aab35ca0861bf396d09aebc85a4.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_30fc2aab35ca0861bf396d09aebc85a4.jpg
new file mode 100644
index 000000000..60207a829
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_30fc2aab35ca0861bf396d09aebc85a4.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_352eb0101b7c88107520ba719432bbb2.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_352eb0101b7c88107520ba719432bbb2.jpg
new file mode 100644
index 000000000..f7e84e33d
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_352eb0101b7c88107520ba719432bbb2.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3efc2d0f29a8e12c5a690fc6c9288854.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3efc2d0f29a8e12c5a690fc6c9288854.jpg
new file mode 100644
index 000000000..17a5927e2
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3efc2d0f29a8e12c5a690fc6c9288854.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f1b1455c4a7d13c5aeb7510f9a6a581.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f1b1455c4a7d13c5aeb7510f9a6a581.jpg
new file mode 100644
index 000000000..93b914161
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f1b1455c4a7d13c5aeb7510f9a6a581.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_bottomleft.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_bottomleft.jpg
new file mode 100644
index 000000000..9a6255687
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_bottomleft.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_center.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_center.jpg
new file mode 100644
index 000000000..b2db97485
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_center.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_600x0_resize_q75_box.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_600x0_resize_q75_box.jpg
new file mode 100644
index 000000000..a5ad199d8
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_600x0_resize_q75_box.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6c5c12ac79d3455ccb1993d51eec3cdf.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6c5c12ac79d3455ccb1993d51eec3cdf.jpg
new file mode 100644
index 000000000..e77e78d7b
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6c5c12ac79d3455ccb1993d51eec3cdf.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_7d9bc4700565266807dc476421066137.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_7d9bc4700565266807dc476421066137.jpg
new file mode 100644
index 000000000..ee246814d
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_7d9bc4700565266807dc476421066137.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_9f00027c376fe8556cc9996c47f23f78.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_9f00027c376fe8556cc9996c47f23f78.jpg
new file mode 100644
index 000000000..e7db706c2
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_9f00027c376fe8556cc9996c47f23f78.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_abf356affd7d70d6bec3b3498b572191.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_abf356affd7d70d6bec3b3498b572191.jpg
new file mode 100644
index 000000000..9688c99c3
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_abf356affd7d70d6bec3b3498b572191.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c36da6818db1ab630c3f87f65170003b.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c36da6818db1ab630c3f87f65170003b.jpg
new file mode 100644
index 000000000..41b42a883
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c36da6818db1ab630c3f87f65170003b.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_cb45fcba865177290c89dc9f41d6ff7a.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_cb45fcba865177290c89dc9f41d6ff7a.jpg
new file mode 100644
index 000000000..f09ff9e33
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_cb45fcba865177290c89dc9f41d6ff7a.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_d30c10468b33df9010d185a8fe8f0491.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_d30c10468b33df9010d185a8fe8f0491.jpg
new file mode 100644
index 000000000..0b7d4e5d0
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_d30c10468b33df9010d185a8fe8f0491.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_de1fe6c0f40e7165355507d0f1748083.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_de1fe6c0f40e7165355507d0f1748083.jpg
new file mode 100644
index 000000000..7e35750db
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_de1fe6c0f40e7165355507d0f1748083.jpg
Binary files differ
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_f6d8fe32ce3e83abf130e91e33456914.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_f6d8fe32ce3e83abf130e91e33456914.jpg
new file mode 100644
index 000000000..b67650061
--- /dev/null
+++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_f6d8fe32ce3e83abf130e91e33456914.jpg
Binary files differ
diff --git a/resources/testhelpers_test.go b/resources/testhelpers_test.go
index adf752ecc..bc24fb8f2 100644
--- a/resources/testhelpers_test.go
+++ b/resources/testhelpers_test.go
@@ -102,11 +102,13 @@ func newTargetPaths(link string) func() page.TargetPaths {
}
}
-func newTestResourceOsFs(c *qt.C) *Spec {
+func newTestResourceOsFs(c *qt.C) (*Spec, string) {
cfg := createTestCfg()
cfg.Set("baseURL", "https://example.com")
- workDir, _ := ioutil.TempDir("", "hugores")
+ workDir, err := ioutil.TempDir("", "hugores")
+ c.Assert(err, qt.IsNil)
+ c.Assert(workDir, qt.Not(qt.Equals), "")
if runtime.GOOS == "darwin" && !strings.HasPrefix(workDir, "/private") {
// To get the entry folder in line with the rest. This its a little bit
@@ -127,7 +129,8 @@ func newTestResourceOsFs(c *qt.C) *Spec {
spec, err := NewSpec(s, filecaches, nil, output.DefaultFormats, media.DefaultTypes)
c.Assert(err, qt.IsNil)
- return spec
+
+ return spec, workDir
}
@@ -139,6 +142,7 @@ func fetchImage(c *qt.C, name string) resource.Image {
spec := newTestResourceSpec(specDescriptor{c: c})
return fetchImageForSpec(spec, c, name)
}
+
func fetchImageForSpec(spec *Spec, c *qt.C, name string) resource.Image {
r := fetchResourceForSpec(spec, c, name)
@@ -153,8 +157,9 @@ func fetchImageForSpec(spec *Spec, c *qt.C, name string) resource.Image {
func fetchResourceForSpec(spec *Spec, c *qt.C, name string) resource.ContentResource {
src, err := os.Open(filepath.FromSlash("testdata/" + name))
c.Assert(err, qt.IsNil)
-
- out, err := helpers.OpenFileForWriting(spec.Fs.Source, name)
+ workDir := spec.WorkingDir
+ targetFilename := filepath.Join(workDir, name)
+ out, err := helpers.OpenFileForWriting(spec.Fs.Source, targetFilename)
c.Assert(err, qt.IsNil)
_, err = io.Copy(out, src)
out.Close()
@@ -163,8 +168,9 @@ func fetchResourceForSpec(spec *Spec, c *qt.C, name string) resource.ContentReso
factory := newTargetPaths("/a")
- r, err := spec.New(ResourceSourceDescriptor{Fs: spec.Fs.Source, TargetPaths: factory, LazyPublish: true, SourceFilename: name})
+ r, err := spec.New(ResourceSourceDescriptor{Fs: spec.Fs.Source, TargetPaths: factory, LazyPublish: true, RelTargetFilename: name, SourceFilename: targetFilename})
c.Assert(err, qt.IsNil)
+ c.Assert(r, qt.Not(qt.IsNil))
return r.(resource.ContentResource)
}
diff --git a/resources/transform.go b/resources/transform.go
index 72b9479df..0792515c4 100644
--- a/resources/transform.go
+++ b/resources/transform.go
@@ -21,6 +21,7 @@ import (
"strings"
"sync"
+ "github.com/disintegration/gift"
"github.com/spf13/afero"
bp "github.com/gohugoio/hugo/bufferpool"
@@ -172,6 +173,10 @@ func (r *resourceAdapter) Fit(spec string) (resource.Image, error) {
return r.getImageOps().Fit(spec)
}
+func (r *resourceAdapter) Filter(filters ...gift.Filter) (resource.Image, error) {
+ return r.getImageOps().Filter(filters...)
+}
+
func (r *resourceAdapter) Height() int {
return r.getImageOps().Height()
}