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