1use super::{
7 JxlBasicInfo, JxlBitstreamInput, JxlColorProfile, JxlDecoderInner, JxlDecoderOptions,
8 JxlOutputBuffer, JxlPixelFormat, ProcessingResult,
9};
10#[cfg(test)]
11use crate::frame::Frame;
12use crate::{
13 api::JxlFrameHeader,
14 container::{frame_index::FrameIndexBox, gain_map::GainMapBundle},
15 error::Result,
16};
17use states::*;
18use std::marker::PhantomData;
19
20pub mod states {
21 pub trait JxlState {}
22 pub struct Initialized;
23 pub struct WithImageInfo;
24 pub struct WithFrameInfo;
25 impl JxlState for Initialized {}
26 impl JxlState for WithImageInfo {}
27 impl JxlState for WithFrameInfo {}
28}
29
30pub struct JxlDecoder<State: JxlState> {
35 inner: Box<JxlDecoderInner>,
36 _state: PhantomData<State>,
37}
38
39#[cfg(test)]
40pub type FrameCallback = dyn FnMut(&Frame, usize) -> Result<()>;
41
42impl<S: JxlState> JxlDecoder<S> {
43 fn wrap_inner(inner: Box<JxlDecoderInner>) -> Self {
44 Self {
45 inner,
46 _state: PhantomData,
47 }
48 }
49
50 #[cfg(test)]
52 pub fn set_frame_callback(&mut self, callback: Box<FrameCallback>) {
53 self.inner.set_frame_callback(callback);
54 }
55
56 #[cfg(test)]
57 pub fn decoded_frames(&self) -> usize {
58 self.inner.decoded_frames()
59 }
60
61 #[cfg(feature = "jpeg")]
65 pub fn take_jpeg_reconstruction(&mut self) -> Option<Vec<u8>> {
66 self.inner.take_jpeg_reconstruction()
67 }
68
69 pub fn frame_index(&self) -> Option<&FrameIndexBox> {
75 self.inner.frame_index()
76 }
77
78 pub fn gain_map(&self) -> Option<&GainMapBundle> {
85 self.inner.gain_map()
86 }
87
88 pub fn take_gain_map(&mut self) -> Option<GainMapBundle> {
91 self.inner.take_gain_map()
92 }
93
94 pub fn exif(&self) -> Option<&[u8]> {
103 self.inner.exif()
104 }
105
106 pub fn take_exif(&mut self) -> Option<Vec<u8>> {
108 self.inner.take_exif()
109 }
110
111 pub fn xmp(&self) -> Option<&[u8]> {
118 self.inner.xmp()
119 }
120
121 pub fn take_xmp(&mut self) -> Option<Vec<u8>> {
123 self.inner.take_xmp()
124 }
125
126 pub fn rewind(mut self) -> JxlDecoder<Initialized> {
128 self.inner.rewind();
129 JxlDecoder::wrap_inner(self.inner)
130 }
131
132 fn map_inner_processing_result<SuccessState: JxlState>(
133 self,
134 inner_result: ProcessingResult<(), ()>,
135 ) -> ProcessingResult<JxlDecoder<SuccessState>, Self> {
136 match inner_result {
137 ProcessingResult::Complete { .. } => ProcessingResult::Complete {
138 result: JxlDecoder::wrap_inner(self.inner),
139 },
140 ProcessingResult::NeedsMoreInput { size_hint, .. } => {
141 ProcessingResult::NeedsMoreInput {
142 size_hint,
143 fallback: self,
144 }
145 }
146 }
147 }
148}
149
150impl JxlDecoder<Initialized> {
151 pub fn new(options: JxlDecoderOptions) -> Self {
152 Self::wrap_inner(Box::new(JxlDecoderInner::new(options)))
153 }
154
155 pub fn process(
156 mut self,
157 input: &mut impl JxlBitstreamInput,
158 ) -> Result<ProcessingResult<JxlDecoder<WithImageInfo>, Self>> {
159 let inner_result = self.inner.process(input, None)?;
160 Ok(self.map_inner_processing_result(inner_result))
161 }
162}
163
164impl JxlDecoder<WithImageInfo> {
165 pub fn basic_info(&self) -> &JxlBasicInfo {
169 self.inner.basic_info().unwrap()
170 }
171
172 pub fn embedded_color_profile(&self) -> &JxlColorProfile {
174 self.inner.embedded_color_profile().unwrap()
175 }
176
177 pub fn output_color_profile(&self) -> &JxlColorProfile {
179 self.inner.output_color_profile().unwrap()
180 }
181
182 pub fn set_output_color_profile(&mut self, profile: JxlColorProfile) -> Result<()> {
185 self.inner.set_output_color_profile(profile)
186 }
187
188 pub fn current_pixel_format(&self) -> &JxlPixelFormat {
190 self.inner.current_pixel_format().unwrap()
191 }
192
193 pub fn set_pixel_format(&mut self, pixel_format: JxlPixelFormat) {
198 self.inner.set_pixel_format(pixel_format);
199 }
200
201 pub fn process(
202 mut self,
203 input: &mut impl JxlBitstreamInput,
204 ) -> Result<ProcessingResult<JxlDecoder<WithFrameInfo>, Self>> {
205 let inner_result = self.inner.process(input, None)?;
206 Ok(self.map_inner_processing_result(inner_result))
207 }
208
209 pub fn flush_pixels(&mut self, buffers: &mut [JxlOutputBuffer<'_>]) -> Result<()> {
213 self.inner.flush_pixels(buffers)
214 }
215
216 pub fn has_more_frames(&self) -> bool {
217 self.inner.has_more_frames()
218 }
219
220 #[cfg(test)]
221 pub(crate) fn set_use_simple_pipeline(&mut self, u: bool) {
222 self.inner.set_use_simple_pipeline(u);
223 }
224}
225
226impl JxlDecoder<WithFrameInfo> {
227 pub fn skip_frame(
229 mut self,
230 input: &mut impl JxlBitstreamInput,
231 ) -> Result<ProcessingResult<JxlDecoder<WithImageInfo>, Self>> {
232 let inner_result = self.inner.process(input, None)?;
233 Ok(self.map_inner_processing_result(inner_result))
234 }
235
236 pub fn frame_header(&self) -> JxlFrameHeader {
237 self.inner.frame_header().unwrap()
238 }
239
240 pub fn num_completed_passes(&self) -> usize {
242 self.inner.num_completed_passes().unwrap()
243 }
244
245 pub fn flush_pixels(&mut self, buffers: &mut [JxlOutputBuffer<'_>]) -> Result<()> {
249 self.inner.flush_pixels(buffers)
250 }
251
252 pub fn process<In: JxlBitstreamInput>(
260 mut self,
261 input: &mut In,
262 buffers: &mut [JxlOutputBuffer<'_>],
263 ) -> Result<ProcessingResult<JxlDecoder<WithImageInfo>, Self>> {
264 let inner_result = self.inner.process(input, Some(buffers))?;
265 Ok(self.map_inner_processing_result(inner_result))
266 }
267}
268
269#[cfg(test)]
270pub(crate) mod tests {
271 use super::*;
272 use crate::api::{JxlDataFormat, JxlDecoderOptions};
273 use crate::error::Error;
274 use crate::image::{Image, Rect};
275 use jxl_macros::for_each_test_file;
276 use std::path::Path;
277
278 #[test]
279 fn decode_small_chunks() {
280 arbtest::arbtest(|u| {
281 decode(
282 &std::fs::read("resources/test/green_queen_vardct_e3.jxl").unwrap(),
283 u.arbitrary::<u8>().unwrap() as usize + 1,
284 false,
285 false,
286 None,
287 )
288 .unwrap();
289 Ok(())
290 });
291 }
292
293 #[allow(clippy::type_complexity)]
294 pub fn decode(
295 mut input: &[u8],
296 chunk_size: usize,
297 use_simple_pipeline: bool,
298 do_flush: bool,
299 callback: Option<Box<dyn FnMut(&Frame, usize) -> Result<(), Error>>>,
300 ) -> Result<(usize, Vec<Vec<Image<f32>>>), Error> {
301 let options = JxlDecoderOptions::default();
302 let mut initialized_decoder = JxlDecoder::<states::Initialized>::new(options);
303
304 if let Some(callback) = callback {
305 initialized_decoder.set_frame_callback(callback);
306 }
307
308 let mut chunk_input = &input[0..0];
309
310 macro_rules! advance_decoder {
311 ($decoder: ident $(, $extra_arg: expr)? $(; $flush_arg: expr)?) => {
312 loop {
313 chunk_input =
314 &input[..(chunk_input.len().saturating_add(chunk_size)).min(input.len())];
315 let available_before = chunk_input.len();
316 let process_result = $decoder.process(&mut chunk_input $(, $extra_arg)?);
317 input = &input[(available_before - chunk_input.len())..];
318 match process_result.unwrap() {
319 ProcessingResult::Complete { result } => break result,
320 ProcessingResult::NeedsMoreInput { fallback, .. } => {
321 $(
322 let mut fallback = fallback;
323 if do_flush && !input.is_empty() {
324 fallback.flush_pixels($flush_arg)?;
325 }
326 )?
327 if input.is_empty() {
328 panic!("Unexpected end of input");
329 }
330 $decoder = fallback;
331 }
332 }
333 }
334 };
335 }
336
337 let mut decoder_with_image_info = advance_decoder!(initialized_decoder);
339 decoder_with_image_info.set_use_simple_pipeline(use_simple_pipeline);
340
341 let basic_info = decoder_with_image_info.basic_info().clone();
343 assert!(basic_info.bit_depth.bits_per_sample() > 0);
344
345 let (buffer_width, buffer_height) = basic_info.size;
347 assert!(buffer_width > 0);
348 assert!(buffer_height > 0);
349
350 let default_format = decoder_with_image_info.current_pixel_format();
352 let requested_format = JxlPixelFormat {
353 color_type: default_format.color_type,
354 color_data_format: Some(JxlDataFormat::f32()),
355 extra_channel_format: default_format
356 .extra_channel_format
357 .iter()
358 .map(|_| Some(JxlDataFormat::f32()))
359 .collect(),
360 };
361 decoder_with_image_info.set_pixel_format(requested_format);
362
363 let pixel_format = decoder_with_image_info.current_pixel_format().clone();
365
366 let num_channels = pixel_format.color_type.samples_per_pixel();
367 assert!(num_channels > 0);
368
369 let mut frames = vec![];
370
371 loop {
372 let mut buffers = vec![Image::new_with_value(
374 (buffer_width * num_channels, buffer_height),
375 f32::NAN,
376 )?];
377
378 for ecf in pixel_format.extra_channel_format.iter() {
379 if ecf.is_none() {
380 continue;
381 }
382 buffers.push(Image::new_with_value(
383 (buffer_width, buffer_height),
384 f32::NAN,
385 )?);
386 }
387
388 let mut api_buffers: Vec<_> = buffers
389 .iter_mut()
390 .map(|b| {
391 JxlOutputBuffer::from_image_rect_mut(
392 b.get_rect_mut(Rect {
393 origin: (0, 0),
394 size: b.size(),
395 })
396 .into_raw(),
397 )
398 })
399 .collect();
400
401 let mut decoder_with_frame_info =
403 advance_decoder!(decoder_with_image_info; &mut api_buffers);
404 decoder_with_image_info =
405 advance_decoder!(decoder_with_frame_info, &mut api_buffers; &mut api_buffers);
406
407 for buf in buffers.iter() {
409 let (xs, ys) = buf.size();
410 for y in 0..ys {
411 let row = buf.row(y);
412 for (x, v) in row.iter().enumerate() {
413 assert!(!v.is_nan(), "NaN at {x} {y} (image size {xs}x{ys})");
414 }
415 }
416 }
417
418 frames.push(buffers);
419
420 if !decoder_with_image_info.has_more_frames() {
422 let decoded_frames = decoder_with_image_info.decoded_frames();
423
424 assert!(decoded_frames > 0, "No frames were decoded");
426
427 return Ok((decoded_frames, frames));
428 }
429 }
430 }
431
432 fn decode_test_file(path: &Path) -> Result<(), Error> {
433 decode(&std::fs::read(path)?, usize::MAX, false, false, None)?;
434 Ok(())
435 }
436
437 for_each_test_file!(decode_test_file);
438
439 fn decode_test_file_chunks(path: &Path) -> Result<(), Error> {
440 decode(&std::fs::read(path)?, 1, false, false, None)?;
441 Ok(())
442 }
443
444 for_each_test_file!(decode_test_file_chunks);
445
446 fn compare_frames(
447 _path: &Path,
448 fc: usize,
449 f: &[Image<f32>],
450 sf: &[Image<f32>],
451 ) -> Result<(), Error> {
452 assert_eq!(
453 f.len(),
454 sf.len(),
455 "Frame {fc} has different channels counts",
456 );
457 for (c, (b, sb)) in f.iter().zip(sf.iter()).enumerate() {
458 assert_eq!(
459 b.size(),
460 sb.size(),
461 "Channel {c} in frame {fc} has different sizes",
462 );
463 let sz = b.size();
464 for y in 0..sz.1 {
465 for x in 0..sz.0 {
466 assert_eq!(
467 b.row(y)[x],
468 sb.row(y)[x],
469 "Pixels differ at position ({x}, {y}), channel {c}"
470 );
471 }
472 }
473 }
474 Ok(())
475 }
476
477 fn compare_pipelines(path: &Path) -> Result<(), Error> {
478 let file = std::fs::read(path)?;
479 let simple_frames = decode(&file, usize::MAX, true, false, None)?.1;
480 let frames = decode(&file, usize::MAX, false, false, None)?.1;
481 assert_eq!(frames.len(), simple_frames.len());
482 for (fc, (f, sf)) in frames
483 .into_iter()
484 .zip(simple_frames.into_iter())
485 .enumerate()
486 {
487 compare_frames(path, fc, &f, &sf)?;
488 }
489 Ok(())
490 }
491
492 for_each_test_file!(compare_pipelines);
493
494 fn compare_incremental(path: &Path) -> Result<(), Error> {
495 let file = std::fs::read(path).unwrap();
496 let (_, one_shot_frames) = decode(&file, usize::MAX, false, false, None)?;
498 let (_, frames) = decode(&file, 123, false, true, None)?;
500
501 assert_eq!(one_shot_frames.len(), frames.len());
503 for (fc, (f, sf)) in frames
504 .into_iter()
505 .zip(one_shot_frames.into_iter())
506 .enumerate()
507 {
508 compare_frames(path, fc, &f, &sf)?;
509 }
510
511 Ok(())
512 }
513
514 for_each_test_file!(compare_incremental);
515
516 #[test]
517 fn test_preview_size_none_for_regular_files() {
518 let file = std::fs::read("resources/test/basic.jxl").unwrap();
519 let options = JxlDecoderOptions::default();
520 let mut decoder = JxlDecoder::<states::Initialized>::new(options);
521 let mut input = file.as_slice();
522 let decoder = loop {
523 match decoder.process(&mut input).unwrap() {
524 ProcessingResult::Complete { result } => break result,
525 ProcessingResult::NeedsMoreInput { fallback, .. } => decoder = fallback,
526 }
527 };
528 assert!(decoder.basic_info().preview_size.is_none());
529 }
530
531 #[test]
532 fn test_preview_size_some_for_preview_files() {
533 let file = std::fs::read("resources/test/with_preview.jxl").unwrap();
534 let options = JxlDecoderOptions::default();
535 let mut decoder = JxlDecoder::<states::Initialized>::new(options);
536 let mut input = file.as_slice();
537 let decoder = loop {
538 match decoder.process(&mut input).unwrap() {
539 ProcessingResult::Complete { result } => break result,
540 ProcessingResult::NeedsMoreInput { fallback, .. } => decoder = fallback,
541 }
542 };
543 assert_eq!(decoder.basic_info().preview_size, Some((16, 16)));
544 }
545
546 #[test]
547 fn test_num_completed_passes() {
548 use crate::image::{Image, Rect};
549 let file = std::fs::read("resources/test/basic.jxl").unwrap();
550 let options = JxlDecoderOptions::default();
551 let mut decoder = JxlDecoder::<states::Initialized>::new(options);
552 let mut input = file.as_slice();
553 let mut decoder_with_info = loop {
555 match decoder.process(&mut input).unwrap() {
556 ProcessingResult::Complete { result } => break result,
557 ProcessingResult::NeedsMoreInput { fallback, .. } => decoder = fallback,
558 }
559 };
560 let info = decoder_with_info.basic_info().clone();
561 let mut decoder_with_frame = loop {
562 match decoder_with_info.process(&mut input).unwrap() {
563 ProcessingResult::Complete { result } => break result,
564 ProcessingResult::NeedsMoreInput { fallback, .. } => {
565 decoder_with_info = fallback;
566 }
567 }
568 };
569 assert_eq!(decoder_with_frame.num_completed_passes(), 0);
571 let mut output = Image::<f32>::new((info.size.0 * 3, info.size.1)).unwrap();
573 let rect = Rect {
574 size: output.size(),
575 origin: (0, 0),
576 };
577 let mut bufs = [JxlOutputBuffer::from_image_rect_mut(
578 output.get_rect_mut(rect).into_raw(),
579 )];
580 loop {
581 match decoder_with_frame.process(&mut input, &mut bufs).unwrap() {
582 ProcessingResult::Complete { .. } => break,
583 ProcessingResult::NeedsMoreInput { fallback, .. } => decoder_with_frame = fallback,
584 }
585 }
586 }
587
588 #[test]
589 fn test_set_pixel_format() {
590 use crate::api::{JxlColorType, JxlDataFormat, JxlPixelFormat};
591
592 let file = std::fs::read("resources/test/basic.jxl").unwrap();
593 let options = JxlDecoderOptions::default();
594 let mut decoder = JxlDecoder::<states::Initialized>::new(options);
595 let mut input = file.as_slice();
596 let mut decoder = loop {
597 match decoder.process(&mut input).unwrap() {
598 ProcessingResult::Complete { result } => break result,
599 ProcessingResult::NeedsMoreInput { fallback, .. } => decoder = fallback,
600 }
601 };
602 let default_format = decoder.current_pixel_format().clone();
604 assert_eq!(default_format.color_type, JxlColorType::Rgb);
605
606 let new_format = JxlPixelFormat {
608 color_type: JxlColorType::Grayscale,
609 color_data_format: Some(JxlDataFormat::U8 { bit_depth: 8 }),
610 extra_channel_format: vec![],
611 };
612 decoder.set_pixel_format(new_format.clone());
613
614 assert_eq!(decoder.current_pixel_format(), &new_format);
616 }
617
618 #[test]
619 fn test_set_output_color_profile() {
620 use crate::api::JxlColorProfile;
621
622 let file = std::fs::read("resources/test/basic.jxl").unwrap();
623 let options = JxlDecoderOptions::default();
624 let mut decoder = JxlDecoder::<states::Initialized>::new(options);
625 let mut input = file.as_slice();
626 let mut decoder = loop {
627 match decoder.process(&mut input).unwrap() {
628 ProcessingResult::Complete { result } => break result,
629 ProcessingResult::NeedsMoreInput { fallback, .. } => decoder = fallback,
630 }
631 };
632
633 let embedded = decoder.embedded_color_profile().clone();
635 let result = decoder.set_output_color_profile(embedded);
636 assert!(result.is_ok());
637
638 let icc_profile = JxlColorProfile::Icc(vec![0u8; 100]);
640 let result = decoder.set_output_color_profile(icc_profile);
641 assert!(result.is_err());
642 }
643
644 #[test]
645 fn test_default_output_tf_by_pixel_format() {
646 use crate::api::{JxlColorEncoding, JxlTransferFunction};
647
648 let file = std::fs::read("resources/test/lossy_with_icc.jxl").unwrap();
650 let options = JxlDecoderOptions::default();
651 let mut decoder = JxlDecoder::<states::Initialized>::new(options);
652 let mut input = file.as_slice();
653 let mut decoder = loop {
654 match decoder.process(&mut input).unwrap() {
655 ProcessingResult::Complete { result } => break result,
656 ProcessingResult::NeedsMoreInput { fallback, .. } => decoder = fallback,
657 }
658 };
659
660 assert_eq!(
662 *decoder.output_color_profile().transfer_function().unwrap(),
663 JxlTransferFunction::Linear,
664 );
665
666 decoder.set_pixel_format(JxlPixelFormat::rgba8(0));
668 assert_eq!(
669 *decoder.output_color_profile().transfer_function().unwrap(),
670 JxlTransferFunction::SRGB,
671 );
672
673 decoder.set_pixel_format(JxlPixelFormat::rgba_f16(0));
674 assert_eq!(
675 *decoder.output_color_profile().transfer_function().unwrap(),
676 JxlTransferFunction::Linear,
677 );
678
679 decoder.set_pixel_format(JxlPixelFormat::rgba16(0));
680 assert_eq!(
681 *decoder.output_color_profile().transfer_function().unwrap(),
682 JxlTransferFunction::SRGB,
683 );
684
685 let profile = JxlColorProfile::Simple(JxlColorEncoding::srgb(false));
688 decoder.set_output_color_profile(profile.clone()).unwrap();
689 decoder.set_pixel_format(JxlPixelFormat::rgba_f16(0));
690 assert!(decoder.output_color_profile() == &profile);
691 }
692
693 #[test]
694 fn test_fill_opaque_alpha_both_pipelines() {
695 use crate::api::{JxlColorType, JxlDataFormat, JxlPixelFormat};
696 use crate::image::{Image, Rect};
697
698 let file = std::fs::read("resources/test/basic.jxl").unwrap();
700
701 let rgba_format = JxlPixelFormat {
703 color_type: JxlColorType::Rgba,
704 color_data_format: Some(JxlDataFormat::f32()),
705 extra_channel_format: vec![],
706 };
707
708 for use_simple in [true, false] {
710 let options = JxlDecoderOptions::default();
711 let decoder = JxlDecoder::<states::Initialized>::new(options);
712 let mut input = file.as_slice();
713
714 macro_rules! advance_decoder {
716 ($decoder:expr) => {
717 loop {
718 match $decoder.process(&mut input).unwrap() {
719 ProcessingResult::Complete { result } => break result,
720 ProcessingResult::NeedsMoreInput { fallback, .. } => {
721 if input.is_empty() {
722 panic!("Unexpected end of input");
723 }
724 $decoder = fallback;
725 }
726 }
727 }
728 };
729 ($decoder:expr, $buffers:expr) => {
730 loop {
731 match $decoder.process(&mut input, $buffers).unwrap() {
732 ProcessingResult::Complete { result } => break result,
733 ProcessingResult::NeedsMoreInput { fallback, .. } => {
734 if input.is_empty() {
735 panic!("Unexpected end of input");
736 }
737 $decoder = fallback;
738 }
739 }
740 }
741 };
742 }
743
744 let mut decoder = decoder;
745 let mut decoder = advance_decoder!(decoder);
746 decoder.set_use_simple_pipeline(use_simple);
747
748 decoder.set_pixel_format(rgba_format.clone());
750
751 let basic_info = decoder.basic_info().clone();
752 let (width, height) = basic_info.size;
753
754 let mut decoder = advance_decoder!(decoder);
756
757 let mut color_buffer = Image::<f32>::new((width * 4, height)).unwrap();
759 let mut buffers: Vec<_> = vec![JxlOutputBuffer::from_image_rect_mut(
760 color_buffer
761 .get_rect_mut(Rect {
762 origin: (0, 0),
763 size: (width * 4, height),
764 })
765 .into_raw(),
766 )];
767
768 let _decoder = advance_decoder!(decoder, &mut buffers);
770
771 for y in 0..height {
773 let row = color_buffer.row(y);
774 for x in 0..width {
775 let alpha = row[x * 4 + 3];
776 assert_eq!(
777 alpha, 1.0,
778 "Alpha at ({},{}) should be 1.0, got {} (use_simple={})",
779 x, y, alpha, use_simple
780 );
781 }
782 }
783 }
784 }
785
786 #[test]
789 fn test_premultiply_output_straight_alpha() {
790 use crate::api::{JxlColorType, JxlDataFormat, JxlPixelFormat};
791
792 let file =
794 std::fs::read("resources/test/conformance_test_images/alpha_nonpremultiplied.jxl")
795 .unwrap();
796
797 let rgba_format = JxlPixelFormat {
800 color_type: JxlColorType::Rgba,
801 color_data_format: Some(JxlDataFormat::f32()),
802 extra_channel_format: vec![None],
803 };
804
805 for use_simple in [true, false] {
807 let (straight_buffer, width, height) =
808 decode_with_format::<f32>(&file, &rgba_format, use_simple, false);
809 let (premul_buffer, _, _) =
810 decode_with_format::<f32>(&file, &rgba_format, use_simple, true);
811
812 let mut found_semitransparent = false;
814 for y in 0..height {
815 let straight_row = straight_buffer.row(y);
816 let premul_row = premul_buffer.row(y);
817 for x in 0..width {
818 let sr = straight_row[x * 4];
819 let sg = straight_row[x * 4 + 1];
820 let sb = straight_row[x * 4 + 2];
821 let sa = straight_row[x * 4 + 3];
822
823 let pr = premul_row[x * 4];
824 let pg = premul_row[x * 4 + 1];
825 let pb = premul_row[x * 4 + 2];
826 let pa = premul_row[x * 4 + 3];
827
828 assert!(
830 (sa - pa).abs() < 1e-5,
831 "Alpha mismatch at ({},{}): straight={}, premul={} (use_simple={})",
832 x,
833 y,
834 sa,
835 pa,
836 use_simple
837 );
838
839 let expected_r = sr * sa;
841 let expected_g = sg * sa;
842 let expected_b = sb * sa;
843
844 let tol = 0.01;
846 assert!(
847 (expected_r - pr).abs() < tol,
848 "R mismatch at ({},{}): expected={}, got={} (use_simple={})",
849 x,
850 y,
851 expected_r,
852 pr,
853 use_simple
854 );
855 assert!(
856 (expected_g - pg).abs() < tol,
857 "G mismatch at ({},{}): expected={}, got={} (use_simple={})",
858 x,
859 y,
860 expected_g,
861 pg,
862 use_simple
863 );
864 assert!(
865 (expected_b - pb).abs() < tol,
866 "B mismatch at ({},{}): expected={}, got={} (use_simple={})",
867 x,
868 y,
869 expected_b,
870 pb,
871 use_simple
872 );
873
874 if sa > 0.01 && sa < 0.99 {
875 found_semitransparent = true;
876 }
877 }
878 }
879
880 assert!(
882 found_semitransparent,
883 "Test image should have semi-transparent pixels (use_simple={})",
884 use_simple
885 );
886 }
887 }
888
889 #[test]
892 fn test_premultiply_output_already_premultiplied() {
893 use crate::api::{JxlColorType, JxlDataFormat, JxlPixelFormat};
894
895 let file = std::fs::read("resources/test/conformance_test_images/alpha_premultiplied.jxl")
897 .unwrap();
898
899 let rgba_format = JxlPixelFormat {
901 color_type: JxlColorType::Rgba,
902 color_data_format: Some(JxlDataFormat::f32()),
903 extra_channel_format: vec![None],
904 };
905
906 for use_simple in [true, false] {
908 let (without_flag_buffer, width, height) =
909 decode_with_format::<f32>(&file, &rgba_format, use_simple, false);
910 let (with_flag_buffer, _, _) =
911 decode_with_format::<f32>(&file, &rgba_format, use_simple, true);
912
913 for y in 0..height {
916 let without_row = without_flag_buffer.row(y);
917 let with_row = with_flag_buffer.row(y);
918 for x in 0..width {
919 for c in 0..4 {
920 let without_val = without_row[x * 4 + c];
921 let with_val = with_row[x * 4 + c];
922 assert!(
923 (without_val - with_val).abs() < 1e-5,
924 "Mismatch at ({},{}) channel {}: without_flag={}, with_flag={} (use_simple={})",
925 x,
926 y,
927 c,
928 without_val,
929 with_val,
930 use_simple
931 );
932 }
933 }
934 }
935 }
936 }
937
938 #[test]
942 fn test_animation_with_reference_frames() {
943 use crate::api::{JxlColorType, JxlDataFormat, JxlPixelFormat};
944 use crate::image::{Image, Rect};
945
946 let file =
948 std::fs::read("resources/test/conformance_test_images/animation_spline.jxl").unwrap();
949
950 let options = JxlDecoderOptions::default();
951 let decoder = JxlDecoder::<states::Initialized>::new(options);
952 let mut input = file.as_slice();
953
954 let mut decoder = decoder;
956 let mut decoder = loop {
957 match decoder.process(&mut input).unwrap() {
958 ProcessingResult::Complete { result } => break result,
959 ProcessingResult::NeedsMoreInput { fallback, .. } => {
960 decoder = fallback;
961 }
962 }
963 };
964
965 let rgb_format = JxlPixelFormat {
967 color_type: JxlColorType::Rgb,
968 color_data_format: Some(JxlDataFormat::f32()),
969 extra_channel_format: vec![],
970 };
971 decoder.set_pixel_format(rgb_format);
972
973 let basic_info = decoder.basic_info().clone();
974 let (width, height) = basic_info.size;
975
976 let mut frame_count = 0;
977
978 loop {
980 let mut decoder_frame = loop {
982 match decoder.process(&mut input).unwrap() {
983 ProcessingResult::Complete { result } => break result,
984 ProcessingResult::NeedsMoreInput { fallback, .. } => {
985 decoder = fallback;
986 }
987 }
988 };
989
990 let mut color_buffer = Image::<f32>::new((width * 3, height)).unwrap();
992 let mut buffers: Vec<_> = vec![JxlOutputBuffer::from_image_rect_mut(
993 color_buffer
994 .get_rect_mut(Rect {
995 origin: (0, 0),
996 size: (width * 3, height),
997 })
998 .into_raw(),
999 )];
1000
1001 decoder = loop {
1004 match decoder_frame.process(&mut input, &mut buffers).unwrap() {
1005 ProcessingResult::Complete { result } => break result,
1006 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1007 decoder_frame = fallback;
1008 }
1009 }
1010 };
1011
1012 frame_count += 1;
1013
1014 if !decoder.has_more_frames() {
1016 break;
1017 }
1018 }
1019
1020 assert!(
1022 frame_count > 1,
1023 "Expected multiple frames in animation, got {}",
1024 frame_count
1025 );
1026 }
1027
1028 #[test]
1029 fn test_skip_frame_then_decode_next() {
1030 use crate::api::{JxlColorType, JxlDataFormat, JxlPixelFormat};
1031 use crate::image::{Image, Rect};
1032
1033 let file =
1035 std::fs::read("resources/test/conformance_test_images/animation_spline.jxl").unwrap();
1036
1037 let options = JxlDecoderOptions::default();
1038 let decoder = JxlDecoder::<states::Initialized>::new(options);
1039 let mut input = file.as_slice();
1040
1041 let mut decoder = decoder;
1043 let mut decoder = loop {
1044 match decoder.process(&mut input).unwrap() {
1045 ProcessingResult::Complete { result } => break result,
1046 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1047 decoder = fallback;
1048 }
1049 }
1050 };
1051
1052 let rgb_format = JxlPixelFormat {
1054 color_type: JxlColorType::Rgb,
1055 color_data_format: Some(JxlDataFormat::f32()),
1056 extra_channel_format: vec![],
1057 };
1058 decoder.set_pixel_format(rgb_format);
1059
1060 let basic_info = decoder.basic_info().clone();
1061 let (width, height) = basic_info.size;
1062
1063 let mut decoder_frame = loop {
1065 match decoder.process(&mut input).unwrap() {
1066 ProcessingResult::Complete { result } => break result,
1067 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1068 decoder = fallback;
1069 }
1070 }
1071 };
1072
1073 let mut decoder = loop {
1075 match decoder_frame.skip_frame(&mut input).unwrap() {
1076 ProcessingResult::Complete { result } => break result,
1077 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1078 decoder_frame = fallback;
1079 }
1080 }
1081 };
1082
1083 assert!(
1084 decoder.has_more_frames(),
1085 "Animation should have more frames"
1086 );
1087
1088 let mut decoder_frame = loop {
1091 match decoder.process(&mut input).unwrap() {
1092 ProcessingResult::Complete { result } => break result,
1093 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1094 decoder = fallback;
1095 }
1096 }
1097 };
1098
1099 let mut color_buffer = Image::<f32>::new((width * 3, height)).unwrap();
1101 let mut buffers: Vec<_> = vec![JxlOutputBuffer::from_image_rect_mut(
1102 color_buffer
1103 .get_rect_mut(Rect {
1104 origin: (0, 0),
1105 size: (width * 3, height),
1106 })
1107 .into_raw(),
1108 )];
1109
1110 let decoder = loop {
1111 match decoder_frame.process(&mut input, &mut buffers).unwrap() {
1112 ProcessingResult::Complete { result } => break result,
1113 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1114 decoder_frame = fallback;
1115 }
1116 }
1117 };
1118
1119 let _ = decoder.has_more_frames();
1122 }
1123
1124 #[test]
1128 fn test_output_format_u8_matches_f32() {
1129 use crate::api::{JxlColorType, JxlDataFormat, JxlPixelFormat};
1130
1131 let file = std::fs::read("resources/test/conformance_test_images/bicycles.jxl").unwrap();
1133
1134 for (color_type, num_samples) in [(JxlColorType::Rgb, 3), (JxlColorType::Bgra, 4)] {
1136 let f32_format = JxlPixelFormat {
1137 color_type,
1138 color_data_format: Some(JxlDataFormat::f32()),
1139 extra_channel_format: vec![],
1140 };
1141 let u8_format = JxlPixelFormat {
1142 color_type,
1143 color_data_format: Some(JxlDataFormat::U8 { bit_depth: 8 }),
1144 extra_channel_format: vec![],
1145 };
1146
1147 for use_simple in [true, false] {
1149 let (f32_buffer, width, height) =
1150 decode_with_format::<f32>(&file, &f32_format, use_simple, false);
1151 let (u8_buffer, _, _) =
1152 decode_with_format::<u8>(&file, &u8_format, use_simple, false);
1153
1154 let tolerance = 0.003;
1157 let mut max_error: f32 = 0.0;
1158
1159 for y in 0..height {
1160 let f32_row = f32_buffer.row(y);
1161 let u8_row = u8_buffer.row(y);
1162 for x in 0..(width * num_samples) {
1163 let f32_val = f32_row[x].clamp(0.0, 1.0);
1164 let u8_val = u8_row[x] as f32 / 255.0;
1165 let error = (f32_val - u8_val).abs();
1166 max_error = max_error.max(error);
1167 assert!(
1168 error < tolerance,
1169 "{:?} u8 mismatch at ({},{}): f32={}, u8={} (scaled={}), error={} (use_simple={})",
1170 color_type,
1171 x,
1172 y,
1173 f32_val,
1174 u8_row[x],
1175 u8_val,
1176 error,
1177 use_simple
1178 );
1179 }
1180 }
1181 }
1182 }
1183 }
1184
1185 #[test]
1187 fn test_output_format_u16_matches_f32() {
1188 use crate::api::{Endianness, JxlColorType, JxlDataFormat, JxlPixelFormat};
1189
1190 let file = std::fs::read("resources/test/conformance_test_images/bicycles.jxl").unwrap();
1191
1192 for (color_type, num_samples) in [(JxlColorType::Rgb, 3), (JxlColorType::Bgra, 4)] {
1194 let f32_format = JxlPixelFormat {
1195 color_type,
1196 color_data_format: Some(JxlDataFormat::f32()),
1197 extra_channel_format: vec![],
1198 };
1199 let u16_format = JxlPixelFormat {
1200 color_type,
1201 color_data_format: Some(JxlDataFormat::U16 {
1202 endianness: Endianness::native(),
1203 bit_depth: 16,
1204 }),
1205 extra_channel_format: vec![],
1206 };
1207
1208 for use_simple in [true, false] {
1209 let (f32_buffer, width, height) =
1210 decode_with_format::<f32>(&file, &f32_format, use_simple, false);
1211 let (u16_buffer, _, _) =
1212 decode_with_format::<u16>(&file, &u16_format, use_simple, false);
1213
1214 let tolerance = 0.0001;
1216
1217 for y in 0..height {
1218 let f32_row = f32_buffer.row(y);
1219 let u16_row = u16_buffer.row(y);
1220 for x in 0..(width * num_samples) {
1221 let f32_val = f32_row[x].clamp(0.0, 1.0);
1222 let u16_val = u16_row[x] as f32 / 65535.0;
1223 let error = (f32_val - u16_val).abs();
1224 assert!(
1225 error < tolerance,
1226 "{:?} u16 mismatch at ({},{}): f32={}, u16={} (scaled={}), error={} (use_simple={})",
1227 color_type,
1228 x,
1229 y,
1230 f32_val,
1231 u16_row[x],
1232 u16_val,
1233 error,
1234 use_simple
1235 );
1236 }
1237 }
1238 }
1239 }
1240 }
1241
1242 #[test]
1244 fn test_output_format_f16_matches_f32() {
1245 use crate::api::{Endianness, JxlColorType, JxlDataFormat, JxlPixelFormat};
1246 use crate::util::f16;
1247
1248 let file = std::fs::read("resources/test/conformance_test_images/bicycles.jxl").unwrap();
1249
1250 for (color_type, num_samples) in [(JxlColorType::Rgb, 3), (JxlColorType::Bgra, 4)] {
1252 let f32_format = JxlPixelFormat {
1253 color_type,
1254 color_data_format: Some(JxlDataFormat::f32()),
1255 extra_channel_format: vec![],
1256 };
1257 let f16_format = JxlPixelFormat {
1258 color_type,
1259 color_data_format: Some(JxlDataFormat::F16 {
1260 endianness: Endianness::native(),
1261 }),
1262 extra_channel_format: vec![],
1263 };
1264
1265 for use_simple in [true, false] {
1266 let (f32_buffer, width, height) =
1267 decode_with_format::<f32>(&file, &f32_format, use_simple, false);
1268 let (f16_buffer, _, _) =
1269 decode_with_format::<f16>(&file, &f16_format, use_simple, false);
1270
1271 let tolerance = 0.002;
1274
1275 for y in 0..height {
1276 let f32_row = f32_buffer.row(y);
1277 let f16_row = f16_buffer.row(y);
1278 for x in 0..(width * num_samples) {
1279 let f32_val = f32_row[x];
1280 let f16_val = f16_row[x].to_f32();
1281 let error = (f32_val - f16_val).abs();
1282 assert!(
1283 error < tolerance,
1284 "{:?} f16 mismatch at ({},{}): f32={}, f16={}, error={} (use_simple={})",
1285 color_type,
1286 x,
1287 y,
1288 f32_val,
1289 f16_val,
1290 error,
1291 use_simple
1292 );
1293 }
1294 }
1295 }
1296 }
1297 }
1298
1299 fn decode_with_format<T: crate::image::ImageDataType>(
1301 file: &[u8],
1302 pixel_format: &JxlPixelFormat,
1303 use_simple: bool,
1304 premultiply: bool,
1305 ) -> (Image<T>, usize, usize) {
1306 let options = JxlDecoderOptions {
1307 premultiply_output: premultiply,
1308 ..Default::default()
1309 };
1310 let mut decoder = JxlDecoder::<states::Initialized>::new(options);
1311 let mut input = file;
1312
1313 let mut decoder = loop {
1315 match decoder.process(&mut input).unwrap() {
1316 ProcessingResult::Complete { result } => break result,
1317 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1318 if input.is_empty() {
1319 panic!("Unexpected end of input");
1320 }
1321 decoder = fallback;
1322 }
1323 }
1324 };
1325 decoder.set_use_simple_pipeline(use_simple);
1326 decoder.set_pixel_format(pixel_format.clone());
1327
1328 let basic_info = decoder.basic_info().clone();
1329 let (width, height) = basic_info.size;
1330
1331 let num_samples = pixel_format.color_type.samples_per_pixel();
1332
1333 let decoder = loop {
1335 match decoder.process(&mut input).unwrap() {
1336 ProcessingResult::Complete { result } => break result,
1337 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1338 if input.is_empty() {
1339 panic!("Unexpected end of input");
1340 }
1341 decoder = fallback;
1342 }
1343 }
1344 };
1345
1346 let mut buffer = Image::<T>::new((width * num_samples, height)).unwrap();
1347 let mut buffers: Vec<_> = vec![JxlOutputBuffer::from_image_rect_mut(
1348 buffer
1349 .get_rect_mut(Rect {
1350 origin: (0, 0),
1351 size: (width * num_samples, height),
1352 })
1353 .into_raw(),
1354 )];
1355
1356 let mut decoder = decoder;
1358 loop {
1359 match decoder.process(&mut input, &mut buffers).unwrap() {
1360 ProcessingResult::Complete { .. } => break,
1361 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1362 if input.is_empty() {
1363 panic!("Unexpected end of input");
1364 }
1365 decoder = fallback;
1366 }
1367 }
1368 }
1369
1370 (buffer, width, height)
1371 }
1372
1373 #[test]
1376 fn test_fuzzer_smallbuffer_overflow() {
1377 use std::panic;
1378
1379 let data = include_bytes!("../../tests/testdata/fuzzer_smallbuffer_overflow.jxl");
1380
1381 let result = panic::catch_unwind(|| {
1384 let _ = decode(data, 1024, false, false, None);
1385 });
1386
1387 if let Err(e) = result {
1389 let panic_msg = e
1390 .downcast_ref::<&str>()
1391 .map(|s| s.to_string())
1392 .or_else(|| e.downcast_ref::<String>().cloned())
1393 .unwrap_or_default();
1394 assert!(
1395 !panic_msg.contains("overflow"),
1396 "Unexpected overflow panic: {}",
1397 panic_msg
1398 );
1399 }
1400 }
1401
1402 fn wrap_with_frame_index(
1404 codestream: &[u8],
1405 tnum: u32,
1406 tden: u32,
1407 entries: &[(u64, u64, u64)], ) -> Vec<u8> {
1409 use crate::util::test::build_frame_index_content;
1410
1411 fn make_box(ty: &[u8; 4], content: &[u8]) -> Vec<u8> {
1412 let len = (8 + content.len()) as u32;
1413 let mut buf = Vec::new();
1414 buf.extend(len.to_be_bytes());
1415 buf.extend(ty);
1416 buf.extend(content);
1417 buf
1418 }
1419
1420 let jxli_content = build_frame_index_content(tnum, tden, entries);
1421
1422 let sig = [
1424 0x00, 0x00, 0x00, 0x0c, 0x4a, 0x58, 0x4c, 0x20, 0x0d, 0x0a, 0x87, 0x0a,
1425 ];
1426 let ftyp = make_box(b"ftyp", b"jxl \x00\x00\x00\x00jxl ");
1428 let jxli = make_box(b"jxli", &jxli_content);
1429 let jxlc = make_box(b"jxlc", codestream);
1430
1431 let mut container = Vec::new();
1432 container.extend(&sig);
1433 container.extend(&ftyp);
1434 container.extend(&jxli);
1435 container.extend(&jxlc);
1436 container
1437 }
1438
1439 #[test]
1440 fn test_frame_index_parsed_from_container() {
1441 let codestream =
1443 std::fs::read("resources/test/conformance_test_images/animation_icos4d_5.jxl").unwrap();
1444
1445 let entries = vec![
1448 (0u64, 100u64, 1u64), (500, 100, 1), (600, 100, 1), ];
1452
1453 let container = wrap_with_frame_index(&codestream, 1, 1000, &entries);
1454
1455 let options = JxlDecoderOptions::default();
1457 let mut dec = JxlDecoder::<states::Initialized>::new(options);
1458 let mut input: &[u8] = &container;
1459 let dec = loop {
1460 match dec.process(&mut input).unwrap() {
1461 ProcessingResult::Complete { result } => break result,
1462 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1463 if input.is_empty() {
1464 panic!("Unexpected end of input");
1465 }
1466 dec = fallback;
1467 }
1468 }
1469 };
1470
1471 let fi = dec.frame_index().expect("frame_index should be Some");
1473 assert_eq!(fi.num_frames(), 3);
1474 assert_eq!(fi.tnum, 1);
1475 assert_eq!(fi.tden.get(), 1000);
1476 assert_eq!(fi.entries[0].codestream_offset, 0);
1478 assert_eq!(fi.entries[1].codestream_offset, 500);
1479 assert_eq!(fi.entries[2].codestream_offset, 1100);
1480 assert_eq!(fi.entries[0].duration_ticks, 100);
1481 assert_eq!(fi.entries[2].frame_count, 1);
1482 }
1483
1484 #[test]
1485 fn test_frame_index_none_for_bare_codestream() {
1486 let data =
1488 std::fs::read("resources/test/conformance_test_images/animation_icos4d_5.jxl").unwrap();
1489 let options = JxlDecoderOptions::default();
1490 let mut dec = JxlDecoder::<states::Initialized>::new(options);
1491 let mut input: &[u8] = &data;
1492 let dec = loop {
1493 match dec.process(&mut input).unwrap() {
1494 ProcessingResult::Complete { result } => break result,
1495 ProcessingResult::NeedsMoreInput { fallback, .. } => {
1496 if input.is_empty() {
1497 panic!("Unexpected end of input");
1498 }
1499 dec = fallback;
1500 }
1501 }
1502 };
1503 assert!(dec.frame_index().is_none());
1504 }
1505
1506 #[test]
1508 fn test_fuzzer_xyb_icc_no_panic() {
1509 use crate::api::ProcessingResult;
1510
1511 #[rustfmt::skip]
1512 let data: &[u8] = &[
1513 0xff, 0x0a, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00,
1514 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x25, 0x00,
1515 ];
1516
1517 let opts = JxlDecoderOptions::default();
1518 let mut decoder = JxlDecoderInner::new(opts);
1519 let mut input = data;
1520
1521 if let Ok(ProcessingResult::Complete { .. }) = decoder.process(&mut input, None)
1522 && let Some(profile) = decoder.output_color_profile()
1523 {
1524 let _ = profile.try_as_icc();
1525 }
1526 }
1527
1528 #[test]
1529 fn test_pixel_limit_enforcement() {
1530 let input = std::fs::read("resources/test/green_queen_vardct_e3.jxl").unwrap();
1532
1533 let mut options = JxlDecoderOptions::default();
1535 options.limits.max_pixels = Some(100); let decoder = JxlDecoder::<states::Initialized>::new(options);
1538 let mut input_slice = &input[..];
1539
1540 let result = decoder.process(&mut input_slice);
1542 match result {
1543 Err(err) => {
1544 assert!(
1545 matches!(
1546 err,
1547 Error::LimitExceeded {
1548 resource: "pixels",
1549 ..
1550 }
1551 ),
1552 "Expected LimitExceeded for pixels, got {:?}",
1553 err
1554 );
1555 }
1556 Ok(ProcessingResult::NeedsMoreInput { .. }) => {
1557 panic!("Expected error, got needs more input");
1558 }
1559 Ok(ProcessingResult::Complete { .. }) => {
1560 panic!("Expected error, got success");
1561 }
1562 }
1563 }
1564
1565 #[test]
1566 fn test_restrictive_limits_preset() {
1567 let limits = crate::api::JxlDecoderLimits::restrictive();
1569 assert_eq!(limits.max_pixels, Some(100_000_000));
1570 assert_eq!(limits.max_extra_channels, Some(16));
1571 assert_eq!(limits.max_icc_size, Some(1 << 20));
1572 assert_eq!(limits.max_tree_size, Some(1 << 20));
1573 assert_eq!(limits.max_patches, Some(1 << 16));
1574 assert_eq!(limits.max_spline_points, Some(1 << 16));
1575 assert_eq!(limits.max_reference_frames, Some(2));
1576 assert_eq!(limits.max_memory_bytes, Some(1 << 30));
1577 }
1578
1579 #[test]
1580 fn test_extra_channel_metadata() {
1581 use crate::headers::extra_channels::ExtraChannel;
1582
1583 let file = std::fs::read("resources/test/extra_channels.jxl").unwrap();
1584 let options = JxlDecoderOptions::default();
1585 let mut decoder = JxlDecoder::<states::Initialized>::new(options);
1586 let mut input = file.as_slice();
1587 let decoder = loop {
1588 match decoder.process(&mut input).unwrap() {
1589 ProcessingResult::Complete { result } => break result,
1590 ProcessingResult::NeedsMoreInput { fallback, .. } => decoder = fallback,
1591 }
1592 };
1593 let info = decoder.basic_info();
1594 assert!(
1596 !info.extra_channels.is_empty(),
1597 "expected at least one extra channel"
1598 );
1599
1600 for ec in &info.extra_channels {
1602 assert!(
1604 ec.bits_per_sample > 0 && ec.bits_per_sample <= 32,
1605 "unexpected bits_per_sample: {}",
1606 ec.bits_per_sample
1607 );
1608 assert!(ec.dim_shift <= 3, "unexpected dim_shift: {}", ec.dim_shift);
1610 }
1611 }
1612
1613 #[test]
1614 fn test_extra_channel_alpha_with_new_fields() {
1615 use crate::headers::extra_channels::ExtraChannel;
1616
1617 let file = std::fs::read("resources/test/3x3a_srgb_lossless.jxl").unwrap();
1619 let options = JxlDecoderOptions::default();
1620 let mut decoder = JxlDecoder::<states::Initialized>::new(options);
1621 let mut input = file.as_slice();
1622 let decoder = loop {
1623 match decoder.process(&mut input).unwrap() {
1624 ProcessingResult::Complete { result } => break result,
1625 ProcessingResult::NeedsMoreInput { fallback, .. } => decoder = fallback,
1626 }
1627 };
1628 let info = decoder.basic_info();
1629 assert_eq!(info.extra_channels.len(), 1);
1631 let alpha = &info.extra_channels[0];
1632 assert_eq!(alpha.ec_type, ExtraChannel::Alpha);
1633 assert!(alpha.bits_per_sample > 0);
1634 assert_eq!(alpha.dim_shift, 0);
1636 }
1637
1638 #[test]
1639 fn test_preview_metadata_in_basic_info() {
1640 let file = std::fs::read("resources/test/with_preview.jxl").unwrap();
1642 let options = JxlDecoderOptions::default();
1643 let mut decoder = JxlDecoder::<states::Initialized>::new(options);
1644 let mut input = file.as_slice();
1645 let decoder = loop {
1646 match decoder.process(&mut input).unwrap() {
1647 ProcessingResult::Complete { result } => break result,
1648 ProcessingResult::NeedsMoreInput { fallback, .. } => decoder = fallback,
1649 }
1650 };
1651 let info = decoder.basic_info();
1652 let (pw, ph) = info.preview_size.expect("expected preview_size");
1653 assert!(pw > 0 && ph > 0, "preview dimensions should be positive");
1654 }
1655
1656 #[test]
1657 fn test_stop_cancellation() {
1658 use almost_enough::Stopper;
1659 use enough::Stop;
1660
1661 let stop = Stopper::new();
1662 assert!(!stop.should_stop());
1663 stop.cancel();
1664 assert!(stop.should_stop());
1665 let result: crate::error::Result<()> = stop.check().map_err(Into::into);
1667 assert!(matches!(result, Err(crate::error::Error::Cancelled)));
1668 }
1669}