mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
images: Add images.Overlay filter
This allows for constructs ala: ``` {{ $overlay := $img.Filter (images.Overlay $logo 50 50 )}} ``` Or: ``` {{ $logoFilter := (images.Overlay $logo 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 Fixes #4595 Updates #6731
This commit is contained in:
parent
a2d146ec32
commit
3ba147e702
12 changed files with 101 additions and 2 deletions
|
@ -17,6 +17,30 @@ toc: true
|
||||||
|
|
||||||
See [images.Filter](#filter) for how to apply these filters to an image.
|
See [images.Filter](#filter) for how to apply these filters to an image.
|
||||||
|
|
||||||
|
### Overlay
|
||||||
|
|
||||||
|
{{< new-in "0.80.0" >}}
|
||||||
|
|
||||||
|
{{% funcsig %}}
|
||||||
|
images.Overlay SRC X Y
|
||||||
|
{{% /funcsig %}}
|
||||||
|
|
||||||
|
Overlay creates a filter that overlays the source image at position x y, e.g:
|
||||||
|
|
||||||
|
|
||||||
|
```go-html-template
|
||||||
|
{{ $logoFilter := (images.Overlay $logo 50 50 ) }}
|
||||||
|
{{ $img := $img | images.Filter $logoFilter }}
|
||||||
|
```
|
||||||
|
|
||||||
|
A shorter version of the above, if you only need to apply the filter once:
|
||||||
|
|
||||||
|
```go-html-template
|
||||||
|
{{ $img := $img.Filter (images.Overlay $logo 50 50 )}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above will overlay `$logo` in the upper left corner of `$img` (at position `x=50, y=50`).
|
||||||
|
|
||||||
### Brightness
|
### Brightness
|
||||||
|
|
||||||
{{% funcsig %}}
|
{{% funcsig %}}
|
||||||
|
|
|
@ -242,7 +242,7 @@ func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src im
|
||||||
errOp := conf.Action
|
errOp := conf.Action
|
||||||
errPath := i.getSourceFilename()
|
errPath := i.getSourceFilename()
|
||||||
|
|
||||||
src, err := i.decodeSource()
|
src, err := i.DecodeImage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, &os.PathError{Op: errOp, Path: errPath, Err: err}
|
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
|
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()
|
f, err := i.ReadSeekCloser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, _errors.Wrap(err, "failed to open image for decode")
|
return nil, _errors.Wrap(err, "failed to open image for decode")
|
||||||
|
|
|
@ -533,6 +533,11 @@ func TestImageOperationsGolden(t *testing.T) {
|
||||||
fmt.Println(workDir)
|
fmt.Println(workDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gopher := fetchImageForSpec(spec, c, "gopher-hero8.png")
|
||||||
|
var err error
|
||||||
|
gopher, err = gopher.Resize("30x")
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
|
||||||
// Test PNGs with alpha channel.
|
// Test PNGs with alpha channel.
|
||||||
for _, img := range []string{"gopher-hero8.png", "gradient-circle.png"} {
|
for _, img := range []string{"gopher-hero8.png", "gradient-circle.png"} {
|
||||||
orig := fetchImageForSpec(spec, c, img)
|
orig := fetchImageForSpec(spec, c, img)
|
||||||
|
@ -589,6 +594,7 @@ func TestImageOperationsGolden(t *testing.T) {
|
||||||
f.Invert(),
|
f.Invert(),
|
||||||
f.Hue(22),
|
f.Hue(22),
|
||||||
f.Contrast(32.5),
|
f.Contrast(32.5),
|
||||||
|
f.Overlay(gopher.(images.ImageSource), 20, 30),
|
||||||
}
|
}
|
||||||
|
|
||||||
resized, err := orig.Fill("400x200 center")
|
resized, err := orig.Fill("400x200 center")
|
||||||
|
|
|
@ -25,6 +25,14 @@ const filterAPIVersion = 0
|
||||||
type Filters struct {
|
type Filters struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Overlay creates a filter that overlays src at position x y.
|
||||||
|
func (*Filters) Overlay(src ImageSource, x, y interface{}) gift.Filter {
|
||||||
|
return filter{
|
||||||
|
Options: newFilterOpts(src.Key(), x, y),
|
||||||
|
Filter: overlayFilter{src: src, x: cast.ToInt(x), y: cast.ToInt(y)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Brightness creates a filter that changes the brightness of an image.
|
// Brightness creates a filter that changes the brightness of an image.
|
||||||
// The percentage parameter must be in range (-100, 100).
|
// The percentage parameter must be in range (-100, 100).
|
||||||
func (*Filters) Brightness(percentage interface{}) gift.Filter {
|
func (*Filters) Brightness(percentage interface{}) gift.Filter {
|
||||||
|
|
|
@ -325,3 +325,9 @@ func IsOpaque(img image.Image) bool {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImageSource identifies and decodes an image.
|
||||||
|
type ImageSource interface {
|
||||||
|
DecodeImage() (image.Image, error)
|
||||||
|
Key() string
|
||||||
|
}
|
||||||
|
|
43
resources/images/overlay.go
Normal file
43
resources/images/overlay.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
|
||||||
|
"github.com/disintegration/gift"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ gift.Filter = (*overlayFilter)(nil)
|
||||||
|
|
||||||
|
type overlayFilter struct {
|
||||||
|
src ImageSource
|
||||||
|
x, y int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f overlayFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
|
||||||
|
overlaySrc, err := f.src.DecodeImage()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to decode image: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
gift.New().Draw(dst, src)
|
||||||
|
gift.New().DrawAt(dst, overlaySrc, 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())
|
||||||
|
}
|
|
@ -14,6 +14,8 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
"github.com/gohugoio/hugo/langs"
|
"github.com/gohugoio/hugo/langs"
|
||||||
"github.com/gohugoio/hugo/media"
|
"github.com/gohugoio/hugo/media"
|
||||||
|
@ -59,6 +61,9 @@ type ImageOps interface {
|
||||||
Resize(spec string) (Image, error)
|
Resize(spec string) (Image, error)
|
||||||
Filter(filters ...interface{}) (Image, error)
|
Filter(filters ...interface{}) (Image, error)
|
||||||
Exif() *exif.Exif
|
Exif() *exif.Exif
|
||||||
|
|
||||||
|
// Internal
|
||||||
|
DecodeImage() (image.Image, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceTypeProvider interface {
|
type ResourceTypeProvider interface {
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -16,6 +16,7 @@ package resources
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -264,6 +265,10 @@ func (r *resourceAdapter) Width() int {
|
||||||
return r.getImageOps().Width()
|
return r.getImageOps().Width()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *resourceAdapter) DecodeImage() (image.Image, error) {
|
||||||
|
return r.getImageOps().DecodeImage()
|
||||||
|
}
|
||||||
|
|
||||||
func (r *resourceAdapter) getImageOps() resource.ImageOps {
|
func (r *resourceAdapter) getImageOps() resource.ImageOps {
|
||||||
img, ok := r.target.(resource.ImageOps)
|
img, ok := r.target.(resource.ImageOps)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
Loading…
Reference in a new issue