• 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-2020 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,exception);
189   if (component_image == (Image *) NULL)
190     return((Image *) NULL);
191   component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
192   if (AcquireImageColormap(component_image,MaxColormapSize,exception) == MagickFalse)
193     {
194       component_image=DestroyImage(component_image);
195       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
196     }
197   /*
198     Initialize connected components equivalences.
199   */
200   size=image->columns*image->rows;
201   if (image->columns != (size/image->rows))
202     {
203       component_image=DestroyImage(component_image);
204       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
205     }
206   equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
207   if (equivalences == (MatrixInfo *) NULL)
208     {
209       component_image=DestroyImage(component_image);
210       return((Image *) NULL);
211     }
212   for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
213     (void) SetMatrixElement(equivalences,n,0,&n);
214   object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,sizeof(*object));
215   if (object == (CCObjectInfo *) NULL)
216     {
217       equivalences=DestroyMatrixInfo(equivalences);
218       component_image=DestroyImage(component_image);
219       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
220     }
221   (void) memset(object,0,MaxColormapSize*sizeof(*object));
222   for (i=0; i < (ssize_t) MaxColormapSize; i++)
223   {
224     object[i].id=i;
225     object[i].bounding_box.x=(ssize_t) image->columns;
226     object[i].bounding_box.y=(ssize_t) image->rows;
227     GetPixelInfo(image,&object[i].color);
228   }
229   /*
230     Find connected components.
231   */
232   status=MagickTrue;
233   progress=0;
234   image_view=AcquireVirtualCacheView(image,exception);
235   for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
236   {
237     ssize_t
238       connect4[2][2] = { { -1,  0 }, {  0, -1 } },
239       connect8[4][2] = { { -1, -1 }, { -1,  0 }, { -1,  1 }, {  0, -1 } },
240       dx,
241       dy;
242 
243     if (status == MagickFalse)
244       continue;
245     dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
246     dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
247     for (y=0; y < (ssize_t) image->rows; y++)
248     {
249       register const Quantum
250         *magick_restrict p;
251 
252       register ssize_t
253         x;
254 
255       if (status == MagickFalse)
256         continue;
257       p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
258       if (p == (const Quantum *) NULL)
259         {
260           status=MagickFalse;
261           continue;
262         }
263       p+=GetPixelChannels(image)*image->columns;
264       for (x=0; x < (ssize_t) image->columns; x++)
265       {
266         PixelInfo
267           pixel,
268           target;
269 
270         ssize_t
271           neighbor_offset,
272           obj,
273           offset,
274           ox,
275           oy,
276           root;
277 
278         /*
279           Is neighbor an authentic pixel and a different color than the pixel?
280         */
281         GetPixelInfoPixel(image,p,&pixel);
282         if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
283             ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
284           {
285             p+=GetPixelChannels(image);
286             continue;
287           }
288         neighbor_offset=dy*(GetPixelChannels(image)*image->columns)+dx*
289           GetPixelChannels(image);
290         GetPixelInfoPixel(image,p+neighbor_offset,&target);
291         if (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse)
292           {
293             p+=GetPixelChannels(image);
294             continue;
295           }
296         /*
297           Resolve this equivalence.
298         */
299         offset=y*image->columns+x;
300         neighbor_offset=dy*image->columns+dx;
301         ox=offset;
302         status=GetMatrixElement(equivalences,ox,0,&obj);
303         while (obj != ox)
304         {
305           ox=obj;
306           status=GetMatrixElement(equivalences,ox,0,&obj);
307         }
308         oy=offset+neighbor_offset;
309         status=GetMatrixElement(equivalences,oy,0,&obj);
310         while (obj != oy)
311         {
312           oy=obj;
313           status=GetMatrixElement(equivalences,oy,0,&obj);
314         }
315         if (ox < oy)
316           {
317             status=SetMatrixElement(equivalences,oy,0,&ox);
318             root=ox;
319           }
320         else
321           {
322             status=SetMatrixElement(equivalences,ox,0,&oy);
323             root=oy;
324           }
325         ox=offset;
326         status=GetMatrixElement(equivalences,ox,0,&obj);
327         while (obj != root)
328         {
329           status=GetMatrixElement(equivalences,ox,0,&obj);
330           status=SetMatrixElement(equivalences,ox,0,&root);
331         }
332         oy=offset+neighbor_offset;
333         status=GetMatrixElement(equivalences,oy,0,&obj);
334         while (obj != root)
335         {
336           status=GetMatrixElement(equivalences,oy,0,&obj);
337           status=SetMatrixElement(equivalences,oy,0,&root);
338         }
339         status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
340         p+=GetPixelChannels(image);
341       }
342     }
343   }
344   image_view=DestroyCacheView(image_view);
345   /*
346     Label connected components.
347   */
348   n=0;
349   image_view=AcquireVirtualCacheView(image,exception);
350   component_view=AcquireAuthenticCacheView(component_image,exception);
351   for (y=0; y < (ssize_t) component_image->rows; y++)
352   {
353     register const Quantum
354       *magick_restrict p;
355 
356     register Quantum
357       *magick_restrict q;
358 
359     register ssize_t
360       x;
361 
362     if (status == MagickFalse)
363       continue;
364     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
365     q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
366       1,exception);
367     if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
368       {
369         status=MagickFalse;
370         continue;
371       }
372     for (x=0; x < (ssize_t) component_image->columns; x++)
373     {
374       ssize_t
375         id,
376         offset;
377 
378       offset=y*image->columns+x;
379       status=GetMatrixElement(equivalences,offset,0,&id);
380       if (id != offset)
381         status=GetMatrixElement(equivalences,id,0,&id);
382       else
383         {
384           id=n++;
385           if (id >= (ssize_t) MaxColormapSize)
386             break;
387         }
388       status=SetMatrixElement(equivalences,offset,0,&id);
389       if (x < object[id].bounding_box.x)
390         object[id].bounding_box.x=x;
391       if (x >= (ssize_t) object[id].bounding_box.width)
392         object[id].bounding_box.width=(size_t) x;
393       if (y < object[id].bounding_box.y)
394         object[id].bounding_box.y=y;
395       if (y >= (ssize_t) object[id].bounding_box.height)
396         object[id].bounding_box.height=(size_t) y;
397       object[id].color.red+=QuantumScale*GetPixelRed(image,p);
398       object[id].color.green+=QuantumScale*GetPixelGreen(image,p);
399       object[id].color.blue+=QuantumScale*GetPixelBlue(image,p);
400       if (image->alpha_trait != UndefinedPixelTrait)
401         object[id].color.alpha+=QuantumScale*GetPixelAlpha(image,p);
402       if (image->colorspace == CMYKColorspace)
403         object[id].color.black+=QuantumScale*GetPixelBlack(image,p);
404       object[id].centroid.x+=x;
405       object[id].centroid.y+=y;
406       object[id].area++;
407       SetPixelIndex(component_image,(Quantum) id,q);
408       p+=GetPixelChannels(image);
409       q+=GetPixelChannels(component_image);
410     }
411     if (n > (ssize_t) MaxColormapSize)
412       break;
413     if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
414       status=MagickFalse;
415     if (image->progress_monitor != (MagickProgressMonitor) NULL)
416       {
417         MagickBooleanType
418           proceed;
419 
420         progress++;
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=(ssize_t) strtol(c,&c,10);
568         if (first < 0)
569           first+=(ssize_t) component_image->colors;
570         last=first;
571         while (isspace((int) ((unsigned char) *c)) != 0)
572           c++;
573         if (*c == '-')
574           {
575             last=(ssize_t) strtol(c+1,&c,10);
576             if (last < 0)
577               last+=(ssize_t) component_image->colors;
578           }
579         step=(ssize_t) (first > last ? -1 : 1);
580         for ( ; first != (last+step); first+=step)
581           object[first].census++;
582       }
583       for (i=0; i < (ssize_t) component_image->colors; i++)
584       {
585         if (object[i].census != 0)
586           continue;
587         component_image->alpha_trait=BlendPixelTrait;
588         component_image->colormap[i].alpha_trait=BlendPixelTrait;
589         component_image->colormap[i].alpha=(MagickRealType) TransparentAlpha;
590       }
591     }
592   artifact=GetImageArtifact(image,"connected-components:remove");
593   if (artifact != (const char *) NULL)
594     {
595       /*
596         Remove these object (make them transparent).
597       */
598       for (c=(char *) artifact; *c != '\0';)
599       {
600         while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
601           c++;
602         first=(ssize_t) strtol(c,&c,10);
603         if (first < 0)
604           first+=(ssize_t) component_image->colors;
605         last=first;
606         while (isspace((int) ((unsigned char) *c)) != 0)
607           c++;
608         if (*c == '-')
609           {
610             last=(ssize_t) strtol(c+1,&c,10);
611             if (last < 0)
612               last+=(ssize_t) component_image->colors;
613           }
614         step=(ssize_t) (first > last ? -1 : 1);
615         for ( ; first != (last+step); first+=step)
616         {
617           component_image->alpha_trait=BlendPixelTrait;
618           component_image->colormap[first].alpha_trait=BlendPixelTrait;
619           component_image->colormap[first].alpha=(MagickRealType)
620             TransparentAlpha;
621         }
622       }
623     }
624   (void) SyncImage(component_image,exception);
625   artifact=GetImageArtifact(image,"connected-components:verbose");
626   if ((IsStringTrue(artifact) != MagickFalse) ||
627       (objects != (CCObjectInfo **) NULL))
628     {
629       /*
630         Report statistics on unique object.
631       */
632       for (i=0; i < (ssize_t) component_image->colors; i++)
633       {
634         object[i].bounding_box.width=0;
635         object[i].bounding_box.height=0;
636         object[i].bounding_box.x=(ssize_t) component_image->columns;
637         object[i].bounding_box.y=(ssize_t) component_image->rows;
638         object[i].centroid.x=0;
639         object[i].centroid.y=0;
640         object[i].area=0;
641       }
642       component_view=AcquireVirtualCacheView(component_image,exception);
643       for (y=0; y < (ssize_t) component_image->rows; y++)
644       {
645         register const Quantum
646           *magick_restrict p;
647 
648         register ssize_t
649           x;
650 
651         if (status == MagickFalse)
652           continue;
653         p=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,
654           1,exception);
655         if (p == (const Quantum *) NULL)
656           {
657             status=MagickFalse;
658             continue;
659           }
660         for (x=0; x < (ssize_t) component_image->columns; x++)
661         {
662           size_t
663             id;
664 
665           id=GetPixelIndex(component_image,p);
666           if (x < object[id].bounding_box.x)
667             object[id].bounding_box.x=x;
668           if (x > (ssize_t) object[id].bounding_box.width)
669             object[id].bounding_box.width=(size_t) x;
670           if (y < object[id].bounding_box.y)
671             object[id].bounding_box.y=y;
672           if (y > (ssize_t) object[id].bounding_box.height)
673             object[id].bounding_box.height=(size_t) y;
674           object[id].centroid.x+=x;
675           object[id].centroid.y+=y;
676           object[id].area++;
677           p+=GetPixelChannels(component_image);
678         }
679       }
680       for (i=0; i < (ssize_t) component_image->colors; i++)
681       {
682         object[i].bounding_box.width-=(object[i].bounding_box.x-1);
683         object[i].bounding_box.height-=(object[i].bounding_box.y-1);
684         object[i].centroid.x=object[i].centroid.x/object[i].area;
685         object[i].centroid.y=object[i].centroid.y/object[i].area;
686       }
687       component_view=DestroyCacheView(component_view);
688       qsort((void *) object,component_image->colors,sizeof(*object),
689         CCObjectInfoCompare);
690       if (objects == (CCObjectInfo **) NULL)
691         {
692           (void) fprintf(stdout,
693             "Objects (id: bounding-box centroid area mean-color):\n");
694           for (i=0; i < (ssize_t) component_image->colors; i++)
695           {
696             char
697               mean_color[MagickPathExtent];
698 
699             if (status == MagickFalse)
700               break;
701             if (object[i].area <= area_threshold)
702               continue;
703             GetColorTuple(&object[i].color,MagickFalse,mean_color);
704             (void) fprintf(stdout,
705               "  %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
706               object[i].id,(double) object[i].bounding_box.width,(double)
707               object[i].bounding_box.height,(double) object[i].bounding_box.x,
708               (double) object[i].bounding_box.y,object[i].centroid.x,
709               object[i].centroid.y,(double) object[i].area,mean_color);
710         }
711       }
712     }
713   if (objects == (CCObjectInfo **) NULL)
714     object=(CCObjectInfo *) RelinquishMagickMemory(object);
715   else
716     *objects=object;
717   return(component_image);
718 }
719