Skip to content

Commit

Permalink
Implement writing ICC profiles for JPEG and PNG images.
Browse files Browse the repository at this point in the history
  • Loading branch information
alula authored and kornelski committed Dec 16, 2024
1 parent 5414013 commit 9128bec
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 1 deletion.
50 changes: 50 additions & 0 deletions src/codecs/jpeg/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ static SOS: u8 = 0xDA;
static DQT: u8 = 0xDB;
// Application segments start and end
static APP0: u8 = 0xE0;
static APP2: u8 = 0xE2;

// section K.1
// table K.1
Expand Down Expand Up @@ -346,6 +347,8 @@ pub struct JpegEncoder<W> {
chroma_actable: Cow<'static, [(u8, u16); 256]>,

pixel_density: PixelDensity,

icc_profile: Vec<u8>,
}

impl<W: Write> JpegEncoder<W> {
Expand Down Expand Up @@ -415,6 +418,8 @@ impl<W: Write> JpegEncoder<W> {
chroma_actable: Cow::Borrowed(&STD_CHROMA_AC_HUFF_LUT),

pixel_density: PixelDensity::default(),

icc_profile: Vec::new(),
}
}

Expand Down Expand Up @@ -494,6 +499,9 @@ impl<W: Write> JpegEncoder<W> {
build_jfif_header(&mut buf, self.pixel_density);
self.writer.write_segment(APP0, &buf)?;

// Write ICC profile chunks if present
self.write_icc_profile_chunks()?;

build_frame_header(
&mut buf,
8,
Expand Down Expand Up @@ -648,6 +656,43 @@ impl<W: Write> JpegEncoder<W> {

Ok(())
}

fn write_icc_profile_chunks(&mut self) -> io::Result<()> {
if self.icc_profile.is_empty() {
return Ok(());
}

const MAX_CHUNK_SIZE: usize = 65533 - 14;
const MAX_CHUNK_COUNT: usize = 255;
const MAX_ICC_PROFILE_SIZE: usize = MAX_CHUNK_SIZE * MAX_CHUNK_COUNT;

if self.icc_profile.len() > MAX_ICC_PROFILE_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"ICC profile too large",
));
}

let chunk_iter = self.icc_profile.chunks(MAX_CHUNK_SIZE);
let num_chunks = chunk_iter.len() as u8;
let mut segment = Vec::new();

for (i, chunk) in chunk_iter.enumerate() {
let chunk_number = (i + 1) as u8;
let length = 14 + chunk.len();

segment.clear();
segment.reserve(length);
segment.extend_from_slice(b"ICC_PROFILE\0");
segment.push(chunk_number);
segment.push(num_chunks);
segment.extend_from_slice(chunk);

self.writer.write_segment(APP2, &segment)?;
}

Ok(())
}
}

impl<W: Write> ImageEncoder for JpegEncoder<W> {
Expand All @@ -661,6 +706,11 @@ impl<W: Write> ImageEncoder for JpegEncoder<W> {
) -> ImageResult<()> {
self.encode(buf, width, height, color_type)
}

fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
self.icc_profile = icc_profile;
Ok(())
}
}

fn build_jfif_header(m: &mut Vec<u8>, density: PixelDensity) {
Expand Down
19 changes: 18 additions & 1 deletion src/codecs/png.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! * <http://www.w3.org/TR/PNG/> - The PNG Specification
//!
use std::borrow::Cow;
use std::fmt;
use std::io::{BufRead, Seek, Write};

Expand Down Expand Up @@ -482,6 +483,7 @@ pub struct PngEncoder<W: Write> {
w: W,
compression: CompressionType,
filter: FilterType,
icc_profile: Vec<u8>,
}

/// Compression level of a PNG encoder. The default setting is `Fast`.
Expand Down Expand Up @@ -535,6 +537,7 @@ impl<W: Write> PngEncoder<W> {
w,
compression: CompressionType::default(),
filter: FilterType::default(),
icc_profile: Vec::new(),
}
}

Expand All @@ -559,6 +562,7 @@ impl<W: Write> PngEncoder<W> {
w,
compression,
filter,
icc_profile: Vec::new(),
}
}

Expand Down Expand Up @@ -604,7 +608,15 @@ impl<W: Write> PngEncoder<W> {
FilterType::Adaptive => (png::FilterType::Sub, png::AdaptiveFilterType::Adaptive),
};

let mut encoder = png::Encoder::new(self.w, width, height);
let mut info = png::Info::with_size(width, height);

if !self.icc_profile.is_empty() {
info.icc_profile = Some(Cow::Borrowed(&self.icc_profile));
}

let mut encoder =
png::Encoder::with_info(self.w, info).map_err(|e| ImageError::IoError(e.into()))?;

encoder.set_color(ct);
encoder.set_depth(bits);
encoder.set_compression(comp);
Expand Down Expand Up @@ -669,6 +681,11 @@ impl<W: Write> ImageEncoder for PngEncoder<W> {
))),
}
}

fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
self.icc_profile = icc_profile;
Ok(())
}
}

impl ImageError {
Expand Down

0 comments on commit 9128bec

Please sign in to comment.