39#include "MagickCore/studio.h"
40#include "MagickCore/artifact.h"
41#include "MagickCore/blob.h"
42#include "MagickCore/cache-view.h"
43#include "MagickCore/color.h"
44#include "MagickCore/color-private.h"
45#include "MagickCore/colormap.h"
46#include "MagickCore/colorspace.h"
47#include "MagickCore/constitute.h"
48#include "MagickCore/decorate.h"
49#include "MagickCore/distort.h"
50#include "MagickCore/draw.h"
51#include "MagickCore/enhance.h"
52#include "MagickCore/exception.h"
53#include "MagickCore/exception-private.h"
54#include "MagickCore/effect.h"
55#include "MagickCore/gem.h"
56#include "MagickCore/geometry.h"
57#include "MagickCore/image-private.h"
58#include "MagickCore/list.h"
59#include "MagickCore/log.h"
60#include "MagickCore/matrix.h"
61#include "MagickCore/memory_.h"
62#include "MagickCore/memory-private.h"
63#include "MagickCore/monitor.h"
64#include "MagickCore/monitor-private.h"
65#include "MagickCore/montage.h"
66#include "MagickCore/morphology.h"
67#include "MagickCore/morphology-private.h"
68#include "MagickCore/opencl-private.h"
69#include "MagickCore/paint.h"
70#include "MagickCore/pixel-accessor.h"
71#include "MagickCore/property.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/resource_.h"
74#include "MagickCore/signature-private.h"
75#include "MagickCore/string_.h"
76#include "MagickCore/string-private.h"
77#include "MagickCore/thread-private.h"
78#include "MagickCore/token.h"
79#include "MagickCore/vision.h"
119static int CCObjectInfoCompare(
const void *x,
const void *y)
125 p=(CCObjectInfo *) x;
126 q=(CCObjectInfo *) y;
128 return((
int) (q->bounding_box.y-(ssize_t) p->bounding_box.y));
130 return((
int) (q->bounding_box.x-(ssize_t) p->bounding_box.x));
132 return((
int) (q->bounding_box.height-p->bounding_box.height));
134 return((
int) (q->bounding_box.width-p->bounding_box.width));
136 return((
int) (q->area-(ssize_t) p->area));
138 return((
int) (p->area-(ssize_t) q->area));
140 return((
int) (p->bounding_box.width-q->bounding_box.width));
142 return((
int) (p->bounding_box.height-q->bounding_box.height));
144 return((
int) (p->bounding_box.x-(ssize_t) q->bounding_box.x));
146 return((
int) (p->bounding_box.y-(ssize_t) q->bounding_box.y));
147 return((
int) (q->area-(ssize_t) p->area));
150static void PerimeterThreshold(
const Image *component_image,
151 CCObjectInfo *
object,
const ssize_t metric_index,ExceptionInfo *exception)
160#if defined(MAGICKCORE_OPENMP_SUPPORT)
161 #pragma omp parallel for schedule(dynamic) shared(status) \
162 magick_number_threads(component_image,component_image,component_image->colors,1)
164 for (i=0; i < (ssize_t) component_image->colors; i++)
173 pattern[4] = { 1, 0, 0, 0 };
181 if (status == MagickFalse)
183 component_view=AcquireAuthenticCacheView(component_image,exception);
184 bounding_box=
object[i].bounding_box;
185 for (y=(-1); y < (ssize_t) bounding_box.height; y++)
193 p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
194 bounding_box.y+y,bounding_box.width+2,2,exception);
195 if (p == (
const Quantum *) NULL)
200 for (x=(-1); x < (ssize_t) bounding_box.width; x++)
216 for (v=0; v < 2; v++)
221 for (u=0; u < 2; u++)
226 offset=v*((ssize_t) bounding_box.width+2)*
227 (ssize_t) GetPixelChannels(component_image)+u*
228 (ssize_t) GetPixelChannels(component_image);
229 pixels[2*v+u]=GetPixelIndex(component_image,p+offset);
230 if ((ssize_t) pixels[2*v+u] == i)
239 if ((((ssize_t) pixels[0] == i) && ((ssize_t) pixels[3] == i)) ||
240 (((ssize_t) pixels[1] == i) && ((ssize_t) pixels[2] == i)))
248 p+=(ptrdiff_t) GetPixelChannels(component_image);
251 component_view=DestroyCacheView(component_view);
252 object[i].metric[metric_index]=ceil(MagickSQ1_2*pattern[1]+1.0*pattern[2]+
253 MagickSQ1_2*pattern[3]+MagickSQ2*pattern[0]-0.5);
257static void CircularityThreshold(
const Image *component_image,
258 CCObjectInfo *
object,
const ssize_t metric_index,ExceptionInfo *exception)
267#if defined(MAGICKCORE_OPENMP_SUPPORT)
268 #pragma omp parallel for schedule(dynamic) shared(status) \
269 magick_number_threads(component_image,component_image,component_image->colors,1)
271 for (i=0; i < (ssize_t) component_image->colors; i++)
280 pattern[4] = { 1, 0, 0, 0 };
288 if (status == MagickFalse)
290 component_view=AcquireAuthenticCacheView(component_image,exception);
291 bounding_box=
object[i].bounding_box;
292 for (y=(-1); y < (ssize_t) bounding_box.height; y++)
300 p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
301 bounding_box.y+y,bounding_box.width+2,2,exception);
302 if (p == (
const Quantum *) NULL)
307 for (x=(-1); x < (ssize_t) bounding_box.width; x++)
323 for (v=0; v < 2; v++)
328 for (u=0; u < 2; u++)
333 offset=v*((ssize_t) bounding_box.width+2)*
334 (ssize_t) GetPixelChannels(component_image)+u*
335 (ssize_t) GetPixelChannels(component_image);
336 pixels[2*v+u]=GetPixelIndex(component_image,p+offset);
337 if ((ssize_t) pixels[2*v+u] == i)
346 if ((((ssize_t) pixels[0] == i) && ((ssize_t) pixels[3] == i)) ||
347 (((ssize_t) pixels[1] == i) && ((ssize_t) pixels[2] == i)))
355 p+=(ptrdiff_t) GetPixelChannels(component_image);
358 component_view=DestroyCacheView(component_view);
359 object[i].metric[metric_index]=ceil(MagickSQ1_2*pattern[1]+1.0*pattern[2]+
360 MagickSQ1_2*pattern[3]+MagickSQ2*pattern[0]-0.5);
361 object[i].metric[metric_index]=4.0*MagickPI*
object[i].area/
362 (
object[i].metric[metric_index]*
object[i].metric[metric_index]);
366static void MajorAxisThreshold(
const Image *component_image,
367 CCObjectInfo *
object,
const ssize_t metric_index,ExceptionInfo *exception)
376#if defined(MAGICKCORE_OPENMP_SUPPORT)
377 #pragma omp parallel for schedule(dynamic) shared(status) \
378 magick_number_threads(component_image,component_image,component_image->colors,1)
380 for (i=0; i < (ssize_t) component_image->colors; i++)
394 centroid = { 0.0, 0.0 };
411 if (status == MagickFalse)
413 component_view=AcquireAuthenticCacheView(component_image,exception);
414 bounding_box=
object[i].bounding_box;
415 for (y=0; y < (ssize_t) bounding_box.height; y++)
417 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
418 bounding_box.y+y,bounding_box.width,1,exception);
419 if (p == (
const Quantum *) NULL)
424 for (x=0; x < (ssize_t) bounding_box.width; x++)
426 if ((ssize_t) GetPixelIndex(component_image,p) == i)
432 p+=(ptrdiff_t) GetPixelChannels(component_image);
435 centroid.x=M10*MagickSafeReciprocal(M00);
436 centroid.y=M01*MagickSafeReciprocal(M00);
437 for (y=0; y < (ssize_t) bounding_box.height; y++)
439 if (status == MagickFalse)
441 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
442 bounding_box.y+y,bounding_box.width,1,exception);
443 if (p == (
const Quantum *) NULL)
448 for (x=0; x < (ssize_t) bounding_box.width; x++)
450 if ((ssize_t) GetPixelIndex(component_image,p) == i)
452 M11+=(x-centroid.x)*(y-centroid.y);
453 M20+=(x-centroid.x)*(x-centroid.x);
454 M02+=(y-centroid.y)*(y-centroid.y);
456 p+=(ptrdiff_t) GetPixelChannels(component_image);
459 component_view=DestroyCacheView(component_view);
460 object[i].metric[metric_index]=sqrt((2.0*MagickSafeReciprocal(M00))*
461 ((M20+M02)+sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
465static void MinorAxisThreshold(
const Image *component_image,
466 CCObjectInfo *
object,
const ssize_t metric_index,ExceptionInfo *exception)
475#if defined(MAGICKCORE_OPENMP_SUPPORT)
476 #pragma omp parallel for schedule(dynamic) shared(status) \
477 magick_number_threads(component_image,component_image,component_image->colors,1)
479 for (i=0; i < (ssize_t) component_image->colors; i++)
493 centroid = { 0.0, 0.0 };
510 if (status == MagickFalse)
512 component_view=AcquireAuthenticCacheView(component_image,exception);
513 bounding_box=
object[i].bounding_box;
514 for (y=0; y < (ssize_t) bounding_box.height; y++)
516 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
517 bounding_box.y+y,bounding_box.width,1,exception);
518 if (p == (
const Quantum *) NULL)
523 for (x=0; x < (ssize_t) bounding_box.width; x++)
525 if ((ssize_t) GetPixelIndex(component_image,p) == i)
531 p+=(ptrdiff_t) GetPixelChannels(component_image);
534 centroid.x=M10*MagickSafeReciprocal(M00);
535 centroid.y=M01*MagickSafeReciprocal(M00);
536 for (y=0; y < (ssize_t) bounding_box.height; y++)
538 if (status == MagickFalse)
540 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
541 bounding_box.y+y,bounding_box.width,1,exception);
542 if (p == (
const Quantum *) NULL)
547 for (x=0; x < (ssize_t) bounding_box.width; x++)
549 if ((ssize_t) GetPixelIndex(component_image,p) == i)
551 M11+=(x-centroid.x)*(y-centroid.y);
552 M20+=(x-centroid.x)*(x-centroid.x);
553 M02+=(y-centroid.y)*(y-centroid.y);
555 p+=(ptrdiff_t) GetPixelChannels(component_image);
558 component_view=DestroyCacheView(component_view);
559 object[i].metric[metric_index]=sqrt((2.0*MagickSafeReciprocal(M00))*
560 ((M20+M02)-sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
564static void EccentricityThreshold(
const Image *component_image,
565 CCObjectInfo *
object,
const ssize_t metric_index,ExceptionInfo *exception)
574#if defined(MAGICKCORE_OPENMP_SUPPORT)
575 #pragma omp parallel for schedule(dynamic) shared(status) \
576 magick_number_threads(component_image,component_image,component_image->colors,1)
578 for (i=0; i < (ssize_t) component_image->colors; i++)
592 centroid = { 0.0, 0.0 },
593 ellipse_axis = { 0.0, 0.0 };
610 if (status == MagickFalse)
612 component_view=AcquireAuthenticCacheView(component_image,exception);
613 bounding_box=
object[i].bounding_box;
614 for (y=0; y < (ssize_t) bounding_box.height; y++)
616 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
617 bounding_box.y+y,bounding_box.width,1,exception);
618 if (p == (
const Quantum *) NULL)
623 for (x=0; x < (ssize_t) bounding_box.width; x++)
625 if ((ssize_t) GetPixelIndex(component_image,p) == i)
631 p+=(ptrdiff_t) GetPixelChannels(component_image);
634 centroid.x=M10*MagickSafeReciprocal(M00);
635 centroid.y=M01*MagickSafeReciprocal(M00);
636 for (y=0; y < (ssize_t) bounding_box.height; y++)
638 if (status == MagickFalse)
640 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
641 bounding_box.y+y,bounding_box.width,1,exception);
642 if (p == (
const Quantum *) NULL)
647 for (x=0; x < (ssize_t) bounding_box.width; x++)
649 if ((ssize_t) GetPixelIndex(component_image,p) == i)
651 M11+=(x-centroid.x)*(y-centroid.y);
652 M20+=(x-centroid.x)*(x-centroid.x);
653 M02+=(y-centroid.y)*(y-centroid.y);
655 p+=(ptrdiff_t) GetPixelChannels(component_image);
658 component_view=DestroyCacheView(component_view);
659 ellipse_axis.x=sqrt((2.0*MagickSafeReciprocal(M00))*((M20+M02)+
660 sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
661 ellipse_axis.y=sqrt((2.0*MagickSafeReciprocal(M00))*((M20+M02)-
662 sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
663 object[i].metric[metric_index]=sqrt(1.0-(ellipse_axis.y*ellipse_axis.y*
664 MagickSafeReciprocal(ellipse_axis.x*ellipse_axis.x)));
668static void AngleThreshold(
const Image *component_image,
669 CCObjectInfo *
object,
const ssize_t metric_index,ExceptionInfo *exception)
678#if defined(MAGICKCORE_OPENMP_SUPPORT)
679 #pragma omp parallel for schedule(dynamic) shared(status) \
680 magick_number_threads(component_image,component_image,component_image->colors,1)
682 for (i=0; i < (ssize_t) component_image->colors; i++)
696 centroid = { 0.0, 0.0 };
713 if (status == MagickFalse)
715 component_view=AcquireAuthenticCacheView(component_image,exception);
716 bounding_box=
object[i].bounding_box;
717 for (y=0; y < (ssize_t) bounding_box.height; y++)
719 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
720 bounding_box.y+y,bounding_box.width,1,exception);
721 if (p == (
const Quantum *) NULL)
726 for (x=0; x < (ssize_t) bounding_box.width; x++)
728 if ((ssize_t) GetPixelIndex(component_image,p) == i)
734 p+=(ptrdiff_t) GetPixelChannels(component_image);
737 centroid.x=M10*MagickSafeReciprocal(M00);
738 centroid.y=M01*MagickSafeReciprocal(M00);
739 for (y=0; y < (ssize_t) bounding_box.height; y++)
741 if (status == MagickFalse)
743 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
744 bounding_box.y+y,bounding_box.width,1,exception);
745 if (p == (
const Quantum *) NULL)
750 for (x=0; x < (ssize_t) bounding_box.width; x++)
752 if ((ssize_t) GetPixelIndex(component_image,p) == i)
754 M11+=(x-centroid.x)*(y-centroid.y);
755 M20+=(x-centroid.x)*(x-centroid.x);
756 M02+=(y-centroid.y)*(y-centroid.y);
758 p+=(ptrdiff_t) GetPixelChannels(component_image);
761 component_view=DestroyCacheView(component_view);
762 object[i].metric[metric_index]=RadiansToDegrees(1.0/2.0*atan(2.0*M11*
763 MagickSafeReciprocal(M20-M02)));
766 if ((fabs(M20-M02) >= 0.0) && ((M20-M02) < 0.0))
767 object[i].metric[metric_index]+=90.0;
772 if (fabs(M20-M02) >= 0.0)
775 object[i].metric[metric_index]+=90.0;
777 object[i].metric[metric_index]+=180.0;
781 if ((fabs(M20-M02) >= 0.0) && ((M20-M02) < 0.0))
782 object[i].metric[metric_index]+=90.0;
786MagickExport Image *ConnectedComponentsImage(
const Image *image,
787 const size_t connectivity,CCObjectInfo **objects,ExceptionInfo *exception)
789#define ConnectedComponentsImageTag "ConnectedComponents/Image"
805 *metrics[CCMaxMetrics];
828 connect4[2][2] = { { -1, 0 }, { 0, -1 } },
829 connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
842 assert(image != (Image *) NULL);
843 assert(image->signature == MagickCoreSignature);
844 assert(exception != (ExceptionInfo *) NULL);
845 assert(exception->signature == MagickCoreSignature);
846 if (IsEventLogging() != MagickFalse)
847 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
848 if (objects != (CCObjectInfo **) NULL)
849 *objects=(CCObjectInfo *) NULL;
850 component_image=CloneImage(image,0,0,MagickTrue,exception);
851 if (component_image == (Image *) NULL)
852 return((Image *) NULL);
853 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
854 if (AcquireImageColormap(component_image,MaxColormapSize,exception) == MagickFalse)
856 component_image=DestroyImage(component_image);
857 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
862 size=image->columns*image->rows;
863 if (image->columns != (size/image->rows))
865 component_image=DestroyImage(component_image);
866 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
868 equivalences=AcquireMatrixInfo(size,1,
sizeof(ssize_t),exception);
869 if (equivalences == (MatrixInfo *) NULL)
871 component_image=DestroyImage(component_image);
872 return((Image *) NULL);
874 for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
875 (
void) SetMatrixElement(equivalences,n,0,&n);
876 object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,
sizeof(*
object));
877 if (
object == (CCObjectInfo *) NULL)
879 equivalences=DestroyMatrixInfo(equivalences);
880 component_image=DestroyImage(component_image);
881 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
883 (void) memset(
object,0,MaxColormapSize*
sizeof(*
object));
884 for (i=0; i < (ssize_t) MaxColormapSize; i++)
887 object[i].bounding_box.x=(ssize_t) image->columns;
888 object[i].bounding_box.y=(ssize_t) image->rows;
889 GetPixelInfo(image,&
object[i].color);
896 image_view=AcquireVirtualCacheView(image,exception);
897 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
899 if (status == MagickFalse)
901 dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
902 dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
903 for (y=0; y < (ssize_t) image->rows; y++)
911 if (status == MagickFalse)
913 p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
914 if (p == (
const Quantum *) NULL)
919 p+=(ptrdiff_t) GetPixelChannels(image)*image->columns;
920 for (x=0; x < (ssize_t) image->columns; x++)
937 GetPixelInfoPixel(image,p,&pixel);
938 if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
939 ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
941 p+=(ptrdiff_t) GetPixelChannels(image);
944 neighbor_offset=dy*((ssize_t) GetPixelChannels(image)*(ssize_t)
945 image->columns)+dx*(ssize_t) GetPixelChannels(image);
946 GetPixelInfoPixel(image,p+neighbor_offset,&target);
947 if (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse)
949 p+=(ptrdiff_t) GetPixelChannels(image);
955 offset=y*(ssize_t) image->columns+x;
956 neighbor_offset=dy*(ssize_t) image->columns+dx;
958 status=GetMatrixElement(equivalences,ox,0,&obj);
962 status=GetMatrixElement(equivalences,ox,0,&obj);
964 oy=offset+neighbor_offset;
965 status=GetMatrixElement(equivalences,oy,0,&obj);
969 status=GetMatrixElement(equivalences,oy,0,&obj);
973 status=SetMatrixElement(equivalences,oy,0,&ox);
978 status=SetMatrixElement(equivalences,ox,0,&oy);
982 status=GetMatrixElement(equivalences,ox,0,&obj);
985 status=GetMatrixElement(equivalences,ox,0,&obj);
986 status=SetMatrixElement(equivalences,ox,0,&root);
988 oy=offset+neighbor_offset;
989 status=GetMatrixElement(equivalences,oy,0,&obj);
992 status=GetMatrixElement(equivalences,oy,0,&obj);
993 status=SetMatrixElement(equivalences,oy,0,&root);
995 status=SetMatrixElement(equivalences,y*(ssize_t) image->columns+x,0,
997 p+=(ptrdiff_t) GetPixelChannels(image);
1005 component_view=AcquireAuthenticCacheView(component_image,exception);
1006 for (y=0; y < (ssize_t) component_image->rows; y++)
1017 if (status == MagickFalse)
1019 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1020 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
1022 if ((p == (
const Quantum *) NULL) || (q == (Quantum *) NULL))
1027 for (x=0; x < (ssize_t) component_image->columns; x++)
1033 offset=y*(ssize_t) image->columns+x;
1034 status=GetMatrixElement(equivalences,offset,0,&
id);
1036 status=GetMatrixElement(equivalences,
id,0,&
id);
1040 if (
id >= (ssize_t) MaxColormapSize)
1043 status=SetMatrixElement(equivalences,offset,0,&
id);
1044 if (x <
object[
id].bounding_box.x)
1045 object[id].bounding_box.x=x;
1046 if (x >= (ssize_t)
object[
id].bounding_box.width)
1047 object[id].bounding_box.width=(size_t) x;
1048 if (y <
object[
id].bounding_box.y)
1049 object[id].bounding_box.y=y;
1050 if (y >= (ssize_t)
object[
id].bounding_box.height)
1051 object[id].bounding_box.height=(size_t) y;
1052 object[id].color.red+=QuantumScale*(double) GetPixelRed(image,p);
1053 object[id].color.green+=QuantumScale*(double) GetPixelGreen(image,p);
1054 object[id].color.blue+=QuantumScale*(double) GetPixelBlue(image,p);
1055 if (image->alpha_trait != UndefinedPixelTrait)
1056 object[id].color.alpha+=QuantumScale*(double) GetPixelAlpha(image,p);
1057 if (image->colorspace == CMYKColorspace)
1058 object[id].color.black+=QuantumScale*(double) GetPixelBlack(image,p);
1059 object[id].centroid.x+=x;
1060 object[id].centroid.y+=y;
1062 SetPixelIndex(component_image,(Quantum)
id,q);
1063 p+=(ptrdiff_t) GetPixelChannels(image);
1064 q+=(ptrdiff_t) GetPixelChannels(component_image);
1066 if (n > (ssize_t) MaxColormapSize)
1068 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
1070 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1076 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
1078 if (proceed == MagickFalse)
1082 component_view=DestroyCacheView(component_view);
1083 image_view=DestroyCacheView(image_view);
1084 equivalences=DestroyMatrixInfo(equivalences);
1085 if (n > (ssize_t) MaxColormapSize)
1087 object=(CCObjectInfo *) RelinquishMagickMemory(
object);
1088 component_image=DestroyImage(component_image);
1089 ThrowImageException(ResourceLimitError,
"TooManyObjects");
1094 component_image->colors=(size_t) n;
1095 for (i=0; i < (ssize_t) component_image->colors; i++)
1097 object[i].bounding_box.width=(size_t) ((ssize_t)
1098 object[i].bounding_box.width-(
object[i].bounding_box.x-1));
1099 object[i].bounding_box.height=(size_t) ((ssize_t)
1100 object[i].bounding_box.height-(
object[i].bounding_box.y-1));
1101 object[i].color.red/=(QuantumScale*
object[i].area);
1102 object[i].color.green/=(QuantumScale*
object[i].area);
1103 object[i].color.blue/=(QuantumScale*
object[i].area);
1104 if (image->alpha_trait != UndefinedPixelTrait)
1105 object[i].color.alpha/=(QuantumScale*
object[i].area);
1106 if (image->colorspace == CMYKColorspace)
1107 object[i].color.black/=(QuantumScale*
object[i].area);
1108 object[i].centroid.x/=
object[i].area;
1109 object[i].centroid.y/=
object[i].area;
1110 max_threshold+=
object[i].area;
1111 if (
object[i].area >
object[background_id].area)
1114 max_threshold+=MagickEpsilon;
1116 artifact=GetImageArtifact(image,
"connected-components:background-id");
1117 if (artifact != (
const char *) NULL)
1118 background_id=(ssize_t) StringToLong(artifact);
1119 artifact=GetImageArtifact(image,
"connected-components:area-threshold");
1120 if (artifact != (
const char *) NULL)
1125 (void) MagickSscanf(artifact,
"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1126 for (i=0; i < (ssize_t) component_image->colors; i++)
1127 if (((
object[i].area < min_threshold) ||
1128 (
object[i].area >= max_threshold)) && (i != background_id))
1129 object[i].merge=MagickTrue;
1131 artifact=GetImageArtifact(image,
"connected-components:keep-colors");
1132 if (artifact != (
const char *) NULL)
1140 for (i=0; i < (ssize_t) component_image->colors; i++)
1141 object[i].merge=MagickTrue;
1142 for (p=artifact; ; )
1145 color[MagickPathExtent];
1153 for (q=p; *q !=
'\0'; q++)
1156 (void) CopyMagickString(color,p,(
size_t) MagickMin(q-p+1,
1158 (void) QueryColorCompliance(color,AllCompliance,&pixel,exception);
1159 for (i=0; i < (ssize_t) component_image->colors; i++)
1160 if (IsFuzzyEquivalencePixelInfo(&
object[i].color,&pixel) != MagickFalse)
1161 object[i].merge=MagickFalse;
1167 artifact=GetImageArtifact(image,
"connected-components:keep-ids");
1168 if (artifact == (
const char *) NULL)
1169 artifact=GetImageArtifact(image,
"connected-components:keep");
1170 if (artifact != (
const char *) NULL)
1175 for (i=0; i < (ssize_t) component_image->colors; i++)
1176 object[i].merge=MagickTrue;
1177 for (c=(
char *) artifact; *c !=
'\0'; )
1179 while ((isspace((
int) ((
unsigned char) *c)) != 0) || (*c ==
','))
1182 first=(ssize_t) strtol(c,&d,10);
1187 first+=(ssize_t) component_image->colors;
1189 while (isspace((
int) ((
unsigned char) *c)) != 0)
1193 last=(ssize_t) strtol(c+1,&c,10);
1195 last+=(ssize_t) component_image->colors;
1197 step=(ssize_t) (first > last ? -1 : 1);
1198 for ( ; first != (last+step); first+=step)
1200 (first < (ssize_t) component_image->colors))
1201 object[first].merge=MagickFalse;
1204 artifact=GetImageArtifact(image,
"connected-components:keep-top");
1205 if (artifact != (
const char *) NULL)
1216 top_ids=(ssize_t) StringToLong(artifact);
1219 if (top_ids >= (ssize_t) component_image->colors)
1220 top_ids=(ssize_t) component_image->colors-1;
1221 top_objects=(CCObjectInfo *) AcquireQuantumMemory(component_image->colors,
1222 sizeof(*top_objects));
1223 if (top_objects == (CCObjectInfo *) NULL)
1225 object=(CCObjectInfo *) RelinquishMagickMemory(
object);
1226 component_image=DestroyImage(component_image);
1227 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
1229 (void) memcpy(top_objects,
object,component_image->colors*
sizeof(*
object));
1230 qsort((
void *) top_objects,component_image->colors,
sizeof(*top_objects),
1231 CCObjectInfoCompare);
1232 for (i=top_ids+1; i < (ssize_t) component_image->colors; i++)
1234 ssize_t
id = (ssize_t) top_objects[i].
id;
1235 if ((
id >= 0) && (
id < (ssize_t) component_image->colors))
1236 object[id].merge=MagickTrue;
1238 top_objects=(CCObjectInfo *) RelinquishMagickMemory(top_objects);
1240 artifact=GetImageArtifact(image,
"connected-components:remove-colors");
1241 if (artifact != (
const char *) NULL)
1249 for (p=artifact; ; )
1252 color[MagickPathExtent];
1260 for (q=p; *q !=
'\0'; q++)
1263 (void) CopyMagickString(color,p,(
size_t) MagickMin(q-p+1,
1265 (void) QueryColorCompliance(color,AllCompliance,&pixel,exception);
1266 for (i=0; i < (ssize_t) component_image->colors; i++)
1267 if (IsFuzzyEquivalencePixelInfo(&
object[i].color,&pixel) != MagickFalse)
1268 object[i].merge=MagickTrue;
1274 artifact=GetImageArtifact(image,
"connected-components:remove-ids");
1275 if (artifact == (
const char *) NULL)
1276 artifact=GetImageArtifact(image,
"connected-components:remove");
1277 if (artifact != (
const char *) NULL)
1278 for (c=(
char *) artifact; *c !=
'\0'; )
1283 while ((isspace((
int) ((
unsigned char) *c)) != 0) || (*c ==
','))
1286 first=(ssize_t) strtol(c,&d,10);
1291 first+=(ssize_t) component_image->colors;
1293 while (isspace((
int) ((
unsigned char) *c)) != 0)
1297 last=(ssize_t) strtol(c+1,&c,10);
1299 last+=(ssize_t) component_image->colors;
1301 step=(ssize_t) (first > last ? -1 : 1);
1302 for ( ; first != (last+step); first+=step)
1304 (first < (ssize_t) component_image->colors))
1305 object[first].merge=MagickTrue;
1307 artifact=GetImageArtifact(image,
"connected-components:perimeter-threshold");
1308 if (artifact != (
const char *) NULL)
1313 (void) MagickSscanf(artifact,
"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1314 metrics[++n]=
"perimeter";
1315 PerimeterThreshold(component_image,
object,n,exception);
1316 for (i=0; i < (ssize_t) component_image->colors; i++)
1317 if (((
object[i].metric[n] < min_threshold) ||
1318 (
object[i].metric[n] >= max_threshold)) && (i != background_id))
1319 object[i].merge=MagickTrue;
1321 artifact=GetImageArtifact(image,
"connected-components:circularity-threshold");
1322 if (artifact != (
const char *) NULL)
1327 (void) MagickSscanf(artifact,
"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1328 metrics[++n]=
"circularity";
1329 CircularityThreshold(component_image,
object,n,exception);
1330 for (i=0; i < (ssize_t) component_image->colors; i++)
1331 if (((
object[i].metric[n] < min_threshold) ||
1332 (
object[i].metric[n] >= max_threshold)) && (i != background_id))
1333 object[i].merge=MagickTrue;
1335 artifact=GetImageArtifact(image,
"connected-components:diameter-threshold");
1336 if (artifact != (
const char *) NULL)
1341 (void) MagickSscanf(artifact,
"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1342 metrics[++n]=
"diameter";
1343 for (i=0; i < (ssize_t) component_image->colors; i++)
1345 object[i].metric[n]=ceil(sqrt(4.0*
object[i].area/MagickPI)-0.5);
1346 if (((
object[i].metric[n] < min_threshold) ||
1347 (
object[i].metric[n] >= max_threshold)) && (i != background_id))
1348 object[i].merge=MagickTrue;
1351 artifact=GetImageArtifact(image,
"connected-components:major-axis-threshold");
1352 if (artifact != (
const char *) NULL)
1357 (void) MagickSscanf(artifact,
"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1358 metrics[++n]=
"major-axis";
1359 MajorAxisThreshold(component_image,
object,n,exception);
1360 for (i=0; i < (ssize_t) component_image->colors; i++)
1361 if (((
object[i].metric[n] < min_threshold) ||
1362 (
object[i].metric[n] >= max_threshold)) && (i != background_id))
1363 object[i].merge=MagickTrue;
1365 artifact=GetImageArtifact(image,
"connected-components:minor-axis-threshold");
1366 if (artifact != (
const char *) NULL)
1371 (void) MagickSscanf(artifact,
"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1372 metrics[++n]=
"minor-axis";
1373 MinorAxisThreshold(component_image,
object,n,exception);
1374 for (i=0; i < (ssize_t) component_image->colors; i++)
1375 if (((
object[i].metric[n] < min_threshold) ||
1376 (
object[i].metric[n] >= max_threshold)) && (i != background_id))
1377 object[i].merge=MagickTrue;
1379 artifact=GetImageArtifact(image,
"connected-components:eccentricity-threshold");
1380 if (artifact != (
const char *) NULL)
1385 (void) MagickSscanf(artifact,
"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1386 metrics[++n]=
"eccentricity";
1387 EccentricityThreshold(component_image,
object,n,exception);
1388 for (i=0; i < (ssize_t) component_image->colors; i++)
1389 if (((
object[i].metric[n] < min_threshold) ||
1390 (
object[i].metric[n] >= max_threshold)) && (i != background_id))
1391 object[i].merge=MagickTrue;
1393 artifact=GetImageArtifact(image,
"connected-components:angle-threshold");
1394 if (artifact != (
const char *) NULL)
1399 (void) MagickSscanf(artifact,
"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1400 metrics[++n]=
"angle";
1401 AngleThreshold(component_image,
object,n,exception);
1402 for (i=0; i < (ssize_t) component_image->colors; i++)
1403 if (((
object[i].metric[n] < min_threshold) ||
1404 (
object[i].metric[n] >= max_threshold)) && (i != background_id))
1405 object[i].merge=MagickTrue;
1410 component_view=AcquireAuthenticCacheView(component_image,exception);
1411 object_view=AcquireVirtualCacheView(component_image,exception);
1412 (void) SetCacheViewVirtualPixelMethod(object_view,TileVirtualPixelMethod);
1413 for (i=0; i < (ssize_t) component_image->colors; i++)
1424 if (status == MagickFalse)
1426 if ((
object[i].merge == MagickFalse) || (i == background_id))
1431 for (j=0; j < (ssize_t) component_image->colors; j++)
1433 bounding_box=
object[i].bounding_box;
1434 for (y=0; y < (ssize_t) bounding_box.height; y++)
1442 if (status == MagickFalse)
1444 p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
1445 bounding_box.y+y,bounding_box.width,1,exception);
1446 if (p == (
const Quantum *) NULL)
1451 for (x=0; x < (ssize_t) bounding_box.width; x++)
1456 if (status == MagickFalse)
1458 j=(ssize_t) GetPixelIndex(component_image,p);
1460 for (k=0; k < (ssize_t) (connectivity > 4 ? 4 : 2); k++)
1468 if (status == MagickFalse)
1470 dx=connectivity > 4 ? connect8[k][1] : connect4[k][1];
1471 dy=connectivity > 4 ? connect8[k][0] : connect4[k][0];
1472 q=GetCacheViewVirtualPixels(object_view,bounding_box.x+x+dx,
1473 bounding_box.y+y+dy,1,1,exception);
1474 if (q == (
const Quantum *) NULL)
1479 j=(ssize_t) GetPixelIndex(component_image,q);
1483 p+=(ptrdiff_t) GetPixelChannels(component_image);
1490 for (j=1; j < (ssize_t) component_image->colors; j++)
1491 if (
object[j].census >
object[
id].census)
1494 for (y=0; y < (ssize_t) bounding_box.height; y++)
1502 if (status == MagickFalse)
1504 q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
1505 bounding_box.y+y,bounding_box.width,1,exception);
1506 if (q == (Quantum *) NULL)
1511 for (x=0; x < (ssize_t) bounding_box.width; x++)
1513 if ((ssize_t) GetPixelIndex(component_image,q) == i)
1514 SetPixelIndex(component_image,(Quantum)
id,q);
1515 q+=(ptrdiff_t) GetPixelChannels(component_image);
1517 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
1521 object_view=DestroyCacheView(object_view);
1522 component_view=DestroyCacheView(component_view);
1523 artifact=GetImageArtifact(image,
"connected-components:mean-color");
1524 if (IsStringTrue(artifact) != MagickFalse)
1529 for (i=0; i < (ssize_t) component_image->colors; i++)
1530 component_image->colormap[i]=
object[i].color;
1532 (void) SyncImage(component_image,exception);
1533 artifact=GetImageArtifact(image,
"connected-components:verbose");
1534 if ((IsStringTrue(artifact) != MagickFalse) ||
1535 (objects != (CCObjectInfo **) NULL))
1544 for (i=0; i < (ssize_t) component_image->colors; i++)
1546 object[i].bounding_box.width=0;
1547 object[i].bounding_box.height=0;
1548 object[i].bounding_box.x=(ssize_t) component_image->columns;
1549 object[i].bounding_box.y=(ssize_t) component_image->rows;
1550 object[i].centroid.x=0;
1551 object[i].centroid.y=0;
1552 object[i].census=
object[i].area == 0.0 ? 0.0 : 1.0;
1555 component_view=AcquireVirtualCacheView(component_image,exception);
1556 for (y=0; y < (ssize_t) component_image->rows; y++)
1564 if (status == MagickFalse)
1566 p=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,
1568 if (p == (
const Quantum *) NULL)
1573 for (x=0; x < (ssize_t) component_image->columns; x++)
1578 id=(size_t) GetPixelIndex(component_image,p);
1579 if (x <
object[
id].bounding_box.x)
1580 object[id].bounding_box.x=x;
1581 if (x > (ssize_t)
object[
id].bounding_box.width)
1582 object[id].bounding_box.width=(size_t) x;
1583 if (y <
object[
id].bounding_box.y)
1584 object[id].bounding_box.y=y;
1585 if (y > (ssize_t)
object[
id].bounding_box.height)
1586 object[id].bounding_box.height=(size_t) y;
1587 object[id].centroid.x+=x;
1588 object[id].centroid.y+=y;
1590 p+=(ptrdiff_t) GetPixelChannels(component_image);
1593 for (i=0; i < (ssize_t) component_image->colors; i++)
1595 object[i].bounding_box.width=(size_t) ((ssize_t)
1596 object[i].bounding_box.width-(
object[i].bounding_box.x-1));
1597 object[i].bounding_box.height=(size_t) ((ssize_t)
1598 object[i].bounding_box.height-(
object[i].bounding_box.y-1));
1599 object[i].centroid.x=
object[i].centroid.x/
object[i].area;
1600 object[i].centroid.y=
object[i].centroid.y/
object[i].area;
1602 component_view=DestroyCacheView(component_view);
1604 artifact=GetImageArtifact(image,
"connected-components:sort-order");
1605 if (artifact != (
const char *) NULL)
1606 if (LocaleCompare(artifact,
"decreasing") == 0)
1609 artifact=GetImageArtifact(image,
"connected-components:sort");
1610 if (artifact != (
const char *) NULL)
1612 if (LocaleCompare(artifact,
"area") == 0)
1614 if (LocaleCompare(artifact,
"width") == 0)
1616 if (LocaleCompare(artifact,
"height") == 0)
1618 if (LocaleCompare(artifact,
"x") == 0)
1620 if (LocaleCompare(artifact,
"y") == 0)
1623 for (i=0; i < (ssize_t) component_image->colors; i++)
1624 object[i].key=order*key;
1625 qsort((
void *)
object,component_image->colors,
sizeof(*
object),
1626 CCObjectInfoCompare);
1627 if (objects == (CCObjectInfo **) NULL)
1632 artifact=GetImageArtifact(image,
1633 "connected-components:exclude-header");
1634 if (IsStringTrue(artifact) == MagickFalse)
1636 (void) fprintf(stdout,
"Objects (");
1637 artifact=GetImageArtifact(image,
1638 "connected-components:exclude-ids");
1639 if (IsStringTrue(artifact) == MagickFalse)
1640 (void) fprintf(stdout,
"id: ");
1641 (void) fprintf(stdout,
"bounding-box centroid area mean-color");
1642 for (j=0; j <= n; j++)
1643 (
void) fprintf(stdout,
" %s",metrics[j]);
1644 (void) fprintf(stdout,
"):\n");
1646 for (i=0; i < (ssize_t) component_image->colors; i++)
1647 if (
object[i].census > 0.0)
1650 mean_color[MagickPathExtent];
1652 GetColorTuple(&
object[i].color,MagickFalse,mean_color);
1653 (void) fprintf(stdout,
" ");
1654 artifact=GetImageArtifact(image,
1655 "connected-components:exclude-ids");
1656 if (IsStringTrue(artifact) == MagickFalse)
1657 (void) fprintf(stdout,
"%.20g: ",(
double)
object[i].
id);
1658 (void) fprintf(stdout,
1659 "%.20gx%.20g%+.20g%+.20g %.1f,%.1f %.*g %s",(
double)
1660 object[i].bounding_box.width,(
double)
1661 object[i].bounding_box.height,(
double)
1662 object[i].bounding_box.x,(
double)
object[i].bounding_box.y,
1663 object[i].centroid.x,
object[i].centroid.y,
1664 GetMagickPrecision(),(
double)
object[i].area,mean_color);
1665 for (j=0; j <= n; j++)
1666 (
void) fprintf(stdout,
" %.*g",GetMagickPrecision(),
1667 object[i].metric[j]);
1668 (void) fprintf(stdout,
"\n");
1672 if (objects == (CCObjectInfo **) NULL)
1673 object=(CCObjectInfo *) RelinquishMagickMemory(
object);
1676 return(component_image);
1703MagickExport Image *IntegralImage(
const Image *image,ExceptionInfo *exception)
1705#define IntegralImageTag "Integral/Image"
1726 assert(image != (
const Image *) NULL);
1727 assert(image->signature == MagickCoreSignature);
1728 assert(exception != (ExceptionInfo *) NULL);
1729 assert(exception->signature == MagickCoreSignature);
1730 if (IsEventLogging() != MagickFalse)
1731 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",image->filename);
1732 integral_image=CloneImage(image,0,0,MagickTrue,exception);
1733 if (integral_image == (Image *) NULL)
1734 return((Image *) NULL);
1735 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse)
1737 integral_image=DestroyImage(integral_image);
1738 return((Image *) NULL);
1745 image_view=AcquireVirtualCacheView(integral_image,exception);
1746 integral_view=AcquireAuthenticCacheView(integral_image,exception);
1747 for (y=0; y < (ssize_t) integral_image->rows; y++)
1761 if (status == MagickFalse)
1763 p=GetCacheViewVirtualPixels(integral_view,0,y-1,integral_image->columns,1,
1765 q=GetCacheViewAuthenticPixels(integral_view,0,y,integral_image->columns,1,
1767 if ((p == (
const Quantum *) NULL) || (p == (Quantum *) NULL))
1772 for (x=0; x < (ssize_t) integral_image->columns; x++)
1777 for (i=0; i < (ssize_t) GetPixelChannels(integral_image); i++)
1782 PixelTrait traits = GetPixelChannelTraits(integral_image,
1784 if (traits == UndefinedPixelTrait)
1786 if ((traits & CopyPixelTrait) != 0)
1790 sum+=(double) (q-GetPixelChannels(integral_image))[i];
1793 if ((x > 0) && (y > 0))
1794 sum-=(double) (p-GetPixelChannels(integral_image))[i];
1795 q[i]=ClampToQuantum(sum);
1797 p+=(ptrdiff_t) GetPixelChannels(integral_image);
1798 q+=(ptrdiff_t) GetPixelChannels(integral_image);
1800 sync=SyncCacheViewAuthenticPixels(integral_view,exception);
1801 if (sync == MagickFalse)
1803 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1809 proceed=SetImageProgress(integral_image,IntegralImageTag,progress,
1810 integral_image->rows);
1811 if (proceed == MagickFalse)
1815 integral_view=DestroyCacheView(integral_view);
1816 image_view=DestroyCacheView(image_view);
1817 if (status == MagickFalse)
1818 integral_image=DestroyImage(integral_image);
1819 return(integral_image);