MagickCore  6.9.12-67
Convert, Edit, Or Compose Bitmap Images
 All Data Structures
histogram.c
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 
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 
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 
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 
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 
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 (IsEventLogging() != 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 */
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  {
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 */
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 */
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 */
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 */
565 MagickExport ColorPacket *GetImageHistogram(const Image *image,
566  size_t *number_colors,ExceptionInfo *exception)
567 {
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  {
587  *root;
588 
589  *number_colors=cube_info->colors;
590  root=histogram;
591  DefineImageHistogram(image,cube_info->root,&root);
592  }
593  cube_info=DestroyCubeInfo(image,cube_info);
594  }
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 */
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 
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 
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 
811 MagickExport MagickBooleanType IdentifyPaletteImage(const Image *image,
812  ExceptionInfo *exception)
813 {
814  assert(image != (Image *) NULL);
815  assert(image->signature == MagickCoreSignature);
816  if (IsEventLogging() != 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 */
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 
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 (IsEventLogging() != 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 */
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 (IsEventLogging() != 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 
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 
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 
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 
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  {
1241  number_colors=cube_info->colors;
1242  cube_info=DestroyCubeInfo(image,cube_info);
1243  }
1244  return(number_colors);
1245  }
1246  histogram=GetImageHistogram(image,&number_colors,exception);
1247  if (histogram == (ColorPacket *) NULL)
1248  return(number_colors);
1249  qsort((void *) histogram,(size_t) number_colors,sizeof(*histogram),
1250  HistogramCompare);
1251  GetMagickPixelPacket(image,&pixel);
1252  p=histogram;
1253  status=MagickTrue;
1254  for (i=0; i < (ssize_t) number_colors; i++)
1255  {
1256  SetMagickPixelPacket(image,&p->pixel,&p->index,&pixel);
1257  (void) CopyMagickString(tuple,"(",MaxTextExtent);
1258  ConcatenateColorComponent(&pixel,RedChannel,NoCompliance,tuple);
1259  (void) ConcatenateMagickString(tuple,",",MaxTextExtent);
1260  ConcatenateColorComponent(&pixel,GreenChannel,NoCompliance,tuple);
1261  (void) ConcatenateMagickString(tuple,",",MaxTextExtent);
1262  ConcatenateColorComponent(&pixel,BlueChannel,NoCompliance,tuple);
1263  if (pixel.colorspace == CMYKColorspace)
1264  {
1265  (void) ConcatenateMagickString(tuple,",",MaxTextExtent);
1266  ConcatenateColorComponent(&pixel,IndexChannel,NoCompliance,tuple);
1267  }
1268  if (pixel.matte != MagickFalse)
1269  {
1270  (void) ConcatenateMagickString(tuple,",",MaxTextExtent);
1271  ConcatenateColorComponent(&pixel,OpacityChannel,NoCompliance,tuple);
1272  }
1273  (void) ConcatenateMagickString(tuple,")",MaxTextExtent);
1274  (void) QueryMagickColorname(image,&pixel,SVGCompliance,color,exception);
1275  GetColorTuple(&pixel,MagickTrue,hex);
1276  (void) sprintf(count,"%10.20g:",(double) ((MagickOffsetType) p->count));
1277  (void) FormatLocaleFile(file," %s %s %s %s\n",count,tuple,hex,color);
1278  if (image->progress_monitor != (MagickProgressMonitor) NULL)
1279  {
1280  MagickBooleanType
1281  proceed;
1282 
1283  proceed=SetImageProgress(image,HistogramImageTag,(MagickOffsetType) i,
1284  number_colors);
1285  if (proceed == MagickFalse)
1286  status=MagickFalse;
1287  }
1288  p++;
1289  }
1290  (void) fflush(file);
1291  histogram=(ColorPacket *) RelinquishMagickMemory(histogram);
1292  if (status == MagickFalse)
1293  return(0);
1294  return(number_colors);
1295 }
1296 
1297 /*
1298 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1299 % %
1300 % %
1301 % %
1302 % U n i q u e I m a g e C o l o r s %
1303 % %
1304 % %
1305 % %
1306 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1307 %
1308 % UniqueImageColors() returns the unique colors of an image.
1309 %
1310 % The format of the UniqueImageColors method is:
1311 %
1312 % Image *UniqueImageColors(const Image *image,ExceptionInfo *exception)
1313 %
1314 % A description of each parameter follows.
1315 %
1316 % o image: the image.
1317 %
1318 % o exception: return any errors or warnings in this structure.
1319 %
1320 */
1321 
1322 static void UniqueColorsToImage(Image *unique_image,CacheView *unique_view,
1323  CubeInfo *cube_info,const NodeInfo *node_info,ExceptionInfo *exception)
1324 {
1325 #define UniqueColorsImageTag "UniqueColors/Image"
1326 
1327  MagickBooleanType
1328  status;
1329 
1330  ssize_t
1331  i;
1332 
1333  size_t
1334  number_children;
1335 
1336  /*
1337  Traverse any children.
1338  */
1339  number_children=unique_image->matte == MagickFalse ? 8UL : 16UL;
1340  for (i=0; i < (ssize_t) number_children; i++)
1341  if (node_info->child[i] != (NodeInfo *) NULL)
1342  UniqueColorsToImage(unique_image,unique_view,cube_info,
1343  node_info->child[i],exception);
1344  if (node_info->level == (MaxTreeDepth-1))
1345  {
1346  ColorPacket
1347  *p;
1348 
1349  IndexPacket
1350  *magick_restrict indexes;
1351 
1352  PixelPacket
1353  *magick_restrict q;
1354 
1355  status=MagickTrue;
1356  p=node_info->list;
1357  for (i=0; i < (ssize_t) node_info->number_unique; i++)
1358  {
1359  q=QueueCacheViewAuthenticPixels(unique_view,cube_info->x,0,1,1,
1360  exception);
1361  if (q == (PixelPacket *) NULL)
1362  continue;
1363  indexes=GetCacheViewAuthenticIndexQueue(unique_view);
1364  *q=p->pixel;
1365  if (unique_image->colorspace == CMYKColorspace)
1366  *indexes=p->index;
1367  if (SyncCacheViewAuthenticPixels(unique_view,exception) == MagickFalse)
1368  break;
1369  cube_info->x++;
1370  p++;
1371  }
1372  if (unique_image->progress_monitor != (MagickProgressMonitor) NULL)
1373  {
1374  MagickBooleanType
1375  proceed;
1376 
1377  proceed=SetImageProgress(unique_image,UniqueColorsImageTag,
1378  cube_info->progress,cube_info->colors);
1379  if (proceed == MagickFalse)
1380  status=MagickFalse;
1381  }
1382  cube_info->progress++;
1383  if (status == MagickFalse)
1384  return;
1385  }
1386 }
1387 
1388 MagickExport Image *UniqueImageColors(const Image *image,
1389  ExceptionInfo *exception)
1390 {
1391  CacheView
1392  *unique_view;
1393 
1394  CubeInfo
1395  *cube_info;
1396 
1397  Image
1398  *unique_image;
1399 
1400  cube_info=ClassifyImageColors(image,exception);
1401  if (cube_info == (CubeInfo *) NULL)
1402  return((Image *) NULL);
1403  unique_image=CloneImage(image,cube_info->colors,1,MagickTrue,exception);
1404  if (unique_image == (Image *) NULL)
1405  return(unique_image);
1406  if (SetImageStorageClass(unique_image,DirectClass) == MagickFalse)
1407  {
1408  InheritException(exception,&unique_image->exception);
1409  unique_image=DestroyImage(unique_image);
1410  return((Image *) NULL);
1411  }
1412  unique_view=AcquireVirtualCacheView(unique_image,exception);
1413  UniqueColorsToImage(unique_image,unique_view,cube_info,cube_info->root,
1414  exception);
1415  unique_view=DestroyCacheView(unique_view);
1416  cube_info=DestroyCubeInfo(image,cube_info);
1417  return(unique_image);
1418 }
Definition: image.h:152