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 max_h = 0u8;
115 let mut max_v = 0u8;
116 for (idx, &(h, v)) in components.iter().enumerate() {
117 packed[idx] = (h, v);
118 max_h = max_h.max(h);
119 max_v = max_v.max(v);
120 }
121 Self {
122 components: packed,
123 component_count: components.len() as u8,
124 max_h,
125 max_v,
126 }
127 }
128
129 pub fn len(&self) -> usize {
131 self.component_count as usize
132 }
133
134 pub fn is_empty(&self) -> bool {
136 self.component_count == 0
137 }
138
139 pub fn component(&self, index: usize) -> Option<(u8, u8)> {
141 self.components().get(index).copied()
142 }
143
144 pub fn components(&self) -> &[(u8, u8)] {
146 &self.components[..self.component_count as usize]
147 }
148
149 pub(crate) fn iter(&self) -> impl Iterator<Item = (u8, u8)> + '_ {
150 self.components().iter().copied()
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub struct McuGeometry {
157 pub width: u32,
159 pub height: u32,
161 pub columns: u32,
163 pub rows: u32,
165 pub count: u32,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct RestartIndex {
172 pub scan_data_offset: usize,
174 pub interval_mcus: u32,
176 pub segments: Vec<RestartSegment>,
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub struct RestartSegment {
183 pub start_mcu: u32,
185 pub entropy_offset: usize,
187 pub marker_offset: Option<usize>,
189 pub marker: Option<u8>,
191}
192
193impl McuGeometry {
194 pub(crate) fn from_sampling(dimensions: (u32, u32), sampling: SamplingFactors) -> Self {
195 let width = u32::from(sampling.max_h) * 8;
196 let height = u32::from(sampling.max_v) * 8;
197 let columns = dimensions.0.div_ceil(width);
198 let rows = dimensions.1.div_ceil(height);
199 Self {
200 width,
201 height,
202 columns,
203 rows,
204 count: columns.saturating_mul(rows),
205 }
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub struct Rect {
212 pub x: u32,
214 pub y: u32,
216 pub w: u32,
218 pub h: u32,
220}
221
222impl Rect {
223 pub fn full(dims: (u32, u32)) -> Self {
225 Self {
226 x: 0,
227 y: 0,
228 w: dims.0,
229 h: dims.1,
230 }
231 }
232
233 pub fn is_within(&self, dims: (u32, u32)) -> bool {
235 let (w, h) = dims;
236 self.x.checked_add(self.w).is_some_and(|r| r <= w)
237 && self.y.checked_add(self.h).is_some_and(|b| b <= h)
238 }
239}
240
241impl From<j2k_core::Rect> for Rect {
242 fn from(rect: j2k_core::Rect) -> Self {
243 Self {
244 x: rect.x,
245 y: rect.y,
246 w: rect.w,
247 h: rect.h,
248 }
249 }
250}
251
252impl From<Rect> for j2k_core::Rect {
253 fn from(rect: Rect) -> Self {
254 Self {
255 x: rect.x,
256 y: rect.y,
257 w: rect.w,
258 h: rect.h,
259 }
260 }
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub(crate) enum OutputFormat {
267 Rgb8,
268 Rgb8Scaled { factor: DownscaleFactor },
269 Rgba8 { alpha: u8 },
270 Rgba8Scaled { alpha: u8, factor: DownscaleFactor },
271 Gray8,
272 Gray8Scaled { factor: DownscaleFactor },
273 Gray16,
274 Gray16Scaled { factor: DownscaleFactor },
275 Rgb16,
276 Rgb16Scaled { factor: DownscaleFactor },
277 Rgba16 { alpha: u16 },
278 Rgba16Scaled { alpha: u16, factor: DownscaleFactor },
279}
280
281impl OutputFormat {
282 pub(crate) fn bytes_per_pixel(self) -> usize {
283 match self {
284 Self::Rgb8 | Self::Rgb8Scaled { .. } => 3,
285 Self::Rgba8 { .. } | Self::Rgba8Scaled { .. } => 4,
286 Self::Gray8 | Self::Gray8Scaled { .. } => 1,
287 Self::Gray16 | Self::Gray16Scaled { .. } => 2,
288 Self::Rgb16 | Self::Rgb16Scaled { .. } => 6,
289 Self::Rgba16 { .. } | Self::Rgba16Scaled { .. } => 8,
290 }
291 }
292
293 pub(crate) fn downscale(self) -> DownscaleFactor {
294 match self {
295 Self::Rgb8
296 | Self::Rgba8 { .. }
297 | Self::Gray8
298 | Self::Gray16
299 | Self::Rgb16
300 | Self::Rgba16 { .. } => DownscaleFactor::Full,
301 Self::Rgb8Scaled { factor }
302 | Self::Rgba8Scaled { factor, .. }
303 | Self::Gray8Scaled { factor }
304 | Self::Gray16Scaled { factor }
305 | Self::Rgb16Scaled { factor }
306 | Self::Rgba16Scaled { factor, .. } => factor,
307 }
308 }
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
314pub(crate) enum DownscaleFactor {
315 Full,
316 Half,
317 Quarter,
318 Eighth,
319}
320
321impl DownscaleFactor {
322 pub(crate) const fn denominator(self) -> u32 {
323 match self {
324 Self::Full => 1,
325 Self::Half => 2,
326 Self::Quarter => 4,
327 Self::Eighth => 8,
328 }
329 }
330
331 pub(crate) const fn output_block_size(self) -> u32 {
332 match self {
333 Self::Full => 8,
334 Self::Half => 4,
335 Self::Quarter => 2,
336 Self::Eighth => 1,
337 }
338 }
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343pub enum ColorTransform {
344 Auto,
346 ForceRgb,
348 ForceYCbCr,
350}
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub struct DecodeOptions {
355 color_transform: ColorTransform,
356}
357
358impl Default for DecodeOptions {
359 fn default() -> Self {
360 Self {
361 color_transform: ColorTransform::Auto,
362 }
363 }
364}
365
366impl DecodeOptions {
367 pub fn set_color_transform(&mut self, color_transform: ColorTransform) {
369 self.color_transform = color_transform;
370 }
371
372 pub fn color_transform(&self) -> ColorTransform {
374 self.color_transform
375 }
376
377 #[must_use]
379 pub fn with_color_transform(mut self, color_transform: ColorTransform) -> Self {
380 self.set_color_transform(color_transform);
381 self
382 }
383
384 pub(crate) fn apply_to_info(self, info: &mut Info) {
385 match (self.color_transform, info.sampling.len()) {
386 (ColorTransform::Auto, _) => {}
387 (ColorTransform::ForceRgb, 3) => info.color_space = ColorSpace::Rgb,
388 (ColorTransform::ForceYCbCr, 3) => info.color_space = ColorSpace::YCbCr,
389 (ColorTransform::ForceRgb | ColorTransform::ForceYCbCr, _) => {}
390 }
391 }
392}
393
394#[derive(Debug, Clone, PartialEq, Eq)]
399pub struct Info {
400 pub dimensions: (u32, u32),
402 pub color_space: ColorSpace,
404 pub sampling: SamplingFactors,
406 pub sof_kind: SofKind,
408 pub bit_depth: u8,
410 pub restart_interval: Option<u16>,
412 pub mcu_geometry: McuGeometry,
414 pub scan_count: u16,
416}
417
418impl Info {
419 pub fn to_core_info(&self) -> j2k_core::Info {
421 j2k_core::Info {
422 dimensions: self.dimensions,
423 components: self.sampling.len() as u8,
424 colorspace: core_colorspace(self.color_space),
425 bit_depth: self.bit_depth,
426 tile_layout: None,
427 coded_unit_layout: Some(j2k_core::CodedUnitLayout {
428 unit_width: self.mcu_geometry.width,
429 unit_height: self.mcu_geometry.height,
430 units_x: self.mcu_geometry.columns,
431 units_y: self.mcu_geometry.rows,
432 }),
433 restart_interval: self.restart_interval.map(u32::from),
434 resolution_levels: 1,
435 }
436 }
437}
438
439fn core_colorspace(color_space: ColorSpace) -> j2k_core::Colorspace {
440 match color_space {
441 ColorSpace::Grayscale => j2k_core::Colorspace::Grayscale,
442 ColorSpace::YCbCr => j2k_core::Colorspace::YCbCr,
443 ColorSpace::Rgb => j2k_core::Colorspace::Rgb,
444 ColorSpace::Cmyk => j2k_core::Colorspace::Cmyk,
445 ColorSpace::Ycck => j2k_core::Colorspace::Ycck,
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 #[test]
454 fn rect_full_matches_dimensions() {
455 let r = Rect::full((1024, 768));
456 assert_eq!(
457 r,
458 Rect {
459 x: 0,
460 y: 0,
461 w: 1024,
462 h: 768
463 }
464 );
465 }
466
467 #[test]
468 fn rect_is_within_accepts_contained_rect() {
469 assert!(Rect {
470 x: 0,
471 y: 0,
472 w: 100,
473 h: 100
474 }
475 .is_within((100, 100)));
476 assert!(Rect {
477 x: 10,
478 y: 20,
479 w: 30,
480 h: 40
481 }
482 .is_within((100, 100)));
483 }
484
485 #[test]
486 fn rect_is_within_rejects_overflowing_rect() {
487 assert!(!Rect {
488 x: 50,
489 y: 50,
490 w: 60,
491 h: 10
492 }
493 .is_within((100, 100)));
494 assert!(!Rect {
495 x: u32::MAX,
496 y: 0,
497 w: 1,
498 h: 1
499 }
500 .is_within((100, 100)));
501 }
502
503 #[test]
504 fn output_format_bytes_per_pixel_matches_spec() {
505 assert_eq!(OutputFormat::Rgb8.bytes_per_pixel(), 3);
506 assert_eq!(
507 OutputFormat::Rgb8Scaled {
508 factor: DownscaleFactor::Quarter
509 }
510 .bytes_per_pixel(),
511 3
512 );
513 assert_eq!(OutputFormat::Rgba8 { alpha: 255 }.bytes_per_pixel(), 4);
514 assert_eq!(
515 OutputFormat::Rgba8Scaled {
516 alpha: 255,
517 factor: DownscaleFactor::Half,
518 }
519 .bytes_per_pixel(),
520 4
521 );
522 assert_eq!(OutputFormat::Gray8.bytes_per_pixel(), 1);
523 assert_eq!(
524 OutputFormat::Gray8Scaled {
525 factor: DownscaleFactor::Half
526 }
527 .bytes_per_pixel(),
528 1
529 );
530 assert_eq!(OutputFormat::Gray16.bytes_per_pixel(), 2);
531 assert_eq!(
532 OutputFormat::Gray16Scaled {
533 factor: DownscaleFactor::Half
534 }
535 .bytes_per_pixel(),
536 2
537 );
538 assert_eq!(OutputFormat::Rgb16.bytes_per_pixel(), 6);
539 assert_eq!(
540 OutputFormat::Rgb16Scaled {
541 factor: DownscaleFactor::Half
542 }
543 .bytes_per_pixel(),
544 6
545 );
546 assert_eq!(
547 OutputFormat::Rgba16 { alpha: u16::MAX }.bytes_per_pixel(),
548 8
549 );
550 assert_eq!(
551 OutputFormat::Rgba16Scaled {
552 alpha: u16::MAX,
553 factor: DownscaleFactor::Half
554 }
555 .bytes_per_pixel(),
556 8
557 );
558 }
559
560 #[test]
561 fn sampling_factors_store_components_without_heap_state() {
562 let sampling =
563 SamplingFactors::from_components(&[(2, 2), (1, 1), (1, 1)]).expect("sampling");
564 assert_eq!(sampling.len(), 3);
565 assert_eq!(sampling.component(0), Some((2, 2)));
566 assert_eq!(sampling.component(1), Some((1, 1)));
567 assert_eq!(sampling.component(3), None);
568 assert_eq!(sampling.components(), &[(2, 2), (1, 1), (1, 1)]);
569 assert_eq!(sampling.max_h, 2);
570 assert_eq!(sampling.max_v, 2);
571 }
572
573 #[test]
574 fn sampling_factors_reject_empty_component_list() {
575 assert!(matches!(
576 SamplingFactors::from_components(&[]),
577 Err(SamplingFactorsError::Empty)
578 ));
579 }
580
581 #[test]
582 fn sampling_factors_accept_supported_component_counts() {
583 for components in [
584 &[(1, 1)][..],
585 &[(2, 2), (1, 1), (1, 1)][..],
586 &[(1, 1), (1, 1), (1, 1), (1, 1)][..],
587 ] {
588 let sampling = SamplingFactors::from_components(components).expect("sampling");
589 assert_eq!(sampling.len(), components.len());
590 assert_eq!(sampling.components(), components);
591 }
592 }
593
594 #[test]
595 fn sampling_factors_reject_invalid_factors() {
596 assert!(matches!(
597 SamplingFactors::from_components(&[(0, 1)]),
598 Err(SamplingFactorsError::InvalidSampling {
599 component: 0,
600 h: 0,
601 v: 1
602 })
603 ));
604 assert!(matches!(
605 SamplingFactors::from_components(&[(1, 5)]),
606 Err(SamplingFactorsError::InvalidSampling {
607 component: 0,
608 h: 1,
609 v: 5
610 })
611 ));
612 }
613
614 #[test]
615 fn sampling_factors_reject_more_than_four_components_without_panic() {
616 assert!(matches!(
617 SamplingFactors::from_components(&[(1, 1); 5]),
618 Err(SamplingFactorsError::TooManyComponents { count: 5 })
619 ));
620 }
621
622 #[test]
623 fn info_to_core_info_preserves_metadata_for_device_adapters() {
624 let info = Info {
625 dimensions: (32, 16),
626 color_space: ColorSpace::YCbCr,
627 sampling: SamplingFactors::from_components(&[(2, 2), (1, 1), (1, 1)])
628 .expect("sampling"),
629 sof_kind: SofKind::Baseline8,
630 bit_depth: 8,
631 restart_interval: Some(2),
632 mcu_geometry: McuGeometry {
633 width: 16,
634 height: 16,
635 columns: 2,
636 rows: 1,
637 count: 2,
638 },
639 scan_count: 1,
640 };
641
642 let core = info.to_core_info();
643
644 assert_eq!(core.dimensions, (32, 16));
645 assert_eq!(core.components, 3);
646 assert_eq!(core.colorspace, j2k_core::Colorspace::YCbCr);
647 assert_eq!(core.bit_depth, 8);
648 assert_eq!(core.tile_layout, None);
649 assert_eq!(
650 core.coded_unit_layout,
651 Some(j2k_core::CodedUnitLayout {
652 unit_width: 16,
653 unit_height: 16,
654 units_x: 2,
655 units_y: 1,
656 })
657 );
658 assert_eq!(core.restart_interval, Some(2));
659 assert_eq!(core.resolution_levels, 1);
660 }
661
662 #[test]
663 fn info_to_core_info_preserves_four_component_colorspaces() {
664 for (color_space, core_colorspace) in [
665 (ColorSpace::Cmyk, j2k_core::Colorspace::Cmyk),
666 (ColorSpace::Ycck, j2k_core::Colorspace::Ycck),
667 ] {
668 let info = Info {
669 dimensions: (64, 32),
670 color_space,
671 sampling: SamplingFactors::from_components(&[(1, 1), (1, 1), (1, 1), (1, 1)])
672 .expect("sampling"),
673 sof_kind: SofKind::Baseline8,
674 bit_depth: 8,
675 restart_interval: None,
676 mcu_geometry: McuGeometry {
677 width: 8,
678 height: 8,
679 columns: 8,
680 rows: 4,
681 count: 32,
682 },
683 scan_count: 1,
684 };
685
686 let core = info.to_core_info();
687
688 assert_eq!(core.components, 4);
689 assert_eq!(core.colorspace, core_colorspace);
690 assert_eq!(
691 core.coded_unit_layout,
692 Some(j2k_core::CodedUnitLayout {
693 unit_width: 8,
694 unit_height: 8,
695 units_x: 8,
696 units_y: 4,
697 })
698 );
699 }
700 }
701}