ext/RMagick/rmimage.c in rmagick-1.9.0 vs ext/RMagick/rmimage.c in rmagick-1.9.1

- old
+ new

@@ -1,6 +1,6 @@ -/* $Id: rmimage.c,v 1.99 2005/06/19 20:26:34 rmagick Exp $ */ +/* $Id: rmimage.c,v 1.118 2005/09/07 21:51:45 rmagick Exp $ */ /*============================================================================\ | Copyright (C) 2005 by Timothy P. Hunter | Name: rmimage.c | Author: Tim Hunter | Purpose: Image class method definitions for RMagick @@ -237,57 +237,11 @@ } } return self; } -/* - Method: Image#properties [{ |k,v| block }] - Purpose: Traverse the attributes and yield to the block. - If no block, return a hash of all the attribute - keys & values - Notes: I use the word "properties" to distinguish between - these "user-added" attribute strings and Image - object attributes. -*/ -VALUE -Image_properties(VALUE self) -{ - Image *image; - const ImageAttribute *attr; - volatile VALUE attr_hash; - Data_Get_Struct(self, Image, image); - - // If block, iterate over attributes - if (rb_block_given_p()) - { - volatile VALUE ary = rb_ary_new2(2); - for (attr = image->attributes; attr; attr = Next_Attribute) - { - // Store the next ptr where Image#aset can see it. - // The app may decide to delete that attribute. - Next_Attribute = attr->next; - rb_ary_store(ary, 0, rb_str_new2(attr->key)); - rb_ary_store(ary, 1, rb_str_new2(attr->value)); - rb_yield(ary); - } - - return self; - } - - // otherwise return properties hash - else - { - attr_hash = rb_hash_new(); - for (attr = image->attributes; attr; attr = attr->next) - { - rb_hash_aset(attr_hash, rb_str_new2(attr->key), rb_str_new2(attr->value)); - } - return attr_hash; - } -} - /* Method: Image#background_color Purpose: Return the name of the background color as a String. */ VALUE @@ -372,13 +326,17 @@ ChannelType channels; ExceptionInfo exception; channels = extract_channels(&argc, argv); + if (argc > 1) + { + raise_ChannelType_error(argv[argc-1]); + } if (argc == 0) { - rb_raise(rb_eArgError, "wrong number of arguments (0 for 1 or more)"); + rb_raise(rb_eArgError, "no threshold specified"); } GetExceptionInfo(&exception); Data_Get_Struct(self, Image, image); @@ -394,37 +352,26 @@ #endif } /* - Method: Image#border_color - Purpose: Return the name of the border color as a String. + * Method: Image#black_threshold(red_channel [, green_channel + * [, blue_channel [, opacity_channel]]]); + * Purpose: Call BlackThresholdImage */ VALUE -Image_border_color(VALUE self) +Image_black_threshold(int argc, VALUE *argv, VALUE self) { - Image *image; - - Data_Get_Struct(self, Image, image); - return PixelPacket_to_Color_Name(image, &image->border_color); +#if defined(HAVE_BLACKTHRESHOLDIMAGE) + return threshold_image(argc, argv, self, BlackThresholdImage); +#else + rm_not_implemented(); + return (VALUE)0; +#endif } -/* - Method: Image#border_color= - Purpose: Set the the border color -*/ -VALUE -Image_border_color_eq(VALUE self, VALUE color) -{ - Image *image; - rm_check_frozen(self); - Data_Get_Struct(self, Image, image); - Color_to_PixelPacket(&image->border_color, color); - return self; -} - DEF_ATTR_ACCESSOR(Image, blur, dbl) /* * Method: Image#blur_channel(radius = 0.0, sigma = 1.0, channel=AllChannels) @@ -465,10 +412,11 @@ rm_not_implemented(); return (VALUE)0; #endif } + /* Method: Image#blur_image(radius=0.0, sigma=1.0) Purpose: Blur the image Notes: The "blur" name is used for the attribute */ @@ -476,10 +424,11 @@ Image_blur_image(int argc, VALUE *argv, VALUE self) { return effect_image(self, argc, argv, BlurImage); } + /* Method: Image#border(width, height, color) Image#border!(width, height, color) Purpose: surrounds the image with a border of the specified width, height, and named color @@ -541,11 +490,41 @@ VALUE color) { return border(False, self, width, height, color); } + /* + Method: Image#border_color + Purpose: Return the name of the border color as a String. +*/ +VALUE +Image_border_color(VALUE self) +{ + Image *image; + + Data_Get_Struct(self, Image, image); + return PixelPacket_to_Color_Name(image, &image->border_color); +} + +/* + Method: Image#border_color= + Purpose: Set the the border color +*/ +VALUE +Image_border_color_eq(VALUE self, VALUE color) +{ + Image *image; + + rm_check_frozen(self); + Data_Get_Struct(self, Image, image); + Color_to_PixelPacket(&image->border_color, color); + return self; +} + + +/* Method: Image#bounding_box Purpose: returns the bounding box of an image canvas */ VALUE Image_bounding_box(VALUE self) { @@ -558,10 +537,11 @@ box = GetImageBoundingBox(image, &exception); HANDLE_ERROR return Rectangle_from_RectangleInfo(&box); } + /* Method: Image.capture(silent=false, frame=false, descend=false, screen=false, @@ -687,10 +667,11 @@ rm_not_implemented(); return (VALUE)0; #endif } + /* Method: Image#changed? Purpose: Return true if any pixel in the image has been altered since the image was constituted. */ @@ -732,70 +713,10 @@ return rm_image_new(new_image); } /* - Method: Image#compare_channel(ref_image, metric [, channel...]) - Purpose: compares one or more channels in two images and returns - the specified distortion metric and a comparison image. - Notes: If no channels are specified, the default is AllChannels. - That case is the equivalent of the CompareImages method in - ImageMagick. - - Originally this method was called channel_compare, but - that doesn't match the general naming convention that - methods which accept multiple optional ChannelType - arguments have names that end in _channel. So I renamed - the method to compare_channel but kept channel_compare as - an alias. -*/ -VALUE Image_compare_channel( - int argc, - VALUE *argv, - VALUE self) -{ -#if defined(HAVE_COMPAREIMAGECHANNELS) - - Image *image, *r_image, *difference_image; - double distortion; - volatile VALUE ary; - MetricType metric_type; - ChannelType channels; - ExceptionInfo exception; - - channels = extract_channels(&argc, argv); - if (argc < 2) - { - rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or more)", argc); - } - - Data_Get_Struct(self, Image, image); - Data_Get_Struct(ImageList_cur_image(argv[0]), Image, r_image); - VALUE_TO_ENUM(argv[1], metric_type, MetricType); - - GetExceptionInfo(&exception); - difference_image = CompareImageChannels(image - , r_image - , channels - , metric_type - , &distortion - , &exception); - HANDLE_ERROR - - ary = rb_ary_new2(2); - rb_ary_store(ary, 0, rm_image_new(difference_image)); - rb_ary_store(ary, 1, rb_float_new(distortion)); - - return ary; -#else - rm_not_implemented(); - return (VALUE)0; -#endif -} - - -/* Method: Image#channel_depth(channel_depth=AllChannels) Purpose: GetImageChannelDepth */ VALUE Image_channel_depth(int argc, VALUE *argv, VALUE self) @@ -1062,37 +983,23 @@ rm_not_implemented(); return (VALUE)0; #endif } -/* - * Method: Image#black_threshold(red_channel [, green_channel - * [, blue_channel [, opacity_channel]]]); - * Purpose: Call BlackThresholdImage -*/ -VALUE -Image_black_threshold(int argc, VALUE *argv, VALUE self) -{ -#if defined(HAVE_BLACKTHRESHOLDIMAGE) - return threshold_image(argc, argv, self, BlackThresholdImage); -#else - rm_not_implemented(); - return (VALUE)0; -#endif -} - /* Method: Image#channel_threshold(red_channel, green_channel=MaxRGB, blue_channel=MaxRGB, opacity_channel=MaxRGB) Purpose: Same as Image#threshold except that you can specify a separate threshold for each channel */ VALUE Image_channel_threshold(int argc, VALUE *argv, VALUE self) { + rb_warning("This method is deprecated in this release of " Q(MAGICKNAME) + ". Use bilevel_channel instead."); return threshold_image(argc, argv, self, #if defined(HAVE_THRESHOLDIMAGECHANNEL) ThresholdImageChannel #else ChannelThresholdImage @@ -1649,11 +1556,11 @@ image->colormap[index] = new_color; return PixelPacket_to_Color_Name(image, &color); } -DEF_ATTR_READER(Image, colors, int) +DEF_ATTR_READER(Image, colors, ulong) /* Method: Image#colorspace Purpose: Return theImage pixel interpretation. If the colorspace is RGB the pixels are red, green, blue. If matte is true, then @@ -1723,14 +1630,93 @@ #endif return self; } + DEF_ATTR_READER(Image, columns, int) -DEF_ATTR_READER(Image, compose, int) + /* + Method: Image#compare_channel(ref_image, metric [, channel...]) + Purpose: compares one or more channels in two images and returns + the specified distortion metric and a comparison image. + Notes: If no channels are specified, the default is AllChannels. + That case is the equivalent of the CompareImages method in + ImageMagick. + + Originally this method was called channel_compare, but + that doesn't match the general naming convention that + methods which accept multiple optional ChannelType + arguments have names that end in _channel. So I renamed + the method to compare_channel but kept channel_compare as + an alias. +*/ +VALUE Image_compare_channel( + int argc, + VALUE *argv, + VALUE self) +{ +#if defined(HAVE_COMPAREIMAGECHANNELS) + + Image *image, *r_image, *difference_image; + double distortion; + volatile VALUE ary; + MetricType metric_type; + ChannelType channels; + ExceptionInfo exception; + + channels = extract_channels(&argc, argv); + if (argc > 2) + { + raise_ChannelType_error(argv[argc-1]); + } + if (argc != 2) + { + rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or more)", argc); + } + + Data_Get_Struct(self, Image, image); + Data_Get_Struct(ImageList_cur_image(argv[0]), Image, r_image); + VALUE_TO_ENUM(argv[1], metric_type, MetricType); + + GetExceptionInfo(&exception); + difference_image = CompareImageChannels(image + , r_image + , channels + , metric_type + , &distortion + , &exception); + HANDLE_ERROR + + ary = rb_ary_new2(2); + rb_ary_store(ary, 0, rm_image_new(difference_image)); + rb_ary_store(ary, 1, rb_float_new(distortion)); + + return ary; +#else + rm_not_implemented(); + return (VALUE)0; +#endif +} + + +/* + Method: Image#compose -> composite_op + Purpose: Return the composite operator attribute +*/ +VALUE Image_compose(VALUE self) +{ + Image *image; + + Data_Get_Struct(self, Image, image); + + return CompositeOperator_new(image->compose); +} + + +/* Method: Image#compose=composite_op Purpose: Set the composite operator attribute */ VALUE Image_compose_eq( VALUE self, @@ -1771,15 +1757,15 @@ ExceptionInfo exception; long x_offset; long y_offset; Data_Get_Struct(self, Image, image); - Data_Get_Struct(ImageList_cur_image(argv[0]), Image, comp_image); switch (argc) { case 3: // argv[1] is gravity, argv[2] is composite_op + Data_Get_Struct(ImageList_cur_image(argv[0]), Image, comp_image); VALUE_TO_ENUM(argv[1], gravity, GravityType); VALUE_TO_ENUM(argv[2], operator, CompositeOperator); // convert gravity to x, y offsets switch (gravity) @@ -1826,16 +1812,18 @@ } break; case 4: // argv[1], argv[2] is x_off, y_off, // argv[3] is composite_op + Data_Get_Struct(ImageList_cur_image(argv[0]), Image, comp_image); x_offset = NUM2LONG(argv[1]); y_offset = NUM2LONG(argv[2]); VALUE_TO_ENUM(argv[3], operator, CompositeOperator); break; case 5: + Data_Get_Struct(ImageList_cur_image(argv[0]), Image, comp_image); VALUE_TO_ENUM(argv[1], gravity, GravityType); x_offset = NUM2LONG(argv[2]); y_offset = NUM2LONG(argv[3]); VALUE_TO_ENUM(argv[4], operator, CompositeOperator); @@ -1875,10 +1863,11 @@ y_offset = 0; } if (bang) { + rm_check_frozen(self); (void) CompositeImage(image, operator, comp_image, x_offset, y_offset); HANDLE_ERROR_IMG(image) return self; } else @@ -2214,10 +2203,14 @@ Data_Get_Struct(self, Image, image); channels = extract_channels(&argc, argv); // There are 2 required arguments. + if (argc > 2) + { + raise_ChannelType_error(argv[argc-1]); + } if (argc != 2) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or more)", argc); } @@ -2478,12 +2471,14 @@ nmean = rb_float_new(image->error.normalized_mean_error); nmax = rb_float_new(image->error.normalized_maximum_error); return rb_ary_new3(3, mean, nmean, nmax); } + DEF_ATTR_READER(Image, directory, str) + /* Method: Image#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 @@ -2606,14 +2601,39 @@ } return self; } -DEF_ATTR_ACCESSOR(Image, dispose, ulong) +/* + Method: Image#dispose + Purpose: Return the dispose attribute as a DisposeType enum +*/ +VALUE +Image_dispose(VALUE self) +{ + Image *image; + Data_Get_Struct(self, Image, image); + return DisposeType_new(image->dispose); +} /* + Method: Image#dispose= + Purpose: Set the dispose attribute +*/ +VALUE +Image_dispose_eq(VALUE self, VALUE dispose) +{ + Image *image; + + rm_check_frozen(self); + Data_Get_Struct(self, Image, image); + VALUE_TO_ENUM(dispose, image->dispose, DisposeType); + return self; +} + +/* Method: Image#_dump(aDepth) Purpose: implement marshalling Returns: a string representing the dumped image Notes: uses ImageToBlob - use the MIFF format in the blob since it's the most general @@ -2783,13 +2803,13 @@ rb_raise(rb_eArgError, "wrong number of arguments (%d for 0 to 2)", argc); break; } Data_Get_Struct(self, Image, image); - if (sigma <= 0.0) + if (sigma == 0.0) { - rb_raise(rb_eArgError, "sigma must be > 0.0"); + rb_raise(rb_eArgError, "sigma must be != 0.0"); } GetExceptionInfo(&exception); new_image = (effector)(image, radius, sigma, &exception); HANDLE_ERROR @@ -3005,10 +3025,11 @@ } DEF_ATTR_READER(Image, filename, str) + /* Method: Image#filesize Purpose: Return the image filesize */ VALUE Image_filesize(VALUE self) @@ -3017,10 +3038,11 @@ Data_Get_Struct(self, Image, image); return INT2FIX(GetBlobSize(image)); } + /* Method: Image#filter, filter= Purpose: Get/set filter type */ VALUE @@ -3041,10 +3063,11 @@ Data_Get_Struct(self, Image, image); VALUE_TO_ENUM(filter, image->filter, FilterTypes); return self; } + /* Method: Image#flip Image#flip! Purpose: creates a vertical mirror image by reflecting the pixels around the central x-axis @@ -3283,10 +3306,11 @@ Data_Get_Struct(self, Image, image); image->fuzz = rm_fuzz_to_dbl(fuzz); return self; } + DEF_ATTR_ACCESSOR(Image, gamma, dbl) /* * Method: Image#gamma_channel(gamma, channel=AllChannels) @@ -3473,11 +3497,11 @@ geom_str = rb_funcall(geometry, ID_to_s, 0); geom = STRING_PTR(geom_str); if (!IsGeometry(geom)) { - rb_raise(rb_eArgError, "invalid geometry: %s", geom); + rb_raise(rb_eTypeError, "invalid geometry: %s", geom); } magick_clone_string(&image->geometry, geom); return self; } @@ -3663,81 +3687,149 @@ Method: Image#import_pixels Purpose: store image pixel data from an array Notes: See Image#export_pixels */ VALUE -Image_import_pixels( - VALUE self, - VALUE x_arg, - VALUE y_arg, - VALUE cols_arg, - VALUE rows_arg, - VALUE map_arg, - VALUE pixel_ary) +Image_import_pixels(int argc, VALUE *argv, VALUE self) { #if defined(HAVE_IMPORTIMAGEPIXELS) Image *image, *clone_image; long x_off, y_off; unsigned long cols, rows; unsigned long npixels; - long n; + long n, buffer_l; char *map; - volatile int *pixels; + volatile VALUE pixel_arg, pixel_ary; + StorageType stg_type = CharPixel; + size_t type_sz, map_l; + volatile int *pixels = NULL; + volatile void *buffer; unsigned int okay; ExceptionInfo exception; rm_check_frozen(self); - Data_Get_Struct(self, Image, image); - map = STRING_PTR(map_arg); - x_off = NUM2LONG(x_arg); - y_off = NUM2LONG(y_arg); - cols = NUM2ULONG(cols_arg); - rows = NUM2ULONG(rows_arg); + switch (argc) + { + case 7: + VALUE_TO_ENUM(argv[6], stg_type, StorageType); + case 6: + x_off = NUM2LONG(argv[0]); + y_off = NUM2LONG(argv[1]); + cols = NUM2ULONG(argv[2]); + rows = NUM2ULONG(argv[3]); + map = STRING_PTR(argv[4]); + pixel_arg = argv[5]; + break; + default: + rb_raise(rb_eArgError, "wrong number of arguments (%d for 6 or 7)", argc); + break; + } + Data_Get_Struct(self, Image, image); + if (x_off < 0 || y_off < 0 || cols <= 0 || rows <= 0) { rb_raise(rb_eArgError, "invalid import geometry"); } - npixels = cols * rows * strlen(map); + map_l = strlen(map); + npixels = cols * rows * map_l; - // rb_Array converts objects that are not Arrays to Arrays if possible, - // and raises TypeError if it can't. - pixel_ary = rb_Array(pixel_ary); - - if (RARRAY(pixel_ary)->len < npixels) + // Assume that any object that responds to :to_str is a string buffer containing + // binary pixel data. + if (rb_respond_to(pixel_arg, rb_intern("to_str"))) { - rb_raise(rb_eArgError, "pixel array too small (need %lu, got %ld)" - , npixels, RARRAY(pixel_ary)->len); - } + buffer = (void *)STRING_PTR_LEN(pixel_arg, buffer_l); + switch (stg_type) + { + case CharPixel: + type_sz = 1; + break; + case ShortPixel: + type_sz = sizeof(unsigned short); + break; + case IntegerPixel: + type_sz = sizeof(unsigned int); + break; + case LongPixel: + type_sz = sizeof(unsigned long); + break; +#if defined(HAVE_QUANTUMPIXEL) + case QuantumPixel: + type_sz = sizeof(Quantum); + break; +#endif + default: + rb_raise(rb_eArgError, "unsupported storage type %s", StorageType_name(stg_type)); + break; + } - // Get array for integer pixels. Use Ruby's memory so GC will clean up after us - // in case of an exception. - pixels = ALLOC_N(int, npixels); - if (!pixels) // app recovered from exception... - { - return self; + if (buffer_l % type_sz != 0) + { + rb_raise(rb_eArgError, "pixel buffer must be an exact multiple of the storage type size"); + } + if ((buffer_l / type_sz) % map_l != 0) + { + rb_raise(rb_eArgError, "pixel buffer must contain an exact multiple of the map length"); + } + if (buffer_l/type_sz < npixels) + { + rb_raise(rb_eArgError, "pixel buffer too small (need %lu channel values, got %ld)" + , npixels, buffer_l/type_sz); + } } - - for (n = 0; n < npixels; n++) + // Otherwise convert the argument to an array and convert the array elements + // to binary pixel data. + else { - volatile VALUE p = rb_ary_entry(pixel_ary, n); - long q = ScaleQuantumToLong(NUM2LONG(p)); - pixels[n] = (int) q; + // rb_Array converts an object that is not an array to an array if possible, + // and raises TypeError if it can't. It usually is possible. + pixel_ary = rb_Array(pixel_arg); + + if (RARRAY(pixel_ary)->len % map_l != 0) + { + rb_raise(rb_eArgError, "pixel array must contain an exact multiple of the map length"); + } + if (RARRAY(pixel_ary)->len < npixels) + { + rb_raise(rb_eArgError, "pixel array too small (need %lu elements, got %ld)" + , npixels, RARRAY(pixel_ary)->len); + } + + // Get array for integer pixels. Use Ruby's memory so GC will clean up after us + // in case of an exception. + pixels = ALLOC_N(int, npixels); + if (!pixels) // app recovered from exception... + { + return self; + } + + for (n = 0; n < npixels; n++) + { + volatile VALUE p = rb_ary_entry(pixel_ary, n); + long q = ScaleQuantumToLong(NUM2LONG(p)); + pixels[n] = (int) q; + } + buffer = (void *) pixels; + stg_type = IntegerPixel; } + // Import into a clone - ImportImagePixels destroys the input image if an error occurs. GetExceptionInfo(&exception); clone_image = CloneImage(image, 0, 0, True, &exception); HANDLE_ERROR - okay = ImportImagePixels(clone_image, x_off, y_off, cols, rows, map, IntegerPixel, (void *)pixels); + okay = ImportImagePixels(clone_image, x_off, y_off, cols, rows, map, stg_type, (const void *)buffer); // Free pixel array before checking for errors. If an error occurred, ImportImagePixels // destroyed the clone image, so we don't have to. - xfree((void *)pixels); + if (pixels) + { + xfree((void *)pixels); + } if (!okay) { HANDLE_ERROR_IMG(clone_image) // Shouldn't get here... @@ -3780,11 +3872,15 @@ x += sprintf(buffer+x, "%s=>", image->magick_filename); } // Print current filename. x += sprintf(buffer+x, "%s", image->filename); // Print scene number. +#if defined(HAVE_GETNEXTIMAGEINLIST) + if ((GetPreviousImageInList(image) != NULL) && (GetNextImageInList(image) != NULL) && image->scene > 0) +#else if ((image->previous || image->next) && image->scene > 0) +#endif { x += sprintf(buffer+x, "[%lu]", image->scene); } // Print format x += sprintf(buffer+x, " %s ", image->magick); @@ -4566,10 +4662,11 @@ Image_monitor_eq(VALUE self, VALUE monitor) { #if defined(HAVE_SETIMAGEPROGRESSMONITOR) Image *image; + rm_check_frozen(self); Data_Get_Struct(self, Image, image); if (NIL_P(monitor)) { image->progress_monitor = NULL; @@ -4661,13 +4758,13 @@ Data_Get_Struct(self, Image, image); radius = NUM2DBL(radius_arg); sigma = NUM2DBL(sigma_arg); angle = NUM2DBL(angle_arg); - if (sigma <= 0.0) + if (sigma == 0.0) { - rb_raise(rb_eArgError, "sigma must be > 0.0"); + rb_raise(rb_eArgError, "sigma must be != 0.0"); } GetExceptionInfo(&exception); new_image = MotionBlurImage(image, radius, sigma, angle, &exception); HANDLE_ERROR @@ -4994,18 +5091,18 @@ VALUE Image_number_colors(VALUE self) { Image *image; ExceptionInfo exception; - unsigned int n = 0; + unsigned long n = 0; Data_Get_Struct(self, Image, image); GetExceptionInfo(&exception); - n = GetNumberColors(image, NULL, &exception); + n = (unsigned long) GetNumberColors(image, NULL, &exception); HANDLE_ERROR - return INT2FIX(n); + return ULONG2NUM(n); } DEF_ATTR_ACCESSOR(Image, offset, long) /* @@ -5236,11 +5333,15 @@ GetExceptionInfo(&exception); old_color = *AcquireImagePixels(image, x, y, 1, 1, &exception); HANDLE_ERROR // PseudoClass +#if defined(HAVE_IMAGE_STORAGE_CLASS) + if (image->storage_class == PseudoClass) +#else if (image->class == PseudoClass) +#endif { IndexPacket *indexes = GetIndexes(image); old_color = image->colormap[*indexes]; } if (!image->matte) @@ -5257,16 +5358,24 @@ return Pixel_from_PixelPacket(&image->background_color); } // Set the color of a pixel. Return previous color. // Convert to DirectClass +#if defined(HAVE_IMAGE_STORAGE_CLASS) + if (image->storage_class == PseudoClass) +#else if (image->class == PseudoClass) +#endif { SyncImage(image); magick_free(image->colormap); image->colormap = NULL; +#if defined(HAVE_IMAGE_STORAGE_CLASS) + image->storage_class = DirectClass; +#else image->class = DirectClass; +#endif } pixel = GetImagePixels(image, x, y, 1, 1); if (pixel) { @@ -5435,10 +5544,16 @@ HANDLE_ERROR_IMG(image) return self; } +#if defined(HAVE_IMAGE_QUALITY) +DEF_ATTR_READER(Image, quality, ulong) +#endif + + + /* Method: Image#quantum_depth -> 8, 16, or 32 Purpose: Return image depth to nearest quantum Notes: IM 6.0.0 introduced GetImageQuantumDepth, IM 6.0.5 added a 2nd argument. The MagickFalse argument @@ -5640,43 +5755,11 @@ return (VALUE)0; #endif } - /* - Method: Image#radial_blur(angle) - Purpose: Call RadialBlurImage - Notes: Angle is in degrees -*/ -VALUE -Image_radial_blur(VALUE self, VALUE angle) -{ -#if defined(HAVE_RADIALBLURIMAGE) - Image *image, *new_image; - ExceptionInfo exception; - - Data_Get_Struct(self, Image, image); - GetExceptionInfo(&exception); - - new_image = RadialBlurImage(image, NUM2DBL(angle), &exception); - HANDLE_ERROR - - return rm_image_new(new_image); -#else - rm_not_implemented(); - return (VALUE)0; -#endif -} - - -#if defined(HAVE_IMAGE_QUALITY) -DEF_ATTR_READER(Image, quality, ulong) -#endif - - -/* Method: Image#quantize(<number_colors<, colorspace<, dither<, tree_depth<, measure_error>>>>>) defaults: 256, Magick::RGBColorspace, true, 0, false Purpose: call QuantizeImage */ VALUE @@ -5714,11 +5797,38 @@ QuantizeImage(&quantize_info, new_image); return rm_image_new(new_image); } + /* + Method: Image#radial_blur(angle) + Purpose: Call RadialBlurImage + Notes: Angle is in degrees +*/ +VALUE +Image_radial_blur(VALUE self, VALUE angle) +{ +#if defined(HAVE_RADIALBLURIMAGE) + Image *image, *new_image; + ExceptionInfo exception; + + Data_Get_Struct(self, Image, image); + GetExceptionInfo(&exception); + + new_image = RadialBlurImage(image, NUM2DBL(angle), &exception); + HANDLE_ERROR + + return rm_image_new(new_image); +#else + rm_not_implemented(); + return (VALUE)0; +#endif +} + + +/* Method: Image#random_channel_threshold Purpose: changes the value of individual pixels based on the intensity of each pixel compared to a random threshold. The result is a low-contrast, two color image. Args: `channel_arg' can be "all", "intensity", "opacity", "matte" @@ -6441,11 +6551,82 @@ SetImageOpacity(image, opacity); return self; } + /* + Method: Image#properties [{ |k,v| block }] + Purpose: Traverse the attributes and yield to the block. + If no block, return a hash of all the attribute + keys & values + Notes: I use the word "properties" to distinguish between + these "user-added" attribute strings and Image + object attributes. +*/ +VALUE +Image_properties(VALUE self) +{ + Image *image; + const ImageAttribute *attr; + volatile VALUE attr_hash; + + Data_Get_Struct(self, Image, image); + + // If block, iterate over attributes + if (rb_block_given_p()) + { + volatile VALUE ary = rb_ary_new2(2); + +#if defined(HAVE_GETNEXTIMAGEATTRIBUTE) + ResetImageAttributeIterator(image); + attr = GetNextImageAttribute(image); + while (attr) + { + rb_ary_store(ary, 0, rb_str_new2(attr->key)); + rb_ary_store(ary, 1, rb_str_new2(attr->value)); + rb_yield(ary); + attr = GetNextImageAttribute(image); + } +#else + for (attr = image->attributes; attr; attr = Next_Attribute) + { + // Store the next ptr where Image#aset can see it. + // The app may decide to delete that attribute. + Next_Attribute = attr->next; + rb_ary_store(ary, 0, rb_str_new2(attr->key)); + rb_ary_store(ary, 1, rb_str_new2(attr->value)); + rb_yield(ary); + } +#endif + return self; + } + + // otherwise return properties hash + else + { + attr_hash = rb_hash_new(); +#if defined(HAVE_GETNEXTIMAGEATTRIBUTE) + ResetImageAttributeIterator(image); + attr = GetNextImageAttribute(image); + while (attr) + { + rb_hash_aset(attr_hash, rb_str_new2(attr->key), rb_str_new2(attr->value)); + attr = GetNextImageAttribute(image); + } +#else + for (attr = image->attributes; attr; attr = attr->next) + { + rb_hash_aset(attr_hash, rb_str_new2(attr->key), rb_str_new2(attr->value)); + } +#endif + return attr_hash; + } +} + + +/* Method: Image#shade(shading=false, azimuth=30, elevation=30) Purpose: shines a distant light on an image to create a three-dimensional effect. You control the positioning of the light with azimuth and elevation; azimuth is measured in degrees off the x axis and elevation is measured in pixels above the Z axis @@ -6544,37 +6725,10 @@ return (VALUE)0; #endif } /* - Method: Image#shave(width, height) - Image#shave!(width, height) - Purpose: shaves pixels from the image edges, leaving a rectangle - of the specified width & height in the center - Returns: shave: a new image - shave!: self, shaved -*/ -VALUE -Image_shave( - VALUE self, - VALUE width, - VALUE height) -{ - return xform_image(False, self, INT2FIX(0), INT2FIX(0), width, height, ShaveImage); -} - -VALUE -Image_shave_bang( - VALUE self, - VALUE width, - VALUE height) -{ - rm_check_frozen(self); - return xform_image(True, self, INT2FIX(0), INT2FIX(0), width, height, ShaveImage); -} - -/* Method: Image#sharpen(radius=0, sigma=1) Purpose: sharpens an image Returns: a new image */ VALUE @@ -6628,11 +6782,41 @@ rm_not_implemented(); return (VALUE)0; #endif } + /* + Method: Image#shave(width, height) + Image#shave!(width, height) + Purpose: shaves pixels from the image edges, leaving a rectangle + of the specified width & height in the center + Returns: shave: a new image + shave!: self, shaved +*/ +VALUE +Image_shave( + VALUE self, + VALUE width, + VALUE height) +{ + return xform_image(False, self, INT2FIX(0), INT2FIX(0), width, height, ShaveImage); +} + + +VALUE +Image_shave_bang( + VALUE self, + VALUE width, + VALUE height) +{ + rm_check_frozen(self); + return xform_image(True, self, INT2FIX(0), INT2FIX(0), width, height, ShaveImage); +} + + +/* Method: Image#shear(x_shear, y_shear) Purpose: Calls ShearImage Notes: shear angles are measured in degrees Returns: a new image */ @@ -6797,10 +6981,11 @@ { rb_raise(Class_ImageMagickError, "can't get image signature"); } res = memcmp(sigA->value, sigB->value, 64); + res = res > 0 ? 1 : (res < 0 ? -1 : 0); // reduce to 1, -1, 0 return INT2FIX(res); } /* @@ -7000,11 +7185,15 @@ Image_class_type(VALUE self) { Image *image; Data_Get_Struct(self, Image, image); +#if defined(HAVE_IMAGE_STORAGE_CLASS) + return ClassType_new(image->storage_class); +#else return ClassType_new(image->class); +#endif } /* Method: Image#class_type= Purpose: change the image's storage class @@ -7019,24 +7208,36 @@ rm_check_frozen(self); Data_Get_Struct(self, Image, image); VALUE_TO_ENUM(new_class_type, class_type, ClassType); +#if defined(HAVE_IMAGE_STORAGE_CLASS) + if (image->storage_class == PseudoClass && class_type == DirectClass) +#else if (image->class == PseudoClass && class_type == DirectClass) +#endif { SyncImage(image); magick_free(image->colormap); image->colormap = NULL; } +#if defined(HAVE_IMAGE_STORAGE_CLASS) + else if (image->storage_class == DirectClass && class_type == PseudoClass) +#else else if (image->class == DirectClass && class_type == PseudoClass) +#endif { GetQuantizeInfo(&qinfo); qinfo.number_colors = MaxRGB+1; QuantizeImage(&qinfo, image); } +#if defined(HAVE_IMAGE_STORAGE_CLASS) + image->storage_class = class_type; +#else image->class = class_type; +#endif return self; } /* Method: Image#store_pixels @@ -7072,18 +7273,20 @@ { rb_raise(rb_eRangeError, "geometry (%lux%lu%+ld%+ld) exceeds image bounds" , cols, rows, x, y); } + size = cols * rows; + rm_check_ary_len(new_pixels, size); + SetImageType(image, TrueColorType); // Get a pointer to the pixels. Replace the values with the PixelPackets // from the pixels argument. pixels = GetImagePixels(image, x, y, cols, rows); if (pixels) { - size = cols * rows; for (n = 0; n < size; n++) { new_pixel = rb_ary_entry(new_pixels, n); Data_Get_Struct(new_pixel, Pixel, pixel); pixels[n] = *pixel; @@ -7568,11 +7771,21 @@ HANDLE_ERROR return rb_str_new2(name); } -DEF_ATTR_READER(Image, total_colors, ulong) +/* + Method: Image#total_colors + Purpose: alias for Image#number_colors + Notes: This used to be a direct reference to the `total_colors' field in Image + but that field is not reliable. +*/ +VALUE +Image_total_colors(VALUE self) +{ + return Image_number_colors(self); +} /* Method: Image#transparent(color-name<, opacity>) Image#transparent(pixel<, opacity>) Purpose: Call TransparentImage @@ -7672,10 +7885,12 @@ /* Method: Image#image_type=(type) Purpose: Call SetImageType to set the type of the image Note: Can't use type & type= b/c of Object#type. + This setter is useless. Leave for backward compatibility + but don't document it. */ VALUE Image_image_type_eq(VALUE self, VALUE type) { Image *image; ImageType it; @@ -7964,22 +8179,43 @@ gravity, width, height or gravity, x, y, width, height If the 2nd or 3rd, compute new x, y values. + The argument list can have a trailing true, false, or nil argument. + If present and true, after cropping reset the page fields in the image. + Call xform_image to do the cropping. */ static VALUE cropper(int bang, int argc, VALUE *argv, VALUE self) { volatile VALUE x, y, width, height; unsigned long nx = 0, ny = 0; unsigned long columns, rows; + int reset_page = 0; GravityType gravity; MagickEnum *magick_enum; Image *image; + VALUE cropped; + // Check for a "reset page" trailing argument. + if (argc >= 1) + { + switch (TYPE(argv[argc-1])) + { + case T_TRUE: + reset_page = 1; + // fall thru + case T_FALSE: + case T_NIL: + argc -= 1; + default: + break; + } + } + switch (argc) { case 5: Data_Get_Struct(self, Image, image); @@ -8081,15 +8317,29 @@ x = ULONG2NUM(nx); y = ULONG2NUM(ny); break; default: - rb_raise(rb_eArgError, "wrong number of arguments (%d for 3, 4, or 5)", argc); + if (reset_page) + { + rb_raise(rb_eArgError, "wrong number of arguments (%d for 4, 5, or 6)", argc); + } + else + { + rb_raise(rb_eArgError, "wrong number of arguments (%d for 3, 4, or 5)", argc); + } break; } - return xform_image(bang, self, x, y, width, height, CropImage); + cropped = xform_image(bang, self, x, y, width, height, CropImage); + if (reset_page) + { + Data_Get_Struct(cropped, Image, image); + image->page.x = image->page.y = 0L; + image->page.width = image->page.height = 0UL; + } + return cropped; } /* @@ -8181,14 +8431,14 @@ } /* Static: raise_ChannelType_error - Purpose: raise ArgumentError when an non-ChannelType object + Purpose: raise TypeError when an non-ChannelType object is unexpectedly encountered */ static void raise_ChannelType_error(VALUE arg) { - rb_raise(rb_eArgError, "argument needs to be a ChannelType (%s given)" + rb_raise(rb_eTypeError, "argument needs to be a ChannelType (%s given)" , rb_class2name(CLASS_OF(arg))); }