use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SofKind {
Baseline8,
Extended8,
Extended12,
Progressive8,
Progressive12,
Lossless,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorSpace {
Grayscale,
YCbCr,
Rgb,
Cmyk,
Ycck,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SamplingFactors {
components: [(u8, u8); 4],
component_count: u8,
pub max_h: u8,
pub max_v: u8,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum SamplingFactorsError {
#[error("sampling metadata must contain at least one component")]
Empty,
#[error("sampling metadata supports at most four components, got {count}")]
TooManyComponents {
count: usize,
},
#[error("invalid sampling ({h}x{v}) for component {component}")]
InvalidSampling {
component: usize,
h: u8,
v: u8,
},
}
impl SamplingFactors {
pub fn from_components(components: &[(u8, u8)]) -> Result<Self, SamplingFactorsError> {
if components.is_empty() {
return Err(SamplingFactorsError::Empty);
}
if components.len() > 4 {
return Err(SamplingFactorsError::TooManyComponents {
count: components.len(),
});
}
for (idx, &(h, v)) in components.iter().enumerate() {
if !(1..=4).contains(&h) || !(1..=4).contains(&v) {
return Err(SamplingFactorsError::InvalidSampling {
component: idx,
h,
v,
});
}
}
Ok(Self::from_validated_components(components))
}
pub(crate) fn from_validated_components(components: &[(u8, u8)]) -> Self {
debug_assert!(!components.is_empty());
debug_assert!(components.len() <= 4);
debug_assert!(components
.iter()
.all(|&(h, v)| (1..=4).contains(&h) && (1..=4).contains(&v)));
let mut packed = [(0u8, 0u8); 4];
let mut max_h = 0u8;
let mut max_v = 0u8;
for (idx, &(h, v)) in components.iter().enumerate() {
packed[idx] = (h, v);
max_h = max_h.max(h);
max_v = max_v.max(v);
}
Self {
components: packed,
component_count: components.len() as u8,
max_h,
max_v,
}
}
pub fn len(&self) -> usize {
self.component_count as usize
}
pub fn is_empty(&self) -> bool {
self.component_count == 0
}
pub fn component(&self, index: usize) -> Option<(u8, u8)> {
self.components().get(index).copied()
}
pub fn components(&self) -> &[(u8, u8)] {
&self.components[..self.component_count as usize]
}
pub(crate) fn iter(&self) -> impl Iterator<Item = (u8, u8)> + '_ {
self.components().iter().copied()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct McuGeometry {
pub width: u32,
pub height: u32,
pub columns: u32,
pub rows: u32,
pub count: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RestartIndex {
pub scan_data_offset: usize,
pub interval_mcus: u32,
pub segments: Vec<RestartSegment>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RestartSegment {
pub start_mcu: u32,
pub entropy_offset: usize,
pub marker_offset: Option<usize>,
pub marker: Option<u8>,
}
impl McuGeometry {
pub(crate) fn from_sampling(dimensions: (u32, u32), sampling: SamplingFactors) -> Self {
let width = u32::from(sampling.max_h) * 8;
let height = u32::from(sampling.max_v) * 8;
let columns = dimensions.0.div_ceil(width);
let rows = dimensions.1.div_ceil(height);
Self {
width,
height,
columns,
rows,
count: columns.saturating_mul(rows),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect {
pub x: u32,
pub y: u32,
pub w: u32,
pub h: u32,
}
impl Rect {
pub fn full(dims: (u32, u32)) -> Self {
Self {
x: 0,
y: 0,
w: dims.0,
h: dims.1,
}
}
pub fn is_within(&self, dims: (u32, u32)) -> bool {
let (w, h) = dims;
self.x.checked_add(self.w).is_some_and(|r| r <= w)
&& self.y.checked_add(self.h).is_some_and(|b| b <= h)
}
}
impl From<j2k_core::Rect> for Rect {
fn from(rect: j2k_core::Rect) -> Self {
Self {
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
}
}
}
impl From<Rect> for j2k_core::Rect {
fn from(rect: Rect) -> Self {
Self {
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OutputFormat {
Rgb8,
Rgb8Scaled { factor: DownscaleFactor },
Rgba8 { alpha: u8 },
Rgba8Scaled { alpha: u8, factor: DownscaleFactor },
Gray8,
Gray8Scaled { factor: DownscaleFactor },
Gray16,
Gray16Scaled { factor: DownscaleFactor },
Rgb16,
Rgb16Scaled { factor: DownscaleFactor },
Rgba16 { alpha: u16 },
Rgba16Scaled { alpha: u16, factor: DownscaleFactor },
}
impl OutputFormat {
pub(crate) fn bytes_per_pixel(self) -> usize {
match self {
Self::Rgb8 | Self::Rgb8Scaled { .. } => 3,
Self::Rgba8 { .. } | Self::Rgba8Scaled { .. } => 4,
Self::Gray8 | Self::Gray8Scaled { .. } => 1,
Self::Gray16 | Self::Gray16Scaled { .. } => 2,
Self::Rgb16 | Self::Rgb16Scaled { .. } => 6,
Self::Rgba16 { .. } | Self::Rgba16Scaled { .. } => 8,
}
}
pub(crate) fn downscale(self) -> DownscaleFactor {
match self {
Self::Rgb8
| Self::Rgba8 { .. }
| Self::Gray8
| Self::Gray16
| Self::Rgb16
| Self::Rgba16 { .. } => DownscaleFactor::Full,
Self::Rgb8Scaled { factor }
| Self::Rgba8Scaled { factor, .. }
| Self::Gray8Scaled { factor }
| Self::Gray16Scaled { factor }
| Self::Rgb16Scaled { factor }
| Self::Rgba16Scaled { factor, .. } => factor,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DownscaleFactor {
Full,
Half,
Quarter,
Eighth,
}
impl DownscaleFactor {
pub(crate) const fn denominator(self) -> u32 {
match self {
Self::Full => 1,
Self::Half => 2,
Self::Quarter => 4,
Self::Eighth => 8,
}
}
pub(crate) const fn output_block_size(self) -> u32 {
match self {
Self::Full => 8,
Self::Half => 4,
Self::Quarter => 2,
Self::Eighth => 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorTransform {
Auto,
ForceRgb,
ForceYCbCr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodeOptions {
color_transform: ColorTransform,
}
impl Default for DecodeOptions {
fn default() -> Self {
Self {
color_transform: ColorTransform::Auto,
}
}
}
impl DecodeOptions {
pub fn set_color_transform(&mut self, color_transform: ColorTransform) {
self.color_transform = color_transform;
}
pub fn color_transform(&self) -> ColorTransform {
self.color_transform
}
#[must_use]
pub fn with_color_transform(mut self, color_transform: ColorTransform) -> Self {
self.set_color_transform(color_transform);
self
}
pub(crate) fn apply_to_info(self, info: &mut Info) {
match (self.color_transform, info.sampling.len()) {
(ColorTransform::Auto, _) => {}
(ColorTransform::ForceRgb, 3) => info.color_space = ColorSpace::Rgb,
(ColorTransform::ForceYCbCr, 3) => info.color_space = ColorSpace::YCbCr,
(ColorTransform::ForceRgb | ColorTransform::ForceYCbCr, _) => {}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Info {
pub dimensions: (u32, u32),
pub color_space: ColorSpace,
pub sampling: SamplingFactors,
pub sof_kind: SofKind,
pub bit_depth: u8,
pub restart_interval: Option<u16>,
pub mcu_geometry: McuGeometry,
pub scan_count: u16,
}
impl Info {
pub fn to_core_info(&self) -> j2k_core::Info {
j2k_core::Info {
dimensions: self.dimensions,
components: self.sampling.len() as u8,
colorspace: core_colorspace(self.color_space),
bit_depth: self.bit_depth,
tile_layout: None,
coded_unit_layout: Some(j2k_core::CodedUnitLayout {
unit_width: self.mcu_geometry.width,
unit_height: self.mcu_geometry.height,
units_x: self.mcu_geometry.columns,
units_y: self.mcu_geometry.rows,
}),
restart_interval: self.restart_interval.map(u32::from),
resolution_levels: 1,
}
}
}
fn core_colorspace(color_space: ColorSpace) -> j2k_core::Colorspace {
match color_space {
ColorSpace::Grayscale => j2k_core::Colorspace::Grayscale,
ColorSpace::YCbCr => j2k_core::Colorspace::YCbCr,
ColorSpace::Rgb => j2k_core::Colorspace::Rgb,
ColorSpace::Cmyk => j2k_core::Colorspace::Cmyk,
ColorSpace::Ycck => j2k_core::Colorspace::Ycck,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rect_full_matches_dimensions() {
let r = Rect::full((1024, 768));
assert_eq!(
r,
Rect {
x: 0,
y: 0,
w: 1024,
h: 768
}
);
}
#[test]
fn rect_is_within_accepts_contained_rect() {
assert!(Rect {
x: 0,
y: 0,
w: 100,
h: 100
}
.is_within((100, 100)));
assert!(Rect {
x: 10,
y: 20,
w: 30,
h: 40
}
.is_within((100, 100)));
}
#[test]
fn rect_is_within_rejects_overflowing_rect() {
assert!(!Rect {
x: 50,
y: 50,
w: 60,
h: 10
}
.is_within((100, 100)));
assert!(!Rect {
x: u32::MAX,
y: 0,
w: 1,
h: 1
}
.is_within((100, 100)));
}
#[test]
fn output_format_bytes_per_pixel_matches_spec() {
assert_eq!(OutputFormat::Rgb8.bytes_per_pixel(), 3);
assert_eq!(
OutputFormat::Rgb8Scaled {
factor: DownscaleFactor::Quarter
}
.bytes_per_pixel(),
3
);
assert_eq!(OutputFormat::Rgba8 { alpha: 255 }.bytes_per_pixel(), 4);
assert_eq!(
OutputFormat::Rgba8Scaled {
alpha: 255,
factor: DownscaleFactor::Half,
}
.bytes_per_pixel(),
4
);
assert_eq!(OutputFormat::Gray8.bytes_per_pixel(), 1);
assert_eq!(
OutputFormat::Gray8Scaled {
factor: DownscaleFactor::Half
}
.bytes_per_pixel(),
1
);
assert_eq!(OutputFormat::Gray16.bytes_per_pixel(), 2);
assert_eq!(
OutputFormat::Gray16Scaled {
factor: DownscaleFactor::Half
}
.bytes_per_pixel(),
2
);
assert_eq!(OutputFormat::Rgb16.bytes_per_pixel(), 6);
assert_eq!(
OutputFormat::Rgb16Scaled {
factor: DownscaleFactor::Half
}
.bytes_per_pixel(),
6
);
assert_eq!(
OutputFormat::Rgba16 { alpha: u16::MAX }.bytes_per_pixel(),
8
);
assert_eq!(
OutputFormat::Rgba16Scaled {
alpha: u16::MAX,
factor: DownscaleFactor::Half
}
.bytes_per_pixel(),
8
);
}
#[test]
fn sampling_factors_store_components_without_heap_state() {
let sampling =
SamplingFactors::from_components(&[(2, 2), (1, 1), (1, 1)]).expect("sampling");
assert_eq!(sampling.len(), 3);
assert_eq!(sampling.component(0), Some((2, 2)));
assert_eq!(sampling.component(1), Some((1, 1)));
assert_eq!(sampling.component(3), None);
assert_eq!(sampling.components(), &[(2, 2), (1, 1), (1, 1)]);
assert_eq!(sampling.max_h, 2);
assert_eq!(sampling.max_v, 2);
}
#[test]
fn sampling_factors_reject_empty_component_list() {
assert!(matches!(
SamplingFactors::from_components(&[]),
Err(SamplingFactorsError::Empty)
));
}
#[test]
fn sampling_factors_accept_supported_component_counts() {
for components in [
&[(1, 1)][..],
&[(2, 2), (1, 1), (1, 1)][..],
&[(1, 1), (1, 1), (1, 1), (1, 1)][..],
] {
let sampling = SamplingFactors::from_components(components).expect("sampling");
assert_eq!(sampling.len(), components.len());
assert_eq!(sampling.components(), components);
}
}
#[test]
fn sampling_factors_reject_invalid_factors() {
assert!(matches!(
SamplingFactors::from_components(&[(0, 1)]),
Err(SamplingFactorsError::InvalidSampling {
component: 0,
h: 0,
v: 1
})
));
assert!(matches!(
SamplingFactors::from_components(&[(1, 5)]),
Err(SamplingFactorsError::InvalidSampling {
component: 0,
h: 1,
v: 5
})
));
}
#[test]
fn sampling_factors_reject_more_than_four_components_without_panic() {
assert!(matches!(
SamplingFactors::from_components(&[(1, 1); 5]),
Err(SamplingFactorsError::TooManyComponents { count: 5 })
));
}
#[test]
fn info_to_core_info_preserves_metadata_for_device_adapters() {
let info = Info {
dimensions: (32, 16),
color_space: ColorSpace::YCbCr,
sampling: SamplingFactors::from_components(&[(2, 2), (1, 1), (1, 1)])
.expect("sampling"),
sof_kind: SofKind::Baseline8,
bit_depth: 8,
restart_interval: Some(2),
mcu_geometry: McuGeometry {
width: 16,
height: 16,
columns: 2,
rows: 1,
count: 2,
},
scan_count: 1,
};
let core = info.to_core_info();
assert_eq!(core.dimensions, (32, 16));
assert_eq!(core.components, 3);
assert_eq!(core.colorspace, j2k_core::Colorspace::YCbCr);
assert_eq!(core.bit_depth, 8);
assert_eq!(core.tile_layout, None);
assert_eq!(
core.coded_unit_layout,
Some(j2k_core::CodedUnitLayout {
unit_width: 16,
unit_height: 16,
units_x: 2,
units_y: 1,
})
);
assert_eq!(core.restart_interval, Some(2));
assert_eq!(core.resolution_levels, 1);
}
#[test]
fn info_to_core_info_preserves_four_component_colorspaces() {
for (color_space, core_colorspace) in [
(ColorSpace::Cmyk, j2k_core::Colorspace::Cmyk),
(ColorSpace::Ycck, j2k_core::Colorspace::Ycck),
] {
let info = Info {
dimensions: (64, 32),
color_space,
sampling: SamplingFactors::from_components(&[(1, 1), (1, 1), (1, 1), (1, 1)])
.expect("sampling"),
sof_kind: SofKind::Baseline8,
bit_depth: 8,
restart_interval: None,
mcu_geometry: McuGeometry {
width: 8,
height: 8,
columns: 8,
rows: 4,
count: 32,
},
scan_count: 1,
};
let core = info.to_core_info();
assert_eq!(core.components, 4);
assert_eq!(core.colorspace, core_colorspace);
assert_eq!(
core.coded_unit_layout,
Some(j2k_core::CodedUnitLayout {
unit_width: 8,
unit_height: 8,
units_x: 8,
units_y: 4,
})
);
}
}
}