Skip to content

Commit e615132

Browse files
committed
Introduce discard_color_profile DecoderCommand; Color profile transform cache limited to 12 total; bugfixes;
1 parent 933b98e commit e615132

File tree

4 files changed

+114
-72
lines changed

4 files changed

+114
-72
lines changed

c_components/lib/codecs.c

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ struct flow_context_codec_set * flow_context_get_default_codec_set()
3030

3131

3232
void flow_decoder_color_info_init(struct flow_decoder_color_info * color){
33-
memset(&color, 0, sizeof(struct flow_decoder_color_info));
33+
memset(color, 0, sizeof(struct flow_decoder_color_info));
3434
color->gamma = 0.45455;
3535
}
3636

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

52-
static unsigned long hash_close_profile(cmsHPROFILE profile){
53-
uint32_t outputsize;
54-
if (!cmsSaveProfileToMem(profile, 0, &outputsize)) {
55-
cmsCloseProfile(profile);
56-
return 0;
57-
}
58-
unsigned char *buffer = ( unsigned char *) malloc(outputsize);
59-
if (buffer == 0){
60-
cmsCloseProfile(profile);
61-
return 0;
62-
}
63-
if (!cmsSaveProfileToMem(profile, buffer, &outputsize)){
64-
free(buffer);
65-
cmsCloseProfile(profile);
66-
return 0;
67-
}
68-
unsigned long hash = hash_profile_bytes(buffer, outputsize);
69-
free(buffer);
70-
cmsCloseProfile(profile);
71-
return hash;
72-
}
52+
//static unsigned long hash_close_profile(cmsHPROFILE profile){
53+
// uint32_t outputsize;
54+
// if (!cmsSaveProfileToMem(profile, 0, &outputsize)) {
55+
// cmsCloseProfile(profile);
56+
// return 0;
57+
// }
58+
// unsigned char *buffer = ( unsigned char *) malloc(outputsize);
59+
// if (buffer == 0){
60+
// cmsCloseProfile(profile);
61+
// return 0;
62+
// }
63+
// if (!cmsSaveProfileToMem(profile, buffer, &outputsize)){
64+
// free(buffer);
65+
// cmsCloseProfile(profile);
66+
// return 0;
67+
// }
68+
// unsigned long hash = hash_profile_bytes(buffer, outputsize);
69+
// free(buffer);
70+
// cmsCloseProfile(profile);
71+
// return hash;
72+
//}
7373

7474
// We save an allocation in png decoding by ignoring an sRGB profile (we assume sRGB anyway).
7575
// We don't save this allocation yet in jpeg decoding, as the profile is segmented.

c_components/lib/codecs_jpeg.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,13 @@ static bool flow_codecs_jpg_decoder_interpret_metadata(flow_c * c, struct flow_c
405405

406406
if (state->color.source == flow_codec_color_profile_source_null) {
407407
if (read_icc_profile(c, state->cinfo, &icc_buffer, &icc_buffer_len)) {
408-
state->color.profile_buf = icc_buffer;
409-
state->color.buf_length = icc_buffer_len;
410-
state->color.source = flow_codec_color_profile_source_ICCP;
408+
if (!is_srgb(icc_buffer, icc_buffer_len)) {
409+
state->color.profile_buf = icc_buffer;
410+
state->color.buf_length = icc_buffer_len;
411+
state->color.source = flow_codec_color_profile_source_ICCP;
412+
}else{
413+
state->color.source = flow_codec_color_profile_source_sRGB;
414+
}
411415
}
412416
}
413417

imageflow_core/src/codecs/mod.rs

Lines changed: 83 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use ::std;
22
use std::sync::*;
33
use ::for_other_imageflow_crates::preludes::external_without_std::*;
44
use ::ffi;
5-
use ::{Context, CError, Result, JsonResponse, ErrorKind, FlowError};
5+
use ::{Context, CError, Result, JsonResponse, ErrorKind, FlowError, ErrorCategory};
66
use ::ffi::CodecInstance;
77
use ::ffi::BitmapBgra;
88
use ffi::DecoderColorInfo;
@@ -89,7 +89,8 @@ impl CodecInstanceContainer {
8989
}
9090

9191
struct ClassicDecoder{
92-
classic: CodecInstance
92+
classic: CodecInstance,
93+
ignore_color_profile: bool,
9394
}
9495

9596
impl ClassicDecoder {
@@ -107,7 +108,8 @@ impl ClassicDecoder {
107108
direction: IoDirection::In,
108109
io_id: io_id,
109110
io: io.get_io_ptr()
110-
}
111+
},
112+
ignore_color_profile: false
111113
}))
112114
}
113115
}
@@ -190,7 +192,9 @@ impl Decoder for ClassicDecoder{
190192
if result.is_null() {
191193
Err(cerror!(c))
192194
}else {
193-
ColorTransformCache::transform_to_srgb(unsafe{ &mut *result}, &color_info);
195+
if !self.ignore_color_profile {
196+
ColorTransformCache::transform_to_srgb(unsafe { &mut *result }, &color_info)?;
197+
}
194198
ColorTransformCache::dispose_color_info(&mut color_info);
195199

196200

@@ -220,6 +224,10 @@ impl Decoder for ClassicDecoder{
220224
Ok(())
221225
}
222226
}
227+
},
228+
s::DecoderCommand::DiscardColorProfile => {
229+
self.ignore_color_profile = true;
230+
Ok(())
223231
}
224232
}
225233
}
@@ -371,8 +379,8 @@ impl From<lcms2::Error> for FlowError{
371379
}
372380
}
373381
lazy_static!{
374-
static ref PROFILE_TRANSFORMS: ::chashmap::CHashMap<u64, Transform<u8,u8,ThreadContext, DisallowCache>> = ::chashmap::CHashMap::with_capacity(4);
375-
static ref GAMA_TRANSFORMS: ::chashmap::CHashMap<u64, Transform<u8,u8, ThreadContext,DisallowCache>> = ::chashmap::CHashMap::with_capacity(4);
382+
static ref PROFILE_TRANSFORMS: ::chashmap::CHashMap<u64, Transform<u32,u32,ThreadContext, DisallowCache>> = ::chashmap::CHashMap::with_capacity(4);
383+
static ref GAMA_TRANSFORMS: ::chashmap::CHashMap<u64, Transform<u32,u32, ThreadContext,DisallowCache>> = ::chashmap::CHashMap::with_capacity(4);
376384

377385
}
378386

@@ -388,77 +396,105 @@ impl ColorTransformCache{
388396
}
389397
}
390398

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

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

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

402-
let pixel_format = ColorTransformCache::get_pixel_format(frame.fmt);
410+
if color.profile_buffer.is_null() || color.buffer_length < 1{
411+
unreachable!();
412+
}
413+
let srgb = Profile::new_srgb_context(ThreadContext::new()); // Save 1ms by caching - but not sync
414+
415+
let bytes = unsafe { slice::from_raw_parts(color.profile_buffer, color.buffer_length) };
403416

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

406-
let transform = match color.source {
421+
let transform = Transform::new_flags_context(ThreadContext::new(),
422+
&p, pixel_format, &srgb, pixel_format, Intent::Perceptual, Flags::NO_CACHE)?;
423+
424+
Ok(transform)
425+
}
426+
fn hash(color: &ffi::DecoderColorInfo, pixel_format: PixelFormat) -> Option<u64>{
427+
match color.source {
407428
ffi::ColorProfileSource::Null | ffi::ColorProfileSource::sRGB => None,
408429
ffi::ColorProfileSource::GAMA_CHRM => {
409430
let struct_bytes = unsafe {
410431
slice::from_raw_parts(color as *const DecoderColorInfo as *const u8, mem::size_of::<DecoderColorInfo>())
411432
};
412-
let hash = imageflow_helpers::hashing::hash_64(struct_bytes);
413-
if !GAMA_TRANSFORMS.contains_key(&hash) {
414-
let transform = ColorTransformCache::create_gama_transform(color, pixel_format)?;
415-
416-
GAMA_TRANSFORMS.insert_new(hash, transform);
417-
}
418-
Some(GAMA_TRANSFORMS.get(&hash).unwrap())
419-
433+
Some(imageflow_helpers::hashing::hash_64(struct_bytes) ^ pixel_format as u64)
420434
},
421435
ffi::ColorProfileSource::ICCP | ffi::ColorProfileSource::ICCP_GRAY => {
422436
if !color.profile_buffer.is_null() && color.buffer_length > 0 {
423-
424-
//TODO: handle gray transform on rgb expanded images.
425-
//TODO: Add test coverage for grayscale png
426-
427437
let bytes = unsafe { slice::from_raw_parts(color.profile_buffer, color.buffer_length) };
428438

429439
// Skip first 80 bytes when hashing.
430-
let hash = imageflow_helpers::hashing::hash_64(&bytes[80..]);
431-
if !PROFILE_TRANSFORMS.contains_key(&hash) {
432-
let srgb = Profile::new_srgb_context(ThreadContext::new()); // Save 1ms by caching
433-
let pixel_format = match frame.fmt {
434-
ffi::PixelFormat::Bgr32 | ffi::PixelFormat::Bgra32 => PixelFormat::BGRA_8,
435-
ffi::PixelFormat::Bgr24 => PixelFormat::BGR_8,
436-
ffi::PixelFormat::Gray8 => PixelFormat::GRAY_8
437-
};
438-
let p = Profile::new_icc_context(ThreadContext::new(),bytes)?;
439-
440-
let transform = Transform::new_flags_context(ThreadContext::new(),
441-
&p, pixel_format, &srgb, pixel_format, Intent::Perceptual, flags)?;
442-
PROFILE_TRANSFORMS.insert_new(hash, transform);
443-
}
444-
Some(PROFILE_TRANSFORMS.get(&hash).unwrap())
440+
Some(imageflow_helpers::hashing::hash_64(&bytes[80..]) ^ pixel_format as u64)
445441
} else {
446-
unreachable!();
442+
unreachable!("Profile source should never be set to ICCP without a profile buffer");
447443
}
448444
}
449-
};
445+
}
446+
}
447+
448+
fn apply_transform(frame: &mut BitmapBgra, transform: &Transform<u32,u32, ThreadContext,DisallowCache>) {
449+
for row in 0..frame.h {
450+
let mut pixels = unsafe{ slice::from_raw_parts_mut(frame.pixels.offset((row * frame.stride) as isize) as *mut u32, frame.w as usize) };
451+
transform.transform_in_place(pixels)
452+
}
453+
}
450454

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

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

462+
match color.source {
463+
ffi::ColorProfileSource::Null | ffi::ColorProfileSource::sRGB => Ok(()),
464+
ffi::ColorProfileSource::GAMA_CHRM => {
453465

454-
if let Some(p) = transform{
455-
for row in 0..frame.h {
456-
let mut bytes = unsafe{ slice::from_raw_parts_mut(frame.pixels.offset((row * frame.stride) as isize), frame.w as usize * frame.fmt.bytes()) };
457-
(*p).transform_in_place(bytes)
466+
// Cache up to 4 GAMA x PixelFormat transforms
467+
if GAMA_TRANSFORMS.len() > 3{
468+
let transform = ColorTransformCache::create_gama_transform(color, pixel_format)?;
469+
ColorTransformCache::apply_transform(frame, &transform);
470+
Ok(())
471+
}else{
472+
let hash = ColorTransformCache::hash(color, pixel_format).unwrap();
473+
if !GAMA_TRANSFORMS.contains_key(&hash) {
474+
let transform = ColorTransformCache::create_gama_transform(color, pixel_format)?;
475+
GAMA_TRANSFORMS.insert_new(hash, transform);
476+
}
477+
ColorTransformCache::apply_transform(frame, &*GAMA_TRANSFORMS.get(&hash).unwrap());
478+
Ok(())
479+
}
480+
},
481+
ffi::ColorProfileSource::ICCP | ffi::ColorProfileSource::ICCP_GRAY => {
482+
// Cache up to 9 ICC profile x PixelFormat transforms
483+
if PROFILE_TRANSFORMS.len() > 8{
484+
let transform = ColorTransformCache::create_profile_transform(color, pixel_format)?;
485+
ColorTransformCache::apply_transform(frame, &transform);
486+
Ok(())
487+
}else{
488+
let hash = ColorTransformCache::hash(color, pixel_format).unwrap();
489+
if !PROFILE_TRANSFORMS.contains_key(&hash) {
490+
let transform = ColorTransformCache::create_profile_transform(color, pixel_format)?;
491+
PROFILE_TRANSFORMS.insert_new(hash, transform);
492+
}
493+
ColorTransformCache::apply_transform(frame, &*PROFILE_TRANSFORMS.get(&hash).unwrap());
494+
Ok(())
495+
}
458496
}
459497
}
460-
Ok(())
461-
462498
}
463499

464500
pub fn dispose_color_info(color: &mut ffi::DecoderColorInfo){

imageflow_types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,8 @@ pub struct JpegIDCTDownscaleHints {
877877
pub enum DecoderCommand {
878878
#[serde(rename="jpeg_downscale_hints")]
879879
JpegDownscaleHints(JpegIDCTDownscaleHints),
880+
#[serde(rename="discard_color_profile")]
881+
DiscardColorProfile
880882
}
881883
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
882884
pub struct TellDecoder001 {

0 commit comments

Comments
 (0)