images: Fix WebP quality and hint parameters being ignored
Fixes #14316
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"}}
|
||||
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 752 B |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.1 KiB |