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.

Files changed (54) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +21 -0
  3. data/.travis.yml +57 -0
  4. data/CONTRIBUTING.md +22 -0
  5. data/ChangeLog +47 -4
  6. data/Gemfile +7 -0
  7. data/{README.rc → README.textile} +34 -104
  8. data/Rakefile +160 -24
  9. data/before_install_linux.sh +12 -0
  10. data/before_install_osx.sh +2 -0
  11. data/doc/.cvsignore +1 -0
  12. data/doc/ex/images/image_with_profile.jpg +0 -0
  13. data/doc/ex/mask.rb +1 -1
  14. data/examples/identify.rb +1 -1
  15. data/ext/RMagick/extconf.rb +60 -23
  16. data/ext/RMagick/rmagick.c +15 -15
  17. data/ext/RMagick/rmagick.h +9 -7
  18. data/ext/RMagick/rmdraw.c +12 -12
  19. data/ext/RMagick/rmenum.c +2 -2
  20. data/ext/RMagick/rmfill.c +25 -25
  21. data/ext/RMagick/rmilist.c +121 -104
  22. data/ext/RMagick/rmimage.c +737 -546
  23. data/ext/RMagick/rminfo.c +15 -15
  24. data/ext/RMagick/rmmain.c +27 -3
  25. data/ext/RMagick/rmpixel.c +25 -27
  26. data/ext/RMagick/rmstruct.c +1 -1
  27. data/ext/RMagick/rmutil.c +18 -18
  28. data/lib/RMagick.rb +1 -1962
  29. data/lib/rmagick.rb +1 -0
  30. data/lib/rmagick/version.rb +3 -1
  31. data/lib/rmagick_internal.rb +1964 -0
  32. data/rmagick.gemspec +14 -5
  33. data/test/Image2.rb +7 -3
  34. data/test/Image3.rb +54 -23
  35. data/test/ImageList2.rb +1 -1
  36. data/test/Image_attributes.rb +27 -10
  37. data/test/Import_Export.rb +11 -1
  38. data/test/Info.rb +4 -4
  39. data/test/Magick.rb +14 -54
  40. data/test/Pixel.rb +3 -4
  41. data/test/{all_basic.rb → test_all_basic.rb} +9 -17
  42. data/test/tmpnam_test.rb +50 -0
  43. metadata +50 -21
  44. data/README +0 -15
  45. data/README-Mac-OSX.txt +0 -1
  46. data/build_tarball.rake +0 -215
  47. data/lib/rvg/to_c.rb +0 -103
  48. data/metaconfig +0 -7
  49. data/pkg/rmagick-2.13.3.rc1.gem +0 -0
  50. data/post-clean.rb +0 -12
  51. data/post-install.rb +0 -50
  52. data/post-setup.rb +0 -254
  53. data/setup.rb +0 -1585
  54. data/uninstall.rb +0 -76
@@ -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
- GetExceptionInfo(&exception);
107
+ exception = AcquireExceptionInfo();
108
108
  name = StringValuePtr(color);
109
- okay = QueryColorDatabase(name, &pp, &exception);
110
- (void) DestroyExceptionInfo(&exception);
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 QuantumDepth == 16 || QuantumDepth == 32 || QuantumDepth == 64
963
+ #if MAGICKCORE_QUANTUM_DEPTH == 16 || MAGICKCORE_QUANTUM_DEPTH == 32 || MAGICKCORE_QUANTUM_DEPTH == 64
964
964
  case 16:
965
- #if QuantumDepth == 32 || QuantumDepth == 64
965
+ #if MAGICKCORE_QUANTUM_DEPTH == 32 || MAGICKCORE_QUANTUM_DEPTH == 64
966
966
  case 32:
967
- #if QuantumDepth == 64
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
- GetExceptionInfo(&exception);
1368
- magick_info = GetMagickInfo(info->magick, &exception);
1369
- (void) DestroyExceptionInfo(&exception);
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
- GetExceptionInfo(&exception);
1397
+ exception = AcquireExceptionInfo();
1398
1398
 
1399
1399
  mgk = StringValuePtr(magick);
1400
- m = GetMagickInfo(mgk, &exception);
1400
+ m = GetMagickInfo(mgk, exception);
1401
1401
  CHECK_EXCEPTION()
