gd2 1.0 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
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