zune_core/options/
decoder.rs

1/*
2 * Copyright (c) 2023.
3 *
4 * This software is free software;
5 *
6 * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
7 */
8
9//! Global Decoder options
10#![allow(clippy::zero_prefixed_literal)]
11
12use crate::bit_depth::ByteEndian;
13use crate::colorspace::ColorSpace;
14
15/// A decoder that can handle errors
16fn decoder_error_tolerance_mode() -> DecoderFlags {
17    // similar to fast options currently, so no need to write a new one
18    fast_options()
19}
20/// Fast decoder options
21///
22/// Enables all intrinsics + unsafe routines
23///
24/// Disables png adler and crc checking.
25fn fast_options() -> DecoderFlags {
26    DecoderFlags {
27        inflate_confirm_adler:        false,
28        png_confirm_crc:              false,
29        jpg_error_on_non_conformance: false,
30
31        zune_use_unsafe: true,
32        zune_use_neon:   true,
33        zune_use_avx:    true,
34        zune_use_avx2:   true,
35        zune_use_sse2:   true,
36        zune_use_sse3:   true,
37        zune_use_sse41:  true,
38
39        png_add_alpha_channel:     false,
40        png_strip_16_bit_to_8_bit: false,
41        png_decode_animated:       true,
42        jxl_decode_animated:       true
43    }
44}
45
46/// Command line options error resilient and fast
47///
48/// Features
49/// - Ignore CRC and Adler in png
50/// - Do not error out on non-conformance in jpg
51/// - Use unsafe paths
52fn cmd_options() -> DecoderFlags {
53    DecoderFlags {
54        inflate_confirm_adler:        false,
55        png_confirm_crc:              false,
56        jpg_error_on_non_conformance: false,
57
58        zune_use_unsafe: true,
59        zune_use_neon:   true,
60        zune_use_avx:    true,
61        zune_use_avx2:   true,
62        zune_use_sse2:   true,
63        zune_use_sse3:   true,
64        zune_use_sse41:  true,
65
66        png_add_alpha_channel:     false,
67        png_strip_16_bit_to_8_bit: false,
68
69        png_decode_animated: true,
70        jxl_decode_animated: true
71    }
72}
73
74/// Decoder options that are flags
75///
76/// NOTE: When you extend this, add true or false to
77/// all options above that return a `DecoderFlag`
78#[derive(Copy, Debug, Clone, Default)]
79pub struct DecoderFlags {
80    /// Whether the decoder should confirm and report adler mismatch
81    inflate_confirm_adler:        bool,
82    /// Whether the PNG decoder should confirm crc
83    png_confirm_crc:              bool,
84    /// Whether the png decoder should error out on image non-conformance
85    jpg_error_on_non_conformance: bool,
86    /// Whether the decoder should use unsafe  platform specific intrinsics
87    ///
88    /// This will also shut down platform specific intrinsics `(ZUNE_USE_{EXT})` value
89    zune_use_unsafe:              bool,
90    /// Whether we should use SSE2.
91    ///
92    /// This should be enabled for all x64 platforms but can be turned off if
93    /// `ZUNE_USE_UNSAFE` is false
94    zune_use_sse2:                bool,
95    /// Whether we should use SSE3 instructions where possible.
96    zune_use_sse3:                bool,
97    /// Whether we should use sse4.1 instructions where possible.
98    zune_use_sse41:               bool,
99    /// Whether we should use avx instructions where possible.
100    zune_use_avx:                 bool,
101    /// Whether we should use avx2 instructions where possible.
102    zune_use_avx2:                bool,
103    /// Whether the png decoder should add alpha channel where possible.
104    png_add_alpha_channel:        bool,
105    /// Whether we should use neon instructions where possible.
106    zune_use_neon:                bool,
107    /// Whether the png decoder should strip 16 bit to 8 bit
108    png_strip_16_bit_to_8_bit:    bool,
109    /// Decode all frames for an animated images
110    png_decode_animated:          bool,
111    jxl_decode_animated:          bool
112}
113
114/// Decoder options
115///
116/// Not all options are respected by decoders all decoders
117#[derive(Debug, Copy, Clone)]
118pub struct DecoderOptions {
119    /// Maximum width for which decoders will
120    /// not try to decode images larger than
121    /// the specified width.
122    ///
123    /// - Default value: 16384
124    /// - Respected by: `all decoders`
125    max_width:      usize,
126    /// Maximum height for which decoders will not
127    /// try to decode images larger than the
128    /// specified height
129    ///
130    /// - Default value: 16384
131    /// - Respected by: `all decoders`
132    max_height:     usize,
133    /// Output colorspace
134    ///
135    /// The jpeg decoder allows conversion to a separate colorspace
136    /// than the input.
137    ///
138    /// I.e you can convert a RGB jpeg image to grayscale without
139    /// first decoding it to RGB to get
140    ///
141    /// - Default value: `ColorSpace::RGB`
142    /// - Respected by: `jpeg`
143    out_colorspace: ColorSpace,
144
145    /// Maximum number of scans allowed
146    /// for progressive jpeg images
147    ///
148    /// Progressive jpegs have scans
149    ///
150    /// - Default value:100
151    /// - Respected by: `jpeg`
152    max_scans:     usize,
153    /// Maximum size for deflate.
154    /// Respected by all decoders that use inflate/deflate
155    deflate_limit: usize,
156    /// Boolean flags that influence decoding
157    flags:         DecoderFlags,
158    /// The byte endian of the returned bytes will be stored in
159    /// in case a single pixel spans more than a byte
160    endianness:    ByteEndian
161}
162
163/// Initializers
164impl DecoderOptions {
165    /// Create the decoder with options  setting most configurable
166    /// options to be their safe counterparts
167    ///
168    /// This is the same as `default` option as default initializes
169    /// options to the  safe variant.
170    ///
171    /// Note, decoders running on this will be slower as it disables
172    /// platform specific intrinsics
173    pub fn new_safe() -> DecoderOptions {
174        DecoderOptions::default()
175    }
176
177    /// Create the decoder with options setting the configurable options
178    /// to the fast  counterparts
179    ///
180    /// This enables platform specific code paths and enable use of unsafe
181    pub fn new_fast() -> DecoderOptions {
182        let flag = fast_options();
183        DecoderOptions::default().set_decoder_flags(flag)
184    }
185
186    /// Create the decoder options with the following characteristics
187    ///
188    /// - Use unsafe paths.
189    /// - Ignore error checksuming, e.g in png we do not confirm adler and crc in this mode
190    /// - Enable fast intrinsics paths
191    pub fn new_cmd() -> DecoderOptions {
192        let flag = cmd_options();
193        DecoderOptions::default().set_decoder_flags(flag)
194    }
195}
196
197/// Global options respected by all decoders
198impl DecoderOptions {
199    /// Get maximum width configured for which the decoder
200    /// should not try to decode images greater than this width
201    pub const fn max_width(&self) -> usize {
202        self.max_width
203    }
204
205    /// Get maximum height configured for which the decoder should
206    /// not try to decode images greater than this height
207    pub const fn max_height(&self) -> usize {
208        self.max_height
209    }
210
211    /// Return true whether the decoder should be in strict mode
212    /// And reject most errors
213    pub fn strict_mode(&self) -> bool {
214        self.flags.jpg_error_on_non_conformance
215            | self.flags.png_confirm_crc
216            | self.flags.inflate_confirm_adler
217    }
218    /// Return true if the decoder should use unsafe
219    /// routines where possible
220    pub const fn use_unsafe(&self) -> bool {
221        self.flags.zune_use_unsafe
222    }
223
224    /// Set maximum width for which the decoder should not try
225    /// decoding images greater than that width
226    ///
227    /// # Arguments
228    ///
229    /// * `width`:  The maximum width allowed
230    ///
231    /// returns: DecoderOptions
232    pub fn set_max_width(mut self, width: usize) -> Self {
233        self.max_width = width;
234        self
235    }
236
237    /// Set maximum height for which the decoder should not try
238    /// decoding images greater than that height
239    /// # Arguments
240    ///
241    /// * `height`: The maximum height allowed
242    ///
243    /// returns: DecoderOptions
244    ///
245    pub fn set_max_height(mut self, height: usize) -> Self {
246        self.max_height = height;
247        self
248    }
249
250    /// Whether the routines can use unsafe platform specific
251    /// intrinsics when necessary
252    ///
253    /// Platform intrinsics are implemented for operations which
254    /// the compiler can't auto-vectorize, or we can do a marginably
255    /// better job at it
256    ///
257    /// All decoders with unsafe routines respect it.
258    ///
259    /// Treat this with caution, disabling it will cause slowdowns but
260    /// it's provided for mainly for debugging use.
261    ///
262    /// - Respected by: `png` and `jpeg`(decoders with unsafe routines)
263    pub fn set_use_unsafe(mut self, yes: bool) -> Self {
264        // first clear the flag
265        self.flags.zune_use_unsafe = yes;
266        self
267    }
268
269    fn set_decoder_flags(mut self, flags: DecoderFlags) -> Self {
270        self.flags = flags;
271        self
272    }
273    /// Set whether the decoder should be in standards conforming/
274    /// strict mode
275    ///
276    /// This reduces the error tolerance level for the decoders and invalid
277    /// samples will be rejected by the decoder
278    ///
279    /// # Arguments
280    ///
281    /// * `yes`:
282    ///
283    /// returns: DecoderOptions
284    ///
285    pub fn set_strict_mode(mut self, yes: bool) -> Self {
286        self.flags.jpg_error_on_non_conformance = yes;
287        self.flags.png_confirm_crc = yes;
288        self.flags.inflate_confirm_adler = yes;
289        self
290    }
291
292    /// Set the byte endian for which raw samples will be stored in
293    /// in case a single pixel sample spans more than a byte.
294    ///
295    /// The default is usually native endian hence big endian values
296    /// will be converted to little endian on little endian systems,
297    ///
298    /// and little endian values will be converted to big endian on big endian systems
299    ///
300    /// # Arguments
301    ///
302    /// * `endian`: The endianness to which to set the bytes to
303    ///
304    /// returns: DecoderOptions
305    pub fn set_byte_endian(mut self, endian: ByteEndian) -> Self {
306        self.endianness = endian;
307        self
308    }
309
310    /// Get the byte endian for which samples that span more than one byte will
311    /// be treated
312    pub const fn byte_endian(&self) -> ByteEndian {
313        self.endianness
314    }
315}
316
317/// PNG specific options
318impl DecoderOptions {
319    /// Whether the inflate decoder should confirm
320    /// adler checksums
321    pub const fn inflate_get_confirm_adler(&self) -> bool {
322        self.flags.inflate_confirm_adler
323    }
324    /// Set whether the inflate decoder should confirm
325    /// adler checksums
326    pub fn inflate_set_confirm_adler(mut self, yes: bool) -> Self {
327        self.flags.inflate_confirm_adler = yes;
328        self
329    }
330    /// Get default inflate limit for which the decoder
331    /// will not try to decompress further
332    pub const fn inflate_get_limit(&self) -> usize {
333        self.deflate_limit
334    }
335    /// Set the default inflate limit for which decompressors
336    /// relying on inflate won't surpass this limit
337    #[must_use]
338    pub fn inflate_set_limit(mut self, limit: usize) -> Self {
339        self.deflate_limit = limit;
340        self
341    }
342    /// Whether the inflate decoder should confirm
343    /// crc 32 checksums
344    pub const fn png_get_confirm_crc(&self) -> bool {
345        self.flags.png_confirm_crc
346    }
347    /// Set whether the png decoder should confirm
348    /// CRC 32 checksums
349    #[must_use]
350    pub fn png_set_confirm_crc(mut self, yes: bool) -> Self {
351        self.flags.png_confirm_crc = yes;
352        self
353    }
354    /// Set whether the png decoder should add an alpha channel to
355    /// images where possible.
356    ///
357    /// For Luma images, it converts it to Luma+Alpha
358    ///
359    /// For RGB images it converts it to RGB+Alpha
360    pub fn png_set_add_alpha_channel(mut self, yes: bool) -> Self {
361        self.flags.png_add_alpha_channel = yes;
362        self
363    }
364    /// Return true whether the png decoder should add an alpha
365    /// channel to images where possible
366    pub const fn png_get_add_alpha_channel(&self) -> bool {
367        self.flags.png_add_alpha_channel
368    }
369
370    /// Whether the png decoder should reduce 16 bit images to 8 bit
371    /// images implicitly.
372    ///
373    /// Equivalent to [png::Transformations::STRIP_16](https://docs.rs/png/latest/png/struct.Transformations.html#associatedconstant.STRIP_16)
374    pub fn png_set_strip_to_8bit(mut self, yes: bool) -> Self {
375        self.flags.png_strip_16_bit_to_8_bit = yes;
376        self
377    }
378
379    /// Return a boolean indicating whether the png decoder should reduce
380    /// 16 bit images to 8 bit images implicitly
381    pub const fn png_get_strip_to_8bit(&self) -> bool {
382        self.flags.png_strip_16_bit_to_8_bit
383    }
384
385    /// Return whether `zune-image` should decode animated images or
386    /// whether we should just decode the first frame only
387    pub const fn png_decode_animated(&self) -> bool {
388        self.flags.png_decode_animated
389    }
390    /// Set  whether `zune-image` should decode animated images or
391    /// whether we should just decode the first frame only
392    pub const fn png_set_decode_animated(mut self, yes: bool) -> Self {
393        self.flags.png_decode_animated = yes;
394        self
395    }
396}
397
398/// JPEG specific options
399impl DecoderOptions {
400    /// Get maximum scans for which the jpeg decoder
401    /// should not go above for progressive images
402    pub const fn jpeg_get_max_scans(&self) -> usize {
403        self.max_scans
404    }
405
406    /// Set maximum scans for which the jpeg decoder should
407    /// not exceed when reconstructing images.
408    pub fn jpeg_set_max_scans(mut self, max_scans: usize) -> Self {
409        self.max_scans = max_scans;
410        self
411    }
412    /// Get expected output colorspace set by the user for which the image
413    /// is expected to be reconstructed into.
414    ///
415    /// This may be different from the
416    pub const fn jpeg_get_out_colorspace(&self) -> ColorSpace {
417        self.out_colorspace
418    }
419    /// Set expected colorspace for which the jpeg output is expected to be in
420    ///
421    /// This is mainly provided as is, we do not guarantee the decoder can convert to all colorspaces
422    /// and the decoder can change it internally when it sees fit.
423    #[must_use]
424    pub fn jpeg_set_out_colorspace(mut self, colorspace: ColorSpace) -> Self {
425        self.out_colorspace = colorspace;
426        self
427    }
428}
429
430/// Intrinsics support
431///
432/// These routines are compiled depending
433/// on the platform they are used, if compiled for a platform
434/// it doesn't support,(e.g avx2 on Arm), it will always return `false`
435impl DecoderOptions {
436    /// Use SSE 2 code paths where possible
437    ///
438    /// This checks for existence of SSE2 first and returns
439    /// false if it's not present
440    #[allow(unreachable_code)]
441    pub fn use_sse2(&self) -> bool {
442        let opt = self.flags.zune_use_sse2 | self.flags.zune_use_unsafe;
443        // options says no
444        if !opt {
445            return false;
446        }
447
448        #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
449        {
450            // where we can do runtime check if feature is present
451            #[cfg(feature = "std")]
452            {
453                if is_x86_feature_detected!("sse2") {
454                    return true;
455                }
456            }
457            // where we can't do runtime check if feature is present
458            // check if the compile feature had it enabled
459            #[cfg(all(not(feature = "std"), target_feature = "sse2"))]
460            {
461                return true;
462            }
463        }
464        // everything failed return false
465        false
466    }
467
468    /// Use SSE 3 paths where possible
469    ///
470    ///
471    /// This also checks for SSE3 support and returns false if
472    /// it's not present
473    #[allow(unreachable_code)]
474    pub fn use_sse3(&self) -> bool {
475        let opt = self.flags.zune_use_sse3 | self.flags.zune_use_unsafe;
476        // options says no
477        if !opt {
478            return false;
479        }
480
481        #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
482        {
483            // where we can do runtime check if feature is present
484            #[cfg(feature = "std")]
485            {
486                if is_x86_feature_detected!("sse3") {
487                    return true;
488                }
489            }
490            // where we can't do runtime check if feature is present
491            // check if the compile feature had it enabled
492            #[cfg(all(not(feature = "std"), target_feature = "sse3"))]
493            {
494                return true;
495            }
496        }
497        // everything failed return false
498        false
499    }
500
501    /// Use SSE4 paths where possible
502    ///
503    /// This also checks for sse 4.1 support and returns false if it
504    /// is not present
505    #[allow(unreachable_code)]
506    pub fn use_sse41(&self) -> bool {
507        let opt = self.flags.zune_use_sse41 | self.flags.zune_use_unsafe;
508        // options says no
509        if !opt {
510            return false;
511        }
512
513        #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
514        {
515            // where we can do runtime check if feature is present
516            #[cfg(feature = "std")]
517            {
518                if is_x86_feature_detected!("sse4.1") {
519                    return true;
520                }
521            }
522            // where we can't do runtime check if feature is present
523            // check if the compile feature had it enabled
524            #[cfg(all(not(feature = "std"), target_feature = "sse4.1"))]
525            {
526                return true;
527            }
528        }
529        // everything failed return false
530        false
531    }
532
533    /// Use AVX paths where possible
534    ///
535    /// This also checks for AVX support and returns false if it's
536    /// not present
537    #[allow(unreachable_code)]
538    pub fn use_avx(&self) -> bool {
539        let opt = self.flags.zune_use_avx | self.flags.zune_use_unsafe;
540        // options says no
541        if !opt {
542            return false;
543        }
544
545        #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
546        {
547            // where we can do runtime check if feature is present
548            #[cfg(feature = "std")]
549            {
550                if is_x86_feature_detected!("avx") {
551                    return true;
552                }
553            }
554            // where we can't do runitme check if feature is present
555            // check if the compile feature had it enabled
556            #[cfg(all(not(feature = "std"), target_feature = "avx"))]
557            {
558                return true;
559            }
560        }
561        // everything failed return false
562        false
563    }
564
565    /// Use avx2 paths where possible
566    ///
567    /// This also checks for AVX2 support and returns false if it's not
568    /// present
569    #[allow(unreachable_code)]
570    pub fn use_avx2(&self) -> bool {
571        let opt = self.flags.zune_use_avx2 | self.flags.zune_use_unsafe;
572        // options says no
573        if !opt {
574            return false;
575        }
576
577        #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
578        {
579            // where we can do runtime check if feature is present
580            #[cfg(feature = "std")]
581            {
582                if is_x86_feature_detected!("avx2") {
583                    return true;
584                }
585            }
586            // where we can't do runitme check if feature is present
587            // check if the compile feature had it enabled
588            #[cfg(all(not(feature = "std"), target_feature = "avx2"))]
589            {
590                return true;
591            }
592        }
593        // everything failed return false
594        false
595    }
596
597    #[allow(unreachable_code)]
598    pub fn use_neon(&self) -> bool {
599        let opt = self.flags.zune_use_neon | self.flags.zune_use_unsafe;
600        // options says no
601        if !opt {
602            return false;
603        }
604
605        #[cfg(target_arch = "aarch64")]
606        {
607            // aarch64 implies neon on a compliant cpu
608            // but for real prod should do something better here
609            return true;
610        }
611        // everything failed return false
612        false
613    }
614}
615
616/// JPEG_XL specific options
617impl DecoderOptions {
618    /// Return whether `zune-image` should decode animated images or
619    /// whether we should just decode the first frame only
620    pub const fn jxl_decode_animated(&self) -> bool {
621        self.flags.jxl_decode_animated
622    }
623    /// Set  whether `zune-image` should decode animated images or
624    /// whether we should just decode the first frame only
625    pub const fn jxl_set_decode_animated(mut self, yes: bool) -> Self {
626        self.flags.jxl_decode_animated = yes;
627        self
628    }
629}
630impl Default for DecoderOptions {
631    /// Create a default and sane option for decoders
632    ///
633    /// The following are the defaults
634    ///
635    /// - All decoders
636    ///     - max_width: 16536
637    ///     - max_height: 16535
638    ///     - use_unsafe: Use unsafe intrinsics where possible.
639    ///
640    /// - JPEG
641    ///     - max_scans: 100 (progressive images only, artificial cap to prevent a specific DOS)
642    ///     - error_on_non_conformance: False (slightly corrupt images will be allowed)
643    /// - DEFLATE
644    ///     - deflate_limit: 1GB (will not continue decoding deflate archives larger than this)
645    /// - PNG
646    ///   - endianness: Default endianess is Big Endian when decoding 16 bit images to be viewed as 8 byte images
647    ///   - confirm_crc: False (CRC will not be confirmed to be safe)
648    ///   - strip_16_bit_to_8: False, 16 bit images are handled as 16 bit images
649    ///   - add alpha: False, alpha channel is not added where it isn't present
650    ///   - decode_animated: True: All frames in an animated image are decoded
651    ///
652    ///  - JXL
653    ///    - decode_animated: True: All frames in an animated image are decoded
654    ///
655    fn default() -> Self {
656        Self {
657            out_colorspace: ColorSpace::RGB,
658            max_width:      1 << 14,
659            max_height:     1 << 14,
660            max_scans:      100,
661            deflate_limit:  1 << 30,
662            flags:          decoder_error_tolerance_mode(),
663            endianness:     ByteEndian::BE
664        }
665    }
666}