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}