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.
- data/ChangeLog +232 -0
- data/Makefile.in +28 -0
- data/README.html +404 -0
- data/README.txt +397 -0
- data/configure +8554 -0
- data/configure.ac +497 -0
- data/doc/comtasks.html +241 -0
- data/doc/constants.html +1195 -0
- data/doc/css/doc.css +299 -0
- data/doc/css/popup.css +34 -0
- data/doc/draw.html +3108 -0
- data/doc/ex/Adispatch.rb +43 -0
- data/doc/ex/Zconstitute.rb +9 -0
- data/doc/ex/adaptive_threshold.rb +19 -0
- data/doc/ex/add_noise.rb +18 -0
- data/doc/ex/affine.rb +48 -0
- data/doc/ex/affine_transform.rb +20 -0
- data/doc/ex/arc.rb +47 -0
- data/doc/ex/arcpath.rb +33 -0
- data/doc/ex/average.rb +15 -0
- data/doc/ex/axes.rb +64 -0
- data/doc/ex/bilevel_channel.rb +20 -0
- data/doc/ex/blur_image.rb +12 -0
- data/doc/ex/border.rb +10 -0
- data/doc/ex/bounding_box.rb +48 -0
- data/doc/ex/cbezier1.rb +40 -0
- data/doc/ex/cbezier2.rb +40 -0
- data/doc/ex/cbezier3.rb +40 -0
- data/doc/ex/cbezier4.rb +41 -0
- data/doc/ex/cbezier5.rb +41 -0
- data/doc/ex/cbezier6.rb +51 -0
- data/doc/ex/channel.rb +26 -0
- data/doc/ex/channel_threshold.rb +48 -0
- data/doc/ex/charcoal.rb +12 -0
- data/doc/ex/chop.rb +29 -0
- data/doc/ex/circle.rb +31 -0
- data/doc/ex/clip_path.rb +56 -0
- data/doc/ex/coalesce.rb +60 -0
- data/doc/ex/color_fill_to_border.rb +29 -0
- data/doc/ex/color_floodfill.rb +28 -0
- data/doc/ex/color_histogram.rb +60 -0
- data/doc/ex/color_reset.rb +11 -0
- data/doc/ex/colorize.rb +16 -0
- data/doc/ex/colors.rb +65 -0
- data/doc/ex/composite.rb +135 -0
- data/doc/ex/contrast.rb +37 -0
- data/doc/ex/crop.rb +31 -0
- data/doc/ex/crop_with_gravity.rb +46 -0
- data/doc/ex/cycle_colormap.rb +21 -0
- data/doc/ex/demo.rb +324 -0
- data/doc/ex/drawcomp.rb +42 -0
- data/doc/ex/drop_shadow.rb +60 -0
- data/doc/ex/edge.rb +11 -0
- data/doc/ex/ellipse.rb +43 -0
- data/doc/ex/emboss.rb +11 -0
- data/doc/ex/enhance.rb +28 -0
- data/doc/ex/equalize.rb +11 -0
- data/doc/ex/flatten_images.rb +38 -0
- data/doc/ex/flip.rb +11 -0
- data/doc/ex/flop.rb +11 -0
- data/doc/ex/fonts.rb +20 -0
- data/doc/ex/frame.rb +12 -0
- data/doc/ex/gaussian_blur.rb +11 -0
- data/doc/ex/get_multiline_type_metrics.rb +53 -0
- data/doc/ex/get_pixels.rb +48 -0
- data/doc/ex/get_type_metrics.rb +140 -0
- data/doc/ex/gradientfill.rb +27 -0
- data/doc/ex/grav.rb +44 -0
- data/doc/ex/gravity.rb +80 -0
- data/doc/ex/hatchfill.rb +27 -0
- data/doc/ex/images/Ballerina.jpg +0 -0
- data/doc/ex/images/Ballerina3.jpg +0 -0
- data/doc/ex/images/Button_0.gif +0 -0
- data/doc/ex/images/Button_1.gif +0 -0
- data/doc/ex/images/Button_2.gif +0 -0
- data/doc/ex/images/Button_3.gif +0 -0
- data/doc/ex/images/Button_4.gif +0 -0
- data/doc/ex/images/Button_5.gif +0 -0
- data/doc/ex/images/Button_6.gif +0 -0
- data/doc/ex/images/Button_7.gif +0 -0
- data/doc/ex/images/Button_8.gif +0 -0
- data/doc/ex/images/Button_9.gif +0 -0
- data/doc/ex/images/Button_A.gif +0 -0
- data/doc/ex/images/Button_B.gif +0 -0
- data/doc/ex/images/Button_C.gif +0 -0
- data/doc/ex/images/Button_D.gif +0 -0
- data/doc/ex/images/Button_E.gif +0 -0
- data/doc/ex/images/Button_F.gif +0 -0
- data/doc/ex/images/Button_G.gif +0 -0
- data/doc/ex/images/Button_H.gif +0 -0
- data/doc/ex/images/Button_I.gif +0 -0
- data/doc/ex/images/Button_J.gif +0 -0
- data/doc/ex/images/Button_K.gif +0 -0
- data/doc/ex/images/Button_L.gif +0 -0
- data/doc/ex/images/Button_M.gif +0 -0
- data/doc/ex/images/Button_N.gif +0 -0
- data/doc/ex/images/Button_O.gif +0 -0
- data/doc/ex/images/Button_P.gif +0 -0
- data/doc/ex/images/Button_Q.gif +0 -0
- data/doc/ex/images/Button_R.gif +0 -0
- data/doc/ex/images/Button_S.gif +0 -0
- data/doc/ex/images/Button_T.gif +0 -0
- data/doc/ex/images/Button_U.gif +0 -0
- data/doc/ex/images/Button_V.gif +0 -0
- data/doc/ex/images/Button_W.gif +0 -0
- data/doc/ex/images/Button_X.gif +0 -0
- data/doc/ex/images/Button_Y.gif +0 -0
- data/doc/ex/images/Button_Z.gif +0 -0
- data/doc/ex/images/Cheetah.jpg +0 -0
- data/doc/ex/images/Coffee.wmf +0 -0
- data/doc/ex/images/Flower_Hat.jpg +0 -0
- data/doc/ex/images/Gold_Statue.jpg +0 -0
- data/doc/ex/images/Hot_Air_Balloons.jpg +0 -0
- data/doc/ex/images/Hot_Air_Balloons_H.jpg +0 -0
- data/doc/ex/images/No.wmf +0 -0
- data/doc/ex/images/Polynesia.jpg +0 -0
- data/doc/ex/images/Red_Rocks.jpg +0 -0
- data/doc/ex/images/Shorts.jpg +0 -0
- data/doc/ex/images/Snake.wmf +0 -0
- data/doc/ex/images/Violin.jpg +0 -0
- data/doc/ex/images/graydient230x6.gif +0 -0
- data/doc/ex/images/logo400x83.gif +0 -0
- data/doc/ex/images/model.miff +0 -0
- data/doc/ex/images/notimplemented.gif +0 -0
- data/doc/ex/images/smile.miff +0 -0
- data/doc/ex/images/spin.gif +0 -0
- data/doc/ex/implode.rb +32 -0
- data/doc/ex/level.rb +12 -0
- data/doc/ex/level_channel.rb +33 -0
- data/doc/ex/line.rb +40 -0
- data/doc/ex/map.rb +28 -0
- data/doc/ex/map_f.rb +15 -0
- data/doc/ex/matte_fill_to_border.rb +42 -0
- data/doc/ex/matte_floodfill.rb +35 -0
- data/doc/ex/matte_replace.rb +42 -0
- data/doc/ex/median_filter.rb +28 -0
- data/doc/ex/modulate.rb +11 -0
- data/doc/ex/mono.rb +23 -0
- data/doc/ex/morph.rb +26 -0
- data/doc/ex/mosaic.rb +35 -0
- data/doc/ex/motion_blur.rb +11 -0
- data/doc/ex/negate.rb +11 -0
- data/doc/ex/negate_channel.rb +19 -0
- data/doc/ex/normalize.rb +11 -0
- data/doc/ex/oil_paint.rb +11 -0
- data/doc/ex/opacity.rb +38 -0
- data/doc/ex/opaque.rb +14 -0
- data/doc/ex/ordered_dither.rb +11 -0
- data/doc/ex/path.rb +62 -0
- data/doc/ex/pattern1.rb +25 -0
- data/doc/ex/pattern2.rb +26 -0
- data/doc/ex/polygon.rb +24 -0
- data/doc/ex/polyline.rb +23 -0
- data/doc/ex/posterize.rb +19 -0
- data/doc/ex/preview.rb +16 -0
- data/doc/ex/qbezierpath.rb +49 -0
- data/doc/ex/quantize-m.rb +25 -0
- data/doc/ex/radial_blur.rb +19 -0
- data/doc/ex/raise.rb +11 -0
- data/doc/ex/random_channel_threshold.rb +17 -0
- data/doc/ex/random_threshold_channel.rb +18 -0
- data/doc/ex/rectangle.rb +33 -0
- data/doc/ex/reduce_noise.rb +28 -0
- data/doc/ex/roll.rb +9 -0
- data/doc/ex/rotate.rb +43 -0
- data/doc/ex/rotate_f.rb +14 -0
- data/doc/ex/roundrect.rb +32 -0
- data/doc/ex/rubyname.rb +31 -0
- data/doc/ex/segment.rb +11 -0
- data/doc/ex/shade.rb +11 -0
- data/doc/ex/shave.rb +15 -0
- data/doc/ex/shear.rb +10 -0
- data/doc/ex/skewx.rb +50 -0
- data/doc/ex/skewy.rb +45 -0
- data/doc/ex/smile.rb +124 -0
- data/doc/ex/solarize.rb +11 -0
- data/doc/ex/splice.rb +16 -0
- data/doc/ex/spread.rb +11 -0
- data/doc/ex/stegano.rb +50 -0
- data/doc/ex/stroke_dasharray.rb +41 -0
- data/doc/ex/stroke_linecap.rb +44 -0
- data/doc/ex/stroke_linejoin.rb +48 -0
- data/doc/ex/stroke_width.rb +47 -0
- data/doc/ex/swirl.rb +17 -0
- data/doc/ex/text.rb +32 -0
- data/doc/ex/text_align.rb +36 -0
- data/doc/ex/text_antialias.rb +33 -0
- data/doc/ex/text_undercolor.rb +26 -0
- data/doc/ex/texture_fill_to_border.rb +34 -0
- data/doc/ex/texture_floodfill.rb +31 -0
- data/doc/ex/texturefill.rb +25 -0
- data/doc/ex/threshold.rb +13 -0
- data/doc/ex/to_blob.rb +14 -0
- data/doc/ex/translate.rb +37 -0
- data/doc/ex/transparent.rb +38 -0
- data/doc/ex/trim.rb +25 -0
- data/doc/ex/unsharp_mask.rb +28 -0
- data/doc/ex/viewex.rb +36 -0
- data/doc/ex/wave.rb +9 -0
- data/doc/ilist.html +1592 -0
- data/doc/image1.html +3009 -0
- data/doc/image2.html +2169 -0
- data/doc/image3.html +2815 -0
- data/doc/imageattrs.html +1319 -0
- data/doc/imusage.html +403 -0
- data/doc/index.html +418 -0
- data/doc/info.html +949 -0
- data/doc/magick.html +439 -0
- data/doc/scripts/doc.js +9 -0
- data/doc/struct.html +1334 -0
- data/doc/usage.html +1318 -0
- data/examples/describe.rb +44 -0
- data/examples/histogram.rb +289 -0
- data/examples/image_opacity.rb +29 -0
- data/examples/import_export.rb +31 -0
- data/examples/pattern_fill.rb +38 -0
- data/examples/rotating_text.rb +47 -0
- data/examples/thumbnail.rb +65 -0
- data/examples/vignette.rb +79 -0
- data/ext/RMagick/MANIFEST +239 -0
- data/ext/RMagick/extconf.rb.in +21 -0
- data/ext/RMagick/rmagick.h +938 -0
- data/ext/RMagick/rmagick_config.h.in +170 -0
- data/ext/RMagick/rmdraw.c +1308 -0
- data/ext/RMagick/rmfill.c +609 -0
- data/ext/RMagick/rmilist.c +685 -0
- data/ext/RMagick/rmimage.c +7980 -0
- data/ext/RMagick/rminfo.c +982 -0
- data/ext/RMagick/rmmain.c +1497 -0
- data/ext/RMagick/rmutil.c +2685 -0
- data/install.rb +1015 -0
- data/lib/RMagick.rb +1486 -0
- data/metaconfig.in +6 -0
- data/post-clean.rb +12 -0
- data/post-install.rb +36 -0
- data/post-setup.rb +245 -0
- data/rmagick.gemspec +22 -0
- data/uninstall.rb +71 -0
- 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
|