From 73da9ae83c8a00a469a665722614dfde62ce6a0f Mon Sep 17 00:00:00 2001 From: Cristy Date: Sat, 14 Mar 2020 17:56:39 -0400 Subject: [PATCH] Add support for returning the convex hull of an image --- ChangeLog | 6 +- MagickCore/attribute.c | 318 +++++++++++++++++++++++++++++++++++++++++ MagickCore/attribute.h | 5 +- MagickCore/effect.c | 1 + MagickCore/property.c | 59 ++++++-- 5 files changed, 373 insertions(+), 16 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5794b1de01..86763ee199 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,10 @@ -2020-03-07 7.0.10-1 Cristy +2020-03-14 7.0.10-1 Cristy * Release ImageMagick version 7.0.10-1, GIT revision 17... +2020-03-14 7.0.10-1 Cristy + * Add support for returning the convex hull of an image with the + %[canvas-hull] property. + 2020-03-09 7.0.10-1 Dirk Lemstra * Added option to specify the preferred version when writing a PDF file with -define pdf:version=version (e.g. 1.7). diff --git a/MagickCore/attribute.c b/MagickCore/attribute.c index 1dc001df88..265b627e7a 100644 --- a/MagickCore/attribute.c +++ b/MagickCore/attribute.c @@ -519,6 +519,324 @@ MagickExport RectangleInfo GetImageBoundingBox(const Image *image, % % % % % % ++ G e t I m a g e C o n v e x H u l l % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% GetImageConvexHull() returns the convex hull points of an image canvas. +% +% The format of the GetImageConvexHull method is: +% +% PointInfo *GetImageConvexHull(const Image *image, +% size_t number_coordinates,ExceptionInfo *exception) +% +% A description of each parameter follows: +% +% o image: the image. +% +% o number_coordinates: the number of coordinates in the convex hull. +% +% o exception: return any errors or warnings in this structure. +% +*/ + +static double LexicographicalSort(PointInfo *p1,PointInfo *p2,PointInfo *p3) +{ + /* + Sort first by x-coordinate, and in case of a tie, by y-coordinate. + */ + return((p2->x-p1->x)*(p3->y-p1->y)-(p2->y-p1->y)*(p3->x-p1->x)); +} + +void ConvexHull(PointInfo *coordinates,size_t number_coordinates, + PointInfo ***monotone_chain,size_t *chain_length) +{ + PointInfo + **chain; + + register ssize_t + i; + + size_t + demark, + n; + + /* + Construct the upper and lower hulls: rightmost to leftmost counterclockwise. + */ + chain=(*monotone_chain); + n=0; + for (i=0; i < (ssize_t) number_coordinates; i++) + { + while ((n >= 2) && + (LexicographicalSort(chain[n-2],chain[n-1],&coordinates[i]) <= 0.0)) + n--; + chain[n++]=(&coordinates[i]); + } + demark=n+1; + for (i=(ssize_t) number_coordinates-2; i >= 0; i--) + { + while ((n >= demark) && + (LexicographicalSort(chain[n-2],chain[n-1],&coordinates[i]) <= 0.0)) + n--; + chain[n++]=(&coordinates[i]); + } + *monotone_chain=chain; + *chain_length=n; +} + +static PixelInfo GetEdgeBackgroundColor(const Image *image, + const CacheView *image_view,ExceptionInfo *exception) +{ + CacheView + *edge_view; + + const char + *artifact; + + double + edge_factor, + factor; + + Image + *edge_image; + + PixelInfo + background, + edge_background, + pixel; + + RectangleInfo + edge_geometry; + + register ssize_t + i; + + register const Quantum + *p; + + ssize_t + y; + + /* + Identify background color from edge of image. + */ + edge_factor=(-1.0); + GetPixelInfo(image,&edge_background); + for (i=0; i < 4; i++) + { + GravityType + gravity; + + switch (i) + { + case 0: + default: + { + gravity=WestGravity; + edge_geometry.width=1; + edge_geometry.height=0; + } + case 1: + { + gravity=EastGravity; + edge_geometry.width=1; + edge_geometry.height=0; + } + case 2: + { + gravity=NorthGravity; + edge_geometry.width=0; + edge_geometry.height=1; + } + case 3: + { + gravity=SouthGravity; + edge_geometry.width=0; + edge_geometry.height=1; + } + } + edge_geometry.x=0; + edge_geometry.y=0; + switch (gravity) + { + case NorthWestGravity: + case NorthGravity: + default: + { + p=GetCacheViewVirtualPixels(image_view,0,0,1,1,exception); + break; + } + case NorthEastGravity: + case EastGravity: + { + p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,0,1,1, + exception); + break; + } + case SouthEastGravity: + case SouthGravity: + { + p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1, + (ssize_t) image->rows-1,1,1,exception); + break; + } + case SouthWestGravity: + case WestGravity: + { + p=GetCacheViewVirtualPixels(image_view,0,(ssize_t) image->rows-1,1,1, + exception); + break; + } + } + GetPixelInfoPixel(image,p,&background); + artifact=GetImageArtifact(image,"convex-hull:background-color"); + if (artifact != (const char *) NULL) + (void) QueryColorCompliance(artifact,AllCompliance,&background,exception); + GravityAdjustGeometry(image->columns,image->rows,gravity,&edge_geometry); + edge_image=CropImage(image,&edge_geometry,exception); + if (edge_image == (Image *) NULL) + break; + factor=0.0; + edge_view=AcquireVirtualCacheView(edge_image,exception); + for (y=0; y < (ssize_t) edge_image->rows; y++) + { + register ssize_t + x; + + p=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1, + exception); + if (p == (const Quantum *) NULL) + break; + for (x=0; x < (ssize_t) edge_image->columns; x++) + { + GetPixelInfoPixel(edge_image,p,&pixel); + if (IsFuzzyEquivalencePixelInfo(&pixel,&background) == MagickFalse) + factor++; + p+=GetPixelChannels(edge_image); + } + } + factor/=((double) edge_image->columns*edge_image->rows); + if (factor > edge_factor) + { + edge_background=background; + edge_factor=factor; + } + edge_view=DestroyCacheView(edge_view); + edge_image=DestroyImage(edge_image); + } + return(edge_background); +} + +MagickExport PointInfo *GetImageConvexHull(const Image *image, + size_t *number_coordinates,ExceptionInfo *exception) +{ + CacheView + *image_view; + + MagickBooleanType + status; + + MemoryInfo + *coordinate_info; + + PixelInfo + background; + + PointInfo + *convex_hull, + *coordinates, + **monotone_chain; + + size_t + n; + + ssize_t + y; + + /* + Identify convex hull coordinates of image foreground object(s). + */ + assert(image != (Image *) NULL); + assert(image->signature == MagickCoreSignature); + if (image->debug != MagickFalse) + (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); + *number_coordinates=0; + coordinate_info=AcquireVirtualMemory(image->columns,image->rows* + sizeof(*coordinates)); + monotone_chain=(PointInfo **) AcquireQuantumMemory(2*image->columns,2* + image->rows*sizeof(*monotone_chain)); + if ((coordinate_info == (MemoryInfo *) NULL) || + (monotone_chain == (PointInfo **) NULL)) + { + if (monotone_chain != (PointInfo **) NULL) + monotone_chain=(PointInfo **) RelinquishMagickMemory(monotone_chain); + if (coordinate_info != (MemoryInfo *) NULL) + coordinate_info=RelinquishVirtualMemory(coordinate_info); + return((PointInfo *) NULL); + } + coordinates=(PointInfo *) GetVirtualMemoryBlob(coordinate_info); + image_view=AcquireVirtualCacheView(image,exception); + background=GetEdgeBackgroundColor(image,image_view,exception); + status=MagickTrue; + n=0; + for (y=0; y < (ssize_t) image->rows; y++) + { + register const Quantum + *p; + + register ssize_t + x; + + if (status == MagickFalse) + continue; + p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); + if (p == (const Quantum *) NULL) + { + status=MagickFalse; + continue; + } + for (x=0; x < (ssize_t) image->columns; x++) + { + PixelInfo + pixel; + + GetPixelInfoPixel(image,p,&pixel); + if (IsFuzzyEquivalencePixelInfo(&pixel,&background) == MagickFalse) + { + coordinates[n].x=(double) x; + coordinates[n].y=(double) y; + n++; + } + p+=GetPixelChannels(image); + } + } + image_view=DestroyCacheView(image_view); + /* + Return the convex hull of the image foreground object(s). + */ + ConvexHull(coordinates,n,&monotone_chain,number_coordinates); + convex_hull=(PointInfo *) AcquireQuantumMemory(*number_coordinates, + sizeof(*convex_hull)); + if (convex_hull == (PointInfo *) NULL) + { + coordinate_info=RelinquishVirtualMemory(coordinate_info); + return((PointInfo *) NULL); + } + for (n=0; n < *number_coordinates; n++) + convex_hull[n]=(*monotone_chain[n]); + monotone_chain=(PointInfo **) RelinquishMagickMemory(monotone_chain); + coordinate_info=RelinquishVirtualMemory(coordinate_info); + return(convex_hull); +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % % G e t I m a g e D e p t h % % % % % diff --git a/MagickCore/attribute.h b/MagickCore/attribute.h index 677e07b5d5..1563984dcc 100644 --- a/MagickCore/attribute.h +++ b/MagickCore/attribute.h @@ -38,8 +38,11 @@ extern MagickExport MagickBooleanType SetImageDepth(Image *,const size_t,ExceptionInfo *), SetImageType(Image *,const ImageType,ExceptionInfo *); +extern MagickExport PointInfo + *GetImageConvexHull(const Image *,size_t *,ExceptionInfo *); + extern MagickExport RectangleInfo - GetImageBoundingBox(const Image *,ExceptionInfo *exception); + GetImageBoundingBox(const Image *,ExceptionInfo *); extern MagickExport size_t GetImageDepth(const Image *,ExceptionInfo *), diff --git a/MagickCore/effect.c b/MagickCore/effect.c index fbf159e810..4e80952c84 100644 --- a/MagickCore/effect.c +++ b/MagickCore/effect.c @@ -2718,6 +2718,7 @@ MagickExport Image *PreviewImage(const Image *image,const PreviewType preview, sigma+=0.25; if (preview_image == (Image *) NULL) break; + preview_image->alpha_trait=UndefinedPixelTrait; (void) DeleteImageProperty(preview_image,"label"); (void) SetImageProperty(preview_image,"label",label,exception); AppendImageToList(&images,preview_image); diff --git a/MagickCore/property.c b/MagickCore/property.c index 5be95e9cb4..63fab8755d 100644 --- a/MagickCore/property.c +++ b/MagickCore/property.c @@ -2405,7 +2405,7 @@ MagickExport const char *GetImageProperty(const Image *image, % % The returned string is stored in a structure somewhere, and should not be % directly freed. If the string was generated (common) the string will be -% stored as as either as artifact or option 'get-property'. These may be +% stored as as either as artifact or option 'magick-property'. These may be % deleted (cleaned up) when no longer required, but neither artifact or % option is guranteed to exist. % @@ -2814,13 +2814,13 @@ static const char *GetMagickPropertyLetter(ImageInfo *image_info, */ if (image != (Image *) NULL) { - (void) SetImageArtifact(image,"get-property",value); - return(GetImageArtifact(image,"get-property")); + (void) SetImageArtifact(image,"magick-property",value); + return(GetImageArtifact(image,"magick-property")); } else { - (void) SetImageOption(image_info,"get-property",value); - return(GetImageOption(image_info,"get-property")); + (void) SetImageOption(image_info,"magick-property",value); + return(GetImageOption(image_info,"magick-property")); } } return((char *) NULL); @@ -2873,7 +2873,7 @@ MagickExport const char *GetMagickProperty(ImageInfo *image_info, WarnNoImageReturn("\"%%[%s]\"",property); geometry=GetImageBoundingBox(image,exception); - (void) FormatLocaleString(value,MagickPathExtent,"%g,%g %g,%g\n", + (void) FormatLocaleString(value,MagickPathExtent,"%g,%g %g,%g", (double) geometry.x,(double) geometry.y, (double) geometry.x+geometry.width, (double) geometry.y+geometry.height); @@ -2924,6 +2924,37 @@ MagickExport const char *GetMagickProperty(ImageInfo *image_info, image->compression); break; } + if (LocaleCompare("convex-hull",property) == 0) + { + char + *points; + + PointInfo + *convex_hull; + + register ssize_t + n; + + size_t + number_points; + + WarnNoImageReturn("\"%%[%s]\"",property); + convex_hull=GetImageConvexHull(image,&number_points,exception); + if (convex_hull == (PointInfo *) NULL) + break; + points=AcquireString(""); + for (n=0; n < (ssize_t) number_points; n++) + { + (void) FormatLocaleString(value,MagickPathExtent,"%g,%g ", + convex_hull[n].x,convex_hull[n].y); + (void) ConcatenateString(&points,value); + } + convex_hull=(PointInfo *) RelinquishMagickMemory(convex_hull); + (void) SetImageArtifact(image,"convex-hull",points); + points=DestroyString(points); + string=GetImageArtifact(image,"convex-hull"); + break; + } if (LocaleCompare("copyright",property) == 0) { (void) CopyMagickString(value,GetMagickCopyright(),MagickPathExtent); @@ -3323,13 +3354,13 @@ MagickExport const char *GetMagickProperty(ImageInfo *image_info, */ if (image != (Image *) NULL) { - (void) SetImageArtifact(image,"get-property",value); - return(GetImageArtifact(image,"get-property")); + (void) SetImageArtifact(image,"magick-property",value); + return(GetImageArtifact(image,"magick-property")); } else { - (void) SetImageOption(image_info,"get-property",value); - return(GetImageOption(image_info,"get-property")); + (void) SetImageOption(image_info,"magick-property",value); + return(GetImageOption(image_info,"magick-property")); } } return((char *) NULL); @@ -3676,8 +3707,8 @@ RestoreMSCWarning if (string != (char *) NULL) { AppendString2Text(string); - (void) DeleteImageArtifact(property_image,"get-property"); - (void) DeleteImageOption(property_info,"get-property"); + (void) DeleteImageArtifact(property_image,"magick-property"); + (void) DeleteImageOption(property_info,"magick-property"); continue; } (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning, @@ -3963,8 +3994,8 @@ RestoreMSCWarning if (string != (const char *) NULL) { AppendString2Text(string); - (void)DeleteImageArtifact(property_image,"get-property"); - (void)DeleteImageOption(property_info,"get-property"); + (void) DeleteImageArtifact(property_image,"magick-property"); + (void) DeleteImageOption(property_info,"magick-property"); continue; } if (IsGlob(pattern) != MagickFalse)