From f027dd62352d1c160bc67e64bd3207cd7247aac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 16 Dec 2020 13:52:47 +0100 Subject: [PATCH] images: Add images.Overlay filter This allows for constructs ala: ``` {{ $overlay := $img.Filter ($logo | images.Overlay 50 50 )}} ``` Or: ``` {{ $logoFilter := ($logo | images.Overlay 50 50 ) }} {{ $overlay := $img | images.Filter $logoFilter }} ``` Which will overlay the logo in the top left corner (x=50, y=50) of `$img`. Fixes #8057 --- resources/image.go | 6 +++-- resources/images/filters.go | 15 ++++++++++++ resources/images/image.go | 5 ++++ resources/images/overlay.go | 37 +++++++++++++++++++++++++++++ resources/resource/resourcetypes.go | 5 ++++ resources/transform.go | 5 ++++ 6 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 resources/images/overlay.go diff --git a/resources/image.go b/resources/image.go index ed303613f79..0396c2208e8 100644 --- a/resources/image.go +++ b/resources/image.go @@ -242,7 +242,7 @@ func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src im errOp := conf.Action errPath := i.getSourceFilename() - src, err := i.decodeSource() + src, err := i.DecodeImage() if err != nil { return nil, nil, &os.PathError{Op: errOp, Path: errPath, Err: err} } @@ -324,7 +324,9 @@ func (i *imageResource) decodeImageConfig(action, spec string) (images.ImageConf return conf, nil } -func (i *imageResource) decodeSource() (image.Image, error) { +// DecodeImage decodes the image source into an Image. +// This an internal method and may change. +func (i *imageResource) DecodeImage() (image.Image, error) { f, err := i.ReadSeekCloser() if err != nil { return nil, _errors.Wrap(err, "failed to open image for decode") diff --git a/resources/images/filters.go b/resources/images/filters.go index dd7b5834563..ba65e980dcb 100644 --- a/resources/images/filters.go +++ b/resources/images/filters.go @@ -16,6 +16,7 @@ package images import ( "github.com/disintegration/gift" + "github.com/pkg/errors" "github.com/spf13/cast" ) @@ -25,6 +26,20 @@ const filterAPIVersion = 0 type Filters struct { } +// Overlay creates a filter that overlays an image onto +// another at position x y. +func (*Filters) Overlay(x, y interface{}, id ImageDecoder) (gift.Filter, error) { + src, err := id.DecodeImage() + if err != nil { + return nil, errors.Wrap(err, "failed to decode image") + } + + return filter{ + Options: newFilterOpts(x, y), // TODO1 src + Filter: &overlayFilter{src: src, x: cast.ToInt(x), y: cast.ToInt(y)}, + }, nil +} + // 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 { diff --git a/resources/images/image.go b/resources/images/image.go index 88eed2f7eb7..7cea0a03706 100644 --- a/resources/images/image.go +++ b/resources/images/image.go @@ -325,3 +325,8 @@ func IsOpaque(img image.Image) bool { return false } + +// ImageDecoder decodes an image. +type ImageDecoder interface { + DecodeImage() (image.Image, error) +} diff --git a/resources/images/overlay.go b/resources/images/overlay.go new file mode 100644 index 00000000000..02dcc16c3a3 --- /dev/null +++ b/resources/images/overlay.go @@ -0,0 +1,37 @@ +// Copyright 2020 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 ( + "image" + "image/draw" + + "github.com/disintegration/gift" +) + +var _ gift.Filter = (*overlayFilter)(nil) + +type overlayFilter struct { + src image.Image + x, y int +} + +func (f *overlayFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) { + gift.New().Draw(dst, src) + gift.New().DrawAt(dst, f.src, image.Pt(f.x, f.y), gift.OverOperator) +} + +func (f *overlayFilter) Bounds(srcBounds image.Rectangle) image.Rectangle { + return image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) +} diff --git a/resources/resource/resourcetypes.go b/resources/resource/resourcetypes.go index f42372fa396..206ce8de8d0 100644 --- a/resources/resource/resourcetypes.go +++ b/resources/resource/resourcetypes.go @@ -14,6 +14,8 @@ package resource import ( + "image" + "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/media" @@ -59,6 +61,9 @@ type ImageOps interface { Resize(spec string) (Image, error) Filter(filters ...interface{}) (Image, error) Exif() *exif.Exif + + // Internal + DecodeImage() (image.Image, error) } type ResourceTypeProvider interface { diff --git a/resources/transform.go b/resources/transform.go index a9ec8467137..9007ead180f 100644 --- a/resources/transform.go +++ b/resources/transform.go @@ -16,6 +16,7 @@ package resources import ( "bytes" "fmt" + "image" "io" "path" "strings" @@ -264,6 +265,10 @@ func (r *resourceAdapter) Width() int { return r.getImageOps().Width() } +func (r *resourceAdapter) DecodeImage() (image.Image, error) { + return r.getImageOps().DecodeImage() +} + func (r *resourceAdapter) getImageOps() resource.ImageOps { img, ok := r.target.(resource.ImageOps) if !ok {