gd2 1.0 → 1.1

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/Rakefile CHANGED
@@ -16,7 +16,7 @@ spec = Gem::Specification.new do |s|
16
16
 
17
17
  s.files = FileList['README', 'COPY*', 'Rakefile', 'lib/**/*.rb']
18
18
  s.autorequire = 'gd2'
19
- s.requirements << 'libgd, v2.0.33 or greater'
19
+ s.requirements << 'libgd, v2.0.33 or greater'
20
20
  s.test_files = FileList['test/*.rb']
21
21
 
22
22
  s.has_rdoc = true
data/lib/gd2.rb CHANGED
@@ -24,9 +24,9 @@ require 'dl'
24
24
  require 'rbconfig'
25
25
 
26
26
  module GD2
27
- VERSION = '1.0'.freeze
27
+ VERSION = '1.1'.freeze
28
28
 
29
- LIB = DL.dlopen(Config::CONFIG['arch'] == 'powerpc-darwin' ?
29
+ LIB = DL.dlopen(Config::CONFIG['arch'].include?('powerpc-darwin') ?
30
30
  'libgd.2.dylib' : 'libgd.so.2')
31
31
  SYM = {
32
32
  :gdImageCreate => 'PII',
@@ -70,6 +70,12 @@ module GD2
70
70
  :gdImagePolygon => '0PPII',
71
71
  :gdImageOpenPolygon => '0PPII',
72
72
  :gdImageFilledPolygon => '0PPII',
73
+ :gdImageArc => '0PIIIIIII',
74
+ :gdImageFilledArc => '0PIIIIIIII',
75
+ # :gdImageEllipse => '0PIIIII',
76
+ :gdImageFilledEllipse => '0PIIIII',
77
+ :gdImageFill => '0PIII',
78
+ :gdImageFillToBorder => '0PIIII',
73
79
  :gdImageSetClip => '0PIIII',
74
80
  :gdImageGetClip => '0Piiii',
75
81
  :gdImageBoundsSafe => 'IPII',
@@ -127,7 +133,7 @@ module GD2
127
133
  CMP_INTERLACE = 128 # Interlaced setting
128
134
  CMP_TRUECOLOR = 256 # Truecolor vs palette differs
129
135
 
130
- # Format flags for Image#gd2_data
136
+ # Format flags for Image#gd2
131
137
 
132
138
  FMT_RAW = 1
133
139
  FMT_COMPRESSED = 2
@@ -158,6 +164,7 @@ class Numeric
158
164
  def degrees
159
165
  self * 2 * Math::PI / 360
160
166
  end
167
+ alias degree degrees
161
168
  end
162
169
 
163
170
  if not self.instance_methods.include? 'to_degrees'
@@ -20,6 +20,8 @@
20
20
  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
21
  #
22
22
 
23
+ require 'matrix'
24
+
23
25
  module GD2
24
26
  class Canvas
25
27
  class NoColorSelectedError < StandardError; end
@@ -36,6 +38,15 @@ module GD2
36
38
  [@x, @y]
37
39
  end
38
40
 
41
+ def transform!(matrix)
42
+ @x, @y = (Matrix.row_vector([@x, @y, 1]) * matrix)[0, 0..1]
43
+ self
44
+ end
45
+
46
+ def transform(matrix)
47
+ dup.transform!(matrix)
48
+ end
49
+
39
50
  def draw(image, mode)
40
51
  image.set_pixel(@x, @y, mode)
41
52
  end
@@ -103,6 +114,73 @@ module GD2
103
114
  end
104
115
  end
105
116
 
117
+ class Arc
118
+ def initialize(center, width, height, range)
119
+ @center, @width, @height = center, width, height
120
+ @range = Range.new(360.degrees - range.end, 360.degrees - range.begin,
121
+ range.exclude_end?)
122
+ end
123
+
124
+ def draw(image, mode)
125
+ SYM[:gdImageArc].call(image.image_ptr, @center.x, @center.y,
126
+ @width, @height,
127
+ @range.begin.to_degrees.round, @range.end.to_degrees.round, mode)
128
+ nil
129
+ end
130
+ end
131
+
132
+ class Wedge < Arc
133
+ # Arc styles
134
+
135
+ ARC = 0
136
+ PIE = ARC
137
+ CHORD = 1
138
+ NO_FILL = 2
139
+ EDGED = 4
140
+
141
+ def initialize(center, width, height, range, chord = false)
142
+ super(center, width, height, range)
143
+ @chord = chord
144
+ end
145
+
146
+ def draw(image, mode)
147
+ SYM[:gdImageFilledArc].call(image.image_ptr, @center.x, @center.y,
148
+ @width, @height,
149
+ @range.begin.to_degrees.round, @range.end.to_degrees.round,
150
+ mode, style)
151
+ nil
152
+ end
153
+
154
+ def style
155
+ (@chord ? CHORD : ARC) | NO_FILL | EDGED
156
+ end
157
+ end
158
+
159
+ class FilledWedge < Wedge
160
+ def style
161
+ super & ~(NO_FILL | EDGED)
162
+ end
163
+ end
164
+
165
+ class Ellipse
166
+ def initialize(center, width, height)
167
+ @center, @width, @height = center, width, height
168
+ end
169
+
170
+ def draw(image, mode)
171
+ SYM[:gdImageArc].call(image.image_ptr, @center.x, @center.y,
172
+ @width, @height, 0, 360, mode)
173
+ nil
174
+ end
175
+ end
176
+
177
+ class FilledEllipse < Ellipse
178
+ def draw(image, mode)
179
+ SYM[:gdImageFilledEllipse].call(image.image_ptr, @center.x, @center.y,
180
+ @width, @height, mode)
181
+ end
182
+ end
183
+
106
184
  class Text
107
185
  def initialize(font, point, angle, string)
108
186
  @font = font
@@ -134,23 +212,25 @@ module GD2
134
212
  end
135
213
  end
136
214
 
137
- attr_reader :color, :thickness, :style, :brush, :tile, :dont_blend
215
+ attr_reader :color, :thickness, :style, :brush, :tile, :dont_blend,
216
+ :transformation_matrix
138
217
  attr_accessor :anti_aliasing, :font
139
218
 
140
219
  # Special colors
141
220
 
142
- STYLED = -2
143
- BRUSHED = -3
144
- STYLED_BRUSHED = -4
145
- TILED = -5
221
+ STYLED = -2
222
+ BRUSHED = -3
223
+ STYLED_BRUSHED = -4
224
+ TILED = -5
146
225
 
147
- TRANSPARENT = -6 # Line styles only; not a color index
148
- ANTI_ALIASED = -7
226
+ TRANSPARENT = -6 # Line styles only; not a color index
227
+ ANTI_ALIASED = -7
149
228
 
150
229
  def initialize(image)
151
230
  @image = image
152
231
  self.thickness = 1
153
232
  self.anti_aliasing = false
233
+ @transformation_matrix = Matrix.identity(3)
154
234
  move_to(0, 0)
155
235
  end
156
236
 
@@ -167,7 +247,7 @@ module GD2
167
247
  if @style = ary
168
248
  SYM[:gdImageSetStyle].call(@image.image_ptr,
169
249
  ary.map { |c|
170
- !c ? TRANSPARENT : c == true ? -1 : @image.color2pixel(c)
250
+ !c ? TRANSPARENT : true == c ? -1 : @image.color2pixel(c)
171
251
  }, ary.length)
172
252
  end
173
253
  end
@@ -190,8 +270,37 @@ module GD2
190
270
  @dont_blend = color ? @image.color2pixel(color) : nil
191
271
  end
192
272
 
273
+ def affine_transform(a, b, c, d, tx, ty)
274
+ old_matrix = @transformation_matrix
275
+ begin
276
+ @transformation_matrix = Matrix[[a, b, 0], [c, d, 0], [tx, ty, 1]] *
277
+ @transformation_matrix
278
+ yield
279
+ ensure
280
+ @transformation_matrix = old_matrix
281
+ end
282
+ end
283
+
284
+ def translate(tx, ty, &block)
285
+ affine_transform(1, 0, 0, 1, tx, ty, &block)
286
+ end
287
+
288
+ def scale(sx, sy = sx, &block)
289
+ affine_transform(sx, 0, 0, sy, 0, 0, &block)
290
+ end
291
+
292
+ def rotate(angle, &block)
293
+ cos = Math.cos(angle)
294
+ sin = Math.sin(angle)
295
+ affine_transform(cos, sin, -sin, cos, 0, 0, &block)
296
+ end
297
+
298
+ def cartesian(&block)
299
+ affine_transform(1, 0, 0, -1, 0, @image.height - 1, &block)
300
+ end
301
+
193
302
  def point(x, y)
194
- Point.new(x, y)
303
+ Point.new(x, y).transform!(transformation_matrix)
195
304
  end
196
305
 
197
306
  def move_to(x, y)
@@ -200,12 +309,14 @@ module GD2
200
309
  end
201
310
 
202
311
  def move(x, y)
203
- @point = point(@point.x + x, @point.y + y)
312
+ @point.transform!(Matrix[[1, 0, 0], [0, 1, 0], [x, y, 1]] *
313
+ @transformation_matrix)
314
+ # @point = point(@point.x + x, @point.y + y)
204
315
  self
205
316
  end
206
317
 
207
318
  def location
208
- @point.coordinates
319
+ @point.transform(transformation_matrix.inverse).coordinates
209
320
  end
210
321
 
211
322
  def line(x1, y1, x2, y2)
@@ -214,14 +325,26 @@ module GD2
214
325
 
215
326
  def line_to(x, y)
216
327
  point2 = point(x, y)
217
- line(@point.x, @point.y, point2.x, point2.y)
328
+ Line.new(@point, point2).draw(@image, line_pixel)
218
329
  @point = point2
219
330
  self
220
331
  end
221
332
 
333
+ def fill
334
+ SYM[:gdImageFill].call(@image.image_ptr, @point.x, @point.y, fill_pixel)
335
+ self
336
+ end
337
+
338
+ def fill_to(border)
339
+ # An apparent bug in gd prevents us from using fill_pixel
340
+ SYM[:gdImageFillToBorder].call(@image.image_ptr, @point.x, @point.y,
341
+ @image.color2pixel(border), pixel)
342
+ self
343
+ end
344
+
222
345
  def rectangle(x1, y1, x2, y2, filled = false)
223
- (filled ? FilledRectangle : Rectangle).new(point(x1, y1),
224
- point(x2, y2)).draw(@image, filled ? fill_pixel : line_pixel)
346
+ (filled ? FilledRectangle : Rectangle).new(point(x1, y1), point(x2, y2)).
347
+ draw(@image, filled ? fill_pixel : line_pixel)
225
348
  end
226
349
 
227
350
  def polygon(points, filled = false, open = false)
@@ -233,6 +356,24 @@ module GD2
233
356
  end
234
357
  end
235
358
 
359
+ def arc(cx, cy, width, height, range)
360
+ Arc.new(point(cx, cy), width, height, range).draw(@image, line_pixel)
361
+ end
362
+
363
+ def wedge(cx, cy, width, height, range, filled = false, chord = false)
364
+ (filled ? FilledWedge : Wedge).new(point(cx, cy), width, height,
365
+ range, chord).draw(@image, filled ? fill_pixel : line_pixel)
366
+ end
367
+
368
+ def ellipse(cx, cy, width, height, filled = false)
369
+ (filled ? FilledEllipse : Ellipse).new(point(cx, cy), width, height).
370
+ draw(@image, filled ? fill_pixel : line_pixel)
371
+ end
372
+
373
+ def circle(cx, cy, diameter, filled = false)
374
+ ellipse(cx, cy, diameter, diameter, filled)
375
+ end
376
+
236
377
  def text(string, angle = 0.0)
237
378
  Text.new(get_font, @point, angle, string).draw(@image, pixel)
238
379
  end
@@ -127,7 +127,7 @@ module GD2
127
127
  # Compare this color with another color. Returns *true* if the associated
128
128
  # red, green, blue, and alpha components are identical.
129
129
  def ==(other)
130
- rgba == other.rgba
130
+ other.kind_of?(Color) && rgba == other.rgba
131
131
  end
132
132
 
133
133
  # Return *true* if this color is visually identical to another color.
@@ -30,11 +30,11 @@ module GD2
30
30
  #
31
31
  # The following font classes may be used without further instantiation:
32
32
  #
33
+ # Font::Tiny
33
34
  # Font::Small
34
- # Font::Large
35
35
  # Font::MediumBold
36
+ # Font::Large
36
37
  # Font::Giant
37
- # Font::Tiny
38
38
  #
39
39
  # == TrueType Fonts
40
40
  #
@@ -144,47 +144,52 @@ module GD2
144
144
  end
145
145
  private_class_method :data_type
146
146
 
147
- # Import an image from a file with the given +filename+. The file extension
148
- # is used to determine the image type (JPEG, PNG, GIF, WBMP, GD, GD2, XBM,
149
- # or XPM). The resulting image will be either of class Image::TrueColor or
150
- # Image::IndexedColor.
147
+ # Import an image from a file with the given +filename+. The :format option
148
+ # or the file extension is used to determine the image type (jpeg, png,
149
+ # gif, wbmp, gd, gd2, xbm, or xpm). The resulting image will be either of
150
+ # class Image::TrueColor or Image::IndexedColor.
151
151
  #
152
- # If the file type is GD2, it is optionally possible to extract only a part
153
- # of the image. Use options :x, :y, :w, and :h to specify the part of the
154
- # image to import.
152
+ # If the file format is gd2, it is optionally possible to extract only a
153
+ # part of the image. Use options :x, :y, :width, and :height to specify the
154
+ # part of the image to import.
155
155
  def self.import(filename, options = {})
156
- md = filename.match /\.([^.]+)\z/
157
- ext = md ? md[1].downcase : nil
158
- if ext == 'xpm'
156
+ unless format = options.delete(:format)
157
+ md = filename.match /\.([^.]+)\z/
158
+ format = md ? md[1].downcase : nil
159
+ end
160
+ format = format.to_sym if format
161
+
162
+ if format == :xpm
159
163
  raise ArgumentError, "Unexpected options #{options.inspect}" unless
160
164
  options.empty?
161
165
  ptr = SYM[:gdImageCreateFromXpm].call(filename)[0]
162
- elsif ext == 'gd2' && !options.empty?
163
- x, y, w, h =
166
+ elsif format == :gd2 && !options.empty?
167
+ x, y, width, height =
164
168
  options.delete(:x) || 0, options.delete(:y) || 0,
165
- options.delete(:w), options.delete(:h)
169
+ options.delete(:width) || options.delete(:w),
170
+ options.delete(:height) || options.delete(:h)
166
171
  raise ArgumentError, "Unexpected options #{options.inspect}" unless
167
172
  options.empty?
168
- raise ArgumentError, 'Missing required option :w' if w.nil?
169
- raise ArgumentError, 'Missing required option :h' if h.nil?
173
+ raise ArgumentError, 'Missing required option :width' if width.nil?
174
+ raise ArgumentError, 'Missing required option :height' if height.nil?
170
175
  ptr = File.open(filename) do |file|
171
- SYM[:gdImageCreateFromGd2Part].call(file, x, y, w, h)[0]
176
+ SYM[:gdImageCreateFromGd2Part].call(file, x, y, width, height)[0]
172
177
  end
173
178
  else
174
179
  raise ArgumentError, "Unexpected options #{options.inspect}" unless
175
180
  options.empty?
176
181
  create_sym = {
177
- 'jpeg' => :gdImageCreateFromJpeg,
178
- 'jpg' => :gdImageCreateFromJpeg,
179
- 'png' => :gdImageCreateFromPng,
180
- 'gif' => :gdImageCreateFromGif,
181
- 'wbmp' => :gdImageCreateFromWBMP,
182
- 'gd' => :gdImageCreateFromGd,
183
- 'gd2' => :gdImageCreateFromGd2,
184
- 'xbm' => :gdImageCreateFromXbm
185
- }[ext]
182
+ :jpeg => :gdImageCreateFromJpeg,
183
+ :jpg => :gdImageCreateFromJpeg,
184
+ :png => :gdImageCreateFromPng,
185
+ :gif => :gdImageCreateFromGif,
186
+ :wbmp => :gdImageCreateFromWBMP,
187
+ :gd => :gdImageCreateFromGd,
188
+ :gd2 => :gdImageCreateFromGd2,
189
+ :xbm => :gdImageCreateFromXbm
190
+ }[format]
186
191
  raise UnrecognizedImageTypeError,
187
- 'File extension is not recognized' unless create_sym
192
+ 'Format (or file extension) is not recognized' unless create_sym
188
193
  ptr = File.open(filename) { |file| SYM[create_sym].call(file)[0] }
189
194
  end
190
195
  raise LibraryError unless ptr
@@ -405,8 +410,8 @@ module GD2
405
410
  # operations.
406
411
  def with_clipping(x1, y1, x2, y2) #:yields: image
407
412
  clip = clipping
413
+ set_clip = SYM[:gdImageSetClip]
408
414
  begin
409
- set_clip = SYM[:gdImageSetClip]
410
415
  set_clip.call(image_ptr, x1, y1, x2, y2)
411
416
  yield self
412
417
  self
@@ -434,37 +439,42 @@ module GD2
434
439
  end
435
440
 
436
441
  # Export this image to a file with the given +filename+. The image format
437
- # is determined by the file extension (JPEG, PNG, GIF, WBMP, GD, or GD2).
438
- # Returns the size of the written image data. The +options+ are as
439
- # arguments for the Image#jpeg, Image#png, Image#wbmp, or Image#gd2
440
- # methods.
442
+ # is determined by the :format option, or by the file extension (jpeg, png,
443
+ # gif, wbmp, gd, or gd2). Returns the size of the written image data.
444
+ # Additional +options+ are as arguments for the Image#jpeg, Image#png,
445
+ # Image#wbmp, or Image#gd2 methods.
441
446
  def export(filename, options = {})
442
- md = filename.match /\.([^.]+)\z/
443
- case ext = md ? md[1].downcase : nil
444
- when 'jpeg', 'jpg'
447
+ unless format = options.delete(:format)
448
+ md = filename.match /\.([^.]+)\z/
449
+ format = md ? md[1].downcase : nil
450
+ end
451
+ format = format.to_sym if format
452
+
453
+ case format
454
+ when :jpeg, :jpg
445
455
  write_sym = :gdImageJpeg
446
456
  args = [nil, options.delete(:quality) || -1]
447
- when 'png'
457
+ when :png
448
458
  write_sym = :gdImagePngEx
449
459
  args = [nil, options.delete(:level) || -1]
450
- when 'gif'
460
+ when :gif
451
461
  write_sym = :gdImageGif
452
462
  args = [nil]
453
- when 'wbmp'
463
+ when :wbmp
454
464
  write_sym = :gdImageWBMP
455
465
  fgcolor = options.delete(:fgcolor)
456
466
  raise ArgumentError, 'Missing required option :fgcolor' if fgcolor.nil?
457
467
  args = [color2pixel(fgcolor), nil]
458
- when 'gd'
468
+ when :gd
459
469
  write_sym = :gdImageGd
460
470
  args = [nil]
461
- when 'gd2'
471
+ when :gd2
462
472
  write_sym = :gdImageGd2
463
473
  args = [nil, options.delete(:chunk_size) || 0,
464
- options.delete(:fmt) || options.delete(:format) || FMT_COMPRESSED]
474
+ options.delete(:fmt) || FMT_COMPRESSED]
465
475
  else
