Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
11110 lines (9243 sloc) 283 KB
/* $Id: rmimage.c,v 1.192.2.5.2.6 2008/05/05 22:59:40 rmagick Exp $ */
/*============================================================================\
| Copyright (C) 2008 by Timothy P. Hunter
| Name: rmimage.c
| Author: Tim Hunter
| Purpose: Image class method definitions for RMagick
\============================================================================*/
#include "rmagick.h"
// magick_config.h in GraphicsMagick doesn't define HasX11
#if defined(HAVE_XIMPORTIMAGE)
#if !defined(HasX11)
#define HasX11
#endif
#include "magick/xwindow.h" // XImageInfo
#endif
typedef Image *(effector_t)(const Image *, const double, const double, ExceptionInfo *);
typedef Image *(flipper_t)(const Image *, ExceptionInfo *);
typedef Image *(magnifier_t)(const Image *, ExceptionInfo *);
typedef Image *(reader_t)(const Info *, ExceptionInfo *);
typedef Image *(scaler_t)(const Image *, const unsigned long, const unsigned long, ExceptionInfo *);
typedef unsigned int (thresholder_t)(Image *, const char *);
typedef Image *(xformer_t)(const Image *, const RectangleInfo *, ExceptionInfo *);
static VALUE cropper(int, int, VALUE *, VALUE);
static VALUE effect_image(VALUE, int, VALUE *, effector_t);
static VALUE flipflop(int, VALUE, flipper_t);
static VALUE rd_image(VALUE, VALUE, reader_t);
static VALUE rotate(int, int, VALUE *, VALUE);
static VALUE scale(int, int, VALUE *, VALUE, scaler_t);
static VALUE threshold_image(int, VALUE *, VALUE, thresholder_t);
static VALUE xform_image(int, VALUE, VALUE, VALUE, VALUE, VALUE, xformer_t);
static VALUE array_from_images(Image *);
static ImageAttribute *Next_Attribute;
static const char *BlackPointCompensationKey = "PROFILE:black-point-compensation";
/*
Static: adaptive_method
Purpose: call Adaptive(Blur|Sharpen)Image
*/
#if defined(HAVE_ADAPTIVEBLURIMAGECHANNEL) || defined(HAVE_ADAPTIVESHARPENIMAGE)
static VALUE adaptive_method(
int argc,
VALUE *argv,
VALUE self,
Image *fp(const Image *, const double, const double, ExceptionInfo *))
{
Image *image, *new_image;
double radius = 0.0;
double sigma = 1.0;
ExceptionInfo exception;
switch (argc)
{
case 2:
sigma = NUM2DBL(argv[1]);
case 1:
radius = NUM2DBL(argv[0]);
case 0:
break;
default:
rb_raise(rb_eArgError, "wrong number of arguments (%d for 0 to 2)", argc);
break;
}
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
new_image = (fp)(image, radius, sigma, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
}
/*
Static: adaptive_channel_method
Purpose: call Adaptive(Blur|Sharpen)ImageChannel
*/
static VALUE adaptive_channel_method(
int argc,
VALUE *argv,
VALUE self,
Image *fp(const Image *, const ChannelType, const double, const double, ExceptionInfo *))
{
Image *image, *new_image;
double radius = 0.0;
double sigma = 1.0;
ExceptionInfo exception;
ChannelType channels;
channels = extract_channels(&argc, argv);
switch (argc)
{
case 2:
sigma = NUM2DBL(argv[1]);
case 1:
radius = NUM2DBL(argv[0]);
case 0:
break;
default:
raise_ChannelType_error(argv[argc-1]);
break;
}
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
new_image = (fp)(image, channels, radius, sigma, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
}
#endif
/*
Method: Image#adaptive_blur(radius=0.0, sigma=1.0)
Purpose: call AdaptiveBlurImage
*/
VALUE
Image_adaptive_blur(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_ADAPTIVEBLURIMAGECHANNEL)
return adaptive_method(argc, argv, self, AdaptiveBlurImage);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#adaptive_blur_channel(radius=0.0, sigma=1.0[ , channel...])
Purpose: call AdaptiveBlurImageChannel
*/
VALUE
Image_adaptive_blur_channel(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_ADAPTIVEBLURIMAGECHANNEL)
return adaptive_channel_method(argc, argv, self, AdaptiveBlurImageChannel);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#adaptive_resize(scale)
Image#adaptive_resize(cols, rows)
Purpose: Call AdaptiveResizeImage
*/
VALUE
Image_adaptive_resize(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_ADAPTIVERESIZEIMAGE)
Image *image, *new_image;
unsigned long rows, columns;
double scale, drows, dcols;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
switch (argc)
{
case 2:
rows = NUM2ULONG(argv[1]);
columns = NUM2ULONG(argv[0]);
break;
case 1:
scale = NUM2DBL(argv[0]);
if (scale < 0.0)
{
rb_raise(rb_eArgError, "invalid scale value (%g given)", scale);
}
drows = scale * image->rows + 0.5;
dcols = scale * image->columns + 0.5;
if (drows > (double)ULONG_MAX || dcols > (double)ULONG_MAX)
{
rb_raise(rb_eRangeError, "resized image too big");
}
rows = (unsigned long) drows;
columns = (unsigned long) dcols;
break;
default:
rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc);
break;
}
GetExceptionInfo(&exception);
new_image = AdaptiveResizeImage(image, columns, rows, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#adaptive_sharpen(radius=0.0, sigma=1.0)
Purpose: call AdaptiveSharpenImage
*/
VALUE
Image_adaptive_sharpen(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_ADAPTIVESHARPENIMAGE)
return adaptive_method(argc, argv, self, AdaptiveSharpenImage);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#adaptive_sharpen_channel(radius=0.0, sigma=1.0[, channel...])
Purpose: Call AdaptiveSharpenImageChannel
*/
VALUE
Image_adaptive_sharpen_channel(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_ADAPTIVESHARPENIMAGE)
return adaptive_channel_method(argc, argv, self, AdaptiveSharpenImageChannel);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#adaptive_threshold(width=3, height=3, offset=0)
Purpose: selects an individual threshold for each pixel based on
the range of intensity values in its local neighborhood.
This allows for thresholding of an image whose global
intensity histogram doesn't contain distinctive peaks.
Returns: a new image
*/
VALUE
Image_adaptive_threshold(int argc, VALUE *argv, VALUE self)
{
Image *image, *new_image;
unsigned long width = 3, height = 3;
long offset = 0;
ExceptionInfo exception;
switch (argc)
{
case 3:
offset = NUM2LONG(argv[2]);
case 2:
height = NUM2ULONG(argv[1]);
case 1:
width = NUM2ULONG(argv[0]);
case 0:
break;
default:
rb_raise(rb_eArgError, "wrong number of arguments (%d for 0 to 3)", argc);
}
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
new_image = AdaptiveThresholdImage(image, width, height, offset, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
}
/*
Method: Image#add_noise(noise_type)
Purpose: add random noise to a copy of the image
Returns: a new image
*/
VALUE
Image_add_noise(VALUE self, VALUE noise)
{
Image *image, *new_image;
NoiseType noise_type;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
VALUE_TO_ENUM(noise, noise_type, NoiseType);
GetExceptionInfo(&exception);
new_image = AddNoiseImage(image, noise_type, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
}
/*
Method: Image#add_noise_channel(noise_type[,channel...])
Purpose: add random noise to a copy of the image
Returns: a new image
*/
VALUE
Image_add_noise_channel(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_ADDNOISEIMAGECHANNEL)
Image *image, *new_image;
NoiseType noise_type;
ExceptionInfo exception;
ChannelType channels;
channels = extract_channels(&argc, argv);
// There must be 1 remaining argument.
if (argc == 0)
{
rb_raise(rb_eArgError, "missing noise type argument");
}
else if (argc > 1)
{
raise_ChannelType_error(argv[argc-1]);
}
Data_Get_Struct(self, Image, image);
VALUE_TO_ENUM(argv[0], noise_type, NoiseType);
channels &= ~OpacityChannel;
GetExceptionInfo(&exception);
new_image = AddNoiseImageChannel(image, channels, noise_type, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#add_profile(name)
Purpose: adds all the profiles in the specified file
Notes: `name' is the profile filename
*/
VALUE
Image_add_profile(VALUE self, VALUE name)
{
#if defined(HAVE_GETNEXTIMAGEPROFILE)
// ImageMagick code based on the code for the "-profile" option in mogrify.c
Image *image, *profile_image;
ImageInfo *info;
ExceptionInfo exception;
char *profile_name;
char *profile_filename = NULL;
long profile_filename_l = 0;
const StringInfo *profile;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
// ProfileImage issues a warning if something goes wrong.
profile_filename = STRING_PTR_LEN(name, profile_filename_l);
info = CloneImageInfo(NULL);
info->client_data= (void *)GetImageProfile(image,"8bim");
strncpy(info->filename, profile_filename, min((size_t)profile_filename_l, sizeof(info->filename)));
info->filename[MaxTextExtent-1] = '\0';
GetExceptionInfo(&exception);
profile_image = ReadImage(info, &exception);
(void) DestroyImageInfo(info);
rm_check_exception(&exception, profile_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(profile_image);
ResetImageProfileIterator(profile_image);
profile_name = GetNextImageProfile(profile_image);
while (profile_name)
{
profile = GetImageProfile(profile_image, profile_name);
if (profile)
{
(void)ProfileImage(image, profile_name, profile->datum, (unsigned long)profile->length, MagickFalse);
if (image->exception.severity >= ErrorException)
{
break;
}
}
profile_name = GetNextImageProfile(profile_image);
}
(void) DestroyImage(profile_image);
rm_check_image_exception(image, RetainOnError);
#else
// GraphicsMagick code based on the code for the "-profile" option in command.c
Image *image, *profile_image;
ImageInfo *info;
ExceptionInfo exception;
#if !defined(HAVE_IMAGE_IPTC_PROFILE)
ProfileInfo profile_info;
#endif
char *profile_filename = NULL;
long profile_filename_l = 0;
const unsigned char *profile;
size_t profile_l;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
// ProfileImage issues a warning if something goes wrong.
profile_filename = STRING_PTR_LEN(name, profile_filename_l);
info = CloneImageInfo(NULL);
#if !defined(HAVE_IMAGE_IPTC_PROFILE)
profile_info.name="IPTC";
profile_info.info=(unsigned char *) GetImageProfile(image, profile_info.name, &profile_info.length);
info->client_data=&profile_info;
#else
info->client_data= (void *) &image->iptc_profile;
#endif
strncpy(info->filename, profile_filename, min(profile_filename_l, sizeof(info->filename)));
info->filename[MaxTextExtent-1] = '\0';
GetExceptionInfo(&exception);
profile_image = ReadImage(info, &exception);
(void) DestroyImageInfo(info);
rm_check_exception(&exception, profile_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(profile_image);
/* IPTC NewsPhoto Profile */
profile = GetImageProfile(profile_image, "IPTC", &profile_l);
if (profile)
{
(void)SetImageProfile(image, "IPTC", profile, profile_l);
if (image->exception.severity >= ErrorException)
{
goto done;
}
}
#if defined(HAVE_ALLOCATEIMAGEPROFILEITERATOR)
/* GraphicsMagick 1.2 */
{
ImageProfileIterator profile_iterator;
const char *profile_name;
size_t profile_length;
profile_iterator = AllocateImageProfileIterator(profile_image);
while (NextImageProfile(profile_iterator, &profile_name, &profile, &profile_length) != MagickFail)
{
if ((rm_strcasecmp(profile_name, "ICC") == 0) || (rm_strcasecmp(profile_name, "ICM") == 0))
(void) ProfileImage(image, profile_name, (unsigned char *) profile, profile_length, True);
else
(void) SetImageProfile(image, profile_name, profile, profile_length);
}
DeallocateImageProfileIterator(profile_iterator);
}
#else
{
long x;
ProfileInfo *generic;
/* ICC ICM Profile */
profile = GetImageProfile(profile_image, "ICM", &profile_l);
if (profile)
{
(void)SetImageProfile(image, "ICM", profile, profile_l);
if (image->exception.severity >= ErrorException)
{
goto done;
}
}
/* Generic Profiles */
for (x = 0; x < (long)profile_image->generic_profiles; x++)
{
generic = profile_image->generic_profile + x;
(void)SetImageProfile(image, generic->name, generic->info, generic->length);
if (image->exception.severity >= ErrorException)
{
break;
}
}
}
#endif
done:
(void) DestroyImage(profile_image);
rm_check_image_exception(image, RetainOnError);
#endif
return self;
}
/*
Method: Image#affine_transform(affine_matrix)
Purpose: transforms an image as dictated by the affine matrix argument
Returns: a new image
*/
VALUE
Image_affine_transform(VALUE self, VALUE affine)
{
Image *image, *new_image;
ExceptionInfo exception;
AffineMatrix matrix;
Data_Get_Struct(self, Image, image);
// Convert Magick::AffineMatrix to AffineMatrix structure.
AffineMatrix_to_AffineMatrix(&matrix, affine);
GetExceptionInfo(&exception);
new_image = AffineTransformImage(image, &matrix, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
}
/*
Method: Image#["key"]
Image#[:key]
Purpose: Return the image property associated with "key"
Returns: property value or nil if key doesn't exist
Notes: Use Image#[]= (aset) to establish more properties
or change the value of an existing property.
*/
VALUE
Image_aref(VALUE self, VALUE key_arg)
{
Image *image;
char *key;
const char *attr;
switch (TYPE(key_arg))
{
case T_NIL:
return Qnil;
case T_SYMBOL:
key = rb_id2name((ID)SYM2ID(key_arg));
break;
default:
key = STRING_PTR(key_arg);
if (*key == '\0')
{
return Qnil;
}
break;
}
Data_Get_Struct(self, Image, image);
if (rm_strcasecmp(key, "EXIF:*") == 0)
{
return rm_exif_by_entry(image);
}
else if (rm_strcasecmp(key, "EXIF:!") == 0)
{
return rm_exif_by_number(image);
}
attr = rm_get_property(image, key);
return attr ? rb_str_new2(attr) : Qnil;
}
/*
Method: Image#["key"] = attr
Image#[:key] = attr
Purpose: Update or add image attribute "key"
Returns: self
Notes: Specify attr=nil to remove the key from the list.
SetImageAttribute normally APPENDS the new value
to any existing value. Since this usage is tremendously
counter-intuitive, this function always deletes the
existing value before setting the new value.
There's no use checking the return value since
SetImageAttribute returns "False" for many reasons,
some legitimate.
*/
VALUE
Image_aset(VALUE self, VALUE key_arg, VALUE attr_arg)
{
Image *image;
char *key, *attr;
unsigned int okay;
rm_check_frozen(self);
attr = attr_arg == Qnil ? NULL : STRING_PTR(attr_arg);
switch (TYPE(key_arg))
{
case T_NIL:
return self;
case T_SYMBOL:
key = rb_id2name((ID)SYM2ID(key_arg));
break;
default:
key = STRING_PTR(key_arg);
if (*key == '\0')
{
return self;
}
break;
}
Data_Get_Struct(self, Image, image);
// If we're currently looping over the attributes via
// Image_properties (below) then check to see if we're
// about to delete the next attribute. If so, change
// the "next" pointer to point to the attribute following
// this one. (No, this isn't thread-safe!)
#if !defined(HAVE_GETIMAGEPROPERTY) && !defined(HAVE_GETNEXTIMAGEATTRIBUTE)
if (Next_Attribute)
{
const ImageAttribute *attribute = GetImageAttribute(image, key);
if (attribute && attribute == Next_Attribute)
{
Next_Attribute = attribute->next;
}
}
#endif
// Delete existing value. SetImageAttribute returns False if
// the attribute doesn't exist - we don't care.
(void) rm_set_property(image, key, NULL);
// Set new value
if (attr)
{
okay = rm_set_property(image, key, attr);
if (!okay)
{
rb_warning("SetImageAttribute failed (probably out of memory)");
}
}
return self;
}
/*
Static: crisscross
Purpose: Handle #transverse, #transform methods
*/
#if defined(HAVE_TRANSPOSEIMAGE) || defined(HAVE_TRANSVERSEIMAGE)
static VALUE
crisscross(
int bang,
VALUE self,
Image *fp(const Image *, ExceptionInfo *))
{
Image *image, *new_image;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
new_image = (fp)(image, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
if (bang)
{
DATA_PTR(self) = new_image;
(void) DestroyImage(image);
return self;
}
return rm_image_new(new_image);
}
#endif
/*
Method: Image#auto_orient
Purpose: Implement mogrify's -auto_orient option
automatically orient image based on EXIF orientation value
Notes: See mogrify.c in ImageMagick 6.2.8.
*/
static VALUE auto_orient(int bang, VALUE self)
{
#if defined(HAVE_TRANSPOSEIMAGE) || defined(HAVE_TRANSVERSEIMAGE)
Image *image;
volatile VALUE new_image;
VALUE degrees[1];
Data_Get_Struct(self, Image, image);
switch (image->orientation)
{
case TopRightOrientation:
new_image = flipflop(bang, self, FlopImage);
break;
case BottomRightOrientation:
degrees[0] = rb_float_new(180.0);
new_image = rotate(bang, 1, degrees, self);
break;
case BottomLeftOrientation:
new_image = flipflop(bang, self, FlipImage);
break;
case LeftTopOrientation:
new_image = crisscross(bang, self, TransposeImage);
break;
case RightTopOrientation:
degrees[0] = rb_float_new(90.0);
new_image = rotate(bang, 1, degrees, self);
break;
case RightBottomOrientation:
new_image = crisscross(bang, self, TransverseImage);
break;
case LeftBottomOrientation:
degrees[0] = rb_float_new(270.0);
new_image = rotate(bang, 1, degrees, self);
break;
default: // Return IMMEDIATELY
return bang ? Qnil : Image_copy(self);
break;
}
Data_Get_Struct(new_image, Image, image);
image->orientation = TopLeftOrientation;
return new_image;
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
VALUE
Image_auto_orient(VALUE self)
{
return auto_orient(False, self);
}
/*
Returns nil if the image is already properly oriented
*/
VALUE
Image_auto_orient_bang(VALUE self)
{
rm_check_frozen(self);
return auto_orient(True, self);
}
/*
Method: Image#background_color
Purpose: Return the name of the background color as a String.
*/
VALUE
Image_background_color(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
return PixelPacket_to_Color_Name(image, &image->background_color);
}
/*
Method: Image#background_color=
Purpose: Set the the background color to the specified color spec.
*/
VALUE
Image_background_color_eq(VALUE self, VALUE color)
{
Image *image;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
Color_to_PixelPacket(&image->background_color, color);
return self;
}
/*
Method: Image#base_columns
Purpose: Return the number of rows (before transformations)
*/
VALUE Image_base_columns(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
return INT2FIX(image->magick_columns);
}
/*
Method: Image#base_filename
Purpose: Return the image filename (before transformations)
Notes: If there is no base filename, return the current filename.
*/
VALUE Image_base_filename(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
if (*image->magick_filename)
{
return rb_str_new2(image->magick_filename);
}
else
{
return rb_str_new2(image->filename);
}
}
/*
Method: Image#base_rows
Purpose: Return the number of rows (before transformations)
*/
VALUE Image_base_rows(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
return INT2FIX(image->magick_rows);
}
/*
Method: Image#bias -> bias
Image#bias = a number between 0.0 and 1.0 or "NN%"
Purpose: Get/set image bias (used when convolving an image)
*/
VALUE Image_bias(VALUE self)
{
#if defined(HAVE_IMAGE_BIAS)
Image *image;
Data_Get_Struct(self, Image, image);
return rb_float_new(image->bias);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
VALUE Image_bias_eq(VALUE self, VALUE pct)
{
#if defined(HAVE_IMAGE_BIAS)
Image *image;
double bias;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
bias = rm_percentage(pct);
image->bias = bias * MaxRGB;
return self;
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
* Method: Image#bilevel_channel(threshold, channel=AllChannels)
* Returns a new image
*/
VALUE
Image_bilevel_channel(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_BILEVELIMAGECHANNEL)
Image *image, *new_image;
ChannelType channels;
channels = extract_channels(&argc, argv);
if (argc > 1)
{
raise_ChannelType_error(argv[argc-1]);
}
if (argc == 0)
{
rb_raise(rb_eArgError, "no threshold specified");
}
Data_Get_Struct(self, Image, image);
new_image = rm_clone_image(image);
(void)BilevelImageChannel(new_image, channels, NUM2DBL(argv[0]));
rm_check_image_exception(new_image, DestroyOnError);
return rm_image_new(new_image);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#black_point_compensation
Purpose: Return current value
*/
VALUE
Image_black_point_compensation(VALUE self)
{
Image *image;
const ImageAttribute *attr;
volatile VALUE value;
Data_Get_Struct(self, Image, image);
attr = GetImageAttribute(image, BlackPointCompensationKey);
if (attr && rm_strcasecmp(attr->value, "true") == 0)
{
value = Qtrue;
}
else
{
value = Qfalse;
}
return value;
}
/*
Method: Image#black_point_compensation=true or false
Purpose: Set black point compensation attribute
*/
VALUE
Image_black_point_compensation_eq(VALUE self, VALUE arg)
{
Image *image;
char *value;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
(void) SetImageAttribute(image, BlackPointCompensationKey, NULL);
value = RTEST(arg) ? "true" : "false";
(void) SetImageAttribute(image, BlackPointCompensationKey, value);
return self;
}
/*
* Method: Image#black_threshold(red_channel [, green_channel
* [, blue_channel [, opacity_channel]]]);
* Purpose: Call BlackThresholdImage
*/
VALUE
Image_black_threshold(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_BLACKTHRESHOLDIMAGE)
return threshold_image(argc, argv, self, BlackThresholdImage);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Static: get_relative_offsets
Purpose: compute offsets using the gravity to determine what the
offsets are relative to
*/
static void
get_relative_offsets(
VALUE grav,
Image *image,
Image *mark,
long *x_offset,
long *y_offset)
{
MagickEnum *magick_enum;
GravityType gravity;
VALUE_TO_ENUM(grav, gravity, GravityType);
switch(gravity)
{
case NorthEastGravity:
case EastGravity:
case SouthEastGravity:
*x_offset = (long)(image->columns) - (long)(mark->columns) - *x_offset;
break;
case NorthGravity:
case SouthGravity:
case CenterGravity:
case StaticGravity:
*x_offset += (long)(image->columns/2) - (long)(mark->columns/2);
break;
default:
break;
}
switch(gravity)
{
case SouthWestGravity:
case SouthGravity:
case SouthEastGravity:
*y_offset = (long)(image->rows) - (long)(mark->rows) - *y_offset;
break;
case EastGravity:
case WestGravity:
case CenterGravity:
case StaticGravity:
*y_offset += (long)(image->rows/2) - (long)(mark->rows/2);
break;
case NorthEastGravity:
case NorthGravity:
// Don't let these run into the default case
break;
default:
Data_Get_Struct(grav, MagickEnum, magick_enum);
rb_warning("gravity type `%s' has no effect", rb_id2name(magick_enum->id));
break;
}
}
/*
Static: get_offsets_from_gravity
Purpose: compute watermark offsets from gravity type
*/
static void
get_offsets_from_gravity(
GravityType gravity,
Image *image,
Image *mark,
long *x_offset,
long *y_offset)
{
switch (gravity)
{
case ForgetGravity:
case NorthWestGravity:
*x_offset = 0;
*y_offset = 0;
break;
case NorthGravity:
*x_offset = ((long)(image->columns) - (long)(mark->columns)) / 2;
*y_offset = 0;
break;
case NorthEastGravity:
*x_offset = (long)(image->columns) - (long)(mark->columns);
*y_offset = 0;
break;
case WestGravity:
*x_offset = 0;
*y_offset = ((long)(image->rows) - (long)(mark->rows)) / 2;
break;
case StaticGravity:
case CenterGravity:
default:
*x_offset = ((long)(image->columns) - (long)(mark->columns)) / 2;
*y_offset = ((long)(image->rows) - (long)(mark->rows)) / 2;
break;
case EastGravity:
*x_offset = (long)(image->columns) - (long)(mark->columns);
*y_offset = ((long)(image->rows) - (long)(mark->rows)) / 2;
break;
case SouthWestGravity:
*x_offset = 0;
*y_offset = (long)(image->rows) - (long)(mark->rows);
break;
case SouthGravity:
*x_offset = ((long)(image->columns) - (long)(mark->columns)) / 2;
*y_offset = (long)(image->rows) - (long)(mark->rows);
break;
case SouthEastGravity:
*x_offset = (long)(image->columns) - (long)(mark->columns);
*y_offset = (long)(image->rows) - (long)(mark->rows);
break;
}
}
/*
Static: check_for_long_value
Purpose: called from rb_protect, returns the number if obj is really
a numeric value.
*/
static VALUE check_for_long_value(VALUE obj)
{
long t;
t = NUM2LONG(obj);
t = t; // placate gcc
return (VALUE)0;
}
/*
Static: get_composite_offsets
Purpose: compute x- and y-offset of source image for a compositing method
*/
static void get_composite_offsets(
int argc,
VALUE *argv,
Image *dest,
Image *src,
long *x_offset,
long *y_offset)
{
GravityType gravity;
int exc = 0;
if (CLASS_OF(argv[0]) == Class_GravityType)
{
VALUE_TO_ENUM(argv[0], gravity, GravityType);
switch (argc)
{
// Gravity + offset(s). Offsets are relative to the image edges
// as specified by the gravity.
case 3:
*y_offset = NUM2LONG(argv[2]);
case 2:
*x_offset = NUM2LONG(argv[1]);
get_relative_offsets(argv[0], dest, src, x_offset, y_offset);
break;
case 1:
// No offsets specified. Compute offset based on the gravity alone.
get_offsets_from_gravity(gravity, dest, src, x_offset, y_offset);
break;
}
}
// Gravity not specified at all. Offsets are measured from the
// NorthWest corner. The arguments must be numbers.
else
{
(void)rb_protect(check_for_long_value, argv[0], &exc);
if (exc)
{
rb_raise(rb_eArgError, "expected GravityType, got %s"
, rb_class2name(CLASS_OF(argv[0])));
}
*x_offset = NUM2LONG(argv[0]);
if (argc > 1)
{
*y_offset = NUM2LONG(argv[1]);
}
}
}
/*
Static: blend_geometry
Purpose: Convert 2 doubles to a blend or dissolve geometry string.
Notes: the geometry buffer needs to be at least 16 characters long.
For safety's sake this function asserts that it is at least
20 characters long.
The percentages must be in the range -1000 < n < 1000. This
is far in excess of what xMagick will allow.
*/
static void
blend_geometry(
char *geometry,
size_t geometry_l,
double src_percent,
double dst_percent)
{
size_t sz = 0;
int fw, prec;
if (fabs(src_percent) >= 1000.0 || fabs(dst_percent) >= 1000.0)
{
if (fabs(src_percent) < 1000.0)
{
src_percent = dst_percent;
}
rb_raise(rb_eArgError, "%g is out of range +/-999.99", src_percent);
}
assert(geometry_l >= 20);
memset(geometry, 0xdf, geometry_l);
fw = 4;
prec = 0;
if (src_percent != floor(src_percent))
{
prec = 2;
fw += 3;
}
sz = (size_t)sprintf(geometry, "%*.*f", -fw, prec, src_percent);
assert(sz < geometry_l);
sz = strcspn(geometry, " ");
// if dst_percent was nil don't add to the geometry
if (dst_percent != -1.0)
{
fw = 4;
prec = 0;
if (dst_percent != floor(dst_percent))
{
prec = 2;
fw += 3;
}
sz += (size_t)sprintf(geometry+sz, "x%*.*f", -fw, prec, dst_percent);
assert(sz < geometry_l);
sz = strcspn(geometry, " ");
}
if (sz < geometry_l)
{
memset(geometry+sz, 0x00, geometry_l-sz);
}
}
static VALUE
special_composite(
Image *image,
Image *overlay,
double image_pct,
double overlay_pct,
long x_off,
long y_off,
CompositeOperator op)
{
Image *new_image;
char geometry[20];
blend_geometry(geometry, sizeof(geometry), image_pct, overlay_pct);
(void) CloneString(&overlay->geometry, geometry);
new_image = rm_clone_image(image);
(void) CompositeImage(new_image, op, overlay, x_off, y_off);
rm_check_image_exception(new_image, DestroyOnError);
return rm_image_new(new_image);
}
/*
Method: Image#blend(overlay, src_percent, dst_percent, x_offset=0, y_offset=0)
Image#dissolve(overlay, src_percent, dst_percent, gravity, x_offset=0, y_offset=0)
Purpose: Corresponds to the composite -blend operation
Notes: `percent' can be a number or a string in the form "NN%"
The default value for dst_percent is 100.0-src_percent
*/
VALUE
Image_blend(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_COLORDODGECOMPOSITEOP)
Image *image, *overlay;
double src_percent, dst_percent;
long x_offset = 0L, y_offset = 0L;
Data_Get_Struct(self, Image, image);
if (argc < 1)
{
rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
}
if (argc > 3)
{
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
get_composite_offsets(argc-3, &argv[3], image, overlay, &x_offset, &y_offset);
// There must be 3 arguments left
argc = 3;
}
switch (argc)
{
case 3:
dst_percent = rm_percentage(argv[2]) * 100.0;
src_percent = rm_percentage(argv[1]) * 100.0;
break;
case 2:
src_percent = rm_percentage(argv[1]) * 100.0;
dst_percent = FMAX(100.0 - src_percent, 0);
break;
default:
rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
break;
}
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
return special_composite(image, overlay, src_percent, dst_percent
, x_offset, y_offset, BlendCompositeOp);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
DEF_ATTR_ACCESSOR(Image, blur, dbl)
/*
* Method: Image#blur_channel(radius = 0.0, sigma = 1.0, channel=AllChannels)
* Purpose: Call BlurImageChannel
*/
VALUE
Image_blur_channel(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_BLURIMAGECHANNEL)
Image *image, *new_image;
ExceptionInfo exception;
ChannelType channels;
double radius = 0.0, sigma = 1.0;
Data_Get_Struct(self, Image, image);
channels = extract_channels(&argc, argv);
// There can be 0, 1, or 2 remaining arguments.
switch (argc)
{
case 2:
sigma = NUM2DBL(argv[1]);
case 1:
radius = NUM2DBL(argv[0]);
case 0:
break;
default:
raise_ChannelType_error(argv[argc-1]);
}
GetExceptionInfo(&exception);
new_image = BlurImageChannel(image, channels, radius, sigma, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#blur_image(radius=0.0, sigma=1.0)
Purpose: Blur the image
Notes: The "blur" name is used for the attribute
*/
VALUE
Image_blur_image(int argc, VALUE *argv, VALUE self)
{
return effect_image(self, argc, argv, BlurImage);
}
/*
Method: Image#border(width, height, color)
Image#border!(width, height, color)
Purpose: surrounds the image with a border of the specified width,
height, and named color
*/
static VALUE border(
int bang,
VALUE self,
VALUE width,
VALUE height,
VALUE color)
{
Image *image, *new_image;
PixelPacket old_border;
ExceptionInfo exception;
RectangleInfo rect = {0};
Data_Get_Struct(self, Image, image);
rect.width = NUM2UINT(width);
rect.height = NUM2UINT(height);
// Save current border color - we'll want to restore it afterwards.
old_border = image->border_color;
Color_to_PixelPacket(&image->border_color, color);
GetExceptionInfo(&exception);
new_image = BorderImage(image, &rect, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
if (bang)
{
new_image->border_color = old_border;
DATA_PTR(self) = new_image;
(void) DestroyImage(image);
return self;
}
image->border_color = old_border;
return rm_image_new(new_image);
}
VALUE
Image_border_bang(
VALUE self,
VALUE width,
VALUE height,
VALUE color)
{
rm_check_frozen(self);
return border(True, self, width, height, color);
}
VALUE
Image_border(
VALUE self,
VALUE width,
VALUE height,
VALUE color)
{
return border(False, self, width, height, color);
}
/*
Method: Image#border_color
Purpose: Return the name of the border color as a String.
*/
VALUE
Image_border_color(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
return PixelPacket_to_Color_Name(image, &image->border_color);
}
/*
Method: Image#border_color=
Purpose: Set the the border color
*/
VALUE
Image_border_color_eq(VALUE self, VALUE color)
{
Image *image;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
Color_to_PixelPacket(&image->border_color, color);
return self;
}
/*
Method: Image#bounding_box
Purpose: returns the bounding box of an image canvas
*/
VALUE Image_bounding_box(VALUE self)
{
Image *image;
RectangleInfo box;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
box = GetImageBoundingBox(image, &exception);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
return Rectangle_from_RectangleInfo(&box);
}
/*
Method: Image.capture(silent=false,
frame=false,
descend=false,
screen=false,
borders=false) { optional parms }
Purpose: do a screen capture
*/
VALUE
Image_capture(
int argc,
VALUE *argv,
VALUE self)
{
#ifdef HAVE_XIMPORTIMAGE
Image *image;
ImageInfo *image_info;
volatile VALUE info_obj;
XImportInfo ximage_info;
self = self; // Suppress "never referenced" message from icc
XGetImportInfo(&ximage_info);
switch (argc)
{
case 5:
ximage_info.borders = (MagickBooleanType)RTEST(argv[4]);
case 4:
ximage_info.screen = (MagickBooleanType)RTEST(argv[3]);
case 3:
ximage_info.descend = (MagickBooleanType)RTEST(argv[2]);
case 2:
ximage_info.frame = (MagickBooleanType)RTEST(argv[1]);
case 1:
ximage_info.silent = (MagickBooleanType)RTEST(argv[0]);
case 0:
break;
default:
rb_raise(rb_eArgError, "wrong number of arguments (%d for 0 to 5)", argc);
break;
}
// Get optional parms.
// Set info->filename = "root", window ID number or window name,
// or nothing to do an interactive capture
// Set info->server_name to the server name
// Also info->colorspace, depth, dither, interlace, type
info_obj = rm_info_new();
Data_Get_Struct(info_obj, Info, image_info);
// If an error occurs, IM will call our error handler and we raise an exception.
image = XImportImage(image_info, &ximage_info);
rm_check_image_exception(image, DestroyOnError);
rm_ensure_result(image);
return rm_image_new(image);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#change_geometry(geometry_string) { |cols, rows, image| }
Purpose: parse geometry string, compute new image geometry
*/
VALUE
Image_change_geometry(VALUE self, VALUE geom_arg)
{
#if defined(HAVE_PARSESIZEGEOMETRY)
Image *image;
RectangleInfo rect = {0};
volatile VALUE geom_str;
char *geometry;
unsigned int flags;
volatile VALUE ary;
Data_Get_Struct(self, Image, image);
geom_str = rb_funcall(geom_arg, rm_ID_to_s, 0);
geometry = STRING_PTR(geom_str);
flags = ParseSizeGeometry(image, geometry, &rect);
if (flags == NoValue)
{
rb_raise(rb_eArgError, "invalid geometry string `%s'", geometry);
}
ary = rb_ary_new2(3);
rb_ary_store(ary, 0, ULONG2NUM(rect.width));
rb_ary_store(ary, 1, ULONG2NUM(rect.height));
rb_ary_store(ary, 2, self);
return rb_yield(ary);
#else
Image *image;
char *geometry;
unsigned int flags;
long x, y;
unsigned long width, height;
volatile VALUE ary;
volatile VALUE geom_str;
Data_Get_Struct(self, Image, image);
geom_str = rb_funcall(geom_arg, rm_ID_to_s, 0);
geometry = STRING_PTR(geom_str);
width = image->columns;
height = image->rows;
flags = GetMagickGeometry(geometry, &x, &y, &width, &height);
if (flags == NoValue)
{
rb_raise(rb_eArgError, "invalid geometry string `%s'", geometry);
}
ary = rb_ary_new2(3);
rb_ary_store(ary, 0, ULONG2NUM(width));
rb_ary_store(ary, 1, ULONG2NUM(height));
rb_ary_store(ary, 2, self);
return rb_yield(ary);
#endif
}
/*
Method: Image#changed?
Purpose: Return true if any pixel in the image has been altered since
the image was constituted.
*/
VALUE
Image_changed_q(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
return IsTaintImage(image) ? Qtrue : Qfalse;
}
/*
Method: Image#channel
Purpose: Extract a channel from the image. A channel is a particular color
component of each pixel in the image.
*/
VALUE
Image_channel(VALUE self, VALUE channel_arg)
{
Image *image, *new_image;
ChannelType channel;
Data_Get_Struct(self, Image, image);
VALUE_TO_ENUM(channel_arg, channel, ChannelType);
new_image = rm_clone_image(image);
#if defined(HAVE_SEPARATEIMAGECHANNEL)
(void) SeparateImageChannel(new_image, channel);
#else
(void) ChannelImage(new_image, channel);
#endif
rm_check_image_exception(new_image, DestroyOnError);
rm_ensure_result(new_image);
return rm_image_new(new_image);
}
/*
Method: Image#channel_depth(channel_depth=AllChannels)
Purpose: GetImageChannelDepth
*/
VALUE
Image_channel_depth(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_GETIMAGECHANNELDEPTH)
Image *image;
ChannelType channels;
unsigned long channel_depth;
ExceptionInfo exception;
channels = extract_channels(&argc, argv);
// Ensure all arguments consumed.
if (argc > 0)
{
raise_ChannelType_error(argv[argc-1]);
}
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
channel_depth = GetImageChannelDepth(image, channels, &exception);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
return ULONG2NUM(channel_depth);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#channel_extrema(channel=AllChannels)
Purpose: Returns an array [min, max] where 'min' and 'max'
are the minimum and maximum values of all channels.
Notes: GM's implementation is very different from ImageMagick.
This method follows the IM API very closely and then
shoehorn's the GM API to more-or-less fit. Note that
IM allows you to specify more than one channel argument.
GM does not.
*/
VALUE
Image_channel_extrema(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_GETIMAGECHANNELEXTREMA) // ImageMagick 6.0.0
Image *image;
ChannelType channels;
ExceptionInfo exception;
unsigned long min, max;
volatile VALUE ary;
Data_Get_Struct(self, Image, image);
channels = extract_channels(&argc, argv);
// Ensure all arguments consumed.
if (argc > 0)
{
raise_ChannelType_error(argv[argc-1]);
}
GetExceptionInfo(&exception);
(void) GetImageChannelExtrema(image, channels, &min, &max, &exception);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
ary = rb_ary_new2(2);
rb_ary_store(ary, 0, ULONG2NUM(min));
rb_ary_store(ary, 1, ULONG2NUM(max));
return ary;
#elif defined(HAVE_GETIMAGESTATISTICS) // GraphicsMagick 1.1
Image *image;
ChannelType channel;
ImageStatistics stats;
ExceptionInfo exception;
volatile VALUE ary;
volatile VALUE type_name;
if (argc == 0)
{
rb_raise(rb_eArgError, "GraphicsMagick requires at least one channel argument.");
}
else if (argc > 1)
{
rb_raise(rb_eArgError, "GraphicsMagick does not support multi-channel statistics."
" Specify only 1 channel.");
}
VALUE_TO_ENUM(argv[0], channel, ChannelType);
if (channel == AllChannels)
{
rb_raise(rb_eArgError, "GraphicsMagick does not support multi-channel statistics."
" Specify only 1 channel.");
}
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
(void) GetImageStatistics(image, &stats, &exception);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
ary = rb_ary_new2(2);
switch(channel)
{
case RedChannel:
case CyanChannel:
rb_ary_store(ary, 0, ULONG2NUM((unsigned long)(MaxRGB*stats.red.minimum)));
rb_ary_store(ary, 1, ULONG2NUM((unsigned long)(MaxRGB*stats.red.maximum)));
break;
case GreenChannel:
case MagentaChannel:
rb_ary_store(ary, 0, ULONG2NUM((unsigned long)(MaxRGB*stats.green.minimum)));
rb_ary_store(ary, 1, ULONG2NUM((unsigned long)(MaxRGB*stats.green.maximum)));
break;
case BlueChannel:
case YellowChannel:
rb_ary_store(ary, 0, ULONG2NUM((unsigned long)(MaxRGB*stats.blue.minimum)));
rb_ary_store(ary, 1, ULONG2NUM((unsigned long)(MaxRGB*stats.blue.maximum)));
break;
case OpacityChannel:
case BlackChannel:
case MatteChannel:
rb_ary_store(ary, 0, ULONG2NUM((unsigned long)(MaxRGB*stats.opacity.minimum)));
rb_ary_store(ary, 1, ULONG2NUM((unsigned long)(MaxRGB*stats.opacity.maximum)));
break;
default:
type_name = Enum_to_s(argv[0]);
rb_raise(rb_eArgError, "unsupported channel type: %s",
STRING_PTR(type_name));
}
return ary;
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
* Method: Image#channel_mean(channel=AllChannels)
* Returns An array [mean, std. deviation]
*/
VALUE
Image_channel_mean(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_GETIMAGECHANNELMEAN) // ImageMagick 6.0.0
Image *image;
ChannelType channels;
ExceptionInfo exception;
double mean, stddev;
volatile VALUE ary;
Data_Get_Struct(self, Image, image);
channels = extract_channels(&argc, argv);
// Ensure all arguments consumed.
if (argc > 0)
{
raise_ChannelType_error(argv[argc-1]);
}
GetExceptionInfo(&exception);
(void) GetImageChannelMean(image, channels, &mean, &stddev, &exception);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
ary = rb_ary_new2(2);
rb_ary_store(ary, 0, rb_float_new(mean));
rb_ary_store(ary, 1, rb_float_new(stddev));
return ary;
#elif defined(HAVE_GETIMAGESTATISTICS) // GraphicsMagick 1.1
Image *image;
ChannelType channel;
ImageStatistics stats;
ExceptionInfo exception;
volatile VALUE ary;
volatile VALUE type_name;
if (argc == 0)
{
rb_raise(rb_eArgError, "GraphicsMagick requires at least one channel argument.");
}
else if (argc > 1)
{
rb_raise(rb_eArgError, "GraphicsMagick does not support multi-channel statistics."
" Specify only 1 channel.");
}
VALUE_TO_ENUM(argv[0], channel, ChannelType);
if (channel == AllChannels)
{
rb_raise(rb_eArgError, "GraphicsMagick does not support multi-channel statistics."
" Specify only 1 channel.");
}
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
(void) GetImageStatistics(image, &stats, &exception);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
ary = rb_ary_new2(2);
switch(channel)
{
case RedChannel:
case CyanChannel:
rb_ary_store(ary, 0, rb_float_new(stats.red.mean));
rb_ary_store(ary, 1, rb_float_new(stats.red.standard_deviation));
break;
case GreenChannel:
case MagentaChannel:
rb_ary_store(ary, 0, rb_float_new(stats.green.mean));
rb_ary_store(ary, 1, rb_float_new(stats.green.standard_deviation));
break;
case BlueChannel:
case YellowChannel:
rb_ary_store(ary, 0, rb_float_new(stats.blue.mean));
rb_ary_store(ary, 1, rb_float_new(stats.blue.standard_deviation));
break;
case OpacityChannel:
case BlackChannel:
case MatteChannel:
rb_ary_store(ary, 0, rb_float_new(stats.opacity.mean));
rb_ary_store(ary, 1, rb_float_new(stats.opacity.standard_deviation));
break;
default:
type_name = Enum_to_s(argv[0]);
rb_raise(rb_eArgError, "unsupported channel type: %s",
STRING_PTR(type_name));
}
return ary;
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#channel_threshold(red_channel, green_channel=MaxRGB,
blue_channel=MaxRGB, opacity_channel=MaxRGB)
Purpose: Same as Image#threshold except that you can specify
a separate threshold for each channel
*/
VALUE
Image_channel_threshold(int argc, VALUE *argv, VALUE self)
{
rb_warning("This method is deprecated in this release of " Q(MAGICKNAME)
". Use bilevel_channel instead.");
return threshold_image(argc, argv, self,
#if defined(HAVE_THRESHOLDIMAGECHANNEL)
ThresholdImageChannel
#else
ChannelThresholdImage
#endif
);
}
/*
Method: Image#charcoal(radius=0.0, sigma=1.0)
Purpose: Return a new image that is a copy of the input image with the
edges highlighted
*/
VALUE
Image_charcoal(int argc, VALUE *argv, VALUE self)
{
return effect_image(self, argc, argv, CharcoalImage);
}
/*
Method: Image#chop
Purpose: removes a region of an image and collapses the image to occupy
the removed portion
*/
VALUE
Image_chop(
VALUE self,
VALUE x,
VALUE y,
VALUE width,
VALUE height)
{
return xform_image(False, self, x, y, width, height, ChopImage);
}
/*
Method: Image#chromaticity
Purpose: Return the red, green, blue, and white-point chromaticity
values as a Magick::ChromaticityInfo.
*/
VALUE
Image_chromaticity(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
return ChromaticityInfo_new(&image->chromaticity);
}
/*
Method: Image#chromaticity=
Purpose: Set the red, green, blue, and white-point chromaticity
values from a Magick::ChromaticityInfo.
*/
VALUE
Image_chromaticity_eq(VALUE self, VALUE chroma)
{
Image *image;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
ChromaticityInfo_to_ChromaticityInfo(&image->chromaticity, chroma);
return self;
}
/*
Method: Image#clone
Purpose: Copy an image, along with its frozen and tainted state.
*/
VALUE
Image_clone(VALUE self)
{
volatile VALUE clone;
clone = Image_dup(self);
if (OBJ_FROZEN(self))
{
(void) rb_obj_freeze(clone);
}
return clone;
}
/*
Method: Image_color_histogram(VALUE self);
Purpose: Call GetColorHistogram (>= GM 1.1)
GetImageHistogram (>= IM 5.5.8)
Notes: returns hash {aPixel=>count}
*/
VALUE
Image_color_histogram(VALUE self)
{
#if defined(HAVE_GETCOLORHISTOGRAM)
Image *image;
volatile VALUE hash, pixel;
unsigned long x, colors;
HistogramColorPacket *histogram;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
histogram = GetColorHistogram(image, &colors, &exception);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
hash = rb_hash_new();
for (x = 0; x < colors; x++)
{
pixel = Pixel_from_PixelPacket(&histogram[x].pixel);
rb_hash_aset(hash, pixel, ULONG2NUM(histogram[x].count));
}
/*
The histogram array is specifically allocated by malloc because it is
supposed to be freed by the caller.
*/
free(histogram);
return hash;
#elif defined(HAVE_GETIMAGEHISTOGRAM)
Image *image, *dc_copy = NULL;
volatile VALUE hash, pixel;
unsigned long x, colors;
ColorPacket *histogram;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
// If image not DirectClass make a DirectClass copy.
if (image->storage_class != DirectClass)
{
dc_copy = rm_clone_image(image);
(void) SyncImage(dc_copy);
magick_free(dc_copy->colormap);
dc_copy->colormap = NULL;
dc_copy->storage_class = DirectClass;
image = dc_copy;
}
GetExceptionInfo(&exception);
histogram = GetImageHistogram(image, &colors, &exception);
if (histogram == NULL)
{
if (dc_copy)
{
(void) DestroyImage(dc_copy);
}
rb_raise(rb_eNoMemError, "not enough memory to continue");
}
if (exception.severity != UndefinedException)
{
(void) RelinquishMagickMemory(histogram);
rm_check_exception(&exception, dc_copy, DestroyOnError);
}
(void) DestroyExceptionInfo(&exception);
hash = rb_hash_new();
for (x = 0; x < colors; x++)
{
pixel = Pixel_from_PixelPacket(&histogram[x].pixel);
(void) rb_hash_aset(hash, pixel, ULONG2NUM((unsigned long)histogram[x].count));
}
/*
Christy evidently didn't agree with Bob's memory management.
*/
(void) RelinquishMagickMemory(histogram);
if (dc_copy)
{
(void) DestroyImage(dc_copy);
}
return hash;
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Static: set_profile(target_image, name, profile_image)
Purpose: The `profile_image' argument is an IPTC or ICC profile. Store
all the profiles in the profile in the target image.
Called from Image_color_profile_eq and Image_iptc_profile_eq
*/
static VALUE set_profile(VALUE self, const char *name, VALUE profile)
{
#if defined(HAVE_GETNEXTIMAGEPROFILE)
Image *image, *profile_image;
ImageInfo *info;
const MagickInfo *m;
ExceptionInfo exception;
char *profile_name;
char *profile_blob;
long profile_length;
const StringInfo *profile_data;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
profile_blob = STRING_PTR_LEN(profile, profile_length);
GetExceptionInfo(&exception);
m = GetMagickInfo(name, &exception);
CHECK_EXCEPTION()
info = CloneImageInfo(NULL);
if (!info)
{
rb_raise(rb_eNoMemError, "not enough memory to continue");
}
strncpy(info->magick, m->name, MaxTextExtent);
info->magick[MaxTextExtent-1] = '\0';
profile_image = BlobToImage(info, profile_blob, (size_t)profile_length, &exception);
(void) DestroyImageInfo(info);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
ResetImageProfileIterator(profile_image);
profile_name = GetNextImageProfile(profile_image);
while (profile_name)
{
if (rm_strcasecmp(profile_name, name) == 0)
{
profile_data = GetImageProfile(profile_image, profile_name);
if (profile)
{
(void)ProfileImage(image, profile_name, profile_data->datum
, (unsigned long)profile_data->length
, (MagickBooleanType)False);
if (image->exception.severity >= ErrorException)
{
break;
}
}
}
profile_name = GetNextImageProfile(profile_image);
}
(void) DestroyImage(profile_image);
rm_check_image_exception(image, RetainOnError);
#else
Image *image, *profile_image;
ImageInfo *info;
ExceptionInfo exception;
const MagickInfo *m;
char *profile_blob;
long profile_length;
const unsigned char *profile_data;
size_t profile_data_l;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
profile_blob = STRING_PTR_LEN(profile, profile_length);
GetExceptionInfo(&exception);
m = GetMagickInfo(name, &exception);
CHECK_EXCEPTION()
info = CloneImageInfo(NULL);
if (!info)
{
rb_raise(rb_eNoMemError, "not enough memory to continue");
}
strncpy(info->magick, m->name, MaxTextExtent);
info->magick[MaxTextExtent-1] = '\0';
profile_image = BlobToImage(info, profile_blob, profile_length, &exception);
(void) DestroyImageInfo(info);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
// GraphicsMagick uses "ICM" to refer to the ICC profile.
if (rm_strcasecmp(name, "ICC") == 0)
{
profile_data = GetImageProfile(profile_image, "ICM", &profile_data_l);
}
else
{
profile_data = GetImageProfile(profile_image, name, &profile_data_l);
}
if (profile_data)
{
(void)SetImageProfile(image, name, profile_data, profile_data_l);
}
(void) DestroyImage(profile_image);
rm_check_image_exception(image, RetainOnError);
#endif
return self;
}
/*
Method: Image#color_profile
Purpose: Return the ICC color profile as a String.
Notes: If there is no profile, returns ""
This method has no real use but is retained for compatibility
with earlier releases of RMagick, where it had no real use either.
*/
VALUE
Image_color_profile(VALUE self)
{
Image *image;
#if defined(HAVE_ACQUIRESTRINGINFO)
const StringInfo *profile;
Data_Get_Struct(self, Image, image);
profile = GetImageProfile(image, "icc");
if (!profile)
{
return Qnil;
}
return rb_str_new((char *)profile->datum, (long)profile->length);
#else
const unsigned char *profile;
size_t length;
Data_Get_Struct(self, Image, image);
profile = GetImageProfile(image, "ICM", &length);
if (!profile)
{
return Qnil;
}
return rb_str_new((char *)profile, (long)length);
#endif
}
/*
Method: Image#color_profile=(String)
Purpose: Set the ICC color profile. The argument is a string.
Notes: Pass nil to remove any existing profile.
Removes any existing profile before adding the new one.
*/
VALUE
Image_color_profile_eq(VALUE self, VALUE profile)
{
(void) Image_delete_profile(self, rb_str_new2("ICC"));
if (profile != Qnil)
{
(void) set_profile(self, "ICC", profile);
}
return self;
}
/*
Method: Image#color_flood_fill(target_color, fill_color, x, y, method)
Purpose: changes the color value of any pixel that matches target_color
and is an immediate neighbor.
Notes: use fuzz= to specify the tolerance amount (see Image_opaque)
Accepts either the FloodfillMethod or the FillToBorderMethod
*/
VALUE
Image_color_flood_fill(
VALUE self,
VALUE target_color,
VALUE fill_color,
VALUE xv,
VALUE yv,
VALUE method)
{
Image *image, *new_image;
PixelPacket target;
DrawInfo *draw_info;
PixelPacket fill;
long x, y;
int fill_method;
Data_Get_Struct(self, Image, image);
// The target and fill args can be either a color name or
// a Magick::Pixel.
Color_to_PixelPacket(&target, target_color);
Color_to_PixelPacket(&fill, fill_color);
x = NUM2LONG(xv);
y = NUM2LONG(yv);
if ((unsigned long)x > image->columns || (unsigned long)y > image->rows)
{
rb_raise(rb_eArgError, "target out of range. %lux%lu given, image is %lux%lu"
, x, y, image->columns, image->rows);
}
VALUE_TO_ENUM(method, fill_method, PaintMethod);
if (!(fill_method == FloodfillMethod || fill_method == FillToBorderMethod))
{
rb_raise(rb_eArgError, "paint method must be FloodfillMethod or "
"FillToBorderMethod (%d given)", fill_method);
}
draw_info = CloneDrawInfo(NULL, NULL);
if (!draw_info)
{
rb_raise(rb_eNoMemError, "not enough memory to continue");
}
draw_info->fill = fill;
new_image = rm_clone_image(image);
(void) ColorFloodfillImage(new_image, draw_info, target, x, y, (PaintMethod)fill_method);
// No need to check for error
(void) DestroyDrawInfo(draw_info);
return rm_image_new(new_image);
}
/*
Method: Image#colorize(r, g, b, target)
Purpose: blends the fill color specified by "target" with each pixel in
the image. Specify the percentage blend for each r, g, b
component.
*/
VALUE
Image_colorize(
int argc,
VALUE *argv,
VALUE self)
{
Image *image, *new_image;
double red, green, blue, matte;
char opacity[50];
PixelPacket target;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
if (argc == 4)
{
red = floor(100*NUM2DBL(argv[0])+0.5);
green = floor(100*NUM2DBL(argv[1])+0.5);
blue = floor(100*NUM2DBL(argv[2])+0.5);
Color_to_PixelPacket(&target, argv[3]);
sprintf(opacity, "%f/%f/%f", red, green, blue);
}
else if (argc == 5)
{
red = floor(100*NUM2DBL(argv[0])+0.5);
green = floor(100*NUM2DBL(argv[1])+0.5);
blue = floor(100*NUM2DBL(argv[2])+0.5);
matte = floor(100*NUM2DBL(argv[3])+0.5);
Color_to_PixelPacket(&target, argv[4]);
sprintf(opacity, "%f/%f/%f/%f", red, green, blue, matte);
}
else
{
rb_raise(rb_eArgError, "wrong number of arguments (%d for 4 or 5)", argc);
}
GetExceptionInfo(&exception);
new_image = ColorizeImage(image, opacity, target, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
}
/*
Method: Image#colormap(index<, new-color>)
Purpose: return the color in the colormap at the specified index. If
a new color is specified, replaces the color at the index
with the new color.
Returns: the name of the color.
Notes: The "new-color" argument can be either a color name or
a Magick::Pixel.
*/
VALUE
Image_colormap(int argc, VALUE *argv, VALUE self)
{
Image *image;
unsigned long index;
PixelPacket color, new_color;
Data_Get_Struct(self, Image, image);
// We can handle either 1 or 2 arguments. Nothing else.
if (argc == 0 || argc > 2)
{
rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc);
}
index = NUM2ULONG(argv[0]);
if (index > MaxRGB)
{
rb_raise(rb_eIndexError, "index out of range");
}
// If this is a simple "get" operation, ensure the image has a colormap.
if (argc == 1)
{
if (!image->colormap)
{
rb_raise(rb_eIndexError, "image does not contain a colormap");
}
// Validate the index
if (index > image->colors-1)
{
rb_raise(rb_eIndexError, "index out of range");
}
return PixelPacket_to_Color_Name(image, &image->colormap[index]);
}
// This is a "set" operation. Things are different.
rm_check_frozen(self);
// Replace with new color? The arg can be either a color name or
// a Magick::Pixel.
Color_to_PixelPacket(&new_color, argv[1]);
// Handle no colormap or current colormap too small.
if (!image->colormap || index > image->colors-1)
{
PixelPacket black = {0};
unsigned long i;
if (!image->colormap)
{
image->colormap = (PixelPacket *)magick_malloc((index+1)*sizeof(PixelPacket));
image->colors = 0;
}
else
{
image->colormap = magick_realloc(image->colormap, (index+1)*sizeof(PixelPacket));
}
for (i = image->colors; i < index; i++)
{
image->colormap[i] = black;
}
image->colors = index+1;
}
// Save the current color so we can return it. Set the new color.
color = image->colormap[index];
image->colormap[index] = new_color;
return PixelPacket_to_Color_Name(image, &color);
}
DEF_ATTR_READER(Image, colors, ulong)
/*
Method: Image#colorspace
Purpose: Return theImage pixel interpretation. If the colorspace is
RGB the pixels are red, green, blue. If matte is true, then
red, green, blue, and index. If it is CMYK, the pixels are
cyan, yellow, magenta, black. Otherwise the colorspace is
ignored.
*/
VALUE
Image_colorspace(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
return ColorspaceType_new(image->colorspace);
}
/*
Method: Image#colorspace=Magick::ColorspaceType
Purpose: Set the image's colorspace
Notes: Ref: Magick++'s Magick::colorSpace method
*/
VALUE
Image_colorspace_eq(VALUE self, VALUE colorspace)
{
Image *image;
ColorspaceType new_cs;
rm_check_frozen(self);
VALUE_TO_ENUM(colorspace, new_cs, ColorspaceType);
Data_Get_Struct(self, Image, image);
#if defined(HAVE_SETIMAGECOLORSPACE)
// SetImageColorspace was introduced in 5.5.7. It is essentially
// identical to the code below. It either works or throws an exception.
(void) SetImageColorspace(image, new_cs);
// No need to check for errors
#else
if (new_cs == image->colorspace)
{
return self;
}
if (new_cs != RGBColorspace &&
new_cs != TransparentColorspace &&
new_cs != GRAYColorspace)
{
if (image->colorspace != RGBColorspace &&
image->colorspace != TransparentColorspace &&
image->colorspace != GRAYColorspace)
{
TransformRGBImage(image, image->colorspace);
}
RGBTransformImage(image, new_cs);
}
else if (new_cs == RGBColorspace || new_cs == TransparentColorspace || new_cs == GRAYColorspace)
{
TransformRGBImage(image, image->colorspace);
}
#endif
return self;
}
DEF_ATTR_READER(Image, columns, int)
/*
Method: Image#compare_channel(ref_image, metric [, channel...])
Purpose: compares one or more channels in two images and returns
the specified distortion metric and a comparison image.
Notes: If no channels are specified, the default is AllChannels.
That case is the equivalent of the CompareImages method in
ImageMagick.
Originally this method was called channel_compare, but
that doesn't match the general naming convention that
methods which accept multiple optional ChannelType
arguments have names that end in _channel. So I renamed
the method to compare_channel but kept channel_compare as
an alias.
*/
VALUE Image_compare_channel(
int argc,
VALUE *argv,
VALUE self)
{
#if defined(HAVE_COMPAREIMAGECHANNELS)
Image *image, *r_image, *difference_image;
double distortion;
volatile VALUE ary;
MetricType metric_type;
ChannelType channels;
ExceptionInfo exception;
channels = extract_channels(&argc, argv);
if (argc > 2)
{
raise_ChannelType_error(argv[argc-1]);
}
if (argc != 2)
{
rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or more)", argc);
}
Data_Get_Struct(self, Image, image);
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, r_image);
VALUE_TO_ENUM(argv[1], metric_type, MetricType);
GetExceptionInfo(&exception);
difference_image = CompareImageChannels(image
, r_image
, channels
, metric_type
, &distortion
, &exception);
rm_check_exception(&exception, difference_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(difference_image);
ary = rb_ary_new2(2);
rb_ary_store(ary, 0, rm_image_new(difference_image));
rb_ary_store(ary, 1, rb_float_new(distortion));
return ary;
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#compose -> composite_op
Purpose: Return the composite operator attribute
*/
VALUE Image_compose(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
return CompositeOperator_new(image->compose);
}
/*
Method: Image#compose=composite_op
Purpose: Set the composite operator attribute
*/
VALUE Image_compose_eq(
VALUE self,
VALUE compose_arg)
{
Image *image;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
VALUE_TO_ENUM(compose_arg, image->compose, CompositeOperator);
return self;
}
/*
Method: Image#composite(image, x_off, y_off, composite_op)
Image#composite(image, gravity, composite_op)
Image#composite(image, gravity, x_off, y_off, composite_op)
Purpose: Call CompositeImage
Notes: the other image can be either an Image or an Image.
The use of the GravityType to position the composited
image is based on Magick++. The `gravity' argument has
the same effect as the -gravity option does in the
`composite' utility.
Returns: new composited image, or nil
*/
static VALUE composite(
int bang,
int argc,
VALUE *argv,
VALUE self,
ChannelType channels)
{
Image *image, *new_image;
Image *comp_image;
CompositeOperator operator;
GravityType gravity;
MagickEnum *magick_enum;
signed long x_offset;
signed long y_offset;
Data_Get_Struct(self, Image, image);
switch (argc)
{
case 3: // argv[1] is gravity, argv[2] is composite_op
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, comp_image);
VALUE_TO_ENUM(argv[1], gravity, GravityType);
VALUE_TO_ENUM(argv[2], operator, CompositeOperator);
// convert gravity to x, y offsets
switch (gravity)
{
case ForgetGravity:
case NorthWestGravity:
x_offset = 0;
y_offset = 0;
break;
case NorthGravity:
x_offset = ((long)(image->columns) - (long)(comp_image->columns)) / 2;
y_offset = 0;
break;
case NorthEastGravity:
x_offset = (long)(image->columns) - (long)(comp_image->columns);
y_offset = 0;
break;
case WestGravity:
x_offset = 0;
y_offset = ((long)(image->rows) - (long)(comp_image->rows)) / 2;
break;
case StaticGravity:
case CenterGravity:
default:
x_offset = ((long)(image->columns) - (long)(comp_image->columns)) / 2;
y_offset = ((long)(image->rows) - (long)(comp_image->rows)) / 2;
break;
case EastGravity:
x_offset = (long)(image->columns) - (long)(comp_image->columns);
y_offset = ((long)(image->rows) - (long)(comp_image->rows)) / 2;
break;
case SouthWestGravity:
x_offset = 0;
y_offset = (long)(image->rows) - (long)(comp_image->rows);
break;
case SouthGravity:
x_offset = ((long)(image->columns) - (long)(comp_image->columns)) / 2;
y_offset = (long)(image->rows) - (long)(comp_image->rows);
break;
case SouthEastGravity:
x_offset = (long)(image->columns) - (long)(comp_image->columns);
y_offset = (long)(image->rows) - (long)(comp_image->rows);
break;
}
break;
case 4: // argv[1], argv[2] is x_off, y_off,
// argv[3] is composite_op
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, comp_image);
x_offset = NUM2LONG(argv[1]);
y_offset = NUM2LONG(argv[2]);
VALUE_TO_ENUM(argv[3], operator, CompositeOperator);
break;
case 5:
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, comp_image);
VALUE_TO_ENUM(argv[1], gravity, GravityType);
x_offset = NUM2LONG(argv[2]);
y_offset = NUM2LONG(argv[3]);
VALUE_TO_ENUM(argv[4], operator, CompositeOperator);
switch(gravity)
{
case NorthEastGravity:
case EastGravity:
case SouthEastGravity:
x_offset = ((long)(image->columns) - (long)(comp_image->columns)) - x_offset;
break;
case NorthGravity:
case SouthGravity:
case CenterGravity:
case StaticGravity:
x_offset += (long)(image->columns/2) - (long)(comp_image->columns/2);
break;
default:
break;
}
switch(gravity)
{
case SouthWestGravity:
case SouthGravity:
case SouthEastGravity:
y_offset = ((long)(image->rows) - (long)(comp_image->rows)) - y_offset;
break;
case EastGravity:
case WestGravity:
case CenterGravity:
case StaticGravity:
y_offset += (long)(image->rows/2) - (long)(comp_image->rows/2);
break;
case NorthEastGravity:
case NorthGravity:
// Don't let these run into the default case
break;
default:
Data_Get_Struct(argv[1], MagickEnum, magick_enum);
rb_warning("gravity type `%s' has no effect", rb_id2name(magick_enum->id));
break;
}
break;
default:
rb_raise(rb_eArgError, "wrong number of arguments (%d for 3, 4, or 5)", argc);
break;
}
if (bang)
{
rm_check_frozen(self);
#if defined(HAVE_COMPOSITEIMAGECHANNEL)
(void) CompositeImageChannel(image, channels, operator, comp_image, x_offset, y_offset);
#else
(void) CompositeImage(image, operator, comp_image, x_offset, y_offset);
#endif
rm_check_image_exception(image, RetainOnError);
return self;
}
else
{
new_image = rm_clone_image(image);
#if defined(HAVE_COMPOSITEIMAGECHANNEL)
(void) CompositeImageChannel(new_image, channels, operator, comp_image, x_offset, y_offset);
#else
(void) CompositeImage(new_image, operator, comp_image, x_offset, y_offset);
#endif
rm_check_image_exception(new_image, DestroyOnError);
return rm_image_new(new_image);
}
}
VALUE Image_composite_bang(
int argc,
VALUE *argv,
VALUE self)
{
#if defined(HAVE_ALLCHANNELS)
ChannelType channels = (AllChannels &~ OpacityChannel);
#else
ChannelType channels = (0xff &~ OpacityChannel);
#endif
return composite(True, argc, argv, self, channels);
}
VALUE Image_composite(
int argc,
VALUE *argv,
VALUE self)
{
#if defined(HAVE_ALLCHANNELS)
ChannelType channels = (AllChannels &~ OpacityChannel);
#else
ChannelType channels = (0xff &~ OpacityChannel);
#endif
return composite(False, argc, argv, self, channels);
}
/*
Method: Image#composite_affine(composite, affine_matrix)
Purpose: composites the source over the destination image as
dictated by the affine transform.
*/
VALUE
Image_composite_affine(
VALUE self,
VALUE source,
VALUE affine_matrix)
{
Image *image, *composite, *new_image;
AffineMatrix affine;
Data_Get_Struct(self, Image, image);
Data_Get_Struct(source, Image, composite);
new_image = rm_clone_image(image);
AffineMatrix_to_AffineMatrix(&affine, affine_matrix);
(void) DrawAffineImage(new_image, composite, &affine);
rm_check_image_exception(new_image, DestroyOnError);
return rm_image_new(new_image);
}
/*
Method: Image#composite_channel(src_image, geometry, composite_operator[, channel...])
Image#composite_channel!(src_image, geometry, composite_operator[, channel...])
Purpose: Call CompositeImageChannel
*/
static VALUE
composite_channel(int bang, int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_COMPOSITEIMAGECHANNEL)
ChannelType channels;
channels = extract_channels(&argc, argv);
// There must be 3, 4, or 5 remaining arguments.
if (argc < 3)
{
rb_raise(rb_eArgError, "composite operator not specified");
}
else if (argc > 5)
{
raise_ChannelType_error(argv[argc-1]);
}
return composite(bang, argc, argv, self, channels);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
VALUE Image_composite_channel(int argc, VALUE *argv, VALUE self)
{
return composite_channel(False, argc, argv, self);
}
VALUE Image_composite_channel_bang(int argc, VALUE *argv, VALUE self)
{
return composite_channel(True, argc, argv, self);
}
/*
Method: Image#compression
Image#compression=
Purpose: Get/set the compresion attribute
*/
VALUE
Image_compression(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
return CompressionType_new(image->compression);
}
VALUE
Image_compression_eq(VALUE self, VALUE compression)
{
Image *image;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
VALUE_TO_ENUM(compression, image->compression, CompressionType);
return self;
}
/*
Method: Image#compress_colormap!
Purpose: call CompressImageColormap
Notes: API was CompressColormap until 5.4.9
*/
VALUE
Image_compress_colormap_bang(VALUE self)
{
Image *image;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
(void) CompressImageColormap(image);
rm_check_image_exception(image, RetainOnError);
return self;
}
/*
Method: Image.constitute(width, height, map, pixels)
Purpose: Creates an Image from the supplied pixel data. The
pixel data must be in scanline order, top-to-bottom.
The pixel data is an array of either all Fixed or all Float
elements. If Fixed, the elements must be in the range
[0..MaxRGB]. If Float, the elements must be normalized [0..1].
The "map" argument reflects the expected ordering of the pixel
array. It can be any combination or order of R = red, G = green,
B = blue, A = alpha, C = cyan, Y = yellow, M = magenta,
K = black, or I = intensity (for grayscale).
The pixel array must have width X height X strlen(map) elements.
Raises: ArgumentError, TypeError
*/
VALUE
Image_constitute(VALUE class, VALUE width_arg, VALUE height_arg
, VALUE map_arg, VALUE pixels_arg)
{
Image *image;
ExceptionInfo exception;
volatile VALUE pixel, pixel0;
unsigned long width, height;
long x, npixels;
char *map;
long map_l;
union
{
volatile float *f;
volatile Quantum *i;
volatile void *v;
} pixels;
int type;
StorageType stg_type;
class = class; // Suppress "never referenced" message from icc
// rb_Array converts objects that are not Arrays to Arrays if possible,
// and raises TypeError if it can't.
pixels_arg = rb_Array(pixels_arg);
width = NUM2ULONG(width_arg);
height = NUM2ULONG(height_arg);
if (width == 0 || height == 0)
{
rb_raise(rb_eArgError, "width and height must be non-zero");
}
map = STRING_PTR_LEN(map_arg, map_l);
npixels = (long)(width * height * map_l);
if (RARRAY(pixels_arg)->len != npixels)
{
rb_raise(rb_eArgError, "wrong number of array elements (%ld for %ld)"
, RARRAY(pixels_arg)->len, npixels);
}
// Inspect the first element in the pixels array to determine the expected
// type of all the elements. Allocate the pixel buffer.
pixel0 = rb_ary_entry(pixels_arg, 0);
if (TYPE(pixel0) == T_FLOAT)
{
pixels.f = ALLOC_N(volatile float, npixels);
stg_type = FloatPixel;
}
else if (TYPE(pixel0) == T_FIXNUM)
{
pixels.i = ALLOC_N(volatile Quantum, npixels);
stg_type = FIX_STG_TYPE;
}
else
{
rb_raise(rb_eTypeError, "element 0 in pixel array is %s, must be Fixnum or Double"
, rb_class2name(CLASS_OF(pixel0)));
}
type = TYPE(pixel0);
// Convert the array elements to the appropriate C type, store in pixel
// buffer.
for (x = 0; x < npixels; x++)
{
pixel = rb_ary_entry(pixels_arg, x);
if (TYPE(pixel) != type)
{
rb_raise(rb_eTypeError, "element %ld in pixel array is %s, expected %s"
, x, rb_class2name(CLASS_OF(pixel)),rb_class2name(CLASS_OF(pixel0)));
}
if (type == T_FLOAT)
{
pixels.f[x] = (float) NUM2DBL(pixel);
if (pixels.f[x] < 0.0 || pixels.f[x] > 1.0)
{
rb_raise(rb_eArgError, "element %ld is out of range [0..1]: %f", x, pixels.f[x]);
}
}
else
{
pixels.i[x] = (Quantum)FIX2LONG(pixel);
}
}
// Release the pixel buffer before any exception can be raised.
GetExceptionInfo(&exception);
#if defined(HAVE_IMPORTIMAGEPIXELS)
// This is based on ConstituteImage in IM 5.5.7
image = AllocateImage(NULL);
if (!image)
{
rb_raise(rb_eNoMemError, "not enough memory to continue.");
}
SetImageExtent(image, width, height);
(void) SetImageBackgroundColor(image);
(void) ImportImagePixels(image, 0, 0, width, height, map, stg_type, (void *)pixels.v);
rm_check_image_exception(image, DestroyOnError);
#else
image = ConstituteImage(width, height, map, stg_type, (void *)pixels.v, &exception);
rm_check_exception(&exception, image, DestroyOnError);
#endif
(void) DestroyExceptionInfo(&exception);
DestroyConstitute();
xfree((void *)pixels.v);
return rm_image_new(image);
}
/*
Method: Image#contrast(<sharpen>)
Purpose: enhances the intensity differences between the lighter and
darker elements of the image. Set sharpen to "true" to
increase the image contrast otherwise the contrast is reduced.
Notes: if omitted, "sharpen" defaults to 0
Returns: new contrasted image
*/
VALUE
Image_contrast(int argc, VALUE *argv, VALUE self)
{
Image *image, *new_image;
unsigned int sharpen = 0;
if (argc > 1)
{
rb_raise(rb_eArgError, "wrong number of arguments (%d for 0 or 1)", argc);
}
else if (argc == 1)
{
sharpen = RTEST(argv[0]);
}
Data_Get_Struct(self, Image, image);
new_image = rm_clone_image(image);
(void) ContrastImage(new_image, sharpen);
rm_check_image_exception(new_image, DestroyOnError);
return rm_image_new(new_image);
}
/*
Static: get_black_white_point
Purpose: Convert percentages to #pixels. If the white-point (2nd)
argument is not supplied set it to #pixels - black-point.
*/
static void get_black_white_point(
Image *image,
int argc,
VALUE *argv,
double *black_point,
double *white_point)
{
double pixels;
pixels = (double) (image->columns * image->rows);
switch (argc)
{
case 2:
if (rm_check_num2dbl(argv[0]))
{
*black_point = NUM2DBL(argv[0]);
}
else
{
*black_point = pixels * rm_str_to_pct(argv[0]);
}
if (rm_check_num2dbl(argv[1]))
{
*white_point = NUM2DBL(argv[1]);
}
else
{
*white_point = pixels * rm_str_to_pct(argv[1]);
}
break;
case 1:
if (rm_check_num2dbl(argv[0]))
{
*black_point = NUM2DBL(argv[0]);
}
else
{
*black_point = pixels * rm_str_to_pct(argv[0]);
}
*white_point = pixels - *black_point;
break;
default:
rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc);
break;
}
return;
}
/*
Method: Image#contrast_stretch_channel(black_point <, white_point> <, channel...>)
Purpose: Call ContrastStretchImageChannel
Notes: If white_point is not specified then it is #pixels-black_point.
Both black_point and white_point can be specified as Floats
or as percentages, i.e. "10%"
*/
VALUE
Image_contrast_stretch_channel(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_CONTRASTSTRETCHIMAGECHANNEL)
Image *image, *new_image;
ChannelType channels;
double black_point, white_point;
channels = extract_channels(&argc, argv);
if (argc > 2)
{
raise_ChannelType_error(argv[argc-1]);
}
Data_Get_Struct(self, Image, image);
get_black_white_point(image, argc, argv, &black_point, &white_point);
new_image = rm_clone_image(image);
(void) ContrastStretchImageChannel(new_image, channels, black_point, white_point);
rm_check_image_exception(new_image, DestroyOnError);
return rm_image_new(new_image);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#convolve(order, kernel)
Purpose: apply a custom convolution kernel to the image
Notes: "order" is the number of rows and columns in the kernel
"kernel" is an order**2 array of doubles
*/
VALUE
Image_convolve(
VALUE self,
VALUE order_arg,
VALUE kernel_arg)
{
Image *image, *new_image;
volatile double *kernel;
unsigned int x, order;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
order = NUM2UINT(order_arg);
kernel_arg = rb_Array(kernel_arg);
rm_check_ary_len(kernel_arg, (long)(order*order));
// Convert the kernel array argument to an array of doubles
kernel = (volatile double *)ALLOC_N(double, order*order);
for (x = 0; x < order*order; x++)
{
kernel[x] = NUM2DBL(rb_ary_entry(kernel_arg, (long)x));
}
GetExceptionInfo(&exception);
new_image = ConvolveImage(image, order, (double *)kernel, &exception);
xfree((double *)kernel);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
}
/*
* Method: Image#convolve_channel(order, kernel[, channel[, channel...]])
* Purpose: call ConvolveImageChannel
*/
VALUE
Image_convolve_channel(
int argc,
VALUE *argv,
VALUE self)
{
#if defined(HAVE_CONVOLVEIMAGECHANNEL)
Image *image, *new_image;
volatile double *kernel;
volatile VALUE ary;
unsigned int x, order;
ChannelType channels;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
channels = extract_channels(&argc, argv);
// There are 2 required arguments.
if (argc > 2)
{
raise_ChannelType_error(argv[argc-1]);
}
if (argc != 2)
{
rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or more)", argc);
}
order = NUM2UINT(argv[0]);
ary = argv[1];
rm_check_ary_len(ary, (long)(order*order));
kernel = ALLOC_N(double, (long)(order*order));
// Convert the kernel array argument to an array of doubles
for (x = 0; x < order*order; x++)
{
kernel[x] = NUM2DBL(rb_ary_entry(ary, (long)x));
}
GetExceptionInfo(&exception);
new_image = ConvolveImageChannel(image, channels, order, (double *)kernel, &exception);
xfree((double *)kernel);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#copy
Purpose: Alias for dup
*/
VALUE
Image_copy(VALUE self)
{
return rb_funcall(self, rm_ID_dup, 0);
}
/*
Method: Image#initialize_copy
Purpose: initialize copy, clone, dup
*/
VALUE
Image_init_copy(VALUE copy, VALUE orig)
{
Image *image;
Data_Get_Struct(orig, Image, image);
DATA_PTR(copy) = rm_clone_image(image);
return copy;
}
/*
Method: Image#crop(x, y, width, height)
Image#crop(gravity, width, height)
Image#crop!(x, y, width, height)
Image#crop!(gravity, width, height)
Purpose: Extract a region of the image defined by width, height, x, y
*/
VALUE
Image_crop(int argc, VALUE *argv, VALUE self)
{
return cropper(False, argc, argv, self);
}
VALUE
Image_crop_bang(int argc, VALUE *argv, VALUE self)
{
rm_check_frozen(self);
return cropper(True, argc, argv, self);
}
/*
Method: Image#cycle_colormap
Purpose: Call CycleColormapImage
*/
VALUE
Image_cycle_colormap(VALUE self, VALUE amount)
{
Image *image, *new_image;
int amt;
Data_Get_Struct(self, Image, image);
new_image = rm_clone_image(image);
amt = NUM2INT(amount);
(void) CycleColormapImage(new_image, amt);
// No need to check for an error
return rm_image_new(new_image);
}
/*
Method: Image#density
Purpose: Get the x & y resolutions.
Returns: A string in the form "XresxYres"
*/
VALUE
Image_density(VALUE self)
{
Image *image;
char density[128];
Data_Get_Struct(self, Image, image);
sprintf(density, "%gx%g", image->x_resolution, image->y_resolution);
return rb_str_new2(density);
}
/*
Method: Image#density="XxY"
Image#density=aGeometry
Purpose: Set the x & y resolutions in the image
Notes: The density is a string of the form "XresxYres" or simply "Xres".
If the y resolution is not specified, set it equal to the x
resolution. This is equivalent to PerlMagick's handling of
density.
The density can also be a Geometry object. The width attribute
is used for the x resolution. The height attribute is used for
the y resolution. If the height attribute is missing, the
width attribute is used for both.
*/
VALUE
Image_density_eq(VALUE self, VALUE density_arg)
{
Image *image;
char *density;
volatile VALUE x_val, y_val;
int count;
double x_res, y_res;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
// Get the Class ID for the Geometry class.
if (!Class_Geometry)
{
Class_Geometry = rb_const_get(Module_Magick, rm_ID_Geometry);
}
// Geometry object. Width and height attributes are always positive.
if (CLASS_OF(density_arg) == Class_Geometry)
{
x_val = rb_funcall(density_arg, rm_ID_width, 0);
x_res = NUM2DBL(x_val);
y_val = rb_funcall(density_arg, rm_ID_height, 0);
y_res = NUM2DBL(y_val);
if(x_res == 0.0)
{
rb_raise(rb_eArgError, "invalid x resolution: %f", x_res);
}
image->y_resolution = y_res != 0.0 ? y_res : x_res;
image->x_resolution = x_res;
}
// Convert the argument to a string
else
{
density = STRING_PTR(density_arg);
if (!IsGeometry(density))
{
rb_raise(rb_eArgError, "invalid density geometry %s", density);
}
count = sscanf(density, "%lfx%lf", &image->x_resolution, &image->y_resolution);
if (count < 2)
{
image->y_resolution = image->x_resolution;
}
}
return self;
}
/*
Method: Image#depth
Purpose: Return the image depth (8 or 16).
Note: If all pixels have lower-order bytes equal to higher-order
bytes, the depth will be reported as 8 even if the depth
field in the Image structure says 16.
*/
VALUE
Image_depth(VALUE self)
{
Image *image;
unsigned long depth = 0;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
depth = GetImageDepth(image, &exception);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
return INT2FIX(depth);
}
DEF_ATTR_ACCESSOR(Image, delay, ulong)
/*
Method: Image#delete_profile(name)
Purpose: call ProfileImage
Notes: name is the name of the profile to be deleted
*/
VALUE
Image_delete_profile(VALUE self, VALUE name)
{
Image *image;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
(void) ProfileImage(image, STRING_PTR(name), NULL, 0, MagickTrue);
rm_check_image_exception(image, RetainOnError);
return self;
}
/*
Method: Image#despeckle
Purpose: reduces the speckle noise in an image while preserving the
edges of the original image
Returns: a new image
*/
VALUE
Image_despeckle(VALUE self)
{
Image *image, *new_image;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
new_image = DespeckleImage(image, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
(void) DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
}
/*
Method: Image#difference
Purpose: Call the IsImagesEqual function
Returns: An array with 3 values:
[0] mean error per pixel
[1] normalized mean error
[2] normalized maximum error
Notes: "other" can be either an Image or an Image
*/
VALUE Image_difference(VALUE self, VALUE other)
{
Image *image;
Image *image2;
volatile VALUE mean, nmean, nmax;
Data_Get_Struct(self, Image, image);
Data_Get_Struct(ImageList_cur_image(other), Image, image2);
(void) IsImagesEqual(image, image2);
// No need to check for error
mean = rb_float_new(image->error.mean_error_per_pixel);
nmean = rb_float_new(image->error.normalized_mean_error);
nmax = rb_float_new(image->error.normalized_maximum_error);
return rb_ary_new3(3, mean, nmean, nmax);
}
DEF_ATTR_READER(Image, directory, str)
/*
Method: Image#displace(displacement_map, x_amp, y_amp, x_offset=0, y_offset=0)
Image#displace(displacement_map, x_amp, y_amp, gravity, x_offset=0, y_offset=0)
Purpose: Implement the -displace option of xMagick's composite command
Notes: If y_amp is omitted the default is x_amp.
*/
VALUE
Image_displace(int argc, VALUE *argv, VALUE self)
{
Image *image, *displacement_map;
double x_amplitude = 0.0, y_amplitude = 0.0;
long x_offset = 0L, y_offset = 0L;
Data_Get_Struct(self, Image, image);
if (argc < 2)
{
rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
}
if (argc > 3)
{
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, displacement_map);
get_composite_offsets(argc-3, &argv[3], image, displacement_map, &x_offset, &y_offset);
// There must be 3 arguments left
argc = 3;
}
switch (argc)
{
case 3:
y_amplitude = NUM2DBL(argv[2]);
x_amplitude = NUM2DBL(argv[1]);
break;
case 2:
x_amplitude = NUM2DBL(argv[1]);
y_amplitude = x_amplitude;
break;
}
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, displacement_map);
return special_composite(image, displacement_map, x_amplitude, y_amplitude
, x_offset, y_offset, DisplaceCompositeOp);
}
/*
Method: Image#dispatch(x, y, columns, rows, map <, float>)
Purpose: Extracts pixel data from the image and returns it as an
array of pixels. The "x", "y", "width" and "height" parameters
specify the rectangle to be extracted. The "map" parameter
reflects the expected ordering of the pixel array. It can be
any combination or order of R = red, G = green, B = blue, A =
alpha, C = cyan, Y = yellow, M = magenta, K = black, or I =
intensity (for grayscale). If the "float" parameter is specified
and true, the pixel data is returned as floating-point numbers
in the range [0..1]. By default the pixel data is returned as
integers in the range [0..MaxRGB].
Returns: an Array
Raises: ArgumentError
*/
VALUE
Image_dispatch(int argc, VALUE *argv, VALUE self)
{
Image *image;
long x, y;
unsigned long columns, rows, n, npixels;
volatile VALUE pixels_ary;
StorageType stg_type = FIX_STG_TYPE;
char *map;
long mapL;
MagickBooleanType okay;
ExceptionInfo exception;
union
{
volatile Quantum *i;
volatile double *f;
volatile void *v;
} pixels;
if (argc < 5 || argc > 6)
{
rb_raise(rb_eArgError, "wrong number of arguments (%d for 5 or 6)", argc);
}
x = NUM2LONG(argv[0]);
y = NUM2LONG(argv[1]);
columns = NUM2ULONG(argv[2]);
rows = NUM2ULONG(argv[3]);
map = STRING_PTR_LEN(argv[4], mapL);
if (argc == 6)
{
stg_type = RTEST(argv[5]) ? DoublePixel : FIX_STG_TYPE;
}
// Compute the size of the pixel array and allocate the memory.
npixels = columns * rows * mapL;
pixels.v = stg_type == FIX_STG_TYPE ? (void *) ALLOC_N(Quantum, npixels)
: (void *) ALLOC_N(double, npixels);
// Create the Ruby array for the pixels. Return this even if DispatchImage fails.
pixels_ary = rb_ary_new();
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
okay =
#if defined(HAVE_EXPORTIMAGEPIXELS)
ExportImagePixels
#else
DispatchImage
#endif
(image, x, y, columns, rows, map, stg_type, (void *)pixels.v, &exception);
if (!okay)
{
goto exit;
}
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
// Convert the pixel data to the appropriate Ruby type
if (stg_type == FIX_STG_TYPE)
{
for (n = 0; n < npixels; n++)
{
(void) rb_ary_push(pixels_ary, UINT2NUM((unsigned int) pixels.i[n]));
}
}
else
{
for (n = 0; n < npixels; n++)
{
(void) rb_ary_push(pixels_ary, rb_float_new((double)pixels.f[n]));
}
}
exit:
xfree((void *)pixels.v);
return pixels_ary;
}
/*
Method: Image#display
Purpose: display the image to an X window screen
*/
VALUE Image_display(VALUE self)
{
Image *image;
Info *info;
volatile VALUE info_obj;
Data_Get_Struct(self, Image, image);
if (image->rows == 0 || image->columns == 0)
{
rb_raise(rb_eArgError, "invalid image geometry (%lux%lu)", image->rows, image->columns);
}
info_obj = rm_info_new();
Data_Get_Struct(info_obj, Info, info);
(void) DisplayImages(info, image);
rm_check_image_exception(image, RetainOnError);
return self;
}
/*
Method: Image#dispose
Purpose: Return the dispose attribute as a DisposeType enum
*/
VALUE
Image_dispose(VALUE self)
{
Image *image;
Data_Get_Struct(self, Image, image);
return DisposeType_new(image->dispose);
}
/*
Method: Image#dispose=
Purpose: Set the dispose attribute
*/
VALUE
Image_dispose_eq(VALUE self, VALUE dispose)
{
Image *image;
rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
VALUE_TO_ENUM(dispose, image->dispose, DisposeType);
return self;
}
#if defined(GRAPHICSMAGICK)
/*
Static: create_mattes
Purpose: GraphicsMagick establishes the source image mattes in
command.c, before calling CompositeImage. This function does
that step for Image_dissolve when we're built for GraphicsMagick.
*/
static void
create_mattes(Image *image, double src_percent)
{
long x, y;
PixelPacket *q;
if (!image->matte)
{
SetImageOpacity(image,OpaqueOpacity);
}
for (y = 0; y < (long) image->rows; y++)
{
q = GetImagePixels(image, 0, y, image->columns, 1);
if (q == NULL)
{
break;
}
for (x = 0; x < (long) image->columns; x++)
{
q->opacity = (Quantum) (((MaxRGB - q->opacity) * src_percent) / 100.0);
q += 1;
}
if (!SyncImagePixels(image))
{
break;
}
}
}
#endif
/*
Method: Image#dissolve(overlay, src_percent, dst_percent, x_offset=0, y_offset=0)
Image#dissolve(overlay, src_percent, dst_percent, gravity, x_offset=0, y_offset=0)
Purpose: Corresponds to the composite -dissolve operation
Notes: `percent' can be a number or a string in the form "NN%"
The "default" value of dst_percent is -1.0, which tells
blend_geometry to leave it out of the geometry string.
*/
VALUE
Image_dissolve(int argc, VALUE *argv, VALUE self)
{
Image *image, *overlay;
double src_percent, dst_percent = -1.0;
long x_offset = 0L, y_offset = 0L;
volatile VALUE composite;
Data_Get_Struct(self, Image, image);
if (argc < 1)
{
rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
}
if (argc > 3)
{
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
get_composite_offsets(argc-3, &argv[3], image, overlay, &x_offset, &y_offset);
// There must be 3 arguments left
argc = 3;
}
switch (argc)
{
case 3:
dst_percent = rm_percentage(argv[2]) * 100.0;
case 2:
src_percent = rm_percentage(argv[1]) * 100.0;
break;
default:
rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
break;
}
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
// GraphicsMagick needs an extra step (ref: GM's command.c)
#if defined(GRAPHICSMAGICK)
overlay = rm_clone_image(overlay);
create_mattes(overlay, src_percent);
#endif
composite = special_composite(image, overlay, src_percent, dst_percent
, x_offset, y_offset, DissolveCompositeOp);
#if defined(GRAPHICSMAGICK)
(void) DestroyImage(overlay);
#endif
return composite;
}
/*
* Method: Image#distortion_channel(reconstructed_image, metric[, channel...])
* Purpose: Call GetImageChannelDistortion
*/
VALUE
Image_distortion_channel(int argc, VALUE *argv, VALUE self)
{
#if defined(HAVE_GETIMAGECHANNELDISTORTION)
Image *image, *reconstruct;
ChannelType channels;
ExceptionInfo exception;
MetricType metric;
double distortion;
Data_Get_Struct(self, Image, image);
channels = extract_channels(&argc, argv);
if (argc > 2)
{
raise_ChannelType_error(argv[argc-1]);
}
if (argc < 2)
{
rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or more)", argc);
}
Data_Get_Struct(ImageList_cur_image(argv[0]), Image, reconstruct);
VALUE_TO_ENUM(argv[1], metric, MetricType);
GetExceptionInfo(&exception);
(void) GetImageChannelDistortion(image, reconstruct, channels
, metric, &distortion, &exception);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
return rb_float_new(distortion);
#else
rm_not_implemented();
return (VALUE)0;
#endif
}
/*
Method: Image#_dump(aDepth)
Purpose: implement marshalling
Returns: a string representing the dumped image
Notes: uses ImageToBlob - use the MIFF format
in the blob since it's the most general
*/
VALUE
Image__dump(VALUE self, VALUE depth)
{
Image *image;
ImageInfo *info;
void *blob;
size_t length;
DumpedImage mi;
volatile VALUE str;
ExceptionInfo exception;
depth = depth; // Suppress "never referenced" message from icc
Data_Get_Struct(self, Image, image);
info = CloneImageInfo(NULL);
if (!info)
{
rb_raise(rb_eNoMemError, "not enough memory to continue");
}
strcpy(info->magick, image->magick);
GetExceptionInfo(&exception);
blob = ImageToBlob(info, image, &length, &exception);
// Free ImageInfo first - error handling may raise an exception
(void) DestroyImageInfo(info);
CHECK_EXCEPTION()
(void) DestroyExceptionInfo(&exception);
if (!blob)
{
rb_raise(rb_eNoMemError, "not enough memory to continue");
}