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
 
 {{% funcsig %}}
 
                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}
                }
        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")
 
                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.
        for _, img := range []string{"gopher-hero8.png", "gradient-circle.png"} {
                orig := fetchImageForSpec(spec, c, img)
                        f.Invert(),
                        f.Hue(22),
                        f.Contrast(32.5),
+                       f.Overlay(gopher.(images.ImageSource), 20, 30),
                }
 
                resized, err := orig.Fill("400x200 center")
 
 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.
 // The percentage parameter must be in range (-100, 100).
 func (*Filters) Brightness(percentage interface{}) gift.Filter {
 
 
        return false
 }
+
+// ImageSource identifies and decodes an image.
+type ImageSource interface {
+       DecodeImage() (image.Image, error)
+       Key() string
+}
 
--- /dev/null
+// 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())
+}
 
 package resource
 
 import (
+       "image"
+
        "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/langs"
        "github.com/gohugoio/hugo/media"
        Resize(spec string) (Image, error)
        Filter(filters ...interface{}) (Image, error)
        Exif() *exif.Exif
+
+       // Internal
+       DecodeImage() (image.Image, error)
 }
 
 type ResourceTypeProvider interface {
 
 import (
        "bytes"
        "fmt"
+       "image"
        "io"
        "path"
        "strings"
        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 {