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