rmagick4j 0.3.1-java → 0.3.2-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/RMagick.jar CHANGED
Binary file
data/lib/RMagick.rb ADDED
@@ -0,0 +1,1768 @@
1
+ # $Id: RMagick.rb,v 1.51 2007/01/20 23:13:50 rmagick Exp $
2
+ #==============================================================================
3
+ # Copyright (C) 2007 by Timothy P. Hunter
4
+ # Name: RMagick.rb
5
+ # Author: Tim Hunter
6
+ # Purpose: Extend Ruby to interface with ImageMagick.
7
+ # Notes: RMagick.so defines the classes. The code below adds methods
8
+ # to the classes.
9
+ #==============================================================================
10
+
11
+ require 'RMagick.so'
12
+
13
+ module Magick
14
+ @@formats = nil
15
+
16
+ def Magick.formats(&block)
17
+ @@formats ||= Magick.init_formats
18
+ if block_given?
19
+ @@formats.each { |k,v| yield(k,v) }
20
+ self
21
+ else
22
+ @@formats
23
+ end
24
+ end
25
+
26
+
27
+ # Geometry class and related enum constants
28
+ class GeometryValue < Enum
29
+ # no methods
30
+ end
31
+
32
+ PercentGeometry = GeometryValue.new(:PercentGeometry, 1)
33
+ AspectGeometry = GeometryValue.new(:AspectGeometry, 2)
34
+ LessGeometry = GeometryValue.new(:LessGeometry, 3)
35
+ GreaterGeometry = GeometryValue.new(:GreaterGeometry, 4)
36
+ AreaGeometry = GeometryValue.new(:AreaGeometry, 5)
37
+
38
+ class Geometry
39
+ FLAGS = ['', '%', '!', '<', '>', '@']
40
+ RFLAGS = { '%' => PercentGeometry,
41
+ '!' => AspectGeometry,
42
+ '<' => LessGeometry,
43
+ '>' => GreaterGeometry,
44
+ '@' => AreaGeometry }
45
+
46
+ attr_accessor :width, :height, :x, :y, :flag
47
+
48
+ def initialize(width=nil, height=nil, x=nil, y=nil, flag=nil)
49
+
50
+ # Support floating-point width and height arguments so Geometry
51
+ # objects can be used to specify Image#density= arguments.
52
+ if width == nil
53
+ @width = 0
54
+ elsif width.to_f >= 0.0
55
+ @width = width.to_f
56
+ else
57
+ Kernel.raise ArgumentError, "width must be >= 0: #{width}"
58
+ end
59
+ if height == nil
60
+ @height = 0
61
+ elsif height.to_f >= 0.0
62
+ @height = height.to_f
63
+ else
64
+ Kernel.raise ArgumentError, "height must be >= 0: #{height}"
65
+ end
66
+
67
+ @x = x.to_i
68
+ @y = y.to_i
69
+ @flag = flag
70
+ end
71
+
72
+ # Construct an object from a geometry string
73
+ RE = /\A(\d*)(?:x(\d+))?([-+]\d+)?([-+]\d+)?([%!<>@]?)\Z/
74
+
75
+ def Geometry.from_s(str)
76
+ Kernel.raise(ArgumentError, "no geometry string specified") unless str
77
+
78
+ m = RE.match(str)
79
+ if m
80
+ width = m[1].to_i
81
+ height = m[2].to_i
82
+ x = m[3].to_i
83
+ y = m[4].to_i
84
+ flag = RFLAGS[m[5]]
85
+ else
86
+ Kernel.raise ArgumentError, "invalid geometry format"
87
+ end
88
+ Geometry.new(width, height, x, y, flag)
89
+ end
90
+
91
+ # Convert object to a geometry string
92
+ def to_s
93
+ str = ''
94
+ str << sprintf("%g", @width) if @width > 0
95
+ str << 'x' if (@width > 0 || @height > 0)
96
+ str << sprintf("%g", @height) if @height > 0
97
+ str << sprintf("%+d%+d", @x, @y) if (@x != 0 || @y != 0)
98
+ str << FLAGS[@flag.to_i]
99
+ end
100
+ end
101
+
102
+
103
+ class Draw
104
+
105
+ # Thse hashes are used to map Magick constant
106
+ # values to the strings used in the primitives.
107
+ ALIGN_TYPE_NAMES = {
108
+ LeftAlign.to_i => 'left',
109
+ RightAlign.to_i => 'right',
110
+ CenterAlign.to_i => 'center'
111
+ }.freeze
112
+ ANCHOR_TYPE_NAMES = {
113
+ StartAnchor.to_i => 'start',
114
+ MiddleAnchor.to_i => 'middle',
115
+ EndAnchor.to_i => 'end'
116
+ }.freeze
117
+ DECORATION_TYPE_NAMES = {
118
+ NoDecoration.to_i => 'none',
119
+ UnderlineDecoration.to_i => 'underline',
120
+ OverlineDecoration.to_i => 'overline',
121
+ LineThroughDecoration.to_i => 'line-through'
122
+ }.freeze
123
+ FONT_WEIGHT_NAMES = {
124
+ AnyWeight.to_i => 'all',
125
+ NormalWeight.to_i => 'normal',
126
+ BoldWeight.to_i => 'bold',
127
+ BolderWeight.to_i => 'bolder',
128
+ LighterWeight.to_i => 'lighter',
129
+ }.freeze
130
+ GRAVITY_NAMES = {
131
+ NorthWestGravity.to_i => 'northwest',
132
+ NorthGravity.to_i => 'north',
133
+ NorthEastGravity.to_i => 'northeast',
134
+ WestGravity.to_i => 'west',
135
+ CenterGravity.to_i => 'center',
136
+ EastGravity.to_i => 'east',
137
+ SouthWestGravity.to_i => 'southwest',
138
+ SouthGravity.to_i => 'south',
139
+ SouthEastGravity.to_i => 'southeast'
140
+ }.freeze
141
+ PAINT_METHOD_NAMES = {
142
+ PointMethod.to_i => 'point',
143
+ ReplaceMethod.to_i => 'replace',
144
+ FloodfillMethod.to_i => 'floodfill',
145
+ FillToBorderMethod.to_i => 'filltoborder',
146
+ ResetMethod.to_i => 'reset'
147
+ }.freeze
148
+ STRETCH_TYPE_NAMES = {
149
+ NormalStretch.to_i => 'normal',
150
+ UltraCondensedStretch.to_i => 'ultra-condensed',
151
+ ExtraCondensedStretch.to_i => 'extra-condensed',
152
+ CondensedStretch.to_i => 'condensed',
153
+ SemiCondensedStretch.to_i => 'semi-condensed',
154
+ SemiExpandedStretch.to_i => 'semi-expanded',
155
+ ExpandedStretch.to_i => 'expanded',
156
+ ExtraExpandedStretch.to_i => 'extra-expanded',
157
+ UltraExpandedStretch.to_i => 'ultra-expanded',
158
+ AnyStretch.to_i => 'all'
159
+ }.freeze
160
+ STYLE_TYPE_NAMES = {
161
+ NormalStyle.to_i => 'normal',
162
+ ItalicStyle.to_i => 'italic',
163
+ ObliqueStyle.to_i => 'oblique',
164
+ AnyStyle.to_i => 'all'
165
+ }.freeze
166
+
167
+ private
168
+ def enquote(str)
169
+ if str.length > 2 && /\A(?:\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})\z/.match(str)
170
+ return str
171
+ else
172
+ return '"' + str + '"'
173
+ end
174
+ end
175
+
176
+ public
177
+
178
+ # Apply coordinate transformations to support scaling (s), rotation (r),
179
+ # and translation (t). Angles are specified in radians.
180
+ def affine(sx, rx, ry, sy, tx, ty)
181
+ primitive "affine " + sprintf("%g,%g,%g,%g,%g,%g", sx, rx, ry, sy, tx, ty)
182
+ end
183
+
184
+ # Draw an arc.
185
+ def arc(startX, startY, endX, endY, startDegrees, endDegrees)
186
+ primitive "arc " + sprintf("%g,%g %g,%g %g,%g",
187
+ startX, startY, endX, endY, startDegrees, endDegrees)
188
+ end
189
+
190
+ # Draw a bezier curve.
191
+ def bezier(*points)
192
+ if points.length == 0
193
+ Kernel.raise ArgumentError, "no points specified"
194
+ elsif points.length % 2 != 0
195
+ Kernel.raise ArgumentError, "odd number of arguments specified"
196
+ end
197
+ primitive "bezier " + points.join(',')
198
+ end
199
+
200
+ # Draw a circle
201
+ def circle(originX, originY, perimX, perimY)
202
+ primitive "circle " + sprintf("%g,%g %g,%g", originX, originY, perimX, perimY)
203
+ end
204
+
205
+ # Invoke a clip-path defined by def_clip_path.
206
+ def clip_path(name)
207
+ primitive "clip-path #{name}"
208
+ end
209
+
210
+ # Define the clipping rule.
211
+ def clip_rule(rule)
212
+ if ( not ["evenodd", "nonzero"].include?(rule.downcase) )
213
+ Kernel.raise ArgumentError, "Unknown clipping rule #{rule}"
214
+ end
215
+ primitive "clip-rule #{rule}"
216
+ end
217
+
218
+ # Define the clip units
219
+ def clip_units(unit)
220
+ if ( not ["userspace", "userspaceonuse", "objectboundingbox"].include?(unit.downcase) )
221
+ Kernel.raise ArgumentError, "Unknown clip unit #{unit}"
222
+ end
223
+ primitive "clip-units #{unit}"
224
+ end
225
+
226
+ # Set color in image according to specified colorization rule. Rule is one of
227
+ # point, replace, floodfill, filltoborder,reset
228
+ def color(x, y, method)
229
+ if ( not PAINT_METHOD_NAMES.has_key?(method.to_i) )
230
+ Kernel.raise ArgumentError, "Unknown PaintMethod: #{method}"
231
+ end
232
+ primitive "color #{x},#{y},#{PAINT_METHOD_NAMES[method.to_i]}"
233
+ end
234
+
235
+ # Specify EITHER the text decoration (none, underline, overline,
236
+ # line-through) OR the text solid background color (any color name or spec)
237
+ def decorate(decoration)
238
+ if ( DECORATION_TYPE_NAMES.has_key?(decoration.to_i) )
239
+ primitive "decorate #{DECORATION_TYPE_NAMES[decoration.to_i]}"
240
+ else
241
+ primitive "decorate #{enquote(decoration)}"
242
+ end
243
+ end
244
+
245
+ # Define a clip-path. A clip-path is a sequence of primitives
246
+ # bracketed by the "push clip-path <name>" and "pop clip-path"
247
+ # primitives. Upon advice from the IM guys, we also bracket
248
+ # the clip-path primitives with "push(pop) defs" and "push
249
+ # (pop) graphic-context".
250
+ def define_clip_path(name)
251
+ begin
252
+ push('defs')
253
+ push('clip-path ', name)
254
+ push('graphic-context')
255
+ yield
256
+ ensure
257
+ pop('graphic-context')
258
+ pop('clip-path')
259
+ pop('defs')
260
+ end
261
+ end
262
+
263
+ # Draw an ellipse
264
+ def ellipse(originX, originY, width, height, arcStart, arcEnd)
265
+ primitive "ellipse " + sprintf("%g,%g %g,%g %g,%g",
266
+ originX, originY, width, height, arcStart, arcEnd)
267
+ end
268
+
269
+ # Let anything through, but the only defined argument
270
+ # is "UTF-8". All others are apparently ignored.
271
+ def encoding(encoding)
272
+ primitive "encoding #{encoding}"
273
+ end
274
+
275
+ # Specify object fill, a color name or pattern name
276
+ def fill(colorspec)
277
+ primitive "fill #{enquote(colorspec)}"
278
+ end
279
+ alias fill_color fill
280
+ alias fill_pattern fill
281
+
282
+ # Specify fill opacity (use "xx%" to indicate percentage)
283
+ def fill_opacity(opacity)
284
+ primitive "fill-opacity #{opacity}"
285
+ end
286
+
287
+ def fill_rule(rule)
288
+ if ( not ["evenodd", "nonzero"].include?(rule.downcase) )
289
+ Kernel.raise ArgumentError, "Unknown fill rule #{rule}"
290
+ end
291
+ primitive "fill-rule #{rule}"
292
+ end
293
+
294
+ # Specify text drawing font
295
+ def font(name)
296
+ primitive "font #{name}"
297
+ end
298
+
299
+ def font_family(name)
300
+ primitive "font-family \'#{name}\'"
301
+ end
302
+
303
+ def font_stretch(stretch)
304
+ if ( not STRETCH_TYPE_NAMES.has_key?(stretch.to_i) )
305
+ Kernel.raise ArgumentError, "Unknown stretch type"
306
+ end
307
+ primitive "font-stretch #{STRETCH_TYPE_NAMES[stretch.to_i]}"
308
+ end
309
+
310
+ def font_style(style)
311
+ if ( not STYLE_TYPE_NAMES.has_key?(style.to_i) )
312
+ Kernel.raise ArgumentError, "Unknown style type"
313
+ end
314
+ primitive "font-style #{STYLE_TYPE_NAMES[style.to_i]}"
315
+ end
316
+
317
+ # The font weight argument can be either a font weight
318
+ # constant or [100,200,...,900]
319
+ def font_weight(weight)
320
+ if ( FONT_WEIGHT_NAMES.has_key?(weight.to_i) )
321
+ primitive "font-weight #{FONT_WEIGHT_NAMES[weight.to_i]}"
322
+ else
323
+ primitive "font-weight #{weight}"
324
+ end
325
+ end
326
+
327
+ # Specify the text positioning gravity, one of:
328
+ # NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast
329
+ def gravity(grav)
330
+ if ( not GRAVITY_NAMES.has_key?(grav.to_i) )
331
+ Kernel.raise ArgumentError, "Unknown text positioning gravity"
332
+ end
333
+ primitive "gravity #{GRAVITY_NAMES[grav.to_i]}"
334
+ end
335
+
336
+ # Draw a line
337
+ def line(startX, startY, endX, endY)
338
+ primitive "line " + sprintf("%g,%g %g,%g", startX, startY, endX, endY)
339
+ end
340
+
341
+ # Set matte (make transparent) in image according to the specified
342
+ # colorization rule
343
+ def matte(x, y, method)
344
+ if ( not PAINT_METHOD_NAMES.has_key?(method.to_i) )
345
+ Kernel.raise ArgumentError, "Unknown paint method"
346
+ end
347
+ primitive "matte #{x},#{y} #{PAINT_METHOD_NAMES[method.to_i]}"
348
+ end
349
+
350
+ # Specify drawing fill and stroke opacities. If the value is a string
351
+ # ending with a %, the number will be multiplied by 0.01.
352
+ def opacity(opacity)
353
+ if (Numeric === opacity)
354
+ if (opacity < 0 || opacity > 1.0)
355
+ Kernel.raise ArgumentError, "opacity must be >= 0 and <= 1.0"
356
+ end
357
+ end
358
+ primitive "opacity #{opacity}"
359
+ end
360
+
361
+ # Draw using SVG-compatible path drawing commands. Note that the
362
+ # primitive requires that the commands be surrounded by quotes or
363
+ # apostrophes. Here we simply use apostrophes.
364
+ def path(cmds)
365
+ primitive "path '" + cmds + "'"
366
+ end
367
+
368
+ # Define a pattern. In the block, call primitive methods to
369
+ # draw the pattern. Reference the pattern by using its name
370
+ # as the argument to the 'fill' or 'stroke' methods
371
+ def pattern(name, x, y, width, height)
372
+ begin
373
+ push('defs')
374
+ push("pattern #{name} #{x} #{y} #{width} #{height}")
375
+ push('graphic-context')
376
+ yield
377
+ ensure
378
+ pop('graphic-context')
379
+ pop('pattern')
380
+ pop('defs')
381
+ end
382
+ end
383
+
384
+ # Set point to fill color.
385
+ def point(x, y)
386
+ primitive "point #{x},#{y}"
387
+ end
388
+
389
+ # Specify the font size in points. Yes, the primitive is "font-size" but
390
+ # in other places this value is called the "pointsize". Give it both names.
391
+ def pointsize(points)
392
+ primitive "font-size #{points}"
393
+ end
394
+ alias font_size pointsize
395
+
396
+ # Draw a polygon
397
+ def polygon(*points)
398
+ if points.length == 0
399
+ Kernel.raise ArgumentError, "no points specified"
400
+ elsif points.length % 2 != 0
401
+ Kernel.raise ArgumentError, "odd number of points specified"
402
+ end
403
+ primitive "polygon " + points.join(',')
404
+ end
405
+
406
+ # Draw a polyline
407
+ def polyline(*points)
408
+ if points.length == 0
409
+ Kernel.raise ArgumentError, "no points specified"
410
+ elsif points.length % 2 != 0
411
+ Kernel.raise ArgumentError, "odd number of points specified"
412
+ end
413
+ primitive "polyline " + points.join(',')
414
+ end
415
+
416
+ # Return to the previously-saved set of whatever
417
+ # pop('graphic-context') (the default if no arguments)
418
+ # pop('defs')
419
+ # pop('gradient')
420
+ # pop('pattern')
421
+
422
+ def pop(*what)
423
+ if what.length == 0
424
+ primitive "pop graphic-context"
425
+ else
426
+ # to_s allows a Symbol to be used instead of a String
427
+ primitive "pop " + what.to_s
428
+ end
429
+ end
430
+
431
+ # Push the current set of drawing options. Also you can use
432
+ # push('graphic-context') (the default if no arguments)
433
+ # push('defs')
434
+ # push('gradient')
435
+ # push('pattern')
436
+ def push(*what)
437
+ if what.length == 0
438
+ primitive "push graphic-context"
439
+ else
440
+ # to_s allows a Symbol to be used instead of a String
441
+ primitive "push " + what.to_s
442
+ end
443
+ end
444
+
445
+ # Draw a rectangle
446
+ def rectangle(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
447
+ primitive "rectangle " + sprintf("%g,%g %g,%g",
448
+ upper_left_x, upper_left_y, lower_right_x, lower_right_y)
449
+ end
450
+
451
+ # Specify coordinate space rotation. "angle" is measured in degrees
452
+ def rotate(angle)
453
+ primitive "rotate #{angle}"
454
+ end
455
+
456
+ # Draw a rectangle with rounded corners
457
+ def roundrectangle(center_x, center_y, width, height, corner_width, corner_height)
458
+ primitive "roundrectangle " + sprintf("%g,%g,%g,%g,%g,%g",
459
+ center_x, center_y, width, height, corner_width, corner_height)
460
+ end
461
+
462
+ # Specify scaling to be applied to coordinate space on subsequent drawing commands.
463
+ def scale(x, y)
464
+ primitive "scale #{x},#{y}"
465
+ end
466
+
467
+ def skewx(angle)
468
+ primitive "skewX #{angle}"
469
+ end
470
+
471
+ def skewy(angle)
472
+ primitive "skewY #{angle}"
473
+ end
474
+
475
+ # Specify the object stroke, a color name or pattern name.
476
+ def stroke(colorspec)
477
+ primitive "stroke #{enquote(colorspec)}"
478
+ end
479
+ alias stroke_color stroke
480
+ alias stroke_pattern stroke
481
+
482
+ # Specify if stroke should be antialiased or not
483
+ def stroke_antialias(bool)
484
+ bool = bool ? '1' : '0'
485
+ primitive "stroke-antialias #{bool}"
486
+ end
487
+
488
+ # Specify a stroke dash pattern
489
+ def stroke_dasharray(*list)
490
+ if list.length == 0
491
+ primitive "stroke-dasharray none"
492
+ else
493
+ list.each { |x|
494
+ if x <= 0 then
495
+ Kernel.raise ArgumentError, "dash array elements must be > 0 (#{x} given)"
496
+ end
497
+ }
498
+ primitive "stroke-dasharray #{list.join(',')}"
499
+ end
500
+ end
501
+
502
+ # Specify the initial offset in the dash pattern
503
+ def stroke_dashoffset(value=0)
504
+ primitive "stroke-dashoffset #{value}"
505
+ end
506
+
507
+ def stroke_linecap(value)
508
+ if ( not ["butt", "round", "square"].include?(value.downcase) )
509
+ Kernel.raise ArgumentError, "Unknown linecap type: #{value}"
510
+ end
511
+ primitive "stroke-linecap #{value}"
512
+ end
513
+
514
+ def stroke_linejoin(value)
515
+ if ( not ["round", "miter", "bevel"].include?(value.downcase) )
516
+ Kernel.raise ArgumentError, "Unknown linejoin type: #{value}"
517
+ end
518
+ primitive "stroke-linejoin #{value}"
519
+ end
520
+
521
+ def stroke_miterlimit(value)
522
+ if (value < 1)
523
+ Kernel.raise ArgumentError, "miterlimit must be >= 1"
524
+ end
525
+ primitive "stroke-miterlimit #{value}"
526
+ end
527
+
528
+ # Specify opacity of stroke drawing color
529
+ # (use "xx%" to indicate percentage)
530
+ def stroke_opacity(value)
531
+ primitive "stroke-opacity #{value}"
532
+ end
533
+
534
+ # Specify stroke (outline) width in pixels.
535
+ def stroke_width(pixels)
536
+ primitive "stroke-width #{pixels}"
537
+ end
538
+
539
+ # Draw text at position x,y. Add quotes to text that is not already quoted.
540
+ def text(x, y, text)
541
+ if text.to_s.empty?
542
+ Kernel.raise ArgumentError, "missing text argument"
543
+ end
544
+ if text.length > 2 && /\A(?:\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})\z/.match(text)
545
+ ; # text already quoted
546
+ elsif !text['\'']
547
+ text = '\''+text+'\''
548
+ elsif !text['"']
549
+ text = '"'+text+'"'
550
+ elsif !(text['{'] || text['}'])
551
+ text = '{'+text+'}'
552
+ else
553
+ # escape existing braces, surround with braces
554
+ text = '{' + text.gsub(/[}]/) { |b| '\\' + b } + '}'
555
+ end
556
+ primitive "text #{x},#{y} #{text}"
557
+ end
558
+
559
+ # Specify text alignment relative to a given point
560
+ def text_align(alignment)
561
+ if ( not ALIGN_TYPE_NAMES.has_key?(alignment.to_i) )
562
+ Kernel.raise ArgumentError, "Unknown alignment constant: #{alignment}"
563
+ end
564
+ primitive "text-align #{ALIGN_TYPE_NAMES[alignment.to_i]}"
565
+ end
566
+
567
+ # SVG-compatible version of text_align
568
+ def text_anchor(anchor)
569
+ if ( not ANCHOR_TYPE_NAMES.has_key?(anchor.to_i) )
570
+ Kernel.raise ArgumentError, "Unknown anchor constant: #{anchor}"
571
+ end
572
+ primitive "text-anchor #{ANCHOR_TYPE_NAMES[anchor.to_i]}"
573
+ end
574
+
575
+ # Specify if rendered text is to be antialiased.
576
+ def text_antialias(boolean)
577
+ boolean = boolean ? '1' : '0'
578
+ primitive "text-antialias #{boolean}"
579
+ end
580
+
581
+ # Specify color underneath text
582
+ def text_undercolor(color)
583
+ primitive "text-undercolor #{enquote(color)}"
584
+ end
585
+
586
+ # Specify center of coordinate space to use for subsequent drawing
587
+ # commands.
588
+ def translate(x, y)
589
+ primitive "translate #{x},#{y}"
590
+ end
591
+ end # class Magick::Draw
592
+
593
+
594
+ # Define IPTC record number:dataset tags for use with Image#get_iptc_dataset
595
+ module IPTC
596
+ module Envelope
597
+ Model_Version = "1:00"
598
+ Destination = "1:05"
599
+ File_Format = "1:20"
600
+ File_Format_Version = "1:22"
601
+ Service_Identifier = "1:30"
602
+ Envelope_Number = "1:40"
603
+ Product_ID = "1:50"
604
+ Envelope_Priority = "1:60"
605
+ Date_Sent = "1:70"
606
+ Time_Sent = "1:80"
607
+ Coded_Character_Set = "1:90"
608
+ UNO = "1:100"
609
+ Unique_Name_of_Object = "1:100"
610
+ ARM_Identifier = "1:120"
611
+ ARM_Version = "1:122"
612
+ end
613
+
614
+ module Application
615
+ #Record_Version = "2:00" abends with IM 6.2.9
616
+ Object_Type_Reference = "2:03"
617
+ Object_Name = "2:05"
618
+ Title = "2:05"
619
+ Edit_Status = "2:07"
620
+ Editorial_Update = "2:08"
621
+ Urgency = "2:10"
622
+ Subject_Reference = "2:12"
623
+ Category = "2:15"
624
+ Supplemental_Category = "2:20"
625
+ Fixture_Identifier = "2:22"
626
+ Keywords = "2:25"
627
+ Content_Location_Code = "2:26"
628
+ Content_Location_Name = "2:27"
629
+ Release_Date = "2:30"
630
+ Release_Time = "2:35"
631
+ Expiration_Date = "2:37"
632
+ Expiration_Time = "2:35"
633
+ Special_Instructions = "2:40"
634
+ Action_Advised = "2:42"
635
+ Reference_Service = "2:45"
636
+ Reference_Date = "2:47"
637
+ Reference_Number = "2:50"
638
+ Date_Created = "2:55"
639
+ Time_Created = "2:60"
640
+ Digital_Creation_Date = "2:62"
641
+ Digital_Creation_Time = "2:63"
642
+ Originating_Program = "2:65"
643
+ Program_Version = "2:70"
644
+ Object_Cycle = "2:75"
645
+ By_Line = "2:80"
646
+ Author = "2:80"
647
+ By_Line_Title = "2:85"
648
+ Author_Position = "2:85"
649
+ City = "2:90"
650
+ Sub_Location = "2:92"
651
+ Province = "2:95"
652
+ State = "2:95"
653
+ Country_Primary_Location_Code = "2:100"
654
+ Country_Primary_Location_Name = "2:101"
655
+ Original_Transmission_Reference = "2:103"
656
+ Headline = "2:105"
657
+ Credit = "2:110"
658
+ Source = "2:115"
659
+ Copyright_Notice = "2:116"
660
+ Contact = "2:118"
661
+ Abstract = "2:120"
662
+ Caption = "2:120"
663
+ Editor = "2:122"
664
+ Caption_Writer = "2:122"
665
+ Rasterized_Caption = "2:125"
666
+ Image_Type = "2:130"
667
+ Image_Orientation = "2:131"
668
+ Language_Identifier = "2:135"
669
+ Audio_Type = "2:150"
670
+ Audio_Sampling_Rate = "2:151"
671
+ Audio_Sampling_Resolution = "2:152"
672
+ Audio_Duration = "2:153"
673
+ Audio_Outcue = "2:154"
674
+ ObjectData_Preview_File_Format = "2:200"
675
+ ObjectData_Preview_File_Format_Version = "2:201"
676
+ ObjectData_Preview_Data = "2:202"
677
+ end
678
+
679
+ module Pre_ObjectData_Descriptor
680
+ Size_Mode = "7:10"
681
+ Max_Subfile_Size = "7:20"
682
+ ObjectData_Size_Announced = "7:90"
683
+ Maximum_ObjectData_Size = "7:95"
684
+ end
685
+
686
+ module ObjectData
687
+ Subfile = "8:10"
688
+ end
689
+
690
+ module Post_ObjectData_Descriptor
691
+ Confirmed_ObjectData_Size = "9:10"
692
+ end
693
+
694
+ # Make all constants above immutable
695
+ constants.each do |record|
696
+ rec = const_get(record)
697
+ rec.constants.each { |ds| rec.const_get(ds).freeze }
698
+ end
699
+
700
+ end # module Magick::IPTC
701
+
702
+ # Ruby-level Magick::Image methods
703
+ class Image
704
+ include Comparable
705
+
706
+ # Provide an alternate version of Draw#annotate, for folks who
707
+ # want to find it in this class.
708
+ def annotate(draw, width, height, x, y, text, &block)
709
+ draw.annotate(self, width, height, x, y, text, &block)
710
+ self
711
+ end
712
+
713
+ # Set the color at x,y
714
+ def color_point(x, y, fill)
715
+ f = copy
716
+ f.pixel_color(x, y, fill)
717
+ return f
718
+ end
719
+
720
+ # Set all pixels that have the same color as the pixel at x,y and
721
+ # are neighbors to the fill color
722
+ def color_floodfill(x, y, fill)
723
+ target = pixel_color(x, y)
724
+ color_flood_fill(target, fill, x, y, Magick::FloodfillMethod)
725
+ end
726
+
727
+ # Set all pixels that are neighbors of x,y and are not the border color
728
+ # to the fill color
729
+ def color_fill_to_border(x, y, fill)
730
+ color_flood_fill(border_color, fill, x, y, Magick::FillToBorderMethod)
731
+ end
732
+
733
+ # Set all pixels to the fill color. Very similar to Image#erase!
734
+ # Accepts either String or Pixel arguments
735
+ def color_reset!(fill)
736
+ save = background_color
737
+ # Change the background color _outside_ the begin block
738
+ # so that if this object is frozen the exeception will be
739
+ # raised before we have to handle it explicitly.
740
+ self.background_color = fill
741
+ begin
742
+ erase!
743
+ ensure
744
+ self.background_color = save
745
+ end
746
+ self
747
+ end
748
+
749
+ # Force an image to exact dimensions without changing the aspect ratio.
750
+ # Resize and crop if necessary. (Thanks to Jerett Taylor!)
751
+ def crop_resized(ncols, nrows, gravity=CenterGravity)
752
+ copy.crop_resized!(ncols, nrows, gravity)
753
+ end
754
+
755
+ def crop_resized!(ncols, nrows, gravity=CenterGravity)
756
+ if ncols != columns || nrows != rows
757
+ scale = [ncols/columns.to_f, nrows/rows.to_f].max
758
+ resize!(scale*columns+0.5, scale*rows+0.5)
759
+ end
760
+ crop!(gravity, ncols, nrows, true) if ncols != columns || nrows != rows
761
+ self
762
+ end
763
+
764
+ # Used by ImageList methods - see ImageList#cur_image
765
+ def cur_image
766
+ self
767
+ end
768
+
769
+ # Retrieve EXIF data by entry or all. If one or more entry names specified,
770
+ # return the values associated with the entries. If no entries specified,
771
+ # return all entries and values. The return value is an array of [name,value]
772
+ # arrays.
773
+ def get_exif_by_entry(*entry)
774
+ ary = Array.new
775
+ if entry.length == 0
776
+ exif_data = self['EXIF:*']
777
+ if exif_data
778
+ exif_data.split("\n").each { |exif| ary.push(exif.split('=')) }
779
+ end
780
+ else
781
+ entry.each do |name|
782
+ rval = self["EXIF:#{name}"]
783
+ ary.push([name, rval])
784
+ end
785
+ end
786
+ return ary
787
+ end
788
+
789
+ # Retrieve EXIF data by tag number or all tag/value pairs. The return value is a hash.
790
+ def get_exif_by_number(*tag)
791
+ hash = Hash.new
792
+ if tag.length == 0
793
+ exif_data = self['EXIF:!']
794
+ if exif_data
795
+ exif_data.split("\n").each do |exif|
796
+ tag, value = exif.split('=')
797
+ tag = tag[1,4].hex
798
+ hash[tag] = value
799
+ end
800
+ end
801
+ else
802
+ tag.each do |num|
803
+ rval = self["EXIF:#{'#%04X' % num}"]
804
+ hash[num] = rval == 'unknown' ? nil : rval
805
+ end
806
+ end
807
+ return hash
808
+ end
809
+
810
+ # Retrieve IPTC information by record number:dataset tag constant defined in
811
+ # Magick::IPTC, above.
812
+ def get_iptc_dataset(ds)
813
+ self['IPTC:'+ds]
814
+ end
815
+
816
+ # Iterate over IPTC record number:dataset tags, yield for each non-nil dataset
817
+ def each_iptc_dataset
818
+ Magick::IPTC.constants.each do |record|
819
+ rec = Magick::IPTC.const_get(record)
820
+ rec.constants.each do |dataset|
821
+ data_field = get_iptc_dataset(rec.const_get(dataset))
822
+ yield(dataset, data_field) unless data_field.nil?
823
+ end
824
+ end
825
+ nil
826
+ end
827
+
828
+ # Patches problematic change to the order of arguments in 1.11.0.
829
+ # Before this release, the order was
830
+ # black_point, gamma, white_point
831
+ # RMagick 1.11.0 changed this to
832
+ # black_point, white_point, gamma
833
+ # This fix tries to determine if the arguments are in the old order and
834
+ # if so, swaps the gamma and white_point arguments. Then it calls
835
+ # level2, which simply accepts the arguments as given.
836
+
837
+ # Inspect the gamma and white point values and swap them if they
838
+ # look like they're in the old order.
839
+
840
+ # (Thanks to Al Evans for the suggestion.)
841
+ def level(black_point=0.0, white_point=nil, gamma=nil)
842
+ black_point = Float(black_point)
843
+
844
+ white_point ||= Magick::MaxRGB - black_point
845
+ white_point = Float(white_point)
846
+
847
+ gamma_arg = gamma
848
+ gamma ||= 1.0
849
+ gamma = Float(gamma)
850
+
851
+ if gamma.abs > 10.0 || white_point.abs <= 10.0 || white_point.abs < gamma.abs
852
+ gamma, white_point = white_point, gamma
853
+ unless gamma_arg
854
+ white_point = Magick::MaxRGB - black_point
855
+ end
856
+ end
857
+
858
+ return level2(black_point, white_point, gamma)
859
+ end
860
+
861
+ # These four methods are equivalent to the Draw#matte method
862
+ # with the "Point", "Replace", "Floodfill", "FilltoBorder", and
863
+ # "Replace" arguments, respectively.
864
+
865
+ # Make the pixel at (x,y) transparent.
866
+ def matte_point(x, y)
867
+ f = copy
868
+ f.opacity = OpaqueOpacity unless f.matte
869
+ pixel = f.pixel_color(x,y)
870
+ pixel.opacity = TransparentOpacity
871
+ f.pixel_color(x, y, pixel)
872
+ return f
873
+ end
874
+
875
+ # Make transparent all pixels that are the same color as the
876
+ # pixel at (x, y).
877
+ def matte_replace(x, y)
878
+ f = copy
879
+ f.opacity = OpaqueOpacity unless f.matte
880
+ target = f.pixel_color(x, y)
881
+ f.transparent(target)
882
+ end
883
+
884
+ # Make transparent any pixel that matches the color of the pixel
885
+ # at (x,y) and is a neighbor.
886
+ def matte_floodfill(x, y)
887
+ f = copy
888
+ f.opacity = OpaqueOpacity unless f.matte
889
+ target = f.pixel_color(x, y)
890
+ f.matte_flood_fill(target, TransparentOpacity,
891
+ x, y, FloodfillMethod)
892
+ end
893
+
894
+ # Make transparent any neighbor pixel that is not the border color.
895
+ def matte_fill_to_border(x, y)
896
+ f = copy
897
+ f.opacity = Magick::OpaqueOpacity unless f.matte
898
+ f.matte_flood_fill(border_color, TransparentOpacity,
899
+ x, y, FillToBorderMethod)
900
+ end
901
+
902
+ # Make all pixels transparent.
903
+ def matte_reset!
904
+ self.opacity = Magick::TransparentOpacity
905
+ self
906
+ end
907
+
908
+ # Corresponds to ImageMagick's -resample option
909
+ def resample(x_res=72.0, y_res=nil)
910
+ y_res ||= x_res
911
+ width = x_res * columns / x_resolution + 0.5
912
+ height = y_res * rows / y_resolution + 0.5
913
+ self.x_resolution = x_res
914
+ self.y_resolution = y_res
915
+ resize(width, height)
916
+ end
917
+
918
+ # Convenience method to resize retaining the aspect ratio.
919
+ # (Thanks to Robert Manni!)
920
+ def resize_to_fit(cols, rows)
921
+ change_geometry(Geometry.new(cols, rows)) do |ncols, nrows|
922
+ resize(ncols, nrows)
923
+ end
924
+ end
925
+
926
+ def resize_to_fit!(cols, rows)
927
+ change_geometry(Geometry.new(cols, rows)) do |ncols, nrows|
928
+ resize!(ncols, nrows)
929
+ end
930
+ end
931
+
932
+ # Replace matching neighboring pixels with texture pixels
933
+ def texture_floodfill(x, y, texture)
934
+ target = pixel_color(x, y)
935
+ texture_flood_fill(target, texture, x, y, FloodfillMethod)
936
+ end
937
+
938
+ # Replace neighboring pixels to border color with texture pixels
939
+ def texture_fill_to_border(x, y, texture)
940
+ texture_flood_fill(border_color, texture, x, y, FillToBorderMethod)
941
+ end
942
+
943
+ # Construct a view. If a block is present, yield and pass the view
944
+ # object, otherwise return the view object.
945
+ def view(x, y, width, height)
946
+ view = View.new(self, x, y, width, height)
947
+
948
+ if block_given?
949
+ begin
950
+ yield(view)
951
+ ensure
952
+ view.sync
953
+ end
954
+ return nil
955
+ else
956
+ return view
957
+ end
958
+ end
959
+
960
+ # Magick::Image::View class
961
+ class View
962
+ attr_reader :x, :y, :width, :height
963
+ attr_accessor :dirty
964
+
965
+ def initialize(img, x, y, width, height)
966
+ if width <= 0 || height <= 0
967
+ Kernel.raise ArgumentError, "invalid geometry (#{width}x#{height}+#{x}+#{y})"
968
+ end
969
+ if x < 0 || y < 0 || (x+width) > img.columns || (y+height) > img.rows
970
+ Kernel.raise RangeError, "geometry (#{width}x#{height}+#{x}+#{y}) exceeds image boundary"
971
+ end
972
+ @view = img.get_pixels(x, y, width, height)
973
+ @img = img
974
+ @x = x
975
+ @y = y
976
+ @width = width
977
+ @height = height
978
+ @dirty = false
979
+ end
980
+
981
+ def [](*args)
982
+ rows = Rows.new(@view, @width, @height, args)
983
+ rows.add_observer(self)
984
+ return rows
985
+ end
986
+
987
+ # Store changed pixels back to image
988
+ def sync(force=false)
989
+ @img.store_pixels(x, y, width, height, @view) if (@dirty || force)
990
+ return (@dirty || force)
991
+ end
992
+
993
+ # Get update from Rows - if @dirty ever becomes
994
+ # true, don't change it back to false!
995
+ def update(rows)
996
+ @dirty = true
997
+ rows.delete_observer(self) # No need to tell us again.
998
+ nil
999
+ end
1000
+
1001
+ # Magick::Image::View::Pixels
1002
+ # Defines channel attribute getters/setters
1003
+ class Pixels < Array
1004
+ include Observable
1005
+
1006
+ # Define a getter and a setter for each channel.
1007
+ [:red, :green, :blue, :opacity].each do |c|
1008
+ module_eval <<-END_EVAL
1009
+ def #{c}
1010
+ return collect { |p| p.#{c} }
1011
+ end
1012
+ def #{c}=(v)
1013
+ each { |p| p.#{c} = v }
1014
+ changed
1015
+ notify_observers(self)
1016
+ nil
1017
+ end
1018
+ END_EVAL
1019
+ end
1020
+
1021
+ end # class Magick::Image::View::Pixels
1022
+
1023
+ # Magick::Image::View::Rows
1024
+ class Rows
1025
+ include Observable
1026
+
1027
+ def initialize(view, width, height, rows)
1028
+ @view = view
1029
+ @width = width
1030
+ @height = height
1031
+ @rows = rows
1032
+ end
1033
+
1034
+ def [](*args)
1035
+ cols(args)
1036
+
1037
+ # Both View::Pixels and Magick::Pixel implement Observable
1038
+ if @unique
1039
+ pixels = @view[@rows[0]*@width + @cols[0]]
1040
+ pixels.add_observer(self)
1041
+ else
1042
+ pixels = View::Pixels.new
1043
+ each do |x|
1044
+ p = @view[x]
1045
+ p.add_observer(self)
1046
+ pixels << p
1047
+ end
1048
+ end
1049
+ pixels
1050
+ end
1051
+
1052
+ def []=(*args)
1053
+ rv = args.delete_at(-1) # get rvalue
1054
+ if ! rv.is_a?(Pixel) # must be a Pixel or a color name
1055
+ begin
1056
+ rv = Pixel.from_color(rv)
1057
+ rescue TypeError
1058
+ Kernel.raise TypeError, "cannot convert #{rv.class} into Pixel"
1059
+ end
1060
+ end
1061
+ cols(args)
1062
+ each { |x| @view[x] = rv.dup }
1063
+ changed
1064
+ notify_observers(self)
1065
+ nil
1066
+ end
1067
+
1068
+ # A pixel has been modified. Tell the view.
1069
+ def update(pixel)
1070
+ changed
1071
+ notify_observers(self)
1072
+ pixel.delete_observer(self) # Don't need to hear again.
1073
+ nil
1074
+ end
1075
+
1076
+ private
1077
+
1078
+ def cols(*args)
1079
+ @cols = args[0] # remove the outermost array
1080
+ @unique = false
1081
+
1082
+ # Convert @rows to an Enumerable object
1083
+ case @rows.length
1084
+ when 0 # Create a Range for all the rows
1085
+ @rows = Range.new(0, @height, true)
1086
+ when 1 # Range, Array, or a single integer
1087
+ # if the single element is already an Enumerable
1088
+ # object, get it.
1089
+ if @rows.first.respond_to? :each
1090
+ @rows = @rows.first
1091
+ else
1092
+ @rows = Integer(@rows.first)
1093
+ if @rows < 0
1094
+ @rows += @height
1095
+ end
1096
+ if @rows < 0 || @rows > @height-1
1097
+ Kernel.raise IndexError, "index [#{@rows}] out of range"
1098
+ end
1099
+ # Convert back to an array
1100
+ @rows = Array.new(1, @rows)
1101
+ @unique = true
1102
+ end
1103
+ when 2
1104
+ # A pair of integers representing the starting column and the number of columns
1105
+ start = Integer(@rows[0])
1106
+ length = Integer(@rows[1])
1107
+
1108
+ # Negative start -> start from last row
1109
+ if start < 0
1110
+ start += @height
1111
+ end
1112
+
1113
+ if start > @height || start < 0 || length < 0
1114
+ Kernel.raise IndexError, "index [#{@rows.first}] out of range"
1115
+ else
1116
+ if start + length > @height
1117
+ length = @height - length
1118
+ length = [length, 0].max
1119
+ end
1120
+ end
1121
+ # Create a Range for the specified set of rows
1122
+ @rows = Range.new(start, start+length, true)
1123
+ end
1124
+
1125
+ case @cols.length
1126
+ when 0 # all rows
1127
+ @cols = Range.new(0, @width, true) # convert to range
1128
+ @unique = false
1129
+ when 1 # Range, Array, or a single integer
1130
+ # if the single element is already an Enumerable
1131
+ # object, get it.
1132
+ if @cols.first.respond_to? :each
1133
+ @cols = @cols.first
1134
+ @unique = false
1135
+ else
1136
+ @cols = Integer(@cols.first)
1137
+ if @cols < 0
1138
+ @cols += @width
1139
+ end
1140
+ if @cols < 0 || @cols > @width-1
1141
+ Kernel.raise IndexError, "index [#{@cols}] out of range"
1142
+ end
1143
+ # Convert back to array
1144
+ @cols = Array.new(1, @cols)
1145
+ @unique &&= true
1146
+ end
1147
+ when 2
1148
+ # A pair of integers representing the starting column and the number of columns
1149
+ start = Integer(@cols[0])
1150
+ length = Integer(@cols[1])
1151
+
1152
+ # Negative start -> start from last row
1153
+ if start < 0
1154
+ start += @width
1155
+ end
1156
+
1157
+ if start > @width || start < 0 || length < 0
1158
+ ; #nop
1159
+ else
1160
+ if start + length > @width
1161
+ length = @width - length
1162
+ length = [length, 0].max
1163
+ end
1164
+ end
1165
+ # Create a Range for the specified set of columns
1166
+ @cols = Range.new(start, start+length, true)
1167
+ @unique = false
1168
+ end
1169
+
1170
+ end
1171
+
1172
+ # iterator called from subscript methods
1173
+ def each
1174
+ maxrows = @height - 1
1175
+ maxcols = @width - 1
1176
+
1177
+ @rows.each do |j|
1178
+ if j > maxrows
1179
+ Kernel.raise IndexError, "index [#{j}] out of range"
1180
+ end
1181
+ @cols.each do |i|
1182
+ if i > maxcols
1183
+ Kernel.raise IndexError, "index [#{i}] out of range"
1184
+ end
1185
+ yield j*@width + i
1186
+ end
1187
+ end
1188
+ nil # useless return value
1189
+ end
1190
+
1191
+ end # class Magick::Image::View::Rows
1192
+
1193
+ end # class Magick::Image::View
1194
+
1195
+ end # class Magick::Image
1196
+
1197
+ class ImageList < Array
1198
+
1199
+ include Comparable
1200
+
1201
+ undef_method :assoc
1202
+ undef_method :flatten! # These methods are undefined
1203
+ undef_method :flatten # because they're not useful
1204
+ undef_method :join # for an ImageList object
1205
+ undef_method :pack
1206
+ undef_method :rassoc
1207
+ undef_method :transpose if Array.instance_methods(false).include? 'transpose'
1208
+ undef_method :zip if Array.instance_methods(false).include? 'zip'
1209
+
1210
+ attr_reader :scene
1211
+
1212
+ protected
1213
+
1214
+ def is_a_image(obj)
1215
+ unless obj.kind_of? Magick::Image
1216
+ Kernel.raise ArgumentError, "Magick::Image required (#{obj.class} given)"
1217
+ end
1218
+ true
1219
+ end
1220
+
1221
+ # Ensure array is always an array of Magick::Image objects
1222
+ def is_a_image_array(ary)
1223
+ unless ary.respond_to? :each
1224
+ Kernel.raise ArgumentError, "Magick::ImageList or array of Magick::Images required (#{ary.class} given)"
1225
+ end
1226
+ ary.each { |obj| is_a_image obj }
1227
+ true
1228
+ end
1229
+
1230
+ # Find old current image, update @scene
1231
+ # cfid is the id of the old current image.
1232
+ def set_cf(cfid)
1233
+ if length == 0
1234
+ @scene = nil
1235
+ return
1236
+ # Don't bother looking for current image
1237
+ elsif @scene == nil || @scene >= length
1238
+ @scene = length - 1
1239
+ return
1240
+ elsif cfid != nil
1241
+ each_with_index do |f,i|
1242
+ if f.__id__ == cfid
1243
+ @scene = i
1244
+ return
1245
+ end
1246
+ end
1247
+ end
1248
+ @scene = length - 1
1249
+ end
1250
+
1251
+ public
1252
+
1253
+ # Allow scene to be set to nil
1254
+ def scene=(n)
1255
+ if n.nil?
1256
+ Kernel.raise IndexError, "scene number out of bounds" unless length == 0
1257
+ @scene = nil
1258
+ return @scene
1259
+ elsif length == 0
1260
+ Kernel.raise IndexError, "scene number out of bounds"
1261
+ end
1262
+
1263
+ n = Integer(n)
1264
+ if n < 0 || n > length - 1
1265
+ Kernel.raise IndexError, "scene number out of bounds"
1266
+ end
1267
+ @scene = n
1268
+ return @scene
1269
+ end
1270
+
1271
+ def [](*args)
1272
+ if (args.length > 1) || args[0].kind_of?(Range)
1273
+ self.class.new.replace super
1274
+ else
1275
+ super
1276
+ end
1277
+ end
1278
+
1279
+ def []=(*args)
1280
+ if args.length == 3 # f[start,length] = [f1,f2...]
1281
+ args[2].kind_of?(Magick::Image) || is_a_image_array(args[2])
1282
+ super
1283
+ args[0] = args[0] + length if (args[0] < 0)
1284
+ args[1] = length - args[0] if (args[0] + args[1] > length)
1285
+ if args[2].kind_of?(Magick::Image)
1286
+ @scene = args[0]
1287
+ else
1288
+ @scene = args[0] + args[2].length - 1
1289
+ end
1290
+ elsif args[0].kind_of? Range # f[first..last] = [f1,f2...]
1291
+ args[1].kind_of?(Magick::Image) || is_a_image_array(args[1])
1292
+ super
1293
+ @scene = args[0].end
1294
+ else # f[index] = f1
1295
+ is_a_image args[1]
1296
+ super # index can be negative
1297
+ @scene = args[0] < 0 ? length + args[0] : args[0]
1298
+ end
1299
+ args.last # return value is always assigned value
1300
+ end
1301
+
1302
+ def &(other)
1303
+ is_a_image_array other
1304
+ cfid = self[@scene].__id__ rescue nil
1305
+ a = self.class.new.replace super
1306
+ a.set_cf cfid
1307
+ return a
1308
+ end
1309
+
1310
+ def *(n)
1311
+ unless n.kind_of? Integer
1312
+ Kernel.raise ArgumentError, "Integer required (#{n.class} given)"
1313
+ end
1314
+ cfid = self[@scene].__id__ rescue nil
1315
+ a = self.class.new.replace super
1316
+ a.set_cf cfid
1317
+ return a
1318
+ end
1319
+
1320
+ def +(other)
1321
+ cfid = self[@scene].__id__ rescue nil
1322
+ a = self.class.new.replace super
1323
+ a.set_cf cfid
1324
+ return a
1325
+ end
1326
+
1327
+ def -(other)
1328
+ is_a_image_array other
1329
+ cfid = self[@scene].__id__ rescue nil
1330
+ a = self.class.new.replace super
1331
+ a.set_cf cfid
1332
+ return a
1333
+ end
1334
+
1335
+ def <<(obj)
1336
+ is_a_image obj
1337
+ a = super
1338
+ @scene = length-1
1339
+ return a
1340
+ end
1341
+
1342
+ def |(other)
1343
+ is_a_image_array other
1344
+ cfid = self[@scene].__id__ rescue nil
1345
+ a = self.class.new.replace super
1346
+ a.set_cf cfid
1347
+ return a
1348
+ end
1349
+
1350
+ def clear
1351
+ @scene = nil
1352
+ super
1353
+ end
1354
+
1355
+ def collect(&block)
1356
+ cfid = self[@scene].__id__ rescue nil
1357
+ a = self.class.new.replace super
1358
+ a.set_cf cfid
1359
+ return a
1360
+ end
1361
+
1362
+ def collect!(&block)
1363
+ super
1364
+ is_a_image_array self
1365
+ self
1366
+ end
1367
+
1368
+ def compact
1369
+ cfid = self[@scene].__id__ rescue nil
1370
+ a = self.class.new.replace super
1371
+ a.set_cf cfid
1372
+ return a
1373
+ end
1374
+
1375
+ def compact!
1376
+ cfid = self[@scene].__id__ rescue nil
1377
+ a = super # returns nil if no changes were made
1378
+ set_cf cfid
1379
+ return a
1380
+ end
1381
+
1382
+ def concat(other)
1383
+ is_a_image_array other
1384
+ a = super
1385
+ @scene = length-1
1386
+ return a
1387
+ end
1388
+
1389
+ def delete(obj, &block)
1390
+ is_a_image obj
1391
+ cfid = self[@scene].__id__ rescue nil
1392
+ a = super
1393
+ set_cf cfid
1394
+ return a
1395
+ end
1396
+
1397
+ def delete_at(ndx)
1398
+ cfid = self[@scene].__id__ rescue nil
1399
+ a = super
1400
+ set_cf cfid
1401
+ return a
1402
+ end
1403
+
1404
+ def delete_if(&block)
1405
+ cfid = self[@scene].__id__ rescue nil
1406
+ a = super
1407
+ set_cf cfid
1408
+ return a
1409
+ end
1410
+
1411
+ def fill(*args, &block)
1412
+ is_a_image args[0] unless block_given?
1413
+ cfid = self[@scene].__id__ rescue nil
1414
+ super
1415
+ is_a_image_array self
1416
+ set_cf cfid
1417
+ return self
1418
+ end
1419
+
1420
+ def find_all(&block)
1421
+ cfid = self[@scene].__id__ rescue nil
1422
+ a = super
1423
+ a.set_cf cfid
1424
+ return a
1425
+ end
1426
+
1427
+ if self.superclass.instance_methods(true).include? 'insert' then
1428
+ def insert(*args)
1429
+ Kernel.raise(ArgumentError, "can't insert nil") unless args.length > 1
1430
+ is_a_image_array args[1,args.length-1]
1431
+ cfid = self[@scene].__id__ rescue nil
1432
+ super
1433
+ set_cf cfid
1434
+ return self
1435
+ end
1436
+ end
1437
+
1438
+ # Enumerable (or Array) has a #map method that conflicts with our
1439
+ # own #map method. RMagick.so has defined a synonym for that #map
1440
+ # called Array#__ary_map__. Here, we define Magick::ImageList#__map__
1441
+ # to allow the use of the Enumerable/Array#map method on ImageList objects.
1442
+ def __map__(&block)
1443
+ cfid = self[@scene].__id__ rescue nil
1444
+ ensure_image = Proc.new do |img|
1445
+ rv = block.call(img)
1446
+ is_a_image rv
1447
+ return rv
1448
+ end
1449
+ a = self.class.new.replace __ary_map__(&ensure_image)
1450
+ a.set_cf cfid
1451
+ return a
1452
+ end
1453
+
1454
+ def map!(&block)
1455
+ ensure_image = Proc.new do |img|
1456
+ rv = block.call(img)
1457
+ is_a_image rv
1458
+ return rv
1459
+ end
1460
+ super(&ensure_image)
1461
+ end
1462
+
1463
+ def pop
1464
+ cfid = self[@scene].__id__ rescue nil
1465
+ a = super # can return nil
1466
+ set_cf cfid
1467
+ return a
1468
+ end
1469
+
1470
+ def push(*objs)
1471
+ objs.each { |o| is_a_image o }
1472
+ super
1473
+ @scene = length - 1
1474
+ self
1475
+ end
1476
+
1477
+ def reject(&block)
1478
+ cfid = self[@scene].__id__ rescue nil
1479
+ a = self.class.new.replace super
1480
+ a.set_cf cfid
1481
+ return a
1482
+ end
1483
+
1484
+ def reject!(&block)
1485
+ cfid = self[@scene].__id__ rescue nil
1486
+ a = super # can return nil
1487
+ set_cf cfid
1488
+ return a
1489
+ end
1490
+
1491
+ def replace(other)
1492
+ is_a_image_array other
1493
+ # Since replace gets called so frequently when @scene == nil
1494
+ # test for it instead of letting rescue catch it.
1495
+ cfid = nil
1496
+ if @scene then
1497
+ cfid = self[@scene].__id__ rescue nil
1498
+ end
1499
+ super
1500
+ # set_cf will fail if the new list has fewer images
1501
+ # than the scene number indicates.
1502
+ @scene = self.length == 0 ? nil : 0
1503
+ set_cf cfid
1504
+ self
1505
+ end
1506
+
1507
+ def reverse
1508
+ cfid = self[@scene].__id__ rescue nil
1509
+ a = self.class.new.replace super
1510
+ a.set_cf cfid
1511
+ return a
1512
+ end
1513
+
1514
+ def reverse!
1515
+ cfid = self[@scene].__id__ rescue nil
1516
+ a = super
1517
+ set_cf cfid
1518
+ return a
1519
+ end
1520
+
1521
+ def select(&block)
1522
+ cfid = self[@scene].__id__ rescue nil
1523
+ a = self.class.new.replace super
1524
+ a.set_cf cfid
1525
+ return a
1526
+ end
1527
+
1528
+ def shift
1529
+ cfid = self[@scene].__id__ rescue nil
1530
+ a = super
1531
+ set_cf cfid
1532
+ return a
1533
+ end
1534
+
1535
+ def slice(*args)
1536
+ self[*args]
1537
+ end
1538
+
1539
+ def slice!(*args)
1540
+ cfid = self[@scene].__id__ rescue nil
1541
+ if args.length > 1 || args[0].kind_of?(Range)
1542
+ a = self.class.new.replace super
1543
+ else
1544
+ a = super
1545
+ end
1546
+ set_cf cfid
1547
+ return a
1548
+ end
1549
+
1550
+ def uniq
1551
+ cfid = self[@scene].__id__ rescue nil
1552
+ a = self.class.new.replace super
1553
+ a.set_cf cfid
1554
+ return a
1555
+ end
1556
+
1557
+ def uniq!(*args)
1558
+ cfid = self[@scene].__id__ rescue nil
1559
+ a = super
1560
+ set_cf cfid
1561
+ return a
1562
+ end
1563
+
1564
+ # @scene -> new object
1565
+ def unshift(obj)
1566
+ is_a_image obj
1567
+ a = super
1568
+ @scene = 0
1569
+ return a
1570
+ end
1571
+
1572
+ # Compare ImageLists
1573
+ # Compare each image in turn until the result of a comparison
1574
+ # is not 0. If all comparisons return 0, then
1575
+ # return if A.scene != B.scene
1576
+ # return A.length <=> B.length
1577
+ def <=>(other)
1578
+ unless other.kind_of? self.class
1579
+ Kernel.raise TypeError, "#{self.class} required (#{other.class} given)"
1580
+ end
1581
+ size = [self.length, other.length].min
1582
+ size.times do |x|
1583
+ r = self[x] <=> other[x]
1584
+ return r unless r == 0
1585
+ end
1586
+ if @scene.nil? && other.scene.nil?
1587
+ return 0
1588
+ elsif @scene.nil? && ! other.scene.nil?
1589
+ Kernel.raise TypeError, "cannot convert nil into #{other.scene.class}"
1590
+ elsif ! @scene.nil? && other.scene.nil?
1591
+ Kernel.raise TypeError, "cannot convert nil into #{self.scene.class}"
1592
+ end
1593
+ r = self.scene <=> other.scene
1594
+ return r unless r == 0
1595
+ return self.length <=> other.length
1596
+ end
1597
+
1598
+ def clone
1599
+ ditto = dup
1600
+ ditto.freeze if frozen?
1601
+ return ditto
1602
+ end
1603
+
1604
+ # Make a deep copy
1605
+ def copy
1606
+ ditto = self.class.new
1607
+ each { |f| ditto << f.copy }
1608
+ ditto.scene = @scene
1609
+ ditto.taint if tainted?
1610
+ return ditto
1611
+ end
1612
+
1613
+ # Return the current image
1614
+ def cur_image
1615
+ if ! @scene
1616
+ Kernel.raise IndexError, "no images in this list"
1617
+ end
1618
+ self[@scene]
1619
+ end
1620
+
1621
+ # Set same delay for all images
1622
+ def delay=(d)
1623
+ if Integer(d) < 0
1624
+ raise ArgumentError, "delay must be greater than or equal to 0"
1625
+ end
1626
+ each { |f| f.delay = Integer(d) }
1627
+ end
1628
+
1629
+ def ticks_per_second=(t)
1630
+ if Integer(t) < 0
1631
+ Kernel.raise ArgumentError, "ticks_per_second must be greater than or equal to 0"
1632
+ end
1633
+ each { |f| f.ticks_per_second = Integer(t) }
1634
+ end
1635
+
1636
+ def dup
1637
+ ditto = self.class.new
1638
+ each {|img| ditto << img}
1639
+ ditto.scene = @scene
1640
+ ditto.taint if tainted?
1641
+ return ditto
1642
+ end
1643
+
1644
+ def from_blob(*blobs, &block)
1645
+ if (blobs.length == 0)
1646
+ Kernel.raise ArgumentError, "no blobs given"
1647
+ end
1648
+ blobs.each { |b|
1649
+ Magick::Image.from_blob(b, &block).each { |n| self << n }
1650
+ }
1651
+ @scene = length - 1
1652
+ self
1653
+ end
1654
+
1655
+ # Initialize new instances
1656
+ def initialize(*filenames)
1657
+ @scene = nil
1658
+ filenames.each { |f|
1659
+ Magick::Image.read(f).each { |n| self << n }
1660
+ }
1661
+ if length > 0
1662
+ @scene = length - 1 # last image in array
1663
+ end
1664
+ self
1665
+ end
1666
+
1667
+ # Call inspect for all the images
1668
+ def inspect
1669
+ ins = '['
1670
+ each {|image| ins << image.inspect << "\n"}
1671
+ ins.chomp("\n") + "]\nscene=#{@scene}"
1672
+ end
1673
+
1674
+ # Set the number of iterations of an animated GIF
1675
+ def iterations=(n)
1676
+ n = Integer(n)
1677
+ if n < 0 || n > 65535
1678
+ Kernel.raise ArgumentError, "iterations must be between 0 and 65535"
1679
+ end
1680
+ each {|f| f.iterations=n}
1681
+ self
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
+ self[@scene].send(methID, *args, &block)
1692
+ else
1693
+ super
1694
+ end
1695
+ rescue NoMethodError
1696
+ Kernel.raise NoMethodError, "undefined method `#{methID.id2name}' for #{self.class}"
1697
+ rescue Exception
1698
+ $@.delete_if { |s| /:in `send'$/.match(s) || /:in `method_missing'$/.match(s) }
1699
+ Kernel.raise
1700
+ end
1701
+ end
1702
+
1703
+ # Ensure respond_to? answers correctly when we are delegating to Image
1704
+ alias_method :__respond_to__?, :respond_to?
1705
+ def respond_to?(methID, priv=false)
1706
+ return true if __respond_to__?(methID, priv)
1707
+ if @scene
1708
+ self[@scene].respond_to?(methID, priv)
1709
+ else
1710
+ super
1711
+ end
1712
+ end
1713
+
1714
+ # Create a new image and add it to the end
1715
+ def new_image(cols, rows, *fill, &info_blk)
1716
+ self << Magick::Image.new(cols, rows, *fill, &info_blk)
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| self << n }
1726
+ }
1727
+ @scene = length - 1
1728
+ self
1729
+ end
1730
+
1731
+ # Read files and concatenate the new images
1732
+ def read(*files, &block)
1733
+ if (files.length == 0)
1734
+ Kernel.raise ArgumentError, "no files given"
1735
+ end
1736
+ files.each { |f|
1737
+ Magick::Image.read(f, &block).each { |n| self << n }
1738
+ }
1739
+ @scene = length - 1
1740
+ self
1741
+ end
1742
+ end # Magick::ImageList
1743
+
1744
+ # Example fill class. Fills the image with the specified background
1745
+ # color, then crosshatches with the specified crosshatch color.
1746
+ # @dist is the number of pixels between hatch lines.
1747
+ # See Magick::Draw examples.
1748
+ class HatchFill
1749
+ def initialize(bgcolor, hatchcolor="white", dist=10)
1750
+ @bgcolor = bgcolor
1751
+ @hatchpixel = Pixel.from_color(hatchcolor)
1752
+ @dist = dist
1753
+ end
1754
+
1755
+ def fill(img) # required
1756
+ img.background_color = @bgcolor
1757
+ img.erase! # sets image to background color
1758
+ pixels = Array.new([img.rows, img.columns].max, @hatchpixel)
1759
+ @dist.step((img.columns-1)/@dist*@dist, @dist) { |x|
1760
+ img.store_pixels(x,0,1,img.rows,pixels)
1761
+ }
1762
+ @dist.step((img.rows-1)/@dist*@dist, @dist) { |y|
1763
+ img.store_pixels(0,y,img.columns,1,pixels)
1764
+ }
1765
+ end
1766
+ end
1767
+
1768
+ end # Magick