/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % S h e a r I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Method ShearImage creates a new image that is a shear_image copy of an % existing one. Shearing slides one edge of an image along the X or Y % axis, creating a parallelogram. An X direction shear slides an edge % along the X axis, while a Y direction shear slides an edge along the Y % axis. The amount of the shear is controlled by a shear angle. For X % direction shears, x_shear is measured relative to the Y axis, and % similarly, for Y direction shears y_shear is measured relative to the % X axis. Empty triangles left over from shearing the image are filled % with the color defined by the pixel at location (0,0). ShearImage % allocates the memory necessary for the new Image structure and returns % a pointer to the new image. % % Method ShearImage is based on the paper "A Fast Algorithm for General % Raster Rotatation" by Alan W. Paeth. % % The format of the ShearImage method is: % % Image *ShearImage(const Image *image,const double x_shear, % const double y_shear,ExceptionInfo *exception) % % A description of each parameter follows. % % o status: Method ShearImage returns a pointer to the image after % rotating. A null image is returned if there is a memory shortage. % % o image: The image; returned from % ReadImage. % % o x_shear, y_shear: Specifies the number of degrees to shear the image. % % o exception: Return any errors or warnings in this structure. % % */ MagickExport Image *ShearImage(const Image *image,const double x_shear, const double y_shear,ExceptionInfo *exception) { Image *integral_image, *shear_image; long x_offset, y_offset; PointInfo shear; RectangleInfo border_info; unsigned long y_width; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); if ((x_shear == 180.0) || (y_shear == 180.0)) ThrowImageException3(ImageError,UnableToShearImage,AngleIsDiscontinuous); /* Initialize shear angle. */ integral_image=IntegralRotateImage(image,0,exception); if (integral_image == (Image *) NULL) ThrowImageException3(ResourceLimitError,MemoryAllocationFailed, UnableToShearImage); shear.x=(-tan(DegreesToRadians(x_shear)/2.0)); shear.y=sin(DegreesToRadians(y_shear)); if ((shear.x == 0.0) || (shear.y == 0.0)) return(integral_image); /* Compute image size. */ x_offset=(long) ceil(fabs(2.0*image->rows*shear.x)-0.5); y_width=(unsigned long) floor(fabs(image->rows*shear.x)+image->columns+0.5); y_offset=(long) ceil(fabs(y_width*shear.y)-0.5); /* Surround image with border. */ integral_image->border_color=integral_image->background_color; border_info.width=x_offset; border_info.height=y_offset; shear_image=BorderImage(integral_image,&border_info,exception); if (shear_image == (Image *) NULL) ThrowImageException3(ResourceLimitError,MemoryAllocationFailed, UnableToShearImage); DestroyImage(integral_image); /* Shear the image. */ shear_image->storage_class=DirectClass; shear_image->matte|=shear_image->background_color.opacity != OpaqueOpacity; XShearImage(shear_image,shear.x,image->columns,image->rows,x_offset, (long) (shear_image->rows-image->rows)/2); YShearImage(shear_image,shear.y,y_width,image->rows, (long) (shear_image->columns-y_width)/2,y_offset); CropToFitImage(&shear_image,shear.x,shear.y,image->columns,image->rows, False,exception); shear_image->page.width=0; shear_image->page.height=0; return(shear_image); }
/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % A v e r a g e I m a g e s % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % The Average() method takes a set of images and averages them together. % Each image in the set must have the same width and height. Average() % returns a single image with each corresponding pixel component of % each image averaged. On failure, a NULL image is returned and % exception describes the reason for the failure. % % The format of the AverageImage method is: % % Image *AverageImages(Image *image,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: The image sequence. % % o exception: Return any errors or warnings in this structure. % % */ MagickExport Image *AverageImages(const Image *image,ExceptionInfo *exception) { ThreadViewDataSet *pixels_sums; Image *average_image; const Image *last_image; long y; unsigned long row_count=0; double number_scenes; unsigned long number_pixels; MagickPassFail status=MagickPass; /* Ensure the image are the same size. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); if (image->next == (Image *) NULL) ThrowImageException3(ImageError,ImageSequenceIsRequired, UnableToAverageImage); { const Image *next; for (next=image; next != (Image *) NULL; next=next->next) { if ((next->columns != image->columns) || (next->rows != image->rows)) ThrowImageException3(OptionError,UnableToAverageImageSequence, ImageWidthsOrHeightsDiffer); } } /* Allocate sum accumulation buffer. */ number_pixels=image->columns; pixels_sums=AllocateThreadViewDataArray(image,exception,number_pixels, sizeof(DoublePixelPacket)); if (pixels_sums == (ThreadViewDataSet *) NULL) ThrowImageException3(ResourceLimitError,MemoryAllocationFailed, UnableToAverageImageSequence); /* Initialize average next attributes. */ average_image=CloneImage(image,image->columns,image->rows,True,exception); if (average_image == (Image *) NULL) { DestroyThreadViewDataSet(pixels_sums); return((Image *) NULL); } average_image->storage_class=DirectClass; number_scenes=(double) GetImageListLength(image); last_image=GetLastImageInList(image); #if defined(HAVE_OPENMP) # pragma omp parallel for schedule(dynamic) shared(row_count, status) #endif for (y=0; y < (long) image->rows; y++) { register DoublePixelPacket *pixels_sum; const Image *next; register const PixelPacket *p; register long x; MagickBool thread_status; thread_status=status; if (thread_status == MagickFail) continue; pixels_sum=AccessThreadViewData(pixels_sums); /* Compute sum over each pixel color component. */ for (next=image; next != (Image *) NULL; next=next->next) { ViewInfo *next_view; next_view=OpenCacheView((Image *) next); if (next_view == (ViewInfo *) NULL) thread_status=MagickFail; if (next_view != (ViewInfo *) NULL) { p=AcquireCacheViewPixels(next_view,0,y,next->columns,1,exception); if (p == (const PixelPacket *) NULL) thread_status=MagickFail; if (p != (const PixelPacket *) NULL) { if (next == image) { for (x=0; x < (long) next->columns; x++) { pixels_sum[x].red=p[x].red; pixels_sum[x].green=p[x].green; pixels_sum[x].blue=p[x].blue; pixels_sum[x].opacity=p[x].opacity; } } else { for (x=0; x < (long) next->columns; x++) { pixels_sum[x].red+=p[x].red; pixels_sum[x].green+=p[x].green; pixels_sum[x].blue+=p[x].blue; pixels_sum[x].opacity+=p[x].opacity; } } } CloseCacheView(next_view); } } /* Average next pixels. */ if (thread_status != MagickFail) { register PixelPacket *q; q=SetImagePixelsEx(average_image,0,y,average_image->columns,1,exception); if (q == (PixelPacket *) NULL) thread_status=MagickFail; if (q != (PixelPacket *) NULL) { for (x=0; x < (long) average_image->columns; x++) { q[x].red=(Quantum) (pixels_sum[x].red/number_scenes+0.5); q[x].green=(Quantum) (pixels_sum[x].green/number_scenes+0.5); q[x].blue=(Quantum) (pixels_sum[x].blue/number_scenes+0.5); q[x].opacity=(Quantum) (pixels_sum[x].opacity/number_scenes+0.5); } if (!SyncImagePixelsEx(average_image,exception)) thread_status=MagickFail; } } #if defined(HAVE_OPENMP) # pragma omp critical (GM_AverageImages) #endif { row_count++; if (QuantumTick(row_count,average_image->rows)) if (!MagickMonitorFormatted(row_count,average_image->rows,exception, "[%s,...,%s] Average image sequence...", image->filename,last_image->filename)) thread_status=MagickFail; if (thread_status == MagickFail) status=MagickFail; } } DestroyThreadViewDataSet(pixels_sums); if (status == MagickFail) { DestroyImage(average_image); average_image=(Image *) NULL; } return(average_image); }
/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R o t a t e I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Method RotateImage creates a new image that is a rotated copy of an % existing one. Positive angles rotate counter-clockwise (right-hand rule), % while negative angles rotate clockwise. Rotated images are usually larger % than the originals and have 'empty' triangular corners. X axis. Empty % triangles left over from shearing the image are filled with the color % specified by the image background_color. RotateImage allocates the memory % necessary for the new Image structure and returns a pointer to the new % image. % % Method RotateImage is based on the paper "A Fast Algorithm for General % Raster Rotatation" by Alan W. Paeth. RotateImage is adapted from a similar % method based on the Paeth paper written by Michael Halle of the Spatial % Imaging Group, MIT Media Lab. % % The format of the RotateImage method is: % % Image *RotateImage(const Image *image,const double degrees, % ExceptionInfo *exception) % % A description of each parameter follows. % % o status: Method RotateImage returns a pointer to the image after % rotating. A null image is returned if there is a memory shortage. % % o image: The image; returned from % ReadImage. % % o degrees: Specifies the number of degrees to rotate the image. % % o exception: Return any errors or warnings in this structure. % % */ MagickExport Image *RotateImage(const Image *image,const double degrees, ExceptionInfo *exception) { double angle; Image *integral_image, *rotate_image; long x_offset, y_offset; PointInfo shear; RectangleInfo border_info; unsigned long height, rotations, width, y_width; /* Adjust rotation angle. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); angle=degrees; while (angle < -45.0) angle+=360.0; for (rotations=0; angle > 45.0; rotations++) angle-=90.0; rotations%=4; /* Calculate shear equations. */ integral_image=IntegralRotateImage(image,rotations,exception); if (integral_image == (Image *) NULL) ThrowImageException3(ResourceLimitError,MemoryAllocationFailed, UnableToRotateImage); shear.x=(-tan(DegreesToRadians(angle)/2.0)); shear.y=sin(DegreesToRadians(angle)); if ((shear.x == 0.0) || (shear.y == 0.0)) return(integral_image); /* Compute image size. */ width=image->columns; height=image->rows; if ((rotations == 1) || (rotations == 3)) { width=image->rows; height=image->columns; } x_offset=(long) ceil(fabs(2.0*height*shear.y)-0.5); y_width=(unsigned long) floor(fabs(height*shear.x)+width+0.5); y_offset=(long) ceil(fabs(y_width*shear.y)-0.5); /* Surround image with a border. */ integral_image->border_color=integral_image->background_color; border_info.width=x_offset; border_info.height=y_offset; rotate_image=BorderImage(integral_image,&border_info,exception); DestroyImage(integral_image); if (rotate_image == (Image *) NULL) ThrowImageException3(ResourceLimitError,MemoryAllocationFailed, UnableToRotateImage); /* Rotate the image. */ rotate_image->storage_class=DirectClass; rotate_image->matte|=rotate_image->background_color.opacity != OpaqueOpacity; XShearImage(rotate_image,shear.x,width,height,x_offset, (long) (rotate_image->rows-height)/2); YShearImage(rotate_image,shear.y,y_width,height, (long) (rotate_image->columns-y_width)/2,y_offset); XShearImage(rotate_image,shear.x,y_width,rotate_image->rows, (long) (rotate_image->columns-y_width)/2,0); CropToFitImage(&rotate_image,shear.x,shear.y,width,height,True,exception); rotate_image->page.width=0; rotate_image->page.height=0; return(rotate_image); }