MagickCore  6.9.13-51
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  *d;
160 
161  const char
162  *artifact;
163 
164  double
165  max_threshold,
166  min_threshold;
167 
168  Image
169  *component_image;
170 
171  MagickBooleanType
172  status;
173 
174  MagickOffsetType
175  progress;
176 
177  MatrixInfo
178  *equivalences;
179 
180  ssize_t
181  i;
182 
183  size_t
184  size;
185 
186  ssize_t
187  background_id,
188  connect4[2][2] = { { -1, 0 }, { 0, -1 } },
189  connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
190  dx,
191  dy,
192  first,
193  last,
194  n,
195  step,
196  y;
197 
198  /*
199  Initialize connected components image attributes.
200  */
201  assert(image != (Image *) NULL);
202  assert(image->signature == MagickCoreSignature);
203  assert(exception != (ExceptionInfo *) NULL);
204  assert(exception->signature == MagickCoreSignature);
205  if (IsEventLogging() != MagickFalse)
206  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
207  component_image=CloneImage(image,0,0,MagickTrue,exception);
208  if (component_image == (Image *) NULL)
209  return((Image *) NULL);
210  component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
211  if (AcquireImageColormap(component_image,MaxColormapSize) == MagickFalse)
212  {
213  component_image=DestroyImage(component_image);
214  ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
215  }
216  /*
217  Initialize connected components equivalences.
218  */
219  size=image->columns*image->rows;
220  if (image->columns != (size/image->rows))
221  {
222  component_image=DestroyImage(component_image);
223  ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
224  }
225  equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
226  if (equivalences == (MatrixInfo *) NULL)
227  {
228  component_image=DestroyImage(component_image);
229  return((Image *) NULL);
230  }
231  for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
232  (void) SetMatrixElement(equivalences,n,0,&n);
233  object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,sizeof(*object));
234  if (object == (CCObjectInfo *) NULL)
235  {
236  equivalences=DestroyMatrixInfo(equivalences);
237  component_image=DestroyImage(component_image);
238  ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
239  }
240  (void) memset(object,0,MaxColormapSize*sizeof(*object));
241  for (i=0; i < (ssize_t) MaxColormapSize; i++)
242  {
243  object[i].id=i;
244  object[i].bounding_box.x=(ssize_t) image->columns;
245  object[i].bounding_box.y=(ssize_t) image->rows;
246  GetMagickPixelPacket(image,&object[i].color);
247  }
248  /*
249  Find connected components.
250  */
251  status=MagickTrue;
252  progress=0;
253  image_view=AcquireVirtualCacheView(image,exception);
254  for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
255  {
256  if (status == MagickFalse)
257  continue;
258  dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
259  dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
260  for (y=0; y < (ssize_t) image->rows; y++)
261  {
262  const PixelPacket
263  *magick_restrict p;
264 
265  ssize_t
266  x;
267 
268  if (status == MagickFalse)
269  continue;
270  p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
271  if (p == (const PixelPacket *) NULL)
272  {
273  status=MagickFalse;
274  continue;
275  }
276  p+=(ptrdiff_t) image->columns;
277  for (x=0; x < (ssize_t) image->columns; x++)
278  {
279  ssize_t
280  neighbor_offset,
281  obj,
282  offset,
283  ox,
284  oy,
285  root;
286 
287  /*
288  Is neighbor an authentic pixel and a different color than the pixel?
289  */
290  if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
291  ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
292  {
293  p++;
294  continue;
295  }
296  neighbor_offset=dy*image->columns+dx;
297  if (IsColorSimilar(image,p,p+neighbor_offset) == MagickFalse)
298  {
299  p++;
300  continue;
301  }
302  /*
303  Resolve this equivalence.
304  */
305  offset=y*image->columns+x;
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++;
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 IndexPacket
357  *magick_restrict indexes;
358 
359  const PixelPacket
360  *magick_restrict p;
361 
362  IndexPacket
363  *magick_restrict component_indexes;
364 
366  *magick_restrict q;
367 
368  ssize_t
369  x;
370 
371  if (status == MagickFalse)
372  continue;
373  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
374  q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
375  1,exception);
376  if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
377  {
378  status=MagickFalse;
379  continue;
380  }
381  indexes=GetCacheViewVirtualIndexQueue(image_view);
382  component_indexes=GetCacheViewAuthenticIndexQueue(component_view);
383  for (x=0; x < (ssize_t) component_image->columns; x++)
384  {
385  ssize_t
386  id,
387  offset;
388 
389  offset=y*image->columns+x;
390  status=GetMatrixElement(equivalences,offset,0,&id);
391  if (id != offset)
392  status=GetMatrixElement(equivalences,id,0,&id);
393  else
394  {
395  id=n++;
396  if (id >= (ssize_t) MaxColormapSize)
397  break;
398  }
399  status=SetMatrixElement(equivalences,offset,0,&id);
400  if (x < object[id].bounding_box.x)
401  object[id].bounding_box.x=x;
402  if (x >= (ssize_t) object[id].bounding_box.width)
403  object[id].bounding_box.width=(size_t) x;
404  if (y < object[id].bounding_box.y)
405  object[id].bounding_box.y=y;
406  if (y >= (ssize_t) object[id].bounding_box.height)
407  object[id].bounding_box.height=(size_t) y;
408  object[id].color.red+=QuantumScale*(MagickRealType) p->red;
409  object[id].color.green+=QuantumScale*(MagickRealType) p->green;
410  object[id].color.blue+=QuantumScale*(MagickRealType) p->blue;
411  if (image->matte != MagickFalse)
412  object[id].color.opacity+=QuantumScale*(MagickRealType) p->opacity;
413  if (image->colorspace == CMYKColorspace)
414  object[id].color.index+=QuantumScale*(MagickRealType) indexes[x];
415  object[id].centroid.x+=x;
416  object[id].centroid.y+=y;
417  object[id].area++;
418  component_indexes[x]=(IndexPacket) id;
419  p++;
420  q++;
421  }
422  if (n > (ssize_t) MaxColormapSize)
423  break;
424  if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
425  status=MagickFalse;
426  if (image->progress_monitor != (MagickProgressMonitor) NULL)
427  {
428  MagickBooleanType
429  proceed;
430 
431  progress++;
432  proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
433  image->rows);
434  if (proceed == MagickFalse)
435  status=MagickFalse;
436  }
437  }
438  component_view=DestroyCacheView(component_view);
439  image_view=DestroyCacheView(image_view);
440  equivalences=DestroyMatrixInfo(equivalences);
441  if (n > (ssize_t) MaxColormapSize)
442  {
443  object=(CCObjectInfo *) RelinquishMagickMemory(object);
444  component_image=DestroyImage(component_image);
445  ThrowImageException(ResourceLimitError,"TooManyObjects");
446  }
447  background_id=0;
448  min_threshold=0.0;
449  max_threshold=0.0;
450  component_image->colors=(size_t) n;
451  for (i=0; i < (ssize_t) component_image->colors; i++)
452  {
453  object[i].bounding_box.width-=(object[i].bounding_box.x-1);
454  object[i].bounding_box.height-=(object[i].bounding_box.y-1);
455  object[i].color.red/=(QuantumScale*object[i].area);
456  object[i].color.green/=(QuantumScale*object[i].area);
457  object[i].color.blue/=(QuantumScale*object[i].area);
458  if (image->matte != MagickFalse)
459  object[i].color.opacity/=(QuantumScale*object[i].area);
460  if (image->colorspace == CMYKColorspace)
461  object[i].color.index/=(QuantumScale*object[i].area);
462  object[i].centroid.x/=object[i].area;
463  object[i].centroid.y/=object[i].area;
464  max_threshold+=object[i].area;
465  if (object[i].area > object[background_id].area)
466  background_id=i;
467  }
468  max_threshold+=MagickEpsilon;
469  artifact=GetImageArtifact(image,"connected-components:background-id");
470  if (artifact != (const char *) NULL)
471  background_id=(ssize_t) StringToLong(artifact);
472  artifact=GetImageArtifact(image,"connected-components:area-threshold");
473  if (artifact != (const char *) NULL)
474  {
475  /*
476  Merge any object not within the min and max area threshold.
477  */
478  (void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
479  for (i=0; i < (ssize_t) component_image->colors; i++)
480  if (((object[i].area < min_threshold) ||
481  (object[i].area >= max_threshold)) && (i != background_id))
482  object[i].merge=MagickTrue;
483  }
484  artifact=GetImageArtifact(image,"connected-components:keep-colors");
485  if (artifact != (const char *) NULL)
486  {
487  const char
488  *p;
489 
490  /*
491  Keep selected objects based on color, merge others.
492  */
493  for (i=0; i < (ssize_t) component_image->colors; i++)
494  object[i].merge=MagickTrue;
495  for (p=artifact; ; )
496  {
497  char
498  color[MagickPathExtent];
499 
501  pixel;
502 
503  const char
504  *q;
505 
506  for (q=p; *q != '\0'; q++)
507  if (*q == ';')
508  break;
509  (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
510  MagickPathExtent));
511  (void) QueryMagickColor(color,&pixel,exception);
512  for (i=0; i < (ssize_t) component_image->colors; i++)
513  if (IsMagickColorSimilar(&object[i].color,&pixel) != MagickFalse)
514  object[i].merge=MagickFalse;
515  if (*q == '\0')
516  break;
517  p=q+1;
518  }
519  }
520  artifact=GetImageArtifact(image,"connected-components:keep-ids");
521  if (artifact == (const char *) NULL)
522  artifact=GetImageArtifact(image,"connected-components:keep");
523  if (artifact != (const char *) NULL)
524  for (c=(char *) artifact; *c != '\0'; )
525  {
526  /*
527  Keep selected objects based on id, merge others.
528  */
529  for (i=0; i < (ssize_t) component_image->colors; i++)
530  object[i].merge=MagickTrue;
531  while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
532  c++;
533  d=c;
534  first=(ssize_t) strtol(c,&d,10);
535  if (d == c)
536  break;
537  c=d;
538  if (first < 0)
539  first+=(ssize_t) component_image->colors;
540  last=first;
541  while (isspace((int) ((unsigned char) *c)) != 0)
542  c++;
543  if (*c == '-')
544  {
545  last=(ssize_t) strtol(c+1,&c,10);
546  if (last < 0)
547  last+=(ssize_t) component_image->colors;
548  }
549  step=(ssize_t) (first > last ? -1 : 1);
550  for ( ; first != (last+step); first+=step)
551  if ((first >= 0) &&
552  (first < (ssize_t) component_image->colors))
553  object[first].merge=MagickFalse;
554  }
555  artifact=GetImageArtifact(image,"connected-components:keep-top");
556  if (artifact != (const char *) NULL)
557  {
559  *top_objects;
560 
561  ssize_t
562  top_ids;
563 
564  /*
565  Keep top objects.
566  */
567  top_ids=(ssize_t) StringToLong(artifact);
568  if (top_ids < 0)
569  top_ids=0;
570  if (top_ids >= (ssize_t) component_image->colors)
571  top_ids=(ssize_t) component_image->colors-1;
572  top_objects=(CCObjectInfo *) AcquireQuantumMemory(component_image->colors,
573  sizeof(*top_objects));
574  if (top_objects == (CCObjectInfo *) NULL)
575  {
576  object=(CCObjectInfo *) RelinquishMagickMemory(object);
577  component_image=DestroyImage(component_image);
578  ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
579  }
580  (void) memcpy(top_objects,object,component_image->colors*sizeof(*object));
581  qsort((void *) top_objects,component_image->colors,sizeof(*top_objects),
582  CCObjectInfoCompare);
583  for (i=top_ids+1; i < (ssize_t) component_image->colors; i++)
584  {
585  ssize_t id = (ssize_t) top_objects[i].id;
586  if ((id >= 0) && (id < (ssize_t) component_image->colors))
587  object[id].merge=MagickTrue;
588  }
589  top_objects=(CCObjectInfo *) RelinquishMagickMemory(top_objects);
590  }
591  artifact=GetImageArtifact(image,"connected-components:remove-colors");
592  if (artifact != (const char *) NULL)
593  {
594  const char
595  *p;
596 
597  /*
598  Remove selected objects based on color, keep others.
599  */
600  for (p=artifact; ; )
601  {
602  char
603  color[MagickPathExtent];
604 
606  pixel;
607 
608  const char
609  *q;
610 
611  for (q=p; *q != '\0'; q++)
612  if (*q == ';')
613  break;
614  (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
615  MagickPathExtent));
616  (void) QueryMagickColor(color,&pixel,exception);
617  for (i=0; i < (ssize_t) component_image->colors; i++)
618  if (IsMagickColorSimilar(&object[i].color,&pixel) != MagickFalse)
619  object[i].merge=MagickTrue;
620  if (*q == '\0')
621  break;
622  p=q+1;
623  }
624  }
625  artifact=GetImageArtifact(image,"connected-components:remove-ids");
626  if (artifact == (const char *) NULL)
627  artifact=GetImageArtifact(image,"connected-components:remove");
628  if (artifact != (const char *) NULL)
629  for (c=(char *) artifact; *c != '\0'; )
630  {
631  /*
632  Remove selected objects based on color, keep others.
633  */
634  while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
635  c++;
636  d=c;
637  first=(ssize_t) strtol(c,&d,10);
638  if (d == c)
639  break;
640  c=d;
641  if (first < 0)
642  first+=(ssize_t) component_image->colors;
643  last=first;
644  while (isspace((int) ((unsigned char) *c)) != 0)
645  c++;
646  if (*c == '-')
647  {
648  last=(ssize_t) strtol(c+1,&c,10);
649  if (last < 0)
650  last+=(ssize_t) component_image->colors;
651  }
652  step=(ssize_t) (first > last ? -1 : 1);
653  for ( ; first != (last+step); first+=step)
654  if ((first >= 0) &&
655  (first < (ssize_t) component_image->colors))
656  object[first].merge=MagickTrue;
657  }
658  /*
659  Merge any object not within the min and max area threshold.
660  */
661  component_view=AcquireAuthenticCacheView(component_image,exception);
662  object_view=AcquireVirtualCacheView(component_image,exception);
663  for (i=0; i < (ssize_t) component_image->colors; i++)
664  {
666  bounding_box;
667 
668  ssize_t
669  j;
670 
671  size_t
672  id;
673 
674  if (status == MagickFalse)
675  continue;
676  if ((object[i].merge == MagickFalse) || (i == background_id))
677  continue; /* keep object */
678  /*
679  Merge this object.
680  */
681  for (j=0; j < (ssize_t) component_image->colors; j++)
682  object[j].census=0;
683  bounding_box=object[i].bounding_box;
684  for (y=0; y < (ssize_t) bounding_box.height; y++)
685  {
686  const IndexPacket
687  *magick_restrict indexes;
688 
689  const PixelPacket
690  *magick_restrict p;
691 
692  ssize_t
693  x;
694 
695  if (status == MagickFalse)
696  continue;
697  p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
698  bounding_box.y+y,bounding_box.width,1,exception);
699  if (p == (const PixelPacket *) NULL)
700  {
701  status=MagickFalse;
702  continue;
703  }
704  indexes=GetCacheViewVirtualIndexQueue(component_view);
705  for (x=0; x < (ssize_t) bounding_box.width; x++)
706  {
707  size_t
708  k;
709 
710  if (status == MagickFalse)
711  continue;
712  j=(ssize_t) indexes[x];
713  if (j == i)
714  for (k=0; k < (ssize_t) (connectivity > 4 ? 4 : 2); k++)
715  {
716  const IndexPacket
717  *magick_restrict indexes;
718 
719  const PixelPacket
720  *p;
721 
722  /*
723  Compute area of adjacent objects.
724  */
725  if (status == MagickFalse)
726  continue;
727  dx=connectivity > 4 ? connect8[k][1] : connect4[k][1];
728  dy=connectivity > 4 ? connect8[k][0] : connect4[k][0];
729  p=GetCacheViewVirtualPixels(object_view,bounding_box.x+x+dx,
730  bounding_box.y+y+dy,1,1,exception);
731  if (p == (const PixelPacket *) NULL)
732  {
733  status=MagickFalse;
734  break;
735  }
736  indexes=GetCacheViewVirtualIndexQueue(object_view);
737  j=(ssize_t) *indexes;
738  if (j != i)
739  object[j].census++;
740  }
741  }
742  }
743  /*
744  Merge with object of greatest adjacent area.
745  */
746  id=0;
747  for (j=1; j < (ssize_t) component_image->colors; j++)
748  if (object[j].census > object[id].census)
749  id=(size_t) j;
750  object[i].area=0.0;
751  for (y=0; y < (ssize_t) bounding_box.height; y++)
752  {
753  IndexPacket
754  *magick_restrict component_indexes;
755 
757  *magick_restrict q;
758 
759  ssize_t
760  x;
761 
762  if (status == MagickFalse)
763  continue;
764  q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
765  bounding_box.y+y,bounding_box.width,1,exception);
766  if (q == (PixelPacket *) NULL)
767  {
768  status=MagickFalse;
769  continue;
770  }
771  component_indexes=GetCacheViewAuthenticIndexQueue(component_view);
772  for (x=0; x < (ssize_t) bounding_box.width; x++)
773  {
774  if ((ssize_t) component_indexes[x] == i)
775  component_indexes[x]=(IndexPacket) id;
776  }
777  if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
778  status=MagickFalse;
779  }
780  }
781  object_view=DestroyCacheView(object_view);
782  component_view=DestroyCacheView(component_view);
783  artifact=GetImageArtifact(image,"connected-components:mean-color");
784  if (IsMagickTrue(artifact) != MagickFalse)
785  {
786  /*
787  Replace object with mean color.
788  */
789  for (i=0; i < (ssize_t) component_image->colors; i++)
790  {
791  component_image->colormap[i].red=ClampToQuantum(object[i].color.red);
792  component_image->colormap[i].green=ClampToQuantum(
793  object[i].color.green);
794  component_image->colormap[i].blue=ClampToQuantum(object[i].color.blue);
795  component_image->colormap[i].opacity=ClampToQuantum(
796  object[i].color.opacity);
797  }
798  }
799  (void) SyncImage(component_image);
800  artifact=GetImageArtifact(image,"connected-components:verbose");
801  if (IsMagickTrue(artifact) != MagickFalse)
802  {
803  /*
804  Report statistics on each unique objects.
805  */
806  for (i=0; i < (ssize_t) component_image->colors; i++)
807  {
808  object[i].bounding_box.width=0;
809  object[i].bounding_box.height=0;
810  object[i].bounding_box.x=(ssize_t) component_image->columns;
811  object[i].bounding_box.y=(ssize_t) component_image->rows;
812  object[i].centroid.x=0;
813  object[i].centroid.y=0;
814  object[i].census=object[i].area == 0.0 ? 0.0 : 1.0;
815  object[i].area=0;
816  }
817  component_view=AcquireVirtualCacheView(component_image,exception);
818  for (y=0; y < (ssize_t) component_image->rows; y++)
819  {
820  const IndexPacket
821  *indexes;
822 
823  const PixelPacket
824  *magick_restrict p;
825 
826  ssize_t
827  x;
828 
829  if (status == MagickFalse)
830  continue;
831  p=GetCacheViewVirtualPixels(component_view,0,y,
832  component_image->columns,1,exception);
833  if (p == (const PixelPacket *) NULL)
834  {
835  status=MagickFalse;
836  continue;
837  }
838  indexes=GetCacheViewVirtualIndexQueue(component_view);
839  for (x=0; x < (ssize_t) component_image->columns; x++)
840  {
841  size_t
842  id;
843 
844  id=(size_t) indexes[x];
845  if (x < object[id].bounding_box.x)
846  object[id].bounding_box.x=x;
847  if (x > (ssize_t) object[id].bounding_box.width)
848  object[id].bounding_box.width=(size_t) x;
849  if (y < object[id].bounding_box.y)
850  object[id].bounding_box.y=y;
851  if (y > (ssize_t) object[id].bounding_box.height)
852  object[id].bounding_box.height=(size_t) y;
853  object[id].centroid.x+=x;
854  object[id].centroid.y+=y;
855  object[id].area++;
856  }
857  }
858  for (i=0; i < (ssize_t) component_image->colors; i++)
859  {
860  object[i].bounding_box.width-=(object[i].bounding_box.x-1);
861  object[i].bounding_box.height-=(object[i].bounding_box.y-1);
862  object[i].centroid.x=object[i].centroid.x/object[i].area;
863  object[i].centroid.y=object[i].centroid.y/object[i].area;
864  }
865  component_view=DestroyCacheView(component_view);
866  qsort((void *) object,component_image->colors,sizeof(*object),
867  CCObjectInfoCompare);
868  artifact=GetImageArtifact(image,"connected-components:exclude-header");
869  if (IsStringTrue(artifact) == MagickFalse)
870  (void) fprintf(stdout,
871  "Objects (id: bounding-box centroid area mean-color):\n");
872  for (i=0; i < (ssize_t) component_image->colors; i++)
873  if (object[i].census > 0.0)
874  {
875  char
876  mean_color[MaxTextExtent];
877 
878  GetColorTuple(&object[i].color,MagickFalse,mean_color);
879  (void) fprintf(stdout,
880  " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
881  object[i].id,(double) object[i].bounding_box.width,(double)
882  object[i].bounding_box.height,(double) object[i].bounding_box.x,
883  (double) object[i].bounding_box.y,object[i].centroid.x,
884  object[i].centroid.y,(double) object[i].area,mean_color);
885  }
886  }
887  object=(CCObjectInfo *) RelinquishMagickMemory(object);
888  return(component_image);
889 }
Definition: image.h:134