1#[cfg(feature = "std")]
8pub mod decode {
9 use core::fmt;
10
11 use azul_core::resources::{RawImage, RawImageFormat};
12 use azul_css::{impl_result, impl_result_inner, U8Vec};
13 use image::{
14 error::{ImageError, LimitError, LimitErrorKind},
15 DynamicImage,
16 };
17
18 #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
20 #[repr(C)]
21 pub enum DecodeImageError {
22 InsufficientMemory,
23 DimensionError,
24 UnsupportedImageFormat,
25 Unknown,
26 }
27
28 impl fmt::Display for DecodeImageError {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::InsufficientMemory => write!(
32 f,
33 "Error decoding image: Not enough memory available to perform encoding \
34 operation"
35 ),
36 Self::DimensionError => {
37 write!(f, "Error decoding image: Wrong dimensions")
38 }
39 Self::UnsupportedImageFormat => {
40 write!(f, "Error decoding image: Invalid data format")
41 }
42 Self::Unknown => write!(f, "Error decoding image: Unknown error"),
43 }
44 }
45 }
46
47 fn translate_image_error_decode(i: ImageError) -> DecodeImageError {
48 match i {
49 ImageError::Limits(l) => match l.kind() {
50 LimitErrorKind::InsufficientMemory => DecodeImageError::InsufficientMemory,
51 LimitErrorKind::DimensionError => DecodeImageError::DimensionError,
52 _ => DecodeImageError::Unknown,
53 },
54 _ => DecodeImageError::Unknown,
55 }
56 }
57
58 impl_result!(
59 RawImage,
60 DecodeImageError,
61 ResultRawImageDecodeImageError,
62 copy = false,
63 [Debug, Clone]
64 );
65
66 #[must_use] pub fn decode_raw_image_from_any_bytes(image_bytes: &[u8]) -> ResultRawImageDecodeImageError {
71 use azul_core::resources::RawImageData;
72
73 let image_format = match image::guess_format(image_bytes) {
74 Ok(o) => o,
75 Err(e) => {
76 return ResultRawImageDecodeImageError::Err(translate_image_error_decode(e));
77 }
78 };
79
80 let decoded = match image::load_from_memory_with_format(image_bytes, image_format) {
81 Ok(o) => o,
82 Err(e) => {
83 return ResultRawImageDecodeImageError::Err(translate_image_error_decode(e));
84 }
85 };
86
87 let ((width, height), data_format, pixels) = match decoded {
88 DynamicImage::ImageLuma8(i) => (
89 i.dimensions(),
90 RawImageFormat::R8,
91 RawImageData::U8(i.into_vec().into()),
92 ),
93 DynamicImage::ImageLumaA8(i) => (
94 i.dimensions(),
95 RawImageFormat::RG8,
96 RawImageData::U8(i.into_vec().into()),
97 ),
98 DynamicImage::ImageRgb8(i) => (
99 i.dimensions(),
100 RawImageFormat::RGB8,
101 RawImageData::U8(i.into_vec().into()),
102 ),
103 DynamicImage::ImageRgba8(i) => (
104 i.dimensions(),
105 RawImageFormat::RGBA8,
106 RawImageData::U8(i.into_vec().into()),
107 ),
108 DynamicImage::ImageLuma16(i) => (
109 i.dimensions(),
110 RawImageFormat::R16,
111 RawImageData::U16(i.into_vec().into()),
112 ),
113 DynamicImage::ImageLumaA16(i) => (
114 i.dimensions(),
115 RawImageFormat::RG16,
116 RawImageData::U16(i.into_vec().into()),
117 ),
118 DynamicImage::ImageRgb16(i) => (
119 i.dimensions(),
120 RawImageFormat::RGB16,
121 RawImageData::U16(i.into_vec().into()),
122 ),
123 DynamicImage::ImageRgba16(i) => (
124 i.dimensions(),
125 RawImageFormat::RGBA16,
126 RawImageData::U16(i.into_vec().into()),
127 ),
128 DynamicImage::ImageRgb32F(i) => (
129 i.dimensions(),
130 RawImageFormat::RGBF32,
131 RawImageData::F32(i.into_vec().into()),
132 ),
133 DynamicImage::ImageRgba32F(i) => (
134 i.dimensions(),
135 RawImageFormat::RGBAF32,
136 RawImageData::F32(i.into_vec().into()),
137 ),
138 _ => {
139 return ResultRawImageDecodeImageError::Err(DecodeImageError::Unknown);
140 }
141 };
142
143 ResultRawImageDecodeImageError::Ok(RawImage {
144 tag: Vec::new().into(),
145 pixels,
146 width: width as usize,
147 height: height as usize,
148 premultiplied_alpha: false,
149 data_format,
150 })
151 }
152
153 #[cfg(test)]
154 mod autotest_generated {
155 use std::collections::BTreeSet;
156
157 use image::error::{
158 DecodingError, ImageFormatHint, ParameterError, ParameterErrorKind, UnsupportedError,
159 UnsupportedErrorKind,
160 };
161
162 use super::*;
163
164 const ALL_ERRORS: [DecodeImageError; 4] = [
165 DecodeImageError::InsufficientMemory,
166 DecodeImageError::DimensionError,
167 DecodeImageError::UnsupportedImageFormat,
168 DecodeImageError::Unknown,
169 ];
170
171 fn err_of(r: &ResultRawImageDecodeImageError) -> DecodeImageError {
172 match r.as_result() {
173 Ok(_) => panic!("expected Err, got Ok"),
174 Err(e) => *e,
175 }
176 }
177
178 #[test]
181 fn display_every_variant_is_non_empty_and_unique() {
182 let rendered = ALL_ERRORS.iter().map(|e| e.to_string()).collect::<Vec<_>>();
183 for (e, s) in ALL_ERRORS.iter().zip(rendered.iter()) {
184 assert!(!s.is_empty(), "empty Display for {e:?}");
185 assert!(
186 s.starts_with("Error decoding image: "),
187 "unexpected Display prefix for {e:?}: {s}"
188 );
189 }
190 let unique = rendered.iter().collect::<BTreeSet<_>>();
191 assert_eq!(unique.len(), ALL_ERRORS.len(), "Display collides: {rendered:?}");
192 }
193
194 #[test]
195 fn display_text_is_stable() {
196 assert_eq!(
197 DecodeImageError::DimensionError.to_string(),
198 "Error decoding image: Wrong dimensions"
199 );
200 assert_eq!(
201 DecodeImageError::UnsupportedImageFormat.to_string(),
202 "Error decoding image: Invalid data format"
203 );
204 assert_eq!(
205 DecodeImageError::Unknown.to_string(),
206 "Error decoding image: Unknown error"
207 );
208 assert_eq!(
211 DecodeImageError::InsufficientMemory.to_string(),
212 "Error decoding image: Not enough memory available to perform encoding operation"
213 );
214 }
215
216 #[test]
217 fn display_ignores_width_and_precision_specs_without_panicking() {
218 for e in ALL_ERRORS {
219 let plain = format!("{e}");
221 assert_eq!(format!("{e:>200}"), plain);
222 assert_eq!(format!("{e:.1}"), plain);
223 assert_eq!(format!("{e:^0}"), plain);
224 assert!(!format!("{e:?}").is_empty());
225 }
226 }
227
228 #[test]
229 fn derived_ord_is_total_and_matches_declaration_order() {
230 for (i, a) in ALL_ERRORS.iter().enumerate() {
231 for (j, b) in ALL_ERRORS.iter().enumerate() {
232 assert_eq!(a.cmp(b), i.cmp(&j), "Ord disagrees for {a:?} vs {b:?}");
233 assert_eq!(a.partial_cmp(b), Some(a.cmp(b)));
234 assert_eq!(a == b, i == j);
235 }
236 }
237 }
238
239 #[test]
242 fn translate_maps_limit_kinds() {
243 assert_eq!(
244 translate_image_error_decode(ImageError::Limits(LimitError::from_kind(
245 LimitErrorKind::InsufficientMemory
246 ))),
247 DecodeImageError::InsufficientMemory
248 );
249 assert_eq!(
250 translate_image_error_decode(ImageError::Limits(LimitError::from_kind(
251 LimitErrorKind::DimensionError
252 ))),
253 DecodeImageError::DimensionError
254 );
255 assert_eq!(
257 translate_image_error_decode(ImageError::Limits(LimitError::from_kind(
258 LimitErrorKind::Unsupported {
259 limits: image::Limits::default(),
260 supported: image::LimitSupport::default(),
261 }
262 ))),
263 DecodeImageError::Unknown
264 );
265 }
266
267 #[test]
268 fn translate_maps_every_other_variant_to_unknown() {
269 let cases = vec![
270 ImageError::IoError(std::io::Error::other("boom")),
271 ImageError::Decoding(DecodingError::new(ImageFormatHint::Unknown, "bad chunk")),
272 ImageError::Unsupported(UnsupportedError::from_format_and_kind(
273 ImageFormatHint::Unknown,
274 UnsupportedErrorKind::Format(ImageFormatHint::Unknown),
275 )),
276 ImageError::Parameter(ParameterError::from_kind(
279 ParameterErrorKind::DimensionMismatch,
280 )),
281 ImageError::Parameter(ParameterError::from_kind(ParameterErrorKind::NoMoreData)),
282 ];
283 for c in cases {
284 let debug = format!("{c:?}");
285 assert_eq!(
286 translate_image_error_decode(c),
287 DecodeImageError::Unknown,
288 "unexpected mapping for {debug}"
289 );
290 }
291 }
292
293 #[test]
296 fn decode_empty_input_errors_without_panicking() {
297 let r = decode_raw_image_from_any_bytes(&[]);
298 assert!(r.is_err());
299 assert_eq!(err_of(&r), DecodeImageError::Unknown);
300 }
301
302 #[test]
303 fn decode_whitespace_and_text_bytes_error() {
304 for input in [
305 &b" "[..],
306 &b"\t\n\r\n"[..],
307 &b"not an image at all"[..],
308 "\u{1F600} combining a\u{0301}\u{0308}".as_bytes(),
309 "\u{FEFF}\u{202E}\u{0000}".as_bytes(),
310 ] {
311 let r = decode_raw_image_from_any_bytes(input);
312 assert!(r.is_err(), "unexpectedly decoded {input:?}");
313 assert_eq!(err_of(&r), DecodeImageError::Unknown);
314 }
315 }
316
317 #[test]
318 fn decode_invalid_utf8_and_garbage_bytes_error() {
319 for input in [
320 &[0xFF, 0xFE, 0x00][..],
321 &[0xFF][..],
322 &[0x00, 0x00, 0x00, 0x00][..],
323 &[0xC0, 0x80, 0xED, 0xA0, 0x80][..],
324 ] {
325 let r = decode_raw_image_from_any_bytes(input);
326 assert!(r.is_err(), "unexpectedly decoded {input:?}");
327 assert_eq!(err_of(&r), DecodeImageError::Unknown);
328 }
329 }
330
331 #[test]
332 fn decode_every_single_byte_input_errors() {
333 for b in 0u8..=255 {
334 let r = decode_raw_image_from_any_bytes(&[b]);
335 assert!(r.is_err(), "single byte {b:#04X} decoded to an image");
336 }
337 }
338
339 #[test]
340 fn decode_extremely_long_garbage_does_not_hang_or_panic() {
341 let huge = vec![0xAAu8; 1_000_000];
342 let r = decode_raw_image_from_any_bytes(&huge);
343 assert!(r.is_err());
344 assert_eq!(err_of(&r), DecodeImageError::Unknown);
345 }
346
347 #[test]
351 fn decode_valid_magic_with_no_payload_errors() {
352 let png_magic = b"\x89PNG\r\n\x1a\n";
353 let gif_magic = b"GIF89a";
354 let bmp_magic = b"BM";
355 let jpeg_magic = b"\xFF\xD8\xFF";
356
357 for magic in [&png_magic[..], &gif_magic[..], &bmp_magic[..], &jpeg_magic[..]] {
358 let mut bytes = magic.to_vec();
359 let r = decode_raw_image_from_any_bytes(&bytes);
360 assert!(r.is_err(), "header-only input decoded: {magic:?}");
361
362 bytes.extend(core::iter::repeat_n(0x7Fu8, 512));
364 let r = decode_raw_image_from_any_bytes(&bytes);
365 assert!(r.is_err(), "header + garbage decoded: {magic:?}");
366 }
367 }
368
369 #[cfg(feature = "png")]
370 fn png_of_2x2_rgba8(fill: u8) -> Vec<u8> {
371 use azul_core::resources::RawImageData;
372
373 let img = RawImage {
374 pixels: RawImageData::U8(vec![fill; 16].into()),
375 width: 2,
376 height: 2,
377 premultiplied_alpha: false,
378 data_format: RawImageFormat::RGBA8,
379 tag: Vec::<u8>::new().into(),
380 };
381 crate::image::encode::encode_png(&img)
382 .into_result()
383 .expect("png encode of a well-formed 2x2 RGBA8 image failed")
384 .as_slice()
385 .to_vec()
386 }
387
388 #[cfg(feature = "png")]
389 #[test]
390 fn decode_of_every_truncation_of_a_real_png_never_panics() {
391 let bytes = png_of_2x2_rgba8(1);
392 assert!(bytes.len() > 33, "PNG shorter than signature + IHDR");
393
394 for len in 0..bytes.len() {
395 let r = decode_raw_image_from_any_bytes(&bytes[..len]);
396 match r.as_result() {
397 Ok(img) => {
400 assert_eq!(
401 (img.width, img.height),
402 (2, 2),
403 "truncation to {len} bytes resized the image"
404 );
405 assert_eq!(img.pixels.get_u8_vec_ref().expect("8-bit data").len(), 16);
406 }
407 Err(e) => {
409 assert_eq!(*e, DecodeImageError::Unknown, "truncation to {len} bytes");
410 }
411 }
412 if len <= 33 {
413 assert!(r.is_err(), "a {len}-byte PNG prefix decoded to an image");
414 }
415 }
416 }
417
418 #[cfg(feature = "png")]
419 #[test]
420 fn decode_of_a_corrupted_png_never_panics_or_invents_geometry() {
421 let bytes = png_of_2x2_rgba8(9);
422
423 for i in 0..bytes.len() {
425 let mut corrupted = bytes.clone();
426 corrupted[i] ^= 0xFF;
427 let r = decode_raw_image_from_any_bytes(&corrupted);
428 if let Ok(img) = r.as_result() {
431 assert!(img.width <= 2 && img.height <= 2, "corrupt byte {i} grew the image");
432 }
433 }
434 }
435 }
436}
437#[cfg(feature = "std")]
438pub mod encode {
439 use alloc::vec::Vec;
440 use core::fmt;
441 use std::io::Cursor;
442
443 use azul_core::resources::{RawImage, RawImageFormat};
444 use azul_css::{impl_result, impl_result_inner, U8Vec};
445 #[cfg(feature = "bmp")]
446 use image::codecs::bmp::BmpEncoder;
447 #[cfg(feature = "gif")]
448 use image::codecs::gif::GifEncoder;
449#[cfg(feature = "jpeg")]
450 use image::codecs::jpeg::JpegEncoder;
451 #[cfg(feature = "png")]
452 use image::codecs::png::PngEncoder;
453 #[cfg(feature = "pnm")]
454 use image::codecs::pnm::PnmEncoder;
455 #[cfg(feature = "tga")]
456 use image::codecs::tga::TgaEncoder;
457 #[cfg(feature = "tiff")]
458 use image::codecs::tiff::TiffEncoder;
459 use image::error::{ImageError, LimitError, LimitErrorKind};
460
461 #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
463 #[repr(C)]
464 pub enum EncodeImageError {
465 EncoderNotAvailable,
467 InsufficientMemory,
468 DimensionError,
469 InvalidData,
470 Unknown,
471 }
472
473 impl fmt::Display for EncodeImageError {
474 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475 use self::EncodeImageError::{EncoderNotAvailable, InsufficientMemory, DimensionError, InvalidData, Unknown};
476 match self {
477 EncoderNotAvailable => write!(
478 f,
479 "Missing encoder (library was not compiled with given codec)"
480 ),
481 InsufficientMemory => write!(
482 f,
483 "Error encoding image: Not enough memory available to perform encoding \
484 operation"
485 ),
486 DimensionError => write!(f, "Error encoding image: Wrong dimensions"),
487 InvalidData => write!(f, "Error encoding image: Invalid data format"),
488 Unknown => write!(f, "Error encoding image: Unknown error"),
489 }
490 }
491 }
492
493 const fn translate_rawimage_colortype(i: RawImageFormat) -> image::ColorType {
494 match i {
495 RawImageFormat::R8 => image::ColorType::L8,
496 RawImageFormat::RG8 => image::ColorType::La8,
497 RawImageFormat::RGB8 | RawImageFormat::BGR8 => image::ColorType::Rgb8,
498 RawImageFormat::RGBA8 | RawImageFormat::BGRA8 => image::ColorType::Rgba8,
499 RawImageFormat::R16 => image::ColorType::L16,
500 RawImageFormat::RG16 => image::ColorType::La16,
501 RawImageFormat::RGB16 => image::ColorType::Rgb16,
502 RawImageFormat::RGBA16 => image::ColorType::Rgba16,
503 RawImageFormat::RGBF32 => image::ColorType::Rgb32F,
504 RawImageFormat::RGBAF32 => image::ColorType::Rgba32F,
505 }
506 }
507
508 fn bgr_to_rgb_swap(pixels: &[u8], format: RawImageFormat) -> Option<Vec<u8>> {
509 match format {
510 RawImageFormat::BGR8 => {
511 let mut out = pixels.to_vec();
512 for chunk in out.chunks_exact_mut(3) {
513 chunk.swap(0, 2);
514 }
515 Some(out)
516 }
517 RawImageFormat::BGRA8 => {
518 let mut out = pixels.to_vec();
519 for chunk in out.chunks_exact_mut(4) {
520 chunk.swap(0, 2);
521 }
522 Some(out)
523 }
524 _ => None,
525 }
526 }
527
528 fn translate_image_error_encode(i: ImageError) -> EncodeImageError {
529 match i {
530 ImageError::Limits(l) => match l.kind() {
531 LimitErrorKind::InsufficientMemory => EncodeImageError::InsufficientMemory,
532 LimitErrorKind::DimensionError => EncodeImageError::DimensionError,
533 _ => EncodeImageError::Unknown,
534 },
535 _ => EncodeImageError::Unknown,
536 }
537 }
538
539 impl_result!(
540 U8Vec,
541 EncodeImageError,
542 ResultU8VecEncodeImageError,
543 copy = false,
544 [Debug, Clone]
545 );
546
547 macro_rules! encode_func {
548 ($func:ident, $encoder:ident, $feature:expr) => {
549 #[cfg(feature = $feature)]
550 pub fn $func(image: &RawImage) -> ResultU8VecEncodeImageError {
551 let width = match u32::try_from(image.width) {
552 Ok(w) => w,
553 Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
554 };
555 let height = match u32::try_from(image.height) {
556 Ok(h) => h,
557 Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
558 };
559 let mut result = Vec::<u8>::new();
560
561 {
562 let mut cursor = Cursor::new(&mut result);
563 let mut encoder = $encoder::new(&mut cursor);
564 let pixels = match image.pixels.get_u8_vec_ref() {
565 Some(s) => s,
566 None => {
567 return ResultU8VecEncodeImageError::Err(EncodeImageError::InvalidData);
568 }
569 };
570
571 let swapped = bgr_to_rgb_swap(pixels.as_ref(), image.data_format);
572 let pixel_bytes = swapped.as_deref().unwrap_or(pixels.as_ref());
573
574 if let Err(e) = encoder.encode(
575 pixel_bytes,
576 width,
577 height,
578 translate_rawimage_colortype(image.data_format).into(),
579 ) {
580 return ResultU8VecEncodeImageError::Err(translate_image_error_encode(e));
581 }
582 }
583
584 ResultU8VecEncodeImageError::Ok(result.into())
585 }
586
587 #[cfg(not(feature = $feature))]
588 #[must_use] pub const fn $func(image: &RawImage) -> ResultU8VecEncodeImageError {
589 ResultU8VecEncodeImageError::Err(EncodeImageError::EncoderNotAvailable)
590 }
591 };
592 }
593
594 encode_func!(encode_bmp, BmpEncoder, "bmp");
595 encode_func!(encode_tga, TgaEncoder, "tga");
596 encode_func!(encode_tiff, TiffEncoder, "tiff");
597 encode_func!(encode_gif, GifEncoder, "gif");
598 encode_func!(encode_pnm, PnmEncoder, "pnm");
599
600 #[cfg(feature = "png")]
601 pub fn encode_png(image: &RawImage) -> ResultU8VecEncodeImageError {
602 use image::ImageEncoder;
603
604 let width = match u32::try_from(image.width) {
605 Ok(w) => w,
606 Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
607 };
608 let height = match u32::try_from(image.height) {
609 Ok(h) => h,
610 Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
611 };
612 let mut result = Vec::<u8>::new();
613
614 {
615 let mut cursor = Cursor::new(&mut result);
616 let mut encoder = PngEncoder::new_with_quality(
617 &mut cursor,
618 image::codecs::png::CompressionType::Best,
619 image::codecs::png::FilterType::Adaptive,
620 );
621 let pixels = match image.pixels.get_u8_vec_ref() {
622 Some(s) => s,
623 None => {
624 return ResultU8VecEncodeImageError::Err(EncodeImageError::InvalidData);
625 }
626 };
627
628 let swapped = bgr_to_rgb_swap(pixels.as_ref(), image.data_format);
629 let pixel_bytes = swapped.as_deref().unwrap_or(pixels.as_ref());
630
631 if let Err(e) = encoder.write_image(
632 pixel_bytes,
633 width,
634 height,
635 translate_rawimage_colortype(image.data_format).into(),
636 ) {
637 return ResultU8VecEncodeImageError::Err(translate_image_error_encode(e));
638 }
639 }
640
641 ResultU8VecEncodeImageError::Ok(result.into())
642 }
643
644 #[cfg(not(feature = "png"))]
645 #[must_use] pub const fn encode_png(image: &RawImage) -> ResultU8VecEncodeImageError {
646 ResultU8VecEncodeImageError::Err(EncodeImageError::EncoderNotAvailable)
647 }
648
649 #[cfg(feature = "jpeg")]
650 pub fn encode_jpeg(image: &RawImage, quality: u8) -> ResultU8VecEncodeImageError {
651 let width = match u32::try_from(image.width) {
652 Ok(w) => w,
653 Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
654 };
655 let height = match u32::try_from(image.height) {
656 Ok(h) => h,
657 Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
658 };
659 let mut result = Vec::<u8>::new();
660
661 {
662 let mut cursor = Cursor::new(&mut result);
663 let mut encoder = JpegEncoder::new_with_quality(&mut cursor, quality);
664 let pixels = match image.pixels.get_u8_vec_ref() {
665 Some(s) => s,
666 None => {
667 return ResultU8VecEncodeImageError::Err(EncodeImageError::InvalidData);
668 }
669 };
670
671 let swapped = bgr_to_rgb_swap(pixels.as_ref(), image.data_format);
672 let pixel_bytes = swapped.as_deref().unwrap_or(pixels.as_ref());
673
674 if let Err(e) = encoder.encode(
675 pixel_bytes,
676 width,
677 height,
678 translate_rawimage_colortype(image.data_format).into(),
679 ) {
680 return ResultU8VecEncodeImageError::Err(translate_image_error_encode(e));
681 }
682 }
683
684 ResultU8VecEncodeImageError::Ok(result.into())
685 }
686
687 #[cfg(not(feature = "jpeg"))]
688 #[must_use] pub const fn encode_jpeg(image: &RawImage, quality: u8) -> ResultU8VecEncodeImageError {
689 ResultU8VecEncodeImageError::Err(EncodeImageError::EncoderNotAvailable)
690 }
691
692 #[cfg(test)]
693 mod autotest_generated {
694 use std::collections::BTreeSet;
695
696 use azul_core::resources::RawImageData;
697 use image::error::{
698 DecodingError, EncodingError, ImageFormatHint, ParameterError, ParameterErrorKind,
699 UnsupportedError, UnsupportedErrorKind,
700 };
701
702 use super::*;
703
704 const ALL_ERRORS: [EncodeImageError; 5] = [
705 EncodeImageError::EncoderNotAvailable,
706 EncodeImageError::InsufficientMemory,
707 EncodeImageError::DimensionError,
708 EncodeImageError::InvalidData,
709 EncodeImageError::Unknown,
710 ];
711
712 const ALL_FORMATS: [RawImageFormat; 12] = [
713 RawImageFormat::R8,
714 RawImageFormat::RG8,
715 RawImageFormat::RGB8,
716 RawImageFormat::RGBA8,
717 RawImageFormat::R16,
718 RawImageFormat::RG16,
719 RawImageFormat::RGB16,
720 RawImageFormat::RGBA16,
721 RawImageFormat::BGR8,
722 RawImageFormat::BGRA8,
723 RawImageFormat::RGBF32,
724 RawImageFormat::RGBAF32,
725 ];
726
727 fn raw_u8(
728 width: usize,
729 height: usize,
730 data_format: RawImageFormat,
731 pixels: Vec<u8>,
732 ) -> RawImage {
733 RawImage {
734 pixels: RawImageData::U8(pixels.into()),
735 width,
736 height,
737 premultiplied_alpha: false,
738 data_format,
739 tag: Vec::<u8>::new().into(),
740 }
741 }
742
743 #[allow(dead_code)]
744 fn err_of(r: &ResultU8VecEncodeImageError) -> EncodeImageError {
745 match r.as_result() {
746 Ok(_) => panic!("expected Err, got Ok"),
747 Err(e) => *e,
748 }
749 }
750
751 #[test]
754 fn display_every_variant_is_non_empty_and_unique() {
755 let rendered = ALL_ERRORS.iter().map(|e| e.to_string()).collect::<Vec<_>>();
756 for (e, s) in ALL_ERRORS.iter().zip(rendered.iter()) {
757 assert!(!s.is_empty(), "empty Display for {e:?}");
758 }
759 let unique = rendered.iter().collect::<BTreeSet<_>>();
760 assert_eq!(unique.len(), ALL_ERRORS.len(), "Display collides: {rendered:?}");
761 }
762
763 #[test]
764 fn display_text_is_stable() {
765 assert_eq!(
767 EncodeImageError::EncoderNotAvailable.to_string(),
768 "Missing encoder (library was not compiled with given codec)"
769 );
770 assert_eq!(
771 EncodeImageError::InsufficientMemory.to_string(),
772 "Error encoding image: Not enough memory available to perform encoding operation"
773 );
774 assert_eq!(
775 EncodeImageError::DimensionError.to_string(),
776 "Error encoding image: Wrong dimensions"
777 );
778 assert_eq!(
779 EncodeImageError::InvalidData.to_string(),
780 "Error encoding image: Invalid data format"
781 );
782 assert_eq!(
783 EncodeImageError::Unknown.to_string(),
784 "Error encoding image: Unknown error"
785 );
786 }
787
788 #[test]
789 fn display_ignores_width_and_precision_specs_without_panicking() {
790 for e in ALL_ERRORS {
791 let plain = format!("{e}");
792 assert_eq!(format!("{e:>512}"), plain);
793 assert_eq!(format!("{e:.0}"), plain);
794 assert!(!format!("{e:?}").is_empty());
795 }
796 }
797
798 #[test]
799 fn derived_ord_is_total_and_matches_declaration_order() {
800 for (i, a) in ALL_ERRORS.iter().enumerate() {
801 for (j, b) in ALL_ERRORS.iter().enumerate() {
802 assert_eq!(a.cmp(b), i.cmp(&j), "Ord disagrees for {a:?} vs {b:?}");
803 assert_eq!(a.partial_cmp(b), Some(a.cmp(b)));
804 }
805 }
806 }
807
808 #[test]
811 fn colortype_mapping_is_exhaustive_and_stable() {
812 use image::ColorType::{L16, L8, La16, La8, Rgb16, Rgb32F, Rgb8, Rgba16, Rgba32F, Rgba8};
813
814 let expected = [
815 (RawImageFormat::R8, L8),
816 (RawImageFormat::RG8, La8),
817 (RawImageFormat::RGB8, Rgb8),
818 (RawImageFormat::RGBA8, Rgba8),
819 (RawImageFormat::R16, L16),
820 (RawImageFormat::RG16, La16),
821 (RawImageFormat::RGB16, Rgb16),
822 (RawImageFormat::RGBA16, Rgba16),
823 (RawImageFormat::BGR8, Rgb8),
824 (RawImageFormat::BGRA8, Rgba8),
825 (RawImageFormat::RGBF32, Rgb32F),
826 (RawImageFormat::RGBAF32, Rgba32F),
827 ];
828 assert_eq!(expected.len(), ALL_FORMATS.len(), "a RawImageFormat variant is untested");
829 for (format, want) in expected {
830 assert_eq!(
831 translate_rawimage_colortype(format),
832 want,
833 "wrong ColorType for {format:?}"
834 );
835 }
836 }
837
838 #[test]
839 fn colortype_maps_bgr_onto_its_rgb_counterpart() {
840 assert_eq!(
843 translate_rawimage_colortype(RawImageFormat::BGR8),
844 translate_rawimage_colortype(RawImageFormat::RGB8)
845 );
846 assert_eq!(
847 translate_rawimage_colortype(RawImageFormat::BGRA8),
848 translate_rawimage_colortype(RawImageFormat::RGBA8)
849 );
850 }
851
852 #[test]
853 fn colortype_is_usable_in_const_context() {
854 const C: image::ColorType = translate_rawimage_colortype(RawImageFormat::RGBA16);
855 assert_eq!(C, image::ColorType::Rgba16);
856 }
857
858 #[test]
859 fn colortype_channel_count_matches_the_source_format() {
860 for f in ALL_FORMATS {
861 let channels = translate_rawimage_colortype(f).channel_count();
862 let want = match f {
863 RawImageFormat::R8 | RawImageFormat::R16 => 1,
864 RawImageFormat::RG8 | RawImageFormat::RG16 => 2,
865 RawImageFormat::RGB8
866 | RawImageFormat::BGR8
867 | RawImageFormat::RGB16
868 | RawImageFormat::RGBF32 => 3,
869 RawImageFormat::RGBA8
870 | RawImageFormat::BGRA8
871 | RawImageFormat::RGBA16
872 | RawImageFormat::RGBAF32 => 4,
873 };
874 assert_eq!(channels, want, "channel count mismatch for {f:?}");
875 }
876 }
877
878 #[test]
881 fn swap_returns_none_for_every_non_bgr_format() {
882 for f in ALL_FORMATS {
883 if matches!(f, RawImageFormat::BGR8 | RawImageFormat::BGRA8) {
884 continue;
885 }
886 assert_eq!(bgr_to_rgb_swap(&[1, 2, 3, 4, 5, 6], f), None, "{f:?} should not swap");
887 assert_eq!(bgr_to_rgb_swap(&[], f), None, "{f:?} should not swap (empty)");
888 }
889 }
890
891 #[test]
892 fn swap_of_empty_input_is_some_empty_not_none() {
893 assert_eq!(bgr_to_rgb_swap(&[], RawImageFormat::BGR8), Some(Vec::new()));
895 assert_eq!(bgr_to_rgb_swap(&[], RawImageFormat::BGRA8), Some(Vec::new()));
896 }
897
898 #[test]
899 fn swap_bgr8_swaps_red_and_blue_only() {
900 assert_eq!(
901 bgr_to_rgb_swap(&[1, 2, 3, 4, 5, 6], RawImageFormat::BGR8),
902 Some(vec![3, 2, 1, 6, 5, 4])
903 );
904 }
905
906 #[test]
907 fn swap_bgra8_preserves_the_alpha_channel() {
908 assert_eq!(
909 bgr_to_rgb_swap(&[1, 2, 3, 200, 4, 5, 6, 201], RawImageFormat::BGRA8),
910 Some(vec![3, 2, 1, 200, 6, 5, 4, 201])
911 );
912 }
913
914 #[test]
917 fn swap_leaves_a_trailing_partial_pixel_untouched() {
918 assert_eq!(bgr_to_rgb_swap(&[1], RawImageFormat::BGR8), Some(vec![1]));
919 assert_eq!(bgr_to_rgb_swap(&[1, 2], RawImageFormat::BGR8), Some(vec![1, 2]));
920 assert_eq!(
921 bgr_to_rgb_swap(&[1, 2, 3, 4], RawImageFormat::BGR8),
922 Some(vec![3, 2, 1, 4])
923 );
924 for len in 0..4usize {
925 let input = (0..len as u8).collect::<Vec<_>>();
926 assert_eq!(
927 bgr_to_rgb_swap(&input, RawImageFormat::BGRA8),
928 Some(input.clone()),
929 "a partial BGRA8 pixel of {len} byte(s) must pass through unchanged"
930 );
931 }
932 assert_eq!(
933 bgr_to_rgb_swap(&[1, 2, 3, 4, 5, 6, 7], RawImageFormat::BGRA8),
934 Some(vec![3, 2, 1, 4, 5, 6, 7])
935 );
936 }
937
938 #[test]
939 fn swap_never_changes_the_length_and_is_its_own_inverse() {
940 for (format, stride) in [(RawImageFormat::BGR8, 3usize), (RawImageFormat::BGRA8, 4)] {
941 for len in 0..64usize {
942 let input = (0..len).map(|i| (i % 251) as u8).collect::<Vec<_>>();
943 let once = bgr_to_rgb_swap(&input, format).expect("BGR format must swap");
944 assert_eq!(once.len(), input.len(), "length changed for {format:?}, len {len}");
945 let twice = bgr_to_rgb_swap(&once, format).expect("BGR format must swap");
946 assert_eq!(twice, input, "swap is not an involution for {format:?}, len {len}");
947 let tail = len - (len / stride) * stride;
949 assert_eq!(once[len - tail..], input[len - tail..]);
950 }
951 }
952 }
953
954 #[test]
955 fn swap_handles_arbitrary_non_utf8_and_unicode_bytes() {
956 assert_eq!(
957 bgr_to_rgb_swap(&[0xFF, 0xFE, 0x00], RawImageFormat::BGR8),
958 Some(vec![0x00, 0xFE, 0xFF])
959 );
960 assert_eq!(
962 bgr_to_rgb_swap("\u{1F600}".as_bytes(), RawImageFormat::BGRA8),
963 Some(vec![0x98, 0x9F, 0xF0, 0x80])
964 );
965 assert_eq!(
966 bgr_to_rgb_swap(b" \t\n", RawImageFormat::BGR8),
967 Some(vec![b' ', b' ', b' ', b'\t', b'\n'])
968 );
969 assert_eq!(
970 bgr_to_rgb_swap(&[0, 0, 0, 255, 255, 255], RawImageFormat::BGR8),
971 Some(vec![0, 0, 0, 255, 255, 255])
972 );
973 }
974
975 #[test]
976 fn swap_of_a_very_large_buffer_does_not_hang() {
977 const LEN: usize = 3 * 333_333 + 2; let input = (0..LEN).map(|i| (i % 256) as u8).collect::<Vec<_>>();
979 let out = bgr_to_rgb_swap(&input, RawImageFormat::BGR8).expect("BGR8 must swap");
980 assert_eq!(out.len(), LEN);
981 assert_eq!(out[0], input[2]);
982 assert_eq!(out[2], input[0]);
983 assert_eq!(out[LEN - 1], input[LEN - 1]); assert_eq!(out[LEN - 2], input[LEN - 2]);
985 }
986
987 #[test]
990 fn translate_maps_limit_kinds() {
991 assert_eq!(
992 translate_image_error_encode(ImageError::Limits(LimitError::from_kind(
993 LimitErrorKind::InsufficientMemory
994 ))),
995 EncodeImageError::InsufficientMemory
996 );
997 assert_eq!(
998 translate_image_error_encode(ImageError::Limits(LimitError::from_kind(
999 LimitErrorKind::DimensionError
1000 ))),
1001 EncodeImageError::DimensionError
1002 );
1003 assert_eq!(
1004 translate_image_error_encode(ImageError::Limits(LimitError::from_kind(
1005 LimitErrorKind::Unsupported {
1006 limits: image::Limits::default(),
1007 supported: image::LimitSupport::default(),
1008 }
1009 ))),
1010 EncodeImageError::Unknown
1011 );
1012 }
1013
1014 #[test]
1015 fn translate_maps_every_other_variant_to_unknown() {
1016 let cases = vec![
1017 ImageError::IoError(std::io::Error::other("boom")),
1018 ImageError::Encoding(EncodingError::new(ImageFormatHint::Unknown, "bad pixel")),
1019 ImageError::Decoding(DecodingError::new(ImageFormatHint::Unknown, "bad chunk")),
1020 ImageError::Unsupported(UnsupportedError::from_format_and_kind(
1023 ImageFormatHint::Unknown,
1024 UnsupportedErrorKind::Format(ImageFormatHint::Unknown),
1025 )),
1026 ImageError::Parameter(ParameterError::from_kind(
1028 ParameterErrorKind::DimensionMismatch,
1029 )),
1030 ];
1031 for c in cases {
1032 let debug = format!("{c:?}");
1033 assert_eq!(
1034 translate_image_error_encode(c),
1035 EncodeImageError::Unknown,
1036 "unexpected mapping for {debug}"
1037 );
1038 }
1039 }
1040
1041 #[cfg(feature = "png")]
1044 #[test]
1045 fn encode_png_round_trips_through_the_decoder() {
1046 use crate::image::decode::decode_raw_image_from_any_bytes;
1047
1048 for (format, bpp) in [
1049 (RawImageFormat::R8, 1usize),
1050 (RawImageFormat::RGB8, 3),
1051 (RawImageFormat::RGBA8, 4),
1052 ] {
1053 let pixels = (0..(2 * 2 * bpp)).map(|i| (i * 7 + 1) as u8).collect::<Vec<_>>();
1054 let encoded = encode_png(&raw_u8(2, 2, format, pixels.clone()))
1055 .into_result()
1056 .unwrap_or_else(|e| panic!("encode_png({format:?}) failed: {e}"));
1057 let bytes = encoded.as_slice();
1058 assert_eq!(&bytes[..8], b"\x89PNG\r\n\x1a\n", "missing PNG signature");
1059
1060 let decoded = decode_raw_image_from_any_bytes(bytes)
1061 .into_result()
1062 .unwrap_or_else(|e| panic!("decode of our own PNG ({format:?}) failed: {e}"));
1063 assert_eq!(decoded.width, 2);
1064 assert_eq!(decoded.height, 2);
1065 assert_eq!(decoded.data_format, format, "format changed across the round trip");
1066 assert!(!decoded.premultiplied_alpha);
1067 assert_eq!(
1068 decoded.pixels.get_u8_vec_ref().expect("8-bit data").as_slice(),
1069 pixels.as_slice(),
1070 "pixels changed across the round trip ({format:?})"
1071 );
1072 }
1073 }
1074
1075 #[cfg(feature = "png")]
1078 #[test]
1079 fn encode_png_swaps_bgr_channels_before_writing() {
1080 use crate::image::decode::decode_raw_image_from_any_bytes;
1081
1082 let bgra = vec![10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160];
1083 let encoded = encode_png(&raw_u8(2, 2, RawImageFormat::BGRA8, bgra.clone()))
1084 .into_result()
1085 .expect("encode_png(BGRA8) failed");
1086 let decoded = decode_raw_image_from_any_bytes(encoded.as_slice())
1087 .into_result()
1088 .expect("decode of our own BGRA8 PNG failed");
1089 assert_eq!(decoded.data_format, RawImageFormat::RGBA8);
1090 let want = bgr_to_rgb_swap(&bgra, RawImageFormat::BGRA8).expect("BGRA8 must swap");
1091 assert_eq!(
1092 decoded.pixels.get_u8_vec_ref().expect("8-bit data").as_slice(),
1093 want.as_slice()
1094 );
1095
1096 let bgr = vec![10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
1097 let encoded = encode_png(&raw_u8(2, 2, RawImageFormat::BGR8, bgr.clone()))
1098 .into_result()
1099 .expect("encode_png(BGR8) failed");
1100 let decoded = decode_raw_image_from_any_bytes(encoded.as_slice())
1101 .into_result()
1102 .expect("decode of our own BGR8 PNG failed");
1103 assert_eq!(decoded.data_format, RawImageFormat::RGB8);
1104 let want = bgr_to_rgb_swap(&bgr, RawImageFormat::BGR8).expect("BGR8 must swap");
1105 assert_eq!(
1106 decoded.pixels.get_u8_vec_ref().expect("8-bit data").as_slice(),
1107 want.as_slice()
1108 );
1109 }
1110
1111 #[cfg(feature = "png")]
1114 #[test]
1115 fn encode_png_rejects_16_bit_and_float_payloads() {
1116 let u16_img = RawImage {
1117 pixels: RawImageData::U16(vec![0u16; 4].into()),
1118 width: 2,
1119 height: 2,
1120 premultiplied_alpha: false,
1121 data_format: RawImageFormat::R16,
1122 tag: Vec::<u8>::new().into(),
1123 };
1124 assert_eq!(err_of(&encode_png(&u16_img)), EncodeImageError::InvalidData);
1125
1126 let f32_img = RawImage {
1127 pixels: RawImageData::F32(vec![0.0f32, f32::NAN, f32::INFINITY, f32::MIN].into()),
1128 width: 2,
1129 height: 2,
1130 premultiplied_alpha: false,
1131 data_format: RawImageFormat::RGBAF32,
1132 tag: Vec::<u8>::new().into(),
1133 };
1134 assert_eq!(err_of(&encode_png(&f32_img)), EncodeImageError::InvalidData);
1135 }
1136
1137 #[cfg(all(feature = "png", target_pointer_width = "64"))]
1138 #[test]
1139 fn encode_png_rejects_dimensions_that_overflow_u32() {
1140 let too_wide = raw_u8(usize::MAX, 1, RawImageFormat::RGBA8, vec![0; 4]);
1141 assert_eq!(err_of(&encode_png(&too_wide)), EncodeImageError::DimensionError);
1142
1143 let too_tall = raw_u8(1, (u32::MAX as usize) + 1, RawImageFormat::RGBA8, vec![0; 4]);
1144 assert_eq!(err_of(&encode_png(&too_tall)), EncodeImageError::DimensionError);
1145 }
1146
1147 #[cfg(feature = "png")]
1148 #[test]
1149 fn encode_png_rejects_zero_dimensions() {
1150 let zero = raw_u8(0, 0, RawImageFormat::RGBA8, Vec::new());
1151 assert!(encode_png(&zero).is_err(), "a 0x0 PNG is not a valid image");
1152
1153 let zero_width = raw_u8(0, 4, RawImageFormat::RGBA8, Vec::new());
1154 assert!(encode_png(&zero_width).is_err());
1155 }
1156
1157 #[cfg(feature = "png")]
1163 #[test]
1164 fn encode_png_never_succeeds_on_a_buffer_of_the_wrong_length() {
1165 for (w, h, format, len) in [
1166 (4usize, 4usize, RawImageFormat::RGBA8, 3usize), (2, 2, RawImageFormat::RGBA8, 15), (2, 2, RawImageFormat::RGBA8, 17), (2, 2, RawImageFormat::R8, 0), ] {
1171 let outcome = std::panic::catch_unwind(move || {
1172 encode_png(&raw_u8(w, h, format, vec![0u8; len])).is_ok()
1173 });
1174 assert!(
1175 !matches!(outcome, Ok(true)),
1176 "encode_png claimed success for {w}x{h} {format:?} with {len} byte(s)"
1177 );
1178 }
1179 }
1180
1181 #[cfg(not(feature = "png"))]
1182 #[test]
1183 fn encode_png_without_the_feature_always_reports_encoder_not_available() {
1184 for img in [
1185 raw_u8(2, 2, RawImageFormat::RGBA8, vec![0; 16]),
1186 raw_u8(0, 0, RawImageFormat::RGBA8, Vec::new()),
1187 raw_u8(usize::MAX, usize::MAX, RawImageFormat::BGRA8, vec![0; 3]),
1188 ] {
1189 assert_eq!(err_of(&encode_png(&img)), EncodeImageError::EncoderNotAvailable);
1190 }
1191 }
1192
1193 #[cfg(feature = "jpeg")]
1198 #[test]
1199 fn encode_jpeg_clamps_quality_at_both_ends() {
1200 let pixels = (0..(4 * 4 * 3)).map(|i| (i * 5) as u8).collect::<Vec<_>>();
1201 let at = |q: u8| {
1202 encode_jpeg(&raw_u8(4, 4, RawImageFormat::RGB8, pixels.clone()), q)
1203 .into_result()
1204 .unwrap_or_else(|e| panic!("encode_jpeg(quality = {q}) failed: {e}"))
1205 .as_slice()
1206 .to_vec()
1207 };
1208
1209 assert_eq!(at(u8::MIN), at(1), "quality 0 must clamp to 1");
1210 assert_eq!(at(u8::MAX), at(100), "quality 255 must clamp to 100");
1211 assert_ne!(at(1), at(100), "quality must actually affect the output");
1212
1213 for q in [0u8, 1, 50, 100, 101, 254, 255] {
1214 let bytes = at(q);
1215 assert_eq!(&bytes[..2], &[0xFF, 0xD8], "missing JPEG SOI marker (quality {q})");
1216 assert_eq!(
1217 &bytes[bytes.len() - 2..],
1218 &[0xFF, 0xD9],
1219 "missing JPEG EOI marker (quality {q})"
1220 );
1221 }
1222 }
1223
1224 #[cfg(feature = "jpeg")]
1225 #[test]
1226 fn encode_jpeg_round_trips_dimensions_through_the_decoder() {
1227 use crate::image::decode::decode_raw_image_from_any_bytes;
1228
1229 let pixels = (0..(8 * 8 * 3)).map(|i| (i % 256) as u8).collect::<Vec<_>>();
1230 let encoded = encode_jpeg(&raw_u8(8, 8, RawImageFormat::RGB8, pixels), 90)
1231 .into_result()
1232 .expect("encode_jpeg(RGB8) failed");
1233 let decoded = decode_raw_image_from_any_bytes(encoded.as_slice())
1235 .into_result()
1236 .expect("decode of our own JPEG failed");
1237 assert_eq!(decoded.width, 8);
1238 assert_eq!(decoded.height, 8);
1239 assert_eq!(decoded.data_format, RawImageFormat::RGB8);
1240 assert_eq!(decoded.pixels.get_u8_vec_ref().expect("8-bit data").len(), 8 * 8 * 3);
1241 }
1242
1243 #[cfg(feature = "jpeg")]
1246 #[test]
1247 fn encode_jpeg_rejects_color_types_it_cannot_write() {
1248 for (format, bpp) in [
1249 (RawImageFormat::RGBA8, 4usize),
1250 (RawImageFormat::BGRA8, 4),
1251 (RawImageFormat::RG8, 2),
1252 ] {
1253 let img = raw_u8(2, 2, format, vec![7u8; 2 * 2 * bpp]);
1254 assert_eq!(
1255 err_of(&encode_jpeg(&img, 80)),
1256 EncodeImageError::Unknown,
1257 "unexpected error for {format:?}"
1258 );
1259 }
1260 assert!(encode_jpeg(&raw_u8(2, 2, RawImageFormat::R8, vec![1; 4]), 80).is_ok());
1262 assert!(encode_jpeg(&raw_u8(2, 2, RawImageFormat::RGB8, vec![1; 12]), 80).is_ok());
1263 }
1264
1265 #[cfg(feature = "jpeg")]
1266 #[test]
1267 fn encode_jpeg_rejects_zero_and_u16_max_plus_one_dimensions() {
1268 assert!(encode_jpeg(&raw_u8(0, 0, RawImageFormat::RGB8, Vec::new()), 75).is_err());
1271 assert!(encode_jpeg(&raw_u8(0, 4, RawImageFormat::RGB8, Vec::new()), 75).is_err());
1272
1273 let too_wide = raw_u8(65_536, 1, RawImageFormat::RGB8, vec![0u8; 65_536 * 3]);
1274 assert_eq!(err_of(&encode_jpeg(&too_wide, 75)), EncodeImageError::Unknown);
1277 }
1278
1279 #[cfg(all(feature = "jpeg", target_pointer_width = "64"))]
1280 #[test]
1281 fn encode_jpeg_rejects_dimensions_that_overflow_u32() {
1282 let too_wide = raw_u8(usize::MAX, 1, RawImageFormat::RGB8, vec![0; 3]);
1283 assert_eq!(err_of(&encode_jpeg(&too_wide, 75)), EncodeImageError::DimensionError);
1284
1285 let too_tall = raw_u8(1, (u32::MAX as usize) + 1, RawImageFormat::RGB8, vec![0; 3]);
1286 assert_eq!(err_of(&encode_jpeg(&too_tall, 0)), EncodeImageError::DimensionError);
1287 }
1288
1289 #[cfg(feature = "jpeg")]
1290 #[test]
1291 fn encode_jpeg_rejects_16_bit_payloads_before_looking_at_quality() {
1292 let u16_img = RawImage {
1293 pixels: RawImageData::U16(vec![u16::MAX; 12].into()),
1294 width: 2,
1295 height: 2,
1296 premultiplied_alpha: false,
1297 data_format: RawImageFormat::RGB16,
1298 tag: Vec::<u8>::new().into(),
1299 };
1300 for q in [0u8, 50, 255] {
1301 assert_eq!(err_of(&encode_jpeg(&u16_img, q)), EncodeImageError::InvalidData);
1302 }
1303 }
1304
1305 #[cfg(feature = "jpeg")]
1307 #[test]
1308 fn encode_jpeg_never_succeeds_on_a_buffer_of_the_wrong_length() {
1309 for q in [0u8, 1, 100, 255] {
1310 for (w, h, len) in [(4usize, 4usize, 3usize), (2, 2, 11), (2, 2, 13)] {
1311 let outcome = std::panic::catch_unwind(move || {
1312 encode_jpeg(&raw_u8(w, h, RawImageFormat::RGB8, vec![0u8; len]), q).is_ok()
1313 });
1314 assert!(
1315 !matches!(outcome, Ok(true)),
1316 "encode_jpeg claimed success for {w}x{h} RGB8, {len} byte(s), quality {q}"
1317 );
1318 }
1319 }
1320 }
1321
1322 #[cfg(not(feature = "jpeg"))]
1323 #[test]
1324 fn encode_jpeg_without_the_feature_always_reports_encoder_not_available() {
1325 for q in [u8::MIN, 1, 50, 100, 101, 254, u8::MAX] {
1327 let img = raw_u8(2, 2, RawImageFormat::RGB8, vec![0; 12]);
1328 assert_eq!(err_of(&encode_jpeg(&img, q)), EncodeImageError::EncoderNotAvailable);
1329 }
1330 let broken = raw_u8(usize::MAX, 0, RawImageFormat::RGBAF32, Vec::new());
1332 assert_eq!(err_of(&encode_jpeg(&broken, 0)), EncodeImageError::EncoderNotAvailable);
1333 }
1334 }
1335}