MagickCore  6.9.13-49
Convert, Edit, Or Compose Bitmap Images
vision.c
1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % V V IIIII SSSSS IIIII OOO N N %
7 % V V I SS I O O NN N %
8 % V V I SSS I O O N N N %
9 % V V I SS I O O N NN %
10 % V IIIII SSSSS IIIII OOO N N %
11 % %
12 % %
13 % MagickCore Computer Vision Methods %
14 % %
15 % Software Design %
16 % Cristy %
17 % September 2014 %
18 % %
19 % %
20 % Copyright 1999 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/license/ %
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 "magick/studio.h"
40 #include "magick/artifact.h"
41 #include "magick/blob.h"
42 #include "magick/cache-view.h"
43 #include "magick/color.h"
44 #include "magick/color-private.h"
45 #include "magick/colormap.h"
46 #include "magick/colorspace.h"
47 #include "magick/constitute.h"
48 #include "magick/decorate.h"
49 #include "magick/distort.h"
50 #include "magick/draw.h"
51 #include "magick/enhance.h"
52 #include "magick/exception.h"
53 #include "magick/exception-private.h"
54 #include "magick/effect.h"
55 #include "magick/gem.h"
56 #include "magick/geometry.h"
57 #include "magick/image-private.h"
58 #include "magick/list.h"
59 #include "magick/log.h"
60 #include "magick/matrix.h"
61 #include "magick/memory_.h"
62 #include "magick/memory-private.h"
63 #include "magick/monitor.h"
64 #include "magick/monitor-private.h"
65 #include "magick/montage.h"
66 #include "magick/morphology.h"
67 #include "magick/morphology-private.h"
68 #include "magick/opencl-private.h"
69 #include "magick/paint.h"
70 #include "magick/pixel-accessor.h"
71 #include "magick/pixel-private.h"
72 #include "magick/property.h"
73 #include "magick/quantum.h"
74 #include "magick/resource_.h"
75 #include "magick/signature-private.h"
76 #include "magick/string_.h"
77 #include "magick/string-private.h"
78 #include "magick/thread-private.h"
79 #include "magick/token.h"
80 #include "magick/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. Choose from 4 or 8-way connectivity.
95 %
96 % The format of the ConnectedComponentsImage method is:
97 %
98 % Image *ConnectedComponentsImage(const Image *image,
99 % const size_t connectivity,ExceptionInfo *exception)
100 %
101 % A description of each parameter follows:
102 %
103 % o image: the image.
104 %
105 % o connectivity: how many neighbors to visit, choose from 4 or 8.
106 %
107 % o exception: return any errors or warnings in this structure.
108 %
109 */
110 
111 typedef struct _CCObjectInfo
112 {
113  ssize_t
114  id;
115 
117  bounding_box;
118 
120  color;
121 
122  PointInfo
123  centroid;
124 
125  double
126  area,
127  census;
128 
129  MagickBooleanType
130  merge;
131 } CCObjectInfo;
132 
133 static int CCObjectInfoCompare(const void *x,const void *y)
134 {
136  *p,
137  *q;
138 
139  p=(CCObjectInfo *) x;
140  q=(CCObjectInfo *) y;
141  return((int) (q->area-(ssize_t) p->area));
142 }
143 
144 MagickExport Image *ConnectedComponentsImage(const Image *image,
145  const size_t connectivity,ExceptionInfo *exception)
146 {
147 #define ConnectedComponentsImageTag "ConnectedComponents/Image"
148 
149  CacheView
150  *component_view,
151  *image_view,
152  *object_view;
153 
155  *object;
156 
157  char
158  *c;
159 
160  const char
161  *artifact;
162 
163  double
164  max_threshold,
165  min_threshold;
166 
167  Image
168  *component_image;
169 
170  MagickBooleanType
171  status;
172 
173  MagickOffsetType
174  progress;
175 
176  MatrixInfo
177  *equivalences;
178 
179  ssize_t
180  i;
181 
182  size_t
183  size;
184 
185  ssize_t
186  background_id,
187  connect4[2][2] = { { -1, 0 }, { 0, -1 } },
188  connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
189  dx,
190  dy,
191  first,
192  last,
193  n,
194  step,
195  y;
196 
197  /*
198  Initialize connected components image attributes.
199  */
200  assert(image != (Image *) NULL);
201  assert(image->signature == MagickCoreSignature);
202  assert(exception != (ExceptionInfo *) NULL);
203  assert(exception->signature == MagickCoreSignature);
204  if (IsEventLogging() != MagickFalse)
205  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
206  component_image=CloneImage(image,0,0,MagickTrue,exception);
207  if (component_image == (Image *) NULL)
208  return((Image *) NULL);
209  component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
210  if (AcquireImageColormap(component_image,MaxColormapSize) == MagickFalse)
211  {
212  component_image=DestroyImage(component_image);
213  ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
214  }
215  /*
216  Initialize connected components equivalences.
217  */
218  size=image->columns*image->rows;
219  if (image->columns != (size/image->rows))
220  {
221  component_image=DestroyImage(component_image);
222  ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
223  }
224  equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
225  if (equivalences == (MatrixInfo *) NULL)
226  {
227  component_image=DestroyImage(component_image);
228  return((Image *) NULL);
229  }
230  for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
231  (void) SetMatrixElement(equivalences,n,0,&n);
232  object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,sizeof(*object));
233  if (object == (CCObjectInfo *) NULL)
234  {
235  equivalences=DestroyMatrixInfo(equivalences);
236  component_image=DestroyImage(component_image);
237  ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
238  }
239  (void) memset(object,0,MaxColormapSize*sizeof(*object));
240  for (i=0; i < (ssize_t) MaxColormapSize; i++)
241  {
242  object[i].id=i;
243  object[i].bounding_box.x=(ssize_t) image->columns;
244  object[i].bounding_box.y=(ssize_t) image->rows;
245  GetMagickPixelPacket(image,&object[i].color);
246  }
247  /*
248  Find connected components.
249  */
250  status=MagickTrue;
251  progress=0;
252  image_view=AcquireVirtualCacheView(image,exception);
253  for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
254  {
255  if (status == MagickFalse)
256  continue;
257  dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
258  dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
259  for (y=0; y < (ssize_t) image->rows; y++)
260  {
261  const PixelPacket
262  *magick_restrict p;
263 
264  ssize_t
265  x;
266 
267  if (status == MagickFalse)
268  continue;
269  p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
270  if (p == (const PixelPacket *) NULL)
271  {
272  status=MagickFalse;
273  continue;
274  }
275  p+=(ptrdiff_t) image->columns;
276  for (x=0; x < (ssize_t) image->columns; x++)
277  {
278  ssize_t
279  neighbor_offset,
280  obj,
281  offset,
282  ox,
283  oy,
284  root;
285 
286  /*
287  Is neighbor an authentic pixel and a different color than the pixel?
288  */
289  if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
290  ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
291  {
292  p++;
293  continue;
294  }
295  neighbor_offset=dy*image->columns+dx;
296  if (IsColorSimilar(image,p,p+neighbor_offset) == MagickFalse)
297  {
298  p++;
299  continue;
300  }
301  /*
302  Resolve this equivalence.
303  */
304  offset=y*image->columns+x;
305  ox=offset;
306  status=GetMatrixElement(equivalences,ox,0,&obj);
307  while (obj != ox)
308  {
309  ox=obj;
310  status=GetMatrixElement(equivalences,ox,0,&obj);
311  }
312  oy=offset+neighbor_offset;
313  status=GetMatrixElement(equivalences,oy,0,&obj);
314  while (obj != oy)
315  {
316  oy=obj;
317  status=GetMatrixElement(equivalences,oy,0,&obj);
318  }
319  if (ox < oy)
320  {
321  status=SetMatrixElement(equivalences,oy,0,&ox);
322  root=ox;
323  }
324  else
325  {
326  status=SetMatrixElement(equivalences,ox,0,&oy);
327  root=oy;
328  }
329  ox=offset;
330  status=GetMatrixElement(equivalences,ox,0,&obj);
331  while (obj != root)
332  {
333  status=GetMatrixElement(equivalences,ox,0,&obj);
334  status=SetMatrixElement(equivalences,ox,0,&root);
335  }
336  oy=offset+neighbor_offset;
337  status=GetMatrixElement(equivalences,oy,0,&obj);
338  while (obj != root)
339  {
340  status=GetMatrixElement(equivalences,oy,0,&obj);
341  status=SetMatrixElement(equivalences,oy,0,&root);
342  }
343  status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
344  p++;
345  }
346  }
347  }
348  /*
349  Label connected components.
350  */
351  n=0;
352  component_view=AcquireAuthenticCacheView(component_image,exception);
353  for (y=0; y < (ssize_t) component_image->rows; y++)
354  {
355  const IndexPacket
356  *magick_restrict indexes;
357 
358  const PixelPacket
359  *magick_restrict p;
360 
361  IndexPacket
362  *magick_restrict component_indexes;
363 
365  *magick_restrict q;
366 
367  ssize_t
368  x;
369 
370  if (status == MagickFalse)
371  continue;
372  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
373  q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
374  1,exception);
375  if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
376  {
377  status=MagickFalse;
378  continue;
379  }
380  indexes=GetCacheViewVirtualIndexQueue(image_view);
381  component_indexes=GetCacheViewAuthenticIndexQueue(component_view);
382  for (x=0; x < (ssize_t) component_image->columns; x++)
383  {
384  ssize_t
385  id,
386  offset;
387 
388  offset=y*image->columns+x;
389  status=GetMatrixElement(equivalences,offset,0,&id);
390  if (id != offset)
391  status=GetMatrixElement(equivalences,id,0,&id);
392  else
393  {
394  id=n++;
395  if (id >= (ssize_t) MaxColormapSize)
396  break;
397  }
398  status=SetMatrixElement(equivalences,offset,0,&id);
399  if (x < object[id].bounding_box.x)
400  object[id].bounding_box.x=x;
401  if (x >= (ssize_t) object[id].bounding_box.width)
402  object[id].bounding_box.width=(size_t) x;
403  if (y < object[id].bounding_box.y)
404  object[id].bounding_box.y=y;
405  if (y >= (ssize_t) object[id].bounding_box.height)
406  object[id].bounding_box.height=(size_t) y;
407  object[id].color.red+=QuantumScale*(MagickRealType) p->red;
408  object[id].color.green+=QuantumScale*(MagickRealType) p->green;
409  object[id].color.blue+=QuantumScale*(MagickRealType) p->blue;
410  if (image->matte != MagickFalse)
411  object[id].color.opacity+=QuantumScale*(MagickRealType) p->opacity;
412  if (image->colorspace == CMYKColorspace)
413  object[id].color.index+=QuantumScale*(MagickRealType) indexes[x];
414  object[id].centroid.x+=x;
415  object[id].centroid.y+=y;
416  object[id].area++;
417  component_indexes[x]=(IndexPacket) id;
418  p++;
419  q++;
420  }
421  if (n > (ssize_t) MaxColormapSize)
422  break;
423  if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
424  status=MagickFalse;
425  if (image->progress_monitor != (MagickProgressMonitor) NULL)
426  {
427  MagickBooleanType
428  proceed;
429 
430  progress++;
431  proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
432  image->rows);
433  if (proceed == MagickFalse)
434  status=MagickFalse;
435  }
436  }
437  component_view=DestroyCacheView(component_view);
438  image_view=DestroyCacheView(image_view);
439  equivalences=DestroyMatrixInfo(equivalences);
440  if (n > (ssize_t) MaxColormapSize)
441  {
442  object=(CCObjectInfo *) RelinquishMagickMemory(object);
443  component_image=DestroyImage(component_image);
444  ThrowImageException(ResourceLimitError,"TooManyObjects");
445  }
446  background_id=0;
447  min_threshold=0.0;
448  max_threshold=0.0;
449  component_image->colors=(size_t) n;
450  for (i=0; i < (ssize_t) component_image->colors; i++)
451  {
452  object[i].bounding_box.width-=(object[i].bounding_box.x-1);
453  object[i].bounding_box.height-=(object[i].bounding_box.y-1);
454  object[i].color.red/=(QuantumScale*object[i].area);
455  object[i].color.green/=(QuantumScale*object[i].area);
456  object[i].color.blue/=(QuantumScale*object[i].area);
457  if (image->matte != MagickFalse)
458  object[i].color.opacity/=(QuantumScale*object[i].area);
459  if (image->colorspace == CMYKColorspace)
460  object[i].color.index/=(QuantumScale*object[i].area);
461  object[i].centroid.x/=object[i].area;
462  object[i].centroid.y/=object[i].area;
463  max_threshold+=object[i].area;
464  if (object[i].area > object[background_id].area)
465  background_id=i;
466  }
467  max_threshold+=MagickEpsilon;
468  artifact=GetImageArtifact(image,"connected-components:background-id");
469  if (artifact != (const char *) NULL)
470  background_id=(ssize_t) StringToLong(artifact);
471  artifact=GetImageArtifact(image,"connected-components:area-threshold");
472  if (artifact != (const char *) NULL)
473  {
474  /*
475  Merge any object not within the min and max area threshold.
476  */
477  (void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
478  for (i=0; i < (ssize_t) component_image->colors; i++)
479  if (((object[i].area < min_threshold) ||
480  (object[i].area >= max_threshold)) && (i != background_id))
481  object[i].merge=MagickTrue;
482  }
483  artifact=GetImageArtifact(image,"connected-components:keep-colors");
484  if (artifact != (const char *) NULL)
485  {
486  const char
487  *p;
488 
489  /*
490  Keep selected objects based on color, merge others.
491  */
492  for (i=0; i < (ssize_t) component_image->colors; i++)
493  object[i].merge=MagickTrue;
494  for (p=artifact; ; )
495  {
496  char
497  color[MagickPathExtent];
498 
500  pixel;
501 
502  const char
503  *q;
504 
505  for (q=p; *q != '\0'; q++)
506  if (*q == ';')
507  break;
508  (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
509  MagickPathExtent));
510  (void) QueryMagickColor(color,&pixel,exception);
511  for (i=0; i < (ssize_t) component_image->colors; i++)
512  if (IsMagickColorSimilar(&object[i].color,&pixel) != MagickFalse)
513  object[i].merge=MagickFalse;
514  if (*q == '\0')
515  break;
516  p=q+1;
517  }
518  }
519  artifact=GetImageArtifact(image,"connected-components:keep-ids");
520  if (artifact == (const char *) NULL)
521  artifact=GetImageArtifact(image,"connected-components:keep");
522  if (artifact != (const char *) NULL)
523  for (c=(char *) artifact; *c != '\0'; )
524  {
525  /*
526  Keep selected objects based on id, merge others.
527  */
528  for (i=0; i < (ssize_t) component_image->colors; i++)
529  object[i].merge=MagickTrue;
530  while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
531  c++;
532  first=(ssize_t) strtol(c,&c,10);
533  if (first < 0)
534  first+=(ssize_t) component_image->colors;
535  last=first;
536  while (isspace((int) ((unsigned char) *c)) != 0)
537  c++;
538  if (*c == '-')
539  {
540  last=(ssize_t) strtol(c+1,&c,10);
541  if (last < 0)
542  last+=(ssize_t) component_image->colors;
543  }
544  step=(ssize_t) (first > last ? -1 : 1);
545  for ( ; first != (last+step); first+=step)
546  if ((first >= 0) &&
547  (first < (ssize_t) component_image->colors))
548  object[first].merge=MagickFalse;
549  }
550  artifact=GetImageArtifact(image,"connected-components:keep-top");
551  if (artifact != (const char *) NULL)
552  {
554  *top_objects;
555 
556  ssize_t
557  top_ids;
558 
559  /*
560  Keep top objects.
561  */
562  top_ids=(ssize_t) StringToLong(artifact);
563  if (top_ids < 0)
564  top_ids=0;
565  if (top_ids >= (ssize_t) component_image->colors)
566  top_ids=(ssize_t) component_image->colors-1;
567  top_objects=(CCObjectInfo *) AcquireQuantumMemory(component_image->colors,
568  sizeof(*top_objects));
569  if (top_objects == (CCObjectInfo *) NULL)
570  {
571  object=(CCObjectInfo *) RelinquishMagickMemory(object);
572  component_image=DestroyImage(component_image);
573  ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
574  }
575  (void) memcpy(top_objects,object,component_image->colors*sizeof(*object));
576  qsort((void *) top_objects,component_image->colors,sizeof(*top_objects),
577  CCObjectInfoCompare);
578  for (i=top_ids+1; i < (ssize_t) component_image->colors; i++)
579  {
580  ssize_t id = (ssize_t) top_objects[i].id;
581  if ((id >= 0) && (id < (ssize_t) component_image->colors))
582  object[id].merge=MagickTrue;
583  }
584  top_objects=(CCObjectInfo *) RelinquishMagickMemory(top_objects);
585  }
586  artifact=GetImageArtifact(image,"connected-components:remove-colors");
587  if (artifact != (const char *) NULL)
588  {
589  const char
590  *p;
591 
592  /*
593  Remove selected objects based on color, keep others.
594  */
595  for (p=artifact; ; )
596  {
597  char
598  color[MagickPathExtent];
599 
601  pixel;
602 
603  const char
604  *q;
605 
606  for (q=p; *q != '\0'; q++)
607  if (*q == ';')
608  break;
609  (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
610  MagickPathExtent));
611  (void) QueryMagickColor(color,&pixel,exception);
612  for (i=0; i < (ssize_t) component_image->colors; i++)
613  if (IsMagickColorSimilar(&object[i].color,&pixel) != MagickFalse)
614  object[i].merge=MagickTrue;
615  if (*q == '\0')
616  break;
617  p=q+1;
618  }
619  }
620  artifact=GetImageArtifact(image,"connected-components:remove-ids");
621  if (artifact == (const char *) NULL)
622  artifact=GetImageArtifact(image,"connected-components:remove");
623  if (artifact != (const char *) NULL)
624  for (c=(char *) artifact; *c != '\0'; )
625  {
626  /*
627  Remove selected objects based on color, keep others.
628  */
629  while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
630  c++;
631  first=(ssize_t) strtol(c,&c,10);
632  if (first < 0)
633  first+=(ssize_t) component_image->colors;
634  last=first;
635  while (isspace((int) ((unsigned char) *c)) != 0)
636  c++;
637  if (*c == '-')
638  {
639  last=(ssize_t) strtol(c+1,&c,10);
640  if (last < 0)
641  last+=(ssize_t) component_image->colors;
642  }
643  step=(ssize_t) (first > last ? -1 : 1);
644  for ( ; first != (last+step); first+=step)
645  if ((first >= 0) &&
646  (first < (ssize_t) component_image->colors))
647  object[first].merge=MagickTrue;
648  }
649  /*
650  Merge any object not within the min and max area threshold.
651  */
652  component_view=AcquireAuthenticCacheView(component_image,exception);
653  object_view=AcquireVirtualCacheView(component_image,exception);
654  for (i=0; i < (ssize_t) component_image->colors; i++)
655  {
657  bounding_box;
658 
659  ssize_t
660  j;
661 
662  size_t
663  id;
664 
665  if (status == MagickFalse)
666  continue;
667  if ((object[i].merge == MagickFalse) || (i == background_id))
668  continue; /* keep object */
669  /*
670  Merge this object.
671  */
672  for (j=0; j < (ssize_t) component_image->colors; j++)
673  object[j].census=0;
674  bounding_box=object[i].bounding_box;
675  for (y=0; y < (ssize_t) bounding_box.height; y++)
676  {
677  const IndexPacket
678  *magick_restrict indexes;
679 
680  const PixelPacket
681  *magick_restrict p;
682 
683  ssize_t
684  x;
685 
686  if (status == MagickFalse)
687  continue;
688  p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
689  bounding_box.y+y,bounding_box.width,1,exception);
690  if (p == (const PixelPacket *) NULL)
691  {
692  status=MagickFalse;
693  continue;
694  }
695  indexes=GetCacheViewVirtualIndexQueue(component_view);
696  for (x=0; x < (ssize_t) bounding_box.width; x++)
697  {
698  size_t
699  k;
700 
701  if (status == MagickFalse)
702  continue;
703  j=(ssize_t) indexes[x];
704  if (j == i)
705  for (k=0; k < (ssize_t) (connectivity > 4 ? 4 : 2); k++)
706  {
707  const IndexPacket
708  *magick_restrict indexes;
709 
710  const PixelPacket
711  *p;
712 
713  /*
714  Compute area of adjacent objects.
715  */
716  if (status == MagickFalse)
717  continue;
718  dx=connectivity > 4 ? connect8[k][1] : connect4[k][1];
719  dy=connectivity > 4 ? connect8[k][0] : connect4[k][0];
720  p=GetCacheViewVirtualPixels(object_view,bounding_box.x+x+dx,
721  bounding_box.y+y+dy,1,1,exception);
722  if (p == (const PixelPacket *) NULL)
723  {
724  status=MagickFalse;
725  break;
726  }
727  indexes=GetCacheViewVirtualIndexQueue(object_view);
728  j=(ssize_t) *indexes;
729  if (j != i)
730  object[j].census++;
731  }
732  }
733  }
734  /*
735  Merge with object of greatest adjacent area.
736  */
737  id=0;
738  for (j=1; j < (ssize_t) component_image->colors; j++)
739  if (object[j].census > object[id].census)
740  id=(size_t) j;
741  object[i].area=0.0;
742  for (y=0; y < (ssize_t) bounding_box.height; y++)
743  {
744  IndexPacket
745  *magick_restrict component_indexes;
746 
748  *magick_restrict q;
749 
750  ssize_t
751  x;
752 
753  if (status == MagickFalse)
754  continue;
755  q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
756  bounding_box.y+y,bounding_box.width,1,exception);
757  if (q == (PixelPacket *) NULL)
758  {
759  status=MagickFalse;
760  continue;
761  }
762  component_indexes=GetCacheViewAuthenticIndexQueue(component_view);
763  for (x=0; x < (ssize_t) bounding_box.width; x++)
764  {
765  if ((ssize_t) component_indexes[x] == i)
766  component_indexes[x]=(IndexPacket) id;
767  }
768  if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
769  status=MagickFalse;
770  }
771  }
772  object_view=DestroyCacheView(object_view);
773  component_view=DestroyCacheView(component_view);
774  artifact=GetImageArtifact(image,"connected-components:mean-color");
775  if (IsMagickTrue(artifact) != MagickFalse)
776  {
777  /*
778  Replace object with mean color.
779  */
780  for (i=0; i < (ssize_t) component_image->colors; i++)
781  {
782  component_image->colormap[i].red=ClampToQuantum(object[i].color.red);
783  component_image->colormap[i].green=ClampToQuantum(
784  object[i].color.green);
785  component_image->colormap[i].blue=ClampToQuantum(object[i].color.blue);
786  component_image->colormap[i].opacity=ClampToQuantum(
787  object[i].color.opacity);
788  }
789  }
790  (void) SyncImage(component_image);
791  artifact=GetImageArtifact(image,"connected-components:verbose");
792  if (IsMagickTrue(artifact) != MagickFalse)
793  {
794  /*
795  Report statistics on each unique objects.
796  */
797  for (i=0; i < (ssize_t) component_image->colors; i++)
798  {
799  object[i].bounding_box.width=0;
800  object[i].bounding_box.height=0;
801  object[i].bounding_box.x=(ssize_t) component_image->columns;
802  object[i].bounding_box.y=(ssize_t) component_image->rows;
803  object[i].centroid.x=0;
804  object[i].centroid.y=0;
805  object[i].census=object[i].area == 0.0 ? 0.0 : 1.0;
806  object[i].area=0;
807  }
808  component_view=AcquireVirtualCacheView(component_image,exception);
809  for (y=0; y < (ssize_t) component_image->rows; y++)
810  {
811  const IndexPacket
812  *indexes;
813 
814  const PixelPacket
815  *magick_restrict p;
816 
817  ssize_t
818  x;
819 
820  if (status == MagickFalse)
821  continue;
822  p=GetCacheViewVirtualPixels(component_view,0,y,
823  component_image->columns,1,exception);
824  if (p == (const PixelPacket *) NULL)
825  {
826  status=MagickFalse;
827  continue;
828  }
829  indexes=GetCacheViewVirtualIndexQueue(component_view);
830  for (x=0; x < (ssize_t) component_image->columns; x++)
831  {
832  size_t
833  id;
834 
835  id=(size_t) indexes[x];
836  if (x < object[id].bounding_box.x)
837  object[id].bounding_box.x=x;
838  if (x > (ssize_t) object[id].bounding_box.width)
839  object[id].bounding_box.width=(size_t) x;
840  if (y < object[id].bounding_box.y)
841  object[id].bounding_box.y=y;
842  if (y > (ssize_t) object[id].bounding_box.height)
843  object[id].bounding_box.height=(size_t) y;
844  object[id].centroid.x+=x;
845  object[id].centroid.y+=y;
846  object[id].area++;
847  }
848  }
849  for (i=0; i < (ssize_t) component_image->colors; i++)
850  {
851  object[i].bounding_box.width-=(object[i].bounding_box.x-1);
852  object[i].bounding_box.height-=(object[i].bounding_box.y-1);
853  object[i].centroid.x=object[i].centroid.x/object[i].area;
854  object[i].centroid.y=object[i].centroid.y/object[i].area;
855  }
856  component_view=DestroyCacheView(component_view);
857  qsort((void *) object,component_image->colors,sizeof(*object),
858  CCObjectInfoCompare);
859  artifact=GetImageArtifact(image,"connected-components:exclude-header");
860  if (IsStringTrue(artifact) == MagickFalse)
861  (void) fprintf(stdout,
862  "Objects (id: bounding-box centroid area mean-color):\n");
863  for (i=0; i < (ssize_t) component_image->colors; i++)
864  if (object[i].census > 0.0)
865  {
866  char
867  mean_color[MaxTextExtent];
868 
869  GetColorTuple(&object[i].color,MagickFalse,mean_color);
870  (void) fprintf(stdout,
871  " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
872  object[i].id,(double) object[i].bounding_box.width,(double)
873  object[i].bounding_box.height,(double) object[i].bounding_box.x,
874  (double) object[i].bounding_box.y,object[i].centroid.x,
875  object[i].centroid.y,(double) object[i].area,mean_color);
876  }
877  }
878  object=(CCObjectInfo *) RelinquishMagickMemory(object);
879  return(component_image);
880 }
Definition: image.h:134