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