gd2 1.0
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/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
|