blob: 8944be2532cd7147893cc8c43ed43a07915be202 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% PPPP RRRR OOO FFFFF IIIII L EEEEE %
7% P P R R O O F I L E %
8% PPPP RRRR O O FFF I L EEE %
9% P R R O O F I L E %
10% P R R OOO F IIIII LLLLL EEEEE %
11% %
12% %
13% MagickCore Image Profile Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy1454be72011-12-19 01:52:48 +000020% Copyright 1999-2012 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37*/
38
39/*
40 Include declarations.
41*/
cristy4c08aed2011-07-01 19:47:50 +000042#include "MagickCore/studio.h"
43#include "MagickCore/attribute.h"
44#include "MagickCore/cache.h"
45#include "MagickCore/color.h"
cristy510d06a2011-07-06 23:43:54 +000046#include "MagickCore/colorspace-private.h"
cristy4c08aed2011-07-01 19:47:50 +000047#include "MagickCore/configure.h"
48#include "MagickCore/exception.h"
49#include "MagickCore/exception-private.h"
50#include "MagickCore/hashmap.h"
51#include "MagickCore/image.h"
52#include "MagickCore/memory_.h"
53#include "MagickCore/monitor.h"
54#include "MagickCore/monitor-private.h"
55#include "MagickCore/option.h"
56#include "MagickCore/pixel-accessor.h"
57#include "MagickCore/profile.h"
cristy7832dc22011-09-05 01:21:53 +000058#include "MagickCore/profile-private.h"
cristy4c08aed2011-07-01 19:47:50 +000059#include "MagickCore/property.h"
60#include "MagickCore/quantum.h"
61#include "MagickCore/quantum-private.h"
cristyac245f82012-05-05 17:13:57 +000062#include "MagickCore/resource_.h"
cristy4c08aed2011-07-01 19:47:50 +000063#include "MagickCore/splay-tree.h"
64#include "MagickCore/string_.h"
65#include "MagickCore/thread-private.h"
66#include "MagickCore/token.h"
67#include "MagickCore/utility.h"
cristy3ed852e2009-09-05 21:47:34 +000068#if defined(MAGICKCORE_LCMS_DELEGATE)
cristyd09bcf92010-03-25 03:04:45 +000069#if defined(MAGICKCORE_HAVE_LCMS_LCMS2_H)
70#include <wchar.h>
71#include <lcms/lcms2.h>
72#elif defined(MAGICKCORE_HAVE_LCMS2_H)
73#include <wchar.h>
74#include "lcms2.h"
75#elif defined(MAGICKCORE_HAVE_LCMS_LCMS_H)
cristy3ed852e2009-09-05 21:47:34 +000076#include <lcms/lcms.h>
77#else
78#include "lcms.h"
79#endif
80#endif
81
82/*
cristy2110f4b2010-04-13 19:15:02 +000083 Define declarations.
84*/
cristy71203402010-06-18 13:12:03 +000085#if !defined(LCMS_VERSION) || (LCMS_VERSION < 2000)
cristy20a78b22010-04-05 01:22:12 +000086#define cmsSigCmykData icSigCmykData
87#define cmsSigGrayData icSigGrayData
88#define cmsSigLabData icSigLabData
89#define cmsSigLuvData icSigLuvData
90#define cmsSigRgbData icSigRgbData
91#define cmsSigXYZData icSigXYZData
92#define cmsSigYCbCrData icSigYCbCrData
cristy902d15b2010-04-07 19:44:01 +000093#define cmsSigLinkClass icSigLinkClass
cristybbf9c612010-04-08 13:37:47 +000094#define cmsColorSpaceSignature icColorSpaceSignature
cristy71203402010-06-18 13:12:03 +000095#define cmsUInt32Number DWORD
96#define cmsSetLogErrorHandler(handler) cmsSetErrorHandler(handler)
97#define cmsCreateTransformTHR(context,source_profile,source_type, \
98 target_profile,target_type,intent,flags) cmsCreateTransform(source_profile, \
99 source_type,target_profile,target_type,intent,flags);
100#define cmsOpenProfileFromMemTHR(context,profile,length) \
101 cmsOpenProfileFromMem(profile,length)
cristyd09bcf92010-03-25 03:04:45 +0000102#endif
103
104/*
cristy092d71c2011-10-14 18:01:29 +0000105 Typedef declarations
106*/
cristy3fac9ec2011-11-17 18:04:39 +0000107struct _ProfileInfo
cristy092d71c2011-10-14 18:01:29 +0000108{
109 char
110 *name;
111
112 size_t
113 length;
114
115 unsigned char
116 *info;
117
118 size_t
119 signature;
120};
cristyd0b7ee52011-10-21 11:19:02 +0000121
122typedef struct _CMSExceptionInfo
123{
124 Image
125 *image;
126
127 ExceptionInfo
128 *exception;
129} CMSExceptionInfo;
cristy092d71c2011-10-14 18:01:29 +0000130
131/*
cristy3ed852e2009-09-05 21:47:34 +0000132%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
133% %
134% %
135% %
136% C l o n e I m a g e P r o f i l e s %
137% %
138% %
139% %
140%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
141%
142% CloneImageProfiles() clones one or more image profiles.
143%
144% The format of the CloneImageProfiles method is:
145%
146% MagickBooleanType CloneImageProfiles(Image *image,
147% const Image *clone_image)
148%
149% A description of each parameter follows:
150%
151% o image: the image.
152%
153% o clone_image: the clone image.
154%
155*/
156MagickExport MagickBooleanType CloneImageProfiles(Image *image,
157 const Image *clone_image)
158{
159 assert(image != (Image *) NULL);
160 assert(image->signature == MagickSignature);
161 if (image->debug != MagickFalse)
162 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
163 assert(clone_image != (const Image *) NULL);
164 assert(clone_image->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +0000165 if (clone_image->profiles != (void *) NULL)
166 image->profiles=CloneSplayTree((SplayTreeInfo *) clone_image->profiles,
167 (void *(*)(void *)) ConstantString,(void *(*)(void *)) CloneStringInfo);
168 return(MagickTrue);
169}
170
171/*
172%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
173% %
174% %
175% %
176% D e l e t e I m a g e P r o f i l e %
177% %
178% %
179% %
180%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
181%
182% DeleteImageProfile() deletes a profile from the image by its name.
183%
184% The format of the DeleteImageProfile method is:
185%
186% MagickBooleanTyupe DeleteImageProfile(Image *image,const char *name)
187%
188% A description of each parameter follows:
189%
190% o image: the image.
191%
192% o name: the profile name.
193%
194*/
cristy04390e72010-03-08 00:50:13 +0000195MagickExport MagickBooleanType DeleteImageProfile(Image *image,const char *name)
cristy3ed852e2009-09-05 21:47:34 +0000196{
197 assert(image != (Image *) NULL);
198 assert(image->signature == MagickSignature);
199 if (image->debug != MagickFalse)
200 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
201 if (image->profiles == (SplayTreeInfo *) NULL)
202 return(MagickFalse);
cristy3ed852e2009-09-05 21:47:34 +0000203 return(DeleteNodeFromSplayTree((SplayTreeInfo *) image->profiles,name));
204}
205
206/*
207%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
208% %
209% %
210% %
211% D e s t r o y I m a g e P r o f i l e s %
212% %
213% %
214% %
215%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
216%
217% DestroyImageProfiles() releases memory associated with an image profile map.
218%
219% The format of the DestroyProfiles method is:
220%
221% void DestroyImageProfiles(Image *image)
222%
223% A description of each parameter follows:
224%
225% o image: the image.
226%
227*/
228MagickExport void DestroyImageProfiles(Image *image)
229{
230 if (image->profiles != (SplayTreeInfo *) NULL)
231 image->profiles=DestroySplayTree((SplayTreeInfo *) image->profiles);
232}
233
234/*
235%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
236% %
237% %
238% %
239% G e t I m a g e P r o f i l e %
240% %
241% %
242% %
243%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
244%
245% GetImageProfile() gets a profile associated with an image by name.
246%
247% The format of the GetImageProfile method is:
248%
249% const StringInfo *GetImageProfile(const Image *image,const char *name)
250%
251% A description of each parameter follows:
252%
253% o image: the image.
254%
255% o name: the profile name.
256%
257*/
258MagickExport const StringInfo *GetImageProfile(const Image *image,
259 const char *name)
260{
261 char
262 key[MaxTextExtent];
263
264 const StringInfo
265 *profile;
266
267 assert(image != (Image *) NULL);
268 assert(image->signature == MagickSignature);
269 if (image->debug != MagickFalse)
270 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
271 if (image->profiles == (SplayTreeInfo *) NULL)
272 return((StringInfo *) NULL);
273 (void) CopyMagickString(key,name,MaxTextExtent);
274 profile=(const StringInfo *) GetValueFromSplayTree((SplayTreeInfo *)
275 image->profiles,key);
276 return(profile);
277}
278
279/*
280%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
281% %
282% %
283% %
284% G e t N e x t I m a g e P r o f i l e %
285% %
286% %
287% %
288%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
289%
290% GetNextImageProfile() gets the next profile name for an image.
291%
292% The format of the GetNextImageProfile method is:
293%
294% char *GetNextImageProfile(const Image *image)
295%
296% A description of each parameter follows:
297%
298% o hash_info: the hash info.
299%
300*/
301MagickExport char *GetNextImageProfile(const Image *image)
302{
303 assert(image != (Image *) NULL);
304 assert(image->signature == MagickSignature);
305 if (image->debug != MagickFalse)
306 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
307 if (image->profiles == (SplayTreeInfo *) NULL)
308 return((char *) NULL);
309 return((char *) GetNextKeyInSplayTree((SplayTreeInfo *) image->profiles));
310}
311
312/*
313%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
314% %
315% %
316% %
317% P r o f i l e I m a g e %
318% %
319% %
320% %
321%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
322%
323% ProfileImage() associates, applies, or removes an ICM, IPTC, or generic
324% profile with / to / from an image. If the profile is NULL, it is removed
325% from the image otherwise added or applied. Use a name of '*' and a profile
326% of NULL to remove all profiles from the image.
327%
328% ICC and ICM profiles are handled as follows: If the image does not have
329% an associated color profile, the one you provide is associated with the
330% image and the image pixels are not transformed. Otherwise, the colorspace
331% transform defined by the existing and new profile are applied to the image
332% pixels and the new profile is associated with the image.
333%
334% The format of the ProfileImage method is:
335%
336% MagickBooleanType ProfileImage(Image *image,const char *name,
337% const void *datum,const size_t length,const MagickBooleanType clone)
338%
339% A description of each parameter follows:
340%
341% o image: the image.
342%
343% o name: Name of profile to add or remove: ICC, IPTC, or generic profile.
344%
345% o datum: the profile data.
346%
347% o length: the length of the profile.
348%
349% o clone: should be MagickFalse.
350%
351*/
352
353#if defined(MAGICKCORE_LCMS_DELEGATE)
354
355static unsigned short **DestroyPixelThreadSet(unsigned short **pixels)
356{
cristybb503372010-05-27 20:51:26 +0000357 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000358 i;
359
360 assert(pixels != (unsigned short **) NULL);
cristyac245f82012-05-05 17:13:57 +0000361 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
cristy3ed852e2009-09-05 21:47:34 +0000362 if (pixels[i] != (unsigned short *) NULL)
363 pixels[i]=(unsigned short *) RelinquishMagickMemory(pixels[i]);
cristyb41ee102010-10-04 16:46:15 +0000364 pixels=(unsigned short **) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +0000365 return(pixels);
366}
367
368static unsigned short **AcquirePixelThreadSet(const size_t columns,
369 const size_t channels)
370{
cristybb503372010-05-27 20:51:26 +0000371 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000372 i;
373
374 unsigned short
375 **pixels;
376
cristybb503372010-05-27 20:51:26 +0000377 size_t
cristy3ed852e2009-09-05 21:47:34 +0000378 number_threads;
379
cristy9357bdd2012-07-30 12:28:34 +0000380 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
cristyb41ee102010-10-04 16:46:15 +0000381 pixels=(unsigned short **) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +0000382 sizeof(*pixels));
383 if (pixels == (unsigned short **) NULL)
384 return((unsigned short **) NULL);
385 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
cristybb503372010-05-27 20:51:26 +0000386 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +0000387 {
388 pixels[i]=(unsigned short *) AcquireQuantumMemory(columns,channels*
389 sizeof(**pixels));
390 if (pixels[i] == (unsigned short *) NULL)
391 return(DestroyPixelThreadSet(pixels));
392 }
393 return(pixels);
394}
395
396static cmsHTRANSFORM *DestroyTransformThreadSet(cmsHTRANSFORM *transform)
397{
cristybb503372010-05-27 20:51:26 +0000398 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000399 i;
400
401 assert(transform != (cmsHTRANSFORM *) NULL);
cristyac245f82012-05-05 17:13:57 +0000402 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
cristy3ed852e2009-09-05 21:47:34 +0000403 if (transform[i] != (cmsHTRANSFORM) NULL)
404 cmsDeleteTransform(transform[i]);
cristyb41ee102010-10-04 16:46:15 +0000405 transform=(cmsHTRANSFORM *) RelinquishMagickMemory(transform);
cristy3ed852e2009-09-05 21:47:34 +0000406 return(transform);
407}
408
cristy7632f502010-06-18 13:26:31 +0000409static cmsHTRANSFORM *AcquireTransformThreadSet(Image *image,
cristyd09bcf92010-03-25 03:04:45 +0000410 const cmsHPROFILE source_profile,const cmsUInt32Number source_type,
cristy20a78b22010-04-05 01:22:12 +0000411 const cmsHPROFILE target_profile,const cmsUInt32Number target_type,
412 const int intent,const cmsUInt32Number flags)
cristy3ed852e2009-09-05 21:47:34 +0000413{
414 cmsHTRANSFORM
415 *transform;
416
cristybb503372010-05-27 20:51:26 +0000417 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000418 i;
419
cristybb503372010-05-27 20:51:26 +0000420 size_t
cristy3ed852e2009-09-05 21:47:34 +0000421 number_threads;
422
cristy9357bdd2012-07-30 12:28:34 +0000423 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
cristyb41ee102010-10-04 16:46:15 +0000424 transform=(cmsHTRANSFORM *) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +0000425 sizeof(*transform));
426 if (transform == (cmsHTRANSFORM *) NULL)
427 return((cmsHTRANSFORM *) NULL);
428 (void) ResetMagickMemory(transform,0,number_threads*sizeof(*transform));
cristybb503372010-05-27 20:51:26 +0000429 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +0000430 {
cristy71203402010-06-18 13:12:03 +0000431 transform[i]=cmsCreateTransformTHR(image,source_profile,source_type,
432 target_profile,target_type,intent,flags);
cristy3ed852e2009-09-05 21:47:34 +0000433 if (transform[i] == (cmsHTRANSFORM) NULL)
434 return(DestroyTransformThreadSet(transform));
435 }
436 return(transform);
437}
438#endif
439
cristyd0b7ee52011-10-21 11:19:02 +0000440#if defined(MAGICKCORE_LCMS_DELEGATE)
441#if defined(LCMS_VERSION) && (LCMS_VERSION >= 2000)
442static void CMSExceptionHandler(cmsContext context,cmsUInt32Number severity,
443 const char *message)
444{
445 CMSExceptionInfo
446 *cms_exception;
447
448 ExceptionInfo
449 *exception;
450
451 Image
452 *image;
453
454 cms_exception=(CMSExceptionInfo *) context;
455 image=cms_exception->image;
456 exception=cms_exception->exception;
457 if (image == (Image *) NULL)
458 {
459 (void) ThrowMagickException(exception,GetMagickModule(),ImageWarning,
anthonye5b39652012-04-21 05:37:29 +0000460 "UnableToTransformColorspace","'%s'","unknown context");
cristyd0b7ee52011-10-21 11:19:02 +0000461 return;
462 }
463 if (image->debug != MagickFalse)
464 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"lcms: #%u, %s",
465 severity,message != (char *) NULL ? message : "no message");
466 (void) ThrowMagickException(exception,GetMagickModule(),ImageWarning,
anthonye5b39652012-04-21 05:37:29 +0000467 "UnableToTransformColorspace","'%s'",image->filename);
cristyd0b7ee52011-10-21 11:19:02 +0000468}
469#else
470static int CMSExceptionHandler(int severity,const char *message)
471{
472 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"lcms: #%d, %s",
473 severity,message != (char *) NULL ? message : "no message");
474 return(1);
475}
476#endif
477#endif
478
cristy3ed852e2009-09-05 21:47:34 +0000479MagickExport MagickBooleanType ProfileImage(Image *image,const char *name,
cristy092d71c2011-10-14 18:01:29 +0000480 const void *datum,const size_t length,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000481{
482#define ProfileImageTag "Profile/Image"
483#define ThrowProfileException(severity,tag,context) \
484{ \
cristye1c67a82010-04-10 02:20:57 +0000485 if (source_profile != (cmsHPROFILE) NULL) \
486 (void) cmsCloseProfile(source_profile); \
cristya9eb12b2010-04-08 01:19:04 +0000487 if (target_profile != (cmsHPROFILE) NULL) \
488 (void) cmsCloseProfile(target_profile); \
cristy3ed852e2009-09-05 21:47:34 +0000489 ThrowBinaryException(severity,tag,context); \
490}
491
492 MagickBooleanType
493 status;
494
495 StringInfo
496 *profile;
497
498 assert(image != (Image *) NULL);
499 assert(image->signature == MagickSignature);
500 if (image->debug != MagickFalse)
501 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
502 assert(name != (const char *) NULL);
503 if ((datum == (const void *) NULL) || (length == 0))
504 {
505 char
506 **arguments,
507 *names;
508
509 int
510 number_arguments;
511
cristybb503372010-05-27 20:51:26 +0000512 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000513 i;
514
515 /*
516 Delete image profile(s).
517 */
518 names=ConstantString(name);
519 (void) SubstituteString(&names,","," ");
520 arguments=StringToArgv(names,&number_arguments);
521 names=DestroyString(names);
522 if (arguments == (char **) NULL)
523 return(MagickTrue);
524 ResetImageProfileIterator(image);
525 for (name=GetNextImageProfile(image); name != (const char *) NULL; )
526 {
cristycee97112010-05-28 00:44:52 +0000527 for (i=1; i < (ssize_t) number_arguments; i++)
cristy3ed852e2009-09-05 21:47:34 +0000528 {
529 if ((*arguments[i] == '!') &&
530 (LocaleCompare(name,arguments[i]+1) == 0))
531 break;
532 if (GlobExpression(name,arguments[i],MagickTrue) != MagickFalse)
533 {
534 (void) DeleteImageProfile(image,name);
cristy3ed852e2009-09-05 21:47:34 +0000535 break;
536 }
537 }
538 name=GetNextImageProfile(image);
539 }
cristycee97112010-05-28 00:44:52 +0000540 for (i=0; i < (ssize_t) number_arguments; i++)
cristy3ed852e2009-09-05 21:47:34 +0000541 arguments[i]=DestroyString(arguments[i]);
542 arguments=(char **) RelinquishMagickMemory(arguments);
543 return(MagickTrue);
544 }
545 /*
546 Add a ICC, IPTC, or generic profile to the image.
547 */
cristy902d15b2010-04-07 19:44:01 +0000548 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000549 profile=AcquireStringInfo((size_t) length);
550 SetStringInfoDatum(profile,(unsigned char *) datum);
cristy902d15b2010-04-07 19:44:01 +0000551 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
cristyd15e6592011-10-15 00:13:06 +0000552 status=SetImageProfile(image,name,profile,exception);
cristy902d15b2010-04-07 19:44:01 +0000553 else
cristy3ed852e2009-09-05 21:47:34 +0000554 {
555 const StringInfo
556 *icc_profile;
557
558 icc_profile=GetImageProfile(image,"icc");
559 if ((icc_profile != (const StringInfo *) NULL) &&
560 (CompareStringInfo(icc_profile,profile) == 0))
561 {
562 const char
563 *value;
564
cristyd15e6592011-10-15 00:13:06 +0000565 value=GetImageProperty(image,"exif:ColorSpace",exception);
cristyd479bca2012-06-04 14:26:56 +0000566 (void) value;
cristy751980d2012-06-03 23:18:35 +0000567 /* Future.
cristy3ed852e2009-09-05 21:47:34 +0000568 if (LocaleCompare(value,"1") != 0)
cristyd15e6592011-10-15 00:13:06 +0000569 (void) SetsRGBImageProfile(image,exception);
570 value=GetImageProperty(image,"exif:InteroperabilityIndex",exception);
cristy3ed852e2009-09-05 21:47:34 +0000571 if (LocaleCompare(value,"R98.") != 0)
cristyd15e6592011-10-15 00:13:06 +0000572 (void) SetsRGBImageProfile(image,exception);
573 value=GetImageProperty(image,"exif:InteroperabilityIndex",exception);
cristy3ed852e2009-09-05 21:47:34 +0000574 if (LocaleCompare(value,"R03.") != 0)
cristyd15e6592011-10-15 00:13:06 +0000575 (void) SetAdobeRGB1998ImageProfile(image,exception);
cristy751980d2012-06-03 23:18:35 +0000576 */
cristy3ed852e2009-09-05 21:47:34 +0000577 icc_profile=GetImageProfile(image,"icc");
578 }
579 if ((icc_profile != (const StringInfo *) NULL) &&
580 (CompareStringInfo(icc_profile,profile) == 0))
581 {
582 profile=DestroyStringInfo(profile);
583 return(MagickTrue);
584 }
585#if !defined(MAGICKCORE_LCMS_DELEGATE)
cristy09b22022011-10-26 14:11:30 +0000586 (void) ThrowMagickException(exception,GetMagickModule(),
cristye2c81ad2011-08-20 22:54:14 +0000587 MissingDelegateWarning,"DelegateLibrarySupportNotBuiltIn",
anthonye5b39652012-04-21 05:37:29 +0000588 "'%s' (LCMS)",image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000589#else
cristy8a20a2d2010-04-08 14:16:27 +0000590 {
cristy8a20a2d2010-04-08 14:16:27 +0000591 cmsHPROFILE
cristye1c67a82010-04-10 02:20:57 +0000592 source_profile;
cristy3ed852e2009-09-05 21:47:34 +0000593
cristyd0b7ee52011-10-21 11:19:02 +0000594 CMSExceptionInfo
595 cms_exception;
596
cristy8a20a2d2010-04-08 14:16:27 +0000597 /*
598 Transform pixel colors as defined by the color profiles.
599 */
cristyd0b7ee52011-10-21 11:19:02 +0000600 cmsSetLogErrorHandler(CMSExceptionHandler);
601 cms_exception.image=image;
602 cms_exception.exception=exception;
cristy5f95f4f2011-10-23 01:01:01 +0000603 (void) cms_exception;
cristyd0b7ee52011-10-21 11:19:02 +0000604 source_profile=cmsOpenProfileFromMemTHR(&cms_exception,
cristy71203402010-06-18 13:12:03 +0000605 GetStringInfoDatum(profile),(cmsUInt32Number)
606 GetStringInfoLength(profile));
cristy8a20a2d2010-04-08 14:16:27 +0000607 if (source_profile == (cmsHPROFILE) NULL)
608 ThrowBinaryException(ResourceLimitError,
609 "ColorspaceColorProfileMismatch",name);
cristyfbaaf3e2010-04-08 14:53:43 +0000610 if ((cmsGetDeviceClass(source_profile) != cmsSigLinkClass) &&
cristye1c67a82010-04-10 02:20:57 +0000611 (icc_profile == (StringInfo *) NULL))
cristyd15e6592011-10-15 00:13:06 +0000612 status=SetImageProfile(image,name,profile,exception);
cristye1c67a82010-04-10 02:20:57 +0000613 else
cristy3ed852e2009-09-05 21:47:34 +0000614 {
cristye1c67a82010-04-10 02:20:57 +0000615 CacheView
616 *image_view;
cristy8a20a2d2010-04-08 14:16:27 +0000617
cristye1c67a82010-04-10 02:20:57 +0000618 ColorspaceType
619 source_colorspace,
620 target_colorspace;
cristy8a20a2d2010-04-08 14:16:27 +0000621
cristye1c67a82010-04-10 02:20:57 +0000622 cmsColorSpaceSignature
623 signature;
cristy8a20a2d2010-04-08 14:16:27 +0000624
cristye1c67a82010-04-10 02:20:57 +0000625 cmsHPROFILE
626 target_profile;
cristy8a20a2d2010-04-08 14:16:27 +0000627
cristye1c67a82010-04-10 02:20:57 +0000628 cmsHTRANSFORM
629 *restrict transform;
cristy8a20a2d2010-04-08 14:16:27 +0000630
cristye1c67a82010-04-10 02:20:57 +0000631 cmsUInt32Number
632 flags,
633 source_type,
634 target_type;
635
cristye1c67a82010-04-10 02:20:57 +0000636 int
637 intent;
638
cristye1c67a82010-04-10 02:20:57 +0000639 MagickBooleanType
640 status;
641
cristy5f959472010-05-27 22:19:46 +0000642 MagickOffsetType
643 progress;
644
cristye1c67a82010-04-10 02:20:57 +0000645 size_t
cristye1c67a82010-04-10 02:20:57 +0000646 source_channels,
647 target_channels;
648
cristy5f959472010-05-27 22:19:46 +0000649 ssize_t
650 y;
651
cristye1c67a82010-04-10 02:20:57 +0000652 unsigned short
653 **restrict source_pixels,
654 **restrict target_pixels;
655
cristye1c67a82010-04-10 02:20:57 +0000656 target_profile=(cmsHPROFILE) NULL;
657 if (icc_profile != (StringInfo *) NULL)
658 {
659 target_profile=source_profile;
cristyd0b7ee52011-10-21 11:19:02 +0000660 source_profile=cmsOpenProfileFromMemTHR(&cms_exception,
cristye1c67a82010-04-10 02:20:57 +0000661 GetStringInfoDatum(icc_profile),(cmsUInt32Number)
662 GetStringInfoLength(icc_profile));
663 if (source_profile == (cmsHPROFILE) NULL)
664 ThrowProfileException(ResourceLimitError,
665 "ColorspaceColorProfileMismatch",name);
666 }
667 switch (cmsGetColorSpace(source_profile))
cristy8a20a2d2010-04-08 14:16:27 +0000668 {
cristye1c67a82010-04-10 02:20:57 +0000669 case cmsSigCmykData:
670 {
671 source_colorspace=CMYKColorspace;
672 source_type=(cmsUInt32Number) TYPE_CMYK_16;
673 source_channels=4;
674 break;
675 }
676 case cmsSigGrayData:
677 {
678 source_colorspace=GRAYColorspace;
679 source_type=(cmsUInt32Number) TYPE_GRAY_16;
680 source_channels=1;
681 break;
682 }
683 case cmsSigLabData:
684 {
685 source_colorspace=LabColorspace;
686 source_type=(cmsUInt32Number) TYPE_Lab_16;
687 source_channels=3;
688 break;
689 }
690 case cmsSigLuvData:
691 {
692 source_colorspace=YUVColorspace;
693 source_type=(cmsUInt32Number) TYPE_YUV_16;
694 source_channels=3;
695 break;
696 }
697 case cmsSigRgbData:
698 {
cristyc511e882012-04-16 21:11:14 +0000699 source_colorspace=sRGBColorspace;
cristye1c67a82010-04-10 02:20:57 +0000700 source_type=(cmsUInt32Number) TYPE_RGB_16;
701 source_channels=3;
702 break;
703 }
704 case cmsSigXYZData:
705 {
706 source_colorspace=XYZColorspace;
707 source_type=(cmsUInt32Number) TYPE_XYZ_16;
708 source_channels=3;
709 break;
710 }
711 case cmsSigYCbCrData:
712 {
713 source_colorspace=YCbCrColorspace;
714 source_type=(cmsUInt32Number) TYPE_YCbCr_16;
715 source_channels=3;
716 break;
717 }
718 default:
719 {
720 source_colorspace=UndefinedColorspace;
721 source_type=(cmsUInt32Number) TYPE_RGB_16;
722 source_channels=3;
723 break;
724 }
cristy8a20a2d2010-04-08 14:16:27 +0000725 }
cristye1c67a82010-04-10 02:20:57 +0000726 signature=cmsGetPCS(source_profile);
727 if (target_profile != (cmsHPROFILE) NULL)
728 signature=cmsGetColorSpace(target_profile);
729 switch (signature)
730 {
731 case cmsSigCmykData:
cristy8a20a2d2010-04-08 14:16:27 +0000732 {
cristye1c67a82010-04-10 02:20:57 +0000733 target_colorspace=CMYKColorspace;
734 target_type=(cmsUInt32Number) TYPE_CMYK_16;
735 target_channels=4;
736 break;
cristy8a20a2d2010-04-08 14:16:27 +0000737 }
cristye1c67a82010-04-10 02:20:57 +0000738 case cmsSigLabData:
cristy8a20a2d2010-04-08 14:16:27 +0000739 {
cristye1c67a82010-04-10 02:20:57 +0000740 target_colorspace=LabColorspace;
741 target_type=(cmsUInt32Number) TYPE_Lab_16;
742 target_channels=3;
743 break;
cristy8a20a2d2010-04-08 14:16:27 +0000744 }
cristye1c67a82010-04-10 02:20:57 +0000745 case cmsSigGrayData:
cristy8a20a2d2010-04-08 14:16:27 +0000746 {
cristye1c67a82010-04-10 02:20:57 +0000747 target_colorspace=GRAYColorspace;
748 target_type=(cmsUInt32Number) TYPE_GRAY_16;
749 target_channels=1;
750 break;
cristy8a20a2d2010-04-08 14:16:27 +0000751 }
cristye1c67a82010-04-10 02:20:57 +0000752 case cmsSigLuvData:
753 {
754 target_colorspace=YUVColorspace;
755 target_type=(cmsUInt32Number) TYPE_YUV_16;
756 target_channels=3;
757 break;
758 }
759 case cmsSigRgbData:
760 {
cristyc511e882012-04-16 21:11:14 +0000761 target_colorspace=sRGBColorspace;
cristye1c67a82010-04-10 02:20:57 +0000762 target_type=(cmsUInt32Number) TYPE_RGB_16;
763 target_channels=3;
764 break;
765 }
766 case cmsSigXYZData:
767 {
768 target_colorspace=XYZColorspace;
769 target_type=(cmsUInt32Number) TYPE_XYZ_16;
770 target_channels=3;
771 break;
772 }
773 case cmsSigYCbCrData:
774 {
775 target_colorspace=YCbCrColorspace;
776 target_type=(cmsUInt32Number) TYPE_YCbCr_16;
777 target_channels=3;
778 break;
779 }
780 default:
781 {
782 target_colorspace=UndefinedColorspace;
783 target_type=(cmsUInt32Number) TYPE_RGB_16;
784 target_channels=3;
785 break;
786 }
787 }
788 if ((source_colorspace == UndefinedColorspace) ||
789 (target_colorspace == UndefinedColorspace))
790 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
791 name);
792 if ((source_colorspace == GRAYColorspace) &&
cristy4c08aed2011-07-01 19:47:50 +0000793 (IsImageGray(image,exception) == MagickFalse))
cristye1c67a82010-04-10 02:20:57 +0000794 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
795 name);
796 if ((source_colorspace == CMYKColorspace) &&
797 (image->colorspace != CMYKColorspace))
798 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
799 name);
800 if ((source_colorspace == XYZColorspace) &&
801 (image->colorspace != XYZColorspace))
802 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
803 name);
804 if ((source_colorspace == YCbCrColorspace) &&
805 (image->colorspace != YCbCrColorspace))
806 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
807 name);
808 if ((source_colorspace != CMYKColorspace) &&
cristye1c67a82010-04-10 02:20:57 +0000809 (source_colorspace != LabColorspace) &&
810 (source_colorspace != XYZColorspace) &&
811 (source_colorspace != YCbCrColorspace) &&
cristy3d9f5ba2012-06-26 13:37:31 +0000812 (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse))
cristye1c67a82010-04-10 02:20:57 +0000813 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
814 name);
815 switch (image->rendering_intent)
816 {
817 case AbsoluteIntent: intent=INTENT_ABSOLUTE_COLORIMETRIC; break;
818 case PerceptualIntent: intent=INTENT_PERCEPTUAL; break;
819 case RelativeIntent: intent=INTENT_RELATIVE_COLORIMETRIC; break;
820 case SaturationIntent: intent=INTENT_SATURATION; break;
cristye540eb42012-04-16 12:50:34 +0000821 default: intent=INTENT_PERCEPTUAL; break;
cristye1c67a82010-04-10 02:20:57 +0000822 }
823 flags=cmsFLAGS_HIGHRESPRECALC;
824#if defined(cmsFLAGS_BLACKPOINTCOMPENSATION)
825 if (image->black_point_compensation != MagickFalse)
826 flags|=cmsFLAGS_BLACKPOINTCOMPENSATION;
827#endif
cristy7632f502010-06-18 13:26:31 +0000828 transform=AcquireTransformThreadSet(image,source_profile,
829 source_type,target_profile,target_type,intent,flags);
cristye1c67a82010-04-10 02:20:57 +0000830 if (transform == (cmsHTRANSFORM *) NULL)
831 ThrowProfileException(ImageError,"UnableToCreateColorTransform",
832 name);
833 /*
834 Transform image as dictated by the source & target image profiles.
835 */
cristye1c67a82010-04-10 02:20:57 +0000836 source_pixels=AcquirePixelThreadSet(image->columns,source_channels);
837 target_pixels=AcquirePixelThreadSet(image->columns,target_channels);
838 if ((source_pixels == (unsigned short **) NULL) ||
839 (target_pixels == (unsigned short **) NULL))
840 {
841 transform=DestroyTransformThreadSet(transform);
842 ThrowProfileException(ResourceLimitError,
843 "MemoryAllocationFailed",image->filename);
844 }
cristy574cc262011-08-05 01:23:58 +0000845 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristye1c67a82010-04-10 02:20:57 +0000846 {
847 target_pixels=DestroyPixelThreadSet(target_pixels);
848 source_pixels=DestroyPixelThreadSet(source_pixels);
849 transform=DestroyTransformThreadSet(transform);
850 if (source_profile != (cmsHPROFILE) NULL)
851 (void) cmsCloseProfile(source_profile);
852 if (target_profile != (cmsHPROFILE) NULL)
853 (void) cmsCloseProfile(target_profile);
854 return(MagickFalse);
855 }
856 if (target_colorspace == CMYKColorspace)
cristy63240882011-08-05 19:05:27 +0000857 (void) SetImageColorspace(image,target_colorspace,exception);
cristye1c67a82010-04-10 02:20:57 +0000858 status=MagickTrue;
859 progress=0;
cristydb070952012-04-20 14:33:00 +0000860 image_view=AcquireAuthenticCacheView(image,exception);
cristye1c67a82010-04-10 02:20:57 +0000861#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000862 #pragma omp parallel for schedule(static,4) shared(status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000863 dynamic_number_threads(image,image->columns,image->rows,1)
cristye1c67a82010-04-10 02:20:57 +0000864#endif
cristybb503372010-05-27 20:51:26 +0000865 for (y=0; y < (ssize_t) image->rows; y++)
cristy8a20a2d2010-04-08 14:16:27 +0000866 {
cristy5c9e6f22010-09-17 17:31:01 +0000867 const int
868 id = GetOpenMPThreadId();
cristyad740052010-07-03 01:38:03 +0000869
cristy8a20a2d2010-04-08 14:16:27 +0000870 MagickBooleanType
cristye1c67a82010-04-10 02:20:57 +0000871 sync;
872
cristybb503372010-05-27 20:51:26 +0000873 register ssize_t
cristye1c67a82010-04-10 02:20:57 +0000874 x;
875
cristy4c08aed2011-07-01 19:47:50 +0000876 register Quantum
cristye1c67a82010-04-10 02:20:57 +0000877 *restrict q;
878
879 register unsigned short
880 *p;
881
882 if (status == MagickFalse)
883 continue;
884 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
885 exception);
cristyacd2ed22011-08-30 01:44:23 +0000886 if (q == (Quantum *) NULL)
cristye1c67a82010-04-10 02:20:57 +0000887 {
888 status=MagickFalse;
889 continue;
890 }
cristye1c67a82010-04-10 02:20:57 +0000891 p=source_pixels[id];
cristybb503372010-05-27 20:51:26 +0000892 for (x=0; x < (ssize_t) image->columns; x++)
cristye1c67a82010-04-10 02:20:57 +0000893 {
cristy4c08aed2011-07-01 19:47:50 +0000894 *p++=ScaleQuantumToShort(GetPixelRed(image,q));
cristye1c67a82010-04-10 02:20:57 +0000895 if (source_channels > 1)
896 {
cristy4c08aed2011-07-01 19:47:50 +0000897 *p++=ScaleQuantumToShort(GetPixelGreen(image,q));
898 *p++=ScaleQuantumToShort(GetPixelBlue(image,q));
cristye1c67a82010-04-10 02:20:57 +0000899 }
900 if (source_channels > 3)
cristy4c08aed2011-07-01 19:47:50 +0000901 *p++=ScaleQuantumToShort(GetPixelBlack(image,q));
cristyed231572011-07-14 02:18:59 +0000902 q+=GetPixelChannels(image);
cristye1c67a82010-04-10 02:20:57 +0000903 }
904 cmsDoTransform(transform[id],source_pixels[id],target_pixels[id],
905 (unsigned int) image->columns);
906 p=target_pixels[id];
cristyed231572011-07-14 02:18:59 +0000907 q-=image->columns*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +0000908 for (x=0; x < (ssize_t) image->columns; x++)
cristye1c67a82010-04-10 02:20:57 +0000909 {
cristyaf6eb932012-01-01 01:22:59 +0000910 if (target_channels == 1)
911 SetPixelGray(image,ScaleShortToQuantum(*p),q);
912 else
913 SetPixelRed(image,ScaleShortToQuantum(*p),q);
cristye1c67a82010-04-10 02:20:57 +0000914 p++;
915 if (target_channels > 1)
916 {
cristy4c08aed2011-07-01 19:47:50 +0000917 SetPixelGreen(image,ScaleShortToQuantum(*p),q);
cristye1c67a82010-04-10 02:20:57 +0000918 p++;
cristy4c08aed2011-07-01 19:47:50 +0000919 SetPixelBlue(image,ScaleShortToQuantum(*p),q);
cristye1c67a82010-04-10 02:20:57 +0000920 p++;
921 }
922 if (target_channels > 3)
923 {
cristy4c08aed2011-07-01 19:47:50 +0000924 SetPixelBlack(image,ScaleShortToQuantum(*p),q);
cristye1c67a82010-04-10 02:20:57 +0000925 p++;
926 }
cristyed231572011-07-14 02:18:59 +0000927 q+=GetPixelChannels(image);
cristye1c67a82010-04-10 02:20:57 +0000928 }
929 sync=SyncCacheViewAuthenticPixels(image_view,exception);
930 if (sync == MagickFalse)
931 status=MagickFalse;
932 if (image->progress_monitor != (MagickProgressMonitor) NULL)
933 {
934 MagickBooleanType
935 proceed;
cristy8a20a2d2010-04-08 14:16:27 +0000936
937#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000938 #pragma omp critical (MagickCore_ProfileImage)
cristy8a20a2d2010-04-08 14:16:27 +0000939#endif
cristye1c67a82010-04-10 02:20:57 +0000940 proceed=SetImageProgress(image,ProfileImageTag,progress++,
941 image->rows);
942 if (proceed == MagickFalse)
943 status=MagickFalse;
944 }
cristy8a20a2d2010-04-08 14:16:27 +0000945 }
cristye1c67a82010-04-10 02:20:57 +0000946 image_view=DestroyCacheView(image_view);
cristy63240882011-08-05 19:05:27 +0000947 (void) SetImageColorspace(image,target_colorspace,exception);
cristye1c67a82010-04-10 02:20:57 +0000948 switch (signature)
949 {
950 case cmsSigRgbData:
951 {
952 image->type=image->matte == MagickFalse ? TrueColorType :
953 TrueColorMatteType;
954 break;
955 }
956 case cmsSigCmykData:
957 {
958 image->type=image->matte == MagickFalse ? ColorSeparationType :
959 ColorSeparationMatteType;
960 break;
961 }
962 case cmsSigGrayData:
963 {
964 image->type=image->matte == MagickFalse ? GrayscaleType :
965 GrayscaleMatteType;
966 break;
967 }
968 default:
969 break;
970 }
971 target_pixels=DestroyPixelThreadSet(target_pixels);
972 source_pixels=DestroyPixelThreadSet(source_pixels);
973 transform=DestroyTransformThreadSet(transform);
974 if (cmsGetDeviceClass(source_profile) != cmsSigLinkClass)
cristyd15e6592011-10-15 00:13:06 +0000975 status=SetImageProfile(image,name,profile,exception);
cristye1c67a82010-04-10 02:20:57 +0000976 if (target_profile != (cmsHPROFILE) NULL)
977 (void) cmsCloseProfile(target_profile);
cristy8a20a2d2010-04-08 14:16:27 +0000978 }
cristyfbaaf3e2010-04-08 14:53:43 +0000979 (void) cmsCloseProfile(source_profile);
cristy8a20a2d2010-04-08 14:16:27 +0000980 }
cristy3ed852e2009-09-05 21:47:34 +0000981#endif
982 }
cristy3ed852e2009-09-05 21:47:34 +0000983 profile=DestroyStringInfo(profile);
984 return(status);
985}
986
987/*
988%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
989% %
990% %
991% %
992% R e m o v e I m a g e P r o f i l e %
993% %
994% %
995% %
996%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
997%
998% RemoveImageProfile() removes a named profile from the image and returns its
999% value.
1000%
1001% The format of the RemoveImageProfile method is:
1002%
1003% void *RemoveImageProfile(Image *image,const char *name)
1004%
1005% A description of each parameter follows:
1006%
1007% o image: the image.
1008%
1009% o name: the profile name.
1010%
1011*/
1012MagickExport StringInfo *RemoveImageProfile(Image *image,const char *name)
1013{
1014 StringInfo
1015 *profile;
1016
1017 assert(image != (Image *) NULL);
1018 assert(image->signature == MagickSignature);
1019 if (image->debug != MagickFalse)
1020 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1021 if (image->profiles == (SplayTreeInfo *) NULL)
1022 return((StringInfo *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001023 profile=(StringInfo *) RemoveNodeFromSplayTree((SplayTreeInfo *)
1024 image->profiles,name);
1025 return(profile);
1026}
1027
1028/*
1029%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1030% %
1031% %
1032% %
1033% R e s e t P r o f i l e I t e r a t o r %
1034% %
1035% %
1036% %
1037%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1038%
1039% ResetImageProfileIterator() resets the image profile iterator. Use it in
1040% conjunction with GetNextImageProfile() to iterate over all the profiles
1041% associated with an image.
1042%
1043% The format of the ResetImageProfileIterator method is:
1044%
1045% ResetImageProfileIterator(Image *image)
1046%
1047% A description of each parameter follows:
1048%
1049% o image: the image.
1050%
1051*/
1052MagickExport void ResetImageProfileIterator(const Image *image)
1053{
1054 assert(image != (Image *) NULL);
1055 assert(image->signature == MagickSignature);
1056 if (image->debug != MagickFalse)
1057 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1058 if (image->profiles == (SplayTreeInfo *) NULL)
1059 return;
1060 ResetSplayTreeIterator((SplayTreeInfo *) image->profiles);
1061}
1062
1063/*
1064%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1065% %
1066% %
1067% %
1068% S e t I m a g e P r o f i l e %
1069% %
1070% %
1071% %
1072%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1073%
1074% SetImageProfile() adds a named profile to the image. If a profile with the
1075% same name already exists, it is replaced. This method differs from the
1076% ProfileImage() method in that it does not apply CMS color profiles.
1077%
1078% The format of the SetImageProfile method is:
1079%
1080% MagickBooleanType SetImageProfile(Image *image,const char *name,
1081% const StringInfo *profile)
1082%
1083% A description of each parameter follows:
1084%
1085% o image: the image.
1086%
1087% o name: the profile name, for example icc, exif, and 8bim (8bim is the
1088% Photoshop wrapper for iptc profiles).
1089%
1090% o profile: A StringInfo structure that contains the named profile.
1091%
1092*/
1093
1094static void *DestroyProfile(void *profile)
1095{
1096 return((void *) DestroyStringInfo((StringInfo *) profile));
1097}
1098
1099static inline const unsigned char *ReadResourceByte(const unsigned char *p,
1100 unsigned char *quantum)
1101{
1102 *quantum=(*p++);
1103 return(p);
1104}
1105
1106static inline const unsigned char *ReadResourceBytes(const unsigned char *p,
1107 const ssize_t count,unsigned char *quantum)
1108{
1109 register ssize_t
1110 i;
1111
1112 for (i=0; i < count; i++)
1113 *quantum++=(*p++);
1114 return(p);
1115}
1116
1117static inline const unsigned char *ReadResourceLong(const unsigned char *p,
cristybb503372010-05-27 20:51:26 +00001118 size_t *quantum)
cristy3ed852e2009-09-05 21:47:34 +00001119{
cristybb503372010-05-27 20:51:26 +00001120 *quantum=(size_t) (*p++ << 24);
1121 *quantum|=(size_t) (*p++ << 16);
1122 *quantum|=(size_t) (*p++ << 8);
1123 *quantum|=(size_t) (*p++ << 0);
cristy3ed852e2009-09-05 21:47:34 +00001124 return(p);
1125}
1126
1127static inline const unsigned char *ReadResourceShort(const unsigned char *p,
1128 unsigned short *quantum)
1129{
1130 *quantum=(unsigned short) (*p++ << 8);
1131 *quantum|=(unsigned short) (*p++ << 0);
1132 return(p);
1133}
1134
1135static MagickBooleanType GetProfilesFromResourceBlock(Image *image,
cristyd15e6592011-10-15 00:13:06 +00001136 const StringInfo *resource_block,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001137{
1138 const unsigned char
1139 *datum;
1140
1141 register const unsigned char
1142 *p;
1143
1144 size_t
1145 length;
1146
1147 StringInfo
1148 *profile;
1149
1150 unsigned char
1151 length_byte;
1152
cristybb503372010-05-27 20:51:26 +00001153 size_t
cristy3ed852e2009-09-05 21:47:34 +00001154 count;
1155
1156 unsigned short
1157 id;
1158
1159 datum=GetStringInfoDatum(resource_block);
1160 length=GetStringInfoLength(resource_block);
1161 for (p=datum; p < (datum+length-16); )
1162 {
1163 if (LocaleNCompare((char *) p,"8BIM",4) != 0)
1164 break;
1165 p+=4;
1166 p=ReadResourceShort(p,&id);
1167 p=ReadResourceByte(p,&length_byte);
1168 p+=length_byte;
1169 if (((length_byte+1) & 0x01) != 0)
1170 p++;
1171 if (p > (datum+length-4))
1172 break;
1173 p=ReadResourceLong(p,&count);
1174 if ((p > (datum+length-count)) || (count > length))
1175 break;
1176 switch (id)
1177 {
1178 case 0x03ed:
1179 {
1180 unsigned short
1181 resolution;
1182
1183 /*
1184 Resolution.
1185 */
1186 p=ReadResourceShort(p,&resolution)+6;
cristy2a11bef2011-10-28 18:33:11 +00001187 image->resolution.x=(double) resolution;
cristy3ed852e2009-09-05 21:47:34 +00001188 p=ReadResourceShort(p,&resolution)+6;
cristy2a11bef2011-10-28 18:33:11 +00001189 image->resolution.y=(double) resolution;
cristy3ed852e2009-09-05 21:47:34 +00001190 break;
1191 }
1192 case 0x0404:
1193 {
1194 /*
1195 IPTC Profile
1196 */
1197 profile=AcquireStringInfo(count);
1198 SetStringInfoDatum(profile,p);
cristyd15e6592011-10-15 00:13:06 +00001199 (void) SetImageProfile(image,"iptc",profile,exception);
cristy3ed852e2009-09-05 21:47:34 +00001200 profile=DestroyStringInfo(profile);
1201 p+=count;
1202 break;
1203 }
1204 case 0x040c:
1205 {
1206 /*
1207 Thumbnail.
1208 */
1209 p+=count;
1210 break;
1211 }
1212 case 0x040f:
1213 {
1214 /*
1215 ICC Profile.
1216 */
1217 profile=AcquireStringInfo(count);
1218 SetStringInfoDatum(profile,p);
cristyd15e6592011-10-15 00:13:06 +00001219 (void) SetImageProfile(image,"icc",profile,exception);
cristy3ed852e2009-09-05 21:47:34 +00001220 profile=DestroyStringInfo(profile);
1221 p+=count;
1222 break;
1223 }
1224 case 0x0422:
1225 {
1226 /*
1227 EXIF Profile.
1228 */
1229 profile=AcquireStringInfo(count);
1230 SetStringInfoDatum(profile,p);
cristyd15e6592011-10-15 00:13:06 +00001231 (void) SetImageProfile(image,"exif",profile,exception);
cristy3ed852e2009-09-05 21:47:34 +00001232 profile=DestroyStringInfo(profile);
1233 p+=count;
1234 break;
1235 }
1236 case 0x0424:
1237 {
1238 /*
1239 XMP Profile.
1240 */
1241 profile=AcquireStringInfo(count);
1242 SetStringInfoDatum(profile,p);
cristyd15e6592011-10-15 00:13:06 +00001243 (void) SetImageProfile(image,"xmp",profile,exception);
cristy3ed852e2009-09-05 21:47:34 +00001244 profile=DestroyStringInfo(profile);
1245 p+=count;
1246 break;
1247 }
1248 default:
1249 {
1250 p+=count;
1251 break;
1252 }
1253 }
1254 if ((count & 0x01) != 0)
1255 p++;
1256 }
1257 return(MagickTrue);
1258}
1259
1260MagickExport MagickBooleanType SetImageProfile(Image *image,const char *name,
cristyd15e6592011-10-15 00:13:06 +00001261 const StringInfo *profile,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001262{
1263 char
1264 key[MaxTextExtent],
1265 property[MaxTextExtent];
1266
1267 MagickBooleanType
1268 status;
1269
1270 assert(image != (Image *) NULL);
1271 assert(image->signature == MagickSignature);
1272 if (image->debug != MagickFalse)
1273 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1274 if (image->profiles == (SplayTreeInfo *) NULL)
1275 image->profiles=NewSplayTree(CompareSplayTreeString,RelinquishMagickMemory,
1276 DestroyProfile);
1277 (void) CopyMagickString(key,name,MaxTextExtent);
1278 status=AddValueToSplayTree((SplayTreeInfo *) image->profiles,
1279 ConstantString(key),CloneStringInfo(profile));
1280 if ((status != MagickFalse) &&
cristy3ed852e2009-09-05 21:47:34 +00001281 ((LocaleCompare(name,"iptc") == 0) || (LocaleCompare(name,"8bim") == 0)))
cristyd15e6592011-10-15 00:13:06 +00001282 (void) GetProfilesFromResourceBlock(image,profile,exception);
cristy3ed852e2009-09-05 21:47:34 +00001283 /*
1284 Inject profile into image properties.
1285 */
cristyb51dff52011-05-19 16:55:47 +00001286 (void) FormatLocaleString(property,MaxTextExtent,"%s:sans",name);
cristyd15e6592011-10-15 00:13:06 +00001287 (void) GetImageProperty(image,property,exception);
cristy3ed852e2009-09-05 21:47:34 +00001288 return(status);
1289}
1290
1291/*
1292%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1293% %
1294% %
1295% %
1296% S y n c I m a g e P r o f i l e s %
1297% %
1298% %
1299% %
1300%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1301%
1302% SyncImageProfiles() synchronizes image properties with the image profiles.
1303% Currently we only support updating the EXIF resolution and orientation.
1304%
1305% The format of the SyncImageProfiles method is:
1306%
1307% MagickBooleanType SyncImageProfiles(Image *image)
1308%
1309% A description of each parameter follows:
1310%
1311% o image: the image.
1312%
1313*/
1314
1315static inline int ReadProfileByte(unsigned char **p,size_t *length)
1316{
1317 int
1318 c;
1319
1320 if (*length < 1)
1321 return(EOF);
1322 c=(int) (*(*p)++);
1323 (*length)--;
1324 return(c);
1325}
1326
1327static inline unsigned short ReadProfileShort(const EndianType endian,
1328 unsigned char *buffer)
1329{
1330 unsigned short
1331 value;
1332
1333 if (endian == MSBEndian)
1334 {
1335 value=(unsigned short) ((((unsigned char *) buffer)[0] << 8) |
1336 ((unsigned char *) buffer)[1]);
1337 return((unsigned short) (value & 0xffff));
1338 }
1339 value=(unsigned short) ((buffer[1] << 8) | buffer[0]);
1340 return((unsigned short) (value & 0xffff));
1341}
1342
cristybb503372010-05-27 20:51:26 +00001343static inline size_t ReadProfileLong(const EndianType endian,
cristy3ed852e2009-09-05 21:47:34 +00001344 unsigned char *buffer)
1345{
cristybb503372010-05-27 20:51:26 +00001346 size_t
cristy3ed852e2009-09-05 21:47:34 +00001347 value;
1348
1349 if (endian == MSBEndian)
1350 {
cristybb503372010-05-27 20:51:26 +00001351 value=(size_t) ((buffer[0] << 24) | (buffer[1] << 16) |
cristy3ed852e2009-09-05 21:47:34 +00001352 (buffer[2] << 8) | buffer[3]);
cristybb503372010-05-27 20:51:26 +00001353 return((size_t) (value & 0xffffffff));
cristy3ed852e2009-09-05 21:47:34 +00001354 }
cristybb503372010-05-27 20:51:26 +00001355 value=(size_t) ((buffer[3] << 24) | (buffer[2] << 16) |
cristy3ed852e2009-09-05 21:47:34 +00001356 (buffer[1] << 8 ) | (buffer[0]));
cristybb503372010-05-27 20:51:26 +00001357 return((size_t) (value & 0xffffffff));
cristy3ed852e2009-09-05 21:47:34 +00001358}
1359
1360static inline void WriteProfileLong(const EndianType endian,
cristybb503372010-05-27 20:51:26 +00001361 const size_t value,unsigned char *p)
cristy3ed852e2009-09-05 21:47:34 +00001362{
1363 unsigned char
1364 buffer[4];
1365
1366 if (endian == MSBEndian)
1367 {
1368 buffer[0]=(unsigned char) (value >> 24);
1369 buffer[1]=(unsigned char) (value >> 16);
1370 buffer[2]=(unsigned char) (value >> 8);
1371 buffer[3]=(unsigned char) value;
1372 (void) CopyMagickMemory(p,buffer,4);
1373 return;
1374 }
1375 buffer[0]=(unsigned char) value;
1376 buffer[1]=(unsigned char) (value >> 8);
1377 buffer[2]=(unsigned char) (value >> 16);
1378 buffer[3]=(unsigned char) (value >> 24);
1379 (void) CopyMagickMemory(p,buffer,4);
1380}
1381
1382static void WriteProfileShort(const EndianType endian,
1383 const unsigned short value,unsigned char *p)
1384{
1385 unsigned char
1386 buffer[2];
1387
1388 if (endian == MSBEndian)
1389 {
1390 buffer[0]=(unsigned char) (value >> 8);
1391 buffer[1]=(unsigned char) value;
1392 (void) CopyMagickMemory(p,buffer,2);
1393 return;
1394 }
1395 buffer[0]=(unsigned char) value;
1396 buffer[1]=(unsigned char) (value >> 8);
1397 (void) CopyMagickMemory(p,buffer,2);
1398}
1399
cristy7832dc22011-09-05 01:21:53 +00001400MagickPrivate MagickBooleanType SyncImageProfiles(Image *image)
cristy3ed852e2009-09-05 21:47:34 +00001401{
1402#define MaxDirectoryStack 16
1403#define EXIF_DELIMITER "\n"
1404#define EXIF_NUM_FORMATS 12
1405#define TAG_EXIF_OFFSET 0x8769
1406#define TAG_INTEROP_OFFSET 0xa005
1407
1408 typedef struct _DirectoryInfo
1409 {
1410 unsigned char
1411 *directory;
1412
cristybb503372010-05-27 20:51:26 +00001413 size_t
cristy3ed852e2009-09-05 21:47:34 +00001414 entry;
1415 } DirectoryInfo;
1416
1417 DirectoryInfo
1418 directory_stack[MaxDirectoryStack];
1419
1420 EndianType
1421 endian;
1422
cristy3ed852e2009-09-05 21:47:34 +00001423 size_t
cristyba978e12010-09-12 20:26:50 +00001424 entry,
1425 length,
1426 number_entries;
cristy3ed852e2009-09-05 21:47:34 +00001427
cristy9d314ff2011-03-09 01:30:28 +00001428 ssize_t
1429 id,
cristy82544b92012-02-28 20:11:42 +00001430 level,
1431 offset;
cristy9d314ff2011-03-09 01:30:28 +00001432
cristy3ed852e2009-09-05 21:47:34 +00001433 static int
1434 format_bytes[] = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};
1435
1436 StringInfo
1437 *profile;
1438
1439 unsigned char
1440 *directory,
1441 *exif;
1442
cristy3ed852e2009-09-05 21:47:34 +00001443 /*
1444 Set EXIF resolution tag.
1445 */
1446 profile=(StringInfo *) GetImageProfile(image,"EXIF");
1447 if (profile == (StringInfo *) NULL)
1448 return(MagickTrue);
1449 length=GetStringInfoLength(profile);
1450 exif=GetStringInfoDatum(profile);
1451 while (length != 0)
1452 {
1453 if (ReadProfileByte(&exif,&length) != 0x45)
1454 continue;
1455 if (ReadProfileByte(&exif,&length) != 0x78)
1456 continue;
1457 if (ReadProfileByte(&exif,&length) != 0x69)
1458 continue;
1459 if (ReadProfileByte(&exif,&length) != 0x66)
1460 continue;
1461 if (ReadProfileByte(&exif,&length) != 0x00)
1462 continue;
1463 if (ReadProfileByte(&exif,&length) != 0x00)
1464 continue;
1465 break;
1466 }
1467 if (length < 16)
1468 return(MagickFalse);
cristycee97112010-05-28 00:44:52 +00001469 id=(ssize_t) ReadProfileShort(LSBEndian,exif);
cristy3ed852e2009-09-05 21:47:34 +00001470 endian=LSBEndian;
1471 if (id == 0x4949)
1472 endian=LSBEndian;
1473 else
1474 if (id == 0x4D4D)
1475 endian=MSBEndian;
1476 else
1477 return(MagickFalse);
1478 if (ReadProfileShort(endian,exif+2) != 0x002a)
1479 return(MagickFalse);
1480 /*
1481 This the offset to the first IFD.
1482 */
cristy82544b92012-02-28 20:11:42 +00001483 offset=(ssize_t) ((int) ReadProfileLong(endian,exif+4));
cristy64de1572012-02-29 02:44:11 +00001484 if ((offset < 0) || (size_t) offset >= length)
cristy3ed852e2009-09-05 21:47:34 +00001485 return(MagickFalse);
1486 directory=exif+offset;
1487 level=0;
1488 entry=0;
1489 do
1490 {
1491 if (level > 0)
1492 {
1493 level--;
1494 directory=directory_stack[level].directory;
1495 entry=directory_stack[level].entry;
1496 }
1497 /*
1498 Determine how many entries there are in the current IFD.
1499 */
1500 number_entries=ReadProfileShort(endian,directory);
1501 for ( ; entry < number_entries; entry++)
1502 {
cristyba978e12010-09-12 20:26:50 +00001503 int
1504 components;
1505
cristy3ed852e2009-09-05 21:47:34 +00001506 register unsigned char
1507 *p,
1508 *q;
1509
1510 size_t
1511 number_bytes;
1512
cristy9d314ff2011-03-09 01:30:28 +00001513 ssize_t
1514 format,
1515 tag_value;
1516
cristy3ed852e2009-09-05 21:47:34 +00001517 q=(unsigned char *) (directory+2+(12*entry));
cristybb503372010-05-27 20:51:26 +00001518 tag_value=(ssize_t) ReadProfileShort(endian,q);
1519 format=(ssize_t) ReadProfileShort(endian,q+2);
cristy3ed852e2009-09-05 21:47:34 +00001520 if ((format-1) >= EXIF_NUM_FORMATS)
1521 break;
cristy4d14d592012-04-03 12:58:43 +00001522 components=(ssize_t) ((int) ReadProfileLong(endian,q+4));
cristy3ed852e2009-09-05 21:47:34 +00001523 number_bytes=(size_t) components*format_bytes[format];
cristyd228c032012-06-03 15:01:15 +00001524 if ((ssize_t) number_bytes < components)
cristy4d14d592012-04-03 12:58:43 +00001525 break; /* prevent overflow */
cristy3ed852e2009-09-05 21:47:34 +00001526 if (number_bytes <= 4)
1527 p=q+8;
1528 else
1529 {
cristy82544b92012-02-28 20:11:42 +00001530 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001531 offset;
1532
1533 /*
1534 The directory entry contains an offset.
1535 */
cristy82544b92012-02-28 20:11:42 +00001536 offset=(ssize_t) ((int) ReadProfileLong(endian,q+8));
cristy3d8d1f12012-02-29 01:59:28 +00001537 if ((size_t) (offset+number_bytes) > length)
cristy3ed852e2009-09-05 21:47:34 +00001538 continue;
cristy3d8d1f12012-02-29 01:59:28 +00001539 if (~length < number_bytes)
1540 continue; /* prevent overflow */
cristy3ed852e2009-09-05 21:47:34 +00001541 p=(unsigned char *) (exif+offset);
1542 }
1543 switch (tag_value)
1544 {
1545 case 0x011a:
1546 {
cristy82544b92012-02-28 20:11:42 +00001547 (void) WriteProfileLong(endian,(size_t) (image->resolution.x+0.5),p);
cristy3ed852e2009-09-05 21:47:34 +00001548 (void) WriteProfileLong(endian,1UL,p+4);
1549 break;
1550 }
1551 case 0x011b:
1552 {
cristy82544b92012-02-28 20:11:42 +00001553 (void) WriteProfileLong(endian,(size_t) (image->resolution.y+0.5),p);
cristy3ed852e2009-09-05 21:47:34 +00001554 (void) WriteProfileLong(endian,1UL,p+4);
1555 break;
1556 }
1557 case 0x0112:
1558 {
cristy91698032012-04-06 17:02:52 +00001559 if (number_bytes == 4)
1560 {
1561 (void) WriteProfileLong(endian,(size_t) image->orientation,p);
1562 break;
1563 }
cristy82544b92012-02-28 20:11:42 +00001564 (void) WriteProfileShort(endian,(unsigned short) image->orientation,
1565 p);
cristy3ed852e2009-09-05 21:47:34 +00001566 break;
1567 }
1568 case 0x0128:
1569 {
cristy91698032012-04-06 17:02:52 +00001570 if (number_bytes == 4)
1571 {
1572 (void) WriteProfileLong(endian,(size_t) (image->units+1),p);
1573 break;
1574 }
cristy82544b92012-02-28 20:11:42 +00001575 (void) WriteProfileShort(endian,(unsigned short) (image->units+1),p);
cristy3ed852e2009-09-05 21:47:34 +00001576 break;
1577 }
1578 default:
1579 break;
1580 }
1581 if ((tag_value == TAG_EXIF_OFFSET) || (tag_value == TAG_INTEROP_OFFSET))
1582 {
cristy82544b92012-02-28 20:11:42 +00001583 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001584 offset;
1585
cristy82544b92012-02-28 20:11:42 +00001586 offset=(ssize_t) ((int) ReadProfileLong(endian,p));
cristy3d8d1f12012-02-29 01:59:28 +00001587 if (((size_t) offset < length) && (level < (MaxDirectoryStack-2)))
cristy3ed852e2009-09-05 21:47:34 +00001588 {
1589 directory_stack[level].directory=directory;
1590 entry++;
1591 directory_stack[level].entry=entry;
1592 level++;
1593 directory_stack[level].directory=exif+offset;
1594 directory_stack[level].entry=0;
1595 level++;
1596 if ((directory+2+(12*number_entries)) > (exif+length))
1597 break;
cristy82544b92012-02-28 20:11:42 +00001598 offset=(ssize_t) ((int) ReadProfileLong(endian,directory+2+(12*
1599 number_entries)));
cristy3d8d1f12012-02-29 01:59:28 +00001600 if ((offset != 0) && ((size_t) offset < length) &&
cristy3ed852e2009-09-05 21:47:34 +00001601 (level < (MaxDirectoryStack-2)))
1602 {
1603 directory_stack[level].directory=exif+offset;
1604 directory_stack[level].entry=0;
1605 level++;
1606 }
1607 }
1608 break;
1609 }
1610 }
1611 } while (level > 0);
1612 return(MagickTrue);
1613}