39#include "magick/studio.h"
40#include "magick/artifact.h"
41#include "magick/blob.h"
42#include "magick/cache-view.h"
43#include "magick/color.h"
44#include "magick/color-private.h"
45#include "magick/colormap.h"
46#include "magick/colorspace.h"
47#include "magick/constitute.h"
48#include "magick/decorate.h"
49#include "magick/distort.h"
50#include "magick/draw.h"
51#include "magick/enhance.h"
52#include "magick/exception.h"
53#include "magick/exception-private.h"
54#include "magick/effect.h"
55#include "magick/gem.h"
56#include "magick/geometry.h"
57#include "magick/image-private.h"
58#include "magick/list.h"
59#include "magick/log.h"
60#include "magick/matrix.h"
61#include "magick/memory_.h"
62#include "magick/memory-private.h"
63#include "magick/monitor.h"
64#include "magick/monitor-private.h"
65#include "magick/montage.h"
66#include "magick/morphology.h"
67#include "magick/morphology-private.h"
68#include "magick/opencl-private.h"
69#include "magick/paint.h"
70#include "magick/pixel-accessor.h"
71#include "magick/pixel-private.h"
72#include "magick/property.h"
73#include "magick/quantum.h"
74#include "magick/resource_.h"
75#include "magick/signature-private.h"
76#include "magick/string_.h"
77#include "magick/string-private.h"
78#include "magick/thread-private.h"
79#include "magick/token.h"
80#include "magick/vision.h"
133static int CCObjectInfoCompare(
const void *x,
const void *y)
139 p=(CCObjectInfo *) x;
140 q=(CCObjectInfo *) y;
141 return((
int) (q->area-(ssize_t) p->area));
144MagickExport Image *ConnectedComponentsImage(
const Image *image,
145 const size_t connectivity,ExceptionInfo *exception)
147#define ConnectedComponentsImageTag "ConnectedComponents/Image"
187 connect4[2][2] = { { -1, 0 }, { 0, -1 } },
188 connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
200 assert(image != (Image *) NULL);
201 assert(image->signature == MagickCoreSignature);
202 assert(exception != (ExceptionInfo *) NULL);
203 assert(exception->signature == MagickCoreSignature);
204 if (IsEventLogging() != MagickFalse)
205 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
206 component_image=CloneImage(image,0,0,MagickTrue,exception);
207 if (component_image == (Image *) NULL)
208 return((Image *) NULL);
209 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
210 if (AcquireImageColormap(component_image,MaxColormapSize) == MagickFalse)
212 component_image=DestroyImage(component_image);
213 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
218 size=image->columns*image->rows;
219 if (image->columns != (size/image->rows))
221 component_image=DestroyImage(component_image);
222 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
224 equivalences=AcquireMatrixInfo(size,1,
sizeof(ssize_t),exception);
225 if (equivalences == (MatrixInfo *) NULL)
227 component_image=DestroyImage(component_image);
228 return((Image *) NULL);
230 for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
231 (
void) SetMatrixElement(equivalences,n,0,&n);
232 object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,
sizeof(*
object));
233 if (
object == (CCObjectInfo *) NULL)
235 equivalences=DestroyMatrixInfo(equivalences);
236 component_image=DestroyImage(component_image);
237 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
239 (void) memset(
object,0,MaxColormapSize*
sizeof(*
object));
240 for (i=0; i < (ssize_t) MaxColormapSize; i++)
243 object[i].bounding_box.x=(ssize_t) image->columns;
244 object[i].bounding_box.y=(ssize_t) image->rows;
245 GetMagickPixelPacket(image,&
object[i].color);
252 image_view=AcquireVirtualCacheView(image,exception);
253 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
255 if (status == MagickFalse)
257 dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
258 dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
259 for (y=0; y < (ssize_t) image->rows; y++)
267 if (status == MagickFalse)
269 p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
270 if (p == (
const PixelPacket *) NULL)
275 p+=(ptrdiff_t) image->columns;
276 for (x=0; x < (ssize_t) image->columns; x++)
289 if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
290 ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
295 neighbor_offset=dy*image->columns+dx;
296 if (IsColorSimilar(image,p,p+neighbor_offset) == MagickFalse)
304 offset=y*image->columns+x;
306 status=GetMatrixElement(equivalences,ox,0,&obj);
310 status=GetMatrixElement(equivalences,ox,0,&obj);
312 oy=offset+neighbor_offset;
313 status=GetMatrixElement(equivalences,oy,0,&obj);
317 status=GetMatrixElement(equivalences,oy,0,&obj);
321 status=SetMatrixElement(equivalences,oy,0,&ox);
326 status=SetMatrixElement(equivalences,ox,0,&oy);
330 status=GetMatrixElement(equivalences,ox,0,&obj);
333 status=GetMatrixElement(equivalences,ox,0,&obj);
334 status=SetMatrixElement(equivalences,ox,0,&root);
336 oy=offset+neighbor_offset;
337 status=GetMatrixElement(equivalences,oy,0,&obj);
340 status=GetMatrixElement(equivalences,oy,0,&obj);
341 status=SetMatrixElement(equivalences,oy,0,&root);
343 status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
352 component_view=AcquireAuthenticCacheView(component_image,exception);
353 for (y=0; y < (ssize_t) component_image->rows; y++)
356 *magick_restrict indexes;
362 *magick_restrict component_indexes;
370 if (status == MagickFalse)
372 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
373 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
375 if ((p == (
const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
380 indexes=GetCacheViewVirtualIndexQueue(image_view);
381 component_indexes=GetCacheViewAuthenticIndexQueue(component_view);
382 for (x=0; x < (ssize_t) component_image->columns; x++)
388 offset=y*image->columns+x;
389 status=GetMatrixElement(equivalences,offset,0,&
id);
391 status=GetMatrixElement(equivalences,
id,0,&
id);
395 if (
id >= (ssize_t) MaxColormapSize)
398 status=SetMatrixElement(equivalences,offset,0,&
id);
399 if (x <
object[
id].bounding_box.x)
400 object[id].bounding_box.x=x;
401 if (x >= (ssize_t)
object[
id].bounding_box.width)
402 object[id].bounding_box.width=(size_t) x;
403 if (y <
object[
id].bounding_box.y)
404 object[id].bounding_box.y=y;
405 if (y >= (ssize_t)
object[
id].bounding_box.height)
406 object[id].bounding_box.height=(size_t) y;
407 object[id].color.red+=QuantumScale*(MagickRealType) p->red;
408 object[id].color.green+=QuantumScale*(MagickRealType) p->green;
409 object[id].color.blue+=QuantumScale*(MagickRealType) p->blue;
410 if (image->matte != MagickFalse)
411 object[id].color.opacity+=QuantumScale*(MagickRealType) p->opacity;
412 if (image->colorspace == CMYKColorspace)
413 object[id].color.index+=QuantumScale*(MagickRealType) indexes[x];
414 object[id].centroid.x+=x;
415 object[id].centroid.y+=y;
417 component_indexes[x]=(IndexPacket)
id;
421 if (n > (ssize_t) MaxColormapSize)
423 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
425 if (image->progress_monitor != (MagickProgressMonitor) NULL)
431 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
433 if (proceed == MagickFalse)
437 component_view=DestroyCacheView(component_view);
438 image_view=DestroyCacheView(image_view);
439 equivalences=DestroyMatrixInfo(equivalences);
440 if (n > (ssize_t) MaxColormapSize)
442 object=(CCObjectInfo *) RelinquishMagickMemory(
object);
443 component_image=DestroyImage(component_image);
444 ThrowImageException(ResourceLimitError,
"TooManyObjects");
449 component_image->colors=(size_t) n;
450 for (i=0; i < (ssize_t) component_image->colors; i++)
452 object[i].bounding_box.width-=(
object[i].bounding_box.x-1);
453 object[i].bounding_box.height-=(
object[i].bounding_box.y-1);
454 object[i].color.red/=(QuantumScale*
object[i].area);
455 object[i].color.green/=(QuantumScale*
object[i].area);
456 object[i].color.blue/=(QuantumScale*
object[i].area);
457 if (image->matte != MagickFalse)
458 object[i].color.opacity/=(QuantumScale*
object[i].area);
459 if (image->colorspace == CMYKColorspace)
460 object[i].color.index/=(QuantumScale*
object[i].area);
461 object[i].centroid.x/=
object[i].area;
462 object[i].centroid.y/=
object[i].area;
463 max_threshold+=
object[i].area;
464 if (
object[i].area >
object[background_id].area)
467 max_threshold+=MagickEpsilon;
468 artifact=GetImageArtifact(image,
"connected-components:background-id");
469 if (artifact != (
const char *) NULL)
470 background_id=(ssize_t) StringToLong(artifact);
471 artifact=GetImageArtifact(image,
"connected-components:area-threshold");
472 if (artifact != (
const char *) NULL)
477 (void) sscanf(artifact,
"%lf%*[ -]%lf",&min_threshold,&max_threshold);
478 for (i=0; i < (ssize_t) component_image->colors; i++)
479 if (((
object[i].area < min_threshold) ||
480 (
object[i].area >= max_threshold)) && (i != background_id))
481 object[i].merge=MagickTrue;
483 artifact=GetImageArtifact(image,
"connected-components:keep-colors");
484 if (artifact != (
const char *) NULL)
492 for (i=0; i < (ssize_t) component_image->colors; i++)
493 object[i].merge=MagickTrue;
497 color[MagickPathExtent];
505 for (q=p; *q !=
'\0'; q++)
508 (void) CopyMagickString(color,p,(
size_t) MagickMin(q-p+1,
510 (void) QueryMagickColor(color,&pixel,exception);
511 for (i=0; i < (ssize_t) component_image->colors; i++)
512 if (IsMagickColorSimilar(&
object[i].color,&pixel) != MagickFalse)
513 object[i].merge=MagickFalse;
519 artifact=GetImageArtifact(image,
"connected-components:keep-ids");
520 if (artifact == (
const char *) NULL)
521 artifact=GetImageArtifact(image,
"connected-components:keep");
522 if (artifact != (
const char *) NULL)
523 for (c=(
char *) artifact; *c !=
'\0'; )
528 for (i=0; i < (ssize_t) component_image->colors; i++)
529 object[i].merge=MagickTrue;
530 while ((isspace((
int) ((
unsigned char) *c)) != 0) || (*c ==
','))
532 first=(ssize_t) strtol(c,&c,10);
534 first+=(ssize_t) component_image->colors;
536 while (isspace((
int) ((
unsigned char) *c)) != 0)
540 last=(ssize_t) strtol(c+1,&c,10);
542 last+=(ssize_t) component_image->colors;
544 step=(ssize_t) (first > last ? -1 : 1);
545 for ( ; first != (last+step); first+=step)
547 (first < (ssize_t) component_image->colors))
548 object[first].merge=MagickFalse;
550 artifact=GetImageArtifact(image,
"connected-components:keep-top");
551 if (artifact != (
const char *) NULL)
562 top_ids=(ssize_t) StringToLong(artifact);
563 top_objects=(CCObjectInfo *) AcquireQuantumMemory(component_image->colors,
564 sizeof(*top_objects));
565 if (top_objects == (CCObjectInfo *) NULL)
567 object=(CCObjectInfo *) RelinquishMagickMemory(
object);
568 component_image=DestroyImage(component_image);
569 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
571 (void) memcpy(top_objects,
object,component_image->colors*
sizeof(*
object));
572 qsort((
void *) top_objects,component_image->colors,
sizeof(*top_objects),
573 CCObjectInfoCompare);
574 for (i=top_ids+1; i < (ssize_t) component_image->colors; i++)
576 ssize_t
id = (ssize_t) top_objects[i].
id;
577 if ((
id >= 0) && (
id < (ssize_t) component_image->colors))
578 object[id].merge=MagickTrue;
580 top_objects=(CCObjectInfo *) RelinquishMagickMemory(top_objects);
582 artifact=GetImageArtifact(image,
"connected-components:remove-colors");
583 if (artifact != (
const char *) NULL)
594 color[MagickPathExtent];
602 for (q=p; *q !=
'\0'; q++)
605 (void) CopyMagickString(color,p,(
size_t) MagickMin(q-p+1,
607 (void) QueryMagickColor(color,&pixel,exception);
608 for (i=0; i < (ssize_t) component_image->colors; i++)
609 if (IsMagickColorSimilar(&
object[i].color,&pixel) != MagickFalse)
610 object[i].merge=MagickTrue;
616 artifact=GetImageArtifact(image,
"connected-components:remove-ids");
617 if (artifact == (
const char *) NULL)
618 artifact=GetImageArtifact(image,
"connected-components:remove");
619 if (artifact != (
const char *) NULL)
620 for (c=(
char *) artifact; *c !=
'\0'; )
625 while ((isspace((
int) ((
unsigned char) *c)) != 0) || (*c ==
','))
627 first=(ssize_t) strtol(c,&c,10);
629 first+=(ssize_t) component_image->colors;
631 while (isspace((
int) ((
unsigned char) *c)) != 0)
635 last=(ssize_t) strtol(c+1,&c,10);
637 last+=(ssize_t) component_image->colors;
639 step=(ssize_t) (first > last ? -1 : 1);
640 for ( ; first != (last+step); first+=step)
642 (first < (ssize_t) component_image->colors))
643 object[first].merge=MagickTrue;
648 component_view=AcquireAuthenticCacheView(component_image,exception);
649 object_view=AcquireVirtualCacheView(component_image,exception);
650 for (i=0; i < (ssize_t) component_image->colors; i++)
661 if (status == MagickFalse)
663 if ((
object[i].merge == MagickFalse) || (i == background_id))
668 for (j=0; j < (ssize_t) component_image->colors; j++)
670 bounding_box=
object[i].bounding_box;
671 for (y=0; y < (ssize_t) bounding_box.height; y++)
674 *magick_restrict indexes;
682 if (status == MagickFalse)
684 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
685 bounding_box.y+y,bounding_box.width,1,exception);
686 if (p == (
const PixelPacket *) NULL)
691 indexes=GetCacheViewVirtualIndexQueue(component_view);
692 for (x=0; x < (ssize_t) bounding_box.width; x++)
697 if (status == MagickFalse)
699 j=(ssize_t) indexes[x];
701 for (k=0; k < (ssize_t) (connectivity > 4 ? 4 : 2); k++)
704 *magick_restrict indexes;
712 if (status == MagickFalse)
714 dx=connectivity > 4 ? connect8[k][1] : connect4[k][1];
715 dy=connectivity > 4 ? connect8[k][0] : connect4[k][0];
716 p=GetCacheViewVirtualPixels(object_view,bounding_box.x+x+dx,
717 bounding_box.y+y+dy,1,1,exception);
718 if (p == (
const PixelPacket *) NULL)
723 indexes=GetCacheViewVirtualIndexQueue(object_view);
724 j=(ssize_t) *indexes;
734 for (j=1; j < (ssize_t) component_image->colors; j++)
735 if (
object[j].census >
object[
id].census)
738 for (y=0; y < (ssize_t) bounding_box.height; y++)
741 *magick_restrict component_indexes;
749 if (status == MagickFalse)
751 q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
752 bounding_box.y+y,bounding_box.width,1,exception);
753 if (q == (PixelPacket *) NULL)
758 component_indexes=GetCacheViewAuthenticIndexQueue(component_view);
759 for (x=0; x < (ssize_t) bounding_box.width; x++)
761 if ((ssize_t) component_indexes[x] == i)
762 component_indexes[x]=(IndexPacket)
id;
764 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
768 object_view=DestroyCacheView(object_view);
769 component_view=DestroyCacheView(component_view);
770 artifact=GetImageArtifact(image,
"connected-components:mean-color");
771 if (IsMagickTrue(artifact) != MagickFalse)
776 for (i=0; i < (ssize_t) component_image->colors; i++)
778 component_image->colormap[i].red=ClampToQuantum(
object[i].color.red);
779 component_image->colormap[i].green=ClampToQuantum(
780 object[i].color.green);
781 component_image->colormap[i].blue=ClampToQuantum(
object[i].color.blue);
782 component_image->colormap[i].opacity=ClampToQuantum(
783 object[i].color.opacity);
786 (void) SyncImage(component_image);
787 artifact=GetImageArtifact(image,
"connected-components:verbose");
788 if (IsMagickTrue(artifact) != MagickFalse)
793 for (i=0; i < (ssize_t) component_image->colors; i++)
795 object[i].bounding_box.width=0;
796 object[i].bounding_box.height=0;
797 object[i].bounding_box.x=(ssize_t) component_image->columns;
798 object[i].bounding_box.y=(ssize_t) component_image->rows;
799 object[i].centroid.x=0;
800 object[i].centroid.y=0;
801 object[i].census=
object[i].area == 0.0 ? 0.0 : 1.0;
804 component_view=AcquireVirtualCacheView(component_image,exception);
805 for (y=0; y < (ssize_t) component_image->rows; y++)
816 if (status == MagickFalse)
818 p=GetCacheViewVirtualPixels(component_view,0,y,
819 component_image->columns,1,exception);
820 if (p == (
const PixelPacket *) NULL)
825 indexes=GetCacheViewVirtualIndexQueue(component_view);
826 for (x=0; x < (ssize_t) component_image->columns; x++)
831 id=(size_t) indexes[x];
832 if (x <
object[
id].bounding_box.x)
833 object[id].bounding_box.x=x;
834 if (x > (ssize_t)
object[
id].bounding_box.width)
835 object[id].bounding_box.width=(size_t) x;
836 if (y <
object[
id].bounding_box.y)
837 object[id].bounding_box.y=y;
838 if (y > (ssize_t)
object[
id].bounding_box.height)
839 object[id].bounding_box.height=(size_t) y;
840 object[id].centroid.x+=x;
841 object[id].centroid.y+=y;
845 for (i=0; i < (ssize_t) component_image->colors; i++)
847 object[i].bounding_box.width-=(
object[i].bounding_box.x-1);
848 object[i].bounding_box.height-=(
object[i].bounding_box.y-1);
849 object[i].centroid.x=
object[i].centroid.x/
object[i].area;
850 object[i].centroid.y=
object[i].centroid.y/
object[i].area;
852 component_view=DestroyCacheView(component_view);
853 qsort((
void *)
object,component_image->colors,
sizeof(*
object),
854 CCObjectInfoCompare);
855 artifact=GetImageArtifact(image,
"connected-components:exclude-header");
856 if (IsStringTrue(artifact) == MagickFalse)
857 (void) fprintf(stdout,
858 "Objects (id: bounding-box centroid area mean-color):\n");
859 for (i=0; i < (ssize_t) component_image->colors; i++)
860 if (
object[i].census > 0.0)
863 mean_color[MaxTextExtent];
865 GetColorTuple(&
object[i].color,MagickFalse,mean_color);
866 (void) fprintf(stdout,
867 " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(
double)
868 object[i].
id,(
double)
object[i].bounding_box.width,(
double)
869 object[i].bounding_box.height,(
double)
object[i].bounding_box.x,
870 (
double)
object[i].bounding_box.y,
object[i].centroid.x,
871 object[i].centroid.y,(
double)
object[i].area,mean_color);
874 object=(CCObjectInfo *) RelinquishMagickMemory(
object);
875 return(component_image);