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)
141 return((
int) (q->area-(ssize_t) p->area));
144MagickExport
Image *ConnectedComponentsImage(
const Image *image,
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);
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);
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));
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);
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,
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)
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)
546 object[first].merge=MagickFalse;
548 artifact=GetImageArtifact(image,
"connected-components:keep-top");
549 if (artifact != (
const char *) NULL)
560 top_ids=(ssize_t) StringToLong(artifact);
561 top_objects=(
CCObjectInfo *) AcquireQuantumMemory(component_image->colors,
562 sizeof(*top_objects));
566 component_image=DestroyImage(component_image);
567 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
569 (void) memcpy(top_objects,
object,component_image->colors*
sizeof(*
object));
570 qsort((
void *) top_objects,component_image->colors,
sizeof(*top_objects),
571 CCObjectInfoCompare);
572 for (i=top_ids+1; i < (ssize_t) component_image->colors; i++)
573 object[top_objects[i].
id].merge=MagickTrue;
574 top_objects=(
CCObjectInfo *) RelinquishMagickMemory(top_objects);
576 artifact=GetImageArtifact(image,
"connected-components:remove-colors");
577 if (artifact != (
const char *) NULL)
588 color[MagickPathExtent];
596 for (q=p; *q !=
'\0'; q++)
599 (void) CopyMagickString(color,p,(
size_t) MagickMin(q-p+1,
601 (void) QueryMagickColor(color,&pixel,exception);
602 for (i=0; i < (ssize_t) component_image->colors; i++)
603 if (IsMagickColorSimilar(&
object[i].color,&pixel) != MagickFalse)
604 object[i].merge=MagickTrue;
610 artifact=GetImageArtifact(image,
"connected-components:remove-ids");
611 if (artifact == (
const char *) NULL)
612 artifact=GetImageArtifact(image,
"connected-components:remove");
613 if (artifact != (
const char *) NULL)
614 for (c=(
char *) artifact; *c !=
'\0'; )
619 while ((isspace((
int) ((
unsigned char) *c)) != 0) || (*c ==
','))
621 first=(ssize_t) strtol(c,&c,10);
623 first+=(ssize_t) component_image->colors;
625 while (isspace((
int) ((
unsigned char) *c)) != 0)
629 last=(ssize_t) strtol(c+1,&c,10);
631 last+=(ssize_t) component_image->colors;
633 step=(ssize_t) (first > last ? -1 : 1);
634 for ( ; first != (last+step); first+=step)
635 object[first].merge=MagickTrue;
640 component_view=AcquireAuthenticCacheView(component_image,exception);
641 object_view=AcquireVirtualCacheView(component_image,exception);
642 for (i=0; i < (ssize_t) component_image->colors; i++)
653 if (status == MagickFalse)
655 if ((
object[i].merge == MagickFalse) || (i == background_id))
660 for (j=0; j < (ssize_t) component_image->colors; j++)
662 bounding_box=
object[i].bounding_box;
663 for (y=0; y < (ssize_t) bounding_box.height; y++)
666 *magick_restrict indexes;
674 if (status == MagickFalse)
676 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
677 bounding_box.y+y,bounding_box.width,1,exception);
683 indexes=GetCacheViewVirtualIndexQueue(component_view);
684 for (x=0; x < (ssize_t) bounding_box.width; x++)
689 if (status == MagickFalse)
691 j=(ssize_t) indexes[x];
693 for (k=0; k < (ssize_t) (connectivity > 4 ? 4 : 2); k++)
696 *magick_restrict indexes;
704 if (status == MagickFalse)
706 dx=connectivity > 4 ? connect8[k][1] : connect4[k][1];
707 dy=connectivity > 4 ? connect8[k][0] : connect4[k][0];
708 p=GetCacheViewVirtualPixels(object_view,bounding_box.x+x+dx,
709 bounding_box.y+y+dy,1,1,exception);
715 indexes=GetCacheViewVirtualIndexQueue(object_view);
716 j=(ssize_t) *indexes;
726 for (j=1; j < (ssize_t) component_image->colors; j++)
727 if (
object[j].census >
object[
id].census)
730 for (y=0; y < (ssize_t) bounding_box.height; y++)
733 *magick_restrict component_indexes;
741 if (status == MagickFalse)
743 q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
744 bounding_box.y+y,bounding_box.width,1,exception);
750 component_indexes=GetCacheViewAuthenticIndexQueue(component_view);
751 for (x=0; x < (ssize_t) bounding_box.width; x++)
753 if ((ssize_t) component_indexes[x] == i)
754 component_indexes[x]=(IndexPacket)
id;
756 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
760 object_view=DestroyCacheView(object_view);
761 component_view=DestroyCacheView(component_view);
762 artifact=GetImageArtifact(image,
"connected-components:mean-color");
763 if (IsMagickTrue(artifact) != MagickFalse)
768 for (i=0; i < (ssize_t) component_image->colors; i++)
770 component_image->colormap[i].red=ClampToQuantum(
object[i].color.red);
771 component_image->colormap[i].green=ClampToQuantum(
772 object[i].color.green);
773 component_image->colormap[i].blue=ClampToQuantum(
object[i].color.blue);
774 component_image->colormap[i].opacity=ClampToQuantum(
775 object[i].color.opacity);
778 (void) SyncImage(component_image);
779 artifact=GetImageArtifact(image,
"connected-components:verbose");
780 if (IsMagickTrue(artifact) != MagickFalse)
785 for (i=0; i < (ssize_t) component_image->colors; i++)
787 object[i].bounding_box.width=0;
788 object[i].bounding_box.height=0;
789 object[i].bounding_box.x=(ssize_t) component_image->columns;
790 object[i].bounding_box.y=(ssize_t) component_image->rows;
791 object[i].centroid.x=0;
792 object[i].centroid.y=0;
793 object[i].census=
object[i].area == 0.0 ? 0.0 : 1.0;
796 component_view=AcquireVirtualCacheView(component_image,exception);
797 for (y=0; y < (ssize_t) component_image->rows; y++)
808 if (status == MagickFalse)
810 p=GetCacheViewVirtualPixels(component_view,0,y,
811 component_image->columns,1,exception);
817 indexes=GetCacheViewVirtualIndexQueue(component_view);
818 for (x=0; x < (ssize_t) component_image->columns; x++)
823 id=(size_t) indexes[x];
824 if (x <
object[
id].bounding_box.x)
825 object[id].bounding_box.x=x;
826 if (x > (ssize_t)
object[
id].bounding_box.width)
827 object[id].bounding_box.width=(size_t) x;
828 if (y <
object[
id].bounding_box.y)
829 object[id].bounding_box.y=y;
830 if (y > (ssize_t)
object[
id].bounding_box.height)
831 object[id].bounding_box.height=(size_t) y;
832 object[id].centroid.x+=x;
833 object[id].centroid.y+=y;
837 for (i=0; i < (ssize_t) component_image->colors; i++)
839 object[i].bounding_box.width-=(
object[i].bounding_box.x-1);
840 object[i].bounding_box.height-=(
object[i].bounding_box.y-1);
841 object[i].centroid.x=
object[i].centroid.x/
object[i].area;
842 object[i].centroid.y=
object[i].centroid.y/
object[i].area;
844 component_view=DestroyCacheView(component_view);
845 qsort((
void *)
object,component_image->colors,
sizeof(*
object),
846 CCObjectInfoCompare);
847 artifact=GetImageArtifact(image,
"connected-components:exclude-header");
848 if (IsStringTrue(artifact) == MagickFalse)
849 (void) fprintf(stdout,
850 "Objects (id: bounding-box centroid area mean-color):\n");
851 for (i=0; i < (ssize_t) component_image->colors; i++)
852 if (
object[i].census > 0.0)
855 mean_color[MaxTextExtent];
857 GetColorTuple(&
object[i].color,MagickFalse,mean_color);
858 (void) fprintf(stdout,
859 " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(
double)
860 object[i].
id,(
double)
object[i].bounding_box.width,(
double)
861 object[i].bounding_box.height,(
double)
object[i].bounding_box.x,
862 (
double)
object[i].bounding_box.y,
object[i].centroid.x,
863 object[i].centroid.y,(
double)
object[i].area,mean_color);
867 return(component_image);