1#![cfg_attr(not(feature = "std"), no_std)]
68#![forbid(unsafe_code)]
69#![forbid(missing_docs)]
70
71extern crate alloc;
72
73use alloc::vec;
74use alloc::vec::Vec;
75
76use crate::error::{bail, err};
77use crate::j2c::Header;
78use crate::jp2::ImageBoxes;
79use crate::jp2::cdef::{ChannelAssociation, ChannelType};
80use crate::jp2::cmap::ComponentMappingType;
81use crate::jp2::colr::{CieLab, EnumeratedColorspace};
82use crate::jp2::icc::ICCMetadata;
83
84pub mod error;
85#[macro_use]
86pub(crate) mod log;
87pub(crate) mod math;
88
89use crate::math::{Level, SIMD_WIDTH, Simd, dispatch, f32x8};
90pub use error::{
91 ColorError, DecodeError, DecodingError, FormatError, MarkerError, Result, TileError,
92 ValidationError,
93};
94pub use j2c::{ComponentData, DecoderContext};
95pub use jp2::DecodedImage;
96
97#[cfg(feature = "image")]
98pub mod integration;
99mod j2c;
100mod jp2;
101pub(crate) mod reader;
102
103pub(crate) const JP2_MAGIC: &[u8] = b"\x00\x00\x00\x0C\x6A\x50\x20\x20";
105pub(crate) const CODESTREAM_MAGIC: &[u8] = b"\xFF\x4F\xFF\x51";
107
108#[derive(Debug, Copy, Clone)]
110pub struct DecodeSettings {
111 pub resolve_palette_indices: bool,
123 pub strict: bool,
128 pub target_resolution: Option<(u32, u32)>,
130}
131
132impl Default for DecodeSettings {
133 fn default() -> Self {
134 Self {
135 resolve_palette_indices: true,
136 strict: false,
137 target_resolution: None,
138 }
139 }
140}
141
142pub struct Image<'a> {
144 pub(crate) codestream: &'a [u8],
146 pub(crate) header: Header<'a>,
148 pub(crate) boxes: ImageBoxes,
151 pub(crate) settings: DecodeSettings,
153 pub(crate) has_alpha: bool,
155 pub(crate) color_space: ColorSpace,
157}
158
159impl<'a> Image<'a> {
160 pub fn new(data: &'a [u8], settings: &DecodeSettings) -> Result<Self> {
162 if data.starts_with(JP2_MAGIC) {
163 jp2::parse(data, *settings)
164 } else if data.starts_with(CODESTREAM_MAGIC) {
165 j2c::parse(data, settings)
166 } else {
167 err!(FormatError::InvalidSignature)
168 }
169 }
170
171 pub fn has_alpha(&self) -> bool {
173 self.has_alpha
174 }
175
176 pub fn color_space(&self) -> &ColorSpace {
178 &self.color_space
179 }
180
181 pub fn width(&self) -> u32 {
183 self.header.size_data.image_width()
184 }
185
186 pub fn height(&self) -> u32 {
188 self.header.size_data.image_height()
189 }
190
191 pub fn original_bit_depth(&self) -> u8 {
194 self.header.component_infos[0].size_info.precision
196 }
197
198 pub fn decode<'b>(
200 &'a self,
201 decoder_context: &'b mut DecoderContext<'a>,
202 ) -> Result<DecodedImage<'b>> {
203 let settings = &self.settings;
204 j2c::decode(self.codestream, &self.header, decoder_context)?;
205 let mut decoded_image = DecodedImage {
206 decoded_components: &mut decoder_context.channel_data,
207 boxes: self.boxes.clone(),
208 };
209
210 if settings.resolve_palette_indices {
212 let components = core::mem::take(decoded_image.decoded_components);
213 *decoded_image.decoded_components =
214 resolve_palette_indices(components, &decoded_image.boxes)?;
215 }
216
217 if let Some(cdef) = &decoded_image.boxes.channel_definition {
218 let mut components = decoded_image
221 .decoded_components
222 .iter()
223 .cloned()
224 .zip(
225 cdef.channel_definitions
226 .iter()
227 .map(|c| match c._association {
228 ChannelAssociation::WholeImage => u16::MAX,
229 ChannelAssociation::Colour(c) => c,
230 }),
231 )
232 .collect::<Vec<_>>();
233 components.sort_by_key(|c1| c1.1);
234 *decoded_image.decoded_components = components.into_iter().map(|c| c.0).collect();
235 }
236
237 let bit_depth = decoded_image.decoded_components[0].bit_depth;
239 convert_color_space(&mut decoded_image, bit_depth)?;
240
241 Ok(decoded_image)
242 }
243}
244
245pub(crate) fn resolve_alpha_and_color_space(
246 boxes: &ImageBoxes,
247 header: &Header<'_>,
248 settings: &DecodeSettings,
249) -> Result<(ColorSpace, bool)> {
250 let mut num_components = header.component_infos.len();
251
252 if settings.resolve_palette_indices
255 && let Some(palette_box) = &boxes.palette
256 {
257 num_components = palette_box.columns.len();
258 }
259
260 let mut has_alpha = false;
261
262 if let Some(cdef) = &boxes.channel_definition {
263 let last = cdef.channel_definitions.last().unwrap();
264 has_alpha = last.channel_type == ChannelType::Opacity;
265 }
266
267 let mut color_space = get_color_space(boxes, num_components)?;
268
269 if !settings.resolve_palette_indices && boxes.palette.is_some() {
271 has_alpha = false;
272 color_space = ColorSpace::Gray;
273 }
274
275 let actual_num_components = header.component_infos.len();
276
277 if boxes.palette.is_none()
279 && actual_num_components
280 != (color_space.num_channels() + if has_alpha { 1 } else { 0 }) as usize
281 {
282 if !settings.strict
283 && actual_num_components == color_space.num_channels() as usize + 1
284 && !has_alpha
285 {
286 has_alpha = true;
289 } else {
290 if actual_num_components == 1 || (actual_num_components == 2 && has_alpha) {
292 color_space = ColorSpace::Gray;
293 } else if actual_num_components == 3 {
294 color_space = ColorSpace::RGB;
295 } else if actual_num_components == 4 {
296 if has_alpha {
297 color_space = ColorSpace::RGB;
298 } else {
299 color_space = ColorSpace::CMYK;
300 }
301 } else {
302 bail!(ValidationError::TooManyChannels);
303 }
304 }
305 }
306
307 Ok((color_space, has_alpha))
308}
309
310#[derive(Debug, Clone)]
312pub enum ColorSpace {
313 Gray,
315 RGB,
317 CMYK,
319 Unknown {
321 num_channels: u8,
323 },
324 Icc {
326 profile: Vec<u8>,
328 num_channels: u8,
330 },
331}
332
333impl ColorSpace {
334 pub fn num_channels(&self) -> u8 {
336 match self {
337 Self::Gray => 1,
338 Self::RGB => 3,
339 Self::CMYK => 4,
340 Self::Unknown { num_channels } => *num_channels,
341 Self::Icc {
342 num_channels: num_components,
343 ..
344 } => *num_components,
345 }
346 }
347}
348
349pub struct Bitmap {
351 pub color_space: ColorSpace,
353 pub data: Vec<u8>,
362 pub has_alpha: bool,
364 pub width: u32,
366 pub height: u32,
368 pub original_bit_depth: u8,
371}
372
373impl DecodedImage<'_> {
374 pub fn components(&self) -> &[ComponentData] {
376 self.decoded_components
377 }
378
379 pub fn data_u8(&self) -> Vec<u8> {
381 let components = self.components();
382 let buffer_size = components[0].samples().len() * components.len();
383 let mut buf = vec![0; buffer_size];
384 self.store_u8_into(&mut buf);
385 buf
386 }
387
388 pub fn store_u8_into(&self, buf: &mut [u8]) {
392 interleave_and_convert(self, buf);
393 }
394}
395
396fn interleave_and_convert(image: &DecodedImage<'_>, buf: &mut [u8]) {
397 let components = &*image.decoded_components;
398 let num_components = components.len();
399
400 let mut all_same_bit_depth = Some(components[0].bit_depth);
401
402 for component in components.iter().skip(1) {
403 if Some(component.bit_depth) != all_same_bit_depth {
404 all_same_bit_depth = None;
405 }
406 }
407
408 let max_len = components[0].container.truncated().len();
409
410 let mut output_iter = buf.iter_mut();
411
412 if all_same_bit_depth == Some(8) && num_components <= 4 {
413 match num_components {
415 1 => {
417 for (output, input) in output_iter.zip(
418 components[0]
419 .container
420 .iter()
421 .map(|v| math::round_f32(*v) as u8),
422 ) {
423 *output = input;
424 }
425 }
426 2 => {
428 let c0 = &components[0];
429 let c1 = &components[1];
430
431 let c0 = &c0.container[..max_len];
432 let c1 = &c1.container[..max_len];
433
434 for i in 0..max_len {
435 *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
436 *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
437 }
438 }
439 3 => {
441 let c0 = &components[0];
442 let c1 = &components[1];
443 let c2 = &components[2];
444
445 let c0 = &c0.container[..max_len];
446 let c1 = &c1.container[..max_len];
447 let c2 = &c2.container[..max_len];
448
449 for i in 0..max_len {
450 *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
451 *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
452 *output_iter.next().unwrap() = math::round_f32(c2[i]) as u8;
453 }
454 }
455 4 => {
457 let c0 = &components[0];
458 let c1 = &components[1];
459 let c2 = &components[2];
460 let c3 = &components[3];
461
462 let c0 = &c0.container[..max_len];
463 let c1 = &c1.container[..max_len];
464 let c2 = &c2.container[..max_len];
465 let c3 = &c3.container[..max_len];
466
467 for i in 0..max_len {
468 *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
469 *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
470 *output_iter.next().unwrap() = math::round_f32(c2[i]) as u8;
471 *output_iter.next().unwrap() = math::round_f32(c3[i]) as u8;
472 }
473 }
474 _ => unreachable!(),
475 }
476 } else {
477 let mul_factor = ((1 << 8) - 1) as f32;
479
480 for sample in 0..max_len {
481 for channel in components.iter() {
482 *output_iter.next().unwrap() = math::round_f32(
483 (channel.container[sample] / ((1_u32 << channel.bit_depth) - 1) as f32)
484 * mul_factor,
485 ) as u8;
486 }
487 }
488 }
489}
490
491fn convert_color_space(image: &mut DecodedImage<'_>, bit_depth: u8) -> Result<()> {
492 if let Some(jp2::colr::ColorSpace::Enumerated(e)) = &image
493 .boxes
494 .color_specification
495 .as_ref()
496 .map(|i| &i.color_space)
497 {
498 match e {
499 EnumeratedColorspace::Sycc => {
500 dispatch!(Level::new(), simd => {
501 sycc_to_rgb(simd, image.decoded_components, bit_depth)
502 })?;
503 }
504 EnumeratedColorspace::CieLab(cielab) => {
505 dispatch!(Level::new(), simd => {
506 cielab_to_rgb(simd, image.decoded_components, bit_depth, cielab)
507 })?;
508 }
509 EnumeratedColorspace::Ycck => {
510 dispatch!(Level::new(), simd => {
516 sycc_to_rgb(simd, image.decoded_components, bit_depth)
517 })?;
518 let max_val = ((1_u32 << bit_depth) - 1) as f32;
520 for comp in image.decoded_components.iter_mut().take(3) {
521 for v in comp.container.iter_mut() {
522 *v = max_val - *v;
523 }
524 }
525 }
526 _ => {}
527 }
528 }
529
530 Ok(())
531}
532
533fn get_color_space(boxes: &ImageBoxes, num_components: usize) -> Result<ColorSpace> {
534 let cs = match boxes
535 .color_specification
536 .as_ref()
537 .map(|c| &c.color_space)
538 .unwrap_or(&jp2::colr::ColorSpace::Unknown)
539 {
540 jp2::colr::ColorSpace::Enumerated(e) => {
541 match e {
542 EnumeratedColorspace::Cmyk => ColorSpace::CMYK,
543 EnumeratedColorspace::Ycck => ColorSpace::CMYK,
547 EnumeratedColorspace::Srgb => ColorSpace::RGB,
548 EnumeratedColorspace::RommRgb => {
549 ColorSpace::Icc {
551 profile: include_bytes!("../assets/ProPhoto-v2-micro.icc").to_vec(),
552 num_channels: 3,
553 }
554 }
555 EnumeratedColorspace::EsRgb => ColorSpace::RGB,
556 EnumeratedColorspace::Greyscale => ColorSpace::Gray,
557 EnumeratedColorspace::Sycc => ColorSpace::RGB,
558 EnumeratedColorspace::CieLab(_) => ColorSpace::Icc {
559 profile: include_bytes!("../assets/LAB.icc").to_vec(),
560 num_channels: 3,
561 },
562 _ => bail!(FormatError::Unsupported),
563 }
564 }
565 jp2::colr::ColorSpace::Icc(icc) => {
566 if let Some(metadata) = ICCMetadata::from_data(icc) {
567 ColorSpace::Icc {
568 profile: icc.clone(),
569 num_channels: metadata.color_space.num_components(),
570 }
571 } else {
572 ColorSpace::RGB
577 }
578 }
579 jp2::colr::ColorSpace::Unknown => match num_components {
580 1 => ColorSpace::Gray,
581 3 => ColorSpace::RGB,
582 4 => ColorSpace::CMYK,
583 _ => ColorSpace::Unknown {
584 num_channels: num_components as u8,
585 },
586 },
587 };
588
589 Ok(cs)
590}
591
592fn resolve_palette_indices(
593 components: Vec<ComponentData>,
594 boxes: &ImageBoxes,
595) -> Result<Vec<ComponentData>> {
596 let Some(palette) = boxes.palette.as_ref() else {
597 return Ok(components);
599 };
600
601 let mapping = boxes.component_mapping.as_ref().unwrap();
602 let mut resolved = Vec::with_capacity(mapping.entries.len());
603
604 for entry in &mapping.entries {
605 let component_idx = entry.component_index as usize;
606 let component = components
607 .get(component_idx)
608 .ok_or(ColorError::PaletteResolutionFailed)?;
609
610 match entry.mapping_type {
611 ComponentMappingType::Direct => resolved.push(component.clone()),
612 ComponentMappingType::Palette { column } => {
613 let column_idx = column as usize;
614 let column_info = palette
615 .columns
616 .get(column_idx)
617 .ok_or(ColorError::PaletteResolutionFailed)?;
618
619 let mut mapped =
620 Vec::with_capacity(component.container.truncated().len() + SIMD_WIDTH);
621
622 for &sample in component.container.truncated() {
623 let index = math::round_f32(sample) as i64;
624 let value = palette
625 .map(index as usize, column_idx)
626 .ok_or(ColorError::PaletteResolutionFailed)?;
627 mapped.push(value as f32);
628 }
629
630 resolved.push(ComponentData {
631 container: math::SimdBuffer::new(mapped),
632 bit_depth: column_info.bit_depth,
633 });
634 }
635 }
636 }
637
638 Ok(resolved)
639}
640
641#[inline(always)]
642fn cielab_to_rgb<S: Simd>(
643 simd: S,
644 components: &mut [ComponentData],
645 bit_depth: u8,
646 lab: &CieLab,
647) -> Result<()> {
648 let (head, _) = components
649 .split_at_mut_checked(3)
650 .ok_or(ColorError::LabConversionFailed)?;
651
652 let [l, a, b] = head else {
653 unreachable!();
654 };
655
656 let prec0 = l.bit_depth;
657 let prec1 = a.bit_depth;
658 let prec2 = b.bit_depth;
659
660 if prec0 < 4 || prec1 < 4 || prec2 < 4 {
662 bail!(ColorError::LabConversionFailed);
663 }
664
665 let rl = lab.rl.unwrap_or(100);
666 let ra = lab.ra.unwrap_or(170);
667 let rb = lab.rb.unwrap_or(200);
668 let ol = lab.ol.unwrap_or(0);
669 let oa = lab.oa.unwrap_or(1 << (bit_depth - 1));
670 let ob = lab
671 .ob
672 .unwrap_or((1 << (bit_depth - 2)) + (1 << (bit_depth - 3)));
673
674 let min_l = -(rl as f32 * ol as f32) / ((1 << prec0) - 1) as f32;
676 let max_l = min_l + rl as f32;
677 let min_a = -(ra as f32 * oa as f32) / ((1 << prec1) - 1) as f32;
678 let max_a = min_a + ra as f32;
679 let min_b = -(rb as f32 * ob as f32) / ((1 << prec2) - 1) as f32;
680 let max_b = min_b + rb as f32;
681
682 let bit_max = (1_u32 << bit_depth) - 1;
683
684 let divisor_l = ((1 << prec0) - 1) as f32;
688 let divisor_a = ((1 << prec1) - 1) as f32;
689 let divisor_b = ((1 << prec2) - 1) as f32;
690
691 let scale_l_final = bit_max as f32 / 100.0;
692 let scale_ab_final = bit_max as f32 / 255.0;
693
694 let l_offset = min_l * scale_l_final;
695 let l_scale = (max_l - min_l) / divisor_l * scale_l_final;
696 let a_offset = (min_a + 128.0) * scale_ab_final;
697 let a_scale = (max_a - min_a) / divisor_a * scale_ab_final;
698 let b_offset = (min_b + 128.0) * scale_ab_final;
699 let b_scale = (max_b - min_b) / divisor_b * scale_ab_final;
700
701 let l_offset_v = f32x8::splat(simd, l_offset);
702 let l_scale_v = f32x8::splat(simd, l_scale);
703 let a_offset_v = f32x8::splat(simd, a_offset);
704 let a_scale_v = f32x8::splat(simd, a_scale);
705 let b_offset_v = f32x8::splat(simd, b_offset);
706 let b_scale_v = f32x8::splat(simd, b_scale);
707
708 for ((l_chunk, a_chunk), b_chunk) in l
712 .container
713 .chunks_exact_mut(SIMD_WIDTH)
714 .zip(a.container.chunks_exact_mut(SIMD_WIDTH))
715 .zip(b.container.chunks_exact_mut(SIMD_WIDTH))
716 {
717 let l_v = f32x8::from_slice(simd, l_chunk);
718 let a_v = f32x8::from_slice(simd, a_chunk);
719 let b_v = f32x8::from_slice(simd, b_chunk);
720
721 l_v.mul_add(l_scale_v, l_offset_v).store(l_chunk);
722 a_v.mul_add(a_scale_v, a_offset_v).store(a_chunk);
723 b_v.mul_add(b_scale_v, b_offset_v).store(b_chunk);
724 }
725
726 Ok(())
727}
728
729#[inline(always)]
730fn sycc_to_rgb<S: Simd>(simd: S, components: &mut [ComponentData], bit_depth: u8) -> Result<()> {
731 let offset = (1_u32 << (bit_depth as u32 - 1)) as f32;
732 let max_value = ((1_u32 << bit_depth as u32) - 1) as f32;
733
734 let (head, _) = components
735 .split_at_mut_checked(3)
736 .ok_or(ColorError::SyccConversionFailed)?;
737
738 let [y, cb, cr] = head else {
739 unreachable!();
740 };
741
742 let offset_v = f32x8::splat(simd, offset);
743 let max_v = f32x8::splat(simd, max_value);
744 let zero_v = f32x8::splat(simd, 0.0);
745 let cr_to_r = f32x8::splat(simd, 1.402);
746 let cb_to_g = f32x8::splat(simd, -0.344136);
747 let cr_to_g = f32x8::splat(simd, -0.714136);
748 let cb_to_b = f32x8::splat(simd, 1.772);
749
750 for ((y_chunk, cb_chunk), cr_chunk) in y
751 .container
752 .chunks_exact_mut(SIMD_WIDTH)
753 .zip(cb.container.chunks_exact_mut(SIMD_WIDTH))
754 .zip(cr.container.chunks_exact_mut(SIMD_WIDTH))
755 {
756 let y_v = f32x8::from_slice(simd, y_chunk);
757 let cb_v = f32x8::from_slice(simd, cb_chunk) - offset_v;
758 let cr_v = f32x8::from_slice(simd, cr_chunk) - offset_v;
759
760 let r = cr_v.mul_add(cr_to_r, y_v);
762 let g = cr_v.mul_add(cr_to_g, cb_v.mul_add(cb_to_g, y_v));
764 let b = cb_v.mul_add(cb_to_b, y_v);
766
767 r.min(max_v).max(zero_v).store(y_chunk);
768 g.min(max_v).max(zero_v).store(cb_chunk);
769 b.min(max_v).max(zero_v).store(cr_chunk);
770 }
771
772 Ok(())
773}
774
775#[cfg(test)]
776mod tests {
777 use super::*;
778
779 #[rustfmt::skip]
792 const MINIMAL_J2C: &[u8] = &[
793 0xFF, 0x4F,
795
796 0xFF, 0x51,
798 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x01, 0xFF, 0x52,
813 0x00, 0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x5C,
826 0x00, 0x04, 0x00, 0x80, 0xFF, 0x90,
832 ];
833
834 #[rustfmt::skip]
843 const MINIMAL_J2C_TILE: &[u8] = &[
844 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x01, 0xFF, 0x93, 0x00, 0xFF, 0xD9, ];
856
857 fn full_j2c() -> Vec<u8> {
858 let mut data = MINIMAL_J2C.to_vec();
859 data.extend_from_slice(MINIMAL_J2C_TILE);
860 data
861 }
862
863 #[test]
864 fn new_minimal_j2c_succeeds() {
865 assert!(Image::new(MINIMAL_J2C, &DecodeSettings::default()).is_ok());
866 }
867
868 #[test]
869 fn new_minimal_j2c_dimensions() {
870 let image = Image::new(MINIMAL_J2C, &DecodeSettings::default()).expect("J2C should parse");
871 assert_eq!(image.width(), 2);
872 assert_eq!(image.height(), 2);
873 }
874
875 #[test]
876 fn new_minimal_j2c_is_greyscale() {
877 let image = Image::new(MINIMAL_J2C, &DecodeSettings::default()).expect("J2C should parse");
878 assert_eq!(image.color_space().num_channels(), 1);
879 }
880
881 #[test]
882 fn new_minimal_j2c_no_alpha() {
883 let image = Image::new(MINIMAL_J2C, &DecodeSettings::default()).expect("J2C should parse");
884 assert!(!image.has_alpha());
885 }
886
887 #[test]
888 fn new_invalid_signature_returns_error() {
889 assert!(Image::new(b"\x00\x00\x00\x00", &DecodeSettings::default()).is_err());
890 }
891
892 fn j2c_with_grid(xsiz: u32, ysiz: u32, xtsiz: u32, ytsiz: u32) -> Vec<u8> {
896 let mut data = MINIMAL_J2C.to_vec();
897 for (offset, value) in [(8, xsiz), (12, ysiz), (24, xtsiz), (28, ytsiz)] {
898 data[offset..offset + 4].copy_from_slice(&value.to_be_bytes());
899 }
900 data
901 }
902
903 #[test]
908 fn decode_minimal_j2c_round_trip() {
909 let data = full_j2c();
910 let image = Image::new(&data, &DecodeSettings::default()).expect("J2C should parse");
911 let mut ctx = DecoderContext::default();
912 let decoded = image.decode(&mut ctx).expect("2x2 greyscale should decode");
913 assert_eq!(decoded.components().len(), 1);
914 assert_eq!(decoded.data_u8().len(), 2 * 2);
915 }
916
917 #[test]
924 fn siz_with_overflowing_tile_count_is_rejected() {
925 let data = j2c_with_grid(u32::MAX, u32::MAX, 1, 1);
926 match Image::new(&data, &DecodeSettings::default()) {
927 Err(DecodeError::Validation(ValidationError::ImageTooLarge)) => {}
928 Err(other) => panic!("expected ImageTooLarge, got {other:?}"),
929 Ok(_) => panic!("a u32::MAX x u32::MAX tile grid must not be accepted"),
930 }
931 }
932
933 #[test]
936 fn siz_with_large_but_valid_tile_count_is_accepted() {
937 let data = j2c_with_grid(65535, 65535, 1, 1);
939 assert!(
940 Image::new(&data, &DecodeSettings::default()).is_ok(),
941 "65535x65535 one-pixel tiles fit in u32 and must be accepted"
942 );
943 }
944
945 #[test]
949 fn zero_target_resolution_does_not_panic() {
950 let settings = DecodeSettings {
951 resolve_palette_indices: false,
952 strict: false,
953 target_resolution: Some((0, 0)),
954 };
955 let image = Image::new(MINIMAL_J2C, &settings).expect("J2C should parse");
956 assert_eq!(image.width(), 2);
957 assert_eq!(image.height(), 2);
958 }
959
960 fn lab_component(samples: &[f32], bit_depth: u8) -> ComponentData {
961 ComponentData {
962 container: math::SimdBuffer::new(samples.to_vec()),
963 bit_depth,
964 }
965 }
966
967 fn cielab_b_channel(lab: &CieLab) -> Vec<f32> {
968 let mut components = [
969 lab_component(&[10.0, 200.0, 128.0, 255.0], 8),
970 lab_component(&[10.0, 200.0, 128.0, 255.0], 8),
971 lab_component(&[10.0, 200.0, 128.0, 255.0], 8),
972 ];
973 dispatch!(Level::new(), simd => {
974 cielab_to_rgb(simd, &mut components, 8, lab)
975 })
976 .expect("LAB conversion should succeed for 8-bit components");
977 components[2].samples().to_vec()
978 }
979
980 #[test]
987 fn cielab_b_range_comes_from_rb_not_ra() {
988 let base = CieLab {
989 rl: Some(100),
990 ol: Some(0),
991 ra: Some(170),
992 oa: Some(128),
993 rb: Some(200),
994 ob: Some(96),
995 };
996 let widened = CieLab {
997 rb: Some(400),
998 ..base
999 };
1000
1001 let from_base = cielab_b_channel(&base);
1002 let from_widened = cielab_b_channel(&widened);
1003
1004 assert_ne!(
1005 from_base, from_widened,
1006 "changing rb must change the b* channel; if it does not, rb is being ignored"
1007 );
1008 }
1009
1010 #[test]
1013 fn cielab_a_range_is_unaffected_by_rb() {
1014 let base = CieLab {
1015 rl: Some(100),
1016 ol: Some(0),
1017 ra: Some(170),
1018 oa: Some(128),
1019 rb: Some(200),
1020 ob: Some(96),
1021 };
1022 let widened = CieLab {
1023 rb: Some(400),
1024 ..base
1025 };
1026
1027 let a_of = |lab: &CieLab| {
1028 let mut components = [
1029 lab_component(&[10.0, 200.0, 128.0, 255.0], 8),
1030 lab_component(&[10.0, 200.0, 128.0, 255.0], 8),
1031 lab_component(&[10.0, 200.0, 128.0, 255.0], 8),
1032 ];
1033 dispatch!(Level::new(), simd => {
1034 cielab_to_rgb(simd, &mut components, 8, lab)
1035 })
1036 .expect("LAB conversion should succeed for 8-bit components");
1037 components[1].samples().to_vec()
1038 };
1039
1040 assert_eq!(a_of(&base), a_of(&widened));
1041 }
1042}