2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
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 %
10 % P R R OOO F IIIII LLLLL EEEEE %
13 % MagickCore Image Profile Methods %
20 % Copyright 1999-2014 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
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. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
42 #include "MagickCore/studio.h"
43 #include "MagickCore/attribute.h"
44 #include "MagickCore/cache.h"
45 #include "MagickCore/color.h"
46 #include "MagickCore/colorspace-private.h"
47 #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"
58 #include "MagickCore/profile-private.h"
59 #include "MagickCore/property.h"
60 #include "MagickCore/quantum.h"
61 #include "MagickCore/quantum-private.h"
62 #include "MagickCore/resource_.h"
63 #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"
68 #if defined(MAGICKCORE_LCMS_DELEGATE)
69 #if defined(MAGICKCORE_HAVE_LCMS_LCMS2_H)
71 #include <lcms/lcms2.h>
72 #elif defined(MAGICKCORE_HAVE_LCMS2_H)
75 #elif defined(MAGICKCORE_HAVE_LCMS_LCMS_H)
76 #include <lcms/lcms.h>
85 #if !defined(LCMS_VERSION) || (LCMS_VERSION < 2000)
86 #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
93 #define cmsSigLinkClass icSigLinkClass
94 #define cmsColorSpaceSignature icColorSpaceSignature
95 #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)
107 static MagickBooleanType
108 SetImageProfileInternal(Image *,const char *,const StringInfo *,
109 const MagickBooleanType,ExceptionInfo *);
112 WriteTo8BimProfile(Image *,const char*,const StringInfo *);
132 typedef struct _CMSExceptionInfo
142 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
146 % C l o n e I m a g e P r o f i l e s %
150 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
152 % CloneImageProfiles() clones one or more image profiles.
154 % The format of the CloneImageProfiles method is:
156 % MagickBooleanType CloneImageProfiles(Image *image,
157 % const Image *clone_image)
159 % A description of each parameter follows:
161 % o image: the image.
163 % o clone_image: the clone image.
166 MagickExport MagickBooleanType CloneImageProfiles(Image *image,
167 const Image *clone_image)
169 assert(image != (Image *) NULL);
170 assert(image->signature == MagickSignature);
171 if (image->debug != MagickFalse)
172 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
173 assert(clone_image != (const Image *) NULL);
174 assert(clone_image->signature == MagickSignature);
175 if (clone_image->profiles != (void *) NULL)
177 if (image->profiles != (void *) NULL)
178 DestroyImageProfiles(image);
179 image->profiles=CloneSplayTree((SplayTreeInfo *) clone_image->profiles,
180 (void *(*)(void *)) ConstantString,(void *(*)(void *)) CloneStringInfo);
186 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
190 % D e l e t e I m a g e P r o f i l e %
194 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
196 % DeleteImageProfile() deletes a profile from the image by its name.
198 % The format of the DeleteImageProfile method is:
200 % MagickBooleanTyupe DeleteImageProfile(Image *image,const char *name)
202 % A description of each parameter follows:
204 % o image: the image.
206 % o name: the profile name.
209 MagickExport MagickBooleanType DeleteImageProfile(Image *image,const char *name)
211 assert(image != (Image *) NULL);
212 assert(image->signature == MagickSignature);
213 if (image->debug != MagickFalse)
214 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
215 if (image->profiles == (SplayTreeInfo *) NULL)
217 WriteTo8BimProfile(image,name,(StringInfo *) NULL);
218 return(DeleteNodeFromSplayTree((SplayTreeInfo *) image->profiles,name));
222 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
226 % D e s t r o y I m a g e P r o f i l e s %
230 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
232 % DestroyImageProfiles() releases memory associated with an image profile map.
234 % The format of the DestroyProfiles method is:
236 % void DestroyImageProfiles(Image *image)
238 % A description of each parameter follows:
240 % o image: the image.
243 MagickExport void DestroyImageProfiles(Image *image)
245 if (image->profiles != (SplayTreeInfo *) NULL)
246 image->profiles=DestroySplayTree((SplayTreeInfo *) image->profiles);
250 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
254 % G e t I m a g e P r o f i l e %
258 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
260 % GetImageProfile() gets a profile associated with an image by name.
262 % The format of the GetImageProfile method is:
264 % const StringInfo *GetImageProfile(const Image *image,const char *name)
266 % A description of each parameter follows:
268 % o image: the image.
270 % o name: the profile name.
273 MagickExport const StringInfo *GetImageProfile(const Image *image,
282 assert(image != (Image *) NULL);
283 assert(image->signature == MagickSignature);
284 if (image->debug != MagickFalse)
285 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
286 if (image->profiles == (SplayTreeInfo *) NULL)
287 return((StringInfo *) NULL);
288 (void) CopyMagickString(key,name,MaxTextExtent);
289 profile=(const StringInfo *) GetValueFromSplayTree((SplayTreeInfo *)
290 image->profiles,key);
295 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
299 % G e t N e x t I m a g e P r o f i l e %
303 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
305 % GetNextImageProfile() gets the next profile name for an image.
307 % The format of the GetNextImageProfile method is:
309 % char *GetNextImageProfile(const Image *image)
311 % A description of each parameter follows:
313 % o hash_info: the hash info.
316 MagickExport char *GetNextImageProfile(const Image *image)
318 assert(image != (Image *) NULL);
319 assert(image->signature == MagickSignature);
320 if (image->debug != MagickFalse)
321 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
322 if (image->profiles == (SplayTreeInfo *) NULL)
323 return((char *) NULL);
324 return((char *) GetNextKeyInSplayTree((SplayTreeInfo *) image->profiles));
328 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
332 % P r o f i l e I m a g e %
336 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
338 % ProfileImage() associates, applies, or removes an ICM, IPTC, or generic
339 % profile with / to / from an image. If the profile is NULL, it is removed
340 % from the image otherwise added or applied. Use a name of '*' and a profile
341 % of NULL to remove all profiles from the image.
343 % ICC and ICM profiles are handled as follows: If the image does not have
344 % an associated color profile, the one you provide is associated with the
345 % image and the image pixels are not transformed. Otherwise, the colorspace
346 % transform defined by the existing and new profile are applied to the image
347 % pixels and the new profile is associated with the image.
349 % The format of the ProfileImage method is:
351 % MagickBooleanType ProfileImage(Image *image,const char *name,
352 % const void *datum,const size_t length,const MagickBooleanType clone)
354 % A description of each parameter follows:
356 % o image: the image.
358 % o name: Name of profile to add or remove: ICC, IPTC, or generic profile.
360 % o datum: the profile data.
362 % o length: the length of the profile.
364 % o clone: should be MagickFalse.
368 #if defined(MAGICKCORE_LCMS_DELEGATE)
370 static unsigned short **DestroyPixelThreadSet(unsigned short **pixels)
375 assert(pixels != (unsigned short **) NULL);
376 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
377 if (pixels[i] != (unsigned short *) NULL)
378 pixels[i]=(unsigned short *) RelinquishMagickMemory(pixels[i]);
379 pixels=(unsigned short **) RelinquishMagickMemory(pixels);
383 static unsigned short **AcquirePixelThreadSet(const size_t columns,
384 const size_t channels)
395 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
396 pixels=(unsigned short **) AcquireQuantumMemory(number_threads,
398 if (pixels == (unsigned short **) NULL)
399 return((unsigned short **) NULL);
400 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
401 for (i=0; i < (ssize_t) number_threads; i++)
403 pixels[i]=(unsigned short *) AcquireQuantumMemory(columns,channels*
405 if (pixels[i] == (unsigned short *) NULL)
406 return(DestroyPixelThreadSet(pixels));
411 static cmsHTRANSFORM *DestroyTransformThreadSet(cmsHTRANSFORM *transform)
416 assert(transform != (cmsHTRANSFORM *) NULL);
417 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
418 if (transform[i] != (cmsHTRANSFORM) NULL)
419 cmsDeleteTransform(transform[i]);
420 transform=(cmsHTRANSFORM *) RelinquishMagickMemory(transform);
424 static cmsHTRANSFORM *AcquireTransformThreadSet(Image *image,
425 const cmsHPROFILE source_profile,const cmsUInt32Number source_type,
426 const cmsHPROFILE target_profile,const cmsUInt32Number target_type,
427 const int intent,const cmsUInt32Number flags)
438 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
439 transform=(cmsHTRANSFORM *) AcquireQuantumMemory(number_threads,
441 if (transform == (cmsHTRANSFORM *) NULL)
442 return((cmsHTRANSFORM *) NULL);
443 (void) ResetMagickMemory(transform,0,number_threads*sizeof(*transform));
444 for (i=0; i < (ssize_t) number_threads; i++)
446 transform[i]=cmsCreateTransformTHR((cmsContext) image,source_profile,
447 source_type,target_profile,target_type,intent,flags);
448 if (transform[i] == (cmsHTRANSFORM) NULL)
449 return(DestroyTransformThreadSet(transform));
455 #if defined(MAGICKCORE_LCMS_DELEGATE)
456 #if defined(LCMS_VERSION) && (LCMS_VERSION >= 2000)
457 static void CMSExceptionHandler(cmsContext context,cmsUInt32Number severity,
469 cms_exception=(CMSExceptionInfo *) context;
470 image=cms_exception->image;
471 exception=cms_exception->exception;
472 if (image == (Image *) NULL)
474 (void) ThrowMagickException(exception,GetMagickModule(),ImageWarning,
475 "UnableToTransformColorspace","`%s'","unknown context");
478 if (image->debug != MagickFalse)
479 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"lcms: #%u, %s",
480 severity,message != (char *) NULL ? message : "no message");
481 (void) ThrowMagickException(exception,GetMagickModule(),ImageWarning,
482 "UnableToTransformColorspace","`%s'",image->filename);
485 static int CMSExceptionHandler(int severity,const char *message)
487 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"lcms: #%d, %s",
488 severity,message != (char *) NULL ? message : "no message");
494 MagickExport MagickBooleanType ProfileImage(Image *image,const char *name,
495 const void *datum,const size_t length,ExceptionInfo *exception)
497 #define ProfileImageTag "Profile/Image"
498 #define ThrowProfileException(severity,tag,context) \
500 if (source_profile != (cmsHPROFILE) NULL) \
501 (void) cmsCloseProfile(source_profile); \
502 if (target_profile != (cmsHPROFILE) NULL) \
503 (void) cmsCloseProfile(target_profile); \
504 ThrowBinaryException(severity,tag,context); \
513 assert(image != (Image *) NULL);
514 assert(image->signature == MagickSignature);
515 if (image->debug != MagickFalse)
516 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
517 assert(name != (const char *) NULL);
518 if ((datum == (const void *) NULL) || (length == 0))
531 Delete image profile(s).
533 names=ConstantString(name);
534 (void) SubstituteString(&names,","," ");
535 arguments=StringToArgv(names,&number_arguments);
536 names=DestroyString(names);
537 if (arguments == (char **) NULL)
539 ResetImageProfileIterator(image);
540 for (name=GetNextImageProfile(image); name != (const char *) NULL; )
542 for (i=1; i < (ssize_t) number_arguments; i++)
544 if ((*arguments[i] == '!') &&
545 (LocaleCompare(name,arguments[i]+1) == 0))
547 if (GlobExpression(name,arguments[i],MagickTrue) != MagickFalse)
549 (void) DeleteImageProfile(image,name);
553 name=GetNextImageProfile(image);
555 for (i=0; i < (ssize_t) number_arguments; i++)
556 arguments[i]=DestroyString(arguments[i]);
557 arguments=(char **) RelinquishMagickMemory(arguments);
561 Add a ICC, IPTC, or generic profile to the image.
564 profile=AcquireStringInfo((size_t) length);
565 SetStringInfoDatum(profile,(unsigned char *) datum);
566 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
567 status=SetImageProfile(image,name,profile,exception);
573 icc_profile=GetImageProfile(image,"icc");
574 if ((icc_profile != (const StringInfo *) NULL) &&
575 (CompareStringInfo(icc_profile,profile) == 0))
580 value=GetImageProperty(image,"exif:ColorSpace",exception);
583 if (LocaleCompare(value,"1") != 0)
584 (void) SetsRGBImageProfile(image,exception);
585 value=GetImageProperty(image,"exif:InteroperabilityIndex",exception);
586 if (LocaleCompare(value,"R98.") != 0)
587 (void) SetsRGBImageProfile(image,exception);
588 value=GetImageProperty(image,"exif:InteroperabilityIndex",exception);
589 if (LocaleCompare(value,"R03.") != 0)
590 (void) SetAdobeRGB1998ImageProfile(image,exception);
592 icc_profile=GetImageProfile(image,"icc");
594 if ((icc_profile != (const StringInfo *) NULL) &&
595 (CompareStringInfo(icc_profile,profile) == 0))
597 profile=DestroyStringInfo(profile);
600 #if !defined(MAGICKCORE_LCMS_DELEGATE)
601 (void) ThrowMagickException(exception,GetMagickModule(),
602 MissingDelegateWarning,"DelegateLibrarySupportNotBuiltIn",
603 "'%s' (LCMS)",image->filename);
613 Transform pixel colors as defined by the color profiles.
615 cmsSetLogErrorHandler(CMSExceptionHandler);
616 cms_exception.image=image;
617 cms_exception.exception=exception;
618 (void) cms_exception;
619 source_profile=cmsOpenProfileFromMemTHR((cmsContext) &cms_exception,
620 GetStringInfoDatum(profile),(cmsUInt32Number)
621 GetStringInfoLength(profile));
622 if (source_profile == (cmsHPROFILE) NULL)
623 ThrowBinaryException(ResourceLimitError,
624 "ColorspaceColorProfileMismatch",name);
625 if ((cmsGetDeviceClass(source_profile) != cmsSigLinkClass) &&
626 (icc_profile == (StringInfo *) NULL))
627 status=SetImageProfile(image,name,profile,exception);
637 cmsColorSpaceSignature
668 **restrict source_pixels,
669 **restrict target_pixels;
671 target_profile=(cmsHPROFILE) NULL;
672 if (icc_profile != (StringInfo *) NULL)
674 target_profile=source_profile;
675 source_profile=cmsOpenProfileFromMemTHR((cmsContext)
676 &cms_exception,GetStringInfoDatum(icc_profile),
677 (cmsUInt32Number) GetStringInfoLength(icc_profile));
678 if (source_profile == (cmsHPROFILE) NULL)
679 ThrowProfileException(ResourceLimitError,
680 "ColorspaceColorProfileMismatch",name);
682 switch (cmsGetColorSpace(source_profile))
686 source_colorspace=CMYKColorspace;
687 source_type=(cmsUInt32Number) TYPE_CMYK_16;
693 source_colorspace=GRAYColorspace;
694 source_type=(cmsUInt32Number) TYPE_GRAY_16;
700 source_colorspace=LabColorspace;
701 source_type=(cmsUInt32Number) TYPE_Lab_16;
707 source_colorspace=YUVColorspace;
708 source_type=(cmsUInt32Number) TYPE_YUV_16;
714 source_colorspace=sRGBColorspace;
715 source_type=(cmsUInt32Number) TYPE_RGB_16;
721 source_colorspace=XYZColorspace;
722 source_type=(cmsUInt32Number) TYPE_XYZ_16;
726 case cmsSigYCbCrData:
728 source_colorspace=YCbCrColorspace;
729 source_type=(cmsUInt32Number) TYPE_YCbCr_16;
735 source_colorspace=UndefinedColorspace;
736 source_type=(cmsUInt32Number) TYPE_RGB_16;
741 signature=cmsGetPCS(source_profile);
742 if (target_profile != (cmsHPROFILE) NULL)
743 signature=cmsGetColorSpace(target_profile);
748 target_colorspace=CMYKColorspace;
749 target_type=(cmsUInt32Number) TYPE_CMYK_16;
755 target_colorspace=LabColorspace;
756 target_type=(cmsUInt32Number) TYPE_Lab_16;
762 target_colorspace=GRAYColorspace;
763 target_type=(cmsUInt32Number) TYPE_GRAY_16;
769 target_colorspace=YUVColorspace;
770 target_type=(cmsUInt32Number) TYPE_YUV_16;
776 target_colorspace=sRGBColorspace;
777 target_type=(cmsUInt32Number) TYPE_RGB_16;
783 target_colorspace=XYZColorspace;
784 target_type=(cmsUInt32Number) TYPE_XYZ_16;
788 case cmsSigYCbCrData:
790 target_colorspace=YCbCrColorspace;
791 target_type=(cmsUInt32Number) TYPE_YCbCr_16;
797 target_colorspace=UndefinedColorspace;
798 target_type=(cmsUInt32Number) TYPE_RGB_16;
803 if ((source_colorspace == UndefinedColorspace) ||
804 (target_colorspace == UndefinedColorspace))
805 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
807 if ((source_colorspace == GRAYColorspace) &&
808 (IsImageGray(image,exception) == MagickFalse))
809 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
811 if ((source_colorspace == CMYKColorspace) &&
812 (image->colorspace != CMYKColorspace))
813 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
815 if ((source_colorspace == XYZColorspace) &&
816 (image->colorspace != XYZColorspace))
817 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
819 if ((source_colorspace == YCbCrColorspace) &&
820 (image->colorspace != YCbCrColorspace))
821 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
823 if ((source_colorspace != CMYKColorspace) &&
824 (source_colorspace != LabColorspace) &&
825 (source_colorspace != XYZColorspace) &&
826 (source_colorspace != YCbCrColorspace) &&
827 (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse))
828 ThrowProfileException(ImageError,"ColorspaceColorProfileMismatch",
830 switch (image->rendering_intent)
832 case AbsoluteIntent: intent=INTENT_ABSOLUTE_COLORIMETRIC; break;
833 case PerceptualIntent: intent=INTENT_PERCEPTUAL; break;
834 case RelativeIntent: intent=INTENT_RELATIVE_COLORIMETRIC; break;
835 case SaturationIntent: intent=INTENT_SATURATION; break;
836 default: intent=INTENT_PERCEPTUAL; break;
838 flags=cmsFLAGS_HIGHRESPRECALC;
839 #if defined(cmsFLAGS_BLACKPOINTCOMPENSATION)
840 if (image->black_point_compensation != MagickFalse)
841 flags|=cmsFLAGS_BLACKPOINTCOMPENSATION;
843 transform=AcquireTransformThreadSet(image,source_profile,
844 source_type,target_profile,target_type,intent,flags);
845 if (transform == (cmsHTRANSFORM *) NULL)
846 ThrowProfileException(ImageError,"UnableToCreateColorTransform",
849 Transform image as dictated by the source & target image profiles.
851 source_pixels=AcquirePixelThreadSet(image->columns,source_channels);
852 target_pixels=AcquirePixelThreadSet(image->columns,target_channels);
853 if ((source_pixels == (unsigned short **) NULL) ||
854 (target_pixels == (unsigned short **) NULL))
856 transform=DestroyTransformThreadSet(transform);
857 ThrowProfileException(ResourceLimitError,
858 "MemoryAllocationFailed",image->filename);
860 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
862 target_pixels=DestroyPixelThreadSet(target_pixels);
863 source_pixels=DestroyPixelThreadSet(source_pixels);
864 transform=DestroyTransformThreadSet(transform);
865 if (source_profile != (cmsHPROFILE) NULL)
866 (void) cmsCloseProfile(source_profile);
867 if (target_profile != (cmsHPROFILE) NULL)
868 (void) cmsCloseProfile(target_profile);
871 if (target_colorspace == CMYKColorspace)
872 (void) SetImageColorspace(image,target_colorspace,exception);
875 image_view=AcquireAuthenticCacheView(image,exception);
876 #if defined(MAGICKCORE_OPENMP_SUPPORT)
877 #pragma omp parallel for schedule(static,4) shared(status) \
878 magick_threads(image,image,image->rows,1)
880 for (y=0; y < (ssize_t) image->rows; y++)
883 id = GetOpenMPThreadId();
894 register unsigned short
897 if (status == MagickFalse)
899 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
901 if (q == (Quantum *) NULL)
907 for (x=0; x < (ssize_t) image->columns; x++)
909 *p++=ScaleQuantumToShort(GetPixelRed(image,q));
910 if (source_channels > 1)
912 *p++=ScaleQuantumToShort(GetPixelGreen(image,q));
913 *p++=ScaleQuantumToShort(GetPixelBlue(image,q));
915 if (source_channels > 3)
916 *p++=ScaleQuantumToShort(GetPixelBlack(image,q));
917 q+=GetPixelChannels(image);
919 cmsDoTransform(transform[id],source_pixels[id],target_pixels[id],
920 (unsigned int) image->columns);
922 q-=image->columns*GetPixelChannels(image);
923 for (x=0; x < (ssize_t) image->columns; x++)
925 if (target_channels == 1)
926 SetPixelGray(image,ScaleShortToQuantum(*p),q);
928 SetPixelRed(image,ScaleShortToQuantum(*p),q);
930 if (target_channels > 1)
932 SetPixelGreen(image,ScaleShortToQuantum(*p),q);
934 SetPixelBlue(image,ScaleShortToQuantum(*p),q);
937 if (target_channels > 3)
939 SetPixelBlack(image,ScaleShortToQuantum(*p),q);
942 q+=GetPixelChannels(image);
944 sync=SyncCacheViewAuthenticPixels(image_view,exception);
945 if (sync == MagickFalse)
947 if (image->progress_monitor != (MagickProgressMonitor) NULL)
952 #if defined(MAGICKCORE_OPENMP_SUPPORT)
953 #pragma omp critical (MagickCore_ProfileImage)
955 proceed=SetImageProgress(image,ProfileImageTag,progress++,
957 if (proceed == MagickFalse)
961 image_view=DestroyCacheView(image_view);
962 (void) SetImageColorspace(image,target_colorspace,exception);
967 image->type=image->alpha_trait != BlendPixelTrait ?
968 TrueColorType : TrueColorMatteType;
973 image->type=image->alpha_trait != BlendPixelTrait ?
974 ColorSeparationType : ColorSeparationMatteType;
979 image->type=image->alpha_trait != BlendPixelTrait ?
980 GrayscaleType : GrayscaleMatteType;
986 target_pixels=DestroyPixelThreadSet(target_pixels);
987 source_pixels=DestroyPixelThreadSet(source_pixels);
988 transform=DestroyTransformThreadSet(transform);
989 if (cmsGetDeviceClass(source_profile) != cmsSigLinkClass)
990 status=SetImageProfile(image,name,profile,exception);
991 if (target_profile != (cmsHPROFILE) NULL)
992 (void) cmsCloseProfile(target_profile);
994 (void) cmsCloseProfile(source_profile);
998 profile=DestroyStringInfo(profile);
1003 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1007 % R e m o v e I m a g e P r o f i l e %
1011 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1013 % RemoveImageProfile() removes a named profile from the image and returns its
1016 % The format of the RemoveImageProfile method is:
1018 % void *RemoveImageProfile(Image *image,const char *name)
1020 % A description of each parameter follows:
1022 % o image: the image.
1024 % o name: the profile name.
1027 MagickExport StringInfo *RemoveImageProfile(Image *image,const char *name)
1032 assert(image != (Image *) NULL);
1033 assert(image->signature == MagickSignature);
1034 if (image->debug != MagickFalse)
1035 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1036 if (image->profiles == (SplayTreeInfo *) NULL)
1037 return((StringInfo *) NULL);
1038 WriteTo8BimProfile(image,name,(StringInfo *) NULL);
1039 profile=(StringInfo *) RemoveNodeFromSplayTree((SplayTreeInfo *)
1040 image->profiles,name);
1045 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1049 % R e s e t P r o f i l e I t e r a t o r %
1053 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1055 % ResetImageProfileIterator() resets the image profile iterator. Use it in
1056 % conjunction with GetNextImageProfile() to iterate over all the profiles
1057 % associated with an image.
1059 % The format of the ResetImageProfileIterator method is:
1061 % ResetImageProfileIterator(Image *image)
1063 % A description of each parameter follows:
1065 % o image: the image.
1068 MagickExport void ResetImageProfileIterator(const Image *image)
1070 assert(image != (Image *) NULL);
1071 assert(image->signature == MagickSignature);
1072 if (image->debug != MagickFalse)
1073 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1074 if (image->profiles == (SplayTreeInfo *) NULL)
1076 ResetSplayTreeIterator((SplayTreeInfo *) image->profiles);
1080 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1084 % S e t I m a g e P r o f i l e %
1088 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1090 % SetImageProfile() adds a named profile to the image. If a profile with the
1091 % same name already exists, it is replaced. This method differs from the
1092 % ProfileImage() method in that it does not apply CMS color profiles.
1094 % The format of the SetImageProfile method is:
1096 % MagickBooleanType SetImageProfile(Image *image,const char *name,
1097 % const StringInfo *profile)
1099 % A description of each parameter follows:
1101 % o image: the image.
1103 % o name: the profile name, for example icc, exif, and 8bim (8bim is the
1104 % Photoshop wrapper for iptc profiles).
1106 % o profile: A StringInfo structure that contains the named profile.
1110 static void *DestroyProfile(void *profile)
1112 return((void *) DestroyStringInfo((StringInfo *) profile));
1115 static inline const unsigned char *ReadResourceByte(const unsigned char *p,
1116 unsigned char *quantum)
1122 static inline const unsigned char *ReadResourceBytes(const unsigned char *p,
1123 const ssize_t count,unsigned char *quantum)
1128 for (i=0; i < count; i++)
1133 static inline const unsigned char *ReadResourceLong(const unsigned char *p,
1134 unsigned int *quantum)
1136 *quantum=(size_t) (*p++ << 24);
1137 *quantum|=(size_t) (*p++ << 16);
1138 *quantum|=(size_t) (*p++ << 8);
1139 *quantum|=(size_t) (*p++ << 0);
1143 static inline const unsigned char *ReadResourceShort(const unsigned char *p,
1144 unsigned short *quantum)
1146 *quantum=(unsigned short) (*p++ << 8);
1147 *quantum|=(unsigned short) (*p++ << 0);
1149 }static inline void WriteResourceLong(unsigned char *p,
1150 const unsigned int quantum)
1155 buffer[0]=(unsigned char) (quantum >> 24);
1156 buffer[1]=(unsigned char) (quantum >> 16);
1157 buffer[2]=(unsigned char) (quantum >> 8);
1158 buffer[3]=(unsigned char) quantum;
1159 (void) CopyMagickMemory(p,buffer,4);
1162 static void WriteTo8BimProfile(Image *image,const char *name,
1163 const StringInfo *profile)
1170 register const unsigned char
1192 if (LocaleCompare(name,"icc") == 0)
1194 else if (LocaleCompare(name,"iptc") == 0)
1196 else if (LocaleCompare(name,"xmp") == 0)
1200 profile_8bim=(StringInfo *)GetValueFromSplayTree((SplayTreeInfo *)
1201 image->profiles,"8bim");
1202 if (profile_8bim == (StringInfo *) NULL)
1204 datum=GetStringInfoDatum(profile_8bim);
1205 length=GetStringInfoLength(profile_8bim);
1206 for (p=datum; p < (datum+length-16); )
1209 if (LocaleNCompare((char *) p,"8BIM",4) != 0)
1212 p=ReadResourceShort(p,&id);
1213 p=ReadResourceByte(p,&length_byte);
1215 if (((length_byte+1) & 0x01) != 0)
1217 if (p > (datum+length-4))
1219 p=ReadResourceLong(p,&value);
1220 count=(ssize_t) value;
1221 if ((p > (datum+length-count)) || (count > length))
1223 if ((count & 0x01) != 0)
1225 if (id == profile_id)
1238 rest=(datum+length)-(p+count);
1239 if (profile == (StringInfo *) NULL)
1242 new_profile=AcquireStringInfo(offset+rest);
1243 (void) CopyMagickMemory(new_profile->datum,datum,offset);
1248 new_count=profile->length;
1249 if ((new_count & 0x01) != 0)
1251 new_profile=AcquireStringInfo(offset+new_count+rest);
1252 (void) CopyMagickMemory(new_profile->datum,datum,offset-4);
1253 WriteResourceLong(new_profile->datum+offset-4,profile->length);
1254 (void) CopyMagickMemory(new_profile->datum+offset,profile->datum,
1257 (void) CopyMagickMemory(new_profile->datum+offset+new_count,p+count,
1259 (void) AddValueToSplayTree((SplayTreeInfo *) image->profiles,
1260 ConstantString("8bim"),CloneStringInfo(new_profile));
1261 new_profile=DestroyStringInfo(new_profile);
1269 static void GetProfilesFromResourceBlock(Image *image,
1270 const StringInfo *resource_block,ExceptionInfo *exception)
1275 register const unsigned char
1296 datum=GetStringInfoDatum(resource_block);
1297 length=GetStringInfoLength(resource_block);
1298 for (p=datum; p < (datum+length-16); )
1300 if (LocaleNCompare((char *) p,"8BIM",4) != 0)
1303 p=ReadResourceShort(p,&id);
1304 p=ReadResourceByte(p,&length_byte);
1306 if (((length_byte+1) & 0x01) != 0)
1308 if (p > (datum+length-4))
1310 p=ReadResourceLong(p,&value);
1311 count=(ssize_t) value;
1312 if ((p > (datum+length-count)) || (count > length))
1327 p=ReadResourceLong(p,&resolution);
1328 image->resolution.x=((double) resolution)/65536.0;
1329 p=ReadResourceShort(p,&units)+2;
1330 p=ReadResourceLong(p,&resolution)+4;
1331 image->resolution.y=((double) resolution)/65536.0;
1333 Values are always stored as pixels per inch.
1335 if ((ResolutionType) units != PixelsPerCentimeterResolution)
1336 image->units=PixelsPerInchResolution;
1339 image->units=PixelsPerCentimeterResolution;
1340 image->resolution.x/=2.54;
1341 image->resolution.y/=2.54;
1350 profile=AcquireStringInfo(count);
1351 SetStringInfoDatum(profile,p);
1352 (void) SetImageProfileInternal(image,"iptc",profile,MagickTrue,
1354 profile=DestroyStringInfo(profile);
1371 profile=AcquireStringInfo(count);
1372 SetStringInfoDatum(profile,p);
1373 (void) SetImageProfileInternal(image,"icc",profile,MagickTrue,
1375 profile=DestroyStringInfo(profile);
1384 profile=AcquireStringInfo(count);
1385 SetStringInfoDatum(profile,p);
1386 (void) SetImageProfileInternal(image,"exif",profile,MagickTrue,
1388 profile=DestroyStringInfo(profile);
1397 profile=AcquireStringInfo(count);
1398 SetStringInfoDatum(profile,p);
1399 (void) SetImageProfileInternal(image,"xmp",profile,MagickTrue,
1401 profile=DestroyStringInfo(profile);
1411 if ((count & 0x01) != 0)
1416 static MagickBooleanType SetImageProfileInternal(Image *image,const char *name,
1417 const StringInfo *profile,const MagickBooleanType recursive,
1418 ExceptionInfo *exception)
1422 property[MaxTextExtent];
1427 assert(image != (Image *) NULL);
1428 assert(image->signature == MagickSignature);
1429 if (image->debug != MagickFalse)
1430 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1431 if (image->profiles == (SplayTreeInfo *) NULL)
1432 image->profiles=NewSplayTree(CompareSplayTreeString,RelinquishMagickMemory,
1434 (void) CopyMagickString(key,name,MaxTextExtent);
1436 status=AddValueToSplayTree((SplayTreeInfo *) image->profiles,
1437 ConstantString(key),CloneStringInfo(profile));
1438 if (status != MagickFalse)
1440 if (LocaleCompare(name,"8bim") == 0)
1441 GetProfilesFromResourceBlock(image,profile,exception);
1442 else if (recursive == MagickFalse)
1443 WriteTo8BimProfile(image,name,profile);
1446 Inject profile into image properties.
1448 (void) FormatLocaleString(property,MaxTextExtent,"%s:*",name);
1449 (void) GetImageProperty(image,property,exception);
1453 MagickExport MagickBooleanType SetImageProfile(Image *image,const char *name,
1454 const StringInfo *profile,ExceptionInfo *exception)
1456 return(SetImageProfileInternal(image,name,profile,MagickFalse,exception));
1460 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1464 % S y n c I m a g e P r o f i l e s %
1468 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1470 % SyncImageProfiles() synchronizes image properties with the image profiles.
1471 % Currently we only support updating the EXIF resolution and orientation.
1473 % The format of the SyncImageProfiles method is:
1475 % MagickBooleanType SyncImageProfiles(Image *image)
1477 % A description of each parameter follows:
1479 % o image: the image.
1483 static inline int ReadProfileByte(unsigned char **p,size_t *length)
1495 static inline unsigned short ReadProfileShort(const EndianType endian,
1496 unsigned char *buffer)
1501 if (endian == LSBEndian)
1503 value=(unsigned short) ((buffer[1] << 8) | buffer[0]);
1504 return((unsigned short) (value & 0xffff));
1506 value=(unsigned short) ((((unsigned char *) buffer)[0] << 8) |
1507 ((unsigned char *) buffer)[1]);
1508 return((unsigned short) (value & 0xffff));
1511 static inline size_t ReadProfileLong(const EndianType endian,
1512 unsigned char *buffer)
1517 if (endian == LSBEndian)
1519 value=(size_t) ((buffer[3] << 24) | (buffer[2] << 16) |
1520 (buffer[1] << 8 ) | (buffer[0]));
1521 return((size_t) (value & 0xffffffff));
1523 value=(size_t) ((buffer[0] << 24) | (buffer[1] << 16) |
1524 (buffer[2] << 8) | buffer[3]);
1525 return((size_t) (value & 0xffffffff));
1528 static inline size_t ReadProfileMSBLong(unsigned char **p,
1537 value=ReadProfileLong(MSBEndian,*p);
1543 static inline unsigned short ReadProfileMSBShort(unsigned char **p,
1552 value=ReadProfileShort(MSBEndian,*p);
1558 static inline void WriteProfileLong(const EndianType endian,
1559 const size_t value,unsigned char *p)
1564 if (endian == LSBEndian)
1566 buffer[0]=(unsigned char) value;
1567 buffer[1]=(unsigned char) (value >> 8);
1568 buffer[2]=(unsigned char) (value >> 16);
1569 buffer[3]=(unsigned char) (value >> 24);
1570 (void) CopyMagickMemory(p,buffer,4);
1573 buffer[0]=(unsigned char) (value >> 24);
1574 buffer[1]=(unsigned char) (value >> 16);
1575 buffer[2]=(unsigned char) (value >> 8);
1576 buffer[3]=(unsigned char) value;
1577 (void) CopyMagickMemory(p,buffer,4);
1580 static void WriteProfileShort(const EndianType endian,
1581 const unsigned short value,unsigned char *p)
1586 if (endian == LSBEndian)
1588 buffer[0]=(unsigned char) value;
1589 buffer[1]=(unsigned char) (value >> 8);
1590 (void) CopyMagickMemory(p,buffer,2);
1593 buffer[0]=(unsigned char) (value >> 8);
1594 buffer[1]=(unsigned char) value;
1595 (void) CopyMagickMemory(p,buffer,2);
1598 static MagickBooleanType Sync8BimProfile(Image *image,StringInfo *profile)
1610 length=GetStringInfoLength(profile);
1611 p=GetStringInfoDatum(profile);
1614 if (ReadProfileByte(&p,&length) != 0x38)
1616 if (ReadProfileByte(&p,&length) != 0x42)
1618 if (ReadProfileByte(&p,&length) != 0x49)
1620 if (ReadProfileByte(&p,&length) != 0x4D)
1623 return(MagickFalse);
1624 id=ReadProfileMSBShort(&p,&length);
1625 count=ReadProfileByte(&p,&length);
1627 return(MagickFalse);
1629 if ((*p & 0x01) == 0)
1631 count=ReadProfileMSBLong(&p,&length);
1633 return(MagickFalse);
1634 if (id == 0x3ED && count == 16)
1636 if (image->units == PixelsPerCentimeterResolution)
1637 WriteProfileLong(MSBEndian, (unsigned int) (image->resolution.x*2.54*
1640 WriteProfileLong(MSBEndian, (unsigned int) (image->resolution.x*
1642 WriteProfileShort(MSBEndian,(unsigned short) image->units,p+4);
1643 if (image->units == PixelsPerCentimeterResolution)
1644 WriteProfileLong(MSBEndian, (unsigned int) (image->resolution.y*2.54*
1647 WriteProfileLong(MSBEndian, (unsigned int) (image->resolution.y*
1649 WriteProfileShort(MSBEndian,(unsigned short) image->units,p+12);
1657 MagickBooleanType SyncExifProfile(Image *image,StringInfo *profile)
1659 #define MaxDirectoryStack 16
1660 #define EXIF_DELIMITER "\n"
1661 #define EXIF_NUM_FORMATS 12
1662 #define TAG_EXIF_OFFSET 0x8769
1663 #define TAG_INTEROP_OFFSET 0xa005
1665 typedef struct _DirectoryInfo
1675 directory_stack[MaxDirectoryStack];
1691 format_bytes[] = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};
1698 Set EXIF resolution tag.
1700 length=GetStringInfoLength(profile);
1701 exif=GetStringInfoDatum(profile);
1703 return(MagickFalse);
1704 id=(ssize_t) ReadProfileShort(LSBEndian,exif);
1705 if ((id != 0x4949) && (id != 0x4D4D))
1709 if (ReadProfileByte(&exif,&length) != 0x45)
1711 if (ReadProfileByte(&exif,&length) != 0x78)
1713 if (ReadProfileByte(&exif,&length) != 0x69)
1715 if (ReadProfileByte(&exif,&length) != 0x66)
1717 if (ReadProfileByte(&exif,&length) != 0x00)
1719 if (ReadProfileByte(&exif,&length) != 0x00)
1724 return(MagickFalse);
1725 id=(ssize_t) ReadProfileShort(LSBEndian,exif);
1734 return(MagickFalse);
1735 if (ReadProfileShort(endian,exif+2) != 0x002a)
1736 return(MagickFalse);
1738 This the offset to the first IFD.
1740 offset=(ssize_t) ((int) ReadProfileLong(endian,exif+4));
1741 if ((offset < 0) || (size_t) offset >= length)
1742 return(MagickFalse);
1743 directory=exif+offset;
1751 directory=directory_stack[level].directory;
1752 entry=directory_stack[level].entry;
1755 Determine how many entries there are in the current IFD.
1757 number_entries=ReadProfileShort(endian,directory);
1758 for ( ; entry < number_entries; entry++)
1763 register unsigned char
1774 q=(unsigned char *) (directory+2+(12*entry));
1775 tag_value=(ssize_t) ReadProfileShort(endian,q);
1776 format=(ssize_t) ReadProfileShort(endian,q+2);
1777 if ((format-1) >= EXIF_NUM_FORMATS)
1779 components=(ssize_t) ((int) ReadProfileLong(endian,q+4));
1780 number_bytes=(size_t) components*format_bytes[format];
1781 if ((ssize_t) number_bytes < components)
1782 break; /* prevent overflow */
1783 if (number_bytes <= 4)
1791 The directory entry contains an offset.
1793 offset=(ssize_t) ((int) ReadProfileLong(endian,q+8));
1794 if ((size_t) (offset+number_bytes) > length)
1796 if (~length < number_bytes)
1797 continue; /* prevent overflow */
1798 p=(unsigned char *) (exif+offset);
1804 (void) WriteProfileLong(endian,(size_t) (image->resolution.x+0.5),p);
1805 (void) WriteProfileLong(endian,1UL,p+4);
1810 (void) WriteProfileLong(endian,(size_t) (image->resolution.y+0.5),p);
1811 (void) WriteProfileLong(endian,1UL,p+4);
1816 if (number_bytes == 4)
1818 (void) WriteProfileLong(endian,(size_t) image->orientation,p);
1821 (void) WriteProfileShort(endian,(unsigned short) image->orientation,
1827 if (number_bytes == 4)
1829 (void) WriteProfileLong(endian,(size_t) (image->units+1),p);
1832 (void) WriteProfileShort(endian,(unsigned short) (image->units+1),p);
1838 if ((tag_value == TAG_EXIF_OFFSET) || (tag_value == TAG_INTEROP_OFFSET))
1843 offset=(ssize_t) ((int) ReadProfileLong(endian,p));
1844 if (((size_t) offset < length) && (level < (MaxDirectoryStack-2)))
1846 directory_stack[level].directory=directory;
1848 directory_stack[level].entry=entry;
1850 directory_stack[level].directory=exif+offset;
1851 directory_stack[level].entry=0;
1853 if ((directory+2+(12*number_entries)) > (exif+length))
1855 offset=(ssize_t) ((int) ReadProfileLong(endian,directory+2+(12*
1857 if ((offset != 0) && ((size_t) offset < length) &&
1858 (level < (MaxDirectoryStack-2)))
1860 directory_stack[level].directory=exif+offset;
1861 directory_stack[level].entry=0;
1868 } while (level > 0);
1872 MagickPrivate MagickBooleanType SyncImageProfiles(Image *image)
1881 profile=(StringInfo *) GetImageProfile(image,"8BIM");
1882 if (profile != (StringInfo *) NULL)
1883 if (Sync8BimProfile(image,profile) == MagickFalse)
1885 profile=(StringInfo *) GetImageProfile(image,"EXIF");
1886 if (profile != (StringInfo *) NULL)
1887 if (SyncExifProfile(image,profile) == MagickFalse)