images: Fix WebP quality and hint parameters being ignored

Fixes #14316
This commit is contained in:
Simon Heimlicher
2025-12-29 16:47:43 +01:00
committed by GitHub
parent b1f7e35a98
commit ea9675f6e7
12 changed files with 56 additions and 10 deletions
+19 -5
View 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,
+21
View 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")
+8 -5
View 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
@@ -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"}}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB