blob: 75154c972a5e15366fdfbdd9286c84be17e5155d [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% QQQ U U AAA N N TTTTT IIIII ZZZZZ EEEEE %
7% Q Q U U A A NN N T I ZZ E %
8% Q Q U U AAAAA N N N T I ZZZ EEEEE %
9% Q QQ U U A A N NN T I ZZ E %
10% QQQQ UUU A A N N T IIIII ZZZZZ EEEEE %
11% %
12% %
13% MagickCore Methods to Reduce the Number of Unique Colors in an Image %
14% %
15% Software Design %
cristyde984cd2013-12-01 14:49:27 +000016% Cristy %
cristy3ed852e2009-09-05 21:47:34 +000017% July 1992 %
18% %
19% %
Cristyd8420112021-01-01 14:52:00 -050020% Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
Cristy9ddfcca2018-09-09 19:46:34 -040026% https://imagemagick.org/script/license.php %
cristy3ed852e2009-09-05 21:47:34 +000027% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36% Realism in computer graphics typically requires using 24 bits/pixel to
37% generate an image. Yet many graphic display devices do not contain the
38% amount of memory necessary to match the spatial and color resolution of
39% the human eye. The Quantize methods takes a 24 bit image and reduces
40% the number of colors so it can be displayed on raster device with less
41% bits per pixel. In most instances, the quantized image closely
42% resembles the original reference image.
43%
44% A reduction of colors in an image is also desirable for image
45% transmission and real-time animation.
46%
47% QuantizeImage() takes a standard RGB or monochrome images and quantizes
48% them down to some fixed number of colors.
49%
50% For purposes of color allocation, an image is a set of n pixels, where
51% each pixel is a point in RGB space. RGB space is a 3-dimensional
52% vector space, and each pixel, Pi, is defined by an ordered triple of
53% red, green, and blue coordinates, (Ri, Gi, Bi).
54%
55% Each primary color component (red, green, or blue) represents an
56% intensity which varies linearly from 0 to a maximum value, Cmax, which
57% corresponds to full saturation of that color. Color allocation is
58% defined over a domain consisting of the cube in RGB space with opposite
59% vertices at (0,0,0) and (Cmax, Cmax, Cmax). QUANTIZE requires Cmax =
60% 255.
61%
62% The algorithm maps this domain onto a tree in which each node
63% represents a cube within that domain. In the following discussion
cristy18856c52014-03-04 14:04:02 +000064% these cubes are defined by the coordinate of two opposite vertices (vertex
cristy6a91c182014-03-04 14:05:01 +000065% nearest the origin in RGB space and the vertex farthest from the origin).
cristy3ed852e2009-09-05 21:47:34 +000066%
67% The tree's root node represents the entire domain, (0,0,0) through
68% (Cmax,Cmax,Cmax). Each lower level in the tree is generated by
69% subdividing one node's cube into eight smaller cubes of equal size.
70% This corresponds to bisecting the parent cube with planes passing
71% through the midpoints of each edge.
72%
73% The basic algorithm operates in three phases: Classification,
74% Reduction, and Assignment. Classification builds a color description
75% tree for the image. Reduction collapses the tree until the number it
76% represents, at most, the number of colors desired in the output image.
77% Assignment defines the output image's color map and sets each pixel's
78% color by restorage_class in the reduced tree. Our goal is to minimize
79% the numerical discrepancies between the original colors and quantized
80% colors (quantization error).
81%
82% Classification begins by initializing a color description tree of
83% sufficient depth to represent each possible input color in a leaf.
84% However, it is impractical to generate a fully-formed color description
85% tree in the storage_class phase for realistic values of Cmax. If
86% colors components in the input image are quantized to k-bit precision,
87% so that Cmax= 2k-1, the tree would need k levels below the root node to
88% allow representing each possible input color in a leaf. This becomes
89% prohibitive because the tree's total number of nodes is 1 +
90% sum(i=1, k, 8k).
91%
92% A complete tree would require 19,173,961 nodes for k = 8, Cmax = 255.
93% Therefore, to avoid building a fully populated tree, QUANTIZE: (1)
94% Initializes data structures for nodes only as they are needed; (2)
95% Chooses a maximum depth for the tree as a function of the desired
96% number of colors in the output image (currently log2(colormap size)).
97%
98% For each pixel in the input image, storage_class scans downward from
99% the root of the color description tree. At each level of the tree it
100% identifies the single node which represents a cube in RGB space
101% containing the pixel's color. It updates the following data for each
102% such node:
103%
104% n1: Number of pixels whose color is contained in the RGB cube which
105% this node represents;
106%
107% n2: Number of pixels whose color is not represented in a node at
108% lower depth in the tree; initially, n2 = 0 for all nodes except
109% leaves of the tree.
110%
111% Sr, Sg, Sb: Sums of the red, green, and blue component values for all
112% pixels not classified at a lower depth. The combination of these sums
Cristy14b1a182017-07-21 12:29:32 -0400113% and n2 will ultimately characterize the mean color of a set of pixels
114% represented by this node.
cristy3ed852e2009-09-05 21:47:34 +0000115%
116% E: the distance squared in RGB space between each pixel contained
117% within a node and the nodes' center. This represents the
118% quantization error for a node.
119%
120% Reduction repeatedly prunes the tree until the number of nodes with n2
121% > 0 is less than or equal to the maximum number of colors allowed in
122% the output image. On any given iteration over the tree, it selects
123% those nodes whose E count is minimal for pruning and merges their color
124% statistics upward. It uses a pruning threshold, Ep, to govern node
125% selection as follows:
126%
127% Ep = 0
128% while number of nodes with (n2 > 0) > required maximum number of colors
129% prune all nodes such that E <= Ep
130% Set Ep to minimum E in remaining nodes
131%
132% This has the effect of minimizing any quantization error when merging
133% two nodes together.
134%
135% When a node to be pruned has offspring, the pruning procedure invokes
136% itself recursively in order to prune the tree from the leaves upward.
137% n2, Sr, Sg, and Sb in a node being pruned are always added to the
138% corresponding data in that node's parent. This retains the pruned
139% node's color characteristics for later averaging.
140%
141% For each node, n2 pixels exist for which that node represents the
142% smallest volume in RGB space containing those pixel's colors. When n2
143% > 0 the node will uniquely define a color in the output image. At the
144% beginning of reduction, n2 = 0 for all nodes except a the leaves of
145% the tree which represent colors present in the input image.
146%
147% The other pixel count, n1, indicates the total number of colors within
148% the cubic volume which the node represents. This includes n1 - n2
149% pixels whose colors should be defined by nodes at a lower level in the
150% tree.
151%
152% Assignment generates the output image from the pruned tree. The output
153% image consists of two parts: (1) A color map, which is an array of
154% color descriptions (RGB triples) for each color present in the output
155% image; (2) A pixel array, which represents each pixel as an index
156% into the color map array.
157%
158% First, the assignment phase makes one pass over the pruned color
159% description tree to establish the image's color map. For each node
160% with n2 > 0, it divides Sr, Sg, and Sb by n2 . This produces the mean
161% color of all pixels that classify no lower than this node. Each of
162% these colors becomes an entry in the color map.
163%
164% Finally, the assignment phase reclassifies each pixel in the pruned
165% tree to identify the deepest node containing the pixel's color. The
166% pixel's value in the pixel array becomes the index of this node's mean
167% color in the color map.
168%
169% This method is based on a similar algorithm written by Paul Raveling.
170%
171*/
172
173/*
174 Include declarations.
175*/
cristy4c08aed2011-07-01 19:47:50 +0000176#include "MagickCore/studio.h"
Cristydd1a91a2018-06-17 09:21:49 -0400177#include "MagickCore/artifact.h"
cristy4c08aed2011-07-01 19:47:50 +0000178#include "MagickCore/attribute.h"
179#include "MagickCore/cache-view.h"
180#include "MagickCore/color.h"
181#include "MagickCore/color-private.h"
182#include "MagickCore/colormap.h"
183#include "MagickCore/colorspace.h"
cristy510d06a2011-07-06 23:43:54 +0000184#include "MagickCore/colorspace-private.h"
Cristyee6da042019-12-19 06:16:25 -0500185#include "MagickCore/compare.h"
cristy4c08aed2011-07-01 19:47:50 +0000186#include "MagickCore/enhance.h"
187#include "MagickCore/exception.h"
188#include "MagickCore/exception-private.h"
189#include "MagickCore/histogram.h"
190#include "MagickCore/image.h"
191#include "MagickCore/image-private.h"
192#include "MagickCore/list.h"
193#include "MagickCore/memory_.h"
Dirk Lemstra06344a02017-10-15 10:10:01 +0200194#include "MagickCore/memory-private.h"
cristy4c08aed2011-07-01 19:47:50 +0000195#include "MagickCore/monitor.h"
196#include "MagickCore/monitor-private.h"
197#include "MagickCore/option.h"
198#include "MagickCore/pixel-accessor.h"
cristy1d1b10f2012-06-02 20:02:55 +0000199#include "MagickCore/pixel-private.h"
cristy4c08aed2011-07-01 19:47:50 +0000200#include "MagickCore/quantize.h"
201#include "MagickCore/quantum.h"
202#include "MagickCore/quantum-private.h"
Cristy63442152019-12-21 09:47:06 -0500203#include "MagickCore/random_.h"
cristyac245f82012-05-05 17:13:57 +0000204#include "MagickCore/resource_.h"
cristy4c08aed2011-07-01 19:47:50 +0000205#include "MagickCore/string_.h"
Cristydd1a91a2018-06-17 09:21:49 -0400206#include "MagickCore/string-private.h"
cristy4c08aed2011-07-01 19:47:50 +0000207#include "MagickCore/thread-private.h"
cristy3ed852e2009-09-05 21:47:34 +0000208
209/*
210 Define declarations.
211*/
cristye1287512010-06-19 17:38:25 +0000212#if !defined(__APPLE__) && !defined(TARGET_OS_IPHONE)
cristy3ed852e2009-09-05 21:47:34 +0000213#define CacheShift 2
cristye1287512010-06-19 17:38:25 +0000214#else
215#define CacheShift 3
216#endif
cristy3ed852e2009-09-05 21:47:34 +0000217#define ErrorQueueLength 16
218#define MaxNodes 266817
219#define MaxTreeDepth 8
220#define NodesInAList 1920
221
222/*
223 Typdef declarations.
224*/
Cristy2f4e2b22017-01-22 17:25:53 -0500225typedef struct _DoublePixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000226{
cristya19f1d72012-08-07 18:24:38 +0000227 double
cristy3ed852e2009-09-05 21:47:34 +0000228 red,
229 green,
230 blue,
cristy4c08aed2011-07-01 19:47:50 +0000231 alpha;
Cristy2f4e2b22017-01-22 17:25:53 -0500232} DoublePixelPacket;
cristy3ed852e2009-09-05 21:47:34 +0000233
234typedef struct _NodeInfo
235{
236 struct _NodeInfo
237 *parent,
238 *child[16];
239
240 MagickSizeType
241 number_unique;
242
Cristy2f4e2b22017-01-22 17:25:53 -0500243 DoublePixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000244 total_color;
245
cristya19f1d72012-08-07 18:24:38 +0000246 double
cristy3ed852e2009-09-05 21:47:34 +0000247 quantize_error;
248
cristybb503372010-05-27 20:51:26 +0000249 size_t
cristy3ed852e2009-09-05 21:47:34 +0000250 color_number,
251 id,
252 level;
253} NodeInfo;
254
255typedef struct _Nodes
256{
257 NodeInfo
258 *nodes;
259
260 struct _Nodes
261 *next;
262} Nodes;
263
264typedef struct _CubeInfo
265{
266 NodeInfo
267 *root;
268
cristybb503372010-05-27 20:51:26 +0000269 size_t
cristy3ed852e2009-09-05 21:47:34 +0000270 colors,
271 maximum_colors;
272
cristybb503372010-05-27 20:51:26 +0000273 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000274 transparent_index;
275
276 MagickSizeType
277 transparent_pixels;
278
Cristy2f4e2b22017-01-22 17:25:53 -0500279 DoublePixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000280 target;
281
cristya19f1d72012-08-07 18:24:38 +0000282 double
cristy3ed852e2009-09-05 21:47:34 +0000283 distance,
284 pruning_threshold,
285 next_threshold;
286
cristybb503372010-05-27 20:51:26 +0000287 size_t
cristy3ed852e2009-09-05 21:47:34 +0000288 nodes,
289 free_nodes,
290 color_number;
291
292 NodeInfo
293 *next_node;
294
295 Nodes
296 *node_queue;
297
cristya321eb72013-06-23 10:42:37 +0000298 MemoryInfo
299 *memory_info;
300
cristybb503372010-05-27 20:51:26 +0000301 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000302 *cache;
303
Cristy2f4e2b22017-01-22 17:25:53 -0500304 DoublePixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000305 error[ErrorQueueLength];
306
cristya19f1d72012-08-07 18:24:38 +0000307 double
cristy3ed852e2009-09-05 21:47:34 +0000308 weights[ErrorQueueLength];
309
310 QuantizeInfo
311 *quantize_info;
312
313 MagickBooleanType
314 associate_alpha;
315
cristybb503372010-05-27 20:51:26 +0000316 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000317 x,
318 y;
319
cristybb503372010-05-27 20:51:26 +0000320 size_t
cristy3ed852e2009-09-05 21:47:34 +0000321 depth;
322
323 MagickOffsetType
324 offset;
325
326 MagickSizeType
327 span;
328} CubeInfo;
329
330/*
331 Method prototypes.
332*/
333static CubeInfo
cristybb503372010-05-27 20:51:26 +0000334 *GetCubeInfo(const QuantizeInfo *,const size_t,const size_t);
cristy3ed852e2009-09-05 21:47:34 +0000335
336static NodeInfo
cristybb503372010-05-27 20:51:26 +0000337 *GetNodeInfo(CubeInfo *,const size_t,const size_t,NodeInfo *);
cristy3ed852e2009-09-05 21:47:34 +0000338
339static MagickBooleanType
cristy018f07f2011-09-04 21:15:19 +0000340 AssignImageColors(Image *,CubeInfo *,ExceptionInfo *),
cristy3ed852e2009-09-05 21:47:34 +0000341 ClassifyImageColors(CubeInfo *,const Image *,ExceptionInfo *),
cristy8a11cb12011-10-19 23:53:34 +0000342 DitherImage(Image *,CubeInfo *,ExceptionInfo *),
Dirk Lemstra25229f22020-04-27 15:01:47 +0200343 SetGrayscaleImage(Image *,ExceptionInfo *),
344 SetImageColormap(Image *,CubeInfo *,ExceptionInfo *);
cristy3ed852e2009-09-05 21:47:34 +0000345
346static void
347 ClosestColor(const Image *,CubeInfo *,const NodeInfo *),
Dirk Lemstra25229f22020-04-27 15:01:47 +0200348 DefineImageColormap(Image *,CubeInfo *,NodeInfo *),
cristy3ed852e2009-09-05 21:47:34 +0000349 DestroyCubeInfo(CubeInfo *),
dirkf20fa432015-11-22 11:03:51 +0100350 PruneLevel(CubeInfo *,const NodeInfo *),
351 PruneToCubeDepth(CubeInfo *,const NodeInfo *),
cristy3ed852e2009-09-05 21:47:34 +0000352 ReduceImageColors(const Image *,CubeInfo *);
353
354/*
355%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
356% %
357% %
358% %
359% A c q u i r e Q u a n t i z e I n f o %
360% %
361% %
362% %
363%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
364%
365% AcquireQuantizeInfo() allocates the QuantizeInfo structure.
366%
367% The format of the AcquireQuantizeInfo method is:
368%
369% QuantizeInfo *AcquireQuantizeInfo(const ImageInfo *image_info)
370%
371% A description of each parameter follows:
372%
373% o image_info: the image info.
374%
375*/
376MagickExport QuantizeInfo *AcquireQuantizeInfo(const ImageInfo *image_info)
377{
378 QuantizeInfo
379 *quantize_info;
380
Dirk Lemstra06344a02017-10-15 10:10:01 +0200381 quantize_info=(QuantizeInfo *) AcquireCriticalMemory(sizeof(*quantize_info));
cristy3ed852e2009-09-05 21:47:34 +0000382 GetQuantizeInfo(quantize_info);
383 if (image_info != (ImageInfo *) NULL)
384 {
385 const char
386 *option;
387
cristycbda6112012-05-27 20:57:16 +0000388 quantize_info->dither_method=image_info->dither == MagickFalse ?
389 NoDitherMethod : RiemersmaDitherMethod;
cristy3ed852e2009-09-05 21:47:34 +0000390 option=GetImageOption(image_info,"dither");
391 if (option != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +0000392 quantize_info->dither_method=(DitherMethod) ParseCommandOption(
cristy3ed852e2009-09-05 21:47:34 +0000393 MagickDitherOptions,MagickFalse,option);
394 quantize_info->measure_error=image_info->verbose;
395 }
396 return(quantize_info);
397}
398
399/*
400%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
401% %
402% %
403% %
404+ A s s i g n I m a g e C o l o r s %
405% %
406% %
407% %
408%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
409%
410% AssignImageColors() generates the output image from the pruned tree. The
411% output image consists of two parts: (1) A color map, which is an array
412% of color descriptions (RGB triples) for each color present in the
413% output image; (2) A pixel array, which represents each pixel as an
414% index into the color map array.
415%
416% First, the assignment phase makes one pass over the pruned color
417% description tree to establish the image's color map. For each node
418% with n2 > 0, it divides Sr, Sg, and Sb by n2 . This produces the mean
419% color of all pixels that classify no lower than this node. Each of
420% these colors becomes an entry in the color map.
421%
422% Finally, the assignment phase reclassifies each pixel in the pruned
423% tree to identify the deepest node containing the pixel's color. The
424% pixel's value in the pixel array becomes the index of this node's mean
425% color in the color map.
426%
427% The format of the AssignImageColors() method is:
428%
429% MagickBooleanType AssignImageColors(Image *image,CubeInfo *cube_info)
430%
431% A description of each parameter follows.
432%
433% o image: the image.
434%
435% o cube_info: A pointer to the Cube structure.
436%
437*/
438
cristy4c08aed2011-07-01 19:47:50 +0000439static inline void AssociateAlphaPixel(const Image *image,
Cristy2f4e2b22017-01-22 17:25:53 -0500440 const CubeInfo *cube_info,const Quantum *pixel,DoublePixelPacket *alpha_pixel)
cristy3ed852e2009-09-05 21:47:34 +0000441{
cristya19f1d72012-08-07 18:24:38 +0000442 double
cristy3ed852e2009-09-05 21:47:34 +0000443 alpha;
444
445 if ((cube_info->associate_alpha == MagickFalse) ||
cristy09e81242015-01-31 17:00:00 +0000446 (GetPixelAlpha(image,pixel) == OpaqueAlpha))
cristy3ed852e2009-09-05 21:47:34 +0000447 {
cristya19f1d72012-08-07 18:24:38 +0000448 alpha_pixel->red=(double) GetPixelRed(image,pixel);
449 alpha_pixel->green=(double) GetPixelGreen(image,pixel);
450 alpha_pixel->blue=(double) GetPixelBlue(image,pixel);
451 alpha_pixel->alpha=(double) GetPixelAlpha(image,pixel);
cristy3ed852e2009-09-05 21:47:34 +0000452 return;
453 }
cristya19f1d72012-08-07 18:24:38 +0000454 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixel));
cristy4c08aed2011-07-01 19:47:50 +0000455 alpha_pixel->red=alpha*GetPixelRed(image,pixel);
456 alpha_pixel->green=alpha*GetPixelGreen(image,pixel);
457 alpha_pixel->blue=alpha*GetPixelBlue(image,pixel);
cristya19f1d72012-08-07 18:24:38 +0000458 alpha_pixel->alpha=(double) GetPixelAlpha(image,pixel);
cristy4c08aed2011-07-01 19:47:50 +0000459}
460
cristyb0de93f2013-05-03 13:39:25 +0000461static inline void AssociateAlphaPixelInfo(const CubeInfo *cube_info,
Cristy2f4e2b22017-01-22 17:25:53 -0500462 const PixelInfo *pixel,DoublePixelPacket *alpha_pixel)
cristy4c08aed2011-07-01 19:47:50 +0000463{
cristya19f1d72012-08-07 18:24:38 +0000464 double
cristy4c08aed2011-07-01 19:47:50 +0000465 alpha;
466
467 if ((cube_info->associate_alpha == MagickFalse) ||
468 (pixel->alpha == OpaqueAlpha))
469 {
cristya19f1d72012-08-07 18:24:38 +0000470 alpha_pixel->red=(double) pixel->red;
471 alpha_pixel->green=(double) pixel->green;
472 alpha_pixel->blue=(double) pixel->blue;
473 alpha_pixel->alpha=(double) pixel->alpha;
cristy4c08aed2011-07-01 19:47:50 +0000474 return;
475 }
cristya19f1d72012-08-07 18:24:38 +0000476 alpha=(double) (QuantumScale*pixel->alpha);
cristy4c08aed2011-07-01 19:47:50 +0000477 alpha_pixel->red=alpha*pixel->red;
478 alpha_pixel->green=alpha*pixel->green;
479 alpha_pixel->blue=alpha*pixel->blue;
cristya19f1d72012-08-07 18:24:38 +0000480 alpha_pixel->alpha=(double) pixel->alpha;
cristy3ed852e2009-09-05 21:47:34 +0000481}
482
cristybb503372010-05-27 20:51:26 +0000483static inline size_t ColorToNodeId(const CubeInfo *cube_info,
Cristy2f4e2b22017-01-22 17:25:53 -0500484 const DoublePixelPacket *pixel,size_t index)
cristy3ed852e2009-09-05 21:47:34 +0000485{
cristybb503372010-05-27 20:51:26 +0000486 size_t
cristy3ed852e2009-09-05 21:47:34 +0000487 id;
488
cristy6f7e0422012-12-25 20:04:53 +0000489 id=(size_t) (((ScaleQuantumToChar(ClampPixel(pixel->red)) >> index) & 0x01) |
490 ((ScaleQuantumToChar(ClampPixel(pixel->green)) >> index) & 0x01) << 1 |
491 ((ScaleQuantumToChar(ClampPixel(pixel->blue)) >> index) & 0x01) << 2);
cristy3ed852e2009-09-05 21:47:34 +0000492 if (cube_info->associate_alpha != MagickFalse)
cristy6f7e0422012-12-25 20:04:53 +0000493 id|=((ScaleQuantumToChar(ClampPixel(pixel->alpha)) >> index) & 0x1) << 3;
cristy3ed852e2009-09-05 21:47:34 +0000494 return(id);
495}
496
dirke0e98812015-07-06 20:52:58 +0000497static MagickBooleanType AssignImageColors(Image *image,CubeInfo *cube_info,
498 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000499{
dirke0e98812015-07-06 20:52:58 +0000500#define AssignImageTag "Assign/Image"
501
Cristy470e71c2018-03-02 20:19:10 -0500502 ColorspaceType
503 colorspace;
504
dirke0e98812015-07-06 20:52:58 +0000505 ssize_t
506 y;
507
cristy3ed852e2009-09-05 21:47:34 +0000508 /*
509 Allocate image colormap.
510 */
Cristy470e71c2018-03-02 20:19:10 -0500511 colorspace=image->colorspace;
Cristya141b122018-03-02 20:12:55 -0500512 if (cube_info->quantize_info->colorspace != UndefinedColorspace)
Cristy0b0d7352015-12-06 18:47:24 -0500513 (void) TransformImageColorspace(image,cube_info->quantize_info->colorspace,
514 exception);
cristy3ed852e2009-09-05 21:47:34 +0000515 cube_info->transparent_pixels=0;
516 cube_info->transparent_index=(-1);
Dirk Lemstra25229f22020-04-27 15:01:47 +0200517 if (SetImageColormap(image,cube_info,exception) == MagickFalse)
518 return(MagickFalse);
cristy3ed852e2009-09-05 21:47:34 +0000519 /*
520 Create a reduced color image.
521 */
cristyf2a82ee2014-05-26 17:49:54 +0000522 if (cube_info->quantize_info->dither_method != NoDitherMethod)
cristy8a11cb12011-10-19 23:53:34 +0000523 (void) DitherImage(image,cube_info,exception);
cristy3ed852e2009-09-05 21:47:34 +0000524 else
525 {
cristy3ed852e2009-09-05 21:47:34 +0000526 CacheView
527 *image_view;
528
cristye9717ac2011-02-20 16:17:17 +0000529 MagickBooleanType
530 status;
531
532 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000533 image_view=AcquireAuthenticCacheView(image,exception);
cristye9717ac2011-02-20 16:17:17 +0000534#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -0400535 #pragma omp parallel for schedule(static) shared(status) \
Cristy8255ca92017-10-09 08:37:35 -0400536 magick_number_threads(image,image,image->rows,1)
cristye9717ac2011-02-20 16:17:17 +0000537#endif
cristybb503372010-05-27 20:51:26 +0000538 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000539 {
cristye9717ac2011-02-20 16:17:17 +0000540 CubeInfo
541 cube;
542
Cristyf2dc1dd2020-12-28 13:59:26 -0500543 Quantum
dirk05d2ff72015-11-18 23:13:43 +0100544 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000545
Cristyf2dc1dd2020-12-28 13:59:26 -0500546 ssize_t
cristye9717ac2011-02-20 16:17:17 +0000547 x;
548
549 ssize_t
550 count;
551
552 if (status == MagickFalse)
553 continue;
cristy3ed852e2009-09-05 21:47:34 +0000554 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
555 exception);
cristyacd2ed22011-08-30 01:44:23 +0000556 if (q == (Quantum *) NULL)
cristye9717ac2011-02-20 16:17:17 +0000557 {
558 status=MagickFalse;
559 continue;
560 }
cristye9717ac2011-02-20 16:17:17 +0000561 cube=(*cube_info);
cristybb503372010-05-27 20:51:26 +0000562 for (x=0; x < (ssize_t) image->columns; x+=count)
cristy3ed852e2009-09-05 21:47:34 +0000563 {
Cristy2f4e2b22017-01-22 17:25:53 -0500564 DoublePixelPacket
cristye9717ac2011-02-20 16:17:17 +0000565 pixel;
566
Cristyf2dc1dd2020-12-28 13:59:26 -0500567 const NodeInfo
cristye9717ac2011-02-20 16:17:17 +0000568 *node_info;
569
Cristyf2dc1dd2020-12-28 13:59:26 -0500570 ssize_t
cristye9717ac2011-02-20 16:17:17 +0000571 i;
572
573 size_t
574 id,
575 index;
576
cristy3ed852e2009-09-05 21:47:34 +0000577 /*
578 Identify the deepest node containing the pixel's color.
579 */
cristybb503372010-05-27 20:51:26 +0000580 for (count=1; (x+count) < (ssize_t) image->columns; count++)
cristy4c08aed2011-07-01 19:47:50 +0000581 {
cristy101ab702011-10-13 13:06:32 +0000582 PixelInfo
cristy4c08aed2011-07-01 19:47:50 +0000583 packet;
584
cristy101ab702011-10-13 13:06:32 +0000585 GetPixelInfoPixel(image,q+count*GetPixelChannels(image),&packet);
cristy4c08aed2011-07-01 19:47:50 +0000586 if (IsPixelEquivalent(image,q,&packet) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000587 break;
cristy4c08aed2011-07-01 19:47:50 +0000588 }
589 AssociateAlphaPixel(image,&cube,q,&pixel);
cristye9717ac2011-02-20 16:17:17 +0000590 node_info=cube.root;
cristybb503372010-05-27 20:51:26 +0000591 for (index=MaxTreeDepth-1; (ssize_t) index > 0; index--)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
cristye9717ac2011-02-20 16:17:17 +0000593 id=ColorToNodeId(&cube,&pixel,index);
cristy3ed852e2009-09-05 21:47:34 +0000594 if (node_info->child[id] == (NodeInfo *) NULL)
595 break;
596 node_info=node_info->child[id];
597 }
598 /*
599 Find closest color among siblings and their children.
600 */
cristye9717ac2011-02-20 16:17:17 +0000601 cube.target=pixel;
cristy09e81242015-01-31 17:00:00 +0000602 cube.distance=(double) (4.0*(QuantumRange+1.0)*(QuantumRange+1.0)+
603 1.0);
cristye9717ac2011-02-20 16:17:17 +0000604 ClosestColor(image,&cube,node_info->parent);
605 index=cube.color_number;
cristybb503372010-05-27 20:51:26 +0000606 for (i=0; i < (ssize_t) count; i++)
cristy3ed852e2009-09-05 21:47:34 +0000607 {
608 if (image->storage_class == PseudoClass)
cristy4c08aed2011-07-01 19:47:50 +0000609 SetPixelIndex(image,(Quantum) index,q);
cristye9717ac2011-02-20 16:17:17 +0000610 if (cube.quantize_info->measure_error == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000611 {
cristye42f6582012-02-11 17:59:50 +0000612 SetPixelRed(image,ClampToQuantum(
613 image->colormap[index].red),q);
614 SetPixelGreen(image,ClampToQuantum(
615 image->colormap[index].green),q);
616 SetPixelBlue(image,ClampToQuantum(
617 image->colormap[index].blue),q);
cristye9717ac2011-02-20 16:17:17 +0000618 if (cube.associate_alpha != MagickFalse)
cristye42f6582012-02-11 17:59:50 +0000619 SetPixelAlpha(image,ClampToQuantum(
620 image->colormap[index].alpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000621 }
cristyed231572011-07-14 02:18:59 +0000622 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000623 }
624 }
625 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
cristye9717ac2011-02-20 16:17:17 +0000626 status=MagickFalse;
627 if (image->progress_monitor != (MagickProgressMonitor) NULL)
628 {
629 MagickBooleanType
630 proceed;
631
cristye9717ac2011-02-20 16:17:17 +0000632 proceed=SetImageProgress(image,AssignImageTag,(MagickOffsetType) y,
633 image->rows);
634 if (proceed == MagickFalse)
635 status=MagickFalse;
636 }
cristy3ed852e2009-09-05 21:47:34 +0000637 }
638 image_view=DestroyCacheView(image_view);
639 }
dirke0e98812015-07-06 20:52:58 +0000640 if (cube_info->quantize_info->measure_error != MagickFalse)
641 (void) GetImageQuantizeError(image,exception);
642 if ((cube_info->quantize_info->number_colors == 2) &&
Cristybeb4c2b2017-12-26 19:43:17 -0500643 ((cube_info->quantize_info->colorspace == LinearGRAYColorspace) ||
644 (cube_info->quantize_info->colorspace == GRAYColorspace)))
dirke0e98812015-07-06 20:52:58 +0000645 {
646 double
647 intensity;
648
dirke0e98812015-07-06 20:52:58 +0000649 /*
650 Monochrome image.
651 */
Cristy96aa7562020-01-12 10:31:04 -0500652 intensity=GetPixelInfoLuma(image->colormap+0) < QuantumRange/2.0 ? 0.0 :
Cristyb0799c42019-12-27 12:57:53 -0500653 QuantumRange;
Cristy971f2e12020-01-03 17:20:25 -0500654 if (image->colors > 1)
655 {
656 intensity=0.0;
657 if (GetPixelInfoLuma(image->colormap+0) >
658 GetPixelInfoLuma(image->colormap+1))
659 intensity=(double) QuantumRange;
660 }
Cristy0b0d7352015-12-06 18:47:24 -0500661 image->colormap[0].red=intensity;
662 image->colormap[0].green=intensity;
663 image->colormap[0].blue=intensity;
Cristyd95b6192016-01-24 18:45:59 -0500664 if (image->colors > 1)
665 {
666 image->colormap[1].red=(double) QuantumRange-intensity;
667 image->colormap[1].green=(double) QuantumRange-intensity;
668 image->colormap[1].blue=(double) QuantumRange-intensity;
669 }
dirke0e98812015-07-06 20:52:58 +0000670 }
cristyea1a8aa2011-10-20 13:24:06 +0000671 (void) SyncImage(image,exception);
Cristya59574a2018-03-02 20:26:16 -0500672 if ((cube_info->quantize_info->colorspace != UndefinedColorspace) &&
Cristyd61de6b2018-03-02 20:28:11 -0500673 (IssRGBCompatibleColorspace(colorspace) == MagickFalse))
Cristy470e71c2018-03-02 20:19:10 -0500674 (void) TransformImageColorspace(image,colorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +0000675 return(MagickTrue);
676}
677
678/*
679%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
680% %
681% %
682% %
683+ C l a s s i f y I m a g e C o l o r s %
684% %
685% %
686% %
687%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
688%
689% ClassifyImageColors() begins by initializing a color description tree
690% of sufficient depth to represent each possible input color in a leaf.
691% However, it is impractical to generate a fully-formed color
692% description tree in the storage_class phase for realistic values of
693% Cmax. If colors components in the input image are quantized to k-bit
694% precision, so that Cmax= 2k-1, the tree would need k levels below the
695% root node to allow representing each possible input color in a leaf.
696% This becomes prohibitive because the tree's total number of nodes is
697% 1 + sum(i=1,k,8k).
698%
699% A complete tree would require 19,173,961 nodes for k = 8, Cmax = 255.
700% Therefore, to avoid building a fully populated tree, QUANTIZE: (1)
701% Initializes data structures for nodes only as they are needed; (2)
702% Chooses a maximum depth for the tree as a function of the desired
703% number of colors in the output image (currently log2(colormap size)).
704%
705% For each pixel in the input image, storage_class scans downward from
706% the root of the color description tree. At each level of the tree it
707% identifies the single node which represents a cube in RGB space
708% containing It updates the following data for each such node:
709%
710% n1 : Number of pixels whose color is contained in the RGB cube
711% which this node represents;
712%
713% n2 : Number of pixels whose color is not represented in a node at
714% lower depth in the tree; initially, n2 = 0 for all nodes except
715% leaves of the tree.
716%
717% Sr, Sg, Sb : Sums of the red, green, and blue component values for
718% all pixels not classified at a lower depth. The combination of
cristy09e81242015-01-31 17:00:00 +0000719% these sums and n2 will ultimately characterize the mean color of a
cristy3ed852e2009-09-05 21:47:34 +0000720% set of pixels represented by this node.
721%
722% E: the distance squared in RGB space between each pixel contained
723% within a node and the nodes' center. This represents the quantization
724% error for a node.
725%
726% The format of the ClassifyImageColors() method is:
727%
728% MagickBooleanType ClassifyImageColors(CubeInfo *cube_info,
729% const Image *image,ExceptionInfo *exception)
730%
731% A description of each parameter follows.
732%
733% o cube_info: A pointer to the Cube structure.
734%
735% o image: the image.
736%
737*/
738
739static inline void SetAssociatedAlpha(const Image *image,CubeInfo *cube_info)
740{
741 MagickBooleanType
742 associate_alpha;
743
cristy09e81242015-01-31 17:00:00 +0000744 associate_alpha=image->alpha_trait == BlendPixelTrait ? MagickTrue :
cristyb0a657e2012-08-29 00:45:37 +0000745 MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +0000746 if ((cube_info->quantize_info->number_colors == 2) &&
Cristybeb4c2b2017-12-26 19:43:17 -0500747 ((cube_info->quantize_info->colorspace == LinearGRAYColorspace) ||
748 (cube_info->quantize_info->colorspace == GRAYColorspace)))
cristy3ed852e2009-09-05 21:47:34 +0000749 associate_alpha=MagickFalse;
750 cube_info->associate_alpha=associate_alpha;
751}
752
753static MagickBooleanType ClassifyImageColors(CubeInfo *cube_info,
754 const Image *image,ExceptionInfo *exception)
755{
756#define ClassifyImageTag "Classify/Image"
757
cristyc4c8d132010-01-07 01:58:38 +0000758 CacheView
759 *image_view;
760
Cristy2f4e2b22017-01-22 17:25:53 -0500761 DoublePixelPacket
Cristy2392b4b2015-12-04 18:28:23 -0500762 error,
763 mid,
764 midpoint,
765 pixel;
766
cristy3ed852e2009-09-05 21:47:34 +0000767 MagickBooleanType
768 proceed;
769
cristya19f1d72012-08-07 18:24:38 +0000770 double
cristy3ed852e2009-09-05 21:47:34 +0000771 bisect;
772
773 NodeInfo
774 *node_info;
775
cristy3ed852e2009-09-05 21:47:34 +0000776 size_t
cristyecc31b12011-02-13 00:32:29 +0000777 count,
cristy3ed852e2009-09-05 21:47:34 +0000778 id,
779 index,
780 level;
781
cristyecc31b12011-02-13 00:32:29 +0000782 ssize_t
783 y;
784
cristy3ed852e2009-09-05 21:47:34 +0000785 /*
786 Classify the first cube_info->maximum_colors colors to a tree depth of 8.
787 */
788 SetAssociatedAlpha(image,cube_info);
Cristyc6c450d2020-01-18 11:21:12 -0500789 if (cube_info->quantize_info->colorspace != image->colorspace)
790 {
791 if ((cube_info->quantize_info->colorspace != UndefinedColorspace) &&
792 (cube_info->quantize_info->colorspace != CMYKColorspace))
793 (void) TransformImageColorspace((Image *) image,
794 cube_info->quantize_info->colorspace,exception);
795 else
796 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
797 (void) TransformImageColorspace((Image *) image,sRGBColorspace,
798 exception);
799 }
cristya19f1d72012-08-07 18:24:38 +0000800 midpoint.red=(double) QuantumRange/2.0;
801 midpoint.green=(double) QuantumRange/2.0;
802 midpoint.blue=(double) QuantumRange/2.0;
803 midpoint.alpha=(double) QuantumRange/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000804 error.alpha=0.0;
cristy46ff2672012-12-14 15:32:26 +0000805 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +0000806 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000807 {
Cristyf2dc1dd2020-12-28 13:59:26 -0500808 const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100809 *magick_restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000810
Cristyf2dc1dd2020-12-28 13:59:26 -0500811 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000812 x;
813
814 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000815 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000816 break;
817 if (cube_info->nodes > MaxNodes)
818 {
819 /*
820 Prune one level if the color tree is too large.
821 */
dirkf20fa432015-11-22 11:03:51 +0100822 PruneLevel(cube_info,cube_info->root);
cristy3ed852e2009-09-05 21:47:34 +0000823 cube_info->depth--;
824 }
cristybb503372010-05-27 20:51:26 +0000825 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) count)
cristy3ed852e2009-09-05 21:47:34 +0000826 {
827 /*
828 Start at the root and descend the color cube tree.
829 */
cristybb66d9c2010-10-09 01:40:31 +0000830 for (count=1; (x+(ssize_t) count) < (ssize_t) image->columns; count++)
cristy4c08aed2011-07-01 19:47:50 +0000831 {
cristy101ab702011-10-13 13:06:32 +0000832 PixelInfo
cristy4c08aed2011-07-01 19:47:50 +0000833 packet;
834
cristy101ab702011-10-13 13:06:32 +0000835 GetPixelInfoPixel(image,p+count*GetPixelChannels(image),&packet);
cristy4c08aed2011-07-01 19:47:50 +0000836 if (IsPixelEquivalent(image,p,&packet) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000837 break;
cristy4c08aed2011-07-01 19:47:50 +0000838 }
839 AssociateAlphaPixel(image,cube_info,p,&pixel);
cristy3ed852e2009-09-05 21:47:34 +0000840 index=MaxTreeDepth-1;
cristya19f1d72012-08-07 18:24:38 +0000841 bisect=((double) QuantumRange+1.0)/2.0;
cristy3ed852e2009-09-05 21:47:34 +0000842 mid=midpoint;
843 node_info=cube_info->root;
844 for (level=1; level <= MaxTreeDepth; level++)
845 {
cristyb4d27c32014-09-24 12:59:33 +0000846 double
847 distance;
848
cristy3ed852e2009-09-05 21:47:34 +0000849 bisect*=0.5;
850 id=ColorToNodeId(cube_info,&pixel,index);
851 mid.red+=(id & 1) != 0 ? bisect : -bisect;
852 mid.green+=(id & 2) != 0 ? bisect : -bisect;
853 mid.blue+=(id & 4) != 0 ? bisect : -bisect;
cristy4c08aed2011-07-01 19:47:50 +0000854 mid.alpha+=(id & 8) != 0 ? bisect : -bisect;
cristy3ed852e2009-09-05 21:47:34 +0000855 if (node_info->child[id] == (NodeInfo *) NULL)
856 {
857 /*
858 Set colors of new node to contain pixel.
859 */
860 node_info->child[id]=GetNodeInfo(cube_info,id,level,node_info);
861 if (node_info->child[id] == (NodeInfo *) NULL)
cristyac21aa22014-01-15 00:51:18 +0000862 {
863 (void) ThrowMagickException(exception,GetMagickModule(),
864 ResourceLimitError,"MemoryAllocationFailed","`%s'",
865 image->filename);
866 continue;
867 }
cristy3ed852e2009-09-05 21:47:34 +0000868 if (level == MaxTreeDepth)
869 cube_info->colors++;
870 }
871 /*
872 Approximate the quantization error represented by this node.
873 */
874 node_info=node_info->child[id];
875 error.red=QuantumScale*(pixel.red-mid.red);
876 error.green=QuantumScale*(pixel.green-mid.green);
877 error.blue=QuantumScale*(pixel.blue-mid.blue);
878 if (cube_info->associate_alpha != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000879 error.alpha=QuantumScale*(pixel.alpha-mid.alpha);
cristyb4d27c32014-09-24 12:59:33 +0000880 distance=(double) (error.red*error.red+error.green*error.green+
881 error.blue*error.blue+error.alpha*error.alpha);
Cristya7edff62020-07-14 20:41:04 -0400882 if (IsNaN(distance) != 0)
cristyb4d27c32014-09-24 12:59:33 +0000883 distance=0.0;
884 node_info->quantize_error+=count*sqrt(distance);
cristy3ed852e2009-09-05 21:47:34 +0000885 cube_info->root->quantize_error+=node_info->quantize_error;
886 index--;
887 }
888 /*
889 Sum RGB for this leaf for later derivation of the mean cube color.
890 */
891 node_info->number_unique+=count;
cristy1aeeff32013-02-21 21:51:24 +0000892 node_info->total_color.red+=count*QuantumScale*ClampPixel(pixel.red);
893 node_info->total_color.green+=count*QuantumScale*ClampPixel(pixel.green);
894 node_info->total_color.blue+=count*QuantumScale*ClampPixel(pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +0000895 if (cube_info->associate_alpha != MagickFalse)
Cristy146c4822015-11-23 21:13:27 -0500896 node_info->total_color.alpha+=count*QuantumScale*
897 ClampPixel(pixel.alpha);
898 else
899 node_info->total_color.alpha+=count*QuantumScale*
Cristy8847db92018-03-10 16:55:54 -0500900 ClampPixel((MagickRealType) OpaqueAlpha);
cristyed231572011-07-14 02:18:59 +0000901 p+=count*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000902 }
903 if (cube_info->colors > cube_info->maximum_colors)
904 {
dirkf20fa432015-11-22 11:03:51 +0100905 PruneToCubeDepth(cube_info,cube_info->root);
cristy3ed852e2009-09-05 21:47:34 +0000906 break;
907 }
cristycee97112010-05-28 00:44:52 +0000908 proceed=SetImageProgress(image,ClassifyImageTag,(MagickOffsetType) y,
909 image->rows);
cristy3ed852e2009-09-05 21:47:34 +0000910 if (proceed == MagickFalse)
911 break;
912 }
cristybb503372010-05-27 20:51:26 +0000913 for (y++; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000914 {
Cristyf2dc1dd2020-12-28 13:59:26 -0500915 const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100916 *magick_restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000917
Cristyf2dc1dd2020-12-28 13:59:26 -0500918 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000919 x;
920
921 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000922 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000923 break;
924 if (cube_info->nodes > MaxNodes)
925 {
926 /*
927 Prune one level if the color tree is too large.
928 */
dirkf20fa432015-11-22 11:03:51 +0100929 PruneLevel(cube_info,cube_info->root);
cristy3ed852e2009-09-05 21:47:34 +0000930 cube_info->depth--;
931 }
cristybb503372010-05-27 20:51:26 +0000932 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) count)
cristy3ed852e2009-09-05 21:47:34 +0000933 {
934 /*
935 Start at the root and descend the color cube tree.
936 */
cristybb66d9c2010-10-09 01:40:31 +0000937 for (count=1; (x+(ssize_t) count) < (ssize_t) image->columns; count++)
cristy4c08aed2011-07-01 19:47:50 +0000938 {
cristy101ab702011-10-13 13:06:32 +0000939 PixelInfo
cristy4c08aed2011-07-01 19:47:50 +0000940 packet;
941
cristy101ab702011-10-13 13:06:32 +0000942 GetPixelInfoPixel(image,p+count*GetPixelChannels(image),&packet);
cristy4c08aed2011-07-01 19:47:50 +0000943 if (IsPixelEquivalent(image,p,&packet) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000944 break;
cristy4c08aed2011-07-01 19:47:50 +0000945 }
946 AssociateAlphaPixel(image,cube_info,p,&pixel);
cristy3ed852e2009-09-05 21:47:34 +0000947 index=MaxTreeDepth-1;
cristya19f1d72012-08-07 18:24:38 +0000948 bisect=((double) QuantumRange+1.0)/2.0;
cristy3ed852e2009-09-05 21:47:34 +0000949 mid=midpoint;
950 node_info=cube_info->root;
951 for (level=1; level <= cube_info->depth; level++)
952 {
cristyb4d27c32014-09-24 12:59:33 +0000953 double
954 distance;
955
cristy3ed852e2009-09-05 21:47:34 +0000956 bisect*=0.5;
957 id=ColorToNodeId(cube_info,&pixel,index);
958 mid.red+=(id & 1) != 0 ? bisect : -bisect;
959 mid.green+=(id & 2) != 0 ? bisect : -bisect;
960 mid.blue+=(id & 4) != 0 ? bisect : -bisect;
cristy4c08aed2011-07-01 19:47:50 +0000961 mid.alpha+=(id & 8) != 0 ? bisect : -bisect;
cristy3ed852e2009-09-05 21:47:34 +0000962 if (node_info->child[id] == (NodeInfo *) NULL)
963 {
964 /*
965 Set colors of new node to contain pixel.
966 */
967 node_info->child[id]=GetNodeInfo(cube_info,id,level,node_info);
968 if (node_info->child[id] == (NodeInfo *) NULL)
cristyac21aa22014-01-15 00:51:18 +0000969 {
970 (void) ThrowMagickException(exception,GetMagickModule(),
971 ResourceLimitError,"MemoryAllocationFailed","%s",
972 image->filename);
973 continue;
974 }
cristy3ed852e2009-09-05 21:47:34 +0000975 if (level == cube_info->depth)
976 cube_info->colors++;
977 }
978 /*
979 Approximate the quantization error represented by this node.
980 */
981 node_info=node_info->child[id];
982 error.red=QuantumScale*(pixel.red-mid.red);
983 error.green=QuantumScale*(pixel.green-mid.green);
984 error.blue=QuantumScale*(pixel.blue-mid.blue);
985 if (cube_info->associate_alpha != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000986 error.alpha=QuantumScale*(pixel.alpha-mid.alpha);
cristyb4d27c32014-09-24 12:59:33 +0000987 distance=(double) (error.red*error.red+error.green*error.green+
988 error.blue*error.blue+error.alpha*error.alpha);
Cristya7edff62020-07-14 20:41:04 -0400989 if (IsNaN(distance) != 0)
cristyb4d27c32014-09-24 12:59:33 +0000990 distance=0.0;
991 node_info->quantize_error+=count*sqrt(distance);
cristy3ed852e2009-09-05 21:47:34 +0000992 cube_info->root->quantize_error+=node_info->quantize_error;
993 index--;
994 }
995 /*
996 Sum RGB for this leaf for later derivation of the mean cube color.
997 */
998 node_info->number_unique+=count;
cristy1aeeff32013-02-21 21:51:24 +0000999 node_info->total_color.red+=count*QuantumScale*ClampPixel(pixel.red);
1000 node_info->total_color.green+=count*QuantumScale*ClampPixel(pixel.green);
1001 node_info->total_color.blue+=count*QuantumScale*ClampPixel(pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00001002 if (cube_info->associate_alpha != MagickFalse)
Cristy146c4822015-11-23 21:13:27 -05001003 node_info->total_color.alpha+=count*QuantumScale*
1004 ClampPixel(pixel.alpha);
1005 else
1006 node_info->total_color.alpha+=count*QuantumScale*
Cristy8847db92018-03-10 16:55:54 -05001007 ClampPixel((MagickRealType) OpaqueAlpha);
cristyed231572011-07-14 02:18:59 +00001008 p+=count*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001009 }
cristycee97112010-05-28 00:44:52 +00001010 proceed=SetImageProgress(image,ClassifyImageTag,(MagickOffsetType) y,
1011 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001012 if (proceed == MagickFalse)
1013 break;
1014 }
1015 image_view=DestroyCacheView(image_view);
Cristyc6c450d2020-01-18 11:21:12 -05001016 if (cube_info->quantize_info->colorspace != image->colorspace)
1017 if ((cube_info->quantize_info->colorspace != UndefinedColorspace) &&
1018 (cube_info->quantize_info->colorspace != CMYKColorspace))
1019 (void) TransformImageColorspace((Image *) image,sRGBColorspace,exception);
cristyac21aa22014-01-15 00:51:18 +00001020 return(y < (ssize_t) image->rows ? MagickFalse : MagickTrue);
cristy3ed852e2009-09-05 21:47:34 +00001021}
1022
1023/*
1024%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1025% %
1026% %
1027% %
1028% C l o n e Q u a n t i z e I n f o %
1029% %
1030% %
1031% %
1032%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1033%
1034% CloneQuantizeInfo() makes a duplicate of the given quantize info structure,
1035% or if quantize info is NULL, a new one.
1036%
1037% The format of the CloneQuantizeInfo method is:
1038%
1039% QuantizeInfo *CloneQuantizeInfo(const QuantizeInfo *quantize_info)
1040%
1041% A description of each parameter follows:
1042%
1043% o clone_info: Method CloneQuantizeInfo returns a duplicate of the given
1044% quantize info, or if image info is NULL a new one.
1045%
1046% o quantize_info: a structure of type info.
1047%
1048*/
1049MagickExport QuantizeInfo *CloneQuantizeInfo(const QuantizeInfo *quantize_info)
1050{
1051 QuantizeInfo
1052 *clone_info;
1053
Dirk Lemstra06344a02017-10-15 10:10:01 +02001054 clone_info=(QuantizeInfo *) AcquireCriticalMemory(sizeof(*clone_info));
cristy3ed852e2009-09-05 21:47:34 +00001055 GetQuantizeInfo(clone_info);
1056 if (quantize_info == (QuantizeInfo *) NULL)
1057 return(clone_info);
1058 clone_info->number_colors=quantize_info->number_colors;
1059 clone_info->tree_depth=quantize_info->tree_depth;
cristy3ed852e2009-09-05 21:47:34 +00001060 clone_info->dither_method=quantize_info->dither_method;
1061 clone_info->colorspace=quantize_info->colorspace;
1062 clone_info->measure_error=quantize_info->measure_error;
1063 return(clone_info);
1064}
1065
1066/*
1067%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1068% %
1069% %
1070% %
1071+ C l o s e s t C o l o r %
1072% %
1073% %
1074% %
1075%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1076%
1077% ClosestColor() traverses the color cube tree at a particular node and
1078% determines which colormap entry best represents the input color.
1079%
1080% The format of the ClosestColor method is:
1081%
1082% void ClosestColor(const Image *image,CubeInfo *cube_info,
1083% const NodeInfo *node_info)
1084%
1085% A description of each parameter follows.
1086%
1087% o image: the image.
1088%
1089% o cube_info: A pointer to the Cube structure.
1090%
1091% o node_info: the address of a structure of type NodeInfo which points to a
1092% node in the color cube tree that is to be pruned.
1093%
1094*/
1095static void ClosestColor(const Image *image,CubeInfo *cube_info,
1096 const NodeInfo *node_info)
1097{
Cristyf2dc1dd2020-12-28 13:59:26 -05001098 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001099 i;
1100
cristybb503372010-05-27 20:51:26 +00001101 size_t
cristy3ed852e2009-09-05 21:47:34 +00001102 number_children;
1103
1104 /*
1105 Traverse any children.
1106 */
1107 number_children=cube_info->associate_alpha == MagickFalse ? 8UL : 16UL;
cristybb503372010-05-27 20:51:26 +00001108 for (i=0; i < (ssize_t) number_children; i++)
cristy3ed852e2009-09-05 21:47:34 +00001109 if (node_info->child[i] != (NodeInfo *) NULL)
1110 ClosestColor(image,cube_info,node_info->child[i]);
1111 if (node_info->number_unique != 0)
1112 {
cristya19f1d72012-08-07 18:24:38 +00001113 double
cristy3ed852e2009-09-05 21:47:34 +00001114 pixel;
1115
Cristyf2dc1dd2020-12-28 13:59:26 -05001116 double
cristy3ed852e2009-09-05 21:47:34 +00001117 alpha,
1118 beta,
1119 distance;
1120
Cristyf2dc1dd2020-12-28 13:59:26 -05001121 DoublePixelPacket
Cristy2392b4b2015-12-04 18:28:23 -05001122 *magick_restrict q;
1123
Cristyf2dc1dd2020-12-28 13:59:26 -05001124 PixelInfo
dirk05d2ff72015-11-18 23:13:43 +01001125 *magick_restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001126
cristy3ed852e2009-09-05 21:47:34 +00001127 /*
1128 Determine if this color is "closest".
1129 */
1130 p=image->colormap+node_info->color_number;
1131 q=(&cube_info->target);
1132 alpha=1.0;
1133 beta=1.0;
cristy847620f2011-02-09 02:24:21 +00001134 if (cube_info->associate_alpha != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001135 {
cristya19f1d72012-08-07 18:24:38 +00001136 alpha=(double) (QuantumScale*p->alpha);
1137 beta=(double) (QuantumScale*q->alpha);
cristy3ed852e2009-09-05 21:47:34 +00001138 }
cristy4c08aed2011-07-01 19:47:50 +00001139 pixel=alpha*p->red-beta*q->red;
cristy3ed852e2009-09-05 21:47:34 +00001140 distance=pixel*pixel;
cristy36fbc3b2011-02-09 02:30:04 +00001141 if (distance <= cube_info->distance)
cristy3ed852e2009-09-05 21:47:34 +00001142 {
cristy4c08aed2011-07-01 19:47:50 +00001143 pixel=alpha*p->green-beta*q->green;
cristy3ed852e2009-09-05 21:47:34 +00001144 distance+=pixel*pixel;
cristy36fbc3b2011-02-09 02:30:04 +00001145 if (distance <= cube_info->distance)
cristy3ed852e2009-09-05 21:47:34 +00001146 {
cristy4c08aed2011-07-01 19:47:50 +00001147 pixel=alpha*p->blue-beta*q->blue;
cristy3ed852e2009-09-05 21:47:34 +00001148 distance+=pixel*pixel;
cristy36fbc3b2011-02-09 02:30:04 +00001149 if (distance <= cube_info->distance)
cristy3ed852e2009-09-05 21:47:34 +00001150 {
Cristy29e8af72015-10-17 11:43:59 -04001151 if (cube_info->associate_alpha != MagickFalse)
1152 {
1153 pixel=p->alpha-q->alpha;
1154 distance+=pixel*pixel;
1155 }
cristyc4080402011-02-09 02:55:58 +00001156 if (distance <= cube_info->distance)
cristy3ed852e2009-09-05 21:47:34 +00001157 {
1158 cube_info->distance=distance;
1159 cube_info->color_number=node_info->color_number;
1160 }
1161 }
1162 }
1163 }
1164 }
1165}
1166
1167/*
1168%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1169% %
1170% %
1171% %
1172% C o m p r e s s I m a g e C o l o r m a p %
1173% %
1174% %
1175% %
1176%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1177%
1178% CompressImageColormap() compresses an image colormap by removing any
1179% duplicate or unused color entries.
1180%
1181% The format of the CompressImageColormap method is:
1182%
cristy018f07f2011-09-04 21:15:19 +00001183% MagickBooleanType CompressImageColormap(Image *image,
1184% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001185%
1186% A description of each parameter follows:
1187%
1188% o image: the image.
1189%
cristy018f07f2011-09-04 21:15:19 +00001190% o exception: return any errors or warnings in this structure.
1191%
cristy3ed852e2009-09-05 21:47:34 +00001192*/
cristy018f07f2011-09-04 21:15:19 +00001193MagickExport MagickBooleanType CompressImageColormap(Image *image,
1194 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001195{
1196 QuantizeInfo
1197 quantize_info;
1198
1199 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001200 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001201 if (image->debug != MagickFalse)
1202 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
Cristy14b1a182017-07-21 12:29:32 -04001203 if (IsPaletteImage(image) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001204 return(MagickFalse);
1205 GetQuantizeInfo(&quantize_info);
1206 quantize_info.number_colors=image->colors;
1207 quantize_info.tree_depth=MaxTreeDepth;
cristy018f07f2011-09-04 21:15:19 +00001208 return(QuantizeImage(&quantize_info,image,exception));
cristy3ed852e2009-09-05 21:47:34 +00001209}
1210
1211/*
1212%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1213% %
1214% %
1215% %
1216+ D e f i n e I m a g e C o l o r m a p %
1217% %
1218% %
1219% %
1220%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1221%
1222% DefineImageColormap() traverses the color cube tree and notes each colormap
1223% entry. A colormap entry is any node in the color cube tree where the
Dirk Lemstra25229f22020-04-27 15:01:47 +02001224% of unique colors is not zero.
cristy3ed852e2009-09-05 21:47:34 +00001225%
1226% The format of the DefineImageColormap method is:
1227%
Dirk Lemstra25229f22020-04-27 15:01:47 +02001228% void DefineImageColormap(Image *image,CubeInfo *cube_info,
cristy3ed852e2009-09-05 21:47:34 +00001229% NodeInfo *node_info)
1230%
1231% A description of each parameter follows.
1232%
1233% o image: the image.
1234%
1235% o cube_info: A pointer to the Cube structure.
1236%
1237% o node_info: the address of a structure of type NodeInfo which points to a
1238% node in the color cube tree that is to be pruned.
1239%
1240*/
Dirk Lemstra25229f22020-04-27 15:01:47 +02001241static void DefineImageColormap(Image *image,CubeInfo *cube_info,
cristy3ed852e2009-09-05 21:47:34 +00001242 NodeInfo *node_info)
1243{
Cristyf2dc1dd2020-12-28 13:59:26 -05001244 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001245 i;
1246
cristybb503372010-05-27 20:51:26 +00001247 size_t
cristy3ed852e2009-09-05 21:47:34 +00001248 number_children;
1249
1250 /*
1251 Traverse any children.
1252 */
1253 number_children=cube_info->associate_alpha == MagickFalse ? 8UL : 16UL;
cristybb503372010-05-27 20:51:26 +00001254 for (i=0; i < (ssize_t) number_children; i++)
cristy3ed852e2009-09-05 21:47:34 +00001255 if (node_info->child[i] != (NodeInfo *) NULL)
Dirk Lemstra25229f22020-04-27 15:01:47 +02001256 DefineImageColormap(image,cube_info,node_info->child[i]);
cristy3ed852e2009-09-05 21:47:34 +00001257 if (node_info->number_unique != 0)
1258 {
Cristyf2dc1dd2020-12-28 13:59:26 -05001259 double
cristy3ed852e2009-09-05 21:47:34 +00001260 alpha;
1261
Cristyf2dc1dd2020-12-28 13:59:26 -05001262 PixelInfo
dirk05d2ff72015-11-18 23:13:43 +01001263 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001264
1265 /*
1266 Colormap entry is defined by the mean color in this cube.
1267 */
1268 q=image->colormap+image->colors;
cristya19f1d72012-08-07 18:24:38 +00001269 alpha=(double) ((MagickOffsetType) node_info->number_unique);
cristy3e3ec3a2012-11-03 23:11:06 +00001270 alpha=PerceptibleReciprocal(alpha);
cristy3ed852e2009-09-05 21:47:34 +00001271 if (cube_info->associate_alpha == MagickFalse)
1272 {
cristy1aeeff32013-02-21 21:51:24 +00001273 q->red=(double) ClampToQuantum(alpha*QuantumRange*
1274 node_info->total_color.red);
1275 q->green=(double) ClampToQuantum(alpha*QuantumRange*
1276 node_info->total_color.green);
1277 q->blue=(double) ClampToQuantum(alpha*QuantumRange*
1278 node_info->total_color.blue);
1279 q->alpha=(double) OpaqueAlpha;
cristy3ed852e2009-09-05 21:47:34 +00001280 }
1281 else
1282 {
cristya19f1d72012-08-07 18:24:38 +00001283 double
cristy3ed852e2009-09-05 21:47:34 +00001284 opacity;
1285
cristy1aeeff32013-02-21 21:51:24 +00001286 opacity=(double) (alpha*QuantumRange*node_info->total_color.alpha);
cristy09e81242015-01-31 17:00:00 +00001287 q->alpha=(double) ClampToQuantum(opacity);
cristy4c08aed2011-07-01 19:47:50 +00001288 if (q->alpha == OpaqueAlpha)
cristy3ed852e2009-09-05 21:47:34 +00001289 {
cristy1aeeff32013-02-21 21:51:24 +00001290 q->red=(double) ClampToQuantum(alpha*QuantumRange*
1291 node_info->total_color.red);
1292 q->green=(double) ClampToQuantum(alpha*QuantumRange*
1293 node_info->total_color.green);
1294 q->blue=(double) ClampToQuantum(alpha*QuantumRange*
1295 node_info->total_color.blue);
cristy3ed852e2009-09-05 21:47:34 +00001296 }
1297 else
1298 {
cristya19f1d72012-08-07 18:24:38 +00001299 double
cristy3ed852e2009-09-05 21:47:34 +00001300 gamma;
1301
cristya19f1d72012-08-07 18:24:38 +00001302 gamma=(double) (QuantumScale*q->alpha);
cristy3e3ec3a2012-11-03 23:11:06 +00001303 gamma=PerceptibleReciprocal(gamma);
cristy1aeeff32013-02-21 21:51:24 +00001304 q->red=(double) ClampToQuantum(alpha*gamma*QuantumRange*
1305 node_info->total_color.red);
1306 q->green=(double) ClampToQuantum(alpha*gamma*QuantumRange*
1307 node_info->total_color.green);
1308 q->blue=(double) ClampToQuantum(alpha*gamma*QuantumRange*
1309 node_info->total_color.blue);
cristy3ed852e2009-09-05 21:47:34 +00001310 if (node_info->number_unique > cube_info->transparent_pixels)
1311 {
1312 cube_info->transparent_pixels=node_info->number_unique;
cristybb503372010-05-27 20:51:26 +00001313 cube_info->transparent_index=(ssize_t) image->colors;
cristy3ed852e2009-09-05 21:47:34 +00001314 }
1315 }
1316 }
1317 node_info->color_number=image->colors++;
1318 }
cristy3ed852e2009-09-05 21:47:34 +00001319}
1320
1321/*
1322%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1323% %
1324% %
1325% %
1326+ D e s t r o y C u b e I n f o %
1327% %
1328% %
1329% %
1330%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1331%
1332% DestroyCubeInfo() deallocates memory associated with an image.
1333%
1334% The format of the DestroyCubeInfo method is:
1335%
1336% DestroyCubeInfo(CubeInfo *cube_info)
1337%
1338% A description of each parameter follows:
1339%
1340% o cube_info: the address of a structure of type CubeInfo.
1341%
1342*/
1343static void DestroyCubeInfo(CubeInfo *cube_info)
1344{
Cristyf2dc1dd2020-12-28 13:59:26 -05001345 Nodes
cristy3ed852e2009-09-05 21:47:34 +00001346 *nodes;
1347
1348 /*
1349 Release color cube tree storage.
1350 */
1351 do
1352 {
1353 nodes=cube_info->node_queue->next;
1354 cube_info->node_queue->nodes=(NodeInfo *) RelinquishMagickMemory(
1355 cube_info->node_queue->nodes);
1356 cube_info->node_queue=(Nodes *) RelinquishMagickMemory(
1357 cube_info->node_queue);
1358 cube_info->node_queue=nodes;
1359 } while (cube_info->node_queue != (Nodes *) NULL);
cristya321eb72013-06-23 10:42:37 +00001360 if (cube_info->memory_info != (MemoryInfo *) NULL)
1361 cube_info->memory_info=RelinquishVirtualMemory(cube_info->memory_info);
cristy3ed852e2009-09-05 21:47:34 +00001362 cube_info->quantize_info=DestroyQuantizeInfo(cube_info->quantize_info);
1363 cube_info=(CubeInfo *) RelinquishMagickMemory(cube_info);
1364}
1365
1366/*
1367%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1368% %
1369% %
1370% %
1371% D e s t r o y Q u a n t i z e I n f o %
1372% %
1373% %
1374% %
1375%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1376%
1377% DestroyQuantizeInfo() deallocates memory associated with an QuantizeInfo
1378% structure.
1379%
1380% The format of the DestroyQuantizeInfo method is:
1381%
1382% QuantizeInfo *DestroyQuantizeInfo(QuantizeInfo *quantize_info)
1383%
1384% A description of each parameter follows:
1385%
1386% o quantize_info: Specifies a pointer to an QuantizeInfo structure.
1387%
1388*/
1389MagickExport QuantizeInfo *DestroyQuantizeInfo(QuantizeInfo *quantize_info)
1390{
1391 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
1392 assert(quantize_info != (QuantizeInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001393 assert(quantize_info->signature == MagickCoreSignature);
1394 quantize_info->signature=(~MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001395 quantize_info=(QuantizeInfo *) RelinquishMagickMemory(quantize_info);
1396 return(quantize_info);
1397}
1398
1399/*
1400%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1401% %
1402% %
1403% %
1404+ D i t h e r I m a g e %
1405% %
1406% %
1407% %
1408%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1409%
1410% DitherImage() distributes the difference between an original image and
1411% the corresponding color reduced algorithm to neighboring pixels using
1412% serpentine-scan Floyd-Steinberg error diffusion. DitherImage returns
1413% MagickTrue if the image is dithered otherwise MagickFalse.
1414%
1415% The format of the DitherImage method is:
1416%
cristy8a11cb12011-10-19 23:53:34 +00001417% MagickBooleanType DitherImage(Image *image,CubeInfo *cube_info,
1418% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001419%
1420% A description of each parameter follows.
1421%
1422% o image: the image.
1423%
1424% o cube_info: A pointer to the Cube structure.
1425%
cristy8a11cb12011-10-19 23:53:34 +00001426% o exception: return any errors or warnings in this structure.
1427%
cristy3ed852e2009-09-05 21:47:34 +00001428*/
1429
Cristy2f4e2b22017-01-22 17:25:53 -05001430static DoublePixelPacket **DestroyPixelThreadSet(DoublePixelPacket **pixels)
cristye9717ac2011-02-20 16:17:17 +00001431{
Cristyf2dc1dd2020-12-28 13:59:26 -05001432 ssize_t
cristye9717ac2011-02-20 16:17:17 +00001433 i;
1434
Cristy2f4e2b22017-01-22 17:25:53 -05001435 assert(pixels != (DoublePixelPacket **) NULL);
cristyac245f82012-05-05 17:13:57 +00001436 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
Cristy2f4e2b22017-01-22 17:25:53 -05001437 if (pixels[i] != (DoublePixelPacket *) NULL)
1438 pixels[i]=(DoublePixelPacket *) RelinquishMagickMemory(pixels[i]);
1439 pixels=(DoublePixelPacket **) RelinquishMagickMemory(pixels);
cristye9717ac2011-02-20 16:17:17 +00001440 return(pixels);
1441}
1442
Cristy2f4e2b22017-01-22 17:25:53 -05001443static DoublePixelPacket **AcquirePixelThreadSet(const size_t count)
cristye9717ac2011-02-20 16:17:17 +00001444{
Cristy2f4e2b22017-01-22 17:25:53 -05001445 DoublePixelPacket
cristye9717ac2011-02-20 16:17:17 +00001446 **pixels;
1447
Cristyf2dc1dd2020-12-28 13:59:26 -05001448 ssize_t
cristye9717ac2011-02-20 16:17:17 +00001449 i;
1450
1451 size_t
1452 number_threads;
1453
cristy9357bdd2012-07-30 12:28:34 +00001454 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
Cristy2f4e2b22017-01-22 17:25:53 -05001455 pixels=(DoublePixelPacket **) AcquireQuantumMemory(number_threads,
cristye9717ac2011-02-20 16:17:17 +00001456 sizeof(*pixels));
Cristy2f4e2b22017-01-22 17:25:53 -05001457 if (pixels == (DoublePixelPacket **) NULL)
1458 return((DoublePixelPacket **) NULL);
Cristy81bfff22018-03-10 07:58:31 -05001459 (void) memset(pixels,0,number_threads*sizeof(*pixels));
cristye9717ac2011-02-20 16:17:17 +00001460 for (i=0; i < (ssize_t) number_threads; i++)
1461 {
Cristy2f4e2b22017-01-22 17:25:53 -05001462 pixels[i]=(DoublePixelPacket *) AcquireQuantumMemory(count,2*
Cristy2392b4b2015-12-04 18:28:23 -05001463 sizeof(**pixels));
Cristy2f4e2b22017-01-22 17:25:53 -05001464 if (pixels[i] == (DoublePixelPacket *) NULL)
cristye9717ac2011-02-20 16:17:17 +00001465 return(DestroyPixelThreadSet(pixels));
1466 }
1467 return(pixels);
1468}
1469
cristyca972de2010-06-20 23:37:02 +00001470static inline ssize_t CacheOffset(CubeInfo *cube_info,
Cristy2f4e2b22017-01-22 17:25:53 -05001471 const DoublePixelPacket *pixel)
cristyca972de2010-06-20 23:37:02 +00001472{
1473#define RedShift(pixel) (((pixel) >> CacheShift) << (0*(8-CacheShift)))
1474#define GreenShift(pixel) (((pixel) >> CacheShift) << (1*(8-CacheShift)))
1475#define BlueShift(pixel) (((pixel) >> CacheShift) << (2*(8-CacheShift)))
1476#define AlphaShift(pixel) (((pixel) >> CacheShift) << (3*(8-CacheShift)))
1477
1478 ssize_t
1479 offset;
1480
cristy6f7e0422012-12-25 20:04:53 +00001481 offset=(ssize_t) (RedShift(ScaleQuantumToChar(ClampPixel(pixel->red))) |
1482 GreenShift(ScaleQuantumToChar(ClampPixel(pixel->green))) |
1483 BlueShift(ScaleQuantumToChar(ClampPixel(pixel->blue))));
cristyca972de2010-06-20 23:37:02 +00001484 if (cube_info->associate_alpha != MagickFalse)
cristy6f7e0422012-12-25 20:04:53 +00001485 offset|=AlphaShift(ScaleQuantumToChar(ClampPixel(pixel->alpha)));
cristyca972de2010-06-20 23:37:02 +00001486 return(offset);
1487}
1488
cristy8a11cb12011-10-19 23:53:34 +00001489static MagickBooleanType FloydSteinbergDither(Image *image,CubeInfo *cube_info,
1490 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001491{
1492#define DitherImageTag "Dither/Image"
1493
cristyc4c8d132010-01-07 01:58:38 +00001494 CacheView
1495 *image_view;
1496
Cristydd1a91a2018-06-17 09:21:49 -04001497 const char
1498 *artifact;
1499
1500 double
1501 amount;
1502
Cristy2f4e2b22017-01-22 17:25:53 -05001503 DoublePixelPacket
Cristy2392b4b2015-12-04 18:28:23 -05001504 **pixels;
1505
cristy3ed852e2009-09-05 21:47:34 +00001506 MagickBooleanType
cristye9717ac2011-02-20 16:17:17 +00001507 status;
cristy3ed852e2009-09-05 21:47:34 +00001508
cristy847620f2011-02-09 02:24:21 +00001509 ssize_t
cristy847620f2011-02-09 02:24:21 +00001510 y;
1511
cristy3ed852e2009-09-05 21:47:34 +00001512 /*
1513 Distribute quantization error using Floyd-Steinberg.
1514 */
cristye9717ac2011-02-20 16:17:17 +00001515 pixels=AcquirePixelThreadSet(image->columns);
Cristy2f4e2b22017-01-22 17:25:53 -05001516 if (pixels == (DoublePixelPacket **) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001517 return(MagickFalse);
cristye9717ac2011-02-20 16:17:17 +00001518 status=MagickTrue;
Cristydd1a91a2018-06-17 09:21:49 -04001519 amount=1.0;
1520 artifact=GetImageArtifact(image,"dither:diffusion-amount");
1521 if (artifact != (const char *) NULL)
1522 amount=StringToDoubleInterval(artifact,1.0);
cristy46ff2672012-12-14 15:32:26 +00001523 image_view=AcquireAuthenticCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00001524 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001525 {
cristye9717ac2011-02-20 16:17:17 +00001526 const int
1527 id = GetOpenMPThreadId();
1528
1529 CubeInfo
1530 cube;
1531
Cristy2f4e2b22017-01-22 17:25:53 -05001532 DoublePixelPacket
cristye9717ac2011-02-20 16:17:17 +00001533 *current,
1534 *previous;
1535
Cristyf2dc1dd2020-12-28 13:59:26 -05001536 Quantum
dirk05d2ff72015-11-18 23:13:43 +01001537 *magick_restrict q;
cristyecc31b12011-02-13 00:32:29 +00001538
Cristyf2dc1dd2020-12-28 13:59:26 -05001539 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001540 x;
1541
cristye9717ac2011-02-20 16:17:17 +00001542 size_t
1543 index;
1544
1545 ssize_t
1546 v;
1547
1548 if (status == MagickFalse)
1549 continue;
cristy3ed852e2009-09-05 21:47:34 +00001550 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001551 if (q == (Quantum *) NULL)
cristye9717ac2011-02-20 16:17:17 +00001552 {
1553 status=MagickFalse;
cristy00cbdd62011-02-20 17:29:26 +00001554 continue;
cristye9717ac2011-02-20 16:17:17 +00001555 }
cristye9717ac2011-02-20 16:17:17 +00001556 cube=(*cube_info);
1557 current=pixels[id]+(y & 0x01)*image->columns;
1558 previous=pixels[id]+((y+1) & 0x01)*image->columns;
cristy4c08aed2011-07-01 19:47:50 +00001559 v=(ssize_t) ((y & 0x01) != 0 ? -1 : 1);
cristybb503372010-05-27 20:51:26 +00001560 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001561 {
Cristy2f4e2b22017-01-22 17:25:53 -05001562 DoublePixelPacket
cristye9717ac2011-02-20 16:17:17 +00001563 color,
1564 pixel;
1565
Cristyf2dc1dd2020-12-28 13:59:26 -05001566 ssize_t
cristye9717ac2011-02-20 16:17:17 +00001567 i;
1568
1569 ssize_t
1570 u;
1571
cristy4c08aed2011-07-01 19:47:50 +00001572 u=(y & 0x01) != 0 ? (ssize_t) image->columns-1-x : x;
cristy99372ba2015-01-29 23:54:45 +00001573 AssociateAlphaPixel(image,&cube,q+u*GetPixelChannels(image),&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001574 if (x > 0)
1575 {
Cristydd1a91a2018-06-17 09:21:49 -04001576 pixel.red+=7.0*amount*current[u-v].red/16;
1577 pixel.green+=7.0*amount*current[u-v].green/16;
1578 pixel.blue+=7.0*amount*current[u-v].blue/16;
cristye9717ac2011-02-20 16:17:17 +00001579 if (cube.associate_alpha != MagickFalse)
Cristydd1a91a2018-06-17 09:21:49 -04001580 pixel.alpha+=7.0*amount*current[u-v].alpha/16;
cristy3ed852e2009-09-05 21:47:34 +00001581 }
1582 if (y > 0)
1583 {
cristybb503372010-05-27 20:51:26 +00001584 if (x < (ssize_t) (image->columns-1))
cristy3ed852e2009-09-05 21:47:34 +00001585 {
1586 pixel.red+=previous[u+v].red/16;
1587 pixel.green+=previous[u+v].green/16;
1588 pixel.blue+=previous[u+v].blue/16;
cristye9717ac2011-02-20 16:17:17 +00001589 if (cube.associate_alpha != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001590 pixel.alpha+=previous[u+v].alpha/16;
cristy3ed852e2009-09-05 21:47:34 +00001591 }
Cristydd1a91a2018-06-17 09:21:49 -04001592 pixel.red+=5.0*amount*previous[u].red/16;
1593 pixel.green+=5.0*amount*previous[u].green/16;
1594 pixel.blue+=5.0*amount*previous[u].blue/16;
cristye9717ac2011-02-20 16:17:17 +00001595 if (cube.associate_alpha != MagickFalse)
Cristydd1a91a2018-06-17 09:21:49 -04001596 pixel.alpha+=5.0*amount*previous[u].alpha/16;
cristy3ed852e2009-09-05 21:47:34 +00001597 if (x > 0)
1598 {
Cristydd1a91a2018-06-17 09:21:49 -04001599 pixel.red+=3.0*amount*previous[u-v].red/16;
1600 pixel.green+=3.0*amount*previous[u-v].green/16;
1601 pixel.blue+=3.0*amount*previous[u-v].blue/16;
cristye9717ac2011-02-20 16:17:17 +00001602 if (cube.associate_alpha != MagickFalse)
Cristydd1a91a2018-06-17 09:21:49 -04001603 pixel.alpha+=3.0*amount*previous[u-v].alpha/16;
cristy3ed852e2009-09-05 21:47:34 +00001604 }
1605 }
cristy6f7e0422012-12-25 20:04:53 +00001606 pixel.red=(double) ClampPixel(pixel.red);
1607 pixel.green=(double) ClampPixel(pixel.green);
1608 pixel.blue=(double) ClampPixel(pixel.blue);
cristye9717ac2011-02-20 16:17:17 +00001609 if (cube.associate_alpha != MagickFalse)
cristy6f7e0422012-12-25 20:04:53 +00001610 pixel.alpha=(double) ClampPixel(pixel.alpha);
cristye9717ac2011-02-20 16:17:17 +00001611 i=CacheOffset(&cube,&pixel);
1612 if (cube.cache[i] < 0)
cristy3ed852e2009-09-05 21:47:34 +00001613 {
Cristyf2dc1dd2020-12-28 13:59:26 -05001614 NodeInfo
cristy3ed852e2009-09-05 21:47:34 +00001615 *node_info;
1616
Cristyf2dc1dd2020-12-28 13:59:26 -05001617 size_t
dirkaa6a11c2015-10-18 12:21:23 +02001618 node_id;
cristy3ed852e2009-09-05 21:47:34 +00001619
1620 /*
1621 Identify the deepest node containing the pixel's color.
1622 */
cristye9717ac2011-02-20 16:17:17 +00001623 node_info=cube.root;
cristybb503372010-05-27 20:51:26 +00001624 for (index=MaxTreeDepth-1; (ssize_t) index > 0; index--)
cristy3ed852e2009-09-05 21:47:34 +00001625 {
dirkaa6a11c2015-10-18 12:21:23 +02001626 node_id=ColorToNodeId(&cube,&pixel,index);
1627 if (node_info->child[node_id] == (NodeInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001628 break;
dirkaa6a11c2015-10-18 12:21:23 +02001629 node_info=node_info->child[node_id];
cristy3ed852e2009-09-05 21:47:34 +00001630 }
1631 /*
1632 Find closest color among siblings and their children.
1633 */
cristye9717ac2011-02-20 16:17:17 +00001634 cube.target=pixel;
cristy7bdaee52013-02-21 21:29:02 +00001635 cube.distance=(double) (4.0*(QuantumRange+1.0)*(QuantumRange+1.0)+
1636 1.0);
cristye9717ac2011-02-20 16:17:17 +00001637 ClosestColor(image,&cube,node_info->parent);
1638 cube.cache[i]=(ssize_t) cube.color_number;
cristy3ed852e2009-09-05 21:47:34 +00001639 }
1640 /*
1641 Assign pixel to closest colormap entry.
1642 */
cristye9717ac2011-02-20 16:17:17 +00001643 index=(size_t) cube.cache[i];
cristy3ed852e2009-09-05 21:47:34 +00001644 if (image->storage_class == PseudoClass)
cristy99372ba2015-01-29 23:54:45 +00001645 SetPixelIndex(image,(Quantum) index,q+u*GetPixelChannels(image));
cristye9717ac2011-02-20 16:17:17 +00001646 if (cube.quantize_info->measure_error == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001647 {
cristy99372ba2015-01-29 23:54:45 +00001648 SetPixelRed(image,ClampToQuantum(image->colormap[index].red),
1649 q+u*GetPixelChannels(image));
1650 SetPixelGreen(image,ClampToQuantum(image->colormap[index].green),
1651 q+u*GetPixelChannels(image));
1652 SetPixelBlue(image,ClampToQuantum(image->colormap[index].blue),
1653 q+u*GetPixelChannels(image));
cristye9717ac2011-02-20 16:17:17 +00001654 if (cube.associate_alpha != MagickFalse)
cristy99372ba2015-01-29 23:54:45 +00001655 SetPixelAlpha(image,ClampToQuantum(image->colormap[index].alpha),
1656 q+u*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00001657 }
1658 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
cristye9717ac2011-02-20 16:17:17 +00001659 status=MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +00001660 /*
1661 Store the error.
1662 */
cristyb0de93f2013-05-03 13:39:25 +00001663 AssociateAlphaPixelInfo(&cube,image->colormap+index,&color);
cristy3ed852e2009-09-05 21:47:34 +00001664 current[u].red=pixel.red-color.red;
1665 current[u].green=pixel.green-color.green;
1666 current[u].blue=pixel.blue-color.blue;
cristye9717ac2011-02-20 16:17:17 +00001667 if (cube.associate_alpha != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001668 current[u].alpha=pixel.alpha-color.alpha;
cristye9717ac2011-02-20 16:17:17 +00001669 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1670 {
1671 MagickBooleanType
1672 proceed;
1673
cristye9717ac2011-02-20 16:17:17 +00001674 proceed=SetImageProgress(image,DitherImageTag,(MagickOffsetType) y,
1675 image->rows);
1676 if (proceed == MagickFalse)
1677 status=MagickFalse;
1678 }
cristy3ed852e2009-09-05 21:47:34 +00001679 }
1680 }
cristy3ed852e2009-09-05 21:47:34 +00001681 image_view=DestroyCacheView(image_view);
cristye9717ac2011-02-20 16:17:17 +00001682 pixels=DestroyPixelThreadSet(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001683 return(MagickTrue);
1684}
1685
1686static MagickBooleanType
cristy8a11cb12011-10-19 23:53:34 +00001687 RiemersmaDither(Image *,CacheView *,CubeInfo *,const unsigned int,
cristy09e81242015-01-31 17:00:00 +00001688 ExceptionInfo *);
cristy3ed852e2009-09-05 21:47:34 +00001689
1690static void Riemersma(Image *image,CacheView *image_view,CubeInfo *cube_info,
cristy8a11cb12011-10-19 23:53:34 +00001691 const size_t level,const unsigned int direction,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001692{
1693 if (level == 1)
1694 switch (direction)
1695 {
1696 case WestGravity:
1697 {
cristy8a11cb12011-10-19 23:53:34 +00001698 (void) RiemersmaDither(image,image_view,cube_info,EastGravity,
1699 exception);
1700 (void) RiemersmaDither(image,image_view,cube_info,SouthGravity,
1701 exception);
1702 (void) RiemersmaDither(image,image_view,cube_info,WestGravity,
1703 exception);
cristy3ed852e2009-09-05 21:47:34 +00001704 break;
1705 }
1706 case EastGravity:
1707 {
cristy8a11cb12011-10-19 23:53:34 +00001708 (void) RiemersmaDither(image,image_view,cube_info,WestGravity,
1709 exception);
1710 (void) RiemersmaDither(image,image_view,cube_info,NorthGravity,
1711 exception);
1712 (void) RiemersmaDither(image,image_view,cube_info,EastGravity,
1713 exception);
cristy3ed852e2009-09-05 21:47:34 +00001714 break;
1715 }
1716 case NorthGravity:
1717 {
cristy8a11cb12011-10-19 23:53:34 +00001718 (void) RiemersmaDither(image,image_view,cube_info,SouthGravity,
1719 exception);
1720 (void) RiemersmaDither(image,image_view,cube_info,EastGravity,
1721 exception);
1722 (void) RiemersmaDither(image,image_view,cube_info,NorthGravity,
1723 exception);
cristy3ed852e2009-09-05 21:47:34 +00001724 break;
1725 }
1726 case SouthGravity:
1727 {
cristy8a11cb12011-10-19 23:53:34 +00001728 (void) RiemersmaDither(image,image_view,cube_info,NorthGravity,
1729 exception);
1730 (void) RiemersmaDither(image,image_view,cube_info,WestGravity,
1731 exception);
1732 (void) RiemersmaDither(image,image_view,cube_info,SouthGravity,
1733 exception);
cristy3ed852e2009-09-05 21:47:34 +00001734 break;
1735 }
1736 default:
1737 break;
1738 }
1739 else
1740 switch (direction)
1741 {
1742 case WestGravity:
1743 {
cristy8a11cb12011-10-19 23:53:34 +00001744 Riemersma(image,image_view,cube_info,level-1,NorthGravity,
1745 exception);
1746 (void) RiemersmaDither(image,image_view,cube_info,EastGravity,
1747 exception);
1748 Riemersma(image,image_view,cube_info,level-1,WestGravity,
1749 exception);
1750 (void) RiemersmaDither(image,image_view,cube_info,SouthGravity,
1751 exception);
1752 Riemersma(image,image_view,cube_info,level-1,WestGravity,
1753 exception);
1754 (void) RiemersmaDither(image,image_view,cube_info,WestGravity,
1755 exception);
1756 Riemersma(image,image_view,cube_info,level-1,SouthGravity,
1757 exception);
cristy3ed852e2009-09-05 21:47:34 +00001758 break;
1759 }
1760 case EastGravity:
1761 {
cristy8a11cb12011-10-19 23:53:34 +00001762 Riemersma(image,image_view,cube_info,level-1,SouthGravity,
1763 exception);
1764 (void) RiemersmaDither(image,image_view,cube_info,WestGravity,
1765 exception);
1766 Riemersma(image,image_view,cube_info,level-1,EastGravity,
1767 exception);
1768 (void) RiemersmaDither(image,image_view,cube_info,NorthGravity,
1769 exception);
1770 Riemersma(image,image_view,cube_info,level-1,EastGravity,
1771 exception);
1772 (void) RiemersmaDither(image,image_view,cube_info,EastGravity,
1773 exception);
1774 Riemersma(image,image_view,cube_info,level-1,NorthGravity,
1775 exception);
cristy3ed852e2009-09-05 21:47:34 +00001776 break;
1777 }
1778 case NorthGravity:
1779 {
cristy8a11cb12011-10-19 23:53:34 +00001780 Riemersma(image,image_view,cube_info,level-1,WestGravity,
1781 exception);
1782 (void) RiemersmaDither(image,image_view,cube_info,SouthGravity,
1783 exception);
1784 Riemersma(image,image_view,cube_info,level-1,NorthGravity,
1785 exception);
1786 (void) RiemersmaDither(image,image_view,cube_info,EastGravity,
1787 exception);
1788 Riemersma(image,image_view,cube_info,level-1,NorthGravity,
1789 exception);
1790 (void) RiemersmaDither(image,image_view,cube_info,NorthGravity,
1791 exception);
1792 Riemersma(image,image_view,cube_info,level-1,EastGravity,
1793 exception);
cristy3ed852e2009-09-05 21:47:34 +00001794 break;
1795 }
1796 case SouthGravity:
1797 {
cristy8a11cb12011-10-19 23:53:34 +00001798 Riemersma(image,image_view,cube_info,level-1,EastGravity,
1799 exception);
1800 (void) RiemersmaDither(image,image_view,cube_info,NorthGravity,
1801 exception);
1802 Riemersma(image,image_view,cube_info,level-1,SouthGravity,
1803 exception);
1804 (void) RiemersmaDither(image,image_view,cube_info,WestGravity,
1805 exception);
1806 Riemersma(image,image_view,cube_info,level-1,SouthGravity,
1807 exception);
1808 (void) RiemersmaDither(image,image_view,cube_info,SouthGravity,
1809 exception);
1810 Riemersma(image,image_view,cube_info,level-1,WestGravity,
1811 exception);
cristy3ed852e2009-09-05 21:47:34 +00001812 break;
1813 }
1814 default:
1815 break;
1816 }
1817}
1818
1819static MagickBooleanType RiemersmaDither(Image *image,CacheView *image_view,
cristy8a11cb12011-10-19 23:53:34 +00001820 CubeInfo *cube_info,const unsigned int direction,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001821{
1822#define DitherImageTag "Dither/Image"
1823
Cristy2f4e2b22017-01-22 17:25:53 -05001824 DoublePixelPacket
cristy3ed852e2009-09-05 21:47:34 +00001825 color,
1826 pixel;
1827
Cristy2392b4b2015-12-04 18:28:23 -05001828 MagickBooleanType
1829 proceed;
1830
Cristyf2dc1dd2020-12-28 13:59:26 -05001831 CubeInfo
cristy3ed852e2009-09-05 21:47:34 +00001832 *p;
1833
cristybb503372010-05-27 20:51:26 +00001834 size_t
cristy3ed852e2009-09-05 21:47:34 +00001835 index;
1836
1837 p=cube_info;
cristybb503372010-05-27 20:51:26 +00001838 if ((p->x >= 0) && (p->x < (ssize_t) image->columns) &&
1839 (p->y >= 0) && (p->y < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00001840 {
Cristyf2dc1dd2020-12-28 13:59:26 -05001841 Quantum
dirk05d2ff72015-11-18 23:13:43 +01001842 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001843
Cristyf2dc1dd2020-12-28 13:59:26 -05001844 ssize_t
cristyecc31b12011-02-13 00:32:29 +00001845 i;
1846
cristy3ed852e2009-09-05 21:47:34 +00001847 /*
1848 Distribute error.
1849 */
cristy3ed852e2009-09-05 21:47:34 +00001850 q=GetCacheViewAuthenticPixels(image_view,p->x,p->y,1,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001851 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001852 return(MagickFalse);
cristy4c08aed2011-07-01 19:47:50 +00001853 AssociateAlphaPixel(image,cube_info,q,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001854 for (i=0; i < ErrorQueueLength; i++)
1855 {
1856 pixel.red+=p->weights[i]*p->error[i].red;
1857 pixel.green+=p->weights[i]*p->error[i].green;
1858 pixel.blue+=p->weights[i]*p->error[i].blue;
1859 if (cube_info->associate_alpha != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001860 pixel.alpha+=p->weights[i]*p->error[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001861 }
cristy6f7e0422012-12-25 20:04:53 +00001862 pixel.red=(double) ClampPixel(pixel.red);
1863 pixel.green=(double) ClampPixel(pixel.green);
1864 pixel.blue=(double) ClampPixel(pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00001865 if (cube_info->associate_alpha != MagickFalse)
cristy6f7e0422012-12-25 20:04:53 +00001866 pixel.alpha=(double) ClampPixel(pixel.alpha);
cristyca972de2010-06-20 23:37:02 +00001867 i=CacheOffset(cube_info,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001868 if (p->cache[i] < 0)
1869 {
Cristyf2dc1dd2020-12-28 13:59:26 -05001870 NodeInfo
cristy3ed852e2009-09-05 21:47:34 +00001871 *node_info;
1872
Cristyf2dc1dd2020-12-28 13:59:26 -05001873 size_t
cristy3ed852e2009-09-05 21:47:34 +00001874 id;
1875
1876 /*
1877 Identify the deepest node containing the pixel's color.
1878 */
1879 node_info=p->root;
cristybb503372010-05-27 20:51:26 +00001880 for (index=MaxTreeDepth-1; (ssize_t) index > 0; index--)
cristy3ed852e2009-09-05 21:47:34 +00001881 {
1882 id=ColorToNodeId(cube_info,&pixel,index);
1883 if (node_info->child[id] == (NodeInfo *) NULL)
1884 break;
1885 node_info=node_info->child[id];
1886 }
1887 /*
1888 Find closest color among siblings and their children.
1889 */
1890 p->target=pixel;
cristya19f1d72012-08-07 18:24:38 +00001891 p->distance=(double) (4.0*(QuantumRange+1.0)*((double)
cristy3ed852e2009-09-05 21:47:34 +00001892 QuantumRange+1.0)+1.0);
1893 ClosestColor(image,p,node_info->parent);
cristybb503372010-05-27 20:51:26 +00001894 p->cache[i]=(ssize_t) p->color_number;
cristy3ed852e2009-09-05 21:47:34 +00001895 }
1896 /*
1897 Assign pixel to closest colormap entry.
1898 */
cristy4c08aed2011-07-01 19:47:50 +00001899 index=(size_t) p->cache[i];
cristy3ed852e2009-09-05 21:47:34 +00001900 if (image->storage_class == PseudoClass)
cristy4c08aed2011-07-01 19:47:50 +00001901 SetPixelIndex(image,(Quantum) index,q);
cristy3ed852e2009-09-05 21:47:34 +00001902 if (cube_info->quantize_info->measure_error == MagickFalse)
1903 {
cristye42f6582012-02-11 17:59:50 +00001904 SetPixelRed(image,ClampToQuantum(image->colormap[index].red),q);
1905 SetPixelGreen(image,ClampToQuantum(image->colormap[index].green),q);
1906 SetPixelBlue(image,ClampToQuantum(image->colormap[index].blue),q);
cristy3ed852e2009-09-05 21:47:34 +00001907 if (cube_info->associate_alpha != MagickFalse)
cristye42f6582012-02-11 17:59:50 +00001908 SetPixelAlpha(image,ClampToQuantum(image->colormap[index].alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001909 }
1910 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1911 return(MagickFalse);
1912 /*
1913 Propagate the error as the last entry of the error queue.
1914 */
Cristyfc31dc52018-03-10 10:42:46 -05001915 (void) memmove(p->error,p->error+1,(ErrorQueueLength-1)*
cristy3ed852e2009-09-05 21:47:34 +00001916 sizeof(p->error[0]));
cristyb0de93f2013-05-03 13:39:25 +00001917 AssociateAlphaPixelInfo(cube_info,image->colormap+index,&color);
cristy3ed852e2009-09-05 21:47:34 +00001918 p->error[ErrorQueueLength-1].red=pixel.red-color.red;
1919 p->error[ErrorQueueLength-1].green=pixel.green-color.green;
1920 p->error[ErrorQueueLength-1].blue=pixel.blue-color.blue;
1921 if (cube_info->associate_alpha != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001922 p->error[ErrorQueueLength-1].alpha=pixel.alpha-color.alpha;
cristy3ed852e2009-09-05 21:47:34 +00001923 proceed=SetImageProgress(image,DitherImageTag,p->offset,p->span);
1924 if (proceed == MagickFalse)
1925 return(MagickFalse);
1926 p->offset++;
1927 }
1928 switch (direction)
1929 {
1930 case WestGravity: p->x--; break;
1931 case EastGravity: p->x++; break;
1932 case NorthGravity: p->y--; break;
1933 case SouthGravity: p->y++; break;
1934 }
1935 return(MagickTrue);
1936}
1937
cristy8a11cb12011-10-19 23:53:34 +00001938static MagickBooleanType DitherImage(Image *image,CubeInfo *cube_info,
1939 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001940{
cristyc4c8d132010-01-07 01:58:38 +00001941 CacheView
1942 *image_view;
1943
cristy3ed852e2009-09-05 21:47:34 +00001944 MagickBooleanType
1945 status;
1946
Cristyf2dc1dd2020-12-28 13:59:26 -05001947 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001948 i;
1949
cristybb503372010-05-27 20:51:26 +00001950 size_t
cristy3ed852e2009-09-05 21:47:34 +00001951 depth;
1952
cristyfb7e9cd2011-02-20 16:26:15 +00001953 if (cube_info->quantize_info->dither_method != RiemersmaDitherMethod)
cristy8a11cb12011-10-19 23:53:34 +00001954 return(FloydSteinbergDither(image,cube_info,exception));
cristy3ed852e2009-09-05 21:47:34 +00001955 /*
cristycee97112010-05-28 00:44:52 +00001956 Distribute quantization error along a Hilbert curve.
cristy3ed852e2009-09-05 21:47:34 +00001957 */
Cristydb3982f2019-08-16 08:01:49 -04001958 (void) memset(cube_info->error,0,ErrorQueueLength*sizeof(*cube_info->error));
cristy3ed852e2009-09-05 21:47:34 +00001959 cube_info->x=0;
1960 cube_info->y=0;
cristybb503372010-05-27 20:51:26 +00001961 i=MagickMax((ssize_t) image->columns,(ssize_t) image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001962 for (depth=1; i != 0; depth++)
1963 i>>=1;
cristybb503372010-05-27 20:51:26 +00001964 if ((ssize_t) (1L << depth) < MagickMax((ssize_t) image->columns,(ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00001965 depth++;
1966 cube_info->offset=0;
1967 cube_info->span=(MagickSizeType) image->columns*image->rows;
cristy46ff2672012-12-14 15:32:26 +00001968 image_view=AcquireAuthenticCacheView(image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001969 if (depth > 1)
cristy8a11cb12011-10-19 23:53:34 +00001970 Riemersma(image,image_view,cube_info,depth-1,NorthGravity,exception);
1971 status=RiemersmaDither(image,image_view,cube_info,ForgetGravity,exception);
cristy3ed852e2009-09-05 21:47:34 +00001972 image_view=DestroyCacheView(image_view);
1973 return(status);
1974}
1975
1976/*
1977%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1978% %
1979% %
1980% %
1981+ G e t C u b e I n f o %
1982% %
1983% %
1984% %
1985%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1986%
1987% GetCubeInfo() initialize the Cube data structure.
1988%
1989% The format of the GetCubeInfo method is:
1990%
1991% CubeInfo GetCubeInfo(const QuantizeInfo *quantize_info,
cristybb503372010-05-27 20:51:26 +00001992% const size_t depth,const size_t maximum_colors)
cristy3ed852e2009-09-05 21:47:34 +00001993%
1994% A description of each parameter follows.
1995%
1996% o quantize_info: Specifies a pointer to an QuantizeInfo structure.
1997%
1998% o depth: Normally, this integer value is zero or one. A zero or
1999% one tells Quantize to choose a optimal tree depth of Log4(number_colors).
2000% A tree of this depth generally allows the best representation of the
2001% reference image with the least amount of memory and the fastest
2002% computational speed. In some cases, such as an image with low color
2003% dispersion (a few number of colors), a value other than
2004% Log4(number_colors) is required. To expand the color tree completely,
2005% use a value of 8.
2006%
2007% o maximum_colors: maximum colors.
2008%
2009*/
2010static CubeInfo *GetCubeInfo(const QuantizeInfo *quantize_info,
cristybb503372010-05-27 20:51:26 +00002011 const size_t depth,const size_t maximum_colors)
cristy3ed852e2009-09-05 21:47:34 +00002012{
2013 CubeInfo
2014 *cube_info;
2015
cristya19f1d72012-08-07 18:24:38 +00002016 double
cristy3ed852e2009-09-05 21:47:34 +00002017 sum,
2018 weight;
2019
Cristyf2dc1dd2020-12-28 13:59:26 -05002020 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002021 i;
2022
cristyecc31b12011-02-13 00:32:29 +00002023 size_t
2024 length;
2025
cristy3ed852e2009-09-05 21:47:34 +00002026 /*
2027 Initialize tree to describe color cube_info.
2028 */
Cristy8357b5d2020-11-22 12:39:10 +00002029 cube_info=(CubeInfo *) AcquireMagickMemory(sizeof(*cube_info));
cristy3ed852e2009-09-05 21:47:34 +00002030 if (cube_info == (CubeInfo *) NULL)
2031 return((CubeInfo *) NULL);
Cristy81bfff22018-03-10 07:58:31 -05002032 (void) memset(cube_info,0,sizeof(*cube_info));
cristy3ed852e2009-09-05 21:47:34 +00002033 cube_info->depth=depth;
2034 if (cube_info->depth > MaxTreeDepth)
2035 cube_info->depth=MaxTreeDepth;
2036 if (cube_info->depth < 2)
2037 cube_info->depth=2;
2038 cube_info->maximum_colors=maximum_colors;
2039 /*
2040 Initialize root node.
2041 */
2042 cube_info->root=GetNodeInfo(cube_info,0,0,(NodeInfo *) NULL);
2043 if (cube_info->root == (NodeInfo *) NULL)
2044 return((CubeInfo *) NULL);
2045 cube_info->root->parent=cube_info->root;
2046 cube_info->quantize_info=CloneQuantizeInfo(quantize_info);
cristycbda6112012-05-27 20:57:16 +00002047 if (cube_info->quantize_info->dither_method == NoDitherMethod)
cristy3ed852e2009-09-05 21:47:34 +00002048 return(cube_info);
2049 /*
2050 Initialize dither resources.
2051 */
2052 length=(size_t) (1UL << (4*(8-CacheShift)));
cristya321eb72013-06-23 10:42:37 +00002053 cube_info->memory_info=AcquireVirtualMemory(length,sizeof(*cube_info->cache));
2054 if (cube_info->memory_info == (MemoryInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002055 return((CubeInfo *) NULL);
cristya321eb72013-06-23 10:42:37 +00002056 cube_info->cache=(ssize_t *) GetVirtualMemoryBlob(cube_info->memory_info);
cristy3ed852e2009-09-05 21:47:34 +00002057 /*
2058 Initialize color cache.
2059 */
Cristy54a65de2019-08-16 08:03:40 -04002060 (void) memset(cube_info->cache,(-1),sizeof(*cube_info->cache)*length);
cristy3ed852e2009-09-05 21:47:34 +00002061 /*
cristycee97112010-05-28 00:44:52 +00002062 Distribute weights along a curve of exponential decay.
cristy3ed852e2009-09-05 21:47:34 +00002063 */
2064 weight=1.0;
2065 for (i=0; i < ErrorQueueLength; i++)
2066 {
cristy3e3ec3a2012-11-03 23:11:06 +00002067 cube_info->weights[ErrorQueueLength-i-1]=PerceptibleReciprocal(weight);
cristy3ed852e2009-09-05 21:47:34 +00002068 weight*=exp(log(((double) QuantumRange+1.0))/(ErrorQueueLength-1.0));
2069 }
2070 /*
2071 Normalize the weighting factors.
2072 */
2073 weight=0.0;
2074 for (i=0; i < ErrorQueueLength; i++)
2075 weight+=cube_info->weights[i];
2076 sum=0.0;
2077 for (i=0; i < ErrorQueueLength; i++)
2078 {
2079 cube_info->weights[i]/=weight;
2080 sum+=cube_info->weights[i];
2081 }
2082 cube_info->weights[0]+=1.0-sum;
2083 return(cube_info);
2084}
2085
2086/*
2087%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2088% %
2089% %
2090% %
2091+ G e t N o d e I n f o %
2092% %
2093% %
2094% %
2095%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2096%
2097% GetNodeInfo() allocates memory for a new node in the color cube tree and
2098% presets all fields to zero.
2099%
2100% The format of the GetNodeInfo method is:
2101%
cristybb503372010-05-27 20:51:26 +00002102% NodeInfo *GetNodeInfo(CubeInfo *cube_info,const size_t id,
2103% const size_t level,NodeInfo *parent)
cristy3ed852e2009-09-05 21:47:34 +00002104%
2105% A description of each parameter follows.
2106%
2107% o node: The GetNodeInfo method returns a pointer to a queue of nodes.
2108%
2109% o id: Specifies the child number of the node.
2110%
2111% o level: Specifies the level in the storage_class the node resides.
2112%
2113*/
cristybb503372010-05-27 20:51:26 +00002114static NodeInfo *GetNodeInfo(CubeInfo *cube_info,const size_t id,
2115 const size_t level,NodeInfo *parent)
cristy3ed852e2009-09-05 21:47:34 +00002116{
2117 NodeInfo
2118 *node_info;
2119
2120 if (cube_info->free_nodes == 0)
2121 {
2122 Nodes
2123 *nodes;
2124
2125 /*
2126 Allocate a new queue of nodes.
2127 */
Cristy8357b5d2020-11-22 12:39:10 +00002128 nodes=(Nodes *) AcquireMagickMemory(sizeof(*nodes));
cristy3ed852e2009-09-05 21:47:34 +00002129 if (nodes == (Nodes *) NULL)
2130 return((NodeInfo *) NULL);
2131 nodes->nodes=(NodeInfo *) AcquireQuantumMemory(NodesInAList,
2132 sizeof(*nodes->nodes));
2133 if (nodes->nodes == (NodeInfo *) NULL)
2134 return((NodeInfo *) NULL);
2135 nodes->next=cube_info->node_queue;
2136 cube_info->node_queue=nodes;
2137 cube_info->next_node=nodes->nodes;
2138 cube_info->free_nodes=NodesInAList;
2139 }
2140 cube_info->nodes++;
2141 cube_info->free_nodes--;
2142 node_info=cube_info->next_node++;
Cristy81bfff22018-03-10 07:58:31 -05002143 (void) memset(node_info,0,sizeof(*node_info));
cristy3ed852e2009-09-05 21:47:34 +00002144 node_info->parent=parent;
2145 node_info->id=id;
2146 node_info->level=level;
2147 return(node_info);
2148}
2149
2150/*
2151%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2152% %
2153% %
2154% %
2155% G e t I m a g e Q u a n t i z e E r r o r %
2156% %
2157% %
2158% %
2159%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2160%
2161% GetImageQuantizeError() measures the difference between the original
2162% and quantized images. This difference is the total quantization error.
2163% The error is computed by summing over all pixels in an image the distance
2164% squared in RGB space between each reference pixel value and its quantized
2165% value. These values are computed:
2166%
2167% o mean_error_per_pixel: This value is the mean error for any single
2168% pixel in the image.
2169%
2170% o normalized_mean_square_error: This value is the normalized mean
2171% quantization error for any single pixel in the image. This distance
2172% measure is normalized to a range between 0 and 1. It is independent
2173% of the range of red, green, and blue values in the image.
2174%
2175% o normalized_maximum_square_error: Thsi value is the normalized
2176% maximum quantization error for any single pixel in the image. This
2177% distance measure is normalized to a range between 0 and 1. It is
2178% independent of the range of red, green, and blue values in your image.
2179%
2180% The format of the GetImageQuantizeError method is:
2181%
cristy8a11cb12011-10-19 23:53:34 +00002182% MagickBooleanType GetImageQuantizeError(Image *image,
2183% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002184%
2185% A description of each parameter follows.
2186%
2187% o image: the image.
2188%
cristy8a11cb12011-10-19 23:53:34 +00002189% o exception: return any errors or warnings in this structure.
2190%
cristy3ed852e2009-09-05 21:47:34 +00002191*/
cristy8a11cb12011-10-19 23:53:34 +00002192MagickExport MagickBooleanType GetImageQuantizeError(Image *image,
2193 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002194{
cristyc4c8d132010-01-07 01:58:38 +00002195 CacheView
2196 *image_view;
2197
cristya19f1d72012-08-07 18:24:38 +00002198 double
cristy3ed852e2009-09-05 21:47:34 +00002199 alpha,
2200 area,
2201 beta,
2202 distance,
2203 maximum_error,
2204 mean_error,
2205 mean_error_per_pixel;
2206
cristyecc31b12011-02-13 00:32:29 +00002207 ssize_t
Cristy7dbf5b22019-05-11 08:26:39 -04002208 index,
cristyecc31b12011-02-13 00:32:29 +00002209 y;
2210
cristy3ed852e2009-09-05 21:47:34 +00002211 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00002212 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00002213 if (image->debug != MagickFalse)
2214 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8a11cb12011-10-19 23:53:34 +00002215 image->total_colors=GetNumberColors(image,(FILE *) NULL,exception);
Cristy81bfff22018-03-10 07:58:31 -05002216 (void) memset(&image->error,0,sizeof(image->error));
cristy3ed852e2009-09-05 21:47:34 +00002217 if (image->storage_class == DirectClass)
2218 return(MagickTrue);
2219 alpha=1.0;
2220 beta=1.0;
2221 area=3.0*image->columns*image->rows;
2222 maximum_error=0.0;
2223 mean_error_per_pixel=0.0;
2224 mean_error=0.0;
cristy46ff2672012-12-14 15:32:26 +00002225 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00002226 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002227 {
Cristyf2dc1dd2020-12-28 13:59:26 -05002228 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01002229 *magick_restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002230
Cristyf2dc1dd2020-12-28 13:59:26 -05002231 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002232 x;
2233
2234 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002235 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002236 break;
cristybb503372010-05-27 20:51:26 +00002237 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002238 {
Cristy7dbf5b22019-05-11 08:26:39 -04002239 index=(ssize_t) GetPixelIndex(image,p);
cristy09e81242015-01-31 17:00:00 +00002240 if (image->alpha_trait == BlendPixelTrait)
cristy3ed852e2009-09-05 21:47:34 +00002241 {
cristya19f1d72012-08-07 18:24:38 +00002242 alpha=(double) (QuantumScale*GetPixelAlpha(image,p));
2243 beta=(double) (QuantumScale*image->colormap[index].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002244 }
cristy3bdd9252014-12-21 20:01:43 +00002245 distance=fabs((double) (alpha*GetPixelRed(image,p)-beta*
2246 image->colormap[index].red));
cristy3ed852e2009-09-05 21:47:34 +00002247 mean_error_per_pixel+=distance;
2248 mean_error+=distance*distance;
2249 if (distance > maximum_error)
2250 maximum_error=distance;
cristy3bdd9252014-12-21 20:01:43 +00002251 distance=fabs((double) (alpha*GetPixelGreen(image,p)-beta*
2252 image->colormap[index].green));
cristy3ed852e2009-09-05 21:47:34 +00002253 mean_error_per_pixel+=distance;
2254 mean_error+=distance*distance;
2255 if (distance > maximum_error)
2256 maximum_error=distance;
cristy3bdd9252014-12-21 20:01:43 +00002257 distance=fabs((double) (alpha*GetPixelBlue(image,p)-beta*
2258 image->colormap[index].blue));
cristy3ed852e2009-09-05 21:47:34 +00002259 mean_error_per_pixel+=distance;
2260 mean_error+=distance*distance;
2261 if (distance > maximum_error)
2262 maximum_error=distance;
cristyed231572011-07-14 02:18:59 +00002263 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002264 }
2265 }
2266 image_view=DestroyCacheView(image_view);
Cristy76eb72f2016-11-27 08:21:05 -05002267 image->error.mean_error_per_pixel=(double) mean_error_per_pixel/area;
2268 image->error.normalized_mean_error=(double) QuantumScale*QuantumScale*
2269 mean_error/area;
2270 image->error.normalized_maximum_error=(double) QuantumScale*maximum_error;
cristy3ed852e2009-09-05 21:47:34 +00002271 return(MagickTrue);
2272}
2273
2274/*
2275%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2276% %
2277% %
2278% %
2279% G e t Q u a n t i z e I n f o %
2280% %
2281% %
2282% %
2283%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2284%
2285% GetQuantizeInfo() initializes the QuantizeInfo structure.
2286%
2287% The format of the GetQuantizeInfo method is:
2288%
2289% GetQuantizeInfo(QuantizeInfo *quantize_info)
2290%
2291% A description of each parameter follows:
2292%
2293% o quantize_info: Specifies a pointer to a QuantizeInfo structure.
2294%
2295*/
2296MagickExport void GetQuantizeInfo(QuantizeInfo *quantize_info)
2297{
2298 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2299 assert(quantize_info != (QuantizeInfo *) NULL);
Cristy81bfff22018-03-10 07:58:31 -05002300 (void) memset(quantize_info,0,sizeof(*quantize_info));
cristy3ed852e2009-09-05 21:47:34 +00002301 quantize_info->number_colors=256;
cristy3ed852e2009-09-05 21:47:34 +00002302 quantize_info->dither_method=RiemersmaDitherMethod;
2303 quantize_info->colorspace=UndefinedColorspace;
2304 quantize_info->measure_error=MagickFalse;
cristye1c94d92015-06-28 12:16:33 +00002305 quantize_info->signature=MagickCoreSignature;
cristy3ed852e2009-09-05 21:47:34 +00002306}
2307
2308/*
2309%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2310% %
2311% %
2312% %
Cristyee6da042019-12-19 06:16:25 -05002313% K m e a n s I m a g e %
2314% %
2315% %
2316% %
2317%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2318%
2319% KmeansImage() applies k-means color reduction to an image. This is a
2320% colorspace clustering or segmentation technique.
2321%
2322% The format of the KmeansImage method is:
2323%
2324% MagickBooleanType KmeansImage(Image *image,const size_t number_colors,
Cristy1fce5c52019-12-31 09:51:14 -05002325% const size_t max_iterations,const double tolerance,
Cristyee6da042019-12-19 06:16:25 -05002326% ExceptionInfo *exception)
2327%
2328% A description of each parameter follows:
2329%
2330% o image: the image.
2331%
2332% o number_colors: number of colors to use as seeds.
2333%
2334% o max_iterations: maximum number of iterations while converging.
2335%
Cristy1fce5c52019-12-31 09:51:14 -05002336% o tolerance: the maximum tolerance.
Cristyee6da042019-12-19 06:16:25 -05002337%
2338% o exception: return any errors or warnings in this structure.
2339%
2340*/
2341
Cristyf1f68eb2019-12-27 18:22:57 -05002342typedef struct _KmeansInfo
2343{
2344 double
Cristyf1f68eb2019-12-27 18:22:57 -05002345 red,
2346 green,
2347 blue,
2348 alpha,
Cristy193f3952019-12-28 11:48:52 -05002349 black,
2350 count,
2351 distortion;
Cristyf1f68eb2019-12-27 18:22:57 -05002352} KmeansInfo;
2353
2354static KmeansInfo **DestroyKmeansThreadSet(KmeansInfo **kmeans_info)
2355{
Cristyf2dc1dd2020-12-28 13:59:26 -05002356 ssize_t
Cristyf1f68eb2019-12-27 18:22:57 -05002357 i;
2358
2359 assert(kmeans_info != (KmeansInfo **) NULL);
2360 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
2361 if (kmeans_info[i] != (KmeansInfo *) NULL)
2362 kmeans_info[i]=(KmeansInfo *) RelinquishMagickMemory(kmeans_info[i]);
2363 kmeans_info=(KmeansInfo **) RelinquishMagickMemory(kmeans_info);
2364 return(kmeans_info);
2365}
2366
2367static KmeansInfo **AcquireKmeansThreadSet(const size_t number_colors)
2368{
2369 KmeansInfo
2370 **kmeans_info;
2371
Cristyf2dc1dd2020-12-28 13:59:26 -05002372 ssize_t
Cristyf1f68eb2019-12-27 18:22:57 -05002373 i;
2374
2375 size_t
2376 number_threads;
2377
2378 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
2379 kmeans_info=(KmeansInfo **) AcquireQuantumMemory(number_threads,
2380 sizeof(*kmeans_info));
2381 if (kmeans_info == (KmeansInfo **) NULL)
2382 return((KmeansInfo **) NULL);
2383 (void) memset(kmeans_info,0,number_threads*sizeof(*kmeans_info));
2384 for (i=0; i < (ssize_t) number_threads; i++)
2385 {
2386 kmeans_info[i]=(KmeansInfo *) AcquireQuantumMemory(number_colors,
2387 sizeof(**kmeans_info));
2388 if (kmeans_info[i] == (KmeansInfo *) NULL)
2389 return(DestroyKmeansThreadSet(kmeans_info));
2390 }
2391 return(kmeans_info);
2392}
2393
Cristyb5642a62019-12-31 16:49:38 -05002394static inline double KmeansMetric(const Image *magick_restrict image,
Cristyee6da042019-12-19 06:16:25 -05002395 const Quantum *magick_restrict p,const PixelInfo *magick_restrict q)
2396{
Cristyf2dc1dd2020-12-28 13:59:26 -05002397 double
Cristyee6da042019-12-19 06:16:25 -05002398 gamma,
Cristyb5642a62019-12-31 16:49:38 -05002399 metric,
Cristyee6da042019-12-19 06:16:25 -05002400 pixel;
2401
2402 gamma=1.0;
Cristyb5642a62019-12-31 16:49:38 -05002403 metric=0.0;
2404 if ((image->alpha_trait != UndefinedPixelTrait) ||
2405 (q->alpha_trait != UndefinedPixelTrait))
Cristyee6da042019-12-19 06:16:25 -05002406 {
Cristy793f9c22020-01-18 14:39:17 -05002407 pixel=GetPixelAlpha(image,p)-(q->alpha_trait != UndefinedPixelTrait ?
2408 q->alpha : OpaqueAlpha);
Cristyb5642a62019-12-31 16:49:38 -05002409 metric+=pixel*pixel;
Cristy793f9c22020-01-18 14:39:17 -05002410 if (image->alpha_trait != UndefinedPixelTrait)
2411 gamma*=QuantumScale*GetPixelAlpha(image,p);
2412 if (q->alpha_trait != UndefinedPixelTrait)
2413 gamma*=QuantumScale*q->alpha;
Cristyee6da042019-12-19 06:16:25 -05002414 }
Cristyee6da042019-12-19 06:16:25 -05002415 if (image->colorspace == CMYKColorspace)
2416 {
Cristyc300b3a2020-01-18 15:14:42 -05002417 pixel=QuantumScale*(GetPixelBlack(image,p)-q->black);
Cristyb5642a62019-12-31 16:49:38 -05002418 metric+=gamma*pixel*pixel;
2419 gamma*=QuantumScale*(QuantumRange-GetPixelBlack(image,p));
2420 gamma*=QuantumScale*(QuantumRange-q->black);
Cristyee6da042019-12-19 06:16:25 -05002421 }
Cristyb5642a62019-12-31 16:49:38 -05002422 metric*=3.0;
2423 pixel=QuantumScale*(GetPixelRed(image,p)-q->red);
Cristyc6c450d2020-01-18 11:21:12 -05002424 if (IsHueCompatibleColorspace(image->colorspace) != MagickFalse)
Cristyee6da042019-12-19 06:16:25 -05002425 {
Cristyb5642a62019-12-31 16:49:38 -05002426 if (fabs((double) pixel) > 0.5)
2427 pixel-=0.5;
Cristyee6da042019-12-19 06:16:25 -05002428 pixel*=2.0;
2429 }
Cristyb5642a62019-12-31 16:49:38 -05002430 metric+=gamma*pixel*pixel;
2431 pixel=QuantumScale*(GetPixelGreen(image,p)-q->green);
2432 metric+=gamma*pixel*pixel;
2433 pixel=QuantumScale*(GetPixelBlue(image,p)-q->blue);
2434 metric+=gamma*pixel*pixel;
2435 return(metric);
Cristyee6da042019-12-19 06:16:25 -05002436}
2437
2438MagickExport MagickBooleanType KmeansImage(Image *image,
Cristy1fce5c52019-12-31 09:51:14 -05002439 const size_t number_colors,const size_t max_iterations,const double tolerance,
2440 ExceptionInfo *exception)
Cristyee6da042019-12-19 06:16:25 -05002441{
2442#define KmeansImageTag "Kmeans/Image"
Cristy193f3952019-12-28 11:48:52 -05002443#define RandomColorComponent(info) (QuantumRange*GetPseudoRandomValue(info))
Cristyee6da042019-12-19 06:16:25 -05002444
Cristye574a422019-12-19 19:26:53 -05002445 CacheView
2446 *image_view;
2447
Cristy50b321f2019-12-20 21:18:19 -05002448 const char
Cristy63442152019-12-21 09:47:06 -05002449 *colors;
Cristy50b321f2019-12-20 21:18:19 -05002450
Cristyee6da042019-12-19 06:16:25 -05002451 double
Cristy4bd83192019-12-31 13:33:57 -05002452 previous_tolerance;
Cristyee6da042019-12-19 06:16:25 -05002453
Cristyf1f68eb2019-12-27 18:22:57 -05002454 KmeansInfo
2455 **kmeans_pixels;
2456
Cristyee6da042019-12-19 06:16:25 -05002457 MagickBooleanType
Cristydc157f02019-12-19 16:32:14 -05002458 verbose,
Cristyee6da042019-12-19 06:16:25 -05002459 status;
2460
Cristyf2dc1dd2020-12-28 13:59:26 -05002461 ssize_t
Cristyee6da042019-12-19 06:16:25 -05002462 n;
2463
Cristyf1f68eb2019-12-27 18:22:57 -05002464 size_t
2465 number_threads;
2466
Cristyee6da042019-12-19 06:16:25 -05002467 assert(image != (Image *) NULL);
2468 assert(image->signature == MagickCoreSignature);
2469 if (image->debug != MagickFalse)
2470 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2471 assert(exception != (ExceptionInfo *) NULL);
2472 assert(exception->signature == MagickCoreSignature);
Cristy63442152019-12-21 09:47:06 -05002473 colors=GetImageArtifact(image,"kmeans:seed-colors");
2474 if (colors == (const char *) NULL)
Cristy50b321f2019-12-20 21:18:19 -05002475 {
Cristy193f3952019-12-28 11:48:52 -05002476 CubeInfo
2477 *cube_info;
Cristy50b321f2019-12-20 21:18:19 -05002478
Cristy193f3952019-12-28 11:48:52 -05002479 QuantizeInfo
2480 *quantize_info;
2481
2482 size_t
2483 colors,
2484 depth;
2485
2486 /*
2487 Seed clusters from color quantization.
2488 */
Cristy50b321f2019-12-20 21:18:19 -05002489 quantize_info=AcquireQuantizeInfo((ImageInfo *) NULL);
Cristyc6c450d2020-01-18 11:21:12 -05002490 quantize_info->colorspace=image->colorspace;
Cristy50b321f2019-12-20 21:18:19 -05002491 quantize_info->number_colors=number_colors;
2492 quantize_info->dither_method=NoDitherMethod;
Cristy193f3952019-12-28 11:48:52 -05002493 colors=number_colors;
2494 for (depth=1; colors != 0; depth++)
2495 colors>>=2;
2496 cube_info=GetCubeInfo(quantize_info,depth,number_colors);
2497 if (cube_info == (CubeInfo *) NULL)
2498 {
2499 quantize_info=DestroyQuantizeInfo(quantize_info);
2500 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2501 image->filename);
2502 }
2503 status=ClassifyImageColors(cube_info,image,exception);
2504 if (status != MagickFalse)
2505 {
2506 if (cube_info->colors > cube_info->maximum_colors)
2507 ReduceImageColors(image,cube_info);
Dirk Lemstra25229f22020-04-27 15:01:47 +02002508 status=SetImageColormap(image,cube_info,exception);
Cristy193f3952019-12-28 11:48:52 -05002509 }
2510 DestroyCubeInfo(cube_info);
Cristy50b321f2019-12-20 21:18:19 -05002511 quantize_info=DestroyQuantizeInfo(quantize_info);
2512 if (status == MagickFalse)
Cristy193f3952019-12-28 11:48:52 -05002513 return(status);
Cristy50b321f2019-12-20 21:18:19 -05002514 }
Cristy63442152019-12-21 09:47:06 -05002515 else
2516 {
2517 char
2518 color[MagickPathExtent];
2519
Cristyf2dc1dd2020-12-28 13:59:26 -05002520 const char
Cristy63442152019-12-21 09:47:06 -05002521 *p;
2522
Cristy193f3952019-12-28 11:48:52 -05002523 /*
2524 Seed clusters from color list (e.g. red;green;blue).
2525 */
Cristyff136912020-01-17 20:39:20 -05002526 status=AcquireImageColormap(image,number_colors,exception);
2527 if (status == MagickFalse)
2528 return(status);
Cristy63442152019-12-21 09:47:06 -05002529 for (n=0, p=colors; n < (ssize_t) image->colors; n++)
2530 {
Cristyf2dc1dd2020-12-28 13:59:26 -05002531 const char
Cristy63442152019-12-21 09:47:06 -05002532 *q;
2533
2534 for (q=p; *q != '\0'; q++)
2535 if (*q == ';')
2536 break;
Cristy74b37332019-12-21 19:36:19 -05002537 (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
2538 MagickPathExtent));
Cristy193f3952019-12-28 11:48:52 -05002539 (void) QueryColorCompliance(color,AllCompliance,image->colormap+n,
Cristy63442152019-12-21 09:47:06 -05002540 exception);
2541 if (*q == '\0')
Cristy76b96952019-12-21 16:00:54 -05002542 {
2543 n++;
2544 break;
2545 }
Cristy63442152019-12-21 09:47:06 -05002546 p=q+1;
2547 }
2548 if (n < (ssize_t) image->colors)
2549 {
2550 RandomInfo
2551 *random_info;
2552
Cristy28164982019-12-22 08:02:31 -05002553 /*
Cristy193f3952019-12-28 11:48:52 -05002554 Seed clusters from random values.
Cristy28164982019-12-22 08:02:31 -05002555 */
Cristy63442152019-12-21 09:47:06 -05002556 random_info=AcquireRandomInfo();
2557 for ( ; n < (ssize_t) image->colors; n++)
Cristyb8f86f82019-12-22 20:52:26 -05002558 {
Cristy193f3952019-12-28 11:48:52 -05002559 (void) QueryColorCompliance("#000",AllCompliance,image->colormap+n,
Cristyf1f68eb2019-12-27 18:22:57 -05002560 exception);
2561 image->colormap[n].red=RandomColorComponent(random_info);
2562 image->colormap[n].green=RandomColorComponent(random_info);
2563 image->colormap[n].blue=RandomColorComponent(random_info);
Cristy193f3952019-12-28 11:48:52 -05002564 if (image->alpha_trait != BlendPixelTrait)
2565 image->colormap[n].alpha=RandomColorComponent(random_info);
2566 if (image->colorspace == CMYKColorspace)
2567 image->colormap[n].black=RandomColorComponent(random_info);
Cristyb8f86f82019-12-22 20:52:26 -05002568 }
Cristy63442152019-12-21 09:47:06 -05002569 random_info=DestroyRandomInfo(random_info);
2570 }
2571 }
Cristyee6da042019-12-19 06:16:25 -05002572 /*
2573 Iterative refinement.
2574 */
Cristy193f3952019-12-28 11:48:52 -05002575 kmeans_pixels=AcquireKmeansThreadSet(number_colors);
2576 if (kmeans_pixels == (KmeansInfo **) NULL)
2577 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2578 image->filename);
Cristy4bd83192019-12-31 13:33:57 -05002579 previous_tolerance=0.0;
Cristydc157f02019-12-19 16:32:14 -05002580 verbose=IsStringTrue(GetImageArtifact(image,"debug"));
Cristyf1f68eb2019-12-27 18:22:57 -05002581 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
Cristye574a422019-12-19 19:26:53 -05002582 image_view=AcquireAuthenticCacheView(image,exception);
Cristy746cd9c2019-12-20 21:24:43 -05002583 for (n=0; n < (ssize_t) max_iterations; n++)
Cristyee6da042019-12-19 06:16:25 -05002584 {
Cristyee6da042019-12-19 06:16:25 -05002585 double
2586 distortion;
2587
Cristyf2dc1dd2020-12-28 13:59:26 -05002588 ssize_t
Cristyee6da042019-12-19 06:16:25 -05002589 i;
2590
2591 ssize_t
2592 y;
2593
Cristyf1f68eb2019-12-27 18:22:57 -05002594 for (i=0; i < (ssize_t) number_threads; i++)
2595 (void) memset(kmeans_pixels[i],0,image->colors*sizeof(*kmeans_pixels[i]));
2596#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy193f3952019-12-28 11:48:52 -05002597 #pragma omp parallel for schedule(dynamic) shared(status) \
Cristyf1f68eb2019-12-27 18:22:57 -05002598 magick_number_threads(image,image,image->rows,1)
2599#endif
Cristyee6da042019-12-19 06:16:25 -05002600 for (y=0; y < (ssize_t) image->rows; y++)
2601 {
Cristyf1f68eb2019-12-27 18:22:57 -05002602 const int
2603 id = GetOpenMPThreadId();
2604
Cristyf2dc1dd2020-12-28 13:59:26 -05002605 Quantum
Cristyee6da042019-12-19 06:16:25 -05002606 *magick_restrict q;
2607
Cristyf2dc1dd2020-12-28 13:59:26 -05002608 ssize_t
Cristyee6da042019-12-19 06:16:25 -05002609 x;
2610
Cristyf1f68eb2019-12-27 18:22:57 -05002611 if (status == MagickFalse)
2612 continue;
Cristyee6da042019-12-19 06:16:25 -05002613 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2614 if (q == (Quantum *) NULL)
2615 {
2616 status=MagickFalse;
Cristyf1f68eb2019-12-27 18:22:57 -05002617 continue;
Cristyee6da042019-12-19 06:16:25 -05002618 }
2619 for (x=0; x < (ssize_t) image->columns; x++)
2620 {
2621 double
2622 min_distance;
2623
Cristyf2dc1dd2020-12-28 13:59:26 -05002624 ssize_t
Cristyee6da042019-12-19 06:16:25 -05002625 i;
2626
2627 ssize_t
2628 j;
2629
2630 /*
Cristyf1f68eb2019-12-27 18:22:57 -05002631 Assign each pixel whose mean has the least squared color distance.
Cristyee6da042019-12-19 06:16:25 -05002632 */
2633 j=0;
Cristyb5642a62019-12-31 16:49:38 -05002634 min_distance=KmeansMetric(image,q,image->colormap+0);
Cristyee6da042019-12-19 06:16:25 -05002635 for (i=1; i < (ssize_t) image->colors; i++)
2636 {
2637 double
2638 distance;
2639
2640 if (min_distance <= MagickEpsilon)
2641 break;
Cristyb5642a62019-12-31 16:49:38 -05002642 distance=KmeansMetric(image,q,image->colormap+i);
Cristyee6da042019-12-19 06:16:25 -05002643 if (distance < min_distance)
2644 {
2645 min_distance=distance;
2646 j=i;
2647 }
2648 }
Cristyf1f68eb2019-12-27 18:22:57 -05002649 kmeans_pixels[id][j].red+=QuantumScale*GetPixelRed(image,q);
2650 kmeans_pixels[id][j].green+=QuantumScale*GetPixelGreen(image,q);
2651 kmeans_pixels[id][j].blue+=QuantumScale*GetPixelBlue(image,q);
Cristye574a422019-12-19 19:26:53 -05002652 if (image->alpha_trait != BlendPixelTrait)
Cristyf1f68eb2019-12-27 18:22:57 -05002653 kmeans_pixels[id][j].alpha+=QuantumScale*GetPixelAlpha(image,q);
Cristye574a422019-12-19 19:26:53 -05002654 if (image->colorspace == CMYKColorspace)
Cristyf1f68eb2019-12-27 18:22:57 -05002655 kmeans_pixels[id][j].black+=QuantumScale*GetPixelBlack(image,q);
2656 kmeans_pixels[id][j].count++;
2657 kmeans_pixels[id][j].distortion+=min_distance;
Cristy746cd9c2019-12-20 21:24:43 -05002658 SetPixelIndex(image,(Quantum) j,q);
Cristyee6da042019-12-19 06:16:25 -05002659 q+=GetPixelChannels(image);
2660 }
Cristy19aadb92019-12-27 16:57:46 -05002661 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
Cristyf1f68eb2019-12-27 18:22:57 -05002662 status=MagickFalse;
Cristyee6da042019-12-19 06:16:25 -05002663 }
Cristy19aadb92019-12-27 16:57:46 -05002664 if (status == MagickFalse)
2665 break;
Cristyee6da042019-12-19 06:16:25 -05002666 /*
Cristy193f3952019-12-28 11:48:52 -05002667 Reduce sums to [0] entry.
Cristyf1f68eb2019-12-27 18:22:57 -05002668 */
2669 for (i=1; i < (ssize_t) number_threads; i++)
2670 {
Cristyf2dc1dd2020-12-28 13:59:26 -05002671 ssize_t
Cristyf1f68eb2019-12-27 18:22:57 -05002672 j;
2673
2674 for (j=0; j < (ssize_t) image->colors; j++)
2675 {
2676 kmeans_pixels[0][j].red+=kmeans_pixels[i][j].red;
2677 kmeans_pixels[0][j].green+=kmeans_pixels[i][j].green;
2678 kmeans_pixels[0][j].blue+=kmeans_pixels[i][j].blue;
2679 if (image->alpha_trait != BlendPixelTrait)
2680 kmeans_pixels[0][j].alpha+=kmeans_pixels[i][j].alpha;
2681 if (image->colorspace == CMYKColorspace)
2682 kmeans_pixels[0][j].black+=kmeans_pixels[i][j].black;
2683 kmeans_pixels[0][j].count+=kmeans_pixels[i][j].count;
2684 kmeans_pixels[0][j].distortion+=kmeans_pixels[i][j].distortion;
2685 }
2686 }
2687 /*
Cristyee6da042019-12-19 06:16:25 -05002688 Calculate the new means (centroids) of the pixels in the new clusters.
2689 */
Cristyf1f68eb2019-12-27 18:22:57 -05002690 distortion=0.0;
Cristye574a422019-12-19 19:26:53 -05002691 for (i=0; i < (ssize_t) image->colors; i++)
Cristyee6da042019-12-19 06:16:25 -05002692 {
Cristy58710592019-12-21 10:04:11 -05002693 double
Cristyf1f68eb2019-12-27 18:22:57 -05002694 gamma;
Cristy58710592019-12-21 10:04:11 -05002695
Cristyf1f68eb2019-12-27 18:22:57 -05002696 gamma=PerceptibleReciprocal((double) kmeans_pixels[0][i].count);
2697 image->colormap[i].red=gamma*QuantumRange*kmeans_pixels[0][i].red;
2698 image->colormap[i].green=gamma*QuantumRange*kmeans_pixels[0][i].green;
2699 image->colormap[i].blue=gamma*QuantumRange*kmeans_pixels[0][i].blue;
Cristye574a422019-12-19 19:26:53 -05002700 if (image->alpha_trait != BlendPixelTrait)
Cristyf1f68eb2019-12-27 18:22:57 -05002701 image->colormap[i].alpha=gamma*QuantumRange*kmeans_pixels[0][i].alpha;
Cristye574a422019-12-19 19:26:53 -05002702 if (image->colorspace == CMYKColorspace)
Cristyf1f68eb2019-12-27 18:22:57 -05002703 image->colormap[i].black=gamma*QuantumRange*kmeans_pixels[0][i].black;
2704 distortion+=kmeans_pixels[0][i].distortion;
Cristyee6da042019-12-19 06:16:25 -05002705 }
Cristydc157f02019-12-19 16:32:14 -05002706 if (verbose != MagickFalse)
Cristy2f0e0632019-12-28 13:59:38 -05002707 (void) FormatLocaleFile(stderr,"distortion[%.20g]: %*g %*g\n",(double) n,
2708 GetMagickPrecision(),distortion,GetMagickPrecision(),
Cristy4bd83192019-12-31 13:33:57 -05002709 fabs(distortion-previous_tolerance));
2710 if (fabs(distortion-previous_tolerance) <= tolerance)
Cristyee6da042019-12-19 06:16:25 -05002711 break;
Cristy4bd83192019-12-31 13:33:57 -05002712 previous_tolerance=distortion;
Cristyee6da042019-12-19 06:16:25 -05002713 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2714 {
2715 MagickBooleanType
2716 proceed;
2717
Cristy746cd9c2019-12-20 21:24:43 -05002718 proceed=SetImageProgress(image,KmeansImageTag,(MagickOffsetType) n,
2719 max_iterations);
Cristyee6da042019-12-19 06:16:25 -05002720 if (proceed == MagickFalse)
2721 status=MagickFalse;
2722 }
2723 }
Cristye574a422019-12-19 19:26:53 -05002724 image_view=DestroyCacheView(image_view);
Cristyf1f68eb2019-12-27 18:22:57 -05002725 kmeans_pixels=DestroyKmeansThreadSet(kmeans_pixels);
Cristyee6da042019-12-19 06:16:25 -05002726 if (image->progress_monitor != (MagickProgressMonitor) NULL)
Cristy746cd9c2019-12-20 21:24:43 -05002727 (void) SetImageProgress(image,KmeansImageTag,(MagickOffsetType)
2728 max_iterations-1,max_iterations);
Cristyb5dffba2019-12-19 18:18:00 -05002729 if (status == MagickFalse)
2730 return(status);
2731 return(SyncImage(image,exception));
Cristyee6da042019-12-19 06:16:25 -05002732}
2733
2734/*
2735%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2736% %
2737% %
2738% %
cristy018f07f2011-09-04 21:15:19 +00002739% P o s t e r i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002740% %
2741% %
2742% %
2743%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2744%
2745% PosterizeImage() reduces the image to a limited number of colors for a
2746% "poster" effect.
2747%
2748% The format of the PosterizeImage method is:
2749%
cristybb503372010-05-27 20:51:26 +00002750% MagickBooleanType PosterizeImage(Image *image,const size_t levels,
cristycbda6112012-05-27 20:57:16 +00002751% const DitherMethod dither_method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002752%
2753% A description of each parameter follows:
2754%
2755% o image: Specifies a pointer to an Image structure.
2756%
2757% o levels: Number of color levels allowed in each channel. Very low values
2758% (2, 3, or 4) have the most visible effect.
2759%
cristycbda6112012-05-27 20:57:16 +00002760% o dither_method: choose from UndefinedDitherMethod, NoDitherMethod,
2761% RiemersmaDitherMethod, FloydSteinbergDitherMethod.
cristy3ed852e2009-09-05 21:47:34 +00002762%
cristy018f07f2011-09-04 21:15:19 +00002763% o exception: return any errors or warnings in this structure.
2764%
cristy3ed852e2009-09-05 21:47:34 +00002765*/
cristyd1a2c0f2011-02-09 14:14:50 +00002766
cristy72844f12013-04-28 23:52:36 +00002767static inline double MagickRound(double x)
cristy4d727152011-02-10 19:57:21 +00002768{
2769 /*
cristyecc31b12011-02-13 00:32:29 +00002770 Round the fraction to nearest integer.
cristy4d727152011-02-10 19:57:21 +00002771 */
cristyae0a3fc2013-04-29 08:29:35 +00002772 if ((x-floor(x)) < (ceil(x)-x))
cristy72844f12013-04-28 23:52:36 +00002773 return(floor(x));
2774 return(ceil(x));
cristy4d727152011-02-10 19:57:21 +00002775}
2776
cristyd1a2c0f2011-02-09 14:14:50 +00002777MagickExport MagickBooleanType PosterizeImage(Image *image,const size_t levels,
cristycbda6112012-05-27 20:57:16 +00002778 const DitherMethod dither_method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002779{
cristyd1a2c0f2011-02-09 14:14:50 +00002780#define PosterizeImageTag "Posterize/Image"
Cristy7b058692019-10-11 20:20:19 -04002781#define PosterizePixel(pixel) ClampToQuantum((MagickRealType) QuantumRange*( \
2782 MagickRound(QuantumScale*pixel*(levels-1)))/MagickMax((ssize_t) levels-1,1))
cristyd1a2c0f2011-02-09 14:14:50 +00002783
cristyc4c8d132010-01-07 01:58:38 +00002784 CacheView
cristyd1a2c0f2011-02-09 14:14:50 +00002785 *image_view;
cristyc4c8d132010-01-07 01:58:38 +00002786
cristy3ed852e2009-09-05 21:47:34 +00002787 MagickBooleanType
2788 status;
2789
cristyd1a2c0f2011-02-09 14:14:50 +00002790 MagickOffsetType
2791 progress;
2792
cristy3ed852e2009-09-05 21:47:34 +00002793 QuantizeInfo
2794 *quantize_info;
2795
Cristyf2dc1dd2020-12-28 13:59:26 -05002796 ssize_t
cristy847620f2011-02-09 02:24:21 +00002797 i;
2798
cristy847620f2011-02-09 02:24:21 +00002799 ssize_t
cristyd1a2c0f2011-02-09 14:14:50 +00002800 y;
cristy847620f2011-02-09 02:24:21 +00002801
cristy3ed852e2009-09-05 21:47:34 +00002802 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00002803 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00002804 if (image->debug != MagickFalse)
2805 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy09e81242015-01-31 17:00:00 +00002806 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00002807 assert(exception->signature == MagickCoreSignature);
cristyd1a2c0f2011-02-09 14:14:50 +00002808 if (image->storage_class == PseudoClass)
2809#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04002810 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristydce076e2017-10-10 19:45:45 -04002811 magick_number_threads(image,image,image->colors,1)
cristyd1a2c0f2011-02-09 14:14:50 +00002812#endif
2813 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002814 {
cristyd1a2c0f2011-02-09 14:14:50 +00002815 /*
2816 Posterize colormap.
2817 */
cristyed231572011-07-14 02:18:59 +00002818 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristye42f6582012-02-11 17:59:50 +00002819 image->colormap[i].red=(double)
2820 PosterizePixel(image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002821 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristye42f6582012-02-11 17:59:50 +00002822 image->colormap[i].green=(double)
2823 PosterizePixel(image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002824 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristye42f6582012-02-11 17:59:50 +00002825 image->colormap[i].blue=(double)
2826 PosterizePixel(image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002827 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristye42f6582012-02-11 17:59:50 +00002828 image->colormap[i].alpha=(double)
2829 PosterizePixel(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002830 }
cristyd1a2c0f2011-02-09 14:14:50 +00002831 /*
2832 Posterize image.
2833 */
2834 status=MagickTrue;
2835 progress=0;
cristy46ff2672012-12-14 15:32:26 +00002836 image_view=AcquireAuthenticCacheView(image,exception);
cristyd1a2c0f2011-02-09 14:14:50 +00002837#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04002838 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristy8255ca92017-10-09 08:37:35 -04002839 magick_number_threads(image,image,image->rows,1)
cristyd1a2c0f2011-02-09 14:14:50 +00002840#endif
2841 for (y=0; y < (ssize_t) image->rows; y++)
2842 {
Cristyf2dc1dd2020-12-28 13:59:26 -05002843 Quantum
dirk05d2ff72015-11-18 23:13:43 +01002844 *magick_restrict q;
cristyd1a2c0f2011-02-09 14:14:50 +00002845
Cristyf2dc1dd2020-12-28 13:59:26 -05002846 ssize_t
cristyd1a2c0f2011-02-09 14:14:50 +00002847 x;
2848
2849 if (status == MagickFalse)
2850 continue;
2851 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002852 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002853 {
cristyd1a2c0f2011-02-09 14:14:50 +00002854 status=MagickFalse;
2855 continue;
cristy3ed852e2009-09-05 21:47:34 +00002856 }
cristyd1a2c0f2011-02-09 14:14:50 +00002857 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002858 {
cristyed231572011-07-14 02:18:59 +00002859 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002860 SetPixelRed(image,PosterizePixel(GetPixelRed(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002861 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002862 SetPixelGreen(image,PosterizePixel(GetPixelGreen(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002863 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002864 SetPixelBlue(image,PosterizePixel(GetPixelBlue(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002865 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002866 (image->colorspace == CMYKColorspace))
2867 SetPixelBlack(image,PosterizePixel(GetPixelBlack(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002868 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy09e81242015-01-31 17:00:00 +00002869 (image->alpha_trait == BlendPixelTrait))
cristy4c08aed2011-07-01 19:47:50 +00002870 SetPixelAlpha(image,PosterizePixel(GetPixelAlpha(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002871 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002872 }
cristyd1a2c0f2011-02-09 14:14:50 +00002873 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2874 status=MagickFalse;
2875 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2876 {
2877 MagickBooleanType
2878 proceed;
2879
Cristyfc89eec2018-11-11 21:10:06 -05002880#if defined(MAGICKCORE_OPENMP_SUPPORT)
2881 #pragma omp atomic
2882#endif
2883 progress++;
2884 proceed=SetImageProgress(image,PosterizeImageTag,progress,image->rows);
cristyd1a2c0f2011-02-09 14:14:50 +00002885 if (proceed == MagickFalse)
2886 status=MagickFalse;
2887 }
2888 }
2889 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002890 quantize_info=AcquireQuantizeInfo((ImageInfo *) NULL);
cristyd1a2c0f2011-02-09 14:14:50 +00002891 quantize_info->number_colors=(size_t) MagickMin((ssize_t) levels*levels*
2892 levels,MaxColormapSize+1);
cristycbda6112012-05-27 20:57:16 +00002893 quantize_info->dither_method=dither_method;
cristy3e9cad02011-02-20 01:42:00 +00002894 quantize_info->tree_depth=MaxTreeDepth;
cristy018f07f2011-09-04 21:15:19 +00002895 status=QuantizeImage(quantize_info,image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002896 quantize_info=DestroyQuantizeInfo(quantize_info);
cristy3ed852e2009-09-05 21:47:34 +00002897 return(status);
2898}
2899
2900/*
2901%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2902% %
2903% %
2904% %
2905+ P r u n e C h i l d %
2906% %
2907% %
2908% %
2909%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2910%
2911% PruneChild() deletes the given node and merges its statistics into its
2912% parent.
2913%
2914% The format of the PruneSubtree method is:
2915%
dirkf20fa432015-11-22 11:03:51 +01002916% PruneChild(CubeInfo *cube_info,const NodeInfo *node_info)
cristy3ed852e2009-09-05 21:47:34 +00002917%
2918% A description of each parameter follows.
2919%
cristy3ed852e2009-09-05 21:47:34 +00002920% o cube_info: A pointer to the Cube structure.
2921%
2922% o node_info: pointer to node in color cube tree that is to be pruned.
2923%
2924*/
dirkf20fa432015-11-22 11:03:51 +01002925static void PruneChild(CubeInfo *cube_info,const NodeInfo *node_info)
cristy3ed852e2009-09-05 21:47:34 +00002926{
2927 NodeInfo
2928 *parent;
2929
Cristyf2dc1dd2020-12-28 13:59:26 -05002930 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002931 i;
2932
cristybb503372010-05-27 20:51:26 +00002933 size_t
cristy3ed852e2009-09-05 21:47:34 +00002934 number_children;
2935
2936 /*
2937 Traverse any children.
2938 */
2939 number_children=cube_info->associate_alpha == MagickFalse ? 8UL : 16UL;
cristybb503372010-05-27 20:51:26 +00002940 for (i=0; i < (ssize_t) number_children; i++)
cristy3ed852e2009-09-05 21:47:34 +00002941 if (node_info->child[i] != (NodeInfo *) NULL)
dirkf20fa432015-11-22 11:03:51 +01002942 PruneChild(cube_info,node_info->child[i]);
Cristy9f57fa72020-09-02 19:22:51 -04002943 /*
2944 Merge color statistics into parent.
2945 */
2946 parent=node_info->parent;
2947 parent->number_unique+=node_info->number_unique;
2948 parent->total_color.red+=node_info->total_color.red;
2949 parent->total_color.green+=node_info->total_color.green;
2950 parent->total_color.blue+=node_info->total_color.blue;
2951 parent->total_color.alpha+=node_info->total_color.alpha;
2952 parent->child[node_info->id]=(NodeInfo *) NULL;
2953 cube_info->nodes--;
cristy3ed852e2009-09-05 21:47:34 +00002954}
2955
2956/*
2957%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2958% %
2959% %
2960% %
2961+ P r u n e L e v e l %
2962% %
2963% %
2964% %
2965%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2966%
2967% PruneLevel() deletes all nodes at the bottom level of the color tree merging
2968% their color statistics into their parent node.
2969%
2970% The format of the PruneLevel method is:
2971%
dirkf20fa432015-11-22 11:03:51 +01002972% PruneLevel(CubeInfo *cube_info,const NodeInfo *node_info)
cristy3ed852e2009-09-05 21:47:34 +00002973%
2974% A description of each parameter follows.
2975%
cristy3ed852e2009-09-05 21:47:34 +00002976% o cube_info: A pointer to the Cube structure.
2977%
2978% o node_info: pointer to node in color cube tree that is to be pruned.
2979%
2980*/
dirkf20fa432015-11-22 11:03:51 +01002981static void PruneLevel(CubeInfo *cube_info,const NodeInfo *node_info)
cristy3ed852e2009-09-05 21:47:34 +00002982{
Cristyf2dc1dd2020-12-28 13:59:26 -05002983 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002984 i;
2985
cristybb503372010-05-27 20:51:26 +00002986 size_t
cristy3ed852e2009-09-05 21:47:34 +00002987 number_children;
2988
2989 /*
2990 Traverse any children.
2991 */
2992 number_children=cube_info->associate_alpha == MagickFalse ? 8UL : 16UL;
cristybb503372010-05-27 20:51:26 +00002993 for (i=0; i < (ssize_t) number_children; i++)
cristy3ed852e2009-09-05 21:47:34 +00002994 if (node_info->child[i] != (NodeInfo *) NULL)
dirkf20fa432015-11-22 11:03:51 +01002995 PruneLevel(cube_info,node_info->child[i]);
cristy3ed852e2009-09-05 21:47:34 +00002996 if (node_info->level == cube_info->depth)
dirkf20fa432015-11-22 11:03:51 +01002997 PruneChild(cube_info,node_info);
cristy3ed852e2009-09-05 21:47:34 +00002998}
2999
3000/*
3001%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3002% %
3003% %
3004% %
3005+ P r u n e T o C u b e D e p t h %
3006% %
3007% %
3008% %
3009%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3010%
3011% PruneToCubeDepth() deletes any nodes at a depth greater than
3012% cube_info->depth while merging their color statistics into their parent
3013% node.
3014%
3015% The format of the PruneToCubeDepth method is:
3016%
dirkf20fa432015-11-22 11:03:51 +01003017% PruneToCubeDepth(CubeInfo *cube_info,const NodeInfo *node_info)
cristy3ed852e2009-09-05 21:47:34 +00003018%
3019% A description of each parameter follows.
3020%
3021% o cube_info: A pointer to the Cube structure.
3022%
3023% o node_info: pointer to node in color cube tree that is to be pruned.
3024%
3025*/
dirkf20fa432015-11-22 11:03:51 +01003026static void PruneToCubeDepth(CubeInfo *cube_info,const NodeInfo *node_info)
cristy3ed852e2009-09-05 21:47:34 +00003027{
Cristyf2dc1dd2020-12-28 13:59:26 -05003028 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003029 i;
3030
cristybb503372010-05-27 20:51:26 +00003031 size_t
cristy3ed852e2009-09-05 21:47:34 +00003032 number_children;
3033
3034 /*
3035 Traverse any children.
3036 */
3037 number_children=cube_info->associate_alpha == MagickFalse ? 8UL : 16UL;
cristybb503372010-05-27 20:51:26 +00003038 for (i=0; i < (ssize_t) number_children; i++)
cristy3ed852e2009-09-05 21:47:34 +00003039 if (node_info->child[i] != (NodeInfo *) NULL)
dirkf20fa432015-11-22 11:03:51 +01003040 PruneToCubeDepth(cube_info,node_info->child[i]);
cristy3ed852e2009-09-05 21:47:34 +00003041 if (node_info->level > cube_info->depth)
dirkf20fa432015-11-22 11:03:51 +01003042 PruneChild(cube_info,node_info);
cristy3ed852e2009-09-05 21:47:34 +00003043}
3044
3045/*
3046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3047% %
3048% %
3049% %
3050% Q u a n t i z e I m a g e %
3051% %
3052% %
3053% %
3054%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3055%
3056% QuantizeImage() analyzes the colors within a reference image and chooses a
3057% fixed number of colors to represent the image. The goal of the algorithm
3058% is to minimize the color difference between the input and output image while
3059% minimizing the processing time.
3060%
3061% The format of the QuantizeImage method is:
3062%
3063% MagickBooleanType QuantizeImage(const QuantizeInfo *quantize_info,
cristy018f07f2011-09-04 21:15:19 +00003064% Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003065%
3066% A description of each parameter follows:
3067%
3068% o quantize_info: Specifies a pointer to an QuantizeInfo structure.
3069%
3070% o image: the image.
3071%
cristy018f07f2011-09-04 21:15:19 +00003072% o exception: return any errors or warnings in this structure.
3073%
cristy3ed852e2009-09-05 21:47:34 +00003074*/
3075MagickExport MagickBooleanType QuantizeImage(const QuantizeInfo *quantize_info,
cristy018f07f2011-09-04 21:15:19 +00003076 Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003077{
3078 CubeInfo
3079 *cube_info;
3080
3081 MagickBooleanType
3082 status;
3083
cristybb503372010-05-27 20:51:26 +00003084 size_t
cristy3ed852e2009-09-05 21:47:34 +00003085 depth,
3086 maximum_colors;
3087
3088 assert(quantize_info != (const QuantizeInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003089 assert(quantize_info->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003090 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003091 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003092 if (image->debug != MagickFalse)
3093 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy09e81242015-01-31 17:00:00 +00003094 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003095 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003096 maximum_colors=quantize_info->number_colors;
3097 if (maximum_colors == 0)
3098 maximum_colors=MaxColormapSize;
3099 if (maximum_colors > MaxColormapSize)
3100 maximum_colors=MaxColormapSize;
cristy09e81242015-01-31 17:00:00 +00003101 if (image->alpha_trait != BlendPixelTrait)
cristy400e7012012-08-08 13:41:59 +00003102 {
dirkf1d85482015-04-06 00:36:00 +00003103 if (SetImageGray(image,exception) != MagickFalse)
cristy400e7012012-08-08 13:41:59 +00003104 (void) SetGrayscaleImage(image,exception);
3105 }
cristy3ed852e2009-09-05 21:47:34 +00003106 depth=quantize_info->tree_depth;
3107 if (depth == 0)
3108 {
cristybb503372010-05-27 20:51:26 +00003109 size_t
cristy3ed852e2009-09-05 21:47:34 +00003110 colors;
3111
3112 /*
3113 Depth of color tree is: Log4(colormap size)+2.
3114 */
3115 colors=maximum_colors;
3116 for (depth=1; colors != 0; depth++)
3117 colors>>=2;
cristycbda6112012-05-27 20:57:16 +00003118 if ((quantize_info->dither_method != NoDitherMethod) && (depth > 2))
cristy3ed852e2009-09-05 21:47:34 +00003119 depth--;
cristy09e81242015-01-31 17:00:00 +00003120 if ((image->alpha_trait == BlendPixelTrait) && (depth > 5))
cristy3ed852e2009-09-05 21:47:34 +00003121 depth--;
dirkf1d85482015-04-06 00:36:00 +00003122 if (SetImageGray(image,exception) != MagickFalse)
cristya8be0ae2014-04-02 21:10:45 +00003123 depth=MaxTreeDepth;
cristy3ed852e2009-09-05 21:47:34 +00003124 }
3125 /*
3126 Initialize color cube.
3127 */
3128 cube_info=GetCubeInfo(quantize_info,depth,maximum_colors);
3129 if (cube_info == (CubeInfo *) NULL)
3130 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3131 image->filename);
cristy8a11cb12011-10-19 23:53:34 +00003132 status=ClassifyImageColors(cube_info,image,exception);
cristy3ed852e2009-09-05 21:47:34 +00003133 if (status != MagickFalse)
3134 {
3135 /*
Cristy8eb55f52019-10-12 14:14:41 -04003136 Reduce the number of colors in the image.
cristy3ed852e2009-09-05 21:47:34 +00003137 */
dirkc7720592015-07-06 20:46:23 +00003138 if (cube_info->colors > cube_info->maximum_colors)
dirkbce08592015-06-27 20:26:03 +00003139 ReduceImageColors(image,cube_info);
dirkc7720592015-07-06 20:46:23 +00003140 status=AssignImageColors(image,cube_info,exception);
cristy3ed852e2009-09-05 21:47:34 +00003141 }
3142 DestroyCubeInfo(cube_info);
3143 return(status);
3144}
3145
3146/*
3147%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3148% %
3149% %
3150% %
3151% Q u a n t i z e I m a g e s %
3152% %
3153% %
3154% %
3155%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3156%
3157% QuantizeImages() analyzes the colors within a set of reference images and
3158% chooses a fixed number of colors to represent the set. The goal of the
3159% algorithm is to minimize the color difference between the input and output
3160% images while minimizing the processing time.
3161%
3162% The format of the QuantizeImages method is:
3163%
3164% MagickBooleanType QuantizeImages(const QuantizeInfo *quantize_info,
cristy018f07f2011-09-04 21:15:19 +00003165% Image *images,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003166%
3167% A description of each parameter follows:
3168%
3169% o quantize_info: Specifies a pointer to an QuantizeInfo structure.
3170%
3171% o images: Specifies a pointer to a list of Image structures.
3172%
cristy018f07f2011-09-04 21:15:19 +00003173% o exception: return any errors or warnings in this structure.
3174%
cristy3ed852e2009-09-05 21:47:34 +00003175*/
3176MagickExport MagickBooleanType QuantizeImages(const QuantizeInfo *quantize_info,
cristy018f07f2011-09-04 21:15:19 +00003177 Image *images,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003178{
3179 CubeInfo
3180 *cube_info;
3181
3182 Image
3183 *image;
3184
3185 MagickBooleanType
3186 proceed,
3187 status;
3188
3189 MagickProgressMonitor
3190 progress_monitor;
3191
Cristyf2dc1dd2020-12-28 13:59:26 -05003192 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003193 i;
3194
cristybb503372010-05-27 20:51:26 +00003195 size_t
cristy3ed852e2009-09-05 21:47:34 +00003196 depth,
3197 maximum_colors,
3198 number_images;
3199
3200 assert(quantize_info != (const QuantizeInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003201 assert(quantize_info->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003202 assert(images != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003203 assert(images->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003204 if (images->debug != MagickFalse)
3205 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
cristy09e81242015-01-31 17:00:00 +00003206 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003207 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003208 if (GetNextImageInList(images) == (Image *) NULL)
3209 {
3210 /*
3211 Handle a single image with QuantizeImage.
3212 */
cristy018f07f2011-09-04 21:15:19 +00003213 status=QuantizeImage(quantize_info,images,exception);
cristy3ed852e2009-09-05 21:47:34 +00003214 return(status);
3215 }
3216 status=MagickFalse;
3217 maximum_colors=quantize_info->number_colors;
3218 if (maximum_colors == 0)
3219 maximum_colors=MaxColormapSize;
3220 if (maximum_colors > MaxColormapSize)
3221 maximum_colors=MaxColormapSize;
3222 depth=quantize_info->tree_depth;
3223 if (depth == 0)
3224 {
cristybb503372010-05-27 20:51:26 +00003225 size_t
cristy3ed852e2009-09-05 21:47:34 +00003226 colors;
3227
3228 /*
3229 Depth of color tree is: Log4(colormap size)+2.
3230 */
3231 colors=maximum_colors;
3232 for (depth=1; colors != 0; depth++)
3233 colors>>=2;
cristycbda6112012-05-27 20:57:16 +00003234 if (quantize_info->dither_method != NoDitherMethod)
cristy3ed852e2009-09-05 21:47:34 +00003235 depth--;
3236 }
3237 /*
3238 Initialize color cube.
3239 */
3240 cube_info=GetCubeInfo(quantize_info,depth,maximum_colors);
3241 if (cube_info == (CubeInfo *) NULL)
3242 {
cristy8a11cb12011-10-19 23:53:34 +00003243 (void) ThrowMagickException(exception,GetMagickModule(),
cristyefe601c2013-01-05 17:51:12 +00003244 ResourceLimitError,"MemoryAllocationFailed","`%s'",images->filename);
cristy3ed852e2009-09-05 21:47:34 +00003245 return(MagickFalse);
3246 }
3247 number_images=GetImageListLength(images);
3248 image=images;
3249 for (i=0; image != (Image *) NULL; i++)
3250 {
3251 progress_monitor=SetImageProgressMonitor(image,(MagickProgressMonitor) NULL,
3252 image->client_data);
cristy8a11cb12011-10-19 23:53:34 +00003253 status=ClassifyImageColors(cube_info,image,exception);
cristy3ed852e2009-09-05 21:47:34 +00003254 if (status == MagickFalse)
3255 break;
3256 (void) SetImageProgressMonitor(image,progress_monitor,image->client_data);
cristycee97112010-05-28 00:44:52 +00003257 proceed=SetImageProgress(image,AssignImageTag,(MagickOffsetType) i,
3258 number_images);
cristy3ed852e2009-09-05 21:47:34 +00003259 if (proceed == MagickFalse)
3260 break;
3261 image=GetNextImageInList(image);
3262 }
3263 if (status != MagickFalse)
3264 {
3265 /*
3266 Reduce the number of colors in an image sequence.
3267 */
3268 ReduceImageColors(images,cube_info);
3269 image=images;
3270 for (i=0; image != (Image *) NULL; i++)
3271 {
3272 progress_monitor=SetImageProgressMonitor(image,(MagickProgressMonitor)
3273 NULL,image->client_data);
cristy018f07f2011-09-04 21:15:19 +00003274 status=AssignImageColors(image,cube_info,exception);
cristy3ed852e2009-09-05 21:47:34 +00003275 if (status == MagickFalse)
3276 break;
3277 (void) SetImageProgressMonitor(image,progress_monitor,
3278 image->client_data);
cristycee97112010-05-28 00:44:52 +00003279 proceed=SetImageProgress(image,AssignImageTag,(MagickOffsetType) i,
3280 number_images);
cristy3ed852e2009-09-05 21:47:34 +00003281 if (proceed == MagickFalse)
3282 break;
3283 image=GetNextImageInList(image);
3284 }
3285 }
3286 DestroyCubeInfo(cube_info);
3287 return(status);
3288}
3289
3290/*
3291%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3292% %
3293% %
3294% %
cristyb2f9ca82013-10-25 14:23:25 +00003295+ Q u a n t i z e E r r o r F l a t t e n %
3296% %
3297% %
3298% %
3299%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3300%
3301% QuantizeErrorFlatten() traverses the color cube and flattens the quantization
cristya5573bc2013-10-26 14:13:34 +00003302% error into a sorted 1D array. This accelerates the color reduction process.
cristyb2f9ca82013-10-25 14:23:25 +00003303%
3304% Contributed by Yoya.
3305%
cristy46a9a152015-02-01 15:02:17 +00003306% The format of the QuantizeErrorFlatten method is:
cristyb2f9ca82013-10-25 14:23:25 +00003307%
dirk5289ead2015-11-22 10:51:52 +01003308% size_t QuantizeErrorFlatten(const CubeInfo *cube_info,
cristy06909862013-10-25 21:23:03 +00003309% const NodeInfo *node_info,const ssize_t offset,
cristy09e81242015-01-31 17:00:00 +00003310% double *quantize_error)
cristyb2f9ca82013-10-25 14:23:25 +00003311%
3312% A description of each parameter follows.
3313%
cristyb2f9ca82013-10-25 14:23:25 +00003314% o cube_info: A pointer to the Cube structure.
3315%
3316% o node_info: pointer to node in color cube tree that is current pointer.
3317%
3318% o offset: quantize error offset.
3319%
cristyb2f9ca82013-10-25 14:23:25 +00003320% o quantize_error: the quantization error vector.
3321%
3322*/
dirk5289ead2015-11-22 10:51:52 +01003323static size_t QuantizeErrorFlatten(const CubeInfo *cube_info,
cristy09e81242015-01-31 17:00:00 +00003324 const NodeInfo *node_info,const ssize_t offset,double *quantize_error)
cristyb2f9ca82013-10-25 14:23:25 +00003325{
Cristyf2dc1dd2020-12-28 13:59:26 -05003326 ssize_t
cristyb2f9ca82013-10-25 14:23:25 +00003327 i;
3328
3329 size_t
3330 n,
3331 number_children;
3332
cristy06909862013-10-25 21:23:03 +00003333 if (offset >= (ssize_t) cube_info->nodes)
cristyb2f9ca82013-10-25 14:23:25 +00003334 return(0);
3335 quantize_error[offset]=node_info->quantize_error;
3336 n=1;
3337 number_children=cube_info->associate_alpha == MagickFalse ? 8UL : 16UL;
3338 for (i=0; i < (ssize_t) number_children ; i++)
3339 if (node_info->child[i] != (NodeInfo *) NULL)
dirk5289ead2015-11-22 10:51:52 +01003340 n+=QuantizeErrorFlatten(cube_info,node_info->child[i],offset+n,
cristy06909862013-10-25 21:23:03 +00003341 quantize_error);
cristyb2f9ca82013-10-25 14:23:25 +00003342 return(n);
3343}
3344
3345/*
3346%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3347% %
3348% %
3349% %
cristy3ed852e2009-09-05 21:47:34 +00003350+ R e d u c e %
3351% %
3352% %
3353% %
3354%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3355%
3356% Reduce() traverses the color cube tree and prunes any node whose
3357% quantization error falls below a particular threshold.
3358%
3359% The format of the Reduce method is:
3360%
dirkf20fa432015-11-22 11:03:51 +01003361% Reduce(CubeInfo *cube_info,const NodeInfo *node_info)
cristy3ed852e2009-09-05 21:47:34 +00003362%
3363% A description of each parameter follows.
3364%
cristy3ed852e2009-09-05 21:47:34 +00003365% o cube_info: A pointer to the Cube structure.
3366%
3367% o node_info: pointer to node in color cube tree that is to be pruned.
3368%
3369*/
dirkf20fa432015-11-22 11:03:51 +01003370static void Reduce(CubeInfo *cube_info,const NodeInfo *node_info)
cristy3ed852e2009-09-05 21:47:34 +00003371{
Cristyf2dc1dd2020-12-28 13:59:26 -05003372 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003373 i;
3374
cristybb503372010-05-27 20:51:26 +00003375 size_t
cristy3ed852e2009-09-05 21:47:34 +00003376 number_children;
3377
3378 /*
3379 Traverse any children.
3380 */
3381 number_children=cube_info->associate_alpha == MagickFalse ? 8UL : 16UL;
cristybb503372010-05-27 20:51:26 +00003382 for (i=0; i < (ssize_t) number_children; i++)
cristy3ed852e2009-09-05 21:47:34 +00003383 if (node_info->child[i] != (NodeInfo *) NULL)
dirkf20fa432015-11-22 11:03:51 +01003384 Reduce(cube_info,node_info->child[i]);
cristy3ed852e2009-09-05 21:47:34 +00003385 if (node_info->quantize_error <= cube_info->pruning_threshold)
dirkf20fa432015-11-22 11:03:51 +01003386 PruneChild(cube_info,node_info);
cristy3ed852e2009-09-05 21:47:34 +00003387 else
3388 {
3389 /*
3390 Find minimum pruning threshold.
3391 */
3392 if (node_info->number_unique > 0)
3393 cube_info->colors++;
3394 if (node_info->quantize_error < cube_info->next_threshold)
3395 cube_info->next_threshold=node_info->quantize_error;
3396 }
3397}
3398
3399/*
3400%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3401% %
3402% %
3403% %
3404+ R e d u c e I m a g e C o l o r s %
3405% %
3406% %
3407% %
3408%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3409%
3410% ReduceImageColors() repeatedly prunes the tree until the number of nodes
3411% with n2 > 0 is less than or equal to the maximum number of colors allowed
3412% in the output image. On any given iteration over the tree, it selects
3413% those nodes whose E value is minimal for pruning and merges their
3414% color statistics upward. It uses a pruning threshold, Ep, to govern
3415% node selection as follows:
3416%
3417% Ep = 0
3418% while number of nodes with (n2 > 0) > required maximum number of colors
3419% prune all nodes such that E <= Ep
3420% Set Ep to minimum E in remaining nodes
3421%
3422% This has the effect of minimizing any quantization error when merging
3423% two nodes together.
3424%
3425% When a node to be pruned has offspring, the pruning procedure invokes
3426% itself recursively in order to prune the tree from the leaves upward.
3427% n2, Sr, Sg, and Sb in a node being pruned are always added to the
3428% corresponding data in that node's parent. This retains the pruned
3429% node's color characteristics for later averaging.
3430%
3431% For each node, n2 pixels exist for which that node represents the
3432% smallest volume in RGB space containing those pixel's colors. When n2
3433% > 0 the node will uniquely define a color in the output image. At the
3434% beginning of reduction, n2 = 0 for all nodes except a the leaves of
3435% the tree which represent colors present in the input image.
3436%
3437% The other pixel count, n1, indicates the total number of colors
3438% within the cubic volume which the node represents. This includes n1 -
3439% n2 pixels whose colors should be defined by nodes at a lower level in
3440% the tree.
3441%
3442% The format of the ReduceImageColors method is:
3443%
3444% ReduceImageColors(const Image *image,CubeInfo *cube_info)
3445%
3446% A description of each parameter follows.
3447%
3448% o image: the image.
3449%
3450% o cube_info: A pointer to the Cube structure.
3451%
3452*/
cristyb2f9ca82013-10-25 14:23:25 +00003453
cristy09e81242015-01-31 17:00:00 +00003454static int QuantizeErrorCompare(const void *error_p,const void *error_q)
cristyb2f9ca82013-10-25 14:23:25 +00003455{
cristy09e81242015-01-31 17:00:00 +00003456 double
cristyb2f9ca82013-10-25 14:23:25 +00003457 *p,
3458 *q;
3459
cristy09e81242015-01-31 17:00:00 +00003460 p=(double *) error_p;
3461 q=(double *) error_q;
cristyb2f9ca82013-10-25 14:23:25 +00003462 if (*p > *q)
3463 return(1);
cristy09e81242015-01-31 17:00:00 +00003464 if (fabs(*q-*p) <= MagickEpsilon)
cristyb2f9ca82013-10-25 14:23:25 +00003465 return(0);
3466 return(-1);
3467}
3468
cristy3ed852e2009-09-05 21:47:34 +00003469static void ReduceImageColors(const Image *image,CubeInfo *cube_info)
3470{
3471#define ReduceImageTag "Reduce/Image"
3472
3473 MagickBooleanType
3474 proceed;
3475
3476 MagickOffsetType
3477 offset;
3478
cristybb503372010-05-27 20:51:26 +00003479 size_t
cristy3ed852e2009-09-05 21:47:34 +00003480 span;
3481
3482 cube_info->next_threshold=0.0;
cristy4c475102015-01-11 15:08:21 +00003483 if (cube_info->colors > cube_info->maximum_colors)
cristyb2f9ca82013-10-25 14:23:25 +00003484 {
cristy09e81242015-01-31 17:00:00 +00003485 double
cristyb2f9ca82013-10-25 14:23:25 +00003486 *quantize_error;
3487
3488 /*
3489 Enable rapid reduction of the number of unique colors.
3490 */
cristy09e81242015-01-31 17:00:00 +00003491 quantize_error=(double *) AcquireQuantumMemory(cube_info->nodes,
cristyb2f9ca82013-10-25 14:23:25 +00003492 sizeof(*quantize_error));
cristy09e81242015-01-31 17:00:00 +00003493 if (quantize_error != (double *) NULL)
cristyb2f9ca82013-10-25 14:23:25 +00003494 {
dirk5289ead2015-11-22 10:51:52 +01003495 (void) QuantizeErrorFlatten(cube_info,cube_info->root,0,
cristy06909862013-10-25 21:23:03 +00003496 quantize_error);
cristy09e81242015-01-31 17:00:00 +00003497 qsort(quantize_error,cube_info->nodes,sizeof(double),
3498 QuantizeErrorCompare);
cristy4c475102015-01-11 15:08:21 +00003499 if (cube_info->nodes > (110*(cube_info->maximum_colors+1)/100))
3500 cube_info->next_threshold=quantize_error[cube_info->nodes-110*
3501 (cube_info->maximum_colors+1)/100];
cristy09e81242015-01-31 17:00:00 +00003502 quantize_error=(double *) RelinquishMagickMemory(quantize_error);
cristyb2f9ca82013-10-25 14:23:25 +00003503 }
3504 }
cristy3ed852e2009-09-05 21:47:34 +00003505 for (span=cube_info->colors; cube_info->colors > cube_info->maximum_colors; )
3506 {
3507 cube_info->pruning_threshold=cube_info->next_threshold;
3508 cube_info->next_threshold=cube_info->root->quantize_error-1;
3509 cube_info->colors=0;
dirkf20fa432015-11-22 11:03:51 +01003510 Reduce(cube_info,cube_info->root);
cristy3ed852e2009-09-05 21:47:34 +00003511 offset=(MagickOffsetType) span-cube_info->colors;
3512 proceed=SetImageProgress(image,ReduceImageTag,offset,span-
3513 cube_info->maximum_colors+1);
3514 if (proceed == MagickFalse)
3515 break;
3516 }
3517}
3518
3519/*
3520%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3521% %
3522% %
3523% %
3524% R e m a p I m a g e %
3525% %
3526% %
3527% %
3528%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3529%
cristy09e81242015-01-31 17:00:00 +00003530% RemapImage() replaces the colors of an image with the closest of the colors
3531% from the reference image.
cristy3ed852e2009-09-05 21:47:34 +00003532%
3533% The format of the RemapImage method is:
3534%
3535% MagickBooleanType RemapImage(const QuantizeInfo *quantize_info,
cristy018f07f2011-09-04 21:15:19 +00003536% Image *image,const Image *remap_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003537%
3538% A description of each parameter follows:
3539%
3540% o quantize_info: Specifies a pointer to an QuantizeInfo structure.
3541%
3542% o image: the image.
3543%
3544% o remap_image: the reference image.
3545%
cristy018f07f2011-09-04 21:15:19 +00003546% o exception: return any errors or warnings in this structure.
3547%
cristy3ed852e2009-09-05 21:47:34 +00003548*/
3549MagickExport MagickBooleanType RemapImage(const QuantizeInfo *quantize_info,
cristy018f07f2011-09-04 21:15:19 +00003550 Image *image,const Image *remap_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003551{
3552 CubeInfo
3553 *cube_info;
3554
3555 MagickBooleanType
3556 status;
3557
3558 /*
3559 Initialize color cube.
3560 */
3561 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003562 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003563 if (image->debug != MagickFalse)
3564 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3565 assert(remap_image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003566 assert(remap_image->signature == MagickCoreSignature);
cristy09e81242015-01-31 17:00:00 +00003567 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003568 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003569 cube_info=GetCubeInfo(quantize_info,MaxTreeDepth,
3570 quantize_info->number_colors);
3571 if (cube_info == (CubeInfo *) NULL)
3572 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3573 image->filename);
cristy8a11cb12011-10-19 23:53:34 +00003574 status=ClassifyImageColors(cube_info,remap_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00003575 if (status != MagickFalse)
3576 {
3577 /*
3578 Classify image colors from the reference image.
3579 */
3580 cube_info->quantize_info->number_colors=cube_info->colors;
cristy018f07f2011-09-04 21:15:19 +00003581 status=AssignImageColors(image,cube_info,exception);
cristy3ed852e2009-09-05 21:47:34 +00003582 }
3583 DestroyCubeInfo(cube_info);
3584 return(status);
3585}
3586
3587/*
3588%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3589% %
3590% %
3591% %
3592% R e m a p I m a g e s %
3593% %
3594% %
3595% %
3596%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3597%
3598% RemapImages() replaces the colors of a sequence of images with the
3599% closest color from a reference image.
3600%
3601% The format of the RemapImage method is:
3602%
3603% MagickBooleanType RemapImages(const QuantizeInfo *quantize_info,
cristy018f07f2011-09-04 21:15:19 +00003604% Image *images,Image *remap_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003605%
3606% A description of each parameter follows:
3607%
3608% o quantize_info: Specifies a pointer to an QuantizeInfo structure.
3609%
3610% o images: the image sequence.
3611%
3612% o remap_image: the reference image.
3613%
cristy018f07f2011-09-04 21:15:19 +00003614% o exception: return any errors or warnings in this structure.
3615%
cristy3ed852e2009-09-05 21:47:34 +00003616*/
3617MagickExport MagickBooleanType RemapImages(const QuantizeInfo *quantize_info,
cristy018f07f2011-09-04 21:15:19 +00003618 Image *images,const Image *remap_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003619{
3620 CubeInfo
3621 *cube_info;
3622
3623 Image
3624 *image;
3625
3626 MagickBooleanType
3627 status;
3628
3629 assert(images != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003630 assert(images->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003631 if (images->debug != MagickFalse)
3632 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
cristy09e81242015-01-31 17:00:00 +00003633 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003634 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003635 image=images;
3636 if (remap_image == (Image *) NULL)
3637 {
3638 /*
3639 Create a global colormap for an image sequence.
3640 */
cristy018f07f2011-09-04 21:15:19 +00003641 status=QuantizeImages(quantize_info,images,exception);
cristy3ed852e2009-09-05 21:47:34 +00003642 return(status);
3643 }
3644 /*
3645 Classify image colors from the reference image.
3646 */
3647 cube_info=GetCubeInfo(quantize_info,MaxTreeDepth,
3648 quantize_info->number_colors);
3649 if (cube_info == (CubeInfo *) NULL)
3650 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3651 image->filename);
cristy018f07f2011-09-04 21:15:19 +00003652 status=ClassifyImageColors(cube_info,remap_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00003653 if (status != MagickFalse)
3654 {
3655 /*
3656 Classify image colors from the reference image.
3657 */
3658 cube_info->quantize_info->number_colors=cube_info->colors;
3659 image=images;
3660 for ( ; image != (Image *) NULL; image=GetNextImageInList(image))
3661 {
cristy018f07f2011-09-04 21:15:19 +00003662 status=AssignImageColors(image,cube_info,exception);
cristy3ed852e2009-09-05 21:47:34 +00003663 if (status == MagickFalse)
3664 break;
3665 }
3666 }
3667 DestroyCubeInfo(cube_info);
3668 return(status);
3669}
3670
3671/*
3672%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3673% %
3674% %
3675% %
3676% S e t G r a y s c a l e I m a g e %
3677% %
3678% %
3679% %
3680%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3681%
3682% SetGrayscaleImage() converts an image to a PseudoClass grayscale image.
3683%
3684% The format of the SetGrayscaleImage method is:
3685%
cristy09e81242015-01-31 17:00:00 +00003686% MagickBooleanType SetGrayscaleImage(Image *image,
3687% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003688%
3689% A description of each parameter follows:
3690%
3691% o image: The image.
3692%
cristy018f07f2011-09-04 21:15:19 +00003693% o exception: return any errors or warnings in this structure.
3694%
cristy3ed852e2009-09-05 21:47:34 +00003695*/
3696
3697#if defined(__cplusplus) || defined(c_plusplus)
3698extern "C" {
3699#endif
3700
3701static int IntensityCompare(const void *x,const void *y)
3702{
Cristyfb2d8572019-10-28 14:28:34 -04003703 double
3704 intensity;
3705
cristy101ab702011-10-13 13:06:32 +00003706 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003707 *color_1,
3708 *color_2;
3709
cristy101ab702011-10-13 13:06:32 +00003710 color_1=(PixelInfo *) x;
3711 color_2=(PixelInfo *) y;
Cristyfb2d8572019-10-28 14:28:34 -04003712 intensity=GetPixelInfoIntensity((const Image *) NULL,color_1)-
3713 GetPixelInfoIntensity((const Image *) NULL,color_2);
3714 if (intensity < (double) INT_MIN)
3715 intensity=(double) INT_MIN;
3716 if (intensity > (double) INT_MAX)
3717 intensity=(double) INT_MAX;
cristycee97112010-05-28 00:44:52 +00003718 return((int) intensity);
cristy3ed852e2009-09-05 21:47:34 +00003719}
3720
3721#if defined(__cplusplus) || defined(c_plusplus)
3722}
3723#endif
3724
cristy018f07f2011-09-04 21:15:19 +00003725static MagickBooleanType SetGrayscaleImage(Image *image,
3726 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003727{
cristyc4c8d132010-01-07 01:58:38 +00003728 CacheView
3729 *image_view;
3730
cristyecc31b12011-02-13 00:32:29 +00003731 MagickBooleanType
3732 status;
cristy3ed852e2009-09-05 21:47:34 +00003733
cristy101ab702011-10-13 13:06:32 +00003734 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003735 *colormap;
3736
Cristyf2dc1dd2020-12-28 13:59:26 -05003737 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003738 i;
3739
Cristy7dbf5b22019-05-11 08:26:39 -04003740 size_t
3741 extent;
3742
cristyecc31b12011-02-13 00:32:29 +00003743 ssize_t
3744 *colormap_index,
3745 j,
3746 y;
cristy3ed852e2009-09-05 21:47:34 +00003747
cristy3ed852e2009-09-05 21:47:34 +00003748 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003749 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003750 if (image->type != GrayscaleType)
Cristybeb4c2b2017-12-26 19:43:17 -05003751 (void) TransformImageColorspace(image,GRAYColorspace,exception);
Cristy7dbf5b22019-05-11 08:26:39 -04003752 extent=MagickMax(image->colors+1,MagickMax(MaxColormapSize,MaxMap+1));
3753 colormap_index=(ssize_t *) AcquireQuantumMemory(extent,
3754 sizeof(*colormap_index));
cristybb503372010-05-27 20:51:26 +00003755 if (colormap_index == (ssize_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003756 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3757 image->filename);
3758 if (image->storage_class != PseudoClass)
3759 {
Cristy7dbf5b22019-05-11 08:26:39 -04003760 (void) memset(colormap_index,(-1),extent*sizeof(*colormap_index));
dirkd5152402015-07-04 10:05:10 +00003761 if (AcquireImageColormap(image,MaxColormapSize,exception) == MagickFalse)
Cristy0417cea2017-07-17 13:47:41 -04003762 {
3763 colormap_index=(ssize_t *) RelinquishMagickMemory(colormap_index);
3764 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3765 image->filename);
3766 }
cristy3ed852e2009-09-05 21:47:34 +00003767 image->colors=0;
3768 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +00003769 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003770#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04003771 #pragma omp parallel for schedule(static) shared(status) \
Cristy8255ca92017-10-09 08:37:35 -04003772 magick_number_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003773#endif
cristybb503372010-05-27 20:51:26 +00003774 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003775 {
Cristyf2dc1dd2020-12-28 13:59:26 -05003776 Quantum
dirk05d2ff72015-11-18 23:13:43 +01003777 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003778
Cristyf2dc1dd2020-12-28 13:59:26 -05003779 ssize_t
cristyecc31b12011-02-13 00:32:29 +00003780 x;
3781
cristy3ed852e2009-09-05 21:47:34 +00003782 if (status == MagickFalse)
3783 continue;
3784 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3785 exception);
cristyacd2ed22011-08-30 01:44:23 +00003786 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003787 {
3788 status=MagickFalse;
3789 continue;
3790 }
cristybb503372010-05-27 20:51:26 +00003791 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003792 {
Cristyf2dc1dd2020-12-28 13:59:26 -05003793 size_t
cristy3ed852e2009-09-05 21:47:34 +00003794 intensity;
3795
cristy4c08aed2011-07-01 19:47:50 +00003796 intensity=ScaleQuantumToMap(GetPixelRed(image,q));
cristy3ed852e2009-09-05 21:47:34 +00003797 if (colormap_index[intensity] < 0)
3798 {
cristyb5d5f722009-11-04 03:03:49 +00003799#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003800 #pragma omp critical (MagickCore_SetGrayscaleImage)
cristy3ed852e2009-09-05 21:47:34 +00003801#endif
ImageMagick3a177d82018-10-07 08:12:29 -04003802 if (colormap_index[intensity] < 0)
3803 {
3804 colormap_index[intensity]=(ssize_t) image->colors;
3805 image->colormap[image->colors].red=(double)
3806 GetPixelRed(image,q);
3807 image->colormap[image->colors].green=(double)
3808 GetPixelGreen(image,q);
3809 image->colormap[image->colors].blue=(double)
3810 GetPixelBlue(image,q);
3811 image->colors++;
3812 }
cristy3ed852e2009-09-05 21:47:34 +00003813 }
cristyaeded782012-09-11 23:39:36 +00003814 SetPixelIndex(image,(Quantum) colormap_index[intensity],q);
cristyed231572011-07-14 02:18:59 +00003815 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003816 }
3817 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3818 status=MagickFalse;
3819 }
3820 image_view=DestroyCacheView(image_view);
3821 }
Cristy7dbf5b22019-05-11 08:26:39 -04003822 (void) memset(colormap_index,0,extent*sizeof(*colormap_index));
cristybb503372010-05-27 20:51:26 +00003823 for (i=0; i < (ssize_t) image->colors; i++)
cristye42f6582012-02-11 17:59:50 +00003824 image->colormap[i].alpha=(double) i;
cristy101ab702011-10-13 13:06:32 +00003825 qsort((void *) image->colormap,image->colors,sizeof(PixelInfo),
cristy3ed852e2009-09-05 21:47:34 +00003826 IntensityCompare);
cristyaaaff842013-06-30 02:48:11 +00003827 colormap=(PixelInfo *) AcquireQuantumMemory(image->colors,sizeof(*colormap));
cristy101ab702011-10-13 13:06:32 +00003828 if (colormap == (PixelInfo *) NULL)
Dirk Lemstra7b604a52017-07-17 23:13:50 +02003829 {
3830 colormap_index=(ssize_t *) RelinquishMagickMemory(colormap_index);
3831 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3832 image->filename);
3833 }
cristy3ed852e2009-09-05 21:47:34 +00003834 j=0;
3835 colormap[j]=image->colormap[0];
cristybb503372010-05-27 20:51:26 +00003836 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003837 {
cristy101ab702011-10-13 13:06:32 +00003838 if (IsPixelInfoEquivalent(&colormap[j],&image->colormap[i]) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003839 {
3840 j++;
3841 colormap[j]=image->colormap[i];
3842 }
cristy4c08aed2011-07-01 19:47:50 +00003843 colormap_index[(ssize_t) image->colormap[i].alpha]=j;
cristy3ed852e2009-09-05 21:47:34 +00003844 }
cristybb503372010-05-27 20:51:26 +00003845 image->colors=(size_t) (j+1);
cristy101ab702011-10-13 13:06:32 +00003846 image->colormap=(PixelInfo *) RelinquishMagickMemory(image->colormap);
cristy3ed852e2009-09-05 21:47:34 +00003847 image->colormap=colormap;
3848 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +00003849 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003850#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04003851 #pragma omp parallel for schedule(static) shared(status) \
Cristy8255ca92017-10-09 08:37:35 -04003852 magick_number_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003853#endif
cristybb503372010-05-27 20:51:26 +00003854 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003855 {
Cristyf2dc1dd2020-12-28 13:59:26 -05003856 Quantum
dirk05d2ff72015-11-18 23:13:43 +01003857 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003858
Cristyf2dc1dd2020-12-28 13:59:26 -05003859 ssize_t
cristyecc31b12011-02-13 00:32:29 +00003860 x;
3861
cristy3ed852e2009-09-05 21:47:34 +00003862 if (status == MagickFalse)
3863 continue;
3864 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003865 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003866 {
3867 status=MagickFalse;
3868 continue;
3869 }
cristybb503372010-05-27 20:51:26 +00003870 for (x=0; x < (ssize_t) image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +00003871 {
3872 SetPixelIndex(image,(Quantum) colormap_index[ScaleQuantumToMap(
3873 GetPixelIndex(image,q))],q);
cristyed231572011-07-14 02:18:59 +00003874 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00003875 }
cristy3ed852e2009-09-05 21:47:34 +00003876 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3877 status=MagickFalse;
3878 }
3879 image_view=DestroyCacheView(image_view);
cristybb503372010-05-27 20:51:26 +00003880 colormap_index=(ssize_t *) RelinquishMagickMemory(colormap_index);
cristy3ed852e2009-09-05 21:47:34 +00003881 image->type=GrayscaleType;
dirkf1d85482015-04-06 00:36:00 +00003882 if (SetImageMonochrome(image,exception) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003883 image->type=BilevelType;
3884 return(status);
3885}
Dirk Lemstra25229f22020-04-27 15:01:47 +02003886
3887/*
3888%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3889% %
3890% %
3891% %
3892+ S e t I m a g e C o l o r m a p %
3893% %
3894% %
3895% %
3896%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3897%
3898% SetImageColormap() traverses the color cube tree and sets the colormap of
3899% the image. A colormap entry is any node in the color cube tree where the
3900% of unique colors is not zero.
3901%
3902% The format of the SetImageColormap method is:
3903%
3904% MagickBooleanType SetImageColormap(Image *image,CubeInfo *cube_info,
3905% ExceptionInfo *node_info)
3906%
3907% A description of each parameter follows.
3908%
3909% o image: the image.
3910%
3911% o cube_info: A pointer to the Cube structure.
3912%
3913% o exception: return any errors or warnings in this structure.
3914%
3915*/
3916
3917MagickBooleanType SetImageColormap(Image *image,CubeInfo *cube_info,
3918 ExceptionInfo *exception)
3919{
3920 size_t
3921 number_colors;
3922
3923 number_colors=MagickMax(cube_info->maximum_colors,cube_info->colors);
3924 if (AcquireImageColormap(image,number_colors,exception) == MagickFalse)
3925 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3926 image->filename);
3927 image->colors=0;
3928 DefineImageColormap(image,cube_info,cube_info->root);
3929 if (image->colors != number_colors)
3930 {
3931 image->colormap=(PixelInfo *) ResizeQuantumMemory(image->colormap,
3932 image->colors+1,sizeof(*image->colormap));
3933 if (image->colormap == (PixelInfo *) NULL)
3934 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3935 image->filename);
3936 }
3937 return(MagickTrue);
3938}