blob: ce40d904545ad1b35f989b2c909f2a0dc3da0b29 [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"
cristy8bd0b702014-11-01 13:00:45 +000073#include "MagickCore/quantum-private.h"
cristy6e0b3bc2014-10-19 17:51:42 +000074#include "MagickCore/resource_.h"
75#include "MagickCore/signature-private.h"
76#include "MagickCore/string_.h"
cristyc6ca5712014-10-26 22:13:07 +000077#include "MagickCore/string-private.h"
cristy6e0b3bc2014-10-19 17:51:42 +000078#include "MagickCore/thread-private.h"
cristy016b7642014-10-25 13:09:57 +000079#include "MagickCore/token.h"
cristy6e0b3bc2014-10-19 17:51:42 +000080#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. 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%
cristy016b7642014-10-25 13:09:57 +0000105% o connectivity: how many neighbors to visit, choose from 4 or 8.
cristy6e0b3bc2014-10-19 17:51:42 +0000106%
107% o exception: return any errors or warnings in this structure.
108%
109*/
cristy016b7642014-10-25 13:09:57 +0000110
111typedef struct _CCObject
112{
113 ssize_t
114 id;
115
116 RectangleInfo
117 bounding_box;
118
cristyc6ca5712014-10-26 22:13:07 +0000119 PixelInfo
120 color;
121
cristy016b7642014-10-25 13:09:57 +0000122 PointInfo
123 centroid;
124
cristy82eae112014-10-27 11:37:17 +0000125 size_t
126 area,
127 census;
cristy016b7642014-10-25 13:09:57 +0000128} CCObject;
129
130static int CCObjectCompare(const void *x,const void *y)
131{
132 CCObject
133 *p,
134 *q;
135
136 p=(CCObject *) x;
137 q=(CCObject *) y;
138 return((int) (q->area-(ssize_t) p->area));
139}
140
cristy8bd0b702014-11-01 13:00:45 +0000141static MagickBooleanType GrayscaleConnectedComponents(Image *image,
142 const size_t number_objects,ExceptionInfo *exception)
cristy016b7642014-10-25 13:09:57 +0000143{
144 CacheView
145 *image_view;
146
cristy016b7642014-10-25 13:09:57 +0000147 MagickBooleanType
148 status;
149
cristy016b7642014-10-25 13:09:57 +0000150 ssize_t
151 y;
152
cristy7f4f7c62014-10-27 00:29:16 +0000153 /*
cristy8bd0b702014-11-01 13:00:45 +0000154 Grayscale image.
cristy7f4f7c62014-10-27 00:29:16 +0000155 */
cristy8bd0b702014-11-01 13:00:45 +0000156 if (number_objects > 255)
157 ThrowBinaryException(ResourceLimitError,"TooManyObjects",image->filename);
cristy016b7642014-10-25 13:09:57 +0000158 status=MagickTrue;
cristy8bd0b702014-11-01 13:00:45 +0000159 image_view=AcquireAuthenticCacheView(image,exception);
160#if defined(MAGICKCORE_OPENMP_SUPPORT)
161 #pragma omp parallel for schedule(static,4) shared(status) \
162 magick_threads(image,image,image->rows,1)
163#endif
cristy016b7642014-10-25 13:09:57 +0000164 for (y=0; y < (ssize_t) image->rows; y++)
165 {
cristy8bd0b702014-11-01 13:00:45 +0000166 register Quantum
cristyc6ca5712014-10-26 22:13:07 +0000167 *restrict q;
cristy016b7642014-10-25 13:09:57 +0000168
169 register ssize_t
170 x;
171
172 if (status == MagickFalse)
173 continue;
cristy8bd0b702014-11-01 13:00:45 +0000174 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
175 if (q == (Quantum *) NULL)
cristy016b7642014-10-25 13:09:57 +0000176 {
177 status=MagickFalse;
178 continue;
179 }
180 for (x=0; x < (ssize_t) image->columns; x++)
181 {
cristy8bd0b702014-11-01 13:00:45 +0000182 SetPixelRed(image,ScaleCharToQuantum(GetPixelRed(image,q)),q);
183 SetPixelGreen(image,ScaleCharToQuantum(GetPixelGreen(image,q)),q);
184 SetPixelBlue(image,ScaleCharToQuantum(GetPixelBlue(image,q)),q);
185 q+=GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000186 }
cristy8bd0b702014-11-01 13:00:45 +0000187 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
188 status=MagickFalse;
cristy016b7642014-10-25 13:09:57 +0000189 }
cristy016b7642014-10-25 13:09:57 +0000190 image_view=DestroyCacheView(image_view);
cristyc6ca5712014-10-26 22:13:07 +0000191 return(status);
192}
193
cristy7f4f7c62014-10-27 00:29:16 +0000194static MagickBooleanType MergeConnectedComponents(Image *image,
195 const size_t number_objects,const double area_threshold,
196 ExceptionInfo *exception)
cristyc6ca5712014-10-26 22:13:07 +0000197{
198 CacheView
cristyc6ca5712014-10-26 22:13:07 +0000199 *image_view;
200
201 CCObject
202 *object;
203
204 MagickBooleanType
205 status;
206
207 register ssize_t
208 i;
209
210 ssize_t
211 y;
212
cristy7f4f7c62014-10-27 00:29:16 +0000213 /*
214 Collect statistics on unique objects.
215 */
cristyc6ca5712014-10-26 22:13:07 +0000216 object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
217 if (object == (CCObject *) NULL)
218 {
219 (void) ThrowMagickException(exception,GetMagickModule(),
220 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
221 return(MagickFalse);
222 }
223 (void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
224 for (i=0; i < (ssize_t) number_objects; i++)
225 {
226 object[i].id=i;
cristy7f4f7c62014-10-27 00:29:16 +0000227 object[i].bounding_box.x=(ssize_t) image->columns;
228 object[i].bounding_box.y=(ssize_t) image->rows;
cristyc6ca5712014-10-26 22:13:07 +0000229 }
230 status=MagickTrue;
231 image_view=AcquireVirtualCacheView(image,exception);
cristyc6ca5712014-10-26 22:13:07 +0000232 for (y=0; y < (ssize_t) image->rows; y++)
233 {
234 register const Quantum
cristy7f4f7c62014-10-27 00:29:16 +0000235 *restrict p;
cristyc6ca5712014-10-26 22:13:07 +0000236
237 register ssize_t
238 x;
239
240 if (status == MagickFalse)
241 continue;
242 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy7f4f7c62014-10-27 00:29:16 +0000243 if (p == (const Quantum *) NULL)
cristyc6ca5712014-10-26 22:13:07 +0000244 {
245 status=MagickFalse;
246 continue;
247 }
248 for (x=0; x < (ssize_t) image->columns; x++)
249 {
cristy7f4f7c62014-10-27 00:29:16 +0000250 i=(ssize_t) *p;
cristyc6ca5712014-10-26 22:13:07 +0000251 if (x < object[i].bounding_box.x)
252 object[i].bounding_box.x=x;
253 if (x > (ssize_t) object[i].bounding_box.width)
254 object[i].bounding_box.width=(size_t) x;
255 if (y < object[i].bounding_box.y)
256 object[i].bounding_box.y=y;
257 if (y > (ssize_t) object[i].bounding_box.height)
258 object[i].bounding_box.height=(size_t) y;
cristy82eae112014-10-27 11:37:17 +0000259 object[i].area++;
cristyc6ca5712014-10-26 22:13:07 +0000260 p+=GetPixelChannels(image);
cristyc6ca5712014-10-26 22:13:07 +0000261 }
262 }
cristy730e8f22014-10-27 00:30:57 +0000263 image_view=DestroyCacheView(image_view);
cristyc6ca5712014-10-26 22:13:07 +0000264 for (i=0; i < (ssize_t) number_objects; i++)
265 {
266 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
267 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
cristyc6ca5712014-10-26 22:13:07 +0000268 }
cristy7f4f7c62014-10-27 00:29:16 +0000269 /*
270 Merge objects below area threshold.
271 */
cristy730e8f22014-10-27 00:30:57 +0000272 image_view=AcquireAuthenticCacheView(image,exception);
cristyc6ca5712014-10-26 22:13:07 +0000273 for (i=0; i < (ssize_t) number_objects; i++)
274 {
cristy82eae112014-10-27 11:37:17 +0000275 RectangleInfo
276 bounding_box;
277
278 register ssize_t
279 j;
280
281 size_t
282 census,
283 id;
284
285 if (status == MagickFalse)
286 continue;
287 if ((double) object[i].area >= area_threshold)
288 continue;
289 for (j=0; j < (ssize_t) number_objects; j++)
290 object[j].census=0;
291 bounding_box=object[i].bounding_box;
292 for (y=0; y < (ssize_t) bounding_box.height+2; y++)
293 {
294 register const Quantum
295 *restrict p;
296
297 register ssize_t
298 x;
299
300 if (status == MagickFalse)
301 continue;
302 p=GetCacheViewVirtualPixels(image_view,bounding_box.x-1,bounding_box.y+y-
303 1,bounding_box.width+2,1,exception);
304 if (p == (const Quantum *) NULL)
305 {
306 status=MagickFalse;
307 continue;
308 }
309 for (x=0; x < (ssize_t) bounding_box.width+2; x++)
310 {
311 j=(ssize_t) *p;
312 if (j != i)
313 object[j].census++;
314 p+=GetPixelChannels(image);
315 }
316 }
317 census=0;
318 id=0;
319 for (j=0; j < (ssize_t) number_objects; j++)
320 if (census < object[j].census)
321 {
322 census=object[j].census;
323 id=(size_t) j;
324 }
325 for (y=0; y < (ssize_t) bounding_box.height; y++)
326 {
327 register Quantum
328 *restrict q;
329
330 register ssize_t
331 x;
332
333 if (status == MagickFalse)
334 continue;
335 q=GetCacheViewAuthenticPixels(image_view,bounding_box.x,bounding_box.y+y,
336 bounding_box.width,1,exception);
337 if (q == (Quantum *) NULL)
338 {
339 status=MagickFalse;
340 continue;
341 }
342 for (x=0; x < (ssize_t) bounding_box.width; x++)
343 {
344 if ((ssize_t) *q == i)
345 *q=(Quantum) id;
346 q+=GetPixelChannels(image);
347 }
348 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
349 status=MagickFalse;
350 }
cristy016b7642014-10-25 13:09:57 +0000351 }
cristy7f4f7c62014-10-27 00:29:16 +0000352 image_view=DestroyCacheView(image_view);
cristy016b7642014-10-25 13:09:57 +0000353 object=(CCObject *) RelinquishMagickMemory(object);
354 return(status);
355}
356
cristy8bd0b702014-11-01 13:00:45 +0000357static MagickBooleanType StatisticsComponentsStatistics(const Image *image,
358 const Image *component_image,const size_t number_objects,
359 ExceptionInfo *exception)
360{
361 CacheView
362 *component_view,
363 *image_view;
364
365 CCObject
366 *object;
367
368 MagickBooleanType
369 status;
370
371 register ssize_t
372 i;
373
374 ssize_t
375 y;
376
377 /*
378 Collect statistics on unique objects.
379 */
380 object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
381 if (object == (CCObject *) NULL)
382 {
383 (void) ThrowMagickException(exception,GetMagickModule(),
384 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
385 return(MagickFalse);
386 }
387 (void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
388 for (i=0; i < (ssize_t) number_objects; i++)
389 {
390 object[i].id=i;
391 object[i].bounding_box.x=(ssize_t) component_image->columns;
392 object[i].bounding_box.y=(ssize_t) component_image->rows;
393 GetPixelInfo(image,&object[i].color);
394 }
395 status=MagickTrue;
396 image_view=AcquireVirtualCacheView(image,exception);
397 component_view=AcquireVirtualCacheView(component_image,exception);
398 for (y=0; y < (ssize_t) image->rows; y++)
399 {
400 register const Quantum
401 *restrict p,
402 *restrict q;
403
404 register ssize_t
405 x;
406
407 if (status == MagickFalse)
408 continue;
409 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
410 q=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,1,
411 exception);
412 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
413 {
414 status=MagickFalse;
415 continue;
416 }
417 for (x=0; x < (ssize_t) image->columns; x++)
418 {
419 i=(ssize_t) *q;
420 if (x < object[i].bounding_box.x)
421 object[i].bounding_box.x=x;
422 if (x > (ssize_t) object[i].bounding_box.width)
423 object[i].bounding_box.width=(size_t) x;
424 if (y < object[i].bounding_box.y)
425 object[i].bounding_box.y=y;
426 if (y > (ssize_t) object[i].bounding_box.height)
427 object[i].bounding_box.height=(size_t) y;
428 object[i].color.red+=GetPixelRed(image,p);
429 object[i].color.green+=GetPixelGreen(image,p);
430 object[i].color.blue+=GetPixelBlue(image,p);
431 object[i].color.alpha+=GetPixelAlpha(image,p);
432 object[i].color.black+=GetPixelBlack(image,p);
433 object[i].centroid.x+=x;
434 object[i].centroid.y+=y;
435 object[i].area++;
436 p+=GetPixelChannels(image);
437 q+=GetPixelChannels(component_image);
438 }
439 }
440 for (i=0; i < (ssize_t) number_objects; i++)
441 {
442 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
443 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
444 object[i].color.red=(MagickRealType) (object[i].color.red/
445 object[i].area);
446 object[i].color.green=(MagickRealType) (object[i].color.green/
447 object[i].area);
448 object[i].color.blue=(MagickRealType) (object[i].color.blue/
449 object[i].area);
450 object[i].color.alpha=(MagickRealType) (object[i].color.alpha/
451 object[i].area);
452 object[i].color.black=(MagickRealType) (object[i].color.black/
453 object[i].area);
454 object[i].centroid.x=object[i].centroid.x/object[i].area;
455 object[i].centroid.y=object[i].centroid.y/object[i].area;
456 }
457 component_view=DestroyCacheView(component_view);
458 image_view=DestroyCacheView(image_view);
459 /*
460 Report statistics on unique objects.
461 */
462 qsort((void *) object,number_objects,sizeof(*object),CCObjectCompare);
463 (void) fprintf(stdout,
464 "Objects (id: bounding-box centroid area mean-color):\n");
465 for (i=0; i < (ssize_t) number_objects; i++)
466 {
467 char
468 mean_color[MaxTextExtent];
469
470 if (status == MagickFalse)
471 break;
472 if (object[i].area < MagickEpsilon)
473 continue;
474 GetColorTuple(&object[i].color,MagickFalse,mean_color);
475 (void) fprintf(stdout,
476 " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
477 object[i].id,(double) object[i].bounding_box.width,(double)
478 object[i].bounding_box.height,(double) object[i].bounding_box.x,
479 (double) object[i].bounding_box.y,object[i].centroid.x,
480 object[i].centroid.y,(double) object[i].area,mean_color);
481 }
482 object=(CCObject *) RelinquishMagickMemory(object);
483 return(status);
484}
485
cristy6e0b3bc2014-10-19 17:51:42 +0000486MagickExport Image *ConnectedComponentsImage(const Image *image,
487 const size_t connectivity,ExceptionInfo *exception)
488{
489#define ConnectedComponentsImageTag "ConnectedComponents/Image"
490
491 CacheView
492 *image_view,
493 *component_view;
494
cristy016b7642014-10-25 13:09:57 +0000495 const char
496 *artifact;
497
cristyc6ca5712014-10-26 22:13:07 +0000498 double
499 area_threshold;
500
cristy6e0b3bc2014-10-19 17:51:42 +0000501 Image
502 *component_image;
503
504 MagickBooleanType
505 status;
506
507 MagickOffsetType
508 progress;
509
cristy016b7642014-10-25 13:09:57 +0000510 MatrixInfo
511 *equivalences;
512
513 size_t
514 size;
515
cristy6e0b3bc2014-10-19 17:51:42 +0000516 ssize_t
cristy016b7642014-10-25 13:09:57 +0000517 n,
cristy6e0b3bc2014-10-19 17:51:42 +0000518 y;
519
520 /*
521 Initialize connected components image attributes.
522 */
523 assert(image != (Image *) NULL);
524 assert(image->signature == MagickSignature);
525 if (image->debug != MagickFalse)
526 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
527 assert(exception != (ExceptionInfo *) NULL);
528 assert(exception->signature == MagickSignature);
529 component_image=CloneImage(image,image->columns,image->rows,MagickTrue,
530 exception);
531 if (component_image == (Image *) NULL)
532 return((Image *) NULL);
cristy016b7642014-10-25 13:09:57 +0000533 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
534 component_image->colorspace=GRAYColorspace;
cristy6e0b3bc2014-10-19 17:51:42 +0000535 if (SetImageStorageClass(component_image,DirectClass,exception) == MagickFalse)
536 {
537 component_image=DestroyImage(component_image);
538 return((Image *) NULL);
539 }
540 /*
cristy016b7642014-10-25 13:09:57 +0000541 Initialize connected components equivalences.
542 */
543 size=image->columns*image->rows;
544 if (image->columns != (size/image->rows))
545 {
546 component_image=DestroyImage(component_image);
547 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
548 }
549 equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
550 if (equivalences == (MatrixInfo *) NULL)
551 {
552 component_image=DestroyImage(component_image);
553 return((Image *) NULL);
554 }
555 for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
556 status=SetMatrixElement(equivalences,n,0,&n);
557 /*
558 Find connected components.
cristy6e0b3bc2014-10-19 17:51:42 +0000559 */
560 status=MagickTrue;
561 progress=0;
562 image_view=AcquireVirtualCacheView(image,exception);
cristy016b7642014-10-25 13:09:57 +0000563 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
564 {
565 ssize_t
566 connect4[2][2] = { { -1, 0 }, { 0, -1 } },
567 connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
568 dx,
569 dy;
570
571 if (status == MagickFalse)
572 continue;
573 dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
574 dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
575 for (y=0; y < (ssize_t) image->rows; y++)
576 {
577 register const Quantum
578 *restrict p;
579
580 register ssize_t
581 x;
582
583 if (status == MagickFalse)
584 continue;
585 p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
586 if (p == (const Quantum *) NULL)
587 {
588 status=MagickFalse;
589 continue;
590 }
cristy00f8eb42014-10-25 13:46:10 +0000591 p+=image->columns*GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000592 for (x=0; x < (ssize_t) image->columns; x++)
593 {
594 PixelInfo
595 pixel,
596 target;
597
598 ssize_t
599 neighbor_offset,
600 object,
cristy00f8eb42014-10-25 13:46:10 +0000601 offset,
cristy016b7642014-10-25 13:09:57 +0000602 ox,
603 oy,
cristy016b7642014-10-25 13:09:57 +0000604 root;
605
606 /*
607 Is neighbor an authentic pixel and a different color than the pixel?
608 */
cristy00f8eb42014-10-25 13:46:10 +0000609 GetPixelInfoPixel(image,p,&pixel);
cristy016b7642014-10-25 13:09:57 +0000610 neighbor_offset=dy*(image->columns*GetPixelChannels(image))+dx*
611 GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000612 GetPixelInfoPixel(image,p+neighbor_offset,&target);
613 if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
614 ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows) ||
cristydfc9c0e2014-10-31 17:20:14 +0000615 (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse))
cristy016b7642014-10-25 13:09:57 +0000616 {
617 p+=GetPixelChannels(image);
618 continue;
619 }
620 /*
621 Resolve this equivalence.
622 */
cristy00f8eb42014-10-25 13:46:10 +0000623 offset=y*image->columns+x;
624 neighbor_offset=dy*image->columns+dx;
625 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000626 status=GetMatrixElement(equivalences,ox,0,&object);
627 while (object != ox) {
628 ox=object;
629 status=GetMatrixElement(equivalences,ox,0,&object);
630 }
cristy00f8eb42014-10-25 13:46:10 +0000631 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000632 status=GetMatrixElement(equivalences,oy,0,&object);
633 while (object != oy) {
634 oy=object;
635 status=GetMatrixElement(equivalences,oy,0,&object);
636 }
637 if (ox < oy)
638 {
639 status=SetMatrixElement(equivalences,oy,0,&ox);
640 root=ox;
641 }
642 else
643 {
644 status=SetMatrixElement(equivalences,ox,0,&oy);
645 root=oy;
646 }
cristy00f8eb42014-10-25 13:46:10 +0000647 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000648 status=GetMatrixElement(equivalences,ox,0,&object);
649 while (object != root) {
650 status=GetMatrixElement(equivalences,ox,0,&object);
651 status=SetMatrixElement(equivalences,ox,0,&root);
652 }
cristy00f8eb42014-10-25 13:46:10 +0000653 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000654 status=GetMatrixElement(equivalences,oy,0,&object);
655 while (object != root) {
656 status=GetMatrixElement(equivalences,oy,0,&object);
657 status=SetMatrixElement(equivalences,oy,0,&root);
658 }
659 status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
660 p+=GetPixelChannels(image);
661 }
662 }
663 }
664 image_view=DestroyCacheView(image_view);
665 /*
666 Label connected components.
667 */
668 n=0;
cristy6e0b3bc2014-10-19 17:51:42 +0000669 component_view=AcquireAuthenticCacheView(component_image,exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000670 for (y=0; y < (ssize_t) component_image->rows; y++)
671 {
672 register Quantum
673 *restrict q;
674
675 register ssize_t
676 x;
677
678 if (status == MagickFalse)
679 continue;
680 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
681 1,exception);
682 if (q == (Quantum *) NULL)
683 {
684 status=MagickFalse;
685 continue;
686 }
687 for (x=0; x < (ssize_t) component_image->columns; x++)
688 {
cristy016b7642014-10-25 13:09:57 +0000689 ssize_t
690 object,
691 offset;
692
693 offset=y*image->columns+x;
694 status=GetMatrixElement(equivalences,offset,0,&object);
695 if (object == offset)
696 {
697 object=n++;
698 status=SetMatrixElement(equivalences,offset,0,&object);
699 }
700 else
701 {
702 status=GetMatrixElement(equivalences,object,0,&object);
703 status=SetMatrixElement(equivalences,offset,0,&object);
704 }
705 *q=(Quantum) (object > (ssize_t) QuantumRange ? (ssize_t) QuantumRange :
706 object);
707 q+=GetPixelChannels(component_image);
cristy6e0b3bc2014-10-19 17:51:42 +0000708 }
709 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
710 status=MagickFalse;
711 if (image->progress_monitor != (MagickProgressMonitor) NULL)
712 {
713 MagickBooleanType
714 proceed;
715
cristy6e0b3bc2014-10-19 17:51:42 +0000716 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress++,
717 image->rows);
718 if (proceed == MagickFalse)
719 status=MagickFalse;
720 }
721 }
722 component_view=DestroyCacheView(component_view);
cristy016b7642014-10-25 13:09:57 +0000723 equivalences=DestroyMatrixInfo(equivalences);
cristyd86ee392014-10-28 10:16:44 +0000724 if (n > QuantumRange)
725 {
726 component_image=DestroyImage(component_image);
727 ThrowImageException(ResourceLimitError,"TooManyObjects");
728 }
cristy8bd0b702014-11-01 13:00:45 +0000729 artifact=GetImageArtifact(image,"connected-components:grayscale");
730 if (IsStringTrue(artifact) != MagickFalse)
731 status=GrayscaleConnectedComponents(component_image,n,exception);
cristyc6ca5712014-10-26 22:13:07 +0000732 artifact=GetImageArtifact(image,"connected-components:area-threshold");
cristy7f4f7c62014-10-27 00:29:16 +0000733 area_threshold=0.0;
cristyc6ca5712014-10-26 22:13:07 +0000734 if (artifact != (const char *) NULL)
735 area_threshold=StringToDouble(artifact,(char **) NULL);
cristy40898402014-10-26 22:22:02 +0000736 if (area_threshold > 0.0)
cristy7f4f7c62014-10-27 00:29:16 +0000737 status=MergeConnectedComponents(component_image,(size_t) n,area_threshold,
738 exception);
cristydfc9c0e2014-10-31 17:20:14 +0000739 artifact=GetImageArtifact(image,"connected-components:verbose");
740 if (IsStringTrue(artifact) != MagickFalse)
cristy8bd0b702014-11-01 13:00:45 +0000741 status=StatisticsComponentsStatistics(image,component_image,(size_t) n,
cristydfc9c0e2014-10-31 17:20:14 +0000742 exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000743 if (status == MagickFalse)
744 component_image=DestroyImage(component_image);
745 return(component_image);
746}