From: cristy Date: Fri, 16 Apr 2010 12:38:47 +0000 (+0000) Subject: (no commit message) X-Git-Tag: 7.0.1-0~9641 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1bd4a3a6b7cc5505da6698c4e61423a091d9d79f;p=imagemagick --- diff --git a/coders/histogram.c b/coders/histogram.c index 8d4b84193..db2293f16 100644 --- a/coders/histogram.c +++ b/coders/histogram.c @@ -3,18 +3,19 @@ % % % % % % -% H H IIIII SSSSS TTTTT OOO GGGG RRRR AAA M M % -% H H I SS T O O G R R A A MM MM % -% HHHHH I SSS T O O G GG RRRR AAAAA M M M % -% H H I SS T O O G G R R A A M M % -% H H IIIII SSSSS T OOO GGG R R A A M M % +% H H IIIII SSSSS TTTTT OOO GGGG RRRR AAA M M % +% H H I SS T O O G R R A A MM MM % +% HHHHH I SSS T O O G GG RRRR AAAAA M M M % +% H H I SS T O O G G R R A A M M % +% H H IIIII SSSSS T OOO GGG R R A A M M % % % % % -% Write A Histogram Image. % +% MagickCore Histogram Methods % % % % Software Design % -% John Cristy % -% July 1992 % +% Anthony Thyssen % +% Fred Weinhaus % +% August 2009 % % % % % % Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization % @@ -40,71 +41,263 @@ Include declarations. */ #include "magick/studio.h" -#include "magick/property.h" -#include "magick/blob.h" -#include "magick/blob-private.h" -#include "magick/cache.h" -#include "magick/color.h" +#include "magick/cache-view.h" #include "magick/color-private.h" -#include "magick/constitute.h" +#include "magick/enhance.h" #include "magick/exception.h" #include "magick/exception-private.h" -#include "magick/geometry.h" +#include "magick/hashmap.h" #include "magick/histogram.h" -#include "magick/image-private.h" -#include "magick/magick.h" +#include "magick/image.h" +#include "magick/list.h" #include "magick/memory_.h" -#include "magick/monitor.h" #include "magick/monitor-private.h" -#include "magick/resource_.h" -#include "magick/quantum-private.h" -#include "magick/static.h" +#include "magick/option.h" +#include "magick/pixel-private.h" +#include "magick/prepress.h" +#include "magick/quantize.h" +#include "magick/registry.h" +#include "magick/semaphore.h" +#include "magick/splay-tree.h" #include "magick/statistic.h" #include "magick/string_.h" -#include "magick/module.h" + +/* + Define declarations. +*/ +#define MaxTreeDepth 8 +#define NodesInAList 1536 + +/* + 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; /* Forward declarations. */ -static MagickBooleanType - WriteHISTOGRAMImage(const ImageInfo *,Image *); +static CubeInfo + *GetCubeInfo(void); + +static NodeInfo + *GetNodeInfo(CubeInfo *,const unsigned long); + +static void + DestroyColorCube(const Image *,NodeInfo *); /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % -% R e g i s t e r H I S T O G R A M I m a g e % ++ C l a s s i f y I m a g e C o l o r s % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% RegisterHISTOGRAMImage() adds attributes for the Histogram image format -% to the list of supported formats. The attributes include the image format -% tag, a method to read and/or write the format, whether the format -% supports the saving of more than one frame to the same file or blob, -% whether the format supports native in-memory I/O, and a brief -% description of the format. +% 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: % -% The format of the RegisterHISTOGRAMImage method is: +% CubeInfo *ClassifyImageColors(const Image *image, +% ExceptionInfo *exception) +% +% A description of each parameter follows. % -% unsigned long RegisterHISTOGRAMImage(void) +% o image: the image. +% +% o exception: return any errors or warnings in this structure. % */ -ModuleExport unsigned long RegisterHISTOGRAMImage(void) + +static inline unsigned long ColorToNodeId(const Image *image, + const MagickPixelPacket *pixel,unsigned long index) +{ + unsigned long + id; + + id=(unsigned long) ( + ((ScaleQuantumToChar(ClampToQuantum(pixel->red)) >> index) & 0x01) | + ((ScaleQuantumToChar(ClampToQuantum(pixel->green)) >> index) & 0x01) << 1 | + ((ScaleQuantumToChar(ClampToQuantum(pixel->blue)) >> index) & 0x01) << 2); + if (image->matte != MagickFalse) + id|=((ScaleQuantumToChar(ClampToQuantum(pixel->opacity)) >> index) & + 0x01) << 3; + return(id); +} + +static CubeInfo *ClassifyImageColors(const Image *image, + ExceptionInfo *exception) { - MagickInfo - *entry; - - entry=SetMagickInfo("HISTOGRAM"); - entry->encoder=(EncodeImageHandler *) WriteHISTOGRAMImage; - entry->adjoin=MagickFalse; - entry->format_type=ExplicitFormatType; - entry->description=ConstantString("Histogram of the image"); - entry->module=ConstantString("HISTOGRAM"); - (void) RegisterMagickInfo(entry); - return(MagickImageCoderSignature); +#define EvaluateImageTag " Compute image colors... " + + CacheView + *image_view; + + 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; + + /* + 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); } /* @@ -112,23 +305,62 @@ ModuleExport unsigned long RegisterHISTOGRAMImage(void) % % % % % % -% U n r e g i s t e r H I S T O G R A M I m a g e % ++ D e f i n e I m a g e H i s t o g r a m % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% UnregisterHISTOGRAMImage() removes format registrations made by the -% HISTOGRAM module from the list of supported formats. +% 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. % -% The format of the UnregisterHISTOGRAMImage method is: +% 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. % -% UnregisterHISTOGRAMImage(void) +% o histogram: the image histogram. % */ -ModuleExport void UnregisterHISTOGRAMImage(void) +static void DefineImageHistogram(const Image *image,NodeInfo *node_info, + ColorPacket **histogram) { - (void) UnregisterMagickInfo("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++; + } + } } /* @@ -136,83 +368,291 @@ ModuleExport void UnregisterHISTOGRAMImage(void) % % % % % % -% W r i t e H I S T O G R A M I m a g e % ++ D e s t r o y C u b e I n f o % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% WriteHISTOGRAMImage() writes an image to a file in Histogram format. -% The image shows a histogram of the color (or gray) values in the image. The -% image consists of three overlaid histograms: a red one for the red channel, -% a green one for the green channel, and a blue one for the blue channel. The -% image comment contains a list of unique pixel values and the number of times -% each occurs in the image. +% 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 % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % -% This method is strongly based on a similar one written by -% muquit@warm.semcor.com which in turn is based on ppmhistmap of netpbm. +% DestroyColorCube() traverses the color cube tree and frees the list of +% unique colors. % -% The format of the WriteHISTOGRAMImage method is: +% The format of the DestroyColorCube method is: % -% MagickBooleanType WriteHISTOGRAMImage(const ImageInfo *image_info, -% Image *image) +% void DestroyColorCube(const Image *image,const NodeInfo *node_info) % % A description of each parameter follows. % -% o image_info: the image info. +% o image: the image. % -% 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 inline size_t MagickMax(const size_t x,const size_t y) +static void DestroyColorCube(const Image *image,NodeInfo *node_info) { - if (x > y) - return(x); - return(y); + 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 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; -static MagickBooleanType WriteHISTOGRAMImage(const ImageInfo *image_info, - Image *image) + /* + Initialize tree to describe color cube. + */ + cube_info=(CubeInfo *) AcquireAlignedMemory(1,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) { -#define HistogramDensity "256x200" + ColorPacket + *histogram; - ChannelType - channel; + CubeInfo + *cube_info; - char - filename[MaxTextExtent]; + *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; - ExceptionInfo - *exception; + *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; - FILE - *file; + if (cube_info->free_nodes == 0) + { + Nodes + *nodes; - Image - *histogram_image; + /* + Allocate a new nodes of nodes. + */ + nodes=(Nodes *) AcquireAlignedMemory(1,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); +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% 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 - ImageInfo - *write_info; + CacheView + *image_view; - int - unique_file; + CubeInfo + *cube_info; long y; - MagickBooleanType - status; - MagickPixelPacket - *histogram; + pixel, + target; - MagickRealType - maximum, - scale; - - RectangleInfo - geometry; + register const IndexPacket + *indexes; register const PixelPacket *p; @@ -220,163 +660,664 @@ static MagickBooleanType WriteHISTOGRAMImage(const ImageInfo *image_info, register long x; - register PixelPacket - *q, - *r; + register NodeInfo + *node_info; - size_t - length; + register long + i; + + unsigned long + id, + index, + level; - /* - Allocate histogram image. - */ - assert(image_info != (const ImageInfo *) NULL); - assert(image_info->signature == MagickSignature); assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) - (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", - image_info->filename); - SetGeometry(image,&geometry); - if (image_info->density == (char *) NULL) - (void) ParseAbsoluteGeometry(HistogramDensity,&geometry); - else - (void) ParseAbsoluteGeometry(image_info->density,&geometry); - histogram_image=CloneImage(image,geometry.width,geometry.height,MagickTrue, - &image->exception); - if (histogram_image == (Image *) NULL) - ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed"); - (void) SetImageStorageClass(histogram_image,DirectClass); + (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); + if ((image->storage_class == PseudoClass) && (image->colors <= 256)) + return(MagickTrue); + if (image->storage_class == PseudoClass) + return(MagickFalse); /* - Allocate histogram count arrays. + Initialize color description tree. */ - length=MagickMax((size_t) ScaleQuantumToChar((Quantum) QuantumRange)+1UL, - histogram_image->columns); - histogram=(MagickPixelPacket *) AcquireQuantumMemory(length, - sizeof(*histogram)); - if (histogram == (MagickPixelPacket *) NULL) + cube_info=GetCubeInfo(); + if (cube_info == (CubeInfo *) NULL) { - histogram_image=DestroyImage(histogram_image); - ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed"); + (void) ThrowMagickException(exception,GetMagickModule(), + ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); + return(MagickFalse); } - /* - Initialize histogram count arrays. - */ - channel=image_info->channel; - (void) ResetMagickMemory(histogram,0,length*sizeof(*histogram)); + GetMagickPixelPacket(image,&pixel); + GetMagickPixelPacket(image,&target); + image_view=AcquireCacheView(image); for (y=0; y < (long) image->rows; y++) { - p=GetVirtualPixels(image,0,y,image->columns,1,&image->exception); + 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++) { - if ((channel & RedChannel) != 0) - histogram[ScaleQuantumToChar(GetRedPixelComponent(p))].red++; - if ((channel & GreenChannel) != 0) - histogram[ScaleQuantumToChar(GetGreenPixelComponent(p))].green++; - if ((channel & BlueChannel) != 0) - histogram[ScaleQuantumToChar(GetBluePixelComponent(p))].blue++; + /* + 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; } - maximum=histogram[0].red; - for (x=0; x < (long) histogram_image->columns; x++) - { - if (((channel & RedChannel) != 0) && (maximum < histogram[x].red)) - maximum=histogram[x].red; - if (((channel & GreenChannel) != 0) && (maximum < histogram[x].green)) - maximum=histogram[x].green; - if (((channel & BlueChannel) != 0) && (maximum < histogram[x].blue)) - maximum=histogram[x].blue; - } - scale=(MagickRealType) histogram_image->rows/maximum; + image_view=DestroyCacheView(image_view); + cube_info=DestroyCubeInfo(image,cube_info); + 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) +{ + CacheView + *image_view; + + 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; + + 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 histogram image. + Initialize color description tree. */ - exception=(&image->exception); - (void) QueryColorDatabase("#000000",&histogram_image->background_color, - &image->exception); - (void) SetImageBackgroundColor(histogram_image); - for (x=0; x < (long) histogram_image->columns; x++) + 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++) { - q=GetAuthenticPixels(histogram_image,x,0,1,histogram_image->rows,exception); - if (q == (PixelPacket *) NULL) + p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); + if (p == (const PixelPacket *) NULL) break; - if ((channel & RedChannel) != 0) + 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++) { - y=(long) ceil(histogram_image->rows-scale*histogram[x].red-0.5); - r=q+y; - for ( ; y < (long) histogram_image->rows; y++) - { - r->red=(Quantum) QuantumRange; - r++; - } + 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 ((channel & GreenChannel) != 0) + if (level < MaxTreeDepth) + break; + for (i=0; i < (long) node_info->number_unique; i++) { - y=(long) ceil(histogram_image->rows-scale*histogram[x].green-0.5); - r=q+y; - for ( ; y < (long) histogram_image->rows; y++) + 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 { - r->green=(Quantum) QuantumRange; - r++; + /* + 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); +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% M i n M a x S t r e t c h I m a g e % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% MinMaxStretchImage() uses the exact minimum and maximum values found in +% each of the channels given, as the BlackPoint and WhitePoint to linearly +% stretch the colors (and histogram) of the image. The stretch points are +% also moved further inward by the adjustment values given. +% +% If the adjustment values are both zero this function is equivelent to a +% perfect normalization (or autolevel) of the image. +% +% Each channel is stretched independantally of each other (producing color +% distortion) unless the special 'SyncChannels' flag is also provided in the +% channels setting. If this flag is present the minimum and maximum point +% will be extracted from all the given channels, and those channels will be +% stretched by exactly the same amount (preventing color distortion). +% +% In the special case that only ONE value is found in a channel of the image +% that value is not stretched, that value is left as is. +% +% The 'SyncChannels' is turned on in the 'DefaultChannels' setting by +% default. +% +% The format of the MinMaxStretchImage method is: +% +% MagickBooleanType MinMaxStretchImage(Image *image, +% const ChannelType channel, const double black_adjust, +% const double white_adjust) +% +% A description of each parameter follows: +% +% o image: The image to auto-level +% +% o channel: The channels to auto-level. If the special 'SyncChannels' +% flag is set, all the given channels are stretched by the same amount. +% +% o black_adjust, white_adjust: Move the Black/White Point inward +% from the minimum and maximum points by this color value. +% +*/ + +MagickExport MagickBooleanType MinMaxStretchImage(Image *image, + const ChannelType channel,const double black_value,const double white_value) +{ + double + min,max; + + MagickStatusType + status; + + status=MagickTrue; + if ((channel & SyncChannels) != 0) + { + /* + Auto-level all channels equally. + */ + (void) GetImageChannelRange(image,channel,&min,&max,&image->exception); + min+=black_value; + max-=white_value; + if ( fabs(min-max) >= MagickEpsilon ) + status = LevelImageChannel(image,channel,min,max,1.0); + return(status); + } + /* + Auto-level each channel separately. + */ + if ((channel & RedChannel) != 0) + { + (void) GetImageChannelRange(image,RedChannel,&min,&max,&image->exception); + min+=black_value; + max-=white_value; + if ( fabs(min-max) >= MagickEpsilon ) + status&=LevelImageChannel(image,RedChannel,min,max,1.0); + } + if ((channel & GreenChannel) != 0) + { + (void) GetImageChannelRange(image,GreenChannel,&min,&max, + &image->exception); + min+=black_value; + max-=white_value; + if ( fabs(min-max) >= MagickEpsilon ) + status&=LevelImageChannel(image,GreenChannel,min,max,1.0); + } + if ((channel & BlueChannel) != 0) + { + (void) GetImageChannelRange(image,BlueChannel,&min,&max, + &image->exception); + min+=black_value; + max-=white_value; + if ( fabs(min-max) >= MagickEpsilon ) + status&=LevelImageChannel(image,BlueChannel,min,max,1.0); + } + if (((channel & OpacityChannel) != 0) && + (image->matte == MagickTrue)) + { + (void) GetImageChannelRange(image,OpacityChannel,&min,&max, + &image->exception); + min+=black_value; + max-=white_value; + if ( fabs(min-max) >= MagickEpsilon ) + status&=LevelImageChannel(image,OpacityChannel,min,max,1.0); + } + if (((channel & IndexChannel) != 0) && + (image->colorspace == CMYKColorspace)) + { + (void) GetImageChannelRange(image,IndexChannel,&min,&max, + &image->exception); + min+=black_value; + max-=white_value; + if ( fabs(min-max) >= MagickEpsilon ) + status&=LevelImageChannel(image,IndexChannel,min,max,1.0); + } + return(status); +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% 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; + + MagickBooleanType + status; + + 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 ((channel & BlueChannel) != 0) + if (pixel.matte != MagickFalse) { - y=(long) ceil(histogram_image->rows-scale*histogram[x].blue-0.5); - r=q+y; - for ( ; y < (long) histogram_image->rows; y++) - { - r->blue=(Quantum) QuantumRange; - r++; - } + (void) ConcatenateMagickString(tuple,",",MaxTextExtent); + ConcatenateColorComponent(&pixel,OpacityChannel,X11Compliance,tuple); } - if (SyncAuthenticPixels(histogram_image,exception) == MagickFalse) - break; - status=SetImageProgress(image,SaveImageTag,y,histogram_image->rows); - if (status == MagickFalse) - break; + (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) + { + MagickBooleanType + proceed; + + proceed=SetImageProgress(image,HistogramImageTag,i,number_colors); + if (proceed == MagickFalse) + status=MagickFalse; + } + p++; } + (void) fflush(file); + histogram=(ColorPacket *) RelinquishMagickMemory(histogram); + return(number_colors); +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% U n i q u e I m a g e C o l o r s % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% UniqueImageColors() returns the unique colors of an image. +% +% The format of the UniqueImageColors method is: +% +% Image *UniqueImageColors(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 void UniqueColorsToImage(Image *image,CubeInfo *cube_info, + const NodeInfo *node_info,ExceptionInfo *exception) +{ +#define UniqueColorsImageTag "UniqueColors/Image" + + MagickBooleanType + status; + + register long + i; + + unsigned long + number_children; + /* - Relinquish resources. + Traverse any children. */ - histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram); - file=(FILE *) NULL; - unique_file=AcquireUniqueFileResource(filename); - if (unique_file != -1) - file=fdopen(unique_file,"wb"); - if ((unique_file != -1) && (file != (FILE *) NULL)) + number_children=image->matte == MagickFalse ? 8UL : 16UL; + for (i=0; i < (long) number_children; i++) + if (node_info->child[i] != (NodeInfo *) NULL) + UniqueColorsToImage(image,cube_info,node_info->child[i],exception); + if (node_info->level == (MaxTreeDepth-1)) { - char - *property; + register ColorPacket + *p; - /* - Add a histogram as an image comment. - */ - (void) GetNumberColors(image,file,&image->exception); - (void) fclose(file); - property=FileToString(filename,~0UL,&image->exception); - if (property != (char *) NULL) + register IndexPacket + *restrict indexes; + + register PixelPacket + *restrict q; + + p=node_info->list; + for (i=0; i < (long) node_info->number_unique; i++) + { + q=QueueAuthenticPixels(image,cube_info->x,0,1,1,exception); + if (q == (PixelPacket *) NULL) + continue; + indexes=GetAuthenticIndexQueue(image); + *q=p->pixel; + if (image->colorspace == CMYKColorspace) + *indexes=p->index; + if (SyncAuthenticPixels(image,exception) == MagickFalse) + break; + cube_info->x++; + p++; + } + if (image->progress_monitor != (MagickProgressMonitor) NULL) { - (void) SetImageProperty(histogram_image,"comment",property); - property=DestroyString(property); + MagickBooleanType + proceed; + + proceed=SetImageProgress(image,UniqueColorsImageTag, + cube_info->progress,cube_info->colors); + if (proceed == MagickFalse) + status=MagickFalse; } + cube_info->progress++; } - (void) RelinquishUniqueFileResource(filename); - /* - Write Histogram image. - */ - (void) CopyMagickString(histogram_image->filename,image_info->filename, - MaxTextExtent); - write_info=CloneImageInfo(image_info); - (void) SetImageInfo(write_info,1,&image->exception); - if (LocaleCompare(write_info->magick,"HISTOGRAM") == 0) - (void) FormatMagickString(histogram_image->filename,MaxTextExtent, - "miff:%s",write_info->filename); - status=WriteImage(write_info,histogram_image); - histogram_image=DestroyImage(histogram_image); - write_info=DestroyImageInfo(write_info); - return(status); +} + +MagickExport Image *UniqueImageColors(const Image *image, + ExceptionInfo *exception) +{ + CubeInfo + *cube_info; + + Image + *unique_image; + + cube_info=ClassifyImageColors(image,exception); + if (cube_info == (CubeInfo *) NULL) + return((Image *) NULL); + unique_image=CloneImage(image,cube_info->colors,1,MagickTrue,exception); + if (unique_image == (Image *) NULL) + return(unique_image); + if (SetImageStorageClass(unique_image,DirectClass) == MagickFalse) + { + InheritException(exception,&unique_image->exception); + unique_image=DestroyImage(unique_image); + return((Image *) NULL); + } + UniqueColorsToImage(unique_image,cube_info,cube_info->root,exception); + if (cube_info->colors < MaxColormapSize) + { + QuantizeInfo + *quantize_info; + + quantize_info=AcquireQuantizeInfo((ImageInfo *) NULL); + quantize_info->number_colors=MaxColormapSize; + quantize_info->dither=MagickFalse; + quantize_info->tree_depth=8; + (void) QuantizeImage(quantize_info,unique_image); + quantize_info=DestroyQuantizeInfo(quantize_info); + } + cube_info=DestroyCubeInfo(image,cube_info); + return(unique_image); }