ext/RMagick/rmimage.c in rmagick-1.13.0 vs ext/RMagick/rmimage.c in rmagick-1.14.0
- old
+ new
@@ -1,6 +1,6 @@
-/* $Id: rmimage.c,v 1.153 2006/06/28 23:07:16 rmagick Exp $ */
+/* $Id: rmimage.c,v 1.183 2006/09/27 21:26:35 rmagick Exp $ */
/*============================================================================\
| Copyright (C) 2006 by Timothy P. Hunter
| Name: rmimage.c
| Author: Tim Hunter
| Purpose: Image class method definitions for RMagick
@@ -26,31 +26,34 @@
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, VALUE, VALUE);
+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 ChannelType extract_channels(int *, VALUE *);
-static void raise_ChannelType_error(VALUE);
static ImageAttribute *Next_Attribute;
+static const char *BlackPointCompensationKey = "PROFILE:black-point-compensation";
+
/*
- Method: Image#adaptive_sharpen(radius=0.0, sigma=1.0)
- Purpose: call AdaptiveSharpenImage
+ Static: adaptive_method
+ Purpose: call Adaptive(Blur|Sharpen)Image
*/
-VALUE
-Image_adaptive_sharpen(int argc, VALUE *argv, VALUE self)
+#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 *))
{
-#if defined(HAVE_ADAPTIVESHARPENIMAGE)
Image *image, *new_image;
double radius = 0.0;
double sigma = 1.0;
ExceptionInfo exception;
@@ -69,36 +72,32 @@
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
- new_image = AdaptiveSharpenImage(image, radius, sigma, &exception);
+ new_image = (fp)(image, radius, sigma, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
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_channel(radius=0.0, sigma=1.0[, channel...])
- Purpose: Call AdaptiveSharpenImageChannel
+ Static: adaptive_channel_method
+ Purpose: call Adaptive(Blur|Sharpen)ImageChannel
*/
-VALUE
-Image_adaptive_sharpen_channel(int argc, VALUE *argv, VALUE self)
+static VALUE adaptive_channel_method(
+ int argc,
+ VALUE *argv,
+ VALUE self,
+ Image *fp(const Image *, const ChannelType, const double, const double, ExceptionInfo *))
{
-#if defined(HAVE_ADAPTIVESHARPENIMAGE)
-
Image *image, *new_image;
double radius = 0.0;
double sigma = 1.0;
ExceptionInfo exception;
ChannelType channels;
@@ -120,28 +119,152 @@
Data_Get_Struct(self, Image, image);
GetExceptionInfo(&exception);
- new_image = AdaptiveSharpenImageChannel(image, channels, radius, sigma, &exception);
+ new_image = (fp)(image, channels, radius, sigma, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
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 > ULONG_MAX || dcols > 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);
+
+ 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.
@@ -253,11 +376,142 @@
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(profile_filename_l, sizeof(info->filename)));
+ info->filename[MaxTextExtent-1] = '\0';
+
+ GetExceptionInfo(&exception);
+ profile_image = ReadImage(info, &exception);
+ DestroyImageInfo(info);
+ rm_check_exception(&exception, profile_image, DestroyOnError);
+ 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, False);
+ if (image->exception.severity >= ErrorException)
+ {
+ break;
+ }
+ }
+ profile_name = GetNextImageProfile(profile_image);
+ }
+
+ 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;
+ char *profile_filename = NULL;
+ long profile_filename_l = 0;
+ ProfileInfo *generic;
+ const unsigned char *profile;
+ size_t profile_l;
+ long x;
+
+ 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 *) &image->iptc_profile;
+ strncpy(info->filename, profile_filename, min(profile_filename_l, sizeof(info->filename)));
+ info->filename[MaxTextExtent-1] = '\0';
+
+ GetExceptionInfo(&exception);
+ profile_image = ReadImage(info, &exception);
+ DestroyImageInfo(info);
+ rm_check_exception(&exception, profile_image, DestroyOnError);
+ 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;
+ }
+ }
+
+ /* 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;
+ }
+ }
+
+done:
+ 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
@@ -407,11 +661,11 @@
#if defined(HAVE_TRANSPOSEIMAGE) || defined(HAVE_TRANSVERSEIMAGE)
static VALUE
crisscross(
int bang,
VALUE self,
- Image *(fp)(const Image *, ExceptionInfo *))
+ Image *fp(const Image *, ExceptionInfo *))
{
Image *image, *new_image;
ExceptionInfo exception;
@@ -447,21 +701,23 @@
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:
- new_image = rotate(bang, self, rb_float_new(180.0));
+ degrees[0] = rb_float_new(180.0);
+ new_image = rotate(bang, 1, degrees, self);
break;
case BottomLeftOrientation:
new_image = flipflop(bang, self, FlipImage);
break;
@@ -469,19 +725,21 @@
case LeftTopOrientation:
new_image = crisscross(bang, self, TransposeImage);
break;
case RightTopOrientation:
- new_image = rotate(bang, self, rb_float_new(90.0));
+ 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:
- new_image = rotate(bang, self, rb_float_new(270.0));
+ 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;
@@ -515,11 +773,10 @@
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
@@ -529,10 +786,11 @@
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
@@ -544,10 +802,11 @@
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)
@@ -589,11 +848,49 @@
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
@@ -628,10 +925,57 @@
#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
@@ -644,10 +988,328 @@
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:
+ *x_offset = (long)(image->columns) - (long)(mark->columns) - *x_offset;
+ break;
+ case SouthWestGravity:
+ case SouthGravity:
+ *y_offset = (long)(image->rows) - (long)(mark->rows) - *y_offset;
+ break;
+ case SouthEastGravity:
+ *x_offset = (long)(image->columns) - (long)(mark->columns) - *x_offset;
+ *y_offset = (long)(image->rows) - (long)(mark->rows) - *y_offset;
+ 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
+ {
+ *x_offset = rb_protect(check_for_long_value, argv[0], &exc);
+ if (exc)
+ {
+ rb_raise(rb_eArgError, "expected GravityType, got %s", rb_obj_classname(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)
+{
+ int 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 != (int)(src_percent))
+ {
+ prec = 2;
+ fw += 3;
+ }
+
+ sz = 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 != (int)(dst_percent))
+ {
+ prec = 2;
+ fw += 3;
+ }
+
+
+ sz += 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);
+ 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)
@@ -1336,40 +1998,11 @@
Data_Get_Struct(self, Image, image);
ChromaticityInfo_to_ChromaticityInfo(&image->chromaticity, chroma);
return self;
}
-/*
- Method: Image#clip_mask=(mask-image)
- Purpose: associates a clip mask with the image
- Notes: pass "nil" for the mask-image to remove the current clip mask.
- The two images must have the same dimensions.
-*/
-VALUE
-Image_clip_mask_eq(VALUE self, VALUE mask)
-{
- Image *image, *mask_image;
- Image *clip_mask;
- rm_check_frozen(self);
- Data_Get_Struct(self, Image, image);
-
- if (mask != Qnil)
- {
- Data_Get_Struct(ImageList_cur_image(mask), Image, mask_image);
- clip_mask = rm_clone_image(mask_image);
-
- (void) SetImageClipMask(image, clip_mask);
- }
- else
- {
- (void) SetImageClipMask(image, NULL);
- }
-
- return self;
-}
-
/*
Method: Image#clone
Purpose: Copy an image, along with its frozen and tainted state.
*/
VALUE
@@ -1487,183 +2120,190 @@
rm_not_implemented();
return (VALUE)0;
#endif
}
+
+
/*
- Method: Image#color_profile
- Purpose: Return the ICC color profile as a String.
- Notes: If there is no profile, returns ""
+ 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
*/
-VALUE
-Image_color_profile(VALUE self)
+static VALUE set_profile(VALUE self, const char *name, VALUE profile)
{
- Image *image;
- volatile 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;
-#if defined(HAVE_GETIMAGEPROFILE)
- /* Both IM 6.0.0 and GM 1.1. define GetImageProfile */
- /* but the implementations are different. IM 6.0.0 */
- /* uses a StringInfo type. That's our feature test. */
+ rm_check_frozen(self);
+ Data_Get_Struct(self, Image, image);
-#if defined(HAVE_ACQUIRESTRINGINFO)
- char *str;
- StringInfo *str_info;
+ profile_blob = STRING_PTR_LEN(profile, profile_length);
- Data_Get_Struct(self, Image, image);
+ GetExceptionInfo(&exception);
+ m = GetMagickInfo(name, &exception);
+ CHECK_EXCEPTION()
- str_info = (StringInfo *)GetImageProfile(image, "icc");
- if (!str_info)
+ info = CloneImageInfo(NULL);
+ if (!info)
{
- profile = Qnil;
+ rb_raise(rb_eNoMemError, "not enough memory to continue");
}
- else
+
+ strncpy(info->magick, m->name, MaxTextExtent);
+ info->magick[MaxTextExtent-1] = '\0';
+
+ profile_image = BlobToImage(info, profile_blob, profile_length, &exception);
+ DestroyImageInfo(info);
+ CHECK_EXCEPTION()
+ DestroyExceptionInfo(&exception);
+
+ ResetImageProfileIterator(profile_image);
+ profile_name = GetNextImageProfile(profile_image);
+ while (profile_name)
{
- str = StringInfoToString(str_info);
- profile = rb_str_new2(str);
- DestroyString(str);
+ 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, False);
+ if (image->exception.severity >= ErrorException)
+ {
+ break;
+ }
+ }
+ }
+ profile_name = GetNextImageProfile(profile_image);
}
-#else /* !defined(HAVE_ACQUIRESTRINGINFO) */
- const unsigned char *str;
- size_t length;
+ 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 = Qnil; /* Assume no profile defined */
- length = 0;
- str = GetImageProfile(image, "icc", &length);
- if (str)
+ profile_blob = STRING_PTR_LEN(profile, profile_length);
+
+ GetExceptionInfo(&exception);
+ m = GetMagickInfo(name, &exception);
+ CHECK_EXCEPTION()
+
+ info = CloneImageInfo(NULL);
+ if (!info)
{
- profile = rb_str_new((char *)str, length);
+ rb_raise(rb_eNoMemError, "not enough memory to continue");
}
-#endif
+ strncpy(info->magick, m->name, MaxTextExtent);
+ info->magick[MaxTextExtent-1] = '\0';
-#else
- Data_Get_Struct(self, Image, image);
+ profile_image = BlobToImage(info, profile_blob, profile_length, &exception);
+ DestroyImageInfo(info);
+ CHECK_EXCEPTION()
+ DestroyExceptionInfo(&exception);
- // Ensure consistency between the data field and the length field. If
- // one field indicates that there is no profile, make the other agree.
- if (image->color_profile.info == NULL)
+ // GraphicsMagick uses "ICM" to refer to the ICC profile.
+ if (rm_strcasecmp(name, "ICC") == 0)
{
- image->color_profile.length = 0;
+ profile_data = GetImageProfile(profile_image, "ICM", &profile_data_l);
}
- else if (image->color_profile.length == 0
- && image->color_profile.info)
+ else
{
- magick_free(image->color_profile.info);
- image->color_profile.info = NULL;
+ profile_data = GetImageProfile(profile_image, name, &profile_data_l);
}
- if (image->color_profile.length == 0)
+ if (profile_data)
{
- profile = Qnil;
+ (void)SetImageProfile(image, name, profile_data, profile_data_l);
}
- profile = rb_str_new((const char *)image->color_profile.info
- , image->color_profile.length);
+
+ DestroyImage(profile_image);
+ rm_check_image_exception(image, RetainOnError);
+
#endif
- return profile;
+ return self;
}
+
/*
- Method: Image#color_profile=(String)
- Purpose: Set the ICC color profile. The argument is a string.
- Notes: Pass nil to remove any existing profile
+ 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_eq(VALUE self, VALUE profile)
+Image_color_profile(VALUE self)
{
Image *image;
-#if defined(HAVE_GETIMAGEPROFILE)
-
- /* Both IM 6.0.0 and GM 1.1. define SetImageProfile */
- /* but the implementations are different. IM 6.0.0 */
- /* uses a StringInfo type. That's our feature test. */
-
#if defined(HAVE_ACQUIRESTRINGINFO)
- StringInfo *str_info;
- unsigned int status = True;
+ const StringInfo *profile;
- rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
-
- if (profile == Qnil)
+ profile = GetImageProfile(image, "icc");
+ if (!profile)
{
-#if defined(HAVE_NEW_REMOVEIMAGEPROFILE)
- (void)RemoveImageProfile(image, "icc");
-#else
- str_info = RemoveImageProfile(image, "icc");
- if(str_info)
- {
- DestroyStringInfo(str_info);
- }
-#endif
+ return Qnil;
}
- else
- {
- str_info = StringToStringInfo(STRING_PTR(profile));
- if (str_info)
- {
- if (str_info->length > 0)
- {
- status = SetImageProfile(image, "icc", str_info);
- }
- DestroyStringInfo(str_info);
+ return rb_str_new((char *)profile->datum, (long)profile->length);
- if(!status)
- {
- rb_raise(rb_eNoMemError, "not enough memory to continue");
- }
- }
- }
+#else
-#else /* !defined(HAVE_ACQUIRESTRINGINFO) */
- unsigned char *prof = NULL;
- long prof_l = 0;
+ const unsigned char *profile;
+ size_t length;
- rm_check_frozen(self);
Data_Get_Struct(self, Image, image);
- if (profile == Qnil)
+ profile = GetImageProfile(image, "ICM", &length);
+ if (!profile)
{
- (void) SetImageProfile(image, "icc", NULL, 0);
+ return Qnil;
}
- else
- {
- prof = (unsigned char *)STRING_PTR_LEN(profile, prof_l);
- (void) SetImageProfile(image, "icc", prof, (size_t)prof_l);
- }
-#endif /* defined(HAVE_SETIMAGEPROFILE) */
-#else
+ return rb_str_new((char *)profile, (long)length);
- char *prof = NULL;
- long prof_l = 0;
+#endif
+}
- rm_check_frozen(self);
- Data_Get_Struct(self, Image, image);
-
+/*
+ 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)
{
- prof = STRING_PTR_LEN(profile, prof_l);
+ (void) set_profile(self, "ICC", profile);
}
-
- magick_free(image->color_profile.info);
- image->color_profile.info = NULL;
-
- if (prof_l > 0)
- {
- image->color_profile.info = magick_malloc((size_t)prof_l);
- memcpy(image->color_profile.info, prof, (size_t)prof_l);
- image->color_profile.length = prof_l;
- }
-
-#endif
return self;
}
/*
Method: Image#color_flood_fill(target_color, fill_color, x, y, method)
@@ -2855,10 +3495,30 @@
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, True);
+ 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
*/
@@ -2911,10 +3571,57 @@
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, y_amplitude;
+ 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
@@ -3062,11 +3769,115 @@
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)
+ 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)
@@ -3127,10 +3938,14 @@
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);
@@ -3179,20 +3994,19 @@
}
/*
Method: Image#each_profile
Purpose: Iterate over image profiles
- Notes: 5.5.8 and later
+ Notes: ImageMagick only
*/
VALUE
Image_each_profile(VALUE self)
{
#if defined(HAVE_GETNEXTIMAGEPROFILE)
Image *image;
volatile VALUE ary, val;
- char *str, *name;
- StringInfo *str_info;
+ char *name;
Data_Get_Struct(self, Image, image);
ResetImageProfileIterator(image);
@@ -3200,22 +4014,40 @@
name = GetNextImageProfile(image);
while (name)
{
rb_ary_store(ary, 0, rb_str_new2(name));
-
- str_info = (StringInfo *)GetImageProfile(image, name);
- if (str_info)
+#if defined(HAVE_ACQUIRESTRINGINFO)
{
- str = StringInfoToString(str_info);
- rb_ary_store(ary, 1, rb_str_new2(str));
- DestroyString(str);
+ const StringInfo *profile;
+
+ profile = GetImageProfile(image, name);
+ if (!profile)
+ {
+ rb_ary_store(ary, 1, Qnil);
+ }
+ else
+ {
+ rb_ary_store(ary, 1, rb_str_new((char *)profile->datum, (long)profile->length));
+ }
}
- else
+#else
{
- rb_ary_store(ary, 1, Qnil);
+ unsigned char *profile;
+ size_t length;
+
+ profile = GetImageProfile(image, "iptc", &length);
+ if (!profile)
+ {
+ rb_ary_store(ary, 1, Qnil);
+ }
+ else
+ {
+ rb_ary_store(ary, 1, rb_string_new((char *)profile, (long)length));
+ }
}
+#endif
val = rb_yield(ary);
name = GetNextImageProfile(image);
}
return val;
@@ -3480,11 +4312,11 @@
{
xfree((unsigned int *)pixels);
CHECK_EXCEPTION()
// Should never get here...
- rb_raise(rb_eStandardError, "ExportImagePixels failed with no explanation.");
+ rm_magick_error("ExportImagePixels failed with no explanation.", NULL);
}
DestroyExceptionInfo(&exception);
ary = rb_ary_new2(npixels);
@@ -3604,11 +4436,11 @@
// Let GC have the string buffer.
rb_str_resize(string, 0);
CHECK_EXCEPTION()
// Should never get here...
- rb_raise(rb_eStandardError, "ExportImagePixels failed with no explanation.");
+ rm_magick_error("ExportImagePixels failed with no explanation.", NULL);
}
DestroyExceptionInfo(&exception);
return string;
@@ -3694,10 +4526,64 @@
return self;
}
/*
+ * Method: Image#find_similar_region(target, x=0, y=0)
+ * Purpose: Search for a region in the image that is "similar" to the
+ * target image.
+ */
+VALUE
+Image_find_similar_region(int argc, VALUE *argv, VALUE self)
+{
+#if defined(HAVE_ISIMAGESIMILAR)
+ Image *image, *target;
+ volatile VALUE region;
+ long x = 0L, y = 0L;
+ ExceptionInfo exception;
+ unsigned int okay;
+
+ Data_Get_Struct(self, Image, image);
+
+ switch (argc)
+ {
+ case 3:
+ y = NUM2LONG(argv[2]);
+ case 2:
+ x = NUM2LONG(argv[1]);
+ case 1:
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, target);
+ break;
+ default:
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 to 3)", argc);
+ break;
+ }
+
+ GetExceptionInfo(&exception);
+ okay = IsImageSimilar(image, target, &x, &y, &exception);
+ CHECK_EXCEPTION();
+ DestroyExceptionInfo(&exception);
+
+ if (!okay)
+ {
+ return Qnil;
+ }
+
+ region = rb_ary_new2(2);
+ rb_ary_store(region, 0L, LONG2NUM(x));
+ rb_ary_store(region, 1L, LONG2NUM(y));
+
+ return region;
+
+#else
+ rm_not_implemented();
+ return (VALUE)0;
+#endif
+}
+
+
+/*
Method: Image#flip
Image#flip!
Purpose: creates a vertical mirror image by reflecting the pixels around
the central x-axis
Returns: flip: a new, flipped image
@@ -3993,21 +4879,21 @@
#endif
}
/*
- Method: Image#gamma_correct(red_gamma<, green_gamma<, blue_gamma
- <, opacity_gamma>>>)
+ Method: Image#gamma_correct(red_gamma<, green_gamma<, blue_gamma>>>)
Purpose: gamma-correct an image
Notes: At least red_gamma must be specified. If one or more levels are
omitted, the last specified number is used as the default.
+ For backward compatibility accept a 4th argument but ignore it.
*/
VALUE
Image_gamma_correct(int argc, VALUE *argv, VALUE self)
{
Image *image, *new_image;
- double red_gamma, green_gamma, blue_gamma, opacity_gamma;
+ double red_gamma, green_gamma, blue_gamma;
char gamma[50];
switch(argc)
{
case 1:
@@ -4017,35 +4903,29 @@
// cause ImageMagick to segv.
if (red_gamma == 1.0 || fabs(red_gamma) < 0.003)
{
rb_raise(rb_eArgError, "invalid gamma value (%f)", red_gamma);
}
- green_gamma = blue_gamma = opacity_gamma = red_gamma;
+ green_gamma = blue_gamma = red_gamma;
break;
case 2:
red_gamma = NUM2DBL(argv[0]);
green_gamma = NUM2DBL(argv[1]);
- blue_gamma = opacity_gamma = green_gamma;
+ blue_gamma = green_gamma;
break;
case 3:
- red_gamma = NUM2DBL(argv[0]);
- green_gamma = NUM2DBL(argv[1]);
- blue_gamma = NUM2DBL(argv[2]);
- opacity_gamma = blue_gamma;
- break;
case 4:
red_gamma = NUM2DBL(argv[0]);
green_gamma = NUM2DBL(argv[1]);
blue_gamma = NUM2DBL(argv[2]);
- opacity_gamma = NUM2DBL(argv[3]);
break;
default:
- rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 to 4)", argc);
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 to 3)", argc);
break;
}
- sprintf(gamma, "%f,%f,%f,%f", red_gamma, green_gamma, blue_gamma, opacity_gamma);
+ sprintf(gamma, "%f,%f,%f", red_gamma, green_gamma, blue_gamma);
Data_Get_Struct(self, Image, image);
new_image = rm_clone_image(image);
(void) GammaImage(new_image, gamma);
@@ -4499,11 +5379,11 @@
if (!okay)
{
rm_check_image_exception(image, RetainOnError);
// Shouldn't get here...
- rb_raise(rb_eStandardError, "ImportImagePixels failed with no explanation.");
+ rm_magick_error("ImportImagePixels failed with no explanation.", NULL);
}
return self;
#else
@@ -4641,10 +5521,11 @@
buffer[x] = '\0';
return rb_str_new2(buffer);
}
+
/*
Method: Image#interlace
Purpose: get the interlace attribute
*/
VALUE
@@ -4655,10 +5536,11 @@
Data_Get_Struct(self, Image, image);
return InterlaceType_new(image->interlace);
}
+
/*
Method: Image#interlace=
Purpose: set the interlace attribute
*/
VALUE
@@ -4670,177 +5552,67 @@
Data_Get_Struct(self, Image, image);
VALUE_TO_ENUM(interlace, image->interlace, InterlaceType);
return self;
}
+
/*
Method: Image#iptc_profile
Purpose: Return the IPTC profile as a String.
- Notes: If there is no profile, returns ""
+ Notes: If there is no profile, returns Qnil
*/
VALUE
Image_iptc_profile(VALUE self)
{
Image *image;
- volatile VALUE profile;
-#if defined(HAVE_GETIMAGEPROFILE)
- /* Both IM 6.0.0 and GM 1.1. define GetImageProfile */
- /* but the implementations are different. IM 6.0.0 */
- /* uses a StringInfo type. That's our feature test. */
-
#if defined(HAVE_ACQUIRESTRINGINFO)
- StringInfo *str_info;
- char *str;
+ const StringInfo *profile;
Data_Get_Struct(self, Image, image);
- profile = Qnil;
-
- str_info = (StringInfo *)GetImageProfile(image, "iptc");
- if (str_info)
+ profile = GetImageProfile(image, "iptc");
+ if (!profile)
{
- str = StringInfoToString(str_info);
- profile = rb_str_new2(str);
- DestroyString(str);
+ return Qnil;
}
-#else /* !defined(HAVE_ACQUIRESTRINGINFO) */
- const unsigned char *prof;
- size_t length;
+ return rb_str_new((char *)profile->datum, (long)profile->length);
- Data_Get_Struct(self, Image, image);
-
- profile = Qnil; /* Assume no profile defined */
-
- prof = GetImageProfile(image, "iptc", &length);
- if (prof)
- {
- profile = rb_str_new((char *)prof, (long) length);
- }
-#endif
-
#else
+ const unsigned char *profile;
+ size_t length;
+
Data_Get_Struct(self, Image, image);
- // Ensure consistency between the data field and the length field. If
- // one field indicates that there is no profile, make the other agree.
- if (image->iptc_profile.info == NULL)
+ profile = GetImageProfile(image, "iptc", &length);
+ if (!profile)
{
- image->iptc_profile.length = 0;
+ return Qnil;
}
- else if (image->iptc_profile.length == 0
- && image->iptc_profile.info)
- {
- magick_free(image->iptc_profile.info);
- image->iptc_profile.info = NULL;
- }
- if (image->iptc_profile.length == 0)
- {
- profile = Qnil;
- }
- profile = rb_str_new((const char *)image->iptc_profile.info
- , image->iptc_profile.length);
-#endif
+ return rb_str_new((char *)profile, (long)length);
- return profile;
+#endif
}
+
+
/*
Method: Image#iptc_profile=(String)
Purpose: Set the IPTC profile. The argument is a string.
Notes: Pass nil to remove any existing profile
*/
VALUE
Image_iptc_profile_eq(VALUE self, VALUE profile)
{
- Image *image;
-
-#if defined(HAVE_GETIMAGEPROFILE)
- /* Both IM 6.0.0 and GM 1.1. define GetImageProfile */
- /* but the implementations are different. IM 6.0.0 */
- /* uses a StringInfo type. That's our feature test. */
-
-#if defined(HAVE_ACQUIRESTRINGINFO)
- StringInfo *str_info;
- unsigned int status = True;
-
- rm_check_frozen(self);
- Data_Get_Struct(self, Image, image);
-
- if (profile == Qnil)
- {
-#if defined(HAVE_NEW_REMOVEIMAGEPROFILE)
- (void)RemoveImageProfile(image, "iptc");
-#else
- str_info = RemoveImageProfile(image, "iptc");
- if(str_info)
- {
- DestroyStringInfo(str_info);
- }
-#endif
- }
- else
- {
- str_info = StringToStringInfo(STRING_PTR(profile));
- if (str_info)
- {
- if (str_info->length > 0)
- {
- status = SetImageProfile(image, "iptc", str_info);
- }
-
- DestroyStringInfo(str_info);
-
- if(!status)
- {
- rb_raise(rb_eNoMemError, "not enough memory to continue");
- }
- }
- }
-#else /* !defined(HAVE_ACQUIRESTRINGINFO) */
- const unsigned char *prof = NULL;
- long prof_l = 0;
-
- rm_check_frozen(self);
- Data_Get_Struct(self, Image, image);
-
- if (profile == Qnil)
- {
- (void) SetImageProfile(image, "iptc", NULL, 0);
- }
- else
- {
- prof = (unsigned char *)STRING_PTR_LEN(profile, prof_l);
- (void) SetImageProfile(image, "iptc", prof, (size_t)prof_l);
- }
-#endif
-
-#else
-
- char *prof = NULL;
- long prof_l = 0;
-
- rm_check_frozen(self);
- Data_Get_Struct(self, Image, image);
-
+ (void) Image_delete_profile(self, rb_str_new2("IPTC"));
if (profile != Qnil)
{
- prof = STRING_PTR_LEN(profile, prof_l);
+ (void) set_profile(self, "IPTC", profile);
}
- magick_free(image->iptc_profile.info);
- image->iptc_profile.info = NULL;
- if (prof_l > 0)
- {
- image->iptc_profile.info = magick_malloc((size_t)prof_l);
- memcpy(image->iptc_profile.info, prof, (size_t)prof_l);
- image->iptc_profile.length = (size_t) prof_l;
- }
-
-#endif
return self;
}
/*
These are undocumented methods. The writer is
@@ -5096,10 +5868,138 @@
rm_check_image_exception(new_image, DestroyOnError);
return rm_image_new(new_image);
}
+
+/*
+ Method: Image#mask
+ Purpose: Return the image's clip mask, or nil if it doesn't have a clip
+ mask.
+ Notes: Distinguish from Image#clip_mask
+*/
+VALUE
+Image_mask(VALUE self)
+{
+ Image *image, *mask;
+ ExceptionInfo exception;
+
+ Data_Get_Struct(self, Image, image);
+
+ GetExceptionInfo(&exception);
+
+#if defined(HAVE_GETIMAGECLIPMASK)
+
+ // The returned clip mask is a clone, ours to keep.
+ mask = GetImageClipMask(image, &exception);
+ rm_check_exception(&exception, mask, DestroyOnError);
+
+#else
+ mask = image->clip_mask;
+#endif
+
+ DestroyExceptionInfo(&exception);
+
+ return mask ? rm_image_new(mask) : Qnil;
+}
+
+
+/*
+ Method: Image#mask=(mask-image)
+ Purpose: associates a clip mask with the image
+ Notes: pass "nil" for the mask-image to remove the current clip mask.
+ If the clip mask is not the same size as the target image,
+ resizes the clip mask to match the target.
+ Notes: Distinguish from Image#clip_mask=
+*/
+VALUE
+Image_mask_eq(VALUE self, VALUE mask)
+{
+ Image *image, *mask_image, *resized_image;
+ Image *clip_mask;
+ long x, y;
+ PixelPacket *q;
+ ExceptionInfo exception;
+
+ rm_check_frozen(self);
+ Data_Get_Struct(self, Image, image);
+
+ if (mask != Qnil)
+ {
+ Data_Get_Struct(ImageList_cur_image(mask), Image, mask_image);
+ clip_mask = rm_clone_image(mask_image);
+
+ // Resize if necessary
+ if (clip_mask->columns != image->columns || clip_mask->rows != image->rows)
+ {
+ GetExceptionInfo(&exception);
+ resized_image = ResizeImage(clip_mask, image->columns, image->rows
+ , UndefinedFilter, 0.0, &exception);
+ rm_check_exception(&exception, resized_image, DestroyOnError);
+ DestroyExceptionInfo(&exception);
+ rm_ensure_result(resized_image);
+ (void) DestroyImage(clip_mask);
+ clip_mask = resized_image;
+ }
+
+ // The following section is copied from mogrify.c (6.2.8-8)
+ for (y = 0; y < (long) clip_mask->rows; y++)
+ {
+ q = GetImagePixels(clip_mask, 0, y, clip_mask->columns, 1);
+ if (!q)
+ {
+ break;
+ }
+ for (x = 0; x < (long) clip_mask->columns; x++)
+ {
+ if (clip_mask->matte == False)
+ {
+ q->opacity = PIXEL_INTENSITY(q);
+ }
+ q->red = q->opacity;
+ q->green = q->opacity;
+ q->blue = q->opacity;
+ q += 1;
+ }
+ if (SyncImagePixels(clip_mask) == False)
+ {
+ (void) DestroyImage(clip_mask);
+ rm_magick_error("SyncImagePixels failed", NULL);
+ }
+ }
+
+#if defined(HAVE_SETIMAGESTORAGECLASS)
+ if (SetImageStorageClass(clip_mask, DirectClass) == False)
+ {
+ (void) DestroyImage(clip_mask);
+ rm_magick_error("SetImageStorageClass failed", NULL);
+ }
+#else
+ if (clip_mask->storage_class == PseudoClass)
+ {
+ SyncImage(image);
+ clip_mask->storage_class = DirectClass;
+ }
+#endif
+
+ clip_mask->matte = True;
+
+ // SetImageClipMask clones the clip_mask image. We can
+ // destroy our copy after SetImageClipMask is done with it.
+
+ (void) SetImageClipMask(image, clip_mask);
+ (void) DestroyImage(clip_mask);
+ }
+ else
+ {
+ (void) SetImageClipMask(image, NULL);
+ }
+
+ return self;
+}
+
+
DEF_ATTR_ACCESSOR(Image, matte, bool)
/*
Method: Image#matte_color
Purpose: Return the matte color
@@ -5398,51 +6298,78 @@
}
magick_clone_string(&image->montage, STRING_PTR(montage));
return self;
}
+
/*
- Method: Image#motion_blur(radius, sigma, angle)
- Purpose: simulates motion blur. Convolves the image with a Gaussian
- operator of the given radius and standard deviation (sigma).
- For reasonable results, radius should be larger than sigma.
- Use a radius of 0 and motion_blur selects a suitable radius
- for you. Angle gives the angle of the blurring motion.
+ Static: motion_blur(int argc, VALUE *argv, VALUE self, magick_api)
+ Purpose: called from Image_motion_blur and Image_sketch
*/
-VALUE
-Image_motion_blur(
+static VALUE
+motion_blur(
+ int argc,
+ VALUE *argv,
VALUE self,
- VALUE radius_arg,
- VALUE sigma_arg,
- VALUE angle_arg)
+ Image *fp(const Image *, const double, const double, const double, ExceptionInfo *))
{
Image *image, *new_image;
- double radius, sigma, angle;
+ double radius = 0.0;
+ double sigma = 1.0;
+ double angle = 0.0;
ExceptionInfo exception;
- Data_Get_Struct(self, Image, image);
- radius = NUM2DBL(radius_arg);
- sigma = NUM2DBL(sigma_arg);
- angle = NUM2DBL(angle_arg);
+ switch (argc)
+ {
+ case 3:
+ angle = NUM2DBL(argv[2]);
+ 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 1 to 3)", argc);
+ break;
+ }
if (sigma == 0.0)
{
rb_raise(rb_eArgError, "sigma must be != 0.0");
}
+ Data_Get_Struct(self, Image, image);
+
GetExceptionInfo(&exception);
- new_image = MotionBlurImage(image, radius, sigma, angle, &exception);
+ new_image = (fp)(image, radius, sigma, angle, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
return rm_image_new(new_image);
}
+
/*
+ Method: Image#motion_blur(radius=0.0, sigma=1.0, angle=0.0)
+ Purpose: simulates motion blur. Convolves the image with a Gaussian
+ operator of the given radius and standard deviation (sigma).
+ For reasonable results, radius should be larger than sigma.
+ Use a radius of 0 and motion_blur selects a suitable radius
+ for you. Angle gives the angle of the blurring motion.
+*/
+VALUE
+Image_motion_blur(int argc, VALUE *argv, VALUE self)
+{
+ return motion_blur(argc, argv, self, MotionBlurImage);
+}
+
+
+/*
Method: Image#negate(grayscale=false)
Purpose: negates the colors in the reference image. The grayscale option
means that only grayscale values within the image are negated.
Notes: The default for grayscale is false.
@@ -5515,12 +6442,11 @@
/*
Method: Image.new(cols, rows<, fill>) <{info block}>
Purpose: Create a new Image with "cols" columns and "rows" rows.
- If the fill argument is omitted, create a SolidFill object
- using the background color
+ If the fill argument is omitted, fill with the background color
Returns: A new Image
Note: This routine creates an Info structure to use when allocating
the Image structure. The caller can supply an info parm block to
use for initializing the Info.
*/
@@ -6114,10 +7040,51 @@
}
return Pixel_from_PixelPacket(&old_color);
}
+
+/*
+ Method: Image.pixel_interpolation_method
+ Image.pixel_interpolation_method=method
+ Purpose: Get/set the "interpolate" field in the Image structure.
+ Ref: Image.interpolate_pixel_color
+*/
+VALUE
+Image_pixel_interpolation_method(VALUE self)
+{
+#if defined(HAVE_INTERPOLATEPIXELCOLOR)
+ Image *image;
+
+ Data_Get_Struct(self, Image, image);
+ return InterpolatePixelMethod_new(image->interpolate);
+
+#else
+ rm_not_implemented();
+ return (VALUE)0;
+#endif
+}
+
+
+VALUE
+Image_pixel_interpolation_method_eq(VALUE self, VALUE method)
+{
+#if defined(HAVE_INTERPOLATEPIXELCOLOR)
+ Image *image;
+
+ rm_check_frozen(self);
+ Data_Get_Struct(self, Image, image);
+ VALUE_TO_ENUM(method, image->interpolate, InterpolatePixelMethod);
+ return self;
+
+#else
+ rm_not_implemented();
+ return (VALUE)0;
+#endif
+}
+
+
#if 0
/*
Method: Image.plasma(x1, y1, x2, y2, attenuate, depth)
x1, y1, x2, y2 - the region to apply plasma fractals values
attenuate - the plasma attenuation factor
@@ -6232,40 +7199,30 @@
}
/*
Method: Image#profile!(name, profile)
- Purpose: call ProfileImage
- Notes: modifies current image
- History: added 'True' value for 'clone' argument for IM 5.4.7
+ Purpose: If "profile" is nil, deletes the profile. Otherwise "profile"
+ must be a string containing the specified profile.
*/
VALUE
-Image_profile_bang(
- VALUE self,
- VALUE name,
- VALUE profile)
+Image_profile_bang(VALUE self, VALUE name, VALUE profile)
{
- Image *image;
- char *prof = NULL;
- long prof_l = 0;
- rm_check_frozen(self);
- Data_Get_Struct(self, Image, image);
-
- // ProfileImage issues a warning if something goes wrong.
- if (profile != Qnil)
+ if (profile == Qnil)
{
- prof = STRING_PTR_LEN(profile, prof_l);
+ return Image_delete_profile(self, name);
}
- (void) ProfileImage(image, STRING_PTR(name), (const unsigned char *)prof
- , (size_t)prof_l, True);
- rm_check_image_exception(image, RetainOnError);
+ else
+ {
+ return set_profile(self, STRING_PTR(name), profile);
+ }
- return self;
}
+
#if defined(HAVE_IMAGE_QUALITY)
DEF_ATTR_READER(Image, quality, ulong)
#endif
@@ -6438,10 +7395,18 @@
qop = DivideEvaluateOperator;
break;
case LShiftQuantumOperator:
qop = LeftShiftEvaluateOperator;
break;
+#if defined(HAVE_MAXEVALUATEOPERATOR)
+ case MaxQuantumOperator:
+ qop = MaxEvaluateOperator;
+ break;
+ case MinQuantumOperator:
+ qop = MinEvaluateOperator;
+ break;
+#endif
case MultiplyQuantumOperator:
qop = MultiplyEvaluateOperator;
break;
case OrQuantumOperator:
qop = OrEvaluateOperator;
@@ -6456,11 +7421,11 @@
qop = XorEvaluateOperator;
break;
}
GetExceptionInfo(&exception);
- (void) EvaluateImageChannel(image, channel, operator, rvalue, &exception);
+ (void) EvaluateImageChannel(image, channel, qop, rvalue, &exception);
CHECK_EXCEPTION()
DestroyExceptionInfo(&exception);
return self;
@@ -7003,11 +7968,11 @@
}
drows = scale * image->rows + 0.5;
dcols = scale * image->columns + 0.5;
if (drows > ULONG_MAX || dcols > ULONG_MAX)
{
- rb_raise(rb_eRangeError, "resulting image too big");
+ rb_raise(rb_eRangeError, "resized image too big");
}
rows = (unsigned long) drows;
columns = (unsigned long) dcols;
break;
default:
@@ -7069,25 +8034,55 @@
return rm_image_new(new_image);
}
/*
- Method: Image#rotate(degrees)
+ Method: Image#rotate(degrees [,'<' | '>'])
Purpose: creates a new image that is a rotated copy of an existing one
Image#rotate!(degrees)
Purpose: rotates the image by the specified number of degrees
+ Note: If the 2nd argument is '<' rotate only if width < height.
+ If the 2nd argument is '>' rotate only if width > height.
*/
static VALUE
-rotate(int bang, VALUE self, VALUE degrees)
+rotate(int bang, int argc, VALUE *argv, VALUE self)
{
Image *image, *new_image;
+ double degrees;
+ char *arrow;
+ long arrow_l;
ExceptionInfo exception;
Data_Get_Struct(self, Image, image);
+
+ switch (argc)
+ {
+ case 2:
+ arrow = STRING_PTR_LEN(argv[1], arrow_l);
+ if (arrow_l != 1 || (*arrow != '<' && *arrow != '>'))
+ {
+ rb_raise(rb_eArgError, "second argument must be '<' or '>', '%s' given", arrow);
+ }
+ if (*arrow == '>' && image->columns <= image->rows)
+ {
+ return Qnil;
+ }
+ if (*arrow == '<' && image->columns >= image->rows)
+ {
+ return Qnil;
+ }
+ case 1:
+ degrees = NUM2DBL(argv[0]);
+ break;
+ default:
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc);
+ break;
+ }
+
GetExceptionInfo(&exception);
- new_image = RotateImage(image, NUM2DBL(degrees), &exception);
+ new_image = RotateImage(image, degrees, &exception);
rm_check_exception(&exception, new_image, DestroyOnError);
DestroyExceptionInfo(&exception);
rm_ensure_result(new_image);
@@ -7100,20 +8095,20 @@
}
return rm_image_new(new_image);
}
VALUE
-Image_rotate(VALUE self, VALUE degrees)
+Image_rotate(int argc, VALUE *argv, VALUE self)
{
- return rotate(False, self, degrees);
+ return rotate(False, argc, argv, self);
}
VALUE
-Image_rotate_bang(VALUE self, VALUE degrees)
+Image_rotate_bang(int argc, VALUE *argv, VALUE self)
{
rm_check_frozen(self);
- return rotate(True, self, degrees);
+ return rotate(True, argc, argv, self);
}
DEF_ATTR_READER(Image, rows, int)
/*
@@ -7190,11 +8185,11 @@
}
drows = scale * image->rows + 0.5;
dcols = scale * image->columns + 0.5;
if (drows > ULONG_MAX || dcols > ULONG_MAX)
{
- rb_raise(rb_eRangeError, "resulting image too big");
+ rb_raise(rb_eRangeError, "resized image too big");
}
rows = (unsigned long) drows;
columns = (unsigned long) dcols;
break;
default:
@@ -7719,11 +8714,29 @@
return Qnil;
}
return rb_str_new(signature->value, 64);
}
+
+
/*
+ Method: Image#sketch(radius=0.0, sigma=1.0, angle=0.0)
+ Purpose: Call SketchImage
+*/
+VALUE
+Image_sketch(int argc, VALUE *argv, VALUE self)
+{
+#if defined(HAVE_SKETCHIMAGE)
+ return motion_blur(argc, argv, self, SketchImage);
+#else
+ rm_not_implemented();
+ return (VALUE)0;
+#endif
+}
+
+
+/*
Method: Image#solarize(threshold=50.0)
Purpose: applies a special effect to the image, similar to the effect
achieved in a photo darkroom by selectively exposing areas of
photo sensitive paper to light. Threshold ranges from 0 to
MaxRGB and is a measure of the extent of the solarization.
@@ -8349,11 +9362,11 @@
}
drows = scale * image->rows + 0.5;
dcols = scale * image->columns + 0.5;
if (drows > ULONG_MAX || dcols > ULONG_MAX)
{
- rb_raise(rb_eRangeError, "resulting image too big");
+ rb_raise(rb_eRangeError, "resized image too big");
}
rows = (unsigned long) drows;
columns = (unsigned long) dcols;
break;
default:
@@ -8710,10 +9723,50 @@
return rm_image_new(new_image);
}
/*
+ Method: Image#transparent_color
+ Purpose: Return the name of the transparent color as a String.
+*/
+VALUE
+Image_transparent_color(VALUE self)
+{
+#if defined(HAVE_IMAGE_TRANSPARENT_COLOR)
+ Image *image;
+
+ Data_Get_Struct(self, Image, image);
+ return PixelPacket_to_Color_Name(image, &image->transparent_color);
+#else
+ rm_not_implemented();
+ return (VALUE)0;
+#endif
+}
+
+
+/*
+ Method: Image#transparent_color=
+ Purpose: Set the the transparent color to the specified color spec.
+*/
+VALUE
+Image_transparent_color_eq(VALUE self, VALUE color)
+{
+#if defined(HAVE_IMAGE_TRANSPARENT_COLOR)
+ Image *image;
+
+ rm_check_frozen(self);
+ Data_Get_Struct(self, Image, image);
+ Color_to_PixelPacket(&image->transparent_color, color);
+ return self;
+#else
+ rm_not_implemented();
+ return (VALUE)0;
+#endif
+}
+
+
+/*
* Method: Image#transpose
* Image#transpose!
* Purpose: Call TransposeImage
*/
VALUE
@@ -8862,10 +9915,38 @@
return ImageType_new(type);
}
/*
+ Method: Image#unique_colors
+ Purpose: Call UniqueImageColors
+*/
+VALUE
+Image_unique_colors(VALUE self)
+{
+#if defined(HAVE_UNIQUEIMAGECOLORS)
+ Image *image, *new_image;
+ ExceptionInfo exception;
+
+ Data_Get_Struct(self, Image, image);
+ GetExceptionInfo(&exception);
+
+ new_image = UniqueImageColors(image, &exception);
+ rm_check_exception(&exception, new_image, DestroyOnError);
+ DestroyExceptionInfo(&exception);
+
+ rm_ensure_result(new_image);
+
+ return rm_image_new(new_image);
+#else
+ rm_not_implemented();
+ return (VALUE)0;
+#endif
+}
+
+
+/*
Method: Image#units
Purpose: Get the resolution type field
*/
VALUE
Image_units(VALUE self)
@@ -9095,11 +10176,69 @@
VALUE_TO_ENUM(method, vpm, VirtualPixelMethod);
(void) SetImageVirtualPixelMethod(image, vpm);
return self;
}
+
+
+
/*
+ Method: Image#watermark(mark, brightness=100.0, saturation=100.0
+ , [gravity,] x_off=0, y_off=0)
+ Purpose: add a watermark to an image
+ Notes: x_off and y_off can be negative, which means measure from the right/bottom
+ of the target image.
+*/
+VALUE
+Image_watermark(int argc, VALUE *argv, VALUE self)
+{
+ Image *image, *overlay, *new_image;
+ double src_percent = 100.0, dst_percent = 100.0;
+ long x_offset = 0L, y_offset = 0L;
+ char geometry[20];
+
+ 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;
+ case 1:
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
+ break;
+ default:
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
+ break;
+ }
+
+ blend_geometry(geometry, sizeof(geometry), src_percent, dst_percent);
+ CloneString(&overlay->geometry, geometry);
+
+ new_image = rm_clone_image(image);
+ (void) CompositeImage(new_image, ModulateCompositeOp, overlay, x_offset, y_offset);
+
+ rm_check_image_exception(new_image, DestroyOnError);
+
+ return rm_image_new(new_image);
+}
+
+/*
Method: Image#wave(amplitude=25.0, wavelength=150.0)
Purpose: creates a "ripple" effect in the image by shifting the pixels
vertically along a sine wave whose amplitude and wavelength is
specified by the given parameters.
Returns: self
@@ -9216,12 +10355,16 @@
rm_check_image_exception(image, RetainOnError);
return self;
}
+
DEF_ATTR_ACCESSOR(Image, x_resolution, dbl)
+DEF_ATTR_ACCESSOR(Image, y_resolution, dbl)
+
+
/*
Static: cropper
Purpose: determine if the argument list is
x, y, width, height
or
@@ -9437,23 +10580,21 @@
return rm_image_new(new_image);
}
-DEF_ATTR_ACCESSOR(Image, y_resolution, dbl)
-
/*
- Static: extract_channels
+ Extern: extract_channels
Purpose: Remove all the ChannelType arguments from the
end of the argument list.
Returns: A ChannelType value suitable for passing into
an xMagick function. Returns AllChannels if
no channel arguments were found. Returns the
number of remaining arguments.
*/
-static ChannelType extract_channels(
+ChannelType extract_channels(
int *argc,
VALUE *argv)
{
volatile VALUE arg;
ChannelType channels, ch_arg;
@@ -9485,14 +10626,14 @@
return channels;
}
/*
- Static: raise_ChannelType_error
+ Extern: raise_ChannelType_error
Purpose: raise TypeError when an non-ChannelType object
is unexpectedly encountered
*/
-static void
+void
raise_ChannelType_error(VALUE arg)
{
rb_raise(rb_eTypeError, "argument needs to be a ChannelType (%s given)"
, rb_class2name(CLASS_OF(arg)));
}