blob: 0bbae0c0ac89dd450f79fd26b94c6e8a8fc56202 [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% %
cristyb56bb242014-11-25 17:12:48 +000020% Copyright 1999-2015 ImageMagick Studio LLC, a non-profit organization %
cristy6e0b3bc2014-10-19 17:51:42 +000021% 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
cristy3bdd9252014-12-21 20:01:43 +0000125 double
cristy82eae112014-10-27 11:37:17 +0000126 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
cristy7f4f7c62014-10-27 00:29:16 +0000141static MagickBooleanType MergeConnectedComponents(Image *image,
142 const size_t number_objects,const double area_threshold,
143 ExceptionInfo *exception)
cristyc6ca5712014-10-26 22:13:07 +0000144{
145 CacheView
cristyc6ca5712014-10-26 22:13:07 +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 */
cristyc6ca5712014-10-26 22:13:07 +0000163 object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
cristy3bdd9252014-12-21 20:01:43 +0000164 if (object == (CCObject *) NULL)
165 {
166 (void) ThrowMagickException(exception,GetMagickModule(),
167 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
168 return(MagickFalse);
169 }
cristyc6ca5712014-10-26 22:13:07 +0000170 (void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
cristy3bdd9252014-12-21 20:01:43 +0000171 for (i=0; i < (ssize_t) number_objects; i++)
172 {
cristyc6ca5712014-10-26 22:13:07 +0000173 object[i].id=i;
cristy7f4f7c62014-10-27 00:29:16 +0000174 object[i].bounding_box.x=(ssize_t) image->columns;
175 object[i].bounding_box.y=(ssize_t) image->rows;
cristyc6ca5712014-10-26 22:13:07 +0000176 }
177 status=MagickTrue;
178 image_view=AcquireVirtualCacheView(image,exception);
cristy3bdd9252014-12-21 20:01:43 +0000179 for (y=0; y < (ssize_t) image->rows; y++)
180 {
cristyc6ca5712014-10-26 22:13:07 +0000181 register const Quantum
cristy7f4f7c62014-10-27 00:29:16 +0000182 *restrict p;
cristyc6ca5712014-10-26 22:13:07 +0000183
184 register ssize_t
185 x;
186
187 if (status == MagickFalse)
188 continue;
189 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3bdd9252014-12-21 20:01:43 +0000190 if (p == (const Quantum *) NULL)
191 {
192 status=MagickFalse;
193 continue;
194 }
195 for (x=0; x < (ssize_t) image->columns; x++)
196 {
cristy2ce3d8d2015-07-19 18:53:01 +0000197 i=(ssize_t) GetPixelIntensity(image,p);
cristyc6ca5712014-10-26 22:13:07 +0000198 if (x < object[i].bounding_box.x)
199 object[i].bounding_box.x=x;
200 if (x > (ssize_t) object[i].bounding_box.width)
201 object[i].bounding_box.width=(size_t) x;
202 if (y < object[i].bounding_box.y)
203 object[i].bounding_box.y=y;
204 if (y > (ssize_t) object[i].bounding_box.height)
205 object[i].bounding_box.height=(size_t) y;
cristy82eae112014-10-27 11:37:17 +0000206 object[i].area++;
cristyc6ca5712014-10-26 22:13:07 +0000207 p+=GetPixelChannels(image);
cristyc6ca5712014-10-26 22:13:07 +0000208 }
209 }
cristy730e8f22014-10-27 00:30:57 +0000210 image_view=DestroyCacheView(image_view);
cristy3bdd9252014-12-21 20:01:43 +0000211 for (i=0; i < (ssize_t) number_objects; i++)
212 {
cristyc6ca5712014-10-26 22:13:07 +0000213 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
214 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
cristyc6ca5712014-10-26 22:13:07 +0000215 }
cristy7f4f7c62014-10-27 00:29:16 +0000216 /*
217 Merge objects below area threshold.
218 */
cristy730e8f22014-10-27 00:30:57 +0000219 image_view=AcquireAuthenticCacheView(image,exception);
cristy3bdd9252014-12-21 20:01:43 +0000220 for (i=0; i < (ssize_t) number_objects; i++)
221 {
222 double
223 census;
224
cristy82eae112014-10-27 11:37:17 +0000225 RectangleInfo
226 bounding_box;
227
228 register ssize_t
229 j;
230
231 size_t
cristy82eae112014-10-27 11:37:17 +0000232 id;
233
234 if (status == MagickFalse)
235 continue;
236 if ((double) object[i].area >= area_threshold)
237 continue;
238 for (j=0; j < (ssize_t) number_objects; j++)
239 object[j].census=0;
240 bounding_box=object[i].bounding_box;
cristy3bdd9252014-12-21 20:01:43 +0000241 for (y=0; y < (ssize_t) bounding_box.height+2; y++)
242 {
cristy82eae112014-10-27 11:37:17 +0000243 register const Quantum
244 *restrict p;
245
246 register ssize_t
247 x;
248
249 if (status == MagickFalse)
250 continue;
251 p=GetCacheViewVirtualPixels(image_view,bounding_box.x-1,bounding_box.y+y-
252 1,bounding_box.width+2,1,exception);
cristy3bdd9252014-12-21 20:01:43 +0000253 if (p == (const Quantum *) NULL)
254 {
255 status=MagickFalse;
256 continue;
257 }
258 for (x=0; x < (ssize_t) bounding_box.width+2; x++)
259 {
cristy2ce3d8d2015-07-19 18:53:01 +0000260 j=(ssize_t) GetPixelIntensity(image,p);
cristy82eae112014-10-27 11:37:17 +0000261 if (j != i)
262 object[j].census++;
263 p+=GetPixelChannels(image);
264 }
265 }
266 census=0;
267 id=0;
268 for (j=0; j < (ssize_t) number_objects; j++)
cristy98973fa2014-12-23 00:52:04 +0000269 if (census < object[j].census)
cristy3bdd9252014-12-21 20:01:43 +0000270 {
271 census=object[j].census;
272 id=(size_t) j;
273 }
cristybd807712014-12-20 02:18:13 +0000274 object[id].area+=object[i].area;
cristy3bdd9252014-12-21 20:01:43 +0000275 for (y=0; y < (ssize_t) bounding_box.height; y++)
276 {
cristy82eae112014-10-27 11:37:17 +0000277 register Quantum
278 *restrict q;
279
280 register ssize_t
281 x;
282
283 if (status == MagickFalse)
284 continue;
285 q=GetCacheViewAuthenticPixels(image_view,bounding_box.x,bounding_box.y+y,
286 bounding_box.width,1,exception);
cristy3bdd9252014-12-21 20:01:43 +0000287 if (q == (Quantum *) NULL)
288 {
289 status=MagickFalse;
290 continue;
291 }
292 for (x=0; x < (ssize_t) bounding_box.width; x++)
293 {
cristy2ce3d8d2015-07-19 18:53:01 +0000294 if ((ssize_t) GetPixelIntensity(image,q) == i)
cristy82eae112014-10-27 11:37:17 +0000295 *q=(Quantum) id;
296 q+=GetPixelChannels(image);
297 }
298 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
299 status=MagickFalse;
300 }
cristy016b7642014-10-25 13:09:57 +0000301 }
cristy7f4f7c62014-10-27 00:29:16 +0000302 image_view=DestroyCacheView(image_view);
cristy016b7642014-10-25 13:09:57 +0000303 object=(CCObject *) RelinquishMagickMemory(object);
304 return(status);
305}
306
cristy8bd0b702014-11-01 13:00:45 +0000307static MagickBooleanType StatisticsComponentsStatistics(const Image *image,
308 const Image *component_image,const size_t number_objects,
309 ExceptionInfo *exception)
310{
311 CacheView
312 *component_view,
313 *image_view;
314
315 CCObject
316 *object;
317
318 MagickBooleanType
319 status;
320
321 register ssize_t
322 i;
323
324 ssize_t
325 y;
326
327 /*
328 Collect statistics on unique objects.
329 */
330 object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
cristy3bdd9252014-12-21 20:01:43 +0000331 if (object == (CCObject *) NULL)
332 {
333 (void) ThrowMagickException(exception,GetMagickModule(),
334 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
335 return(MagickFalse);
336 }
cristy8bd0b702014-11-01 13:00:45 +0000337 (void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
cristy3bdd9252014-12-21 20:01:43 +0000338 for (i=0; i < (ssize_t) number_objects; i++)
339 {
cristy8bd0b702014-11-01 13:00:45 +0000340 object[i].id=i;
341 object[i].bounding_box.x=(ssize_t) component_image->columns;
342 object[i].bounding_box.y=(ssize_t) component_image->rows;
343 GetPixelInfo(image,&object[i].color);
344 }
345 status=MagickTrue;
346 image_view=AcquireVirtualCacheView(image,exception);
347 component_view=AcquireVirtualCacheView(component_image,exception);
cristy3bdd9252014-12-21 20:01:43 +0000348 for (y=0; y < (ssize_t) image->rows; y++)
349 {
cristy8bd0b702014-11-01 13:00:45 +0000350 register const Quantum
351 *restrict p,
352 *restrict q;
353
354 register ssize_t
355 x;
356
357 if (status == MagickFalse)
358 continue;
359 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
360 q=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,1,
361 exception);
cristy3bdd9252014-12-21 20:01:43 +0000362 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
363 {
364 status=MagickFalse;
365 continue;
366 }
367 for (x=0; x < (ssize_t) image->columns; x++)
368 {
cristy2ce3d8d2015-07-19 18:53:01 +0000369 i=(ssize_t) GetPixelIntensity(image,q);
cristy8bd0b702014-11-01 13:00:45 +0000370 if (x < object[i].bounding_box.x)
371 object[i].bounding_box.x=x;
372 if (x > (ssize_t) object[i].bounding_box.width)
373 object[i].bounding_box.width=(size_t) x;
374 if (y < object[i].bounding_box.y)
375 object[i].bounding_box.y=y;
376 if (y > (ssize_t) object[i].bounding_box.height)
377 object[i].bounding_box.height=(size_t) y;
378 object[i].color.red+=GetPixelRed(image,p);
379 object[i].color.green+=GetPixelGreen(image,p);
380 object[i].color.blue+=GetPixelBlue(image,p);
381 object[i].color.alpha+=GetPixelAlpha(image,p);
382 object[i].color.black+=GetPixelBlack(image,p);
383 object[i].centroid.x+=x;
384 object[i].centroid.y+=y;
385 object[i].area++;
386 p+=GetPixelChannels(image);
387 q+=GetPixelChannels(component_image);
388 }
389 }
cristy3bdd9252014-12-21 20:01:43 +0000390 for (i=0; i < (ssize_t) number_objects; i++)
391 {
cristy8bd0b702014-11-01 13:00:45 +0000392 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
393 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
cristy3bdd9252014-12-21 20:01:43 +0000394 object[i].color.red=object[i].color.red/object[i].area;
395 object[i].color.green=object[i].color.green/object[i].area;
396 object[i].color.blue=object[i].color.blue/object[i].area;
397 object[i].color.alpha=object[i].color.alpha/object[i].area;
398 object[i].color.black=object[i].color.black/object[i].area;
cristy8bd0b702014-11-01 13:00:45 +0000399 object[i].centroid.x=object[i].centroid.x/object[i].area;
400 object[i].centroid.y=object[i].centroid.y/object[i].area;
401 }
402 component_view=DestroyCacheView(component_view);
403 image_view=DestroyCacheView(image_view);
404 /*
405 Report statistics on unique objects.
406 */
407 qsort((void *) object,number_objects,sizeof(*object),CCObjectCompare);
408 (void) fprintf(stdout,
409 "Objects (id: bounding-box centroid area mean-color):\n");
cristy3bdd9252014-12-21 20:01:43 +0000410 for (i=0; i < (ssize_t) number_objects; i++)
411 {
cristy8bd0b702014-11-01 13:00:45 +0000412 char
cristy151b66d2015-04-15 10:50:31 +0000413 mean_color[MagickPathExtent];
cristy8bd0b702014-11-01 13:00:45 +0000414
415 if (status == MagickFalse)
416 break;
417 if (object[i].area < MagickEpsilon)
418 continue;
419 GetColorTuple(&object[i].color,MagickFalse,mean_color);
420 (void) fprintf(stdout,
421 " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
422 object[i].id,(double) object[i].bounding_box.width,(double)
423 object[i].bounding_box.height,(double) object[i].bounding_box.x,
424 (double) object[i].bounding_box.y,object[i].centroid.x,
425 object[i].centroid.y,(double) object[i].area,mean_color);
426 }
427 object=(CCObject *) RelinquishMagickMemory(object);
428 return(status);
429}
430
cristy6e0b3bc2014-10-19 17:51:42 +0000431MagickExport Image *ConnectedComponentsImage(const Image *image,
432 const size_t connectivity,ExceptionInfo *exception)
433{
434#define ConnectedComponentsImageTag "ConnectedComponents/Image"
435
436 CacheView
437 *image_view,
438 *component_view;
439
cristy016b7642014-10-25 13:09:57 +0000440 const char
441 *artifact;
442
cristyc6ca5712014-10-26 22:13:07 +0000443 double
444 area_threshold;
445
cristy6e0b3bc2014-10-19 17:51:42 +0000446 Image
447 *component_image;
448
449 MagickBooleanType
450 status;
451
452 MagickOffsetType
453 progress;
454
cristy016b7642014-10-25 13:09:57 +0000455 MatrixInfo
456 *equivalences;
457
458 size_t
459 size;
460
cristy6e0b3bc2014-10-19 17:51:42 +0000461 ssize_t
cristy016b7642014-10-25 13:09:57 +0000462 n,
cristy6e0b3bc2014-10-19 17:51:42 +0000463 y;
464
465 /*
466 Initialize connected components image attributes.
467 */
468 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000469 assert(image->signature == MagickCoreSignature);
cristy6e0b3bc2014-10-19 17:51:42 +0000470 if (image->debug != MagickFalse)
471 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
472 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000473 assert(exception->signature == MagickCoreSignature);
cristy6e0b3bc2014-10-19 17:51:42 +0000474 component_image=CloneImage(image,image->columns,image->rows,MagickTrue,
475 exception);
476 if (component_image == (Image *) NULL)
477 return((Image *) NULL);
cristy016b7642014-10-25 13:09:57 +0000478 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
479 component_image->colorspace=GRAYColorspace;
cristy7ac8c5b2014-11-16 14:29:44 +0000480 status=SetImageStorageClass(component_image,DirectClass,exception);
cristy3bdd9252014-12-21 20:01:43 +0000481 if (status == MagickFalse)
482 {
483 component_image=DestroyImage(component_image);
484 return((Image *) NULL);
485 }
cristy6e0b3bc2014-10-19 17:51:42 +0000486 /*
cristy016b7642014-10-25 13:09:57 +0000487 Initialize connected components equivalences.
488 */
489 size=image->columns*image->rows;
cristy3bdd9252014-12-21 20:01:43 +0000490 if (image->columns != (size/image->rows))
491 {
492 component_image=DestroyImage(component_image);
493 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
494 }
cristy016b7642014-10-25 13:09:57 +0000495 equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
cristy3bdd9252014-12-21 20:01:43 +0000496 if (equivalences == (MatrixInfo *) NULL)
497 {
498 component_image=DestroyImage(component_image);
499 return((Image *) NULL);
500 }
cristy016b7642014-10-25 13:09:57 +0000501 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);
cristy3bdd9252014-12-21 20:01:43 +0000509 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
510 {
cristy016b7642014-10-25 13:09:57 +0000511 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];
cristy3bdd9252014-12-21 20:01:43 +0000521 for (y=0; y < (ssize_t) image->rows; y++)
522 {
cristy016b7642014-10-25 13:09:57 +0000523 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);
cristy3bdd9252014-12-21 20:01:43 +0000532 if (p == (const Quantum *) NULL)
533 {
534 status=MagickFalse;
535 continue;
536 }
cristy4f7c4342014-12-07 15:55:04 +0000537 p+=GetPixelChannels(image)*image->columns;
cristy3bdd9252014-12-21 20:01:43 +0000538 for (x=0; x < (ssize_t) image->columns; x++)
539 {
cristy016b7642014-10-25 13:09:57 +0000540 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);
cristy4f7c4342014-12-07 15:55:04 +0000556 neighbor_offset=dy*(GetPixelChannels(image)*image->columns)+dx*
cristy016b7642014-10-25 13:09:57 +0000557 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) ||
cristy3bdd9252014-12-21 20:01:43 +0000561 (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse))
562 {
563 p+=GetPixelChannels(image);
564 continue;
565 }
cristy016b7642014-10-25 13:09:57 +0000566 /*
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);
cristy3bdd9252014-12-21 20:01:43 +0000573 while (object != ox)
574 {
cristy016b7642014-10-25 13:09:57 +0000575 ox=object;
576 status=GetMatrixElement(equivalences,ox,0,&object);
577 }
cristy00f8eb42014-10-25 13:46:10 +0000578 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000579 status=GetMatrixElement(equivalences,oy,0,&object);
cristy3bdd9252014-12-21 20:01:43 +0000580 while (object != oy)
581 {
cristy016b7642014-10-25 13:09:57 +0000582 oy=object;
583 status=GetMatrixElement(equivalences,oy,0,&object);
584 }
cristy3bdd9252014-12-21 20:01:43 +0000585 if (ox < oy)
586 {
587 status=SetMatrixElement(equivalences,oy,0,&ox);
588 root=ox;
589 }
590 else
591 {
592 status=SetMatrixElement(equivalences,ox,0,&oy);
593 root=oy;
594 }
cristy00f8eb42014-10-25 13:46:10 +0000595 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000596 status=GetMatrixElement(equivalences,ox,0,&object);
cristy3bdd9252014-12-21 20:01:43 +0000597 while (object != root)
598 {
cristy016b7642014-10-25 13:09:57 +0000599 status=GetMatrixElement(equivalences,ox,0,&object);
600 status=SetMatrixElement(equivalences,ox,0,&root);
601 }
cristy00f8eb42014-10-25 13:46:10 +0000602 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000603 status=GetMatrixElement(equivalences,oy,0,&object);
cristy3bdd9252014-12-21 20:01:43 +0000604 while (object != root)
605 {
cristy016b7642014-10-25 13:09:57 +0000606 status=GetMatrixElement(equivalences,oy,0,&object);
607 status=SetMatrixElement(equivalences,oy,0,&root);
608 }
609 status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
610 p+=GetPixelChannels(image);
611 }
612 }
613 }
614 image_view=DestroyCacheView(image_view);
615 /*
616 Label connected components.
617 */
618 n=0;
cristy6e0b3bc2014-10-19 17:51:42 +0000619 component_view=AcquireAuthenticCacheView(component_image,exception);
cristy3bdd9252014-12-21 20:01:43 +0000620 for (y=0; y < (ssize_t) component_image->rows; y++)
621 {
cristy6e0b3bc2014-10-19 17:51:42 +0000622 register Quantum
623 *restrict q;
624
625 register ssize_t
626 x;
627
628 if (status == MagickFalse)
629 continue;
630 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
631 1,exception);
cristy3bdd9252014-12-21 20:01:43 +0000632 if (q == (Quantum *) NULL)
633 {
634 status=MagickFalse;
635 continue;
636 }
637 for (x=0; x < (ssize_t) component_image->columns; x++)
638 {
cristy016b7642014-10-25 13:09:57 +0000639 ssize_t
640 object,
641 offset;
642
643 offset=y*image->columns+x;
644 status=GetMatrixElement(equivalences,offset,0,&object);
cristy3bdd9252014-12-21 20:01:43 +0000645 if (object == offset)
646 {
647 object=n++;
648 status=SetMatrixElement(equivalences,offset,0,&object);
649 }
650 else
651 {
652 status=GetMatrixElement(equivalences,object,0,&object);
653 status=SetMatrixElement(equivalences,offset,0,&object);
654 }
cristy016b7642014-10-25 13:09:57 +0000655 *q=(Quantum) (object > (ssize_t) QuantumRange ? (ssize_t) QuantumRange :
656 object);
657 q+=GetPixelChannels(component_image);
cristy6e0b3bc2014-10-19 17:51:42 +0000658 }
659 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
660 status=MagickFalse;
cristy3bdd9252014-12-21 20:01:43 +0000661 if (image->progress_monitor != (MagickProgressMonitor) NULL)
662 {
663 MagickBooleanType
664 proceed;
cristy6e0b3bc2014-10-19 17:51:42 +0000665
cristy3bdd9252014-12-21 20:01:43 +0000666 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress++,
667 image->rows);
668 if (proceed == MagickFalse)
669 status=MagickFalse;
670 }
cristy6e0b3bc2014-10-19 17:51:42 +0000671 }
672 component_view=DestroyCacheView(component_view);
cristy016b7642014-10-25 13:09:57 +0000673 equivalences=DestroyMatrixInfo(equivalences);
cristy3bdd9252014-12-21 20:01:43 +0000674 if (n > (ssize_t) QuantumRange)
675 {
676 component_image=DestroyImage(component_image);
677 ThrowImageException(ResourceLimitError,"TooManyObjects");
678 }
cristyc6ca5712014-10-26 22:13:07 +0000679 artifact=GetImageArtifact(image,"connected-components:area-threshold");
cristy7f4f7c62014-10-27 00:29:16 +0000680 area_threshold=0.0;
cristyc6ca5712014-10-26 22:13:07 +0000681 if (artifact != (const char *) NULL)
682 area_threshold=StringToDouble(artifact,(char **) NULL);
cristy40898402014-10-26 22:22:02 +0000683 if (area_threshold > 0.0)
cristy7f4f7c62014-10-27 00:29:16 +0000684 status=MergeConnectedComponents(component_image,(size_t) n,area_threshold,
685 exception);
cristydfc9c0e2014-10-31 17:20:14 +0000686 artifact=GetImageArtifact(image,"connected-components:verbose");
687 if (IsStringTrue(artifact) != MagickFalse)
cristy8bd0b702014-11-01 13:00:45 +0000688 status=StatisticsComponentsStatistics(image,component_image,(size_t) n,
cristydfc9c0e2014-10-31 17:20:14 +0000689 exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000690 if (status == MagickFalse)
691 component_image=DestroyImage(component_image);
692 return(component_image);
693}