• 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-2021 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     *component_view,
138     *image_view,
139     *object_view;
140 
141   CCObjectInfo
142     *object;
143 
144   char
145     *c;
146 
147   const char
148     *artifact,
149     *metrics[CCMaxMetrics];
150 
151   double
152     max_threshold,
153     min_threshold;
154 
155   Image
156     *component_image;
157 
158   MagickBooleanType
159     status;
160 
161   MagickOffsetType
162     progress;
163 
164   MatrixInfo
165     *equivalences;
166 
167   RectangleInfo
168     bounding_box;
169 
170   ssize_t
171     i;
172 
173   size_t
174     size;
175 
176   ssize_t
177     background_id,
178     connect4[2][2] = { { -1,  0 }, {  0, -1 } },
179     connect8[4][2] = { { -1, -1 }, { -1,  0 }, { -1,  1 }, {  0, -1 } },
180     dx,
181     dy,
182     first,
183     last,
184     n,
185     step,
186     y;
187 
188   /*
189     Initialize connected components image attributes.
190   */
191   assert(image != (Image *) NULL);
192   assert(image->signature == MagickCoreSignature);
193   if (image->debug != MagickFalse)
194     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
195   assert(exception != (ExceptionInfo *) NULL);
196   assert(exception->signature == MagickCoreSignature);
197   if (objects != (CCObjectInfo **) NULL)
198     *objects=(CCObjectInfo *) NULL;
199   component_image=CloneImage(image,0,0,MagickTrue,exception);
200   if (component_image == (Image *) NULL)
201     return((Image *) NULL);
202   component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
203   if (AcquireImageColormap(component_image,MaxColormapSize,exception) == MagickFalse)
204     {
205       component_image=DestroyImage(component_image);
206       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
207     }
208   /*
209     Initialize connected components equivalences.
210   */
211   size=image->columns*image->rows;
212   if (image->columns != (size/image->rows))
213     {
214       component_image=DestroyImage(component_image);
215       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
216     }
217   equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
218   if (equivalences == (MatrixInfo *) NULL)
219     {
220       component_image=DestroyImage(component_image);
221       return((Image *) NULL);
222     }
223   for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
224     (void) SetMatrixElement(equivalences,n,0,&n);
225   object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,sizeof(*object));
226   if (object == (CCObjectInfo *) NULL)
227     {
228       equivalences=DestroyMatrixInfo(equivalences);
229       component_image=DestroyImage(component_image);
230       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
231     }
232   (void) memset(object,0,MaxColormapSize*sizeof(*object));
233   for (i=0; i < (ssize_t) MaxColormapSize; i++)
234   {
235     object[i].id=i;
236     object[i].bounding_box.x=(ssize_t) image->columns;
237     object[i].bounding_box.y=(ssize_t) image->rows;
238     GetPixelInfo(image,&object[i].color);
239   }
240   /*
241     Find connected components.
242   */
243   status=MagickTrue;
244   progress=0;
245   image_view=AcquireVirtualCacheView(image,exception);
246   for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
247   {
248     if (status == MagickFalse)
249       continue;
250     dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
251     dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
252     for (y=0; y < (ssize_t) image->rows; y++)
253     {
254       const Quantum
255         *magick_restrict p;
256 
257       ssize_t
258         x;
259 
260       if (status == MagickFalse)
261         continue;
262       p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
263       if (p == (const Quantum *) NULL)
264         {
265           status=MagickFalse;
266           continue;
267         }
268       p+=GetPixelChannels(image)*image->columns;
269       for (x=0; x < (ssize_t) image->columns; x++)
270       {
271         PixelInfo
272           pixel,
273           target;
274 
275         ssize_t
276           neighbor_offset,
277           obj,
278           offset,
279           ox,
280           oy,
281           root;
282 
283         /*
284           Is neighbor an authentic pixel and a different color than the pixel?
285         */
286         GetPixelInfoPixel(image,p,&pixel);
287         if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
288             ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
289           {
290             p+=GetPixelChannels(image);
291             continue;
292           }
293         neighbor_offset=dy*(GetPixelChannels(image)*image->columns)+dx*
294           GetPixelChannels(image);
295         GetPixelInfoPixel(image,p+neighbor_offset,&target);
296         if (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse)
297           {
298             p+=GetPixelChannels(image);
299             continue;
300           }
301         /*
302           Resolve this equivalence.
303         */
304         offset=y*image->columns+x;
305         neighbor_offset=dy*image->columns+dx;
306         ox=offset;
307         status=GetMatrixElement(equivalences,ox,0,&obj);
308         while (obj != ox)
309         {
310           ox=obj;
311           status=GetMatrixElement(equivalences,ox,0,&obj);
312         }
313         oy=offset+neighbor_offset;
314         status=GetMatrixElement(equivalences,oy,0,&obj);
315         while (obj != oy)
316         {
317           oy=obj;
318           status=GetMatrixElement(equivalences,oy,0,&obj);
319         }
320         if (ox < oy)
321           {
322             status=SetMatrixElement(equivalences,oy,0,&ox);
323             root=ox;
324           }
325         else
326           {
327             status=SetMatrixElement(equivalences,ox,0,&oy);
328             root=oy;
329           }
330         ox=offset;
331         status=GetMatrixElement(equivalences,ox,0,&obj);
332         while (obj != root)
333         {
334           status=GetMatrixElement(equivalences,ox,0,&obj);
335           status=SetMatrixElement(equivalences,ox,0,&root);
336         }
337         oy=offset+neighbor_offset;
338         status=GetMatrixElement(equivalences,oy,0,&obj);
339         while (obj != root)
340         {
341           status=GetMatrixElement(equivalences,oy,0,&obj);
342           status=SetMatrixElement(equivalences,oy,0,&root);
343         }
344         status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
345         p+=GetPixelChannels(image);
346       }
347     }
348   }
349   /*
350     Label connected components.
351   */
352   n=0;
353   component_view=AcquireAuthenticCacheView(component_image,exception);
354   for (y=0; y < (ssize_t) component_image->rows; y++)
355   {
356     const Quantum
357       *magick_restrict p;
358 
359     Quantum
360       *magick_restrict q;
361 
362     ssize_t
363       x;
364 
365     if (status == MagickFalse)
366       continue;
367     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
368     q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
369       1,exception);
370     if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
371       {
372         status=MagickFalse;
373         continue;
374       }
375     for (x=0; x < (ssize_t) component_image->columns; x++)
376     {
377       ssize_t
378         id,
379         offset;
380 
381       offset=y*image->columns+x;
382       status=GetMatrixElement(equivalences,offset,0,&id);
383       if (id != offset)
384         status=GetMatrixElement(equivalences,id,0,&id);
385       else
386         {
387           id=n++;
388           if (id >= (ssize_t) MaxColormapSize)
389             break;
390         }
391       status=SetMatrixElement(equivalences,offset,0,&id);
392       if (x < object[id].bounding_box.x)
393         object[id].bounding_box.x=x;
394       if (x >= (ssize_t) object[id].bounding_box.width)
395         object[id].bounding_box.width=(size_t) x;
396       if (y < object[id].bounding_box.y)
397         object[id].bounding_box.y=y;
398       if (y >= (ssize_t) object[id].bounding_box.height)
399         object[id].bounding_box.height=(size_t) y;
400       object[id].color.red+=QuantumScale*GetPixelRed(image,p);
401       object[id].color.green+=QuantumScale*GetPixelGreen(image,p);
402       object[id].color.blue+=QuantumScale*GetPixelBlue(image,p);
403       if (image->alpha_trait != UndefinedPixelTrait)
404         object[id].color.alpha+=QuantumScale*GetPixelAlpha(image,p);
405       if (image->colorspace == CMYKColorspace)
406         object[id].color.black+=QuantumScale*GetPixelBlack(image,p);
407       object[id].centroid.x+=x;
408       object[id].centroid.y+=y;
409       object[id].area++;
410       SetPixelIndex(component_image,(Quantum) id,q);
411       p+=GetPixelChannels(image);
412       q+=GetPixelChannels(component_image);
413     }
414     if (n > (ssize_t) MaxColormapSize)
415       break;
416     if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
417       status=MagickFalse;
418     if (image->progress_monitor != (MagickProgressMonitor) NULL)
419       {
420         MagickBooleanType
421           proceed;
422 
423         progress++;
424         proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
425           image->rows);
426         if (proceed == MagickFalse)
427           status=MagickFalse;
428       }
429   }
430   component_view=DestroyCacheView(component_view);
431   image_view=DestroyCacheView(image_view);
432   equivalences=DestroyMatrixInfo(equivalences);
433   if (n > (ssize_t) MaxColormapSize)
434     {
435       object=(CCObjectInfo *) RelinquishMagickMemory(object);
436       component_image=DestroyImage(component_image);
437       ThrowImageException(ResourceLimitError,"TooManyObjects");
438     }
439   background_id=0;
440   min_threshold=0.0;
441   max_threshold=0.0;
442   component_image->colors=(size_t) n;
443   for (i=0; i < (ssize_t) component_image->colors; i++)
444   {
445     object[i].bounding_box.width-=(object[i].bounding_box.x-1);
446     object[i].bounding_box.height-=(object[i].bounding_box.y-1);
447     object[i].color.red/=(QuantumScale*object[i].area);
448     object[i].color.green/=(QuantumScale*object[i].area);
449     object[i].color.blue/=(QuantumScale*object[i].area);
450     if (image->alpha_trait != UndefinedPixelTrait)
451       object[i].color.alpha/=(QuantumScale*object[i].area);
452     if (image->colorspace == CMYKColorspace)
453       object[i].color.black/=(QuantumScale*object[i].area);
454     object[i].centroid.x/=object[i].area;
455     object[i].centroid.y/=object[i].area;
456     max_threshold+=object[i].area;
457     if (object[i].area > object[background_id].area)
458       background_id=i;
459   }
460   max_threshold+=MagickEpsilon;
461   n=(-1);
462   artifact=GetImageArtifact(image,"connected-components:background-id");
463   if (artifact != (const char *) NULL)
464     background_id=(ssize_t) StringToLong(artifact);
465   artifact=GetImageArtifact(image,"connected-components:area-threshold");
466   if (artifact != (const char *) NULL)
467     {
468       /*
469         Merge any object not within the min and max area threshold.
470       */
471       (void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
472       for (i=0; i < (ssize_t) component_image->colors; i++)
473         if (((object[i].area < min_threshold) ||
474              (object[i].area >= max_threshold)) && (i != background_id))
475           object[i].merge=MagickTrue;
476     }
477   artifact=GetImageArtifact(image,"connected-components:keep-colors");
478   if (artifact != (const char *) NULL)
479     {
480       const char
481         *p;
482 
483       /*
484         Keep selected objects based on color, merge others.
485       */
486       for (i=0; i < (ssize_t) component_image->colors; i++)
487         object[i].merge=MagickTrue;
488       for (p=artifact;  ; )
489       {
490         char
491           color[MagickPathExtent];
492 
493         PixelInfo
494           pixel;
495 
496         const char
497           *q;
498 
499         for (q=p; *q != '\0'; q++)
500           if (*q == ';')
501             break;
502         (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
503           MagickPathExtent));
504         (void) QueryColorCompliance(color,AllCompliance,&pixel,exception);
505         for (i=0; i < (ssize_t) component_image->colors; i++)
506           if (IsFuzzyEquivalencePixelInfo(&object[i].color,&pixel) != MagickFalse)
507             object[i].merge=MagickFalse;
508         if (*q == '\0')
509           break;
510         p=q+1;
511       }
512     }
513   artifact=GetImageArtifact(image,"connected-components:keep-ids");
514   if (artifact == (const char *) NULL)
515     artifact=GetImageArtifact(image,"connected-components:keep");
516   if (artifact != (const char *) NULL)
517     {
518       /*
519         Keep selected objects based on id, merge others.
520       */
521       for (i=0; i < (ssize_t) component_image->colors; i++)
522         object[i].merge=MagickTrue;
523       for (c=(char *) artifact; *c != '\0'; )
524       {
525         while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
526           c++;
527         first=(ssize_t) strtol(c,&c,10);
528         if (first < 0)
529           first+=(ssize_t) component_image->colors;
530         last=first;
531         while (isspace((int) ((unsigned char) *c)) != 0)
532           c++;
533         if (*c == '-')
534           {
535             last=(ssize_t) strtol(c+1,&c,10);
536             if (last < 0)
537               last+=(ssize_t) component_image->colors;
538           }
539         step=(ssize_t) (first > last ? -1 : 1);
540         for ( ; first != (last+step); first+=step)
541           object[first].merge=MagickFalse;
542       }
543     }
544   artifact=GetImageArtifact(image,"connected-components:keep-top");
545   if (artifact != (const char *) NULL)
546     {
547       CCObjectInfo
548         *top_objects;
549 
550       ssize_t
551         top_ids;
552 
553       /*
554         Keep top objects.
555       */
556       top_ids=(ssize_t) StringToLong(artifact);
557       top_objects=(CCObjectInfo *) AcquireQuantumMemory(component_image->colors,
558         sizeof(*top_objects));
559       if (top_objects == (CCObjectInfo *) NULL)
560         {
561           object=(CCObjectInfo *) RelinquishMagickMemory(object);
562           component_image=DestroyImage(component_image);
563           ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
564         }
565       (void) memcpy(top_objects,object,component_image->colors*sizeof(*object));
566       qsort((void *) top_objects,component_image->colors,sizeof(*top_objects),
567         CCObjectInfoCompare);
568       for (i=top_ids+1; i < (ssize_t) component_image->colors; i++)
569         object[top_objects[i].id].merge=MagickTrue;
570       top_objects=(CCObjectInfo *) RelinquishMagickMemory(top_objects);
571     }
572   artifact=GetImageArtifact(image,"connected-components:remove-colors");
573   if (artifact != (const char *) NULL)
574     {
575       const char
576         *p;
577 
578       /*
579         Remove selected objects based on color, keep others.
580       */
581       for (p=artifact;  ; )
582       {
583         char
584           color[MagickPathExtent];
585 
586         PixelInfo
587           pixel;
588 
589         const char
590           *q;
591 
592         for (q=p; *q != '\0'; q++)
593           if (*q == ';')
594             break;
595         (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
596           MagickPathExtent));
597         (void) QueryColorCompliance(color,AllCompliance,&pixel,exception);
598         for (i=0; i < (ssize_t) component_image->colors; i++)
599           if (IsFuzzyEquivalencePixelInfo(&object[i].color,&pixel) != MagickFalse)
600             object[i].merge=MagickTrue;
601         if (*q == '\0')
602           break;
603         p=q+1;
604       }
605     }
606   artifact=GetImageArtifact(image,"connected-components:remove-ids");
607   if (artifact == (const char *) NULL)
608     artifact=GetImageArtifact(image,"connected-components:remove");
609   if (artifact != (const char *) NULL)
610     for (c=(char *) artifact; *c != '\0'; )
611     {
612       /*
613         Remove selected objects based on id, keep others.
614       */
615       while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
616         c++;
617       first=(ssize_t) strtol(c,&c,10);
618       if (first < 0)
619         first+=(ssize_t) component_image->colors;
620       last=first;
621       while (isspace((int) ((unsigned char) *c)) != 0)
622         c++;
623       if (*c == '-')
624         {
625           last=(ssize_t) strtol(c+1,&c,10);
626           if (last < 0)
627             last+=(ssize_t) component_image->colors;
628         }
629       step=(ssize_t) (first > last ? -1 : 1);
630       for ( ; first != (last+step); first+=step)
631         object[first].merge=MagickTrue;
632     }
633   artifact=GetImageArtifact(image,"connected-components:perimeter-threshold");
634   if (artifact != (const char *) NULL)
635     {
636       /*
637         Merge any object not within the min and max perimeter threshold.
638       */
639       (void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
640       metrics[++n]="perimeter";
641 #if defined(MAGICKCORE_OPENMP_SUPPORT)
642       #pragma omp parallel for schedule(dynamic) shared(status) \
643         magick_number_threads(component_image,component_image,component_image->colors,1)
644 #endif
645       for (i=0; i < (ssize_t) component_image->colors; i++)
646       {
647         CacheView
648           *component_view;
649 
650         RectangleInfo
651           bounding_box;
652 
653         size_t
654           pattern[4] = { 1, 0, 0, 0 };
655 
656         ssize_t
657           y;
658 
659         /*
660           Compute perimeter of each object.
661         */
662         if (status == MagickFalse)
663           continue;
664         component_view=AcquireAuthenticCacheView(component_image,exception);
665         bounding_box=object[i].bounding_box;
666         for (y=(-1); y < (ssize_t) bounding_box.height+1; y++)
667         {
668           const Quantum
669             *magick_restrict p;
670 
671           ssize_t
672             x;
673 
674           if (status == MagickFalse)
675             continue;
676           p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
677             bounding_box.y+y,bounding_box.width+2,2,exception);
678           if (p == (const Quantum *) NULL)
679             {
680               status=MagickFalse;
681               break;
682             }
683           for (x=(-1); x < (ssize_t) bounding_box.width+1; x++)
684           {
685             Quantum
686               pixels[4];
687 
688             ssize_t
689               v;
690 
691             size_t
692               foreground;
693 
694             /*
695               An Algorithm for Calculating Objects’ Shape Features in Binary
696               Images, Lifeng He, Yuyan Chao.
697             */
698             foreground=0;
699             for (v=0; v < 2; v++)
700             {
701               ssize_t
702                 u;
703 
704               for (u=0; u < 2; u++)
705               {
706                 ssize_t
707                   offset;
708 
709                 offset=v*(bounding_box.width+2)*
710                   GetPixelChannels(component_image)+u*
711                   GetPixelChannels(component_image);
712                 pixels[2*v+u]=GetPixelIndex(component_image,p+offset);
713                 if ((ssize_t) pixels[2*v+u] == i)
714                   foreground++;
715               }
716             }
717             if (foreground == 1)
718               pattern[1]++;
719             else
720               if (foreground == 2)
721                 {
722                   if ((((ssize_t) pixels[0] == i) &&
723                        ((ssize_t) pixels[3] == i)) ||
724                       (((ssize_t) pixels[1] == i) &&
725                        ((ssize_t) pixels[2] == i)))
726                     pattern[0]++;  /* diagonal */
727                   else
728                     pattern[2]++;
729                 }
730               else
731                 if (foreground == 3)
732                   pattern[3]++;
733             p+=GetPixelChannels(component_image);
734           }
735         }
736         component_view=DestroyCacheView(component_view);
737         object[i].metric[n]=ceil(MagickSQ1_2*pattern[1]+1.0*pattern[2]+
738           MagickSQ1_2*pattern[3]+MagickSQ2*pattern[0]-0.5);
739       }
740       for (i=0; i < (ssize_t) component_image->colors; i++)
741         if (((object[i].metric[n] < min_threshold) ||
742              (object[i].metric[n] >= max_threshold)) && (i != background_id))
743           object[i].merge=MagickTrue;
744     }
745   artifact=GetImageArtifact(image,"connected-components:circularity-threshold");
746   if (artifact != (const char *) NULL)
747     {
748       /*
749         Merge any object not within the min and max circularity threshold.
750       */
751       (void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
752       metrics[++n]="circularity";
753 #if defined(MAGICKCORE_OPENMP_SUPPORT)
754       #pragma omp parallel for schedule(dynamic) shared(status) \
755         magick_number_threads(component_image,component_image,component_image->colors,1)
756 #endif
757       for (i=0; i < (ssize_t) component_image->colors; i++)
758       {
759         CacheView
760           *component_view;
761 
762         RectangleInfo
763           bounding_box;
764 
765         size_t
766           pattern[4] = { 1, 0, 0, 0 };
767 
768         ssize_t
769           y;
770 
771         /*
772           Compute perimeter of each object.
773         */
774         if (status == MagickFalse)
775           continue;
776         component_view=AcquireAuthenticCacheView(component_image,exception);
777         bounding_box=object[i].bounding_box;
778         for (y=(-1); y < (ssize_t) bounding_box.height; y++)
779         {
780           const Quantum
781             *magick_restrict p;
782 
783           ssize_t
784             x;
785 
786           if (status == MagickFalse)
787             continue;
788           p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
789             bounding_box.y+y,bounding_box.width+2,2,exception);
790           if (p == (const Quantum *) NULL)
791             {
792               status=MagickFalse;
793               break;
794             }
795           for (x=(-1); x < (ssize_t) bounding_box.width; x++)
796           {
797             Quantum
798               pixels[4];
799 
800             ssize_t
801               v;
802 
803             size_t
804               foreground;
805 
806             /*
807               An Algorithm for Calculating Objects’ Shape Features in Binary
808               Images, Lifeng He, Yuyan Chao.
809             */
810             foreground=0;
811             for (v=0; v < 2; v++)
812             {
813               ssize_t
814                 u;
815 
816               for (u=0; u < 2; u++)
817               {
818                 ssize_t
819                   offset;
820 
821                 offset=v*(bounding_box.width+2)*
822                   GetPixelChannels(component_image)+u*
823                   GetPixelChannels(component_image);
824                 pixels[2*v+u]=GetPixelIndex(component_image,p+offset);
825                 if ((ssize_t) pixels[2*v+u] == i)
826                   foreground++;
827               }
828             }
829             if (foreground == 1)
830               pattern[1]++;
831             else
832               if (foreground == 2)
833                 {
834                   if ((((ssize_t) pixels[0] == i) &&
835                        ((ssize_t) pixels[3] == i)) ||
836                       (((ssize_t) pixels[1] == i) &&
837                        ((ssize_t) pixels[2] == i)))
838                     pattern[0]++;  /* diagonal */
839                   else
840                     pattern[2]++;
841                 }
842               else
843                 if (foreground == 3)
844                   pattern[3]++;
845             p+=GetPixelChannels(component_image);
846           }
847         }
848         component_view=DestroyCacheView(component_view);
849         object[i].metric[n]=ceil(MagickSQ1_2*pattern[1]+1.0*pattern[2]+
850           MagickSQ1_2*pattern[3]+MagickSQ2*pattern[0]-0.5);
851         object[i].metric[n]=4.0*MagickPI*object[i].area/(object[i].metric[n]*
852           object[i].metric[n]);
853       }
854       for (i=0; i < (ssize_t) component_image->colors; i++)
855         if (((object[i].metric[n] < min_threshold) ||
856              (object[i].metric[n] >= max_threshold)) && (i != background_id))
857           object[i].merge=MagickTrue;
858     }
859   artifact=GetImageArtifact(image,"connected-components:diameter-threshold");
860   if (artifact != (const char *) NULL)
861     {
862       /*
863         Merge any object not within the min and max diameter threshold.
864       */
865       (void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
866       metrics[++n]="diameter";
867       for (i=0; i < (ssize_t) component_image->colors; i++)
868       {
869         object[i].metric[n]=ceil(sqrt(4.0*object[i].area/MagickPI)-0.5);
870         if (((object[i].metric[n] < min_threshold) ||
871              (object[i].metric[n] >= max_threshold)) && (i != background_id))
872           object[i].merge=MagickTrue;
873       }
874     }
875   artifact=GetImageArtifact(image,"connected-components:major-axis-threshold");
876   if (artifact != (const char *) NULL)
877     {
878       /*
879         Merge any object not within the min and max ellipse major threshold.
880       */
881       (void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
882       metrics[++n]="major-axis";
883 #if defined(MAGICKCORE_OPENMP_SUPPORT)
884       #pragma omp parallel for schedule(dynamic) shared(status) \
885         magick_number_threads(component_image,component_image,component_image->colors,1)
886 #endif
887       for (i=0; i < (ssize_t) component_image->colors; i++)
888       {
889         CacheView
890           *component_view;
891 
892         double
893           M00 = 0.0,
894           M01 = 0.0,
895           M02 = 0.0,
896           M10 = 0.0,
897           M11 = 0.0,
898           M20 = 0.0;
899 
900         PointInfo
901           centroid = { 0.0, 0.0 };
902 
903         RectangleInfo
904           bounding_box;
905 
906         const Quantum
907           *magick_restrict p;
908 
909         ssize_t
910           x;
911 
912         ssize_t
913           y;
914 
915         /*
916           Compute ellipse major axis of each object.
917         */
918         if (status == MagickFalse)
919           continue;
920         component_view=AcquireAuthenticCacheView(component_image,exception);
921         bounding_box=object[i].bounding_box;
922         for (y=0; y < (ssize_t) bounding_box.height; y++)
923         {
924           if (status == MagickFalse)
925             continue;
926           p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
927             bounding_box.y+y,bounding_box.width,1,exception);
928           if (p == (const Quantum *) NULL)
929             {
930               status=MagickFalse;
931               break;
932             }
933           for (x=0; x < (ssize_t) bounding_box.width; x++)
934           {
935             if ((ssize_t) GetPixelIndex(component_image,p) == i)
936               {
937                 M00++;
938                 M10+=x;
939                 M01+=y;
940               }
941             p+=GetPixelChannels(component_image);
942           }
943         }
944         centroid.x=M10*PerceptibleReciprocal(M00);
945         centroid.y=M01*PerceptibleReciprocal(M00);
946         for (y=0; y < (ssize_t) bounding_box.height; y++)
947         {
948           if (status == MagickFalse)
949             continue;
950           p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
951             bounding_box.y+y,bounding_box.width,1,exception);
952           if (p == (const Quantum *) NULL)
953             {
954               status=MagickFalse;
955               break;
956             }
957           for (x=0; x < (ssize_t) bounding_box.width; x++)
958           {
959             if ((ssize_t) GetPixelIndex(component_image,p) == i)
960               {
961                 M11+=(x-centroid.x)*(y-centroid.y);
962                 M20+=(x-centroid.x)*(x-centroid.x);
963                 M02+=(y-centroid.y)*(y-centroid.y);
964               }
965             p+=GetPixelChannels(component_image);
966           }
967         }
968         component_view=DestroyCacheView(component_view);
969         object[i].metric[n]=sqrt((2.0*PerceptibleReciprocal(M00))*((M20+M02)+
970           sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
971       }
972       for (i=0; i < (ssize_t) component_image->colors; i++)
973         if (((object[i].metric[n] < min_threshold) ||
974              (object[i].metric[n] >= max_threshold)) && (i != background_id))
975           object[i].merge=MagickTrue;
976     }
977   artifact=GetImageArtifact(image,"connected-components:minor-axis-threshold");
978   if (artifact != (const char *) NULL)
979     {
980       /*
981         Merge any object not within the min and max ellipse minor threshold.
982       */
983       (void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
984       metrics[++n]="minor-axis";
985 #if defined(MAGICKCORE_OPENMP_SUPPORT)
986       #pragma omp parallel for schedule(dynamic) shared(status) \
987         magick_number_threads(component_image,component_image,component_image->colors,1)
988 #endif
989       for (i=0; i < (ssize_t) component_image->colors; i++)
990       {
991         CacheView
992           *component_view;
993 
994         double
995           M00 = 0.0,
996           M01 = 0.0,
997           M02 = 0.0,
998           M10 = 0.0,
999           M11 = 0.0,
1000           M20 = 0.0;
1001 
1002         PointInfo
1003           centroid = { 0.0, 0.0 };
1004 
1005         RectangleInfo
1006           bounding_box;
1007 
1008         const Quantum
1009           *magick_restrict p;
1010 
1011         ssize_t
1012           x;
1013 
1014         ssize_t
1015           y;
1016 
1017         /*
1018           Compute ellipse major axis of each object.
1019         */
1020         if (status == MagickFalse)
1021           continue;
1022         component_view=AcquireAuthenticCacheView(component_image,exception);
1023         bounding_box=object[i].bounding_box;
1024         for (y=0; y < (ssize_t) bounding_box.height; y++)
1025         {
1026           if (status == MagickFalse)
1027             continue;
1028           p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
1029             bounding_box.y+y,bounding_box.width,1,exception);
1030           if (p == (const Quantum *) NULL)
1031             {
1032               status=MagickFalse;
1033               break;
1034             }
1035           for (x=0; x < (ssize_t) bounding_box.width; x++)
1036           {
1037             if ((ssize_t) GetPixelIndex(component_image,p) == i)
1038               {
1039                 M00++;
1040                 M10+=x;
1041                 M01+=y;
1042               }
1043             p+=GetPixelChannels(component_image);
1044           }
1045         }
1046         centroid.x=M10*PerceptibleReciprocal(M00);
1047         centroid.y=M01*PerceptibleReciprocal(M00);
1048         for (y=0; y < (ssize_t) bounding_box.height; y++)
1049         {
1050           if (status == MagickFalse)
1051             continue;
1052           p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
1053             bounding_box.y+y,bounding_box.width,1,exception);
1054           if (p == (const Quantum *) NULL)
1055             {
1056               status=MagickFalse;
1057               break;
1058             }
1059           for (x=0; x < (ssize_t) bounding_box.width; x++)
1060           {
1061             if ((ssize_t) GetPixelIndex(component_image,p) == i)
1062               {
1063                 M11+=(x-centroid.x)*(y-centroid.y);
1064                 M20+=(x-centroid.x)*(x-centroid.x);
1065                 M02+=(y-centroid.y)*(y-centroid.y);
1066               }
1067             p+=GetPixelChannels(component_image);
1068           }
1069         }
1070         component_view=DestroyCacheView(component_view);
1071         object[i].metric[n]=sqrt((2.0*PerceptibleReciprocal(M00))*((M20+M02)-
1072           sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
1073       }
1074       for (i=0; i < (ssize_t) component_image->colors; i++)
1075         if (((object[i].metric[n] < min_threshold) ||
1076              (object[i].metric[n] >= max_threshold)) && (i != background_id))
1077           object[i].merge=MagickTrue;
1078     }
1079   artifact=GetImageArtifact(image,
1080     "connected-components:eccentricity-threshold");
1081   if (artifact != (const char *) NULL)
1082     {
1083       /*
1084         Merge any object not within the min and max eccentricity threshold.
1085       */
1086       (void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1087       metrics[++n]="eccentricy";
1088 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1089       #pragma omp parallel for schedule(dynamic) shared(status) \
1090         magick_number_threads(component_image,component_image,component_image->colors,1)
1091 #endif
1092       for (i=0; i < (ssize_t) component_image->colors; i++)
1093       {
1094         CacheView
1095           *component_view;
1096 
1097         double
1098           M00 = 0.0,
1099           M01 = 0.0,
1100           M02 = 0.0,
1101           M10 = 0.0,
1102           M11 = 0.0,
1103           M20 = 0.0;
1104 
1105         PointInfo
1106           centroid = { 0.0, 0.0 },
1107           ellipse_axis = { 0.0, 0.0 };
1108 
1109         RectangleInfo
1110           bounding_box;
1111 
1112         const Quantum
1113           *magick_restrict p;
1114 
1115         ssize_t
1116           x;
1117 
1118         ssize_t
1119           y;
1120 
1121         /*
1122           Compute eccentricity of each object.
1123         */
1124         if (status == MagickFalse)
1125           continue;
1126         component_view=AcquireAuthenticCacheView(component_image,exception);
1127         bounding_box=object[i].bounding_box;
1128         for (y=0; y < (ssize_t) bounding_box.height; y++)
1129         {
1130           if (status == MagickFalse)
1131             continue;
1132           p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
1133             bounding_box.y+y,bounding_box.width,1,exception);
1134           if (p == (const Quantum *) NULL)
1135             {
1136               status=MagickFalse;
1137               break;
1138             }
1139           for (x=0; x < (ssize_t) bounding_box.width; x++)
1140           {
1141             if ((ssize_t) GetPixelIndex(component_image,p) == i)
1142               {
1143                 M00++;
1144                 M10+=x;
1145                 M01+=y;
1146               }
1147             p+=GetPixelChannels(component_image);
1148           }
1149         }
1150         centroid.x=M10*PerceptibleReciprocal(M00);
1151         centroid.y=M01*PerceptibleReciprocal(M00);
1152         for (y=0; y < (ssize_t) bounding_box.height; y++)
1153         {
1154           if (status == MagickFalse)
1155             continue;
1156           p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
1157             bounding_box.y+y,bounding_box.width,1,exception);
1158           if (p == (const Quantum *) NULL)
1159             {
1160               status=MagickFalse;
1161               break;
1162             }
1163           for (x=0; x < (ssize_t) bounding_box.width; x++)
1164           {
1165             if ((ssize_t) GetPixelIndex(component_image,p) == i)
1166               {
1167                 M11+=(x-centroid.x)*(y-centroid.y);
1168                 M20+=(x-centroid.x)*(x-centroid.x);
1169                 M02+=(y-centroid.y)*(y-centroid.y);
1170               }
1171             p+=GetPixelChannels(component_image);
1172           }
1173         }
1174         component_view=DestroyCacheView(component_view);
1175         ellipse_axis.x=sqrt((2.0*PerceptibleReciprocal(M00))*((M20+M02)+
1176           sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
1177         ellipse_axis.y=sqrt((2.0*PerceptibleReciprocal(M00))*((M20+M02)-
1178           sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
1179         object[i].metric[n]=sqrt(1.0-(ellipse_axis.y*ellipse_axis.y*
1180           PerceptibleReciprocal(ellipse_axis.x*ellipse_axis.x)));
1181       }
1182       for (i=0; i < (ssize_t) component_image->colors; i++)
1183         if (((object[i].metric[n] < min_threshold) ||
1184              (object[i].metric[n] >= max_threshold)) && (i != background_id))
1185           object[i].merge=MagickTrue;
1186     }
1187   artifact=GetImageArtifact(image,"connected-components:angle-threshold");
1188   if (artifact != (const char *) NULL)
1189     {
1190       /*
1191         Merge any object not within the min and max ellipse angle threshold.
1192       */
1193       (void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1194       metrics[++n]="angle";
1195 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1196       #pragma omp parallel for schedule(dynamic) shared(status) \
1197         magick_number_threads(component_image,component_image,component_image->colors,1)
1198 #endif
1199       for (i=0; i < (ssize_t) component_image->colors; i++)
1200       {
1201         CacheView
1202           *component_view;
1203 
1204         double
1205           M00 = 0.0,
1206           M01 = 0.0,
1207           M02 = 0.0,
1208           M10 = 0.0,
1209           M11 = 0.0,
1210           M20 = 0.0;
1211 
1212         PointInfo
1213           centroid = { 0.0, 0.0 };
1214 
1215         RectangleInfo
1216           bounding_box;
1217 
1218         const Quantum
1219           *magick_restrict p;
1220 
1221         ssize_t
1222           x;
1223 
1224         ssize_t
1225           y;
1226 
1227         /*
1228           Compute ellipse angle of each object.
1229         */
1230         if (status == MagickFalse)
1231           continue;
1232         component_view=AcquireAuthenticCacheView(component_image,exception);
1233         bounding_box=object[i].bounding_box;
1234         for (y=0; y < (ssize_t) bounding_box.height; y++)
1235         {
1236           if (status == MagickFalse)
1237             continue;
1238           p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
1239             bounding_box.y+y,bounding_box.width,1,exception);
1240           if (p == (const Quantum *) NULL)
1241             {
1242               status=MagickFalse;
1243               break;
1244             }
1245           for (x=0; x < (ssize_t) bounding_box.width; x++)
1246           {
1247             if ((ssize_t) GetPixelIndex(component_image,p) == i)
1248               {
1249                 M00++;
1250                 M10+=x;
1251                 M01+=y;
1252               }
1253             p+=GetPixelChannels(component_image);
1254           }
1255         }
1256         centroid.x=M10*PerceptibleReciprocal(M00);
1257         centroid.y=M01*PerceptibleReciprocal(M00);
1258         for (y=0; y < (ssize_t) bounding_box.height; y++)
1259         {
1260           if (status == MagickFalse)
1261             continue;
1262           p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
1263             bounding_box.y+y,bounding_box.width,1,exception);
1264           if (p == (const Quantum *) NULL)
1265             {
1266               status=MagickFalse;
1267               break;
1268             }
1269           for (x=0; x < (ssize_t) bounding_box.width; x++)
1270           {
1271             if ((ssize_t) GetPixelIndex(component_image,p) == i)
1272               {
1273                 M11+=(x-centroid.x)*(y-centroid.y);
1274                 M20+=(x-centroid.x)*(x-centroid.x);
1275                 M02+=(y-centroid.y)*(y-centroid.y);
1276               }
1277             p+=GetPixelChannels(component_image);
1278           }
1279         }
1280         component_view=DestroyCacheView(component_view);
1281         object[i].metric[n]=RadiansToDegrees(1.0/2.0*atan(2.0*M11*
1282           PerceptibleReciprocal(M20-M02)));
1283         if (fabs(M11) < 0.0)
1284            {
1285              if ((fabs(M20-M02) >= 0.0) && ((M20-M02) < 0.0))
1286                object[i].metric[n]+=90.0;
1287            }
1288          else
1289            if (M11 < 0.0)
1290              {
1291                if (fabs(M20-M02) >= 0.0)
1292                  {
1293                    if ((M20-M02) < 0.0)
1294                      object[i].metric[n]+=90.0;
1295                    else
1296                      object[i].metric[n]+=180.0;
1297                  }
1298              }
1299            else
1300              if ((fabs(M20-M02) >= 0.0) && ((M20-M02) < 0.0))
1301                object[i].metric[n]+=90.0;
1302       }
1303       for (i=0; i < (ssize_t) component_image->colors; i++)
1304         if (((object[i].metric[n] < min_threshold) ||
1305              (object[i].metric[n] >= max_threshold)) && (i != background_id))
1306           object[i].merge=MagickTrue;
1307     }
1308   /*
1309     Merge any object not within the min and max area threshold.
1310   */
1311   component_view=AcquireAuthenticCacheView(component_image,exception);
1312   object_view=AcquireVirtualCacheView(component_image,exception);
1313   for (i=0; i < (ssize_t) component_image->colors; i++)
1314   {
1315     ssize_t
1316       j;
1317 
1318     size_t
1319       id;
1320 
1321     if (status == MagickFalse)
1322       continue;
1323     if ((object[i].merge == MagickFalse) || (i == background_id))
1324       continue;  /* keep object */
1325     /*
1326       Merge this object.
1327     */
1328     for (j=0; j < (ssize_t) component_image->colors; j++)
1329       object[j].census=0;
1330     bounding_box=object[i].bounding_box;
1331     for (y=0; y < (ssize_t) bounding_box.height; y++)
1332     {
1333       const Quantum
1334         *magick_restrict p;
1335 
1336       ssize_t
1337         x;
1338 
1339       if (status == MagickFalse)
1340         continue;
1341       p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
1342         bounding_box.y+y,bounding_box.width,1,exception);
1343       if (p == (const Quantum *) NULL)
1344         {
1345           status=MagickFalse;
1346           continue;
1347         }
1348       for (x=0; x < (ssize_t) bounding_box.width; x++)
1349       {
1350         ssize_t
1351           n;
1352 
1353         if (status == MagickFalse)
1354           continue;
1355         j=(ssize_t) GetPixelIndex(component_image,p);
1356         if (j == i)
1357           for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
1358           {
1359             const Quantum
1360               *p;
1361 
1362             /*
1363               Compute area of adjacent objects.
1364             */
1365             if (status == MagickFalse)
1366               continue;
1367             dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
1368             dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
1369             p=GetCacheViewVirtualPixels(object_view,bounding_box.x+x+dx,
1370               bounding_box.y+y+dy,1,1,exception);
1371             if (p == (const Quantum *) NULL)
1372               {
1373                 status=MagickFalse;
1374                 break;
1375               }
1376             j=(ssize_t) GetPixelIndex(component_image,p);
1377             if (j != i)
1378               object[j].census++;
1379           }
1380         p+=GetPixelChannels(component_image);
1381       }
1382     }
1383     /*
1384       Merge with object of greatest adjacent area.
1385     */
1386     id=0;
1387     for (j=1; j < (ssize_t) component_image->colors; j++)
1388       if (object[j].census > object[id].census)
1389         id=(size_t) j;
1390     object[i].area=0.0;
1391     for (y=0; y < (ssize_t) bounding_box.height; y++)
1392     {
1393       Quantum
1394         *magick_restrict q;
1395 
1396       ssize_t
1397         x;
1398 
1399       if (status == MagickFalse)
1400         continue;
1401       q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
1402         bounding_box.y+y,bounding_box.width,1,exception);
1403       if (q == (Quantum *) NULL)
1404         {
1405           status=MagickFalse;
1406           continue;
1407         }
1408       for (x=0; x < (ssize_t) bounding_box.width; x++)
1409       {
1410         if ((ssize_t) GetPixelIndex(component_image,q) == i)
1411           SetPixelIndex(component_image,(Quantum) id,q);
1412         q+=GetPixelChannels(component_image);
1413       }
1414       if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
1415         status=MagickFalse;
1416     }
1417   }
1418   object_view=DestroyCacheView(object_view);
1419   component_view=DestroyCacheView(component_view);
1420   artifact=GetImageArtifact(image,"connected-components:mean-color");
1421   if (IsStringTrue(artifact) != MagickFalse)
1422     {
1423       /*
1424         Replace object with mean color.
1425       */
1426       for (i=0; i < (ssize_t) component_image->colors; i++)
1427         component_image->colormap[i]=object[i].color;
1428     }
1429   (void) SyncImage(component_image,exception);
1430   artifact=GetImageArtifact(image,"connected-components:verbose");
1431   if ((IsStringTrue(artifact) != MagickFalse) ||
1432       (objects != (CCObjectInfo **) NULL))
1433     {
1434       /*
1435         Report statistics on each unique object.
1436       */
1437       for (i=0; i < (ssize_t) component_image->colors; i++)
1438       {
1439         object[i].bounding_box.width=0;
1440         object[i].bounding_box.height=0;
1441         object[i].bounding_box.x=(ssize_t) component_image->columns;
1442         object[i].bounding_box.y=(ssize_t) component_image->rows;
1443         object[i].centroid.x=0;
1444         object[i].centroid.y=0;
1445         object[i].census=object[i].area == 0.0 ? 0.0 : 1.0;
1446         object[i].area=0;
1447       }
1448       component_view=AcquireVirtualCacheView(component_image,exception);
1449       for (y=0; y < (ssize_t) component_image->rows; y++)
1450       {
1451         const Quantum
1452           *magick_restrict p;
1453 
1454         ssize_t
1455           x;
1456 
1457         if (status == MagickFalse)
1458           continue;
1459         p=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,
1460           1,exception);
1461         if (p == (const Quantum *) NULL)
1462           {
1463             status=MagickFalse;
1464             continue;
1465           }
1466         for (x=0; x < (ssize_t) component_image->columns; x++)
1467         {
1468           size_t
1469             id;
1470 
1471           id=(size_t) GetPixelIndex(component_image,p);
1472           if (x < object[id].bounding_box.x)
1473             object[id].bounding_box.x=x;
1474           if (x > (ssize_t) object[id].bounding_box.width)
1475             object[id].bounding_box.width=(size_t) x;
1476           if (y < object[id].bounding_box.y)
1477             object[id].bounding_box.y=y;
1478           if (y > (ssize_t) object[id].bounding_box.height)
1479             object[id].bounding_box.height=(size_t) y;
1480           object[id].centroid.x+=x;
1481           object[id].centroid.y+=y;
1482           object[id].area++;
1483           p+=GetPixelChannels(component_image);
1484         }
1485       }
1486       for (i=0; i < (ssize_t) component_image->colors; i++)
1487       {
1488         object[i].bounding_box.width-=(object[i].bounding_box.x-1);
1489         object[i].bounding_box.height-=(object[i].bounding_box.y-1);
1490         object[i].centroid.x=object[i].centroid.x/object[i].area;
1491         object[i].centroid.y=object[i].centroid.y/object[i].area;
1492       }
1493       component_view=DestroyCacheView(component_view);
1494       qsort((void *) object,component_image->colors,sizeof(*object),
1495         CCObjectInfoCompare);
1496       if (objects == (CCObjectInfo **) NULL)
1497         {
1498           ssize_t
1499             j;
1500 
1501           artifact=GetImageArtifact(image,
1502             "connected-components:exclude-header");
1503           if (IsStringTrue(artifact) == MagickFalse)
1504             {
1505               (void) fprintf(stdout,"Objects (");
1506               artifact=GetImageArtifact(image,
1507                 "connected-components:exclude-ids");
1508               if (IsStringTrue(artifact) == MagickFalse)
1509                 (void) fprintf(stdout,"id: ");
1510               (void) fprintf(stdout,"bounding-box centroid area mean-color");
1511               for (j=0; j <= n; j++)
1512                 (void) fprintf(stdout," %s",metrics[j]);
1513               (void) fprintf(stdout,"):\n");
1514             }
1515           for (i=0; i < (ssize_t) component_image->colors; i++)
1516             if (object[i].census > 0.0)
1517               {
1518                 char
1519                   mean_color[MagickPathExtent];
1520 
1521                 GetColorTuple(&object[i].color,MagickFalse,mean_color);
1522                 (void) fprintf(stdout,"  ");
1523                 artifact=GetImageArtifact(image,
1524                   "connected-components:exclude-ids");
1525                 if (IsStringTrue(artifact) == MagickFalse)
1526                   (void) fprintf(stdout,"%.20g: ",(double) object[i].id);
1527                 (void) fprintf(stdout,
1528                   "%.20gx%.20g%+.20g%+.20g %.1f,%.1f %.*g %s",(double)
1529                   object[i].bounding_box.width,(double)
1530                   object[i].bounding_box.height,(double)
1531                   object[i].bounding_box.x,(double) object[i].bounding_box.y,
1532                   object[i].centroid.x,object[i].centroid.y,
1533                   GetMagickPrecision(),(double) object[i].area,mean_color);
1534                 for (j=0; j <= n; j++)
1535                   (void) fprintf(stdout," %.*g",GetMagickPrecision(),
1536                     object[i].metric[j]);
1537                 (void) fprintf(stdout,"\n");
1538               }
1539         }
1540     }
1541   if (objects == (CCObjectInfo **) NULL)
1542     object=(CCObjectInfo *) RelinquishMagickMemory(object);
1543   else
1544     *objects=object;
1545   return(component_image);
1546 }
1547