1 use super::*;
2 use crate::context::Context;
3 use std::fmt;
4 use std::path::Path;
5 use std::ptr;
6 use std::io;
7 use std::io::Read;
8 use std::fs::File;
9 use std::os::raw::c_void;
10 use std::default::Default;
11 use foreign_types::ForeignTypeRef;
12 
13 /// An ICC color profile
14 pub struct Profile<Context = GlobalContext> {
15     pub(crate) handle: ffi::HPROFILE,
16     _context_ref: PhantomData<Context>,
17 }
18 
19 unsafe impl<'a, C: Send> Send for Profile<C> {}
20 
21 /// These are the basic functions on opening profiles.
22 /// For simpler operation, you must open two profiles using `new_file`, and then create a transform with these open profiles with `Transform`.
23 /// Using this transform you can color correct your bitmaps.
24 impl Profile<GlobalContext> {
25     /// Parse ICC profile from the in-memory array
26     #[inline]
new_icc(data: &[u8]) -> LCMSResult<Self>27     pub fn new_icc(data: &[u8]) -> LCMSResult<Self> {
28         Self::new_icc_context(GlobalContext::new(), data)
29     }
30 
31     /// Load ICC profile file from disk
32     #[inline]
new_file<P: AsRef<Path>>(path: P) -> io::Result<Self>33     pub fn new_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
34         Self::new_file_context(GlobalContext::new(), path)
35     }
36 
37     /// Create an ICC virtual profile for sRGB space. sRGB is a standard RGB color space created cooperatively by HP and Microsoft in 1996 for use on monitors, printers, and the Internet.
38     #[inline]
new_srgb() -> Self39     pub fn new_srgb() -> Self {
40         Self::new_srgb_context(GlobalContext::new())
41     }
42 
43     /// This function creates a display RGB profile based on White point, primaries and transfer functions. It populates following tags; this conform a standard RGB Display Profile, and then I add (As per addendum II) chromaticity tag.
44     ///
45     ///   1. ProfileDescriptionTag
46     ///   2. MediaWhitePointTag
47     ///   3. RedColorantTag
48     ///   4. GreenColorantTag
49     ///   5. BlueColorantTag
50     ///   6. RedTRCTag
51     ///   7. GreenTRCTag
52     ///   8. BlueTRCTag
53     ///   9. Chromatic adaptation Tag
54     ///   10. ChromaticityTag
55     #[inline]
new_rgb(white_point: &CIExyY, primaries: &CIExyYTRIPLE, transfer_function: &[&ToneCurve]) -> LCMSResult<Self>56     pub fn new_rgb(white_point: &CIExyY,
57                    primaries: &CIExyYTRIPLE,
58                    transfer_function: &[&ToneCurve])
59                    -> LCMSResult<Self> {
60         Self::new_rgb_context(GlobalContext::new(), white_point, primaries, transfer_function)
61     }
62 
63     /// This function creates a gray profile based on White point and transfer function. It populates following tags; this conform a standard gray display profile.
64     ///
65     ///   1. ProfileDescriptionTag
66     ///   2. MediaWhitePointTag
67     ///   3. GrayTRCTag
68     #[inline]
new_gray(white_point: &CIExyY, curve: &ToneCurve) -> LCMSResult<Self>69     pub fn new_gray(white_point: &CIExyY, curve: &ToneCurve) -> LCMSResult<Self> {
70         Self::new_gray_context(GlobalContext::new(), white_point, curve)
71     }
72 
73     /// Creates a XYZ  XYZ identity, marking it as v4 ICC profile.  WhitePoint used in Absolute colorimetric intent  is D50.
74     #[inline]
new_xyz() -> Self75     pub fn new_xyz() -> Self {
76         Self::new_handle(unsafe { ffi::cmsCreateXYZProfile() }).unwrap()
77     }
78 
79     /// Creates a fake NULL profile. This profile return 1 channel as always 0. Is useful only for gamut checking tricks.
80     #[inline]
new_null() -> Self81     pub fn new_null() -> Self {
82         Self::new_handle(unsafe { ffi::cmsCreateNULLProfile() }).unwrap()
83     }
84 
85     /// Creates an empty profile object, ready to be populated by the programmer.
86     ///
87     /// WARNING: The obtained profile without adding any information is not directly useable.
88     #[inline]
new_placeholder() -> Self89     pub fn new_placeholder() -> Self {
90         Self::new_handle(unsafe { ffi::cmsCreateProfilePlaceholder(ptr::null_mut()) }).unwrap()
91     }
92 
93     /// This is a devicelink operating in CMYK for ink-limiting. Currently only cmsSigCmykData is supported.
94     /// Limit: Amount of ink limiting in % (0..400%)
ink_limiting(color_space: ColorSpaceSignature, limit: f64) -> LCMSResult<Self>95     pub fn ink_limiting(color_space: ColorSpaceSignature, limit: f64) -> LCMSResult<Self> {
96         Self::new_handle(unsafe { ffi::cmsCreateInkLimitingDeviceLink(color_space, limit) })
97     }
98 
99     /// Generates a device-link profile from a given color transform. This profile can then be used by any other function accepting profile handle.
100     /// Depending on the specified version number, the implementation of the devicelink may vary. Accepted versions are in range 1.0…4.3
101     #[inline]
new_device_link<F, T>(transform: &Transform<F, T>, version: f64, flags: Flags) -> LCMSResult<Self>102     pub fn new_device_link<F, T>(transform: &Transform<F, T>, version: f64, flags: Flags) -> LCMSResult<Self> {
103         Self::new_handle(unsafe { ffi::cmsTransform2DeviceLink(transform.handle, version, flags.bits()) })
104     }
105 }
106 
107 impl<Ctx: Context> Profile<Ctx> {
108     /// Create ICC file in memory buffer
icc(&self) -> LCMSResult<Vec<u8>>109     pub fn icc(&self) -> LCMSResult<Vec<u8>> {
110         unsafe {
111             let mut len = 0;
112             if ffi::cmsSaveProfileToMem(self.handle, std::ptr::null_mut(), &mut len) == 0 {
113                 return Err(Error::ObjectCreationError);
114             }
115             let mut data = vec![0u8; len as usize];
116             if len == 0 || ffi::cmsSaveProfileToMem(self.handle, data.as_mut_ptr() as *mut c_void, &mut len) == 0 {
117                 return Err(Error::ObjectCreationError);
118             }
119             Ok(data)
120         }
121     }
122 
123     /// Gets the device class signature from profile header.
124     #[inline]
device_class(&self) -> ProfileClassSignature125     pub fn device_class(&self) -> ProfileClassSignature {
126         unsafe { ffi::cmsGetDeviceClass(self.handle) }
127     }
128 
129     /// Sets the device class signature in profile header.
130     #[inline]
set_device_class(&mut self, cls: ProfileClassSignature)131     pub fn set_device_class(&mut self, cls: ProfileClassSignature) {
132         unsafe { ffi::cmsSetDeviceClass(self.handle, cls) }
133     }
134 
135     /// Returns the profile ICC version in the same format as it is stored in the header.
136     #[inline]
encoded_icc_version(&self) -> u32137     pub fn encoded_icc_version(&self) -> u32 {
138         unsafe { ffi::cmsGetEncodedICCversion(self.handle) }
139     }
140 
141     #[inline]
set_encoded_icc_version(&self, v: u32)142     pub fn set_encoded_icc_version(&self, v: u32) {
143         unsafe { ffi::cmsSetEncodedICCversion(self.handle, v) }
144     }
145 
146     /// Gets the attribute flags. Currently defined values correspond to the low 4 bytes of the 8 byte attribute quantity.
147     ///
148     ///  * `Reflective`
149     ///  * `Transparency`
150     ///  * `Glossy`
151     ///  * `Matte`
152 
153     #[inline]
header_attributes(&self) -> u64154     pub fn header_attributes(&self) -> u64 {
155         let mut flags = 0;
156         unsafe {
157             ffi::cmsGetHeaderAttributes(self.handle, &mut flags);
158         }
159         flags
160     }
161 
162     /// Sets the attribute flags in the profile header.
163     #[inline]
set_header_attributes(&mut self, flags: u64)164     pub fn set_header_attributes(&mut self, flags: u64) {
165         unsafe {
166             ffi::cmsSetHeaderAttributes(self.handle, flags);
167         }
168     }
169 
170     #[inline]
header_creator(&self) -> u32171     pub fn header_creator(&self) -> u32 {
172         unsafe { ffi::cmsGetHeaderCreator(self.handle) }
173     }
174 
175     /// Get header flags of given ICC profile object.
176     ///
177     /// The profile flags field does contain flags to indicate various hints for the CMM such as distributed processing and caching options.
178     /// The least-significant 16 bits are reserved for the ICC. Flags in bit positions 0 and 1 shall be used as indicated in Table 7 of LCMS PDF.
179     #[inline]
header_flags(&self) -> u32180     pub fn header_flags(&self) -> u32 {
181         unsafe { ffi::cmsGetHeaderFlags(self.handle) }
182     }
183 
184     /// Sets header flags of given ICC profile object. Valid flags are defined in Table 7 of LCMS PDF.
185     #[inline]
set_header_flags(&mut self, flags: u32)186     pub fn set_header_flags(&mut self, flags: u32) {
187         unsafe { ffi::cmsSetHeaderFlags(self.handle, flags); }
188     }
189 
190     /// Returns the manufacturer signature as described in the header.
191     ///
192     /// This funcionality is widely superseded by the manufaturer tag. Of use only in elder profiles.
193     #[inline]
header_manufacturer(&self) -> u32194     pub fn header_manufacturer(&self) -> u32 {
195         unsafe { ffi::cmsGetHeaderManufacturer(self.handle) }
196     }
197 
198     /// Sets the manufacturer signature in the header.
199     ///
200     /// This funcionality is widely superseded by the manufaturer tag. Of use only in elder profiles.
201     #[deprecated(note = "This funcionality is widely superseded by the manufaturer tag")]
202     #[inline]
set_header_manufacturer(&mut self, m: u32)203     pub fn set_header_manufacturer(&mut self, m: u32) {
204         unsafe { ffi::cmsSetHeaderManufacturer(self.handle, m) }
205     }
206 
207     /// Returns the model signature as described in the header.
208     ///
209     /// This funcionality is widely superseded by the model tag. Of use only in elder profiles.
210     #[inline]
header_model(&self) -> u32211     pub fn header_model(&self) -> u32 {
212         unsafe { ffi::cmsGetHeaderModel(self.handle) }
213     }
214 
215     /// Sets the model signature in the profile header.
216     ///
217     /// This funcionality is widely superseded by the model tag. Of use only in elder profiles.
218     #[deprecated(note = "This funcionality is widely superseded by the model tag")]
219     #[inline]
set_header_model(&mut self, model: u32)220     pub fn set_header_model(&mut self, model: u32) {
221         unsafe {
222             ffi::cmsSetHeaderModel(self.handle, model);
223         }
224     }
225 
226     /// Gets the profile header rendering intent.
227     ///
228     /// From the ICC spec: “The rendering intent field shall specify the rendering intent which should be used
229     /// (or, in the case of a Devicelink profile, was used) when this profile is (was) combined with another profile.
230     /// In a sequence of more than two profiles, it applies to the combination of this profile and the next profile in the sequence and not to the entire sequence.
231     /// Typically, the user or application will set the rendering intent dynamically at runtime or embedding time.
232     /// Therefore, this flag may not have any meaning until the profile is used in some context, e.g. in a Devicelink or an embedded source profile.”
233     #[inline]
header_rendering_intent(&self) -> Intent234     pub fn header_rendering_intent(&self) -> Intent {
235         unsafe { ffi::cmsGetHeaderRenderingIntent(self.handle) }
236     }
237 
238     #[inline]
set_header_rendering_intent(&mut self, intent: Intent)239     pub fn set_header_rendering_intent(&mut self, intent: Intent) {
240         unsafe { ffi::cmsSetHeaderRenderingIntent(self.handle, intent) }
241     }
242 
243     /// Gets the profile connection space used by the given profile, using the ICC convention.
244     #[inline]
pcs(&self) -> ColorSpaceSignature245     pub fn pcs(&self) -> ColorSpaceSignature {
246         unsafe { ffi::cmsGetPCS(self.handle) }
247     }
248 
249     /// Sets the profile connection space signature in profile header, using ICC convention.
250     #[inline]
set_pcs(&mut self, pcs: ColorSpaceSignature)251     pub fn set_pcs(&mut self, pcs: ColorSpaceSignature) {
252         unsafe { ffi::cmsSetPCS(self.handle, pcs) }
253     }
254 
info(&self, info: InfoType, locale: Locale) -> Option<String>255     pub fn info(&self, info: InfoType, locale: Locale) -> Option<String> {
256         let size = unsafe {
257             ffi::cmsGetProfileInfo(self.handle,
258                                    info,
259                                    locale.language_ptr(),
260                                    locale.country_ptr(),
261                                    std::ptr::null_mut(),
262                                    0)
263         };
264         if 0 == size {
265             return None;
266         }
267 
268         let wchar_bytes = std::mem::size_of::<ffi::wchar_t>();
269         let mut data = vec![0; size as usize / wchar_bytes];
270         unsafe {
271             let len = data.len() * wchar_bytes;
272             let res = ffi::cmsGetProfileInfo(self.handle,
273                                              info,
274                                              locale.language_ptr(),
275                                              locale.country_ptr(),
276                                              (&mut data).as_mut_ptr(),
277                                              len as u32);
278             if 0 == res {
279                 return None;
280             }
281         }
282         Some(data.into_iter()
283             .take_while(|&c| c > 0)
284             .map(|c| std::char::from_u32(c as u32).unwrap())
285             .collect())
286     }
287 
288     /// Returns the profile ICC version. The version is decoded to readable floating point format.
289     #[inline]
version(&self) -> f64290     pub fn version(&self) -> f64 {
291         unsafe { ffi::cmsGetProfileVersion(self.handle) }
292     }
293 
294     /// Sets the ICC version in profile header. The version is given to this function as a float n.m
295     #[inline]
set_version(&mut self, ver: f64)296     pub fn set_version(&mut self, ver: f64) {
297         unsafe {
298             ffi::cmsSetProfileVersion(self.handle, ver);
299         }
300     }
301 
302     #[inline]
tag_signatures(&self) -> Vec<TagSignature>303     pub fn tag_signatures(&self) -> Vec<TagSignature> {
304         unsafe {
305             (0..ffi::cmsGetTagCount(self.handle)).map(|n| ffi::cmsGetTagSignature(self.handle, n as u32)).collect()
306         }
307     }
308 
309     #[inline]
detect_black_point(&self, intent: Intent) -> Option<CIEXYZ>310     pub fn detect_black_point(&self, intent: Intent) -> Option<CIEXYZ> {
311         unsafe {
312             let mut b = Default::default();
313             if ffi::cmsDetectBlackPoint(&mut b, self.handle, intent, 0) != 0 {
314                 Some(b)
315             } else {
316                 None
317             }
318         }
319     }
320 
321     #[inline]
detect_destination_black_point(&self, intent: Intent) -> Option<CIEXYZ>322     pub fn detect_destination_black_point(&self, intent: Intent) -> Option<CIEXYZ> {
323         unsafe {
324             let mut b = Default::default();
325             if ffi::cmsDetectDestinationBlackPoint(&mut b, self.handle, intent, 0) != 0 {
326                 Some(b)
327             } else {
328                 None
329             }
330         }
331     }
332 
333     #[inline]
detect_tac(&self) -> f64334     pub fn detect_tac(&self) -> f64 {
335         unsafe { ffi::cmsDetectTAC(self.handle) }
336     }
337 
338     /// Gets the color space used by the given profile, using the ICC convention.
339     #[inline]
color_space(&self) -> ColorSpaceSignature340     pub fn color_space(&self) -> ColorSpaceSignature {
341         unsafe {
342             let v = ffi::cmsGetColorSpace(self.handle);
343             if 0 != v as u32 {v} else {ColorSpaceSignature::Sig1colorData}
344         }
345     }
346 
347     /// Sets the profile connection space signature in profile header, using ICC convention.
348     #[inline]
set_color_space(&mut self, sig: ColorSpaceSignature)349     pub fn set_color_space(&mut self, sig: ColorSpaceSignature) {
350         unsafe { ffi::cmsSetColorSpace(self.handle, sig) }
351     }
352 
353     #[inline]
is_clut(&self, intent: Intent, used_direction: u32) -> bool354     pub fn is_clut(&self, intent: Intent, used_direction: u32) -> bool {
355         unsafe { ffi::cmsIsCLUT(self.handle, intent, used_direction) != 0 }
356     }
357 
358     #[inline]
is_intent_supported(&self, intent: Intent, used_direction: u32) -> bool359     pub fn is_intent_supported(&self, intent: Intent, used_direction: u32) -> bool {
360         unsafe { ffi::cmsIsIntentSupported(self.handle, intent, used_direction) != 0 }
361     }
362 
363     #[inline]
is_matrix_shaper(&self) -> bool364     pub fn is_matrix_shaper(&self) -> bool {
365         unsafe { ffi::cmsIsMatrixShaper(self.handle) != 0 }
366     }
367 
368     #[inline]
has_tag(&self, sig: TagSignature) -> bool369     pub fn has_tag(&self, sig: TagSignature) -> bool {
370         unsafe { ffi::cmsIsTag(self.handle, sig) != 0 }
371     }
372 
373     #[inline]
read_tag(&self, sig: TagSignature) -> Tag<'_>374     pub fn read_tag(&self, sig: TagSignature) -> Tag<'_> {
375         unsafe { Tag::new(sig, ffi::cmsReadTag(self.handle, sig) as *const u8) }
376     }
377 
378     #[inline]
write_tag(&mut self, sig: TagSignature, tag: Tag<'_>) -> bool379     pub fn write_tag(&mut self, sig: TagSignature, tag: Tag<'_>) -> bool {
380         unsafe {
381             ffi::cmsWriteTag(self.handle, sig, tag.data_for_signature(sig) as *const _) != 0
382         }
383     }
384 
385     #[inline]
remove_tag(&mut self, sig: TagSignature) -> bool386     pub fn remove_tag(&mut self, sig: TagSignature) -> bool {
387         unsafe {
388             ffi::cmsWriteTag(self.handle, sig, std::ptr::null()) != 0
389         }
390     }
391 
392     #[inline]
link_tag(&mut self, sig: TagSignature, dst: TagSignature) -> bool393     pub fn link_tag(&mut self, sig: TagSignature, dst: TagSignature) -> bool {
394         unsafe {
395             ffi::cmsLinkTag(self.handle, sig, dst) != 0
396         }
397     }
398 
399     /// Retrieves the Profile ID stored in the profile header.
400     #[inline]
profile_id(&self) -> ffi::ProfileID401     pub fn profile_id(&self) -> ffi::ProfileID {
402         let mut id = ffi::ProfileID::default();
403         unsafe {
404             ffi::cmsGetHeaderProfileID(self.handle, &mut id as *mut ffi::ProfileID as *mut _);
405         }
406         id
407     }
408 
409     /// Computes a MD5 checksum and stores it as Profile ID in the profile header.
410     #[inline]
set_default_profile_id(&mut self)411     pub fn set_default_profile_id(&mut self) {
412         unsafe {
413             ffi::cmsMD5computeID(self.handle);
414         }
415     }
416 
417     #[inline]
set_profile_id(&mut self, id: ffi::ProfileID)418     pub fn set_profile_id(&mut self, id: ffi::ProfileID) {
419         unsafe {
420             ffi::cmsSetHeaderProfileID(self.handle, &id as *const ffi::ProfileID as *mut _);
421         }
422     }
423 }
424 
425 /// Per-context functions that can be used with a `ThreadContext`
426 impl<Ctx: Context> Profile<Ctx> {
427     #[inline]
new_icc_context(context: impl AsRef<Ctx>, data: &[u8]) -> LCMSResult<Self>428     pub fn new_icc_context(context: impl AsRef<Ctx>, data: &[u8]) -> LCMSResult<Self> {
429         Self::new_handle(unsafe {
430             ffi::cmsOpenProfileFromMemTHR(context.as_ref().as_ptr(), data.as_ptr() as *const c_void, data.len() as u32)
431         })
432     }
433 
434     #[inline]
new_file_context<P: AsRef<Path>>(context: impl AsRef<Ctx>, path: P) -> io::Result<Self>435     pub fn new_file_context<P: AsRef<Path>>(context: impl AsRef<Ctx>, path: P) -> io::Result<Self> {
436         let mut buf = Vec::new();
437         File::open(path)?.read_to_end(&mut buf)?;
438         Self::new_icc_context(context, &buf).map_err(|_| io::ErrorKind::Other.into())
439     }
440 
441     #[inline]
new_srgb_context(context: impl AsRef<Ctx>) -> Self442     pub fn new_srgb_context(context: impl AsRef<Ctx>) -> Self {
443         Self::new_handle(unsafe { ffi::cmsCreate_sRGBProfileTHR(context.as_ref().as_ptr()) }).unwrap()
444     }
445 
446     #[inline]
new_rgb_context(context: impl AsRef<Ctx>, white_point: &CIExyY, primaries: &CIExyYTRIPLE, transfer_function: &[&ToneCurve]) -> LCMSResult<Self>447     pub fn new_rgb_context(context: impl AsRef<Ctx>, white_point: &CIExyY,
448                    primaries: &CIExyYTRIPLE,
449                    transfer_function: &[&ToneCurve])
450                    -> LCMSResult<Self> {
451         assert_eq!(3, transfer_function.len());
452         Self::new_handle(unsafe {
453             ffi::cmsCreateRGBProfileTHR(context.as_ref().as_ptr(),
454                                      white_point,
455                                      primaries,
456                                      [transfer_function[0].as_ptr() as *const _,
457                                       transfer_function[1].as_ptr() as *const _,
458                                       transfer_function[2].as_ptr() as *const _]
459                                          .as_ptr())
460         })
461     }
462 
463     #[inline]
new_gray_context(context: impl AsRef<Ctx>, white_point: &CIExyY, curve: &ToneCurve) -> LCMSResult<Self>464     pub fn new_gray_context(context: impl AsRef<Ctx>, white_point: &CIExyY, curve: &ToneCurve) -> LCMSResult<Self> {
465         Self::new_handle(unsafe { ffi::cmsCreateGrayProfileTHR(context.as_ref().as_ptr(), white_point, curve.as_ptr()) })
466     }
467 
468     /// This is a devicelink operating in the target colorspace with as many transfer functions as components.
469     /// Number of tone curves must be sufficient for the color space.
470     #[inline]
new_linearization_device_link_context(context: impl AsRef<Ctx>, color_space: ColorSpaceSignature, curves: &[ToneCurveRef]) -> LCMSResult<Self>471     pub unsafe fn new_linearization_device_link_context(context: impl AsRef<Ctx>, color_space: ColorSpaceSignature, curves: &[ToneCurveRef]) -> LCMSResult<Self> {
472         let v: Vec<_> = curves.iter().map(|c| c.as_ptr() as *const _).collect();
473         Self::new_handle(ffi::cmsCreateLinearizationDeviceLinkTHR(context.as_ref().as_ptr(), color_space, v.as_ptr()))
474     }
475 
476     /// Creates an abstract devicelink operating in Lab for Bright/Contrast/Hue/Saturation and white point translation.
477     /// White points are specified as temperatures ºK
478     ///
479     /// nLUTPoints : Resulting color map resolution
480     /// Bright: Bright increment. May be negative
481     /// Contrast : Contrast increment. May be negative.
482     /// Hue : Hue displacement in degree.
483     /// Saturation: Saturation increment. May be negative
484     /// TempSrc: Source white point temperature
485     /// TempDest: Destination white point temperature.
486     /// To prevent white point adjustment, set Temp to None
487     #[inline]
new_bchsw_abstract_context(context: impl AsRef<Ctx>, lut_points: usize, bright: f64, contrast: f64, hue: f64, saturation: f64, temp_src_dst: Option<(u32, u32)>) -> LCMSResult<Self>488     pub fn new_bchsw_abstract_context(context: impl AsRef<Ctx>, lut_points: usize, bright: f64, contrast: f64, hue: f64, saturation: f64,
489                                       temp_src_dst: Option<(u32, u32)>) -> LCMSResult<Self> {
490         let (temp_src, temp_dest) = temp_src_dst.unwrap_or((0,0));
491         Self::new_handle(unsafe {
492             ffi::cmsCreateBCHSWabstractProfileTHR(context.as_ref().as_ptr(), lut_points as _, bright, contrast, hue, saturation, temp_src as _, temp_dest as _)
493         })
494     }
495 
496     #[inline]
new_handle(handle: ffi::HPROFILE) -> LCMSResult<Self>497     fn new_handle(handle: ffi::HPROFILE) -> LCMSResult<Self> {
498         if handle.is_null() {
499             return Err(Error::ObjectCreationError);
500         }
501         Ok(Profile {
502             handle,
503             _context_ref: PhantomData,
504         })
505     }
506 
507     /// This is a devicelink operating in CMYK for ink-limiting. Currently only cmsSigCmykData is supported.
508     /// Limit: Amount of ink limiting in % (0..400%)
509     #[inline]
ink_limiting_context(context: impl AsRef<Ctx>, color_space: ColorSpaceSignature, limit: f64) -> LCMSResult<Self>510     pub fn ink_limiting_context(context: impl AsRef<Ctx>, color_space: ColorSpaceSignature, limit: f64) -> LCMSResult<Self> {
511         Self::new_handle(unsafe { ffi::cmsCreateInkLimitingDeviceLinkTHR(context.as_ref().as_ptr(), color_space, limit) })
512     }
513 
514     /// Creates a XYZ  XYZ identity, marking it as v4 ICC profile.  WhitePoint used in Absolute colorimetric intent  is D50.
515     #[inline]
new_xyz_context(context: impl AsRef<Ctx>) -> Self516     pub fn new_xyz_context(context: impl AsRef<Ctx>) -> Self {
517         Self::new_handle(unsafe { ffi::cmsCreateXYZProfileTHR(context.as_ref().as_ptr()) }).unwrap()
518     }
519 
520     /// Creates a fake NULL profile. This profile return 1 channel as always 0. Is useful only for gamut checking tricks.
521     #[inline]
new_null_context(context: impl AsRef<Ctx>) -> Self522     pub fn new_null_context(context: impl AsRef<Ctx>) -> Self {
523         Self::new_handle(unsafe { ffi::cmsCreateNULLProfileTHR(context.as_ref().as_ptr()) }).unwrap()
524     }
525 
526     /// Creates a Lab  Lab identity, marking it as v2 ICC profile.
527     ///
528     /// Adjustments for accomodating PCS endoing shall be done by Little CMS when using this profile.
new_lab2_context(context: impl AsRef<Ctx>, white_point: &CIExyY) -> LCMSResult<Self>529     pub fn new_lab2_context(context: impl AsRef<Ctx>, white_point: &CIExyY) -> LCMSResult<Self> {
530         Self::new_handle(unsafe { ffi::cmsCreateLab2ProfileTHR(context.as_ref().as_ptr(), white_point) })
531     }
532 
533     /// Creates a Lab  Lab identity, marking it as v4 ICC profile.
534     #[inline]
new_lab4_context(context: impl AsRef<Ctx>, white_point: &CIExyY) -> LCMSResult<Self>535     pub fn new_lab4_context(context: impl AsRef<Ctx>, white_point: &CIExyY) -> LCMSResult<Self> {
536         Self::new_handle(unsafe { ffi::cmsCreateLab4ProfileTHR(context.as_ref().as_ptr(), white_point) })
537     }
538 }
539 
540 impl<Context> Drop for Profile<Context> {
drop(&mut self)541     fn drop(&mut self) {
542         unsafe {
543             ffi::cmsCloseProfile(self.handle);
544         }
545     }
546 }
547 
548 
549 #[test]
tags_read()550 fn tags_read() {
551     let prof = Profile::new_srgb();
552     assert!(prof.read_tag(TagSignature::BToD0Tag).is_none());
553     assert_eq!(CIEXYZ::d50().X, match prof.read_tag(TagSignature::MediaWhitePointTag) {
554         Tag::CIEXYZ(xyz) => xyz.X,
555         _ => panic!(),
556     });
557 }
558 
559 #[test]
tags_write()560 fn tags_write() {
561     let mut p = Profile::new_placeholder();
562     let mut mlu = MLU::new(1);
563     mlu.set_text_ascii("Testing", Locale::new("en_GB"));
564     assert!(p.write_tag(TagSignature::CopyrightTag, Tag::MLU(&mlu)));
565 
566     let xyz = CIEXYZ{X:1., Y:2., Z:3.};
567     assert!(p.write_tag(TagSignature::RedColorantTag, Tag::CIEXYZ(&xyz)));
568 
569     assert!(p.has_tag(TagSignature::CopyrightTag));
570     assert!(p.has_tag(TagSignature::RedColorantTag));
571     assert!(!p.has_tag(TagSignature::BlueColorantTag));
572 
573     assert_eq!(&xyz, match p.read_tag(TagSignature::RedColorantTag) {
574         Tag::CIEXYZ(d) => d,
575         _ => panic!(),
576     });
577 
578     assert_eq!(Ok("Testing".to_owned()), match p.read_tag(TagSignature::CopyrightTag) {
579         Tag::MLU(mlu) => mlu.text(Locale::none()),
580         _ => panic!(),
581     });
582 }
583 
584 impl fmt::Debug for Profile {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result585     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
586         let mut s = f.debug_struct("Profile");
587         let l = Locale::none();
588         s.field("Description", &self.info(InfoType::Description, l));
589         s.field("Manufacturer", &self.info(InfoType::Manufacturer, l));
590         s.field("Model", &self.info(InfoType::Model, l));
591         s.field("Copyright", &self.info(InfoType::Copyright, l));
592         s.finish()
593     }
594 }
595 
596 #[test]
setters()597 fn setters() {
598     let mut p = Profile::new_placeholder();
599     assert_eq!(ColorSpaceSignature::Sig1colorData, p.color_space());
600     p.set_color_space(ColorSpaceSignature::RgbData);
601     assert_eq!(ColorSpaceSignature::RgbData, p.color_space());
602 }
603 
604 #[test]
icc()605 fn icc() {
606     let prof = Profile::new_xyz();
607     assert!(prof.icc().unwrap().len() > 300);
608     assert!(format!("{:?}", prof).contains("XYZ identity"));
609 }
610 
611 #[test]
bad_icc()612 fn bad_icc() {
613     let err = Profile::new_icc(&[1, 2, 3]);
614     assert!(err.is_err());
615 }
616 
617 #[test]
unwind_safety()618 fn unwind_safety() {
619     let ref profile = Profile::new_xyz();
620     std::panic::catch_unwind(|| {
621         profile.clone()
622     }).unwrap();
623 }
624