diff options
author | Bjørn Erik Pedersen <[email protected]> | 2019-08-26 19:12:41 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2019-08-28 15:59:54 +0200 |
commit | 823f53c861bb49aecc6104e0add39fc3b0729025 (patch) | |
tree | 64a55d1c41de09b67305ad69a3600f3091d4f1fc /resources | |
parent | f9978ed16476ca6d233a89669c62c798cdf9db9d (diff) | |
download | hugo-823f53c861bb49aecc6104e0add39fc3b0729025.tar.gz hugo-823f53c861bb49aecc6104e0add39fc3b0729025.zip |
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')
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 Binary files differnew file mode 100644 index 000000000..9b004b897 --- /dev/null +++ b/resources/testdata/gohugoio24.png diff --git a/resources/testdata/gohugoio8.png b/resources/testdata/gohugoio8.png Binary files differnew file mode 100644 index 000000000..0993f90e4 --- /dev/null +++ b/resources/testdata/gohugoio8.png 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 Binary files differnew file mode 100644 index 000000000..d2f0afd27 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_100x100_fill_box_center_2.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_14fabac035a010e707ee3733f6590555.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_14fabac035a010e707ee3733f6590555.png Binary files differnew file mode 100644 index 000000000..25ac82485 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_14fabac035a010e707ee3733f6590555.png 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 Binary files differnew file mode 100644 index 000000000..5abf378b4 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x0_resize_q50_r90_box_2.png 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 Binary files differnew file mode 100644 index 000000000..cd56200ea --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x100_resize_box_2.png 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 Binary files differnew file mode 100644 index 000000000..dd11ce7ed --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x100_fill_nearestneighbor_topleft_2.png 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 Binary files differnew file mode 100644 index 000000000..59ac93c1c --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fill_gaussian_smart1_2.png 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 Binary files differnew file mode 100644 index 000000000..5ad74bf79 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fit_linear_2.png 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 Binary files differnew file mode 100644 index 000000000..76deeabc7 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_bottomleft_2.png 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 Binary files differnew file mode 100644 index 000000000..76deeabc7 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_center_2.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_55b828db27003cb979bac711748f4789.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_55b828db27003cb979bac711748f4789.png Binary files differnew file mode 100644 index 000000000..362be673b --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_55b828db27003cb979bac711748f4789.png 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 Binary files differnew file mode 100644 index 000000000..28028b72d --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_600x0_resize_box_2.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_621ae6f4010e2eb164521f54f653df1f.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_621ae6f4010e2eb164521f54f653df1f.png Binary files differnew file mode 100644 index 000000000..0991ca984 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_621ae6f4010e2eb164521f54f653df1f.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_65ffdad1306cecec4d21bac1edd47c44.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_65ffdad1306cecec4d21bac1edd47c44.png Binary files differnew file mode 100644 index 000000000..841d369ef --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_65ffdad1306cecec4d21bac1edd47c44.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_84b0614b9f84c94c0773ef49ae868d0b.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_84b0614b9f84c94c0773ef49ae868d0b.png Binary files differnew file mode 100644 index 000000000..174649232 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_84b0614b9f84c94c0773ef49ae868d0b.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_874d58b1c4b4b538f7ade152b3e57df8.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_874d58b1c4b4b538f7ade152b3e57df8.png Binary files differnew file mode 100644 index 000000000..eba4b1e66 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_874d58b1c4b4b538f7ade152b3e57df8.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_958fee7992cf502355355c021148638b.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_958fee7992cf502355355c021148638b.png Binary files differnew file mode 100644 index 000000000..dde14757c --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_958fee7992cf502355355c021148638b.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9c5c204a4fc82e861344066bc8d0c7db.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9c5c204a4fc82e861344066bc8d0c7db.png Binary files differnew file mode 100644 index 000000000..32c5b49d8 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9c5c204a4fc82e861344066bc8d0c7db.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a0088abf33fdbf6be1651a71e7d4dc33.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a0088abf33fdbf6be1651a71e7d4dc33.png Binary files differnew file mode 100644 index 000000000..93f8dfda2 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a0088abf33fdbf6be1651a71e7d4dc33.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cdb3de8b01145d94ba41047655e42695.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cdb3de8b01145d94ba41047655e42695.png Binary files differnew file mode 100644 index 000000000..a48a0f25a --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cdb3de8b01145d94ba41047655e42695.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cfc2eacca4b2748852f953954207d615.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cfc2eacca4b2748852f953954207d615.png Binary files differnew file mode 100644 index 000000000..0ce82e49c --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_cfc2eacca4b2748852f953954207d615.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1ad299f68cb4b3e1eba2ab7633e7857.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1ad299f68cb4b3e1eba2ab7633e7857.png Binary files differnew file mode 100644 index 000000000..2fece7804 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1ad299f68cb4b3e1eba2ab7633e7857.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1f39c78ba8a0ada8233161edeed27ee.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1f39c78ba8a0ada8233161edeed27ee.png Binary files differnew file mode 100644 index 000000000..603b95ae0 --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d1f39c78ba8a0ada8233161edeed27ee.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_dd36fa3cc8ae7cf4d686caf1a171284b.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_dd36fa3cc8ae7cf4d686caf1a171284b.png Binary files differnew file mode 100644 index 000000000..46fa3fd1b --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_dd36fa3cc8ae7cf4d686caf1a171284b.png diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_f5d42d1797f90edd6379e0b082fdd53b.png b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_f5d42d1797f90edd6379e0b082fdd53b.png Binary files differnew file mode 100644 index 000000000..697ac914e --- /dev/null +++ b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_f5d42d1797f90edd6379e0b082fdd53b.png 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 Binary files differnew file mode 100644 index 000000000..0eef0aaf3 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_100x100_fill_box_center_2.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_1bf2d9610b385893204d0a57ef8d1532.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_1bf2d9610b385893204d0a57ef8d1532.png Binary files differnew file mode 100644 index 000000000..69aa35885 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_1bf2d9610b385893204d0a57ef8d1532.png 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 Binary files differnew file mode 100644 index 000000000..c35f00722 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x0_resize_q50_r90_box_2.png 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 Binary files differnew file mode 100644 index 000000000..6ddb55158 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x100_resize_box_2.png 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 Binary files differnew file mode 100644 index 000000000..08eccf7cd --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x100_fill_nearestneighbor_topleft_2.png 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 Binary files differnew file mode 100644 index 000000000..f62d093a0 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fill_gaussian_smart1_2.png 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 Binary files differnew file mode 100644 index 000000000..0660c20d7 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fit_linear_2.png 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 Binary files differnew file mode 100644 index 000000000..acde6a0f7 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_bottomleft_2.png 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 Binary files differnew file mode 100644 index 000000000..acde6a0f7 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_center_2.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_41369feac467f9ecec9ef46911b04fa1.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_41369feac467f9ecec9ef46911b04fa1.png Binary files differnew file mode 100644 index 000000000..53dd0b224 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_41369feac467f9ecec9ef46911b04fa1.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_4c320010919da2d8b63ed24818b4d8e1.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_4c320010919da2d8b63ed24818b4d8e1.png Binary files differnew file mode 100644 index 000000000..c8f782598 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_4c320010919da2d8b63ed24818b4d8e1.png 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 Binary files differnew file mode 100644 index 000000000..40fffa23a --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_600x0_resize_box_2.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_7852bca7fb011b36d030e4d35d8e1d90.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_7852bca7fb011b36d030e4d35d8e1d90.png Binary files differnew file mode 100644 index 000000000..c96e04108 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_7852bca7fb011b36d030e4d35d8e1d90.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_798ebb7a9e9dc7edd40e2832eb77e457.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_798ebb7a9e9dc7edd40e2832eb77e457.png Binary files differnew file mode 100644 index 000000000..156b42f43 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_798ebb7a9e9dc7edd40e2832eb77e457.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_84a8d324276a96584446750f06d04bd4.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_84a8d324276a96584446750f06d04bd4.png Binary files differnew file mode 100644 index 000000000..7134de473 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_84a8d324276a96584446750f06d04bd4.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_8544b956dc08b714975ae52d4dcfdd78.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_8544b956dc08b714975ae52d4dcfdd78.png Binary files differnew file mode 100644 index 000000000..5a27e2fad --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_8544b956dc08b714975ae52d4dcfdd78.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_888208ddeeeb3dcfe84697903ddffe30.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_888208ddeeeb3dcfe84697903ddffe30.png Binary files differnew file mode 100644 index 000000000..1fa2bc9de --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_888208ddeeeb3dcfe84697903ddffe30.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9660b4bf59aeb8ac8714d3e466af6197.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9660b4bf59aeb8ac8714d3e466af6197.png Binary files differnew file mode 100644 index 000000000..414acff3b --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9660b4bf59aeb8ac8714d3e466af6197.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9a86fee686dd5973923f5ef5c3b0bc74.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9a86fee686dd5973923f5ef5c3b0bc74.png Binary files differnew file mode 100644 index 000000000..37dc0f798 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9a86fee686dd5973923f5ef5c3b0bc74.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9d4c2220235b3c2d9fa6506be571560f.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9d4c2220235b3c2d9fa6506be571560f.png Binary files differnew file mode 100644 index 000000000..2def214c8 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_9d4c2220235b3c2d9fa6506be571560f.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_bac1f274c6786fdb63dd215df2226cd9.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_bac1f274c6786fdb63dd215df2226cd9.png Binary files differnew file mode 100644 index 000000000..325c31acd --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_bac1f274c6786fdb63dd215df2226cd9.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c1ced24877f4b1baf563997e33cadcfa.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c1ced24877f4b1baf563997e33cadcfa.png Binary files differnew file mode 100644 index 000000000..1a229a429 --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c1ced24877f4b1baf563997e33cadcfa.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c74bb417b961e09cf1aac2130b7b9b85.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c74bb417b961e09cf1aac2130b7b9b85.png Binary files differnew file mode 100644 index 000000000..51f6cfa7e --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c74bb417b961e09cf1aac2130b7b9b85.png diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_de67126dc370f606d57f2c229b3accab.png b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_de67126dc370f606d57f2c229b3accab.png Binary files differnew file mode 100644 index 000000000..a5852e14c --- /dev/null +++ b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_de67126dc370f606d57f2c229b3accab.png diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_0d1b300da7a815ed567b6dadb6f2ce5e.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_0d1b300da7a815ed567b6dadb6f2ce5e.jpg Binary files differnew file mode 100644 index 000000000..1e2cb535b --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_0d1b300da7a815ed567b6dadb6f2ce5e.jpg 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 Binary files differnew file mode 100644 index 000000000..8e6164e32 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x100_fill_q75_box_center.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_17fd3c558d78ce249b5f0bcbe1ddbffb.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_17fd3c558d78ce249b5f0bcbe1ddbffb.jpg Binary files differnew file mode 100644 index 000000000..2aa3dad2b --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_17fd3c558d78ce249b5f0bcbe1ddbffb.jpg 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 Binary files differnew file mode 100644 index 000000000..05d98c67a --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x0_resize_q50_r90_box.jpg 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 Binary files differnew file mode 100644 index 000000000..f12dd18fc --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_resize_q75_box.jpg 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 Binary files differnew file mode 100644 index 000000000..8ac3b2524 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x100_fill_q75_nearestneighbor_topleft.jpg 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 Binary files differnew file mode 100644 index 000000000..03de912fb --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fill_q75_gaussian_smart1.jpg 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 Binary files differnew file mode 100644 index 000000000..3801c17d9 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fit_q75_linear.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_30fc2aab35ca0861bf396d09aebc85a4.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_30fc2aab35ca0861bf396d09aebc85a4.jpg Binary files differnew file mode 100644 index 000000000..60207a829 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_30fc2aab35ca0861bf396d09aebc85a4.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_352eb0101b7c88107520ba719432bbb2.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_352eb0101b7c88107520ba719432bbb2.jpg Binary files differnew file mode 100644 index 000000000..f7e84e33d --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_352eb0101b7c88107520ba719432bbb2.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3efc2d0f29a8e12c5a690fc6c9288854.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3efc2d0f29a8e12c5a690fc6c9288854.jpg Binary files differnew file mode 100644 index 000000000..17a5927e2 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3efc2d0f29a8e12c5a690fc6c9288854.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f1b1455c4a7d13c5aeb7510f9a6a581.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f1b1455c4a7d13c5aeb7510f9a6a581.jpg Binary files differnew file mode 100644 index 000000000..93b914161 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f1b1455c4a7d13c5aeb7510f9a6a581.jpg 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 Binary files differnew file mode 100644 index 000000000..9a6255687 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_bottomleft.jpg 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 Binary files differnew file mode 100644 index 000000000..b2db97485 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_center.jpg 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 Binary files differnew file mode 100644 index 000000000..a5ad199d8 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_600x0_resize_q75_box.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6c5c12ac79d3455ccb1993d51eec3cdf.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6c5c12ac79d3455ccb1993d51eec3cdf.jpg Binary files differnew file mode 100644 index 000000000..e77e78d7b --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6c5c12ac79d3455ccb1993d51eec3cdf.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_7d9bc4700565266807dc476421066137.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_7d9bc4700565266807dc476421066137.jpg Binary files differnew file mode 100644 index 000000000..ee246814d --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_7d9bc4700565266807dc476421066137.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_9f00027c376fe8556cc9996c47f23f78.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_9f00027c376fe8556cc9996c47f23f78.jpg Binary files differnew file mode 100644 index 000000000..e7db706c2 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_9f00027c376fe8556cc9996c47f23f78.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_abf356affd7d70d6bec3b3498b572191.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_abf356affd7d70d6bec3b3498b572191.jpg Binary files differnew file mode 100644 index 000000000..9688c99c3 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_abf356affd7d70d6bec3b3498b572191.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c36da6818db1ab630c3f87f65170003b.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c36da6818db1ab630c3f87f65170003b.jpg Binary files differnew file mode 100644 index 000000000..41b42a883 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c36da6818db1ab630c3f87f65170003b.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_cb45fcba865177290c89dc9f41d6ff7a.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_cb45fcba865177290c89dc9f41d6ff7a.jpg Binary files differnew file mode 100644 index 000000000..f09ff9e33 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_cb45fcba865177290c89dc9f41d6ff7a.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_d30c10468b33df9010d185a8fe8f0491.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_d30c10468b33df9010d185a8fe8f0491.jpg Binary files differnew file mode 100644 index 000000000..0b7d4e5d0 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_d30c10468b33df9010d185a8fe8f0491.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_de1fe6c0f40e7165355507d0f1748083.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_de1fe6c0f40e7165355507d0f1748083.jpg Binary files differnew file mode 100644 index 000000000..7e35750db --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_de1fe6c0f40e7165355507d0f1748083.jpg diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_f6d8fe32ce3e83abf130e91e33456914.jpg b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_f6d8fe32ce3e83abf130e91e33456914.jpg Binary files differnew file mode 100644 index 000000000..b67650061 --- /dev/null +++ b/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_f6d8fe32ce3e83abf130e91e33456914.jpg 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() } |