rmagick4j 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. data/History.txt +84 -0
  2. data/LICENSE.txt +9 -0
  3. data/Manifest.txt +142 -0
  4. data/README.txt +25 -0
  5. data/Rakefile +109 -0
  6. data/lib/RMagick.rb +1861 -0
  7. data/lib/jhlabs-filters.jar +0 -0
  8. data/lib/magick4j.jar +0 -0
  9. data/lib/rmagick4j/constants.rb +21 -0
  10. data/lib/rmagick4j/draw.rb +86 -0
  11. data/lib/rmagick4j/enum.rb +120 -0
  12. data/lib/rmagick4j/gradient_fill.rb +14 -0
  13. data/lib/rmagick4j/image.rb +289 -0
  14. data/lib/rmagick4j/image_list.rb +21 -0
  15. data/lib/rmagick4j/pixel.rb +170 -0
  16. data/lib/rmagick4j/rmagick4j.rb +19 -0
  17. data/lib/rmagick4j/texture_fill.rb +14 -0
  18. data/lib/rmagick4j/type_metric.rb +8 -0
  19. data/lib/rmagick4j/version.rb +5 -0
  20. data/lib/rvg/clippath.rb +48 -0
  21. data/lib/rvg/container.rb +131 -0
  22. data/lib/rvg/deep_equal.rb +56 -0
  23. data/lib/rvg/describable.rb +53 -0
  24. data/lib/rvg/embellishable.rb +417 -0
  25. data/lib/rvg/misc.rb +739 -0
  26. data/lib/rvg/paint.rb +55 -0
  27. data/lib/rvg/pathdata.rb +131 -0
  28. data/lib/rvg/rvg.rb +283 -0
  29. data/lib/rvg/stretchable.rb +168 -0
  30. data/lib/rvg/stylable.rb +118 -0
  31. data/lib/rvg/text.rb +187 -0
  32. data/lib/rvg/transformable.rb +133 -0
  33. data/lib/rvg/units.rb +66 -0
  34. data/lib/svgsalamander.jar +0 -0
  35. data/test/RMagickTestSuite.rb +129 -0
  36. data/test/eyetests/bullseye.rb +150 -0
  37. data/test/eyetests/tests/draw_arc_basic.rb +16 -0
  38. data/test/eyetests/tests/draw_circle_affine.rb +19 -0
  39. data/test/eyetests/tests/draw_circle_basic.rb +16 -0
  40. data/test/eyetests/tests/draw_ellipse_affine.rb +19 -0
  41. data/test/eyetests/tests/draw_ellipse_basic.rb +16 -0
  42. data/test/eyetests/tests/draw_line_affine.rb +24 -0
  43. data/test/eyetests/tests/draw_line_basic.rb +20 -0
  44. data/test/eyetests/tests/draw_pattern_1.rb +35 -0
  45. data/test/eyetests/tests/draw_polygon_affine.rb +21 -0
  46. data/test/eyetests/tests/draw_polygon_basic.rb +19 -0
  47. data/test/eyetests/tests/draw_polyline_affine.rb +19 -0
  48. data/test/eyetests/tests/draw_polyline_basic.rb +18 -0
  49. data/test/eyetests/tests/draw_rectangle_affine.rb +16 -0
  50. data/test/eyetests/tests/draw_rectangle_basic.rb +16 -0
  51. data/test/eyetests/tests/draw_rmagick_test_01.rb +35 -0
  52. data/test/eyetests/tests/draw_rmagick_test_02.rb +40 -0
  53. data/test/eyetests/tests/draw_rmagick_test_03.rb +17 -0
  54. data/test/eyetests/tests/draw_rmagick_test_04.rb +21 -0
  55. data/test/eyetests/tests/draw_roundrectangle_affine.rb +19 -0
  56. data/test/eyetests/tests/draw_roundrectangle_basic.rb +16 -0
  57. data/test/eyetests/tests/gradient_fill_horizontal_diagonal_fill.rb +9 -0
  58. data/test/eyetests/tests/gradient_fill_horizontal_fill.rb +9 -0
  59. data/test/eyetests/tests/gradient_fill_point_fill.rb +9 -0
  60. data/test/eyetests/tests/gradient_fill_vertical_diagonal_fill.rb +9 -0
  61. data/test/eyetests/tests/gradient_fill_vertical_fill.rb +9 -0
  62. data/test/eyetests/tests/gruff_accumulator_bar.rb +29 -0
  63. data/test/eyetests/tests/gruff_area_1.rb +16 -0
  64. data/test/eyetests/tests/gruff_area_2.rb +27 -0
  65. data/test/eyetests/tests/gruff_bar_1.rb +29 -0
  66. data/test/eyetests/tests/gruff_bar_2.rb +19 -0
  67. data/test/eyetests/tests/gruff_line_1.rb +13 -0
  68. data/test/eyetests/tests/gruff_line_2.rb +20 -0
  69. data/test/eyetests/tests/gruff_net_1.rb +16 -0
  70. data/test/eyetests/tests/gruff_net_2.rb +16 -0
  71. data/test/eyetests/tests/gruff_pie_1.rb +21 -0
  72. data/test/eyetests/tests/gruff_pie_2.rb +22 -0
  73. data/test/eyetests/tests/gruff_scene_1.rb +14 -0
  74. data/test/eyetests/tests/gruff_scene_2.rb +14 -0
  75. data/test/eyetests/tests/gruff_sidebar.rb +32 -0
  76. data/test/eyetests/tests/gruff_sidestacked_bar_1.rb +26 -0
  77. data/test/eyetests/tests/gruff_sidestacked_bar_2.rb +26 -0
  78. data/test/eyetests/tests/gruff_spider_1.rb +22 -0
  79. data/test/eyetests/tests/gruff_spider_2.rb +15 -0
  80. data/test/eyetests/tests/gruff_stacked_bar_1.rb +23 -0
  81. data/test/eyetests/tests/gruff_stacked_bar_2.rb +26 -0
  82. data/test/eyetests/tests/hatch_fill.rb +9 -0
  83. data/test/eyetests/tests/image_list_flatten_images.rb +15 -0
  84. data/test/eyetests/tests/new_image.rb +24 -0
  85. data/test/eyetests/tests/path_a_command_01.rb +16 -0
  86. data/test/eyetests/tests/path_a_command_02.rb +19 -0
  87. data/test/eyetests/tests/path_c_command_01.rb +16 -0
  88. data/test/eyetests/tests/path_c_command_02.rb +16 -0
  89. data/test/eyetests/tests/path_complex_01.rb +16 -0
  90. data/test/eyetests/tests/path_espiral_01.rb +28 -0
  91. data/test/eyetests/tests/path_h_command_01.rb +16 -0
  92. data/test/eyetests/tests/path_h_command_02.rb +16 -0
  93. data/test/eyetests/tests/path_l_command_01.rb +16 -0
  94. data/test/eyetests/tests/path_l_command_02.rb +16 -0
  95. data/test/eyetests/tests/path_m_command_01.rb +16 -0
  96. data/test/eyetests/tests/path_m_command_02.rb +16 -0
  97. data/test/eyetests/tests/path_m_command_03.rb +16 -0
  98. data/test/eyetests/tests/path_q_command_01.rb +16 -0
  99. data/test/eyetests/tests/path_q_command_02.rb +16 -0
  100. data/test/eyetests/tests/path_q_command_03.rb +25 -0
  101. data/test/eyetests/tests/path_s_command_01.rb +16 -0
  102. data/test/eyetests/tests/path_s_command_02.rb +16 -0
  103. data/test/eyetests/tests/path_star_01.rb +16 -0
  104. data/test/eyetests/tests/path_t_command_01.rb +16 -0
  105. data/test/eyetests/tests/path_t_command_02.rb +16 -0
  106. data/test/eyetests/tests/path_v_command_01.rb +16 -0
  107. data/test/eyetests/tests/path_v_command_02.rb +16 -0
  108. data/test/eyetests/tests/store_pixel_smiley.rb +123 -0
  109. data/test/eyetests/tests/texture_fill.rb +11 -0
  110. data/test/gruff_tests/test/gruff_test_case.rb +120 -0
  111. data/test/gruff_tests/test/monkey_gruff.rb +7 -0
  112. data/test/gruff_tests/test/test_accumulator_bar.rb +50 -0
  113. data/test/gruff_tests/test/test_area.rb +134 -0
  114. data/test/gruff_tests/test/test_bar.rb +283 -0
  115. data/test/gruff_tests/test/test_base.rb +8 -0
  116. data/test/gruff_tests/test/test_bullet.rb +26 -0
  117. data/test/gruff_tests/test/test_legend.rb +71 -0
  118. data/test/gruff_tests/test/test_line.rb +493 -0
  119. data/test/gruff_tests/test/test_mini_bar.rb +32 -0
  120. data/test/gruff_tests/test/test_mini_pie.rb +20 -0
  121. data/test/gruff_tests/test/test_mini_side_bar.rb +37 -0
  122. data/test/gruff_tests/test/test_net.rb +230 -0
  123. data/test/gruff_tests/test/test_photo.rb +41 -0
  124. data/test/gruff_tests/test/test_pie.rb +154 -0
  125. data/test/gruff_tests/test/test_scene.rb +100 -0
  126. data/test/gruff_tests/test/test_side_bar.rb +12 -0
  127. data/test/gruff_tests/test/test_sidestacked_bar.rb +89 -0
  128. data/test/gruff_tests/test/test_spider.rb +216 -0
  129. data/test/gruff_tests/test/test_stacked_bar.rb +52 -0
  130. data/test/images/clown.jpg +0 -0
  131. data/test/images/texture.jpg +0 -0
  132. data/test/implemented_methods.rb +18 -0
  133. data/test/spec/draw_spec.rb +24 -0
  134. data/test/spec/image_constants.rb +76 -0
  135. data/test/spec/image_spec.rb +23 -0
  136. data/test/spec/pixel_spec.rb +84 -0
  137. data/test/spec/stories/geometry_runner.rb +13 -0
  138. data/test/spec/stories/geometry_steps.rb +24 -0
  139. data/test/spec/stories/geometry_story.rb +116 -0
  140. data/test/spec/stories/image_filling_runner.rb +13 -0
  141. data/test/spec/stories/image_filling_steps.rb +64 -0
  142. data/test/spec/stories/image_filling_story.rb +41 -0
  143. metadata +215 -0
