Skip to content

Commit

Permalink
Introduce discard_color_profile DecoderCommand; Color profile transfo…
Browse files Browse the repository at this point in the history
…rm cache limited to 12 total; bugfixes;
  • Loading branch information
lilith committed Sep 15, 2017
1 parent 933b98e commit e615132
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 72 deletions.
44 changes: 22 additions & 22 deletions c_components/lib/codecs.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct flow_context_codec_set * flow_context_get_default_codec_set()


void flow_decoder_color_info_init(struct flow_decoder_color_info * color){
memset(&color, 0, sizeof(struct flow_decoder_color_info));
memset(color, 0, sizeof(struct flow_decoder_color_info));
color->gamma = 0.45455;
}

Expand All @@ -49,27 +49,27 @@ static unsigned long hash_profile_bytes(unsigned char * profile, size_t profile_
return djb2_buffer(profile + sizeof(cmsICCHeader), profile_len - sizeof(cmsICCHeader));
}

static unsigned long hash_close_profile(cmsHPROFILE profile){
uint32_t outputsize;
if (!cmsSaveProfileToMem(profile, 0, &outputsize)) {
cmsCloseProfile(profile);
return 0;
}
unsigned char *buffer = ( unsigned char *) malloc(outputsize);
if (buffer == 0){
cmsCloseProfile(profile);
return 0;
}
if (!cmsSaveProfileToMem(profile, buffer, &outputsize)){
free(buffer);
cmsCloseProfile(profile);
return 0;
}
unsigned long hash = hash_profile_bytes(buffer, outputsize);
free(buffer);
cmsCloseProfile(profile);
return hash;
}
//static unsigned long hash_close_profile(cmsHPROFILE profile){
// uint32_t outputsize;
// if (!cmsSaveProfileToMem(profile, 0, &outputsize)) {
// cmsCloseProfile(profile);
// return 0;
// }
// unsigned char *buffer = ( unsigned char *) malloc(outputsize);
// if (buffer == 0){
// cmsCloseProfile(profile);
// return 0;
// }
// if (!cmsSaveProfileToMem(profile, buffer, &outputsize)){
// free(buffer);
// cmsCloseProfile(profile);
// return 0;
// }
// unsigned long hash = hash_profile_bytes(buffer, outputsize);
// free(buffer);
// cmsCloseProfile(profile);
// return hash;
//}

// We save an allocation in png decoding by ignoring an sRGB profile (we assume sRGB anyway).
// We don't save this allocation yet in jpeg decoding, as the profile is segmented.
Expand Down
10 changes: 7 additions & 3 deletions c_components/lib/codecs_jpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,13 @@ static bool flow_codecs_jpg_decoder_interpret_metadata(flow_c * c, struct flow_c

if (state->color.source == flow_codec_color_profile_source_null) {
if (read_icc_profile(c, state->cinfo, &icc_buffer, &icc_buffer_len)) {
state->color.profile_buf = icc_buffer;
state->color.buf_length = icc_buffer_len;
state->color.source = flow_codec_color_profile_source_ICCP;
if (!is_srgb(icc_buffer, icc_buffer_len)) {
state->color.profile_buf = icc_buffer;
state->color.buf_length = icc_buffer_len;
state->color.source = flow_codec_color_profile_source_ICCP;
}else{
state->color.source = flow_codec_color_profile_source_sRGB;
}
}
}

Expand Down
130 changes: 83 additions & 47 deletions imageflow_core/src/codecs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ::std;
use std::sync::*;
use ::for_other_imageflow_crates::preludes::external_without_std::*;
use ::ffi;
use ::{Context, CError, Result, JsonResponse, ErrorKind, FlowError};
use ::{Context, CError, Result, JsonResponse, ErrorKind, FlowError, ErrorCategory};
use ::ffi::CodecInstance;
use ::ffi::BitmapBgra;
use ffi::DecoderColorInfo;
Expand Down Expand Up @@ -89,7 +89,8 @@ impl CodecInstanceContainer {
}

struct ClassicDecoder{
classic: CodecInstance
classic: CodecInstance,
ignore_color_profile: bool,
}

impl ClassicDecoder {
Expand All @@ -107,7 +108,8 @@ impl ClassicDecoder {
direction: IoDirection::In,
io_id: io_id,
io: io.get_io_ptr()
}
},
ignore_color_profile: false
}))
}
}
Expand Down Expand Up @@ -190,7 +192,9 @@ impl Decoder for ClassicDecoder{
if result.is_null() {
Err(cerror!(c))
}else {
ColorTransformCache::transform_to_srgb(unsafe{ &mut *result}, &color_info);
if !self.ignore_color_profile {
ColorTransformCache::transform_to_srgb(unsafe { &mut *result }, &color_info)?;
}
ColorTransformCache::dispose_color_info(&mut color_info);


Expand Down Expand Up @@ -220,6 +224,10 @@ impl Decoder for ClassicDecoder{
Ok(())
}
}
},
s::DecoderCommand::DiscardColorProfile => {
self.ignore_color_profile = true;
Ok(())
}
}
}
Expand Down Expand Up @@ -371,8 +379,8 @@ impl From<lcms2::Error> for FlowError{
}
}
lazy_static!{
static ref PROFILE_TRANSFORMS: ::chashmap::CHashMap<u64, Transform<u8,u8,ThreadContext, DisallowCache>> = ::chashmap::CHashMap::with_capacity(4);
static ref GAMA_TRANSFORMS: ::chashmap::CHashMap<u64, Transform<u8,u8, ThreadContext,DisallowCache>> = ::chashmap::CHashMap::with_capacity(4);
static ref PROFILE_TRANSFORMS: ::chashmap::CHashMap<u64, Transform<u32,u32,ThreadContext, DisallowCache>> = ::chashmap::CHashMap::with_capacity(4);
static ref GAMA_TRANSFORMS: ::chashmap::CHashMap<u64, Transform<u32,u32, ThreadContext,DisallowCache>> = ::chashmap::CHashMap::with_capacity(4);

}

