]> granicus.if.org Git - imagemagick/blob - MagickCore/vision.c
Handle short months in png_write_iTIME()
[imagemagick] / MagickCore / vision.c
1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 %                                                                             %
4 %                                                                             %
5 %                                                                             %
6 %                   V   V  IIIII  SSSSS  IIIII   OOO   N   N                  %
7 %                   V   V    I    SS       I    O   O  NN  N                  %
8 %                   V   V    I     SSS     I    O   O  N N N                  %
9 %                    V V     I       SS    I    O   O  N  NN                  %
10 %                     V    IIIII  SSSSS  IIIII   OOO   N   N                  %
11 %                                                                             %
12 %                                                                             %
13 %                      MagickCore Computer Vision Methods                     %
14 %                                                                             %
15 %                              Software Design                                %
16 %                                   Cristy                                    %
17 %                               September 2014                                %
18 %                                                                             %
19 %                                                                             %
20 %  Copyright 1999-2017 ImageMagick Studio LLC, a non-profit organization      %
21 %  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 %                                                                             %
26 %    https://www.imagemagick.org/script/license.php                           %
27 %                                                                             %
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 %
37 */
38 \f
39 #include "MagickCore/studio.h"
40 #include "MagickCore/artifact.h"
41 #include "MagickCore/blob.h"
42 #include "MagickCore/cache-view.h"
43 #include "MagickCore/color.h"
44 #include "MagickCore/color-private.h"
45 #include "MagickCore/colormap.h"
46 #include "MagickCore/colorspace.h"
47 #include "MagickCore/constitute.h"
48 #include "MagickCore/decorate.h"
49 #include "MagickCore/distort.h"
50 #include "MagickCore/draw.h"
51 #include "MagickCore/enhance.h"
52 #include "MagickCore/exception.h"
53 #include "MagickCore/exception-private.h"
54 #include "MagickCore/effect.h"
55 #include "MagickCore/gem.h"
56 #include "MagickCore/geometry.h"
57 #include "MagickCore/image-private.h"
58 #include "MagickCore/list.h"
59 #include "MagickCore/log.h"
60 #include "MagickCore/matrix.h"
61 #include "MagickCore/memory_.h"
62 #include "MagickCore/memory-private.h"
63 #include "MagickCore/monitor.h"
64 #include "MagickCore/monitor-private.h"
65 #include "MagickCore/montage.h"
66 #include "MagickCore/morphology.h"
67 #include "MagickCore/morphology-private.h"
68 #include "MagickCore/opencl-private.h"
69 #include "MagickCore/paint.h"
70 #include "MagickCore/pixel-accessor.h"
71 #include "MagickCore/pixel-private.h"
72 #include "MagickCore/property.h"
73 #include "MagickCore/quantum.h"
74 #include "MagickCore/resource_.h"
75 #include "MagickCore/signature-private.h"
76 #include "MagickCore/string_.h"
77 #include "MagickCore/string-private.h"
78 #include "MagickCore/thread-private.h"
79 #include "MagickCore/token.h"
80 #include "MagickCore/vision.h"
81 \f
82 /*
83 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
84 %                                                                             %
85 %                                                                             %
86 %                                                                             %
87 %     C o n n e c t e d C o m p o n e n t s I m a g e                         %
88 %                                                                             %
89 %                                                                             %
90 %                                                                             %
91 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
92 %
93 %  ConnectedComponentsImage() returns the connected-components of the image
94 %  uniquely labeled.  The returned connected components image colors member
95 %  defines the number of unique objects.  Choose from 4 or 8-way connectivity.
96 %
97 %  You are responsible for freeing the connected components objects resources
98 %  with this statement;
99 %
100 %    objects = (CCObjectInfo *) RelinquishMagickMemory(objects);
101 %
102 %  The format of the ConnectedComponentsImage method is:
103 %
104 %      Image *ConnectedComponentsImage(const Image *image,
105 %        const size_t connectivity,CCObjectInfo **objects,
106 %        ExceptionInfo *exception)
107 %
108 %  A description of each parameter follows:
109 %
110 %    o image: the image.
111 %
112 %    o connectivity: how many neighbors to visit, choose from 4 or 8.
113 %
114 %    o objects: return the attributes of each unique object.
115 %
116 %    o exception: return any errors or warnings in this structure.
117 %
118 */
119
120 static int CCObjectInfoCompare(const void *x,const void *y)
121 {
122   CCObjectInfo
123     *p,
124     *q;
125
126   p=(CCObjectInfo *) x;
127   q=(CCObjectInfo *) y;
128   return((int) (q->area-(ssize_t) p->area));
129 }
130
131 MagickExport Image *ConnectedComponentsImage(const Image *image,
132   const size_t connectivity,CCObjectInfo **objects,ExceptionInfo *exception)
133 {
134 #define ConnectedComponentsImageTag  "ConnectedComponents/Image"
135
136   CacheView
137     *image_view,
138     *component_view;
139
140   CCObjectInfo
141     *object;
142
143   char
144     *c;
145
146   const char
147     *artifact;
148
149   double
150     area_threshold;
151
152   Image
153     *component_image;
154
155   MagickBooleanType
156     status;
157
158   MagickOffsetType
159     progress;
160
161   MatrixInfo
162     *equivalences;
163
164   register ssize_t
165     i;
166
167   size_t
168     size;
169
170   ssize_t
171     first,
172     last,
173     n,
174     step,
175     y;
176
177   /*
178     Initialize connected components image attributes.
179   */
180   assert(image != (Image *) NULL);
181   assert(image->signature == MagickCoreSignature);
182   if (image->debug != MagickFalse)
183     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
184   assert(exception != (ExceptionInfo *) NULL);
185   assert(exception->signature == MagickCoreSignature);
186   if (objects != (CCObjectInfo **) NULL)
187     *objects=(CCObjectInfo *) NULL;
188   component_image=CloneImage(image,image->columns,image->rows,MagickTrue,
189     exception);
190   if (component_image == (Image *) NULL)
191     return((Image *) NULL);
192   component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
193   if (AcquireImageColormap(component_image,MaxColormapSize,exception) == MagickFalse)
194     {
195       component_image=DestroyImage(component_image);
196       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
197     }
198   /*
199     Initialize connected components equivalences.
200   */
201   size=image->columns*image->rows;
202   if (image->columns != (size/image->rows))
203     {
204       component_image=DestroyImage(component_image);
205       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
206     }
207   equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
208   if (equivalences == (MatrixInfo *) NULL)
209     {
210       component_image=DestroyImage(component_image);
211       return((Image *) NULL);
212     }
213   for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
214     (void) SetMatrixElement(equivalences,n,0,&n);
215   object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,sizeof(*object));
216   if (object == (CCObjectInfo *) NULL)
217     {
218       equivalences=DestroyMatrixInfo(equivalences);
219       component_image=DestroyImage(component_image);
220       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
221     }
222   (void) ResetMagickMemory(object,0,MaxColormapSize*sizeof(*object));
223   for (i=0; i < (ssize_t) MaxColormapSize; i++)
224   {
225     object[i].id=i;
226     object[i].bounding_box.x=(ssize_t) image->columns;
227     object[i].bounding_box.y=(ssize_t) image->rows;
228     GetPixelInfo(image,&object[i].color);
229   }
230   /*
231     Find connected components.
232   */
233   status=MagickTrue;
234   progress=0;
235   image_view=AcquireVirtualCacheView(image,exception);
236   for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
237   {
238     ssize_t
239       connect4[2][2] = { { -1,  0 }, {  0, -1 } },
240       connect8[4][2] = { { -1, -1 }, { -1,  0 }, { -1,  1 }, {  0, -1 } },
241       dx,
242       dy;
243
244     if (status == MagickFalse)
245       continue;
246     dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
247     dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
248     for (y=0; y < (ssize_t) image->rows; y++)
249     {
250       register const Quantum
251         *magick_restrict p;
252
253       register ssize_t
254         x;
255
256       if (status == MagickFalse)
257         continue;
258       p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
259       if (p == (const Quantum *) NULL)
260         {
261           status=MagickFalse;
262           continue;
263         }
264       p+=GetPixelChannels(image)*image->columns;
265       for (x=0; x < (ssize_t) image->columns; x++)
266       {
267         PixelInfo
268           pixel,
269           target;
270
271         ssize_t
272           neighbor_offset,
273           obj,
274           offset,
275           ox,
276           oy,
277           root;
278
279         /*
280           Is neighbor an authentic pixel and a different color than the pixel?
281         */
282         GetPixelInfoPixel(image,p,&pixel);
283         if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
284             ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
285           {
286             p+=GetPixelChannels(image);
287             continue;
288           }
289         neighbor_offset=dy*(GetPixelChannels(image)*image->columns)+dx*
290           GetPixelChannels(image);
291         GetPixelInfoPixel(image,p+neighbor_offset,&target);
292         if (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse)
293           {
294             p+=GetPixelChannels(image);
295             continue;
296           }
297         /*
298           Resolve this equivalence.
299         */
300         offset=y*image->columns+x;
301         neighbor_offset=dy*image->columns+dx;
302         ox=offset;
303         status=GetMatrixElement(equivalences,ox,0,&obj);
304         while (obj != ox)
305         {
306           ox=obj;
307           status=GetMatrixElement(equivalences,ox,0,&obj);
308         }
309         oy=offset+neighbor_offset;
310         status=GetMatrixElement(equivalences,oy,0,&obj);
311         while (obj != oy)
312         {
313           oy=obj;
314           status=GetMatrixElement(equivalences,oy,0,&obj);
315         }
316         if (ox < oy)
317           {
318             status=SetMatrixElement(equivalences,oy,0,&ox);
319             root=ox;
320           }
321         else
322           {
323             status=SetMatrixElement(equivalences,ox,0,&oy);
324             root=oy;
325           }
326         ox=offset;
327         status=GetMatrixElement(equivalences,ox,0,&obj);
328         while (obj != root)
329         {
330           status=GetMatrixElement(equivalences,ox,0,&obj);
331           status=SetMatrixElement(equivalences,ox,0,&root);
332         }
333         oy=offset+neighbor_offset;
334         status=GetMatrixElement(equivalences,oy,0,&obj);
335         while (obj != root)
336         {
337           status=GetMatrixElement(equivalences,oy,0,&obj);
338           status=SetMatrixElement(equivalences,oy,0,&root);
339         }
340         status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
341         p+=GetPixelChannels(image);
342       }
343     }
344   }
345   image_view=DestroyCacheView(image_view);
346   /*
347     Label connected components.
348   */
349   n=0;
350   image_view=AcquireVirtualCacheView(image,exception);
351   component_view=AcquireAuthenticCacheView(component_image,exception);
352   for (y=0; y < (ssize_t) component_image->rows; y++)
353   {
354     register const Quantum
355       *magick_restrict p;
356
357     register Quantum
358       *magick_restrict q;
359
360     register ssize_t
361       x;
362
363     if (status == MagickFalse)
364       continue;
365     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
366     q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
367       1,exception);
368     if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
369       {
370         status=MagickFalse;
371         continue;
372       }
373     for (x=0; x < (ssize_t) component_image->columns; x++)
374     {
375       ssize_t
376         id,
377         offset;
378
379       offset=y*image->columns+x;
380       status=GetMatrixElement(equivalences,offset,0,&id);
381       if (id != offset)
382         status=GetMatrixElement(equivalences,id,0,&id);
383       else
384         {
385           id=n++;
386           if (id >= (ssize_t) MaxColormapSize)
387             break;
388         }
389       status=SetMatrixElement(equivalences,offset,0,&id);
390       if (x < object[id].bounding_box.x)
391         object[id].bounding_box.x=x;
392       if (x >= (ssize_t) object[id].bounding_box.width)
393         object[id].bounding_box.width=(size_t) x;
394       if (y < object[id].bounding_box.y)
395         object[id].bounding_box.y=y;
396       if (y >= (ssize_t) object[id].bounding_box.height)
397         object[id].bounding_box.height=(size_t) y;
398       object[id].color.red+=QuantumScale*GetPixelRed(image,p);
399       object[id].color.green+=QuantumScale*GetPixelGreen(image,p);
400       object[id].color.blue+=QuantumScale*GetPixelBlue(image,p);
401       if (image->alpha_trait != UndefinedPixelTrait)
402         object[id].color.alpha+=QuantumScale*GetPixelAlpha(image,p);
403       if (image->colorspace == CMYKColorspace)
404         object[id].color.black+=QuantumScale*GetPixelBlack(image,p);
405       object[id].centroid.x+=x;
406       object[id].centroid.y+=y;
407       object[id].area++;
408       SetPixelIndex(component_image,(Quantum) id,q);
409       p+=GetPixelChannels(image);
410       q+=GetPixelChannels(component_image);
411     }
412     if (n > (ssize_t) MaxColormapSize)
413       break;
414     if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
415       status=MagickFalse;
416     if (image->progress_monitor != (MagickProgressMonitor) NULL)
417       {
418         MagickBooleanType
419           proceed;
420
421         proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress++,
422           image->rows);
423         if (proceed == MagickFalse)
424           status=MagickFalse;
425       }
426   }
427   component_view=DestroyCacheView(component_view);
428   image_view=DestroyCacheView(image_view);
429   equivalences=DestroyMatrixInfo(equivalences);
430   if (n > (ssize_t) MaxColormapSize)
431     {
432       object=(CCObjectInfo *) RelinquishMagickMemory(object);
433       component_image=DestroyImage(component_image);
434       ThrowImageException(ResourceLimitError,"TooManyObjects");
435     }
436   component_image->colors=(size_t) n;
437   for (i=0; i < (ssize_t) component_image->colors; i++)
438   {
439     object[i].bounding_box.width-=(object[i].bounding_box.x-1);
440     object[i].bounding_box.height-=(object[i].bounding_box.y-1);
441     object[i].color.red=QuantumRange*(object[i].color.red/object[i].area);
442     object[i].color.green=QuantumRange*(object[i].color.green/object[i].area);
443     object[i].color.blue=QuantumRange*(object[i].color.blue/object[i].area);
444     if (image->alpha_trait != UndefinedPixelTrait)
445       object[i].color.alpha=QuantumRange*(object[i].color.alpha/object[i].area);
446     if (image->colorspace == CMYKColorspace)
447       object[i].color.black=QuantumRange*(object[i].color.black/object[i].area);
448     object[i].centroid.x=object[i].centroid.x/object[i].area;
449     object[i].centroid.y=object[i].centroid.y/object[i].area;
450   }
451   artifact=GetImageArtifact(image,"connected-components:area-threshold");
452   area_threshold=0.0;
453   if (artifact != (const char *) NULL)
454     area_threshold=StringToDouble(artifact,(char **) NULL);
455   if (area_threshold > 0.0)
456     {
457       /*
458         Merge object below area threshold.
459       */
460       component_view=AcquireAuthenticCacheView(component_image,exception);
461       for (i=0; i < (ssize_t) component_image->colors; i++)
462       {
463         double
464           census;
465
466         RectangleInfo
467           bounding_box;
468
469         register ssize_t
470           j;
471
472         size_t
473           id;
474
475         if (status == MagickFalse)
476           continue;
477         if ((double) object[i].area >= area_threshold)
478           continue;
479         for (j=0; j < (ssize_t) component_image->colors; j++)
480           object[j].census=0;
481         bounding_box=object[i].bounding_box;
482         for (y=0; y < (ssize_t) bounding_box.height+2; y++)
483         {
484           register const Quantum
485             *magick_restrict p;
486
487           register ssize_t
488             x;
489
490           if (status == MagickFalse)
491             continue;
492           p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
493             bounding_box.y+y-1,bounding_box.width+2,1,exception);
494           if (p == (const Quantum *) NULL)
495             {
496               status=MagickFalse;
497               continue;
498             }
499           for (x=0; x < (ssize_t) bounding_box.width+2; x++)
500           {
501             j=(ssize_t) GetPixelIndex(component_image,p);
502             if (j != i)
503               object[j].census++;
504             p+=GetPixelChannels(component_image);
505           }
506         }
507         census=0;
508         id=0;
509         for (j=0; j < (ssize_t) component_image->colors; j++)
510           if (census < object[j].census)
511             {
512               census=object[j].census;
513               id=(size_t) j;
514             }
515         object[id].area+=object[i].area;
516         for (y=0; y < (ssize_t) bounding_box.height; y++)
517         {
518           register Quantum
519             *magick_restrict q;
520
521           register ssize_t
522             x;
523
524           if (status == MagickFalse)
525             continue;
526           q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
527             bounding_box.y+y,bounding_box.width,1,exception);
528           if (q == (Quantum *) NULL)
529             {
530               status=MagickFalse;
531               continue;
532             }
533           for (x=0; x < (ssize_t) bounding_box.width; x++)
534           {
535             if ((ssize_t) GetPixelIndex(component_image,q) == i)
536               SetPixelIndex(component_image,(Quantum) id,q);
537             q+=GetPixelChannels(component_image);
538           }
539           if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
540             status=MagickFalse;
541         }
542       }
543       component_view=DestroyCacheView(component_view);
544       (void) SyncImage(component_image,exception);
545     }
546   artifact=GetImageArtifact(image,"connected-components:mean-color");
547   if (IsStringTrue(artifact) != MagickFalse)
548     {
549       /*
550         Replace object with mean color.
551       */
552       for (i=0; i < (ssize_t) component_image->colors; i++)
553         component_image->colormap[i]=object[i].color;
554     }
555   artifact=GetImageArtifact(image,"connected-components:keep");
556   if (artifact != (const char *) NULL)
557     {
558       /*
559         Keep these object (make others transparent).
560       */
561       for (i=0; i < (ssize_t) component_image->colors; i++)
562         object[i].census=0;
563       for (c=(char *) artifact; *c != '\0';)
564       {
565         while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
566           c++;
567         first=strtol(c,&c,10);
568         if (first < 0)
569           first+=(long) component_image->colors;
570         last=first;
571         while (isspace((int) ((unsigned char) *c)) != 0)
572           c++;
573         if (*c == '-')
574           {
575             last=strtol(c+1,&c,10);
576             if (last < 0)
577               last+=(long) component_image->colors;
578           }
579         for (step=first > last ? -1 : 1; first != (last+step); first+=step)
580           object[first].census++;
581       }
582       for (i=0; i < (ssize_t) component_image->colors; i++)
583       {
584         if (object[i].census != 0)
585           continue;
586         component_image->alpha_trait=BlendPixelTrait;
587         component_image->colormap[i].alpha=TransparentAlpha;
588       }
589     }
590   artifact=GetImageArtifact(image,"connected-components:remove");
591   if (artifact != (const char *) NULL)
592     {
593       /*
594         Remove these object (make them transparent).
595       */
596       for (c=(char *) artifact; *c != '\0';)
597       {
598         while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
599           c++;
600         first=strtol(c,&c,10);
601         if (first < 0)
602           first+=(long) component_image->colors;
603         last=first;
604         while (isspace((int) ((unsigned char) *c)) != 0)
605           c++;
606         if (*c == '-')
607           {
608             last=strtol(c+1,&c,10);
609             if (last < 0)
610               last+=(long) component_image->colors;
611           }
612         for (step=first > last ? -1 : 1; first != (last+step); first+=step)
613         {
614           component_image->alpha_trait=BlendPixelTrait;
615           component_image->colormap[first].alpha=TransparentAlpha;
616         }
617       }
618     }
619   (void) SyncImage(component_image,exception);
620   artifact=GetImageArtifact(image,"connected-components:verbose");
621   if ((IsStringTrue(artifact) != MagickFalse) ||
622       (objects != (CCObjectInfo **) NULL))
623     {
624       /*
625         Report statistics on unique object.
626       */
627       for (i=0; i < (ssize_t) component_image->colors; i++)
628       {
629         object[i].bounding_box.width=0;
630         object[i].bounding_box.height=0;
631         object[i].bounding_box.x=(ssize_t) component_image->columns;
632         object[i].bounding_box.y=(ssize_t) component_image->rows;
633         object[i].centroid.x=0;
634         object[i].centroid.y=0;
635         object[i].area=0;
636       }
637       component_view=AcquireVirtualCacheView(component_image,exception);
638       for (y=0; y < (ssize_t) component_image->rows; y++)
639       {
640         register const Quantum
641           *magick_restrict p;
642
643         register ssize_t
644           x;
645
646         if (status == MagickFalse)
647           continue;
648         p=GetCacheViewVirtualPixels(component_view,0,y,
649           component_image->columns,1,exception);
650         if (p == (const Quantum *) NULL)
651           {
652             status=MagickFalse;
653             continue;
654           }
655         for (x=0; x < (ssize_t) component_image->columns; x++)
656         {
657           size_t
658             id;
659
660           id=GetPixelIndex(component_image,p);
661           if (x < object[id].bounding_box.x)
662             object[id].bounding_box.x=x;
663           if (x > (ssize_t) object[id].bounding_box.width)
664             object[id].bounding_box.width=(size_t) x;
665           if (y < object[id].bounding_box.y)
666             object[id].bounding_box.y=y;
667           if (y > (ssize_t) object[id].bounding_box.height)
668             object[id].bounding_box.height=(size_t) y;
669           object[id].centroid.x+=x;
670           object[id].centroid.y+=y;
671           object[id].area++;
672           p+=GetPixelChannels(component_image);
673         }
674       }
675       for (i=0; i < (ssize_t) component_image->colors; i++)
676       {
677         object[i].bounding_box.width-=(object[i].bounding_box.x-1);
678         object[i].bounding_box.height-=(object[i].bounding_box.y-1);
679         object[i].centroid.x=object[i].centroid.x/object[i].area;
680         object[i].centroid.y=object[i].centroid.y/object[i].area;
681       }
682       component_view=DestroyCacheView(component_view);
683       qsort((void *) object,component_image->colors,sizeof(*object),
684         CCObjectInfoCompare);
685       if (objects == (CCObjectInfo **) NULL)
686         {
687           (void) fprintf(stdout,
688             "Objects (id: bounding-box centroid area mean-color):\n");
689           for (i=0; i < (ssize_t) component_image->colors; i++)
690           {
691             char
692               mean_color[MagickPathExtent];
693
694             if (status == MagickFalse)
695               break;
696             if (object[i].area <= area_threshold)
697               continue;
698             GetColorTuple(&object[i].color,MagickFalse,mean_color);
699             (void) fprintf(stdout,
700               "  %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
701               object[i].id,(double) object[i].bounding_box.width,(double)
702               object[i].bounding_box.height,(double) object[i].bounding_box.x,
703               (double) object[i].bounding_box.y,object[i].centroid.x,
704               object[i].centroid.y,(double) object[i].area,mean_color);
705         }
706       }
707     }
708   if (objects == (CCObjectInfo **) NULL)
709     object=(CCObjectInfo *) RelinquishMagickMemory(object);
710   else
711     *objects=object;
712   return(component_image);
713 }