/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % CCCC OOO L OOO RRRR % % C O O L O O R R % % C O O L O O RRRR % % C O O L O O R R % % CCCC OOO LLLLL OOO R R % % % % % % MagickCore Color Methods % % % % Software Design % % John Cristy % % July 1992 % % % % % % Copyright 1999-2009 ImageMagick Studio LLC, a non-profit organization % % dedicated to making software imaging solutions freely available. % % % % You may not use this file except in compliance with the License. You may % % obtain a copy of the License at % % % % http://www.imagemagick.org/script/license.php % % % % Unless required by applicable law or agreed to in writing, software % % distributed under the License is distributed on an "AS IS" BASIS, % % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % % See the License for the specific language governing permissions and % % limitations under the License. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % We use linked-lists because splay-trees do not currently support duplicate % key / value pairs (.e.g X11 green compliance and SVG green compliance). % */ /* Include declarations. */ #include "magick/studio.h" #include "magick/blob.h" #include "magick/cache-view.h" #include "magick/cache.h" #include "magick/color.h" #include "magick/color-private.h" #include "magick/client.h" #include "magick/configure.h" #include "magick/exception.h" #include "magick/exception-private.h" #include "magick/gem.h" #include "magick/geometry.h" #include "magick/image-private.h" #include "magick/memory_.h" #include "magick/monitor.h" #include "magick/monitor-private.h" #include "magick/option.h" #include "magick/pixel-private.h" #include "magick/quantize.h" #include "magick/quantum.h" #include "magick/semaphore.h" #include "magick/string_.h" #include "magick/token.h" #include "magick/utility.h" #include "magick/xml-tree.h" /* Define declarations. */ #define ColorFilename "colors.xml" #define MaxTreeDepth 8 #define NodesInAList 1536 /* Declare color map. */ static const char *ColorMap = (const char *) "" "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; /* Typedef declarations. */ typedef struct _NodeInfo { struct _NodeInfo *child[16]; ColorPacket *list; MagickSizeType number_unique; unsigned long level; } NodeInfo; typedef struct _Nodes { NodeInfo nodes[NodesInAList]; struct _Nodes *next; } Nodes; typedef struct _CubeInfo { NodeInfo *root; long x, progress; unsigned long colors, free_nodes; NodeInfo *node_info; Nodes *node_queue; } CubeInfo; /* Static declarations. */ static LinkedListInfo *color_list = (LinkedListInfo *) NULL; static SemaphoreInfo *color_semaphore = (SemaphoreInfo *) NULL; static volatile MagickBooleanType instantiate_color = MagickFalse; /* Forward declarations. */ static CubeInfo *GetCubeInfo(void); static NodeInfo *GetNodeInfo(CubeInfo *,const unsigned long); static MagickBooleanType InitializeColorList(ExceptionInfo *), LoadColorLists(const char *,ExceptionInfo *); static void DestroyColorCube(const Image *,NodeInfo *); /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + C l a s s i f y I m a g e C o l o r s % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ClassifyImageColors() builds a populated CubeInfo tree for the specified % image. The returned tree should be deallocated using DestroyCubeInfo() % once it is no longer needed. % % The format of the ClassifyImageColors() method is: % % CubeInfo *ClassifyImageColors(const Image *image, % ExceptionInfo *exception) % % A description of each parameter follows. % % o image: the image. % % o exception: return any errors or warnings in this structure. % */ static inline unsigned long ColorToNodeId(const Image *image, const MagickPixelPacket *pixel,unsigned long index) { unsigned long id; id=(unsigned long) ( ((ScaleQuantumToChar(RoundToQuantum(pixel->red)) >> index) & 0x01) | ((ScaleQuantumToChar(RoundToQuantum(pixel->green)) >> index) & 0x01) << 1 | ((ScaleQuantumToChar(RoundToQuantum(pixel->blue)) >> index) & 0x01) << 2); if (image->matte != MagickFalse) id|=((ScaleQuantumToChar(RoundToQuantum(pixel->opacity)) >> index) & 0x01) << 3; return(id); } static CubeInfo *ClassifyImageColors(const Image *image, ExceptionInfo *exception) { #define EvaluateImageTag " Compute image colors... " CubeInfo *cube_info; long y; MagickBooleanType proceed; MagickPixelPacket pixel, target; NodeInfo *node_info; register const IndexPacket *indexes; register const PixelPacket *p; register long i, x; register unsigned long id, index, level; CacheView *image_view; /* Initialize color description tree. */ assert(image != (const Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); cube_info=GetCubeInfo(); if (cube_info == (CubeInfo *) NULL) { (void) ThrowMagickException(exception,GetMagickModule(), ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); return(cube_info); } GetMagickPixelPacket(image,&pixel); GetMagickPixelPacket(image,&target); image_view=AcquireCacheView(image); for (y=0; y < (long) image->rows; y++) { p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); if (p == (const PixelPacket *) NULL) break; indexes=GetCacheViewVirtualIndexQueue(image_view); for (x=0; x < (long) image->columns; x++) { /* Start at the root and proceed level by level. */ node_info=cube_info->root; index=MaxTreeDepth-1; for (level=1; level < MaxTreeDepth; level++) { SetMagickPixelPacket(image,p,indexes+x,&pixel); id=ColorToNodeId(image,&pixel,index); if (node_info->child[id] == (NodeInfo *) NULL) { node_info->child[id]=GetNodeInfo(cube_info,level); if (node_info->child[id] == (NodeInfo *) NULL) { (void) ThrowMagickException(exception,GetMagickModule(), ResourceLimitError,"MemoryAllocationFailed","`%s'", image->filename); return(0); } } node_info=node_info->child[id]; index--; } for (i=0; i < (long) node_info->number_unique; i++) { SetMagickPixelPacket(image,&node_info->list[i].pixel, &node_info->list[i].index,&target); if (IsMagickColorEqual(&pixel,&target) != MagickFalse) break; } if (i < (long) node_info->number_unique) node_info->list[i].count++; else { if (node_info->number_unique == 0) node_info->list=(ColorPacket *) AcquireMagickMemory( sizeof(*node_info->list)); else node_info->list=(ColorPacket *) ResizeQuantumMemory(node_info->list, (size_t) (i+1),sizeof(*node_info->list)); if (node_info->list == (ColorPacket *) NULL) { (void) ThrowMagickException(exception,GetMagickModule(), ResourceLimitError,"MemoryAllocationFailed","`%s'", image->filename); return(0); } node_info->list[i].pixel=(*p); if ((image->colorspace == CMYKColorspace) || (image->storage_class == PseudoClass)) node_info->list[i].index=indexes[x]; node_info->list[i].count=1; node_info->number_unique++; cube_info->colors++; } p++; } proceed=SetImageProgress(image,EvaluateImageTag,y,image->rows); if (proceed == MagickFalse) break; } image_view=DestroyCacheView(image_view); return(cube_info); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + C o n c a t e n a t e C o l o r C o m p o n e n t % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ConcatenateColorComponent() returns the pixel as a canonical string. % % The format of the ConcatenateColorComponent() method is: % % void ConcatenateColorComponent(const MagickPixelPacket *pixel, % const ChannelType channel,const ComplianceType compliance,char *tuple) % % A description of each parameter follows. % % o pixel: The pixel. % % channel: The channel. % % o compliance: Adhere to this color standard: SVG, X11, or XPM. % % tuple: The color tuple. % */ MagickExport void ConcatenateColorComponent(const MagickPixelPacket *pixel, const ChannelType channel,const ComplianceType compliance,char *tuple) { char component[MaxTextExtent]; MagickRealType color; color=0.0; switch (channel) { case RedChannel: { color=pixel->red; break; } case GreenChannel: { color=pixel->green; break; } case BlueChannel: { color=pixel->blue; break; } case AlphaChannel: { color=QuantumRange-pixel->opacity; break; } case IndexChannel: { color=pixel->index; break; } default: break; } if (compliance != SVGCompliance) { if (pixel->depth > 16) { (void) FormatMagickString(component,MaxTextExtent,"%10lu", (unsigned long) ScaleQuantumToLong(RoundToQuantum(color))); (void) ConcatenateMagickString(tuple,component,MaxTextExtent); return; } if (pixel->depth > 8) { (void) FormatMagickString(component,MaxTextExtent,"%5d", ScaleQuantumToShort(RoundToQuantum(color))); (void) ConcatenateMagickString(tuple,component,MaxTextExtent); return; } (void) FormatMagickString(component,MaxTextExtent,"%3d", ScaleQuantumToChar(RoundToQuantum(color))); (void) ConcatenateMagickString(tuple,component,MaxTextExtent); return; } if (channel == OpacityChannel) { (void) FormatMagickString(component,MaxTextExtent,"%g", (double) (QuantumScale*color)); (void) ConcatenateMagickString(tuple,component,MaxTextExtent); return; } if (pixel->depth > 8) { (void) FormatMagickString(component,MaxTextExtent,"%g%%", (double) (100.0*QuantumScale*color)); (void) ConcatenateMagickString(tuple,component,MaxTextExtent); return; } (void) FormatMagickString(component,MaxTextExtent,"%d", ScaleQuantumToChar(RoundToQuantum(color))); (void) ConcatenateMagickString(tuple,component,MaxTextExtent); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + D e f i n e I m a g e H i s t o g r a m % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % DefineImageHistogram() traverses the color cube tree and notes each colormap % entry. A colormap entry is any node in the color cube tree where the % of unique colors is not zero. % % The format of the DefineImageHistogram method is: % % DefineImageHistogram(const Image *image,NodeInfo *node_info, % ColorPacket **unique_colors) % % A description of each parameter follows. % % o image: the image. % % o node_info: the address of a structure of type NodeInfo which points to a % node in the color cube tree that is to be pruned. % % o histogram: the image histogram. % */ static void DefineImageHistogram(const Image *image,NodeInfo *node_info, ColorPacket **histogram) { register long i; unsigned long number_children; /* Traverse any children. */ number_children=image->matte == MagickFalse ? 8UL : 16UL; for (i=0; i < (long) number_children; i++) if (node_info->child[i] != (NodeInfo *) NULL) DefineImageHistogram(image,node_info->child[i],histogram); if (node_info->level == (MaxTreeDepth-1)) { register ColorPacket *p; p=node_info->list; for (i=0; i < (long) node_info->number_unique; i++) { (*histogram)->pixel=p->pixel; (*histogram)->index=p->index; (*histogram)->count=p->count; (*histogram)++; p++; } } } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + D e s t r o y C o l o r L i s t % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % DestroyColorList() deallocates memory associated with the color list. % % The format of the DestroyColorList method is: % % DestroyColorList(void) % */ static void *DestroyColorElement(void *color_info) { register ColorInfo *p; p=(ColorInfo *) color_info; if (p->path != (char *) NULL) p->path=DestroyString(p->path); if (p->name != (char *) NULL) p->name=DestroyString(p->name); p=(ColorInfo *) RelinquishMagickMemory(p); return((void *) NULL); } MagickExport void DestroyColorList(void) { AcquireSemaphoreInfo(&color_semaphore); if (color_list != (LinkedListInfo *) NULL) color_list=DestroyLinkedList(color_list,DestroyColorElement); instantiate_color=MagickFalse; RelinquishSemaphoreInfo(color_semaphore); DestroySemaphoreInfo(&color_semaphore); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + D e s t r o y C u b e I n f o % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % DestroyCubeInfo() deallocates memory associated with a CubeInfo structure. % % The format of the DestroyCubeInfo method is: % % DestroyCubeInfo(const Image *image,CubeInfo *cube_info) % % A description of each parameter follows: % % o image: the image. % % o cube_info: the address of a structure of type CubeInfo. % */ static CubeInfo *DestroyCubeInfo(const Image *image,CubeInfo *cube_info) { register Nodes *nodes; /* Release color cube tree storage. */ DestroyColorCube(image,cube_info->root); do { nodes=cube_info->node_queue->next; cube_info->node_queue=(Nodes *) RelinquishMagickMemory(cube_info->node_queue); cube_info->node_queue=nodes; } while (cube_info->node_queue != (Nodes *) NULL); return((CubeInfo *) RelinquishMagickMemory(cube_info)); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + D e s t r o y C o l o r C u b e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % DestroyColorCube() traverses the color cube tree and frees the list of % unique colors. % % The format of the DestroyColorCube method is: % % void DestroyColorCube(const Image *image,const NodeInfo *node_info) % % A description of each parameter follows. % % o image: the image. % % o node_info: the address of a structure of type NodeInfo which points to a % node in the color cube tree that is to be pruned. % */ static void DestroyColorCube(const Image *image,NodeInfo *node_info) { register long i; unsigned long number_children; /* Traverse any children. */ number_children=image->matte == MagickFalse ? 8UL : 16UL; for (i=0; i < (long) number_children; i++) if (node_info->child[i] != (NodeInfo *) NULL) DestroyColorCube(image,node_info->child[i]); if (node_info->list != (ColorPacket *) NULL) node_info->list=(ColorPacket *) RelinquishMagickMemory(node_info->list); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + G e t C o l o r I n f o % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % GetColorInfo() searches the color list for the specified name and if found % returns attributes for that color. % % The format of the GetColorInfo method is: % % const PixelPacket *GetColorInfo(const char *name, % ExceptionInfo *exception) % % A description of each parameter follows: % % o color_info: search the color list for the specified name and if found % return attributes for that color. % % o name: the color name. % % o exception: return any errors or warnings in this structure. % */ MagickExport const ColorInfo *GetColorInfo(const char *name, ExceptionInfo *exception) { char colorname[MaxTextExtent]; register const ColorInfo *p; register char *q; assert(exception != (ExceptionInfo *) NULL); if ((color_list == (LinkedListInfo *) NULL) || (instantiate_color == MagickFalse)) if (InitializeColorList(exception) == MagickFalse) return((const ColorInfo *) NULL); if ((color_list == (LinkedListInfo *) NULL) || (IsLinkedListEmpty(color_list) != MagickFalse)) return((const ColorInfo *) NULL); if ((name == (const char *) NULL) || (LocaleCompare(name,"*") == 0)) return((const ColorInfo *) GetValueFromLinkedList(color_list,0)); /* Strip names of whitespace. */ (void) CopyMagickString(colorname,name,MaxTextExtent); for (q=colorname; *q != '\0'; q++) { if (isspace((int) ((unsigned char) *q)) == 0) continue; (void) CopyMagickString(q,q+1,MaxTextExtent); q--; } /* Search for color tag. */ AcquireSemaphoreInfo(&color_semaphore); ResetLinkedListIterator(color_list); p=(const ColorInfo *) GetNextValueInLinkedList(color_list); while (p != (const ColorInfo *) NULL) { if (LocaleCompare(colorname,p->name) == 0) break; p=(const ColorInfo *) GetNextValueInLinkedList(color_list); } if (p == (ColorInfo *) NULL) (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning, "UnrecognizedColor","`%s'",name); else (void) InsertValueInLinkedList(color_list,0, RemoveElementByValueFromLinkedList(color_list,p)); RelinquishSemaphoreInfo(color_semaphore); return(p); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % G e t C o l o r I n f o L i s t % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % GetColorInfoList() returns any colors that match the specified pattern. % % The format of the GetColorInfoList function is: % % const ColorInfo **GetColorInfoList(const char *pattern, % unsigned long *number_colors,ExceptionInfo *exception) % % A description of each parameter follows: % % o pattern: Specifies a pointer to a text string containing a pattern. % % o number_colors: This integer returns the number of colors in the list. % % o exception: return any errors or warnings in this structure. % */ #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif static int ColorInfoCompare(const void *x,const void *y) { const ColorInfo **p, **q; p=(const ColorInfo **) x, q=(const ColorInfo **) y; if (LocaleCompare((*p)->path,(*q)->path) == 0) return(LocaleCompare((*p)->name,(*q)->name)); return(LocaleCompare((*p)->path,(*q)->path)); } #if defined(__cplusplus) || defined(c_plusplus) } #endif MagickExport const ColorInfo **GetColorInfoList(const char *pattern, unsigned long *number_colors,ExceptionInfo *exception) { const ColorInfo **colors; register const ColorInfo *p; register long i; /* Allocate color list. */ assert(pattern != (char *) NULL); (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",pattern); assert(number_colors != (unsigned long *) NULL); *number_colors=0; p=GetColorInfo("*",exception); if (p == (const ColorInfo *) NULL) return((const ColorInfo **) NULL); colors=(const ColorInfo **) AcquireQuantumMemory((size_t) GetNumberOfElementsInLinkedList(color_list)+1UL,sizeof(*colors)); if (colors == (const ColorInfo **) NULL) return((const ColorInfo **) NULL); /* Generate color list. */ AcquireSemaphoreInfo(&color_semaphore); ResetLinkedListIterator(color_list); p=(const ColorInfo *) GetNextValueInLinkedList(color_list); for (i=0; p != (const ColorInfo *) NULL; ) { if ((p->stealth == MagickFalse) && (GlobExpression(p->name,pattern,MagickFalse) != MagickFalse)) colors[i++]=p; p=(const ColorInfo *) GetNextValueInLinkedList(color_list); } RelinquishSemaphoreInfo(color_semaphore); qsort((void *) colors,(size_t) i,sizeof(*colors),ColorInfoCompare); colors[i]=(ColorInfo *) NULL; *number_colors=(unsigned long) i; return(colors); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % G e t C o l o r L i s t % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % GetColorList() returns any colors that match the specified pattern. % % The format of the GetColorList function is: % % char **GetColorList(const char *pattern,unsigned long *number_colors, % ExceptionInfo *exception) % % A description of each parameter follows: % % o pattern: Specifies a pointer to a text string containing a pattern. % % o number_colors: This integer returns the number of colors in the list. % % o exception: return any errors or warnings in this structure. % */ #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif static int ColorCompare(const void *x,const void *y) { register const char **p, **q; p=(const char **) x; q=(const char **) y; return(LocaleCompare(*p,*q)); } #if defined(__cplusplus) || defined(c_plusplus) } #endif MagickExport char **GetColorList(const char *pattern, unsigned long *number_colors,ExceptionInfo *exception) { char **colors; register const ColorInfo *p; register long i; /* Allocate color list. */ assert(pattern != (char *) NULL); (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",pattern); assert(number_colors != (unsigned long *) NULL); *number_colors=0; p=GetColorInfo("*",exception); if (p == (const ColorInfo *) NULL) return((char **) NULL); colors=(char **) AcquireQuantumMemory((size_t) GetNumberOfElementsInLinkedList(color_list)+1UL,sizeof(*colors)); if (colors == (char **) NULL) return((char **) NULL); /* Generate color list. */ AcquireSemaphoreInfo(&color_semaphore); ResetLinkedListIterator(color_list); p=(const ColorInfo *) GetNextValueInLinkedList(color_list); for (i=0; p != (const ColorInfo *) NULL; ) { if ((p->stealth == MagickFalse) && (GlobExpression(p->name,pattern,MagickFalse) != MagickFalse)) colors[i++]=ConstantString(p->name); p=(const ColorInfo *) GetNextValueInLinkedList(color_list); } RelinquishSemaphoreInfo(color_semaphore); qsort((void *) colors,(size_t) i,sizeof(*colors),ColorCompare); colors[i]=(char *) NULL; *number_colors=(unsigned long) i; return(colors); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + G e t C o l o r T u p l e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % GetColorTuple() returns a color as a color tuple string (e.g. rgba(255,0,0)) % or hex string (e.g. #FF0000). % % The format of the GetColorTuple method is: % % GetColorTuple(const MagickPixelPacket *pixel,const MagickBooleanType hex, % char *tuple) % % A description of each parameter follows. % % o pixel: the pixel. % % o hex: A value other than zero returns the tuple in a hexidecimal format. % % o tuple: Return the color tuple as this string. % */ static void ConcatentateHexColorComponent(const MagickPixelPacket *pixel, const ChannelType channel,char *tuple) { char component[MaxTextExtent]; MagickRealType color; color=0.0; switch (channel) { case RedChannel: { color=pixel->red; break; } case GreenChannel: { color=pixel->green; break; } case BlueChannel: { color=pixel->blue; break; } case OpacityChannel: { color=(MagickRealType) QuantumRange-pixel->opacity; break; } case IndexChannel: { color=pixel->index; break; } default: break; } if (pixel->depth > 32) { (void) FormatMagickString(component,MaxTextExtent,"%08lX", ScaleQuantumToLong(RoundToQuantum(color))); (void) ConcatenateMagickString(tuple,component,MaxTextExtent); return; } if (pixel->depth > 16) { (void) FormatMagickString(component,MaxTextExtent,"%08X", (unsigned int) ScaleQuantumToLong(RoundToQuantum(color))); (void) ConcatenateMagickString(tuple,component,MaxTextExtent); return; } if (pixel->depth > 8) { (void) FormatMagickString(component,MaxTextExtent,"%04X", ScaleQuantumToShort(RoundToQuantum(color))); (void) ConcatenateMagickString(tuple,component,MaxTextExtent); return; } (void) FormatMagickString(component,MaxTextExtent,"%02X", ScaleQuantumToChar(RoundToQuantum(color))); (void) ConcatenateMagickString(tuple,component,MaxTextExtent); return; } MagickExport void GetColorTuple(const MagickPixelPacket *pixel, const MagickBooleanType hex,char *tuple) { MagickPixelPacket color; assert(pixel != (const MagickPixelPacket *) NULL); assert(tuple != (char *) NULL); (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",tuple); *tuple='\0'; if (hex != MagickFalse) { /* Convert pixel to hex color. */ (void) ConcatenateMagickString(tuple,"#",MaxTextExtent); ConcatentateHexColorComponent(pixel,RedChannel,tuple); ConcatentateHexColorComponent(pixel,GreenChannel,tuple); ConcatentateHexColorComponent(pixel,BlueChannel,tuple); if (pixel->colorspace == CMYKColorspace) ConcatentateHexColorComponent(pixel,IndexChannel,tuple); if ((pixel->matte != MagickFalse) && (pixel->opacity != OpaqueOpacity)) ConcatentateHexColorComponent(pixel,OpacityChannel,tuple); return; } /* Convert pixel to rgb() or cmyk() color. */ color=(*pixel); if (color.depth > 8) { #define SVGCompliant(component) ((MagickRealType) \ ScaleCharToQuantum(ScaleQuantumToChar(RoundToQuantum(component)))); MagickStatusType status; /* SVG requires color depths > 8 expressed as percentages. */ status=color.red == SVGCompliant(color.red); status&=color.green == SVGCompliant(color.green); status&=color.blue == SVGCompliant(color.blue); if (color.colorspace != CMYKColorspace) status&=color.index == SVGCompliant(color.index); if (color.matte != MagickFalse) status&=color.opacity == SVGCompliant(color.opacity); if (status != MagickFalse) color.depth=8; } (void) ConcatenateMagickString(tuple,MagickOptionToMnemonic( MagickColorspaceOptions,(long) color.colorspace),MaxTextExtent); if (color.matte != MagickFalse) (void) ConcatenateMagickString(tuple,"a",MaxTextExtent); (void) ConcatenateMagickString(tuple,"(",MaxTextExtent); ConcatenateColorComponent(&color,RedChannel,SVGCompliance,tuple); (void) ConcatenateMagickString(tuple,",",MaxTextExtent); ConcatenateColorComponent(&color,GreenChannel,SVGCompliance,tuple); (void) ConcatenateMagickString(tuple,",",MaxTextExtent); ConcatenateColorComponent(&color,BlueChannel,SVGCompliance,tuple); if (color.colorspace == CMYKColorspace) { (void) ConcatenateMagickString(tuple,",",MaxTextExtent); ConcatenateColorComponent(&color,IndexChannel,SVGCompliance,tuple); } if (color.matte != MagickFalse) { (void) ConcatenateMagickString(tuple,",",MaxTextExtent); ConcatenateColorComponent(&color,AlphaChannel,SVGCompliance,tuple); } (void) ConcatenateMagickString(tuple,")",MaxTextExtent); LocaleLower(tuple); return; } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + G e t C u b e I n f o % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % GetCubeInfo() initializes the CubeInfo data structure. % % The format of the GetCubeInfo method is: % % cube_info=GetCubeInfo() % % A description of each parameter follows. % % o cube_info: A pointer to the Cube structure. % */ static CubeInfo *GetCubeInfo(void) { CubeInfo *cube_info; /* Initialize tree to describe color cube. */ cube_info=(CubeInfo *) AcquireMagickMemory(sizeof(*cube_info)); if (cube_info == (CubeInfo *) NULL) return((CubeInfo *) NULL); (void) ResetMagickMemory(cube_info,0,sizeof(*cube_info)); /* Initialize root node. */ cube_info->root=GetNodeInfo(cube_info,0); if (cube_info->root == (NodeInfo *) NULL) return((CubeInfo *) NULL); return(cube_info); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % G e t I m a g e H i s t o g r a m % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % GetImageHistogram() returns the unique colors in an image. % % The format of the GetImageHistogram method is: % % unsigned long GetImageHistogram(const Image *image, % unsigned long *number_colors,ExceptionInfo *exception) % % A description of each parameter follows. % % o image: the image. % % o file: Write a histogram of the color distribution to this file handle. % % o exception: return any errors or warnings in this structure. % */ MagickExport ColorPacket *GetImageHistogram(const Image *image, unsigned long *number_colors,ExceptionInfo *exception) { ColorPacket *histogram; CubeInfo *cube_info; *number_colors=0; histogram=(ColorPacket *) NULL; cube_info=ClassifyImageColors(image,exception); if (cube_info != (CubeInfo *) NULL) { histogram=(ColorPacket *) AcquireQuantumMemory((size_t) cube_info->colors, sizeof(*histogram)); if (histogram == (ColorPacket *) NULL) (void) ThrowMagickException(exception,GetMagickModule(), ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); else { ColorPacket *root; *number_colors=cube_info->colors; root=histogram; DefineImageHistogram(image,cube_info->root,&root); } } cube_info=DestroyCubeInfo(image,cube_info); return(histogram); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + G e t N o d e I n f o % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % GetNodeInfo() allocates memory for a new node in the color cube tree and % presets all fields to zero. % % The format of the GetNodeInfo method is: % % NodeInfo *GetNodeInfo(CubeInfo *cube_info,const unsigned long level) % % A description of each parameter follows. % % o cube_info: A pointer to the CubeInfo structure. % % o level: Specifies the level in the storage_class the node resides. % */ static NodeInfo *GetNodeInfo(CubeInfo *cube_info,const unsigned long level) { NodeInfo *node_info; if (cube_info->free_nodes == 0) { Nodes *nodes; /* Allocate a new nodes of nodes. */ nodes=(Nodes *) AcquireMagickMemory(sizeof(*nodes)); if (nodes == (Nodes *) NULL) return((NodeInfo *) NULL); nodes->next=cube_info->node_queue; cube_info->node_queue=nodes; cube_info->node_info=nodes->nodes; cube_info->free_nodes=NodesInAList; } cube_info->free_nodes--; node_info=cube_info->node_info++; (void) ResetMagickMemory(node_info,0,sizeof(*node_info)); node_info->level=level; return(node_info); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % G e t N u m b e r C o l o r s % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % GetNumberColors() returns the number of unique colors in an image. % % The format of the GetNumberColors method is: % % unsigned long GetNumberColors(const Image *image,FILE *file, % ExceptionInfo *exception) % % A description of each parameter follows. % % o image: the image. % % o file: Write a histogram of the color distribution to this file handle. % % o exception: return any errors or warnings in this structure. % */ #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif static int HistogramCompare(const void *x,const void *y) { const ColorPacket *color_1, *color_2; color_1=(const ColorPacket *) x; color_2=(const ColorPacket *) y; if (color_2->pixel.red != color_1->pixel.red) return((int) color_1->pixel.red-(int) color_2->pixel.red); if (color_2->pixel.green != color_1->pixel.green) return((int) color_1->pixel.green-(int) color_2->pixel.green); if (color_2->pixel.blue != color_1->pixel.blue) return((int) color_1->pixel.blue-(int) color_2->pixel.blue); return((int) color_2->count-(int) color_1->count); } #if defined(__cplusplus) || defined(c_plusplus) } #endif MagickExport unsigned long GetNumberColors(const Image *image,FILE *file, ExceptionInfo *exception) { #define HistogramImageTag "Histogram/Image" char color[MaxTextExtent], hex[MaxTextExtent], tuple[MaxTextExtent]; ColorPacket *histogram; MagickPixelPacket pixel; register ColorPacket *p; register long i; unsigned long number_colors; number_colors=0; if (file == (FILE *) NULL) { CubeInfo *cube_info; cube_info=ClassifyImageColors(image,exception); if (cube_info != (CubeInfo *) NULL) number_colors=cube_info->colors; cube_info=DestroyCubeInfo(image,cube_info); return(number_colors); } histogram=GetImageHistogram(image,&number_colors,exception); if (histogram == (ColorPacket *) NULL) return(number_colors); qsort((void *) histogram,(size_t) number_colors,sizeof(*histogram), HistogramCompare); GetMagickPixelPacket(image,&pixel); p=histogram; for (i=0; i < (long) number_colors; i++) { SetMagickPixelPacket(image,&p->pixel,&p->index,&pixel); (void) CopyMagickString(tuple,"(",MaxTextExtent); ConcatenateColorComponent(&pixel,RedChannel,X11Compliance,tuple); (void) ConcatenateMagickString(tuple,",",MaxTextExtent); ConcatenateColorComponent(&pixel,GreenChannel,X11Compliance,tuple); (void) ConcatenateMagickString(tuple,",",MaxTextExtent); ConcatenateColorComponent(&pixel,BlueChannel,X11Compliance,tuple); if (pixel.colorspace == CMYKColorspace) { (void) ConcatenateMagickString(tuple,",",MaxTextExtent); ConcatenateColorComponent(&pixel,IndexChannel,X11Compliance,tuple); } if (pixel.matte != MagickFalse) { (void) ConcatenateMagickString(tuple,",",MaxTextExtent); ConcatenateColorComponent(&pixel,OpacityChannel,X11Compliance,tuple); } (void) ConcatenateMagickString(tuple,")",MaxTextExtent); (void) QueryMagickColorname(image,&pixel,SVGCompliance,color,exception); GetColorTuple(&pixel,MagickTrue,hex); (void) fprintf(file,MagickSizeFormat,p->count); (void) fprintf(file,": %s %s %s\n",tuple,hex,color); if ((image->progress_monitor != (MagickProgressMonitor) NULL) && (QuantumTick(i,number_colors) != MagickFalse)) (void) image->progress_monitor(HistogramImageTag,i,number_colors, image->client_data); p++; } (void) fflush(file); histogram=(ColorPacket *) RelinquishMagickMemory(histogram); return(number_colors); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + I n i t i a l i z e C o l o r L i s t % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % InitializeColorList() initializes the color list. % % The format of the InitializeColorList method is: % % MagickBooleanType InitializeColorList(ExceptionInfo *exception) % % A description of each parameter follows. % % o exception: return any errors or warnings in this structure. % */ static MagickBooleanType InitializeColorList(ExceptionInfo *exception) { if ((color_list == (LinkedListInfo *) NULL) && (instantiate_color == MagickFalse)) { AcquireSemaphoreInfo(&color_semaphore); if ((color_list == (LinkedListInfo *) NULL) && (instantiate_color == MagickFalse)) { (void) LoadColorLists(ColorFilename,exception); instantiate_color=MagickTrue; } RelinquishSemaphoreInfo(color_semaphore); } return(color_list != (LinkedListInfo *) NULL ? MagickTrue : MagickFalse); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + I s C o l o r S i m i l a r % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsColorSimilar() returns MagickTrue if the distance between two colors is % less than the specified distance in a linear three dimensional color space. % This method is used by ColorFloodFill() and other algorithms which % compare two colors. % % The format of the IsColorSimilar method is: % % void IsColorSimilar(const Image *image,const PixelPacket *p, % const PixelPacket *q) % % A description of each parameter follows: % % o image: the image. % % o p: Pixel p. % % o q: Pixel q. % */ static inline double MagickMax(const double x,const double y) { if (x > y) return(x); return(y); } MagickExport MagickBooleanType IsColorSimilar(const Image *image, const PixelPacket *p,const PixelPacket *q) { MagickRealType fuzz, pixel; register MagickRealType alpha, beta, distance; if ((image->fuzz == 0.0) && (image->matte == MagickFalse)) return(IsColorEqual(p,q)); fuzz=3.0*MagickMax(image->fuzz,MagickSQ1_2)*MagickMax(image->fuzz, MagickSQ1_2); alpha=1.0; beta=1.0; if (image->matte != MagickFalse) { alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity)); beta=(MagickRealType) (QuantumScale*(QuantumRange-q->opacity)); } pixel=alpha*p->red-beta*q->red; distance=pixel*pixel; if (distance > fuzz) return(MagickFalse); pixel=alpha*p->green-beta*q->green; distance+=pixel*pixel; if (distance > fuzz) return(MagickFalse); pixel=alpha*p->blue-beta*q->blue; distance+=pixel*pixel; if (distance > fuzz) return(MagickFalse); return(MagickTrue); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s G r a y I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsGrayImage() returns MagickTrue if all the pixels in the image have the % same red, green, and blue intensities. % % The format of the IsGrayImage method is: % % MagickBooleanType IsGrayImage(const Image *image, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o exception: return any errors or warnings in this structure. % */ MagickExport MagickBooleanType IsGrayImage(const Image *image, ExceptionInfo *exception) { ImageType type; register const PixelPacket *p; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if ((image->type == BilevelType) || (image->type == GrayscaleType) || (image->type == GrayscaleMatteType)) return(MagickTrue); if (image->colorspace == CMYKColorspace) return(MagickFalse); type=BilevelType; switch (image->storage_class) { case DirectClass: case UndefinedClass: { long y; register long x; CacheView *image_view; image_view=AcquireCacheView(image); for (y=0; y < (long) image->rows; y++) { p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); if (p == (const PixelPacket *) NULL) break; for (x=0; x < (long) image->columns; x++) { if (IsGrayPixel(p) == MagickFalse) { type=UndefinedType; break; } if ((type == BilevelType) && (IsMonochromePixel(p) == MagickFalse)) type=GrayscaleType; p++; } if (type == UndefinedType) break; } image_view=DestroyCacheView(image_view); break; } case PseudoClass: { register long i; p=image->colormap; for (i=0; i < (long) image->colors; i++) { if (IsGrayPixel(p) == MagickFalse) { type=UndefinedType; break; } if ((type == BilevelType) && (IsMonochromePixel(p) == MagickFalse)) type=GrayscaleType; p++; } break; } } if (type == UndefinedType) return(MagickFalse); ((Image *) image)->type=type; if ((type == GrayscaleType) && (image->matte != MagickFalse)) ((Image *) image)->type=GrayscaleMatteType; return(MagickTrue); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s H i s t o g r a m I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsHistogramImage() returns MagickTrue if the image has 1024 unique colors or % less. % % The format of the IsHistogramImage method is: % % MagickBooleanType IsHistogramImage(const Image *image, % ExceptionInfo *exception) % % A description of each parameter follows. % % o image: the image. % % o exception: return any errors or warnings in this structure. % */ MagickExport MagickBooleanType IsHistogramImage(const Image *image, ExceptionInfo *exception) { #define MaximumUniqueColors 1024 CubeInfo *cube_info; long y; MagickPixelPacket pixel, target; register const IndexPacket *indexes; register const PixelPacket *p; register long x; register NodeInfo *node_info; register long i; unsigned long id, index, level; CacheView *image_view; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if ((image->storage_class == PseudoClass) && (image->colors <= 256)) return(MagickTrue); if (image->storage_class == PseudoClass) return(MagickFalse); /* Initialize color description tree. */ cube_info=GetCubeInfo(); if (cube_info == (CubeInfo *) NULL) { (void) ThrowMagickException(exception,GetMagickModule(), ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); return(MagickFalse); } GetMagickPixelPacket(image,&pixel); GetMagickPixelPacket(image,&target); image_view=AcquireCacheView(image); for (y=0; y < (long) image->rows; y++) { p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); if (p == (const PixelPacket *) NULL) break; indexes=GetCacheViewVirtualIndexQueue(image_view); for (x=0; x < (long) image->columns; x++) { /* Start at the root and proceed level by level. */ node_info=cube_info->root; index=MaxTreeDepth-1; for (level=1; level < MaxTreeDepth; level++) { SetMagickPixelPacket(image,p,indexes+x,&pixel); id=ColorToNodeId(image,&pixel,index); if (node_info->child[id] == (NodeInfo *) NULL) { node_info->child[id]=GetNodeInfo(cube_info,level); if (node_info->child[id] == (NodeInfo *) NULL) { (void) ThrowMagickException(exception,GetMagickModule(), ResourceLimitError,"MemoryAllocationFailed","`%s'", image->filename); break; } } node_info=node_info->child[id]; index--; } if (level < MaxTreeDepth) break; for (i=0; i < (long) node_info->number_unique; i++) { SetMagickPixelPacket(image,&node_info->list[i].pixel, &node_info->list[i].index,&target); if (IsMagickColorEqual(&pixel,&target) != MagickFalse) break; } if (i < (long) node_info->number_unique) node_info->list[i].count++; else { /* Add this unique color to the color list. */ if (node_info->number_unique == 0) node_info->list=(ColorPacket *) AcquireMagickMemory( sizeof(*node_info->list)); else node_info->list=(ColorPacket *) ResizeQuantumMemory(node_info->list, (size_t) (i+1),sizeof(*node_info->list)); if (node_info->list == (ColorPacket *) NULL) { (void) ThrowMagickException(exception,GetMagickModule(), ResourceLimitError,"MemoryAllocationFailed","`%s'", image->filename); break; } node_info->list[i].pixel=(*p); if ((image->colorspace == CMYKColorspace) || (image->storage_class == PseudoClass)) node_info->list[i].index=indexes[x]; node_info->list[i].count=1; node_info->number_unique++; cube_info->colors++; if (cube_info->colors > MaximumUniqueColors) break; } p++; } if (x < (long) image->columns) break; } image_view=DestroyCacheView(image_view); cube_info=DestroyCubeInfo(image,cube_info); return(y < (long) image->rows ? MagickFalse : MagickTrue); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + I s I m a g e S i m i l a r % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsImageSimilar() returns true if the target is similar to a region of the % image. % % The format of the IsImageSimilar method is: % % MagickBooleanType IsImageSimilar(const Image *image, % const Image *target_image,long *x_offset,long *y_offset, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o target_image: the target image. % % o x_offset: On input the starting x position to search for a match; % on output the x position of the first match found. % % o y_offset: On input the starting y position to search for a match; % on output the y position of the first match found. % % o exception: return any errors or warnings in this structure. % */ MagickExport MagickBooleanType IsImageSimilar(const Image *image, const Image *target_image,long *x_offset,long *y_offset, ExceptionInfo *exception) { #define SearchImageText " Searching image... " long j, y; MagickBooleanType status; MagickPixelPacket target, pixel; register const PixelPacket *p, *q; register const IndexPacket *indexes, *target_indexes; register long i, x; CacheView *image_view, *target_view; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(target_image != (Image *) NULL); assert(target_image->signature == MagickSignature); assert(x_offset != (long *) NULL); assert(y_offset != (long *) NULL); assert(exception != (ExceptionInfo *) NULL); x=0; GetMagickPixelPacket(image,&pixel); GetMagickPixelPacket(image,&target); image_view=AcquireCacheView(image); target_view=AcquireCacheView(target_image); for (y=(*y_offset); y < (long) image->rows; y++) { for (x=y == 0 ? *x_offset : 0; x < (long) image->columns; x++) { for (j=0; j < (long) target_image->rows; j++) { for (i=0; i < (long) target_image->columns; i++) { p=GetCacheViewVirtualPixels(image_view,x+i,y+j,1,1,exception); indexes=GetCacheViewVirtualIndexQueue(image_view); SetMagickPixelPacket(image,p,indexes,&pixel); q=GetCacheViewVirtualPixels(target_view,i,j,1,1,exception); target_indexes=GetCacheViewVirtualIndexQueue(target_view); SetMagickPixelPacket(image,q,target_indexes,&target); if (IsMagickColorSimilar(&pixel,&target) == MagickFalse) break; } if (i < (long) target_image->columns) break; } if (j == (long) target_image->rows) break; } if (x < (long) image->columns) break; if ((image->progress_monitor != (MagickProgressMonitor) NULL) && (QuantumTick(y,image->rows) != MagickFalse)) { status=image->progress_monitor(SearchImageText,y,image->rows, image->client_data); if (status == MagickFalse) break; } } target_view=DestroyCacheView(target_view); image_view=DestroyCacheView(image_view); *x_offset=x; *y_offset=y; return(y < (long) image->rows ? MagickTrue : MagickFalse); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + I s M a g i c k C o l o r S i m i l a r % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsMagickColorSimilar() returns true if the distance between two colors is % less than the specified distance in a linear three dimensional color space. % This method is used by ColorFloodFill() and other algorithms which % compare two colors. % % The format of the IsMagickColorSimilar method is: % % MagickBooleanType IsMagickColorSimilar(const MagickPixelPacket *p, % const MagickPixelPacket *q) % % A description of each parameter follows: % % o p: Pixel p. % % o q: Pixel q. % */ MagickExport MagickBooleanType IsMagickColorSimilar(const MagickPixelPacket *p, const MagickPixelPacket *q) { MagickRealType fuzz, pixel; register MagickRealType alpha, beta, distance; if ((p->fuzz == 0.0) && (q->fuzz == 0.0)) return(IsMagickColorEqual(p,q)); if (p->fuzz == 0.0) fuzz=MagickMax(q->fuzz,MagickSQ1_2)*MagickMax(q->fuzz,MagickSQ1_2); else if (q->fuzz == 0.0) fuzz=3.0*MagickMax(p->fuzz,MagickSQ1_2)*MagickMax(p->fuzz,MagickSQ1_2); else fuzz=3.0*MagickMax(p->fuzz,MagickSQ1_2)*MagickMax(q->fuzz,MagickSQ1_2); alpha=1.0; if (p->matte != MagickFalse) alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity)); beta=1.0; if (q->matte != MagickFalse) beta=(MagickRealType) (QuantumScale*(QuantumRange-q->opacity)); if (p->colorspace == CMYKColorspace) { alpha*=(MagickRealType) (QuantumScale*(QuantumRange-p->index)); beta*=(MagickRealType) (QuantumScale*(QuantumRange-q->index)); } pixel=alpha*p->red-beta*q->red; if ((p->colorspace == HSLColorspace) || (p->colorspace == HSBColorspace) || (p->colorspace == HWBColorspace)) { if (fabs(p->red-q->red) > (QuantumRange/2)) { if (p->red > (QuantumRange/2)) pixel=alpha*(p->red-QuantumRange)-beta*q->red; else pixel=alpha*p->red-beta*(q->red-QuantumRange); } pixel*=2; } distance=pixel*pixel; if (distance > fuzz) return(MagickFalse); pixel=alpha*p->green-beta*q->green; distance+=pixel*pixel; if (distance > fuzz) return(MagickFalse); pixel=alpha*p->blue-beta*q->blue; distance+=pixel*pixel; if (distance > fuzz) return(MagickFalse); pixel=p->opacity-q->opacity; distance+=pixel*pixel; if (distance > fuzz) return(MagickFalse); return(MagickTrue); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s M o n o c h r o m e I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsMonochromeImage() returns MagickTrue if all the pixels in the image have % the same red, green, and blue intensities and the intensity is either % 0 or QuantumRange. % % The format of the IsMonochromeImage method is: % % MagickBooleanType IsMonochromeImage(const Image *image, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o exception: return any errors or warnings in this structure. % */ MagickExport MagickBooleanType IsMonochromeImage(const Image *image, ExceptionInfo *exception) { ImageType type; register const PixelPacket *p; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if (image->type == BilevelType) return(MagickTrue); if (image->colorspace == CMYKColorspace) return(MagickFalse); type=BilevelType; switch (image->storage_class) { case DirectClass: case UndefinedClass: { long y; register long x; CacheView *image_view; image_view=AcquireCacheView(image); for (y=0; y < (long) image->rows; y++) { p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); if (p == (const PixelPacket *) NULL) break; for (x=0; x < (long) image->columns; x++) { if (IsMonochromePixel(p) == MagickFalse) { type=UndefinedType; break; } p++; } if (type == UndefinedType) break; } image_view=DestroyCacheView(image_view); if (y == (long) image->rows) ((Image *) image)->type=BilevelType; break; } case PseudoClass: { register long i; p=image->colormap; for (i=0; i < (long) image->colors; i++) { if (IsMonochromePixel(p) == MagickFalse) { type=UndefinedType; break; } p++; } break; } } if (type == UndefinedType) return(MagickFalse); ((Image *) image)->type=type; return(MagickTrue); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + I s O p a c i t y S i m i l a r % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsOpacitySimilar() returns true if the distance between two opacity % values is less than the specified distance in a linear color space. This % method is used by MatteFloodFill() and other algorithms which compare % two opacity values. % % The format of the IsOpacitySimilar method is: % % void IsOpacitySimilar(const Image *image,const PixelPacket *p, % const PixelPacket *q) % % A description of each parameter follows: % % o image: the image. % % o p: Pixel p. % % o q: Pixel q. % */ MagickExport MagickBooleanType IsOpacitySimilar(const Image *image, const PixelPacket *p,const PixelPacket *q) { MagickRealType fuzz, pixel; register MagickRealType distance; if (image->matte == MagickFalse) return(MagickTrue); if (p->opacity == q->opacity) return(MagickTrue); fuzz=MagickMax(image->fuzz,MagickSQ1_2)*MagickMax(image->fuzz,MagickSQ1_2); pixel=(MagickRealType) p->opacity-(MagickRealType) q->opacity; distance=pixel*pixel; if (distance > fuzz) return(MagickFalse); return(MagickTrue); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s O p a q u e I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsOpaqueImage() returns MagickTrue if none of the pixels in the image have % an opacity value other than opaque (0). % % The format of the IsOpaqueImage method is: % % MagickBooleanType IsOpaqueImage(const Image *image, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o exception: return any errors or warnings in this structure. % */ MagickExport MagickBooleanType IsOpaqueImage(const Image *image, ExceptionInfo *exception) { long y; register const PixelPacket *p; register long x; CacheView *image_view; /* Determine if image is opaque. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if (image->matte == MagickFalse) return(MagickTrue); image_view=AcquireCacheView(image); for (y=0; y < (long) image->rows; y++) { p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); if (p == (const PixelPacket *) NULL) break; for (x=0; x < (long) image->columns; x++) { if (p->opacity != OpaqueOpacity) break; p++; } if (x < (long) image->columns) break; } image_view=DestroyCacheView(image_view); return(y < (long) image->rows ? MagickFalse : MagickTrue); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s P a l e t t e I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsPaletteImage() returns MagickTrue if the image is PseudoClass and has 256 % unique colors or less. % % The format of the IsPaletteImage method is: % % MagickBooleanType IsPaletteImage(const Image *image, % ExceptionInfo *exception) % % A description of each parameter follows. % % o image: the image. % % o exception: return any errors or warnings in this structure. % */ MagickExport MagickBooleanType IsPaletteImage(const Image *image, ExceptionInfo *exception) { CubeInfo *cube_info; long y; MagickPixelPacket pixel, target; register const IndexPacket *indexes; register const PixelPacket *p; register long x; register NodeInfo *node_info; register long i; unsigned long id, index, level; CacheView *image_view; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if ((image->storage_class == PseudoClass) && (image->colors <= 256)) return(MagickTrue); if (image->storage_class == PseudoClass) return(MagickFalse); /* Initialize color description tree. */ cube_info=GetCubeInfo(); if (cube_info == (CubeInfo *) NULL) { (void) ThrowMagickException(exception,GetMagickModule(), ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); return(MagickFalse); } GetMagickPixelPacket(image,&pixel); GetMagickPixelPacket(image,&target); image_view=AcquireCacheView(image); for (y=0; y < (long) image->rows; y++) { p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); if (p == (const PixelPacket *) NULL) break; indexes=GetCacheViewVirtualIndexQueue(image_view); for (x=0; x < (long) image->columns; x++) { /* Start at the root and proceed level by level. */ node_info=cube_info->root; index=MaxTreeDepth-1; for (level=1; level < MaxTreeDepth; level++) { SetMagickPixelPacket(image,p,indexes+x,&pixel); id=ColorToNodeId(image,&pixel,index); if (node_info->child[id] == (NodeInfo *) NULL) { node_info->child[id]=GetNodeInfo(cube_info,level); if (node_info->child[id] == (NodeInfo *) NULL) { (void) ThrowMagickException(exception,GetMagickModule(), ResourceLimitError,"MemoryAllocationFailed","`%s'", image->filename); break; } } node_info=node_info->child[id]; index--; } if (level < MaxTreeDepth) break; for (i=0; i < (long) node_info->number_unique; i++) { SetMagickPixelPacket(image,&node_info->list[i].pixel, &node_info->list[i].index,&target); if (IsMagickColorEqual(&pixel,&target) != MagickFalse) break; } if (i < (long) node_info->number_unique) node_info->list[i].count++; else { /* Add this unique color to the color list. */ if (node_info->number_unique == 0) node_info->list=(ColorPacket *) AcquireMagickMemory( sizeof(*node_info->list)); else node_info->list=(ColorPacket *) ResizeQuantumMemory(node_info->list, (size_t) (i+1),sizeof(*node_info->list)); if (node_info->list == (ColorPacket *) NULL) { (void) ThrowMagickException(exception,GetMagickModule(), ResourceLimitError,"MemoryAllocationFailed","`%s'", image->filename); break; } node_info->list[i].pixel=(*p); if ((image->colorspace == CMYKColorspace) || (image->storage_class == PseudoClass)) node_info->list[i].index=indexes[x]; node_info->list[i].count=1; node_info->number_unique++; cube_info->colors++; if (cube_info->colors > 256) break; } p++; } if (x < (long) image->columns) break; } image_view=DestroyCacheView(image_view); cube_info=DestroyCubeInfo(image,cube_info); return(y < (long) image->rows ? MagickFalse : MagickTrue); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % L i s t C o l o r I n f o % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ListColorInfo() lists color names to the specified file. Color names % are a convenience. Rather than defining a color by its red, green, and % blue intensities just use a color name such as white, blue, or yellow. % % The format of the ListColorInfo method is: % % MagickBooleanType ListColorInfo(FILE *file,ExceptionInfo *exception) % % A description of each parameter follows. % % o file: List color names to this file handle. % % o exception: return any errors or warnings in this structure. % */ MagickExport MagickBooleanType ListColorInfo(FILE *file, ExceptionInfo *exception) { char tuple[MaxTextExtent]; const char *path; const ColorInfo **color_info; register long i; unsigned long number_colors; /* List name and attributes of each color in the list. */ if (file == (const FILE *) NULL) file=stdout; color_info=GetColorInfoList("*",&number_colors,exception); if (color_info == (const ColorInfo **) NULL) return(MagickFalse); path=(const char *) NULL; for (i=0; i < (long) number_colors; i++) { if (color_info[i]->stealth != MagickFalse) continue; if ((path == (const char *) NULL) || (LocaleCompare(path,color_info[i]->path) != 0)) { if (color_info[i]->path != (char *) NULL) (void) fprintf(file,"\nPath: %s\n\n",color_info[i]->path); (void) fprintf(file,"Name Color " " Compliance\n"); (void) fprintf(file,"-------------------------------------------------" "------------------------------\n"); } path=color_info[i]->path; (void) fprintf(file,"%-21.21s ",color_info[i]->name); GetColorTuple(&color_info[i]->color,MagickFalse,tuple); (void) fprintf(file,"%-45.45s ",tuple); if ((color_info[i]->compliance & SVGCompliance) != 0) (void) fprintf(file,"SVG "); if ((color_info[i]->compliance & X11Compliance) != 0) (void) fprintf(file,"X11 "); if ((color_info[i]->compliance & XPMCompliance) != 0) (void) fprintf(file,"XPM "); (void) fprintf(file,"\n"); } color_info=(const ColorInfo **) RelinquishMagickMemory((void *) color_info); (void) fflush(file); return(MagickTrue); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + L o a d C o l o r L i s t % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % LoadColorList() loads the color configuration file which provides a mapping % between color attributes and a color name. % % The format of the LoadColorList method is: % % MagickBooleanType LoadColorList(const char *xml,const char *filename, % const unsigned long depth,ExceptionInfo *exception) % % A description of each parameter follows: % % o xml: The color list in XML format. % % o filename: The color list filename. % % o depth: depth of statements. % % o exception: return any errors or warnings in this structure. % */ static MagickBooleanType LoadColorList(const char *xml,const char *filename, const unsigned long depth,ExceptionInfo *exception) { char keyword[MaxTextExtent], *token; ColorInfo *color_info; const char *q; MagickBooleanType status; /* Load the color map file. */ (void) LogMagickEvent(ConfigureEvent,GetMagickModule(), "Loading color file \"%s\" ...",filename); if (xml == (char *) NULL) return(MagickFalse); if (color_list == (LinkedListInfo *) NULL) { color_list=NewLinkedList(0); if (color_list == (LinkedListInfo *) NULL) { ThrowFileException(exception,ResourceLimitError, "MemoryAllocationFailed",filename); return(MagickFalse); } } status=MagickTrue; color_info=(ColorInfo *) NULL; token=AcquireString(xml); for (q=(char *) xml; *q != '\0'; ) { /* Interpret XML. */ GetMagickToken(q,&q,token); if (*token == '\0') break; (void) CopyMagickString(keyword,token,MaxTextExtent); if (LocaleNCompare(keyword,"",2) != 0) && (*q != '\0')) GetMagickToken(q,&q,token); continue; } if (LocaleNCompare(keyword,"