1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % H H IIIII SSSSS TTTTT OOO GGGG RRRR AAA M M %
7 % H H I SS T O O G R R A A MM MM %
8 % HHHHH I SSS T O O G GG RRRR AAAAA M M M %
9 % H H I SS T O O G G R R A A M M %
10 % H H IIIII SSSSS T OOO GGG R R A A M M %
11 % %
12 % %
13 % MagickCore Histogram Methods %
14 % %
15 % Software Design %
16 % Anthony Thyssen %
17 % Fred Weinhaus %
18 % August 2009 %
19 % %
20 % %
21 % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
22 % dedicated to making software imaging solutions freely available. %
23 % %
24 % You may not use this file except in compliance with the License. You may %
25 % obtain a copy of the License at %
26 % %
27 % https://imagemagick.org/script/license.php %
28 % %
29 % Unless required by applicable law or agreed to in writing, software %
30 % distributed under the License is distributed on an "AS IS" BASIS, %
31 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
32 % See the License for the specific language governing permissions and %
33 % limitations under the License. %
34 % %
35 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
36 %
37 %
38 */
39
40 /*
41 Include declarations.
42 */
43 #include "magick/studio.h"
44 #include "magick/cache-view.h"
45 #include "magick/color-private.h"
46 #include "magick/enhance.h"
47 #include "magick/exception.h"
48 #include "magick/exception-private.h"
49 #include "magick/hashmap.h"
50 #include "magick/histogram.h"
51 #include "magick/image.h"
52 #include "magick/list.h"
53 #include "magick/memory_.h"
54 #include "magick/monitor-private.h"
55 #include "magick/pixel-private.h"
56 #include "magick/prepress.h"
57 #include "magick/quantize.h"
58 #include "magick/registry.h"
59 #include "magick/semaphore.h"
60 #include "magick/splay-tree.h"
61 #include "magick/statistic.h"
62 #include "magick/string_.h"
63
64 /*
65 Define declarations.
66 */
67 #define MaxTreeDepth 8
68 #define NodesInAList 1536
69
70 /*
71 Typedef declarations.
72 */
73 typedef struct _NodeInfo
74 {
75 struct _NodeInfo
76 *child[16];
77
78 ColorPacket
79 *list;
80
81 size_t
82 extent;
83
84 MagickSizeType
85 number_unique;
86
87 size_t
88 level;
89 } NodeInfo;
90
91 typedef struct _Nodes
92 {
93 NodeInfo
94 nodes[NodesInAList];
95
96 struct _Nodes
97 *next;
98 } Nodes;
99
100 typedef struct _CubeInfo
101 {
102 NodeInfo
103 *root;
104
105 ssize_t
106 x;
107
108 MagickOffsetType
109 progress;
110
111 size_t
112 colors,
113 free_nodes;
114
115 NodeInfo
116 *node_info;
117
118 Nodes
119 *node_queue;
120 } CubeInfo;
121
122 /*
123 Forward declarations.
124 */
125 static CubeInfo
126 *GetCubeInfo(void);
127
128 static NodeInfo
129 *GetNodeInfo(CubeInfo *,const size_t);
130
131 static void
132 DestroyColorCube(const Image *,NodeInfo *);
133
134 /*
135 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
136 % %
137 % %
138 % %
139 + C l a s s i f y I m a g e C o l o r s %
140 % %
141 % %
142 % %
143 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
144 %
145 % ClassifyImageColors() builds a populated CubeInfo tree for the specified
146 % image. The returned tree should be deallocated using DestroyCubeInfo()
147 % once it is no longer needed.
148 %
149 % The format of the ClassifyImageColors() method is:
150 %
151 % CubeInfo *ClassifyImageColors(const Image *image,
152 % ExceptionInfo *exception)
153 %
154 % A description of each parameter follows.
155 %
156 % o image: the image.
157 %
158 % o exception: return any errors or warnings in this structure.
159 %
160 */
161
ColorToNodeId(const Image * image,const MagickPixelPacket * pixel,size_t index)162 static inline size_t ColorToNodeId(const Image *image,
163 const MagickPixelPacket *pixel,size_t index)
164 {
165 size_t
166 id;
167
168 id=(size_t) (
169 ((ScaleQuantumToChar(ClampToQuantum(pixel->red)) >> index) & 0x01) |
170 ((ScaleQuantumToChar(ClampToQuantum(pixel->green)) >> index) & 0x01) << 1 |
171 ((ScaleQuantumToChar(ClampToQuantum(pixel->blue)) >> index) & 0x01) << 2);
172 if (image->matte != MagickFalse)
173 id|=((ScaleQuantumToChar(ClampToQuantum(pixel->opacity)) >> index) &
174 0x01) << 3;
175 return(id);
176 }
177
IsMagickColorMatch(const MagickPixelPacket * p,const MagickPixelPacket * q)178 static inline MagickBooleanType IsMagickColorMatch(const MagickPixelPacket *p,
179 const MagickPixelPacket *q)
180 {
181 MagickRealType
182 alpha,
183 beta;
184
185 alpha=p->matte == MagickFalse ? OpaqueOpacity : p->opacity;
186 beta=q->matte == MagickFalse ? OpaqueOpacity : q->opacity;
187 if (AbsolutePixelValue(alpha-beta) >= MagickEpsilon)
188 return(MagickFalse);
189 if ((AbsolutePixelValue(alpha-TransparentOpacity) < MagickEpsilon) ||
190 (AbsolutePixelValue(beta-TransparentOpacity) < MagickEpsilon))
191 return(MagickTrue); /* no color component if pixel is transparent */
192 if (AbsolutePixelValue(p->red-q->red) >= MagickEpsilon)
193 return(MagickFalse);
194 if (AbsolutePixelValue(p->green-q->green) >= MagickEpsilon)
195 return(MagickFalse);
196 if (AbsolutePixelValue(p->blue-q->blue) >= MagickEpsilon)
197 return(MagickFalse);
198 if (p->colorspace == CMYKColorspace)
199 {
200 if (AbsolutePixelValue(p->index-q->index) >= MagickEpsilon)
201 return(MagickFalse);
202 }
203 return(MagickTrue);
204 }
205
206
ClassifyImageColors(const Image * image,ExceptionInfo * exception)207 static CubeInfo *ClassifyImageColors(const Image *image,
208 ExceptionInfo *exception)
209 {
210 #define EvaluateImageTag " Compute image colors... "
211
212 CacheView
213 *image_view;
214
215 CubeInfo
216 *cube_info;
217
218 MagickBooleanType
219 proceed;
220
221 MagickPixelPacket
222 pixel,
223 target;
224
225 NodeInfo
226 *node_info;
227
228 const IndexPacket
229 *indexes;
230
231 const PixelPacket
232 *p;
233
234 size_t
235 id,
236 index,
237 level;
238
239 ssize_t
240 i,
241 x;
242
243 ssize_t
244 y;
245
246 /*
247 Initialize color description tree.
248 */
249 assert(image != (const Image *) NULL);
250 assert(image->signature == MagickCoreSignature);
251 if (image->debug != MagickFalse)
252 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
253 cube_info=GetCubeInfo();
254 if (cube_info == (CubeInfo *) NULL)
255 {
256 (void) ThrowMagickException(exception,GetMagickModule(),
257 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
258 return(cube_info);
259 }
260 GetMagickPixelPacket(image,&pixel);
261 GetMagickPixelPacket(image,&target);
262 image_view=AcquireVirtualCacheView(image,exception);
263 for (y=0; y < (ssize_t) image->rows; y++)
264 {
265 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
266 if (p == (const PixelPacket *) NULL)
267 break;
268 indexes=GetCacheViewVirtualIndexQueue(image_view);
269 for (x=0; x < (ssize_t) image->columns; x++)
270 {
271 /*
272 Start at the root and proceed level by level.
273 */
274 node_info=cube_info->root;
275 index=MaxTreeDepth-1;
276 for (level=1; level < MaxTreeDepth; level++)
277 {
278 SetMagickPixelPacket(image,p,indexes+x,&pixel);
279 id=ColorToNodeId(image,&pixel,index);
280 if (node_info->child[id] == (NodeInfo *) NULL)
281 {
282 node_info->child[id]=GetNodeInfo(cube_info,level);
283 if (node_info->child[id] == (NodeInfo *) NULL)
284 {
285 (void) ThrowMagickException(exception,GetMagickModule(),
286 ResourceLimitError,"MemoryAllocationFailed","`%s'",
287 image->filename);
288 return(0);
289 }
290 }
291 node_info=node_info->child[id];
292 index--;
293 }
294 for (i=0; i < (ssize_t) node_info->number_unique; i++)
295 {
296 SetMagickPixelPacket(image,&node_info->list[i].pixel,
297 &node_info->list[i].index,&target);
298 if (IsMagickColorMatch(&pixel,&target) != MagickFalse)
299 break;
300 }
301 if (i < (ssize_t) node_info->number_unique)
302 node_info->list[i].count++;
303 else
304 {
305 if (node_info->number_unique == 0)
306 {
307 node_info->extent=1;
308 node_info->list=(ColorPacket *) AcquireQuantumMemory(
309 node_info->extent,sizeof(*node_info->list));
310 }
311 else
312 if (i >= (ssize_t) node_info->extent)
313 {
314 node_info->extent<<=1;
315 node_info->list=(ColorPacket *) ResizeQuantumMemory(
316 node_info->list,node_info->extent,sizeof(*node_info->list));
317 }
318 if (node_info->list == (ColorPacket *) NULL)
319 {
320 (void) ThrowMagickException(exception,GetMagickModule(),
321 ResourceLimitError,"MemoryAllocationFailed","`%s'",
322 image->filename);
323 return(0);
324 }
325 node_info->list[i].pixel=(*p);
326 if ((image->colorspace == CMYKColorspace) ||
327 (image->storage_class == PseudoClass))
328 node_info->list[i].index=GetPixelIndex(indexes+x);
329 node_info->list[i].count=1;
330 node_info->number_unique++;
331 cube_info->colors++;
332 }
333 p++;
334 }
335 proceed=SetImageProgress(image,EvaluateImageTag,(MagickOffsetType) y,
336 image->rows);
337 if (proceed == MagickFalse)
338 break;
339 }
340 image_view=DestroyCacheView(image_view);
341 return(cube_info);
342 }
343
344 /*
345 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
346 % %
347 % %
348 % %
349 + D e f i n e I m a g e H i s t o g r a m %
350 % %
351 % %
352 % %
353 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
354 %
355 % DefineImageHistogram() traverses the color cube tree and notes each colormap
356 % entry. A colormap entry is any node in the color cube tree where the
357 % of unique colors is not zero.
358 %
359 % The format of the DefineImageHistogram method is:
360 %
361 % DefineImageHistogram(const Image *image,NodeInfo *node_info,
362 % ColorPacket **unique_colors)
363 %
364 % A description of each parameter follows.
365 %
366 % o image: the image.
367 %
368 % o node_info: the address of a structure of type NodeInfo which points to a
369 % node in the color cube tree that is to be pruned.
370 %
371 % o histogram: the image histogram.
372 %
373 */
DefineImageHistogram(const Image * image,NodeInfo * node_info,ColorPacket ** histogram)374 static void DefineImageHistogram(const Image *image,NodeInfo *node_info,
375 ColorPacket **histogram)
376 {
377 ssize_t
378 i;
379
380 size_t
381 number_children;
382
383 /*
384 Traverse any children.
385 */
386 number_children=image->matte == MagickFalse ? 8UL : 16UL;
387 for (i=0; i < (ssize_t) number_children; i++)
388 if (node_info->child[i] != (NodeInfo *) NULL)
389 DefineImageHistogram(image,node_info->child[i],histogram);
390 if (node_info->level == (MaxTreeDepth-1))
391 {
392 ColorPacket
393 *p;
394
395 p=node_info->list;
396 for (i=0; i < (ssize_t) node_info->number_unique; i++)
397 {
398 (*histogram)->pixel=p->pixel;
399 (*histogram)->index=p->index;
400 (*histogram)->count=p->count;
401 (*histogram)++;
402 p++;
403 }
404 }
405 }
406
407 /*
408 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
409 % %
410 % %
411 % %
412 + D e s t r o y C u b e I n f o %
413 % %
414 % %
415 % %
416 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
417 %
418 % DestroyCubeInfo() deallocates memory associated with a CubeInfo structure.
419 %
420 % The format of the DestroyCubeInfo method is:
421 %
422 % DestroyCubeInfo(const Image *image,CubeInfo *cube_info)
423 %
424 % A description of each parameter follows:
425 %
426 % o image: the image.
427 %
428 % o cube_info: the address of a structure of type CubeInfo.
429 %
430 */
DestroyCubeInfo(const Image * image,CubeInfo * cube_info)431 static CubeInfo *DestroyCubeInfo(const Image *image,CubeInfo *cube_info)
432 {
433 Nodes
434 *nodes;
435
436 /*
437 Release color cube tree storage.
438 */
439 DestroyColorCube(image,cube_info->root);
440 do
441 {
442 nodes=cube_info->node_queue->next;
443 cube_info->node_queue=(Nodes *)
444 RelinquishMagickMemory(cube_info->node_queue);
445 cube_info->node_queue=nodes;
446 } while (cube_info->node_queue != (Nodes *) NULL);
447 return((CubeInfo *) RelinquishMagickMemory(cube_info));
448 }
449
450 /*
451 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
452 % %
453 % %
454 % %
455 + D e s t r o y C o l o r C u b e %
456 % %
457 % %
458 % %
459 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
460 %
461 % DestroyColorCube() traverses the color cube tree and frees the list of
462 % unique colors.
463 %
464 % The format of the DestroyColorCube method is:
465 %
466 % void DestroyColorCube(const Image *image,const NodeInfo *node_info)
467 %
468 % A description of each parameter follows.
469 %
470 % o image: the image.
471 %
472 % o node_info: the address of a structure of type NodeInfo which points to a
473 % node in the color cube tree that is to be pruned.
474 %
475 */
DestroyColorCube(const Image * image,NodeInfo * node_info)476 static void DestroyColorCube(const Image *image,NodeInfo *node_info)
477 {
478 ssize_t
479 i;
480
481 size_t
482 number_children;
483
484 /*
485 Traverse any children.
486 */
487 number_children=image->matte == MagickFalse ? 8UL : 16UL;
488 for (i=0; i < (ssize_t) number_children; i++)
489 if (node_info->child[i] != (NodeInfo *) NULL)
490 DestroyColorCube(image,node_info->child[i]);
491 if (node_info->list != (ColorPacket *) NULL)
492 node_info->list=(ColorPacket *) RelinquishMagickMemory(node_info->list);
493 }
494
495 /*
496 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
497 % %
498 % %
499 % %
500 + G e t C u b e I n f o %
501 % %
502 % %
503 % %
504 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
505 %
506 % GetCubeInfo() initializes the CubeInfo data structure.
507 %
508 % The format of the GetCubeInfo method is:
509 %
510 % cube_info=GetCubeInfo()
511 %
512 % A description of each parameter follows.
513 %
514 % o cube_info: A pointer to the Cube structure.
515 %
516 */
GetCubeInfo(void)517 static CubeInfo *GetCubeInfo(void)
518 {
519 CubeInfo
520 *cube_info;
521
522 /*
523 Initialize tree to describe color cube.
524 */
525 cube_info=(CubeInfo *) AcquireMagickMemory(sizeof(*cube_info));
526 if (cube_info == (CubeInfo *) NULL)
527 return((CubeInfo *) NULL);
528 (void) memset(cube_info,0,sizeof(*cube_info));
529 /*
530 Initialize root node.
531 */
532 cube_info->root=GetNodeInfo(cube_info,0);
533 if (cube_info->root == (NodeInfo *) NULL)
534 return((CubeInfo *) NULL);
535 return(cube_info);
536 }
537
538 /*
539 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
540 % %
541 % %
542 % %
543 % G e t I m a g e H i s t o g r a m %
544 % %
545 % %
546 % %
547 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
548 %
549 % GetImageHistogram() returns the unique colors in an image.
550 %
551 % The format of the GetImageHistogram method is:
552 %
553 % size_t GetImageHistogram(const Image *image,
554 % size_t *number_colors,ExceptionInfo *exception)
555 %
556 % A description of each parameter follows.
557 %
558 % o image: the image.
559 %
560 % o file: Write a histogram of the color distribution to this file handle.
561 %
562 % o exception: return any errors or warnings in this structure.
563 %
564 */
GetImageHistogram(const Image * image,size_t * number_colors,ExceptionInfo * exception)565 MagickExport ColorPacket *GetImageHistogram(const Image *image,
566 size_t *number_colors,ExceptionInfo *exception)
567 {
568 ColorPacket
569 *histogram;
570
571 CubeInfo
572 *cube_info;
573
574 *number_colors=0;
575 histogram=(ColorPacket *) NULL;
576 cube_info=ClassifyImageColors(image,exception);
577 if (cube_info != (CubeInfo *) NULL)
578 {
579 histogram=(ColorPacket *) AcquireQuantumMemory((size_t)
580 cube_info->colors+1,sizeof(*histogram));
581 if (histogram == (ColorPacket *) NULL)
582 (void) ThrowMagickException(exception,GetMagickModule(),
583 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
584 else
585 {
586 ColorPacket
587 *root;
588
589 *number_colors=cube_info->colors;
590 root=histogram;
591 DefineImageHistogram(image,cube_info->root,&root);
592 }
593 }
594 cube_info=DestroyCubeInfo(image,cube_info);
595 return(histogram);
596 }
597
598 /*
599 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
600 % %
601 % %
602 % %
603 + G e t N o d e I n f o %
604 % %
605 % %
606 % %
607 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
608 %
609 % GetNodeInfo() allocates memory for a new node in the color cube tree and
610 % presets all fields to zero.
611 %
612 % The format of the GetNodeInfo method is:
613 %
614 % NodeInfo *GetNodeInfo(CubeInfo *cube_info,const size_t level)
615 %
616 % A description of each parameter follows.
617 %
618 % o cube_info: A pointer to the CubeInfo structure.
619 %
620 % o level: Specifies the level in the storage_class the node resides.
621 %
622 */
GetNodeInfo(CubeInfo * cube_info,const size_t level)623 static NodeInfo *GetNodeInfo(CubeInfo *cube_info,const size_t level)
624 {
625 NodeInfo
626 *node_info;
627
628 if (cube_info->free_nodes == 0)
629 {
630 Nodes
631 *nodes;
632
633 /*
634 Allocate a new nodes of nodes.
635 */
636 nodes=(Nodes *) AcquireMagickMemory(sizeof(*nodes));
637 if (nodes == (Nodes *) NULL)
638 return((NodeInfo *) NULL);
639 nodes->next=cube_info->node_queue;
640 cube_info->node_queue=nodes;
641 cube_info->node_info=nodes->nodes;
642 cube_info->free_nodes=NodesInAList;
643 }
644 cube_info->free_nodes--;
645 node_info=cube_info->node_info++;
646 (void) memset(node_info,0,sizeof(*node_info));
647 node_info->level=level;
648 return(node_info);
649 }
650
651 /*
652 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
653 % %
654 % %
655 % %
656 % I d e n t i f y P a l e t t e I m a g e %
657 % %
658 % %
659 % %
660 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
661 %
662 % IdentifyPaletteImage() returns MagickTrue if the image has 256 unique colors
663 % or less.
664 %
665 % The format of the IdentifyPaletteImage method is:
666 %
667 % MagickBooleanType IdentifyPaletteImage(const Image *image,
668 % ExceptionInfo *exception)
669 %
670 % A description of each parameter follows.
671 %
672 % o image: the image.
673 %
674 % o exception: return any errors or warnings in this structure.
675 %
676 */
677
CheckImageColors(const Image * image,ExceptionInfo * exception,size_t max_colors)678 static MagickBooleanType CheckImageColors(const Image *image,
679 ExceptionInfo *exception,size_t max_colors)
680 {
681 CacheView
682 *image_view;
683
684 CubeInfo
685 *cube_info;
686
687 MagickPixelPacket
688 pixel,
689 target;
690
691 const IndexPacket
692 *indexes;
693
694 const PixelPacket
695 *p;
696
697 ssize_t
698 x;
699
700 NodeInfo
701 *node_info;
702
703 ssize_t
704 i;
705
706 size_t
707 id,
708 index,
709 level;
710
711 ssize_t
712 y;
713
714 if (image->storage_class == PseudoClass)
715 return((image->colors <= max_colors) ? MagickTrue : MagickFalse);
716 /*
717 Initialize color description tree.
718 */
719 cube_info=GetCubeInfo();
720 if (cube_info == (CubeInfo *) NULL)
721 {
722 (void) ThrowMagickException(exception,GetMagickModule(),
723 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
724 return(MagickFalse);
725 }
726 GetMagickPixelPacket(image,&pixel);
727 GetMagickPixelPacket(image,&target);
728 image_view=AcquireVirtualCacheView(image,exception);
729 for (y=0; y < (ssize_t) image->rows; y++)
730 {
731 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
732 if (p == (const PixelPacket *) NULL)
733 break;
734 indexes=GetCacheViewVirtualIndexQueue(image_view);
735 if (indexes == (const IndexPacket *) NULL)
736 break;
737 for (x=0; x < (ssize_t) image->columns; x++)
738 {
739 /*
740 Start at the root and proceed level by level.
741 */
742 node_info=cube_info->root;
743 index=MaxTreeDepth-1;
744 for (level=1; level < MaxTreeDepth; level++)
745 {
746 SetMagickPixelPacket(image,p,indexes+x,&pixel);
747 id=ColorToNodeId(image,&pixel,index);
748 if (node_info->child[id] == (NodeInfo *) NULL)
749 {
750 node_info->child[id]=GetNodeInfo(cube_info,level);
751 if (node_info->child[id] == (NodeInfo *) NULL)
752 {
753 (void) ThrowMagickException(exception,GetMagickModule(),
754 ResourceLimitError,"MemoryAllocationFailed","`%s'",
755 image->filename);
756 break;
757 }
758 }
759 node_info=node_info->child[id];
760 index--;
761 }
762 if (level < MaxTreeDepth)
763 break;
764 for (i=0; i < (ssize_t) node_info->number_unique; i++)
765 {
766 SetMagickPixelPacket(image,&node_info->list[i].pixel,
767 &node_info->list[i].index,&target);
768 if (IsMagickColorMatch(&pixel,&target) != MagickFalse)
769 break;
770 }
771 if (i < (ssize_t) node_info->number_unique)
772 node_info->list[i].count++;
773 else
774 {
775 /*
776 Add this unique color to the color list.
777 */
778 if (node_info->list == (ColorPacket *) NULL)
779 node_info->list=(ColorPacket *) AcquireQuantumMemory(1,
780 sizeof(*node_info->list));
781 else
782 node_info->list=(ColorPacket *) ResizeQuantumMemory(node_info->list,
783 (size_t) (i+1),sizeof(*node_info->list));
784 if (node_info->list == (ColorPacket *) NULL)
785 {
786 (void) ThrowMagickException(exception,GetMagickModule(),
787 ResourceLimitError,"MemoryAllocationFailed","`%s'",
788 image->filename);
789 break;
790 }
791 node_info->list[i].pixel=(*p);
792 if ((image->colorspace == CMYKColorspace) ||
793 (image->storage_class == PseudoClass))
794 node_info->list[i].index=GetPixelIndex(indexes+x);
795 node_info->list[i].count=1;
796 node_info->number_unique++;
797 cube_info->colors++;
798 if (cube_info->colors > max_colors)
799 break;
800 }
801 p++;
802 }
803 if (x < (ssize_t) image->columns)
804 break;
805 }
806 image_view=DestroyCacheView(image_view);
807 cube_info=DestroyCubeInfo(image,cube_info);
808 return(y < (ssize_t) image->rows ? MagickFalse : MagickTrue);
809 }
810
IdentifyPaletteImage(const Image * image,ExceptionInfo * exception)811 MagickExport MagickBooleanType IdentifyPaletteImage(const Image *image,
812 ExceptionInfo *exception)
813 {
814 assert(image != (Image *) NULL);
815 assert(image->signature == MagickCoreSignature);
816 if (image->debug != MagickFalse)
817 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
818 return(CheckImageColors(image,exception,256));
819 }
820
821 /*
822 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
823 % %
824 % %
825 % %
826 % I s H i s t o g r a m I m a g e %
827 % %
828 % %
829 % %
830 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
831 %
832 % IsHistogramImage() returns MagickTrue if the image has 1024 unique colors or
833 % less.
834 %
835 % The format of the IsHistogramImage method is:
836 %
837 % MagickBooleanType IsHistogramImage(const Image *image,
838 % ExceptionInfo *exception)
839 %
840 % A description of each parameter follows.
841 %
842 % o image: the image.
843 %
844 % o exception: return any errors or warnings in this structure.
845 %
846 */
IsHistogramImage(const Image * image,ExceptionInfo * exception)847 MagickExport MagickBooleanType IsHistogramImage(const Image *image,
848 ExceptionInfo *exception)
849 {
850 #define MaximumUniqueColors 1024
851
852 CacheView
853 *image_view;
854
855 CubeInfo
856 *cube_info;
857
858 MagickPixelPacket
859 pixel,
860 target;
861
862 const IndexPacket
863 *indexes;
864
865 const PixelPacket
866 *p;
867
868 ssize_t
869 x;
870
871 NodeInfo
872 *node_info;
873
874 ssize_t
875 i;
876
877 size_t
878 id,
879 index,
880 level;
881
882 ssize_t
883 y;
884
885 assert(image != (Image *) NULL);
886 assert(image->signature == MagickCoreSignature);
887 if (image->debug != MagickFalse)
888 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
889 if ((image->storage_class == PseudoClass) &&
890 (image->colors <= MaximumUniqueColors))
891 return(MagickTrue);
892 if (image->storage_class == PseudoClass)
893 return(MagickFalse);
894 /*
895 Initialize color description tree.
896 */
897 cube_info=GetCubeInfo();
898 if (cube_info == (CubeInfo *) NULL)
899 {
900 (void) ThrowMagickException(exception,GetMagickModule(),
901 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
902 return(MagickFalse);
903 }
904 GetMagickPixelPacket(image,&pixel);
905 GetMagickPixelPacket(image,&target);
906 image_view=AcquireVirtualCacheView(image,exception);
907 for (y=0; y < (ssize_t) image->rows; y++)
908 {
909 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
910 if (p == (const PixelPacket *) NULL)
911 break;
912 indexes=GetCacheViewVirtualIndexQueue(image_view);
913 for (x=0; x < (ssize_t) image->columns; x++)
914 {
915 /*
916 Start at the root and proceed level by level.
917 */
918 node_info=cube_info->root;
919 index=MaxTreeDepth-1;
920 for (level=1; level < MaxTreeDepth; level++)
921 {
922 SetMagickPixelPacket(image,p,indexes+x,&pixel);
923 id=ColorToNodeId(image,&pixel,index);
924 if (node_info->child[id] == (NodeInfo *) NULL)
925 {
926 node_info->child[id]=GetNodeInfo(cube_info,level);
927 if (node_info->child[id] == (NodeInfo *) NULL)
928 {
929 (void) ThrowMagickException(exception,GetMagickModule(),
930 ResourceLimitError,"MemoryAllocationFailed","`%s'",
931 image->filename);
932 break;
933 }
934 }
935 node_info=node_info->child[id];
936 index--;
937 }
938 if (level < MaxTreeDepth)
939 break;
940 for (i=0; i < (ssize_t) node_info->number_unique; i++)
941 {
942 SetMagickPixelPacket(image,&node_info->list[i].pixel,
943 &node_info->list[i].index,&target);
944 if (IsMagickColorMatch(&pixel,&target) != MagickFalse)
945 break;
946 }
947 if (i < (ssize_t) node_info->number_unique)
948 node_info->list[i].count++;
949 else
950 {
951 /*
952 Add this unique color to the color list.
953 */
954 if (node_info->number_unique == 0)
955 node_info->list=(ColorPacket *) AcquireQuantumMemory(1,
956 sizeof(*node_info->list));
957 else
958 node_info->list=(ColorPacket *) ResizeQuantumMemory(node_info->list,
959 (size_t) (i+1),sizeof(*node_info->list));
960 if (node_info->list == (ColorPacket *) NULL)
961 {
962 (void) ThrowMagickException(exception,GetMagickModule(),
963 ResourceLimitError,"MemoryAllocationFailed","`%s'",
964 image->filename);
965 break;
966 }
967 node_info->list[i].pixel=(*p);
968 if ((image->colorspace == CMYKColorspace) ||
969 (image->storage_class == PseudoClass))
970 node_info->list[i].index=GetPixelIndex(indexes+x);
971 node_info->list[i].count=1;
972 node_info->number_unique++;
973 cube_info->colors++;
974 if (cube_info->colors > MaximumUniqueColors)
975 break;
976 }
977 p++;
978 }
979 if (x < (ssize_t) image->columns)
980 break;
981 }
982 image_view=DestroyCacheView(image_view);
983 cube_info=DestroyCubeInfo(image,cube_info);
984 return(y < (ssize_t) image->rows ? MagickFalse : MagickTrue);
985 }
986
987 /*
988 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
989 % %
990 % %
991 % %
992 % I s P a l e t t e I m a g e %
993 % %
994 % %
995 % %
996 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
997 %
998 % IsPaletteImage() returns MagickTrue if the image is PseudoClass and has 256
999 % unique colors or less.
1000 %
1001 % The format of the IsPaletteImage method is:
1002 %
1003 % MagickBooleanType IsPaletteImage(const Image *image,
1004 % ExceptionInfo *exception)
1005 %
1006 % A description of each parameter follows.
1007 %
1008 % o image: the image.
1009 %
1010 % o exception: return any errors or warnings in this structure.
1011 %
1012 */
IsPaletteImage(const Image * image,ExceptionInfo * exception)1013 MagickExport MagickBooleanType IsPaletteImage(const Image *image,
1014 ExceptionInfo *exception)
1015 {
1016 assert(image != (Image *) NULL);
1017 assert(image->signature == MagickCoreSignature);
1018 magick_unreferenced(exception);
1019 if (image->debug != MagickFalse)
1020 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1021 if (image->storage_class != PseudoClass)
1022 return(MagickFalse);
1023 return((image->colors <= 256) ? MagickTrue : MagickFalse);
1024 }
1025
1026 /*
1027 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1028 % %
1029 % %
1030 % %
1031 % M i n M a x S t r e t c h I m a g e %
1032 % %
1033 % %
1034 % %
1035 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1036 %
1037 % MinMaxStretchImage() uses the exact minimum and maximum values found in
1038 % each of the channels given, as the BlackPoint and WhitePoint to linearly
1039 % stretch the colors (and histogram) of the image. The stretch points are
1040 % also moved further inward by the adjustment values given.
1041 %
1042 % If the adjustment values are both zero this function is equivalent to a
1043 % perfect normalization (or autolevel) of the image.
1044 %
1045 % Each channel is stretched independantally of each other (producing color
1046 % distortion) unless the special 'SyncChannels' flag is also provided in the
1047 % channels setting. If this flag is present the minimum and maximum point
1048 % will be extracted from all the given channels, and those channels will be
1049 % stretched by exactly the same amount (preventing color distortion).
1050 %
1051 % In the special case that only ONE value is found in a channel of the image
1052 % that value is not stretched, that value is left as is.
1053 %
1054 % The 'SyncChannels' is turned on in the 'DefaultChannels' setting by
1055 % default.
1056 %
1057 % The format of the MinMaxStretchImage method is:
1058 %
1059 % MagickBooleanType MinMaxStretchImage(Image *image,
1060 % const ChannelType channel, const double black_adjust,
1061 % const double white_adjust)
1062 %
1063 % A description of each parameter follows:
1064 %
1065 % o image: The image to auto-level
1066 %
1067 % o channel: The channels to auto-level. If the special 'SyncChannels'
1068 % flag is set, all the given channels are stretched by the same amount.
1069 %
1070 % o black_adjust, white_adjust: Move the Black/White Point inward
1071 % from the minimum and maximum points by this color value.
1072 %
1073 */
1074
MinMaxStretchImage(Image * image,const ChannelType channel,const double black_value,const double white_value)1075 MagickExport MagickBooleanType MinMaxStretchImage(Image *image,
1076 const ChannelType channel,const double black_value,const double white_value)
1077 {
1078 double
1079 min,
1080 max;
1081
1082 MagickStatusType
1083 status;
1084
1085 status=MagickTrue;
1086 if ((channel & SyncChannels) != 0)
1087 {
1088 /*
1089 Auto-level all channels equally.
1090 */
1091 (void) GetImageChannelRange(image,channel,&min,&max,&image->exception);
1092 min+=black_value;
1093 max-=white_value;
1094 if (fabs(min-max) >= MagickEpsilon)
1095 status&=LevelImageChannel(image,channel,min,max,1.0);
1096 return(status != 0 ? MagickTrue : MagickFalse);
1097 }
1098 /*
1099 Auto-level each channel separately.
1100 */
1101 if ((channel & RedChannel) != 0)
1102 {
1103 (void) GetImageChannelRange(image,RedChannel,&min,&max,&image->exception);
1104 min+=black_value;
1105 max-=white_value;
1106 if (fabs(min-max) >= MagickEpsilon)
1107 status&=LevelImageChannel(image,RedChannel,min,max,1.0);
1108 }
1109 if ((channel & GreenChannel) != 0)
1110 {
1111 (void) GetImageChannelRange(image,GreenChannel,&min,&max,
1112 &image->exception);
1113 min+=black_value;
1114 max-=white_value;
1115 if (fabs(min-max) >= MagickEpsilon)
1116 status&=LevelImageChannel(image,GreenChannel,min,max,1.0);
1117 }
1118 if ((channel & BlueChannel) != 0)
1119 {
1120 (void) GetImageChannelRange(image,BlueChannel,&min,&max,
1121 &image->exception);
1122 min+=black_value;
1123 max-=white_value;
1124 if (fabs(min-max) >= MagickEpsilon)
1125 status&=LevelImageChannel(image,BlueChannel,min,max,1.0);
1126 }
1127 if (((channel & OpacityChannel) != 0) &&
1128 (image->matte != MagickFalse))
1129 {
1130 (void) GetImageChannelRange(image,OpacityChannel,&min,&max,
1131 &image->exception);
1132 min+=black_value;
1133 max-=white_value;
1134 if (fabs(min-max) >= MagickEpsilon)
1135 status&=LevelImageChannel(image,OpacityChannel,min,max,1.0);
1136 }
1137 if (((channel & IndexChannel) != 0) &&
1138 (image->colorspace == CMYKColorspace))
1139 {
1140 (void) GetImageChannelRange(image,IndexChannel,&min,&max,
1141 &image->exception);
1142 min+=black_value;
1143 max-=white_value;
1144 if (fabs(min-max) >= MagickEpsilon)
1145 status&=LevelImageChannel(image,IndexChannel,min,max,1.0);
1146 }
1147 return(status != 0 ? MagickTrue : MagickFalse);
1148 }
1149
1150 /*
1151 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1152 % %
1153 % %
1154 % %
1155 % G e t N u m b e r C o l o r s %
1156 % %
1157 % %
1158 % %
1159 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1160 %
1161 % GetNumberColors() returns the number of unique colors in an image.
1162 %
1163 % The format of the GetNumberColors method is:
1164 %
1165 % size_t GetNumberColors(const Image *image,FILE *file,
1166 % ExceptionInfo *exception)
1167 %
1168 % A description of each parameter follows.
1169 %
1170 % o image: the image.
1171 %
1172 % o file: Write a histogram of the color distribution to this file handle.
1173 %
1174 % o exception: return any errors or warnings in this structure.
1175 %
1176 */
1177
1178 #if defined(__cplusplus) || defined(c_plusplus)
1179 extern "C" {
1180 #endif
1181
HistogramCompare(const void * x,const void * y)1182 static int HistogramCompare(const void *x,const void *y)
1183 {
1184 const ColorPacket
1185 *color_1,
1186 *color_2;
1187
1188 color_1=(const ColorPacket *) x;
1189 color_2=(const ColorPacket *) y;
1190 if (color_2->pixel.red != color_1->pixel.red)
1191 return((int) ((ssize_t) color_1->pixel.red-(ssize_t) color_2->pixel.red));
1192 if (color_2->pixel.green != color_1->pixel.green)
1193 return((int) ((ssize_t) color_1->pixel.green-(ssize_t) color_2->pixel.green));
1194 if (color_2->pixel.blue != color_1->pixel.blue)
1195 return((int) ((ssize_t) color_1->pixel.blue-(ssize_t) color_2->pixel.blue));
1196 return((int) ((ssize_t) color_2->count-(ssize_t) color_1->count));
1197 }
1198
1199 #if defined(__cplusplus) || defined(c_plusplus)
1200 }
1201 #endif
1202
GetNumberColors(const Image * image,FILE * file,ExceptionInfo * exception)1203 MagickExport size_t GetNumberColors(const Image *image,FILE *file,
1204 ExceptionInfo *exception)
1205 {
1206 #define HistogramImageTag "Histogram/Image"
1207
1208 char
1209 color[MaxTextExtent],
1210 count[MaxTextExtent],
1211 hex[MaxTextExtent],
1212 tuple[MaxTextExtent];
1213
1214 ColorPacket
1215 *histogram;
1216
1217 MagickBooleanType
1218 status;
1219
1220 MagickPixelPacket
1221 pixel;
1222
1223 ColorPacket
1224 *p;
1225
1226 ssize_t
1227 i;
1228
1229 size_t
1230 number_colors;
1231
1232 number_colors=0;
1233 if (file == (FILE *) NULL)
1234 {
1235 CubeInfo
1236 *cube_info;
1237
1238 cube_info=ClassifyImageColors(image,exception);
1239 if (cube_info != (CubeInfo *) NULL)
1240 number_colors=cube_info->colors;
1241 cube_info=DestroyCubeInfo(image,cube_info);
1242 return(number_colors);
1243 }
1244 histogram=GetImageHistogram(image,&number_colors,exception);
1245 if (histogram == (ColorPacket *) NULL)
1246 return(number_colors);
1247 qsort((void *) histogram,(size_t) number_colors,sizeof(*histogram),
1248 HistogramCompare);
1249 GetMagickPixelPacket(image,&pixel);
1250 p=histogram;
1251 status=MagickTrue;
1252 for (i=0; i < (ssize_t) number_colors; i++)
1253 {
1254 SetMagickPixelPacket(image,&p->pixel,&p->index,&pixel);
1255 (void) CopyMagickString(tuple,"(",MaxTextExtent);
1256 ConcatenateColorComponent(&pixel,RedChannel,NoCompliance,tuple);
1257 (void) ConcatenateMagickString(tuple,",",MaxTextExtent);
1258 ConcatenateColorComponent(&pixel,GreenChannel,NoCompliance,tuple);
1259 (void) ConcatenateMagickString(tuple,",",MaxTextExtent);
1260 ConcatenateColorComponent(&pixel,BlueChannel,NoCompliance,tuple);
1261 if (pixel.colorspace == CMYKColorspace)
1262 {
1263 (void) ConcatenateMagickString(tuple,",",MaxTextExtent);
1264 ConcatenateColorComponent(&pixel,IndexChannel,NoCompliance,tuple);
1265 }
1266 if (pixel.matte != MagickFalse)
1267 {
1268 (void) ConcatenateMagickString(tuple,",",MaxTextExtent);
1269 ConcatenateColorComponent(&pixel,OpacityChannel,NoCompliance,tuple);
1270 }
1271 (void) ConcatenateMagickString(tuple,")",MaxTextExtent);
1272 (void) QueryMagickColorname(image,&pixel,SVGCompliance,color,exception);
1273 GetColorTuple(&pixel,MagickTrue,hex);
1274 (void) sprintf(count,"%.20g:",(double) ((MagickOffsetType) p->count));
1275 (void) FormatLocaleFile(file," %s %s %s %s\n",count,tuple,hex,color);
1276 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1277 {
1278 MagickBooleanType
1279 proceed;
1280
1281 proceed=SetImageProgress(image,HistogramImageTag,(MagickOffsetType) i,
1282 number_colors);
1283 if (proceed == MagickFalse)
1284 status=MagickFalse;
1285 }
1286 p++;
1287 }
1288 (void) fflush(file);
1289 histogram=(ColorPacket *) RelinquishMagickMemory(histogram);
1290 if (status == MagickFalse)
1291 return(0);
1292 return(number_colors);
1293 }
1294
1295 /*
1296 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1297 % %
1298 % %
1299 % %
1300 % U n i q u e I m a g e C o l o r s %
1301 % %
1302 % %
1303 % %
1304 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1305 %
1306 % UniqueImageColors() returns the unique colors of an image.
1307 %
1308 % The format of the UniqueImageColors method is:
1309 %
1310 % Image *UniqueImageColors(const Image *image,ExceptionInfo *exception)
1311 %
1312 % A description of each parameter follows.
1313 %
1314 % o image: the image.
1315 %
1316 % o exception: return any errors or warnings in this structure.
1317 %
1318 */
1319
UniqueColorsToImage(Image * unique_image,CacheView * unique_view,CubeInfo * cube_info,const NodeInfo * node_info,ExceptionInfo * exception)1320 static void UniqueColorsToImage(Image *unique_image,CacheView *unique_view,
1321 CubeInfo *cube_info,const NodeInfo *node_info,ExceptionInfo *exception)
1322 {
1323 #define UniqueColorsImageTag "UniqueColors/Image"
1324
1325 MagickBooleanType
1326 status;
1327
1328 ssize_t
1329 i;
1330
1331 size_t
1332 number_children;
1333
1334 /*
1335 Traverse any children.
1336 */
1337 number_children=unique_image->matte == MagickFalse ? 8UL : 16UL;
1338 for (i=0; i < (ssize_t) number_children; i++)
1339 if (node_info->child[i] != (NodeInfo *) NULL)
1340 UniqueColorsToImage(unique_image,unique_view,cube_info,
1341 node_info->child[i],exception);
1342 if (node_info->level == (MaxTreeDepth-1))
1343 {
1344 ColorPacket
1345 *p;
1346
1347 IndexPacket
1348 *magick_restrict indexes;
1349
1350 PixelPacket
1351 *magick_restrict q;
1352
1353 status=MagickTrue;
1354 p=node_info->list;
1355 for (i=0; i < (ssize_t) node_info->number_unique; i++)
1356 {
1357 q=QueueCacheViewAuthenticPixels(unique_view,cube_info->x,0,1,1,
1358 exception);
1359 if (q == (PixelPacket *) NULL)
1360 continue;
1361 indexes=GetCacheViewAuthenticIndexQueue(unique_view);
1362 *q=p->pixel;
1363 if (unique_image->colorspace == CMYKColorspace)
1364 *indexes=p->index;
1365 if (SyncCacheViewAuthenticPixels(unique_view,exception) == MagickFalse)
1366 break;
1367 cube_info->x++;
1368 p++;
1369 }
1370 if (unique_image->progress_monitor != (MagickProgressMonitor) NULL)
1371 {
1372 MagickBooleanType
1373 proceed;
1374
1375 proceed=SetImageProgress(unique_image,UniqueColorsImageTag,
1376 cube_info->progress,cube_info->colors);
1377 if (proceed == MagickFalse)
1378 status=MagickFalse;
1379 }
1380 cube_info->progress++;
1381 if (status == MagickFalse)
1382 return;
1383 }
1384 }
1385
UniqueImageColors(const Image * image,ExceptionInfo * exception)1386 MagickExport Image *UniqueImageColors(const Image *image,
1387 ExceptionInfo *exception)
1388 {
1389 CacheView
1390 *unique_view;
1391
1392 CubeInfo
1393 *cube_info;
1394
1395 Image
1396 *unique_image;
1397
1398 cube_info=ClassifyImageColors(image,exception);
1399 if (cube_info == (CubeInfo *) NULL)
1400 return((Image *) NULL);
1401 unique_image=CloneImage(image,cube_info->colors,1,MagickTrue,exception);
1402 if (unique_image == (Image *) NULL)
1403 return(unique_image);
1404 if (SetImageStorageClass(unique_image,DirectClass) == MagickFalse)
1405 {
1406 InheritException(exception,&unique_image->exception);
1407 unique_image=DestroyImage(unique_image);
1408 return((Image *) NULL);
1409 }
1410 unique_view=AcquireVirtualCacheView(unique_image,exception);
1411 UniqueColorsToImage(unique_image,unique_view,cube_info,cube_info->root,
1412 exception);
1413 unique_view=DestroyCacheView(unique_view);
1414 cube_info=DestroyCubeInfo(image,cube_info);
1415 return(unique_image);
1416 }
1417