#![allow(dead_code)]
#![allow(unused_imports)]
use crate::error::{Error, Result};
pub const ICC_PROFILE_SIGNATURE: &[u8; 12] = b"ICC_PROFILE\0";
const XYB_PROFILE_MARKER: &[u8] = b"XYB";
pub fn extract_icc_profile(jpeg_data: &[u8]) -> Option<Vec<u8>> {
let mut chunks: Vec<(u8, Vec<u8>)> = Vec::new();
let mut i = 0;
while i < jpeg_data.len().saturating_sub(1) {
if jpeg_data[i] == 0xFF && jpeg_data[i + 1] == 0xE2 {
if i + 4 > jpeg_data.len() {
break;
}
let length = ((jpeg_data[i + 2] as usize) << 8) | (jpeg_data[i + 3] as usize);
if i + 2 + length > jpeg_data.len() {
break;
}
if length >= 16 && &jpeg_data[i + 4..i + 16] == ICC_PROFILE_SIGNATURE {
let chunk_num = jpeg_data[i + 16];
let _total_chunks = jpeg_data[i + 17];
let icc_data = jpeg_data[i + 18..i + 2 + length].to_vec();
chunks.push((chunk_num, icc_data));
}
i += 2 + length;
} else {
i += 1;
}
}
if chunks.is_empty() {
return None;
}
chunks.sort_by_key(|(num, _)| *num);
let profile: Vec<u8> = chunks.into_iter().flat_map(|(_, data)| data).collect();
Some(profile)
}
pub fn is_xyb_profile(icc_data: &[u8]) -> bool {
if icc_data.len() >= 8 && &icc_data[4..8] == b"jxl " {
return true;
}
const XYB_UTF16BE: [u8; 6] = [0, b'X', 0, b'Y', 0, b'B'];
icc_data
.windows(XYB_PROFILE_MARKER.len())
.any(|w| w == XYB_PROFILE_MARKER)
|| icc_data.windows(6).any(|w| w == XYB_UTF16BE)
}
#[cfg(feature = "cms-lcms2")]
pub fn apply_icc_transform(
rgb_data: &[u8],
_width: usize,
_height: usize,
icc_profile: &[u8],
) -> Result<Vec<u8>> {
use lcms2::{Intent, PixelFormat, Profile, Transform};
let input_profile =
Profile::new_icc(icc_profile).map_err(|e| Error::icc_error(format!("lcms2: {e}")))?;
let srgb = Profile::new_srgb();
let transform = Transform::new(
&input_profile,
PixelFormat::RGB_8,
&srgb,
PixelFormat::RGB_8,
Intent::RelativeColorimetric,
)
.map_err(|e| Error::icc_error(format!("lcms2 transform: {e}")))?;
let pixels: Vec<[u8; 3]> = rgb_data
.chunks_exact(3)
.map(|c| [c[0], c[1], c[2]])
.collect();
let mut output = vec![[0u8; 3]; pixels.len()];
transform.transform_pixels(&pixels, &mut output);
Ok(output.into_iter().flatten().collect())
}
#[cfg(all(feature = "cms-moxcms", not(feature = "cms-lcms2")))]
pub fn apply_icc_transform(
rgb_data: &[u8],
_width: usize,
_height: usize,
icc_profile: &[u8],
) -> Result<Vec<u8>> {
use moxcms::{ColorProfile, Layout, TransformOptions};
let input_profile = ColorProfile::new_from_slice(icc_profile)
.map_err(|e| Error::icc_error(format!("moxcms: {e:?}")))?;
let srgb = ColorProfile::new_srgb();
let transform = input_profile
.create_transform_8bit(Layout::Rgb, &srgb, Layout::Rgb, TransformOptions::default())
.map_err(|e| Error::icc_error(format!("moxcms transform: {e:?}")))?;
let mut output = vec![0u8; rgb_data.len()];
transform
.transform(rgb_data, &mut output)
.map_err(|e| Error::icc_error(format!("moxcms transform execution: {e:?}")))?;
Ok(output)
}
#[cfg(not(any(feature = "cms-lcms2", feature = "cms-moxcms")))]
pub fn apply_icc_transform(
rgb_data: &[u8],
_width: usize,
_height: usize,
_icc_profile: &[u8],
) -> Result<Vec<u8>> {
Ok(rgb_data.to_vec())
}
#[cfg(any(feature = "cms-lcms2", feature = "cms-moxcms"))]
pub fn decode_jpeg_with_icc(jpeg_data: &[u8]) -> Result<(Vec<u8>, usize, usize)> {
let icc_profile = extract_icc_profile(jpeg_data);
use zune_jpeg::zune_core::bytestream::ZCursor;
use zune_jpeg::JpegDecoder;
let cursor = ZCursor::new(jpeg_data);
let mut decoder = JpegDecoder::new(cursor);
let pixels = decoder
.decode()
.map_err(|e| Error::decode_error(format!("jpeg decode: {e:?}")))?;
let (width, height) = decoder
.dimensions()
.ok_or_else(|| Error::decode_error("no image dimensions".to_string()))?;
let output = if let Some(ref profile) = icc_profile {
apply_icc_transform(&pixels, width, height, profile)?
} else {
pixels
};
Ok((output, width, height))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_xyb_profile() {
let xyb_profile = b"...XYB_Per...";
assert!(is_xyb_profile(xyb_profile));
let srgb = b"sRGB IEC61966-2.1";
assert!(!is_xyb_profile(srgb));
}
#[test]
fn test_extract_icc_profile_empty() {
let no_icc = [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10]; assert!(extract_icc_profile(&no_icc).is_none());
}
}