rmagick 2.13.3 → 2.13.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rmagick might be problematic. Click here for more details.
- checksums.yaml +5 -13
- data/.gitignore +21 -0
- data/.travis.yml +57 -0
- data/CONTRIBUTING.md +22 -0
- data/ChangeLog +47 -4
- data/Gemfile +7 -0
- data/{README.rc → README.textile} +34 -104
- data/Rakefile +160 -24
- data/before_install_linux.sh +12 -0
- data/before_install_osx.sh +2 -0
- data/doc/.cvsignore +1 -0
- data/doc/ex/images/image_with_profile.jpg +0 -0
- data/doc/ex/mask.rb +1 -1
- data/examples/identify.rb +1 -1
- data/ext/RMagick/extconf.rb +60 -23
- data/ext/RMagick/rmagick.c +15 -15
- data/ext/RMagick/rmagick.h +9 -7
- data/ext/RMagick/rmdraw.c +12 -12
- data/ext/RMagick/rmenum.c +2 -2
- data/ext/RMagick/rmfill.c +25 -25
- data/ext/RMagick/rmilist.c +121 -104
- data/ext/RMagick/rmimage.c +737 -546
- data/ext/RMagick/rminfo.c +15 -15
- data/ext/RMagick/rmmain.c +27 -3
- data/ext/RMagick/rmpixel.c +25 -27
- data/ext/RMagick/rmstruct.c +1 -1
- data/ext/RMagick/rmutil.c +18 -18
- data/lib/RMagick.rb +1 -1962
- data/lib/rmagick.rb +1 -0
- data/lib/rmagick/version.rb +3 -1
- data/lib/rmagick_internal.rb +1964 -0
- data/rmagick.gemspec +14 -5
- data/test/Image2.rb +7 -3
- data/test/Image3.rb +54 -23
- data/test/ImageList2.rb +1 -1
- data/test/Image_attributes.rb +27 -10
- data/test/Import_Export.rb +11 -1
- data/test/Info.rb +4 -4
- data/test/Magick.rb +14 -54
- data/test/Pixel.rb +3 -4
- data/test/{all_basic.rb → test_all_basic.rb} +9 -17
- data/test/tmpnam_test.rb +50 -0
- metadata +50 -21
- data/README +0 -15
- data/README-Mac-OSX.txt +0 -1
- data/build_tarball.rake +0 -215
- data/lib/rvg/to_c.rb +0 -103
- data/metaconfig +0 -7
- data/pkg/rmagick-2.13.3.rc1.gem +0 -0
- data/post-clean.rb +0 -12
- data/post-install.rb +0 -50
- data/post-setup.rb +0 -254
- data/setup.rb +0 -1585
- data/uninstall.rb +0 -76
data/ext/RMagick/rminfo.c
CHANGED
@@ -93,7 +93,7 @@ static VALUE set_color_option(VALUE self, const char *option, VALUE color)
|
|
93
93
|
Info *info;
|
94
94
|
char *name;
|
95
95
|
PixelPacket pp;
|
96
|
-
ExceptionInfo exception;
|
96
|
+
ExceptionInfo *exception;
|
97
97
|
MagickBooleanType okay;
|
98
98
|
|
99
99
|
Data_Get_Struct(self, Info, info);
|
@@ -104,10 +104,10 @@ static VALUE set_color_option(VALUE self, const char *option, VALUE color)
|
|
104
104
|
}
|
105
105
|
else
|
106
106
|
{
|
107
|
-
|
107
|
+
exception = AcquireExceptionInfo();
|
108
108
|
name = StringValuePtr(color);
|
109
|
-
okay = QueryColorDatabase(name, &pp,
|
110
|
-
(void) DestroyExceptionInfo(
|
109
|
+
okay = QueryColorDatabase(name, &pp, exception);
|
110
|
+
(void) DestroyExceptionInfo(exception);
|
111
111
|
if (!okay)
|
112
112
|
{
|
113
113
|
rb_raise(rb_eArgError, "invalid color name `%s'", name);
|
@@ -960,11 +960,11 @@ Info_depth_eq(VALUE self, VALUE depth)
|
|
960
960
|
switch (d)
|
961
961
|
{
|
962
962
|
case 8: // always okay
|
963
|
-
#if
|
963
|
+
#if MAGICKCORE_QUANTUM_DEPTH == 16 || MAGICKCORE_QUANTUM_DEPTH == 32 || MAGICKCORE_QUANTUM_DEPTH == 64
|
964
964
|
case 16:
|
965
|
-
#if
|
965
|
+
#if MAGICKCORE_QUANTUM_DEPTH == 32 || MAGICKCORE_QUANTUM_DEPTH == 64
|
966
966
|
case 32:
|
967
|
-
#if
|
967
|
+
#if MAGICKCORE_QUANTUM_DEPTH == 64
|
968
968
|
case 64:
|
969
969
|
#endif
|
970
970
|
#endif
|
@@ -1359,14 +1359,14 @@ VALUE Info_format(VALUE self)
|
|
1359
1359
|
{
|
1360
1360
|
Info *info;
|
1361
1361
|
const MagickInfo *magick_info ;
|
1362
|
-
ExceptionInfo exception;
|
1362
|
+
ExceptionInfo *exception;
|
1363
1363
|
|
1364
1364
|
Data_Get_Struct(self, Info, info);
|
1365
1365
|
if (*info->magick)
|
1366
1366
|
{
|
1367
|
-
|
1368
|
-
magick_info = GetMagickInfo(info->magick,
|
1369
|
-
(void) DestroyExceptionInfo(
|
1367
|
+
exception = AcquireExceptionInfo();
|
1368
|
+
magick_info = GetMagickInfo(info->magick, exception);
|
1369
|
+
(void) DestroyExceptionInfo(exception);
|
1370
1370
|
|
1371
1371
|
return magick_info ? rb_str_new2(magick_info->name) : Qnil;
|
1372
1372
|
}
|
@@ -1390,16 +1390,16 @@ Info_format_eq(VALUE self, VALUE magick)
|
|
1390
1390
|
Info *info;
|
1391
1391
|
const MagickInfo *m;
|
1392
1392
|
char *mgk;
|
1393
|
-
ExceptionInfo exception;
|
1393
|
+
ExceptionInfo *exception;
|
1394
1394
|
|
1395
1395
|
Data_Get_Struct(self, Info, info);
|
1396
1396
|
|
1397
|
-
|
1397
|
+
exception = AcquireExceptionInfo();
|
1398
1398
|
|
1399
1399
|
mgk = StringValuePtr(magick);
|
1400
|
-
m = GetMagickInfo(mgk,
|
1400
|
+
m = GetMagickInfo(mgk, exception);
|
1401
1401
|
CHECK_EXCEPTION()
|
1402
|
-
(void) DestroyExceptionInfo(
|
1402
|
+
(void) DestroyExceptionInfo(exception);
|
1403
1403
|
|
1404
1404
|
if (!m)
|
1405
1405
|
{
|
data/ext/RMagick/rmmain.c
CHANGED
@@ -22,6 +22,7 @@ void Init_RMagick(void);
|
|
22
22
|
|
23
23
|
static void test_Magick_version(void);
|
24
24
|
static void version_constants(void);
|
25
|
+
static void features_constant(void);
|
25
26
|
|
26
27
|
|
27
28
|
|
@@ -431,6 +432,8 @@ Init_RMagick2(void)
|
|
431
432
|
rb_define_method(Class_Image, "random_threshold_channel", Image_random_threshold_channel, -1);
|
432
433
|
rb_define_method(Class_Image, "recolor", Image_recolor, 1);
|
433
434
|
rb_define_method(Class_Image, "reduce_noise", Image_reduce_noise, 1);
|
435
|
+
rb_define_method(Class_Image, "resample", Image_resample, -1);
|
436
|
+
rb_define_method(Class_Image, "resample!", Image_resample_bang, -1);
|
434
437
|
rb_define_method(Class_Image, "resize", Image_resize, -1);
|
435
438
|
rb_define_method(Class_Image, "resize!", Image_resize_bang, -1);
|
436
439
|
rb_define_method(Class_Image, "roll", Image_roll, 2);
|
@@ -818,13 +821,13 @@ Init_RMagick2(void)
|
|
818
821
|
|
819
822
|
|
820
823
|
// Miscellaneous fixed-point constants
|
821
|
-
DEF_CONST(MaxRGB);
|
822
824
|
DEF_CONST(QuantumRange);
|
823
|
-
DEF_CONST(
|
825
|
+
DEF_CONST(MAGICKCORE_QUANTUM_DEPTH);
|
824
826
|
DEF_CONST(OpaqueOpacity);
|
825
827
|
DEF_CONST(TransparentOpacity);
|
826
828
|
|
827
829
|
version_constants();
|
830
|
+
features_constant();
|
828
831
|
|
829
832
|
/*-----------------------------------------------------------------------*/
|
830
833
|
/* Class Magick::Enum */
|
@@ -946,7 +949,7 @@ Init_RMagick2(void)
|
|
946
949
|
ENUMERATOR(HSLColorspace)
|
947
950
|
ENUMERATOR(HWBColorspace)
|
948
951
|
ENUMERATOR(HSBColorspace)
|
949
|
-
ENUMERATOR(
|
952
|
+
ENUMERATOR(LabColorspace)
|
950
953
|
ENUMERATOR(Rec601LumaColorspace)
|
951
954
|
ENUMERATOR(Rec601YCbCrColorspace)
|
952
955
|
ENUMERATOR(Rec709LumaColorspace)
|
@@ -1703,3 +1706,24 @@ version_constants(void)
|
|
1703
1706
|
rb_define_const(Module_Magick, "Long_version", str);
|
1704
1707
|
|
1705
1708
|
}
|
1709
|
+
|
1710
|
+
|
1711
|
+
/**
|
1712
|
+
* Create Features constant.
|
1713
|
+
*
|
1714
|
+
* No Ruby usage (internal function)
|
1715
|
+
*/
|
1716
|
+
static void
|
1717
|
+
features_constant(void)
|
1718
|
+
{
|
1719
|
+
volatile VALUE features;
|
1720
|
+
|
1721
|
+
#if defined(HAVE_GETMAGICKFEATURES)
|
1722
|
+
features = rb_str_new2(GetMagickFeatures());
|
1723
|
+
#else
|
1724
|
+
features = rb_str_new2(MagickSupport);
|
1725
|
+
#endif
|
1726
|
+
|
1727
|
+
rb_obj_freeze(features);
|
1728
|
+
rb_define_const(Module_Magick, "Magick_features", features);
|
1729
|
+
}
|
data/ext/RMagick/rmpixel.c
CHANGED
@@ -217,12 +217,12 @@ Color_Name_to_PixelPacket(PixelPacket *color, VALUE name_arg)
|
|
217
217
|
{
|
218
218
|
MagickBooleanType okay;
|
219
219
|
char *name;
|
220
|
-
ExceptionInfo exception;
|
220
|
+
ExceptionInfo *exception;
|
221
221
|
|
222
|
-
|
222
|
+
exception = AcquireExceptionInfo();
|
223
223
|
name = StringValuePtr(name_arg);
|
224
|
-
okay = QueryColorDatabase(name, color,
|
225
|
-
(void) DestroyExceptionInfo(
|
224
|
+
okay = QueryColorDatabase(name, color, exception);
|
225
|
+
(void) DestroyExceptionInfo(exception);
|
226
226
|
if (!okay)
|
227
227
|
{
|
228
228
|
rb_raise(rb_eArgError, "invalid color name %s", name);
|
@@ -444,15 +444,15 @@ VALUE
|
|
444
444
|
Pixel_from_color(VALUE class, VALUE name)
|
445
445
|
{
|
446
446
|
PixelPacket pp;
|
447
|
-
ExceptionInfo exception;
|
447
|
+
ExceptionInfo *exception;
|
448
448
|
MagickBooleanType okay;
|
449
449
|
|
450
450
|
class = class; // defeat "never referenced" message from icc
|
451
451
|
|
452
|
-
|
453
|
-
okay = QueryColorDatabase(StringValuePtr(name), &pp,
|
452
|
+
exception = AcquireExceptionInfo();
|
453
|
+
okay = QueryColorDatabase(StringValuePtr(name), &pp, exception);
|
454
454
|
CHECK_EXCEPTION()
|
455
|
-
(void) DestroyExceptionInfo(
|
455
|
+
(void) DestroyExceptionInfo(exception);
|
456
456
|
|
457
457
|
if (!okay)
|
458
458
|
{
|
@@ -488,7 +488,7 @@ Pixel_from_hsla(int argc, VALUE *argv, VALUE class)
|
|
488
488
|
{
|
489
489
|
double h, s, l, a = 1.0;
|
490
490
|
MagickPixelPacket pp;
|
491
|
-
ExceptionInfo exception;
|
491
|
+
ExceptionInfo *exception;
|
492
492
|
char name[50];
|
493
493
|
MagickBooleanType alpha = MagickFalse;
|
494
494
|
|
@@ -502,7 +502,7 @@ Pixel_from_hsla(int argc, VALUE *argv, VALUE class)
|
|
502
502
|
case 3:
|
503
503
|
// saturation and lightness are out of 255 in new ImageMagicks and
|
504
504
|
// out of 100 in old ImageMagicks. Compromise: always use %.
|
505
|
-
l = rm_percentage(argv[2],255.0);
|
505
|
+
l = rm_percentage(argv[2],255.0);
|
506
506
|
s = rm_percentage(argv[1],255.0);
|
507
507
|
h = rm_percentage(argv[0],360.0);
|
508
508
|
break;
|
@@ -547,12 +547,12 @@ Pixel_from_hsla(int argc, VALUE *argv, VALUE class)
|
|
547
547
|
sprintf(name, "hsl(%-2.1f,%-2.1f,%-2.1f)", h, s, l);
|
548
548
|
}
|
549
549
|
|
550
|
-
|
550
|
+
exception = AcquireExceptionInfo();
|
551
551
|
|
552
|
-
(void) QueryMagickColor(name, &pp,
|
552
|
+
(void) QueryMagickColor(name, &pp, exception);
|
553
553
|
CHECK_EXCEPTION()
|
554
554
|
|
555
|
-
(void) DestroyExceptionInfo(
|
555
|
+
(void) DestroyExceptionInfo(exception);
|
556
556
|
|
557
557
|
return Pixel_from_MagickPixelPacket(&pp);
|
558
558
|
}
|
@@ -666,10 +666,8 @@ Pixel_hash(VALUE self)
|
|
666
666
|
hash += ScaleQuantumToChar(pixel->green) << 16;
|
667
667
|
hash += ScaleQuantumToChar(pixel->blue) << 8;
|
668
668
|
hash += ScaleQuantumToChar(pixel->opacity);
|
669
|
-
hash >>= 1;
|
670
|
-
|
671
|
-
return INT2FIX(hash);
|
672
669
|
|
670
|
+
return UINT2NUM(hash >> 1);
|
673
671
|
}
|
674
672
|
|
675
673
|
|
@@ -877,7 +875,7 @@ Pixel_spaceship(VALUE self, VALUE other)
|
|
877
875
|
/**
|
878
876
|
* Return [hue, saturation, lightness, alpha] in the same ranges as
|
879
877
|
* Pixel_from_hsla.
|
880
|
-
*
|
878
|
+
*
|
881
879
|
*
|
882
880
|
* Ruby usage:
|
883
881
|
* - @verbatim Pixel#to_hsla @endverbatim
|
@@ -913,7 +911,7 @@ Pixel_to_hsla(VALUE self)
|
|
913
911
|
}
|
914
912
|
else
|
915
913
|
{
|
916
|
-
alpha =
|
914
|
+
alpha = (double)(QuantumRange - pixel->opacity) / (double)QuantumRange;
|
917
915
|
}
|
918
916
|
|
919
917
|
hsla = rb_ary_new3(4, rb_float_new(hue), rb_float_new(sat), rb_float_new(lum), rb_float_new(alpha));
|
@@ -962,7 +960,7 @@ Pixel_to_HSL(VALUE self)
|
|
962
960
|
* Notes:
|
963
961
|
* - Default compliance is AllCompliance
|
964
962
|
* - Default matte is false
|
965
|
-
* - Default depth is
|
963
|
+
* - Default depth is MAGICKCORE_QUANTUM_DEPTH
|
966
964
|
* - Default hex is false
|
967
965
|
* - The conversion respects the value of the 'opacity' field in the Pixel
|
968
966
|
*
|
@@ -980,10 +978,10 @@ Pixel_to_color(int argc, VALUE *argv, VALUE self)
|
|
980
978
|
MagickPixelPacket mpp;
|
981
979
|
MagickBooleanType hex = MagickFalse;
|
982
980
|
char name[MaxTextExtent];
|
983
|
-
ExceptionInfo exception;
|
981
|
+
ExceptionInfo *exception;
|
984
982
|
ComplianceType compliance = AllCompliance;
|
985
983
|
unsigned int matte = MagickFalse;
|
986
|
-
unsigned int depth =
|
984
|
+
unsigned int depth = MAGICKCORE_QUANTUM_DEPTH;
|
987
985
|
|
988
986
|
switch (argc)
|
989
987
|
{
|
@@ -996,10 +994,10 @@ Pixel_to_color(int argc, VALUE *argv, VALUE self)
|
|
996
994
|
switch (depth)
|
997
995
|
{
|
998
996
|
case 8:
|
999
|
-
#if
|
997
|
+
#if MAGICKCORE_QUANTUM_DEPTH == 16 || MAGICKCORE_QUANTUM_DEPTH == 32
|
1000
998
|
case 16:
|
1001
999
|
#endif
|
1002
|
-
#if
|
1000
|
+
#if MAGICKCORE_QUANTUM_DEPTH == 32
|
1003
1001
|
case 32:
|
1004
1002
|
#endif
|
1005
1003
|
break;
|
@@ -1028,7 +1026,7 @@ Pixel_to_color(int argc, VALUE *argv, VALUE self)
|
|
1028
1026
|
GetMagickPixelPacket(image, &mpp);
|
1029
1027
|
rm_set_magick_pixel_packet(pixel, &mpp);
|
1030
1028
|
|
1031
|
-
|
1029
|
+
exception = AcquireExceptionInfo();
|
1032
1030
|
|
1033
1031
|
#if defined(HAVE_NEW_QUERYMAGICKCOLORNAME)
|
1034
1032
|
// Support for hex-format color names moved out of QueryMagickColorname
|
@@ -1044,14 +1042,14 @@ Pixel_to_color(int argc, VALUE *argv, VALUE self)
|
|
1044
1042
|
}
|
1045
1043
|
else
|
1046
1044
|
{
|
1047
|
-
(void) QueryMagickColorname(image, &mpp, compliance, name,
|
1045
|
+
(void) QueryMagickColorname(image, &mpp, compliance, name, exception);
|
1048
1046
|
}
|
1049
1047
|
#else
|
1050
|
-
(void) QueryMagickColorname(image, &mpp, compliance, hex, name,
|
1048
|
+
(void) QueryMagickColorname(image, &mpp, compliance, hex, name, exception);
|
1051
1049
|
#endif
|
1052
1050
|
(void) DestroyImage(image);
|
1053
1051
|
CHECK_EXCEPTION()
|
1054
|
-
(void) DestroyExceptionInfo(
|
1052
|
+
(void) DestroyExceptionInfo(exception);
|
1055
1053
|
|
1056
1054
|
// Always return a string, even if it's ""
|
1057
1055
|
return rb_str_new2(name);
|
data/ext/RMagick/rmstruct.c
CHANGED
@@ -347,7 +347,7 @@ Color_to_s(VALUE self)
|
|
347
347
|
Export_ColorInfo(&ci, self);
|
348
348
|
|
349
349
|
sprintf(buff, "name=%s, compliance=%s, "
|
350
|
-
#if (
|
350
|
+
#if (MAGICKCORE_QUANTUM_DEPTH == 32 || MAGICKCORE_QUANTUM_DEPTH == 64) && defined(HAVE_TYPE_LONG_DOUBLE)
|
351
351
|
"color.red=%Lg, color.green=%Lg, color.blue=%Lg, color.opacity=%Lg ",
|
352
352
|
#else
|
353
353
|
"color.red=%g, color.green=%g, color.blue=%g, color.opacity=%g ",
|
data/ext/RMagick/rmutil.c
CHANGED
@@ -627,13 +627,13 @@ VALUE
|
|
627
627
|
rm_pixelpacket_to_color_name(Image *image, PixelPacket *color)
|
628
628
|
{
|
629
629
|
char name[MaxTextExtent];
|
630
|
-
ExceptionInfo exception;
|
630
|
+
ExceptionInfo *exception;
|
631
631
|
|
632
|
-
|
632
|
+
exception = AcquireExceptionInfo();
|
633
633
|
|
634
|
-
(void) QueryColorname(image, color, X11Compliance, name,
|
634
|
+
(void) QueryColorname(image, color, X11Compliance, name, exception);
|
635
635
|
CHECK_EXCEPTION()
|
636
|
-
(void) DestroyExceptionInfo(
|
636
|
+
(void) DestroyExceptionInfo(exception);
|
637
637
|
|
638
638
|
return rb_str_new2(name);
|
639
639
|
}
|
@@ -698,11 +698,11 @@ rm_write_temp_image(Image *image, char *temp_name)
|
|
698
698
|
#define TMPNAM_CLASS_VAR "@@_tmpnam_"
|
699
699
|
|
700
700
|
MagickBooleanType okay;
|
701
|
-
ExceptionInfo exception;
|
701
|
+
ExceptionInfo *exception;
|
702
702
|
volatile VALUE id_value;
|
703
703
|
int id;
|
704
704
|
|
705
|
-
|
705
|
+
exception = AcquireExceptionInfo();
|
706
706
|
|
707
707
|
|
708
708
|
// 'id' is always the value of its previous use
|
@@ -722,9 +722,9 @@ rm_write_temp_image(Image *image, char *temp_name)
|
|
722
722
|
sprintf(temp_name, "mpri:%d", id);
|
723
723
|
|
724
724
|
// Omit "mpri:" from filename to form the key
|
725
|
-
okay = SetImageRegistry(ImageRegistryType, temp_name+5, image,
|
725
|
+
okay = SetImageRegistry(ImageRegistryType, temp_name+5, image, exception);
|
726
726
|
CHECK_EXCEPTION()
|
727
|
-
DestroyExceptionInfo(
|
727
|
+
DestroyExceptionInfo(exception);
|
728
728
|
if (!okay)
|
729
729
|
{
|
730
730
|
rb_raise(rb_eRuntimeError, "SetImageRegistry failed.");
|
@@ -1389,16 +1389,16 @@ Image *
|
|
1389
1389
|
rm_clone_image(Image *image)
|
1390
1390
|
{
|
1391
1391
|
Image *clone;
|
1392
|
-
ExceptionInfo exception;
|
1392
|
+
ExceptionInfo *exception;
|
1393
1393
|
|
1394
|
-
|
1395
|
-
clone = CloneImage(image, 0, 0, MagickTrue,
|
1394
|
+
exception = AcquireExceptionInfo();
|
1395
|
+
clone = CloneImage(image, 0, 0, MagickTrue, exception);
|
1396
1396
|
if (!clone)
|
1397
1397
|
{
|
1398
1398
|
rb_raise(rb_eNoMemError, "not enough memory to continue");
|
1399
1399
|
}
|
1400
|
-
rm_check_exception(
|
1401
|
-
(void) DestroyExceptionInfo(
|
1400
|
+
rm_check_exception(exception, clone, DestroyOnError);
|
1401
|
+
(void) DestroyExceptionInfo(exception);
|
1402
1402
|
|
1403
1403
|
return clone;
|
1404
1404
|
}
|
@@ -1486,7 +1486,7 @@ rm_split(Image *image)
|
|
1486
1486
|
void
|
1487
1487
|
rm_check_image_exception(Image *imglist, ErrorRetention retention)
|
1488
1488
|
{
|
1489
|
-
ExceptionInfo exception;
|
1489
|
+
ExceptionInfo *exception;
|
1490
1490
|
Image *badboy = NULL;
|
1491
1491
|
Image *image;
|
1492
1492
|
|
@@ -1495,7 +1495,7 @@ rm_check_image_exception(Image *imglist, ErrorRetention retention)
|
|
1495
1495
|
return;
|
1496
1496
|
}
|
1497
1497
|
|
1498
|
-
|
1498
|
+
exception = AcquireExceptionInfo();
|
1499
1499
|
|
1500
1500
|
// Find the image with the highest severity
|
1501
1501
|
image = GetFirstImageInList(imglist);
|
@@ -1506,7 +1506,7 @@ rm_check_image_exception(Image *imglist, ErrorRetention retention)
|
|
1506
1506
|
if (!badboy || image->exception.severity > badboy->exception.severity)
|
1507
1507
|
{
|
1508
1508
|
badboy = image;
|
1509
|
-
InheritException(
|
1509
|
+
InheritException(exception, &badboy->exception);
|
1510
1510
|
}
|
1511
1511
|
|
1512
1512
|
ClearMagickException(&image->exception);
|
@@ -1516,10 +1516,10 @@ rm_check_image_exception(Image *imglist, ErrorRetention retention)
|
|
1516
1516
|
|
1517
1517
|
if (badboy)
|
1518
1518
|
{
|
1519
|
-
rm_check_exception(
|
1519
|
+
rm_check_exception(exception, imglist, retention);
|
1520
1520
|
}
|
1521
1521
|
|
1522
|
-
(void) DestroyExceptionInfo(
|
1522
|
+
(void) DestroyExceptionInfo(exception);
|
1523
1523
|
}
|
1524
1524
|
|
1525
1525
|
|
data/lib/RMagick.rb
CHANGED
@@ -1,1962 +1 @@
|
|
1
|
-
|
2
|
-
#==============================================================================
|
3
|
-
# Copyright (C) 2009 by Timothy P. Hunter
|
4
|
-
# Name: RMagick.rb
|
5
|
-
# Author: Tim Hunter
|
6
|
-
# Purpose: Extend Ruby to interface with ImageMagick.
|
7
|
-
# Notes: RMagick2.so defines the classes. The code below adds methods
|
8
|
-
# to the classes.
|
9
|
-
#==============================================================================
|
10
|
-
|
11
|
-
require 'RMagick2.so'
|
12
|
-
|
13
|
-
module Magick
|
14
|
-
@formats = nil
|
15
|
-
@trace_proc = nil
|
16
|
-
@exit_block_set_up = nil
|
17
|
-
|
18
|
-
class << self
|
19
|
-
def formats(&block)
|
20
|
-
@formats ||= init_formats()
|
21
|
-
if block_given?
|
22
|
-
@formats.each { |k,v| yield k, v }
|
23
|
-
self
|
24
|
-
else
|
25
|
-
@formats
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# remove reference to the proc at exit
|
30
|
-
def trace_proc=(p)
|
31
|
-
if @trace_proc.nil? && !p.nil? && !@exit_block_set_up
|
32
|
-
at_exit { @trace_proc = nil }
|
33
|
-
@exit_block_set_up = true
|
34
|
-
end
|
35
|
-
@trace_proc = p
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Geometry class and related enum constants
|
40
|
-
class GeometryValue < Enum
|
41
|
-
# no methods
|
42
|
-
end
|
43
|
-
|
44
|
-
PercentGeometry = GeometryValue.new(:PercentGeometry, 1).freeze
|
45
|
-
AspectGeometry = GeometryValue.new(:AspectGeometry, 2).freeze
|
46
|
-
LessGeometry = GeometryValue.new(:LessGeometry, 3).freeze
|
47
|
-
GreaterGeometry = GeometryValue.new(:GreaterGeometry, 4).freeze
|
48
|
-
AreaGeometry = GeometryValue.new(:AreaGeometry, 5).freeze
|
49
|
-
MinimumGeometry = GeometryValue.new(:MinimumGeometry, 6).freeze
|
50
|
-
|
51
|
-
class Geometry
|
52
|
-
FLAGS = ['', '%', '!', '<', '>', '@', '^']
|
53
|
-
RFLAGS = { '%' => PercentGeometry,
|
54
|
-
'!' => AspectGeometry,
|
55
|
-
'<' => LessGeometry,
|
56
|
-
'>' => GreaterGeometry,
|
57
|
-
'@' => AreaGeometry,
|
58
|
-
'^' => MinimumGeometry }
|
59
|
-
|
60
|
-
attr_accessor :width, :height, :x, :y, :flag
|
61
|
-
|
62
|
-
def initialize(width=nil, height=nil, x=nil, y=nil, flag=nil)
|
63
|
-
raise(ArgumentError, "width set to #{width.to_s}") if width.is_a? GeometryValue
|
64
|
-
raise(ArgumentError, "height set to #{height.to_s}") if height.is_a? GeometryValue
|
65
|
-
raise(ArgumentError, "x set to #{x.to_s}") if x.is_a? GeometryValue
|
66
|
-
raise(ArgumentError, "y set to #{y.to_s}") if y.is_a? GeometryValue
|
67
|
-
|
68
|
-
# Support floating-point width and height arguments so Geometry
|
69
|
-
# objects can be used to specify Image#density= arguments.
|
70
|
-
if width == nil
|
71
|
-
@width = 0
|
72
|
-
elsif width.to_f >= 0.0
|
73
|
-
@width = width.to_f
|
74
|
-
else
|
75
|
-
Kernel.raise ArgumentError, "width must be >= 0: #{width}"
|
76
|
-
end
|
77
|
-
if height == nil
|
78
|
-
@height = 0
|
79
|
-
elsif height.to_f >= 0.0
|
80
|
-
@height = height.to_f
|
81
|
-
else
|
82
|
-
Kernel.raise ArgumentError, "height must be >= 0: #{height}"
|
83
|
-
end
|
84
|
-
|
85
|
-
@x = x.to_i
|
86
|
-
@y = y.to_i
|
87
|
-
@flag = flag
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
# Construct an object from a geometry string
|
92
|
-
W = /(\d+\.\d+%?)|(\d*%?)/
|
93
|
-
H = W
|
94
|
-
X = /(?:([-+]\d+))?/
|
95
|
-
Y = X
|
96
|
-
RE = /\A#{W}x?#{H}#{X}#{Y}([!<>@\^]?)\Z/
|
97
|
-
|
98
|
-
def Geometry.from_s(str)
|
99
|
-
|
100
|
-
m = RE.match(str)
|
101
|
-
if m
|
102
|
-
width = (m[1] || m[2]).to_f
|
103
|
-
height = (m[3] || m[4]).to_f
|
104
|
-
x = m[5].to_i
|
105
|
-
y = m[6].to_i
|
106
|
-
flag = RFLAGS[m[7]]
|
107
|
-
else
|
108
|
-
Kernel.raise ArgumentError, "invalid geometry format"
|
109
|
-
end
|
110
|
-
if str['%']
|
111
|
-
flag = PercentGeometry
|
112
|
-
end
|
113
|
-
Geometry.new(width, height, x, y, flag)
|
114
|
-
end
|
115
|
-
|
116
|
-
# Convert object to a geometry string
|
117
|
-
def to_s
|
118
|
-
str = ''
|
119
|
-
if @width > 0
|
120
|
-
fmt = @width.truncate == @width ? "%d" : "%.2f"
|
121
|
-
str << sprintf(fmt, @width)
|
122
|
-
str << '%' if @flag == PercentGeometry
|
123
|
-
end
|
124
|
-
|
125
|
-
if (@width > 0 && @flag != PercentGeometry) || (@height > 0)
|
126
|
-
str << 'x'
|
127
|
-
end
|
128
|
-
|
129
|
-
if @height > 0
|
130
|
-
fmt = @height.truncate == @height ? "%d" : "%.2f"
|
131
|
-
str << sprintf(fmt, @height)
|
132
|
-
str << '%' if @flag == PercentGeometry
|
133
|
-
end
|
134
|
-
str << sprintf("%+d%+d", @x, @y) if (@x != 0 || @y != 0)
|
135
|
-
if @flag != PercentGeometry
|
136
|
-
str << FLAGS[@flag.to_i]
|
137
|
-
end
|
138
|
-
str
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
|
143
|
-
class Draw
|
144
|
-
|
145
|
-
# Thse hashes are used to map Magick constant
|
146
|
-
# values to the strings used in the primitives.
|
147
|
-
ALIGN_TYPE_NAMES = {
|
148
|
-
LeftAlign.to_i => 'left',
|
149
|
-
RightAlign.to_i => 'right',
|
150
|
-
CenterAlign.to_i => 'center'
|
151
|
-
}.freeze
|
152
|
-
ANCHOR_TYPE_NAMES = {
|
153
|
-
StartAnchor.to_i => 'start',
|
154
|
-
MiddleAnchor.to_i => 'middle',
|
155
|
-
EndAnchor.to_i => 'end'
|
156
|
-
}.freeze
|
157
|
-
DECORATION_TYPE_NAMES = {
|
158
|
-
NoDecoration.to_i => 'none',
|
159
|
-
UnderlineDecoration.to_i => 'underline',
|
160
|
-
OverlineDecoration.to_i => 'overline',
|
161
|
-
LineThroughDecoration.to_i => 'line-through'
|
162
|
-
}.freeze
|
163
|
-
FONT_WEIGHT_NAMES = {
|
164
|
-
AnyWeight.to_i => 'all',
|
165
|
-
NormalWeight.to_i => 'normal',
|
166
|
-
BoldWeight.to_i => 'bold',
|
167
|
-
BolderWeight.to_i => 'bolder',
|
168
|
-
LighterWeight.to_i => 'lighter',
|
169
|
-
}.freeze
|
170
|
-
GRAVITY_NAMES = {
|
171
|
-
NorthWestGravity.to_i => 'northwest',
|
172
|
-
NorthGravity.to_i => 'north',
|
173
|
-
NorthEastGravity.to_i => 'northeast',
|
174
|
-
WestGravity.to_i => 'west',
|
175
|
-
CenterGravity.to_i => 'center',
|
176
|
-
EastGravity.to_i => 'east',
|
177
|
-
SouthWestGravity.to_i => 'southwest',
|
178
|
-
SouthGravity.to_i => 'south',
|
179
|
-
SouthEastGravity.to_i => 'southeast'
|
180
|
-
}.freeze
|
181
|
-
PAINT_METHOD_NAMES = {
|
182
|
-
PointMethod.to_i => 'point',
|
183
|
-
ReplaceMethod.to_i => 'replace',
|
184
|
-
FloodfillMethod.to_i => 'floodfill',
|
185
|
-
FillToBorderMethod.to_i => 'filltoborder',
|
186
|
-
ResetMethod.to_i => 'reset'
|
187
|
-
}.freeze
|
188
|
-
STRETCH_TYPE_NAMES = {
|
189
|
-
NormalStretch.to_i => 'normal',
|
190
|
-
UltraCondensedStretch.to_i => 'ultra-condensed',
|
191
|
-
ExtraCondensedStretch.to_i => 'extra-condensed',
|
192
|
-
CondensedStretch.to_i => 'condensed',
|
193
|
-
SemiCondensedStretch.to_i => 'semi-condensed',
|
194
|
-
SemiExpandedStretch.to_i => 'semi-expanded',
|
195
|
-
ExpandedStretch.to_i => 'expanded',
|
196
|
-
ExtraExpandedStretch.to_i => 'extra-expanded',
|
197
|
-
UltraExpandedStretch.to_i => 'ultra-expanded',
|
198
|
-
AnyStretch.to_i => 'all'
|
199
|
-
}.freeze
|
200
|
-
STYLE_TYPE_NAMES = {
|
201
|
-
NormalStyle.to_i => 'normal',
|
202
|
-
ItalicStyle.to_i => 'italic',
|
203
|
-
ObliqueStyle.to_i => 'oblique',
|
204
|
-
AnyStyle.to_i => 'all'
|
205
|
-
}.freeze
|
206
|
-
|
207
|
-
private
|
208
|
-
def enquote(str)
|
209
|
-
if str.length > 2 && /\A(?:\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})\z/.match(str)
|
210
|
-
return str
|
211
|
-
else
|
212
|
-
return '"' + str + '"'
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
public
|
217
|
-
|
218
|
-
# Apply coordinate transformations to support scaling (s), rotation (r),
|
219
|
-
# and translation (t). Angles are specified in radians.
|
220
|
-
def affine(sx, rx, ry, sy, tx, ty)
|
221
|
-
primitive "affine " + sprintf("%g,%g,%g,%g,%g,%g", sx, rx, ry, sy, tx, ty)
|
222
|
-
end
|
223
|
-
|
224
|
-
# Draw an arc.
|
225
|
-
def arc(startX, startY, endX, endY, startDegrees, endDegrees)
|
226
|
-
primitive "arc " + sprintf("%g,%g %g,%g %g,%g",
|
227
|
-
startX, startY, endX, endY, startDegrees, endDegrees)
|
228
|
-
end
|
229
|
-
|
230
|
-
# Draw a bezier curve.
|
231
|
-
def bezier(*points)
|
232
|
-
if points.length == 0
|
233
|
-
Kernel.raise ArgumentError, "no points specified"
|
234
|
-
elsif points.length % 2 != 0
|
235
|
-
Kernel.raise ArgumentError, "odd number of arguments specified"
|
236
|
-
end
|
237
|
-
primitive "bezier " + points.join(',')
|
238
|
-
end
|
239
|
-
|
240
|
-
# Draw a circle
|
241
|
-
def circle(originX, originY, perimX, perimY)
|
242
|
-
primitive "circle " + sprintf("%g,%g %g,%g", originX, originY, perimX, perimY)
|
243
|
-
end
|
244
|
-
|
245
|
-
# Invoke a clip-path defined by def_clip_path.
|
246
|
-
def clip_path(name)
|
247
|
-
primitive "clip-path #{name}"
|
248
|
-
end
|
249
|
-
|
250
|
-
# Define the clipping rule.
|
251
|
-
def clip_rule(rule)
|
252
|
-
if ( not ["evenodd", "nonzero"].include?(rule.downcase) )
|
253
|
-
Kernel.raise ArgumentError, "Unknown clipping rule #{rule}"
|
254
|
-
end
|
255
|
-
primitive "clip-rule #{rule}"
|
256
|
-
end
|
257
|
-
|
258
|
-
# Define the clip units
|
259
|
-
def clip_units(unit)
|
260
|
-
if ( not ["userspace", "userspaceonuse", "objectboundingbox"].include?(unit.downcase) )
|
261
|
-
Kernel.raise ArgumentError, "Unknown clip unit #{unit}"
|
262
|
-
end
|
263
|
-
primitive "clip-units #{unit}"
|
264
|
-
end
|
265
|
-
|
266
|
-
# Set color in image according to specified colorization rule. Rule is one of
|
267
|
-
# point, replace, floodfill, filltoborder,reset
|
268
|
-
def color(x, y, method)
|
269
|
-
if ( not PAINT_METHOD_NAMES.has_key?(method.to_i) )
|
270
|
-
Kernel.raise ArgumentError, "Unknown PaintMethod: #{method}"
|
271
|
-
end
|
272
|
-
primitive "color #{x},#{y},#{PAINT_METHOD_NAMES[method.to_i]}"
|
273
|
-
end
|
274
|
-
|
275
|
-
# Specify EITHER the text decoration (none, underline, overline,
|
276
|
-
# line-through) OR the text solid background color (any color name or spec)
|
277
|
-
def decorate(decoration)
|
278
|
-
if ( DECORATION_TYPE_NAMES.has_key?(decoration.to_i) )
|
279
|
-
primitive "decorate #{DECORATION_TYPE_NAMES[decoration.to_i]}"
|
280
|
-
else
|
281
|
-
primitive "decorate #{enquote(decoration)}"
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
# Define a clip-path. A clip-path is a sequence of primitives
|
286
|
-
# bracketed by the "push clip-path <name>" and "pop clip-path"
|
287
|
-
# primitives. Upon advice from the IM guys, we also bracket
|
288
|
-
# the clip-path primitives with "push(pop) defs" and "push
|
289
|
-
# (pop) graphic-context".
|
290
|
-
def define_clip_path(name)
|
291
|
-
begin
|
292
|
-
push('defs')
|
293
|
-
push('clip-path', name)
|
294
|
-
push('graphic-context')
|
295
|
-
yield
|
296
|
-
ensure
|
297
|
-
pop('graphic-context')
|
298
|
-
pop('clip-path')
|
299
|
-
pop('defs')
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
# Draw an ellipse
|
304
|
-
def ellipse(originX, originY, width, height, arcStart, arcEnd)
|
305
|
-
primitive "ellipse " + sprintf("%g,%g %g,%g %g,%g",
|
306
|
-
originX, originY, width, height, arcStart, arcEnd)
|
307
|
-
end
|
308
|
-
|
309
|
-
# Let anything through, but the only defined argument
|
310
|
-
# is "UTF-8". All others are apparently ignored.
|
311
|
-
def encoding(encoding)
|
312
|
-
primitive "encoding #{encoding}"
|
313
|
-
end
|
314
|
-
|
315
|
-
# Specify object fill, a color name or pattern name
|
316
|
-
def fill(colorspec)
|
317
|
-
primitive "fill #{enquote(colorspec)}"
|
318
|
-
end
|
319
|
-
alias fill_color fill
|
320
|
-
alias fill_pattern fill
|
321
|
-
|
322
|
-
# Specify fill opacity (use "xx%" to indicate percentage)
|
323
|
-
def fill_opacity(opacity)
|
324
|
-
primitive "fill-opacity #{opacity}"
|
325
|
-
end
|
326
|
-
|
327
|
-
def fill_rule(rule)
|
328
|
-
if ( not ["evenodd", "nonzero"].include?(rule.downcase) )
|
329
|
-
Kernel.raise ArgumentError, "Unknown fill rule #{rule}"
|
330
|
-
end
|
331
|
-
primitive "fill-rule #{rule}"
|
332
|
-
end
|
333
|
-
|
334
|
-
# Specify text drawing font
|
335
|
-
def font(name)
|
336
|
-
primitive "font #{name}"
|
337
|
-
end
|
338
|
-
|
339
|
-
def font_family(name)
|
340
|
-
primitive "font-family \'#{name}\'"
|
341
|
-
end
|
342
|
-
|
343
|
-
def font_stretch(stretch)
|
344
|
-
if ( not STRETCH_TYPE_NAMES.has_key?(stretch.to_i) )
|
345
|
-
Kernel.raise ArgumentError, "Unknown stretch type"
|
346
|
-
end
|
347
|
-
primitive "font-stretch #{STRETCH_TYPE_NAMES[stretch.to_i]}"
|
348
|
-
end
|
349
|
-
|
350
|
-
def font_style(style)
|
351
|
-
if ( not STYLE_TYPE_NAMES.has_key?(style.to_i) )
|
352
|
-
Kernel.raise ArgumentError, "Unknown style type"
|
353
|
-
end
|
354
|
-
primitive "font-style #{STYLE_TYPE_NAMES[style.to_i]}"
|
355
|
-
end
|
356
|
-
|
357
|
-
# The font weight argument can be either a font weight
|
358
|
-
# constant or [100,200,...,900]
|
359
|
-
def font_weight(weight)
|
360
|
-
if ( FONT_WEIGHT_NAMES.has_key?(weight.to_i) )
|
361
|
-
primitive "font-weight #{FONT_WEIGHT_NAMES[weight.to_i]}"
|
362
|
-
else
|
363
|
-
primitive "font-weight #{weight}"
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
# Specify the text positioning gravity, one of:
|
368
|
-
# NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast
|
369
|
-
def gravity(grav)
|
370
|
-
if ( not GRAVITY_NAMES.has_key?(grav.to_i) )
|
371
|
-
Kernel.raise ArgumentError, "Unknown text positioning gravity"
|
372
|
-
end
|
373
|
-
primitive "gravity #{GRAVITY_NAMES[grav.to_i]}"
|
374
|
-
end
|
375
|
-
|
376
|
-
# IM 6.5.5-8 and later
|
377
|
-
def interline_spacing(space)
|
378
|
-
begin
|
379
|
-
Float(space)
|
380
|
-
rescue ArgumentError
|
381
|
-
Kernel.raise ArgumentError, "invalid value for interline_spacing"
|
382
|
-
rescue TypeError
|
383
|
-
Kernel.raise TypeError, "can't convert #{space.class} into Float"
|
384
|
-
end
|
385
|
-
primitive "interline-spacing #{space}"
|
386
|
-
end
|
387
|
-
|
388
|
-
# IM 6.4.8-3 and later
|
389
|
-
def interword_spacing(space)
|
390
|
-
begin
|
391
|
-
Float(space)
|
392
|
-
rescue ArgumentError
|
393
|
-
Kernel.raise ArgumentError, "invalid value for interword_spacing"
|
394
|
-
rescue TypeError
|
395
|
-
Kernel.raise TypeError, "can't convert #{space.class} into Float"
|
396
|
-
end
|
397
|
-
primitive "interword-spacing #{space}"
|
398
|
-
end
|
399
|
-
|
400
|
-
# IM 6.4.8-3 and later
|
401
|
-
def kerning(space)
|
402
|
-
begin
|
403
|
-
Float(space)
|
404
|
-
rescue ArgumentError
|
405
|
-
Kernel.raise ArgumentError, "invalid value for kerning"
|
406
|
-
rescue TypeError
|
407
|
-
Kernel.raise TypeError, "can't convert #{space.class} into Float"
|
408
|
-
end
|
409
|
-
primitive "kerning #{space}"
|
410
|
-
end
|
411
|
-
|
412
|
-
# Draw a line
|
413
|
-
def line(startX, startY, endX, endY)
|
414
|
-
primitive "line " + sprintf("%g,%g %g,%g", startX, startY, endX, endY)
|
415
|
-
end
|
416
|
-
|
417
|
-
# Set matte (make transparent) in image according to the specified
|
418
|
-
# colorization rule
|
419
|
-
def matte(x, y, method)
|
420
|
-
if ( not PAINT_METHOD_NAMES.has_key?(method.to_i) )
|
421
|
-
Kernel.raise ArgumentError, "Unknown paint method"
|
422
|
-
end
|
423
|
-
primitive "matte #{x},#{y} #{PAINT_METHOD_NAMES[method.to_i]}"
|
424
|
-
end
|
425
|
-
|
426
|
-
# Specify drawing fill and stroke opacities. If the value is a string
|
427
|
-
# ending with a %, the number will be multiplied by 0.01.
|
428
|
-
def opacity(opacity)
|
429
|
-
if (Numeric === opacity)
|
430
|
-
if (opacity < 0 || opacity > 1.0)
|
431
|
-
Kernel.raise ArgumentError, "opacity must be >= 0 and <= 1.0"
|
432
|
-
end
|
433
|
-
end
|
434
|
-
primitive "opacity #{opacity}"
|
435
|
-
end
|
436
|
-
|
437
|
-
# Draw using SVG-compatible path drawing commands. Note that the
|
438
|
-
# primitive requires that the commands be surrounded by quotes or
|
439
|
-
# apostrophes. Here we simply use apostrophes.
|
440
|
-
def path(cmds)
|
441
|
-
primitive "path '" + cmds + "'"
|
442
|
-
end
|
443
|
-
|
444
|
-
# Define a pattern. In the block, call primitive methods to
|
445
|
-
# draw the pattern. Reference the pattern by using its name
|
446
|
-
# as the argument to the 'fill' or 'stroke' methods
|
447
|
-
def pattern(name, x, y, width, height)
|
448
|
-
begin
|
449
|
-
push('defs')
|
450
|
-
push("pattern #{name} #{x} #{y} #{width} #{height}")
|
451
|
-
push('graphic-context')
|
452
|
-
yield
|
453
|
-
ensure
|
454
|
-
pop('graphic-context')
|
455
|
-
pop('pattern')
|
456
|
-
pop('defs')
|
457
|
-
end
|
458
|
-
end
|
459
|
-
|
460
|
-
# Set point to fill color.
|
461
|
-
def point(x, y)
|
462
|
-
primitive "point #{x},#{y}"
|
463
|
-
end
|
464
|
-
|
465
|
-
# Specify the font size in points. Yes, the primitive is "font-size" but
|
466
|
-
# in other places this value is called the "pointsize". Give it both names.
|
467
|
-
def pointsize(points)
|
468
|
-
primitive "font-size #{points}"
|
469
|
-
end
|
470
|
-
alias font_size pointsize
|
471
|
-
|
472
|
-
# Draw a polygon
|
473
|
-
def polygon(*points)
|
474
|
-
if points.length == 0
|
475
|
-
Kernel.raise ArgumentError, "no points specified"
|
476
|
-
elsif points.length % 2 != 0
|
477
|
-
Kernel.raise ArgumentError, "odd number of points specified"
|
478
|
-
end
|
479
|
-
primitive "polygon " + points.join(',')
|
480
|
-
end
|
481
|
-
|
482
|
-
# Draw a polyline
|
483
|
-
def polyline(*points)
|
484
|
-
if points.length == 0
|
485
|
-
Kernel.raise ArgumentError, "no points specified"
|
486
|
-
elsif points.length % 2 != 0
|
487
|
-
Kernel.raise ArgumentError, "odd number of points specified"
|
488
|
-
end
|
489
|
-
primitive "polyline " + points.join(',')
|
490
|
-
end
|
491
|
-
|
492
|
-
# Return to the previously-saved set of whatever
|
493
|
-
# pop('graphic-context') (the default if no arguments)
|
494
|
-
# pop('defs')
|
495
|
-
# pop('gradient')
|
496
|
-
# pop('pattern')
|
497
|
-
|
498
|
-
def pop(*what)
|
499
|
-
if what.length == 0
|
500
|
-
primitive "pop graphic-context"
|
501
|
-
else
|
502
|
-
# to_s allows a Symbol to be used instead of a String
|
503
|
-
primitive "pop " + what.map {|w| w.to_s}.join(' ')
|
504
|
-
end
|
505
|
-
end
|
506
|
-
|
507
|
-
# Push the current set of drawing options. Also you can use
|
508
|
-
# push('graphic-context') (the default if no arguments)
|
509
|
-
# push('defs')
|
510
|
-
# push('gradient')
|
511
|
-
# push('pattern')
|
512
|
-
def push(*what)
|
513
|
-
if what.length == 0
|
514
|
-
primitive "push graphic-context"
|
515
|
-
else
|
516
|
-
# to_s allows a Symbol to be used instead of a String
|
517
|
-
primitive "push " + what.map {|w| w.to_s}.join(' ')
|
518
|
-
end
|
519
|
-
end
|
520
|
-
|
521
|
-
# Draw a rectangle
|
522
|
-
def rectangle(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
|
523
|
-
primitive "rectangle " + sprintf("%g,%g %g,%g",
|
524
|
-
upper_left_x, upper_left_y, lower_right_x, lower_right_y)
|
525
|
-
end
|
526
|
-
|
527
|
-
# Specify coordinate space rotation. "angle" is measured in degrees
|
528
|
-
def rotate(angle)
|
529
|
-
primitive "rotate #{angle}"
|
530
|
-
end
|
531
|
-
|
532
|
-
# Draw a rectangle with rounded corners
|
533
|
-
def roundrectangle(center_x, center_y, width, height, corner_width, corner_height)
|
534
|
-
primitive "roundrectangle " + sprintf("%g,%g,%g,%g,%g,%g",
|
535
|
-
center_x, center_y, width, height, corner_width, corner_height)
|
536
|
-
end
|
537
|
-
|
538
|
-
# Specify scaling to be applied to coordinate space on subsequent drawing commands.
|
539
|
-
def scale(x, y)
|
540
|
-
primitive "scale #{x},#{y}"
|
541
|
-
end
|
542
|
-
|
543
|
-
def skewx(angle)
|
544
|
-
primitive "skewX #{angle}"
|
545
|
-
end
|
546
|
-
|
547
|
-
def skewy(angle)
|
548
|
-
primitive "skewY #{angle}"
|
549
|
-
end
|
550
|
-
|
551
|
-
# Specify the object stroke, a color name or pattern name.
|
552
|
-
def stroke(colorspec)
|
553
|
-
primitive "stroke #{enquote(colorspec)}"
|
554
|
-
end
|
555
|
-
alias stroke_color stroke
|
556
|
-
alias stroke_pattern stroke
|
557
|
-
|
558
|
-
# Specify if stroke should be antialiased or not
|
559
|
-
def stroke_antialias(bool)
|
560
|
-
bool = bool ? '1' : '0'
|
561
|
-
primitive "stroke-antialias #{bool}"
|
562
|
-
end
|
563
|
-
|
564
|
-
# Specify a stroke dash pattern
|
565
|
-
def stroke_dasharray(*list)
|
566
|
-
if list.length == 0
|
567
|
-
primitive "stroke-dasharray none"
|
568
|
-
else
|
569
|
-
list.each { |x|
|
570
|
-
if x <= 0 then
|
571
|
-
Kernel.raise ArgumentError, "dash array elements must be > 0 (#{x} given)"
|
572
|
-
end
|
573
|
-
}
|
574
|
-
primitive "stroke-dasharray #{list.join(',')}"
|
575
|
-
end
|
576
|
-
end
|
577
|
-
|
578
|
-
# Specify the initial offset in the dash pattern
|
579
|
-
def stroke_dashoffset(value=0)
|
580
|
-
primitive "stroke-dashoffset #{value}"
|
581
|
-
end
|
582
|
-
|
583
|
-
def stroke_linecap(value)
|
584
|
-
if ( not ["butt", "round", "square"].include?(value.downcase) )
|
585
|
-
Kernel.raise ArgumentError, "Unknown linecap type: #{value}"
|
586
|
-
end
|
587
|
-
primitive "stroke-linecap #{value}"
|
588
|
-
end
|
589
|
-
|
590
|
-
def stroke_linejoin(value)
|
591
|
-
if ( not ["round", "miter", "bevel"].include?(value.downcase) )
|
592
|
-
Kernel.raise ArgumentError, "Unknown linejoin type: #{value}"
|
593
|
-
end
|
594
|
-
primitive "stroke-linejoin #{value}"
|
595
|
-
end
|
596
|
-
|
597
|
-
def stroke_miterlimit(value)
|
598
|
-
if (value < 1)
|
599
|
-
Kernel.raise ArgumentError, "miterlimit must be >= 1"
|
600
|
-
end
|
601
|
-
primitive "stroke-miterlimit #{value}"
|
602
|
-
end
|
603
|
-
|
604
|
-
# Specify opacity of stroke drawing color
|
605
|
-
# (use "xx%" to indicate percentage)
|
606
|
-
def stroke_opacity(value)
|
607
|
-
primitive "stroke-opacity #{value}"
|
608
|
-
end
|
609
|
-
|
610
|
-
# Specify stroke (outline) width in pixels.
|
611
|
-
def stroke_width(pixels)
|
612
|
-
primitive "stroke-width #{pixels}"
|
613
|
-
end
|
614
|
-
|
615
|
-
# Draw text at position x,y. Add quotes to text that is not already quoted.
|
616
|
-
def text(x, y, text)
|
617
|
-
if text.to_s.empty?
|
618
|
-
Kernel.raise ArgumentError, "missing text argument"
|
619
|
-
end
|
620
|
-
if text.length > 2 && /\A(?:\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})\z/.match(text)
|
621
|
-
; # text already quoted
|
622
|
-
elsif !text['\'']
|
623
|
-
text = '\''+text+'\''
|
624
|
-
elsif !text['"']
|
625
|
-
text = '"'+text+'"'
|
626
|
-
elsif !(text['{'] || text['}'])
|
627
|
-
text = '{'+text+'}'
|
628
|
-
else
|
629
|
-
# escape existing braces, surround with braces
|
630
|
-
text = '{' + text.gsub(/[}]/) { |b| '\\' + b } + '}'
|
631
|
-
end
|
632
|
-
primitive "text #{x},#{y} #{text}"
|
633
|
-
end
|
634
|
-
|
635
|
-
# Specify text alignment relative to a given point
|
636
|
-
def text_align(alignment)
|
637
|
-
if ( not ALIGN_TYPE_NAMES.has_key?(alignment.to_i) )
|
638
|
-
Kernel.raise ArgumentError, "Unknown alignment constant: #{alignment}"
|
639
|
-
end
|
640
|
-
primitive "text-align #{ALIGN_TYPE_NAMES[alignment.to_i]}"
|
641
|
-
end
|
642
|
-
|
643
|
-
# SVG-compatible version of text_align
|
644
|
-
def text_anchor(anchor)
|
645
|
-
if ( not ANCHOR_TYPE_NAMES.has_key?(anchor.to_i) )
|
646
|
-
Kernel.raise ArgumentError, "Unknown anchor constant: #{anchor}"
|
647
|
-
end
|
648
|
-
primitive "text-anchor #{ANCHOR_TYPE_NAMES[anchor.to_i]}"
|
649
|
-
end
|
650
|
-
|
651
|
-
# Specify if rendered text is to be antialiased.
|
652
|
-
def text_antialias(boolean)
|
653
|
-
boolean = boolean ? '1' : '0'
|
654
|
-
primitive "text-antialias #{boolean}"
|
655
|
-
end
|
656
|
-
|
657
|
-
# Specify color underneath text
|
658
|
-
def text_undercolor(color)
|
659
|
-
primitive "text-undercolor #{enquote(color)}"
|
660
|
-
end
|
661
|
-
|
662
|
-
# Specify center of coordinate space to use for subsequent drawing
|
663
|
-
# commands.
|
664
|
-
def translate(x, y)
|
665
|
-
primitive "translate #{x},#{y}"
|
666
|
-
end
|
667
|
-
end # class Magick::Draw
|
668
|
-
|
669
|
-
|
670
|
-
# Define IPTC record number:dataset tags for use with Image#get_iptc_dataset
|
671
|
-
module IPTC
|
672
|
-
module Envelope
|
673
|
-
Model_Version = "1:00"
|
674
|
-
Destination = "1:05"
|
675
|
-
File_Format = "1:20"
|
676
|
-
File_Format_Version = "1:22"
|
677
|
-
Service_Identifier = "1:30"
|
678
|
-
Envelope_Number = "1:40"
|
679
|
-
Product_ID = "1:50"
|
680
|
-
Envelope_Priority = "1:60"
|
681
|
-
Date_Sent = "1:70"
|
682
|
-
Time_Sent = "1:80"
|
683
|
-
Coded_Character_Set = "1:90"
|
684
|
-
UNO = "1:100"
|
685
|
-
Unique_Name_of_Object = "1:100"
|
686
|
-
ARM_Identifier = "1:120"
|
687
|
-
ARM_Version = "1:122"
|
688
|
-
end
|
689
|
-
|
690
|
-
module Application
|
691
|
-
Record_Version = "2:00"
|
692
|
-
Object_Type_Reference = "2:03"
|
693
|
-
Object_Name = "2:05"
|
694
|
-
Title = "2:05"
|
695
|
-
Edit_Status = "2:07"
|
696
|
-
Editorial_Update = "2:08"
|
697
|
-
Urgency = "2:10"
|
698
|
-
Subject_Reference = "2:12"
|
699
|
-
Category = "2:15"
|
700
|
-
Supplemental_Category = "2:20"
|
701
|
-
Fixture_Identifier = "2:22"
|
702
|
-
Keywords = "2:25"
|
703
|
-
Content_Location_Code = "2:26"
|
704
|
-
Content_Location_Name = "2:27"
|
705
|
-
Release_Date = "2:30"
|
706
|
-
Release_Time = "2:35"
|
707
|
-
Expiration_Date = "2:37"
|
708
|
-
Expiration_Time = "2:35"
|
709
|
-
Special_Instructions = "2:40"
|
710
|
-
Action_Advised = "2:42"
|
711
|
-
Reference_Service = "2:45"
|
712
|
-
Reference_Date = "2:47"
|
713
|
-
Reference_Number = "2:50"
|
714
|
-
Date_Created = "2:55"
|
715
|
-
Time_Created = "2:60"
|
716
|
-
Digital_Creation_Date = "2:62"
|
717
|
-
Digital_Creation_Time = "2:63"
|
718
|
-
Originating_Program = "2:65"
|
719
|
-
Program_Version = "2:70"
|
720
|
-
Object_Cycle = "2:75"
|
721
|
-
By_Line = "2:80"
|
722
|
-
Author = "2:80"
|
723
|
-
By_Line_Title = "2:85"
|
724
|
-
Author_Position = "2:85"
|
725
|
-
City = "2:90"
|
726
|
-
Sub_Location = "2:92"
|
727
|
-
Province = "2:95"
|
728
|
-
State = "2:95"
|
729
|
-
Country_Primary_Location_Code = "2:100"
|
730
|
-
Country_Primary_Location_Name = "2:101"
|
731
|
-
Original_Transmission_Reference = "2:103"
|
732
|
-
Headline = "2:105"
|
733
|
-
Credit = "2:110"
|
734
|
-
Source = "2:115"
|
735
|
-
Copyright_Notice = "2:116"
|
736
|
-
Contact = "2:118"
|
737
|
-
Abstract = "2:120"
|
738
|
-
Caption = "2:120"
|
739
|
-
Editor = "2:122"
|
740
|
-
Caption_Writer = "2:122"
|
741
|
-
Rasterized_Caption = "2:125"
|
742
|
-
Image_Type = "2:130"
|
743
|
-
Image_Orientation = "2:131"
|
744
|
-
Language_Identifier = "2:135"
|
745
|
-
Audio_Type = "2:150"
|
746
|
-
Audio_Sampling_Rate = "2:151"
|
747
|
-
Audio_Sampling_Resolution = "2:152"
|
748
|
-
Audio_Duration = "2:153"
|
749
|
-
Audio_Outcue = "2:154"
|
750
|
-
ObjectData_Preview_File_Format = "2:200"
|
751
|
-
ObjectData_Preview_File_Format_Version = "2:201"
|
752
|
-
ObjectData_Preview_Data = "2:202"
|
753
|
-
end
|
754
|
-
|
755
|
-
module Pre_ObjectData_Descriptor
|
756
|
-
Size_Mode = "7:10"
|
757
|
-
Max_Subfile_Size = "7:20"
|
758
|
-
ObjectData_Size_Announced = "7:90"
|
759
|
-
Maximum_ObjectData_Size = "7:95"
|
760
|
-
end
|
761
|
-
|
762
|
-
module ObjectData
|
763
|
-
Subfile = "8:10"
|
764
|
-
end
|
765
|
-
|
766
|
-
module Post_ObjectData_Descriptor
|
767
|
-
Confirmed_ObjectData_Size = "9:10"
|
768
|
-
end
|
769
|
-
|
770
|
-
# Make all constants above immutable
|
771
|
-
constants.each do |record|
|
772
|
-
rec = const_get(record)
|
773
|
-
rec.constants.each { |ds| rec.const_get(ds).freeze }
|
774
|
-
end
|
775
|
-
|
776
|
-
end # module Magick::IPTC
|
777
|
-
|
778
|
-
# Ruby-level Magick::Image methods
|
779
|
-
class Image
|
780
|
-
include Comparable
|
781
|
-
|
782
|
-
alias_method :affinity, :remap
|
783
|
-
|
784
|
-
# Provide an alternate version of Draw#annotate, for folks who
|
785
|
-
# want to find it in this class.
|
786
|
-
def annotate(draw, width, height, x, y, text, &block)
|
787
|
-
check_destroyed
|
788
|
-
draw.annotate(self, width, height, x, y, text, &block)
|
789
|
-
self
|
790
|
-
end
|
791
|
-
|
792
|
-
# Set the color at x,y
|
793
|
-
def color_point(x, y, fill)
|
794
|
-
f = copy
|
795
|
-
f.pixel_color(x, y, fill)
|
796
|
-
return f
|
797
|
-
end
|
798
|
-
|
799
|
-
# Set all pixels that have the same color as the pixel at x,y and
|
800
|
-
# are neighbors to the fill color
|
801
|
-
def color_floodfill(x, y, fill)
|
802
|
-
target = pixel_color(x, y)
|
803
|
-
color_flood_fill(target, fill, x, y, Magick::FloodfillMethod)
|
804
|
-
end
|
805
|
-
|
806
|
-
# Set all pixels that are neighbors of x,y and are not the border color
|
807
|
-
# to the fill color
|
808
|
-
def color_fill_to_border(x, y, fill)
|
809
|
-
color_flood_fill(border_color, fill, x, y, Magick::FillToBorderMethod)
|
810
|
-
end
|
811
|
-
|
812
|
-
# Set all pixels to the fill color. Very similar to Image#erase!
|
813
|
-
# Accepts either String or Pixel arguments
|
814
|
-
def color_reset!(fill)
|
815
|
-
save = background_color
|
816
|
-
# Change the background color _outside_ the begin block
|
817
|
-
# so that if this object is frozen the exeception will be
|
818
|
-
# raised before we have to handle it explicitly.
|
819
|
-
self.background_color = fill
|
820
|
-
begin
|
821
|
-
erase!
|
822
|
-
ensure
|
823
|
-
self.background_color = save
|
824
|
-
end
|
825
|
-
self
|
826
|
-
end
|
827
|
-
|
828
|
-
# Used by ImageList methods - see ImageList#cur_image
|
829
|
-
def cur_image
|
830
|
-
self
|
831
|
-
end
|
832
|
-
|
833
|
-
# Thanks to Russell Norris!
|
834
|
-
def each_pixel
|
835
|
-
get_pixels(0, 0, columns, rows).each_with_index do |p, n|
|
836
|
-
yield(p, n%columns, n/columns)
|
837
|
-
end
|
838
|
-
self
|
839
|
-
end
|
840
|
-
|
841
|
-
# Retrieve EXIF data by entry or all. If one or more entry names specified,
|
842
|
-
# return the values associated with the entries. If no entries specified,
|
843
|
-
# return all entries and values. The return value is an array of [name,value]
|
844
|
-
# arrays.
|
845
|
-
def get_exif_by_entry(*entry)
|
846
|
-
ary = Array.new
|
847
|
-
if entry.length == 0
|
848
|
-
exif_data = self['EXIF:*']
|
849
|
-
if exif_data
|
850
|
-
exif_data.split("\n").each { |exif| ary.push(exif.split('=')) }
|
851
|
-
end
|
852
|
-
else
|
853
|
-
get_exif_by_entry() # ensure properties is populated with exif data
|
854
|
-
entry.each do |name|
|
855
|
-
rval = self["EXIF:#{name}"]
|
856
|
-
ary.push([name, rval])
|
857
|
-
end
|
858
|
-
end
|
859
|
-
return ary
|
860
|
-
end
|
861
|
-
|
862
|
-
# Retrieve EXIF data by tag number or all tag/value pairs. The return value is a hash.
|
863
|
-
def get_exif_by_number(*tag)
|
864
|
-
hash = Hash.new
|
865
|
-
if tag.length == 0
|
866
|
-
exif_data = self['EXIF:!']
|
867
|
-
if exif_data
|
868
|
-
exif_data.split("\n").each do |exif|
|
869
|
-
tag, value = exif.split('=')
|
870
|
-
tag = tag[1,4].hex
|
871
|
-
hash[tag] = value
|
872
|
-
end
|
873
|
-
end
|
874
|
-
else
|
875
|
-
get_exif_by_number() # ensure properties is populated with exif data
|
876
|
-
tag.each do |num|
|
877
|
-
rval = self['#%04X' % num.to_i]
|
878
|
-
hash[num] = rval == 'unknown' ? nil : rval
|
879
|
-
end
|
880
|
-
end
|
881
|
-
return hash
|
882
|
-
end
|
883
|
-
|
884
|
-
# Retrieve IPTC information by record number:dataset tag constant defined in
|
885
|
-
# Magick::IPTC, above.
|
886
|
-
def get_iptc_dataset(ds)
|
887
|
-
self['IPTC:'+ds]
|
888
|
-
end
|
889
|
-
|
890
|
-
# Iterate over IPTC record number:dataset tags, yield for each non-nil dataset
|
891
|
-
def each_iptc_dataset
|
892
|
-
Magick::IPTC.constants.each do |record|
|
893
|
-
rec = Magick::IPTC.const_get(record)
|
894
|
-
rec.constants.each do |dataset|
|
895
|
-
data_field = get_iptc_dataset(rec.const_get(dataset))
|
896
|
-
yield(dataset, data_field) unless data_field.nil?
|
897
|
-
end
|
898
|
-
end
|
899
|
-
nil
|
900
|
-
end
|
901
|
-
|
902
|
-
# Patches problematic change to the order of arguments in 1.11.0.
|
903
|
-
# Before this release, the order was
|
904
|
-
# black_point, gamma, white_point
|
905
|
-
# RMagick 1.11.0 changed this to
|
906
|
-
# black_point, white_point, gamma
|
907
|
-
# This fix tries to determine if the arguments are in the old order and
|
908
|
-
# if so, swaps the gamma and white_point arguments. Then it calls
|
909
|
-
# level2, which simply accepts the arguments as given.
|
910
|
-
|
911
|
-
# Inspect the gamma and white point values and swap them if they
|
912
|
-
# look like they're in the old order.
|
913
|
-
|
914
|
-
# (Thanks to Al Evans for the suggestion.)
|
915
|
-
def level(black_point=0.0, white_point=nil, gamma=nil)
|
916
|
-
black_point = Float(black_point)
|
917
|
-
|
918
|
-
white_point ||= Magick::QuantumRange - black_point
|
919
|
-
white_point = Float(white_point)
|
920
|
-
|
921
|
-
gamma_arg = gamma
|
922
|
-
gamma ||= 1.0
|
923
|
-
gamma = Float(gamma)
|
924
|
-
|
925
|
-
if gamma.abs > 10.0 || white_point.abs <= 10.0 || white_point.abs < gamma.abs
|
926
|
-
gamma, white_point = white_point, gamma
|
927
|
-
unless gamma_arg
|
928
|
-
white_point = Magick::QuantumRange - black_point
|
929
|
-
end
|
930
|
-
end
|
931
|
-
|
932
|
-
return level2(black_point, white_point, gamma)
|
933
|
-
end
|
934
|
-
|
935
|
-
# These four methods are equivalent to the Draw#matte method
|
936
|
-
# with the "Point", "Replace", "Floodfill", "FilltoBorder", and
|
937
|
-
# "Replace" arguments, respectively.
|
938
|
-
|
939
|
-
# Make the pixel at (x,y) transparent.
|
940
|
-
def matte_point(x, y)
|
941
|
-
f = copy
|
942
|
-
f.opacity = OpaqueOpacity unless f.matte
|
943
|
-
pixel = f.pixel_color(x,y)
|
944
|
-
pixel.opacity = TransparentOpacity
|
945
|
-
f.pixel_color(x, y, pixel)
|
946
|
-
return f
|
947
|
-
end
|
948
|
-
|
949
|
-
# Make transparent all pixels that are the same color as the
|
950
|
-
# pixel at (x, y).
|
951
|
-
def matte_replace(x, y)
|
952
|
-
f = copy
|
953
|
-
f.opacity = OpaqueOpacity unless f.matte
|
954
|
-
target = f.pixel_color(x, y)
|
955
|
-
f.transparent(target)
|
956
|
-
end
|
957
|
-
|
958
|
-
# Make transparent any pixel that matches the color of the pixel
|
959
|
-
# at (x,y) and is a neighbor.
|
960
|
-
def matte_floodfill(x, y)
|
961
|
-
f = copy
|
962
|
-
f.opacity = OpaqueOpacity unless f.matte
|
963
|
-
target = f.pixel_color(x, y)
|
964
|
-
f.matte_flood_fill(target, TransparentOpacity,
|
965
|
-
x, y, FloodfillMethod)
|
966
|
-
end
|
967
|
-
|
968
|
-
# Make transparent any neighbor pixel that is not the border color.
|
969
|
-
def matte_fill_to_border(x, y)
|
970
|
-
f = copy
|
971
|
-
f.opacity = Magick::OpaqueOpacity unless f.matte
|
972
|
-
f.matte_flood_fill(border_color, TransparentOpacity,
|
973
|
-
x, y, FillToBorderMethod)
|
974
|
-
end
|
975
|
-
|
976
|
-
# Make all pixels transparent.
|
977
|
-
def matte_reset!
|
978
|
-
self.opacity = Magick::TransparentOpacity
|
979
|
-
self
|
980
|
-
end
|
981
|
-
|
982
|
-
# Corresponds to ImageMagick's -resample option
|
983
|
-
def resample(x_res=72.0, y_res=nil)
|
984
|
-
y_res ||= x_res
|
985
|
-
width = x_res * columns / x_resolution + 0.5
|
986
|
-
height = y_res * rows / y_resolution + 0.5
|
987
|
-
self.x_resolution = x_res
|
988
|
-
self.y_resolution = y_res
|
989
|
-
resize(width, height)
|
990
|
-
end
|
991
|
-
|
992
|
-
# Force an image to exact dimensions without changing the aspect ratio.
|
993
|
-
# Resize and crop if necessary. (Thanks to Jerett Taylor!)
|
994
|
-
def resize_to_fill(ncols, nrows=nil, gravity=CenterGravity)
|
995
|
-
copy.resize_to_fill!(ncols, nrows, gravity)
|
996
|
-
end
|
997
|
-
|
998
|
-
def resize_to_fill!(ncols, nrows=nil, gravity=CenterGravity)
|
999
|
-
nrows ||= ncols
|
1000
|
-
if ncols != columns || nrows != rows
|
1001
|
-
scale = [ncols/columns.to_f, nrows/rows.to_f].max
|
1002
|
-
resize!(scale*columns+0.5, scale*rows+0.5)
|
1003
|
-
end
|
1004
|
-
crop!(gravity, ncols, nrows, true) if ncols != columns || nrows != rows
|
1005
|
-
self
|
1006
|
-
end
|
1007
|
-
|
1008
|
-
# Preserve aliases used < RMagick 2.0.1
|
1009
|
-
alias_method :crop_resized, :resize_to_fill
|
1010
|
-
alias_method :crop_resized!, :resize_to_fill!
|
1011
|
-
|
1012
|
-
# Convenience method to resize retaining the aspect ratio.
|
1013
|
-
# (Thanks to Robert Manni!)
|
1014
|
-
def resize_to_fit(cols, rows=nil)
|
1015
|
-
rows ||= cols
|
1016
|
-
change_geometry(Geometry.new(cols, rows)) do |ncols, nrows|
|
1017
|
-
resize(ncols, nrows)
|
1018
|
-
end
|
1019
|
-
end
|
1020
|
-
|
1021
|
-
def resize_to_fit!(cols, rows=nil)
|
1022
|
-
rows ||= cols
|
1023
|
-
change_geometry(Geometry.new(cols, rows)) do |ncols, nrows|
|
1024
|
-
resize!(ncols, nrows)
|
1025
|
-
end
|
1026
|
-
end
|
1027
|
-
|
1028
|
-
# Replace matching neighboring pixels with texture pixels
|
1029
|
-
def texture_floodfill(x, y, texture)
|
1030
|
-
target = pixel_color(x, y)
|
1031
|
-
texture_flood_fill(target, texture, x, y, FloodfillMethod)
|
1032
|
-
end
|
1033
|
-
|
1034
|
-
# Replace neighboring pixels to border color with texture pixels
|
1035
|
-
def texture_fill_to_border(x, y, texture)
|
1036
|
-
texture_flood_fill(border_color, texture, x, y, FillToBorderMethod)
|
1037
|
-
end
|
1038
|
-
|
1039
|
-
# Construct a view. If a block is present, yield and pass the view
|
1040
|
-
# object, otherwise return the view object.
|
1041
|
-
def view(x, y, width, height)
|
1042
|
-
view = View.new(self, x, y, width, height)
|
1043
|
-
|
1044
|
-
if block_given?
|
1045
|
-
begin
|
1046
|
-
yield(view)
|
1047
|
-
ensure
|
1048
|
-
view.sync
|
1049
|
-
end
|
1050
|
-
return nil
|
1051
|
-
else
|
1052
|
-
return view
|
1053
|
-
end
|
1054
|
-
end
|
1055
|
-
|
1056
|
-
# Magick::Image::View class
|
1057
|
-
class View
|
1058
|
-
attr_reader :x, :y, :width, :height
|
1059
|
-
attr_accessor :dirty
|
1060
|
-
|
1061
|
-
def initialize(img, x, y, width, height)
|
1062
|
-
img.check_destroyed
|
1063
|
-
if width <= 0 || height <= 0
|
1064
|
-
Kernel.raise ArgumentError, "invalid geometry (#{width}x#{height}+#{x}+#{y})"
|
1065
|
-
end
|
1066
|
-
if x < 0 || y < 0 || (x+width) > img.columns || (y+height) > img.rows
|
1067
|
-
Kernel.raise RangeError, "geometry (#{width}x#{height}+#{x}+#{y}) exceeds image boundary"
|
1068
|
-
end
|
1069
|
-
@view = img.get_pixels(x, y, width, height)
|
1070
|
-
@img = img
|
1071
|
-
@x = x
|
1072
|
-
@y = y
|
1073
|
-
@width = width
|
1074
|
-
@height = height
|
1075
|
-
@dirty = false
|
1076
|
-
end
|
1077
|
-
|
1078
|
-
def [](*args)
|
1079
|
-
rows = Rows.new(@view, @width, @height, args)
|
1080
|
-
rows.add_observer(self)
|
1081
|
-
return rows
|
1082
|
-
end
|
1083
|
-
|
1084
|
-
# Store changed pixels back to image
|
1085
|
-
def sync(force=false)
|
1086
|
-
@img.store_pixels(x, y, width, height, @view) if (@dirty || force)
|
1087
|
-
return (@dirty || force)
|
1088
|
-
end
|
1089
|
-
|
1090
|
-
# Get update from Rows - if @dirty ever becomes
|
1091
|
-
# true, don't change it back to false!
|
1092
|
-
def update(rows)
|
1093
|
-
@dirty = true
|
1094
|
-
rows.delete_observer(self) # No need to tell us again.
|
1095
|
-
nil
|
1096
|
-
end
|
1097
|
-
|
1098
|
-
# Magick::Image::View::Pixels
|
1099
|
-
# Defines channel attribute getters/setters
|
1100
|
-
class Pixels < Array
|
1101
|
-
include Observable
|
1102
|
-
|
1103
|
-
# Define a getter and a setter for each channel.
|
1104
|
-
[:red, :green, :blue, :opacity].each do |c|
|
1105
|
-
module_eval <<-END_EVAL
|
1106
|
-
def #{c}
|
1107
|
-
return collect { |p| p.#{c} }
|
1108
|
-
end
|
1109
|
-
def #{c}=(v)
|
1110
|
-
each { |p| p.#{c} = v }
|
1111
|
-
changed
|
1112
|
-
notify_observers(self)
|
1113
|
-
nil
|
1114
|
-
end
|
1115
|
-
END_EVAL
|
1116
|
-
end
|
1117
|
-
|
1118
|
-
end # class Magick::Image::View::Pixels
|
1119
|
-
|
1120
|
-
# Magick::Image::View::Rows
|
1121
|
-
class Rows
|
1122
|
-
include Observable
|
1123
|
-
|
1124
|
-
def initialize(view, width, height, rows)
|
1125
|
-
@view = view
|
1126
|
-
@width = width
|
1127
|
-
@height = height
|
1128
|
-
@rows = rows
|
1129
|
-
end
|
1130
|
-
|
1131
|
-
def [](*args)
|
1132
|
-
cols(args)
|
1133
|
-
|
1134
|
-
# Both View::Pixels and Magick::Pixel implement Observable
|
1135
|
-
if @unique
|
1136
|
-
pixels = @view[@rows[0]*@width + @cols[0]]
|
1137
|
-
pixels.add_observer(self)
|
1138
|
-
else
|
1139
|
-
pixels = View::Pixels.new
|
1140
|
-
each do |x|
|
1141
|
-
p = @view[x]
|
1142
|
-
p.add_observer(self)
|
1143
|
-
pixels << p
|
1144
|
-
end
|
1145
|
-
end
|
1146
|
-
pixels
|
1147
|
-
end
|
1148
|
-
|
1149
|
-
def []=(*args)
|
1150
|
-
rv = args.delete_at(-1) # get rvalue
|
1151
|
-
if ! rv.is_a?(Pixel) # must be a Pixel or a color name
|
1152
|
-
begin
|
1153
|
-
rv = Pixel.from_color(rv)
|
1154
|
-
rescue TypeError
|
1155
|
-
Kernel.raise TypeError, "cannot convert #{rv.class} into Pixel"
|
1156
|
-
end
|
1157
|
-
end
|
1158
|
-
cols(args)
|
1159
|
-
each { |x| @view[x] = rv.dup }
|
1160
|
-
changed
|
1161
|
-
notify_observers(self)
|
1162
|
-
nil
|
1163
|
-
end
|
1164
|
-
|
1165
|
-
# A pixel has been modified. Tell the view.
|
1166
|
-
def update(pixel)
|
1167
|
-
changed
|
1168
|
-
notify_observers(self)
|
1169
|
-
pixel.delete_observer(self) # Don't need to hear again.
|
1170
|
-
nil
|
1171
|
-
end
|
1172
|
-
|
1173
|
-
private
|
1174
|
-
|
1175
|
-
def cols(*args)
|
1176
|
-
@cols = args[0] # remove the outermost array
|
1177
|
-
@unique = false
|
1178
|
-
|
1179
|
-
# Convert @rows to an Enumerable object
|
1180
|
-
case @rows.length
|
1181
|
-
when 0 # Create a Range for all the rows
|
1182
|
-
@rows = Range.new(0, @height, true)
|
1183
|
-
when 1 # Range, Array, or a single integer
|
1184
|
-
# if the single element is already an Enumerable
|
1185
|
-
# object, get it.
|
1186
|
-
if @rows.first.respond_to? :each
|
1187
|
-
@rows = @rows.first
|
1188
|
-
else
|
1189
|
-
@rows = Integer(@rows.first)
|
1190
|
-
if @rows < 0
|
1191
|
-
@rows += @height
|
1192
|
-
end
|
1193
|
-
if @rows < 0 || @rows > @height-1
|
1194
|
-
Kernel.raise IndexError, "index [#{@rows}] out of range"
|
1195
|
-
end
|
1196
|
-
# Convert back to an array
|
1197
|
-
@rows = Array.new(1, @rows)
|
1198
|
-
@unique = true
|
1199
|
-
end
|
1200
|
-
when 2
|
1201
|
-
# A pair of integers representing the starting column and the number of columns
|
1202
|
-
start = Integer(@rows[0])
|
1203
|
-
length = Integer(@rows[1])
|
1204
|
-
|
1205
|
-
# Negative start -> start from last row
|
1206
|
-
if start < 0
|
1207
|
-
start += @height
|
1208
|
-
end
|
1209
|
-
|
1210
|
-
if start > @height || start < 0 || length < 0
|
1211
|
-
Kernel.raise IndexError, "index [#{@rows.first}] out of range"
|
1212
|
-
else
|
1213
|
-
if start + length > @height
|
1214
|
-
length = @height - length
|
1215
|
-
length = [length, 0].max
|
1216
|
-
end
|
1217
|
-
end
|
1218
|
-
# Create a Range for the specified set of rows
|
1219
|
-
@rows = Range.new(start, start+length, true)
|
1220
|
-
end
|
1221
|
-
|
1222
|
-
case @cols.length
|
1223
|
-
when 0 # all rows
|
1224
|
-
@cols = Range.new(0, @width, true) # convert to range
|
1225
|
-
@unique = false
|
1226
|
-
when 1 # Range, Array, or a single integer
|
1227
|
-
# if the single element is already an Enumerable
|
1228
|
-
# object, get it.
|
1229
|
-
if @cols.first.respond_to? :each
|
1230
|
-
@cols = @cols.first
|
1231
|
-
@unique = false
|
1232
|
-
else
|
1233
|
-
@cols = Integer(@cols.first)
|
1234
|
-
if @cols < 0
|
1235
|
-
@cols += @width
|
1236
|
-
end
|
1237
|
-
if @cols < 0 || @cols > @width-1
|
1238
|
-
Kernel.raise IndexError, "index [#{@cols}] out of range"
|
1239
|
-
end
|
1240
|
-
# Convert back to array
|
1241
|
-
@cols = Array.new(1, @cols)
|
1242
|
-
@unique &&= true
|
1243
|
-
end
|
1244
|
-
when 2
|
1245
|
-
# A pair of integers representing the starting column and the number of columns
|
1246
|
-
start = Integer(@cols[0])
|
1247
|
-
length = Integer(@cols[1])
|
1248
|
-
|
1249
|
-
# Negative start -> start from last row
|
1250
|
-
if start < 0
|
1251
|
-
start += @width
|
1252
|
-
end
|
1253
|
-
|
1254
|
-
if start > @width || start < 0 || length < 0
|
1255
|
-
; #nop
|
1256
|
-
else
|
1257
|
-
if start + length > @width
|
1258
|
-
length = @width - length
|
1259
|
-
length = [length, 0].max
|
1260
|
-
end
|
1261
|
-
end
|
1262
|
-
# Create a Range for the specified set of columns
|
1263
|
-
@cols = Range.new(start, start+length, true)
|
1264
|
-
@unique = false
|
1265
|
-
end
|
1266
|
-
|
1267
|
-
end
|
1268
|
-
|
1269
|
-
# iterator called from subscript methods
|
1270
|
-
def each
|
1271
|
-
maxrows = @height - 1
|
1272
|
-
maxcols = @width - 1
|
1273
|
-
|
1274
|
-
@rows.each do |j|
|
1275
|
-
if j > maxrows
|
1276
|
-
Kernel.raise IndexError, "index [#{j}] out of range"
|
1277
|
-
end
|
1278
|
-
@cols.each do |i|
|
1279
|
-
if i > maxcols
|
1280
|
-
Kernel.raise IndexError, "index [#{i}] out of range"
|
1281
|
-
end
|
1282
|
-
yield j*@width + i
|
1283
|
-
end
|
1284
|
-
end
|
1285
|
-
nil # useless return value
|
1286
|
-
end
|
1287
|
-
|
1288
|
-
end # class Magick::Image::View::Rows
|
1289
|
-
|
1290
|
-
end # class Magick::Image::View
|
1291
|
-
|
1292
|
-
end # class Magick::Image
|
1293
|
-
|
1294
|
-
class ImageList
|
1295
|
-
|
1296
|
-
include Comparable
|
1297
|
-
include Enumerable
|
1298
|
-
attr_reader :scene
|
1299
|
-
|
1300
|
-
private
|
1301
|
-
|
1302
|
-
def get_current()
|
1303
|
-
return @images[@scene].__id__ rescue nil
|
1304
|
-
end
|
1305
|
-
|
1306
|
-
protected
|
1307
|
-
|
1308
|
-
def is_an_image(obj)
|
1309
|
-
unless obj.kind_of? Magick::Image
|
1310
|
-
Kernel.raise ArgumentError, "Magick::Image required (#{obj.class} given)"
|
1311
|
-
end
|
1312
|
-
true
|
1313
|
-
end
|
1314
|
-
|
1315
|
-
# Ensure array is always an array of Magick::Image objects
|
1316
|
-
def is_an_image_array(ary)
|
1317
|
-
unless ary.respond_to? :each
|
1318
|
-
Kernel.raise ArgumentError, "Magick::ImageList or array of Magick::Images required (#{ary.class} given)"
|
1319
|
-
end
|
1320
|
-
ary.each { |obj| is_an_image obj }
|
1321
|
-
true
|
1322
|
-
end
|
1323
|
-
|
1324
|
-
# Find old current image, update scene number
|
1325
|
-
# current is the id of the old current image.
|
1326
|
-
def set_current(current)
|
1327
|
-
if length() == 0
|
1328
|
-
self.scene = nil
|
1329
|
-
return
|
1330
|
-
# Don't bother looking for current image
|
1331
|
-
elsif scene() == nil || scene() >= length()
|
1332
|
-
self.scene = length() - 1
|
1333
|
-
return
|
1334
|
-
elsif current != nil
|
1335
|
-
# Find last instance of "current" in the list.
|
1336
|
-
# If "current" isn't in the list, set current to last image.
|
1337
|
-
self.scene = length() - 1
|
1338
|
-
each_with_index do |f,i|
|
1339
|
-
if f.__id__ == current
|
1340
|
-
self.scene = i
|
1341
|
-
end
|
1342
|
-
end
|
1343
|
-
return
|
1344
|
-
end
|
1345
|
-
self.scene = length() - 1
|
1346
|
-
end
|
1347
|
-
|
1348
|
-
public
|
1349
|
-
|
1350
|
-
# Allow scene to be set to nil
|
1351
|
-
def scene=(n)
|
1352
|
-
if n.nil?
|
1353
|
-
Kernel.raise IndexError, "scene number out of bounds" unless @images.length == 0
|
1354
|
-
@scene = nil
|
1355
|
-
return @scene
|
1356
|
-
elsif @images.length == 0
|
1357
|
-
Kernel.raise IndexError, "scene number out of bounds"
|
1358
|
-
end
|
1359
|
-
|
1360
|
-
n = Integer(n)
|
1361
|
-
if n < 0 || n > length - 1
|
1362
|
-
Kernel.raise IndexError, "scene number out of bounds"
|
1363
|
-
end
|
1364
|
-
@scene = n
|
1365
|
-
return @scene
|
1366
|
-
end
|
1367
|
-
|
1368
|
-
# All the binary operators work the same way.
|
1369
|
-
# 'other' should be either an ImageList or an Array
|
1370
|
-
%w{& + - |}.each do |op|
|
1371
|
-
module_eval <<-END_BINOPS
|
1372
|
-
def #{op}(other)
|
1373
|
-
ilist = self.class.new
|
1374
|
-
begin
|
1375
|
-
a = other #{op} @images
|
1376
|
-
rescue TypeError
|
1377
|
-
Kernel.raise ArgumentError, "Magick::ImageList expected, got " + other.class.to_s
|
1378
|
-
end
|
1379
|
-
current = get_current()
|
1380
|
-
a.each do |image|
|
1381
|
-
is_an_image image
|
1382
|
-
ilist << image
|
1383
|
-
end
|
1384
|
-
ilist.set_current current
|
1385
|
-
return ilist
|
1386
|
-
end
|
1387
|
-
END_BINOPS
|
1388
|
-
end
|
1389
|
-
|
1390
|
-
def *(n)
|
1391
|
-
unless n.kind_of? Integer
|
1392
|
-
Kernel.raise ArgumentError, "Integer required (#{n.class} given)"
|
1393
|
-
end
|
1394
|
-
current = get_current()
|
1395
|
-
ilist = self.class.new
|
1396
|
-
(@images * n).each {|image| ilist << image}
|
1397
|
-
ilist.set_current current
|
1398
|
-
return ilist
|
1399
|
-
end
|
1400
|
-
|
1401
|
-
def <<(obj)
|
1402
|
-
is_an_image obj
|
1403
|
-
@images << obj
|
1404
|
-
@scene = @images.length - 1
|
1405
|
-
self
|
1406
|
-
end
|
1407
|
-
|
1408
|
-
# Compare ImageLists
|
1409
|
-
# Compare each image in turn until the result of a comparison
|
1410
|
-
# is not 0. If all comparisons return 0, then
|
1411
|
-
# return if A.scene != B.scene
|
1412
|
-
# return A.length <=> B.length
|
1413
|
-
def <=>(other)
|
1414
|
-
unless other.kind_of? self.class
|
1415
|
-
Kernel.raise TypeError, "#{self.class} required (#{other.class} given)"
|
1416
|
-
end
|
1417
|
-
size = [self.length, other.length].min
|
1418
|
-
size.times do |x|
|
1419
|
-
r = self[x] <=> other[x]
|
1420
|
-
return r unless r == 0
|
1421
|
-
end
|
1422
|
-
if @scene.nil? && other.scene.nil?
|
1423
|
-
return 0
|
1424
|
-
elsif @scene.nil? && ! other.scene.nil?
|
1425
|
-
Kernel.raise TypeError, "cannot convert nil into #{other.scene.class}"
|
1426
|
-
elsif ! @scene.nil? && other.scene.nil?
|
1427
|
-
Kernel.raise TypeError, "cannot convert nil into #{self.scene.class}"
|
1428
|
-
end
|
1429
|
-
r = self.scene <=> other.scene
|
1430
|
-
return r unless r == 0
|
1431
|
-
return self.length <=> other.length
|
1432
|
-
end
|
1433
|
-
|
1434
|
-
def [](*args)
|
1435
|
-
a = @images[*args]
|
1436
|
-
if a.respond_to?(:each) then
|
1437
|
-
ilist = self.class.new
|
1438
|
-
a.each {|image| ilist << image}
|
1439
|
-
a = ilist
|
1440
|
-
end
|
1441
|
-
return a
|
1442
|
-
end
|
1443
|
-
|
1444
|
-
def []=(*args)
|
1445
|
-
obj = @images.[]=(*args)
|
1446
|
-
if obj && obj.respond_to?(:each) then
|
1447
|
-
is_an_image_array(obj)
|
1448
|
-
set_current obj.last.__id__
|
1449
|
-
elsif obj
|
1450
|
-
is_an_image(obj)
|
1451
|
-
set_current obj.__id__
|
1452
|
-
else
|
1453
|
-
set_current nil
|
1454
|
-
end
|
1455
|
-
return obj
|
1456
|
-
end
|
1457
|
-
|
1458
|
-
[:at, :each, :each_index, :empty?, :fetch,
|
1459
|
-
:first, :hash, :include?, :index, :length, :rindex, :sort!].each do |mth|
|
1460
|
-
module_eval <<-END_SIMPLE_DELEGATES
|
1461
|
-
def #{mth}(*args, &block)
|
1462
|
-
@images.#{mth}(*args, &block)
|
1463
|
-
end
|
1464
|
-
END_SIMPLE_DELEGATES
|
1465
|
-
end
|
1466
|
-
alias_method :size, :length
|
1467
|
-
|
1468
|
-
# Array#nitems is not available in 1.9
|
1469
|
-
if Array.instance_methods.include?("nitems")
|
1470
|
-
def nitems()
|
1471
|
-
@images.nitems()
|
1472
|
-
end
|
1473
|
-
end
|
1474
|
-
|
1475
|
-
def clear
|
1476
|
-
@scene = nil
|
1477
|
-
@images.clear
|
1478
|
-
end
|
1479
|
-
|
1480
|
-
def clone
|
1481
|
-
ditto = dup
|
1482
|
-
ditto.freeze if frozen?
|
1483
|
-
return ditto
|
1484
|
-
end
|
1485
|
-
|
1486
|
-
# override Enumerable#collect
|
1487
|
-
def collect(&block)
|
1488
|
-
current = get_current()
|
1489
|
-
a = @images.collect(&block)
|
1490
|
-
ilist = self.class.new
|
1491
|
-
a.each {|image| ilist << image}
|
1492
|
-
ilist.set_current current
|
1493
|
-
return ilist
|
1494
|
-
end
|
1495
|
-
|
1496
|
-
def collect!(&block)
|
1497
|
-
@images.collect!(&block)
|
1498
|
-
is_an_image_array @images
|
1499
|
-
self
|
1500
|
-
end
|
1501
|
-
|
1502
|
-
# Make a deep copy
|
1503
|
-
def copy
|
1504
|
-
ditto = self.class.new
|
1505
|
-
@images.each { |f| ditto << f.copy }
|
1506
|
-
ditto.scene = @scene
|
1507
|
-
ditto.taint if tainted?
|
1508
|
-
return ditto
|
1509
|
-
end
|
1510
|
-
|
1511
|
-
# Return the current image
|
1512
|
-
def cur_image
|
1513
|
-
if ! @scene
|
1514
|
-
Kernel.raise IndexError, "no images in this list"
|
1515
|
-
end
|
1516
|
-
@images[@scene]
|
1517
|
-
end
|
1518
|
-
|
1519
|
-
# ImageList#map took over the "map" name. Use alternatives.
|
1520
|
-
alias_method :__map__, :collect
|
1521
|
-
alias_method :map!, :collect!
|
1522
|
-
alias_method :__map__!, :collect!
|
1523
|
-
|
1524
|
-
# ImageMagic used affinity in 6.4.3, switch to remap in 6.4.4.
|
1525
|
-
alias_method :affinity, :remap
|
1526
|
-
|
1527
|
-
def compact
|
1528
|
-
current = get_current()
|
1529
|
-
ilist = self.class.new
|
1530
|
-
a = @images.compact
|
1531
|
-
a.each {|image| ilist << image}
|
1532
|
-
ilist.set_current current
|
1533
|
-
return ilist
|
1534
|
-
end
|
1535
|
-
|
1536
|
-
def compact!
|
1537
|
-
current = get_current()
|
1538
|
-
a = @images.compact! # returns nil if no changes were made
|
1539
|
-
set_current current
|
1540
|
-
return a.nil? ? nil : self
|
1541
|
-
end
|
1542
|
-
|
1543
|
-
def concat(other)
|
1544
|
-
is_an_image_array other
|
1545
|
-
other.each {|image| @images << image}
|
1546
|
-
@scene = length-1
|
1547
|
-
return self
|
1548
|
-
end
|
1549
|
-
|
1550
|
-
# Set same delay for all images
|
1551
|
-
def delay=(d)
|
1552
|
-
if Integer(d) < 0
|
1553
|
-
raise ArgumentError, "delay must be greater than or equal to 0"
|
1554
|
-
end
|
1555
|
-
@images.each { |f| f.delay = Integer(d) }
|
1556
|
-
end
|
1557
|
-
|
1558
|
-
def delete(obj, &block)
|
1559
|
-
is_an_image obj
|
1560
|
-
current = get_current()
|
1561
|
-
a = @images.delete(obj, &block)
|
1562
|
-
set_current current
|
1563
|
-
return a
|
1564
|
-
end
|
1565
|
-
|
1566
|
-
def delete_at(ndx)
|
1567
|
-
current = get_current()
|
1568
|
-
a = @images.delete_at(ndx)
|
1569
|
-
set_current current
|
1570
|
-
return a
|
1571
|
-
end
|
1572
|
-
|
1573
|
-
def delete_if(&block)
|
1574
|
-
current = get_current()
|
1575
|
-
@images.delete_if(&block)
|
1576
|
-
set_current current
|
1577
|
-
self
|
1578
|
-
end
|
1579
|
-
|
1580
|
-
def dup
|
1581
|
-
ditto = self.class.new
|
1582
|
-
@images.each {|img| ditto << img}
|
1583
|
-
ditto.scene = @scene
|
1584
|
-
ditto.taint if tainted?
|
1585
|
-
return ditto
|
1586
|
-
end
|
1587
|
-
|
1588
|
-
def eql?(other)
|
1589
|
-
is_an_image_array other
|
1590
|
-
eql = other.eql?(@images)
|
1591
|
-
begin # "other" is another ImageList
|
1592
|
-
eql &&= @scene == other.scene
|
1593
|
-
rescue NoMethodError
|
1594
|
-
# "other" is a plain Array
|
1595
|
-
end
|
1596
|
-
return eql
|
1597
|
-
end
|
1598
|
-
|
1599
|
-
def fill(*args, &block)
|
1600
|
-
is_an_image args[0] unless block_given?
|
1601
|
-
current = get_current()
|
1602
|
-
@images.fill(*args, &block)
|
1603
|
-
is_an_image_array self
|
1604
|
-
set_current current
|
1605
|
-
self
|
1606
|
-
end
|
1607
|
-
|
1608
|
-
# Override Enumerable's find_all
|
1609
|
-
def find_all(&block)
|
1610
|
-
current = get_current()
|
1611
|
-
a = @images.find_all(&block)
|
1612
|
-
ilist = self.class.new
|
1613
|
-
a.each {|image| ilist << image}
|
1614
|
-
ilist.set_current current
|
1615
|
-
return ilist
|
1616
|
-
end
|
1617
|
-
alias_method :select, :find_all
|
1618
|
-
|
1619
|
-
def from_blob(*blobs, &block)
|
1620
|
-
if (blobs.length == 0)
|
1621
|
-
Kernel.raise ArgumentError, "no blobs given"
|
1622
|
-
end
|
1623
|
-
blobs.each { |b|
|
1624
|
-
Magick::Image.from_blob(b, &block).each { |n| @images << n }
|
1625
|
-
}
|
1626
|
-
@scene = length - 1
|
1627
|
-
self
|
1628
|
-
end
|
1629
|
-
|
1630
|
-
# Initialize new instances
|
1631
|
-
def initialize(*filenames, &block)
|
1632
|
-
@images = []
|
1633
|
-
@scene = nil
|
1634
|
-
filenames.each { |f|
|
1635
|
-
Magick::Image.read(f, &block).each { |n| @images << n }
|
1636
|
-
}
|
1637
|
-
if length > 0
|
1638
|
-
@scene = length - 1 # last image in array
|
1639
|
-
end
|
1640
|
-
self
|
1641
|
-
end
|
1642
|
-
|
1643
|
-
def insert(index, *args)
|
1644
|
-
args.each {|image| is_an_image image}
|
1645
|
-
current = get_current()
|
1646
|
-
@images.insert(index, *args)
|
1647
|
-
set_current current
|
1648
|
-
return self
|
1649
|
-
end
|
1650
|
-
|
1651
|
-
# Call inspect for all the images
|
1652
|
-
def inspect
|
1653
|
-
img = []
|
1654
|
-
@images.each {|image| img << image.inspect }
|
1655
|
-
img = "[" + img.join(",\n") + "]\nscene=#{@scene}"
|
1656
|
-
end
|
1657
|
-
|
1658
|
-
# Set the number of iterations of an animated GIF
|
1659
|
-
def iterations=(n)
|
1660
|
-
n = Integer(n)
|
1661
|
-
if n < 0 || n > 65535
|
1662
|
-
Kernel.raise ArgumentError, "iterations must be between 0 and 65535"
|
1663
|
-
end
|
1664
|
-
@images.each {|f| f.iterations=n}
|
1665
|
-
self
|
1666
|
-
end
|
1667
|
-
|
1668
|
-
def last(*args)
|
1669
|
-
if args.length == 0
|
1670
|
-
a = @images.last
|
1671
|
-
else
|
1672
|
-
a = @images.last(*args)
|
1673
|
-
ilist = self.class.new
|
1674
|
-
a.each {|img| ilist << img}
|
1675
|
-
@scene = a.length - 1
|
1676
|
-
a = ilist
|
1677
|
-
end
|
1678
|
-
return a
|
1679
|
-
end
|
1680
|
-
|
1681
|
-
# Custom marshal/unmarshal for Ruby 1.8.
|
1682
|
-
def marshal_dump()
|
1683
|
-
ary = [@scene]
|
1684
|
-
@images.each {|i| ary << Marshal.dump(i)}
|
1685
|
-
ary
|
1686
|
-
end
|
1687
|
-
|
1688
|
-
def marshal_load(ary)
|
1689
|
-
@scene = ary.shift
|
1690
|
-
@images = []
|
1691
|
-
ary.each {|a| @images << Marshal.load(a)}
|
1692
|
-
end
|
1693
|
-
|
1694
|
-
# The ImageList class supports the Magick::Image class methods by simply sending
|
1695
|
-
# the method to the current image. If the method isn't explicitly supported,
|
1696
|
-
# send it to the current image in the array. If there are no images, send
|
1697
|
-
# it up the line. Catch a NameError and emit a useful message.
|
1698
|
-
def method_missing(methID, *args, &block)
|
1699
|
-
begin
|
1700
|
-
if @scene
|
1701
|
-
@images[@scene].send(methID, *args, &block)
|
1702
|
-
else
|
1703
|
-
super
|
1704
|
-
end
|
1705
|
-
rescue NoMethodError
|
1706
|
-
Kernel.raise NoMethodError, "undefined method `#{methID.id2name}' for #{self.class}"
|
1707
|
-
rescue Exception
|
1708
|
-
$@.delete_if { |s| /:in `send'$/.match(s) || /:in `method_missing'$/.match(s) }
|
1709
|
-
Kernel.raise
|
1710
|
-
end
|
1711
|
-
end
|
1712
|
-
|
1713
|
-
# Create a new image and add it to the end
|
1714
|
-
def new_image(cols, rows, *fill, &info_blk)
|
1715
|
-
self << Magick::Image.new(cols, rows, *fill, &info_blk)
|
1716
|
-
end
|
1717
|
-
|
1718
|
-
def partition(&block)
|
1719
|
-
a = @images.partition(&block)
|
1720
|
-
t = self.class.new
|
1721
|
-
a[0].each { |img| t << img}
|
1722
|
-
t.set_current nil
|
1723
|
-
f = self.class.new
|
1724
|
-
a[1].each { |img| f << img}
|
1725
|
-
f.set_current nil
|
1726
|
-
[t, f]
|
1727
|
-
end
|
1728
|
-
|
1729
|
-
# Ping files and concatenate the new images
|
1730
|
-
def ping(*files, &block)
|
1731
|
-
if (files.length == 0)
|
1732
|
-
Kernel.raise ArgumentError, "no files given"
|
1733
|
-
end
|
1734
|
-
files.each { |f|
|
1735
|
-
Magick::Image.ping(f, &block).each { |n| @images << n }
|
1736
|
-
}
|
1737
|
-
@scene = length - 1
|
1738
|
-
self
|
1739
|
-
end
|
1740
|
-
|
1741
|
-
def pop
|
1742
|
-
current = get_current()
|
1743
|
-
a = @images.pop # can return nil
|
1744
|
-
set_current current
|
1745
|
-
return a
|
1746
|
-
end
|
1747
|
-
|
1748
|
-
def push(*objs)
|
1749
|
-
objs.each do |image|
|
1750
|
-
is_an_image image
|
1751
|
-
@images << image
|
1752
|
-
end
|
1753
|
-
@scene = length - 1
|
1754
|
-
self
|
1755
|
-
end
|
1756
|
-
|
1757
|
-
# Read files and concatenate the new images
|
1758
|
-
def read(*files, &block)
|
1759
|
-
if (files.length == 0)
|
1760
|
-
Kernel.raise ArgumentError, "no files given"
|
1761
|
-
end
|
1762
|
-
files.each { |f|
|
1763
|
-
Magick::Image.read(f, &block).each { |n| @images << n }
|
1764
|
-
}
|
1765
|
-
@scene = length - 1
|
1766
|
-
self
|
1767
|
-
end
|
1768
|
-
|
1769
|
-
# override Enumerable's reject
|
1770
|
-
def reject(&block)
|
1771
|
-
current = get_current()
|
1772
|
-
ilist = self.class.new
|
1773
|
-
a = @images.reject(&block)
|
1774
|
-
a.each {|image| ilist << image}
|
1775
|
-
ilist.set_current current
|
1776
|
-
return ilist
|
1777
|
-
end
|
1778
|
-
|
1779
|
-
def reject!(&block)
|
1780
|
-
current = get_current()
|
1781
|
-
a = @images.reject!(&block)
|
1782
|
-
@images = a if !a.nil?
|
1783
|
-
set_current current
|
1784
|
-
return a.nil? ? nil : self
|
1785
|
-
end
|
1786
|
-
|
1787
|
-
def replace(other)
|
1788
|
-
is_an_image_array other
|
1789
|
-
current = get_current()
|
1790
|
-
@images.clear
|
1791
|
-
other.each {|image| @images << image}
|
1792
|
-
@scene = self.length == 0 ? nil : 0
|
1793
|
-
set_current current
|
1794
|
-
self
|
1795
|
-
end
|
1796
|
-
|
1797
|
-
# Ensure respond_to? answers correctly when we are delegating to Image
|
1798
|
-
alias_method :__respond_to__?, :respond_to?
|
1799
|
-
def respond_to?(methID, priv=false)
|
1800
|
-
return true if __respond_to__?(methID, priv)
|
1801
|
-
if @scene
|
1802
|
-
@images[@scene].respond_to?(methID, priv)
|
1803
|
-
else
|
1804
|
-
super
|
1805
|
-
end
|
1806
|
-
end
|
1807
|
-
|
1808
|
-
def reverse
|
1809
|
-
current = get_current()
|
1810
|
-
a = self.class.new
|
1811
|
-
@images.reverse_each {|image| a << image}
|
1812
|
-
a.set_current current
|
1813
|
-
return a
|
1814
|
-
end
|
1815
|
-
|
1816
|
-
def reverse!
|
1817
|
-
current = get_current()
|
1818
|
-
@images.reverse!
|
1819
|
-
set_current current
|
1820
|
-
self
|
1821
|
-
end
|
1822
|
-
|
1823
|
-
def reverse_each
|
1824
|
-
@images.reverse_each {|image| yield(image)}
|
1825
|
-
self
|
1826
|
-
end
|
1827
|
-
|
1828
|
-
def shift
|
1829
|
-
current = get_current()
|
1830
|
-
a = @images.shift
|
1831
|
-
set_current current
|
1832
|
-
return a
|
1833
|
-
end
|
1834
|
-
|
1835
|
-
def slice(*args)
|
1836
|
-
current = get_current()
|
1837
|
-
slice = @images.slice(*args)
|
1838
|
-
if slice
|
1839
|
-
ilist = self.class.new
|
1840
|
-
if slice.respond_to?(:each) then
|
1841
|
-
slice.each {|image| ilist << image}
|
1842
|
-
else
|
1843
|
-
ilist << slice
|
1844
|
-
end
|
1845
|
-
else
|
1846
|
-
ilist = nil
|
1847
|
-
end
|
1848
|
-
return ilist
|
1849
|
-
end
|
1850
|
-
|
1851
|
-
def slice!(*args)
|
1852
|
-
current = get_current()
|
1853
|
-
a = @images.slice!(*args)
|
1854
|
-
set_current current
|
1855
|
-
return a
|
1856
|
-
end
|
1857
|
-
|
1858
|
-
def ticks_per_second=(t)
|
1859
|
-
if Integer(t) < 0
|
1860
|
-
Kernel.raise ArgumentError, "ticks_per_second must be greater than or equal to 0"
|
1861
|
-
end
|
1862
|
-
@images.each { |f| f.ticks_per_second = Integer(t) }
|
1863
|
-
end
|
1864
|
-
|
1865
|
-
def to_a
|
1866
|
-
a = Array.new
|
1867
|
-
@images.each {|image| a << image}
|
1868
|
-
return a
|
1869
|
-
end
|
1870
|
-
|
1871
|
-
def uniq
|
1872
|
-
current = get_current()
|
1873
|
-
a = self.class.new
|
1874
|
-
@images.uniq.each {|image| a << image}
|
1875
|
-
a.set_current current
|
1876
|
-
return a
|
1877
|
-
end
|
1878
|
-
|
1879
|
-
def uniq!(*args)
|
1880
|
-
current = get_current()
|
1881
|
-
a = @images.uniq!
|
1882
|
-
set_current current
|
1883
|
-
return a.nil? ? nil : self
|
1884
|
-
end
|
1885
|
-
|
1886
|
-
# @scene -> new object
|
1887
|
-
def unshift(obj)
|
1888
|
-
is_an_image obj
|
1889
|
-
@images.unshift(obj)
|
1890
|
-
@scene = 0
|
1891
|
-
self
|
1892
|
-
end
|
1893
|
-
|
1894
|
-
def values_at(*args)
|
1895
|
-
a = @images.values_at(*args)
|
1896
|
-
a = self.class.new
|
1897
|
-
@images.values_at(*args).each {|image| a << image}
|
1898
|
-
a.scene = a.length - 1
|
1899
|
-
return a
|
1900
|
-
end
|
1901
|
-
alias_method :indexes, :values_at
|
1902
|
-
alias_method :indices, :values_at
|
1903
|
-
|
1904
|
-
end # Magick::ImageList
|
1905
|
-
|
1906
|
-
|
1907
|
-
# Collects non-specific optional method arguments
|
1908
|
-
class OptionalMethodArguments
|
1909
|
-
def initialize(img)
|
1910
|
-
@img = img
|
1911
|
-
end
|
1912
|
-
|
1913
|
-
# miscellaneous options like -verbose
|
1914
|
-
def method_missing(mth, val)
|
1915
|
-
@img.define(mth.to_s.tr('_', '-'), val)
|
1916
|
-
end
|
1917
|
-
|
1918
|
-
# set(key, val) corresponds to -set option:key val
|
1919
|
-
def define(key, val = nil)
|
1920
|
-
@img.define(key, val)
|
1921
|
-
end
|
1922
|
-
|
1923
|
-
# accepts Pixel object or color name
|
1924
|
-
def highlight_color=(color)
|
1925
|
-
color = @img.to_color(color) if color.respond_to?(:to_color)
|
1926
|
-
@img.define("highlight-color", color)
|
1927
|
-
end
|
1928
|
-
|
1929
|
-
# accepts Pixel object or color name
|
1930
|
-
def lowlight_color=(color)
|
1931
|
-
color = @img.to_color(color) if color.respond_to?(:to_color)
|
1932
|
-
@img.define("lowlight-color", color)
|
1933
|
-
end
|
1934
|
-
end
|
1935
|
-
|
1936
|
-
|
1937
|
-
# Example fill class. Fills the image with the specified background
|
1938
|
-
# color, then crosshatches with the specified crosshatch color.
|
1939
|
-
# @dist is the number of pixels between hatch lines.
|
1940
|
-
# See Magick::Draw examples.
|
1941
|
-
class HatchFill
|
1942
|
-
def initialize(bgcolor, hatchcolor="white", dist=10)
|
1943
|
-
@bgcolor = bgcolor
|
1944
|
-
@hatchpixel = Pixel.from_color(hatchcolor)
|
1945
|
-
@dist = dist
|
1946
|
-
end
|
1947
|
-
|
1948
|
-
def fill(img) # required
|
1949
|
-
img.background_color = @bgcolor
|
1950
|
-
img.erase! # sets image to background color
|
1951
|
-
pixels = Array.new([img.rows, img.columns].max, @hatchpixel)
|
1952
|
-
@dist.step((img.columns-1)/@dist*@dist, @dist) { |x|
|
1953
|
-
img.store_pixels(x,0,1,img.rows,pixels)
|
1954
|
-
}
|
1955
|
-
@dist.step((img.rows-1)/@dist*@dist, @dist) { |y|
|
1956
|
-
img.store_pixels(0,y,img.columns,1,pixels)
|
1957
|
-
}
|
1958
|
-
end
|
1959
|
-
end
|
1960
|
-
|
1961
|
-
end # Magick
|
1962
|
-
|
1
|
+
require 'rmagick_internal.rb'
|