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"
188 connect4[2][2] = { { -1, 0 }, { 0, -1 } },
189 connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
201 assert(image != (Image *) NULL);
202 assert(image->signature == MagickCoreSignature);
203 assert(exception != (ExceptionInfo *) NULL);
204 assert(exception->signature == MagickCoreSignature);
205 if (IsEventLogging() != MagickFalse)
206 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
207 component_image=CloneImage(image,0,0,MagickTrue,exception);
208 if (component_image == (Image *) NULL)
209 return((Image *) NULL);
210 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
211 if (AcquireImageColormap(component_image,MaxColormapSize) == MagickFalse)
213 component_image=DestroyImage(component_image);
214 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
219 size=image->columns*image->rows;
220 if (image->columns != (size/image->rows))
222 component_image=DestroyImage(component_image);
223 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
225 equivalences=AcquireMatrixInfo(size,1,
sizeof(ssize_t),exception);
226 if (equivalences == (MatrixInfo *) NULL)
228 component_image=DestroyImage(component_image);
229 return((Image *) NULL);
231 for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
232 (
void) SetMatrixElement(equivalences,n,0,&n);
233 object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,
sizeof(*
object));
234 if (
object == (CCObjectInfo *) NULL)
236 equivalences=DestroyMatrixInfo(equivalences);
237 component_image=DestroyImage(component_image);
238 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
240 (void) memset(
object,0,MaxColormapSize*
sizeof(*
object));
241 for (i=0; i < (ssize_t) MaxColormapSize; i++)
244 object[i].bounding_box.x=(ssize_t) image->columns;
245 object[i].bounding_box.y=(ssize_t) image->rows;
246 GetMagickPixelPacket(image,&
object[i].color);
253 image_view=AcquireVirtualCacheView(image,exception);
254 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
256 if (status == MagickFalse)
258 dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
259 dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
260 for (y=0; y < (ssize_t) image->rows; y++)
268 if (status == MagickFalse)
270 p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
271 if (p == (
const PixelPacket *) NULL)
276 p+=(ptrdiff_t) image->columns;
277 for (x=0; x < (ssize_t) image->columns; x++)
290 if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
291 ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
296 neighbor_offset=dy*image->columns+dx;
297 if (IsColorSimilar(image,p,p+neighbor_offset) == MagickFalse)
305 offset=y*image->columns+x;
307 status=GetMatrixElement(equivalences,ox,0,&obj);
311 status=GetMatrixElement(equivalences,ox,0,&obj);
313 oy=offset+neighbor_offset;
314 status=GetMatrixElement(equivalences,oy,0,&obj);
318 status=GetMatrixElement(equivalences,oy,0,&obj);
322 status=SetMatrixElement(equivalences,oy,0,&ox);
327 status=SetMatrixElement(equivalences,ox,0,&oy);
331 status=GetMatrixElement(equivalences,ox,0,&obj);
334 status=GetMatrixElement(equivalences,ox,0,&obj);
335 status=SetMatrixElement(equivalences,ox,0,&root);
337 oy=offset+neighbor_offset;
338 status=GetMatrixElement(equivalences,oy,0,&obj);
341 status=GetMatrixElement(equivalences,oy,0,&obj);
342 status=SetMatrixElement(equivalences,oy,0,&root);
344 status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
353 component_view=AcquireAuthenticCacheView(component_image,exception);
354 for (y=0; y < (ssize_t) component_image->rows; y++)
357 *magick_restrict indexes;
363 *magick_restrict component_indexes;
371 if (status == MagickFalse)
373 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
374 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
376 if ((p == (
const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
381 indexes=GetCacheViewVirtualIndexQueue(image_view);
382 component_indexes=GetCacheViewAuthenticIndexQueue(component_view);
383 for (x=0; x < (ssize_t) component_image->columns; x++)
389 offset=y*image->columns+x;
390 status=GetMatrixElement(equivalences,offset,0,&
id);
392 status=GetMatrixElement(equivalences,
id,0,&
id);
396 if (
id >= (ssize_t) MaxColormapSize)
399 status=SetMatrixElement(equivalences,offset,0,&
id);
400 if (x <
object[
id].bounding_box.x)
401 object[id].bounding_box.x=x;
402 if (x >= (ssize_t)
object[
id].bounding_box.width)
403 object[id].bounding_box.width=(size_t) x;
404 if (y <
object[
id].bounding_box.y)
405 object[id].bounding_box.y=y;
406 if (y >= (ssize_t)
object[
id].bounding_box.height)
407 object[id].bounding_box.height=(size_t) y;
408 object[id].color.red+=QuantumScale*(MagickRealType) p->red;
409 object[id].color.green+=QuantumScale*(MagickRealType) p->green;
410 object[id].color.blue+=QuantumScale*(MagickRealType) p->blue;
411 if (image->matte != MagickFalse)
412 object[id].color.opacity+=QuantumScale*(MagickRealType) p->opacity;
413 if (image->colorspace == CMYKColorspace)
414 object[id].color.index+=QuantumScale*(MagickRealType) indexes[x];
415 object[id].centroid.x+=x;
416 object[id].centroid.y+=y;
418 component_indexes[x]=(IndexPacket)
id;
422 if (n > (ssize_t) MaxColormapSize)
424 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
426 if (image->progress_monitor != (MagickProgressMonitor) NULL)
432 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
434 if (proceed == MagickFalse)
438 component_view=DestroyCacheView(component_view);
439 image_view=DestroyCacheView(image_view);
440 equivalences=DestroyMatrixInfo(equivalences);
441 if (n > (ssize_t) MaxColormapSize)
443 object=(CCObjectInfo *) RelinquishMagickMemory(
object);
444 component_image=DestroyImage(component_image);
445 ThrowImageException(ResourceLimitError,
"TooManyObjects");
450 component_image->colors=(size_t) n;
451 for (i=0; i < (ssize_t) component_image->colors; i++)
453 object[i].bounding_box.width-=(
object[i].bounding_box.x-1);
454 object[i].bounding_box.height-=(
object[i].bounding_box.y-1);
455 object[i].color.red/=(QuantumScale*
object[i].area);
456 object[i].color.green/=(QuantumScale*
object[i].area);
457 object[i].color.blue/=(QuantumScale*
object[i].area);
458 if (image->matte != MagickFalse)
459 object[i].color.opacity/=(QuantumScale*
object[i].area);
460 if (image->colorspace == CMYKColorspace)
461 object[i].color.index/=(QuantumScale*
object[i].area);
462 object[i].centroid.x/=
object[i].area;
463 object[i].centroid.y/=
object[i].area;
464 max_threshold+=
object[i].area;
465 if (
object[i].area >
object[background_id].area)
468 max_threshold+=MagickEpsilon;
469 artifact=GetImageArtifact(image,
"connected-components:background-id");
470 if (artifact != (
const char *) NULL)
471 background_id=(ssize_t) StringToLong(artifact);
472 artifact=GetImageArtifact(image,
"connected-components:area-threshold");
473 if (artifact != (
const char *) NULL)
478 (void) sscanf(artifact,
"%lf%*[ -]%lf",&min_threshold,&max_threshold);
479 for (i=0; i < (ssize_t) component_image->colors; i++)
480 if (((
object[i].area < min_threshold) ||
481 (
object[i].area >= max_threshold)) && (i != background_id))
482 object[i].merge=MagickTrue;
484 artifact=GetImageArtifact(image,
"connected-components:keep-colors");
485 if (artifact != (
const char *) NULL)
493 for (i=0; i < (ssize_t) component_image->colors; i++)
494 object[i].merge=MagickTrue;
498 color[MagickPathExtent];
506 for (q=p; *q !=
'\0'; q++)
509 (void) CopyMagickString(color,p,(
size_t) MagickMin(q-p+1,
511 (void) QueryMagickColor(color,&pixel,exception);
512 for (i=0; i < (ssize_t) component_image->colors; i++)
513 if (IsMagickColorSimilar(&
object[i].color,&pixel) != MagickFalse)
514 object[i].merge=MagickFalse;
520 artifact=GetImageArtifact(image,
"connected-components:keep-ids");
521 if (artifact == (
const char *) NULL)
522 artifact=GetImageArtifact(image,
"connected-components:keep");
523 if (artifact != (
const char *) NULL)
524 for (c=(
char *) artifact; *c !=
'\0'; )
529 for (i=0; i < (ssize_t) component_image->colors; i++)
530 object[i].merge=MagickTrue;
531 while ((isspace((
int) ((
unsigned char) *c)) != 0) || (*c ==
','))
534 first=(ssize_t) strtol(c,&d,10);
539 first+=(ssize_t) component_image->colors;
541 while (isspace((
int) ((
unsigned char) *c)) != 0)
545 last=(ssize_t) strtol(c+1,&c,10);
547 last+=(ssize_t) component_image->colors;
549 step=(ssize_t) (first > last ? -1 : 1);
550 for ( ; first != (last+step); first+=step)
552 (first < (ssize_t) component_image->colors))
553 object[first].merge=MagickFalse;
555 artifact=GetImageArtifact(image,
"connected-components:keep-top");
556 if (artifact != (
const char *) NULL)
567 top_ids=(ssize_t) StringToLong(artifact);
570 if (top_ids >= (ssize_t) component_image->colors)
571 top_ids=(ssize_t) component_image->colors-1;
572 top_objects=(CCObjectInfo *) AcquireQuantumMemory(component_image->colors,
573 sizeof(*top_objects));
574 if (top_objects == (CCObjectInfo *) NULL)
576 object=(CCObjectInfo *) RelinquishMagickMemory(
object);
577 component_image=DestroyImage(component_image);
578 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
580 (void) memcpy(top_objects,
object,component_image->colors*
sizeof(*
object));
581 qsort((
void *) top_objects,component_image->colors,
sizeof(*top_objects),
582 CCObjectInfoCompare);
583 for (i=top_ids+1; i < (ssize_t) component_image->colors; i++)
585 ssize_t
id = (ssize_t) top_objects[i].
id;
586 if ((
id >= 0) && (
id < (ssize_t) component_image->colors))
587 object[id].merge=MagickTrue;
589 top_objects=(CCObjectInfo *) RelinquishMagickMemory(top_objects);
591 artifact=GetImageArtifact(image,
"connected-components:remove-colors");
592 if (artifact != (
const char *) NULL)
603 color[MagickPathExtent];
611 for (q=p; *q !=
'\0'; q++)
614 (void) CopyMagickString(color,p,(
size_t) MagickMin(q-p+1,
616 (void) QueryMagickColor(color,&pixel,exception);
617 for (i=0; i < (ssize_t) component_image->colors; i++)
618 if (IsMagickColorSimilar(&
object[i].color,&pixel) != MagickFalse)
619 object[i].merge=MagickTrue;
625 artifact=GetImageArtifact(image,
"connected-components:remove-ids");
626 if (artifact == (
const char *) NULL)
627 artifact=GetImageArtifact(image,
"connected-components:remove");
628 if (artifact != (
const char *) NULL)
629 for (c=(
char *) artifact; *c !=
'\0'; )
634 while ((isspace((
int) ((
unsigned char) *c)) != 0) || (*c ==
','))
637 first=(ssize_t) strtol(c,&d,10);
642 first+=(ssize_t) component_image->colors;
644 while (isspace((
int) ((
unsigned char) *c)) != 0)
648 last=(ssize_t) strtol(c+1,&c,10);
650 last+=(ssize_t) component_image->colors;
652 step=(ssize_t) (first > last ? -1 : 1);
653 for ( ; first != (last+step); first+=step)
655 (first < (ssize_t) component_image->colors))
656 object[first].merge=MagickTrue;
661 component_view=AcquireAuthenticCacheView(component_image,exception);
662 object_view=AcquireVirtualCacheView(component_image,exception);
663 for (i=0; i < (ssize_t) component_image->colors; i++)
674 if (status == MagickFalse)
676 if ((
object[i].merge == MagickFalse) || (i == background_id))
681 for (j=0; j < (ssize_t) component_image->colors; j++)
683 bounding_box=
object[i].bounding_box;
684 for (y=0; y < (ssize_t) bounding_box.height; y++)
687 *magick_restrict indexes;
695 if (status == MagickFalse)
697 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
698 bounding_box.y+y,bounding_box.width,1,exception);
699 if (p == (
const PixelPacket *) NULL)
704 indexes=GetCacheViewVirtualIndexQueue(component_view);
705 for (x=0; x < (ssize_t) bounding_box.width; x++)
710 if (status == MagickFalse)
712 j=(ssize_t) indexes[x];
714 for (k=0; k < (ssize_t) (connectivity > 4 ? 4 : 2); k++)
717 *magick_restrict indexes;
725 if (status == MagickFalse)
727 dx=connectivity > 4 ? connect8[k][1] : connect4[k][1];
728 dy=connectivity > 4 ? connect8[k][0] : connect4[k][0];
729 p=GetCacheViewVirtualPixels(object_view,bounding_box.x+x+dx,
730 bounding_box.y+y+dy,1,1,exception);
731 if (p == (
const PixelPacket *) NULL)
736 indexes=GetCacheViewVirtualIndexQueue(object_view);
737 j=(ssize_t) *indexes;
747 for (j=1; j < (ssize_t) component_image->colors; j++)
748 if (
object[j].census >
object[
id].census)
751 for (y=0; y < (ssize_t) bounding_box.height; y++)
754 *magick_restrict component_indexes;
762 if (status == MagickFalse)
764 q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
765 bounding_box.y+y,bounding_box.width,1,exception);
766 if (q == (PixelPacket *) NULL)
771 component_indexes=GetCacheViewAuthenticIndexQueue(component_view);
772 for (x=0; x < (ssize_t) bounding_box.width; x++)
774 if ((ssize_t) component_indexes[x] == i)
775 component_indexes[x]=(IndexPacket)
id;
777 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
781 object_view=DestroyCacheView(object_view);
782 component_view=DestroyCacheView(component_view);
783 artifact=GetImageArtifact(image,
"connected-components:mean-color");
784 if (IsMagickTrue(artifact) != MagickFalse)
789 for (i=0; i < (ssize_t) component_image->colors; i++)
791 component_image->colormap[i].red=ClampToQuantum(
object[i].color.red);
792 component_image->colormap[i].green=ClampToQuantum(
793 object[i].color.green);
794 component_image->colormap[i].blue=ClampToQuantum(
object[i].color.blue);
795 component_image->colormap[i].opacity=ClampToQuantum(
796 object[i].color.opacity);
799 (void) SyncImage(component_image);
800 artifact=GetImageArtifact(image,
"connected-components:verbose");
801 if (IsMagickTrue(artifact) != MagickFalse)
806 for (i=0; i < (ssize_t) component_image->colors; i++)
808 object[i].bounding_box.width=0;
809 object[i].bounding_box.height=0;
810 object[i].bounding_box.x=(ssize_t) component_image->columns;
811 object[i].bounding_box.y=(ssize_t) component_image->rows;
812 object[i].centroid.x=0;
813 object[i].centroid.y=0;
814 object[i].census=
object[i].area == 0.0 ? 0.0 : 1.0;
817 component_view=AcquireVirtualCacheView(component_image,exception);
818 for (y=0; y < (ssize_t) component_image->rows; y++)
829 if (status == MagickFalse)
831 p=GetCacheViewVirtualPixels(component_view,0,y,
832 component_image->columns,1,exception);
833 if (p == (
const PixelPacket *) NULL)
838 indexes=GetCacheViewVirtualIndexQueue(component_view);
839 for (x=0; x < (ssize_t) component_image->columns; x++)
844 id=(size_t) indexes[x];
845 if (x <
object[
id].bounding_box.x)
846 object[id].bounding_box.x=x;
847 if (x > (ssize_t)
object[
id].bounding_box.width)
848 object[id].bounding_box.width=(size_t) x;
849 if (y <
object[
id].bounding_box.y)
850 object[id].bounding_box.y=y;
851 if (y > (ssize_t)
object[
id].bounding_box.height)
852 object[id].bounding_box.height=(size_t) y;
853 object[id].centroid.x+=x;
854 object[id].centroid.y+=y;
858 for (i=0; i < (ssize_t) component_image->colors; i++)
860 object[i].bounding_box.width-=(
object[i].bounding_box.x-1);
861 object[i].bounding_box.height-=(
object[i].bounding_box.y-1);
862 object[i].centroid.x=
object[i].centroid.x/
object[i].area;
863 object[i].centroid.y=
object[i].centroid.y/
object[i].area;
865 component_view=DestroyCacheView(component_view);
866 qsort((
void *)
object,component_image->colors,
sizeof(*
object),
867 CCObjectInfoCompare);
868 artifact=GetImageArtifact(image,
"connected-components:exclude-header");
869 if (IsStringTrue(artifact) == MagickFalse)
870 (void) fprintf(stdout,
871 "Objects (id: bounding-box centroid area mean-color):\n");
872 for (i=0; i < (ssize_t) component_image->colors; i++)
873 if (
object[i].census > 0.0)
876 mean_color[MaxTextExtent];
878 GetColorTuple(&
object[i].color,MagickFalse,mean_color);
879 (void) fprintf(stdout,
880 " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(
double)
881 object[i].
id,(
double)
object[i].bounding_box.width,(
double)
882 object[i].bounding_box.height,(
double)
object[i].bounding_box.x,
883 (
double)
object[i].bounding_box.y,
object[i].centroid.x,
884 object[i].centroid.y,(
double)
object[i].area,mean_color);
887 object=(CCObjectInfo *) RelinquishMagickMemory(
object);
888 return(component_image);