blob: 8f9a908138e01a6a4e98d4538d7bdc6ccec8e092 [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
cristy82eae112014-10-27 11:37:17 +0000124 size_t
125 area,
126 census;
cristy016b7642014-10-25 13:09:57 +0000127} CCObject;
128
129static int CCObjectCompare(const void *x,const void *y)
130{
131 CCObject
132 *p,
133 *q;
134
135 p=(CCObject *) x;
136 q=(CCObject *) y;
137 return((int) (q->area-(ssize_t) p->area));
138}
139
140static MagickBooleanType ConnectedComponentsStatistics(const Image *image,
cristyc6ca5712014-10-26 22:13:07 +0000141 const Image *component_image,const size_t number_objects,
142 ExceptionInfo *exception)
cristy016b7642014-10-25 13:09:57 +0000143{
144 CacheView
cristyc6ca5712014-10-26 22:13:07 +0000145 *component_view,
cristy016b7642014-10-25 13:09:57 +0000146 *image_view;
147
148 CCObject
149 *object;
150
151 MagickBooleanType
152 status;
153
154 register ssize_t
155 i;
156
157 ssize_t
158 y;
159
cristy7f4f7c62014-10-27 00:29:16 +0000160 /*
161 Collect statistics on unique objects.
162 */
cristy016b7642014-10-25 13:09:57 +0000163 object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
164 if (object == (CCObject *) NULL)
165 {
166 (void) ThrowMagickException(exception,GetMagickModule(),
167 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
168 return(MagickFalse);
169 }
170 (void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
171 for (i=0; i < (ssize_t) number_objects; i++)
172 {
173 object[i].id=i;
cristyc6ca5712014-10-26 22:13:07 +0000174 object[i].bounding_box.x=(ssize_t) component_image->columns;
175 object[i].bounding_box.y=(ssize_t) component_image->rows;
176 GetPixelInfo(image,&object[i].color);
cristy016b7642014-10-25 13:09:57 +0000177 }
cristy016b7642014-10-25 13:09:57 +0000178 status=MagickTrue;
179 image_view=AcquireVirtualCacheView(image,exception);
cristyc6ca5712014-10-26 22:13:07 +0000180 component_view=AcquireVirtualCacheView(component_image,exception);
cristy016b7642014-10-25 13:09:57 +0000181 for (y=0; y < (ssize_t) image->rows; y++)
182 {
183 register const Quantum
cristyc6ca5712014-10-26 22:13:07 +0000184 *restrict p,
185 *restrict q;
cristy016b7642014-10-25 13:09:57 +0000186
187 register ssize_t
188 x;
189
190 if (status == MagickFalse)
191 continue;
192 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristyc6ca5712014-10-26 22:13:07 +0000193 q=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,1,
194 exception);
195 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy016b7642014-10-25 13:09:57 +0000196 {
197 status=MagickFalse;
198 continue;
199 }
200 for (x=0; x < (ssize_t) image->columns; x++)
201 {
cristyc6ca5712014-10-26 22:13:07 +0000202 i=(ssize_t) *q;
cristy016b7642014-10-25 13:09:57 +0000203 if (x < object[i].bounding_box.x)
204 object[i].bounding_box.x=x;
205 if (x > (ssize_t) object[i].bounding_box.width)
206 object[i].bounding_box.width=(size_t) x;
207 if (y < object[i].bounding_box.y)
208 object[i].bounding_box.y=y;
209 if (y > (ssize_t) object[i].bounding_box.height)
210 object[i].bounding_box.height=(size_t) y;
cristyc6ca5712014-10-26 22:13:07 +0000211 object[i].color.red+=GetPixelRed(image,p);
212 object[i].color.green+=GetPixelGreen(image,p);
213 object[i].color.blue+=GetPixelBlue(image,p);
214 object[i].color.alpha+=GetPixelAlpha(image,p);
215 object[i].color.black+=GetPixelBlack(image,p);
cristyc75924c2014-10-25 14:58:34 +0000216 object[i].centroid.x+=x;
217 object[i].centroid.y+=y;
cristy016b7642014-10-25 13:09:57 +0000218 object[i].area++;
219 p+=GetPixelChannels(image);
cristyc6ca5712014-10-26 22:13:07 +0000220 q+=GetPixelChannels(component_image);
cristy016b7642014-10-25 13:09:57 +0000221 }
222 }
223 for (i=0; i < (ssize_t) number_objects; i++)
224 {
225 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
226 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
cristy40898402014-10-26 22:22:02 +0000227 object[i].color.red=(MagickRealType) (object[i].color.red/
228 object[i].area);
229 object[i].color.green=(MagickRealType) (object[i].color.green/
230 object[i].area);
231 object[i].color.blue=(MagickRealType) (object[i].color.blue/
232 object[i].area);
233 object[i].color.alpha=(MagickRealType) (object[i].color.alpha/
234 object[i].area);
235 object[i].color.black=(MagickRealType) (object[i].color.black/
236 object[i].area);
cristyc75924c2014-10-25 14:58:34 +0000237 object[i].centroid.x=object[i].centroid.x/object[i].area;
238 object[i].centroid.y=object[i].centroid.y/object[i].area;
cristy016b7642014-10-25 13:09:57 +0000239 }
cristyc6ca5712014-10-26 22:13:07 +0000240 component_view=DestroyCacheView(component_view);
cristy016b7642014-10-25 13:09:57 +0000241 image_view=DestroyCacheView(image_view);
cristy7f4f7c62014-10-27 00:29:16 +0000242 /*
243 Report statistics on unique objects.
244 */
cristy016b7642014-10-25 13:09:57 +0000245 qsort((void *) object,number_objects,sizeof(*object),CCObjectCompare);
cristyc6ca5712014-10-26 22:13:07 +0000246 (void) fprintf(stdout,
247 "Objects (id: bounding-box centroid area mean-color):\n");
cristy016b7642014-10-25 13:09:57 +0000248 for (i=0; i < (ssize_t) number_objects; i++)
249 {
cristyc6ca5712014-10-26 22:13:07 +0000250 char
251 mean_color[MaxTextExtent];
252
cristy016b7642014-10-25 13:09:57 +0000253 if (status == MagickFalse)
254 break;
cristydfc9c0e2014-10-31 17:20:14 +0000255 if (object[i].area < MagickEpsilon)
256 continue;
cristyc6ca5712014-10-26 22:13:07 +0000257 GetColorTuple(&object[i].color,MagickFalse,mean_color);
cristy016b7642014-10-25 13:09:57 +0000258 (void) fprintf(stdout,
cristy7f4f7c62014-10-27 00:29:16 +0000259 " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
cristy016b7642014-10-25 13:09:57 +0000260 object[i].id,(double) object[i].bounding_box.width,(double)
261 object[i].bounding_box.height,(double) object[i].bounding_box.x,
262 (double) object[i].bounding_box.y,object[i].centroid.x,
cristyc6ca5712014-10-26 22:13:07 +0000263 object[i].centroid.y,(double) object[i].area,mean_color);
264 }
265 object=(CCObject *) RelinquishMagickMemory(object);
266 return(status);
267}
268
cristy7f4f7c62014-10-27 00:29:16 +0000269static MagickBooleanType MergeConnectedComponents(Image *image,
270 const size_t number_objects,const double area_threshold,
271 ExceptionInfo *exception)
cristyc6ca5712014-10-26 22:13:07 +0000272{
273 CacheView
cristyc6ca5712014-10-26 22:13:07 +0000274 *image_view;
275
276 CCObject
277 *object;
278
279 MagickBooleanType
280 status;
281
282 register ssize_t
283 i;
284
285 ssize_t
286 y;
287
cristy7f4f7c62014-10-27 00:29:16 +0000288 /*
289 Collect statistics on unique objects.
290 */
cristyc6ca5712014-10-26 22:13:07 +0000291 object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
292 if (object == (CCObject *) NULL)
293 {
294 (void) ThrowMagickException(exception,GetMagickModule(),
295 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
296 return(MagickFalse);
297 }
298 (void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
299 for (i=0; i < (ssize_t) number_objects; i++)
300 {
301 object[i].id=i;
cristy7f4f7c62014-10-27 00:29:16 +0000302 object[i].bounding_box.x=(ssize_t) image->columns;
303 object[i].bounding_box.y=(ssize_t) image->rows;
cristyc6ca5712014-10-26 22:13:07 +0000304 }
305 status=MagickTrue;
306 image_view=AcquireVirtualCacheView(image,exception);
cristyc6ca5712014-10-26 22:13:07 +0000307 for (y=0; y < (ssize_t) image->rows; y++)
308 {
309 register const Quantum
cristy7f4f7c62014-10-27 00:29:16 +0000310 *restrict p;
cristyc6ca5712014-10-26 22:13:07 +0000311
312 register ssize_t
313 x;
314
315 if (status == MagickFalse)
316 continue;
317 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy7f4f7c62014-10-27 00:29:16 +0000318 if (p == (const Quantum *) NULL)
cristyc6ca5712014-10-26 22:13:07 +0000319 {
320 status=MagickFalse;
321 continue;
322 }
323 for (x=0; x < (ssize_t) image->columns; x++)
324 {
cristy7f4f7c62014-10-27 00:29:16 +0000325 i=(ssize_t) *p;
cristyc6ca5712014-10-26 22:13:07 +0000326 if (x < object[i].bounding_box.x)
327 object[i].bounding_box.x=x;
328 if (x > (ssize_t) object[i].bounding_box.width)
329 object[i].bounding_box.width=(size_t) x;
330 if (y < object[i].bounding_box.y)
331 object[i].bounding_box.y=y;
332 if (y > (ssize_t) object[i].bounding_box.height)
333 object[i].bounding_box.height=(size_t) y;
cristy82eae112014-10-27 11:37:17 +0000334 object[i].area++;
cristyc6ca5712014-10-26 22:13:07 +0000335 p+=GetPixelChannels(image);
cristyc6ca5712014-10-26 22:13:07 +0000336 }
337 }
cristy730e8f22014-10-27 00:30:57 +0000338 image_view=DestroyCacheView(image_view);
cristyc6ca5712014-10-26 22:13:07 +0000339 for (i=0; i < (ssize_t) number_objects; i++)
340 {
341 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
342 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
cristyc6ca5712014-10-26 22:13:07 +0000343 }
cristy7f4f7c62014-10-27 00:29:16 +0000344 /*
345 Merge objects below area threshold.
346 */
cristy730e8f22014-10-27 00:30:57 +0000347 image_view=AcquireAuthenticCacheView(image,exception);
cristyc6ca5712014-10-26 22:13:07 +0000348 for (i=0; i < (ssize_t) number_objects; i++)
349 {
cristy82eae112014-10-27 11:37:17 +0000350 RectangleInfo
351 bounding_box;
352
353 register ssize_t
354 j;
355
356 size_t
357 census,
358 id;
359
360 if (status == MagickFalse)
361 continue;
362 if ((double) object[i].area >= area_threshold)
363 continue;
364 for (j=0; j < (ssize_t) number_objects; j++)
365 object[j].census=0;
366 bounding_box=object[i].bounding_box;
367 for (y=0; y < (ssize_t) bounding_box.height+2; y++)
368 {
369 register const Quantum
370 *restrict p;
371
372 register ssize_t
373 x;
374
375 if (status == MagickFalse)
376 continue;
377 p=GetCacheViewVirtualPixels(image_view,bounding_box.x-1,bounding_box.y+y-
378 1,bounding_box.width+2,1,exception);
379 if (p == (const Quantum *) NULL)
380 {
381 status=MagickFalse;
382 continue;
383 }
384 for (x=0; x < (ssize_t) bounding_box.width+2; x++)
385 {
386 j=(ssize_t) *p;
387 if (j != i)
388 object[j].census++;
389 p+=GetPixelChannels(image);
390 }
391 }
392 census=0;
393 id=0;
394 for (j=0; j < (ssize_t) number_objects; j++)
395 if (census < object[j].census)
396 {
397 census=object[j].census;
398 id=(size_t) j;
399 }
400 for (y=0; y < (ssize_t) bounding_box.height; y++)
401 {
402 register Quantum
403 *restrict q;
404
405 register ssize_t
406 x;
407
408 if (status == MagickFalse)
409 continue;
410 q=GetCacheViewAuthenticPixels(image_view,bounding_box.x,bounding_box.y+y,
411 bounding_box.width,1,exception);
412 if (q == (Quantum *) NULL)
413 {
414 status=MagickFalse;
415 continue;
416 }
417 for (x=0; x < (ssize_t) bounding_box.width; x++)
418 {
419 if ((ssize_t) *q == i)
420 *q=(Quantum) id;
421 q+=GetPixelChannels(image);
422 }
423 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
424 status=MagickFalse;
425 }
cristy016b7642014-10-25 13:09:57 +0000426 }
cristy7f4f7c62014-10-27 00:29:16 +0000427 image_view=DestroyCacheView(image_view);
cristy016b7642014-10-25 13:09:57 +0000428 object=(CCObject *) RelinquishMagickMemory(object);
429 return(status);
430}
431
cristy6e0b3bc2014-10-19 17:51:42 +0000432MagickExport Image *ConnectedComponentsImage(const Image *image,
433 const size_t connectivity,ExceptionInfo *exception)
434{
435#define ConnectedComponentsImageTag "ConnectedComponents/Image"
436
437 CacheView
438 *image_view,
439 *component_view;
440
cristy016b7642014-10-25 13:09:57 +0000441 const char
442 *artifact;
443
cristyc6ca5712014-10-26 22:13:07 +0000444 double
445 area_threshold;
446
cristy6e0b3bc2014-10-19 17:51:42 +0000447 Image
448 *component_image;
449
450 MagickBooleanType
451 status;
452
453 MagickOffsetType
454 progress;
455
cristy016b7642014-10-25 13:09:57 +0000456 MatrixInfo
457 *equivalences;
458
459 size_t
460 size;
461
cristy6e0b3bc2014-10-19 17:51:42 +0000462 ssize_t
cristy016b7642014-10-25 13:09:57 +0000463 n,
cristy6e0b3bc2014-10-19 17:51:42 +0000464 y;
465
466 /*
467 Initialize connected components image attributes.
468 */
469 assert(image != (Image *) NULL);
470 assert(image->signature == MagickSignature);
471 if (image->debug != MagickFalse)
472 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
473 assert(exception != (ExceptionInfo *) NULL);
474 assert(exception->signature == MagickSignature);
475 component_image=CloneImage(image,image->columns,image->rows,MagickTrue,
476 exception);
477 if (component_image == (Image *) NULL)
478 return((Image *) NULL);
cristy016b7642014-10-25 13:09:57 +0000479 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
480 component_image->colorspace=GRAYColorspace;
cristy6e0b3bc2014-10-19 17:51:42 +0000481 if (SetImageStorageClass(component_image,DirectClass,exception) == MagickFalse)
482 {
483 component_image=DestroyImage(component_image);
484 return((Image *) NULL);
485 }
486 /*
cristy016b7642014-10-25 13:09:57 +0000487 Initialize connected components equivalences.
488 */
489 size=image->columns*image->rows;
490 if (image->columns != (size/image->rows))
491 {
492 component_image=DestroyImage(component_image);
493 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
494 }
495 equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
496 if (equivalences == (MatrixInfo *) NULL)
497 {
498 component_image=DestroyImage(component_image);
499 return((Image *) NULL);
500 }
501 for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
502 status=SetMatrixElement(equivalences,n,0,&n);
503 /*
504 Find connected components.
cristy6e0b3bc2014-10-19 17:51:42 +0000505 */
506 status=MagickTrue;
507 progress=0;
508 image_view=AcquireVirtualCacheView(image,exception);
cristy016b7642014-10-25 13:09:57 +0000509 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
510 {
511 ssize_t
512 connect4[2][2] = { { -1, 0 }, { 0, -1 } },
513 connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
514 dx,
515 dy;
516
517 if (status == MagickFalse)
518 continue;
519 dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
520 dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
521 for (y=0; y < (ssize_t) image->rows; y++)
522 {
523 register const Quantum
524 *restrict p;
525
526 register ssize_t
527 x;
528
529 if (status == MagickFalse)
530 continue;
531 p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
532 if (p == (const Quantum *) NULL)
533 {
534 status=MagickFalse;
535 continue;
536 }
cristy00f8eb42014-10-25 13:46:10 +0000537 p+=image->columns*GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000538 for (x=0; x < (ssize_t) image->columns; x++)
539 {
540 PixelInfo
541 pixel,
542 target;
543
544 ssize_t
545 neighbor_offset,
546 object,
cristy00f8eb42014-10-25 13:46:10 +0000547 offset,
cristy016b7642014-10-25 13:09:57 +0000548 ox,
549 oy,
cristy016b7642014-10-25 13:09:57 +0000550 root;
551
552 /*
553 Is neighbor an authentic pixel and a different color than the pixel?
554 */
cristy00f8eb42014-10-25 13:46:10 +0000555 GetPixelInfoPixel(image,p,&pixel);
cristy016b7642014-10-25 13:09:57 +0000556 neighbor_offset=dy*(image->columns*GetPixelChannels(image))+dx*
557 GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000558 GetPixelInfoPixel(image,p+neighbor_offset,&target);
559 if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
560 ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows) ||
cristydfc9c0e2014-10-31 17:20:14 +0000561 (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse))
cristy016b7642014-10-25 13:09:57 +0000562 {
563 p+=GetPixelChannels(image);
564 continue;
565 }
566 /*
567 Resolve this equivalence.
568 */
cristy00f8eb42014-10-25 13:46:10 +0000569 offset=y*image->columns+x;
570 neighbor_offset=dy*image->columns+dx;
571 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000572 status=GetMatrixElement(equivalences,ox,0,&object);
573 while (object != ox) {
574 ox=object;
575 status=GetMatrixElement(equivalences,ox,0,&object);
576 }
cristy00f8eb42014-10-25 13:46:10 +0000577 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000578 status=GetMatrixElement(equivalences,oy,0,&object);
579 while (object != oy) {
580 oy=object;
581 status=GetMatrixElement(equivalences,oy,0,&object);
582 }
583 if (ox < oy)
584 {
585 status=SetMatrixElement(equivalences,oy,0,&ox);
586 root=ox;
587 }
588 else
589 {
590 status=SetMatrixElement(equivalences,ox,0,&oy);
591 root=oy;
592 }
cristy00f8eb42014-10-25 13:46:10 +0000593 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000594 status=GetMatrixElement(equivalences,ox,0,&object);
595 while (object != root) {
596 status=GetMatrixElement(equivalences,ox,0,&object);
597 status=SetMatrixElement(equivalences,ox,0,&root);
598 }
cristy00f8eb42014-10-25 13:46:10 +0000599 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000600 status=GetMatrixElement(equivalences,oy,0,&object);
601 while (object != root) {
602 status=GetMatrixElement(equivalences,oy,0,&object);
603 status=SetMatrixElement(equivalences,oy,0,&root);
604 }
605 status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
606 p+=GetPixelChannels(image);
607 }
608 }
609 }
610 image_view=DestroyCacheView(image_view);
611 /*
612 Label connected components.
613 */
614 n=0;
cristy6e0b3bc2014-10-19 17:51:42 +0000615 component_view=AcquireAuthenticCacheView(component_image,exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000616 for (y=0; y < (ssize_t) component_image->rows; y++)
617 {
618 register Quantum
619 *restrict q;
620
621 register ssize_t
622 x;
623
624 if (status == MagickFalse)
625 continue;
626 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
627 1,exception);
628 if (q == (Quantum *) NULL)
629 {
630 status=MagickFalse;
631 continue;
632 }
633 for (x=0; x < (ssize_t) component_image->columns; x++)
634 {
cristy016b7642014-10-25 13:09:57 +0000635 ssize_t
636 object,
637 offset;
638
639 offset=y*image->columns+x;
640 status=GetMatrixElement(equivalences,offset,0,&object);
641 if (object == offset)
642 {
643 object=n++;
644 status=SetMatrixElement(equivalences,offset,0,&object);
645 }
646 else
647 {
648 status=GetMatrixElement(equivalences,object,0,&object);
649 status=SetMatrixElement(equivalences,offset,0,&object);
650 }
651 *q=(Quantum) (object > (ssize_t) QuantumRange ? (ssize_t) QuantumRange :
652 object);
653 q+=GetPixelChannels(component_image);
cristy6e0b3bc2014-10-19 17:51:42 +0000654 }
655 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
656 status=MagickFalse;
657 if (image->progress_monitor != (MagickProgressMonitor) NULL)
658 {
659 MagickBooleanType
660 proceed;
661
cristy6e0b3bc2014-10-19 17:51:42 +0000662 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress++,
663 image->rows);
664 if (proceed == MagickFalse)
665 status=MagickFalse;
666 }
667 }
668 component_view=DestroyCacheView(component_view);
cristy016b7642014-10-25 13:09:57 +0000669 equivalences=DestroyMatrixInfo(equivalences);
cristyd86ee392014-10-28 10:16:44 +0000670 if (n > QuantumRange)
671 {
672 component_image=DestroyImage(component_image);
673 ThrowImageException(ResourceLimitError,"TooManyObjects");
674 }
cristyc6ca5712014-10-26 22:13:07 +0000675 artifact=GetImageArtifact(image,"connected-components:area-threshold");
cristy7f4f7c62014-10-27 00:29:16 +0000676 area_threshold=0.0;
cristyc6ca5712014-10-26 22:13:07 +0000677 if (artifact != (const char *) NULL)
678 area_threshold=StringToDouble(artifact,(char **) NULL);
cristy40898402014-10-26 22:22:02 +0000679 if (area_threshold > 0.0)
cristy7f4f7c62014-10-27 00:29:16 +0000680 status=MergeConnectedComponents(component_image,(size_t) n,area_threshold,
681 exception);
cristydfc9c0e2014-10-31 17:20:14 +0000682 artifact=GetImageArtifact(image,"connected-components:verbose");
683 if (IsStringTrue(artifact) != MagickFalse)
684 status=ConnectedComponentsStatistics(image,component_image,(size_t) n,
685 exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000686 if (status == MagickFalse)
687 component_image=DestroyImage(component_image);
688 return(component_image);
689}