@@ -357,13 +357,25 @@ impl QualityIntent {
357357
358358 /// Build an [`EncoderConfig`] for the given encoder using this quality intent.
359359 ///
360- /// Returns default config for each format/encoder. The caller can further
361- /// customize the returned config (e.g. via `EncoderHints` overrides).
360+ /// **Zen codecs** receive `generic_quality` directly — they have their own
361+ /// calibration tables (via `with_generic_quality()`).
362+ /// **C codecs** (mozjpeg, libwebp) receive pre-calibrated native values.
363+ ///
364+ /// The caller can further customize the returned config (e.g. via
365+ /// `EncoderHints` overrides).
362366 pub fn build_config ( & self , encoder : NamedEncoders , matte : Option < Color > ) -> EncoderConfig {
363367 let lossless = self . is_lossless ( ) ;
368+ let is_zen = encoder. is_zen_codec ( ) ;
369+
364370 match encoder. caps ( ) . format {
365371 ImageFormat :: Jpeg => EncoderConfig :: Jpeg {
366- quality : self . mozjpeg_quality ( ) ,
372+ // Zen: pass generic_quality, zen's calibrated_jpeg_quality() maps it
373+ // C (mozjpeg): pass pre-calibrated mozjpeg quality
374+ quality : if is_zen {
375+ self . generic_quality . clamp ( 0.0 , 100.0 ) as u8
376+ } else {
377+ self . mozjpeg_quality ( )
378+ } ,
367379 progressive : true ,
368380 classic : false ,
369381 optimize_huffman : false ,
@@ -391,25 +403,41 @@ impl QualityIntent {
391403 EncoderConfig :: WebP { quality : None , lossless : true , matte }
392404 } else {
393405 EncoderConfig :: WebP {
394- quality : Some ( self . libwebp_quality ( ) ) ,
406+ quality : Some ( if is_zen {
407+ self . generic_quality . clamp ( 0.0 , 100.0 )
408+ } else {
409+ self . libwebp_quality ( )
410+ } ) ,
395411 lossless : false ,
396412 matte,
397413 }
398414 }
399415 }
400416 ImageFormat :: Gif => EncoderConfig :: Gif ,
401417 ImageFormat :: Jxl => {
418+ // JXL is zen-only, but we still produce a proper config
402419 if lossless {
403420 EncoderConfig :: Jxl { distance : None , lossless : true }
404421 } else {
405422 EncoderConfig :: Jxl {
423+ // Zen JXL has with_generic_quality() → calibrated_jxl_quality()
424+ // which returns a distance. We pass generic_quality; the
425+ // instantiation layer calls with_generic_quality().
426+ // The distance field here is used for direct JXL distance
427+ // overrides (e.g. from srcset `jxl-d1.5`).
406428 distance : Some ( self . jxl_distance ( ) ) ,
407429 lossless : false ,
408430 }
409431 }
410432 }
411433 ImageFormat :: Avif => EncoderConfig :: Avif {
412- quality : self . avif_quality ( ) ,
434+ // Zen AVIF has with_generic_quality() → calibrated_avif_quality()
435+ // For zen, pass generic_quality; for C AVIF (hypothetical), calibrate.
436+ quality : if is_zen {
437+ self . generic_quality . clamp ( 0.0 , 100.0 )
438+ } else {
439+ self . avif_quality ( )
440+ } ,
413441 speed : self . avif_speed ( ) ,
414442 lossless,
415443 matte,
@@ -1908,6 +1936,53 @@ mod tests {
19081936 }
19091937 }
19101938
1939+ #[ test]
1940+ fn build_config_zen_jpeg_gets_generic_quality ( ) {
1941+ let q = QualityIntent :: from_value ( 73.0 ) ;
1942+ let zen_cfg = q. build_config ( NamedEncoders :: ZenJpegEncoder , None ) ;
1943+ let c_cfg = q. build_config ( NamedEncoders :: MozJpegEncoder , None ) ;
1944+ match ( zen_cfg, c_cfg) {
1945+ ( EncoderConfig :: Jpeg { quality : zen_q, .. } , EncoderConfig :: Jpeg { quality : c_q, .. } ) => {
1946+ // Zen gets generic_quality (73), C gets calibrated mozjpeg quality (also 73)
1947+ // In this case they happen to match because (73, 73) is an anchor point
1948+ assert_eq ! ( zen_q, 73 ) ;
1949+ assert_eq ! ( c_q, 73 ) ;
1950+ }
1951+ _ => panic ! ( "expected two Jpeg configs" ) ,
1952+ }
1953+ }
1954+
1955+ #[ test]
1956+ fn build_config_zen_webp_gets_generic_quality ( ) {
1957+ let q = QualityIntent :: from_value ( 55.0 ) ;
1958+ let zen_cfg = q. build_config ( NamedEncoders :: ZenWebPEncoder , None ) ;
1959+ let c_cfg = q. build_config ( NamedEncoders :: WebPEncoder , None ) ;
1960+ match ( zen_cfg, c_cfg) {
1961+ (
1962+ EncoderConfig :: WebP { quality : Some ( zen_q) , .. } ,
1963+ EncoderConfig :: WebP { quality : Some ( c_q) , .. } ,
1964+ ) => {
1965+ // Zen gets generic (55.0), C gets calibrated (53.0)
1966+ assert ! ( ( zen_q - 55.0 ) . abs( ) < 0.01 , "zen should get generic_quality 55, got {}" , zen_q) ;
1967+ assert ! ( ( c_q - 53.0 ) . abs( ) < 0.01 , "C should get calibrated 53, got {}" , c_q) ;
1968+ }
1969+ _ => panic ! ( "expected two lossy WebP configs" ) ,
1970+ }
1971+ }
1972+
1973+ #[ test]
1974+ fn build_config_zen_avif_gets_generic_quality ( ) {
1975+ let q = QualityIntent :: from_value ( 73.0 ) ;
1976+ let cfg = q. build_config ( NamedEncoders :: ZenAvifEncoder , None ) ;
1977+ match cfg {
1978+ EncoderConfig :: Avif { quality, .. } => {
1979+ // Zen AVIF should get generic_quality directly (73.0)
1980+ assert ! ( ( quality - 73.0 ) . abs( ) < 0.01 , "zen AVIF should get 73.0, got {}" , quality) ;
1981+ }
1982+ _ => panic ! ( "expected Avif config" ) ,
1983+ }
1984+ }
1985+
19111986 // ── select_and_configure ──────────────────────────────────────────
19121987
19131988 #[ test]
0 commit comments