data/History.txt ADDED
@@ -0,0 +1,84 @@
1
+ == 0.3.5
2
+
3
+ - Implemented Draw primitives (affice, arc, pattern, path)
4
+
5
+ - Improved Image and ImageList:
6
+ - crop ( http://www.imagemagick.org/RMagick/doc/image1.html#crop )
7
+ - rotate ( http://www.imagemagick.org/RMagick/doc/image3.html#rotate )
8
+ - store_pixels (http://www.imagemagick.org/RMagick/doc/image3.html#store_pixels )
9
+ - flatten_images (http://www.imagemagick.org/RMagick/doc/ilist.html#flatten_images )
10
+
11
+ - Implemented more of Pixel (from_HSL, to_HSL, <=>, cmp, intensity)
12
+
13
+ - Implemented the fill classes (http://www.imagemagick.org/RMagick/doc/struct.html#fill )
14
+
15
+ - Added a side-by-side (MRI vs JRuby) image testing tool named Bullseye
16
+
17
+ - Added 680 color names. It can search, but not retrieve the name correctly capitalized.
18
+
19
+ == 0.3.4
20
+
21
+ - No bugfixes nor new features, just added compatibility with JRuby 1.1.
22
+
23
+ == 0.3.3
24
+
25
+ - This is a bug fix release to support updates to the string byte array conversion changes in recent versions of JRuby. This release was tested against JRuby 1.0.0RC3.
26
+
27
+ == 0.3.2
28
+
29
+ Full RMagick required for RMagick4J is included. The gem also includes JH Labs Filters and SVG Salamander, but they aren't much used yet.
30
+
31
+ Use it like so for now:
32
+
33
+ require 'rubygems'
34
+ gem PLATFORM == 'java' ? 'rmagick4j' : 'rmagick'
35
+ require 'RMagick'
36
+
37
+ Also see the project home page at http://code.google.com/p/rmagick4j/
38
+
39
+ == 0.3.1
40
+
41
+ Functionally equivalent to 0.3 release, but it supports JRuby 0.9.8 now. Also, it's a gem. It works along with a standard rmagick gem already installed (for those who keep gems in one place for multiple Ruby installs). For instance, I've tested this as working under both ruby and jruby:
42
+
43
+ require 'rubygems'
44
+ gem 'rmagick4j'
45
+ gem 'rmagick'
46
+ require 'gruff'
47
+
48
+ I hope to release a full rmagick gem in the near future for those who want to use apps unchanged and have a local GEM_HOME just for jruby.
49
+
50
+ == 0.3.0
51
+
52
+ More complete Draw features. Supports most Gruff Graphs unit tests.
53
+
54
+ == 0.2
55
+
56
+ More I/O support including basic handling of write, to_blob, and from_blob. Also upgraded RMagick libs to 1.14.0 and bundled JRuby to 0.9.1. Also reorganized the directory layout better. Moved JRuby files to its own folder. Moved draw support into magickjr package.
57
+
58
+ == 0.1
59
+
60
+ Hello,
61
+
62
+ I've tagged an "m1" release of RMagickJr. Here are the main good things to say about it:
63
+
64
+ - At varying degrees of quality and hackishness, it runs all these samples: http://rmagick.rubyforge.org/portfolio.html
65
+ - The build.xml works and by default shows a sample picture.
66
+
67
+ Put together, this means that other people might be able to run it, and they might be able to put it to some simple use. Well, except that it doesn't support real data export yet. It just displays pictures.
68
+
69
+ Here are some bad things to say about it:
70
+
71
+ - I've tested only on a Mac so far.
72
+ - Some things are super hacks, and some implementation is super shallow.
73
+ - Some of the pictures don't look quite right.
74
+ - Fixing some of what's off (especially the convolution/blurring) will be nontrivial for me.
75
+ - Some project organization is less than ideal. Still need to understand RubyGems, extensions/ext concepts, and rake better.
76
+ - Not much attention paid to super performance.
77
+ - My progress is slow, but that was expected.
78
+ - Probably a lot more.
79
+
80
+ But it's still progress, and I'm actually reading the pick axe (v2 - I'd looked at v1 online a few years ago), so maybe I'll start grokking the Ruby world better soon.
81
+
82
+ Maybe by m2 or m3 (or sometime), I could try to find a real app using RMagick and try to make it work with my version. For the next moment, though, I'll probably try tackling page 2 or 3 of the "portfolio" samples at the RMagick web site. And eventually go into class by class and method by method depth. Or something. I'm just trying to work with tracer bullets for now.
83
+
84
+ And I might rename the project to "rmagick4j" or "rmagick-jruby" or something at some point. Not sure what's best, so I'm sticking with "jr" for now. And that's an accurate representation for now, anyway.
data/LICENSE.txt ADDED
@@ -0,0 +1,9 @@
1
+ The file observer.rb comes from JRuby's distribution of standard Ruby libraries. The file jruby.jar also comes from the JRuby project.
2
+
3
+ RMagick.rb, clown.jpg, most of rmagick.gemspec, and the contents of rvg come from the RMagick project. Much of the content of the test demos (currently in "RMagickTestSuite.rb") come from the RMagick web site.
4
+
5
+ See these respective projects for the licenses and ownership of these files.
6
+
7
+ Other files in RMagickJr are hereby placed in the public domain.
8
+
9
+ - Thomas Palmer
data/Manifest.txt ADDED
@@ -0,0 +1,142 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ LICENSE.txt
6
+ lib/RMagick.rb
7
+ lib/rmagick4j/constants.rb
8
+ lib/rmagick4j/draw.rb
9
+ lib/rmagick4j/enum.rb
10
+ lib/rmagick4j/gradient_fill.rb
11
+ lib/rmagick4j/image.rb
12
+ lib/rmagick4j/image_list.rb
13
+ lib/rmagick4j/pixel.rb
14
+ lib/rmagick4j/rmagick4j.rb
15
+ lib/rmagick4j/texture_fill.rb
16
+ lib/rmagick4j/type_metric.rb
17
+ lib/rmagick4j/version.rb
18
+ lib/rvg/clippath.rb
19
+ lib/rvg/container.rb
20
+ lib/rvg/deep_equal.rb
21
+ lib/rvg/describable.rb
22
+ lib/rvg/embellishable.rb
23
+ lib/rvg/misc.rb
24
+ lib/rvg/paint.rb
25
+ lib/rvg/pathdata.rb
26
+ lib/rvg/rvg.rb
27
+ lib/rvg/stretchable.rb
28
+ lib/rvg/stylable.rb
29
+ lib/rvg/text.rb
30
+ lib/rvg/transformable.rb
31
+ lib/rvg/units.rb
32
+ lib/jhlabs-filters.jar
33
+ lib/magick4j.jar
34
+ lib/svgsalamander.jar
35
+ test/eyetests/bullseye.rb
36
+ test/eyetests/tests/draw_arc_basic.rb
37
+ test/eyetests/tests/draw_circle_affine.rb
38
+ test/eyetests/tests/draw_circle_basic.rb
39
+ test/eyetests/tests/draw_ellipse_affine.rb
40
+ test/eyetests/tests/draw_ellipse_basic.rb
41
+ test/eyetests/tests/draw_line_affine.rb
42
+ test/eyetests/tests/draw_line_basic.rb
43
+ test/eyetests/tests/draw_pattern_1.rb
44
+ test/eyetests/tests/draw_polygon_affine.rb
45
+ test/eyetests/tests/draw_polygon_basic.rb
46
+ test/eyetests/tests/draw_polyline_affine.rb
47
+ test/eyetests/tests/draw_polyline_basic.rb
48
+ test/eyetests/tests/draw_rectangle_affine.rb
49
+ test/eyetests/tests/draw_rectangle_basic.rb
50
+ test/eyetests/tests/draw_rmagick_test_01.rb
51
+ test/eyetests/tests/draw_rmagick_test_02.rb
52
+ test/eyetests/tests/draw_rmagick_test_03.rb
53
+ test/eyetests/tests/draw_rmagick_test_04.rb
54
+ test/eyetests/tests/draw_roundrectangle_affine.rb
55
+ test/eyetests/tests/draw_roundrectangle_basic.rb
56
+ test/eyetests/tests/gradient_fill_horizontal_diagonal_fill.rb
57
+ test/eyetests/tests/gradient_fill_horizontal_fill.rb
58
+ test/eyetests/tests/gradient_fill_point_fill.rb
59
+ test/eyetests/tests/gradient_fill_vertical_diagonal_fill.rb
60
+ test/eyetests/tests/gradient_fill_vertical_fill.rb
61
+ test/eyetests/tests/gruff_accumulator_bar.rb
62
+ test/eyetests/tests/gruff_area_1.rb
63
+ test/eyetests/tests/gruff_area_2.rb
64
+ test/eyetests/tests/gruff_bar_1.rb
65
+ test/eyetests/tests/gruff_bar_2.rb
66
+ test/eyetests/tests/gruff_line_1.rb
67
+ test/eyetests/tests/gruff_line_2.rb
68
+ test/eyetests/tests/gruff_net_1.rb
69
+ test/eyetests/tests/gruff_net_2.rb
70
+ test/eyetests/tests/gruff_pie_1.rb
71
+ test/eyetests/tests/gruff_pie_2.rb
72
+ test/eyetests/tests/gruff_scene_1.rb
73
+ test/eyetests/tests/gruff_scene_2.rb
74
+ test/eyetests/tests/gruff_sidebar.rb
75
+ test/eyetests/tests/gruff_sidestacked_bar_1.rb
76
+ test/eyetests/tests/gruff_sidestacked_bar_2.rb
77
+ test/eyetests/tests/gruff_spider_1.rb
78
+ test/eyetests/tests/gruff_spider_2.rb
79
+ test/eyetests/tests/gruff_stacked_bar_1.rb
80
+ test/eyetests/tests/gruff_stacked_bar_2.rb
81
+ test/eyetests/tests/hatch_fill.rb
82
+ test/eyetests/tests/image_list_flatten_images.rb
83
+ test/eyetests/tests/new_image.rb
84
+ test/eyetests/tests/path_a_command_01.rb
85
+ test/eyetests/tests/path_a_command_02.rb
86
+ test/eyetests/tests/path_c_command_01.rb
87
+ test/eyetests/tests/path_c_command_02.rb
88
+ test/eyetests/tests/path_complex_01.rb
89
+ test/eyetests/tests/path_espiral_01.rb
90
+ test/eyetests/tests/path_h_command_01.rb
91
+ test/eyetests/tests/path_h_command_02.rb
92
+ test/eyetests/tests/path_l_command_01.rb
93
+ test/eyetests/tests/path_l_command_02.rb
94
+ test/eyetests/tests/path_m_command_01.rb
95
+ test/eyetests/tests/path_m_command_02.rb
96
+ test/eyetests/tests/path_m_command_03.rb
97
+ test/eyetests/tests/path_q_command_01.rb
98
+ test/eyetests/tests/path_q_command_02.rb
99
+ test/eyetests/tests/path_q_command_03.rb
100
+ test/eyetests/tests/path_s_command_01.rb
101
+ test/eyetests/tests/path_s_command_02.rb
102
+ test/eyetests/tests/path_star_01.rb
103
+ test/eyetests/tests/path_t_command_01.rb
104
+ test/eyetests/tests/path_t_command_02.rb
105
+ test/eyetests/tests/path_v_command_01.rb
106
+ test/eyetests/tests/path_v_command_02.rb
107
+ test/eyetests/tests/store_pixel_smiley.rb
108
+ test/eyetests/tests/texture_fill.rb
109
+ test/gruff_tests/test/gruff_test_case.rb
110
+ test/gruff_tests/test/monkey_gruff.rb
111
+ test/gruff_tests/test/test_accumulator_bar.rb
112
+ test/gruff_tests/test/test_area.rb
113
+ test/gruff_tests/test/test_bar.rb
114
+ test/gruff_tests/test/test_base.rb
115
+ test/gruff_tests/test/test_bullet.rb
116
+ test/gruff_tests/test/test_legend.rb
117
+ test/gruff_tests/test/test_line.rb
118
+ test/gruff_tests/test/test_mini_bar.rb
119
+ test/gruff_tests/test/test_mini_pie.rb
120
+ test/gruff_tests/test/test_mini_side_bar.rb
121
+ test/gruff_tests/test/test_net.rb
122
+ test/gruff_tests/test/test_photo.rb
123
+ test/gruff_tests/test/test_pie.rb
124
+ test/gruff_tests/test/test_scene.rb
125
+ test/gruff_tests/test/test_side_bar.rb
126
+ test/gruff_tests/test/test_sidestacked_bar.rb
127
+ test/gruff_tests/test/test_spider.rb
128
+ test/gruff_tests/test/test_stacked_bar.rb
129
+ test/implemented_methods.rb
130
+ test/RMagickTestSuite.rb
131
+ test/spec/draw_spec.rb
132
+ test/spec/image_constants.rb
133
+ test/spec/image_spec.rb
134
+ test/spec/pixel_spec.rb
135
+ test/spec/stories/geometry_runner.rb
136
+ test/spec/stories/geometry_steps.rb
137
+ test/spec/stories/geometry_story.rb
138
+ test/spec/stories/image_filling_runner.rb
139
+ test/spec/stories/image_filling_steps.rb
140
+ test/spec/stories/image_filling_story.rb
141
+ test/images/clown.jpg
142
+ test/images/texture.jpg
data/README.txt ADDED
@@ -0,0 +1,25 @@
1
+ RMagick is a Ruby binding to ImageMagick and GraphicsMagick. RMagick4J implements ImageMagick functionality and the C portions of RMagick for use with JRuby.
2
+
3
+ == Authors
4
+
5
+ This project was written by Thomas Palmer, Sergio Rodríguez Arbeo, and Thomas Enebo with lots of help from the JRuby community.
6
+
7
+ == License
8
+
9
+ The file observer.rb comes from JRuby's distribution of standard Ruby libraries. The file jruby.jar also comes from the JRuby project.
10
+
11
+ RMagick.rb, clown.jpg, most of rmagick.gemspec, and the contents of rvg come from the RMagick project. Much of the content of the test demos (currently in "RMagickTestSuite.rb") come from the RMagick web site.
12
+
13
+ See these respective projects for the licenses and ownership of these files.
14
+
15
+ Other files in RMagickJr are hereby placed in the public domain.
16
+
17
+ == Creating
18
+
19
+ To create a gem you should run rake. The useful targets are :clean, :compile, :gem, :release, :test (hoe has more -- see hoe docs). In order to run rake you must have hoe installed.
20
+
21
+ To create a new release, you should:
22
+
23
+ * Add new version entry to History.txt
24
+ * Update lib/rmagick4j/version.rb to contain new version
25
+ * rake release VERSION={{{{YOUR NEW VERSION (e.g. 1.1.1)}}}}
data/Rakefile ADDED
@@ -0,0 +1,109 @@
1
+ #TODO Remove all unnecessary code.
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+
6
+ task :default => [:compile]
7
+
8
+ jar_file = File.join(%w(lib magick4j.jar))
9
+ src_files = FileList['ext/rmagick4j/src/**/*.java'].join(' ')
10
+ classes_dir = File.join(%w(pkg classes))
11
+
12
+ desc "Compile the native Java code."
13
+ task :compile do
14
+ # mkdir_p classes_dir
15
+ # sh "javac -target 1.5 -source 1.5 -d #{classes_dir} #{classpath} #{src_files}"
16
+ # sh "jar cf #{jar_file} -C #{classes_dir} ."
17
+ end
18
+ file jar_file => :compile
19
+
20
+ desc "Clean up any generated file."
21
+ task :clean do
22
+ rm_rf 'pkg'
23
+ # rm_rf jar_file
24
+ end
25
+
26
+ desc "Run gruff unit tests."
27
+ task :gruff_test do
28
+ FileList['test/gruff_tests/test/test_*.rb'].each do |file|
29
+ puts `ruby #{file}`
30
+ puts ''
31
+ end
32
+ end
33
+
34
+ desc "Run a live sample using RMagick4j."
35
+ task :sample do
36
+ load_paths = '-Ijruby -Ilib -Ipkg'
37
+ sh "java #{classpath(jar_file)} org.jruby.Main #{load_paths} test/RMagickTestSuite.rb addWatermark"
38
+ end
39
+
40
+ # MANIFEST does not see this file, so touch it so it always appears
41
+ # to be there (this is a hack and someone more knowledgable can hopefully
42
+ # figure this out.
43
+ #File.open('lib/magick4j.jar', 'a') {}
44
+
45
+ task :spec do
46
+ require 'spec/rake/spectask'
47
+ desc "Runs Java Integration Specs"
48
+
49
+ Spec::Rake::SpecTask.new do |t|
50
+ t.spec_opts ||= []
51
+ t.spec_files = if ENV['class'].nil?
52
+ FileList['test/spec/**']
53
+ else
54
+ File.join('test', 'spec', ENV['class']+'_spec.rb')
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ task :stories do
61
+ if ENV['file'].nil?
62
+ FileList['test/spec/stories/**/*_runner.rb'].each do |runner|
63
+ require runner
64
+ end
65
+ else
66
+ require File.join('test', 'spec', 'stories', ENV['file']+'_runner.rb')
67
+ end
68
+ end
69
+
70
+ MANIFEST = FileList["History.txt", "Manifest.txt", "README.txt",
71
+ "Rakefile", "LICENSE.txt", "lib/**/*.rb", "lib/**/*.jar",
72
+ "test/**/*.rb", "test/**/execute_test", "test/images/*.jpg", "ext/rmagick4j/src/**/*.java"]
73
+
74
+ file "Manifest.txt" => :manifest
75
+ task :manifest do
76
+ File.open("Manifest.txt", "w") {|f| MANIFEST.each {|n| f << "#{n}\n"} }
77
+ end
78
+ Rake::Task['manifest'].invoke # Always regen manifest, so Hoe has up-to-date list of files
79
+
80
+ require File.dirname(__FILE__) + "/lib/rmagick4j/version"
81
+ begin
82
+ require 'hoe'
83
+ Hoe.new("rmagick4j", RMagick4J::Version::VERSION) do |p|
84
+ p.rubyforge_name = "jruby-extras"
85
+ p.url = "http://jruby-extras.rubyforge.org/rmagick4j"
86
+ p.author = "Thomas Palmer, Sergio Rodríguez Arbeo and Thomas Enebo"
87
+ p.email = "serabe@gmail.com, tom.enebo@gmail.com"
88
+ p.summary = "RMagick for Java"
89
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
90
+ p.description = p.paragraphs_of('README.txt', 0...1).join("\n\n")
91
+ end.spec.dependencies.delete_if { |dep| dep.name == "hoe" }
92
+ rescue LoadError
93
+ puts "You really need Hoe installed to be able to package this gem"
94
+ rescue => e
95
+ puts "ignoring error while loading hoe: #{e.to_s}"
96
+ end
97
+
98
+ %w(package install_gem debug_gem gem).each { |t| task t => :compile }
99
+
100
+ # helper methods below
101
+
102
+ def classpath(extra_jars=nil)
103
+ jruby_cpath = Java::java.lang.System.getProperty 'java.class.path'
104
+ path = jruby_cpath ? jruby_cpath.split(File::PATH_SEPARATOR) : []
105
+ path << FileList['lib/*.jar']
106
+ path << extra_jars.split(File::PATH_SEPARATOR) if extra_jars
107
+ "-cp #{path.flatten.join(File::PATH_SEPARATOR)}"
108
+ end
109
+
data/lib/RMagick.rb ADDED
@@ -0,0 +1,1861 @@
1
+ # $Id: RMagick.rb,v 1.72 2008/06/08 13:41:39 rmagick Exp $
2
+ #==============================================================================
3
+ # Copyright (C) 2008 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 File.join(File.dirname(__FILE__), 'rmagick4j', 'rmagick4j.rb')
12
+
13
+ module Magick
14
+ @@formats = nil
15
+
16
+ def Magick.formats(&block)
17
+ @@formats ||= Magick.init_formats
18
+ if block_given?
19
+ @@formats.each { |k,v| yield(k,v) }
20
+ self
21
+ else
22
+ @@formats
23
+ end
24
+ end
25
+
26
+ class << self
27
+ attr_writer :trace_proc
28
+ end
29
+
30
+ # Geometry class and related enum constants
31
+ class GeometryValue < Enum
32
+ # no methods
33
+ end
34
+
35
+ PercentGeometry = GeometryValue.new(:PercentGeometry, 1).freeze
36
+ AspectGeometry = GeometryValue.new(:AspectGeometry, 2).freeze
37
+ LessGeometry = GeometryValue.new(:LessGeometry, 3).freeze
38
+ GreaterGeometry = GeometryValue.new(:GreaterGeometry, 4).freeze
39
+ AreaGeometry = GeometryValue.new(:AreaGeometry, 5).freeze
40
+ MinimumGeometry = GeometryValue.new(:MinimumGeometry, 6).freeze
41
+
42
+ class Geometry
43
+ FLAGS = ['', '%', '!', '<', '>', '@', '^']
44
+ RFLAGS = { '%' => PercentGeometry,
45
+ '!' => AspectGeometry,
46
+ '<' => LessGeometry,
47
+ '>' => GreaterGeometry,
48
+ '@' => AreaGeometry,
49
+ '^' => MinimumGeometry }
50
+
51
+ attr_accessor :width, :height, :x, :y, :flag
52
+
53
+ def initialize(width=nil, height=nil, x=nil, y=nil, flag=nil)
54
+ raise(ArgumentError, "width set to #{width.to_s}") if width.is_a? GeometryValue
55
+ raise(ArgumentError, "height set to #{height.to_s}") if height.is_a? GeometryValue
56
+ raise(ArgumentError, "x set to #{x.to_s}") if x.is_a? GeometryValue
57
+ raise(ArgumentError, "y set to #{y.to_s}") if y.is_a? GeometryValue
58
+
59
+ # Support floating-point width and height arguments so Geometry
60
+ # objects can be used to specify Image#density= arguments.
61
+ if width == nil
62
+ @width = 0
63
+ elsif width.to_f >= 0.0
64
+ @width = width.to_f
65
+ else
66
+ Kernel.raise ArgumentError, "width must be >= 0: #{width}"
67
+ end
68
+ if height == nil
69
+ @height = 0
70
+ elsif height.to_f >= 0.0
71
+ @height = height.to_f
72
+ else
73
+ Kernel.raise ArgumentError, "height must be >= 0: #{height}"
74
+ end
75
+
76
+ @x = x.to_i
77
+ @y = y.to_i
78
+ @flag = flag
79
+
80
+ end
81
+
82
+ # Construct an object from a geometry string
83
+ W = /(\d+\.\d+%?)|(\d*%?)/
84
+ H = W
85
+ X = /(?:([-+]\d+))?/
86
+ Y = X
87
+ RE = /\A#{W}x?#{H}#{X}#{Y}([!<>@\^]?)\Z/
88
+
89
+ def Geometry.from_s(str)
90
+
91
+ m = RE.match(str)
92
+ if m
93
+ width = (m[1] || m[2]).to_f
94
+ height = (m[3] || m[4]).to_f
95
+ x = m[5].to_i
96
+ y = m[6].to_i
97
+ flag = RFLAGS[m[7]]
98
+ else
99
+ Kernel.raise ArgumentError, "invalid geometry format"
100
+ end
101
+ if str['%']
102
+ flag = PercentGeometry
103
+ end
104
+ Geometry.new(width, height, x, y, flag)
105
+ end
106
+
107
+ # Convert object to a geometry string
108
+ def to_s
109
+ str = ''
110
+ if @width > 0
111
+ fmt = @width.truncate == @width ? "%d" : "%.2f"
112
+ str << sprintf(fmt, @width)
113
+ str << '%' if @flag == PercentGeometry
114
+ end
115
+
116
+ if (@width > 0 && @flag != PercentGeometry) || (@height > 0)
117
+ str << 'x'
118
+ end
119
+
120
+ if @height > 0
121
+ fmt = @height.truncate == @height ? "%d" : "%.2f"
122
+ str << sprintf(fmt, @height)
123
+ str << '%' if @flag == PercentGeometry
124
+ end
125
+ str << sprintf("%+d%+d", @x, @y) if (@x != 0 || @y != 0)
126
+ if @flag != PercentGeometry
127
+ str << FLAGS[@flag.to_i]
128
+ end
129
+ str
130
+ end
131
+ end
132
+
133
+
134
+ class Draw
135
+
136
+ # Thse hashes are used to map Magick constant
137
+ # values to the strings used in the primitives.
138
+ ALIGN_TYPE_NAMES = {
139
+ LeftAlign.to_i => 'left',
140
+ RightAlign.to_i => 'right',
141
+ CenterAlign.to_i => 'center'
142
+ }.freeze
143
+ ANCHOR_TYPE_NAMES = {
144
+ StartAnchor.to_i => 'start',
145
+ MiddleAnchor.to_i => 'middle',
146
+ EndAnchor.to_i => 'end'
147
+ }.freeze
148
+ DECORATION_TYPE_NAMES = {
149
+ NoDecoration.to_i => 'none',
150
+ UnderlineDecoration.to_i => 'underline',
151
+ OverlineDecoration.to_i => 'overline',
152
+ LineThroughDecoration.to_i => 'line-through'
153
+ }.freeze
154
+ FONT_WEIGHT_NAMES = {
155
+ AnyWeight.to_i => 'all',
156
+ NormalWeight.to_i => 'normal',
157
+ BoldWeight.to_i => 'bold',
158
+ BolderWeight.to_i => 'bolder',
159
+ LighterWeight.to_i => 'lighter',
160
+ }.freeze
161
+ GRAVITY_NAMES = {
162
+ NorthWestGravity.to_i => 'northwest',
163
+ NorthGravity.to_i => 'north',
164
+ NorthEastGravity.to_i => 'northeast',
165
+ WestGravity.to_i => 'west',
166
+ CenterGravity.to_i => 'center',
167
+ EastGravity.to_i => 'east',
168
+ SouthWestGravity.to_i => 'southwest',
169
+ SouthGravity.to_i => 'south',
170
+ SouthEastGravity.to_i => 'southeast'
171
+ }.freeze
172
+ PAINT_METHOD_NAMES = {
173
+ PointMethod.to_i => 'point',
174
+ ReplaceMethod.to_i => 'replace',
175
+ FloodfillMethod.to_i => 'floodfill',
176
+ FillToBorderMethod.to_i => 'filltoborder',
177
+ ResetMethod.to_i => 'reset'
178
+ }.freeze
179
+ STRETCH_TYPE_NAMES = {
180
+ NormalStretch.to_i => 'normal',
181
+ UltraCondensedStretch.to_i => 'ultra-condensed',
182
+ ExtraCondensedStretch.to_i => 'extra-condensed',
183
+ CondensedStretch.to_i => 'condensed',
184
+ SemiCondensedStretch.to_i => 'semi-condensed',
185
+ SemiExpandedStretch.to_i => 'semi-expanded',
186
+ ExpandedStretch.to_i => 'expanded',
187
+ ExtraExpandedStretch.to_i => 'extra-expanded',
188
+ UltraExpandedStretch.to_i => 'ultra-expanded',
189
+ AnyStretch.to_i => 'all'
190
+ }.freeze
191
+ STYLE_TYPE_NAMES = {
192
+ NormalStyle.to_i => 'normal',
193
+ ItalicStyle.to_i => 'italic',
194
+ ObliqueStyle.to_i => 'oblique',
195
+ AnyStyle.to_i => 'all'
196
+ }.freeze
197
+
198
+ private
199
+ def enquote(str)
200
+ if str.length > 2 && /\A(?:\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})\z/.match(str)
201
+ return str
202
+ else
203
+ return '"' + str + '"'
204
+ end
205
+ end
206
+
207
+ public
208
+
209
+ # Apply coordinate transformations to support scaling (s), rotation (r),
210
+ # and translation (t). Angles are specified in radians.
211
+ def affine(sx, rx, ry, sy, tx, ty)
212
+ primitive "affine " + sprintf("%g,%g,%g,%g,%g,%g", sx, rx, ry, sy, tx, ty)
213
+ end
214
+
215
+ # Draw an arc.
216
+ def arc(startX, startY, endX, endY, startDegrees, endDegrees)
217
+ primitive "arc " + sprintf("%g,%g %g,%g %g,%g",
218
+ startX, startY, endX, endY, startDegrees, endDegrees)
219
+ end
220
+
221
+ # Draw a bezier curve.
222
+ def bezier(*points)
223
+ if points.length == 0
224
+ Kernel.raise ArgumentError, "no points specified"
225
+ elsif points.length % 2 != 0
226
+ Kernel.raise ArgumentError, "odd number of arguments specified"
227
+ end
228
+ primitive "bezier " + points.join(',')
229
+ end
230
+
231
+ # Draw a circle
232
+ def circle(originX, originY, perimX, perimY)
233
+ primitive "circle " + sprintf("%g,%g %g,%g", originX, originY, perimX, perimY)
234
+ end
235
+
236
+ # Invoke a clip-path defined by def_clip_path.
237
+ def clip_path(name)
238
+ primitive "clip-path #{name}"
239
+ end
240
+
241
+ # Define the clipping rule.
242
+ def clip_rule(rule)
243
+ if ( not ["evenodd", "nonzero"].include?(rule.downcase) )
244
+ Kernel.raise ArgumentError, "Unknown clipping rule #{rule}"
245
+ end
246
+ primitive "clip-rule #{rule}"
247
+ end
248
+
249
+ # Define the clip units
250
+ def clip_units(unit)
251
+ if ( not ["userspace", "userspaceonuse", "objectboundingbox"].include?(unit.downcase) )
252
+ Kernel.raise ArgumentError, "Unknown clip unit #{unit}"
253
+ end
254
+ primitive "clip-units #{unit}"
255
+ end
256
+
257
+ # Set color in image according to specified colorization rule. Rule is one of
258
+ # point, replace, floodfill, filltoborder,reset
259
+ def color(x, y, method)
260
+ if ( not PAINT_METHOD_NAMES.has_key?(method.to_i) )
261
+ Kernel.raise ArgumentError, "Unknown PaintMethod: #{method}"
262
+ end
263
+ primitive "color #{x},#{y},#{PAINT_METHOD_NAMES[method.to_i]}"
264
+ end
265
+
266
+ # Specify EITHER the text decoration (none, underline, overline,
267
+ # line-through) OR the text solid background color (any color name or spec)
268
+ def decorate(decoration)
269
+ if ( DECORATION_TYPE_NAMES.has_key?(decoration.to_i) )
270
+ primitive "decorate #{DECORATION_TYPE_NAMES[decoration.to_i]}"
271
+ else
272
+ primitive "decorate #{enquote(decoration)}"
273
+ end
274
+ end
275
+
276
+ # Define a clip-path. A clip-path is a sequence of primitives
277
+ # bracketed by the "push clip-path <name>" and "pop clip-path"
278
+ # primitives. Upon advice from the IM guys, we also bracket
279
+ # the clip-path primitives with "push(pop) defs" and "push
280
+ # (pop) graphic-context".
281
+ def define_clip_path(name)
282
+ begin
283
+ push('defs')
284
+ push('clip-path', name)
285
+ push('graphic-context')
286
+ yield
287
+ ensure
288
+ pop('graphic-context')
289
+ pop('clip-path')
290
+ pop('defs')
291
+ end
292
+ end
293
+
294
+ # Draw an ellipse
295
+ def ellipse(originX, originY, width, height, arcStart, arcEnd)
296
+ primitive "ellipse " + sprintf("%g,%g %g,%g %g,%g",
297
+ originX, originY, width, height, arcStart, arcEnd)
298
+ end
299
+
300
+ # Let anything through, but the only defined argument
301
+ # is "UTF-8". All others are apparently ignored.
302
+ def encoding(encoding)
303
+ primitive "encoding #{encoding}"
304
+ end
305
+
306
+ # Specify object fill, a color name or pattern name
307
+ def fill(colorspec)
308
+ primitive "fill #{enquote(colorspec)}"
309
+ end
310
+ alias fill_color fill
311
+ alias fill_pattern fill
312
+
313
+ # Specify fill opacity (use "xx%" to indicate percentage)
314
+ def fill_opacity(opacity)
315
+ primitive "fill-opacity #{opacity}"
316
+ end
317
+
318
+ def fill_rule(rule)
319
+ if ( not ["evenodd", "nonzero"].include?(rule.downcase) )
320
+ Kernel.raise ArgumentError, "Unknown fill rule #{rule}"
321
+ end
322
+ primitive "fill-rule #{rule}"
323
+ end
324
+
325
+ # Specify text drawing font
326
+ def font(name)
327
+ primitive "font #{name}"
328
+ end
329
+
330
+ def font_family(name)
331
+ primitive "font-family \'#{name}\'"
332
+ end
333
+
334
+ def font_stretch(stretch)
335
+ if ( not STRETCH_TYPE_NAMES.has_key?(stretch.to_i) )
336
+ Kernel.raise ArgumentError, "Unknown stretch type"
337
+ end
338
+ primitive "font-stretch #{STRETCH_TYPE_NAMES[stretch.to_i]}"
339
+ end
340
+
341
+ def font_style(style)
342
+ if ( not STYLE_TYPE_NAMES.has_key?(style.to_i) )
343
+ Kernel.raise ArgumentError, "Unknown style type"
344
+ end
345
+ primitive "font-style #{STYLE_TYPE_NAMES[style.to_i]}"
346
+ end
347
+
348
+ # The font weight argument can be either a font weight
349
+ # constant or [100,200,...,900]
350
+ def font_weight(weight)
351
+ if ( FONT_WEIGHT_NAMES.has_key?(weight.to_i) )
352
+ primitive "font-weight #{FONT_WEIGHT_NAMES[weight.to_i]}"
353
+ else
354
+ primitive "font-weight #{weight}"
355
+ end
356
+ end
357
+
358
+ # Specify the text positioning gravity, one of:
359
+ # NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast
360
+ def gravity(grav)
361
+ if ( not GRAVITY_NAMES.has_key?(grav.to_i) )
362
+ Kernel.raise ArgumentError, "Unknown text positioning gravity"
363
+ end
364
+ primitive "gravity #{GRAVITY_NAMES[grav.to_i]}"
365
+ end
366
+
367
+ # Draw a line
368
+ def line(startX, startY, endX, endY)
369
+ primitive "line " + sprintf("%g,%g %g,%g", startX, startY, endX, endY)
370
+ end
371
+
372
+ # Set matte (make transparent) in image according to the specified
373
+ # colorization rule
374
+ def matte(x, y, method)
375
+ if ( not PAINT_METHOD_NAMES.has_key?(method.to_i) )
376
+ Kernel.raise ArgumentError, "Unknown paint method"
377
+ end
378
+ primitive "matte #{x},#{y} #{PAINT_METHOD_NAMES[method.to_i]}"
379
+ end
380
+
381
+ # Specify drawing fill and stroke opacities. If the value is a string
382
+ # ending with a %, the number will be multiplied by 0.01.
383
+ def opacity(opacity)
384
+ if (Numeric === opacity)
385
+ if (opacity < 0 || opacity > 1.0)
386
+ Kernel.raise ArgumentError, "opacity must be >= 0 and <= 1.0"
387
+ end
388
+ end
389
+ primitive "opacity #{opacity}"
390
+ end
391
+
392
+ # Draw using SVG-compatible path drawing commands. Note that the
393
+ # primitive requires that the commands be surrounded by quotes or
394
+ # apostrophes. Here we simply use apostrophes.
395
+ def path(cmds)
396
+ primitive "path '" + cmds + "'"
397
+ end
398
+
399
+ # Define a pattern. In the block, call primitive methods to
400
+ # draw the pattern. Reference the pattern by using its name
401
+ # as the argument to the 'fill' or 'stroke' methods
402
+ def pattern(name, x, y, width, height)
403
+ begin
404
+ push('defs')
405
+ push("pattern #{name} #{x} #{y} #{width} #{height}")
406
+ push('graphic-context')
407
+ yield
408
+ ensure
409
+ pop('graphic-context')
410
+ pop('pattern')
411
+ pop('defs')
412
+ end
413
+ end
414
+
415
+ # Set point to fill color.
416
+ def point(x, y)
417
+ primitive "point #{x},#{y}"
418
+ end
419
+
420
+ # Specify the font size in points. Yes, the primitive is "font-size" but
421
+ # in other places this value is called the "pointsize". Give it both names.
422
+ def pointsize(points)
423
+ primitive "font-size #{points}"
424
+ end
425
+ alias font_size pointsize
426
+
427
+ # Draw a polygon
428
+ def polygon(*points)
429
+ if points.length == 0
430
+ Kernel.raise ArgumentError, "no points specified"
431
+ elsif points.length % 2 != 0
432
+ Kernel.raise ArgumentError, "odd number of points specified"
433
+ end
434
+ primitive "polygon " + points.join(',')
435
+ end
436
+
437
+ # Draw a polyline
438
+ def polyline(*points)
439
+ if points.length == 0
440
+ Kernel.raise ArgumentError, "no points specified"
441
+ elsif points.length % 2 != 0
442
+ Kernel.raise ArgumentError, "odd number of points specified"
443
+ end
444
+ primitive "polyline " + points.join(',')
445
+ end
446
+
447
+ # Return to the previously-saved set of whatever
448
+ # pop('graphic-context') (the default if no arguments)
449
+ # pop('defs')
450
+ # pop('gradient')
451
+ # pop('pattern')
452
+
453
+ def pop(*what)
454
+ if what.length == 0
455
+ primitive "pop graphic-context"
456
+ else
457
+ # to_s allows a Symbol to be used instead of a String
458
+ primitive "pop " + what.map {|w| w.to_s}.join(' ')
459
+ end
460
+ end
461
+
462
+ # Push the current set of drawing options. Also you can use
463
+ # push('graphic-context') (the default if no arguments)
464
+ # push('defs')
465
+ # push('gradient')
466
+ # push('pattern')
467
+ def push(*what)
468
+ if what.length == 0
469
+ primitive "push graphic-context"
470
+ else
471
+ # to_s allows a Symbol to be used instead of a String
472
+ primitive "push " + what.map {|w| w.to_s}.join(' ')
473
+ end
474
+ end
475
+
476
+ # Draw a rectangle
477
+ def rectangle(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
478
+ primitive "rectangle " + sprintf("%g,%g %g,%g",
479
+ upper_left_x, upper_left_y, lower_right_x, lower_right_y)
480
+ end
481
+
482
+ # Specify coordinate space rotation. "angle" is measured in degrees
483
+ def rotate(angle)
484
+ primitive "rotate #{angle}"
485
+ end
486
+
487
+ # Draw a rectangle with rounded corners
488
+ def roundrectangle(center_x, center_y, width, height, corner_width, corner_height)
489
+ primitive "roundrectangle " + sprintf("%g,%g,%g,%g,%g,%g",
490
+ center_x, center_y, width, height, corner_width, corner_height)
491
+ end
492
+
493
+ # Specify scaling to be applied to coordinate space on subsequent drawing commands.
494
+ def scale(x, y)
495
+ primitive "scale #{x},#{y}"
496
+ end
497
+
498
+ def skewx(angle)
499
+ primitive "skewX #{angle}"
500
+ end
501
+
502
+ def skewy(angle)
503
+ primitive "skewY #{angle}"
504
+ end
505
+
506
+ # Specify the object stroke, a color name or pattern name.
507
+ def stroke(colorspec)
508
+ primitive "stroke #{enquote(colorspec)}"
509
+ end
510
+ alias stroke_color stroke
511
+ alias stroke_pattern stroke
512
+
513
+ # Specify if stroke should be antialiased or not
514
+ def stroke_antialias(bool)
515
+ bool = bool ? '1' : '0'
516
+ primitive "stroke-antialias #{bool}"
517
+ end
518
+
519
+ # Specify a stroke dash pattern
520
+ def stroke_dasharray(*list)
521
+ if list.length == 0
522
+ primitive "stroke-dasharray none"
523
+ else
524
+ list.each { |x|
525
+ if x <= 0 then
526
+ Kernel.raise ArgumentError, "dash array elements must be > 0 (#{x} given)"
527
+ end
528
+ }
529
+ primitive "stroke-dasharray #{list.join(',')}"
530
+ end
531
+ end
532
+
533
+ # Specify the initial offset in the dash pattern
534
+ def stroke_dashoffset(value=0)
535
+ primitive "stroke-dashoffset #{value}"
536
+ end
537
+
538
+ def stroke_linecap(value)
539
+ if ( not ["butt", "round", "square"].include?(value.downcase) )
540
+ Kernel.raise ArgumentError, "Unknown linecap type: #{value}"
541
+ end
542
+ primitive "stroke-linecap #{value}"
543
+ end
544
+
545
+ def stroke_linejoin(value)
546
+ if ( not ["round", "miter", "bevel"].include?(value.downcase) )
547
+ Kernel.raise ArgumentError, "Unknown linejoin type: #{value}"
548
+ end
549
+ primitive "stroke-linejoin #{value}"
550
+ end
551
+
552
+ def stroke_miterlimit(value)
553
+ if (value < 1)
554
+ Kernel.raise ArgumentError, "miterlimit must be >= 1"
555
+ end
556
+ primitive "stroke-miterlimit #{value}"
557
+ end
558
+
559
+ # Specify opacity of stroke drawing color
560
+ # (use "xx%" to indicate percentage)
561
+ def stroke_opacity(value)
562
+ primitive "stroke-opacity #{value}"
563
+ end
564
+
565
+ # Specify stroke (outline) width in pixels.
566
+ def stroke_width(pixels)
567
+ primitive "stroke-width #{pixels}"
568
+ end
569
+
570
+ # Draw text at position x,y. Add quotes to text that is not already quoted.
571
+ def text(x, y, text)
572
+ if text.to_s.empty?
573
+ Kernel.raise ArgumentError, "missing text argument"
574
+ end
575
+ if text.length > 2 && /\A(?:\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})\z/.match(text)
576
+ ; # text already quoted
577
+ elsif !text['\'']
578
+ text = '\''+text+'\''
579
+ elsif !text['"']
580
+ text = '"'+text+'"'
581
+ elsif !(text['{'] || text['}'])
582
+ text = '{'+text+'}'
583
+ else
584
+ # escape existing braces, surround with braces
585
+ text = '{' + text.gsub(/[}]/) { |b| '\\' + b } + '}'
586
+ end
587
+ primitive "text #{x},#{y} #{text}"
588
+ end
589
+
590
+ # Specify text alignment relative to a given point
591
+ def text_align(alignment)
592
+ if ( not ALIGN_TYPE_NAMES.has_key?(alignment.to_i) )
593
+ Kernel.raise ArgumentError, "Unknown alignment constant: #{alignment}"
594
+ end
595
+ primitive "text-align #{ALIGN_TYPE_NAMES[alignment.to_i]}"
596
+ end
597
+
598
+ # SVG-compatible version of text_align
599
+ def text_anchor(anchor)
600
+ if ( not ANCHOR_TYPE_NAMES.has_key?(anchor.to_i) )
601
+ Kernel.raise ArgumentError, "Unknown anchor constant: #{anchor}"
602
+ end
603
+ primitive "text-anchor #{ANCHOR_TYPE_NAMES[anchor.to_i]}"
604
+ end
605
+
606
+ # Specify if rendered text is to be antialiased.
607
+ def text_antialias(boolean)
608
+ boolean = boolean ? '1' : '0'
609
+ primitive "text-antialias #{boolean}"
610
+ end
611
+
612
+ # Specify color underneath text
613
+ def text_undercolor(color)
614
+ primitive "text-undercolor #{enquote(color)}"
615
+ end
616
+
617
+ # Specify center of coordinate space to use for subsequent drawing
618
+ # commands.
619
+ def translate(x, y)
620
+ primitive "translate #{x},#{y}"
621
+ end
622
+ end # class Magick::Draw
623
+
624
+
625
+ # Define IPTC record number:dataset tags for use with Image#get_iptc_dataset
626
+ module IPTC
627
+ module Envelope
628
+ Model_Version = "1:00"
629
+ Destination = "1:05"
630
+ File_Format = "1:20"
631
+ File_Format_Version = "1:22"
632
+ Service_Identifier = "1:30"
633
+ Envelope_Number = "1:40"
634
+ Product_ID = "1:50"
635
+ Envelope_Priority = "1:60"
636
+ Date_Sent = "1:70"
637
+ Time_Sent = "1:80"
638
+ Coded_Character_Set = "1:90"
639
+ UNO = "1:100"
640
+ Unique_Name_of_Object = "1:100"
641
+ ARM_Identifier = "1:120"
642
+ ARM_Version = "1:122"
643
+ end
644
+
645
+ module Application
646
+ Record_Version = "2:00"
647
+ Object_Type_Reference = "2:03"
648
+ Object_Name = "2:05"
649
+ Title = "2:05"
650
+ Edit_Status = "2:07"
651
+ Editorial_Update = "2:08"
652
+ Urgency = "2:10"
653
+ Subject_Reference = "2:12"
654
+ Category = "2:15"
655
+ Supplemental_Category = "2:20"
656
+ Fixture_Identifier = "2:22"
657
+ Keywords = "2:25"
658
+ Content_Location_Code = "2:26"
659
+ Content_Location_Name = "2:27"
660
+ Release_Date = "2:30"
661
+ Release_Time = "2:35"
662
+ Expiration_Date = "2:37"
663
+ Expiration_Time = "2:35"
664
+ Special_Instructions = "2:40"
665
+ Action_Advised = "2:42"
666
+ Reference_Service = "2:45"
667
+ Reference_Date = "2:47"
668
+ Reference_Number = "2:50"
669
+ Date_Created = "2:55"
670
+ Time_Created = "2:60"
671
+ Digital_Creation_Date = "2:62"
672
+ Digital_Creation_Time = "2:63"
673
+ Originating_Program = "2:65"
674
+ Program_Version = "2:70"
675
+ Object_Cycle = "2:75"
676
+ By_Line = "2:80"
677
+ Author = "2:80"
678
+ By_Line_Title = "2:85"
679
+ Author_Position = "2:85"
680
+ City = "2:90"
681
+ Sub_Location = "2:92"
682
+ Province = "2:95"
683
+ State = "2:95"
684
+ Country_Primary_Location_Code = "2:100"
685
+ Country_Primary_Location_Name = "2:101"
686
+ Original_Transmission_Reference = "2:103"
687
+ Headline = "2:105"
688
+ Credit = "2:110"
689
+ Source = "2:115"
690
+ Copyright_Notice = "2:116"
691
+ Contact = "2:118"
692
+ Abstract = "2:120"
693
+ Caption = "2:120"
694
+ Editor = "2:122"
695
+ Caption_Writer = "2:122"
696
+ Rasterized_Caption = "2:125"
697
+ Image_Type = "2:130"
698
+ Image_Orientation = "2:131"
699
+ Language_Identifier = "2:135"
700
+ Audio_Type = "2:150"
701
+ Audio_Sampling_Rate = "2:151"
702
+ Audio_Sampling_Resolution = "2:152"
703
+ Audio_Duration = "2:153"
704
+ Audio_Outcue = "2:154"
705
+ ObjectData_Preview_File_Format = "2:200"
706
+ ObjectData_Preview_File_Format_Version = "2:201"
707
+ ObjectData_Preview_Data = "2:202"
708
+ end
709
+
710
+ module Pre_ObjectData_Descriptor
711
+ Size_Mode = "7:10"
712
+ Max_Subfile_Size = "7:20"
713
+ ObjectData_Size_Announced = "7:90"
714
+ Maximum_ObjectData_Size = "7:95"
715
+ end
716
+
717
+ module ObjectData
718
+ Subfile = "8:10"
719
+ end
720
+
721
+ module Post_ObjectData_Descriptor
722
+ Confirmed_ObjectData_Size = "9:10"
723
+ end
724
+
725
+ # Make all constants above immutable
726
+ constants.each do |record|
727
+ rec = const_get(record)
728
+ rec.constants.each { |ds| rec.const_get(ds).freeze }
729
+ end
730
+
731
+ end # module Magick::IPTC
732
+
733
+ # Ruby-level Magick::Image methods
734
+ class Image
735
+ include Comparable
736
+
737
+ # Provide an alternate version of Draw#annotate, for folks who
738
+ # want to find it in this class.
739
+ def annotate(draw, width, height, x, y, text, &block)
740
+ check_destroyed
741
+ draw.annotate(self, width, height, x, y, text, &block)
742
+ self
743
+ end
744
+
745
+ # Set the color at x,y
746
+ def color_point(x, y, fill)
747
+ f = copy
748
+ f.pixel_color(x, y, fill)
749
+ return f
750
+ end
751
+
752
+ # Set all pixels that have the same color as the pixel at x,y and
753
+ # are neighbors to the fill color
754
+ def color_floodfill(x, y, fill)
755
+ target = pixel_color(x, y)
756
+ color_flood_fill(target, fill, x, y, Magick::FloodfillMethod)
757
+ end
758
+
759
+ # Set all pixels that are neighbors of x,y and are not the border color
760
+ # to the fill color
761
+ def color_fill_to_border(x, y, fill)
762
+ color_flood_fill(border_color, fill, x, y, Magick::FillToBorderMethod)
763
+ end
764
+
765
+ # Set all pixels to the fill color. Very similar to Image#erase!
766
+ # Accepts either String or Pixel arguments
767
+ def color_reset!(fill)
768
+ save = background_color
769
+ # Change the background color _outside_ the begin block
770
+ # so that if this object is frozen the exeception will be
771
+ # raised before we have to handle it explicitly.
772
+ self.background_color = fill
773
+ begin
774
+ erase!
775
+ ensure
776
+ self.background_color = save
777
+ end
778
+ self
779
+ end
780
+
781
+ # Used by ImageList methods - see ImageList#cur_image
782
+ def cur_image
783
+ self
784
+ end
785
+
786
+ # Thanks to Russell Norris!
787
+ def each_pixel
788
+ get_pixels(0, 0, columns, rows).each_with_index do |p, n|
789
+ yield(p, n%columns, n/columns)
790
+ end
791
+ self
792
+ end
793
+
794
+ # Retrieve EXIF data by entry or all. If one or more entry names specified,
795
+ # return the values associated with the entries. If no entries specified,
796
+ # return all entries and values. The return value is an array of [name,value]
797
+ # arrays.
798
+ def get_exif_by_entry(*entry)
799
+ ary = Array.new
800
+ if entry.length == 0
801
+ exif_data = self['EXIF:*']
802
+ if exif_data
803
+ exif_data.split("\n").each { |exif| ary.push(exif.split('=')) }
804
+ end
805
+ else
806
+ get_exif_by_entry() # ensure properties is populated with exif data
807
+ entry.each do |name|
808
+ rval = self["EXIF:#{name}"]
809
+ ary.push([name, rval])
810
+ end
811
+ end
812
+ return ary
813
+ end
814
+
815
+ # Retrieve EXIF data by tag number or all tag/value pairs. The return value is a hash.
816
+ def get_exif_by_number(*tag)
817
+ hash = Hash.new
818
+ if tag.length == 0
819
+ exif_data = self['EXIF:!']
820
+ if exif_data
821
+ exif_data.split("\n").each do |exif|
822
+ tag, value = exif.split('=')
823
+ tag = tag[1,4].hex
824
+ hash[tag] = value
825
+ end
826
+ end
827
+ else
828
+ get_exif_by_number() # ensure properties is populated with exif data
829
+ tag.each do |num|
830
+ rval = self['#%04X' % num.to_i]
831
+ hash[num] = rval == 'unknown' ? nil : rval
832
+ end
833
+ end
834
+ return hash
835
+ end
836
+
837
+ # Retrieve IPTC information by record number:dataset tag constant defined in
838
+ # Magick::IPTC, above.
839
+ def get_iptc_dataset(ds)
840
+ self['IPTC:'+ds]
841
+ end
842
+
843
+ # Iterate over IPTC record number:dataset tags, yield for each non-nil dataset
844
+ def each_iptc_dataset
845
+ Magick::IPTC.constants.each do |record|
846
+ rec = Magick::IPTC.const_get(record)
847
+ rec.constants.each do |dataset|
848
+ data_field = get_iptc_dataset(rec.const_get(dataset))
849
+ yield(dataset, data_field) unless data_field.nil?
850
+ end
851
+ end
852
+ nil
853
+ end
854
+
855
+ # Patches problematic change to the order of arguments in 1.11.0.
856
+ # Before this release, the order was
857
+ # black_point, gamma, white_point
858
+ # RMagick 1.11.0 changed this to
859
+ # black_point, white_point, gamma
860
+ # This fix tries to determine if the arguments are in the old order and
861
+ # if so, swaps the gamma and white_point arguments. Then it calls
862
+ # level2, which simply accepts the arguments as given.
863
+
864
+ # Inspect the gamma and white point values and swap them if they
865
+ # look like they're in the old order.
866
+
867
+ # (Thanks to Al Evans for the suggestion.)
868
+ def level(black_point=0.0, white_point=nil, gamma=nil)
869
+ black_point = Float(black_point)
870
+
871
+ white_point ||= Magick::QuantumRange - black_point
872
+ white_point = Float(white_point)
873
+
874
+ gamma_arg = gamma
875
+ gamma ||= 1.0
876
+ gamma = Float(gamma)
877
+
878
+ if gamma.abs > 10.0 || white_point.abs <= 10.0 || white_point.abs < gamma.abs
879
+ gamma, white_point = white_point, gamma
880
+ unless gamma_arg
881
+ white_point = Magick::QuantumRange - black_point
882
+ end
883
+ end
884
+
885
+ return level2(black_point, white_point, gamma)
886
+ end
887
+
888
+ # These four methods are equivalent to the Draw#matte method
889
+ # with the "Point", "Replace", "Floodfill", "FilltoBorder", and
890
+ # "Replace" arguments, respectively.
891
+
892
+ # Make the pixel at (x,y) transparent.
893
+ def matte_point(x, y)
894
+ f = copy
895
+ f.opacity = OpaqueOpacity unless f.matte
896
+ pixel = f.pixel_color(x,y)
897
+ pixel.opacity = TransparentOpacity
898
+ f.pixel_color(x, y, pixel)
899
+ return f
900
+ end
901
+
902
+ # Make transparent all pixels that are the same color as the
903
+ # pixel at (x, y).
904
+ def matte_replace(x, y)
905
+ f = copy
906
+ f.opacity = OpaqueOpacity unless f.matte
907
+ target = f.pixel_color(x, y)
908
+ f.transparent(target)
909
+ end
910
+
911
+ # Make transparent any pixel that matches the color of the pixel
912
+ # at (x,y) and is a neighbor.
913
+ def matte_floodfill(x, y)
914
+ f = copy
915
+ f.opacity = OpaqueOpacity unless f.matte
916
+ target = f.pixel_color(x, y)
917
+ f.matte_flood_fill(target, TransparentOpacity,
918
+ x, y, FloodfillMethod)
919
+ end
920
+
921
+ # Make transparent any neighbor pixel that is not the border color.
922
+ def matte_fill_to_border(x, y)
923
+ f = copy
924
+ f.opacity = Magick::OpaqueOpacity unless f.matte
925
+ f.matte_flood_fill(border_color, TransparentOpacity,
926
+ x, y, FillToBorderMethod)
927
+ end
928
+
929
+ # Make all pixels transparent.
930
+ def matte_reset!
931
+ self.opacity = Magick::TransparentOpacity
932
+ self
933
+ end
934
+
935
+ # Corresponds to ImageMagick's -resample option
936
+ def resample(x_res=72.0, y_res=nil)
937
+ y_res ||= x_res
938
+ width = x_res * columns / x_resolution + 0.5
939
+ height = y_res * rows / y_resolution + 0.5
940
+ self.x_resolution = x_res
941
+ self.y_resolution = y_res
942
+ resize(width, height)
943
+ end
944
+
945
+ # Force an image to exact dimensions without changing the aspect ratio.
946
+ # Resize and crop if necessary. (Thanks to Jerett Taylor!)
947
+ def resize_to_fill(ncols, nrows=nil, gravity=CenterGravity)
948
+ copy.resize_to_fill!(ncols, nrows, gravity)
949
+ end
950
+
951
+ def resize_to_fill!(ncols, nrows=nil, gravity=CenterGravity)
952
+ nrows ||= ncols
953
+ if ncols != columns || nrows != rows
954
+ scale = [ncols/columns.to_f, nrows/rows.to_f].max
955
+ resize!(scale*columns+0.5, scale*rows+0.5)
956
+ end
957
+ crop!(gravity, ncols, nrows, true) if ncols != columns || nrows != rows
958
+ self
959
+ end
960
+
961
+ # Preserve aliases used < RMagick 2.0.1
962
+ alias_method :crop_resized, :resize_to_fill
963
+ alias_method :crop_resized!, :resize_to_fill!
964
+
965
+ # Convenience method to resize retaining the aspect ratio.
966
+ # (Thanks to Robert Manni!)
967
+ def resize_to_fit(cols, rows=nil)
968
+ rows ||= cols
969
+ change_geometry(Geometry.new(cols, rows)) do |ncols, nrows|
970
+ resize(ncols, nrows)
971
+ end
972
+ end
973
+
974
+ def resize_to_fit!(cols, rows=nil)
975
+ rows ||= cols
976
+ change_geometry(Geometry.new(cols, rows)) do |ncols, nrows|
977
+ resize!(ncols, nrows)
978
+ end
979
+ end
980
+
981
+ # Replace matching neighboring pixels with texture pixels
982
+ def texture_floodfill(x, y, texture)
983
+ target = pixel_color(x, y)
984
+ texture_flood_fill(target, texture, x, y, FloodfillMethod)
985
+ end
986
+
987
+ # Replace neighboring pixels to border color with texture pixels
988
+ def texture_fill_to_border(x, y, texture)
989
+ texture_flood_fill(border_color, texture, x, y, FillToBorderMethod)
990
+ end
991
+
992
+ # Construct a view. If a block is present, yield and pass the view
993
+ # object, otherwise return the view object.
994
+ def view(x, y, width, height)
995
+ view = View.new(self, x, y, width, height)
996
+
997
+ if block_given?
998
+ begin
999
+ yield(view)
1000
+ ensure
1001
+ view.sync
1002
+ end
1003
+ return nil
1004
+ else
1005
+ return view
1006
+ end
1007
+ end
1008
+
1009
+ # Magick::Image::View class
1010
+ class View
1011
+ attr_reader :x, :y, :width, :height
1012
+ attr_accessor :dirty
1013
+
1014
+ def initialize(img, x, y, width, height)
1015
+ img.check_destroyed
1016
+ if width <= 0 || height <= 0
1017
+ Kernel.raise ArgumentError, "invalid geometry (#{width}x#{height}+#{x}+#{y})"
1018
+ end
1019
+ if x < 0 || y < 0 || (x+width) > img.columns || (y+height) > img.rows
1020
+ Kernel.raise RangeError, "geometry (#{width}x#{height}+#{x}+#{y}) exceeds image boundary"
1021
+ end
1022
+ @view = img.get_pixels(x, y, width, height)
1023
+ @img = img
1024
+ @x = x
1025
+ @y = y
1026
+ @width = width
1027
+ @height = height
1028
+ @dirty = false
1029
+ end
1030
+
1031
+ def [](*args)
1032
+ rows = Rows.new(@view, @width, @height, args)
1033
+ rows.add_observer(self)
1034
+ return rows
1035
+ end
1036
+
1037
+ # Store changed pixels back to image
1038
+ def sync(force=false)
1039
+ @img.store_pixels(x, y, width, height, @view) if (@dirty || force)
1040
+ return (@dirty || force)
1041
+ end
1042
+
1043
+ # Get update from Rows - if @dirty ever becomes
1044
+ # true, don't change it back to false!
1045
+ def update(rows)
1046
+ @dirty = true
1047
+ rows.delete_observer(self) # No need to tell us again.
1048
+ nil
1049
+ end
1050
+
1051
+ # Magick::Image::View::Pixels
1052
+ # Defines channel attribute getters/setters
1053
+ class Pixels < Array
1054
+ include Observable
1055
+
1056
+ # Define a getter and a setter for each channel.
1057
+ [:red, :green, :blue, :opacity].each do |c|
1058
+ module_eval <<-END_EVAL
1059
+ def #{c}
1060
+ return collect { |p| p.#{c} }
1061
+ end
1062
+ def #{c}=(v)
1063
+ each { |p| p.#{c} = v }
1064
+ changed
1065
+ notify_observers(self)
1066
+ nil
1067
+ end
1068
+ END_EVAL
1069
+ end
1070
+
1071
+ end # class Magick::Image::View::Pixels
1072
+
1073
+ # Magick::Image::View::Rows
1074
+ class Rows
1075
+ include Observable
1076
+
1077
+ def initialize(view, width, height, rows)
1078
+ @view = view
1079
+ @width = width
1080
+ @height = height
1081
+ @rows = rows
1082
+ end
1083
+
1084
+ def [](*args)
1085
+ cols(args)
1086
+
1087
+ # Both View::Pixels and Magick::Pixel implement Observable
1088
+ if @unique
1089
+ pixels = @view[@rows[0]*@width + @cols[0]]
1090
+ pixels.add_observer(self)
1091
+ else
1092
+ pixels = View::Pixels.new
1093
+ each do |x|
1094
+ p = @view[x]
1095
+ p.add_observer(self)
1096
+ pixels << p
1097
+ end
1098
+ end
1099
+ pixels
1100
+ end
1101
+
1102
+ def []=(*args)
1103
+ rv = args.delete_at(-1) # get rvalue
1104
+ if ! rv.is_a?(Pixel) # must be a Pixel or a color name
1105
+ begin
1106
+ rv = Pixel.from_color(rv)
1107
+ rescue TypeError
1108
+ Kernel.raise TypeError, "cannot convert #{rv.class} into Pixel"
1109
+ end
1110
+ end
1111
+ cols(args)
1112
+ each { |x| @view[x] = rv.dup }
1113
+ changed
1114
+ notify_observers(self)
1115
+ nil
1116
+ end
1117
+
1118
+ # A pixel has been modified. Tell the view.
1119
+ def update(pixel)
1120
+ changed
1121
+ notify_observers(self)
1122
+ pixel.delete_observer(self) # Don't need to hear again.
1123
+ nil
1124
+ end
1125
+
1126
+ private
1127
+
1128
+ def cols(*args)
1129
+ @cols = args[0] # remove the outermost array
1130
+ @unique = false
1131
+
1132
+ # Convert @rows to an Enumerable object
1133
+ case @rows.length
1134
+ when 0 # Create a Range for all the rows
1135
+ @rows = Range.new(0, @height, true)
1136
+ when 1 # Range, Array, or a single integer
1137
+ # if the single element is already an Enumerable
1138
+ # object, get it.
1139
+ if @rows.first.respond_to? :each
1140
+ @rows = @rows.first
1141
+ else
1142
+ @rows = Integer(@rows.first)
1143
+ if @rows < 0
1144
+ @rows += @height
1145
+ end
1146
+ if @rows < 0 || @rows > @height-1
1147
+ Kernel.raise IndexError, "index [#{@rows}] out of range"
1148
+ end
1149
+ # Convert back to an array
1150
+ @rows = Array.new(1, @rows)
1151
+ @unique = true
1152
+ end
1153
+ when 2
1154
+ # A pair of integers representing the starting column and the number of columns
1155
+ start = Integer(@rows[0])
1156
+ length = Integer(@rows[1])
1157
+
1158
+ # Negative start -> start from last row
1159
+ if start < 0
1160
+ start += @height
1161
+ end
1162
+
1163
+ if start > @height || start < 0 || length < 0
1164
+ Kernel.raise IndexError, "index [#{@rows.first}] out of range"
1165
+ else
1166
+ if start + length > @height
1167
+ length = @height - length
1168
+ length = [length, 0].max
1169
+ end
1170
+ end
1171
+ # Create a Range for the specified set of rows
1172
+ @rows = Range.new(start, start+length, true)
1173
+ end
1174
+
1175
+ case @cols.length
1176
+ when 0 # all rows
1177
+ @cols = Range.new(0, @width, true) # convert to range
1178
+ @unique = false
1179
+ when 1 # Range, Array, or a single integer
1180
+ # if the single element is already an Enumerable
1181
+ # object, get it.
1182
+ if @cols.first.respond_to? :each
1183
+ @cols = @cols.first
1184
+ @unique = false
1185
+ else
1186
+ @cols = Integer(@cols.first)
1187
+ if @cols < 0
1188
+ @cols += @width
1189
+ end
1190
+ if @cols < 0 || @cols > @width-1
1191
+ Kernel.raise IndexError, "index [#{@cols}] out of range"
1192
+ end
1193
+ # Convert back to array
1194
+ @cols = Array.new(1, @cols)
1195
+ @unique &&= true
1196
+ end
1197
+ when 2
1198
+ # A pair of integers representing the starting column and the number of columns
1199
+ start = Integer(@cols[0])
1200
+ length = Integer(@cols[1])
1201
+
1202
+ # Negative start -> start from last row
1203
+ if start < 0
1204
+ start += @width
1205
+ end
1206
+
1207
+ if start > @width || start < 0 || length < 0
1208
+ ; #nop
1209
+ else
1210
+ if start + length > @width
1211
+ length = @width - length
1212
+ length = [length, 0].max
1213
+ end
1214
+ end
1215
+ # Create a Range for the specified set of columns
1216
+ @cols = Range.new(start, start+length, true)
1217
+ @unique = false
1218
+ end
1219
+
1220
+ end
1221
+
1222
+ # iterator called from subscript methods
1223
+ def each
1224
+ maxrows = @height - 1
1225
+ maxcols = @width - 1
1226
+
1227
+ @rows.each do |j|
1228
+ if j > maxrows
1229
+ Kernel.raise IndexError, "index [#{j}] out of range"
1230
+ end
1231
+ @cols.each do |i|
1232
+ if i > maxcols
1233
+ Kernel.raise IndexError, "index [#{i}] out of range"
1234
+ end
1235
+ yield j*@width + i
1236
+ end
1237
+ end
1238
+ nil # useless return value
1239
+ end
1240
+
1241
+ end # class Magick::Image::View::Rows
1242
+
1243
+ end # class Magick::Image::View
1244
+
1245
+ end # class Magick::Image
1246
+
1247
+ class ImageList
1248
+
1249
+ include Comparable
1250
+ include Enumerable
1251
+ attr_reader :scene
1252
+
1253
+ private
1254
+
1255
+ def get_current()
1256
+ return @images[@scene].__id__ rescue nil
1257
+ end
1258
+
1259
+ protected
1260
+
1261
+ def is_an_image(obj)
1262
+ unless obj.kind_of? Magick::Image
1263
+ Kernel.raise ArgumentError, "Magick::Image required (#{obj.class} given)"
1264
+ end
1265
+ true
1266
+ end
1267
+
1268
+ # Ensure array is always an array of Magick::Image objects
1269
+ def is_an_image_array(ary)
1270
+ unless ary.respond_to? :each
1271
+ Kernel.raise ArgumentError, "Magick::ImageList or array of Magick::Images required (#{ary.class} given)"
1272
+ end
1273
+ ary.each { |obj| is_an_image obj }
1274
+ true
1275
+ end
1276
+
1277
+ # Find old current image, update scene number
1278
+ # current is the id of the old current image.
1279
+ def set_current(current)
1280
+ if length() == 0
1281
+ self.scene = nil
1282
+ return
1283
+ # Don't bother looking for current image
1284
+ elsif scene() == nil || scene() >= length()
1285
+ self.scene = length() - 1
1286
+ return
1287
+ elsif current != nil
1288
+ # Find last instance of "current" in the list.
1289
+ # If "current" isn't in the list, set current to last image.
1290
+ self.scene = length() - 1
1291
+ each_with_index do |f,i|
1292
+ if f.__id__ == current
1293
+ self.scene = i
1294
+ end
1295
+ end
1296
+ return
1297
+ end
1298
+ self.scene = length() - 1
1299
+ end
1300
+
1301
+ public
1302
+
1303
+ # Allow scene to be set to nil
1304
+ def scene=(n)
1305
+ if n.nil?
1306
+ Kernel.raise IndexError, "scene number out of bounds" unless @images.length == 0
1307
+ @scene = nil
1308
+ return @scene
1309
+ elsif @images.length == 0
1310
+ Kernel.raise IndexError, "scene number out of bounds"
1311
+ end
1312
+
1313
+ n = Integer(n)
1314
+ if n < 0 || n > length - 1
1315
+ Kernel.raise IndexError, "scene number out of bounds"
1316
+ end
1317
+ @scene = n
1318
+ return @scene
1319
+ end
1320
+
1321
+ # All the binary operators work the same way.
1322
+ # 'other' should be either an ImageList or an Array
1323
+ %w{& + - |}.each do |op|
1324
+ module_eval <<-END_BINOPS
1325
+ def #{op}(other)
1326
+ ilist = self.class.new
1327
+ begin
1328
+ a = other #{op} @images
1329
+ rescue TypeError
1330
+ Kernel.raise ArgumentError, "Magick::ImageList expected, got " + other.class.to_s
1331
+ end
1332
+ current = get_current()
1333
+ a.each do |image|
1334
+ is_an_image image
1335
+ ilist << image
1336
+ end
1337
+ ilist.set_current current
1338
+ return ilist
1339
+ end
1340
+ END_BINOPS
1341
+ end
1342
+
1343
+ def *(n)
1344
+ unless n.kind_of? Integer
1345
+ Kernel.raise ArgumentError, "Integer required (#{n.class} given)"
1346
+ end
1347
+ current = get_current()
1348
+ ilist = self.class.new
1349
+ (@images * n).each {|image| ilist << image}
1350
+ ilist.set_current current
1351
+ return ilist
1352
+ end
1353
+
1354
+ def <<(obj)
1355
+ is_an_image obj
1356
+ @images << obj
1357
+ @scene = @images.length - 1
1358
+ self
1359
+ end
1360
+
1361
+ # Compare ImageLists
1362
+ # Compare each image in turn until the result of a comparison
1363
+ # is not 0. If all comparisons return 0, then
1364
+ # return if A.scene != B.scene
1365
+ # return A.length <=> B.length
1366
+ def <=>(other)
1367
+ unless other.kind_of? self.class
1368
+ Kernel.raise TypeError, "#{self.class} required (#{other.class} given)"
1369
+ end
1370
+ size = [self.length, other.length].min
1371
+ size.times do |x|
1372
+ r = self[x] <=> other[x]
1373
+ return r unless r == 0
1374
+ end
1375
+ if @scene.nil? && other.scene.nil?
1376
+ return 0
1377
+ elsif @scene.nil? && ! other.scene.nil?
1378
+ Kernel.raise TypeError, "cannot convert nil into #{other.scene.class}"
1379
+ elsif ! @scene.nil? && other.scene.nil?
1380
+ Kernel.raise TypeError, "cannot convert nil into #{self.scene.class}"
1381
+ end
1382
+ r = self.scene <=> other.scene
1383
+ return r unless r == 0
1384
+ return self.length <=> other.length
1385
+ end
1386
+
1387
+ def [](*args)
1388
+ a = @images[*args]
1389
+ if a.respond_to?(:each) then
1390
+ ilist = self.class.new
1391
+ a.each {|image| ilist << image}
1392
+ a = ilist
1393
+ end
1394
+ return a
1395
+ end
1396
+
1397
+ def []=(*args)
1398
+ obj = @images.[]=(*args)
1399
+ if obj && obj.respond_to?(:each) then
1400
+ is_an_image_array(obj)
1401
+ set_current obj.last.__id__
1402
+ elsif obj
1403
+ is_an_image(obj)
1404
+ set_current obj.__id__
1405
+ else
1406
+ set_current nil
1407
+ end
1408
+ return obj
1409
+ end
1410
+
1411
+ [:at, :each, :each_index, :empty?, :fetch,
1412
+ :first, :hash, :include?, :index, :length, :nitems, :rindex, :sort!].each do |mth|
1413
+ module_eval <<-END_SIMPLE_DELEGATES
1414
+ def #{mth}(*args, &block)
1415
+ @images.#{mth}(*args, &block)
1416
+ end
1417
+ END_SIMPLE_DELEGATES
1418
+ end
1419
+ alias_method :size, :length
1420
+
1421
+ def clear
1422
+ @scene = nil
1423
+ @images.clear
1424
+ end
1425
+
1426
+ def clone
1427
+ ditto = dup
1428
+ ditto.freeze if frozen?
1429
+ return ditto
1430
+ end
1431
+
1432
+ # override Enumerable#collect
1433
+ def collect(&block)
1434
+ current = get_current()
1435
+ a = @images.collect(&block)
1436
+ ilist = self.class.new
1437
+ a.each {|image| ilist << image}
1438
+ ilist.set_current current
1439
+ return ilist
1440
+ end
1441
+
1442
+ def collect!(&block)
1443
+ @images.collect!(&block)
1444
+ is_an_image_array @images
1445
+ self
1446
+ end
1447
+
1448
+ # Make a deep copy
1449
+ def copy
1450
+ ditto = self.class.new
1451
+ @images.each { |f| ditto << f.copy }
1452
+ ditto.scene = @scene
1453
+ ditto.taint if tainted?
1454
+ return ditto
1455
+ end
1456
+
1457
+ # Return the current image
1458
+ def cur_image
1459
+ if ! @scene
1460
+ Kernel.raise IndexError, "no images in this list"
1461
+ end
1462
+ @images[@scene]
1463
+ end
1464
+
1465
+ # ImageList#map took over the "map" name. Use alternatives.
1466
+ alias_method :__map__, :collect
1467
+ alias_method :map!, :collect!
1468
+ alias_method :__map__!, :collect!
1469
+
1470
+ def compact
1471
+ current = get_current()
1472
+ ilist = self.class.new
1473
+ a = @images.compact
1474
+ a.each {|image| ilist << image}
1475
+ ilist.set_current current
1476
+ return ilist
1477
+ end
1478
+
1479
+ def compact!
1480
+ current = get_current()
1481
+ a = @images.compact! # returns nil if no changes were made
1482
+ set_current current
1483
+ return a.nil? ? nil : self
1484
+ end
1485
+
1486
+ def concat(other)
1487
+ is_an_image_array other
1488
+ other.each {|image| @images << image}
1489
+ @scene = length-1
1490
+ return self
1491
+ end
1492
+
1493
+ # Set same delay for all images
1494
+ def delay=(d)
1495
+ if Integer(d) < 0
1496
+ raise ArgumentError, "delay must be greater than or equal to 0"
1497
+ end
1498
+ @images.each { |f| f.delay = Integer(d) }
1499
+ end
1500
+
1501
+ def delete(obj, &block)
1502
+ is_an_image obj
1503
+ current = get_current()
1504
+ a = @images.delete(obj, &block)
1505
+ set_current current
1506
+ return a
1507
+ end
1508
+
1509
+ def delete_at(ndx)
1510
+ current = get_current()
1511
+ a = @images.delete_at(ndx)
1512
+ set_current current
1513
+ return a
1514
+ end
1515
+
1516
+ def delete_if(&block)
1517
+ current = get_current()
1518
+ @images.delete_if(&block)
1519
+ set_current current
1520
+ self
1521
+ end
1522
+
1523
+ def dup
1524
+ ditto = self.class.new
1525
+ @images.each {|img| ditto << img}
1526
+ ditto.scene = @scene
1527
+ ditto.taint if tainted?
1528
+ return ditto
1529
+ end
1530
+
1531
+ def eql?(other)
1532
+ is_an_image_array other
1533
+ eql = other.eql?(@images)
1534
+ begin # "other" is another ImageList
1535
+ eql &&= @scene == other.scene
1536
+ rescue NoMethodError
1537
+ # "other" is a plain Array
1538
+ end
1539
+ return eql
1540
+ end
1541
+
1542
+ def fill(*args, &block)
1543
+ is_an_image args[0] unless block_given?
1544
+ current = get_current()
1545
+ @images.fill(*args, &block)
1546
+ is_an_image_array self
1547
+ set_current current
1548
+ self
1549
+ end
1550
+
1551
+ # Override Enumerable's find_all
1552
+ def find_all(&block)
1553
+ current = get_current()
1554
+ a = @images.find_all(&block)
1555
+ ilist = self.class.new
1556
+ a.each {|image| ilist << image}
1557
+ ilist.set_current current
1558
+ return ilist
1559
+ end
1560
+ alias_method :select, :find_all
1561
+
1562
+ def from_blob(*blobs, &block)
1563
+ if (blobs.length == 0)
1564
+ Kernel.raise ArgumentError, "no blobs given"
1565
+ end
1566
+ blobs.each { |b|
1567
+ Magick::Image.from_blob(b, &block).each { |n| @images << n }
1568
+ }
1569
+ @scene = length - 1
1570
+ self
1571
+ end
1572
+
1573
+ # Initialize new instances
1574
+ def initialize(*filenames)
1575
+ @images = []
1576
+ @scene = nil
1577
+ filenames.each { |f|
1578
+ Magick::Image.read(f).each { |n| @images << n }
1579
+ }
1580
+ if length > 0
1581
+ @scene = length - 1 # last image in array
1582
+ end
1583
+ self
1584
+ end
1585
+
1586
+ def insert(index, *args)
1587
+ args.each {|image| is_an_image image}
1588
+ current = get_current()
1589
+ @images.insert(index, *args)
1590
+ set_current current
1591
+ return self
1592
+ end
1593
+
1594
+ # Call inspect for all the images
1595
+ def inspect
1596
+ img = []
1597
+ @images.each {|image| img << image.inspect }
1598
+ img = "[" + img.join(",\n") + "]\nscene=#{@scene}"
1599
+ end
1600
+
1601
+ # Set the number of iterations of an animated GIF
1602
+ def iterations=(n)
1603
+ n = Integer(n)
1604
+ if n < 0 || n > 65535
1605
+ Kernel.raise ArgumentError, "iterations must be between 0 and 65535"
1606
+ end
1607
+ @images.each {|f| f.iterations=n}
1608
+ self
1609
+ end
1610
+
1611
+ def last(*args)
1612
+ if args.length == 0
1613
+ a = @images.last
1614
+ else
1615
+ a = @images.last(*args)
1616
+ ilist = self.class.new
1617
+ a.each {|img| ilist << img}
1618
+ @scene = a.length - 1
1619
+ a = ilist
1620
+ end
1621
+ return a
1622
+ end
1623
+
1624
+ # The ImageList class supports the Magick::Image class methods by simply sending
1625
+ # the method to the current image. If the method isn't explicitly supported,
1626
+ # send it to the current image in the array. If there are no images, send
1627
+ # it up the line. Catch a NameError and emit a useful message.
1628
+ def method_missing(methID, *args, &block)
1629
+ begin
1630
+ if @scene
1631
+ @images[@scene].send(methID, *args, &block)
1632
+ else
1633
+ super
1634
+ end
1635
+ rescue NoMethodError
1636
+ Kernel.raise NoMethodError, "undefined method `#{methID.id2name}' for #{self.class}"
1637
+ rescue Exception
1638
+ $@.delete_if { |s| /:in `send'$/.match(s) || /:in `method_missing'$/.match(s) }
1639
+ Kernel.raise
1640
+ end
1641
+ end
1642
+
1643
+ # Create a new image and add it to the end
1644
+ def new_image(cols, rows, *fill, &info_blk)
1645
+ self << Magick::Image.new(cols, rows, *fill, &info_blk)
1646
+ end
1647
+
1648
+ def partition(&block)
1649
+ a = @images.partition(&block)
1650
+ t = self.class.new
1651
+ a[0].each { |img| t << img}
1652
+ t.set_current nil
1653
+ f = self.class.new
1654
+ a[1].each { |img| f << img}
1655
+ f.set_current nil
1656
+ [t, f]
1657
+ end
1658
+
1659
+ # Ping files and concatenate the new images
1660
+ def ping(*files, &block)
1661
+ if (files.length == 0)
1662
+ Kernel.raise ArgumentError, "no files given"
1663
+ end
1664
+ files.each { |f|
1665
+ Magick::Image.ping(f, &block).each { |n| @images << n }
1666
+ }
1667
+ @scene = length - 1
1668
+ self
1669
+ end
1670
+
1671
+ def pop
1672
+ current = get_current()
1673
+ a = @images.pop # can return nil
1674
+ set_current current
1675
+ return a
1676
+ end
1677
+
1678
+ def push(*objs)
1679
+ objs.each do |image|
1680
+ is_an_image image
1681
+ @images << image
1682
+ end
1683
+ @scene = length - 1
1684
+ self
1685
+ end
1686
+
1687
+ # Read files and concatenate the new images
1688
+ def read(*files, &block)
1689
+ if (files.length == 0)
1690
+ Kernel.raise ArgumentError, "no files given"
1691
+ end
1692
+ files.each { |f|
1693
+ Magick::Image.read(f, &block).each { |n| @images << n }
1694
+ }
1695
+ @scene = length - 1
1696
+ self
1697
+ end
1698
+
1699
+ # override Enumerable's reject
1700
+ def reject(&block)
1701
+ current = get_current()
1702
+ ilist = self.class.new
1703
+ a = @images.reject(&block)
1704
+ a.each {|image| ilist << image}
1705
+ ilist.set_current current
1706
+ return ilist
1707
+ end
1708
+
1709
+ def reject!(&block)
1710
+ current = get_current()
1711
+ a = @images.reject!(&block)
1712
+ @images = a if !a.nil?
1713
+ set_current current
1714
+ return a.nil? ? nil : self
1715
+ end
1716
+
1717
+ def replace(other)
1718
+ is_an_image_array other
1719
+ current = get_current()
1720
+ @images.clear
1721
+ other.each {|image| @images << image}
1722
+ @scene = self.length == 0 ? nil : 0
1723
+ set_current current
1724
+ self
1725
+ end
1726
+
1727
+ # Ensure respond_to? answers correctly when we are delegating to Image
1728
+ alias_method :__respond_to__?, :respond_to?
1729
+ def respond_to?(methID, priv=false)
1730
+ return true if __respond_to__?(methID, priv)
1731
+ if @scene
1732
+ @images[@scene].respond_to?(methID, priv)
1733
+ else
1734
+ super
1735
+ end
1736
+ end
1737
+
1738
+ def reverse
1739
+ current = get_current()
1740
+ a = self.class.new
1741
+ @images.reverse_each {|image| a << image}
1742
+ a.set_current current
1743
+ return a
1744
+ end
1745
+
1746
+ def reverse!
1747
+ current = get_current()
1748
+ @images.reverse!
1749
+ set_current current
1750
+ self
1751
+ end
1752
+
1753
+ def reverse_each
1754
+ @images.reverse_each {|image| yield(image)}
1755
+ self
1756
+ end
1757
+
1758
+ def shift
1759
+ current = get_current()
1760
+ a = @images.shift
1761
+ set_current current
1762
+ return a
1763
+ end
1764
+
1765
+ def slice(*args)
1766
+ current = get_current()
1767
+ slice = @images.slice(*args)
1768
+ if slice
1769
+ ilist = self.class.new
1770
+ if slice.respond_to?(:each) then
1771
+ slice.each {|image| ilist << image}
1772
+ else
1773
+ ilist << slice
1774
+ end
1775
+ else
1776
+ ilist = nil
1777
+ end
1778
+ return ilist
1779
+ end
1780
+
1781
+ def slice!(*args)
1782
+ current = get_current()
1783
+ a = @images.slice!(*args)
1784
+ set_current current
1785
+ return a
1786
+ end
1787
+
1788
+ def ticks_per_second=(t)
1789
+ if Integer(t) < 0
1790
+ Kernel.raise ArgumentError, "ticks_per_second must be greater than or equal to 0"
1791
+ end
1792
+ @images.each { |f| f.ticks_per_second = Integer(t) }
1793
+ end
1794
+
1795
+ def to_a
1796
+ a = Array.new
1797
+ @images.each {|image| a << image}
1798
+ return a
1799
+ end
1800
+
1801
+ def uniq
1802
+ current = get_current()
1803
+ a = self.class.new
1804
+ @images.uniq.each {|image| a << image}
1805
+ a.set_current current
1806
+ return a
1807
+ end
1808
+
1809
+ def uniq!(*args)
1810
+ current = get_current()
1811
+ a = @images.uniq!
1812
+ set_current current
1813
+ return a.nil? ? nil : self
1814
+ end
1815
+
1816
+ # @scene -> new object
1817
+ def unshift(obj)
1818
+ is_an_image obj
1819
+ @images.unshift(obj)
1820
+ @scene = 0
1821
+ self
1822
+ end
1823
+
1824
+ def values_at(*args)
1825
+ a = @images.values_at(*args)
1826
+ a = self.class.new
1827
+ @images.values_at(*args).each {|image| a << image}
1828
+ a.scene = a.length - 1
1829
+ return a
1830
+ end
1831
+ alias_method :indexes, :values_at
1832
+ alias_method :indices, :values_at
1833
+
1834
+ end # Magick::ImageList
1835
+
1836
+ # Example fill class. Fills the image with the specified background
1837
+ # color, then crosshatches with the specified crosshatch color.
1838
+ # @dist is the number of pixels between hatch lines.
1839
+ # See Magick::Draw examples.
1840
+ class HatchFill
1841
+ def initialize(bgcolor, hatchcolor="white", dist=10)
1842
+ @bgcolor = bgcolor
1843
+ @hatchpixel = Pixel.from_color(hatchcolor)
1844
+ @dist = dist
1845
+ end
1846
+
1847
+ def fill(img) # required
1848
+ img.background_color = @bgcolor
1849
+ img.erase! # sets image to background color
1850
+ pixels = Array.new([img.rows, img.columns].max, @hatchpixel)
1851
+ @dist.step((img.columns-1)/@dist*@dist, @dist) { |x|
1852
+ img.store_pixels(x,0,1,img.rows,pixels)
1853
+ }
1854
+ @dist.step((img.rows-1)/@dist*@dist, @dist) { |y|
1855
+ img.store_pixels(0,y,img.columns,1,pixels)
1856
+ }
1857
+ end
1858
+ end
1859
+
1860
+ end # Magick
1861
+