]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
images: Fix WebP quality and hint parameters being ignored
authorSimon Heimlicher <simon.github@heimlicher.com>
Mon, 29 Dec 2025 15:47:43 +0000 (16:47 +0100)
committerGitHub <noreply@github.com>
Mon, 29 Dec 2025 15:47:43 +0000 (16:47 +0100)
Fixes #14316

12 files changed:
internal/warpc/webp.go
resources/images/codec.go
resources/images/config.go
resources/images/images_golden_integration_test.go
resources/images/testdata/images_golden/process/webp/resize-300x300-webp-drawing.webp [new file with mode: 0644]
resources/images/testdata/images_golden/process/webp/resize-300x300-webp-icon.webp [new file with mode: 0644]
resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q0.webp [new file with mode: 0644]
resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q1.webp [new file with mode: 0644]
resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q100.webp [new file with mode: 0644]
resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q33.webp [new file with mode: 0644]
resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q50-drawing.webp [new file with mode: 0644]
resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q75.webp [new file with mode: 0644]

index f30588577ce0d540b0514502de3090e0209225f8..fe0df8e30f9173d94147ff71fe56f61fb31199c0 100644 (file)
@@ -202,6 +202,10 @@ func (d *WebpCodec) DecodeConfig(r io.Reader) (image.Config, error) {
 }
 
 func (d *WebpCodec) Encode(w io.Writer, img image.Image) error {
+       return d.EncodeOptions(w, img, nil)
+}
+
+func (d *WebpCodec) EncodeOptions(w io.Writer, img image.Image, options map[string]any) error {
        b := img.Bounds()
        if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 {
                return errors.New("webp: image is too large to encode")
@@ -299,6 +303,20 @@ func (d *WebpCodec) Encode(w io.Writer, img image.Image) error {
        // encodeGray
        // decode
        // config
+
+       opts := map[string]any{
+               "quality":     d.quality, // a number between 0 and 100. Set to 0 for lossless.
+               "hint":        d.hint,    // drawing, icon, photo, picture, or text
+               "useSharpYuv": true,      // Use sharp (and slow) RGB->YUV conversion.
+       }
+
+       // Override with per-image options if provided.
+       for _, key := range []string{"quality", "hint"} {
+               if v, ok := options[key]; ok {
+                       opts[key] = v
+               }
+       }
+
        message := Message[WebpInput]{
                Header: Header{
                        Version:       1,
@@ -310,11 +328,7 @@ func (d *WebpCodec) Encode(w io.Writer, img image.Image) error {
                Data: WebpInput{
                        Source:      bytes.NewReader(imageBytes),
                        Destination: w,
-                       Options: map[string]any{
-                               "quality":     d.quality, // a number between 0 and 100. Set to 0 for lossless.
-                               "hint":        d.hint,    // drawing, icon, photo, picture, or text
-                               "useSharpYuv": true,      // Use sharp (and slow) RGB->YUV conversion.
-                       },
+                       Options:     opts,
                        Params: map[string]any{
                                "width":          bounds.Max.X,
                                "height":         bounds.Max.Y,
index 7fe1ccf5bfdc79e5ab8892d1782d56526a7889b2..087643c2650e54587e774bd3c841e3193e025e58 100644 (file)
@@ -46,6 +46,14 @@ type ToEncoder interface {
        EncodeTo(conf ImageConfig, w io.Writer, src image.Image) error
 }
 
+// EncoderWithOptions defines the encoding of an image format with options.
+// This is currently only used for WebP and the options are passed as a map
+// to match the internal WASM API. The options map may be nil when no options
+// need to be overridden.
+type EncoderWithOptions interface {
+       EncodeOptions(w io.Writer, src image.Image, options map[string]any) error
+}
+
 // CodecStdlib defines both decoding and encoding of an image format as defined by the standard library.
 type CodecStdlib interface {
        Decoder
@@ -123,6 +131,19 @@ func (d *Codec) EncodeTo(conf ImageConfig, w io.Writer, img image.Image) error {
        case BMP:
                return bmp.Encode(w, img)
        case WEBP:
+               if enc, ok := d.webp.(EncoderWithOptions); ok {
+                       var opts map[string]any
+                       if conf.qualitySetForImage || conf.hintSetForImage {
+                               opts = make(map[string]any)
+                               if conf.qualitySetForImage {
+                                       opts["quality"] = conf.Quality
+                               }
+                               if conf.hintSetForImage {
+                                       opts["hint"] = conf.Hint
+                               }
+                       }
+                       return enc.EncodeOptions(w, img, opts)
+               }
                return d.webp.Encode(w, img)
        default:
                return errors.New("format not supported")
index f098053077a5dcda4fc837301e17d0ffe64c36bb..32940021ce7d1994727a1b9f0464d6128dc3bd6f 100644 (file)
@@ -226,6 +226,7 @@ func DecodeImageConfig(options []string, defaults *config.ConfigNamespace[Imagin
                        c.Filter = filter
                } else if _, ok := hints[part]; ok {
                        c.Hint = part
+                       c.hintSetForImage = true
                } else if part[0] == '#' {
                        c.BgColor, err = hexStringToColorGo(part[1:])
                        if err != nil {
@@ -236,8 +237,8 @@ func DecodeImageConfig(options []string, defaults *config.ConfigNamespace[Imagin
                        if err != nil {
                                return c, err
                        }
-                       if c.Quality < 1 || c.Quality > 100 {
-                               return c, errors.New("quality ranges from 1 to 100 inclusive")
+                       if c.Quality < 0 || c.Quality > 100 {
+                               return c, errors.New("quality ranges from 0 to 100 inclusive")
                        }
                        c.qualitySetForImage = true
                } else if part[0] == 'r' {
@@ -305,8 +306,9 @@ func DecodeImageConfig(options []string, defaults *config.ConfigNamespace[Imagin
                c.TargetFormat = sourceFormat
        }
 
-       if c.Quality <= 0 && c.TargetFormat.RequiresDefaultQuality() {
-               // We need a quality setting for all JPEGs and WEBPs.
+       if !c.qualitySetForImage && c.Quality <= 0 && c.TargetFormat.RequiresDefaultQuality() {
+               // We need a quality setting for all JPEGs and WEBPs,
+               // unless the user explicitly set quality (e.g., q0 for lossless WebP).
                c.Quality = defaults.Config.Imaging.Quality
        }
 
@@ -358,7 +360,8 @@ type ImageConfig struct {
 
        // Hint about what type of picture this is. Used to optimize encoding
        // when target is set to webp.
-       Hint string
+       Hint            string
+       hintSetForImage bool // Whether the above is set for this image.
 
        Width  int
        Height int
index ff2c3d2074770f84f8c3cc557090b7ab9ec4916a..d5a70702404f09c4d74cdc59831d6477327db0d9 100644 (file)
@@ -388,6 +388,14 @@ Home.
 {{ template "process" (dict "spec" "png" "img" $highContrast) }}
 {{ template "process" (dict "spec" "resize 300x300" "img" $giphy) }}
 {{ template "process" (dict "spec" "resize 300x300 webp" "img" $giphy) }}
+{{ template "process" (dict "spec" "resize 300x300 webp q0" "img" $sunset) }}
+{{ template "process" (dict "spec" "resize 300x300 webp q1" "img" $sunset) }}
+{{ template "process" (dict "spec" "resize 300x300 webp q33" "img" $sunset) }}
+{{ template "process" (dict "spec" "resize 300x300 webp q75" "img" $sunset) }}
+{{ template "process" (dict "spec" "resize 300x300 webp q100" "img" $sunset) }}
+{{ template "process" (dict "spec" "resize 300x300 webp drawing" "img" $sunset) }}
+{{ template "process" (dict "spec" "resize 300x300 webp icon" "img" $sunset) }}
+{{ template "process" (dict "spec" "resize 300x300 webp q50 drawing" "img" $sunset) }}
 {{ template "process" (dict "spec" "resize 400x" "img" $highContrast) }}
 
 {{ define "process"}}
diff --git a/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-drawing.webp b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-drawing.webp
new file mode 100644 (file)
index 0000000..4a94f72
Binary files /dev/null and b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-drawing.webp differ
diff --git a/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-icon.webp b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-icon.webp
new file mode 100644 (file)
index 0000000..cd13d1d
Binary files /dev/null and b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-icon.webp differ
diff --git a/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q0.webp b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q0.webp
new file mode 100644 (file)
index 0000000..ee53c60
Binary files /dev/null and b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q0.webp differ
diff --git a/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q1.webp b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q1.webp
new file mode 100644 (file)
index 0000000..2d1b9f8
Binary files /dev/null and b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q1.webp differ
diff --git a/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q100.webp b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q100.webp
new file mode 100644 (file)
index 0000000..cc4a5c2
Binary files /dev/null and b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q100.webp differ
diff --git a/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q33.webp b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q33.webp
new file mode 100644 (file)
index 0000000..55180b0
Binary files /dev/null and b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q33.webp differ
diff --git a/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q50-drawing.webp b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q50-drawing.webp
new file mode 100644 (file)
index 0000000..1a8e1cf
Binary files /dev/null and b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q50-drawing.webp differ
diff --git a/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q75.webp b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q75.webp
new file mode 100644 (file)
index 0000000..3d81589
Binary files /dev/null and b/resources/images/testdata/images_golden/process/webp/resize-300x300-webp-q75.webp differ