gd2 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +340 -0
- data/COPYRIGHT +18 -0
- data/README +13 -0
- data/Rakefile +32 -0
- data/lib/gd2.rb +184 -0
- data/lib/gd2/canvas.rb +281 -0
- data/lib/gd2/color.rb +236 -0
- data/lib/gd2/font.rb +343 -0
- data/lib/gd2/image.rb +773 -0
- data/lib/gd2/palette.rb +253 -0
- data/test/dl.rb +11 -0
- data/test/image.rb +22 -0
- metadata +57 -0
data/lib/gd2/canvas.rb
ADDED
@@ -0,0 +1,281 @@
|
|
1
|
+
#
|
2
|
+
# Ruby/GD2 -- Ruby binding for gd 2 graphics library
|
3
|
+
#
|
4
|
+
# Copyright © 2005 Robert Leslie
|
5
|
+
#
|
6
|
+
# This file is part of Ruby/GD2.
|
7
|
+
#
|
8
|
+
# Ruby/GD2 is free software; you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU General Public License as published by the Free
|
10
|
+
# Software Foundation; either version 2 of the License, or (at your option)
|
11
|
+
# any later version.
|
12
|
+
#
|
13
|
+
# This program is distributed in the hope that it will be useful, but
|
14
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
15
|
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
16
|
+
# for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License along
|
19
|
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
20
|
+
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
21
|
+
#
|
22
|
+
|
23
|
+
module GD2
|
24
|
+
class Canvas
|
25
|
+
class NoColorSelectedError < StandardError; end
|
26
|
+
class NoFontSelectedError < StandardError; end
|
27
|
+
|
28
|
+
class Point
|
29
|
+
attr_reader :x, :y
|
30
|
+
|
31
|
+
def initialize(x, y)
|
32
|
+
@x, @y = x, y
|
33
|
+
end
|
34
|
+
|
35
|
+
def coordinates
|
36
|
+
[@x, @y]
|
37
|
+
end
|
38
|
+
|
39
|
+
def draw(image, mode)
|
40
|
+
image.set_pixel(@x, @y, mode)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Line
|
45
|
+
def initialize(point1, point2)
|
46
|
+
@p1, @p2 = point1, point2
|
47
|
+
end
|
48
|
+
|
49
|
+
def draw(image, mode)
|
50
|
+
SYM[:gdImageLine].call(image.image_ptr,
|
51
|
+
@p1.x, @p1.y, @p2.x, @p2.y, mode)
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Rectangle
|
57
|
+
def initialize(point1, point2)
|
58
|
+
@p1, @p2 = point1, point2
|
59
|
+
end
|
60
|
+
|
61
|
+
def draw(image, mode)
|
62
|
+
SYM[draw_sym].call(image.image_ptr, @p1.x, @p1.y, @p2.x, @p2.y, mode)
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def draw_sym
|
67
|
+
:gdImageRectangle
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class FilledRectangle < Rectangle
|
72
|
+
def draw_sym
|
73
|
+
:gdImageFilledRectangle
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Polygon
|
78
|
+
def initialize(points)
|
79
|
+
@points = points
|
80
|
+
end
|
81
|
+
|
82
|
+
def draw(image, mode)
|
83
|
+
SYM[draw_sym].call(image.image_ptr, @points.map { |point|
|
84
|
+
point.coordinates.pack('i_i_')
|
85
|
+
}.join('').to_ptr, @points.length, mode)
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def draw_sym
|
90
|
+
:gdImagePolygon
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class OpenPolygon < Polygon
|
95
|
+
def draw_sym
|
96
|
+
:gdImageOpenPolygon
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class FilledPolygon < Polygon
|
101
|
+
def draw_sym
|
102
|
+
:gdImageFilledPolygon
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class Text
|
107
|
+
def initialize(font, point, angle, string)
|
108
|
+
@font = font
|
109
|
+
@point = point
|
110
|
+
@angle = angle
|
111
|
+
@string = string
|
112
|
+
end
|
113
|
+
|
114
|
+
def draw(image, color)
|
115
|
+
@font.draw(image.image_ptr, @point.x, @point.y, @angle, @string, color)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class TextCircle
|
120
|
+
def initialize(font, point, radius, text_radius, fill_portion,
|
121
|
+
top, bottom)
|
122
|
+
@font = font
|
123
|
+
@point = point
|
124
|
+
@radius = radius
|
125
|
+
@text_radius = text_radius
|
126
|
+
@fill_portion = fill_portion
|
127
|
+
@top = top
|
128
|
+
@bottom = bottom
|
129
|
+
end
|
130
|
+
|
131
|
+
def draw(image, color)
|
132
|
+
@font.draw_circle(image.image_ptr, @point.x, @point.y, @radius,
|
133
|
+
@text_radius, @fill_portion, @top, @bottom, color)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
attr_reader :color, :thickness, :style, :brush, :tile, :dont_blend
|
138
|
+
attr_accessor :anti_aliasing, :font
|
139
|
+
|
140
|
+
# Special colors
|
141
|
+
|
142
|
+
STYLED = -2
|
143
|
+
BRUSHED = -3
|
144
|
+
STYLED_BRUSHED = -4
|
145
|
+
TILED = -5
|
146
|
+
|
147
|
+
TRANSPARENT = -6 # Line styles only; not a color index
|
148
|
+
ANTI_ALIASED = -7
|
149
|
+
|
150
|
+
def initialize(image)
|
151
|
+
@image = image
|
152
|
+
self.thickness = 1
|
153
|
+
self.anti_aliasing = false
|
154
|
+
move_to(0, 0)
|
155
|
+
end
|
156
|
+
|
157
|
+
def color=(color)
|
158
|
+
@pixel = @image.color2pixel(@color = color)
|
159
|
+
@brush = @style = nil
|
160
|
+
end
|
161
|
+
|
162
|
+
def thickness=(thickness)
|
163
|
+
SYM[:gdImageSetThickness].call(@image.image_ptr, @thickness = thickness)
|
164
|
+
end
|
165
|
+
|
166
|
+
def style=(ary)
|
167
|
+
if @style = ary
|
168
|
+
SYM[:gdImageSetStyle].call(@image.image_ptr,
|
169
|
+
ary.map { |c|
|
170
|
+
!c ? TRANSPARENT : c == true ? -1 : @image.color2pixel(c)
|
171
|
+
}, ary.length)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def brush=(image)
|
176
|
+
if @brush = image
|
177
|
+
SYM[:gdImageSetBrush].call(@image.image_ptr, image.image_ptr)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def tile=(image)
|
182
|
+
if @tile = image
|
183
|
+
SYM[:gdImageSetTile].call(@image.image_ptr, image.image_ptr)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
alias anti_aliasing? anti_aliasing
|
188
|
+
|
189
|
+
def dont_blend=(color)
|
190
|
+
@dont_blend = color ? @image.color2pixel(color) : nil
|
191
|
+
end
|
192
|
+
|
193
|
+
def point(x, y)
|
194
|
+
Point.new(x, y)
|
195
|
+
end
|
196
|
+
|
197
|
+
def move_to(x, y)
|
198
|
+
@point = point(x, y)
|
199
|
+
self
|
200
|
+
end
|
201
|
+
|
202
|
+
def move(x, y)
|
203
|
+
@point = point(@point.x + x, @point.y + y)
|
204
|
+
self
|
205
|
+
end
|
206
|
+
|
207
|
+
def location
|
208
|
+
@point.coordinates
|
209
|
+
end
|
210
|
+
|
211
|
+
def line(x1, y1, x2, y2)
|
212
|
+
Line.new(point(x1, y1), point(x2, y2)).draw(@image, line_pixel)
|
213
|
+
end
|
214
|
+
|
215
|
+
def line_to(x, y)
|
216
|
+
point2 = point(x, y)
|
217
|
+
line(@point.x, @point.y, point2.x, point2.y)
|
218
|
+
@point = point2
|
219
|
+
self
|
220
|
+
end
|
221
|
+
|
222
|
+
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)
|
225
|
+
end
|
226
|
+
|
227
|
+
def polygon(points, filled = false, open = false)
|
228
|
+
points = points.map { |(x, y)| point(x, y) }
|
229
|
+
if filled
|
230
|
+
FilledPolygon.new(points).draw(@image, fill_pixel)
|
231
|
+
else
|
232
|
+
(open ? OpenPolygon : Polygon).new(points).draw(@image, line_pixel)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def text(string, angle = 0.0)
|
237
|
+
Text.new(get_font, @point, angle, string).draw(@image, pixel)
|
238
|
+
end
|
239
|
+
|
240
|
+
def text_circle(top, bottom, radius, text_radius, fill_portion)
|
241
|
+
TextCircle.new(get_font, @point, radius, text_radius, fill_portion,
|
242
|
+
top, bottom).draw(@image, pixel)
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def get_font
|
248
|
+
raise NoFontSelectedError, 'No font selected' unless @font
|
249
|
+
@font
|
250
|
+
end
|
251
|
+
|
252
|
+
def pixel
|
253
|
+
raise NoColorSelectedError, 'No drawing color selected' unless @pixel
|
254
|
+
@pixel
|
255
|
+
end
|
256
|
+
|
257
|
+
def line_pixel
|
258
|
+
if @style && @brush
|
259
|
+
STYLED_BRUSHED
|
260
|
+
elsif @style
|
261
|
+
STYLED
|
262
|
+
elsif @brush
|
263
|
+
BRUSHED
|
264
|
+
elsif anti_aliasing?
|
265
|
+
if @dont_blend
|
266
|
+
SYM[:gdImageSetAntiAliasedDontBlend].call(@image.image_ptr,
|
267
|
+
pixel, @dont_blend)
|
268
|
+
else
|
269
|
+
SYM[:gdImageSetAntiAliased].call(@image.image_ptr, pixel)
|
270
|
+
end
|
271
|
+
ANTI_ALIASED
|
272
|
+
else
|
273
|
+
pixel
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def fill_pixel
|
278
|
+
@tile ? TILED : pixel
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
data/lib/gd2/color.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
#
|
2
|
+
# Ruby/GD2 -- Ruby binding for gd 2 graphics library
|
3
|
+
#
|
4
|
+
# Copyright © 2005 Robert Leslie
|
5
|
+
#
|
6
|
+
# This file is part of Ruby/GD2.
|
7
|
+
#
|
8
|
+
# Ruby/GD2 is free software; you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU General Public License as published by the Free
|
10
|
+
# Software Foundation; either version 2 of the License, or (at your option)
|
11
|
+
# any later version.
|
12
|
+
#
|
13
|
+
# This program is distributed in the hope that it will be useful, but
|
14
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
15
|
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
16
|
+
# for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License along
|
19
|
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
20
|
+
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
21
|
+
#
|
22
|
+
|
23
|
+
module GD2
|
24
|
+
#
|
25
|
+
# = Description
|
26
|
+
#
|
27
|
+
# Color objects hold the red, green, blue, and alpha components for a pixel
|
28
|
+
# color. Color objects may also be linked to a particular palette index
|
29
|
+
# associated with an image.
|
30
|
+
#
|
31
|
+
# == Creating
|
32
|
+
#
|
33
|
+
# Color objects are created by specifying the individual components:
|
34
|
+
#
|
35
|
+
# red = Color[1.0, 0.0, 0.0]
|
36
|
+
# green = Color[0.0, 1.0, 0.0]
|
37
|
+
# blue = Color[0.0, 0.0, 1.0]
|
38
|
+
#
|
39
|
+
# transparent_yellow = Color[1.0, 1.0, 0.0, 0.5]
|
40
|
+
#
|
41
|
+
# The components may be specified as a percentage, as an explicit value
|
42
|
+
# between 0..RGB_MAX or 0..ALPHA_MAX, or as another color from which the
|
43
|
+
# associated component will be extracted.
|
44
|
+
#
|
45
|
+
class Color
|
46
|
+
attr_reader :rgba #:nodoc:
|
47
|
+
|
48
|
+
# The palette index of this color, if associated with an image palette
|
49
|
+
attr_reader :index
|
50
|
+
|
51
|
+
# The palette of this color, if associated with an image palette
|
52
|
+
attr_reader :palette
|
53
|
+
|
54
|
+
alias to_i rgba
|
55
|
+
|
56
|
+
def self.normalize(value, max, component = nil) #:nodoc:
|
57
|
+
case value
|
58
|
+
when Integer
|
59
|
+
(value < 0) ? 0 : (value > max) ? max : value
|
60
|
+
when Float
|
61
|
+
normalize((value * max).round, max, component)
|
62
|
+
when Color
|
63
|
+
value.send(component)
|
64
|
+
else
|
65
|
+
return normalize(value.to_i, max) if value.respond_to?(:to_i)
|
66
|
+
raise TypeError
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.pack(r, g, b, a) #:nodoc:
|
71
|
+
(a << 24) + (r << 16) + (g << 8) + b
|
72
|
+
end
|
73
|
+
|
74
|
+
class << self
|
75
|
+
alias [] new
|
76
|
+
end
|
77
|
+
|
78
|
+
# Create a new Color object with the given component values.
|
79
|
+
def initialize(r, g, b, a = ALPHA_OPAQUE)
|
80
|
+
r = self.class.normalize(r, RGB_MAX, :red)
|
81
|
+
g = self.class.normalize(g, RGB_MAX, :green)
|
82
|
+
b = self.class.normalize(b, RGB_MAX, :blue)
|
83
|
+
a = self.class.normalize(a, ALPHA_MAX, :alpha)
|
84
|
+
|
85
|
+
init_with_rgba(self.class.pack(r, g, b, a))
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.new_from_rgba(rgba) #:nodoc:
|
89
|
+
allocate.init_with_rgba(rgba)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.new_from_palette(r, g, b, a, index, palette) #:nodoc:
|
93
|
+
allocate.init_with_rgba(pack(r, g, b, a), index, palette)
|
94
|
+
end
|
95
|
+
|
96
|
+
def init_with_rgba(rgba, index = nil, palette = nil) #:nodoc:
|
97
|
+
@rgba = rgba
|
98
|
+
@index = index
|
99
|
+
@palette = palette
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
BLACK = Color[0.0, 0.0, 0.0].freeze
|
104
|
+
WHITE = Color[1.0, 1.0, 1.0].freeze
|
105
|
+
TRANSPARENT = Color[0.0, 0.0, 0.0, ALPHA_TRANSPARENT].freeze
|
106
|
+
|
107
|
+
# Return *true* if this color is associated with the specified palette,
|
108
|
+
# or with any palette if *nil* is given.
|
109
|
+
def from_palette?(palette = nil)
|
110
|
+
@palette && @index && (palette.nil? || palette.equal?(@palette))
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return a string description of this color.
|
114
|
+
def to_s
|
115
|
+
s = 'RGB'
|
116
|
+
s += "A" if alpha != ALPHA_OPAQUE
|
117
|
+
s += "[#{@index}]" if @index
|
118
|
+
s += '#' + [red, green, blue].map { |e| '%02X' % e }.join('')
|
119
|
+
s += '%02X' % alpha if alpha != ALPHA_OPAQUE
|
120
|
+
s
|
121
|
+
end
|
122
|
+
|
123
|
+
def inspect #:nodoc:
|
124
|
+
"<#{to_s}>"
|
125
|
+
end
|
126
|
+
|
127
|
+
# Compare this color with another color. Returns *true* if the associated
|
128
|
+
# red, green, blue, and alpha components are identical.
|
129
|
+
def ==(other)
|
130
|
+
rgba == other.rgba
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return *true* if this color is visually identical to another color.
|
134
|
+
def ===(other)
|
135
|
+
self == other || (self.transparent? && other.transparent?)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Compare this color with another color in a manner that takes into account
|
139
|
+
# palette identities.
|
140
|
+
def eql?(other)
|
141
|
+
self == other &&
|
142
|
+
(palette.nil? || other.palette.nil? ||
|
143
|
+
(palette == other.palette && index == other.index))
|
144
|
+
end
|
145
|
+
|
146
|
+
def hash #:nodoc:
|
147
|
+
rgba.hash
|
148
|
+
end
|
149
|
+
|
150
|
+
def rgba=(value) #:nodoc:
|
151
|
+
@rgba = value
|
152
|
+
@palette[@index] = self if from_palette?
|
153
|
+
end
|
154
|
+
|
155
|
+
# Return the red component of this color (0..RGB_MAX).
|
156
|
+
def red
|
157
|
+
(rgba & 0xFF0000) >> 16
|
158
|
+
end
|
159
|
+
alias r red
|
160
|
+
|
161
|
+
# Modify the red component of this color. If this color is associated
|
162
|
+
# with a palette entry, this also modifies the palette.
|
163
|
+
def red=(value)
|
164
|
+
self.rgba = (rgba & ~0xFF0000) |
|
165
|
+
(self.class.normalize(value, RGB_MAX, :red) << 16)
|
166
|
+
end
|
167
|
+
alias r= red=
|
168
|
+
|
169
|
+
# Return the green component of this color (0..RGB_MAX).
|
170
|
+
def green
|
171
|
+
(rgba & 0x00FF00) >> 8
|
172
|
+
end
|
173
|
+
alias g green
|
174
|
+
|
175
|
+
# Modify the green component of this color. If this color is associated
|
176
|
+
# with a palette entry, this also modifies the palette.
|
177
|
+
def green=(value)
|
178
|
+
self.rgba = (rgba & ~0x00FF00) |
|
179
|
+
(self.class.normalize(value, RGB_MAX, :green) << 8)
|
180
|
+
end
|
181
|
+
alias g= green=
|
182
|
+
|
183
|
+
# Return the blue component of this color (0..RGB_MAX).
|
184
|
+
def blue
|
185
|
+
rgba & 0x0000FF
|
186
|
+
end
|
187
|
+
alias b blue
|
188
|
+
|
189
|
+
# Modify the blue component of this color. If this color is associated
|
190
|
+
# with a palette entry, this also modifies the palette.
|
191
|
+
def blue=(value)
|
192
|
+
self.rgba = (rgba & ~0x0000FF) |
|
193
|
+
self.class.normalize(value, RGB_MAX, :blue)
|
194
|
+
end
|
195
|
+
alias b= blue=
|
196
|
+
|
197
|
+
# Return the alpha component of this color (0..ALPHA_MAX).
|
198
|
+
def alpha
|
199
|
+
(rgba & 0x7F000000) >> 24
|
200
|
+
end
|
201
|
+
alias a alpha
|
202
|
+
|
203
|
+
# Modify the alpha component of this color. If this color is associated
|
204
|
+
# with a palette entry, this also modifies the palette.
|
205
|
+
def alpha=(value)
|
206
|
+
self.rgba = (rgba & ~0xFF000000) |
|
207
|
+
(self.class.normalize(value, ALPHA_MAX, :alpha) << 24)
|
208
|
+
end
|
209
|
+
alias a= alpha=
|
210
|
+
|
211
|
+
# Alpha blend this color with the given color. If this color is associated
|
212
|
+
# with a palette entry, this also modifies the palette.
|
213
|
+
def alpha_blend!(other)
|
214
|
+
self.rgba = SYM[:gdAlphaBlend].call(rgba, other.rgba)[0]
|
215
|
+
self
|
216
|
+
end
|
217
|
+
alias << alpha_blend!
|
218
|
+
|
219
|
+
# Like Color#alpha_blend! except returns a new Color without modifying
|
220
|
+
# the receiver.
|
221
|
+
def alpha_blend(other)
|
222
|
+
dup.alpha_blend!(other)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Return *true* if the alpha channel of this color is completely
|
226
|
+
# transparent.
|
227
|
+
def transparent?
|
228
|
+
alpha == ALPHA_TRANSPARENT
|
229
|
+
end
|
230
|
+
|
231
|
+
# Return *true* if the alpha channel of this color is completely opaque.
|
232
|
+
def opaque?
|
233
|
+
alpha == ALPHA_OPAQUE
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|