diff --git a/resources/images/config.go b/resources/images/config.go index 923b6ad0c..3f0b3eec7 100644 --- a/resources/images/config.go +++ b/resources/images/config.go @@ -163,6 +163,7 @@ func ImageFormatFromMediaSubType(sub string) (Format, ImageResourceType) { const ( defaultJPEGQuality = 75 + defaultAVIFQuality = 60 defaultResampleFilter = "box" defaultBgColor = "#ffffff" defaultHint = "photo" @@ -177,7 +178,6 @@ var ( "resampleFilter": defaultResampleFilter, "bgColor": defaultBgColor, "hint": defaultHint, - "quality": defaultJPEGQuality, "compression": defaultCompression, "webp": map[string]any{ "useSharpYuv": defaultWebpUseSharpYuv, @@ -209,6 +209,7 @@ func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, Ima if err != nil { return ImagingConfigInternal{}, nil, err } + _, qualityExplicit := m["quality"] // Merge in the defaults. hmaps.MergeShallow(m, defaultImaging) @@ -226,6 +227,7 @@ func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, Ima if err := mapstructure.Decode(m, &i.Imaging); err != nil { return i, nil, err } + i.Imaging.qualityExplicit = qualityExplicit if err := i.Imaging.init(); err != nil { return i, nil, err @@ -488,9 +490,15 @@ func (i *ImagingConfigInternal) Compile(externalCfg *ImagingConfig) error { // ImagingConfig contains default image processing configuration. This will be fetched // from site (or language) config. type ImagingConfig struct { - // Default image quality setting (1-100). Only used for JPEG and WebP images. + // Default image quality setting (1-100). Used as the fallback for JPEG, + // WebP and AVIF when no per-format quality is set. When left unset, JPEG + // and WebP default to 75 and AVIF to 60 (its scale differs perceptually). Quality int + // Whether Quality was explicitly set in the config. When false, AVIF uses + // its own lower default instead of the global Quality. + qualityExplicit bool + // Compression method to use. // One of "lossy" or "lossless". // Note that lossless is currently only supported for WebP and AVIF. @@ -534,6 +542,9 @@ func (cfg *ImagingConfig) qualityFor(f Format) int { if q > 0 { return q } + if f == AVIF && !cfg.qualityExplicit { + return defaultAVIFQuality + } return cfg.Quality } @@ -544,6 +555,9 @@ var validMetaSources = map[string]bool{ } func (cfg *ImagingConfig) init() error { + if cfg.Quality == 0 && !cfg.qualityExplicit { + cfg.Quality = defaultJPEGQuality + } if cfg.Quality < 1 || cfg.Quality > 100 { return errors.New("image quality must be a number between 1 and 100") } diff --git a/resources/images/config_test.go b/resources/images/config_test.go index 3cdb91d37..ef53b9208 100644 --- a/resources/images/config_test.go +++ b/resources/images/config_test.go @@ -18,6 +18,8 @@ import ( "strings" "testing" + "github.com/gohugoio/hugo/config" + qt "github.com/frankban/quicktest" ) @@ -150,6 +152,37 @@ func TestImageConfigQualityPerFormat(t *testing.T) { c.Assert(err, qt.ErrorMatches, ".*quality must be.*") } +// See issue 14979. +func TestImageConfigAvifDefaultQuality(t *testing.T) { + c := qt.New(t) + + quality := func(cfg *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal], opts ...string) int { + conf, err := DecodeImageConfig(append([]string{"resize", "100x"}, opts...), cfg, JPEG) + c.Assert(err, qt.IsNil) + return conf.Quality + } + + // With nothing configured, AVIF defaults to 60 while JPEG/WebP stay at 75. + cfg, err := DecodeConfig(map[string]any{}) + c.Assert(err, qt.IsNil) + c.Assert(quality(cfg, "avif"), qt.Equals, 60) + c.Assert(quality(cfg, "jpg"), qt.Equals, 75) + c.Assert(quality(cfg, "webp"), qt.Equals, 75) + + // An explicit global quality applies to AVIF too. + cfg, err = DecodeConfig(map[string]any{"quality": 80}) + c.Assert(err, qt.IsNil) + c.Assert(quality(cfg, "avif"), qt.Equals, 80) + + // An explicit per-format AVIF quality wins over the default. + cfg, err = DecodeConfig(map[string]any{"avif": map[string]any{"quality": 90}}) + c.Assert(err, qt.IsNil) + c.Assert(quality(cfg, "avif"), qt.Equals, 90) + + // A per-image quality still wins. + c.Assert(quality(cfg, "avif", "q33"), qt.Equals, 33) +} + func TestDecodeImageConfig(t *testing.T) { for i, this := range []struct { action string diff --git a/resources/images/testdata/images_golden/process/avif/avif.avif b/resources/images/testdata/images_golden/process/avif/avif.avif index f19cc19b2..db3656528 100644 Binary files a/resources/images/testdata/images_golden/process/avif/avif.avif and b/resources/images/testdata/images_golden/process/avif/avif.avif differ diff --git a/resources/images/testdata/images_golden/process/avif/crop-300x300-smart-avif.avif b/resources/images/testdata/images_golden/process/avif/crop-300x300-smart-avif.avif index a2b8ea341..66e75b055 100644 Binary files a/resources/images/testdata/images_golden/process/avif/crop-300x300-smart-avif.avif and b/resources/images/testdata/images_golden/process/avif/crop-300x300-smart-avif.avif differ diff --git a/resources/images/testdata/images_golden/process/avif/crop-300x300-smart-ff9999-avif.avif b/resources/images/testdata/images_golden/process/avif/crop-300x300-smart-ff9999-avif.avif index e08e61744..2f0afd70c 100644 Binary files a/resources/images/testdata/images_golden/process/avif/crop-300x300-smart-ff9999-avif.avif and b/resources/images/testdata/images_golden/process/avif/crop-300x300-smart-ff9999-avif.avif differ diff --git a/resources/images/testdata/images_golden/process/avif/r1.avif b/resources/images/testdata/images_golden/process/avif/r1.avif index 5e38d2467..660e91d79 100644 Binary files a/resources/images/testdata/images_golden/process/avif/r1.avif and b/resources/images/testdata/images_golden/process/avif/r1.avif differ diff --git a/resources/images/testdata/images_golden/process/avif/r2.avif b/resources/images/testdata/images_golden/process/avif/r2.avif index 9a0883cd6..d4c46f2fd 100644 Binary files a/resources/images/testdata/images_golden/process/avif/r2.avif and b/resources/images/testdata/images_golden/process/avif/r2.avif differ diff --git a/resources/images/testdata/images_golden/process/avifstraws/resize-900x.avif b/resources/images/testdata/images_golden/process/avifstraws/resize-900x.avif index b33b00676..a728de049 100644 Binary files a/resources/images/testdata/images_golden/process/avifstraws/resize-900x.avif and b/resources/images/testdata/images_golden/process/avifstraws/resize-900x.avif differ