use crate::core::{ImageFormat, Pix, PixelDepth};
use crate::io::{IoError, IoResult, header::ImageHeader};
use hayro_jpeg2000::{ColorSpace, DecodeSettings, Image};
use std::io::{Read, Seek};
pub fn read_header_jp2k(data: &[u8]) -> IoResult<ImageHeader> {
let settings = DecodeSettings::default();
let image = Image::new(data, &settings)
.map_err(|e| IoError::DecodeError(format!("JP2K decode error: {}", e)))?;
let width = image.width();
let height = image.height();
let has_alpha = image.has_alpha();
let spp: u32 = match image.color_space() {
ColorSpace::Gray => {
if has_alpha {
4
} else {
1
}
}
_ => {
if has_alpha {
4
} else {
3
}
}
};
let depth: u32 = if spp == 1 { 8 } else { 32 };
Ok(ImageHeader {
width,
height,
depth,
bps: 8,
spp,
has_colormap: false,
num_colors: 0,
format: ImageFormat::Jp2,
x_resolution: None,
y_resolution: None,
})
}
pub fn read_jp2k<R: Read + Seek>(mut reader: R) -> IoResult<Pix> {
let mut data = Vec::new();
reader
.read_to_end(&mut data)
.map_err(|e| IoError::DecodeError(format!("Failed to read JP2K data: {}", e)))?;
read_jp2k_mem(&data)
}
pub fn read_jp2k_mem(data: &[u8]) -> IoResult<Pix> {
let settings = DecodeSettings::default();
let image = Image::new(data, &settings)
.map_err(|e| IoError::DecodeError(format!("JP2K parse error: {}", e)))?;
let width = image.width();
let height = image.height();
let color_space = image.color_space().clone();
let has_alpha = image.has_alpha();
let pixels = image
.decode()
.map_err(|e| IoError::DecodeError(format!("JP2K decode error: {}", e)))?;
match &color_space {
ColorSpace::Gray => {
let num_channels: u32 = if has_alpha { 2 } else { 1 };
if has_alpha {
let pix = Pix::new(width, height, PixelDepth::Bit32)?;
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_spp(4);
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * num_channels) as usize;
let g = pixels[idx];
let a = pixels[idx + 1];
let pixel = compose_rgba(g, g, g, a);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
Ok(pix_mut.into())
} else {
let pix = Pix::new(width, height, PixelDepth::Bit8)?;
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_spp(1);
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) as usize;
let val = pixels[idx];
pix_mut.set_pixel_unchecked(x, y, val as u32);
}
}
Ok(pix_mut.into())
}
}
ColorSpace::RGB => {
let pix = Pix::new(width, height, PixelDepth::Bit32)?;
let mut pix_mut = pix.try_into_mut().unwrap();
if has_alpha {
pix_mut.set_spp(4);
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 4) as usize;
let r = pixels[idx];
let g = pixels[idx + 1];
let b = pixels[idx + 2];
let a = pixels[idx + 3];
let pixel = compose_rgba(r, g, b, a);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
} else {
pix_mut.set_spp(3);
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
let r = pixels[idx];
let g = pixels[idx + 1];
let b = pixels[idx + 2];
let pixel = compose_rgba(r, g, b, 255);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
}
Ok(pix_mut.into())
}
ColorSpace::CMYK => {
let pix = Pix::new(width, height, PixelDepth::Bit32)?;
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_spp(3);
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 4) as usize;
let c = pixels[idx] as f32 / 255.0;
let m = pixels[idx + 1] as f32 / 255.0;
let y_val = pixels[idx + 2] as f32 / 255.0;
let k = pixels[idx + 3] as f32 / 255.0;
let r = (255.0 * (1.0 - c) * (1.0 - k)) as u8;
let g = (255.0 * (1.0 - m) * (1.0 - k)) as u8;
let b = (255.0 * (1.0 - y_val) * (1.0 - k)) as u8;
let pixel = compose_rgba(r, g, b, 255);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
Ok(pix_mut.into())
}
ColorSpace::Unknown { num_channels } => {
let n = *num_channels as usize;
let total_channels = if has_alpha { n + 1 } else { n };
if n == 1 {
if has_alpha {
let pix = Pix::new(width, height, PixelDepth::Bit32)?;
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_spp(4);
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * total_channels as u32) as usize;
let g = pixels[idx];
let a = pixels.get(idx + 1).copied().unwrap_or(255);
let pixel = compose_rgba(g, g, g, a);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
Ok(pix_mut.into())
} else {
let pix = Pix::new(width, height, PixelDepth::Bit8)?;
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_spp(1);
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) as usize;
let val = pixels[idx];
pix_mut.set_pixel_unchecked(x, y, val as u32);
}
}
Ok(pix_mut.into())
}
} else {
let pix = Pix::new(width, height, PixelDepth::Bit32)?;
let mut pix_mut = pix.try_into_mut().unwrap();
if has_alpha {
pix_mut.set_spp(4);
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * total_channels as u32) as usize;
let r = pixels.get(idx).copied().unwrap_or(0);
let g = pixels.get(idx + 1).copied().unwrap_or(0);
let b = pixels.get(idx + 2).copied().unwrap_or(0);
let a = pixels.get(idx + n).copied().unwrap_or(255);
let pixel = compose_rgba(r, g, b, a);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
} else {
pix_mut.set_spp(3);
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * total_channels as u32) as usize;
let r = pixels.get(idx).copied().unwrap_or(0);
let g = pixels.get(idx + 1).copied().unwrap_or(0);
let b = pixels.get(idx + 2).copied().unwrap_or(0);
let pixel = compose_rgba(r, g, b, 255);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
}
Ok(pix_mut.into())
}
}
ColorSpace::Icc {
num_channels,
profile: _,
} => {
let n = *num_channels as usize;
let total_channels = if has_alpha { n + 1 } else { n };
if n == 1 {
if has_alpha {
let pix = Pix::new(width, height, PixelDepth::Bit32)?;
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_spp(4);
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * total_channels as u32) as usize;
let g = pixels[idx];
let a = pixels.get(idx + 1).copied().unwrap_or(255);
let pixel = compose_rgba(g, g, g, a);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
Ok(pix_mut.into())
} else {
let pix = Pix::new(width, height, PixelDepth::Bit8)?;
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_spp(1);
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) as usize;
let val = pixels[idx];
pix_mut.set_pixel_unchecked(x, y, val as u32);
}
}
Ok(pix_mut.into())
}
} else {
let pix = Pix::new(width, height, PixelDepth::Bit32)?;
let mut pix_mut = pix.try_into_mut().unwrap();
if has_alpha {
pix_mut.set_spp(4);
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * total_channels as u32) as usize;
let r = pixels.get(idx).copied().unwrap_or(0);
let g = pixels.get(idx + 1).copied().unwrap_or(0);
let b = pixels.get(idx + 2).copied().unwrap_or(0);
let a = pixels.get(idx + n).copied().unwrap_or(255);
let pixel = compose_rgba(r, g, b, a);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
} else {
pix_mut.set_spp(3);
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * total_channels as u32) as usize;
let r = pixels.get(idx).copied().unwrap_or(0);
let g = pixels.get(idx + 1).copied().unwrap_or(0);
let b = pixels.get(idx + 2).copied().unwrap_or(0);
let pixel = compose_rgba(r, g, b, 255);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
}
Ok(pix_mut.into())
}
}
}
}
#[inline]
fn compose_rgba(r: u8, g: u8, b: u8, a: u8) -> u32 {
((r as u32) << 24) | ((g as u32) << 16) | ((b as u32) << 8) | (a as u32)
}
#[derive(Debug, Clone, Default)]
pub struct Jp2kWriteOptions {
pub quality: u32,
}
pub fn write_jp2k_mem(_pix: &Pix, _options: &Jp2kWriteOptions) -> IoResult<Vec<u8>> {
Err(IoError::UnsupportedFormat(
"JP2K writing not yet supported: no pure-Rust encoder available".to_string(),
))
}
pub fn write_jp2k<W: std::io::Write>(
_pix: &Pix,
_writer: W,
_options: &Jp2kWriteOptions,
) -> IoResult<()> {
Err(IoError::UnsupportedFormat(
"JP2K writing not yet supported: no pure-Rust encoder available".to_string(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compose_rgba() {
let r = 100u8;
let g = 150u8;
let b = 200u8;
let a = 255u8;
let pixel = compose_rgba(r, g, b, a);
assert_eq!((pixel >> 24) & 0xFF, r as u32);
assert_eq!((pixel >> 16) & 0xFF, g as u32);
assert_eq!((pixel >> 8) & 0xFF, b as u32);
assert_eq!(pixel & 0xFF, a as u32);
}
}