rmagick 1.7.1

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 (239) hide show
  1. data/ChangeLog +232 -0
  2. data/Makefile.in +28 -0
  3. data/README.html +404 -0
  4. data/README.txt +397 -0
  5. data/configure +8554 -0
  6. data/configure.ac +497 -0
  7. data/doc/comtasks.html +241 -0
  8. data/doc/constants.html +1195 -0
  9. data/doc/css/doc.css +299 -0
  10. data/doc/css/popup.css +34 -0
  11. data/doc/draw.html +3108 -0
  12. data/doc/ex/Adispatch.rb +43 -0
  13. data/doc/ex/Zconstitute.rb +9 -0
  14. data/doc/ex/adaptive_threshold.rb +19 -0
  15. data/doc/ex/add_noise.rb +18 -0
  16. data/doc/ex/affine.rb +48 -0
  17. data/doc/ex/affine_transform.rb +20 -0
  18. data/doc/ex/arc.rb +47 -0
  19. data/doc/ex/arcpath.rb +33 -0
  20. data/doc/ex/average.rb +15 -0
  21. data/doc/ex/axes.rb +64 -0
  22. data/doc/ex/bilevel_channel.rb +20 -0
  23. data/doc/ex/blur_image.rb +12 -0
  24. data/doc/ex/border.rb +10 -0
  25. data/doc/ex/bounding_box.rb +48 -0
  26. data/doc/ex/cbezier1.rb +40 -0
  27. data/doc/ex/cbezier2.rb +40 -0
  28. data/doc/ex/cbezier3.rb +40 -0
  29. data/doc/ex/cbezier4.rb +41 -0
  30. data/doc/ex/cbezier5.rb +41 -0
  31. data/doc/ex/cbezier6.rb +51 -0
  32. data/doc/ex/channel.rb +26 -0
  33. data/doc/ex/channel_threshold.rb +48 -0
  34. data/doc/ex/charcoal.rb +12 -0
  35. data/doc/ex/chop.rb +29 -0
  36. data/doc/ex/circle.rb +31 -0
  37. data/doc/ex/clip_path.rb +56 -0
  38. data/doc/ex/coalesce.rb +60 -0
  39. data/doc/ex/color_fill_to_border.rb +29 -0
  40. data/doc/ex/color_floodfill.rb +28 -0
  41. data/doc/ex/color_histogram.rb +60 -0
  42. data/doc/ex/color_reset.rb +11 -0
  43. data/doc/ex/colorize.rb +16 -0
  44. data/doc/ex/colors.rb +65 -0
  45. data/doc/ex/composite.rb +135 -0
  46. data/doc/ex/contrast.rb +37 -0
  47. data/doc/ex/crop.rb +31 -0
  48. data/doc/ex/crop_with_gravity.rb +46 -0
  49. data/doc/ex/cycle_colormap.rb +21 -0
  50. data/doc/ex/demo.rb +324 -0
  51. data/doc/ex/drawcomp.rb +42 -0
  52. data/doc/ex/drop_shadow.rb +60 -0
  53. data/doc/ex/edge.rb +11 -0
  54. data/doc/ex/ellipse.rb +43 -0
  55. data/doc/ex/emboss.rb +11 -0
  56. data/doc/ex/enhance.rb +28 -0
  57. data/doc/ex/equalize.rb +11 -0
  58. data/doc/ex/flatten_images.rb +38 -0
  59. data/doc/ex/flip.rb +11 -0
  60. data/doc/ex/flop.rb +11 -0
  61. data/doc/ex/fonts.rb +20 -0
  62. data/doc/ex/frame.rb +12 -0
  63. data/doc/ex/gaussian_blur.rb +11 -0
  64. data/doc/ex/get_multiline_type_metrics.rb +53 -0
  65. data/doc/ex/get_pixels.rb +48 -0
  66. data/doc/ex/get_type_metrics.rb +140 -0
  67. data/doc/ex/gradientfill.rb +27 -0
  68. data/doc/ex/grav.rb +44 -0
  69. data/doc/ex/gravity.rb +80 -0
  70. data/doc/ex/hatchfill.rb +27 -0
  71. data/doc/ex/images/Ballerina.jpg +0 -0
  72. data/doc/ex/images/Ballerina3.jpg +0 -0
  73. data/doc/ex/images/Button_0.gif +0 -0
  74. data/doc/ex/images/Button_1.gif +0 -0
  75. data/doc/ex/images/Button_2.gif +0 -0
  76. data/doc/ex/images/Button_3.gif +0 -0
  77. data/doc/ex/images/Button_4.gif +0 -0
  78. data/doc/ex/images/Button_5.gif +0 -0
  79. data/doc/ex/images/Button_6.gif +0 -0
  80. data/doc/ex/images/Button_7.gif +0 -0
  81. data/doc/ex/images/Button_8.gif +0 -0
  82. data/doc/ex/images/Button_9.gif +0 -0
  83. data/doc/ex/images/Button_A.gif +0 -0
  84. data/doc/ex/images/Button_B.gif +0 -0
  85. data/doc/ex/images/Button_C.gif +0 -0
  86. data/doc/ex/images/Button_D.gif +0 -0
  87. data/doc/ex/images/Button_E.gif +0 -0
  88. data/doc/ex/images/Button_F.gif +0 -0
  89. data/doc/ex/images/Button_G.gif +0 -0
  90. data/doc/ex/images/Button_H.gif +0 -0
  91. data/doc/ex/images/Button_I.gif +0 -0
  92. data/doc/ex/images/Button_J.gif +0 -0
  93. data/doc/ex/images/Button_K.gif +0 -0
  94. data/doc/ex/images/Button_L.gif +0 -0
  95. data/doc/ex/images/Button_M.gif +0 -0
  96. data/doc/ex/images/Button_N.gif +0 -0
  97. data/doc/ex/images/Button_O.gif +0 -0
  98. data/doc/ex/images/Button_P.gif +0 -0
  99. data/doc/ex/images/Button_Q.gif +0 -0
  100. data/doc/ex/images/Button_R.gif +0 -0
  101. data/doc/ex/images/Button_S.gif +0 -0
  102. data/doc/ex/images/Button_T.gif +0 -0
  103. data/doc/ex/images/Button_U.gif +0 -0
  104. data/doc/ex/images/Button_V.gif +0 -0
  105. data/doc/ex/images/Button_W.gif +0 -0
  106. data/doc/ex/images/Button_X.gif +0 -0
  107. data/doc/ex/images/Button_Y.gif +0 -0
  108. data/doc/ex/images/Button_Z.gif +0 -0
  109. data/doc/ex/images/Cheetah.jpg +0 -0
  110. data/doc/ex/images/Coffee.wmf +0 -0
  111. data/doc/ex/images/Flower_Hat.jpg +0 -0
  112. data/doc/ex/images/Gold_Statue.jpg +0 -0
  113. data/doc/ex/images/Hot_Air_Balloons.jpg +0 -0
  114. data/doc/ex/images/Hot_Air_Balloons_H.jpg +0 -0
  115. data/doc/ex/images/No.wmf +0 -0
  116. data/doc/ex/images/Polynesia.jpg +0 -0
  117. data/doc/ex/images/Red_Rocks.jpg +0 -0
  118. data/doc/ex/images/Shorts.jpg +0 -0
  119. data/doc/ex/images/Snake.wmf +0 -0
  120. data/doc/ex/images/Violin.jpg +0 -0
  121. data/doc/ex/images/graydient230x6.gif +0 -0
  122. data/doc/ex/images/logo400x83.gif +0 -0
  123. data/doc/ex/images/model.miff +0 -0
  124. data/doc/ex/images/notimplemented.gif +0 -0
  125. data/doc/ex/images/smile.miff +0 -0
  126. data/doc/ex/images/spin.gif +0 -0
  127. data/doc/ex/implode.rb +32 -0
  128. data/doc/ex/level.rb +12 -0
  129. data/doc/ex/level_channel.rb +33 -0
  130. data/doc/ex/line.rb +40 -0
  131. data/doc/ex/map.rb +28 -0
  132. data/doc/ex/map_f.rb +15 -0
  133. data/doc/ex/matte_fill_to_border.rb +42 -0
  134. data/doc/ex/matte_floodfill.rb +35 -0
  135. data/doc/ex/matte_replace.rb +42 -0
  136. data/doc/ex/median_filter.rb +28 -0
  137. data/doc/ex/modulate.rb +11 -0
  138. data/doc/ex/mono.rb +23 -0
  139. data/doc/ex/morph.rb +26 -0
  140. data/doc/ex/mosaic.rb +35 -0
  141. data/doc/ex/motion_blur.rb +11 -0
  142. data/doc/ex/negate.rb +11 -0
  143. data/doc/ex/negate_channel.rb +19 -0
  144. data/doc/ex/normalize.rb +11 -0
  145. data/doc/ex/oil_paint.rb +11 -0
  146. data/doc/ex/opacity.rb +38 -0
  147. data/doc/ex/opaque.rb +14 -0
  148. data/doc/ex/ordered_dither.rb +11 -0
  149. data/doc/ex/path.rb +62 -0
  150. data/doc/ex/pattern1.rb +25 -0
  151. data/doc/ex/pattern2.rb +26 -0
  152. data/doc/ex/polygon.rb +24 -0
  153. data/doc/ex/polyline.rb +23 -0
  154. data/doc/ex/posterize.rb +19 -0
  155. data/doc/ex/preview.rb +16 -0
  156. data/doc/ex/qbezierpath.rb +49 -0
  157. data/doc/ex/quantize-m.rb +25 -0
  158. data/doc/ex/radial_blur.rb +19 -0
  159. data/doc/ex/raise.rb +11 -0
  160. data/doc/ex/random_channel_threshold.rb +17 -0
  161. data/doc/ex/random_threshold_channel.rb +18 -0
  162. data/doc/ex/rectangle.rb +33 -0
  163. data/doc/ex/reduce_noise.rb +28 -0
  164. data/doc/ex/roll.rb +9 -0
  165. data/doc/ex/rotate.rb +43 -0
  166. data/doc/ex/rotate_f.rb +14 -0
  167. data/doc/ex/roundrect.rb +32 -0
  168. data/doc/ex/rubyname.rb +31 -0
  169. data/doc/ex/segment.rb +11 -0
  170. data/doc/ex/shade.rb +11 -0
  171. data/doc/ex/shave.rb +15 -0
  172. data/doc/ex/shear.rb +10 -0
  173. data/doc/ex/skewx.rb +50 -0
  174. data/doc/ex/skewy.rb +45 -0
  175. data/doc/ex/smile.rb +124 -0
  176. data/doc/ex/solarize.rb +11 -0
  177. data/doc/ex/splice.rb +16 -0
  178. data/doc/ex/spread.rb +11 -0
  179. data/doc/ex/stegano.rb +50 -0
  180. data/doc/ex/stroke_dasharray.rb +41 -0
  181. data/doc/ex/stroke_linecap.rb +44 -0
  182. data/doc/ex/stroke_linejoin.rb +48 -0
  183. data/doc/ex/stroke_width.rb +47 -0
  184. data/doc/ex/swirl.rb +17 -0
  185. data/doc/ex/text.rb +32 -0
  186. data/doc/ex/text_align.rb +36 -0
  187. data/doc/ex/text_antialias.rb +33 -0
  188. data/doc/ex/text_undercolor.rb +26 -0
  189. data/doc/ex/texture_fill_to_border.rb +34 -0
  190. data/doc/ex/texture_floodfill.rb +31 -0
  191. data/doc/ex/texturefill.rb +25 -0
  192. data/doc/ex/threshold.rb +13 -0
  193. data/doc/ex/to_blob.rb +14 -0
  194. data/doc/ex/translate.rb +37 -0
  195. data/doc/ex/transparent.rb +38 -0
  196. data/doc/ex/trim.rb +25 -0
  197. data/doc/ex/unsharp_mask.rb +28 -0
  198. data/doc/ex/viewex.rb +36 -0
  199. data/doc/ex/wave.rb +9 -0
  200. data/doc/ilist.html +1592 -0
  201. data/doc/image1.html +3009 -0
  202. data/doc/image2.html +2169 -0
  203. data/doc/image3.html +2815 -0
  204. data/doc/imageattrs.html +1319 -0
  205. data/doc/imusage.html +403 -0
  206. data/doc/index.html +418 -0
  207. data/doc/info.html +949 -0
  208. data/doc/magick.html +439 -0
  209. data/doc/scripts/doc.js +9 -0
  210. data/doc/struct.html +1334 -0
  211. data/doc/usage.html +1318 -0
  212. data/examples/describe.rb +44 -0
  213. data/examples/histogram.rb +289 -0
  214. data/examples/image_opacity.rb +29 -0
  215. data/examples/import_export.rb +31 -0
  216. data/examples/pattern_fill.rb +38 -0
  217. data/examples/rotating_text.rb +47 -0
  218. data/examples/thumbnail.rb +65 -0
  219. data/examples/vignette.rb +79 -0
  220. data/ext/RMagick/MANIFEST +239 -0
  221. data/ext/RMagick/extconf.rb.in +21 -0
  222. data/ext/RMagick/rmagick.h +938 -0
  223. data/ext/RMagick/rmagick_config.h.in +170 -0
  224. data/ext/RMagick/rmdraw.c +1308 -0
  225. data/ext/RMagick/rmfill.c +609 -0
  226. data/ext/RMagick/rmilist.c +685 -0
  227. data/ext/RMagick/rmimage.c +7980 -0
  228. data/ext/RMagick/rminfo.c +982 -0
  229. data/ext/RMagick/rmmain.c +1497 -0
  230. data/ext/RMagick/rmutil.c +2685 -0
  231. data/install.rb +1015 -0
  232. data/lib/RMagick.rb +1486 -0
  233. data/metaconfig.in +6 -0
  234. data/post-clean.rb +12 -0
  235. data/post-install.rb +36 -0
  236. data/post-setup.rb +245 -0
  237. data/rmagick.gemspec +22 -0
  238. data/uninstall.rb +71 -0
  239. metadata +286 -0
