blob: 906338b9378eb70709ea304f77d6354a08e302f3 [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
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));
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;
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);
cristyc6ca5712014-10-26 22:13:07 +0000179 for (y=0; y < (ssize_t) image->rows; y++)
180 {
181 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);
cristy7f4f7c62014-10-27 00:29:16 +0000190 if (p == (const Quantum *) NULL)
cristyc6ca5712014-10-26 22:13:07 +0000191 {
192 status=MagickFalse;
193 continue;
194 }
195 for (x=0; x < (ssize_t) image->columns; x++)
196 {
cristy7f4f7c62014-10-27 00:29:16 +0000197 i=(ssize_t) *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);
cristyc6ca5712014-10-26 22:13:07 +0000211 for (i=0; i < (ssize_t) number_objects; i++)
212 {
213 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);
cristyc6ca5712014-10-26 22:13:07 +0000220 for (i=0; i < (ssize_t) number_objects; i++)
221 {
cristy82eae112014-10-27 11:37:17 +0000222 RectangleInfo
223 bounding_box;
224
225 register ssize_t
226 j;
227
228 size_t
229 census,
230 id;
231
232 if (status == MagickFalse)
233 continue;
234 if ((double) object[i].area >= area_threshold)
235 continue;
236 for (j=0; j < (ssize_t) number_objects; j++)
237 object[j].census=0;
238 bounding_box=object[i].bounding_box;
239 for (y=0; y < (ssize_t) bounding_box.height+2; y++)
240 {
241 register const Quantum
242 *restrict p;
243
244 register ssize_t
245 x;
246
247 if (status == MagickFalse)
248 continue;
249 p=GetCacheViewVirtualPixels(image_view,bounding_box.x-1,bounding_box.y+y-
250 1,bounding_box.width+2,1,exception);
251 if (p == (const Quantum *) NULL)
252 {
253 status=MagickFalse;
254 continue;
255 }
256 for (x=0; x < (ssize_t) bounding_box.width+2; x++)
257 {
258 j=(ssize_t) *p;
259 if (j != i)
260 object[j].census++;
261 p+=GetPixelChannels(image);
262 }
263 }
264 census=0;
265 id=0;
266 for (j=0; j < (ssize_t) number_objects; j++)
267 if (census < object[j].census)
268 {
269 census=object[j].census;
270 id=(size_t) j;
271 }
272 for (y=0; y < (ssize_t) bounding_box.height; y++)
273 {
274 register Quantum
275 *restrict q;
276
277 register ssize_t
278 x;
279
280 if (status == MagickFalse)
281 continue;
282 q=GetCacheViewAuthenticPixels(image_view,bounding_box.x,bounding_box.y+y,
283 bounding_box.width,1,exception);
284 if (q == (Quantum *) NULL)
285 {
286 status=MagickFalse;
287 continue;
288 }
289 for (x=0; x < (ssize_t) bounding_box.width; x++)
290 {
291 if ((ssize_t) *q == i)
292 *q=(Quantum) id;
293 q+=GetPixelChannels(image);
294 }
295 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
296 status=MagickFalse;
297 }
cristy016b7642014-10-25 13:09:57 +0000298 }
cristy7f4f7c62014-10-27 00:29:16 +0000299 image_view=DestroyCacheView(image_view);
cristy016b7642014-10-25 13:09:57 +0000300 object=(CCObject *) RelinquishMagickMemory(object);
301 return(status);
302}
303
cristy8bd0b702014-11-01 13:00:45 +0000304static MagickBooleanType StatisticsComponentsStatistics(const Image *image,
305 const Image *component_image,const size_t number_objects,
306 ExceptionInfo *exception)
307{
308 CacheView
309 *component_view,
310 *image_view;
311
312 CCObject
313 *object;
314
315 MagickBooleanType
316 status;
317
318 register ssize_t
319 i;
320
321 ssize_t
322 y;
323
324 /*
325 Collect statistics on unique objects.
326 */
327 object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
328 if (object == (CCObject *) NULL)
329 {
330 (void) ThrowMagickException(exception,GetMagickModule(),
331 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
332 return(MagickFalse);
333 }
334 (void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
335 for (i=0; i < (ssize_t) number_objects; i++)
336 {
337 object[i].id=i;
338 object[i].bounding_box.x=(ssize_t) component_image->columns;
339 object[i].bounding_box.y=(ssize_t) component_image->rows;
340 GetPixelInfo(image,&object[i].color);
341 }
342 status=MagickTrue;
343 image_view=AcquireVirtualCacheView(image,exception);
344 component_view=AcquireVirtualCacheView(component_image,exception);
345 for (y=0; y < (ssize_t) image->rows; y++)
346 {
347 register const Quantum
348 *restrict p,
349 *restrict q;
350
351 register ssize_t
352 x;
353
354 if (status == MagickFalse)
355 continue;
356 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
357 q=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,1,
358 exception);
359 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
360 {
361 status=MagickFalse;
362 continue;
363 }
364 for (x=0; x < (ssize_t) image->columns; x++)
365 {
366 i=(ssize_t) *q;
367 if (x < object[i].bounding_box.x)
368 object[i].bounding_box.x=x;
369 if (x > (ssize_t) object[i].bounding_box.width)
370 object[i].bounding_box.width=(size_t) x;
371 if (y < object[i].bounding_box.y)
372 object[i].bounding_box.y=y;
373 if (y > (ssize_t) object[i].bounding_box.height)
374 object[i].bounding_box.height=(size_t) y;
375 object[i].color.red+=GetPixelRed(image,p);
376 object[i].color.green+=GetPixelGreen(image,p);
377 object[i].color.blue+=GetPixelBlue(image,p);
378 object[i].color.alpha+=GetPixelAlpha(image,p);
379 object[i].color.black+=GetPixelBlack(image,p);
380 object[i].centroid.x+=x;
381 object[i].centroid.y+=y;
382 object[i].area++;
383 p+=GetPixelChannels(image);
384 q+=GetPixelChannels(component_image);
385 }
386 }
387 for (i=0; i < (ssize_t) number_objects; i++)
388 {
389 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
390 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
cristy97f96862014-11-03 12:28:08 +0000391 object[i].color.red=ClampToQuantum(object[i].color.red/object[i].area);
392 object[i].color.green=ClampToQuantum(object[i].color.green/object[i].area);
393 object[i].color.blue=ClampToQuantum(object[i].color.blue/object[i].area);
394 object[i].color.alpha=ClampToQuantum(object[i].color.alpha/object[i].area);
395 object[i].color.black=ClampToQuantum(object[i].color.black/object[i].area);
cristy8bd0b702014-11-01 13:00:45 +0000396 object[i].centroid.x=object[i].centroid.x/object[i].area;
397 object[i].centroid.y=object[i].centroid.y/object[i].area;
398 }
399 component_view=DestroyCacheView(component_view);
400 image_view=DestroyCacheView(image_view);
401 /*
402 Report statistics on unique objects.
403 */
404 qsort((void *) object,number_objects,sizeof(*object),CCObjectCompare);
405 (void) fprintf(stdout,
406 "Objects (id: bounding-box centroid area mean-color):\n");
407 for (i=0; i < (ssize_t) number_objects; i++)
408 {
409 char
410 mean_color[MaxTextExtent];
411
412 if (status == MagickFalse)
413 break;
414 if (object[i].area < MagickEpsilon)
415 continue;
416 GetColorTuple(&object[i].color,MagickFalse,mean_color);
417 (void) fprintf(stdout,
418 " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
419 object[i].id,(double) object[i].bounding_box.width,(double)
420 object[i].bounding_box.height,(double) object[i].bounding_box.x,
421 (double) object[i].bounding_box.y,object[i].centroid.x,
422 object[i].centroid.y,(double) object[i].area,mean_color);
423 }
424 object=(CCObject *) RelinquishMagickMemory(object);
425 return(status);
426}
427
cristy6e0b3bc2014-10-19 17:51:42 +0000428MagickExport Image *ConnectedComponentsImage(const Image *image,
429 const size_t connectivity,ExceptionInfo *exception)
430{
431#define ConnectedComponentsImageTag "ConnectedComponents/Image"
432
433 CacheView
434 *image_view,
435 *component_view;
436
cristy016b7642014-10-25 13:09:57 +0000437 const char
438 *artifact;
439
cristyc6ca5712014-10-26 22:13:07 +0000440 double
441 area_threshold;
442
cristy6e0b3bc2014-10-19 17:51:42 +0000443 Image
444 *component_image;
445
446 MagickBooleanType
447 status;
448
449 MagickOffsetType
450 progress;
451
cristy016b7642014-10-25 13:09:57 +0000452 MatrixInfo
453 *equivalences;
454
455 size_t
456 size;
457
cristy6e0b3bc2014-10-19 17:51:42 +0000458 ssize_t
cristy016b7642014-10-25 13:09:57 +0000459 n,
cristy6e0b3bc2014-10-19 17:51:42 +0000460 y;
461
462 /*
463 Initialize connected components image attributes.
464 */
465 assert(image != (Image *) NULL);
466 assert(image->signature == MagickSignature);
467 if (image->debug != MagickFalse)
468 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
469 assert(exception != (ExceptionInfo *) NULL);
470 assert(exception->signature == MagickSignature);
471 component_image=CloneImage(image,image->columns,image->rows,MagickTrue,
472 exception);
473 if (component_image == (Image *) NULL)
474 return((Image *) NULL);
cristy016b7642014-10-25 13:09:57 +0000475 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
476 component_image->colorspace=GRAYColorspace;
cristy6e0b3bc2014-10-19 17:51:42 +0000477 if (SetImageStorageClass(component_image,DirectClass,exception) == MagickFalse)
478 {
479 component_image=DestroyImage(component_image);
480 return((Image *) NULL);
481 }
482 /*
cristy016b7642014-10-25 13:09:57 +0000483 Initialize connected components equivalences.
484 */
485 size=image->columns*image->rows;
486 if (image->columns != (size/image->rows))
487 {
488 component_image=DestroyImage(component_image);
489 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
490 }
491 equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
492 if (equivalences == (MatrixInfo *) NULL)
493 {
494 component_image=DestroyImage(component_image);
495 return((Image *) NULL);
496 }
497 for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
498 status=SetMatrixElement(equivalences,n,0,&n);
499 /*
500 Find connected components.
cristy6e0b3bc2014-10-19 17:51:42 +0000501 */
502 status=MagickTrue;
503 progress=0;
504 image_view=AcquireVirtualCacheView(image,exception);
cristy016b7642014-10-25 13:09:57 +0000505 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
506 {
507 ssize_t
508 connect4[2][2] = { { -1, 0 }, { 0, -1 } },
509 connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
510 dx,
511 dy;
512
513 if (status == MagickFalse)
514 continue;
515 dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
516 dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
517 for (y=0; y < (ssize_t) image->rows; y++)
518 {
519 register const Quantum
520 *restrict p;
521
522 register ssize_t
523 x;
524
525 if (status == MagickFalse)
526 continue;
527 p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
528 if (p == (const Quantum *) NULL)
529 {
530 status=MagickFalse;
531 continue;
532 }
cristy00f8eb42014-10-25 13:46:10 +0000533 p+=image->columns*GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000534 for (x=0; x < (ssize_t) image->columns; x++)
535 {
536 PixelInfo
537 pixel,
538 target;
539
540 ssize_t
541 neighbor_offset,
542 object,
cristy00f8eb42014-10-25 13:46:10 +0000543 offset,
cristy016b7642014-10-25 13:09:57 +0000544 ox,
545 oy,
cristy016b7642014-10-25 13:09:57 +0000546 root;
547
548 /*
549 Is neighbor an authentic pixel and a different color than the pixel?
550 */
cristy00f8eb42014-10-25 13:46:10 +0000551 GetPixelInfoPixel(image,p,&pixel);
cristy016b7642014-10-25 13:09:57 +0000552 neighbor_offset=dy*(image->columns*GetPixelChannels(image))+dx*
553 GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000554 GetPixelInfoPixel(image,p+neighbor_offset,&target);
555 if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
556 ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows) ||
cristydfc9c0e2014-10-31 17:20:14 +0000557 (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse))
cristy016b7642014-10-25 13:09:57 +0000558 {
559 p+=GetPixelChannels(image);
560 continue;
561 }
562 /*
563 Resolve this equivalence.
564 */
cristy00f8eb42014-10-25 13:46:10 +0000565 offset=y*image->columns+x;
566 neighbor_offset=dy*image->columns+dx;
567 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000568 status=GetMatrixElement(equivalences,ox,0,&object);
569 while (object != ox) {
570 ox=object;
571 status=GetMatrixElement(equivalences,ox,0,&object);
572 }
cristy00f8eb42014-10-25 13:46:10 +0000573 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000574 status=GetMatrixElement(equivalences,oy,0,&object);
575 while (object != oy) {
576 oy=object;
577 status=GetMatrixElement(equivalences,oy,0,&object);
578 }
579 if (ox < oy)
580 {
581 status=SetMatrixElement(equivalences,oy,0,&ox);
582 root=ox;
583 }
584 else
585 {
586 status=SetMatrixElement(equivalences,ox,0,&oy);
587 root=oy;
588 }
cristy00f8eb42014-10-25 13:46:10 +0000589 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000590 status=GetMatrixElement(equivalences,ox,0,&object);
591 while (object != root) {
592 status=GetMatrixElement(equivalences,ox,0,&object);
593 status=SetMatrixElement(equivalences,ox,0,&root);
594 }
cristy00f8eb42014-10-25 13:46:10 +0000595 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000596 status=GetMatrixElement(equivalences,oy,0,&object);
597 while (object != root) {
598 status=GetMatrixElement(equivalences,oy,0,&object);
599 status=SetMatrixElement(equivalences,oy,0,&root);
600 }
601 status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
602 p+=GetPixelChannels(image);
603 }
604 }
605 }
606 image_view=DestroyCacheView(image_view);
607 /*
608 Label connected components.
609 */
610 n=0;
cristy6e0b3bc2014-10-19 17:51:42 +0000611 component_view=AcquireAuthenticCacheView(component_image,exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000612 for (y=0; y < (ssize_t) component_image->rows; y++)
613 {
614 register Quantum
615 *restrict q;
616
617 register ssize_t
618 x;
619
620 if (status == MagickFalse)
621 continue;
622 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
623 1,exception);
624 if (q == (Quantum *) NULL)
625 {
626 status=MagickFalse;
627 continue;
628 }
629 for (x=0; x < (ssize_t) component_image->columns; x++)
630 {
cristy016b7642014-10-25 13:09:57 +0000631 ssize_t
632 object,
633 offset;
634
635 offset=y*image->columns+x;
636 status=GetMatrixElement(equivalences,offset,0,&object);
637 if (object == offset)
638 {
639 object=n++;
640 status=SetMatrixElement(equivalences,offset,0,&object);
641 }
642 else
643 {
644 status=GetMatrixElement(equivalences,object,0,&object);
645 status=SetMatrixElement(equivalences,offset,0,&object);
646 }
647 *q=(Quantum) (object > (ssize_t) QuantumRange ? (ssize_t) QuantumRange :
648 object);
649 q+=GetPixelChannels(component_image);
cristy6e0b3bc2014-10-19 17:51:42 +0000650 }
651 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
652 status=MagickFalse;
653 if (image->progress_monitor != (MagickProgressMonitor) NULL)
654 {
655 MagickBooleanType
656 proceed;
657
cristy6e0b3bc2014-10-19 17:51:42 +0000658 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress++,
659 image->rows);
660 if (proceed == MagickFalse)
661 status=MagickFalse;
662 }
663 }
664 component_view=DestroyCacheView(component_view);
cristy016b7642014-10-25 13:09:57 +0000665 equivalences=DestroyMatrixInfo(equivalences);
cristyd86ee392014-10-28 10:16:44 +0000666 if (n > QuantumRange)
667 {
668 component_image=DestroyImage(component_image);
669 ThrowImageException(ResourceLimitError,"TooManyObjects");
670 }
cristyc6ca5712014-10-26 22:13:07 +0000671 artifact=GetImageArtifact(image,"connected-components:area-threshold");
cristy7f4f7c62014-10-27 00:29:16 +0000672 area_threshold=0.0;
cristyc6ca5712014-10-26 22:13:07 +0000673 if (artifact != (const char *) NULL)
674 area_threshold=StringToDouble(artifact,(char **) NULL);
cristy40898402014-10-26 22:22:02 +0000675 if (area_threshold > 0.0)
cristy7f4f7c62014-10-27 00:29:16 +0000676 status=MergeConnectedComponents(component_image,(size_t) n,area_threshold,
677 exception);
cristydfc9c0e2014-10-31 17:20:14 +0000678 artifact=GetImageArtifact(image,"connected-components:verbose");
679 if (IsStringTrue(artifact) != MagickFalse)
cristy8bd0b702014-11-01 13:00:45 +0000680 status=StatisticsComponentsStatistics(image,component_image,(size_t) n,
cristydfc9c0e2014-10-31 17:20:14 +0000681 exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000682 if (status == MagickFalse)
683 component_image=DestroyImage(component_image);
684 return(component_image);
685}