• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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-2019 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://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 
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 
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 
CCObjectInfoCompare(const void * x,const void * y)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 
ConnectedComponentsImage(const Image * image,const size_t connectivity,CCObjectInfo ** objects,ExceptionInfo * exception)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,0,0,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) memset(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 #if defined(MAGICKCORE_OPENMP_SUPPORT)
422         #pragma omp atomic
423 #endif
424         progress++;
425         proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
426           image->rows);
427         if (proceed == MagickFalse)
428           status=MagickFalse;
429       }
430   }
431   component_view=DestroyCacheView(component_view);
432   image_view=DestroyCacheView(image_view);
433   equivalences=DestroyMatrixInfo(equivalences);
434   if (n > (ssize_t) MaxColormapSize)
435     {
436       object=(CCObjectInfo *) RelinquishMagickMemory(object);
437       component_image=DestroyImage(component_image);
438       ThrowImageException(ResourceLimitError,"TooManyObjects");
439     }
440   component_image->colors=(size_t) n;
441   for (i=0; i < (ssize_t) component_image->colors; i++)
442   {
443     object[i].bounding_box.width-=(object[i].bounding_box.x-1);
444     object[i].bounding_box.height-=(object[i].bounding_box.y-1);
445     object[i].color.red=QuantumRange*(object[i].color.red/object[i].area);
446     object[i].color.green=QuantumRange*(object[i].color.green/object[i].area);
447     object[i].color.blue=QuantumRange*(object[i].color.blue/object[i].area);
448     if (image->alpha_trait != UndefinedPixelTrait)
449       object[i].color.alpha=QuantumRange*(object[i].color.alpha/object[i].area);
450     if (image->colorspace == CMYKColorspace)
451       object[i].color.black=QuantumRange*(object[i].color.black/object[i].area);
452     object[i].centroid.x=object[i].centroid.x/object[i].area;
453     object[i].centroid.y=object[i].centroid.y/object[i].area;
454   }
455   artifact=GetImageArtifact(image,"connected-components:area-threshold");
456   area_threshold=0.0;
457   if (artifact != (const char *) NULL)
458     area_threshold=StringToDouble(artifact,(char **) NULL);
459   if (area_threshold > 0.0)
460     {
461       /*
462         Merge object below area threshold.
463       */
464       component_view=AcquireAuthenticCacheView(component_image,exception);
465       for (i=0; i < (ssize_t) component_image->colors; i++)
466       {
467         double
468           census;
469 
470         RectangleInfo
471           bounding_box;
472 
473         register ssize_t
474           j;
475 
476         size_t
477           id;
478 
479         if (status == MagickFalse)
480           continue;
481         if ((double) object[i].area >= area_threshold)
482           continue;
483         for (j=0; j < (ssize_t) component_image->colors; j++)
484           object[j].census=0;
485         bounding_box=object[i].bounding_box;
486         for (y=0; y < (ssize_t) bounding_box.height+2; y++)
487         {
488           register const Quantum
489             *magick_restrict p;
490 
491           register ssize_t
492             x;
493 
494           if (status == MagickFalse)
495             continue;
496           p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
497             bounding_box.y+y-1,bounding_box.width+2,1,exception);
498           if (p == (const Quantum *) NULL)
499             {
500               status=MagickFalse;
501               continue;
502             }
503           for (x=0; x < (ssize_t) bounding_box.width+2; x++)
504           {
505             j=(ssize_t) GetPixelIndex(component_image,p);
506             if (j != i)
507               object[j].census++;
508             p+=GetPixelChannels(component_image);
509           }
510         }
511         census=0;
512         id=0;
513         for (j=0; j < (ssize_t) component_image->colors; j++)
514           if (census < object[j].census)
515             {
516               census=object[j].census;
517               id=(size_t) j;
518             }
519         object[id].area+=object[i].area;
520         for (y=0; y < (ssize_t) bounding_box.height; y++)
521         {
522           register Quantum
523             *magick_restrict q;
524 
525           register ssize_t
526             x;
527 
528           if (status == MagickFalse)
529             continue;
530           q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
531             bounding_box.y+y,bounding_box.width,1,exception);
532           if (q == (Quantum *) NULL)
533             {
534               status=MagickFalse;
535               continue;
536             }
537           for (x=0; x < (ssize_t) bounding_box.width; x++)
538           {
539             if ((ssize_t) GetPixelIndex(component_image,q) == i)
540               SetPixelIndex(component_image,(Quantum) id,q);
541             q+=GetPixelChannels(component_image);
542           }
543           if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
544             status=MagickFalse;
545         }
546       }
547       component_view=DestroyCacheView(component_view);
548       (void) SyncImage(component_image,exception);
549     }
550   artifact=GetImageArtifact(image,"connected-components:mean-color");
551   if (IsStringTrue(artifact) != MagickFalse)
552     {
553       /*
554         Replace object with mean color.
555       */
556       for (i=0; i < (ssize_t) component_image->colors; i++)
557         component_image->colormap[i]=object[i].color;
558     }
559   artifact=GetImageArtifact(image,"connected-components:keep");
560   if (artifact != (const char *) NULL)
561     {
562       /*
563         Keep these object (make others transparent).
564       */
565       for (i=0; i < (ssize_t) component_image->colors; i++)
566         object[i].census=0;
567       for (c=(char *) artifact; *c != '\0';)
568       {
569         while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
570           c++;
571         first=(ssize_t) strtol(c,&c,10);
572         if (first < 0)
573           first+=(ssize_t) component_image->colors;
574         last=first;
575         while (isspace((int) ((unsigned char) *c)) != 0)
576           c++;
577         if (*c == '-')
578           {
579             last=(ssize_t) strtol(c+1,&c,10);
580             if (last < 0)
581               last+=(ssize_t) component_image->colors;
582           }
583         step=(ssize_t) (first > last ? -1 : 1);
584         for ( ; first != (last+step); first+=step)
585           object[first].census++;
586       }
587       for (i=0; i < (ssize_t) component_image->colors; i++)
588       {
589         if (object[i].census != 0)
590           continue;
591         component_image->alpha_trait=BlendPixelTrait;
592         component_image->colormap[i].alpha_trait=BlendPixelTrait;
593         component_image->colormap[i].alpha=(MagickRealType) TransparentAlpha;
594       }
595     }
596   artifact=GetImageArtifact(image,"connected-components:remove");
597   if (artifact != (const char *) NULL)
598     {
599       /*
600         Remove these object (make them transparent).
601       */
602       for (c=(char *) artifact; *c != '\0';)
603       {
604         while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
605           c++;
606         first=(ssize_t) strtol(c,&c,10);
607         if (first < 0)
608           first+=(ssize_t) component_image->colors;
609         last=first;
610         while (isspace((int) ((unsigned char) *c)) != 0)
611           c++;
612         if (*c == '-')
613           {
614             last=(ssize_t) strtol(c+1,&c,10);
615             if (last < 0)
616               last+=(ssize_t) component_image->colors;
617           }
618         step=(ssize_t) (first > last ? -1 : 1);
619         for ( ; first != (last+step); first+=step)
620         {
621           component_image->alpha_trait=BlendPixelTrait;
622           component_image->colormap[first].alpha_trait=BlendPixelTrait;
623           component_image->colormap[first].alpha=(MagickRealType)
624             TransparentAlpha;
625         }
626       }
627     }
628   (void) SyncImage(component_image,exception);
629   artifact=GetImageArtifact(image,"connected-components:verbose");
630   if ((IsStringTrue(artifact) != MagickFalse) ||
631       (objects != (CCObjectInfo **) NULL))
632     {
633       /*
634         Report statistics on unique object.
635       */
636       for (i=0; i < (ssize_t) component_image->colors; i++)
637       {
638         object[i].bounding_box.width=0;
639         object[i].bounding_box.height=0;
640         object[i].bounding_box.x=(ssize_t) component_image->columns;
641         object[i].bounding_box.y=(ssize_t) component_image->rows;
642         object[i].centroid.x=0;
643         object[i].centroid.y=0;
644         object[i].area=0;
645       }
646       component_view=AcquireVirtualCacheView(component_image,exception);
647       for (y=0; y < (ssize_t) component_image->rows; y++)
648       {
649         register const Quantum
650           *magick_restrict p;
651 
652         register ssize_t
653           x;
654 
655         if (status == MagickFalse)
656           continue;
657         p=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,
658           1,exception);
659         if (p == (const Quantum *) NULL)
660           {
661             status=MagickFalse;
662             continue;
663           }
664         for (x=0; x < (ssize_t) component_image->columns; x++)
665         {
666           size_t
667             id;
668 
669           id=GetPixelIndex(component_image,p);
670           if (x < object[id].bounding_box.x)
671             object[id].bounding_box.x=x;
672           if (x > (ssize_t) object[id].bounding_box.width)
673             object[id].bounding_box.width=(size_t) x;
674           if (y < object[id].bounding_box.y)
675             object[id].bounding_box.y=y;
676           if (y > (ssize_t) object[id].bounding_box.height)
677             object[id].bounding_box.height=(size_t) y;
678           object[id].centroid.x+=x;
679           object[id].centroid.y+=y;
680           object[id].area++;
681           p+=GetPixelChannels(component_image);
682         }
683       }
684       for (i=0; i < (ssize_t) component_image->colors; i++)
685       {
686         object[i].bounding_box.width-=(object[i].bounding_box.x-1);
687         object[i].bounding_box.height-=(object[i].bounding_box.y-1);
688         object[i].centroid.x=object[i].centroid.x/object[i].area;
689         object[i].centroid.y=object[i].centroid.y/object[i].area;
690       }
691       component_view=DestroyCacheView(component_view);
692       qsort((void *) object,component_image->colors,sizeof(*object),
693         CCObjectInfoCompare);
694       if (objects == (CCObjectInfo **) NULL)
695         {
696           (void) fprintf(stdout,
697             "Objects (id: bounding-box centroid area mean-color):\n");
698           for (i=0; i < (ssize_t) component_image->colors; i++)
699           {
700             char
701               mean_color[MagickPathExtent];
702 
703             if (status == MagickFalse)
704               break;
705             if (object[i].area <= area_threshold)
706               continue;
707             GetColorTuple(&object[i].color,MagickFalse,mean_color);
708             (void) fprintf(stdout,
709               "  %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
710               object[i].id,(double) object[i].bounding_box.width,(double)
711               object[i].bounding_box.height,(double) object[i].bounding_box.x,
712               (double) object[i].bounding_box.y,object[i].centroid.x,
713               object[i].centroid.y,(double) object[i].area,mean_color);
714         }
715       }
716     }
717   if (objects == (CCObjectInfo **) NULL)
718     object=(CCObjectInfo *) RelinquishMagickMemory(object);
719   else
720     *objects=object;
721   return(component_image);
722 }
723