/* $Id: rmilist.c,v 1.46.2.1 2007/03/04 00:05:34 rmagick Exp $ */ /*============================================================================\ | Copyright (C) 2007 by Timothy P. Hunter | Name: rmilist.c | Author: Tim Hunter | Purpose: ImageList class method definitions for RMagick \============================================================================*/ #include "rmagick.h" /* Method: ImageList#animate() Purpose: repeatedly display the images in the images array to an XWindow screen. The "delay" argument is the number of 1/100ths of a second (0 to 65535) to delay between images. */ VALUE ImageList_animate(int argc, VALUE *argv, VALUE self) { Image *images; Info *info; volatile VALUE info_obj; if (argc > 1) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 0 or 1)", argc); } // Convert the images array to an images sequence. images = rm_images_from_imagelist(self); if (argc == 1) { Image *img; unsigned int delay; delay = NUM2UINT(argv[0]); for (img = images; img; img = GetNextImageInList(img)) { img->delay = delay; } } // Create a new Info object to use with this call info_obj = rm_info_new(); Data_Get_Struct(info_obj, Info, info); (void) AnimateImages(info, images); rm_check_image_exception(images, RetainOnError); rm_split(images); return self; } /* Method: ImageList#append(stack) Purpose: Append all the images by calling ImageAppend Returns: an Frame object for the result */ VALUE ImageList_append(VALUE self, VALUE stack_arg) { Image *images, *new_image; unsigned int stack; ExceptionInfo exception; // Convert the image array to an image sequence. images = rm_images_from_imagelist(self); // If stack == true, stack rectangular images top-to-bottom, // otherwise left-to-right. stack = RTEST(stack_arg); GetExceptionInfo(&exception); new_image = AppendImages(images, stack, &exception); rm_split(images); rm_check_exception(&exception, new_image, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_image); return rm_image_new(new_image); } /* Method: ImageList#average Purpose: Average all images together by calling AverageImages Returns: an Frame object for the averaged image */ VALUE ImageList_average(VALUE self) { Image *images, *new_image; ExceptionInfo exception; // Convert the images array to an images sequence. images = rm_images_from_imagelist(self); GetExceptionInfo(&exception); new_image = AverageImages(images, &exception); rm_split(images); rm_check_exception(&exception, new_image, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_image); return rm_image_new(new_image); } /* Method: ImageList#coalesce Purpose: call CoalesceImages Returns: a new Image with the coalesced image sequence stored in the images array Notes: respects the delay, matte, and start_loop fields in each image. */ VALUE ImageList_coalesce(VALUE self) { Image *images, *new_images; ExceptionInfo exception; // Convert the image array to an image sequence. images = rm_images_from_imagelist(self); GetExceptionInfo(&exception); new_images = CoalesceImages(images, &exception); rm_split(images); rm_check_exception(&exception, new_images, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_images); return rm_imagelist_from_images(new_images); } /* Method: ImageList#deconstruct Purpose: compares each image with the next in a sequence and returns the maximum bounding region of any pixel differences it discovers. Returns: a new imagelist */ VALUE ImageList_deconstruct(VALUE self) { Image *new_images, *images; ExceptionInfo exception; images = rm_images_from_imagelist(self); GetExceptionInfo(&exception); new_images = DeconstructImages(images, &exception); rm_split(images); rm_check_exception(&exception, new_images, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_images); return rm_imagelist_from_images(new_images); } /* Method: ImageList#display Purpose: Display all the images to an X window screen. */ VALUE ImageList_display(VALUE self) { Image *images; Info *info; volatile VALUE info_obj; // Create a new Info object to use with this call info_obj = rm_info_new(); Data_Get_Struct(info_obj, Info, info); // Convert the images array to an images sequence. images = rm_images_from_imagelist(self); (void) DisplayImages(info, images); rm_split(images); rm_check_image_exception(images, RetainOnError); return self; } /* Method: ImageList#flatten_images Purpose: merge all the images into a single image Returns: the new image Notes: Can't use "flatten" because that's an Array method */ VALUE ImageList_flatten_images(VALUE self) { Image *images, *new_image; ExceptionInfo exception; images = rm_images_from_imagelist(self); GetExceptionInfo(&exception); new_image = FlattenImages(images, &exception); rm_split(images); rm_check_exception(&exception, new_image, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_image); return rm_image_new(new_image); } /* Method: ImageList#fx(expression[, channel...]) */ VALUE ImageList_fx(int argc, VALUE *argv, VALUE self) { #if defined(HAVE_FXIMAGECHANNEL) Image *images, *new_image; char *expression; ChannelType channels; ExceptionInfo exception; channels = extract_channels(&argc, argv); // There must be exactly 1 remaining argument. if (argc == 0) { rb_raise(rb_eArgError, "wrong number of arguments (0 for 1 or more)"); } else if (argc > 1) { raise_ChannelType_error(argv[argc-1]); } expression = STRING_PTR(argv[0]); images = rm_images_from_imagelist(self); GetExceptionInfo(&exception); new_image = FxImageChannel(images, channels, expression, &exception); rm_split(images); rm_check_exception(&exception, new_image, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_image); return rm_image_new(new_image); #else rm_not_implemented(); return (VALUE)0; #endif } /* Method: ImageList#map(reference, dither=false) Purpose: Call MapImages Returns: a new ImageList with mapped images. @scene is set to self.scene */ VALUE ImageList_map(int argc, VALUE *argv, VALUE self) { Image *images, *new_images = NULL; Image *map; unsigned int dither = False; volatile VALUE scene, new_imagelist; ExceptionInfo exception; switch (argc) { case 2: dither = RTEST(argv[1]); case 1: Data_Get_Struct(ImageList_cur_image(argv[0]), Image, map); break; default: rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc); break; } if (rm_imagelist_length(self) == 0) { rb_raise(rb_eArgError, "no images in this image list"); } // Convert image array to image sequence, clone image sequence. GetExceptionInfo(&exception); images = rm_images_from_imagelist(self); new_images = CloneImageList(images, &exception); rm_split(images); rm_check_exception(&exception, new_images, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_images); // Call ImageMagick (void) MapImages(new_images, map, dither); rm_check_image_exception(new_images, DestroyOnError); // Set @scene in new ImageList object to same value as in self. new_imagelist = rm_imagelist_from_images(new_images); scene = rb_iv_get(self, "@scene"); (void) rm_imagelist_scene_eq(new_imagelist, scene); return new_imagelist; } /* Method: ImageList#montage <{parm block}> Purpose: Call MontageImages Notes: Creates Montage object, yields to block if present in Montage object's scope. */ VALUE ImageList_montage(VALUE self) { volatile VALUE montage_obj; Montage *montage; Image *new_images, *images; ExceptionInfo exception; // Create a new instance of the Magick::Montage class montage_obj = rm_montage_new(); if (rb_block_given_p()) { // Run the block in the instance's context, allowing the app to modify the // object's attributes. (void) rb_obj_instance_eval(0, NULL, montage_obj); } Data_Get_Struct(montage_obj, Montage, montage); images = rm_images_from_imagelist(self); // If app specified a non-default composition operator, use it for all images. if (montage->compose != UndefinedCompositeOp) { Image *i; for (i = images; i; i = GetNextImageInList(i)) { i->compose = montage->compose; } } GetExceptionInfo(&exception); // MontageImage can return more than one image. new_images = MontageImages(images, montage->info, &exception); rm_split(images); rm_check_exception(&exception, new_images, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_images); return rm_imagelist_from_images(new_images); } /* Method: ImageList#morph(number_images) Purpose: requires a minimum of two images. The first image is transformed into the second by a number of intervening images as specified by "number_images". Returns: a new Image with the images array set to the morph sequence. @scenes = 0 */ VALUE ImageList_morph(VALUE self, VALUE nimages) { Image *images, *new_images; ExceptionInfo exception; long number_images; if (rm_imagelist_length(self) < 1) { rb_raise(rb_eArgError, "no images in this image list"); } // Use a signed long so we can test for a negative argument. number_images = NUM2LONG(nimages); if (number_images <= 0) { rb_raise(rb_eArgError, "number of intervening images must be > 0"); } GetExceptionInfo(&exception); images = rm_images_from_imagelist(self); new_images = MorphImages(images, (unsigned long)number_images, &exception); rm_split(images); rm_check_exception(&exception, new_images, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_images); return rm_imagelist_from_images(new_images); } /* Method: ImageList#mosaic Purpose: merge all the images into a single image Returns: the new image */ VALUE ImageList_mosaic(VALUE self) { Image *images, *new_image; ExceptionInfo exception; GetExceptionInfo(&exception); images = rm_images_from_imagelist(self); new_image = MosaicImages(images, &exception); rm_split(images); rm_check_exception(&exception, new_image, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_image); return rm_image_new(new_image); } /* Method: ImageList#optimize_layers Purpose: Equivalent to -layers option in 6.2.6 Returns: a new imagelist */ VALUE ImageList_optimize_layers(VALUE self, VALUE method) { #if defined(HAVE_COMPAREIMAGELAYERS) Image *images, *new_images; MagickLayerMethod mthd; ExceptionInfo exception; GetExceptionInfo(&exception); VALUE_TO_ENUM(method, mthd, MagickLayerMethod); images = rm_images_from_imagelist(self); switch (mthd) { #if defined(HAVE_COALESCELAYER) case CoalesceLayer: new_images = CoalesceImages(images, &exception); break; case DisposeLayer: new_images = DisposeImages(images, &exception); break; #endif case OptimizeLayer: new_images = OptimizeImageLayers(images, &exception); break; case OptimizePlusLayer: new_images = OptimizePlusImageLayers(images, &exception); break; case CompareAnyLayer: case CompareClearLayer: case CompareOverlayLayer: new_images = CompareImageLayers(images, mthd, &exception); break; default: rb_raise(rb_eArgError, "undefined layer method"); break; } rm_split(images); rm_check_exception(&exception, new_images, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_images); return rm_imagelist_from_images(new_images); #else rm_not_implemented(); return (VALUE)0; #endif } /* External: rm_imagelist_new Purpose: create a new ImageList object with no images Notes: this simply calls ImageList.new() in RMagick.rb */ VALUE rm_imagelist_new() { return rb_funcall(Class_ImageList, rm_ID_new, 0); } /* Extern: rm_imagelist_from_images Purpose: Construct a new imagelist object from a list of images Notes: Sets @scene to 0. */ VALUE rm_imagelist_from_images(Image *images) { volatile VALUE new_imagelist; Image *image; if (!images) { rb_bug("rm_imagelist_from_images called with NULL argument"); } new_imagelist = rm_imagelist_new(); while (images) { image = RemoveFirstImageFromList(&images); rm_imagelist_push(new_imagelist, rm_image_new(image)); } (void) rb_iv_set(new_imagelist, "@scene", INT2FIX(0)); return new_imagelist; } /* Extern: rm_images_from_imagelist Purpose: Convert an array of Image *s to an ImageMagick scene sequence (i.e. a doubly-linked list of Images) Returns: a pointer to the head of the scene sequence list */ Image * rm_images_from_imagelist(VALUE imagelist) { long x, len; Image *head = NULL; Check_Type(imagelist, T_ARRAY); len = rm_imagelist_length(imagelist); if (len == 0) { rb_raise(rb_eArgError, "no images in this image list"); } for (x = 0; x < len; x++) { Image *image; Data_Get_Struct(rb_ary_entry(imagelist, x), Image, image); AppendImageToList(&head, image); } return head; } /* * Extern: rm_imagelist_scene_eq(imagelist, scene) * Purpose: @scene attribute writer */ VALUE rm_imagelist_scene_eq(VALUE imagelist, VALUE scene) { rm_check_frozen(imagelist); (void) rb_iv_set(imagelist, "@scene", scene); return scene; } /* External: rm_imagelist_length Purpose: return the # of images in an imagelist */ int rm_imagelist_length(VALUE imagelist) { volatile VALUE len; len = rb_funcall(imagelist, rm_ID_length, 0); return FIX2INT(len); } /* External: rm_imagelist_push Purpose: push an image onto the end of the imagelist */ void rm_imagelist_push(VALUE imagelist, VALUE image) { rm_check_frozen(imagelist); (void) rb_funcall(imagelist, rm_ID_push, 1, image); } /* Method: ImageList#quantize(>>>>) defaults: 256, Magick::RGBColorspace, true, 0, false Purpose: call QuantizeImages Returns: a new ImageList with quantized images. 'scene' is set to the same value as self.scene */ VALUE ImageList_quantize(int argc, VALUE *argv, VALUE self) { Image *images, *new_images; Image *new_image; QuantizeInfo quantize_info; ExceptionInfo exception; volatile VALUE new_imagelist, scene; GetQuantizeInfo(&quantize_info); switch (argc) { case 5: quantize_info.measure_error = (MagickBooleanType) RTEST(argv[4]); case 4: quantize_info.tree_depth = (unsigned long)NUM2INT(argv[3]); case 3: quantize_info.dither = (MagickBooleanType) RTEST(argv[2]); case 2: VALUE_TO_ENUM(argv[1], quantize_info.colorspace, ColorspaceType); case 1: quantize_info.number_colors = NUM2ULONG(argv[0]); case 0: break; default: rb_raise(rb_eArgError, "wrong number of arguments (%d for 0 to 5)", argc); break; } if (rm_imagelist_length(self) == 0) { rb_raise(rb_eArgError, "no images in this image list"); } // Convert image array to image sequence, clone image sequence. GetExceptionInfo(&exception); images = rm_images_from_imagelist(self); new_images = CloneImageList(images, &exception); rm_split(images); rm_check_exception(&exception, new_images, DestroyOnError); (void) DestroyExceptionInfo(&exception); rm_ensure_result(new_images); (void) QuantizeImages(&quantize_info, new_images); rm_check_exception(&exception, new_images, DestroyOnError); // Create new ImageList object, convert mapped image sequence to images, // append to images array. new_imagelist = rm_imagelist_new(); while ((new_image = ShiftImageList(&new_images))) { rm_imagelist_push(new_imagelist, rm_image_new(new_image)); } // Set @scene in new ImageList object to same value as in self. scene = rb_iv_get(self, "@scene"); (void) rb_iv_set(new_imagelist, "@scene", scene); return new_imagelist; } /* Method: ImageList#to_blob Purpose: returns the imagelist as a blob (a String) Notes: runs an info parm block if present - the user can specify the image format and depth */ VALUE ImageList_to_blob(VALUE self) { Image *images; Info *info; volatile VALUE info_obj; volatile VALUE blob_str; void *blob = NULL; size_t length = 0; ExceptionInfo exception; info_obj = rm_info_new(); Data_Get_Struct(info_obj, Info, info); // Convert the images array to an images sequence. images = rm_images_from_imagelist(self); GetExceptionInfo(&exception); (void) SetImageInfo(info, MagickTrue, &exception); rm_check_exception(&exception, images, RetainOnError); if (*info->magick != '\0') { Image *img; for (img = images; img; img = GetNextImageInList(img)) { strncpy(img->magick, info->magick, sizeof(info->magick)-1); } } // Unconditionally request multi-images support. The worst that // can happen is that there's only one image or the format // doesn't support multi-image files. info->adjoin = MagickTrue; #if defined(HAVE_IMAGESTOBLOB) blob = ImagesToBlob(info, images, &length, &exception); #else blob = ImageToBlob(info, images, &length, &exception); #endif if (exception.severity != UndefinedException) { magick_free((void*)blob); } rm_split(images); CHECK_EXCEPTION() (void) DestroyExceptionInfo(&exception); if (length == 0 || !blob) { return Qnil; } blob_str = rb_str_new(blob, (long)length); magick_free((void*)blob); return blob_str; } /* * Static: file_arg_rescue * Purpose: called when `arg_to_str' raised an exception */ static VALUE file_arg_rescue(VALUE arg) { rb_raise(rb_eTypeError, "argument must be path name or open file (%s given)", rb_class2name(CLASS_OF(arg))); } /* Method: ImageList#write(file) Purpose: Write all the images to the specified file. If the file format supports multi-image files, and the @images array contains more than one image, then the images will be written as a single multi-image file. Otherwise each image will be written to a separate file. Returns self. */ VALUE ImageList_write(VALUE self, VALUE file) { Image *images, *img; Info *info; const MagickInfo *m; volatile VALUE info_obj; char *filename; long filenameL; unsigned long scene; ExceptionInfo exception; info_obj = rm_info_new(); Data_Get_Struct(info_obj, Info, info); if (TYPE(file) == T_FILE) { OpenFile *fptr; // Ensure file is open - raise error if not GetOpenFile(file, fptr); SetImageInfoFile(info, GetReadFile(fptr)); } else { // Convert arg to string. Catch exceptions. file = rb_rescue(rb_String, file, file_arg_rescue, file); // Copy the filename to the Info and to the Image. filename = STRING_PTR_LEN(file, filenameL); filenameL = min(filenameL, MaxTextExtent-1); memcpy(info->filename, filename, (size_t)filenameL); info->filename[filenameL] = '\0'; SetImageInfoFile(info, NULL); } // Convert the images array to an images sequence. images = rm_images_from_imagelist(self); // Copy the filename into each images. Set a scene number to be used if // writing multiple files. (Ref: ImageMagick's utilities/convert.c for (scene = 0, img = images; img; img = GetNextImageInList(img)) { img->scene = scene++; strcpy(img->filename, info->filename); } GetExceptionInfo(&exception); (void) SetImageInfo(info, MagickTrue, &exception); rm_check_exception(&exception, images, RetainOnError); (void) DestroyExceptionInfo(&exception); // Find out if the format supports multi-images files. GetExceptionInfo(&exception); m = GetMagickInfo(info->magick, &exception); rm_check_exception(&exception, images, RetainOnError); (void) DestroyExceptionInfo(&exception); // Tell WriteImage if we want a multi-images file. if (rm_imagelist_length(self) > 1 && m->adjoin) { info->adjoin = MagickTrue; } for (img = images; img; img = GetNextImageInList(img)) { (void) WriteImage(info, img); // images will be split before raising an exception rm_check_image_exception(images, RetainOnError); if (info->adjoin) { break; } } rm_split(images); return self; }