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}