/**************************************************************************//** * Info class method definitions for RMagick. * * Copyright © 2002 - 2009 by Timothy P. Hunter * * Changes since Nov. 2009 copyright © by Benjamin Thomas and Omer Bar-or * * @file rminfo.c * @version $Id: rminfo.c,v 1.79 2009/12/20 02:33:33 baror Exp $ * @author Tim Hunter ******************************************************************************/ #include "rmagick.h" static void Info_free(void *infoptr); static size_t Info_memsize(const void *infoptr); const rb_data_type_t rm_info_data_type = { "Magick::Image::Info", { NULL, Info_free, Info_memsize, }, 0, 0, RUBY_TYPED_FROZEN_SHAREABLE, }; /** * Return the value of the specified option. * * No Ruby usage (internal function) * * @param self this object * @param key the option key * @return the value of key */ static VALUE get_option(VALUE self, const char *key) { Info *info; const char *value; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); value = GetImageOption(info, key); if (value) { return rb_str_new2(value); } return Qnil; } /** * Set the specified option to this value. If the value is nil just unset any * current value. * * No Ruby usage (internal function) * * @param self this object * @param key the option key * @param string the value * @return string */ static VALUE set_option(VALUE self, const char *key, VALUE string) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(string)) { DeleteImageOption(info, key); } else { char *value; value = StringValueCStr(string); SetImageOption(info, key, value); } return string; } /** * Set a color name as the value of the specified option * * No Ruby usage (internal function) * * Notes: * - Call QueryColorCompliance to validate color name. * * @param self this object * @param option the option * @param color the color name * @return color */ static VALUE set_color_option(VALUE self, const char *option, VALUE color) { Info *info; PixelColor pp; MagickBooleanType okay; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(color)) { DeleteImageOption(info, option); } else { char *name; ExceptionInfo *exception; name = StringValueCStr(color); exception = AcquireExceptionInfo(); okay = QueryColorCompliance(name, AllCompliance, &pp, exception); DestroyExceptionInfo(exception); if (!okay) { rb_raise(rb_eArgError, "invalid color name `%s'", name); } SetImageOption(info, option, name); } return color; } /** * Get an Image::Info option floating-point value. * * No Ruby usage (internal function) * * Notes: * - Convert the string value to a float * * @param self this object * @param option the option name * @return the Image::Info option */ static VALUE get_dbl_option(VALUE self, const char *option) { Info *info; const char *value; double d; long n; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); value = GetImageOption(info, option); if (!value) { return Qnil; } d = atof(value); n = (long) floor(d); return d == (double)n ? LONG2NUM(n) : rb_float_new(d); } /** * Set an Image::Info option to a floating-point value. * * No Ruby usage (internal function) * * Notes: * - SetImageOption expects the value to be a string. * * @param self this object * @param option the option name * @param value the value * @return value */ static VALUE set_dbl_option(VALUE self, const char *option, VALUE value) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(value)) { DeleteImageOption(info, option); } else { char buff[50]; double d; int len; long n; d = NUM2DBL(value); n = floor(d); if (d == n) { len = snprintf(buff, sizeof(buff), "%-10ld", n); } else { len = snprintf(buff, sizeof(buff), "%-10.2f", d); } memset(buff+len, '\0', sizeof(buff)-len); SetImageOption(info, option, buff); } return value; } /** * Get antialias value * * @return [Boolean] true if antialias is enabled */ VALUE Info_antialias(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, antialias, boolean, &rm_info_data_type); } /** * Set antialias value * * @param val [Boolean] true or false * @return [Boolean] the given value */ VALUE Info_antialias_eq(VALUE self, VALUE val) { IMPLEMENT_TYPED_ATTR_WRITER(Info, antialias, boolean, &rm_info_data_type); } /** Maximum length of a format (@see Info_aref) */ #define MAX_FORMAT_LEN 60 /** * Get the value of the specified option for the specified format. * * - The 2 argument form is the original form. Added support for a single * argument after ImageMagick started using Set/GetImageOption for options * that aren't represented by fields in the ImageInfo structure. * * @overload [](format, key) * @param format [String] An image format name such as "ps" or "tiff". * @param key [String] A string that identifies the option. * * @overload [](key) * @param key [String] A string that identifies the option. * * @return [String] The value of the option. */ VALUE Info_aref(int argc, VALUE *argv, VALUE self) { Info *info; char *format_p, *key_p; long format_l, key_l; const char *value; char fkey[MaxTextExtent]; switch (argc) { case 2: format_p = rm_str2cstr(argv[0], &format_l); key_p = rm_str2cstr(argv[1], &key_l); if (format_l > MAX_FORMAT_LEN || format_l + key_l > MaxTextExtent-1) { rb_raise(rb_eArgError, "can't reference %.60s:%.1024s - too long", format_p, key_p); } snprintf(fkey, sizeof(fkey), "%.60s:%.*s", format_p, (int)(MaxTextExtent-61), key_p); break; case 1: strlcpy(fkey, StringValueCStr(argv[0]), sizeof(fkey)); break; default: rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc); break; } TypedData_Get_Struct(self, Info, &rm_info_data_type, info); value = GetImageOption(info, fkey); if (!value) { return Qnil; } return rb_str_new2(value); } /** * Define an option. An alternative to {Info#define}. * Use this method to set options for reading or writing certain image formats. * * - Essentially the same function as {Info#define} but paired with {Info#[]} * - If the value is nil it is equivalent to {Info#undefine}. * * @overload []=(format, key) * @param format [String] An image format name such as "ps" or "tiff". * @param key [String] A string that identifies the option. * * @overload []=(key) * @param key [String] A string that identifies the option. * * @return [Magick::Image::Info] self * @see #[] * @see #define * @see #undefine */ VALUE Info_aset(int argc, VALUE *argv, VALUE self) { Info *info; VALUE value; char *format_p, *key_p, *value_p = NULL; long format_l, key_l; char ckey[MaxTextExtent]; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); switch (argc) { case 3: format_p = rm_str2cstr(argv[0], &format_l); key_p = rm_str2cstr(argv[1], &key_l); if (format_l > MAX_FORMAT_LEN || format_l+key_l > MaxTextExtent-1) { rb_raise(rb_eArgError, "%.60s:%.1024s not defined - too long", format_p, key_p); } snprintf(ckey, sizeof(ckey), "%.60s:%.*s", format_p, (int)(sizeof(ckey)-MAX_FORMAT_LEN), key_p); value = argv[2]; break; case 2: strlcpy(ckey, StringValueCStr(argv[0]), sizeof(ckey)); value = argv[1]; break; default: rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or 3)", argc); break; } if (NIL_P(value)) { DeleteImageOption(info, ckey); } else { unsigned int okay; /* Allow any argument that supports to_s */ value = rb_String(value); value_p = StringValueCStr(value); okay = SetImageOption(info, ckey, value_p); if (!okay) { rb_warn("`%s' not defined - SetImageOption failed.", ckey); return Qnil; } } RB_GC_GUARD(value); return self; } /** * Get the attenuate value. * * @return [Float] the attenuate */ VALUE Info_attenuate(VALUE self) { return get_dbl_option(self, "attenuate"); } /** * Set the attenuate value. * * @param value [Float] the attenuate * @return [Float] the attenuate */ VALUE Info_attenuate_eq(VALUE self, VALUE value) { return set_dbl_option(self, "attenuate", value); } /** * Get the authenticate value. * * @return [String] the authenticate */ VALUE Info_authenticate(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); #if defined(IMAGEMAGICK_7) return C_str_to_R_str(GetImageOption(info, "authenticate")); #else return C_str_to_R_str(info->authenticate); #endif } /** * Set the authenticate value. * * @param passwd_arg [String] the authenticating password * @return [String] the given value */ VALUE Info_authenticate_eq(VALUE self, VALUE passwd_arg) { Info *info; char *passwd = NULL; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (!NIL_P(passwd_arg)) { passwd = StringValueCStr(passwd_arg); } #if defined(IMAGEMAGICK_7) if (passwd) { SetImageOption(info, "authenticate", passwd); } else { RemoveImageOption(info, "authenticate"); } #else if (info->authenticate) { magick_free(info->authenticate); info->authenticate = NULL; } if (passwd) { magick_clone_string(&info->authenticate, passwd); } #endif return passwd_arg; } /** * Return the name of the background color as a String * * @return [String] the name of the background color * @see Image#background_color */ VALUE Info_background_color(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return rm_pixelcolor_to_color_name_info(info, &info->background_color); } /** * Set the background color. * * @param bc_arg [Magick::Pixel, String] the background color * @return [Magick::Pixel, String] the given color */ VALUE Info_background_color_eq(VALUE self, VALUE bc_arg) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); Color_to_PixelColor(&info->background_color, bc_arg); return bc_arg; } /** * Return the name of the border color as a String. * * @return [String] the border color name * @see Image#border_color */ VALUE Info_border_color(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return rm_pixelcolor_to_color_name_info(info, &info->border_color); } /** * set the border color * * @param bc_arg [Magick::Pixel, String] the border color * @return [Magick::Pixel, String] the given color */ VALUE Info_border_color_eq(VALUE self, VALUE bc_arg) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); Color_to_PixelColor(&info->border_color, bc_arg); return bc_arg; } /** * Get a caption of image * * @return [String] the caption */ VALUE Info_caption(VALUE self) { return get_option(self, "caption"); } /** * Assigns a caption to an image. * * @param caption [String] the caption * @return [String] the given value */ VALUE Info_caption_eq(VALUE self, VALUE caption) { return set_option(self, "caption", caption); } /** * Set the channels * * @overload channel(channel = Magick::AllChannels) * @param channel [Magick::ChannelType] the channel * * @overload channel(*channels) * @param channels [Magick::ChannelType] the multiple arguments of channel * * @return [Magick::Image::Info] self */ VALUE Info_channel(int argc, VALUE *argv, VALUE self) { Info *info; ChannelType channels; channels = extract_channels(&argc, argv); // Ensure all arguments consumed. if (argc > 0) { raise_ChannelType_error(argv[argc-1]); } TypedData_Get_Struct(self, Info, &rm_info_data_type, info); info->channel = channels; return self; } /** * Get the colorspace type. * * @return [Magick::ColorspaceType] the colorspace type */ VALUE Info_colorspace(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return ColorspaceType_find(info->colorspace); } /** * Set the colorspace type * * @param colorspace [Magick::ColorspaceType] the colorspace type * @return [Magick::ColorspaceType] the given colorspace */ VALUE Info_colorspace_eq(VALUE self, VALUE colorspace) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); VALUE_TO_ENUM(colorspace, info->colorspace, ColorspaceType); return colorspace; } /** * Get the comment. * * @return [String] the comment */ VALUE Info_comment(VALUE self) { return get_option(self, "Comment"); } /** * Set the comment * * @param string [String] the comment * @return [String] the given comment */ VALUE Info_comment_eq(VALUE self, VALUE string) { return set_option(self, "Comment", string); } /** * Get the compression type. * * @return [Magick::CompressionType] the compression type */ VALUE Info_compression(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return CompressionType_find(info->compression); } /** * Set the compression type * * @param type [Magick::CompressionType] the compression type * @return [Magick::CompressionType] the given type */ VALUE Info_compression_eq(VALUE self, VALUE type) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); VALUE_TO_ENUM(type, info->compression, CompressionType); return type; } /** * Define an option. * * @overload Info#define(format, key, value = "") * @param format [String] An image format name such as "ps" or "tiff". * @param key [String] A string that identifies the option. * @param value [String] A value of option * * @return [Magick::Image::Info] self */ VALUE Info_define(int argc, VALUE *argv, VALUE self) { Info *info; char *format, *key; const char *value = ""; long format_l, key_l; char ckey[100]; unsigned int okay; VALUE fmt_arg; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); switch (argc) { case 3: /* Allow any argument that supports to_s */ fmt_arg = rb_String(argv[2]); value = (const char *)StringValueCStr(fmt_arg); case 2: key = rm_str2cstr(argv[1], &key_l); format = rm_str2cstr(argv[0], &format_l); break; default: rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or 3)", argc); } if (2 + format_l + key_l > (long)sizeof(ckey)) { rb_raise(rb_eArgError, "%.20s:%.20s not defined - format or key too long", format, key); } snprintf(ckey, sizeof(ckey), "%s:%s", format, key); DeleteImageOption(info, ckey); okay = SetImageOption(info, ckey, value); if (!okay) { rb_warn("%.20s=\"%.78s\" not defined - SetImageOption failed.", ckey, value); return Qnil; } RB_GC_GUARD(fmt_arg); return self; } /** * Get the delay value. * * @return [Numeric, nil] the delay */ VALUE Info_delay(VALUE self) { Info *info; const char *delay; char *p; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); delay = GetImageOption(info, "delay"); if (delay) { long d; d = strtol(delay, &p, 10); if (*p != '\0') { rb_raise(rb_eRangeError, "failed to convert %s to Numeric", delay); } return LONG2NUM(d); } return Qnil; } /** * Will raise an exception if `arg' can't be converted to an int. * * No Ruby usage (internal function) * * @param arg the argument * @return arg */ static VALUE arg_is_integer(VALUE arg) { return INT2NUM(NUM2INT(arg)); } /** * Set the delay value. * * @param string [String] the delay * @return [String] the given value */ VALUE Info_delay_eq(VALUE self, VALUE string) { Info *info; int not_num; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(string)) { DeleteImageOption(info, "delay"); } else { char dstr[20]; int delay; not_num = 0; rb_protect(arg_is_integer, string, ¬_num); if (not_num) { rb_raise(rb_eTypeError, "failed to convert %s into Integer", rb_class2name(CLASS_OF(string))); } delay = NUM2INT(string); snprintf(dstr, sizeof(dstr), "%d", delay); SetImageOption(info, "delay", dstr); } return string; } /** * Get the density value * * @return [String] the density */ VALUE Info_density(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, density, str, &rm_info_data_type); } /** * Set the text rendering density geometry * * @param density_arg [String] the density * @return [String] the given value * @see https://www.imagemagick.org/Magick++/Geometry.html */ VALUE Info_density_eq(VALUE self, VALUE density_arg) { Info *info; VALUE density; char *dens; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(density_arg)) { magick_free(info->density); info->density = NULL; return self; } density = rb_String(density_arg); dens = StringValueCStr(density); if (!IsGeometry(dens)) { rb_raise(rb_eArgError, "invalid density geometry: %s", dens); } magick_clone_string(&info->density, dens); RB_GC_GUARD(density); return density_arg; } /** * Get the depth value * * @return [Numeric] the depth */ VALUE Info_depth(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, depth, int, &rm_info_data_type); } /** * Set the depth (8, 16, 32, 64). * * @param depth [Numeric] the depth * @return [Numeric] the given depth */ VALUE Info_depth_eq(VALUE self, VALUE depth) { Info *info; unsigned long d; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); d = NUM2ULONG(depth); switch (d) { case 8: // always okay #if MAGICKCORE_QUANTUM_DEPTH == 16 || MAGICKCORE_QUANTUM_DEPTH == 32 || MAGICKCORE_QUANTUM_DEPTH == 64 case 16: #if MAGICKCORE_QUANTUM_DEPTH == 32 || MAGICKCORE_QUANTUM_DEPTH == 64 case 32: #if MAGICKCORE_QUANTUM_DEPTH == 64 case 64: #endif #endif #endif break; default: rb_raise(rb_eArgError, "invalid depth (%lu)", d); break; } info->depth = d; return depth; } /** A dispose option */ static struct { const char *string; /**< the argument given by the user */ const char *enum_name; /**< the enumerator name */ DisposeType enumerator; /**< the enumerator itself */ } Dispose_Option[] = { { "Background", "BackgroundDispose", BackgroundDispose}, { "None", "NoneDispose", NoneDispose}, { "Previous", "PreviousDispose", PreviousDispose}, { "Undefined", "UndefinedDispose", UndefinedDispose}, { "0", "UndefinedDispose", UndefinedDispose}, { "1", "NoneDispose", NoneDispose}, { "2", "BackgroundDispose", BackgroundDispose}, { "3", "PreviousDispose", PreviousDispose}, }; /** Number of dispose options */ #define N_DISPOSE_OPTIONS (int)(sizeof(Dispose_Option)/sizeof(Dispose_Option[0])) /** * Retrieve a dispose option string and convert it to a DisposeType enumerator. * * No Ruby usage (internal function) * * @param name the dispose string * @return the DisposeType enumerator */ DisposeType rm_dispose_to_enum(const char *name) { DisposeType dispose = UndefinedDispose; int x; for (x = 0; x < N_DISPOSE_OPTIONS; x++) { if (strcmp(Dispose_Option[x].string, name) == 0) { dispose = Dispose_Option[x].enumerator; break; } } return dispose; } /** * Retrieve the dispose option string and convert it to a DisposeType * enumerator. * * @return [Magick::DisposeType] a DisposeType enumerator */ VALUE Info_dispose(VALUE self) { Info *info; ID dispose_id; const char *dispose; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); dispose_id = rb_intern("UndefinedDispose"); // Map the dispose option string to a DisposeType enumerator. dispose = GetImageOption(info, "dispose"); if (dispose) { for (int x = 0; x < N_DISPOSE_OPTIONS; x++) { if (strcmp(dispose, Dispose_Option[x].string) == 0) { dispose_id = rb_intern(Dispose_Option[x].enum_name); break; } } } return rb_const_get(Module_Magick, dispose_id); } /** * Convert a DisposeType enumerator into the equivalent dispose option string. * * @param disp [Magic::DisposeType] the DisposeType enumerator * @return [Magic::DisposeType] the given value */ VALUE Info_dispose_eq(VALUE self, VALUE disp) { Info *info; DisposeType dispose; const char *option; int x; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(disp)) { DeleteImageOption(info, "dispose"); return self; } VALUE_TO_ENUM(disp, dispose, DisposeType); option = "Undefined"; for (x = 0; x < N_DISPOSE_OPTIONS; x++) { if (dispose == Dispose_Option[x].enumerator) { option = Dispose_Option[x].string; break; } } SetImageOption(info, "dispose", option); return disp; } /** * Get dither value * * @return [Boolean] true if dither is enabled */ VALUE Info_dither(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, dither, boolean, &rm_info_data_type); } /** * Set dither value * * @param val [Boolean] true if dither will be enabled * @return [Boolean] true if dither is enabled */ VALUE Info_dither_eq(VALUE self, VALUE val) { IMPLEMENT_TYPED_ATTR_WRITER(Info, dither, boolean, &rm_info_data_type); } /** * Get the endian value. * * @return [Magick::EndianType] the endian */ VALUE Info_endian(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return EndianType_find(info->endian); } /** * Set the endian value. * * @param endian [Magick::EndianType] the endian * @return [Magick::EndianType] the given endian */ VALUE Info_endian_eq(VALUE self, VALUE endian) { Info *info; EndianType type = UndefinedEndian; if (endian != Qnil) { VALUE_TO_ENUM(endian, type, EndianType); } TypedData_Get_Struct(self, Info, &rm_info_data_type, info); info->endian = type; return endian; } /** * Get the extract geometry, e.g. "200x200+100+100" * * @return [String] the extract string * @see https://www.imagemagick.org/Magick++/Geometry.html */ VALUE Info_extract(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, extract, str, &rm_info_data_type); } /** * Set the extract geometry. * * @param extract_arg [String] the extract string * @return [String] the given value * @see https://www.imagemagick.org/Magick++/Geometry.html */ VALUE Info_extract_eq(VALUE self, VALUE extract_arg) { Info *info; char *extr; VALUE extract; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(extract_arg)) { magick_free(info->extract); info->extract = NULL; return self; } extract = rb_String(extract_arg); extr = StringValueCStr(extract); if (!IsGeometry(extr)) { rb_raise(rb_eArgError, "invalid extract geometry: %s", extr); } magick_clone_string(&info->extract, extr); RB_GC_GUARD(extract); return extract_arg; } /** * Get the "filename" value. * * @return [String] the file name ("" if filename not set) * @note Only used for Image#capture * @see Image#capture */ VALUE Info_filename(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return rb_str_new2(info->filename); } /** * Set the "filename" value. * * @param filename [String] the file name * @return [String] the given file name * @note Only used for Image#capture * @see Image#capture */ VALUE Info_filename_eq(VALUE self, VALUE filename) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); // Allow "nil" - remove current filename if (NIL_P(filename) || StringValueCStr(filename) == NULL) { info->filename[0] = '\0'; } else { char *fname; // Otherwise copy in filename fname = StringValueCStr(filename); strlcpy(info->filename, fname, sizeof(info->filename)); } return filename; } /** * Return the fill color as a String. * * @return [String] the fill color */ VALUE Info_fill(VALUE self) { return get_option(self, "fill"); } /** * Set the fill color * * @param color [String] the fill color * @return [String] the given value */ VALUE Info_fill_eq(VALUE self, VALUE color) { return set_color_option(self, "fill", color); } /** * Get the text font. * * @return [String] the font */ VALUE Info_font(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, font, str, &rm_info_data_type); } /** * Set the text font. * * @param font_arg [String] the font * @return [String] the given font */ VALUE Info_font_eq(VALUE self, VALUE font_arg) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(font_arg) || StringValueCStr(font_arg) == NULL) { magick_free(info->font); info->font = NULL; } else { char *font; font = StringValueCStr(font_arg); magick_clone_string(&info->font, font); } return font_arg; } /** * Return the image encoding format. * * @return [String, nil] the encoding format */ VALUE Info_format(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (*info->magick) { const MagickInfo *magick_info; ExceptionInfo *exception; exception = AcquireExceptionInfo(); magick_info = GetMagickInfo(info->magick, exception); DestroyExceptionInfo(exception); return magick_info ? rb_str_new2(magick_info->name) : Qnil; } return Qnil; } /** * Set the image encoding format. * * @param magick [String] the encoding format * @return [String] the given format */ VALUE Info_format_eq(VALUE self, VALUE magick) { Info *info; const MagickInfo *m; char *mgk; ExceptionInfo *exception; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); mgk = StringValueCStr(magick); exception = AcquireExceptionInfo(); m = GetMagickInfo(mgk, exception); CHECK_EXCEPTION(); DestroyExceptionInfo(exception); if (!m) { rb_raise(rb_eArgError, "unknown format: %s", mgk); } strlcpy(info->magick, m->name, sizeof(info->magick)); return magick; } /** * Get the fuzz. * * @return [Float] the fuzz * @see Image#fuzz */ VALUE Info_fuzz(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, fuzz, dbl, &rm_info_data_type); } /** * Set the fuzz. * * @param fuzz [Float, String] the fuzz with Float or * percent format "xx%" with String * @return [Float, String] the given value * @see Image#fuzz= */ VALUE Info_fuzz_eq(VALUE self, VALUE fuzz) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); info->fuzz = rm_fuzz_to_dbl(fuzz); return fuzz; } /** A gravity option */ static struct { const char *string; /**< the argument given by the user */ const char *enum_name; /**< the enumerator name */ GravityType enumerator; /**< the enumerator itself */ } Gravity_Option[] = { { "Undefined", "UndefinedGravity", UndefinedGravity}, { "None", "UndefinedGravity", UndefinedGravity}, { "Center", "CenterGravity", CenterGravity}, { "East", "EastGravity", EastGravity}, { "Forget", "ForgetGravity", ForgetGravity}, { "NorthEast", "NorthEastGravity", NorthEastGravity}, { "North", "NorthGravity", NorthGravity}, { "NorthWest", "NorthWestGravity", NorthWestGravity}, { "SouthEast", "SouthEastGravity", SouthEastGravity}, { "South", "SouthGravity", SouthGravity}, { "SouthWest", "SouthWestGravity", SouthWestGravity}, { "West", "WestGravity", WestGravity} }; /** Number of gravity options */ #define N_GRAVITY_OPTIONS (int)(sizeof(Gravity_Option)/sizeof(Gravity_Option[0])) /** * Return the value of the gravity option as a GravityType enumerator. * * No Ruby usage (internal function) * * @param name the name of the gravity option * @return the enumerator for name */ GravityType rm_gravity_to_enum(const char *name) { GravityType gravity = UndefinedGravity; int x; for (x = 0; x < N_GRAVITY_OPTIONS; x++) { if (strcmp(name, Gravity_Option[x].string) == 0) { gravity = Gravity_Option[x].enumerator; break; } } return gravity; } /** * Return the value of the gravity option as a GravityType enumerator. * * @return [Magick::GravityType] the gravity enumerator */ VALUE Info_gravity(VALUE self) { Info *info; const char *gravity; ID gravity_id; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); gravity_id = rb_intern("UndefinedGravity"); // Map the gravity option string to a GravityType enumerator. gravity = GetImageOption(info, "gravity"); if (gravity) { for (int x = 0; x < N_GRAVITY_OPTIONS; x++) { if (strcmp(gravity, Gravity_Option[x].string) == 0) { gravity_id = rb_intern(Gravity_Option[x].enum_name); break; } } } return rb_const_get(Module_Magick, gravity_id); } /** * Convert a GravityType enum to a gravity option name and store in the Info * structure. * * @param grav [Magick::GravityType] the gravity enumerator * @return [Magick::GravityType] the given gravity */ VALUE Info_gravity_eq(VALUE self, VALUE grav) { Info *info; GravityType gravity; const char *option; int x; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(grav)) { DeleteImageOption(info, "gravity"); return self; } VALUE_TO_ENUM(grav, gravity, GravityType); option = "Undefined"; for (x = 0; x < N_GRAVITY_OPTIONS; x++) { if (gravity == Gravity_Option[x].enumerator) { option = Gravity_Option[x].string; break; } } SetImageOption(info, "gravity", option); return grav; } /** * Get the classification type. * * @return [Magick::ImageType] the classification type */ VALUE Info_image_type(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return ImageType_find(info->type); } /** * Set the classification type. * * @param type [Magick::ImageType] the classification type * @return [Magick::ImageType] the given type */ VALUE Info_image_type_eq(VALUE self, VALUE type) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); VALUE_TO_ENUM(type, info->type, ImageType); return type; } /** * Get the interlace type. * * @return [Magick::InterlaceType] the interlace type */ VALUE Info_interlace(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return InterlaceType_find(info->interlace); } /** * Set the interlace type * * @param inter [Magick::InterlaceType] the interlace type * @return [Magick::InterlaceType] the given interlace */ VALUE Info_interlace_eq(VALUE self, VALUE inter) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); VALUE_TO_ENUM(inter, info->interlace, InterlaceType); return inter; } /** * Get the label. * * @return [String] the label */ VALUE Info_label(VALUE self) { return get_option(self, "Label"); } /** * Set the label. * * @param string [String] the label * @return [String] the given label */ VALUE Info_label_eq(VALUE self, VALUE string) { return set_option(self, "Label", string); } /** * Return the name of the matte color as a String. * * @return [String] the name of the matte color * @see Image#matte_color */ VALUE Info_matte_color(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return rm_pixelcolor_to_color_name_info(info, &info->matte_color); } /** * Set the matte color. * * @param matte_arg [Magick::Pixel, String] the name of the matte as a String * @return [Magick::Pixel, String] the given value */ VALUE Info_matte_color_eq(VALUE self, VALUE matte_arg) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); Color_to_PixelColor(&info->matte_color, matte_arg); return matte_arg; } /** * Get the monochrome value. * * @return [Boolean] true or false */ VALUE Info_monochrome(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, monochrome, boolean, &rm_info_data_type); } /** * Set the monochrome value. * * @param val [Boolean] true or false * @return [Boolean] the given value */ VALUE Info_monochrome_eq(VALUE self, VALUE val) { IMPLEMENT_TYPED_ATTR_WRITER(Info, monochrome, boolean, &rm_info_data_type); } /** * Get the scene number of an image or the first image in a sequence. * * @return [Numeric] the scene number */ VALUE Info_number_scenes(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, number_scenes, ulong, &rm_info_data_type); } /** * Set the scene number of an image or the first image in a sequence. * * @param val [Numeric] the scene number * @return [Numeric] the given value */ VALUE Info_number_scenes_eq(VALUE self, VALUE val) { IMPLEMENT_TYPED_ATTR_WRITER(Info, number_scenes, ulong, &rm_info_data_type); } /** * Return the orientation attribute as an OrientationType enum value. * * @return [Magick::OrientationType] the orientation */ VALUE Info_orientation(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return OrientationType_find(info->orientation); } /** * Set the Orientation type. * * @param inter [Magick::OrientationType] the orientation type as an OrientationType enum value * @return [Magick::OrientationType] the given value */ VALUE Info_orientation_eq(VALUE self, VALUE inter) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); VALUE_TO_ENUM(inter, info->orientation, OrientationType); return inter; } /** * Return origin geometry. * * @return [String] the origin geometry * @see https://www.imagemagick.org/Magick++/Geometry.html */ VALUE Info_origin(VALUE self) { Info *info; const char *origin; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); origin = GetImageOption(info, "origin"); return origin ? rb_str_new2(origin) : Qnil; } /** * Set origin geometry. Argument may be a Geometry object as well as a geometry * string. * * The geometry format is * +-x+-y * * @param origin_arg [String] the origin geometry * @return [String] the given value * @see https://www.imagemagick.org/Magick++/Geometry.html */ VALUE Info_origin_eq(VALUE self, VALUE origin_arg) { Info *info; VALUE origin_str; char *origin; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(origin_arg)) { DeleteImageOption(info, "origin"); return self; } origin_str = rb_String(origin_arg); origin = GetPageGeometry(StringValueCStr(origin_str)); if (IsGeometry(origin) == MagickFalse) { magick_free(origin); rb_raise(rb_eArgError, "invalid origin geometry"); } SetImageOption(info, "origin", origin); magick_free(origin); RB_GC_GUARD(origin_str); return origin_arg; } /** * Get the Postscript page geometry. * * @return [String] the page geometry */ VALUE Info_page(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return info->page ? rb_str_new2(info->page) : Qnil; } /** * Store the Postscript page geometry. Argument may be a Geometry object as well * as a geometry string. * * @param page_arg [String] the geometry * @return [String] the given value * @see https://www.imagemagick.org/Magick++/Geometry.html */ VALUE Info_page_eq(VALUE self, VALUE page_arg) { Info *info; VALUE geom_str; char *geometry; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(page_arg)) { magick_free(info->page); info->page = NULL; return self; } geom_str = rb_String(page_arg); geometry = GetPageGeometry(StringValueCStr(geom_str)); if (*geometry == '\0') { magick_free(info->page); info->page = NULL; return self; } info->page = geometry; RB_GC_GUARD(geom_str); return page_arg; } /** * Get the point size. * * @return [Float] the point size */ VALUE Info_pointsize(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, pointsize, dbl, &rm_info_data_type); } /** * Set the point size. * * @param val [Float] the point size * @return [Float] the given value */ VALUE Info_pointsize_eq(VALUE self, VALUE val) { IMPLEMENT_TYPED_ATTR_WRITER(Info, pointsize, dbl, &rm_info_data_type); } /** * Get the compression level for JPEG, etc. * * @return [Numeric] the compression level */ VALUE Info_quality(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, quality, ulong, &rm_info_data_type); } /** * Get the compression level for JPEG, etc. * * @param val [Numeric] the compression level * @return [Numeric] the given value */ VALUE Info_quality_eq(VALUE self, VALUE val) { IMPLEMENT_TYPED_ATTR_WRITER(Info, quality, ulong, &rm_info_data_type); } /** * Get sampling factors used by JPEG or MPEG-2 encoder and YUV decoder/encoder. * * @return [String, nil] the sampling factors */ VALUE Info_sampling_factor(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (info->sampling_factor) { return rb_str_new2(info->sampling_factor); } else { return Qnil; } } /** * Set sampling factors used by JPEG or MPEG-2 encoder and YUV decoder/encoder. * * @param sampling_factor [String] the sampling factors * @return [String] the given value */ VALUE Info_sampling_factor_eq(VALUE self, VALUE sampling_factor) { Info *info; char *sampling_factor_p = NULL; long sampling_factor_len = 0; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (!NIL_P(sampling_factor)) { sampling_factor_p = rm_str2cstr(sampling_factor, &sampling_factor_len); } if (info->sampling_factor) { magick_free(info->sampling_factor); info->sampling_factor = NULL; } if (sampling_factor_len > 0) { magick_clone_string(&info->sampling_factor, sampling_factor_p); } return sampling_factor; } /** * Get the scene number. * * @return [Numeric] the scene number */ VALUE Info_scene(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return ULONG2NUM(info->scene); } /** * Set the scene number. * * @param scene [Numeric] the scene number * @return [Numeric] the given value */ VALUE Info_scene_eq(VALUE self, VALUE scene) { Info *info; char buf[25]; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); info->scene = NUM2ULONG(scene); snprintf(buf, sizeof(buf), "%"RMIuSIZE"", info->scene); SetImageOption(info, "scene", buf); return scene; } /** * Get the server name. * * @return [String] the server name */ VALUE Info_server_name(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, server_name, str, &rm_info_data_type); } /** * Set the server name. * * @param server_arg [String] the server name * @return [String] the given value */ VALUE Info_server_name_eq(VALUE self, VALUE server_arg) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(server_arg) || StringValueCStr(server_arg) == NULL) { magick_free(info->server_name); info->server_name = NULL; } else { char *server; server = StringValueCStr(server_arg); magick_clone_string(&info->server_name, server); } return server_arg; } /** * Get ths size * * @return [String] the size as a Geometry object * @see https://www.imagemagick.org/Magick++/Geometry.html */ VALUE Info_size(VALUE self) { IMPLEMENT_TYPED_ATTR_READER(Info, size, str, &rm_info_data_type); } /** * Set the size (either as a Geometry object or a Geometry string * * @param size_arg [String] the size * @return [String] the given value * @see https://www.imagemagick.org/Magick++/Geometry.html */ VALUE Info_size_eq(VALUE self, VALUE size_arg) { Info *info; VALUE size; char *sz; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (NIL_P(size_arg)) { magick_free(info->size); info->size = NULL; return self; } size = rb_String(size_arg); sz = StringValueCStr(size); if (!IsGeometry(sz)) { rb_raise(rb_eArgError, "invalid size geometry: %s", sz); } magick_clone_string(&info->size, sz); RB_GC_GUARD(size); return size_arg; } /** * Return the stroke color as a String. * * @return [String] the stroke color */ VALUE Info_stroke(VALUE self) { return get_option(self, "stroke"); } /** * Set the stroke color * * @param color [String] the stroke color * @return [String] the given value */ VALUE Info_stroke_eq(VALUE self, VALUE color) { return set_color_option(self, "stroke", color); } /** * Get stroke width. * * @return [Float] the stroke width */ VALUE Info_stroke_width(VALUE self) { return get_dbl_option(self, "strokewidth"); } /** * Set stroke width. * * @param stroke_width [Float] the stroke width * @return [Float] the given value */ VALUE Info_stroke_width_eq(VALUE self, VALUE stroke_width) { return set_dbl_option(self, "strokewidth", stroke_width); } /** * Set name of texture to tile onto the image background. * * @param texture [Magick::Image] the texture image * @return [Magick::Image] the given image */ VALUE Info_texture_eq(VALUE self, VALUE texture) { Info *info; Image *image; char name[MaxTextExtent]; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); // Delete any existing texture file if (info->texture) { rm_delete_temp_image(info->texture); magick_free(info->texture); info->texture = NULL; } // If argument is nil we're done if (texture == Qnil) { return texture; } // Create a temp copy of the texture and store its name in the texture field image = rm_check_destroyed(texture); rm_write_temp_image(image, name, sizeof(name)); magick_clone_string(&info->texture, name); return texture; } /** * Return tile_offset geometry. * * @return [String, nil] the tile offset * @see https://www.imagemagick.org/Magick++/Geometry.html */ VALUE Info_tile_offset(VALUE self) { Info *info; const char *tile_offset; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); tile_offset = GetImageOption(info, "tile-offset"); if (!tile_offset) { return Qnil; } return rb_str_new2(tile_offset); } /** * Set tile offset geometry. * * @param offset [String] the offset geometry * @return [String] the given value * @see https://www.imagemagick.org/Magick++/Geometry.html */ VALUE Info_tile_offset_eq(VALUE self, VALUE offset) { Info *info; VALUE offset_str; char *tile_offset; offset_str = rb_String(offset); tile_offset = StringValueCStr(offset_str); if (!IsGeometry(tile_offset)) { rb_raise(rb_eArgError, "invalid tile offset geometry: %s", tile_offset); } TypedData_Get_Struct(self, Info, &rm_info_data_type, info); DeleteImageOption(info, "tile-offset"); SetImageOption(info, "tile-offset", tile_offset); RB_GC_GUARD(offset_str); return offset; } /** * Return the name of the transparent color. * * @return [String] the name of the transparent color * @see Image#transparent_color */ VALUE Info_transparent_color(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return rm_pixelcolor_to_color_name_info(info, &info->transparent_color); } /** * Set the transparent color. * * @param tc_arg [String] the transparent color * @return [Magick::Pixel, String] the given value */ VALUE Info_transparent_color_eq(VALUE self, VALUE tc_arg) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); Color_to_PixelColor(&info->transparent_color, tc_arg); return tc_arg; } /** * Undefine image option. * * @param format [String] the format * @param key [String] the key * @return [Magick::Info] self */ VALUE Info_undefine(VALUE self, VALUE format, VALUE key) { Info *info; char *format_p, *key_p; long format_l, key_l; char fkey[MaxTextExtent]; format_p = rm_str2cstr(format, &format_l); key_p = rm_str2cstr(key, &key_l); if (format_l > MAX_FORMAT_LEN || format_l + key_l > MaxTextExtent) { rb_raise(rb_eArgError, "can't undefine %.60s:%.1024s - too long", format_p, key_p); } snprintf(fkey, sizeof(fkey), "%.60s:%.*s", format_p, (int)(MaxTextExtent-61), key_p); TypedData_Get_Struct(self, Info, &rm_info_data_type, info); DeleteImageOption(info, fkey); return self; } /** * Return the undercolor color. * * @return [String] the undercolor */ VALUE Info_undercolor(VALUE self) { return get_option(self, "undercolor"); } /** * Set the undercolor color. * * @param color [String] the undercolor color * @return [String] the given value */ VALUE Info_undercolor_eq(VALUE self, VALUE color) { return set_color_option(self, "undercolor", color); } /** * Get the resolution type. * * @return [Magick::ResolutionType] the resolution type */ VALUE Info_units(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); return ResolutionType_find(info->units); } /** * Set the resolution type * * @param units [Magick::ResolutionType] the resolution type * @return [Magick::ResolutionType] the given value */ VALUE Info_units_eq(VALUE self, VALUE units) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); VALUE_TO_ENUM(units, info->units, ResolutionType); return units; } /** * Get FlashPix viewing parameters. * * @return [String] the viewing parameters */ VALUE Info_view(VALUE self) { Info *info; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); #if defined(IMAGEMAGICK_7) return C_str_to_R_str(GetImageOption(info, "fpx:view")); #else return C_str_to_R_str(info->view); #endif } /** * Set FlashPix viewing parameters. * * @param view_arg [String] the viewing parameters * @return [String] the given value */ VALUE Info_view_eq(VALUE self, VALUE view_arg) { Info *info; char *view = NULL; TypedData_Get_Struct(self, Info, &rm_info_data_type, info); if (!NIL_P(view_arg)) { view = StringValueCStr(view_arg); } #if defined(IMAGEMAGICK_7) if (view) { SetImageOption(info, "fpx:view", view); } else { RemoveImageOption(info, "fpx:view"); } #else if (info->view) { magick_free(info->view); info->view = NULL; } if (view) { magick_clone_string(&info->view, view); } #endif return view_arg; } /** * If there is a texture image, delete it before destroying the Image::Info * structure. * * No Ruby usage (internal function) * * @param infoptr pointer to the Info object */ static void Info_free(void *infoptr) { Info *info = (Info *)infoptr; if (info->texture) { rm_delete_temp_image(info->texture); magick_free(info->texture); info->texture = NULL; } DestroyImageInfo(info); } /** * Get Info object size. * * No Ruby usage (internal function) * * @param infoptr pointer to the Info object */ static size_t Info_memsize(const void *infoptr) { return sizeof(Info); } /** * Create an Image::Info object. * * No Ruby usage (internal function) * * @param class the Ruby class to use * @return a new ImageInfo object */ VALUE Info_alloc(VALUE class) { Info *info; VALUE info_obj; info = CloneImageInfo(NULL); if (!info) { rb_raise(rb_eNoMemError, "not enough memory to initialize Info object"); } info_obj = TypedData_Wrap_Struct(class, &rm_info_data_type, info); RB_GC_GUARD(info_obj); return info_obj; } /** * Provide a Info.new method for internal use. * * No Ruby usage (internal function) * * Notes: * - Takes no parameters, but runs the parm block if present * * @return a new ImageInfo object */ VALUE rm_info_new(void) { VALUE info_obj; info_obj = Info_alloc(Class_Info); RB_GC_GUARD(info_obj); return Info_initialize(info_obj); } /** * If an initializer block is present, run it. * * @overload initialize * * @overload initialize * @yield [Magick::Image::Info] * * @return self */ VALUE Info_initialize(VALUE self) { if (rb_block_given_p()) { rb_yield(self); } return self; }