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