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"
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 *image_view,
138 *component_view;
139
140 CCObjectInfo
141 *object;
142
143 char
144 *c;
145
146 const char
147 *artifact;
148
149 double
150 area_threshold;
151
152 Image
153 *component_image;
154
155 MagickBooleanType
156 status;
157
158 MagickOffsetType
159 progress;
160
161 MatrixInfo
162 *equivalences;
163
164 register ssize_t
165 i;
166
167 size_t
168 size;
169
170 ssize_t
171 first,
172 last,
173 n,
174 step,
175 y;
176
177 /*
178 Initialize connected components image attributes.
179 */
180 assert(image != (Image *) NULL);
181 assert(image->signature == MagickCoreSignature);
182 if (image->debug != MagickFalse)
183 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
184 assert(exception != (ExceptionInfo *) NULL);
185 assert(exception->signature == MagickCoreSignature);
186 if (objects != (CCObjectInfo **) NULL)
187 *objects=(CCObjectInfo *) NULL;
188 component_image=CloneImage(image,0,0,MagickTrue,exception);
189 if (component_image == (Image *) NULL)
190 return((Image *) NULL);
191 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
192 if (AcquireImageColormap(component_image,MaxColormapSize,exception) == MagickFalse)
193 {
194 component_image=DestroyImage(component_image);
195 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
196 }
197 /*
198 Initialize connected components equivalences.
199 */
200 size=image->columns*image->rows;
201 if (image->columns != (size/image->rows))
202 {
203 component_image=DestroyImage(component_image);
204 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
205 }
206 equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
207 if (equivalences == (MatrixInfo *) NULL)
208 {
209 component_image=DestroyImage(component_image);
210 return((Image *) NULL);
211 }
212 for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
213 (void) SetMatrixElement(equivalences,n,0,&n);
214 object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,sizeof(*object));
215 if (object == (CCObjectInfo *) NULL)
216 {
217 equivalences=DestroyMatrixInfo(equivalences);
218 component_image=DestroyImage(component_image);
219 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
220 }
221 (void) memset(object,0,MaxColormapSize*sizeof(*object));
222 for (i=0; i < (ssize_t) MaxColormapSize; i++)
223 {
224 object[i].id=i;
225 object[i].bounding_box.x=(ssize_t) image->columns;
226 object[i].bounding_box.y=(ssize_t) image->rows;
227 GetPixelInfo(image,&object[i].color);
228 }
229 /*
230 Find connected components.
231 */
232 status=MagickTrue;
233 progress=0;
234 image_view=AcquireVirtualCacheView(image,exception);
235 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
236 {
237 ssize_t
238 connect4[2][2] = { { -1, 0 }, { 0, -1 } },
239 connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
240 dx,
241 dy;
242
243 if (status == MagickFalse)
244 continue;
245 dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
246 dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
247 for (y=0; y < (ssize_t) image->rows; y++)
248 {
249 register const Quantum
250 *magick_restrict p;
251
252 register ssize_t
253 x;
254
255 if (status == MagickFalse)
256 continue;
257 p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
258 if (p == (const Quantum *) NULL)
259 {
260 status=MagickFalse;
261 continue;
262 }
263 p+=GetPixelChannels(image)*image->columns;
264 for (x=0; x < (ssize_t) image->columns; x++)
265 {
266 PixelInfo
267 pixel,
268 target;
269
270 ssize_t
271 neighbor_offset,
272 obj,
273 offset,
274 ox,
275 oy,
276 root;
277
278 /*
279 Is neighbor an authentic pixel and a different color than the pixel?
280 */
281 GetPixelInfoPixel(image,p,&pixel);
282 if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
283 ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
284 {
285 p+=GetPixelChannels(image);
286 continue;
287 }
288 neighbor_offset=dy*(GetPixelChannels(image)*image->columns)+dx*
289 GetPixelChannels(image);
290 GetPixelInfoPixel(image,p+neighbor_offset,&target);
291 if (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse)
292 {
293 p+=GetPixelChannels(image);
294 continue;
295 }
296 /*
297 Resolve this equivalence.
298 */
299 offset=y*image->columns+x;
300 neighbor_offset=dy*image->columns+dx;
301 ox=offset;
302 status=GetMatrixElement(equivalences,ox,0,&obj);
303 while (obj != ox)
304 {
305 ox=obj;
306 status=GetMatrixElement(equivalences,ox,0,&obj);
307 }
308 oy=offset+neighbor_offset;
309 status=GetMatrixElement(equivalences,oy,0,&obj);
310 while (obj != oy)
311 {
312 oy=obj;
313 status=GetMatrixElement(equivalences,oy,0,&obj);
314 }
315 if (ox < oy)
316 {
317 status=SetMatrixElement(equivalences,oy,0,&ox);
318 root=ox;
319 }
320 else
321 {
322 status=SetMatrixElement(equivalences,ox,0,&oy);
323 root=oy;
324 }
325 ox=offset;
326 status=GetMatrixElement(equivalences,ox,0,&obj);
327 while (obj != root)
328 {
329 status=GetMatrixElement(equivalences,ox,0,&obj);
330 status=SetMatrixElement(equivalences,ox,0,&root);
331 }
332 oy=offset+neighbor_offset;
333 status=GetMatrixElement(equivalences,oy,0,&obj);
334 while (obj != root)
335 {
336 status=GetMatrixElement(equivalences,oy,0,&obj);
337 status=SetMatrixElement(equivalences,oy,0,&root);
338 }
339 status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
340 p+=GetPixelChannels(image);
341 }
342 }
343 }
344 image_view=DestroyCacheView(image_view);
345 /*
346 Label connected components.
347 */
348 n=0;
349 image_view=AcquireVirtualCacheView(image,exception);
350 component_view=AcquireAuthenticCacheView(component_image,exception);
351 for (y=0; y < (ssize_t) component_image->rows; y++)
352 {
353 register const Quantum
354 *magick_restrict p;
355
356 register Quantum
357 *magick_restrict q;
358
359 register ssize_t
360 x;
361
362 if (status == MagickFalse)
363 continue;
364 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
365 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
366 1,exception);
367 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
368 {
369 status=MagickFalse;
370 continue;
371 }
372 for (x=0; x < (ssize_t) component_image->columns; x++)
373 {
374 ssize_t
375 id,
376 offset;
377
378 offset=y*image->columns+x;
379 status=GetMatrixElement(equivalences,offset,0,&id);
380 if (id != offset)
381 status=GetMatrixElement(equivalences,id,0,&id);
382 else
383 {
384 id=n++;
385 if (id >= (ssize_t) MaxColormapSize)
386 break;
387 }
388 status=SetMatrixElement(equivalences,offset,0,&id);
389 if (x < object[id].bounding_box.x)
390 object[id].bounding_box.x=x;
391 if (x >= (ssize_t) object[id].bounding_box.width)
392 object[id].bounding_box.width=(size_t) x;
393 if (y < object[id].bounding_box.y)
394 object[id].bounding_box.y=y;
395 if (y >= (ssize_t) object[id].bounding_box.height)
396 object[id].bounding_box.height=(size_t) y;
397 object[id].color.red+=QuantumScale*GetPixelRed(image,p);
398 object[id].color.green+=QuantumScale*GetPixelGreen(image,p);
399 object[id].color.blue+=QuantumScale*GetPixelBlue(image,p);
400 if (image->alpha_trait != UndefinedPixelTrait)
401 object[id].color.alpha+=QuantumScale*GetPixelAlpha(image,p);
402 if (image->colorspace == CMYKColorspace)
403 object[id].color.black+=QuantumScale*GetPixelBlack(image,p);
404 object[id].centroid.x+=x;
405 object[id].centroid.y+=y;
406 object[id].area++;
407 SetPixelIndex(component_image,(Quantum) id,q);
408 p+=GetPixelChannels(image);
409 q+=GetPixelChannels(component_image);
410 }
411 if (n > (ssize_t) MaxColormapSize)
412 break;
413 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
414 status=MagickFalse;
415 if (image->progress_monitor != (MagickProgressMonitor) NULL)
416 {
417 MagickBooleanType
418 proceed;
419
420 progress++;
421 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
422 image->rows);
423 if (proceed == MagickFalse)
424 status=MagickFalse;
425 }
426 }
427 component_view=DestroyCacheView(component_view);
428 image_view=DestroyCacheView(image_view);
429 equivalences=DestroyMatrixInfo(equivalences);
430 if (n > (ssize_t) MaxColormapSize)
431 {
432 object=(CCObjectInfo *) RelinquishMagickMemory(object);
433 component_image=DestroyImage(component_image);
434 ThrowImageException(ResourceLimitError,"TooManyObjects");
435 }
436 component_image->colors=(size_t) n;
437 for (i=0; i < (ssize_t) component_image->colors; i++)
438 {
439 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
440 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
441 object[i].color.red=QuantumRange*(object[i].color.red/object[i].area);
442 object[i].color.green=QuantumRange*(object[i].color.green/object[i].area);
443 object[i].color.blue=QuantumRange*(object[i].color.blue/object[i].area);
444 if (image->alpha_trait != UndefinedPixelTrait)
445 object[i].color.alpha=QuantumRange*(object[i].color.alpha/object[i].area);
446 if (image->colorspace == CMYKColorspace)
447 object[i].color.black=QuantumRange*(object[i].color.black/object[i].area);
448 object[i].centroid.x=object[i].centroid.x/object[i].area;
449 object[i].centroid.y=object[i].centroid.y/object[i].area;
450 }
451 artifact=GetImageArtifact(image,"connected-components:area-threshold");
452 area_threshold=0.0;
453 if (artifact != (const char *) NULL)
454 area_threshold=StringToDouble(artifact,(char **) NULL);
455 if (area_threshold > 0.0)
456 {
457 /*
458 Merge object below area threshold.
459 */
460 component_view=AcquireAuthenticCacheView(component_image,exception);
461 for (i=0; i < (ssize_t) component_image->colors; i++)
462 {
463 double
464 census;
465
466 RectangleInfo
467 bounding_box;
468
469 register ssize_t
470 j;
471
472 size_t
473 id;
474
475 if (status == MagickFalse)
476 continue;
477 if ((double) object[i].area >= area_threshold)
478 continue;
479 for (j=0; j < (ssize_t) component_image->colors; j++)
480 object[j].census=0;
481 bounding_box=object[i].bounding_box;
482 for (y=0; y < (ssize_t) bounding_box.height+2; y++)
483 {
484 register const Quantum
485 *magick_restrict p;
486
487 register ssize_t
488 x;
489
490 if (status == MagickFalse)
491 continue;
492 p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
493 bounding_box.y+y-1,bounding_box.width+2,1,exception);
494 if (p == (const Quantum *) NULL)
495 {
496 status=MagickFalse;
497 continue;
498 }
499 for (x=0; x < (ssize_t) bounding_box.width+2; x++)
500 {
501 j=(ssize_t) GetPixelIndex(component_image,p);
502 if (j != i)
503 object[j].census++;
504 p+=GetPixelChannels(component_image);
505 }
506 }
507 census=0;
508 id=0;
509 for (j=0; j < (ssize_t) component_image->colors; j++)
510 if (census < object[j].census)
511 {
512 census=object[j].census;
513 id=(size_t) j;
514 }
515 object[id].area+=object[i].area;
516 for (y=0; y < (ssize_t) bounding_box.height; y++)
517 {
518 register Quantum
519 *magick_restrict q;
520
521 register ssize_t
522 x;
523
524 if (status == MagickFalse)
525 continue;
526 q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
527 bounding_box.y+y,bounding_box.width,1,exception);
528 if (q == (Quantum *) NULL)
529 {
530 status=MagickFalse;
531 continue;
532 }
533 for (x=0; x < (ssize_t) bounding_box.width; x++)
534 {
535 if ((ssize_t) GetPixelIndex(component_image,q) == i)
536 SetPixelIndex(component_image,(Quantum) id,q);
537 q+=GetPixelChannels(component_image);
538 }
539 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
540 status=MagickFalse;
541 }
542 }
543 component_view=DestroyCacheView(component_view);
544 (void) SyncImage(component_image,exception);
545 }
546 artifact=GetImageArtifact(image,"connected-components:mean-color");
547 if (IsStringTrue(artifact) != MagickFalse)
548 {
549 /*
550 Replace object with mean color.
551 */
552 for (i=0; i < (ssize_t) component_image->colors; i++)
553 component_image->colormap[i]=object[i].color;
554 }
555 artifact=GetImageArtifact(image,"connected-components:keep");
556 if (artifact != (const char *) NULL)
557 {
558 /*
559 Keep these object (make others transparent).
560 */
561 for (i=0; i < (ssize_t) component_image->colors; i++)
562 object[i].census=0;
563 for (c=(char *) artifact; *c != '\0';)
564 {
565 while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
566 c++;
567 first=(ssize_t) strtol(c,&c,10);
568 if (first < 0)
569 first+=(ssize_t) component_image->colors;
570 last=first;
571 while (isspace((int) ((unsigned char) *c)) != 0)
572 c++;
573 if (*c == '-')
574 {
575 last=(ssize_t) strtol(c+1,&c,10);
576 if (last < 0)
577 last+=(ssize_t) component_image->colors;
578 }
579 step=(ssize_t) (first > last ? -1 : 1);
580 for ( ; first != (last+step); first+=step)
581 object[first].census++;
582 }
583 for (i=0; i < (ssize_t) component_image->colors; i++)
584 {
585 if (object[i].census != 0)
586 continue;
587 component_image->alpha_trait=BlendPixelTrait;
588 component_image->colormap[i].alpha_trait=BlendPixelTrait;
589 component_image->colormap[i].alpha=(MagickRealType) TransparentAlpha;
590 }
591 }
592 artifact=GetImageArtifact(image,"connected-components:remove");
593 if (artifact != (const char *) NULL)
594 {
595 /*
596 Remove these object (make them transparent).
597 */
598 for (c=(char *) artifact; *c != '\0';)
599 {
600 while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
601 c++;
602 first=(ssize_t) strtol(c,&c,10);
603 if (first < 0)
604 first+=(ssize_t) component_image->colors;
605 last=first;
606 while (isspace((int) ((unsigned char) *c)) != 0)
607 c++;
608 if (*c == '-')
609 {
610 last=(ssize_t) strtol(c+1,&c,10);
611 if (last < 0)
612 last+=(ssize_t) component_image->colors;
613 }
614 step=(ssize_t) (first > last ? -1 : 1);
615 for ( ; first != (last+step); first+=step)
616 {
617 component_image->alpha_trait=BlendPixelTrait;
618 component_image->colormap[first].alpha_trait=BlendPixelTrait;
619 component_image->colormap[first].alpha=(MagickRealType)
620 TransparentAlpha;
621 }
622 }
623 }
624 (void) SyncImage(component_image,exception);
625 artifact=GetImageArtifact(image,"connected-components:verbose");
626 if ((IsStringTrue(artifact) != MagickFalse) ||
627 (objects != (CCObjectInfo **) NULL))
628 {
629 /*
630 Report statistics on unique object.
631 */
632 for (i=0; i < (ssize_t) component_image->colors; i++)
633 {
634 object[i].bounding_box.width=0;
635 object[i].bounding_box.height=0;
636 object[i].bounding_box.x=(ssize_t) component_image->columns;
637 object[i].bounding_box.y=(ssize_t) component_image->rows;
638 object[i].centroid.x=0;
639 object[i].centroid.y=0;
640 object[i].area=0;
641 }
642 component_view=AcquireVirtualCacheView(component_image,exception);
643 for (y=0; y < (ssize_t) component_image->rows; y++)
644 {
645 register const Quantum
646 *magick_restrict p;
647
648 register ssize_t
649 x;
650
651 if (status == MagickFalse)
652 continue;
653 p=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,
654 1,exception);
655 if (p == (const Quantum *) NULL)
656 {
657 status=MagickFalse;
658 continue;
659 }
660 for (x=0; x < (ssize_t) component_image->columns; x++)
661 {
662 size_t
663 id;
664
665 id=GetPixelIndex(component_image,p);
666 if (x < object[id].bounding_box.x)
667 object[id].bounding_box.x=x;
668 if (x > (ssize_t) object[id].bounding_box.width)
669 object[id].bounding_box.width=(size_t) x;
670 if (y < object[id].bounding_box.y)
671 object[id].bounding_box.y=y;
672 if (y > (ssize_t) object[id].bounding_box.height)
673 object[id].bounding_box.height=(size_t) y;
674 object[id].centroid.x+=x;
675 object[id].centroid.y+=y;
676 object[id].area++;
677 p+=GetPixelChannels(component_image);
678 }
679 }
680 for (i=0; i < (ssize_t) component_image->colors; i++)
681 {
682 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
683 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
684 object[i].centroid.x=object[i].centroid.x/object[i].area;
685 object[i].centroid.y=object[i].centroid.y/object[i].area;
686 }
687 component_view=DestroyCacheView(component_view);
688 qsort((void *) object,component_image->colors,sizeof(*object),
689 CCObjectInfoCompare);
690 if (objects == (CCObjectInfo **) NULL)
691 {
692 (void) fprintf(stdout,
693 "Objects (id: bounding-box centroid area mean-color):\n");
694 for (i=0; i < (ssize_t) component_image->colors; i++)
695 {
696 char
697 mean_color[MagickPathExtent];
698
699 if (status == MagickFalse)
700 break;
701 if (object[i].area <= area_threshold)
702 continue;
703 GetColorTuple(&object[i].color,MagickFalse,mean_color);
704 (void) fprintf(stdout,
705 " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
706 object[i].id,(double) object[i].bounding_box.width,(double)
707 object[i].bounding_box.height,(double) object[i].bounding_box.x,
708 (double) object[i].bounding_box.y,object[i].centroid.x,
709 object[i].centroid.y,(double) object[i].area,mean_color);
710 }
711 }
712 }
713 if (objects == (CCObjectInfo **) NULL)
714 object=(CCObjectInfo *) RelinquishMagickMemory(object);
715 else
716 *objects=object;
717 return(component_image);
718 }
719