MagickCore  7.0.10
vision.c
Go to the documentation of this file.
1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % V V IIIII SSSSS IIIII OOO N N %
7 % V V I SS I O O NN N %
8 % V V I SSS I O O N N N %
9 % V V I SS I O O N NN %
10 % V IIIII SSSSS IIIII OOO N N %
11 % %
12 % %
13 % MagickCore Computer Vision Methods %
14 % %
15 % Software Design %
16 % Cristy %
17 % September 2014 %
18 % %
19 % %
20 % Copyright 1999-2020 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
22 % %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
25 % %
26 % https://imagemagick.org/script/license.php %
27 % %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
33 % %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 %
36 %
37 */
38 
39 #include "MagickCore/studio.h"
40 #include "MagickCore/artifact.h"
41 #include "MagickCore/blob.h"
42 #include "MagickCore/cache-view.h"
43 #include "MagickCore/color.h"
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"
54 #include "MagickCore/effect.h"
55 #include "MagickCore/gem.h"
56 #include "MagickCore/geometry.h"
58 #include "MagickCore/list.h"
59 #include "MagickCore/log.h"
60 #include "MagickCore/matrix.h"
61 #include "MagickCore/memory_.h"
63 #include "MagickCore/monitor.h"
65 #include "MagickCore/montage.h"
66 #include "MagickCore/morphology.h"
69 #include "MagickCore/paint.h"
72 #include "MagickCore/property.h"
73 #include "MagickCore/quantum.h"
74 #include "MagickCore/resource_.h"
76 #include "MagickCore/string_.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 
120 static int CCObjectInfoCompare(const void *x,const void *y)
121 {
123  *p,
124  *q;
125 
126  p=(CCObjectInfo *) x;
127  q=(CCObjectInfo *) y;
128  return((int) (q->area-(ssize_t) p->area));
129 }
130 
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 
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 
159  status;
160 
162  progress;
163 
164  MatrixInfo
165  *equivalences;
166 
168  bounding_box;
169 
170  register 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  register const Quantum
255  *magick_restrict p;
256 
257  register 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  register const Quantum
357  *magick_restrict p;
358 
359  register Quantum
360  *magick_restrict q;
361 
362  register 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  {
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) StringToDouble(artifact,(char **) NULL);
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  register 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  register 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,
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  {
548  *top_objects;
549 
550  ssize_t
551  top_ids;
552 
553  /*
554  Keep top objects.
555  */
556  top_ids=(ssize_t) StringToDouble(artifact,(char **) NULL);
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),
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  register 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  register 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,
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 
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  register const Quantum
669  *magick_restrict p;
670 
671  register 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  register 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  register 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 
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  register const Quantum
781  *magick_restrict p;
782 
783  register 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  register 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  register 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 
904  bounding_box;
905 
906  register const Quantum
907  *magick_restrict p;
908 
909  register 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 
1006  bounding_box;
1007 
1008  register const Quantum
1009  *magick_restrict p;
1010 
1011  register 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 
1110  bounding_box;
1111 
1112  register const Quantum
1113  *magick_restrict p;
1114 
1115  register 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 
1216  bounding_box;
1217 
1218  register const Quantum
1219  *magick_restrict p;
1220 
1221  register 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  register 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  register const Quantum
1334  *magick_restrict p;
1335 
1336  register 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  register 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  register 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  register Quantum
1394  *magick_restrict q;
1395 
1396  register 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  register const Quantum
1452  *magick_restrict p;
1453 
1454  register 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),
1496  if (objects == (CCObjectInfo **) NULL)
1497  {
1498  register 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 }
size_t rows
Definition: image.h:172
#define magick_restrict
Definition: MagickCore.h:41
MagickExport CacheView * DestroyCacheView(CacheView *cache_view)
Definition: cache-view.c:252
MagickExport Image * ConnectedComponentsImage(const Image *image, const size_t connectivity, CCObjectInfo **objects, ExceptionInfo *exception)
Definition: vision.c:131
#define MagickSQ1_2
Definition: image-private.h:41
PixelInfo * colormap
Definition: image.h:179
MagickProgressMonitor progress_monitor
Definition: image.h:303
MagickExport MagickBooleanType SyncImage(Image *image, ExceptionInfo *exception)
Definition: image.c:3870
static Quantum GetPixelAlpha(const Image *magick_restrict image, const Quantum *magick_restrict pixel)
static Quantum GetPixelRed(const Image *magick_restrict image, const Quantum *magick_restrict pixel)
size_t signature
Definition: exception.h:123
#define CCMaxMetrics
Definition: vision.h:25
MagickExport const char * GetImageArtifact(const Image *image, const char *artifact)
Definition: artifact.c:273
#define MaxColormapSize
Definition: magick-type.h:78
static double StringToDouble(const char *magick_restrict string, char **magick_restrict sentinal)
#define MagickPI
Definition: image-private.h:40
MagickExport size_t CopyMagickString(char *magick_restrict destination, const char *magick_restrict source, const size_t length)
Definition: string.c:756
#define MAGICKCORE_QUANTUM_DEPTH
Definition: magick-type.h:32
MagickExport const Quantum * GetCacheViewVirtualPixels(const CacheView *cache_view, const ssize_t x, const ssize_t y, const size_t columns, const size_t rows, ExceptionInfo *exception)
Definition: cache-view.c:651
#define MagickSQ2
Definition: image-private.h:42
#define MagickEpsilon
Definition: magick-type.h:114
MagickExport MagickBooleanType GetMatrixElement(const MatrixInfo *matrix_info, const ssize_t x, const ssize_t y, void *value)
Definition: matrix.c:705
size_t width
Definition: geometry.h:130
Definition: log.h:52
ssize_t MagickOffsetType
Definition: magick-type.h:133
MagickExport void GetPixelInfo(const Image *image, PixelInfo *pixel)
Definition: pixel.c:2170
Definition: image.h:151
MagickExport MagickBooleanType SetMatrixElement(const MatrixInfo *matrix_info, const ssize_t x, const ssize_t y, const void *value)
Definition: matrix.c:1110
double x
Definition: geometry.h:123
#define MagickCoreSignature
MagickExport Quantum * GetCacheViewAuthenticPixels(CacheView *cache_view, const ssize_t x, const ssize_t y, const size_t columns, const size_t rows, ExceptionInfo *exception)
Definition: cache-view.c:299
MagickBooleanType
Definition: magick-type.h:169
static double PerceptibleReciprocal(const double x)
MagickExport void * AcquireQuantumMemory(const size_t count, const size_t quantum)
Definition: memory.c:634
MagickExport void GetColorTuple(const PixelInfo *pixel, const MagickBooleanType hex, char *tuple)
Definition: color.c:1518
double y
Definition: geometry.h:123
#define MagickPathExtent
static Quantum GetPixelGreen(const Image *magick_restrict image, const Quantum *magick_restrict pixel)
MagickExport MagickBooleanType IsStringTrue(const char *value)
Definition: string.c:1415
static void GetPixelInfoPixel(const Image *magick_restrict image, const Quantum *magick_restrict pixel, PixelInfo *magick_restrict pixel_info)
PixelTrait alpha_trait
Definition: image.h:280
MagickExport int GetMagickPrecision(void)
Definition: magick.c:942
static Quantum GetPixelIndex(const Image *magick_restrict image, const Quantum *magick_restrict pixel)
static Quantum GetPixelBlack(const Image *magick_restrict image, const Quantum *magick_restrict pixel)
MagickExport Quantum * QueueCacheViewAuthenticPixels(CacheView *cache_view, const ssize_t x, const ssize_t y, const size_t columns, const size_t rows, ExceptionInfo *exception)
Definition: cache-view.c:977
MagickExport MatrixInfo * AcquireMatrixInfo(const size_t columns, const size_t rows, const size_t stride, ExceptionInfo *exception)
Definition: matrix.c:200
MagickExport MagickBooleanType LogMagickEvent(const LogEventType type, const char *module, const char *function, const size_t line, const char *format,...)
Definition: log.c:1660
size_t signature
Definition: image.h:354
#define QuantumScale
Definition: magick-type.h:119
size_t columns
Definition: image.h:172
MagickBooleanType(* MagickProgressMonitor)(const char *, const MagickOffsetType, const MagickSizeType, void *)
Definition: monitor.h:26
ssize_t x
Definition: geometry.h:134
size_t height
Definition: geometry.h:130
MagickExport MagickBooleanType QueryColorCompliance(const char *name, const ComplianceType compliance, PixelInfo *color, ExceptionInfo *exception)
Definition: color.c:2180
size_t colors
Definition: image.h:172
static size_t GetPixelChannels(const Image *magick_restrict image)
MagickExport MagickBooleanType AcquireImageColormap(Image *image, const size_t colors, ExceptionInfo *exception)
Definition: colormap.c:104
char filename[MagickPathExtent]
Definition: image.h:319
#define GetMagickModule()
Definition: log.h:28
#define ThrowImageException(severity, tag)
MagickExport CacheView * AcquireVirtualCacheView(const Image *image, ExceptionInfo *exception)
Definition: cache-view.c:149
static double RadiansToDegrees(const double radians)
Definition: image-private.h:58
double area
Definition: vision.h:42
unsigned short Quantum
Definition: magick-type.h:86
MagickExport MagickBooleanType IsFuzzyEquivalencePixelInfo(const PixelInfo *p, const PixelInfo *q)
Definition: pixel.c:6065
static void SetPixelIndex(const Image *magick_restrict image, const Quantum index, Quantum *magick_restrict pixel)
#define MagickMin(x, y)
Definition: image-private.h:37
MagickExport void * RelinquishMagickMemory(void *memory)
Definition: memory.c:1122
static int CCObjectInfoCompare(const void *x, const void *y)
Definition: vision.c:120
#define MagickExport
MagickExport MagickBooleanType SyncCacheViewAuthenticPixels(CacheView *magick_restrict cache_view, ExceptionInfo *exception)
Definition: cache-view.c:1100
ssize_t y
Definition: geometry.h:134
MagickExport CacheView * AcquireAuthenticCacheView(const Image *image, ExceptionInfo *exception)
Definition: cache-view.c:112
static Quantum GetPixelBlue(const Image *magick_restrict image, const Quantum *magick_restrict pixel)
#define ConnectedComponentsImageTag
MagickExport Image * DestroyImage(Image *image)
Definition: image.c:1160
MagickExport Image * CloneImage(const Image *image, const size_t columns, const size_t rows, const MagickBooleanType detach, ExceptionInfo *exception)
Definition: image.c:775
ColorspaceType colorspace
Definition: image.h:157
MagickExport MagickBooleanType SetImageProgress(const Image *image, const char *tag, const MagickOffsetType offset, const MagickSizeType extent)
Definition: monitor.c:136
MagickBooleanType debug
Definition: image.h:334
MagickExport MatrixInfo * DestroyMatrixInfo(MatrixInfo *matrix_info)
Definition: matrix.c:369
size_t depth
Definition: image.h:172