466
476
  raise UnrecognizedImageTypeError,
467
- 'File extension is not recognized'
477
+ 'Format (or file extension) is not recognized'
468
478
  end
469
479
 
470
480
  raise ArgumentError, "Unrecognized options #{options.inspect}" unless
@@ -549,9 +559,9 @@ module GD2
549
559
  end
550
560
 
551
561
  # Copy a portion of another image to this image, rotating the source
552
- # portion first by the indicated +angle+. The +dst_x+ and +dst_y+ arguments
553
- # indicate the _center_ of the desired destination, and may be floating
554
- # point.
562
+ # portion first by the indicated +angle+ (in radians). The +dst_x+ and
563
+ # +dst_y+ arguments indicate the _center_ of the desired destination, and
564
+ # may be floating point.
555
565
  def copy_from_rotated(other, dst_x, dst_y, src_x, src_y, w, h, angle)
556
566
  SYM[:gdImageCopyRotated].call(image_ptr, other.image_ptr,
557
567
  dst_x.to_f, dst_y.to_f, src_x, src_y, w, h, angle.to_degrees.round)
@@ -568,8 +578,8 @@ module GD2
568
578
  self
569
579
  end
570
580
 
571
- # Rotate this image by the given +angle+ about the given axis coordinates.
572
- # Note that some of the edges of the image may be lost.
581
+ # Rotate this image by the given +angle+ (in radians) about the given axis
582
+ # coordinates. Note that some of the edges of the image may be lost.
573
583
  def rotate!(angle, axis_x = width / 2.0, axis_y = height / 2.0)
