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))); }