summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-09-22 15:15:16 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-09-24 11:54:29 +0200
commitef0e7149d63c64269b852cf68a2af67b94b8eec3 (patch)
tree7182a4d3e396d231a3c62c4ca76055eb3ba3a845
parentc32094ace113412abea354b7119d154168097287 (diff)
Add $image.Process
Which supports all the existing actions: resize, crop, fit, fill. But it also allows plain format conversions: ``` {{ $img = $img.Process "webp" }} ``` Which will be a simple re-encoding of the source image. Fixes #11483
-rw-r--r--common/hstrings/strings.go23
-rw-r--r--docs/content/en/content-management/image-processing/index.md32
-rw-r--r--helpers/general.go12
-rw-r--r--hugolib/site_output_test.go6
-rw-r--r--resources/errorResource.go4
-rw-r--r--resources/image.go109
-rw-r--r--resources/image_test.go51
-rw-r--r--resources/images/config.go39
-rw-r--r--resources/images/config_test.go2
-rw-r--r--resources/images/image.go20
-rw-r--r--resources/images/image_resource.go3
-rw-r--r--resources/page/permalinks.go7
-rw-r--r--resources/transform.go6
13 files changed, 216 insertions, 98 deletions
diff --git a/common/hstrings/strings.go b/common/hstrings/strings.go
index 2fd791f43..88df97607 100644
--- a/common/hstrings/strings.go
+++ b/common/hstrings/strings.go
@@ -99,3 +99,26 @@ var reCache = regexpCache{re: make(map[string]*regexp.Regexp)}
func GetOrCompileRegexp(pattern string) (re *regexp.Regexp, err error) {
return reCache.getOrCompileRegexp(pattern)
}
+
+// InSlice checks if a string is an element of a slice of strings
+// and returns a boolean value.
+func InSlice(arr []string, el string) bool {
+ for _, v := range arr {
+ if v == el {
+ return true
+ }
+ }
+ return false
+}
+
+// InSlicEqualFold checks if a string is an element of a slice of strings
+// and returns a boolean value.
+// It uses strings.EqualFold to compare.
+func InSlicEqualFold(arr []string, el string) bool {
+ for _, v := range arr {
+ if strings.EqualFold(v, el) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/docs/content/en/content-management/image-processing/index.md b/docs/content/en/content-management/image-processing/index.md
index 63c9d4d2f..124cadc68 100644
--- a/docs/content/en/content-management/image-processing/index.md
+++ b/docs/content/en/content-management/image-processing/index.md
@@ -101,12 +101,41 @@ Example 4: Skips rendering if there's problem accessing a remote resource.
## Image processing methods
-The `image` resource implements the [`Resize`], [`Fit`], [`Fill`], [`Crop`], [`Filter`], [`Colors`] and [`Exif`] methods.
+The `image` resource implements the [`Process`], [`Resize`], [`Fit`], [`Fill`], [`Crop`], [`Filter`], [`Colors`] and [`Exif`] methods.
{{% note %}}
Metadata (EXIF, IPTC, XMP, etc.) is not preserved during image transformation. Use the [`Exif`] method with the _original_ image to extract EXIF metadata from JPEG or TIFF images.
{{% /note %}}
+### Process
+
+{{< new-in "0.119.0" >}}
+
+Process processes the image with the given specification. The specification can contain an optional action, one of `resize`, `crop`, `fit` or `fill`. This means that you can use this method instead of [`Resize`], [`Fit`], [`Fill`], or [`Crop`].
+
+See [Options](#image-processing-options) for available options.
+
+You can also use this method apply image processing that does not need any scaling, e.g. format conversions:
+
+```go-html-template
+{{/* Convert the image from JPG to PNG. */}}
+{{ $png := $jpg.Process "png" }}
+```
+
+Some more examples:
+
+```go-html-template
+{{/* Rotate the image 90 degrees counter-clockwise. */}}
+{{ $image := $image.Process "r90" }}
+
+{{/* Scaling actions. */}}
+{{ $image := $image.Process "resize 600x" }}
+{{ $image := $image.Process "crop 600x400" }}
+{{ $image := $image.Process "fit 600x400" }}
+{{ $image := $image.Process "fill 600x400" }}
+```
+
+
### Resize
Resize an image to the specified width and/or height.
@@ -477,6 +506,7 @@ hugo --gc
[github.com/disintegration/imaging]: <https://github.com/disintegration/imaging#image-resizing>
[Smartcrop]: <https://github.com/muesli/smartcrop#smartcrop>
[Exif]: <https://en.wikipedia.org/wiki/Exif>
+[`Process`]: #process
[`Colors`]: #colors
[`Crop`]: #crop
[`Exif`]: #exif
diff --git a/helpers/general.go b/helpers/general.go
index f8f273397..e484b92f0 100644
--- a/helpers/general.go
+++ b/helpers/general.go
@@ -53,18 +53,6 @@ func TCPListen() (net.Listener, *net.TCPAddr, error) {
}
l.Close()
return nil, nil, fmt.Errorf("unable to obtain a valid tcp port: %v", addr)
-
-}
-
-// InStringArray checks if a string is an element of a slice of strings
-// and returns a boolean value.
-func InStringArray(arr []string, el string) bool {
- for _, v := range arr {
- if v == el {
- return true
- }
- }
- return false
}
// FirstUpper returns a string with the first character as upper case.
diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go
index ce415a824..c2a14c3eb 100644
--- a/hugolib/site_output_test.go
+++ b/hugolib/site_output_test.go
@@ -20,12 +20,12 @@ import (
"testing"
qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/resources/kinds"
"github.com/spf13/afero"
- "github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/output"
)
@@ -152,7 +152,7 @@ Len Pages: {{ .Kind }} {{ len .Site.RegularPages }} Page Number: {{ .Paginator.P
// There is currently always a JSON output to make it simpler ...
altFormats := lenOut - 1
- hasHTML := helpers.InStringArray(outputs, "html")
+ hasHTML := hstrings.InSlice(outputs, "html")
b.AssertFileContent("public/index.json",
"List JSON",
fmt.Sprintf("Alt formats: %d", altFormats),
@@ -205,7 +205,7 @@ Len Pages: {{ .Kind }} {{ len .Site.RegularPages }} Page Number: {{ .Paginator.P
b.Assert(json.RelPermalink(), qt.Equals, "/blog/index.json")
b.Assert(json.Permalink(), qt.Equals, "http://example.com/blog/index.json")
- if helpers.InStringArray(outputs, "cal") {
+ if hstrings.InSlice(outputs, "cal") {
cal := of.Get("calendar")
b.Assert(cal, qt.Not(qt.IsNil))
b.Assert(cal.RelPermalink(), qt.Equals, "/blog/index.ics")
diff --git a/resources/errorResource.go b/resources/errorResource.go
index c8c32dfc3..d94207b79 100644
--- a/resources/errorResource.go
+++ b/resources/errorResource.go
@@ -100,6 +100,10 @@ func (e *errorResource) Width() int {
panic(e.ResourceError)
}
+func (e *errorResource) Process(spec string) (images.ImageResource, error) {
+ panic(e.ResourceError)
+}
+
func (e *errorResource) Crop(spec string) (images.ImageResource, error) {
panic(e.ResourceError)
}
diff --git a/resources/image.go b/resources/image.go
index ad2f9de32..5b030fde9 100644
--- a/resources/image.go
+++ b/resources/image.go
@@ -31,6 +31,7 @@ import (
color_extractor "github.com/marekm4/color-extractor"
+ "github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/identity"
@@ -198,75 +199,49 @@ func (i *imageResource) cloneWithUpdates(u *transformationUpdate) (baseResource,
}, nil
}
+var imageActions = []string{images.ActionResize, images.ActionCrop, images.ActionFit, images.ActionFill}
+
+// Process processes the image with the given spec.
+// The spec can contain an optional action, one of "resize", "crop", "fit" or "fill".
+// This makes this method a more flexible version that covers all of Resize, Crop, Fit and Fill,
+// but it also supports e.g. format conversions without any resize action.
+func (i *imageResource) Process(spec string) (images.ImageResource, error) {
+ var action string
+ options := strings.Fields(spec)
+ for i, p := range options {
+ if hstrings.InSlicEqualFold(imageActions, p) {
+ action = p
+ options = append(options[:i], options[i+1:]...)
+ break
+ }
+ }
+ return i.processActionOptions(action, options)
+}
+
// Resize resizes the image to the specified width and height using the specified resampling
// 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) (images.ImageResource, error) {
- 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)
- })
+ return i.processActionSpec(images.ActionResize, spec)
}
// Crop the image to the specified dimensions without resizing using the given anchor point.
// Space delimited config, e.g. `200x300 TopLeft`.
func (i *imageResource) Crop(spec string) (images.ImageResource, error) {
- conf, err := i.decodeImageConfig("crop", spec)
- if err != nil {
- return nil, err
- }
-
- return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
- return i.Proc.ApplyFiltersFromConfig(src, conf)
- })
+ return i.processActionSpec(images.ActionCrop, spec)
}
// Fit scales down the image using the specified resample filter to fit the specified
// maximum width and height.
func (i *imageResource) Fit(spec string) (images.ImageResource, error) {
- 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)
- })
+ return i.processActionSpec(images.ActionFit, spec)
}
// Fill scales the image to the smallest possible size that will cover the specified dimensions,
// crops the resized image to the specified dimensions using the given anchor point.
// Space delimited config, e.g. `200x300 TopLeft`.
func (i *imageResource) Fill(spec string) (images.ImageResource, error) {
- conf, err := i.decodeImageConfig("fill", spec)
- if err != nil {
- return nil, err
- }
-
- img, err := i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
- return i.Proc.ApplyFiltersFromConfig(src, conf)
- })
-
- if err != nil {
- return nil, err
- }
-
- if conf.Anchor == 0 && img.Width() == 0 || img.Height() == 0 {
- // See https://github.com/gohugoio/hugo/issues/7955
- // Smartcrop fails silently in some rare cases.
- // Fall back to a center fill.
- conf.Anchor = gift.CenterAnchor
- conf.AnchorStr = "center"
- return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
- return i.Proc.ApplyFiltersFromConfig(src, conf)
- })
- }
-
- return img, err
+ return i.processActionSpec(images.ActionFill, spec)
}
func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) {
@@ -286,6 +261,39 @@ func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) {
})
}
+func (i *imageResource) processActionSpec(action, spec string) (images.ImageResource, error) {
+ return i.processActionOptions(action, strings.Fields(spec))
+}
+
+func (i *imageResource) processActionOptions(action string, options []string) (images.ImageResource, error) {
+ conf, err := images.DecodeImageConfig(action, options, i.Proc.Cfg, i.Format)
+ if err != nil {
+ return nil, err
+ }
+
+ img, err := i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
+ return i.Proc.ApplyFiltersFromConfig(src, conf)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ if action == images.ActionFill {
+ if conf.Anchor == 0 && img.Width() == 0 || img.Height() == 0 {
+ // See https://github.com/gohugoio/hugo/issues/7955
+ // Smartcrop fails silently in some rare cases.
+ // Fall back to a center fill.
+ conf.Anchor = gift.CenterAnchor
+ conf.AnchorStr = "center"
+ return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
+ return i.Proc.ApplyFiltersFromConfig(src, conf)
+ })
+ }
+ }
+
+ return img, nil
+}
+
// Serialize image processing. The imaging library spins up its own set of Go routines,
// so there is not much to gain from adding more load to the mix. That
// can even have negative effect in low resource scenarios.
@@ -362,7 +370,8 @@ func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src im
}
func (i *imageResource) decodeImageConfig(action, spec string) (images.ImageConfig, error) {
- conf, err := images.DecodeImageConfig(action, spec, i.Proc.Cfg, i.Format)
+ options := strings.Fields(spec)
+ conf, err := images.DecodeImageConfig(action, options, i.Proc.Cfg, i.Format)
if err != nil {
return conf, err
}
diff --git a/resources/image_test.go b/resources/image_test.go
index 751ef3f5d..96cc07b3b 100644
--- a/resources/image_test.go
+++ b/resources/image_test.go
@@ -84,10 +84,7 @@ func TestImageTransformBasic(t *testing.T) {
fileCache := spec.FileCaches.ImageCache().Fs
assertWidthHeight := func(img images.ImageResource, w, h int) {
- c.Helper()
- c.Assert(img, qt.Not(qt.IsNil))
- c.Assert(img.Width(), qt.Equals, w)
- c.Assert(img.Height(), qt.Equals, h)
+ assertWidthHeight(c, img, w, h)
}
colors, err := image.Colors()
@@ -164,6 +161,45 @@ func TestImageTransformBasic(t *testing.T) {
c.Assert(cropped, qt.Equals, croppedAgain)
}
+func TestImageProcess(t *testing.T) {
+ c := qt.New(t)
+ _, img := fetchSunset(c)
+ resized, err := img.Process("resiZe 300x200")
+ c.Assert(err, qt.IsNil)
+ assertWidthHeight(c, resized, 300, 200)
+ rotated, err := resized.Process("R90")
+ c.Assert(err, qt.IsNil)
+ assertWidthHeight(c, rotated, 200, 300)
+ converted, err := img.Process("png")
+ c.Assert(err, qt.IsNil)
+ c.Assert(converted.MediaType().Type, qt.Equals, "image/png")
+
+ checkProcessVsMethod := func(action, spec string) {
+ var expect images.ImageResource
+ var err error
+ switch action {
+ case images.ActionCrop:
+ expect, err = img.Crop(spec)
+ case images.ActionFill:
+ expect, err = img.Fill(spec)
+ case images.ActionFit:
+ expect, err = img.Fit(spec)
+ case images.ActionResize:
+ expect, err = img.Resize(spec)
+ }
+ c.Assert(err, qt.IsNil)
+ got, err := img.Process(spec + " " + action)
+ c.Assert(err, qt.IsNil)
+ assertWidthHeight(c, got, expect.Width(), expect.Height())
+ c.Assert(got.MediaType(), qt.Equals, expect.MediaType())
+ }
+
+ checkProcessVsMethod(images.ActionCrop, "300x200 topleFt")
+ checkProcessVsMethod(images.ActionFill, "300x200 topleft")
+ checkProcessVsMethod(images.ActionFit, "300x200 png")
+ checkProcessVsMethod(images.ActionResize, "300x R90")
+}
+
func TestImageTransformFormat(t *testing.T) {
c := qt.New(t)
@@ -852,3 +888,10 @@ func BenchmarkResizeParallel(b *testing.B) {
}
})
}
+
+func assertWidthHeight(c *qt.C, img images.ImageResource, w, h int) {
+ c.Helper()
+ c.Assert(img, qt.Not(qt.IsNil))
+ c.Assert(img.Width(), qt.Equals, w)
+ c.Assert(img.Height(), qt.Equals, h)
+}
diff --git a/resources/images/config.go b/resources/images/config.go
index a3ca0c359..186f8fa6b 100644
--- a/resources/images/config.go
+++ b/resources/images/config.go
@@ -14,6 +14,7 @@
package images
import (
+ "errors"
"fmt"
"image/color"
"strconv"
@@ -24,13 +25,18 @@ import (
"github.com/gohugoio/hugo/media"
"github.com/mitchellh/mapstructure"
- "errors"
-
"github.com/bep/gowebp/libwebp/webpoptions"
"github.com/disintegration/gift"
)
+const (
+ ActionResize = "resize"
+ ActionCrop = "crop"
+ ActionFit = "fit"
+ ActionFill = "fill"
+)
+
var (
imageFormats = map[string]Format{
".jpg": JPEG,
@@ -90,7 +96,6 @@ var hints = map[string]webpoptions.EncodingPreset{
}
var imageFilters = map[string]gift.Resampling{
-
strings.ToLower("NearestNeighbor"): gift.NearestNeighborResampling,
strings.ToLower("Box"): gift.BoxResampling,
strings.ToLower("Linear"): gift.LinearResampling,
@@ -194,23 +199,23 @@ func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, Ima
return nil, fmt.Errorf("failed to decode media types: %w", err)
}
return ns, nil
-
}
-func DecodeImageConfig(action, config string, defaults *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal], sourceFormat Format) (ImageConfig, error) {
+func DecodeImageConfig(action string, options []string, defaults *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal], sourceFormat Format) (ImageConfig, error) {
var (
c ImageConfig = GetDefaultImageConfig(action, defaults)
err error
)
+ action = strings.ToLower(action)
+
c.Action = action
- if config == "" {
- return c, errors.New("image config cannot be empty")
+ if options == nil {
+ return c, errors.New("image options cannot be empty")
}
- parts := strings.Fields(config)
- for _, part := range parts {
+ for _, part := range options {
part = strings.ToLower(part)
if part == smartCropIdentifier {
@@ -272,19 +277,21 @@ func DecodeImageConfig(action, config string, defaults *config.ConfigNamespace[I
}
switch c.Action {
- case "crop", "fill", "fit":
+ case ActionCrop, ActionFill, ActionFit:
if c.Width == 0 || c.Height == 0 {
return c, errors.New("must provide Width and Height")
}
- case "resize":
+ case ActionResize:
if c.Width == 0 && c.Height == 0 {
return c, errors.New("must provide Width or Height")
}
default:
- return c, fmt.Errorf("BUG: unknown action %q encountered while decoding image configuration", c.Action)
+ if c.Width != 0 || c.Height != 0 {
+ return c, errors.New("width or height are not supported for this action")
+ }
}
- if c.FilterStr == "" {
+ if action != "" && c.FilterStr == "" {
c.FilterStr = defaults.Config.Imaging.ResampleFilter
c.Filter = defaults.Config.ResampleFilter
}
@@ -293,7 +300,7 @@ func DecodeImageConfig(action, config string, defaults *config.ConfigNamespace[I
c.Hint = webpoptions.EncodingPresetPhoto
}
- if c.AnchorStr == "" {
+ if action != "" && c.AnchorStr == "" {
c.AnchorStr = defaults.Config.Imaging.Anchor
c.Anchor = defaults.Config.Anchor
}
@@ -391,7 +398,7 @@ func (i ImageConfig) GetKey(format Format) string {
k += "_" + i.FilterStr
- if strings.EqualFold(i.Action, "fill") || strings.EqualFold(i.Action, "crop") {
+ if i.Action == ActionFill || i.Action == ActionCrop {
k += "_" + anchor
}
@@ -437,7 +444,6 @@ func (i *ImagingConfigInternal) Compile(externalCfg *ImagingConfig) error {
i.ResampleFilter = filter
return nil
-
}
// ImagingConfig contains default image processing configuration. This will be fetched
@@ -487,7 +493,6 @@ func (cfg *ImagingConfig) init() error {
}
type ExifConfig struct {
-
// Regexp matching the Exif fields you want from the (massive) set of Exif info
// available. As we cache this info to disk, this is for performance and
// disk space reasons more than anything.
diff --git a/resources/images/config_test.go b/resources/images/config_test.go
index 2e0d6635d..86f70c1bf 100644
--- a/resources/images/config_test.go
+++ b/resources/images/config_test.go
@@ -106,7 +106,7 @@ func TestDecodeImageConfig(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- result, err := DecodeImageConfig(this.action, this.in, cfg, PNG)
+ result, err := DecodeImageConfig(this.action, strings.Fields(this.in), cfg, PNG)
if b, ok := this.expect.(bool); ok && !b {
if err == nil {
t.Errorf("[%d] parseImageConfig didn't return an expected error", i)
diff --git a/resources/images/image.go b/resources/images/image.go
index 672e8578f..714d0e26d 100644
--- a/resources/images/image.go
+++ b/resources/images/image.go
@@ -14,6 +14,7 @@
package images
import (
+ "errors"
"fmt"
"image"
"image/color"
@@ -35,8 +36,6 @@ import (
"golang.org/x/image/bmp"
"golang.org/x/image/tiff"
- "errors"
-
"github.com/gohugoio/hugo/common/hugio"
)
@@ -245,7 +244,11 @@ func (p *ImageProcessor) ApplyFiltersFromConfig(src image.Image, conf ImageConfi
case "fit":
filters = append(filters, gift.ResizeToFit(conf.Width, conf.Height, conf.Filter))
default:
- return nil, fmt.Errorf("unsupported action: %q", conf.Action)
+
+ }
+
+ if len(filters) == 0 {
+ return p.resolveSrc(src, conf.TargetFormat), nil
}
img, err := p.doFilter(src, conf.TargetFormat, filters...)
@@ -260,8 +263,17 @@ func (p *ImageProcessor) Filter(src image.Image, filters ...gift.Filter) (image.
return p.doFilter(src, 0, filters...)
}
-func (p *ImageProcessor) doFilter(src image.Image, targetFormat Format, filters ...gift.Filter) (image.Image, error) {
+func (p *ImageProcessor) resolveSrc(src image.Image, targetFormat Format) image.Image {
+ if giph, ok := src.(Giphy); ok {
+ g := giph.GIF()
+ if len(g.Image) < 2 || (targetFormat == 0 || targetFormat != GIF) {
+ src = g.Image[0]
+ }
+ }
+ return src
+}
+func (p *ImageProcessor) doFilter(src image.Image, targetFormat Format, filters ...gift.Filter) (image.Image, error) {
filter := gift.New(filters...)
if giph, ok := src.(Giphy); ok {
diff --git a/resources/images/image_resource.go b/resources/images/image_resource.go
index dcd2b4741..be40418b1 100644
--- a/resources/images/image_resource.go
+++ b/resources/images/image_resource.go
@@ -33,6 +33,9 @@ type ImageResourceOps interface {
// Width returns the width of the Image.
Width() int
+ // Process applies the given image processing options to the image.
+ Process(spec string) (ImageResource, error)
+
// Crop an image to match the given dimensions without resizing.
// You must provide both width and height.
// Use the anchor option to change the crop box anchor point.
diff --git a/resources/page/permalinks.go b/resources/page/permalinks.go
index ee9608dac..4577f5240 100644
--- a/resources/page/permalinks.go
+++ b/resources/page/permalinks.go
@@ -14,6 +14,7 @@
package page
import (
+ "errors"
"fmt"
"os"
"path"
@@ -23,8 +24,7 @@ import (
"strings"
"time"
- "errors"
-
+ "github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/resources/kinds"
@@ -396,7 +396,6 @@ func (l PermalinkExpander) toSliceFunc(cut string) func(s []string) []string {
}
return s[n1:n2]
}
-
}
var permalinksKindsSupport = []string{kinds.KindPage, kinds.KindSection, kinds.KindTaxonomy, kinds.KindTerm}
@@ -425,7 +424,7 @@ func DecodePermalinksConfig(m map[string]any) (map[string]map[string]string, err
// [permalinks.key]
// xyz = ???
- if helpers.InStringArray(permalinksKindsSupport, k) {
+ if hstrings.InSlice(permalinksKindsSupport, k) {
// TODO: warn if we overwrite an already set value
for k2, v2 := range v {
switch v2 := v2.(type) {
diff --git a/resources/transform.go b/resources/transform.go
index eb7d8fb0b..0c38345ad 100644
--- a/resources/transform.go
+++ b/resources/transform.go
@@ -195,6 +195,10 @@ func (r resourceAdapter) cloneTo(targetPath string) resource.Resource {
return &r
}
+func (r *resourceAdapter) Process(spec string) (images.ImageResource, error) {
+ return r.getImageOps().Process(spec)
+}
+
func (r *resourceAdapter) Crop(spec string) (images.ImageResource, error) {
return r.getImageOps().Crop(spec)
}
@@ -287,7 +291,6 @@ func (r resourceAdapter) Transform(t ...ResourceTransformation) (ResourceTransfo
}
func (r resourceAdapter) TransformWithContext(ctx context.Context, t ...ResourceTransformation) (ResourceTransformer, error) {
-
r.resourceTransformations = &resourceTransformations{
transformations: append(r.transformations, t...),
}
@@ -459,7 +462,6 @@ func (r *resourceAdapter) transform(publish, setContent bool) error {
errMsg = ". Check your Hugo installation; you need the extended version to build SCSS/SASS with transpiler set to 'libsass'."
} else if tr.Key().Name == "tocss-dart" {
errMsg = ". You need dart-sass-embedded in your system $PATH."
-
} else if tr.Key().Name == "babel" {
errMsg = ". You need to install Babel, see https://gohugo.io/hugo-pipes/babel/"
}