574
584
  ptr = self.class.create_image_ptr(width, height, alpha_blending?)
575
585
  SYM[:gdImageCopyRotated].call(ptr, image_ptr,
@@ -696,8 +706,7 @@ module GD2
696
706
  end
697
707
 
698
708
  def color2pixel(color) #:nodoc:
699
- return color.index if color.from_palette?(palette)
700
- palette.exact!(color).index
709
+ color.from_palette?(palette) ? color.index : palette.exact!(color).index
701
710
  end
702
711
 
703
712
  def alpha_blending? #:nodoc:
@@ -735,8 +744,7 @@ module GD2
735
744
  end
736
745
 
737
746
  def to_indexed_color(colors = MAX_COLORS, dither = true) #:nodoc:
738
- return self if palette.used <= colors
739
- super
747
+ palette.used <= colors ? self : super
740
748
  end
741
749
 
742
750
  # Like Image#merge_from except an optional final argument can be specified
data/test/dl.rb CHANGED
@@ -4,7 +4,7 @@ require 'dl'
4
4
  class DLTest < Test::Unit::TestCase
5
5
  def test_dlopen
6
6
  assert_nothing_raised {
7
- DL.dlopen(Config::CONFIG['arch'] == 'powerpc-darwin' ?
7
+ DL.dlopen(Config::CONFIG['arch'].include?('powerpc-darwin') ?
8
8
  'libgd.2.dylib' : 'libgd.so.2')
9
9
  }
10
10
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: gd2
5
5
  version: !ruby/object:Gem::Version
6
- version: "1.0"
7
- date: 2005-11-14 00:00:00 -08:00
6
+ version: "1.1"
7
+ date: 2006-03-03 00:00:00 -08:00
8
8
  summary: Ruby interface to gd 2 library.
9
9
  require_paths:
10
10
  - lib