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