Skip to content

Commit 755401d

Browse files
images: Fix WebP quality and hint parameters being ignored
The WASM-based WebP encoder introduced in v0.153.0 ignored per-image quality and hint parameters, always using the global site configuration. Root cause: - codec.go only passed quality when conf.Quality > 0, missing lossless (q0) - No tracking for whether hint was explicitly set per-image - webp.go EncodeOptions only handled quality override, not hint Fix: - Add hintSetForImage field to ImageConfig (mirroring qualitySetForImage) - Pass quality/hint to encoder only when explicitly set per-image - Handle both quality and hint overrides in webp.go EncodeOptions Test strategy: - Golden tests validate quality by file size variance: q1=752B, q33=1688B, q75=3188B, q100=17620B - Golden tests validate hint by distinct MD5 hashes: photo=76ebc120..., drawing=bfaaee0d..., icon=55ecc0cd... - Combined test (q50 drawing) validates both parameters together AI assistance disclosure: This code was written by Antigravity with Gemini 3 Pro (High), revised by Claude Code with Opus 4.5 (Thinking), and reviewed by Antigravity with Gemini 3 Pro (High).
1 parent b1f7e35 commit 755401d

File tree

11 files changed

+49
-6
lines changed

11 files changed

+49
-6
lines changed

‎internal/warpc/webp.go‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ func (d *WebpCodec) DecodeConfig(r io.Reader) (image.Config, error) {
202202
}
203203

204204
func (d *WebpCodec) Encode(w io.Writer, img image.Image) error {
205+
return d.EncodeOptions(w, img, nil)
206+
}
207+
208+
func (d *WebpCodec) EncodeOptions(w io.Writer, img image.Image, options map[string]any) error {
205209
b := img.Bounds()
206210
if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 {
207211
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 {
299303
// encodeGray
300304
// decode
301305
// config
306+
307+
opts := map[string]any{
308+
"quality": d.quality, // a number between 0 and 100. Set to 0 for lossless.
309+
"hint": d.hint, // drawing, icon, photo, picture, or text
310+
"useSharpYuv": true, // Use sharp (and slow) RGB->YUV conversion.
311+
}
312+
313+
// Override with per-image options if provided.
314+
for _, key := range []string{"quality", "hint"} {
315+
if v, ok := options[key]; ok {
316+
opts[key] = v
317+
}
318+
}
319+
302320
message := Message[WebpInput]{
303321
Header: Header{
304322
Version: 1,
@@ -310,11 +328,7 @@ func (d *WebpCodec) Encode(w io.Writer, img image.Image) error {
310328
Data: WebpInput{
311329
Source: bytes.NewReader(imageBytes),
312330
Destination: w,
313-
Options: map[string]any{
314-
"quality": d.quality, // a number between 0 and 100. Set to 0 for lossless.
315-
"hint": d.hint, // drawing, icon, photo, picture, or text
316-
"useSharpYuv": true, // Use sharp (and slow) RGB->YUV conversion.
317-
},
331+
Options: opts,
318332
Params: map[string]any{
319333
"width": bounds.Max.X,
320334
"height": bounds.Max.Y,

‎resources/images/codec.go‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ type ToEncoder interface {
4646
EncodeTo(conf ImageConfig, w io.Writer, src image.Image) error
4747
}
4848

49+
// EncoderWithOptions defines the encoding of an image format with options.
50+
// This is currently only used for WebP and the options are passed as a map
51+
// to match the internal WASM API.
52+
type EncoderWithOptions interface {
53+
EncodeOptions(w io.Writer, src image.Image, options map[string]any) error
54+
}
55+
4956
// CodecStdlib defines both decoding and encoding of an image format as defined by the standard library.
5057
type CodecStdlib interface {
5158
Decoder
@@ -123,6 +130,19 @@ func (d *Codec) EncodeTo(conf ImageConfig, w io.Writer, img image.Image) error {
123130
case BMP:
124131
return bmp.Encode(w, img)
125132
case WEBP:
133+
if enc, ok := d.webp.(EncoderWithOptions); ok {
134+
var opts map[string]any
135+
if conf.qualitySetForImage || conf.hintSetForImage {
136+
opts = make(map[string]any)
137+
if conf.qualitySetForImage {
138+
opts["quality"] = conf.Quality
139+
}
140+
if conf.hintSetForImage {
141+
opts["hint"] = conf.Hint
142+
}
143+
}
144+
return enc.EncodeOptions(w, img, opts)
145+
}
126146
return d.webp.Encode(w, img)
127147
default:
128148
return errors.New("format not supported")

‎resources/images/config.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ func DecodeImageConfig(options []string, defaults *config.ConfigNamespace[Imagin
226226
c.Filter = filter
227227
} else if _, ok := hints[part]; ok {
228228
c.Hint = part
229+
c.hintSetForImage = true
229230
} else if part[0] == '#' {
230231
c.BgColor, err = hexStringToColorGo(part[1:])
231232
if err != nil {
@@ -358,7 +359,8 @@ type ImageConfig struct {
358359

359360
// Hint about what type of picture this is. Used to optimize encoding
360361
// when target is set to webp.
361-
Hint string
362+
Hint string
363+
hintSetForImage bool // Whether the above is set for this image.
362364

363365
Width int
364366
Height int

‎resources/images/images_golden_integration_test.go‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,13 @@ Home.
388388
{{ template "process" (dict "spec" "png" "img" $highContrast) }}
389389
{{ template "process" (dict "spec" "resize 300x300" "img" $giphy) }}
390390
{{ template "process" (dict "spec" "resize 300x300 webp" "img" $giphy) }}
391+
{{ template "process" (dict "spec" "resize 300x300 webp q1" "img" $sunset) }}
392+
{{ template "process" (dict "spec" "resize 300x300 webp q33" "img" $sunset) }}
393+
{{ template "process" (dict "spec" "resize 300x300 webp q75" "img" $sunset) }}
394+
{{ template "process" (dict "spec" "resize 300x300 webp q100" "img" $sunset) }}
395+
{{ template "process" (dict "spec" "resize 300x300 webp drawing" "img" $sunset) }}
396+
{{ template "process" (dict "spec" "resize 300x300 webp icon" "img" $sunset) }}
397+
{{ template "process" (dict "spec" "resize 300x300 webp q50 drawing" "img" $sunset) }}
391398
{{ template "process" (dict "spec" "resize 400x" "img" $highContrast) }}
392399
393400
{{ define "process"}}
3.3 KB
Loading
3.29 KB
Loading
752 Bytes
Loading
17.2 KB
Loading
1.65 KB
Loading
2.35 KB
Loading

0 commit comments

Comments
 (0)