Expand All @@ -388,77 +396,105 @@ impl ColorTransformCache{
}
}

fn create_gama_transform(color: &ffi::DecoderColorInfo, pixel_format: PixelFormat) -> Result<Transform<u8,u8, ThreadContext,DisallowCache>>{
fn create_gama_transform(color: &ffi::DecoderColorInfo, pixel_format: PixelFormat) -> Result<Transform<u32,u32, ThreadContext,DisallowCache>>{
let srgb = Profile::new_srgb_context(ThreadContext::new()); // Save 1ms by caching - but not sync

let gama = ToneCurve::new(1f64 / color.gamma);
let p = Profile::new_rgb_context(ThreadContext::new(),&color.white_point, &color.primaries, &[&gama, &gama, &gama] )?;

let transform = Transform::new_flags_context(ThreadContext::new(),&p, pixel_format, &srgb, pixel_format, Intent::Perceptual, flags)?;
let transform = Transform::new_flags_context(ThreadContext::new(),&p, pixel_format, &srgb, pixel_format, Intent::Perceptual, Flags::NO_CACHE)?;
Ok(transform)
}
pub fn transform_to_srgb(frame: &mut BitmapBgra, color: &ffi::DecoderColorInfo) -> Result<()>{
fn create_profile_transform(color: &ffi::DecoderColorInfo, pixel_format: PixelFormat) -> Result<Transform<u32,u32, ThreadContext,DisallowCache>> {

let pixel_format = ColorTransformCache::get_pixel_format(frame.fmt);
if color.profile_buffer.is_null() || color.buffer_length < 1{
unreachable!();
}
let srgb = Profile::new_srgb_context(ThreadContext::new()); // Save 1ms by caching - but not sync

let bytes = unsafe { slice::from_raw_parts(color.profile_buffer, color.buffer_length) };

let flags: Flags<DisallowCache> = Flags::NO_CACHE;
let p = Profile::new_icc_context(ThreadContext::new(), bytes)?;
//TODO: handle gray transform on rgb expanded images.
//TODO: Add test coverage for grayscale png

let transform = match color.source {
let transform = Transform::new_flags_context(ThreadContext::new(),
&p, pixel_format, &srgb, pixel_format, Intent::Perceptual, Flags::NO_CACHE)?;

Ok(transform)
}
fn hash(color: &ffi::DecoderColorInfo, pixel_format: PixelFormat) -> Option<u64>{
match color.source {
ffi::ColorProfileSource::Null | ffi::ColorProfileSource::sRGB => None,
ffi::ColorProfileSource::GAMA_CHRM => {
let struct_bytes = unsafe {
slice::from_raw_parts(color as *const DecoderColorInfo as *const u8, mem::size_of::<DecoderColorInfo>())
};
let hash = imageflow_helpers::hashing::hash_64(struct_bytes);
if !GAMA_TRANSFORMS.contains_key(&hash) {
let transform = ColorTransformCache::create_gama_transform(color, pixel_format)?;

GAMA_TRANSFORMS.insert_new(hash, transform);
}
Some(GAMA_TRANSFORMS.get(&hash).unwrap())

Some(imageflow_helpers::hashing::hash_64(struct_bytes) ^ pixel_format as u64)
},
ffi::ColorProfileSource::ICCP | ffi::ColorProfileSource::ICCP_GRAY => {
if !color.profile_buffer.is_null() && color.buffer_length > 0 {

//TODO: handle gray transform on rgb expanded images.
//TODO: Add test coverage for grayscale png

let bytes = unsafe { slice::from_raw_parts(color.profile_buffer, color.buffer_length) };

// Skip first 80 bytes when hashing.
let hash = imageflow_helpers::hashing::hash_64(&bytes[80..]);
if !PROFILE_TRANSFORMS.contains_key(&hash) {
let srgb = Profile::new_srgb_context(ThreadContext::new()); // Save 1ms by caching
let pixel_format = match frame.fmt {
ffi::PixelFormat::Bgr32 | ffi::PixelFormat::Bgra32 => PixelFormat::BGRA_8,
ffi::PixelFormat::Bgr24 => PixelFormat::BGR_8,
ffi::PixelFormat::Gray8 => PixelFormat::GRAY_8
};
let p = Profile::new_icc_context(ThreadContext::new(),bytes)?;

let transform = Transform::new_flags_context(ThreadContext::new(),
&p, pixel_format, &srgb, pixel_format, Intent::Perceptual, flags)?;
PROFILE_TRANSFORMS.insert_new(hash, transform);
}
Some(PROFILE_TRANSFORMS.get(&hash).unwrap())
Some(imageflow_helpers::hashing::hash_64(&bytes[80..]) ^ pixel_format as u64)
} else {
unreachable!();
unreachable!("Profile source should never be set to ICCP without a profile buffer");
}
}
};
}
}

fn apply_transform(frame: &mut BitmapBgra, transform: &Transform<u32,u32, ThreadContext,DisallowCache>) {
for row in 0..frame.h {
let mut pixels = unsafe{ slice::from_raw_parts_mut(frame.pixels.offset((row * frame.stride) as isize) as *mut u32, frame.w as usize) };
transform.transform_in_place(pixels)
}
}

pub fn transform_to_srgb(frame: &mut BitmapBgra, color: &ffi::DecoderColorInfo) -> Result<()>{

if frame.fmt.bytes() != 4{
return Err(nerror!(ErrorKind::Category(ErrorCategory::InternalError), "Color profile application is only supported for Bgr32 and Bgra32 canvases"));
}
let pixel_format = ColorTransformCache::get_pixel_format(frame.fmt);

match color.source {
ffi::ColorProfileSource::Null | ffi::ColorProfileSource::sRGB => Ok(()),
ffi::ColorProfileSource::GAMA_CHRM => {

if let Some(p) = transform{
for row in 0..frame.h {
let mut bytes = unsafe{ slice::from_raw_parts_mut(frame.pixels.offset((row * frame.stride) as isize), frame.w as usize * frame.fmt.bytes()) };
(*p).transform_in_place(bytes)
// Cache up to 4 GAMA x PixelFormat transforms
if GAMA_TRANSFORMS.len() > 3{
let transform = ColorTransformCache::create_gama_transform(color, pixel_format)?;
ColorTransformCache::apply_transform(frame, &transform);
Ok(())
}else{
let hash = ColorTransformCache::hash(color, pixel_format).unwrap();
if !GAMA_TRANSFORMS.contains_key(&hash) {
let transform = ColorTransformCache::create_gama_transform(color, pixel_format)?;
GAMA_TRANSFORMS.insert_new(hash, transform);
}
ColorTransformCache::apply_transform(frame, &*GAMA_TRANSFORMS.get(&hash).unwrap());
Ok(())
}
},
ffi::ColorProfileSource::ICCP | ffi::ColorProfileSource::ICCP_GRAY => {
// Cache up to 9 ICC profile x PixelFormat transforms
if PROFILE_TRANSFORMS.len() > 8{
let transform = ColorTransformCache::create_profile_transform(color, pixel_format)?;
ColorTransformCache::apply_transform(frame, &transform);
Ok(())
}else{
let hash = ColorTransformCache::hash(color, pixel_format).unwrap();
if !PROFILE_TRANSFORMS.contains_key(&hash) {
let transform = ColorTransformCache::create_profile_transform(color, pixel_format)?;
PROFILE_TRANSFORMS.insert_new(hash, transform);
}
ColorTransformCache::apply_transform(frame, &*PROFILE_TRANSFORMS.get(&hash).unwrap());
Ok(())
}
}
}
Ok(())

}

pub fn dispose_color_info(color: &mut ffi::DecoderColorInfo){
Expand Down
2 changes: 2 additions & 0 deletions imageflow_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,8 @@ pub struct JpegIDCTDownscaleHints {
pub enum DecoderCommand {
#[serde(rename="jpeg_downscale_hints")]
JpegDownscaleHints(JpegIDCTDownscaleHints),
#[serde(rename="discard_color_profile")]
DiscardColorProfile
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct TellDecoder001 {
Expand Down

0 comments on commit e615132

Please sign in to comment.