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 | |
12 | use crate::bit_depth::ByteEndian; |
13 | use crate::colorspace::ColorSpace; |
14 | |
15 | fn 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. |
40 | fn 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 |
67 | fn 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)] |
94 | pub 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)] |
133 | pub 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 |
179 | impl 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 |
213 | impl 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 |
333 | impl 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 |
414 | impl 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` |
450 | impl 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 |
632 | impl 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 | } |
645 | impl 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 | } |
658 | |