You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1243 lines
40 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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 %
% %
% %
% MagickCore Histogram Methods %
% %
% Software Design %
% Anthony Thyssen %
% Fred Weinhaus %
% August 2009 %
% %
% %
% Copyright 1999-2021 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 %
% %
% https://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. %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/
/*
Include declarations.
*/
#include "MagickCore/studio.h"
#include "MagickCore/cache-view.h"
#include "MagickCore/color-private.h"
#include "MagickCore/enhance.h"
#include "MagickCore/exception.h"
#include "MagickCore/exception-private.h"
#include "MagickCore/histogram.h"
#include "MagickCore/image.h"
#include "MagickCore/linked-list.h"
#include "MagickCore/list.h"
#include "MagickCore/memory_.h"
#include "MagickCore/monitor-private.h"
#include "MagickCore/pixel-accessor.h"
#include "MagickCore/prepress.h"
#include "MagickCore/quantize.h"
#include "MagickCore/registry.h"
#include "MagickCore/semaphore.h"
#include "MagickCore/splay-tree.h"
#include "MagickCore/statistic.h"
#include "MagickCore/string_.h"
/*
Define declarations.
*/
#define MaxTreeDepth 8
#define NodesInAList 1536
/*
Typedef declarations.
*/
typedef struct _NodeInfo
{
struct _NodeInfo
*child[16];
PixelInfo
*list;
size_t
extent;
MagickSizeType
number_unique;
size_t
level;
} NodeInfo;
typedef struct _Nodes
{
NodeInfo
nodes[NodesInAList];
struct _Nodes
*next;
} Nodes;
typedef struct _CubeInfo
{
NodeInfo
*root;
ssize_t
x;
MagickOffsetType
progress;
size_t
colors,
free_nodes;
NodeInfo
*node_info;
Nodes
*node_queue;
} CubeInfo;
/*
Forward declarations.
*/
static CubeInfo
*GetCubeInfo(void);
static NodeInfo
*GetNodeInfo(CubeInfo *,const size_t);
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 size_t ColorToNodeId(const PixelInfo *pixel,size_t index)
{
size_t
id;
id=(size_t) (
((ScaleQuantumToChar(ClampToQuantum(pixel->red)) >> index) & 0x01) |
((ScaleQuantumToChar(ClampToQuantum(pixel->green)) >> index) & 0x01) << 1 |
((ScaleQuantumToChar(ClampToQuantum(pixel->blue)) >> index) & 0x01) << 2);
if (pixel->alpha_trait != UndefinedPixelTrait)
id|=((ScaleQuantumToChar(ClampToQuantum(pixel->alpha)) >> index) &
0x01) << 3;
return(id);
}
static inline MagickBooleanType IsPixelInfoColorMatch(
const PixelInfo *magick_restrict p,const PixelInfo *magick_restrict q)
{
MagickRealType
alpha,
beta;
alpha=p->alpha_trait == UndefinedPixelTrait ? (MagickRealType) OpaqueAlpha :
p->alpha;
beta=q->alpha_trait == UndefinedPixelTrait ? (MagickRealType) OpaqueAlpha :
q->alpha;
if (AbsolutePixelValue(alpha-beta) >= MagickEpsilon)
return(MagickFalse);
if (AbsolutePixelValue(p->red-q->red) >= MagickEpsilon)
return(MagickFalse);
if (AbsolutePixelValue(p->green-q->green) >= MagickEpsilon)
return(MagickFalse);
if (AbsolutePixelValue(p->blue-q->blue) >= MagickEpsilon)
return(MagickFalse);
if (p->colorspace == CMYKColorspace)
{
if (AbsolutePixelValue(p->black-q->black) >= MagickEpsilon)
return(MagickFalse);
}
return(MagickTrue);
}
static CubeInfo *ClassifyImageColors(const Image *image,
ExceptionInfo *exception)
{
#define EvaluateImageTag " Compute image colors... "
CacheView
*image_view;
CubeInfo
*cube_info;
MagickBooleanType
proceed;
PixelInfo
pixel;
NodeInfo
*node_info;
const Quantum
*p;
size_t
id,
index,
level;
ssize_t
i,
x;
ssize_t
y;
/*
Initialize color description tree.
*/
assert(image != (const Image *) NULL);
assert(image->signature == MagickCoreSignature);
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);
}
GetPixelInfo(image,&pixel);
image_view=AcquireVirtualCacheView(image,exception);
for (y=0; y < (ssize_t) image->rows; y++)
{
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
break;
for (x=0; x < (ssize_t) 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++)
{
GetPixelInfoPixel(image,p,&pixel);
id=ColorToNodeId(&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 < (ssize_t) node_info->number_unique; i++)
if (IsPixelInfoColorMatch(&pixel,node_info->list+i) != MagickFalse)
break;
if (i < (ssize_t) node_info->number_unique)
node_info->list[i].count++;
else
{
if (node_info->number_unique == 0)
{
node_info->extent=1;
node_info->list=(PixelInfo *) AcquireQuantumMemory(
node_info->extent,sizeof(*node_info->list));
}
else
if (i >= (ssize_t) node_info->extent)
{
node_info->extent<<=1;
node_info->list=(PixelInfo *) ResizeQuantumMemory(
node_info->list,node_info->extent,sizeof(*node_info->list));
}
if (node_info->list == (PixelInfo *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"MemoryAllocationFailed","`%s'",
image->filename);
return(0);
}
node_info->list[i]=pixel;
node_info->list[i].red=(double) GetPixelRed(image,p);
node_info->list[i].green=(double) GetPixelGreen(image,p);
node_info->list[i].blue=(double) GetPixelBlue(image,p);
if (image->colorspace == CMYKColorspace)
node_info->list[i].black=(double) GetPixelBlack(image,p);
node_info->list[i].alpha=(double) GetPixelAlpha(image,p);
node_info->list[i].count=1;
node_info->number_unique++;
cube_info->colors++;
}
p+=GetPixelChannels(image);
}
proceed=SetImageProgress(image,EvaluateImageTag,(MagickOffsetType) y,
image->rows);
if (proceed == MagickFalse)
break;
}
image_view=DestroyCacheView(image_view);
return(cube_info);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
+ 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,
% PixelInfo **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,
PixelInfo **histogram)
{
ssize_t
i;
size_t
number_children;
/*
Traverse any children.
*/
number_children=image->alpha_trait == UndefinedPixelTrait ? 8UL : 16UL;
for (i=0; i < (ssize_t) number_children; i++)
if (node_info->child[i] != (NodeInfo *) NULL)
DefineImageHistogram(image,node_info->child[i],histogram);
if (node_info->level == (MaxTreeDepth-1))
{
PixelInfo
*p;
p=node_info->list;
for (i=0; i < (ssize_t) node_info->number_unique; i++)
{
**histogram=(*p);
(*histogram)++;
p++;
}
}
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
+ 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)
{
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)
{
ssize_t
i;
size_t
number_children;
/*
Traverse any children.
*/
number_children=image->alpha_trait == UndefinedPixelTrait ? 8UL : 16UL;
for (i=0; i < (ssize_t) number_children; i++)
if (node_info->child[i] != (NodeInfo *) NULL)
DestroyColorCube(image,node_info->child[i]);
if (node_info->list != (PixelInfo *) NULL)
node_info->list=(PixelInfo *) 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;
/*
Initialize tree to describe color cube.
*/
cube_info=(CubeInfo *) AcquireMagickMemory(sizeof(*cube_info));
if (cube_info == (CubeInfo *) NULL)
return((CubeInfo *) NULL);
(void) memset(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:
%
% size_t GetImageHistogram(const Image *image,
% size_t *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 PixelInfo *GetImageHistogram(const Image *image,
size_t *number_colors,ExceptionInfo *exception)
{
PixelInfo
*histogram;
CubeInfo
*cube_info;
*number_colors=0;
histogram=(PixelInfo *) NULL;
cube_info=ClassifyImageColors(image,exception);
if (cube_info != (CubeInfo *) NULL)
{
histogram=(PixelInfo *) AcquireQuantumMemory((size_t) cube_info->colors+1,
sizeof(*histogram));
if (histogram == (PixelInfo *) NULL)
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
else
{
PixelInfo
*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 size_t 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 size_t 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) memset(node_info,0,sizeof(*node_info));
node_info->level=level;
return(node_info);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% I d e n t i f y P a l e t t e I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% IdentifyPaletteImage() returns MagickTrue if the image has 256 unique colors
% or less.
%
% The format of the IdentifyPaletteImage method is:
%
% MagickBooleanType IdentifyPaletteImage(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 MagickBooleanType CheckImageColors(const Image *image,
ExceptionInfo *exception,size_t max_colors)
{
CacheView
*image_view;
CubeInfo
*cube_info;
PixelInfo
pixel,
target;
const Quantum
*p;
ssize_t
x;
NodeInfo
*node_info;
ssize_t
i;
size_t
id,
index,
level;
ssize_t
y;
if (image->storage_class == PseudoClass)
return((image->colors <= max_colors) ? MagickTrue : MagickFalse);
/*
Initialize color description tree.
*/
cube_info=GetCubeInfo();
if (cube_info == (CubeInfo *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
return(MagickFalse);
}
GetPixelInfo(image,&pixel);
GetPixelInfo(image,&target);
image_view=AcquireVirtualCacheView(image,exception);
for (y=0; y < (ssize_t) image->rows; y++)
{
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
break;
for (x=0; x < (ssize_t) 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++)
{
GetPixelInfoPixel(image,p,&pixel);
id=ColorToNodeId(&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 < (ssize_t) node_info->number_unique; i++)
{
target=node_info->list[i];
if (IsPixelInfoColorMatch(&pixel,&target) != MagickFalse)
break;
}
if (i < (ssize_t) 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=(PixelInfo *) AcquireQuantumMemory(1,
sizeof(*node_info->list));
else
node_info->list=(PixelInfo *) ResizeQuantumMemory(node_info->list,
(size_t) (i+1),sizeof(*node_info->list));
if (node_info->list == (PixelInfo *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"MemoryAllocationFailed","`%s'",
image->filename);
break;
}
GetPixelInfo(image,&node_info->list[i]);
node_info->list[i].red=(double) GetPixelRed(image,p);
node_info->list[i].green=(double) GetPixelGreen(image,p);
node_info->list[i].blue=(double) GetPixelBlue(image,p);
if (image->colorspace == CMYKColorspace)
node_info->list[i].black=(double) GetPixelBlack(image,p);
node_info->list[i].alpha=(double) GetPixelAlpha(image,p);
node_info->list[i].count=1;
node_info->number_unique++;
cube_info->colors++;
if (cube_info->colors > max_colors)
break;
}
p+=GetPixelChannels(image);
}
if (x < (ssize_t) image->columns)
break;
}
image_view=DestroyCacheView(image_view);
cube_info=DestroyCubeInfo(image,cube_info);
return(y < (ssize_t) image->rows ? MagickFalse : MagickTrue);
}
MagickExport MagickBooleanType IdentifyPaletteImage(const Image *image,
ExceptionInfo *exception)
{
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
return(CheckImageColors(image,exception,256));
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
return(CheckImageColors(image,exception,MaximumUniqueColors));
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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)
%
% A description of each parameter follows.
%
% o image: the image.
%
*/
MagickExport MagickBooleanType IsPaletteImage(const Image *image)
{
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (image->storage_class != PseudoClass)
return(MagickFalse);
return((image->colors <= 256) ? MagickTrue : MagickFalse);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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 equivalent 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 double black,
% const double white,const double gamma,ExceptionInfo *exception)
%
% A description of each parameter follows:
%
% o image: The image to auto-level
%
% o black, white: move the black / white point inward from the minimum and
% maximum points by this color value.
%
% o gamma: the gamma.
%
% o exception: return any errors or warnings in this structure.
%
*/
MagickExport MagickBooleanType MinMaxStretchImage(Image *image,
const double black,const double white,const double gamma,
ExceptionInfo *exception)
{
double
min,
max;
ssize_t
i;
MagickStatusType
status;
status=MagickTrue;
if (image->channel_mask == DefaultChannels)
{
/*
Auto-level all channels equally.
*/
(void) GetImageRange(image,&min,&max,exception);
min+=black;
max-=white;
if (fabs(min-max) >= MagickEpsilon)
status&=LevelImage(image,min,max,gamma,exception);
return(status != 0 ? MagickTrue : MagickFalse);
}
/*
Auto-level each channel.
*/
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
{
ChannelType
channel_mask;
PixelChannel channel = GetPixelChannelChannel(image,i);
PixelTrait traits = GetPixelChannelTraits(image,channel);
if ((traits & UpdatePixelTrait) == 0)
continue;
channel_mask=SetImageChannelMask(image,(ChannelType) (1UL << i));
status&=GetImageRange(image,&min,&max,exception);
min+=black;
max-=white;
if (fabs(min-max) >= MagickEpsilon)
status&=LevelImage(image,min,max,gamma,exception);
(void) SetImageChannelMask(image,channel_mask);
}
return(status != 0 ? MagickTrue : MagickFalse);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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:
%
% size_t 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 PixelInfo
*color_1,
*color_2;
color_1=(const PixelInfo *) x;
color_2=(const PixelInfo *) y;
if (color_2->red != color_1->red)
return((int) ((ssize_t) color_1->red-(ssize_t) color_2->red));
if (color_2->green != color_1->green)
return((int) ((ssize_t) color_1->green-(ssize_t) color_2->green));
if (color_2->blue != color_1->blue)
return((int) ((ssize_t) color_1->blue-(ssize_t) color_2->blue));
return((int) ((ssize_t) color_2->count-(ssize_t) color_1->count));
}
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
MagickExport size_t GetNumberColors(const Image *image,FILE *file,
ExceptionInfo *exception)
{
#define HistogramImageTag "Histogram/Image"
char
color[MagickPathExtent],
count[MagickPathExtent],
hex[MagickPathExtent],
tuple[MagickPathExtent];
PixelInfo
*histogram;
MagickBooleanType
status;
PixelInfo
pixel;
PixelInfo
*p;
ssize_t
i;
size_t
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 == (PixelInfo *) NULL)
return(number_colors);
qsort((void *) histogram,(size_t) number_colors,sizeof(*histogram),
HistogramCompare);
GetPixelInfo(image,&pixel);
p=histogram;
status=MagickTrue;
for (i=0; i < (ssize_t) number_colors; i++)
{
pixel=(*p);
(void) CopyMagickString(tuple,"(",MagickPathExtent);
ConcatenateColorComponent(&pixel,RedPixelChannel,NoCompliance,tuple);
(void) ConcatenateMagickString(tuple,",",MagickPathExtent);
ConcatenateColorComponent(&pixel,GreenPixelChannel,NoCompliance,tuple);
(void) ConcatenateMagickString(tuple,",",MagickPathExtent);
ConcatenateColorComponent(&pixel,BluePixelChannel,NoCompliance,tuple);
if (pixel.colorspace == CMYKColorspace)
{
(void) ConcatenateMagickString(tuple,",",MagickPathExtent);
ConcatenateColorComponent(&pixel,BlackPixelChannel,NoCompliance,
tuple);
}
if (pixel.alpha_trait != UndefinedPixelTrait)
{
(void) ConcatenateMagickString(tuple,",",MagickPathExtent);
ConcatenateColorComponent(&pixel,AlphaPixelChannel,NoCompliance,
tuple);
}
(void) ConcatenateMagickString(tuple,")",MagickPathExtent);
(void) QueryColorname(image,&pixel,SVGCompliance,color,exception);
GetColorTuple(&pixel,MagickTrue,hex);
(void) sprintf(count,"%.20g:",(double) ((MagickOffsetType) p->count));
(void) FormatLocaleFile(file," %s %s %s %s\n",count,tuple,hex,color);
if (image->progress_monitor != (MagickProgressMonitor) NULL)
{
MagickBooleanType
proceed;
proceed=SetImageProgress(image,HistogramImageTag,(MagickOffsetType) i,
number_colors);
if (proceed == MagickFalse)
status=MagickFalse;
}
p++;
}
(void) fflush(file);
histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
if (status == MagickFalse)
return(0);
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 *unique_image,CacheView *unique_view,
CubeInfo *cube_info,const NodeInfo *node_info,ExceptionInfo *exception)
{
#define UniqueColorsImageTag "UniqueColors/Image"
MagickBooleanType
status;
ssize_t
i;
size_t
number_children;
/*
Traverse any children.
*/
number_children=unique_image->alpha_trait == UndefinedPixelTrait ? 8UL : 16UL;
for (i=0; i < (ssize_t) number_children; i++)
if (node_info->child[i] != (NodeInfo *) NULL)
UniqueColorsToImage(unique_image,unique_view,cube_info,
node_info->child[i],exception);
if (node_info->level == (MaxTreeDepth-1))
{
PixelInfo
*p;
Quantum
*magick_restrict q;
status=MagickTrue;
p=node_info->list;
for (i=0; i < (ssize_t) node_info->number_unique; i++)
{
q=QueueCacheViewAuthenticPixels(unique_view,cube_info->x,0,1,1,
exception);
if (q == (Quantum *) NULL)
continue;
SetPixelRed(unique_image,ClampToQuantum(p->red),q);
SetPixelGreen(unique_image,ClampToQuantum(p->green),q);
SetPixelBlue(unique_image,ClampToQuantum(p->blue),q);
SetPixelAlpha(unique_image,ClampToQuantum(p->alpha),q);
if (unique_image->colorspace == CMYKColorspace)
SetPixelBlack(unique_image,ClampToQuantum(p->black),q);
if (SyncCacheViewAuthenticPixels(unique_view,exception) == MagickFalse)
break;
cube_info->x++;
p++;
}
if (unique_image->progress_monitor != (MagickProgressMonitor) NULL)
{
MagickBooleanType
proceed;
proceed=SetImageProgress(unique_image,UniqueColorsImageTag,
cube_info->progress,cube_info->colors);
if (proceed == MagickFalse)
status=MagickFalse;
}
cube_info->progress++;
if (status == MagickFalse)
return;
}
}
MagickExport Image *UniqueImageColors(const Image *image,
ExceptionInfo *exception)
{
CacheView
*unique_view;
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,exception) == MagickFalse)
{
unique_image=DestroyImage(unique_image);
return((Image *) NULL);
}
unique_view=AcquireAuthenticCacheView(unique_image,exception);
UniqueColorsToImage(unique_image,unique_view,cube_info,cube_info->root,
exception);
unique_view=DestroyCacheView(unique_view);
cube_info=DestroyCubeInfo(image,cube_info);
return(unique_image);
}