From b38054b29cede010080db904683554515b221720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deluan=20Quint=C3=A3o?= Date: Mon, 22 Jun 2026 16:33:01 -0400 Subject: [PATCH] perf(artwork): faster image resize + update gen2brain/webp to v0.6.0 (#5652) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(artwork): convert decoded images to a fast-path type before resizing x/image/draw's CatmullRom scaler only has optimized paths for *image.RGBA, *image.NRGBA, *image.Gray and *image.YCbCr. Other concrete types — notably *image.NYCbCrA (from WebP) and *image.Paletted (indexed PNGs) — fall back to a generic per-pixel At()/RGBA() loop that is several times slower. Convert such images to *image.RGBA once before scaling; fast-path types are returned unchanged. This makes resize performance independent of which decoder wins the image.Decode("webp") registration, and also speeds up indexed PNGs. Signed-off-by: Deluan * chore(deps): update gen2brain/webp to v0.6.0 v0.6.0 replaces the wazero WASM runtime with a self-contained wasm2go-transpiled WebP decoder/encoder. This drops the webp -> wazero dependency edge (wazero is still used by the plugin system) and makes the WASM-only build path (32-bit / nodynamic) faster and far lighter on allocations. Signed-off-by: Deluan * perf(artwork): defer fast-path conversion until a resize is needed Move toFastScaleType to just before the CatmullRom.Scale call, after the no-upscale early return. Previously the conversion ran right after decode, so a request for a size >= the source dimensions would allocate and walk a full RGBA copy only to discard it when resizeStaticImage returns nil. The resize path is unchanged; the no-op path drops ~30-40% time and up to ~79% memory for large indexed/WebP artwork. Signed-off-by: Deluan --------- Signed-off-by: Deluan --- core/artwork/reader_resized.go | 17 +++++++++++++++++ go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/core/artwork/reader_resized.go b/core/artwork/reader_resized.go index 08f42f130..cd16cbada 100644 --- a/core/artwork/reader_resized.go +++ b/core/artwork/reader_resized.go @@ -132,6 +132,22 @@ func (a *resizedArtworkReader) resizeImage(ctx context.Context, reader io.Reader return resizeStaticImage(data, a.size, a.square) } +// toFastScaleType converts images whose concrete type has no optimized scaler +// in x/image/draw (e.g. *image.NYCbCrA from WebP, *image.Paletted from indexed +// PNGs) into *image.RGBA, which has a fast path. Without this, CatmullRom.Scale +// falls back to a generic per-pixel At()/RGBA() loop that is several times +// slower. Fast-path types are returned unchanged. +func toFastScaleType(img image.Image) image.Image { + switch img.(type) { + case *image.RGBA, *image.NRGBA, *image.Gray, *image.YCbCr: + return img + default: + rgba := image.NewRGBA(img.Bounds()) + draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Src) + return rgba + } +} + func resizeStaticImage(data []byte, size int, square bool) (io.Reader, int, error) { original, format, err := image.Decode(bytes.NewReader(data)) if err != nil { @@ -169,6 +185,7 @@ func resizeStaticImage(data []byte, size int, square bool) (io.Reader, int, erro dst = image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) dstRect = dst.Bounds() } + original = toFastScaleType(original) xdraw.CatmullRom.Scale(dst, dstRect, original, bounds, draw.Src, nil) buf := bufPool.Get().(*bytes.Buffer) diff --git a/go.mod b/go.mod index abb3e89f0..9bf7358ed 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/extism/go-sdk v1.7.1 github.com/fatih/structs v1.1.0 - github.com/gen2brain/webp v0.5.5 + github.com/gen2brain/webp v0.6.0 github.com/go-chi/chi/v5 v5.3.0 github.com/go-chi/cors v1.2.2 github.com/go-chi/httprate v0.15.0 diff --git a/go.sum b/go.sum index fd254bebb..682a9cfd0 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/gen2brain/webp v0.5.5 h1:MvQR75yIPU/9nSqYT5h13k4URaJK3gf9tgz/ksRbyEg= -github.com/gen2brain/webp v0.5.5/go.mod h1:xOSMzp4aROt2KFW++9qcK/RBTOVC2S9tJG66ip/9Oc0= +github.com/gen2brain/webp v0.6.0 h1:VN/cmeDv78sAKbJayyB1YVm9SHlz5qXO06bo4v6V3hY= +github.com/gen2brain/webp v0.6.0/go.mod h1:iGWMaCSw7t3I/Cv9llzEKmpnR36S8lS8VL/ZVjxU0JE= github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=