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