blob: 81cfd2c3865f65deb6989af9dc8efd84d1745b13 [file] [log] [blame]
cristy6e0b3bc2014-10-19 17:51:42 +00001/*
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-2014 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% http://www.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"
cristy016b7642014-10-25 13:09:57 +000040#include "MagickCore/artifact.h"
cristy6e0b3bc2014-10-19 17:51:42 +000041#include "MagickCore/blob.h"
42#include "MagickCore/cache-view.h"
43#include "MagickCore/color.h"
44#include "MagickCore/color-private.h"
45#include "MagickCore/colorspace.h"
46#include "MagickCore/constitute.h"
47#include "MagickCore/decorate.h"
48#include "MagickCore/distort.h"
49#include "MagickCore/draw.h"
50#include "MagickCore/enhance.h"
51#include "MagickCore/exception.h"
52#include "MagickCore/exception-private.h"
53#include "MagickCore/effect.h"
54#include "MagickCore/gem.h"
55#include "MagickCore/geometry.h"
56#include "MagickCore/image-private.h"
57#include "MagickCore/list.h"
58#include "MagickCore/log.h"
59#include "MagickCore/matrix.h"
60#include "MagickCore/memory_.h"
61#include "MagickCore/memory-private.h"
62#include "MagickCore/monitor.h"
63#include "MagickCore/monitor-private.h"
64#include "MagickCore/montage.h"
65#include "MagickCore/morphology.h"
66#include "MagickCore/morphology-private.h"
67#include "MagickCore/opencl-private.h"
68#include "MagickCore/paint.h"
69#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/pixel-private.h"
71#include "MagickCore/property.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/resource_.h"
74#include "MagickCore/signature-private.h"
75#include "MagickCore/string_.h"
cristyc6ca5712014-10-26 22:13:07 +000076#include "MagickCore/string-private.h"
cristy6e0b3bc2014-10-19 17:51:42 +000077#include "MagickCore/thread-private.h"
cristy016b7642014-10-25 13:09:57 +000078#include "MagickCore/token.h"
cristy6e0b3bc2014-10-19 17:51:42 +000079#include "MagickCore/vision.h"
80
81/*
82%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83% %
84% %
85% %
86% C o n n e c t e d C o m p o n e n t s I m a g e %
87% %
88% %
89% %
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91%
92% ConnectedComponentsImage() returns the connected-components of the image
93% uniquely labeled. Choose from 4 or 8-way connectivity.
94%
95% The format of the ConnectedComponentsImage method is:
96%
97% Image *ConnectedComponentsImage(const Image *image,
98% const size_t connectivity,ExceptionInfo *exception)
99%
100% A description of each parameter follows:
101%
102% o image: the image.
103%
cristy016b7642014-10-25 13:09:57 +0000104% o connectivity: how many neighbors to visit, choose from 4 or 8.
cristy6e0b3bc2014-10-19 17:51:42 +0000105%
106% o exception: return any errors or warnings in this structure.
107%
108*/
cristy016b7642014-10-25 13:09:57 +0000109
110typedef struct _CCObject
111{
112 ssize_t
113 id;
114
115 RectangleInfo
116 bounding_box;
117
cristyc6ca5712014-10-26 22:13:07 +0000118 PixelInfo
119 color;
120
cristy016b7642014-10-25 13:09:57 +0000121 PointInfo
122 centroid;
123
124 ssize_t
125 area;
126} CCObject;
127
128static int CCObjectCompare(const void *x,const void *y)
129{
130 CCObject
131 *p,
132 *q;
133
134 p=(CCObject *) x;
135 q=(CCObject *) y;
136 return((int) (q->area-(ssize_t) p->area));
137}
138
139static MagickBooleanType ConnectedComponentsStatistics(const Image *image,
cristyc6ca5712014-10-26 22:13:07 +0000140 const Image *component_image,const size_t number_objects,
141 ExceptionInfo *exception)
cristy016b7642014-10-25 13:09:57 +0000142{
143 CacheView
cristyc6ca5712014-10-26 22:13:07 +0000144 *component_view,
cristy016b7642014-10-25 13:09:57 +0000145 *image_view;
146
147 CCObject
148 *object;
149
150 MagickBooleanType
151 status;
152
153 register ssize_t
154 i;
155
156 ssize_t
157 y;
158
cristy7f4f7c62014-10-27 00:29:16 +0000159 /*
160 Collect statistics on unique objects.
161 */
cristy016b7642014-10-25 13:09:57 +0000162 object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
163 if (object == (CCObject *) NULL)
164 {
165 (void) ThrowMagickException(exception,GetMagickModule(),
166 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
167 return(MagickFalse);
168 }
169 (void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
170 for (i=0; i < (ssize_t) number_objects; i++)
171 {
172 object[i].id=i;
cristyc6ca5712014-10-26 22:13:07 +0000173 object[i].bounding_box.x=(ssize_t) component_image->columns;
174 object[i].bounding_box.y=(ssize_t) component_image->rows;
175 GetPixelInfo(image,&object[i].color);
cristy016b7642014-10-25 13:09:57 +0000176 }
cristy016b7642014-10-25 13:09:57 +0000177 status=MagickTrue;
178 image_view=AcquireVirtualCacheView(image,exception);
cristyc6ca5712014-10-26 22:13:07 +0000179 component_view=AcquireVirtualCacheView(component_image,exception);
cristy016b7642014-10-25 13:09:57 +0000180 for (y=0; y < (ssize_t) image->rows; y++)
181 {
182 register const Quantum
cristyc6ca5712014-10-26 22:13:07 +0000183 *restrict p,
184 *restrict q;
cristy016b7642014-10-25 13:09:57 +0000185
186 register ssize_t
187 x;
188
189 if (status == MagickFalse)
190 continue;
191 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristyc6ca5712014-10-26 22:13:07 +0000192 q=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,1,
193 exception);
194 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy016b7642014-10-25 13:09:57 +0000195 {
196 status=MagickFalse;
197 continue;
198 }
199 for (x=0; x < (ssize_t) image->columns; x++)
200 {
cristyc6ca5712014-10-26 22:13:07 +0000201 i=(ssize_t) *q;
cristy016b7642014-10-25 13:09:57 +0000202 if (x < object[i].bounding_box.x)
203 object[i].bounding_box.x=x;
204 if (x > (ssize_t) object[i].bounding_box.width)
205 object[i].bounding_box.width=(size_t) x;
206 if (y < object[i].bounding_box.y)
207 object[i].bounding_box.y=y;
208 if (y > (ssize_t) object[i].bounding_box.height)
209 object[i].bounding_box.height=(size_t) y;
cristyc6ca5712014-10-26 22:13:07 +0000210 object[i].color.red+=GetPixelRed(image,p);
211 object[i].color.green+=GetPixelGreen(image,p);
212 object[i].color.blue+=GetPixelBlue(image,p);
213 object[i].color.alpha+=GetPixelAlpha(image,p);
214 object[i].color.black+=GetPixelBlack(image,p);
cristyc75924c2014-10-25 14:58:34 +0000215 object[i].centroid.x+=x;
216 object[i].centroid.y+=y;
cristy016b7642014-10-25 13:09:57 +0000217 object[i].area++;
218 p+=GetPixelChannels(image);
cristyc6ca5712014-10-26 22:13:07 +0000219 q+=GetPixelChannels(component_image);
cristy016b7642014-10-25 13:09:57 +0000220 }
221 }
222 for (i=0; i < (ssize_t) number_objects; i++)
223 {
224 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
225 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
cristy40898402014-10-26 22:22:02 +0000226 object[i].color.red=(MagickRealType) (object[i].color.red/
227 object[i].area);
228 object[i].color.green=(MagickRealType) (object[i].color.green/
229 object[i].area);
230 object[i].color.blue=(MagickRealType) (object[i].color.blue/
231 object[i].area);
232 object[i].color.alpha=(MagickRealType) (object[i].color.alpha/
233 object[i].area);
234 object[i].color.black=(MagickRealType) (object[i].color.black/
235 object[i].area);
cristyc75924c2014-10-25 14:58:34 +0000236 object[i].centroid.x=object[i].centroid.x/object[i].area;
237 object[i].centroid.y=object[i].centroid.y/object[i].area;
cristy016b7642014-10-25 13:09:57 +0000238 }
cristyc6ca5712014-10-26 22:13:07 +0000239 component_view=DestroyCacheView(component_view);
cristy016b7642014-10-25 13:09:57 +0000240 image_view=DestroyCacheView(image_view);
cristy7f4f7c62014-10-27 00:29:16 +0000241 /*
242 Report statistics on unique objects.
243 */
cristy016b7642014-10-25 13:09:57 +0000244 qsort((void *) object,number_objects,sizeof(*object),CCObjectCompare);
cristyc6ca5712014-10-26 22:13:07 +0000245 (void) fprintf(stdout,
246 "Objects (id: bounding-box centroid area mean-color):\n");
cristy016b7642014-10-25 13:09:57 +0000247 for (i=0; i < (ssize_t) number_objects; i++)
248 {
cristyc6ca5712014-10-26 22:13:07 +0000249 char
250 mean_color[MaxTextExtent];
251
cristy016b7642014-10-25 13:09:57 +0000252 if (status == MagickFalse)
253 break;
cristyc6ca5712014-10-26 22:13:07 +0000254 GetColorTuple(&object[i].color,MagickFalse,mean_color);
cristy016b7642014-10-25 13:09:57 +0000255 (void) fprintf(stdout,
cristy7f4f7c62014-10-27 00:29:16 +0000256 " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
cristy016b7642014-10-25 13:09:57 +0000257 object[i].id,(double) object[i].bounding_box.width,(double)
258 object[i].bounding_box.height,(double) object[i].bounding_box.x,
259 (double) object[i].bounding_box.y,object[i].centroid.x,
cristyc6ca5712014-10-26 22:13:07 +0000260 object[i].centroid.y,(double) object[i].area,mean_color);
261 }
262 object=(CCObject *) RelinquishMagickMemory(object);
263 return(status);
264}
265
cristy7f4f7c62014-10-27 00:29:16 +0000266static MagickBooleanType MergeConnectedComponents(Image *image,
267 const size_t number_objects,const double area_threshold,
268 ExceptionInfo *exception)
cristyc6ca5712014-10-26 22:13:07 +0000269{
270 CacheView
cristyc6ca5712014-10-26 22:13:07 +0000271 *image_view;
272
273 CCObject
274 *object;
275
276 MagickBooleanType
277 status;
278
279 register ssize_t
280 i;
281
282 ssize_t
283 y;
284
cristy7f4f7c62014-10-27 00:29:16 +0000285 /*
286 Collect statistics on unique objects.
287 */
cristyc6ca5712014-10-26 22:13:07 +0000288 object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
289 if (object == (CCObject *) NULL)
290 {
291 (void) ThrowMagickException(exception,GetMagickModule(),
292 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
293 return(MagickFalse);
294 }
295 (void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
296 for (i=0; i < (ssize_t) number_objects; i++)
297 {
298 object[i].id=i;
cristy7f4f7c62014-10-27 00:29:16 +0000299 object[i].bounding_box.x=(ssize_t) image->columns;
300 object[i].bounding_box.y=(ssize_t) image->rows;
cristyc6ca5712014-10-26 22:13:07 +0000301 }
302 status=MagickTrue;
303 image_view=AcquireVirtualCacheView(image,exception);
cristyc6ca5712014-10-26 22:13:07 +0000304 for (y=0; y < (ssize_t) image->rows; y++)
305 {
306 register const Quantum
cristy7f4f7c62014-10-27 00:29:16 +0000307 *restrict p;
cristyc6ca5712014-10-26 22:13:07 +0000308
309 register ssize_t
310 x;
311
312 if (status == MagickFalse)
313 continue;
314 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy7f4f7c62014-10-27 00:29:16 +0000315 if (p == (const Quantum *) NULL)
cristyc6ca5712014-10-26 22:13:07 +0000316 {
317 status=MagickFalse;
318 continue;
319 }
320 for (x=0; x < (ssize_t) image->columns; x++)
321 {
cristy7f4f7c62014-10-27 00:29:16 +0000322 i=(ssize_t) *p;
cristyc6ca5712014-10-26 22:13:07 +0000323 if (x < object[i].bounding_box.x)
324 object[i].bounding_box.x=x;
325 if (x > (ssize_t) object[i].bounding_box.width)
326 object[i].bounding_box.width=(size_t) x;
327 if (y < object[i].bounding_box.y)
328 object[i].bounding_box.y=y;
329 if (y > (ssize_t) object[i].bounding_box.height)
330 object[i].bounding_box.height=(size_t) y;
cristyc6ca5712014-10-26 22:13:07 +0000331 p+=GetPixelChannels(image);
cristyc6ca5712014-10-26 22:13:07 +0000332 }
333 }
334 for (i=0; i < (ssize_t) number_objects; i++)
335 {
336 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
337 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
cristyc6ca5712014-10-26 22:13:07 +0000338 }
cristy7f4f7c62014-10-27 00:29:16 +0000339 /*
340 Merge objects below area threshold.
341 */
cristyc6ca5712014-10-26 22:13:07 +0000342 for (i=0; i < (ssize_t) number_objects; i++)
343 {
cristy016b7642014-10-25 13:09:57 +0000344 }
cristy7f4f7c62014-10-27 00:29:16 +0000345 image_view=DestroyCacheView(image_view);
cristy016b7642014-10-25 13:09:57 +0000346 object=(CCObject *) RelinquishMagickMemory(object);
347 return(status);
348}
349
cristy6e0b3bc2014-10-19 17:51:42 +0000350MagickExport Image *ConnectedComponentsImage(const Image *image,
351 const size_t connectivity,ExceptionInfo *exception)
352{
353#define ConnectedComponentsImageTag "ConnectedComponents/Image"
354
355 CacheView
356 *image_view,
357 *component_view;
358
cristy016b7642014-10-25 13:09:57 +0000359 const char
360 *artifact;
361
cristyc6ca5712014-10-26 22:13:07 +0000362 double
363 area_threshold;
364
cristy6e0b3bc2014-10-19 17:51:42 +0000365 Image
366 *component_image;
367
368 MagickBooleanType
369 status;
370
371 MagickOffsetType
372 progress;
373
cristy016b7642014-10-25 13:09:57 +0000374 MatrixInfo
375 *equivalences;
376
377 size_t
378 size;
379
cristy6e0b3bc2014-10-19 17:51:42 +0000380 ssize_t
cristy016b7642014-10-25 13:09:57 +0000381 n,
cristy6e0b3bc2014-10-19 17:51:42 +0000382 y;
383
384 /*
385 Initialize connected components image attributes.
386 */
387 assert(image != (Image *) NULL);
388 assert(image->signature == MagickSignature);
389 if (image->debug != MagickFalse)
390 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
391 assert(exception != (ExceptionInfo *) NULL);
392 assert(exception->signature == MagickSignature);
393 component_image=CloneImage(image,image->columns,image->rows,MagickTrue,
394 exception);
395 if (component_image == (Image *) NULL)
396 return((Image *) NULL);
cristy016b7642014-10-25 13:09:57 +0000397 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
398 component_image->colorspace=GRAYColorspace;
cristy6e0b3bc2014-10-19 17:51:42 +0000399 if (SetImageStorageClass(component_image,DirectClass,exception) == MagickFalse)
400 {
401 component_image=DestroyImage(component_image);
402 return((Image *) NULL);
403 }
404 /*
cristy016b7642014-10-25 13:09:57 +0000405 Initialize connected components equivalences.
406 */
407 size=image->columns*image->rows;
408 if (image->columns != (size/image->rows))
409 {
410 component_image=DestroyImage(component_image);
411 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
412 }
413 equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
414 if (equivalences == (MatrixInfo *) NULL)
415 {
416 component_image=DestroyImage(component_image);
417 return((Image *) NULL);
418 }
419 for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
420 status=SetMatrixElement(equivalences,n,0,&n);
421 /*
422 Find connected components.
cristy6e0b3bc2014-10-19 17:51:42 +0000423 */
424 status=MagickTrue;
425 progress=0;
426 image_view=AcquireVirtualCacheView(image,exception);
cristy016b7642014-10-25 13:09:57 +0000427 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
428 {
429 ssize_t
430 connect4[2][2] = { { -1, 0 }, { 0, -1 } },
431 connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
432 dx,
433 dy;
434
435 if (status == MagickFalse)
436 continue;
437 dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
438 dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
439 for (y=0; y < (ssize_t) image->rows; y++)
440 {
441 register const Quantum
442 *restrict p;
443
444 register ssize_t
445 x;
446
447 if (status == MagickFalse)
448 continue;
449 p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
450 if (p == (const Quantum *) NULL)
451 {
452 status=MagickFalse;
453 continue;
454 }
cristy00f8eb42014-10-25 13:46:10 +0000455 p+=image->columns*GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000456 for (x=0; x < (ssize_t) image->columns; x++)
457 {
458 PixelInfo
459 pixel,
460 target;
461
462 ssize_t
463 neighbor_offset,
464 object,
cristy00f8eb42014-10-25 13:46:10 +0000465 offset,
cristy016b7642014-10-25 13:09:57 +0000466 ox,
467 oy,
cristy016b7642014-10-25 13:09:57 +0000468 root;
469
470 /*
471 Is neighbor an authentic pixel and a different color than the pixel?
472 */
cristy00f8eb42014-10-25 13:46:10 +0000473 GetPixelInfoPixel(image,p,&pixel);
cristy016b7642014-10-25 13:09:57 +0000474 neighbor_offset=dy*(image->columns*GetPixelChannels(image))+dx*
475 GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000476 GetPixelInfoPixel(image,p+neighbor_offset,&target);
477 if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
478 ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows) ||
479 (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse))
480 {
481 p+=GetPixelChannels(image);
482 continue;
483 }
484 /*
485 Resolve this equivalence.
486 */
cristy00f8eb42014-10-25 13:46:10 +0000487 offset=y*image->columns+x;
488 neighbor_offset=dy*image->columns+dx;
489 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000490 status=GetMatrixElement(equivalences,ox,0,&object);
491 while (object != ox) {
492 ox=object;
493 status=GetMatrixElement(equivalences,ox,0,&object);
494 }
cristy00f8eb42014-10-25 13:46:10 +0000495 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000496 status=GetMatrixElement(equivalences,oy,0,&object);
497 while (object != oy) {
498 oy=object;
499 status=GetMatrixElement(equivalences,oy,0,&object);
500 }
501 if (ox < oy)
502 {
503 status=SetMatrixElement(equivalences,oy,0,&ox);
504 root=ox;
505 }
506 else
507 {
508 status=SetMatrixElement(equivalences,ox,0,&oy);
509 root=oy;
510 }
cristy00f8eb42014-10-25 13:46:10 +0000511 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000512 status=GetMatrixElement(equivalences,ox,0,&object);
513 while (object != root) {
514 status=GetMatrixElement(equivalences,ox,0,&object);
515 status=SetMatrixElement(equivalences,ox,0,&root);
516 }
cristy00f8eb42014-10-25 13:46:10 +0000517 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000518 status=GetMatrixElement(equivalences,oy,0,&object);
519 while (object != root) {
520 status=GetMatrixElement(equivalences,oy,0,&object);
521 status=SetMatrixElement(equivalences,oy,0,&root);
522 }
523 status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
524 p+=GetPixelChannels(image);
525 }
526 }
527 }
528 image_view=DestroyCacheView(image_view);
529 /*
530 Label connected components.
531 */
532 n=0;
cristy6e0b3bc2014-10-19 17:51:42 +0000533 component_view=AcquireAuthenticCacheView(component_image,exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000534 for (y=0; y < (ssize_t) component_image->rows; y++)
535 {
536 register Quantum
537 *restrict q;
538
539 register ssize_t
540 x;
541
542 if (status == MagickFalse)
543 continue;
544 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
545 1,exception);
546 if (q == (Quantum *) NULL)
547 {
548 status=MagickFalse;
549 continue;
550 }
551 for (x=0; x < (ssize_t) component_image->columns; x++)
552 {
cristy016b7642014-10-25 13:09:57 +0000553 ssize_t
554 object,
555 offset;
556
557 offset=y*image->columns+x;
558 status=GetMatrixElement(equivalences,offset,0,&object);
559 if (object == offset)
560 {
561 object=n++;
562 status=SetMatrixElement(equivalences,offset,0,&object);
563 }
564 else
565 {
566 status=GetMatrixElement(equivalences,object,0,&object);
567 status=SetMatrixElement(equivalences,offset,0,&object);
568 }
569 *q=(Quantum) (object > (ssize_t) QuantumRange ? (ssize_t) QuantumRange :
570 object);
571 q+=GetPixelChannels(component_image);
cristy6e0b3bc2014-10-19 17:51:42 +0000572 }
573 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
574 status=MagickFalse;
575 if (image->progress_monitor != (MagickProgressMonitor) NULL)
576 {
577 MagickBooleanType
578 proceed;
579
cristy6e0b3bc2014-10-19 17:51:42 +0000580 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress++,
581 image->rows);
582 if (proceed == MagickFalse)
583 status=MagickFalse;
584 }
585 }
586 component_view=DestroyCacheView(component_view);
cristy016b7642014-10-25 13:09:57 +0000587 equivalences=DestroyMatrixInfo(equivalences);
588 artifact=GetImageArtifact(image,"connected-components:verbose");
cristyc6ca5712014-10-26 22:13:07 +0000589 if (IsStringTrue(artifact) != MagickFalse)
590 status=ConnectedComponentsStatistics(image,component_image,(size_t) n,
591 exception);
592 artifact=GetImageArtifact(image,"connected-components:area-threshold");
cristy7f4f7c62014-10-27 00:29:16 +0000593 area_threshold=0.0;
cristyc6ca5712014-10-26 22:13:07 +0000594 if (artifact != (const char *) NULL)
595 area_threshold=StringToDouble(artifact,(char **) NULL);
cristy40898402014-10-26 22:22:02 +0000596 if (area_threshold > 0.0)
cristy7f4f7c62014-10-27 00:29:16 +0000597 status=MergeConnectedComponents(component_image,(size_t) n,area_threshold,
598 exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000599 if (status == MagickFalse)
600 component_image=DestroyImage(component_image);
601 return(component_image);
602}