sandofsky-gd2 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,422 @@
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
+ require 'matrix'
24
+
25
+ module GD2
26
+ class Canvas
27
+ class NoColorSelectedError < StandardError; end
28
+ class NoFontSelectedError < StandardError; end
29
+
30
+ class Point
31
+ attr_reader :x, :y
32
+
33
+ def initialize(x, y)
34
+ @x, @y = x, y
35
+ end
36
+
37
+ def coordinates
38
+ [@x, @y]
39
+ end
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
+
50
+ def draw(image, mode)
51
+ image.set_pixel(@x, @y, mode)
52
+ end
53
+ end
54
+
55
+ class Line
56
+ def initialize(point1, point2)
57
+ @p1, @p2 = point1, point2
58
+ end
59
+
60
+ def draw(image, mode)
61
+ SYM[:gdImageLine].call(image.image_ptr,
62
+ @p1.x, @p1.y, @p2.x, @p2.y, mode)
63
+ nil
64
+ end
65
+ end
66
+
67
+ class Rectangle
68
+ def initialize(point1, point2)
69
+ @p1, @p2 = point1, point2
70
+ end
71
+
72
+ def draw(image, mode)
73
+ SYM[draw_sym].call(image.image_ptr, @p1.x, @p1.y, @p2.x, @p2.y, mode)
74
+ nil
75
+ end
76
+
77
+ def draw_sym
78
+ :gdImageRectangle
79
+ end
80
+ end
81
+
82
+ class FilledRectangle < Rectangle
83
+ def draw_sym
84
+ :gdImageFilledRectangle
85
+ end
86
+ end
87
+
88
+ class Polygon
89
+ def initialize(points)
90
+ @points = points
91
+ end
92
+
93
+ def draw(image, mode)
94
+ SYM[draw_sym].call(image.image_ptr, @points.map { |point|
95
+ point.coordinates.pack('i_i_')
96
+ }.join('').to_ptr, @points.length, mode)
97
+ nil
98
+ end
99
+
100
+ def draw_sym
101
+ :gdImagePolygon
102
+ end
103
+ end
104
+
105
+ class OpenPolygon < Polygon
106
+ def draw_sym
107
+ :gdImageOpenPolygon
108
+ end
109
+ end
110
+
111
+ class FilledPolygon < Polygon
112
+ def draw_sym
113
+ :gdImageFilledPolygon
114
+ end
115
+ end
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
+
184
+ class Text
185
+ def initialize(font, point, angle, string)
186
+ @font = font
187
+ @point = point
188
+ @angle = angle
189
+ @string = string
190
+ end
191
+
192
+ def draw(image, color)
193
+ @font.draw(image.image_ptr, @point.x, @point.y, @angle, @string, color)
194
+ end
195
+ end
196
+
197
+ class TextCircle
198
+ def initialize(font, point, radius, text_radius, fill_portion,
199
+ top, bottom)
200
+ @font = font
201
+ @point = point
202
+ @radius = radius
203
+ @text_radius = text_radius
204
+ @fill_portion = fill_portion
205
+ @top = top
206
+ @bottom = bottom
207
+ end
208
+
209
+ def draw(image, color)
210
+ @font.draw_circle(image.image_ptr, @point.x, @point.y, @radius,
211
+ @text_radius, @fill_portion, @top, @bottom, color)
212
+ end
213
+ end
214
+
215
+ attr_reader :color, :thickness, :style, :brush, :tile, :dont_blend,
216
+ :transformation_matrix
217
+ attr_accessor :anti_aliasing, :font
218
+
219
+ # Special colors
220
+
221
+ STYLED = -2
222
+ BRUSHED = -3
223
+ STYLED_BRUSHED = -4
224
+ TILED = -5
225
+
226
+ TRANSPARENT = -6 # Line styles only; not a color index
227
+ ANTI_ALIASED = -7
228
+
229
+ def initialize(image)
230
+ @image = image
231
+ self.thickness = 1
232
+ self.anti_aliasing = false
233
+ @transformation_matrix = Matrix.identity(3)
234
+ move_to(0, 0)
235
+ end
236
+
237
+ def color=(color)
238
+ @pixel = @image.color2pixel(@color = color)
239
+ @brush = @style = nil
240
+ end
241
+
242
+ def thickness=(thickness)
243
+ SYM[:gdImageSetThickness].call(@image.image_ptr, @thickness = thickness)
244
+ end
245
+
246
+ def style=(ary)
247
+ if @style = ary
248
+ SYM[:gdImageSetStyle].call(@image.image_ptr,
249
+ ary.map { |c|
250
+ !c ? TRANSPARENT : true == c ? -1 : @image.color2pixel(c)
251
+ }, ary.length)
252
+ end
253
+ end
254
+
255
+ def brush=(image)
256
+ if @brush = image
257
+ SYM[:gdImageSetBrush].call(@image.image_ptr, image.image_ptr)
258
+ end
259
+ end
260
+
261
+ def tile=(image)
262
+ if @tile = image
263
+ SYM[:gdImageSetTile].call(@image.image_ptr, image.image_ptr)
264
+ end
265
+ end
266
+
267
+ alias anti_aliasing? anti_aliasing
268
+
269
+ def dont_blend=(color)
270
+ @dont_blend = color ? @image.color2pixel(color) : nil
271
+ end
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
+
302
+ def point(x, y)
303
+ Point.new(x, y).transform!(transformation_matrix)
304
+ end
305
+
306
+ def move_to(x, y)
307
+ @point = point(x, y)
308
+ self
309
+ end
310
+
311
+ def move(x, 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)
315
+ self
316
+ end
317
+
318
+ def location
319
+ @point.transform(transformation_matrix.inverse).coordinates
320
+ end
321
+
322
+ def line(x1, y1, x2, y2)
323
+ Line.new(point(x1, y1), point(x2, y2)).draw(@image, line_pixel)
324
+ end
325
+
326
+ def line_to(x, y)
327
+ point2 = point(x, y)
328
+ Line.new(@point, point2).draw(@image, line_pixel)
329
+ @point = point2
330
+ self
331
+ end
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
+
345
+ def rectangle(x1, y1, x2, y2, filled = false)
346
+ (filled ? FilledRectangle : Rectangle).new(point(x1, y1), point(x2, y2)).
347
+ draw(@image, filled ? fill_pixel : line_pixel)
348
+ end
349
+
350
+ def polygon(points, filled = false, open = false)
351
+ points = points.map { |(x, y)| point(x, y) }
352
+ if filled
353
+ FilledPolygon.new(points).draw(@image, fill_pixel)
354
+ else
355
+ (open ? OpenPolygon : Polygon).new(points).draw(@image, line_pixel)
356
+ end
357
+ end
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
+
377
+ def text(string, angle = 0.0)
378
+ Text.new(get_font, @point, angle, string).draw(@image, pixel)
379
+ end
380
+
381
+ def text_circle(top, bottom, radius, text_radius, fill_portion)
382
+ TextCircle.new(get_font, @point, radius, text_radius, fill_portion,
383
+ top, bottom).draw(@image, pixel)
384
+ end
385
+
386
+ private
387
+
388
+ def get_font
389
+ raise NoFontSelectedError, 'No font selected' unless @font
390
+ @font
391
+ end
392
+
393
+ def pixel
394
+ raise NoColorSelectedError, 'No drawing color selected' unless @pixel
395
+ @pixel
396
+ end
397
+
398
+ def line_pixel
399
+ if @style && @brush
400
+ STYLED_BRUSHED
401
+ elsif @style
402
+ STYLED
403
+ elsif @brush
404
+ BRUSHED
405
+ elsif anti_aliasing?
406
+ if @dont_blend
407
+ SYM[:gdImageSetAntiAliasedDontBlend].call(@image.image_ptr,
408
+ pixel, @dont_blend)
409
+ else
410
+ SYM[:gdImageSetAntiAliased].call(@image.image_ptr, pixel)
411
+ end
412
+ ANTI_ALIASED
413
+ else
414
+ pixel
415
+ end
416
+ end
417
+
418
+ def fill_pixel
419
+ @tile ? TILED : pixel
420
+ end
421
+ end
422
+ end
@@ -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
+ other.kind_of?(Color) && 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