#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#![forbid(missing_docs)]
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use crate::error::{bail, err};
use crate::j2c::{ComponentData, DecodedCodestream, Header};
use crate::jp2::cdef::{ChannelAssociation, ChannelType};
use crate::jp2::cmap::ComponentMappingType;
use crate::jp2::colr::{CieLab, EnumeratedColorspace};
use crate::jp2::icc::ICCMetadata;
use crate::jp2::{DecodedImage, ImageBoxes};
pub mod error;
#[macro_use]
pub(crate) mod log;
pub(crate) mod math;
use crate::math::{Level, SIMD_WIDTH, Simd, dispatch, f32x8};
pub use error::{
ColorError, DecodeError, DecodingError, FormatError, MarkerError, Result, TileError,
ValidationError,
};
#[cfg(feature = "image")]
pub mod integration;
mod j2c;
mod jp2;
pub(crate) mod reader;
pub(crate) const JP2_MAGIC: &[u8] = b"\x00\x00\x00\x0C\x6A\x50\x20\x20";
pub(crate) const CODESTREAM_MAGIC: &[u8] = b"\xFF\x4F\xFF\x51";
#[derive(Debug, Copy, Clone)]
pub struct DecodeSettings {
pub resolve_palette_indices: bool,
pub strict: bool,
pub target_resolution: Option<(u32, u32)>,
}
impl Default for DecodeSettings {
fn default() -> Self {
Self {
resolve_palette_indices: true,
strict: false,
target_resolution: None,
}
}
}
pub struct Image<'a> {
pub(crate) codestream: &'a [u8],
pub(crate) header: Header<'a>,
pub(crate) boxes: ImageBoxes,
pub(crate) settings: DecodeSettings,
pub(crate) has_alpha: bool,
pub(crate) color_space: ColorSpace,
}
impl<'a> Image<'a> {
pub fn new(data: &'a [u8], settings: &DecodeSettings) -> Result<Self> {
if data.starts_with(JP2_MAGIC) {
jp2::parse(data, *settings)
} else if data.starts_with(CODESTREAM_MAGIC) {
j2c::parse(data, settings)
} else {
err!(FormatError::InvalidSignature)
}
}
pub fn has_alpha(&self) -> bool {
self.has_alpha
}
pub fn color_space(&self) -> &ColorSpace {
&self.color_space
}
pub fn width(&self) -> u32 {
self.header.size_data.image_width()
}
pub fn height(&self) -> u32 {
self.header.size_data.image_height()
}
pub fn original_bit_depth(&self) -> u8 {
self.header.component_infos[0].size_info.precision
}
pub fn decode(&self) -> Result<Vec<u8>> {
let buffer_size = self.width() as usize
* self.height() as usize
* (self.color_space.num_channels() as usize + if self.has_alpha { 1 } else { 0 });
let mut buf = vec![0; buffer_size];
self.decode_into(&mut buf)?;
Ok(buf)
}
pub(crate) fn decode_into(&self, buf: &mut [u8]) -> Result<()> {
let settings = &self.settings;
let mut decoded_image =
j2c::decode(self.codestream, &self.header).map(move |data| DecodedImage {
decoded: DecodedCodestream { components: data },
boxes: self.boxes.clone(),
})?;
if settings.resolve_palette_indices {
decoded_image.decoded.components =
resolve_palette_indices(decoded_image.decoded.components, &decoded_image.boxes)?;
}
if let Some(cdef) = &decoded_image.boxes.channel_definition {
let mut components = decoded_image
.decoded
.components
.into_iter()
.zip(
cdef.channel_definitions
.iter()
.map(|c| match c._association {
ChannelAssociation::WholeImage => u16::MAX,
ChannelAssociation::Colour(c) => c,
}),
)
.collect::<Vec<_>>();
components.sort_by(|c1, c2| c1.1.cmp(&c2.1));
decoded_image.decoded.components = components.into_iter().map(|c| c.0).collect();
}
let bit_depth = decoded_image.decoded.components[0].bit_depth;
convert_color_space(&mut decoded_image, bit_depth)?;
interleave_and_convert(decoded_image, buf);
Ok(())
}
}
pub(crate) fn resolve_alpha_and_color_space(
boxes: &ImageBoxes,
header: &Header<'_>,
settings: &DecodeSettings,
) -> Result<(ColorSpace, bool)> {
let mut num_components = header.component_infos.len();
if settings.resolve_palette_indices
&& let Some(palette_box) = &boxes.palette
{
num_components = palette_box.columns.len();
}
let mut has_alpha = false;
if let Some(cdef) = &boxes.channel_definition {
let last = cdef.channel_definitions.last().unwrap();
has_alpha = last.channel_type == ChannelType::Opacity;
}
let mut color_space = get_color_space(boxes, num_components)?;
if !settings.resolve_palette_indices && boxes.palette.is_some() {
has_alpha = false;
color_space = ColorSpace::Gray;
}
let actual_num_components = header.component_infos.len();
if boxes.palette.is_none()
&& actual_num_components
!= (color_space.num_channels() + if has_alpha { 1 } else { 0 }) as usize
{
if !settings.strict
&& actual_num_components == color_space.num_channels() as usize + 1
&& !has_alpha
{
has_alpha = true;
} else {
if actual_num_components == 1 || (actual_num_components == 2 && has_alpha) {
color_space = ColorSpace::Gray;
} else if actual_num_components == 3 {
color_space = ColorSpace::RGB;
} else if actual_num_components == 4 {
if has_alpha {
color_space = ColorSpace::RGB;
} else {
color_space = ColorSpace::CMYK;
}
} else {
bail!(ValidationError::TooManyChannels);
}
}
}
Ok((color_space, has_alpha))
}
#[derive(Debug, Clone)]
pub enum ColorSpace {
Gray,
RGB,
CMYK,
Unknown {
num_channels: u8,
},
Icc {
profile: Vec<u8>,
num_channels: u8,
},
}
impl ColorSpace {
pub fn num_channels(&self) -> u8 {
match self {
Self::Gray => 1,
Self::RGB => 3,
Self::CMYK => 4,
Self::Unknown { num_channels } => *num_channels,
Self::Icc {
num_channels: num_components,
..
} => *num_components,
}
}
}
pub struct Bitmap {
pub color_space: ColorSpace,
pub data: Vec<u8>,
pub has_alpha: bool,
pub width: u32,
pub height: u32,
pub original_bit_depth: u8,
}
fn interleave_and_convert(image: DecodedImage, buf: &mut [u8]) {
let mut components = image.decoded.components;
let num_components = components.len();
let mut all_same_bit_depth = Some(components[0].bit_depth);
for component in components.iter().skip(1) {
if Some(component.bit_depth) != all_same_bit_depth {
all_same_bit_depth = None;
}
}
let max_len = components[0].container.truncated().len();
let mut output_iter = buf.iter_mut();
if all_same_bit_depth == Some(8) && num_components <= 4 {
match num_components {
1 => {
for (output, input) in output_iter.zip(
components[0]
.container
.iter()
.map(|v| math::round_f32(*v) as u8),
) {
*output = input;
}
}
2 => {
let c1 = components.pop().unwrap();
let c0 = components.pop().unwrap();
let c0 = &c0.container[..max_len];
let c1 = &c1.container[..max_len];
for i in 0..max_len {
*output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
*output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
}
}
3 => {
let c2 = components.pop().unwrap();
let c1 = components.pop().unwrap();
let c0 = components.pop().unwrap();
let c0 = &c0.container[..max_len];
let c1 = &c1.container[..max_len];
let c2 = &c2.container[..max_len];
for i in 0..max_len {
*output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
*output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
*output_iter.next().unwrap() = math::round_f32(c2[i]) as u8;
}
}
4 => {
let c3 = components.pop().unwrap();
let c2 = components.pop().unwrap();
let c1 = components.pop().unwrap();
let c0 = components.pop().unwrap();
let c0 = &c0.container[..max_len];
let c1 = &c1.container[..max_len];
let c2 = &c2.container[..max_len];
let c3 = &c3.container[..max_len];
for i in 0..max_len {
*output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
*output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
*output_iter.next().unwrap() = math::round_f32(c2[i]) as u8;
*output_iter.next().unwrap() = math::round_f32(c3[i]) as u8;
}
}
_ => unreachable!(),
}
} else {
let mul_factor = ((1 << 8) - 1) as f32;
for sample in 0..max_len {
for channel in components.iter() {
*output_iter.next().unwrap() = math::round_f32(
(channel.container[sample] / ((1_u32 << channel.bit_depth) - 1) as f32)
* mul_factor,
) as u8;
}
}
}
}
fn convert_color_space(image: &mut DecodedImage, bit_depth: u8) -> Result<()> {
if let Some(jp2::colr::ColorSpace::Enumerated(e)) = &image
.boxes
.color_specification
.as_ref()
.map(|i| &i.color_space)
{
match e {
EnumeratedColorspace::Sycc => {
dispatch!(Level::new(), simd => {
sycc_to_rgb(simd, &mut image.decoded.components, bit_depth)
})?;
}
EnumeratedColorspace::CieLab(cielab) => {
dispatch!(Level::new(), simd => {
cielab_to_rgb(simd, &mut image.decoded.components, bit_depth, cielab)
})?;
}
EnumeratedColorspace::Ycck => {
dispatch!(Level::new(), simd => {
sycc_to_rgb(simd, &mut image.decoded.components, bit_depth)
})?;
let max_val = ((1_u32 << bit_depth) - 1) as f32;
for comp in image.decoded.components.iter_mut().take(3) {
for v in comp.container.iter_mut() {
*v = max_val - *v;
}
}
}
_ => {}
}
}
Ok(())
}
fn get_color_space(boxes: &ImageBoxes, num_components: usize) -> Result<ColorSpace> {
let cs = match boxes
.color_specification
.as_ref()
.map(|c| &c.color_space)
.unwrap_or(&jp2::colr::ColorSpace::Unknown)
{
jp2::colr::ColorSpace::Enumerated(e) => {
match e {
EnumeratedColorspace::Cmyk => ColorSpace::CMYK,
EnumeratedColorspace::Ycck => ColorSpace::CMYK,
EnumeratedColorspace::Srgb => ColorSpace::RGB,
EnumeratedColorspace::RommRgb => {
ColorSpace::Icc {
profile: include_bytes!("../assets/ProPhoto-v2-micro.icc").to_vec(),
num_channels: 3,
}
}
EnumeratedColorspace::EsRgb => ColorSpace::RGB,
EnumeratedColorspace::Greyscale => ColorSpace::Gray,
EnumeratedColorspace::Sycc => ColorSpace::RGB,
EnumeratedColorspace::CieLab(_) => ColorSpace::Icc {
profile: include_bytes!("../assets/LAB.icc").to_vec(),
num_channels: 3,
},
_ => bail!(FormatError::Unsupported),
}
}
jp2::colr::ColorSpace::Icc(icc) => {
if let Some(metadata) = ICCMetadata::from_data(icc) {
ColorSpace::Icc {
profile: icc.clone(),
num_channels: metadata.color_space.num_components(),
}
} else {
ColorSpace::RGB
}
}
jp2::colr::ColorSpace::Unknown => match num_components {
1 => ColorSpace::Gray,
3 => ColorSpace::RGB,
4 => ColorSpace::CMYK,
_ => ColorSpace::Unknown {
num_channels: num_components as u8,
},
},
};
Ok(cs)
}
fn resolve_palette_indices(
components: Vec<ComponentData>,
boxes: &ImageBoxes,
) -> Result<Vec<ComponentData>> {
let Some(palette) = boxes.palette.as_ref() else {
return Ok(components);
};
let mapping = boxes.component_mapping.as_ref().unwrap();
let mut resolved = Vec::with_capacity(mapping.entries.len());
for entry in &mapping.entries {
let component_idx = entry.component_index as usize;
let component = components
.get(component_idx)
.ok_or(ColorError::PaletteResolutionFailed)?;
match entry.mapping_type {
ComponentMappingType::Direct => resolved.push(component.clone()),
ComponentMappingType::Palette { column } => {
let column_idx = column as usize;
let column_info = palette
.columns
.get(column_idx)
.ok_or(ColorError::PaletteResolutionFailed)?;
let mut mapped =
Vec::with_capacity(component.container.truncated().len() + SIMD_WIDTH);
for &sample in component.container.truncated() {
let index = math::round_f32(sample) as i64;
let value = palette
.map(index as usize, column_idx)
.ok_or(ColorError::PaletteResolutionFailed)?;
mapped.push(value as f32);
}
resolved.push(ComponentData {
container: math::SimdBuffer::new(mapped),
bit_depth: column_info.bit_depth,
});
}
}
}
Ok(resolved)
}
#[inline(always)]
fn cielab_to_rgb<S: Simd>(
simd: S,
components: &mut [ComponentData],
bit_depth: u8,
lab: &CieLab,
) -> Result<()> {
let (head, _) = components
.split_at_mut_checked(3)
.ok_or(ColorError::LabConversionFailed)?;
let [l, a, b] = head else {
unreachable!();
};
let prec0 = l.bit_depth;
let prec1 = a.bit_depth;
let prec2 = b.bit_depth;
if prec0 < 4 || prec1 < 4 || prec2 < 4 {
bail!(ColorError::LabConversionFailed);
}
let rl = lab.rl.unwrap_or(100);
let ra = lab.ra.unwrap_or(170);
let rb = lab.ra.unwrap_or(200);
let ol = lab.ol.unwrap_or(0);
let oa = lab.oa.unwrap_or(1 << (bit_depth - 1));
let ob = lab
.ob
.unwrap_or((1 << (bit_depth - 2)) + (1 << (bit_depth - 3)));
let min_l = -(rl as f32 * ol as f32) / ((1 << prec0) - 1) as f32;
let max_l = min_l + rl as f32;
let min_a = -(ra as f32 * oa as f32) / ((1 << prec1) - 1) as f32;
let max_a = min_a + ra as f32;
let min_b = -(rb as f32 * ob as f32) / ((1 << prec2) - 1) as f32;
let max_b = min_b + rb as f32;
let bit_max = (1_u32 << bit_depth) - 1;
let divisor_l = ((1 << prec0) - 1) as f32;
let divisor_a = ((1 << prec1) - 1) as f32;
let divisor_b = ((1 << prec2) - 1) as f32;
let scale_l_final = bit_max as f32 / 100.0;
let scale_ab_final = bit_max as f32 / 255.0;
let l_offset = min_l * scale_l_final;
let l_scale = (max_l - min_l) / divisor_l * scale_l_final;
let a_offset = (min_a + 128.0) * scale_ab_final;
let a_scale = (max_a - min_a) / divisor_a * scale_ab_final;
let b_offset = (min_b + 128.0) * scale_ab_final;
let b_scale = (max_b - min_b) / divisor_b * scale_ab_final;
let l_offset_v = f32x8::splat(simd, l_offset);
let l_scale_v = f32x8::splat(simd, l_scale);
let a_offset_v = f32x8::splat(simd, a_offset);
let a_scale_v = f32x8::splat(simd, a_scale);
let b_offset_v = f32x8::splat(simd, b_offset);
let b_scale_v = f32x8::splat(simd, b_scale);
for ((l_chunk, a_chunk), b_chunk) in l
.container
.chunks_exact_mut(SIMD_WIDTH)
.zip(a.container.chunks_exact_mut(SIMD_WIDTH))
.zip(b.container.chunks_exact_mut(SIMD_WIDTH))
{
let l_v = f32x8::from_slice(simd, l_chunk);
let a_v = f32x8::from_slice(simd, a_chunk);
let b_v = f32x8::from_slice(simd, b_chunk);
l_v.mul_add(l_scale_v, l_offset_v).store(l_chunk);
a_v.mul_add(a_scale_v, a_offset_v).store(a_chunk);
b_v.mul_add(b_scale_v, b_offset_v).store(b_chunk);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[rustfmt::skip]
const MINIMAL_J2C: &[u8] = &[
0xFF, 0x4F,
0xFF, 0x51,
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,
0x00, 0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xFF, 0x5C,
0x00, 0x04, 0x00, 0x80,
0xFF, 0x90,
];
#[test]
fn new_minimal_j2c_succeeds() {
assert!(Image::new(MINIMAL_J2C, &DecodeSettings::default()).is_ok());
}
#[test]
fn new_minimal_j2c_dimensions() {
let image = Image::new(MINIMAL_J2C, &DecodeSettings::default()).expect("J2C should parse");
assert_eq!(image.width(), 2);
assert_eq!(image.height(), 2);
}
#[test]
fn new_minimal_j2c_is_greyscale() {
let image = Image::new(MINIMAL_J2C, &DecodeSettings::default()).expect("J2C should parse");
assert_eq!(image.color_space().num_channels(), 1);
}
#[test]
fn new_minimal_j2c_no_alpha() {
let image = Image::new(MINIMAL_J2C, &DecodeSettings::default()).expect("J2C should parse");
assert!(!image.has_alpha());
}
#[test]
fn new_invalid_signature_returns_error() {
assert!(Image::new(b"\x00\x00\x00\x00", &DecodeSettings::default()).is_err());
}
}
#[inline(always)]
fn sycc_to_rgb<S: Simd>(simd: S, components: &mut [ComponentData], bit_depth: u8) -> Result<()> {
let offset = (1_u32 << (bit_depth as u32 - 1)) as f32;
let max_value = ((1_u32 << bit_depth as u32) - 1) as f32;
let (head, _) = components
.split_at_mut_checked(3)
.ok_or(ColorError::SyccConversionFailed)?;
let [y, cb, cr] = head else {
unreachable!();
};
let offset_v = f32x8::splat(simd, offset);
let max_v = f32x8::splat(simd, max_value);
let zero_v = f32x8::splat(simd, 0.0);
let cr_to_r = f32x8::splat(simd, 1.402);
let cb_to_g = f32x8::splat(simd, -0.344136);
let cr_to_g = f32x8::splat(simd, -0.714136);
let cb_to_b = f32x8::splat(simd, 1.772);
for ((y_chunk, cb_chunk), cr_chunk) in y
.container
.chunks_exact_mut(SIMD_WIDTH)
.zip(cb.container.chunks_exact_mut(SIMD_WIDTH))
.zip(cr.container.chunks_exact_mut(SIMD_WIDTH))
{
let y_v = f32x8::from_slice(simd, y_chunk);
let cb_v = f32x8::from_slice(simd, cb_chunk) - offset_v;
let cr_v = f32x8::from_slice(simd, cr_chunk) - offset_v;
let r = cr_v.mul_add(cr_to_r, y_v);
let g = cr_v.mul_add(cr_to_g, cb_v.mul_add(cb_to_g, y_v));
let b = cb_v.mul_add(cb_to_b, y_v);
r.min(max_v).max(zero_v).store(y_chunk);
g.min(max_v).max(zero_v).store(cb_chunk);
b.min(max_v).max(zero_v).store(cr_chunk);
}
Ok(())
}