Move from github.com/disintegration/gift to github.com/gohugoio/gift

With some performance improvements upstream, there's improvements in memory usage. Note that the benchmark below is full site builds with image processing:

```
goos: darwin
goarch: arm64
pkg: github.com/gohugoio/hugo/resources/images
cpu: Apple M1 Pro
               │ benchcmp.bench │  feat-forkgift-14397.bench   │
               │     sec/op     │   sec/op     vs base         │
ImageResize-10      74.60m ± 4%   73.75m ± 4%  ~ (p=0.093 n=6)

               │ benchcmp.bench │     feat-forkgift-14397.bench      │
               │      B/op      │     B/op      vs base              │
ImageResize-10     32.38Mi ± 1%   29.31Mi ± 1%  -9.49% (p=0.002 n=6)

               │ benchcmp.bench │     feat-forkgift-14397.bench     │
               │   allocs/op    │  allocs/op   vs base              │
ImageResize-10      12.53k ± 1%   12.05k ± 1%  -3.82% (p=0.002 n=6)
```

Closes #14397
This commit is contained in:
Bjørn Erik Pedersen
2026-01-19 11:50:06 +01:00
parent 18784711b5
commit 1a94731669
17 changed files with 97 additions and 34 deletions
+1 -1
View File
@@ -25,7 +25,6 @@ require (
github.com/bits-and-blooms/bitset v1.24.4
github.com/cespare/xxhash/v2 v2.3.0
github.com/clbanning/mxj/v2 v2.7.0
github.com/disintegration/gift v1.2.1
github.com/dustin/go-humanize v1.0.1
github.com/evanw/esbuild v0.27.2
github.com/fatih/color v1.18.0
@@ -36,6 +35,7 @@ require (
github.com/gobuffalo/flect v1.0.3
github.com/gobwas/glob v0.2.3
github.com/goccy/go-yaml v1.19.2
github.com/gohugoio/gift v0.1.0
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6
github.com/gohugoio/go-radix v1.2.0
github.com/gohugoio/hashstructure v0.6.0
+2 -2
View File
@@ -209,8 +209,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc=
github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -270,6 +268,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gohugoio/gift v0.1.0 h1:KDx+OBrSn/sXmnMdMLYocOLn35MrBpnVhgQghHxd7g8=
github.com/gohugoio/gift v0.1.0/go.mod h1:1Mrm5CjF33KpD749Dwj+UAjWZ3LC6cBXGuTMa5XwoP4=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6 h1:pxlAea9eRwuAnt/zKbGqlFO2ZszpIe24YpOVLf+N+4I=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6/go.mod h1:m5hu1im5Qc7LDycVLvee6MPobJiRLBYHklypFJR0/aE=
github.com/gohugoio/go-radix v1.2.0 h1:D5GTk8jIoeXirBSc2P4E4NdHKDrenk9k9N0ctU5Yrhg=
+1 -1
View File
@@ -31,7 +31,7 @@ import (
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/paths"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
"github.com/gohugoio/hugo/resources/images/meta"
"github.com/gohugoio/hugo/resources/internal"
+2 -2
View File
@@ -17,7 +17,7 @@ import (
"image"
"image/draw"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
)
var _ gift.Filter = (*autoOrientFilter)(nil)
@@ -38,7 +38,7 @@ type ImageFilterFromOrientationProvider interface {
AutoOrient(orientation int) gift.Filter
}
func (f autoOrientFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
func (f autoOrientFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) error {
panic("not supported")
}
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"github.com/gohugoio/hugo/media"
"github.com/mitchellh/mapstructure"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
)
const (
+3 -3
View File
@@ -17,7 +17,7 @@ import (
"image"
"image/draw"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
"github.com/makeworld-the-better-one/dither/v2"
)
@@ -62,8 +62,8 @@ var ditherMethodsOrdered = map[string]dither.OrderedDitherMatrix{
"vertical5x3": dither.Vertical5x3,
}
func (f ditherFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
gift.New().Draw(dst, f.ditherer.Dither(src))
func (f ditherFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) error {
return gift.New().Draw(dst, f.ditherer.Dither(src))
}
func (f ditherFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
+1 -1
View File
@@ -25,7 +25,7 @@ import (
"github.com/makeworld-the-better-one/dither/v2"
"github.com/mitchellh/mapstructure"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
"github.com/spf13/cast"
)
+1 -1
View File
@@ -30,7 +30,7 @@ import (
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/images/meta"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
"github.com/gohugoio/hugo/common/himage"
"github.com/gohugoio/hugo/common/hugio"
@@ -50,3 +50,57 @@ Orientation: {{ $orientation }}|eq 6: {{ eq $orientation 6 }}|Type: {{ printf "%
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", "Orientation: 6|eq 6: true|")
}
func BenchmarkImageResize(b *testing.B) {
files := `
-- content/p1/sunrise.jpg --
sourcefilename: ../../resources/testdata/sunrise.jpg
-- content/p1/index.md --
-- content/p2/sunrise.jpg --
sourcefilename: ../../resources/testdata/sunrise.jpg
-- content/p2/index.md --
-- content/p3/sunrise.jpg --
sourcefilename: ../../resources/testdata/sunrise.jpg
-- content/p3/index.md --
-- content/p4/sunrise.jpg --
sourcefilename: ../../resources/testdata/sunrise.jpg
-- content/p4/index.md --
-- content/p5/sunrise.jpg --
sourcefilename: ../../resources/testdata/sunrise.jpg
-- content/p5/index.md --
-- content/p6/sunrise.jpg --
sourcefilename: ../../resources/testdata/sunrise.jpg
-- content/p6/index.md --
-- content/p7/sunrise.jpg --
sourcefilename: ../../resources/testdata/sunrise.jpg
-- content/p7/index.md --
-- content/p8/sunrise.jpg --
sourcefilename: ../../resources/testdata/sunrise.jpg
-- content/p8/index.md --
-- content/p9/sunrise.jpg --
sourcefilename: ../../resources/testdata/sunrise.jpg
-- content/p9/index.md --
-- content/p10/sunrise.jpg --
sourcefilename: ../../resources/testdata/sunrise.jpg
-- content/p10/index.md --
-- layouts/home.html --
Home.
-- layouts/page.html --
Page.
{{ $image := .Resources.Get "sunrise.jpg" }}
{{ ($image.Process "resize 200x200").Publish }}
`
cfg := hugolib.IntegrationTestConfig{
T: b,
TxtarString: files,
}
for b.Loop() {
b.StopTimer()
builder := hugolib.NewIntegrationTestBuilder(cfg).Init()
b.StartTimer()
builder.Build()
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ import (
qt "github.com/frankban/quicktest"
"github.com/google/go-cmp/cmp"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/himage"
"github.com/gohugoio/hugo/common/hugio"
+8 -4
View File
@@ -6,7 +6,7 @@ import (
"image/color"
"image/draw"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
)
// maskFilter applies a mask image to a base image.
@@ -15,10 +15,10 @@ type maskFilter struct {
}
// Draw applies the mask to the base image.
func (f maskFilter) Draw(dst draw.Image, baseImage image.Image, options *gift.Options) {
func (f maskFilter) Draw(dst draw.Image, baseImage image.Image, options *gift.Options) error {
maskImage, err := f.mask.DecodeImage()
if err != nil {
panic(fmt.Sprintf("failed to decode image: %s", err))
return fmt.Errorf("failed to decode image: %s", err)
}
// Ensure the mask is the same size as the base image
@@ -54,7 +54,11 @@ func (f maskFilter) Draw(dst draw.Image, baseImage image.Image, options *gift.Op
draw.DrawMask(outputImage, baseBounds, baseImage, image.Point{}, alphaMask, image.Point{}, draw.Over)
// Copy the result to the destination
gift.New().Draw(dst, outputImage)
if err := gift.New().Draw(dst, outputImage); err != nil {
return fmt.Errorf("failed to draw masked image: %s", err)
}
return nil
}
// Bounds returns the bounds of the resulting image.
+3 -2
View File
@@ -18,7 +18,7 @@ import (
"image/color"
"image/draw"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
)
var _ gift.Filter = (*opacityFilter)(nil)
@@ -27,11 +27,12 @@ type opacityFilter struct {
opacity float32
}
func (f opacityFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
func (f opacityFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) error {
// 0 is fully transparent and 255 is opaque.
alpha := uint8(f.opacity * 255)
mask := image.NewUniform(color.Alpha{alpha})
draw.DrawMask(dst, dst.Bounds(), src, image.Point{}, mask, image.Point{}, draw.Over)
return nil
}
func (f opacityFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
+7 -6
View File
@@ -18,7 +18,7 @@ import (
"image"
"image/draw"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
)
var _ gift.Filter = (*overlayFilter)(nil)
@@ -28,14 +28,15 @@ type overlayFilter struct {
x, y int
}
func (f overlayFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
func (f overlayFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) error {
overlaySrc, err := f.src.DecodeImage()
if err != nil {
panic(fmt.Sprintf("failed to decode image: %s", err))
return fmt.Errorf("failed to decode image: %s", err)
}
gift.New().Draw(dst, src)
gift.New().DrawAt(dst, overlaySrc, image.Pt(f.x, f.y), gift.OverOperator)
if err := gift.New().Draw(dst, src); err != nil {
return err
}
return gift.New().DrawAt(dst, overlaySrc, image.Pt(f.x, f.y), gift.OverOperator)
}
func (f overlayFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
+6 -4
View File
@@ -18,7 +18,7 @@ import (
"image/color"
"image/draw"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
)
var _ gift.Filter = (*paddingFilter)(nil)
@@ -28,7 +28,7 @@ type paddingFilter struct {
ccolor color.Color // canvas color
}
func (f paddingFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
func (f paddingFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) error {
w := src.Bounds().Dx() + f.left + f.right
h := src.Bounds().Dy() + f.top + f.bottom
@@ -41,8 +41,10 @@ func (f paddingFilter) Draw(dst draw.Image, src image.Image, options *gift.Optio
i := image.NewRGBA(image.Rect(0, 0, w, h))
draw.Draw(i, i.Bounds(), image.NewUniform(f.ccolor), image.Point{}, draw.Src)
gift.New().Draw(dst, i)
gift.New().DrawAt(dst, src, image.Pt(f.left, f.top), gift.OverOperator)
if err := gift.New().Draw(dst, i); err != nil {
return err
}
return gift.New().DrawAt(dst, src, image.Pt(f.left, f.top), gift.OverOperator)
}
func (f paddingFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
+2 -2
View File
@@ -17,7 +17,7 @@ import (
"image"
"image/draw"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
)
var _ ImageProcessSpecProvider = (*processFilter)(nil)
@@ -30,7 +30,7 @@ type processFilter struct {
spec string
}
func (f processFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
func (f processFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) error {
panic("not supported")
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
"image"
"math"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
"github.com/muesli/smartcrop"
)
+3 -2
View File
@@ -20,7 +20,7 @@ import (
"io"
"strings"
"github.com/disintegration/gift"
"github.com/gohugoio/gift"
"github.com/gohugoio/hugo/common/hugio"
"golang.org/x/image/font"
@@ -42,7 +42,7 @@ type textFilter struct {
fontSource hugio.ReadSeekCloserProvider
}
func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) error {
// Load and parse font
ttf := goregular.TTF
if f.fontSource != nil {
@@ -143,6 +143,7 @@ func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options)
d.DrawString(line)
y = y + fontHeight + f.linespacing
}
return nil
}
func (f textFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {