lcms2 1.1.1

ICC color profile handling. Rusty wrapper Little CMS
Documentation
use super::*;

extern crate lcms2_sys as ffi;

use std;
use std::os::raw::c_void;
use std::ffi::CString;
use std::default::Default;

impl Profile {
    pub fn new_icc(data: &[u8]) -> Option<Profile> {
        Self::new_handle(unsafe {
            ffi::cmsOpenProfileFromMem(data.as_ptr() as *const c_void, data.len() as u32)
        })
    }

    pub fn new_srgb() -> Profile {
        Self::new_handle(unsafe { ffi::cmsCreate_sRGBProfile() }).unwrap()
    }

    pub fn new_rgb(white_point: &CIExyY,
                   primaries: &CIExyYTRIPLE,
                   transfer_function: &[&ToneCurve])
                   -> Option<Profile> {
        assert_eq!(3, transfer_function.len());
        Self::new_handle(unsafe {
            ffi::cmsCreateRGBProfile(white_point,
                                     primaries,
                                     [transfer_function[0].handle as *const _,
                                      transfer_function[1].handle as *const _,
                                      transfer_function[2].handle as *const _]
                                         .as_ptr())
        })
    }

    pub fn new_gray(white_point: &CIExyY, curve: &ToneCurve) -> Option<Profile> {
        Self::new_handle(unsafe { ffi::cmsCreateGrayProfile(white_point, curve.handle) })
    }

    pub fn new_xyz() -> Profile {
        Self::new_handle(unsafe { ffi::cmsCreateXYZProfile() }).unwrap()
    }

    pub fn new_null() -> Profile {
        Self::new_handle(unsafe { ffi::cmsCreateNULLProfile() }).unwrap()
    }

    pub fn new_lab2(white_point: &CIExyY) -> Option<Profile> {
        Self::new_handle(unsafe { ffi::cmsCreateLab2Profile(white_point) })
    }

    pub fn new_lab4(white_point: &CIExyY) -> Option<Profile> {
        Self::new_handle(unsafe { ffi::cmsCreateLab4Profile(white_point) })
    }

    pub fn new_device_link<F, T>(transform: &Transform<F, T>, version: f64, flags: u32) -> Option<Profile> {
        Self::new_handle(unsafe { ffi::cmsTransform2DeviceLink(transform.handle, version, flags) })
    }

    fn new_handle(handle: ffi::HPROFILE) -> Option<Profile> {
        if handle.is_null() {
            return None;
        }
        Some(Profile { handle: handle })
    }

    pub fn icc(&self) -> Option<Vec<u8>> {
        unsafe {
            let mut len = 0;
            if ffi::cmsSaveProfileToMem(self.handle, std::ptr::null_mut(), &mut len) == 0 {
                return None;
            }
            let mut data = vec![0u8; len as usize];
            if len == 0 || ffi::cmsSaveProfileToMem(self.handle, data.as_mut_ptr() as *mut c_void, &mut len) == 0 {
                return None;
            }
            Some(data)
        }
    }

    pub fn device_class(&self) -> ProfileClassSignature {
        unsafe { ffi::cmsGetDeviceClass(self.handle) }
    }
    pub fn encoded_icc_version(&self) -> u32 {
        unsafe { ffi::cmsGetEncodedICCversion(self.handle) }
    }
    pub fn header_attributes(&self) -> u64 {
        let mut flags = 0;
        unsafe {
            ffi::cmsGetHeaderAttributes(self.handle, &mut flags);
        }
        flags
    }

    pub fn header_creator(&self) -> u32 {
        unsafe { ffi::cmsGetHeaderCreator(self.handle) }
    }
    pub fn header_flags(&self) -> u32 {
        unsafe { ffi::cmsGetHeaderFlags(self.handle) }
    }
    pub fn header_manufacturer(&self) -> u32 {
        unsafe { ffi::cmsGetHeaderManufacturer(self.handle) }
    }
    pub fn header_model(&self) -> u32 {
        unsafe { ffi::cmsGetHeaderModel(self.handle) }
    }

    pub fn header_rendering_intent(&self) -> u32 {
        unsafe { ffi::cmsGetHeaderRenderingIntent(self.handle) }
    }
    pub fn pcs(&self) -> ColorSpaceSignature {
        unsafe { ffi::cmsGetPCS(self.handle) }
    }

    fn context_id(&self) -> Context {
        unsafe { ffi::cmsGetProfileContextID(self.handle) }
    }

    pub fn info(&self, info: InfoType, languagecode: &str, countrycode: &str) -> Option<String> {
        let languagecode = CString::new(languagecode).unwrap();
        let countrycode = CString::new(countrycode).unwrap();

        let size = unsafe {
            ffi::cmsGetProfileInfo(self.handle,
                                   info,
                                   languagecode.as_ptr(),
                                   countrycode.as_ptr(),
                                   std::ptr::null_mut(),
                                   0)
        };
        if 0 == size {
            return None;
        }

        let wchar_bytes = std::mem::size_of::<ffi::wchar_t>();
        let mut data = vec![0; size as usize / wchar_bytes];
        unsafe {
            let len = data.len() * wchar_bytes;
            let res = ffi::cmsGetProfileInfo(self.handle,
                                             info,
                                             languagecode.as_ptr(),
                                             countrycode.as_ptr(),
                                             (&mut data).as_mut_ptr(),
                                             len as u32);
            if 0 == res {
                return None;
            }
        }
        Some(data.into_iter()
            .take_while(|&c| c > 0)
            .map(|c| std::char::from_u32(c as u32).unwrap())
            .collect())
    }

    pub fn version(&self) -> f64 {
        unsafe { ffi::cmsGetProfileVersion(self.handle) }
    }

    pub fn tag_signatures(&self) -> Vec<TagSignature> {
        unsafe {
            (0..ffi::cmsGetTagCount(self.handle)).map(|n| ffi::cmsGetTagSignature(self.handle, n as u32)).collect()
        }
    }

    pub fn detect_black_point(&self, intent: Intent, flags: u32) -> Option<CIEXYZ> {
        unsafe {
            let mut b = Default::default();
            if ffi::cmsDetectBlackPoint(&mut b, self.handle, intent, flags) != 0 {
                Some(b)
            } else {
                None
            }
        }
    }

    pub fn detect_destination_black_point(&self, intent: Intent, flags: u32) -> Option<CIEXYZ> {
        unsafe {
            let mut b = Default::default();
            if ffi::cmsDetectDestinationBlackPoint(&mut b, self.handle, intent, flags) != 0 {
                Some(b)
            } else {
                None
            }
        }
    }

    pub fn detect_tac(&self) -> f64 {
        unsafe { ffi::cmsDetectTAC(self.handle) }
    }

    pub fn color_space(&self) -> ColorSpaceSignature {
        unsafe { ffi::cmsGetColorSpace(self.handle) }
    }

    pub fn is_clut(&self, intent: Intent, used_direction: u32) -> bool {
        unsafe { ffi::cmsIsCLUT(self.handle, intent, used_direction) != 0 }
    }

    pub fn is_intent_supported(&self, intent: Intent, used_direction: u32) -> bool {
        unsafe { ffi::cmsIsIntentSupported(self.handle, intent, used_direction) != 0 }
    }

    pub fn is_matrix_shaper(&self) -> bool {
        unsafe { ffi::cmsIsMatrixShaper(self.handle) != 0 }
    }

    pub fn has_tag(&self, sig: TagSignature) -> bool {
        unsafe { ffi::cmsIsTag(self.handle, sig) != 0 }
    }

    pub fn read_tag<'a>(&'a self, sig: TagSignature) -> Tag<'a> {
        unsafe { Tag::new(sig, ffi::cmsReadTag(self.handle, sig) as *const u8) }
    }
}

impl Drop for Profile {
    fn drop(&mut self) {
        unsafe {
            ffi::cmsCloseProfile(self.handle);
        }
    }
}


#[test]
fn tags() {
    let prof = Profile::new_srgb();
    assert!(prof.read_tag(TagSignature::SigBToD0Tag).is_none());
    assert_eq!(CIEXYZ::d50().X, match prof.read_tag(TagSignature::SigMediaWhitePointTag) {
        Tag::CIEXYZ(xyz) => xyz.X,
        _ => panic!(),
    });
}

#[test]
fn icc() {
    let prof = Profile::new_xyz();
    assert!(prof.icc().unwrap().len() > 300);
}

#[test]
fn bad_icc() {
    let err = Profile::new_icc(&[1,2,3]);
    assert!(err.is_none());
}