jpegli/encode/
encoder_config.rs

1//! Encoder configuration for v2 API.
2
3use super::byte_encoders::{BytesEncoder, RgbEncoder, YCbCrPlanarEncoder};
4use super::encoder_types::{
5    ChromaSubsampling, ColorMode, DownsamplingMethod, PixelLayout, Quality, QuantTableConfig,
6    XybSubsampling, ZeroBiasConfig,
7};
8use crate::error::Result;
9use crate::types::EdgePaddingConfig;
10
11/// JPEG encoder configuration. Dimension-independent, reusable across images.
12#[derive(Clone, Debug)]
13pub struct EncoderConfig {
14    pub(crate) quality: Quality,
15    pub(crate) quant_tables: QuantTableConfig,
16    pub(crate) zero_bias: ZeroBiasConfig,
17    pub(crate) progressive: bool,
18    pub(crate) optimize_huffman: bool,
19    pub(crate) color_mode: ColorMode,
20    pub(crate) downsampling_method: DownsamplingMethod,
21    pub(crate) restart_interval: u16,
22    pub(crate) icc_profile: Option<Vec<u8>>,
23    pub(crate) exif_data: Option<super::exif::Exif>,
24    pub(crate) xmp_data: Option<Vec<u8>>,
25    pub(crate) edge_padding: EdgePaddingConfig,
26    /// Parallel encoding configuration (requires `parallel` feature)
27    #[cfg(feature = "parallel")]
28    pub(crate) parallel: Option<super::encoder_types::ParallelEncoding>,
29    /// Hybrid quantization configuration (requires `experimental-hybrid-trellis` feature)
30    #[cfg(feature = "experimental-hybrid-trellis")]
31    pub(crate) hybrid_config: crate::hybrid::config::HybridConfig,
32}
33
34// Note: No Default impl - quality and subsampling are required via new()
35
36impl EncoderConfig {
37    /// Create a new encoder configuration with required quality and chroma subsampling.
38    ///
39    /// # Arguments
40    /// - `quality`: Quality level (0-100 for jpegli scale, or use `Quality::*` variants)
41    /// - `subsampling`: Chroma subsampling mode
42    ///   - `ChromaSubsampling::Quarter` (4:2:0) - good compression, smaller files
43    ///   - `ChromaSubsampling::None` (4:4:4) - best quality, larger files
44    ///
45    /// # Example
46    /// ```ignore
47    /// use jpegli::encoder::{EncoderConfig, ChromaSubsampling};
48    ///
49    /// let config = EncoderConfig::new(85.0, ChromaSubsampling::Quarter)
50    ///     .progressive(true);
51    /// ```
52    #[must_use]
53    pub fn new(quality: impl Into<Quality>, subsampling: ChromaSubsampling) -> Self {
54        Self {
55            quality: quality.into(),
56            color_mode: ColorMode::YCbCr { subsampling },
57            ..Self::default_internal()
58        }
59    }
60
61    /// Internal default for non-required fields only.
62    fn default_internal() -> Self {
63        Self {
64            quality: Quality::default(),
65            quant_tables: QuantTableConfig::default(),
66            zero_bias: ZeroBiasConfig::default(),
67            progressive: false,
68            optimize_huffman: true,
69            color_mode: ColorMode::default(),
70            downsampling_method: DownsamplingMethod::default(),
71            restart_interval: 0,
72            icc_profile: None,
73            exif_data: None,
74            xmp_data: None,
75            edge_padding: EdgePaddingConfig::default(),
76            #[cfg(feature = "parallel")]
77            parallel: None,
78            #[cfg(feature = "experimental-hybrid-trellis")]
79            hybrid_config: crate::hybrid::config::HybridConfig::default(),
80        }
81    }
82
83    // === Quality & Quantization ===
84
85    /// Override the quality level.
86    ///
87    /// Accepts any type that converts to `Quality`:
88    /// - `f32` or `u8` for ApproxJpegli scale
89    /// - `Quality::ApproxMozjpeg(u8)` for mozjpeg-like quality
90    /// - `Quality::ApproxSsim2(f32)` for SSIMULACRA2 target
91    /// - `Quality::ApproxButteraugli(f32)` for Butteraugli target
92    #[must_use]
93    pub fn quality(mut self, q: impl Into<Quality>) -> Self {
94        self.quality = q.into();
95        self
96    }
97
98    /// Set custom quantization tables.
99    #[must_use]
100    pub fn quant_tables(mut self, config: QuantTableConfig) -> Self {
101        self.quant_tables = config;
102        self
103    }
104
105    /// Set zero-bias configuration.
106    ///
107    /// Zero-bias controls how DCT coefficients are rounded toward zero during
108    /// quantization. The default `Perceptual` mode uses jpegli's quality-adaptive
109    /// tables which are optimized for visual quality.
110    ///
111    /// # Options
112    ///
113    /// - `ZeroBiasConfig::Perceptual` (default) - quality-adaptive tables
114    /// - `ZeroBiasConfig::Disabled` - no zero-bias (standard JPEG behavior)
115    /// - `ZeroBiasConfig::Custom { .. }` - provide custom per-component tables
116    ///
117    /// # Example
118    ///
119    /// ```ignore
120    /// use jpegli::encoder::{EncoderConfig, ZeroBiasConfig};
121    ///
122    /// // Disable zero-bias for standard JPEG behavior
123    /// let config = EncoderConfig::new(85, ChromaSubsampling::None)
124    ///     .zero_bias(ZeroBiasConfig::Disabled);
125    /// ```
126    #[must_use]
127    pub fn zero_bias(mut self, config: ZeroBiasConfig) -> Self {
128        self.zero_bias = config;
129        self
130    }
131
132    // === Encoding Mode ===
133
134    /// Enable or disable progressive encoding.
135    ///
136    /// Progressive encoding produces multiple scans for incremental display.
137    /// Automatically enables optimized Huffman tables (required for progressive).
138    #[must_use]
139    pub fn progressive(mut self, enable: bool) -> Self {
140        self.progressive = enable;
141        if enable {
142            self.optimize_huffman = true;
143        }
144        self
145    }
146
147    /// Enable or disable Huffman table optimization.
148    ///
149    /// When enabled (default), computes optimal Huffman tables from image data.
150    /// When disabled, uses standard JPEG Huffman tables (faster but larger files).
151    ///
152    /// Note: Progressive mode requires optimized Huffman tables.
153    #[must_use]
154    pub fn optimize_huffman(mut self, enable: bool) -> Self {
155        self.optimize_huffman = enable;
156        self
157    }
158
159    /// Set the restart interval (MCUs between restart markers).
160    ///
161    /// Restart markers allow partial decoding and error recovery.
162    /// Set to 0 to disable restart markers (default).
163    #[must_use]
164    pub fn restart_interval(mut self, interval: u16) -> Self {
165        self.restart_interval = interval;
166        self
167    }
168
169    /// Enable parallel encoding for improved throughput on multi-core systems.
170    ///
171    /// When enabled, the encoder uses multiple threads for:
172    /// - DCT computation (block transforms)
173    /// - Entropy/Huffman encoding (via restart markers)
174    ///
175    /// # Restart Marker Behavior
176    ///
177    /// Parallel entropy encoding requires restart markers between segments.
178    /// When parallel encoding is enabled:
179    /// - If `restart_interval` is 0 or too small, it will be **increased** to an
180    ///   optimal value based on thread count and image size
181    /// - User-specified `restart_interval` values are respected as a minimum
182    ///   (the encoder may increase but will not decrease them)
183    ///
184    /// # Performance
185    ///
186    /// - 2 threads: ~1.2-1.6x speedup
187    /// - 4 threads: ~1.3-1.7x speedup
188    /// - Minimum useful size: ~512x512 (smaller images have too much overhead)
189    ///
190    /// # Example
191    ///
192    /// ```ignore
193    /// use jpegli::{EncoderConfig, ParallelEncoding};
194    ///
195    /// let config = EncoderConfig::new()
196    ///     .quality(85)
197    ///     .parallel(ParallelEncoding::Auto);
198    /// ```
199    ///
200    /// Requires the `parallel` feature flag.
201    #[cfg(feature = "parallel")]
202    #[must_use]
203    pub fn parallel(mut self, mode: super::encoder_types::ParallelEncoding) -> Self {
204        self.parallel = Some(mode);
205        self
206    }
207
208    /// Configure hybrid quantization (jpegli AQ + mozjpeg trellis).
209    ///
210    /// Allows fine-tuning all hybrid AQ+trellis parameters.
211    /// See [`HybridConfig`](crate::hybrid::config::HybridConfig) for available options.
212    ///
213    /// Requires the `experimental-hybrid-trellis` feature.
214    #[cfg(feature = "experimental-hybrid-trellis")]
215    #[must_use]
216    pub fn hybrid_config(mut self, config: crate::hybrid::config::HybridConfig) -> Self {
217        self.hybrid_config = config;
218        self
219    }
220
221    // === ICC Profile ===
222
223    /// Attach an ICC color profile to the output JPEG.
224    ///
225    /// The profile will be written as APP2 marker segments with the standard
226    /// "ICC_PROFILE" signature. Large profiles are automatically chunked
227    /// (max 65519 bytes per segment) as required by the ICC profile embedding spec.
228    ///
229    /// Common profiles:
230    /// - sRGB IEC61966-2.1 (~3KB)
231    /// - Display P3 (~0.5KB)
232    /// - Adobe RGB 1998 (~0.5KB)
233    ///
234    /// # Example
235    /// ```ignore
236    /// let srgb_profile = std::fs::read("sRGB.icc")?;
237    /// let config = EncoderConfig::new()
238    ///     .quality(85)
239    ///     .icc_profile(srgb_profile);
240    /// ```
241    #[must_use]
242    pub fn icc_profile(mut self, profile: impl Into<Vec<u8>>) -> Self {
243        self.icc_profile = Some(profile.into());
244        self
245    }
246
247    // === EXIF/XMP Metadata ===
248
249    /// Attach EXIF metadata to the output JPEG.
250    ///
251    /// Use [`Exif::raw`][super::exif::Exif::raw] for raw EXIF bytes, or
252    /// [`Exif::build`][super::exif::Exif::build] to construct from common fields.
253    ///
254    /// The two modes are mutually exclusive at compile time - you cannot
255    /// mix raw bytes with field-based building.
256    ///
257    /// # Examples
258    ///
259    /// Build from fields (orientation and copyright):
260    /// ```ignore
261    /// use jpegli::encoder::{EncoderConfig, ChromaSubsampling, Exif, Orientation};
262    ///
263    /// let config = EncoderConfig::new(85, ChromaSubsampling::Quarter)
264    ///     .exif(Exif::build()
265    ///         .orientation(Orientation::Rotate90)
266    ///         .copyright("© 2024 Example Corp"));
267    /// ```
268    ///
269    /// Use raw EXIF bytes:
270    /// ```ignore
271    /// use jpegli::encoder::{EncoderConfig, ChromaSubsampling, Exif};
272    ///
273    /// let config = EncoderConfig::new(85, ChromaSubsampling::Quarter)
274    ///     .exif(Exif::raw(my_exif_bytes));
275    /// ```
276    ///
277    /// # Notes
278    ///
279    /// - EXIF is placed immediately after SOI, before any other markers
280    /// - Raw bytes should be TIFF data without the "Exif\0\0" prefix (added automatically)
281    /// - Maximum size: 65527 bytes (larger data will be truncated)
282    #[must_use]
283    pub fn exif(mut self, exif: impl Into<super::exif::Exif>) -> Self {
284        self.exif_data = Some(exif.into());
285        self
286    }
287
288    /// Attach XMP metadata to the output JPEG.
289    ///
290    /// The data will be written as an APP1 marker segment with the standard
291    /// Adobe XMP namespace signature. The provided bytes should be the raw XMP
292    /// XML data without the APP1 marker or namespace prefix.
293    ///
294    /// XMP is placed after EXIF (if present) but before ICC profile.
295    ///
296    /// # Maximum Size
297    /// Standard XMP is limited to 65502 bytes (65535 - 2 length - 29 namespace - 2 padding).
298    /// For larger XMP data, use Extended XMP (not yet supported).
299    #[must_use]
300    pub fn xmp(mut self, data: impl Into<Vec<u8>>) -> Self {
301        self.xmp_data = Some(data.into());
302        self
303    }
304
305    // === Color Mode ===
306
307    /// Set the output color mode.
308    #[must_use]
309    pub fn color_mode(mut self, mode: ColorMode) -> Self {
310        self.color_mode = mode;
311        self
312    }
313
314    /// Set the chroma downsampling method.
315    ///
316    /// Only affects RGB/RGBX input with chroma subsampling enabled.
317    /// Ignored for grayscale, YCbCr input, or 4:4:4 subsampling.
318    #[must_use]
319    pub fn downsampling_method(mut self, method: DownsamplingMethod) -> Self {
320        self.downsampling_method = method;
321        self
322    }
323
324    /// Internal: Set edge padding strategy for partial MCU blocks.
325    #[doc(hidden)]
326    #[must_use]
327    pub fn edge_padding_internal(mut self, config: EdgePaddingConfig) -> Self {
328        self.edge_padding = config;
329        self
330    }
331
332    // === Convenience Shortcuts ===
333
334    /// Set YCbCr color mode with specified chroma subsampling.
335    ///
336    /// Common values:
337    /// - `ChromaSubsampling::None` (4:4:4) - default, best quality
338    /// - `ChromaSubsampling::Quarter` (4:2:0) - good compression, smaller files
339    /// - `ChromaSubsampling::HalfHorizontal` (4:2:2) - horizontal subsampling only
340    #[must_use]
341    pub fn ycbcr(self, subsampling: ChromaSubsampling) -> Self {
342        self.color_mode(ColorMode::YCbCr { subsampling })
343    }
344
345    /// Set XYB color mode with B-quarter subsampling (default, perceptually optimized).
346    ///
347    /// XYB is a perceptual color space that can achieve better quality at the same
348    /// file size for some images. Requires linear RGB input (f32 or u16).
349    #[must_use]
350    pub fn xyb(self) -> Self {
351        self.color_mode(ColorMode::Xyb {
352            subsampling: XybSubsampling::BQuarter,
353        })
354    }
355
356    /// Set XYB color mode with full resolution (no subsampling).
357    #[must_use]
358    pub fn xyb_full(self) -> Self {
359        self.color_mode(ColorMode::Xyb {
360            subsampling: XybSubsampling::Full,
361        })
362    }
363
364    /// Set grayscale output mode.
365    ///
366    /// Only the luminance channel is encoded. Works with any input format.
367    #[must_use]
368    pub fn grayscale(self) -> Self {
369        self.color_mode(ColorMode::Grayscale)
370    }
371
372    /// Enable or disable SharpYUV (GammaAwareIterative) downsampling.
373    ///
374    /// SharpYUV produces better color preservation on edges and thin lines,
375    /// at the cost of ~3x slower encoding.
376    #[must_use]
377    pub fn sharp_yuv(self, enable: bool) -> Self {
378        self.downsampling_method(if enable {
379            DownsamplingMethod::GammaAwareIterative
380        } else {
381            DownsamplingMethod::Box
382        })
383    }
384
385    // === Validation ===
386
387    /// Validate the configuration, returning an error for invalid combinations.
388    ///
389    /// Invalid combinations:
390    /// - Progressive mode with disabled Huffman optimization
391    pub fn validate(&self) -> Result<()> {
392        if self.progressive && !self.optimize_huffman {
393            return Err(crate::error::Error::invalid_config(
394                "progressive mode requires optimized Huffman tables".into(),
395            ));
396        }
397        Ok(())
398    }
399
400    // === Encoder Creation ===
401
402    /// Create an encoder from raw bytes with explicit pixel layout.
403    ///
404    /// Use this when working with raw byte buffers and you know the pixel layout.
405    ///
406    /// # Arguments
407    /// - `width`: Image width in pixels
408    /// - `height`: Image height in pixels
409    /// - `layout`: Pixel data layout (channel order, depth, color space)
410    ///
411    /// # Example
412    /// ```ignore
413    /// let config = EncoderConfig::new().quality(85);
414    /// let mut enc = config.encode_from_bytes(1920, 1080, PixelLayout::Rgb8Srgb)?;
415    /// enc.push_packed(&rgb_bytes, Unstoppable)?;
416    /// let jpeg = enc.finish()?;
417    /// ```
418    pub fn encode_from_bytes(
419        &self,
420        width: u32,
421        height: u32,
422        layout: PixelLayout,
423    ) -> Result<BytesEncoder> {
424        self.validate()?;
425        BytesEncoder::new(self.clone(), width, height, layout)
426    }
427
428    /// Create an encoder from rgb crate pixel types.
429    ///
430    /// Layout is inferred from the type parameter. For RGBA/BGRA types,
431    /// the 4th channel is ignored.
432    ///
433    /// # Type Parameter
434    /// - `P`: Pixel type from the `rgb` crate (e.g., `RGB<u8>`, `RGBA<f32>`)
435    ///
436    /// # Example
437    /// ```ignore
438    /// use rgb::RGB;
439    ///
440    /// let config = EncoderConfig::new().quality(85);
441    /// let mut enc = config.encode_from_rgb::<RGB<u8>>(1920, 1080)?;
442    /// enc.push_packed(&pixels, Unstoppable)?;
443    /// let jpeg = enc.finish()?;
444    /// ```
445    pub fn encode_from_rgb<P: super::byte_encoders::Pixel>(
446        &self,
447        width: u32,
448        height: u32,
449    ) -> Result<RgbEncoder<P>> {
450        self.validate()?;
451        RgbEncoder::new(self.clone(), width, height)
452    }
453
454    /// Create an encoder from planar YCbCr data.
455    ///
456    /// Use this when you have pre-converted YCbCr from video decoders, etc.
457    /// Skips RGB->YCbCr conversion entirely.
458    ///
459    /// Only valid with `ColorMode::YCbCr`. XYB mode requires RGB input.
460    ///
461    /// # Example
462    /// ```ignore
463    /// let config = EncoderConfig::new()
464    ///     .quality(85)
465    ///     .ycbcr(ChromaSubsampling::Quarter);
466    ///
467    /// let mut enc = config.encode_from_ycbcr_planar(1920, 1080)?;
468    /// enc.push(&planes, height, Unstoppable)?;
469    /// let jpeg = enc.finish()?;
470    /// ```
471    pub fn encode_from_ycbcr_planar(&self, width: u32, height: u32) -> Result<YCbCrPlanarEncoder> {
472        self.validate()?;
473
474        // Validate color mode
475        if !matches!(self.color_mode, ColorMode::YCbCr { .. }) {
476            return Err(crate::error::Error::invalid_config(
477                "planar YCbCr input requires YCbCr color mode".into(),
478            ));
479        }
480
481        YCbCrPlanarEncoder::new(self.clone(), width, height)
482    }
483
484    // === Resource Estimation ===
485
486    /// Estimate peak memory usage for encoding an image of the given dimensions.
487    ///
488    /// Returns estimated bytes based on color mode, subsampling, and dimensions.
489    /// Delegates to the streaming encoder's estimate which accounts for all
490    /// internal buffers.
491    #[must_use]
492    #[allow(deprecated)]
493    pub fn estimate_memory(&self, width: u32, height: u32) -> usize {
494        use crate::encode::streaming::StreamingEncoder;
495
496        let subsampling = match self.color_mode {
497            ColorMode::YCbCr { subsampling } => subsampling.to_legacy(),
498            ColorMode::Xyb { .. } => crate::types::Subsampling::S444,
499            ColorMode::Grayscale => crate::types::Subsampling::S444,
500        };
501
502        StreamingEncoder::new(width, height)
503            .subsampling(subsampling)
504            .optimize_huffman(self.optimize_huffman)
505            .estimate_memory_usage()
506    }
507
508    /// Returns an absolute ceiling on memory usage.
509    ///
510    /// Unlike `estimate_memory`, this returns a **guaranteed upper bound**
511    /// that actual peak memory will never exceed. Use this for resource reservation
512    /// when you need certainty rather than a close estimate.
513    ///
514    /// The ceiling accounts for:
515    /// - Worst-case token counts per block (high-frequency content)
516    /// - Maximum output buffer size (incompressible images)
517    /// - Vec capacity overhead (allocator rounding)
518    /// - All intermediate buffers at their maximum sizes
519    ///
520    /// # Example
521    ///
522    /// ```rust,ignore
523    /// use jpegli::encoder::EncoderConfig;
524    ///
525    /// let config = EncoderConfig::new().quality(85);
526    /// let ceiling = config.estimate_memory_ceiling(1920, 1080);
527    ///
528    /// // Reserve this much memory - actual usage guaranteed to be less
529    /// let buffer = Vec::with_capacity(ceiling);
530    /// ```
531    #[must_use]
532    #[allow(deprecated)]
533    pub fn estimate_memory_ceiling(&self, width: u32, height: u32) -> usize {
534        use crate::encode::streaming::StreamingEncoder;
535
536        let subsampling = match self.color_mode {
537            ColorMode::YCbCr { subsampling } => subsampling.to_legacy(),
538            ColorMode::Xyb { .. } => crate::types::Subsampling::S444,
539            ColorMode::Grayscale => crate::types::Subsampling::S444,
540        };
541
542        StreamingEncoder::new(width, height)
543            .subsampling(subsampling)
544            .estimate_memory_ceiling()
545    }
546
547    // === Accessors ===
548
549    /// Get the configured quality.
550    #[must_use]
551    pub fn get_quality(&self) -> Quality {
552        self.quality
553    }
554
555    /// Get the configured color mode.
556    #[must_use]
557    pub fn get_color_mode(&self) -> ColorMode {
558        self.color_mode
559    }
560
561    /// Check if progressive mode is enabled.
562    #[must_use]
563    pub fn is_progressive(&self) -> bool {
564        self.progressive
565    }
566
567    /// Check if Huffman optimization is enabled.
568    #[must_use]
569    pub fn is_optimize_huffman(&self) -> bool {
570        self.optimize_huffman
571    }
572
573    /// Get the ICC profile, if set.
574    #[must_use]
575    pub fn get_icc_profile(&self) -> Option<&[u8]> {
576        self.icc_profile.as_deref()
577    }
578
579    /// Get the EXIF data, if set.
580    #[must_use]
581    pub fn get_exif(&self) -> Option<&super::exif::Exif> {
582        self.exif_data.as_ref()
583    }
584
585    /// Get the XMP data, if set.
586    #[must_use]
587    pub fn get_xmp(&self) -> Option<&[u8]> {
588        self.xmp_data.as_deref()
589    }
590
591    /// Internal: Get the configured edge padding.
592    #[doc(hidden)]
593    #[must_use]
594    pub fn get_edge_padding(&self) -> EdgePaddingConfig {
595        self.edge_padding
596    }
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602
603    #[test]
604    fn test_default_config() {
605        let config = EncoderConfig::new(90.0, ChromaSubsampling::None);
606        assert!(matches!(config.quality, Quality::ApproxJpegli(90.0)));
607        assert!(!config.progressive);
608        assert!(config.optimize_huffman);
609        assert!(matches!(
610            config.color_mode,
611            ColorMode::YCbCr {
612                subsampling: ChromaSubsampling::None
613            }
614        ));
615    }
616
617    #[test]
618    fn test_builder_pattern() {
619        let config = EncoderConfig::new(85, ChromaSubsampling::None)
620            .progressive(true)
621            .sharp_yuv(true);
622
623        assert!(matches!(config.quality, Quality::ApproxJpegli(85.0)));
624        assert!(config.progressive);
625        assert!(config.optimize_huffman); // auto-enabled by progressive
626        assert!(matches!(
627            config.color_mode,
628            ColorMode::YCbCr {
629                subsampling: ChromaSubsampling::None
630            }
631        ));
632        assert!(matches!(
633            config.downsampling_method,
634            DownsamplingMethod::GammaAwareIterative
635        ));
636    }
637
638    #[test]
639    fn test_progressive_enables_huffman() {
640        let config = EncoderConfig::new(90.0, ChromaSubsampling::None)
641            .optimize_huffman(false)
642            .progressive(true);
643
644        assert!(config.optimize_huffman);
645    }
646
647    #[test]
648    fn test_validation_progressive_huffman() {
649        let mut config = EncoderConfig::new(90.0, ChromaSubsampling::None);
650        config.progressive = true;
651        config.optimize_huffman = false;
652
653        assert!(config.validate().is_err());
654    }
655
656    #[test]
657    fn test_xyb_shortcuts() {
658        let config = EncoderConfig::new(90.0, ChromaSubsampling::None).xyb();
659        assert!(matches!(
660            config.color_mode,
661            ColorMode::Xyb {
662                subsampling: XybSubsampling::BQuarter
663            }
664        ));
665
666        let config = EncoderConfig::new(90.0, ChromaSubsampling::None).xyb_full();
667        assert!(matches!(
668            config.color_mode,
669            ColorMode::Xyb {
670                subsampling: XybSubsampling::Full
671            }
672        ));
673    }
674}