Skip to main content

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