data/lib/RMagick.rb ADDED
@@ -0,0 +1,1486 @@
1
+ # $Id: RMagick.rb,v 1.22 2004/10/06 22:23:49 rmagick Exp $
2
+ #==============================================================================
3
+ # Copyright (C) 2004 by Timothy P. Hunter
4
+ # Name: RMagick.rb
5
+ # Author: Tim Hunter
6
+ # Purpose: Extend Ruby to interface with ImageMagick.
7
+ # Notes: RMagick.so defines the classes. The code below adds methods
8
+ # to the classes.
9
+ #==============================================================================
10
+
11
+ require 'RMagick.so'
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
+
27
+ # Geometry class and related enum constants
28
+ class GeometryValue < Enum
29
+ # no methods
30
+ end
31
+
32
+ PercentGeometry = GeometryValue.new(:PercentGeometry, 1)
33
+ AspectGeometry = GeometryValue.new(:AspectGeometry, 2)
34
+ LessGeometry = GeometryValue.new(:LessGeometry, 3)
35
+ GreaterGeometry = GeometryValue.new(:GreaterGeometry, 4)
36
+ AreaGeometry = GeometryValue.new(:AreaGeometry, 5)
37
+
38
+ class Geometry
39
+ FLAGS = ['', '%', '!', '<', '>', '@']
40
+ RFLAGS = { '%' => PercentGeometry,
41
+ '!' => AspectGeometry,
42
+ '<' => LessGeometry,
43
+ '>' => GreaterGeometry,
44
+ '@' => AreaGeometry }
45
+
46
+ attr_accessor :width, :height, :x, :y, :flag
47
+
48
+ def initialize(width=nil, height=nil, x=nil, y=nil, flag=nil)
49
+
50
+ # Support floating-point width and height arguments so Geometry
51
+ # objects can be used to specify Image#density= arguments.
52
+ if width == nil
53
+ @width = 0
54
+ elsif width.to_f >= 0.0
55
+ @width = width.to_f
56
+ else
57
+ raise ArgumentError, "width must be >= 0: #{width}"
58
+ end
59
+ if height == nil
60
+ @height = 0
61
+ elsif height.to_f >= 0.0
62
+ @height = height.to_f
63
+ else
64
+ raise ArgumentError, "height must be >= 0: #{height}"
65
+ end
66
+
67
+ @x = x.to_i
68
+ @y = y.to_i
69
+ @flag = flag
70
+ end
71
+
72
+ # Construct an object from a geometry string
73
+ RE = /\A(\d*)(?:x(\d+))?([-+]\d+)?([-+]\d+)?([%!<>@]?)\Z/
74
+
75
+ def Geometry.from_s(str)
76
+ raise(ArgumentError, "no geometry string specified") unless str
77
+
78
+ m = RE.match(str)
79
+ if m
80
+ width = m[1].to_i
81
+ height = m[2].to_i
82
+ x = m[3].to_i
83
+ y = m[4].to_i
84
+ flag = RFLAGS[m[5]]
85
+ else
86
+ raise ArgumentError, "invalid geometry format"
87
+ end
88
+ Geometry.new(width, height, x, y, flag)
89
+ end
90
+
91
+ # Convert object to a geometry string
92
+ def to_s
93
+ str = ''
94
+ str << sprintf("%g", @width) if @width > 0
95
+ str << 'x' if (@width > 0 || @height > 0)
96
+ str << sprintf("%g", @height) if @height > 0
97
+ str << sprintf("%+d%+d", @x, @y) if (@x != 0 || @y != 0)
98
+ str << FLAGS[@flag.to_i]
99
+ end
100
+ end
101
+
102
+
103
+ class Draw
104
+
105
+ # Thse hashes are used to map Magick constant
106
+ # values to the strings used in the primitives.
107
+ ALIGN_TYPE_NAMES = {
108
+ LeftAlign.to_i => 'left',
109
+ RightAlign.to_i => 'right',
110
+ CenterAlign.to_i => 'center'
111
+ }
112
+ ANCHOR_TYPE_NAMES = {
113
+ StartAnchor.to_i => 'start',
114
+ MiddleAnchor.to_i => 'middle',
115
+ EndAnchor.to_i => 'end'
116
+ }
117
+ DECORATION_TYPE_NAMES = {
118
+ NoDecoration.to_i => 'none',
119
+ UnderlineDecoration.to_i => 'underline',
120
+ OverlineDecoration.to_i => 'overline',
121
+ LineThroughDecoration.to_i => 'line-through'
122
+ }
123
+ FONT_WEIGHT_NAMES = {
124
+ AnyWeight.to_i => 'all',
125
+ NormalWeight.to_i => 'normal',
126
+ BoldWeight.to_i => 'bold',
127
+ BolderWeight.to_i => 'bolder',
128
+ LighterWeight.to_i => 'lighter',
129
+ }
130
+ GRAVITY_NAMES = {
131
+ NorthWestGravity.to_i => 'northwest',
132
+ NorthGravity.to_i => 'north',
133
+ NorthEastGravity.to_i => 'northeast',
134
+ WestGravity.to_i => 'west',
135
+ CenterGravity.to_i => 'center',
136
+ EastGravity.to_i => 'east',
137
+ SouthWestGravity.to_i => 'southwest',
138
+ SouthGravity.to_i => 'south',
139
+ SouthEastGravity.to_i => 'southeast'
140
+ }
141
+ PAINT_METHOD_NAMES = {
142
+ PointMethod.to_i => 'point',
143
+ ReplaceMethod.to_i => 'replace',
144
+ FloodfillMethod.to_i => 'floodfill',
145
+ FillToBorderMethod.to_i => 'filltoborder',
146
+ ResetMethod.to_i => 'reset'
147
+ }
148
+ STRETCH_TYPE_NAMES = {
149
+ NormalStretch.to_i => 'normal',
150
+ UltraCondensedStretch.to_i => 'ultra-condensed',
151
+ ExtraCondensedStretch.to_i => 'extra-condensed',
152
+ CondensedStretch.to_i => 'condensed',
153
+ SemiCondensedStretch.to_i => 'semi-condensed',
154
+ SemiExpandedStretch.to_i => 'semi-expanded',
155
+ ExpandedStretch.to_i => 'expanded',
156
+ ExtraExpandedStretch.to_i => 'extra-expanded',
157
+ UltraExpandedStretch.to_i => 'ultra-expanded',
158
+ AnyStretch.to_i => 'all'
159
+ }
160
+ STYLE_TYPE_NAMES = {
161
+ NormalStyle.to_i => 'normal',
162
+ ItalicStyle.to_i => 'italic',
163
+ ObliqueStyle.to_i => 'oblique',
164
+ AnyStyle.to_i => 'all'
165
+ }
166
+
167
+ # Apply coordinate transformations to support scaling (s), rotation (r),
168
+ # and translation (t). Angles are specified in radians.
169
+ def affine(sx, rx, ry, sy, tx, ty)
170
+ primitive "affine " + sprintf("%g,%g,%g,%g,%g,%g", sx, rx, ry, sy, tx, ty)
171
+ end
172
+
173
+ # Draw an arc.
174
+ def arc(startX, startY, endX, endY, startDegrees, endDegrees)
175
+ primitive "arc " + sprintf("%g,%g %g,%g %g,%g",
176
+ startX, startY, endX, endY, startDegrees, endDegrees)
177
+ end
178
+
179
+ # Draw a bezier curve.
180
+ def bezier(*points)
181
+ if points.length == 0
182
+ raise ArgumentError, "no points specified"
183
+ elsif points.length % 2 != 0
184
+ raise ArgumentError, "odd number of arguments specified"
185
+ end
186
+ primitive "bezier " + points.join(',')
187
+ end
188
+
189
+ # Draw a circle
190
+ def circle(originX, originY, perimX, perimY)
191
+ primitive "circle " + sprintf("%g,%g %g,%g", originX, originY, perimX, perimY)
192
+ end
193
+
194
+ # Invoke a clip-path defined by def_clip_path.
195
+ def clip_path(name)
196
+ primitive "clip-path #{name}"
197
+ end
198
+
199
+ # Define the clipping rule.
200
+ def clip_rule(rule)
201
+ if ( not ["evenodd", "nonzero"].include?(rule.downcase) )
202
+ raise ArgumentError, "Unknown clipping rule #{rule}"
203
+ end
204
+ primitive "clip-rule #{rule}"
205
+ end
206
+
207
+ # Define the clip units
208
+ def clip_units(unit)
209
+ if ( not ["userspace", "userspaceonuse", "objectboundingbox"].include?(unit.downcase) )
210
+ raise ArgumentError, "Unknown clip unit #{unit}"
211
+ end
212
+ primitive "clip-units #{unit}"
213
+ end
214
+
215
+ # Set color in image according to specified colorization rule. Rule is one of
216
+ # point, replace, floodfill, filltoborder,reset
217
+ def color(x, y, method)
218
+ if ( not PAINT_METHOD_NAMES.has_key?(method.to_i) )
219
+ raise ArgumentError, "Unknown PaintMethod: #{method}"
220
+ end
221
+ primitive "color #{x},#{y},#{PAINT_METHOD_NAMES[method.to_i]}"
222
+ end
223
+
224
+ # Specify EITHER the text decoration (none, underline, overline,
225
+ # line-through) OR the text solid background color (any color name or spec)
226
+ def decorate(decoration)
227
+ if ( DECORATION_TYPE_NAMES.has_key?(decoration.to_i) )
228
+ primitive "decorate #{DECORATION_TYPE_NAMES[decoration.to_i]}"
229
+ else
230
+ primitive "decorate #{decoration}"
231
+ end
232
+ end
233
+
234
+ # Define a clip-path. A clip-path is a sequence of primitives
235
+ # bracketed by the "push clip-path <name>" and "pop clip-path"
236
+ # primitives. Upon advice from the IM guys, we also bracket
237
+ # the clip-path primitives with "push(pop) defs" and "push
238
+ # (pop) graphic-context".
239
+ def define_clip_path(name)
240
+ begin
241
+ push('defs')
242
+ push('clip-path ', name)
243
+ push('graphic-context')
244
+ yield
245
+ ensure
246
+ pop('graphic-context')
247
+ pop('clip-path')
248
+ pop('defs')
249
+ end
250
+ end
251
+
252
+ # Draw an ellipse
253
+ def ellipse(originX, originY, width, height, arcStart, arcEnd)
254
+ primitive "ellipse " + sprintf("%g,%g %g,%g %g,%g",
255
+ originX, originY, width, height, arcStart, arcEnd)
256
+ end
257
+
258
+ # Let anything through, but the only defined argument
259
+ # is "UTF-8". All others are apparently ignored.
260
+ def encoding(encoding)
261
+ primitive "encoding #{encoding}"
262
+ end
263
+
264
+ # Specify object fill, a color name or pattern name
265
+ def fill(colorspec)
266
+ primitive "fill #{colorspec}"
267
+ end
268
+ alias fill_color fill
269
+ alias fill_pattern fill
270
+
271
+ # Specify fill opacity (use "xx%" to indicate percentage)
272
+ def fill_opacity(opacity)
273
+ primitive "fill-opacity #{opacity}"
274
+ end
275
+
276
+ def fill_rule(rule)
277
+ if ( not ["evenodd", "nonzero"].include?(rule.downcase) )
278
+ raise ArgumentError, "Unknown fill rule #{rule}"
279
+ end
280
+ primitive "fill-rule #{rule}"
281
+ end
282
+
283
+ # Specify text drawing font
284
+ def font(name)
285
+ primitive "font #{name}"
286
+ end
287
+
288
+ def font_family(name)
289
+ primitive "font-family \'#{name}\'"
290
+ end
291
+
292
+ def font_stretch(stretch)
293
+ if ( not STRETCH_TYPE_NAMES.has_key?(stretch.to_i) )
294
+ raise ArgumentError, "Unknown stretch type"
295
+ end
296
+ primitive "font-stretch #{STRETCH_TYPE_NAMES[stretch.to_i]}"
297
+ end
298
+
299
+ def font_style(style)
300
+ if ( not STYLE_TYPE_NAMES.has_key?(style.to_i) )
301
+ raise ArgumentError, "Unknown style type"
302
+ end
303
+ primitive "font-style #{STYLE_TYPE_NAMES[style.to_i]}"
304
+ end
305
+
306
+ # The font weight argument can be either a font weight
307
+ # constant or [100,200,...,900]
308
+ def font_weight(weight)
309
+ if ( FONT_WEIGHT_NAMES.has_key?(weight.to_i) )
310
+ primitive "font-weight #{FONT_WEIGHT_NAMES[weight.to_i]}"
311
+ else
312
+ primitive "font-weight #{weight}"
313
+ end
314
+ end
315
+
316
+ # Specify the text positioning gravity, one of:
317
+ # NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast
318
+ def gravity(grav)
319
+ if ( not GRAVITY_NAMES.has_key?(grav.to_i) )
320
+ raise ArgumentError, "Unknown text positioning gravity"
321
+ end
322
+ primitive "gravity #{GRAVITY_NAMES[grav.to_i]}"
323
+ end
324
+
325
+ # Draw a line
326
+ def line(startX, startY, endX, endY)
327
+ primitive "line " + sprintf("%g,%g %g,%g", startX, startY, endX, endY)
328
+ end
329
+
330
+ # Set matte (make transparent) in image according to the specified
331
+ # colorization rule
332
+ def matte(x, y, rule)
333
+ if ( not PAINT_METHOD_NAMES.has_key?(method.to_i) )
334
+ raise ArgumentError, "Unknown paint method"
335
+ end
336
+ primitive "matte #{x},#{y} #{PAINT_METHOD_NAMES[method.to_i]}"
337
+ end
338
+
339
+ # Specify drawing fill and stroke opacities. If the value is a string
340
+ # ending with a %, the number will be multiplied by 0.01.
341
+ def opacity(opacity)
342
+ if (Numeric === opacity)
343
+ if (opacity < 0 || opacity > 1.0)
344
+ raise ArgumentError, "opacity must be >= 0 and <= 1.0"
345
+ end
346
+ end
347
+ primitive "opacity #{opacity}"
348
+ end
349
+
350
+ # Draw using SVG-compatible path drawing commands. Note that the
351
+ # primitive requires that the commands be surrounded by quotes or
352
+ # apostrophes. Here we simply use apostrophes.
353
+ def path(cmds)
354
+ primitive "path '" + cmds + "'"
355
+ end
356
+
357
+ # Define a pattern. In the block, call primitive methods to
358
+ # draw the pattern. Reference the pattern by using its name
359
+ # as the argument to the 'fill' or 'stroke' methods
360
+ def pattern(name, x, y, width, height)
361
+ begin
362
+ push('defs')
363
+ push("pattern #{name} #{x} #{y} #{width} #{height}")
364
+ push('graphic-context')
365
+ yield
366
+ ensure
367
+ pop('graphic-context')
368
+ pop('pattern')
369
+ pop('defs')
370
+ end
371
+ end
372
+
373
+ # Set point to fill color.
374
+ def point(x, y)
375
+ primitive "point #{x},#{y}"
376
+ end
377
+
378
+ # Specify the font size in points. Yes, the primitive is "font-size" but
379
+ # in other places this value is called the "pointsize". Give it both names.
380
+ def pointsize(points)
381
+ primitive "font-size #{points}"
382
+ end
383
+ alias font_size pointsize
384
+
385
+ # Draw a polygon
386
+ def polygon(*points)
387
+ if points.length == 0
388
+ raise ArgumentError, "no points specified"
389
+ elsif points.length % 2 != 0
390
+ raise ArgumentError, "odd number of points specified"
391
+ end
392
+ primitive "polygon " + points.join(',')
393
+ end
394
+
395
+ # Draw a polyline
396
+ def polyline(*points)
397
+ if points.length == 0
398
+ raise ArgumentError, "no points specified"
399
+ elsif points.length % 2 != 0
400
+ raise ArgumentError, "odd number of points specified"
401
+ end
402
+ primitive "polyline " + points.join(',')
403
+ end
404
+
405
+ # Return to the previously-saved set of whatever
406
+ # pop('graphic-context') (the default if no arguments)
407
+ # pop('defs')
408
+ # pop('gradient')
409
+ # pop('pattern')
410
+
411
+ def pop(*what)
412
+ if what.length == 0
413
+ primitive "pop graphic-context"
414
+ else
415
+ # to_s allows a Symbol to be used instead of a String
416
+ primitive "pop " + what.to_s
417
+ end
418
+ end
419
+
420
+ # Push the current set of drawing options. Also you can use
421
+ # push('graphic-context') (the default if no arguments)
422
+ # push('defs')
423
+ # push('gradient')
424
+ # push('pattern')
425
+ def push(*what)
426
+ if what.length == 0
427
+ primitive "push graphic-context"
428
+ else
429
+ # to_s allows a Symbol to be used instead of a String
430
+ primitive "push " + what.to_s
431
+ end
432
+ end
433
+
434
+ # Draw a rectangle
435
+ def rectangle(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
436
+ primitive "rectangle " + sprintf("%g,%g %g,%g",
437
+ upper_left_x, upper_left_y, lower_right_x, lower_right_y)
438
+ end
439
+
440
+ # Specify coordinate space rotation. "angle" is measured in degrees
441
+ def rotate(angle)
442
+ primitive "rotate #{angle}"
443
+ end
444
+
445
+ # Draw a rectangle with rounded corners
446
+ def roundrectangle(center_x, center_y, width, height, corner_width, corner_height)
447
+ primitive "roundrectangle " + sprintf("%g,%g,%g,%g,%g,%g",
448
+ center_x, center_y, width, height, corner_width, corner_height)
449
+ end
450
+
451
+ # Specify scaling to be applied to coordinate space on subsequent drawing commands.
452
+ def scale(x, y)
453
+ primitive "scale #{x},#{y}"
454
+ end
455
+
456
+ def skewx(angle)
457
+ primitive "skewX #{angle}"
458
+ end
459
+
460
+ def skewy(angle)
461
+ primitive "skewY #{angle}"
462
+ end
463
+
464
+ # Specify the object stroke, a color name or pattern name.
465
+ def stroke(colorspec)
466
+ primitive "stroke #{colorspec}"
467
+ end
468
+ alias stroke_color stroke
469
+ alias stroke_pattern stroke
470
+
471
+ # Specify if stroke should be antialiased or not
472
+ def stroke_antialias(bool)
473
+ bool = bool ? '1' : '0'
474
+ primitive "stroke-antialias #{bool}"
475
+ end
476
+
477
+ # Specify a stroke dash pattern
478
+ def stroke_dasharray(*list)
479
+ if list.length == 0
480
+ primitive "stroke-dasharray none"
481
+ else
482
+ list.each { |x|
483
+ if x <= 0 then
484
+ raise ArgumentError, "dash array elements must be > 0 (#{x} given)"
485
+ end
486
+ }
487
+ primitive "stroke-dasharray #{list.join(',')}"
488
+ end
489
+ end
490
+
491
+ # Specify the initial offset in the dash pattern
492
+ def stroke_dashoffset(value=0)
493
+ primitive "stroke-dashoffset #{value}"
494
+ end
495
+
496
+ def stroke_linecap(value)
497
+ if ( not ["butt", "round", "square"].include?(value.downcase) )
498
+ raise ArgumentError, "Unknown linecap type: #{value}"
499
+ end
500
+ primitive "stroke-linecap #{value}"
501
+ end
502
+
503
+ def stroke_linejoin(value)
504
+ if ( not ["round", "miter", "bevel"].include?(value.downcase) )
505
+ raise ArgumentError, "Unknown linejoin type: #{value}"
506
+ end
507
+ primitive "stroke-linejoin #{value}"
508
+ end
509
+
510
+ def stroke_miterlimit(value)
511
+ if (value < 1)
512
+ raise ArgumentError, "miterlimit must be >= 1"
513
+ end
514
+ primitive "stroke-miterlimit #{value}"
515
+ end
516
+
517
+ # Specify opacity of stroke drawing color
518
+ # (use "xx%" to indicate percentage)
519
+ def stroke_opacity(value)
520
+ primitive "stroke-opacity #{value}"
521
+ end
522
+
523
+ # Specify stroke (outline) width in pixels.
524
+ def stroke_width(pixels)
525
+ primitive "stroke-width #{pixels}"
526
+ end
527
+
528
+ # Draw text at position x,y. Generally it's best to surround the text with
529
+ # quotes. For example,
530
+ # gc.text(100, 100, "'embedded blanks'")
531
+ def text(x, y, text)
532
+ if text.to_s.empty?
533
+ raise ArgumentError, "missing text argument"
534
+ end
535
+ primitive "text #{x},#{y} #{text}"
536
+ end
537
+
538
+ # Specify text alignment relative to a given point
539
+ def text_align(alignment)
540
+ if ( not ALIGN_TYPE_NAMES.has_key?(alignment.to_i) )
541
+ raise ArgumentError, "Unknown alignment constant: #{alignment}"
542
+ end
543
+ primitive "text-align #{ALIGN_TYPE_NAMES[alignment.to_i]}"
544
+ end
545
+
546
+ # SVG-compatible version of text_align
547
+ def text_anchor(anchor)
548
+ if ( not ANCHOR_TYPE_NAMES.has_key?(anchor.to_i) )
549
+ raise ArgumentError, "Unknown anchor constant: #{anchor}"
550
+ end
551
+ primitive "text-anchor #{ANCHOR_TYPE_NAMES[anchor.to_i]}"
552
+ end
553
+
554
+ # Specify if rendered text is to be antialiased.
555
+ def text_antialias(boolean)
556
+ boolean = boolean ? '1' : '0'
557
+ primitive "text-antialias #{boolean}"
558
+ end
559
+
560
+ # Specify color underneath text
561
+ def text_undercolor(color)
562
+ primitive "text-undercolor #{color}"
563
+ end
564
+
565
+ # Specify center of coordinate space to use for subsequent drawing
566
+ # commands.
567
+ def translate(x, y)
568
+ primitive "translate #{x},#{y}"
569
+ end
570
+ end # class Magick::Draw
571
+
572
+ # Ruby-level Magick::Image methods
573
+ class Image
574
+ include Comparable
575
+
576
+ # Provide an alternate version of Draw#annotate, for folks who
577
+ # want to find it in this class.
578
+ def annotate(draw, width, height, x, y, text, &block)
579
+ draw.annotate(self, width, height, x, y, text, &block)
580
+ self
581
+ end
582
+
583
+ # Set the color at x,y
584
+ def color_point(x, y, fill)
585
+ f = copy
586
+ f.pixel_color(x, y, fill)
587
+ return f
588
+ end
589
+
590
+ # Set all pixels that have the same color as the pixel at x,y and
591
+ # are neighbors to the fill color
592
+ def color_floodfill(x, y, fill)
593
+ target = pixel_color(x, y)
594
+ color_flood_fill(target, fill, x, y, Magick::FloodfillMethod)
595
+ end
596
+
597
+ # Set all pixels that are neighbors of x,y and are not the border color
598
+ # to the fill color
599
+ def color_fill_to_border(x, y, fill)
600
+ color_flood_fill(border_color, fill, x, y, Magick::FillToBorderMethod)
601
+ end
602
+
603
+ # Set all pixels to the fill color. Very similar to Image#erase!
604
+ # Accepts either String or Pixel arguments
605
+ def color_reset!(fill)
606
+ save = background_color
607
+ # Change the background color _outside_ the begin block
608
+ # so that if this object is frozen the exeception will be
609
+ # raised before we have to handle it explicitly.
610
+ self.background_color = fill
611
+ begin
612
+ erase!
613
+ ensure
614
+ self.background_color = save
615
+ end
616
+ self
617
+ end
618
+
619
+ # Used by ImageList methods - see ImageList#cur_image
620
+ def cur_image
621
+ self
622
+ end
623
+
624
+ # These four methods are equivalent to the Draw#matte
625
+ # method with the "Point", "Replace", "Floodfill", "FilltoBorder", and
626
+ # "Replace" arguments, respectively.
627
+
628
+ # Make the pixel at (x,y) transparent.
629
+ def matte_point(x, y)
630
+ f = copy
631
+ f.opacity = OpaqueOpacity unless f.matte
632
+ pixel = f.pixel_color(x,y)
633
+ pixel.opacity = TransparentOpacity
634
+ f.pixel_color(x, y, pixel)
635
+ return f
636
+ end
637
+
638
+ # Make transparent all pixels that are the same color as the
639
+ # pixel at (x, y).
640
+ def matte_replace(x, y)
641
+ f = copy
642
+ f.opacity = OpaqueOpacity unless f.matte
643
+ target = f.pixel_color(x, y)
644
+ f.transparent(target)
645
+ end
646
+
647
+ # Make transparent any pixel that matches the color of the pixel
648
+ # at (x,y) and is a neighbor.
649
+ def matte_floodfill(x, y)
650
+ f = copy
651
+ f.opacity = OpaqueOpacity unless f.matte
652
+ target = f.pixel_color(x, y)
653
+ f.matte_flood_fill(target, TransparentOpacity,
654
+ x, y, FloodfillMethod)
655
+ end
656
+
657
+ # Make transparent any neighbor pixel that is not the border color.
658
+ def matte_fill_to_border(x, y)
659
+ f = copy
660
+ f.opacity = Magick::OpaqueOpacity unless f.matte
661
+ f.matte_flood_fill(border_color, TransparentOpacity,
662
+ x, y, FillToBorderMethod)
663
+ end
664
+
665
+ # Make all pixels transparent.
666
+ def matte_reset!
667
+ self.opacity = Magick::TransparentOpacity
668
+ end
669
+
670
+ # Replace matching neighboring pixels with texture pixels
671
+ def texture_floodfill(x, y, texture)
672
+ target = pixel_color(x, y)
673
+ texture_flood_fill(target, texture, x, y, FloodfillMethod)
674
+ end
675
+
676
+ # Replace neighboring pixels to border color with texture pixels
677
+ def texture_fill_to_border(x, y, texture)
678
+ texture_flood_fill(border_color, texture, x, y, FillToBorderMethod)
679
+ end
680
+
681
+ # Retrieve EXIF data by entry or all. If one or more entry names specified,
682
+ # return the values associated with the entries. If no entries specified,
683
+ # return all entries and values. The return value is an array of [name,value]
684
+ # arrays.
685
+ def get_exif_by_entry(*entry)
686
+ ary = Array.new
687
+ if entry.length == 0
688
+ exif_data = self['EXIF:*']
689
+ if exif_data
690
+ exif_data.split("\n").each { |exif| ary.push(exif.split('=')) }
691
+ end
692
+ else
693
+ entry.each do |name|
694
+ rval = self["EXIF:#{name}"]
695
+ ary.push([name, rval])
696
+ end
697
+ end
698
+ return ary
699
+ end
700
+
701
+ # Retrieve EXIF data by tag number or all tag/value pairs. The return value is a hash.
702
+ def get_exif_by_number(*tag)
703
+ hash = Hash.new
704
+ if tag.length == 0
705
+ exif_data = self['EXIF:!']
706
+ if exif_data
707
+ exif_data.split("\n").each do |exif|
708
+ tag, value = exif.split('=')
709
+ tag = tag[1,4].hex
710
+ hash[tag] = value
711
+ end
712
+ end
713
+ else
714
+ tag.each do |num|
715
+ rval = self["EXIF:#{'#%04X' % num}"]
716
+ hash[num] = rval == 'unknown' ? nil : rval
717
+ end
718
+ end
719
+ return hash
720
+ end
721
+
722
+ # Construct a view. If a block is present, yield and pass the view
723
+ # object, otherwise return the view object.
724
+ def view(x, y, width, height)
725
+ view = View.new(self, x, y, width, height)
726
+
727
+ if block_given?
728
+ begin
729
+ yield(view)
730
+ ensure
731
+ view.sync
732
+ end
733
+ return nil
734
+ else
735
+ return view
736
+ end
737
+ end
738
+
739
+ # Magick::Image::View class
740
+ class View
741
+ attr_reader :x, :y, :width, :height
742
+ attr_accessor :dirty
743
+
744
+ def initialize(img, x, y, width, height)
745
+ @view = img.get_pixels(x, y, width, height)
746
+ @img = img
747
+ @x = x
748
+ @y = y
749
+ @width = width
750
+ @height = height
751
+ @dirty = false
752
+ end
753
+
754
+ def [](*args)
755
+ rows = Rows.new(@view, @width, @height, args)
756
+ rows.add_observer(self)
757
+ return rows
758
+ end
759
+
760
+ # Store changed pixels back to image
761
+ def sync(force=false)
762
+ @img.store_pixels(x, y, width, height, @view) if (@dirty || force)
763
+ return (@dirty || force)
764
+ end
765
+
766
+ # Get update from Rows - if @dirty ever becomes
767
+ # true, don't change it back to false!
768
+ def update(rows)
769
+ @dirty = true
770
+ rows.delete_observer(self) # No need to tell us again.
771
+ nil
772
+ end
773
+
774
+ # Magick::Image::View::Pixels
775
+ # Defines channel attribute getters/setters
776
+ class Pixels < Array
777
+ include Observable
778
+
779
+ # Define a getter and a setter for each channel.
780
+ [:red, :green, :blue, :opacity].each do |c|
781
+ module_eval <<-END_EVAL
782
+ def #{c}
783
+ return collect { |p| p.#{c} }
784
+ end
785
+ def #{c}=(v)
786
+ each { |p| p.#{c} = v }
787
+ changed
788
+ notify_observers(self)
789
+ nil
790
+ end
791
+ END_EVAL
792
+ end
793
+
794
+ end # class Magick::Image::View::Pixels
795
+
796
+ # Magick::Image::View::Rows
797
+ class Rows
798
+ include Observable
799
+
800
+ def initialize(view, width, height, rows)
801
+ @view = view
802
+ @width = width
803
+ @height = height
804
+ @rows = rows
805
+ end
806
+
807
+ def [](*args)
808
+ cols(args)
809
+
810
+ # Both View::Pixels and Magick::Pixel implement Observable
811
+ if @unique
812
+ pixels = @view[@rows[0]*@width + @cols[0]]
813
+ pixels.add_observer(self)
814
+ else
815
+ pixels = View::Pixels.new
816
+ each do |x|
817
+ p = @view[x]
818
+ p.add_observer(self)
819
+ pixels << p
820
+ end
821
+ end
822
+ pixels
823
+ end
824
+
825
+ def []=(*args)
826
+ rv = args.delete_at(-1) # get rvalue
827
+ if ! rv.is_a?(Pixel) # must be a Pixel or a color name
828
+ begin
829
+ rv = Pixel.from_color(rv)
830
+ rescue TypeError
831
+ raise TypeError, "cannot convert #{rv.class} into Pixel"
832
+ end
833
+ end
834
+ cols(args)
835
+ each { |x| @view[x] = rv.dup }
836
+ changed
837
+ notify_observers(self)
838
+ nil
839
+ end
840
+
841
+ # A pixel has been modified. Tell the view.
842
+ def update(pixel)
843
+ changed
844
+ notify_observers(self)
845
+ pixel.delete_observer(self) # Don't need to hear again.
846
+ nil
847
+ end
848
+
849
+ private
850
+
851
+ def cols(*args)
852
+ @cols = args[0] # remove the outermost array
853
+ @unique = false
854
+
855
+ # Convert @rows to an Enumerable object
856
+ case @rows.length
857
+ when 0 # Create a Range for all the rows
858
+ @rows = Range.new(0, @height, true)
859
+ when 1 # Range, Array, or a single integer
860
+ # if the single element is already an Enumerable
861
+ # object, get it.
862
+ if @rows.first.respond_to? :each
863
+ @rows = @rows.first
864
+ else
865
+ @rows = Integer(@rows.first)
866
+ if @rows < 0
867
+ @rows += @height
868
+ end
869
+ if @rows < 0 || @rows > @height-1
870
+ raise IndexError, "index [#{@rows}] out of range"
871
+ end
872
+ # Convert back to an array
873
+ @rows = Array.new(1, @rows)
874
+ @unique = true
875
+ end
876
+ when 2
877
+ # A pair of integers representing the starting column and the number of columns
878
+ start = Integer(@rows[0])
879
+ length = Integer(@rows[1])
880
+
881
+ # Negative start -> start from last row
882
+ if start < 0
883
+ start += @height
884
+ end
885
+
886
+ if start > @height || start < 0 || length < 0
887
+ raise IndexError, "index [#{@rows.first}] out of range"
888
+ else
889
+ if start + length > @height
890
+ length = @height - length
891
+ length = [length, 0].max
892
+ end
893
+ end
894
+ # Create a Range for the specified set of rows
895
+ @rows = Range.new(start, start+length, true)
896
+ end
897
+
898
+ case @cols.length
899
+ when 0 # all rows
900
+ @cols = Range.new(0, @width, true) # convert to range
901
+ @unique = false
902
+ when 1 # Range, Array, or a single integer
903
+ # if the single element is already an Enumerable
904
+ # object, get it.
905
+ if @cols.first.respond_to? :each
906
+ @cols = @cols.first
907
+ @unique = false
908
+ else
909
+ @cols = Integer(@cols.first)
910
+ if @cols < 0
911
+ @cols += @width
912
+ end
913
+ if @cols < 0 || @cols > @width-1
914
+ raise IndexError, "index [#{@cols}] out of range"
915
+ end
916
+ # Convert back to array
917
+ @cols = Array.new(1, @cols)
918
+ @unique &&= true
919
+ end
920
+ when 2
921
+ # A pair of integers representing the starting column and the number of columns
922
+ start = Integer(@cols[0])
923
+ length = Integer(@cols[1])
924
+
925
+ # Negative start -> start from last row
926
+ if start < 0
927
+ start += @width
928
+ end
929
+
930
+ if start > @width || start < 0 || length < 0
931
+ ; #nop
932
+ else
933
+ if start + length > @width
934
+ length = @width - length
935
+ length = [length, 0].max
936
+ end
937
+ end
938
+ # Create a Range for the specified set of columns
939
+ @cols = Range.new(start, start+length, true)
940
+ @unique = false
941
+ end
942
+
943
+ end
944
+
945
+ # iterator called from subscript methods
946
+ def each
947
+ maxrows = @height - 1
948
+ maxcols = @width - 1
949
+
950
+ @rows.each do |j|
951
+ if j > maxrows
952
+ raise IndexError, "index [#{j}] out of range"
953
+ end
954
+ @cols.each do |i|
955
+ if i > maxcols
956
+ raise IndexError, "index [#{i}] out of range"
957
+ end
958
+ yield j*@width + i
959
+ end
960
+ end
961
+ nil # useless return value
962
+ end
963
+
964
+ end # class Magick::Image::View::Rows
965
+
966
+ end # class Magick::Image::View
967
+
968
+ end # class Magick::Image
969
+
970
+ class ImageList < Array
971
+
972
+ include Comparable
973
+
974
+ undef_method :assoc
975
+ undef_method :flatten! # These methods are undefined
976
+ undef_method :flatten # because they're not useful
977
+ undef_method :join # for an ImageList object
978
+ undef_method :pack
979
+ undef_method :rassoc
980
+ undef_method :transpose if Array.instance_methods(false).include? 'transpose'
981
+ undef_method :zip if Array.instance_methods(false).include? 'zip'
982
+
983
+ attr_reader :scene
984
+
985
+ protected
986
+
987
+ def is_a_image(obj)
988
+ unless obj.kind_of? Magick::Image
989
+ raise ArgumentError, "Magick::Image required (#{obj.class} given)"
990
+ end
991
+ end
992
+
993
+ # Ensure array is always an array of Magick::Image objects
994
+ def is_a_image_array(ary)
995
+ ary.each { |obj| is_a_image obj }
996
+ end
997
+
998
+ # Find old current image, update @scene
999
+ # cfid is the id of the old current image.
1000
+ def set_cf(cfid)
1001
+ if length == 0
1002
+ @scene = nil
1003
+ return
1004
+ # Don't bother looking for current image
1005
+ elsif @scene == nil || @scene >= length
1006
+ @scene = length - 1
1007
+ return
1008
+ elsif cfid != nil
1009
+ each_with_index do |f,i|
1010
+ if f.__id__ == cfid
1011
+ @scene = i
1012
+ return
1013
+ end
1014
+ end
1015
+ end
1016
+ @scene = length - 1
1017
+ end
1018
+
1019
+ public
1020
+
1021
+ # Allow scene to be set to nil
1022
+ def scene=(n)
1023
+ if (length == 0 && n != nil) || (length > 0 && n == nil)
1024
+ raise IndexError, "scene number out of bounds"
1025
+ elsif (length == 0) || (Integer(n) < 0) || (Integer(n) > length - 1)
1026
+ raise IndexError, "scene number out of bounds"
1027
+ end
1028
+ @scene = Integer(n)
1029
+ end
1030
+
1031
+ def [](*args)
1032
+ if (args.length > 1) || args[0].kind_of?(Range)
1033
+ self.class.new.replace super
1034
+ else
1035
+ super
1036
+ end
1037
+ end
1038
+
1039
+ def []=(*args)
1040
+ if args.length == 3 # f[start,length] = [f1,f2...]
1041
+ is_a_image_array args[2]
1042
+ super
1043
+ if args[1] > 0
1044
+ @scene = args[0] + args[1] - 1
1045
+ else # inserts elements if length == 0
1046
+ @scene = args[0] + args[2].length - 1
1047
+ end
1048
+ elsif args[0].kind_of? Range # f[first..last] = [f1,f2...]
1049
+ is_a_image_array args[1]
1050
+ super
1051
+ @scene = args[0].end
1052
+ else # f[index] = f1
1053
+ is_a_image args[1]
1054
+ super # index can be negative
1055
+ @scene = args[0] < 0 ? length + args[0] : args[0]
1056
+ end
1057
+ args.last # return value is always assigned value
1058
+ end
1059
+
1060
+ def &(other)
1061
+ is_a_image_array other
1062
+ cfid = self[@scene].__id__ rescue nil
1063
+ a = self.class.new.replace super
1064
+ a.set_cf cfid
1065
+ return a
1066
+ end
1067
+
1068
+ def *(n)
1069
+ unless n.kind_of? Integer
1070
+ raise ArgumentError, "Integer required (#{n.class} given)"
1071
+ end
1072
+ cfid = self[@scene].__id__ rescue nil
1073
+ a = self.class.new.replace super
1074
+ a.set_cf cfid
1075
+ return a
1076
+ end
1077
+
1078
+ def +(other)
1079
+ cfid = self[@scene].__id__ rescue nil
1080
+ a = self.class.new.replace super
1081
+ a.set_cf cfid
1082
+ return a
1083
+ end
1084
+
1085
+ def -(other)
1086
+ is_a_image_array other
1087
+ cfid = self[@scene].__id__ rescue nil
1088
+ a = self.class.new.replace super
1089
+ a.set_cf cfid
1090
+ return a
1091
+ end
1092
+
1093
+ def <<(obj)
1094
+ is_a_image obj
1095
+ a = super
1096
+ @scene = length-1
1097
+ return a
1098
+ end
1099
+
1100
+ def |(other)
1101
+ is_a_image_array other
1102
+ cfid = self[@scene].__id__ rescue nil
1103
+ a = self.class.new.replace super
1104
+ a.set_cf cfid
1105
+ return a
1106
+ end
1107
+
1108
+ def clear
1109
+ @scene = nil
1110
+ super
1111
+ end
1112
+
1113
+ def collect(&block)
1114
+ cfid = self[@scene].__id__ rescue nil
1115
+ a = self.class.new.replace super
1116
+ a.set_cf cfid
1117
+ return a
1118
+ end
1119
+
1120
+ def collect!(&block)
1121
+ super
1122
+ is_a_image_array self
1123
+ self
1124
+ end
1125
+
1126
+ def compact
1127
+ cfid = self[@scene].__id__ rescue nil
1128
+ a = self.class.new.replace super
1129
+ a.set_cf cfid
1130
+ return a
1131
+ end
1132
+
1133
+ def compact!
1134
+ cfid = self[@scene].__id__ rescue nil
1135
+ a = super # returns nil if no changes were made
1136
+ set_cf cfid
1137
+ return a
1138
+ end
1139
+
1140
+ def concat(other)
1141
+ is_a_image_array other
1142
+ a = super
1143
+ @scene = length-1
1144
+ return a
1145
+ end
1146
+
1147
+ def delete(obj, &block)
1148
+ is_a_image obj
1149
+ cfid = self[@scene].__id__ rescue nil
1150
+ a = super
1151
+ set_cf cfid
1152
+ return a
1153
+ end
1154
+
1155
+ def delete_at(ndx)
1156
+ cfid = self[@scene].__id__ rescue nil
1157
+ a = super
1158
+ set_cf cfid
1159
+ return a
1160
+ end
1161
+
1162
+ def delete_if(&block)
1163
+ cfid = self[@scene].__id__ rescue nil
1164
+ a = super
1165
+ set_cf cfid
1166
+ return a
1167
+ end
1168
+
1169
+ def fill(obj, *args)
1170
+ is_a_image obj
1171
+ cfid = self[@scene].__id__ rescue nil
1172
+ a = super
1173
+ set_cf cfid
1174
+ return a
1175
+ end
1176
+
1177
+ if Array.instance_methods(false).include? 'fetch' then
1178
+ def fetch(*args,&block)
1179
+ super
1180
+ end
1181
+ end
1182
+
1183
+ if Array.instance_methods(false).include? 'insert' then
1184
+ def insert(*args)
1185
+ cfid = self[@scene].__id__ rescue nil
1186
+ a = self.class.new.replace super
1187
+ a.set_cf cfid
1188
+ return a
1189
+ end
1190
+ end
1191
+
1192
+ # __map__ is a synonym for Array#map. We've used an alias
1193
+ # so it doesn't conflict with our own map method.
1194
+ if Array.instance_methods(false).include? '__map__' then
1195
+ def __map__(&block)
1196
+ cfid = self[@scene].__id__ rescue nil
1197
+ a = self.class.new.replace super
1198
+ a.set_cf cfid
1199
+ return a
1200
+ end
1201
+ end
1202
+
1203
+ def map!(&block)
1204
+ super
1205
+ is_a_image_array self
1206
+ self
1207
+ end
1208
+
1209
+ def pop
1210
+ cfid = self[@scene].__id__ rescue nil
1211
+ a = super
1212
+ set_cf cfid
1213
+ return a
1214
+ end
1215
+
1216
+ def push(*objs)
1217
+ objs.each { |o| is_a_image o }
1218
+ super
1219
+ @scene = length - 1
1220
+ self
1221
+ end
1222
+
1223
+ if Array.instance_methods(false).include? 'reject' then
1224
+ def reject(&block)
1225
+ cfid = self[@scene].__id__ rescue nil
1226
+ a = super
1227
+ set_cf cfid
1228
+ return a
1229
+ end
1230
+ end
1231
+
1232
+ def reject!(&block)
1233
+ cfid = self[@scene].__id__ rescue nil
1234
+ a = super
1235
+ set_cf cfid
1236
+ return a
1237
+ end
1238
+
1239
+ def replace(other)
1240
+ is_a_image_array other
1241
+ # Since replace gets called so frequently when @scene == nil
1242
+ # test for it instead of letting rescue catch it.
1243
+ cfid = nil
1244
+ if @scene then
1245
+ cfid = self[@scene].__id__ rescue nil
1246
+ end
1247
+ super
1248
+ set_cf cfid
1249
+ self
1250
+ end
1251
+
1252
+ def reverse
1253
+ cfid = self[@scene].__id__ rescue nil
1254
+ a = self.class.new.replace super
1255
+ a.set_cf cfid
1256
+ return a
1257
+ end
1258
+
1259
+ def reverse!
1260
+ cfid = self[@scene].__id__ rescue nil
1261
+ a = super
1262
+ set_cf cfid
1263
+ return a
1264
+ end
1265
+
1266
+ if Array.instance_methods(false).include? 'select' then
1267
+ def select(*args,&block)
1268
+ cfid = self[@scene].__id__ rescue nil
1269
+ a = super
1270
+ a.set_cf cfid
1271
+ return a
1272
+ end
1273
+ end
1274
+
1275
+ def shift
1276
+ cfid = self[@scene].__id__ rescue nil
1277
+ a = super
1278
+ set_cf cfid
1279
+ return a
1280
+ end
1281
+
1282
+ def slice(*args)
1283
+ self[*args]
1284
+ end
1285
+
1286
+ def slice!(*args)
1287
+ cfid = self[@scene].__id__ rescue nil
1288
+ if args.length > 1 || args[0].kind_of?(Range)
1289
+ a = self.class.new.replace super
1290
+ else
1291
+ a = super
1292
+ end
1293
+ set_cf cfid
1294
+ return a
1295
+ end
1296
+
1297
+ def uniq
1298
+ cfid = self[@scene].__id__ rescue nil
1299
+ a = self.class.new.replace super
1300
+ a.set_cf cfid
1301
+ return a
1302
+ end
1303
+
1304
+ def uniq!(*args)
1305
+ cfid = self[@scene].__id__ rescue nil
1306
+ a = super
1307
+ set_cf cfid
1308
+ return a
1309
+ end
1310
+
1311
+ # @scene -> new object
1312
+ def unshift(obj)
1313
+ is_a_image obj
1314
+ a = super
1315
+ @scene = 0
1316
+ return a
1317
+ end
1318
+
1319
+ # Compare ImageLists
1320
+ # Compare each image in turn until the result of a comparison
1321
+ # is not 0. If all comparisons return 0, then
1322
+ # return if A.scene != B.scene
1323
+ # return A.length <=> B.length
1324
+ def <=>(other)
1325
+ unless other.kind_of? self.class
1326
+ raise TypeError, "#{self.class} required (#{other.class} given)"
1327
+ end
1328
+ size = [self.length, other.length].min
1329
+ size.times do |x|
1330
+ r = self[x] <=> other[x]
1331
+ return r unless r == 0
1332
+ end
1333
+ r = self.scene <=> other.scene
1334
+ return r unless r == 0
1335
+ return self.length <=> other.length
1336
+ end
1337
+
1338
+ # Make a deep copy
1339
+ def copy
1340
+ ditto = self.class.new
1341
+ each { |f| ditto << f.copy }
1342
+ ditto.scene = @scene
1343
+ ditto.taint if tainted?
1344
+ return ditto
1345
+ end
1346
+
1347
+ # Return the current image
1348
+ def cur_image
1349
+ if ! @scene
1350
+ raise IndexError, "no images in this list"
1351
+ end
1352
+ self[@scene]
1353
+ end
1354
+
1355
+ # Set same delay for all images
1356
+ def delay=(d)
1357
+ if Integer(d) < 0
1358
+ raise ArgumentError, "delay must be greater than 0"
1359
+ end
1360
+ each { |f| f.delay = Integer(d) }
1361
+ end
1362
+
1363
+ def from_blob(*blobs, &block)
1364
+ if (blobs.length == 0)
1365
+ raise ArgumentError, "no blobs given"
1366
+ end
1367
+ blobs.each { |b|
1368
+ Magick::Image.from_blob(b, &block).each { |n| self << n }
1369
+ }
1370
+ @scene = length - 1
1371
+ self
1372
+ end
1373
+
1374
+ # Initialize new instances
1375
+ def initialize(*filenames)
1376
+ @scene = nil
1377
+ filenames.each { |f|
1378
+ Magick::Image.read(f).each { |n| self << n }
1379
+ }
1380
+ if length > 0
1381
+ @scene = length - 1 # last image in array
1382
+ end
1383
+ self
1384
+ end
1385
+
1386
+ # Call inspect for all the images
1387
+ def inspect
1388
+ ins = '['
1389
+ each {|image| ins << image.inspect << "\n"}
1390
+ ins.chomp("\n") + "]\nscene=#{@scene}"
1391
+ end
1392
+
1393
+ # Set the number of iterations of an animated GIF
1394
+ def iterations=(n)
1395
+ if n < 0 || n > 65535
1396
+ raise ArgumentError, "iterations must be between 0 and 65535"
1397
+ end
1398
+ each {|f| f.iterations=n}
1399
+ self
1400
+ end
1401
+
1402
+ # The ImageList class supports the Magick::Image class methods by simply sending
1403
+ # the method to the current image. If the method isn't explicitly supported,
1404
+ # send it to the current image in the array. If there are no images, send
1405
+ # it up the line. Catch a NameError and emit a useful message.
1406
+ def method_missing(methID, *args, &block)
1407
+ begin
1408
+ if @scene
1409
+ self[@scene].send(methID, *args, &block)
1410
+ else
1411
+ super
1412
+ end
1413
+ rescue NameError
1414
+ raise NameError, "undefined method `#{methID.id2name}' for #{self.class}"
1415
+ rescue Exception
1416
+ $@.delete_if { |s| /:in `send'$/.match(s) || /:in `method_missing'$/.match(s) }
1417
+ raise
1418
+ end
1419
+ end
1420
+
1421
+ # Ensure respond_to? answers correctly when we are delegating to Image
1422
+ alias_method :__respond_to__?, :respond_to?
1423
+ def respond_to?(methID, priv=false)
1424
+ return true if __respond_to__?(methID, priv)
1425
+ if @scene
1426
+ self[@scene].respond_to?(methID, priv)
1427
+ else
1428
+ super
1429
+ end
1430
+ end
1431
+
1432
+ # Create a new image and add it to the end
1433
+ def new_image(cols, rows, *fill, &info_blk)
1434
+ self << Magick::Image.new(cols, rows, *fill, &info_blk)
1435
+ end
1436
+
1437
+ # Ping files and concatenate the new images
1438
+ def ping(*files, &block)
1439
+ if (files.length == 0)
1440
+ raise ArgumentError, "no files given"
1441
+ end
1442
+ files.each { |f|
1443
+ Magick::Image.ping(f, &block).each { |n| self << n }
1444
+ }
1445
+ @scene = length - 1
1446
+ self
1447
+ end
1448
+
1449
+ # Read files and concatenate the new images
1450
+ def read(*files, &block)
1451
+ if (files.length == 0)
1452
+ raise ArgumentError, "no files given"
1453
+ end
1454
+ files.each { |f|
1455
+ Magick::Image.read(f, &block).each { |n| self << n }
1456
+ }
1457
+ @scene = length - 1
1458
+ self
1459
+ end
1460
+ end # Magick::ImageList
1461
+
1462
+ # Example fill class. Fills the image with the specified background
1463
+ # color, then crosshatches with the specified crosshatch color.
1464
+ # @dist is the number of pixels between hatch lines.
1465
+ # See Magick::Draw examples.
1466
+ class HatchFill
1467
+ def initialize(bgcolor, hatchcolor="white", dist=10)
1468
+ @bgcolor = bgcolor
1469
+ @hatchpixel = Pixel.from_color(hatchcolor)
1470
+ @dist = dist
1471
+ end
1472
+
1473
+ def fill(img) # required
1474
+ img.background_color = @bgcolor
1475
+ img.erase! # sets image to background color
1476
+ pixels = Array.new([img.rows, img.columns].max, @hatchpixel)
1477
+ @dist.step((img.columns-1)/@dist*@dist, @dist) { |x|
1478
+ img.store_pixels(x,0,1,img.rows,pixels)
1479
+ }
1480
+ @dist.step((img.rows-1)/@dist*@dist, @dist) { |y|
1481
+ img.store_pixels(0,y,img.columns,1,pixels)
1482
+ }
1483
+ end
1484
+ end
1485
+
1486
+ end # Magick