Add images.Process filter

This allows for constructs like:

```
{{ $filters := slice (images.GaussianBlur 8) (images.Grayscale) (images.Process "jpg q30 resize 200x") }}
{{ $img = $img | images.Filter $filters }}
```

Note that the `action` option in `images.Process` is optional (`resize` in the example above), so you can use the above to just set the target format, e.g.:

```
{{ $filters := slice (images.GaussianBlur 8) (images.Grayscale) (images.Process "jpg") }}
{{ $img = $img | images.Filter $filters }}
```

Fixes #8439
This commit is contained in:
Bjørn Erik Pedersen
2023-09-23 11:45:17 +02:00
parent ef0e7149d6
commit 6a246d1152
6 changed files with 204 additions and 16 deletions

View File

@@ -206,15 +206,7 @@ var imageActions = []string{images.ActionResize, images.ActionCrop, images.Actio
// 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
}
}
action, options := i.resolveActionOptions(spec)
return i.processActionOptions(action, options)
}
@@ -245,7 +237,7 @@ func (i *imageResource) Fill(spec string) (images.ImageResource, error) {
}
func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) {
conf := images.GetDefaultImageConfig("filter", i.Proc.Cfg)
var conf images.ImageConfig
var gfilters []gift.Filter
@@ -253,14 +245,77 @@ func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) {
gfilters = append(gfilters, images.ToFilters(f)...)
}
var (
targetFormat images.Format
configSet bool
)
for _, f := range gfilters {
f = images.UnwrapFilter(f)
if specProvider, ok := f.(images.ImageProcessSpecProvider); ok {
action, options := i.resolveActionOptions(specProvider.ImageProcessSpec())
var err error
conf, err = images.DecodeImageConfig(action, options, i.Proc.Cfg, i.Format)
if err != nil {
return nil, err
}
configSet = true
if conf.TargetFormat != 0 {
targetFormat = conf.TargetFormat
// We only support one target format, but prefer the last one,
// so we keep going.
}
}
}
if !configSet {
conf = images.GetDefaultImageConfig("filter", i.Proc.Cfg)
}
conf.Action = "filter"
conf.Key = identity.HashString(gfilters)
conf.TargetFormat = i.Format
conf.TargetFormat = targetFormat
if conf.TargetFormat == 0 {
conf.TargetFormat = i.Format
}
return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
return i.Proc.Filter(src, gfilters...)
filters := gfilters
for j, f := range gfilters {
f = images.UnwrapFilter(f)
if specProvider, ok := f.(images.ImageProcessSpecProvider); ok {
processSpec := specProvider.ImageProcessSpec()
action, options := i.resolveActionOptions(processSpec)
conf, err := images.DecodeImageConfig(action, options, i.Proc.Cfg, i.Format)
if err != nil {
return nil, err
}
pFilters, err := i.Proc.FiltersFromConfig(src, conf)
if err != nil {
return nil, err
}
// Replace the filter with the new filters.
// This slice will be empty if this is just a format conversion.
filters = append(filters[:j], append(pFilters, filters[j+1:]...)...)
}
}
return i.Proc.Filter(src, filters...)
})
}
func (i *imageResource) resolveActionOptions(spec string) (string, []string) {
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 action, options
}
func (i *imageResource) processActionSpec(action, spec string) (images.ImageResource, error) {
return i.processActionOptions(action, strings.Fields(spec))
}