From 1a94731669b9873d33b6d23d0ef35910f1bfe519 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 19 Jan 2026 11:50:06 +0100 Subject: [PATCH] Move from github.com/disintegration/gift to github.com/gohugoio/gift MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 --- go.mod | 2 +- go.sum | 4 +- resources/image.go | 2 +- resources/images/auto_orient.go | 4 +- resources/images/config.go | 2 +- resources/images/dither.go | 6 +-- resources/images/filters.go | 2 +- resources/images/image.go | 2 +- resources/images/images_integration_test.go | 54 +++++++++++++++++++++ resources/images/imagetesting/testing.go | 2 +- resources/images/mask.go | 12 +++-- resources/images/opacity.go | 5 +- resources/images/overlay.go | 13 ++--- resources/images/padding.go | 10 ++-- resources/images/process.go | 4 +- resources/images/smartcrop.go | 2 +- resources/images/text.go | 5 +- 17 files changed, 97 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index 3b94de170..3c9713546 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 1769cf08a..ef96f149e 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/resources/image.go b/resources/image.go index 573c22c9d..070727567 100644 --- a/resources/image.go +++ b/resources/image.go @@ -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" diff --git a/resources/images/auto_orient.go b/resources/images/auto_orient.go index ca4911e81..a57987c78 100644 --- a/resources/images/auto_orient.go +++ b/resources/images/auto_orient.go @@ -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") } diff --git a/resources/images/config.go b/resources/images/config.go index 3277d2a24..9475096ac 100644 --- a/resources/images/config.go +++ b/resources/images/config.go @@ -28,7 +28,7 @@ import ( "github.com/gohugoio/hugo/media" "github.com/mitchellh/mapstructure" - "github.com/disintegration/gift" + "github.com/gohugoio/gift" ) const ( diff --git a/resources/images/dither.go b/resources/images/dither.go index 19d7e088d..825b06e87 100644 --- a/resources/images/dither.go +++ b/resources/images/dither.go @@ -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 { diff --git a/resources/images/filters.go b/resources/images/filters.go index 5fb5dae1f..1bf88645a 100644 --- a/resources/images/filters.go +++ b/resources/images/filters.go @@ -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" ) diff --git a/resources/images/image.go b/resources/images/image.go index 89b15a4f9..1e78e8bbe 100644 --- a/resources/images/image.go +++ b/resources/images/image.go @@ -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" diff --git a/resources/images/images_integration_test.go b/resources/images/images_integration_test.go index d66fa2366..fade5ae3e 100644 --- a/resources/images/images_integration_test.go +++ b/resources/images/images_integration_test.go @@ -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() + } +} diff --git a/resources/images/imagetesting/testing.go b/resources/images/imagetesting/testing.go index 979ca2fc5..418c21685 100644 --- a/resources/images/imagetesting/testing.go +++ b/resources/images/imagetesting/testing.go @@ -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" diff --git a/resources/images/mask.go b/resources/images/mask.go index 5ce7c5d43..b3688ef90 100644 --- a/resources/images/mask.go +++ b/resources/images/mask.go @@ -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. diff --git a/resources/images/opacity.go b/resources/images/opacity.go index 482476c5b..b2eaf5f12 100644 --- a/resources/images/opacity.go +++ b/resources/images/opacity.go @@ -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 { diff --git a/resources/images/overlay.go b/resources/images/overlay.go index 780e28fd1..91934ddb9 100644 --- a/resources/images/overlay.go +++ b/resources/images/overlay.go @@ -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 { diff --git a/resources/images/padding.go b/resources/images/padding.go index 4399312f8..73341b89e 100644 --- a/resources/images/padding.go +++ b/resources/images/padding.go @@ -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 { diff --git a/resources/images/process.go b/resources/images/process.go index fb2e995ce..ed21fce5f 100644 --- a/resources/images/process.go +++ b/resources/images/process.go @@ -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") } diff --git a/resources/images/smartcrop.go b/resources/images/smartcrop.go index 4f706df67..817df738a 100644 --- a/resources/images/smartcrop.go +++ b/resources/images/smartcrop.go @@ -17,7 +17,7 @@ import ( "image" "math" - "github.com/disintegration/gift" + "github.com/gohugoio/gift" "github.com/muesli/smartcrop" ) diff --git a/resources/images/text.go b/resources/images/text.go index 11c6eac41..ae2831638 100644 --- a/resources/images/text.go +++ b/resources/images/text.go @@ -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 { -- 2.39.5