1use alloc::vec::Vec;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum SofKind {
15 Baseline8,
17 Extended8,
19 Extended12,
21 Progressive8,
23 Progressive12,
25 Lossless,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum ColorSpace {
32 Grayscale,
34 YCbCr,
36 Rgb,
38 Cmyk,
40 Ycck,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct SamplingFactors {
47 components: [(u8, u8); 4],
48 component_count: u8,
49 pub max_h: u8,
51 pub max_v: u8,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
57pub enum SamplingFactorsError {
58 #[error("sampling metadata must contain at least one component")]
60 Empty,
61 #[error("sampling metadata supports at most four components, got {count}")]
63 TooManyComponents {
64 count: usize,
66 },
67 #[error("invalid sampling ({h}x{v}) for component {component}")]
69 InvalidSampling {
70 component: usize,
72 h: u8,
74 v: u8,
76 },
77}
78
79impl SamplingFactors {
80 pub fn from_components(components: &[(u8, u8)]) -> Result<Self, SamplingFactorsError> {
87 if components.is_empty() {
88 return Err(SamplingFactorsError::Empty);
89 }
90 if components.len() > 4 {
91 return Err(SamplingFactorsError::TooManyComponents {
92 count: components.len(),
93 });
94 }
95 for (idx, &(h, v)) in components.iter().enumerate() {
96 if !(1..=4).contains(&h) || !(1..=4).contains(&v) {
97 return Err(SamplingFactorsError::InvalidSampling {
98 component: idx,
99 h,
100 v,
101 });
102 }
103 }
104 Ok(Self::from_validated_components(components))
105 }
106
107 pub(crate) fn from_validated_components(components: &[(u8, u8)]) -> Self {
108 debug_assert!(!components.is_empty());
109 debug_assert!(components.len() <= 4);
110 debug_assert!(components
111 .iter()
112 .all(|&(h, v)| (1..=4).contains(&h) && (1..=4).contains(&v)));
113 let mut packed = [(0u8, 0u8); 4];
114 let mut component_count = 0u8;
115 let mut max_h = 0u8;
116 let mut max_v = 0u8;
117 for (idx, &(h, v)) in components.iter().enumerate() {
118 packed[idx] = (h, v);
119 component_count += 1;
120 max_h = max_h.max(h);
121 max_v = max_v.max(v);
122 }
123 Self {
124 components: packed,
125 component_count,
126 max_h,
127 max_v,
128 }
129 }
130
131 #[must_use]
133 pub fn len(&self) -> usize {
134 usize::from(self.component_count)
135 }
136
137 #[must_use]
139 pub fn is_empty(&self) -> bool {
140 self.component_count == 0
141 }
142
143 #[must_use]
145 pub fn component(&self, index: usize) -> Option<(u8, u8)> {
146 self.components().get(index).copied()
147 }
148
149 #[must_use]
151 pub fn components(&self) -> &[(u8, u8)] {
152 &self.components[..usize::from(self.component_count)]
153 }
154
155 pub(crate) fn iter(&self) -> impl Iterator<Item = (u8, u8)> + '_ {
156 self.components().iter().copied()
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub struct McuGeometry {
163 pub width: u32,
165 pub height: u32,
167 pub columns: u32,
169 pub rows: u32,
171 pub count: u32,
173}
174
175#[derive(Debug, PartialEq, Eq)]
181pub struct RestartIndex {
182 pub scan_data_offset: usize,
184 pub interval_mcus: u32,
186 pub segments: Vec<RestartSegment>,
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub struct RestartSegment {
193 pub start_mcu: u32,
195 pub entropy_offset: usize,
197 pub marker_offset: Option<usize>,
199 pub marker: Option<u8>,
201}
202
203impl McuGeometry {
204 pub(crate) fn from_sampling(dimensions: (u32, u32), sampling: SamplingFactors) -> Self {
205 let width = u32::from(sampling.max_h) * 8;
206 let height = u32::from(sampling.max_v) * 8;
207 let columns = dimensions.0.div_ceil(width);
208 let rows = dimensions.1.div_ceil(height);
209 Self {
210 width,
211 height,
212 columns,
213 rows,
214 count: columns.saturating_mul(rows),
215 }
216 }
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub struct Rect {
222 pub x: u32,
224 pub y: u32,
226 pub w: u32,
228 pub h: u32,
230}
231
232impl Rect {
233 #[must_use]
235 pub fn full(dims: (u32, u32)) -> Self {
236 Self {
237 x: 0,
238 y: 0,
239 w: dims.0,
240 h: dims.1,
241 }
242 }
243
244 #[must_use]
246 pub fn is_within(&self, dims: (u32, u32)) -> bool {
247 let (w, h) = dims;
248 self.x.checked_add(self.w).is_some_and(|r| r <= w)
249 && self.y.checked_add(self.h).is_some_and(|b| b <= h)
250 }
251}
252
253impl From<j2k_core::Rect> for Rect {
254 fn from(rect: j2k_core::Rect) -> Self {
255 Self {
256 x: rect.x,
257 y: rect.y,
258 w: rect.w,
259 h: rect.h,
260 }
261 }
262}
263
264impl From<Rect> for j2k_core::Rect {
265 fn from(rect: Rect) -> Self {
266 Self {
267 x: rect.x,
268 y: rect.y,
269 w: rect.w,
270 h: rect.h,
271 }
272 }
273}
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278pub(crate) enum OutputFormat {
279 Rgb8,
280 Rgb8Scaled { factor: DownscaleFactor },
281 Rgba8 { alpha: u8 },
282 Rgba8Scaled { alpha: u8, factor: DownscaleFactor },
283 Gray8,
284 Gray8Scaled { factor: DownscaleFactor },
285 Gray16,
286 Gray16Scaled { factor: DownscaleFactor },
287 Rgb16,
288 Rgb16Scaled { factor: DownscaleFactor },
289 Rgba16 { alpha: u16 },
290 Rgba16Scaled { alpha: u16, factor: DownscaleFactor },
291}
292
293impl OutputFormat {
294 pub(crate) fn bytes_per_pixel(self) -> usize {
295 match self {
296 Self::Rgb8 | Self::Rgb8Scaled { .. } => 3,
297 Self::Rgba8 { .. } | Self::Rgba8Scaled { .. } => 4,
298 Self::Gray8 | Self::Gray8Scaled { .. } => 1,
299 Self::Gray16 | Self::Gray16Scaled { .. } => 2,
300 Self::Rgb16 | Self::Rgb16Scaled { .. } => 6,
301 Self::Rgba16 { .. } | Self::Rgba16Scaled { .. } => 8,
302 }
303 }
304
305 pub(crate) fn downscale(self) -> DownscaleFactor {
306 match self {
307 Self::Rgb8
308 | Self::Rgba8 { .. }
309 | Self::Gray8
310 | Self::Gray16
311 | Self::Rgb16
312 | Self::Rgba16 { .. } => DownscaleFactor::Full,
313 Self::Rgb8Scaled { factor }
314 | Self::Rgba8Scaled { factor, .. }
315 | Self::Gray8Scaled { factor }
316 | Self::Gray16Scaled { factor }
317 | Self::Rgb16Scaled { factor }
318 | Self::Rgba16Scaled { factor, .. } => factor,
319 }
320 }
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326pub(crate) enum DownscaleFactor {
327 Full,
328 Half,
329 Quarter,
330 Eighth,
331}
332
333impl DownscaleFactor {
334 pub(crate) const fn denominator(self) -> u32 {
335 match self {
336 Self::Full => 1,
337 Self::Half => 2,
338 Self::Quarter => 4,
339 Self::Eighth => 8,
340 }
341 }
342
343 pub(crate) const fn output_block_size(self) -> u32 {
344 match self {
345 Self::Full => 8,
346 Self::Half => 4,
347 Self::Quarter => 2,
348 Self::Eighth => 1,
349 }
350 }
351}
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
355pub enum ColorTransform {
356 Auto,
358 ForceRgb,
360 ForceYCbCr,
362}
363
364#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366pub struct DecodeOptions {
367 color_transform: ColorTransform,
368}
369
370impl Default for DecodeOptions {
371 fn default() -> Self {
372 Self {
373 color_transform: ColorTransform::Auto,
374 }
375 }
376}
377
378impl DecodeOptions {
379 pub fn set_color_transform(&mut self, color_transform: ColorTransform) {
381 self.color_transform = color_transform;
382 }
383
384 #[must_use]
386 pub fn color_transform(&self) -> ColorTransform {
387 self.color_transform
388 }
389
390 #[must_use]
392 pub fn with_color_transform(mut self, color_transform: ColorTransform) -> Self {
393 self.set_color_transform(color_transform);
394 self
395 }
396
397 pub(crate) fn apply_to_info(self, info: &mut Info) {
398 match (self.color_transform, info.sampling.len()) {
399 (ColorTransform::ForceRgb, 3) => info.color_space = ColorSpace::Rgb,
400 (ColorTransform::ForceYCbCr, 3) => info.color_space = ColorSpace::YCbCr,
401 (ColorTransform::Auto | ColorTransform::ForceRgb | ColorTransform::ForceYCbCr, _) => {}
402 }
403 }
404}
405
406#[derive(Debug, Clone, PartialEq, Eq)]
411pub struct Info {
412 pub dimensions: (u32, u32),
414 pub color_space: ColorSpace,
416 pub sampling: SamplingFactors,
418 pub sof_kind: SofKind,
420 pub bit_depth: u8,
422 pub restart_interval: Option<u16>,
424 pub mcu_geometry: McuGeometry,
426 pub scan_count: u16,
428}
429
430impl Info {
431 pub fn to_core_info(&self) -> j2k_core::Info {
433 j2k_core::Info {
434 dimensions: self.dimensions,
435 components: u16::from(self.sampling.component_count),
436 colorspace: core_colorspace(self.color_space),
437 bit_depth: self.bit_depth,
438 tile_layout: None,
439 coded_unit_layout: Some(j2k_core::CodedUnitLayout {
440 unit_width: self.mcu_geometry.width,
441 unit_height: self.mcu_geometry.height,
442 units_x: self.mcu_geometry.columns,
443 units_y: self.mcu_geometry.rows,
444 }),
445 restart_interval: self.restart_interval.map(u32::from),
446 resolution_levels: 1,
447 }
448 }
449}
450
451fn core_colorspace(color_space: ColorSpace) -> j2k_core::Colorspace {
452 match color_space {
453 ColorSpace::Grayscale => j2k_core::Colorspace::Grayscale,
454 ColorSpace::YCbCr => j2k_core::Colorspace::YCbCr,
455 ColorSpace::Rgb => j2k_core::Colorspace::Rgb,
456 ColorSpace::Cmyk => j2k_core::Colorspace::Cmyk,
457 ColorSpace::Ycck => j2k_core::Colorspace::Ycck,
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 #[test]
466 fn rect_full_matches_dimensions() {
467 let r = Rect::full((1024, 768));
468 assert_eq!(
469 r,
470 Rect {
471 x: 0,
472 y: 0,
473 w: 1024,
474 h: 768
475 }
476 );
477 }
478
479 #[test]
480 fn rect_is_within_accepts_contained_rect() {
481 assert!(Rect {
482 x: 0,
483 y: 0,
484 w: 100,
485 h: 100
486 }
487 .is_within((100, 100)));
488 assert!(Rect {
489 x: 10,
490 y: 20,
491 w: 30,
492 h: 40
493 }
494 .is_within((100, 100)));
495 }
496
497 #[test]
498 fn rect_is_within_rejects_overflowing_rect() {
499 assert!(!Rect {
500 x: 50,
501 y: 50,
502 w: 60,
503 h: 10
504 }
505 .is_within((100, 100)));
506 assert!(!Rect {
507 x: u32::MAX,
508 y: 0,
509 w: 1,
510 h: 1
511 }
512 .is_within((100, 100)));
513 }
514
515 #[test]
516 fn output_format_bytes_per_pixel_matches_spec() {
517 assert_eq!(OutputFormat::Rgb8.bytes_per_pixel(), 3);
518 assert_eq!(
519 OutputFormat::Rgb8Scaled {
520 factor: DownscaleFactor::Quarter
521 }
522 .bytes_per_pixel(),
523 3
524 );
525 assert_eq!(OutputFormat::Rgba8 { alpha: 255 }.bytes_per_pixel(), 4);
526 assert_eq!(
527 OutputFormat::Rgba8Scaled {
528 alpha: 255,
529 factor: DownscaleFactor::Half,
530 }
531 .bytes_per_pixel(),
532 4
533 );
534 assert_eq!(OutputFormat::Gray8.bytes_per_pixel(), 1);
535 assert_eq!(
536 OutputFormat::Gray8Scaled {
537 factor: DownscaleFactor::Half
538 }
539 .bytes_per_pixel(),
540 1
541 );
542 assert_eq!(OutputFormat::Gray16.bytes_per_pixel(), 2);
543 assert_eq!(
544 OutputFormat::Gray16Scaled {
545 factor: DownscaleFactor::Half
546 }
547 .bytes_per_pixel(),
548 2
549 );
550 assert_eq!(OutputFormat::Rgb16.bytes_per_pixel(), 6);
551 assert_eq!(
552 OutputFormat::Rgb16Scaled {
553 factor: DownscaleFactor::Half
554 }
555 .bytes_per_pixel(),
556 6
557 );
558 assert_eq!(
559 OutputFormat::Rgba16 { alpha: u16::MAX }.bytes_per_pixel(),
560 8
561 );
562 assert_eq!(
563 OutputFormat::Rgba16Scaled {
564 alpha: u16::MAX,
565 factor: DownscaleFactor::Half
566 }
567 .bytes_per_pixel(),
568 8
569 );
570 }
571
572 #[test]
573 fn sampling_factors_store_components_without_heap_state() {
574 let sampling =
575 SamplingFactors::from_components(&[(2, 2), (1, 1), (1, 1)]).expect("sampling");
576 assert_eq!(sampling.len(), 3);
577 assert_eq!(sampling.component(0), Some((2, 2)));
578 assert_eq!(sampling.component(1), Some((1, 1)));
579 assert_eq!(sampling.component(3), None);
580 assert_eq!(sampling.components(), &[(2, 2), (1, 1), (1, 1)]);
581 assert_eq!(sampling.max_h, 2);
582 assert_eq!(sampling.max_v, 2);
583 }
584
585 #[test]
586 fn sampling_factors_reject_empty_component_list() {
587 assert!(matches!(
588 SamplingFactors::from_components(&[]),
589 Err(SamplingFactorsError::Empty)
590 ));
591 }
592
593 #[test]
594 fn sampling_factors_accept_supported_component_counts() {
595 for components in [
596 &[(1, 1)][..],
597 &[(2, 2), (1, 1), (1, 1)][..],
598 &[(1, 1), (1, 1), (1, 1), (1, 1)][..],
599 ] {
600 let sampling = SamplingFactors::from_components(components).expect("sampling");
601 assert_eq!(sampling.len(), components.len());
602 assert_eq!(sampling.components(), components);
603 }
604 }
605
606 #[test]
607 fn sampling_factors_reject_invalid_factors() {
608 assert!(matches!(
609 SamplingFactors::from_components(&[(0, 1)]),
610 Err(SamplingFactorsError::InvalidSampling {
611 component: 0,
612 h: 0,
613 v: 1
614 })
615 ));
616 assert!(matches!(
617 SamplingFactors::from_components(&[(1, 5)]),
618 Err(SamplingFactorsError::InvalidSampling {
619 component: 0,
620 h: 1,
621 v: 5
622 })
623 ));
624 }
625
626 #[test]
627 fn sampling_factors_reject_more_than_four_components_without_panic() {
628 assert!(matches!(
629 SamplingFactors::from_components(&[(1, 1); 5]),
630 Err(SamplingFactorsError::TooManyComponents { count: 5 })
631 ));
632 }
633
634 #[test]
635 fn info_to_core_info_preserves_metadata_for_device_adapters() {
636 let info = Info {
637 dimensions: (32, 16),
638 color_space: ColorSpace::YCbCr,
639 sampling: SamplingFactors::from_components(&[(2, 2), (1, 1), (1, 1)])
640 .expect("sampling"),
641 sof_kind: SofKind::Baseline8,
642 bit_depth: 8,
643 restart_interval: Some(2),
644 mcu_geometry: McuGeometry {
645 width: 16,
646 height: 16,
647 columns: 2,
648 rows: 1,
649 count: 2,
650 },
651 scan_count: 1,
652 };
653
654 let core = info.to_core_info();
655
656 assert_eq!(core.dimensions, (32, 16));
657 assert_eq!(core.components, 3);
658 assert_eq!(core.colorspace, j2k_core::Colorspace::YCbCr);
659 assert_eq!(core.bit_depth, 8);
660 assert_eq!(core.tile_layout, None);
661 assert_eq!(
662 core.coded_unit_layout,
663 Some(j2k_core::CodedUnitLayout {
664 unit_width: 16,
665 unit_height: 16,
666 units_x: 2,
667 units_y: 1,
668 })
669 );
670 assert_eq!(core.restart_interval, Some(2));
671 assert_eq!(core.resolution_levels, 1);
672 }
673
674 #[test]
675 fn info_to_core_info_preserves_four_component_colorspaces() {
676 for (color_space, core_colorspace) in [
677 (ColorSpace::Cmyk, j2k_core::Colorspace::Cmyk),
678 (ColorSpace::Ycck, j2k_core::Colorspace::Ycck),
679 ] {
680 let info = Info {
681 dimensions: (64, 32),
682 color_space,
683 sampling: SamplingFactors::from_components(&[(1, 1), (1, 1), (1, 1), (1, 1)])
684 .expect("sampling"),
685 sof_kind: SofKind::Baseline8,
686 bit_depth: 8,
687 restart_interval: None,
688 mcu_geometry: McuGeometry {
689 width: 8,
690 height: 8,
691 columns: 8,
692 rows: 4,
693 count: 32,
694 },
695 scan_count: 1,
696 };
697
698 let core = info.to_core_info();
699
700 assert_eq!(core.components, 4);
701 assert_eq!(core.colorspace, core_colorspace);
702 assert_eq!(
703 core.coded_unit_layout,
704 Some(j2k_core::CodedUnitLayout {
705 unit_width: 8,
706 unit_height: 8,
707 units_x: 8,
708 units_y: 4,
709 })
710 );
711 }
712 }
713}