1402
- (void) DestroyExceptionInfo(&exception);
1402
+ (void) DestroyExceptionInfo(exception);
1403
1403
 
1404
1404
  if (!m)
1405
1405
  {
@@ -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(QuantumDepth);
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(LABColorspace)
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
+ }
@@ -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
- GetExceptionInfo(&exception);
222
+ exception = AcquireExceptionInfo();
223
223
  name = StringValuePtr(name_arg);
224
- okay = QueryColorDatabase(name, color, &exception);
225
- (void) DestroyExceptionInfo(&exception);
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
- GetExceptionInfo(&exception);
453
- okay = QueryColorDatabase(StringValuePtr(name), &pp, &exception);
452
+ exception = AcquireExceptionInfo();
453
+ okay = QueryColorDatabase(StringValuePtr(name), &pp, exception);
454
454
  CHECK_EXCEPTION()
455
- (void) DestroyExceptionInfo(&exception);
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
- GetExceptionInfo(&exception);
550
+ exception = AcquireExceptionInfo();
551
551
 
552
- (void) QueryMagickColor(name, &pp, &exception);
552
+ (void) QueryMagickColor(name, &pp, exception);
553
553
  CHECK_EXCEPTION()
554
554
 
555
- (void) DestroyExceptionInfo(&exception);
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 = ROUND_TO_QUANTUM(QuantumRange - (pixel->opacity / QuantumRange));
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 QuantumDepth
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 = QuantumDepth;
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 QuantumDepth == 16 || QuantumDepth == 32
997
+ #if MAGICKCORE_QUANTUM_DEPTH == 16 || MAGICKCORE_QUANTUM_DEPTH == 32
1000
998
  case 16:
1001
999
  #endif
1002
- #if QuantumDepth == 32
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
- GetExceptionInfo(&exception);
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, &exception);
1045
+ (void) QueryMagickColorname(image, &mpp, compliance, name, exception);
1048
1046
  }
1049
1047
  #else
1050
- (void) QueryMagickColorname(image, &mpp, compliance, hex, name, &exception);
1048
+ (void) QueryMagickColorname(image, &mpp, compliance, hex, name, exception);
1051
1049
  #endif
1052
1050
  (void) DestroyImage(image);
1053
1051
  CHECK_EXCEPTION()
1054
- (void) DestroyExceptionInfo(&exception);
1052
+ (void) DestroyExceptionInfo(exception);
1055
1053
 
1056
1054
  // Always return a string, even if it's ""
1057
1055
  return rb_str_new2(name);
@@ -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 (QuantumDepth == 32 || QuantumDepth == 64) && defined(HAVE_TYPE_LONG_DOUBLE)
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 ",
@@ -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
- GetExceptionInfo(&exception);
632
+ exception = AcquireExceptionInfo();
633
633
 
634
- (void) QueryColorname(image, color, X11Compliance, name, &exception);
634
+ (void) QueryColorname(image, color, X11Compliance, name, exception);
635
635
  CHECK_EXCEPTION()
636
- (void) DestroyExceptionInfo(&exception);
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
- GetExceptionInfo(&exception);
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, &exception);
725
+ okay = SetImageRegistry(ImageRegistryType, temp_name+5, image, exception);
726
726
  CHECK_EXCEPTION()
727
- DestroyExceptionInfo(&exception);
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
- GetExceptionInfo(&exception);
1395
- clone = CloneImage(image, 0, 0, MagickTrue, &exception);
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(&exception, clone, DestroyOnError);
1401
- (void) DestroyExceptionInfo(&exception);
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
- GetExceptionInfo(&exception);
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(&exception, &badboy->exception);
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(&exception, imglist, retention);
1519
+ rm_check_exception(exception, imglist, retention);
1520
1520
  }
1521
1521
 
1522
- (void) DestroyExceptionInfo(&exception);
1522
+ (void) DestroyExceptionInfo(exception);
1523
1523
  }
1524
1524
 
1525
1525
 
@@ -1,1962 +1 @@
1
- # $Id: RMagick.rb,v 1.84 2009/09/15 22:08:41 rmagick Exp $
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'