tea 0.6.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/COPYING +674 -0
- data/COPYING.LESSER +165 -0
- data/README.rdoc +93 -0
- data/doc/example/bitmap_draw.rb +21 -0
- data/doc/example/bitmap_load.rb +14 -0
- data/doc/example/bitmap_new.rb +19 -0
- data/doc/example/circle.rb +24 -0
- data/doc/example/circle_alpha.rb +45 -0
- data/doc/example/circle_alpha_bitmap.rb +51 -0
- data/doc/example/circle_bitmap.rb +18 -0
- data/doc/example/clip.rb +46 -0
- data/doc/example/event_app.rb +45 -0
- data/doc/example/event_keyboard.rb +43 -0
- data/doc/example/event_mouse.rb +85 -0
- data/doc/example/font_hello.rb +22 -0
- data/doc/example/font_word_wrap.rb +44 -0
- data/doc/example/grab.rb +28 -0
- data/doc/example/init.rb +10 -0
- data/doc/example/lines.rb +49 -0
- data/doc/example/lines_aa.rb +44 -0
- data/doc/example/lines_alpha.rb +33 -0
- data/doc/example/point.rb +26 -0
- data/doc/example/rect.rb +15 -0
- data/doc/example/rect_alpha.rb +75 -0
- data/doc/example/screen_set_mode.rb +18 -0
- data/doc/example/screen_update.rb +14 -0
- data/doc/example/sfont_hello.rb +22 -0
- data/doc/example/smile.png +0 -0
- data/doc/example/smile_bounce.rb +44 -0
- data/doc/example/smile_move.rb +58 -0
- data/doc/example/smile_move_2.rb +78 -0
- data/doc/example/sound.rb +101 -0
- data/doc/example/state_app.rb +33 -0
- data/doc/example/state_keyboard.rb +23 -0
- data/doc/example/state_mouse.rb +60 -0
- data/doc/key_constants.textile +129 -0
- data/doc/key_modifiers.textile +19 -0
- data/doc/reference.textile +421 -0
- data/lib/tea.rb +34 -0
- data/lib/tea/c_bitmap.rb +122 -0
- data/lib/tea/c_error.rb +11 -0
- data/lib/tea/c_font.rb +302 -0
- data/lib/tea/c_sound.rb +144 -0
- data/lib/tea/m_color.rb +50 -0
- data/lib/tea/m_event.rb +65 -0
- data/lib/tea/m_event_app.rb +96 -0
- data/lib/tea/m_event_dispatch.rb +54 -0
- data/lib/tea/m_event_keyboard.rb +311 -0
- data/lib/tea/m_event_mouse.rb +189 -0
- data/lib/tea/mix_blitting.rb +31 -0
- data/lib/tea/mix_clipping.rb +71 -0
- data/lib/tea/mix_grabbing.rb +86 -0
- data/lib/tea/mix_image_saving.rb +70 -0
- data/lib/tea/mix_primitive.rb +613 -0
- data/lib/tea/o_screen.rb +98 -0
- metadata +137 -0
@@ -0,0 +1,613 @@
|
|
1
|
+
# This file holds the classes and methods needed to draw primitives on Bitmaps.
|
2
|
+
|
3
|
+
require 'sdl'
|
4
|
+
|
5
|
+
require 'tea/m_color'
|
6
|
+
|
7
|
+
#
|
8
|
+
module Tea
|
9
|
+
|
10
|
+
# The PrimitiveDrawing mixin enables primitive shapes to be drawn to classes
|
11
|
+
# with an internal SDL::Surface.
|
12
|
+
#
|
13
|
+
# To use this mixin, include it and implement/alias a +primitive_buffer+
|
14
|
+
# method that gets the object's SDL::Surface.
|
15
|
+
#
|
16
|
+
# include 'Primitive'
|
17
|
+
# def primitive_buffer
|
18
|
+
# @internal_sdl_buffer
|
19
|
+
# end
|
20
|
+
module Primitive
|
21
|
+
|
22
|
+
# Clear the drawing buffer. This is the same as drawing a completely black
|
23
|
+
# rectangle over the whole buffer.
|
24
|
+
def clear
|
25
|
+
primitive_buffer.fill_rect 0, 0, primitive_buffer.w, primitive_buffer.h,
|
26
|
+
primitive_color(0x000000ff)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get the color (0xRRGGBBAA) at the point (x, y) on the Bitmap.
|
30
|
+
#
|
31
|
+
# Raises Tea::Error if (x, y) is outside of the Bitmap's area.
|
32
|
+
def [](x, y)
|
33
|
+
buffer = primitive_buffer
|
34
|
+
w = buffer.w
|
35
|
+
h = buffer.h
|
36
|
+
if x < 0 || x >= w || y < 0 || y >= h
|
37
|
+
raise Tea::Error, "can't get color at (#{x}, #{y}), not within #{w}x#{h}", caller
|
38
|
+
end
|
39
|
+
Color.mix(*buffer.get_rgba(buffer[x, y]))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Plot a point at (x, y) with the given color (0xRRGGBBAA) on the Bitmap.
|
43
|
+
#
|
44
|
+
# Raises Tea::Error if (x, y) is outside of the Bitmap's area.
|
45
|
+
def []=(x, y, color)
|
46
|
+
buffer = primitive_buffer
|
47
|
+
w = buffer.w
|
48
|
+
h = buffer.h
|
49
|
+
if x < 0 || x >= w || y < 0 || y >= h
|
50
|
+
raise Tea::Error, "can't plot point (#{x}, #{y}), not within #{w}x#{h}", caller
|
51
|
+
end
|
52
|
+
buffer[x, y] = primitive_color(color)
|
53
|
+
end
|
54
|
+
|
55
|
+
BLEND_MIXER = lambda do |src_r, src_g, src_b, src_a, dest_r, dest_g, dest_b, dest_a, intensity|
|
56
|
+
ai = src_a * intensity
|
57
|
+
ratio = dest_a > 0 ? ai / dest_a.to_f : 1
|
58
|
+
ratio = 1 if ratio > 1
|
59
|
+
final_r = dest_r + (src_r - dest_r) * ratio
|
60
|
+
final_g = dest_g + (src_g - dest_g) * ratio
|
61
|
+
final_b = dest_b + (src_b - dest_b) * ratio
|
62
|
+
final_a = (dest_a + ai < 255) ? (dest_a + ai) : 255
|
63
|
+
[final_r, final_g, final_b, final_a]
|
64
|
+
end
|
65
|
+
|
66
|
+
REPLACE_MIXER = lambda do |src_r, src_g, src_b, src_a, dest_r, dest_g, dest_b, dest_a, intensity|
|
67
|
+
[src_r, src_g, src_b, src_a * intensity]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Draw a rectangle of size w * h with the top-left corner at (x, y) with
|
71
|
+
# the given color (0xRRGGBBAA). Hash arguments that can be used:
|
72
|
+
#
|
73
|
+
# +:mix+:: +:blend+ averages the RGB parts the rectangle and destination
|
74
|
+
# colours according to the colour's alpha (default).
|
75
|
+
# +:replace+ writes over the full RGBA parts of the rectangle
|
76
|
+
# area's pixels.
|
77
|
+
#
|
78
|
+
# Raises Tea::Error if w or h are less than 0, or if +:mix+ is given an
|
79
|
+
# unrecognised symbol.
|
80
|
+
def rect(x, y, w, h, color, options=nil)
|
81
|
+
if w < 0 || h < 0 || (options && options[:mix] == :blend && (w < 1 || h < 1))
|
82
|
+
raise Tea::Error, "can't draw rectangle of size #{w}x#{h}", caller
|
83
|
+
end
|
84
|
+
|
85
|
+
r, g, b, a = Color.split(color)
|
86
|
+
|
87
|
+
if options == nil || options[:mix] == nil
|
88
|
+
mix = (a < 0xff) ? :blend : :replace
|
89
|
+
else
|
90
|
+
unless [:blend, :replace].include?(options[:mix])
|
91
|
+
raise Tea::Error, "invalid mix option \"#{options[:mix]}\"", caller
|
92
|
+
end
|
93
|
+
mix = (a < 0xff) ? options[:mix] : :replace
|
94
|
+
end
|
95
|
+
|
96
|
+
case mix
|
97
|
+
when :blend
|
98
|
+
if primitive_buffer.class == SDL::Screen
|
99
|
+
# SGE's broken alpha blending doesn't matter on the screen, so
|
100
|
+
# optimise for it. rubysdl's draw_rect is off-by-one for width and
|
101
|
+
# height, so compensate for that.
|
102
|
+
primitive_buffer.draw_rect x, y, w - 1, h - 1, primitive_rgba_to_color(r, g, b, 255), true, a
|
103
|
+
else
|
104
|
+
# CAUTION: This is _really_ slow, almost unusably so. Perhaps I
|
105
|
+
# should consider not making :blend the default mix mode.
|
106
|
+
primitive_rect x, y, w, h, r, g, b, a, BLEND_MIXER
|
107
|
+
end
|
108
|
+
when :replace
|
109
|
+
primitive_buffer.fill_rect x, y, w, h, primitive_rgba_to_color(r, g, b, a)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Draw a line from (x1, y1) to (x2, y2) with the given color (0xRRGGBBAA).
|
114
|
+
# Optional hash arguments:
|
115
|
+
#
|
116
|
+
# +:antialias+:: If true, smooth the line with antialiasing.
|
117
|
+
# +:mix+:: +:blend+ averages the RGB parts of the line and
|
118
|
+
# destination colours by the line alpha (default).
|
119
|
+
# +:replace+ writes over the RGBA parts of the line
|
120
|
+
# destination pixels.
|
121
|
+
def line(x1, y1, x2, y2, color, options=nil)
|
122
|
+
r, g, b, a = Color.split(color)
|
123
|
+
|
124
|
+
if options == nil
|
125
|
+
antialias = false
|
126
|
+
mix = (a < 0xff) ? :blend : :replace
|
127
|
+
else
|
128
|
+
if options[:mix] && [:blend, :replace].include?(options[:mix]) == false
|
129
|
+
raise Tea::Error, "invalid mix option \"#{mix}\"", caller
|
130
|
+
end
|
131
|
+
|
132
|
+
antialias = options[:antialias] || false
|
133
|
+
mix = (options[:mix] && a < 0xff) ? options[:mix] : :replace
|
134
|
+
mix = (a < 0xff) ? (options[:mix] ? options[:mix] : :blend) : :replace
|
135
|
+
end
|
136
|
+
|
137
|
+
if primitive_buffer.class == SDL::Screen
|
138
|
+
primitive_buffer.draw_line x1, y1, x2, y2, primitive_rgba_to_color(r, g, b, (mix == :replace ? a : 255)), antialias, (mix == :blend ? a : nil)
|
139
|
+
else
|
140
|
+
if antialias
|
141
|
+
primitive_aa_line x1, y1, x2, y2, r, g, b, a, (mix == :blend ? BLEND_MIXER : REPLACE_MIXER)
|
142
|
+
else
|
143
|
+
primitive_line x1, y1, x2, y2, r, g, b, a, (mix == :blend ? BLEND_MIXER : REPLACE_MIXER)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Draw a circle centred at (x, y) with the given radius and color
|
149
|
+
# (0xRRGGBBAA). Optional hash arguments:
|
150
|
+
#
|
151
|
+
# +:outline+:: If true, do not fill the circle, just draw an outline.
|
152
|
+
# +:antialias+:: If true, smooth the edges of the circle with
|
153
|
+
# antialiasing.
|
154
|
+
# +:mix+:: +:blend+ averages the RGB parts of the circle and
|
155
|
+
# destination colours by the colour's alpha (default).
|
156
|
+
# +:replace+ writes over the RGBA parts of the circle's
|
157
|
+
# destination pixels.
|
158
|
+
#
|
159
|
+
# Raises Tea::Error if the radius is less than 0, or :mix is given an
|
160
|
+
# unrecognised symbol.
|
161
|
+
def circle(x, y, radius, color, options=nil)
|
162
|
+
if radius < 0
|
163
|
+
raise Tea::Error, "can't draw circle with radius #{radius}", caller
|
164
|
+
end
|
165
|
+
|
166
|
+
r, g, b, a = Color.split(color)
|
167
|
+
|
168
|
+
if options == nil
|
169
|
+
outline = false
|
170
|
+
antialias = false
|
171
|
+
mix = (a < 0xff) ? :blend : :replace
|
172
|
+
else
|
173
|
+
if options[:mix] && [:blend, :replace].include?(options[:mix]) == false
|
174
|
+
raise Tea::Error, "invalid mix option \"#{mix}\"", caller
|
175
|
+
end
|
176
|
+
|
177
|
+
outline = options[:outline] || false
|
178
|
+
antialias = options[:antialias] || false
|
179
|
+
mix = (a < 0xff) ? (options[:mix] ? options[:mix] : :blend) : :replace
|
180
|
+
end
|
181
|
+
|
182
|
+
if primitive_buffer.class == SDL::Screen
|
183
|
+
case mix
|
184
|
+
when :blend
|
185
|
+
if !outline && antialias
|
186
|
+
# rubysdl can't draw filled antialiased alpha circles for some reason.
|
187
|
+
# Big endian because the SGE-powered circle antialiasing apparently
|
188
|
+
# doesn't like it any other way.
|
189
|
+
ts = SDL::Surface.new(SDL::SWSURFACE, (radius + 1) * 2, (radius + 1) * 2, 32,
|
190
|
+
0xff000000,
|
191
|
+
0x00ff0000,
|
192
|
+
0x0000ff00,
|
193
|
+
0x000000ff)
|
194
|
+
ts.draw_circle radius + 1, radius + 1, radius, ts.map_rgba(r, g, b, a), true, true
|
195
|
+
SDL::Surface.blit ts, 0, 0, ts.w, ts.h, primitive_buffer, x - radius - 1, y - radius - 1
|
196
|
+
else
|
197
|
+
primitive_buffer.draw_circle x, y, radius, primitive_rgba_to_color(r, g, b, 255),
|
198
|
+
!outline, antialias, (a == 255 ? nil : a)
|
199
|
+
end
|
200
|
+
when :replace
|
201
|
+
primitive_buffer.draw_circle x, y, radius, primitive_color(color), !outline, antialias
|
202
|
+
end
|
203
|
+
else
|
204
|
+
# SGE and alpha mixing don't... mix. Gotta do it ourselves.
|
205
|
+
mixer = (mix == :blend) ? BLEND_MIXER : REPLACE_MIXER
|
206
|
+
primitive_circle x, y, radius, !outline, antialias, r, g, b, a, mixer
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
|
212
|
+
# Convert hex_color of the form 0xRRGGBBAA to a color value the
|
213
|
+
# primitive_buffer understands.
|
214
|
+
def primitive_color(hex_color)
|
215
|
+
primitive_rgba_to_color(*Color.split(hex_color))
|
216
|
+
end
|
217
|
+
|
218
|
+
# Generate a colour compatible with the primitive buffer.
|
219
|
+
def primitive_rgba_to_color(red, green, blue, alpha=255)
|
220
|
+
primitive_buffer.map_rgba(red, green, blue, alpha)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Fractional part of x, for primitive_aa_line.
|
224
|
+
def primitive_fpart(x)
|
225
|
+
x - x.truncate
|
226
|
+
end
|
227
|
+
|
228
|
+
# Inverse fractional part of x, for primitive_aa_line.
|
229
|
+
def primitive_rfpart(x)
|
230
|
+
1 - primitive_fpart(x)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Run a block with the primitive buffer locked.
|
234
|
+
def primitive_buffer_with_lock
|
235
|
+
buffer = primitive_buffer
|
236
|
+
if SDL::Surface.auto_lock?
|
237
|
+
auto_lock = true
|
238
|
+
SDL::Surface.auto_lock_off
|
239
|
+
end
|
240
|
+
buffer.lock if buffer.must_lock?
|
241
|
+
begin
|
242
|
+
yield
|
243
|
+
ensure
|
244
|
+
buffer.unlock if buffer.must_lock?
|
245
|
+
SDL::Surface.auto_lock_on if auto_lock
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Draw a line from (x1, y1) to (x2, y2) of color (red, green, blue). The
|
250
|
+
# +alpha+ is passed to the +mixer+ proc to determine how the line and
|
251
|
+
# bitmap colours should be mixed.
|
252
|
+
#
|
253
|
+
# mixer = { |src_red, src_green, src_blue, src_alpha, dest_red, dest_green, dest_blue, dest_alpha, intensity| ... }
|
254
|
+
def primitive_line(x1, y1, x2, y2, red, green, blue, alpha, mixer)
|
255
|
+
|
256
|
+
buffer = primitive_buffer
|
257
|
+
dx = x2 - x1
|
258
|
+
dy = y2 - y1
|
259
|
+
|
260
|
+
# Optimise for REPLACE_MIXER, which doesn't need source pixel info.
|
261
|
+
if mixer == REPLACE_MIXER
|
262
|
+
plot = Proc.new do |x, y, i|
|
263
|
+
buffer[x, y] = buffer.map_rgba(*mixer.call(red, green, blue, alpha, 0, 0, 0, 0, i))
|
264
|
+
end
|
265
|
+
else
|
266
|
+
plot = Proc.new do |x, y, i|
|
267
|
+
buf_r, buf_g, buf_b, buf_a = buffer.get_rgba(buffer[x, y])
|
268
|
+
buffer[x, y] = buffer.map_rgba(*mixer.call(red, green, blue, alpha, buf_r, buf_g, buf_b, buf_a, i))
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
case
|
273
|
+
when dx == 0 && dy == 0 # point
|
274
|
+
plot.call x1, y1, 1.0
|
275
|
+
when dx == 0 && dy != 0 # vertical line
|
276
|
+
primitive_buffer_with_lock do
|
277
|
+
for y in (y1.to_i)..(y2.to_i)
|
278
|
+
plot.call x1, y, 1.0
|
279
|
+
end
|
280
|
+
end
|
281
|
+
when dx != 0 && dy == 0 # horizontal line
|
282
|
+
primitive_buffer_with_lock do
|
283
|
+
for x in (x1.to_i)..(x2.to_i)
|
284
|
+
plot.call x, y1, 1.0
|
285
|
+
end
|
286
|
+
end
|
287
|
+
else # Use Bresenham's line algorithm, from John Hall's Programming Linux Games.
|
288
|
+
|
289
|
+
# Figure out the x and y spans of the line.
|
290
|
+
xspan = dx + 1
|
291
|
+
yspan = dy + 1
|
292
|
+
|
293
|
+
# Figure out the correct increment for the major axis.
|
294
|
+
# Account for negative spans (x2 < x1, for instance).
|
295
|
+
if xspan < 0
|
296
|
+
xinc = -1
|
297
|
+
xspan = -xspan
|
298
|
+
else
|
299
|
+
xinc = 1
|
300
|
+
end
|
301
|
+
if yspan < 0
|
302
|
+
yinc = -1
|
303
|
+
yspan = -yspan
|
304
|
+
else
|
305
|
+
yinc = 1
|
306
|
+
end
|
307
|
+
|
308
|
+
x = x1
|
309
|
+
y = y1
|
310
|
+
error = 0
|
311
|
+
|
312
|
+
primitive_buffer_with_lock do
|
313
|
+
if xspan < yspan # Draw a mostly vertical line.
|
314
|
+
for step in 0..yspan
|
315
|
+
plot.call x, y, 1.0
|
316
|
+
error += xspan
|
317
|
+
if error >= yspan
|
318
|
+
x += xinc
|
319
|
+
error -= yspan
|
320
|
+
end
|
321
|
+
y += yinc
|
322
|
+
end
|
323
|
+
else # Draw a mostly horizontal line.
|
324
|
+
for step in 0..xspan
|
325
|
+
plot.call x, y, 1.0
|
326
|
+
error += yspan
|
327
|
+
if error >= xspan
|
328
|
+
y += yinc
|
329
|
+
error -= xspan
|
330
|
+
end
|
331
|
+
x += xinc
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Draw an antialiased line from (x1, y1) to (x2, y2) of colour (red, green,
|
340
|
+
# blue). The +alpha+ is passed to the +mixer+ proc to determine how the
|
341
|
+
# line and bitmap colours should be mixed.
|
342
|
+
#
|
343
|
+
# mixer = { |src_red, src_green, src_blue, src_alpha, dest_red, dest_green, dest_blue, dest_alpha, intensity| ... }
|
344
|
+
def primitive_aa_line(x1, y1, x2, y2, red, green, blue, alpha, mixer)
|
345
|
+
|
346
|
+
buffer = primitive_buffer
|
347
|
+
dx = x2 - x1
|
348
|
+
dy = y2 - y1
|
349
|
+
|
350
|
+
# Optimise for REPLACE_MIXER, which doesn't need source pixel info.
|
351
|
+
if mixer == REPLACE_MIXER
|
352
|
+
plot = Proc.new do |x, y, i|
|
353
|
+
buffer[x, y] = buffer.map_rgba(*mixer.call(red, green, blue, alpha, 0, 0, 0, 0, i))
|
354
|
+
end
|
355
|
+
else
|
356
|
+
plot = Proc.new do |x, y, i|
|
357
|
+
buf_r, buf_g, buf_b, buf_a = buffer.get_rgba(buffer[x, y])
|
358
|
+
buffer[x, y] = buffer.map_rgba(*mixer.call(red, green, blue, alpha, buf_r, buf_g, buf_b, buf_a, i))
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
case
|
363
|
+
when dx == 0 && dy == 0 # point
|
364
|
+
plot.call x1, y1, 1.0
|
365
|
+
when dx == 0 && dy != 0 # vertical line
|
366
|
+
primitive_buffer_with_lock do
|
367
|
+
for y in (y1.to_i)..(y2.to_i)
|
368
|
+
plot.call x1, y, 1.0
|
369
|
+
end
|
370
|
+
end
|
371
|
+
when dx != 0 && dy == 0 # horizontal line
|
372
|
+
primitive_buffer_with_lock do
|
373
|
+
for x in (x1.to_i)..(x2.to_i)
|
374
|
+
plot.call x, y1, 1.0
|
375
|
+
end
|
376
|
+
end
|
377
|
+
else # Use Xiaolin Wu's line algorithm, described on Wikipedia.
|
378
|
+
|
379
|
+
if dx.abs > dy.abs # Draw a mostly horizontal line.
|
380
|
+
if x2 < x1
|
381
|
+
x1, x2 = x2, x1
|
382
|
+
y1, y2 = y2, y1
|
383
|
+
end
|
384
|
+
gradient = dy.to_f / dx
|
385
|
+
|
386
|
+
# Handle first endpoint.
|
387
|
+
xend = x1.round
|
388
|
+
yend = y1 + gradient * (xend - x1)
|
389
|
+
xgap = primitive_rfpart(x1 + 0.5)
|
390
|
+
xpxl1 = xend # This will be used in the main loop.
|
391
|
+
ypxl1 = yend.truncate
|
392
|
+
plot.call xpxl1, ypxl1, primitive_rfpart(yend) * xgap
|
393
|
+
plot.call xpxl1, ypxl1 + 1, primitive_fpart(yend) * xgap
|
394
|
+
intery = yend + gradient # First y-intersection for the main loop.
|
395
|
+
|
396
|
+
# Handle second endpoint.
|
397
|
+
xend = x2.round
|
398
|
+
yend = y2 + gradient * (xend - x2)
|
399
|
+
xgap = primitive_fpart(x2 + 0.5)
|
400
|
+
xpxl2 = xend # This will be used in the main loop.
|
401
|
+
ypxl2 = yend.truncate
|
402
|
+
plot.call xpxl2, ypxl2, primitive_rfpart(yend) * xgap
|
403
|
+
plot.call xpxl2, ypxl2 = 1, primitive_fpart(yend) * xgap
|
404
|
+
|
405
|
+
primitive_buffer_with_lock do
|
406
|
+
for x in (xpxl1 + 1)..(xpxl2 - 1)
|
407
|
+
intery_int = intery.truncate
|
408
|
+
plot.call x, intery_int, primitive_rfpart(intery)
|
409
|
+
plot.call x, intery_int + 1, primitive_fpart(intery)
|
410
|
+
intery += gradient
|
411
|
+
end
|
412
|
+
end
|
413
|
+
else # Draw a mostly vertical line.
|
414
|
+
if y2 < y1
|
415
|
+
y1, y2 = y2, y1
|
416
|
+
x1, x2 = x2, x1
|
417
|
+
end
|
418
|
+
gradient = dx.to_f / dy
|
419
|
+
|
420
|
+
# Handle first endpoint.
|
421
|
+
yend = y1.round
|
422
|
+
xend = x1 + gradient * (yend - y1)
|
423
|
+
ygap = primitive_rfpart(y1 + 0.5)
|
424
|
+
ypxl1 = yend # This will be used in the main loop.
|
425
|
+
xpxl1 = xend.truncate
|
426
|
+
plot.call xpxl1, ypxl1, primitive_rfpart(xend) * ygap
|
427
|
+
plot.call xpxl1 + 1, ypxl1, primitive_fpart(xend) * ygap
|
428
|
+
interx = xend + gradient # First x-intersection for the main loop.
|
429
|
+
|
430
|
+
# Handle second endpoint.
|
431
|
+
yend = y2.round
|
432
|
+
xend = x2 + gradient * (yend - y2)
|
433
|
+
ygap = primitive_fpart(y2 + 0.5)
|
434
|
+
ypxl2 = yend # This will be used in the main loop.
|
435
|
+
xpxl2 = xend.truncate
|
436
|
+
plot.call xpxl2, ypxl2, primitive_rfpart(xend) * ygap
|
437
|
+
plot.call xpxl2 + 1, ypxl2, primitive_fpart(xend) * ygap
|
438
|
+
|
439
|
+
primitive_buffer_with_lock do
|
440
|
+
for y in (ypxl1 + 1)..(ypxl2 - 1)
|
441
|
+
interx_int = interx.truncate
|
442
|
+
plot.call interx_int, y, primitive_rfpart(interx)
|
443
|
+
plot.call interx_int + 1, y, primitive_fpart(interx)
|
444
|
+
interx += gradient
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
# Draw a rectangle of size (w, h) with the top-left corner at (x, y), with
|
453
|
+
# the colour (red, green, blue) and mixed with +alpha+ and +mixer+.
|
454
|
+
#
|
455
|
+
# mixer = { |src_red, src_green, src_blue, src_alpha, dest_red, dest_green, dest_blue, dest_alpha, intensity| ... }
|
456
|
+
def primitive_rect(x, y, w, h, red, green, blue, alpha, mixer)
|
457
|
+
buffer = primitive_buffer
|
458
|
+
|
459
|
+
# Keep x, y, w, h within the clipping rectangle.
|
460
|
+
x2 = x + w
|
461
|
+
y2 = y + h
|
462
|
+
clip_x, clip_y, clip_w, clip_h = buffer.get_clip_rect
|
463
|
+
clip_x = 0 unless clip_x
|
464
|
+
clip_y = 0 unless clip_y
|
465
|
+
clip_w = buffer.w unless clip_w
|
466
|
+
clip_h = buffer.h unless clip_h
|
467
|
+
clip_x2 = clip_x + clip_w
|
468
|
+
clip_y2 = clip_y + clip_h
|
469
|
+
x = clip_x if x < clip_x
|
470
|
+
y = clip_y if y < clip_y
|
471
|
+
x2 = clip_x2 if x2 > clip_x2
|
472
|
+
y2 = clip_y2 if y2 > clip_y2
|
473
|
+
w = x2 - x
|
474
|
+
h = y2 - y
|
475
|
+
return unless w > 0 && h > 0
|
476
|
+
|
477
|
+
# Optimise for REPLACE_MIXER, which doesn't need source pixel info.
|
478
|
+
if mixer == REPLACE_MIXER
|
479
|
+
primitive_buffer_with_lock do
|
480
|
+
for py in y...(y + h)
|
481
|
+
for px in x...(x + w)
|
482
|
+
buffer[px, py] = buffer.map_rgba(*mixer.call(red, green, blue, alpha, 0, 0, 0, 0, 1.0))
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
else
|
487
|
+
primitive_buffer_with_lock do
|
488
|
+
for py in y...(y + h)
|
489
|
+
for px in x...(x + w)
|
490
|
+
buf_r, buf_g, buf_b, buf_a = buffer.get_rgba(buffer[px, py])
|
491
|
+
buffer[px, py] = buffer.map_rgba(*mixer.call(red, green, blue, alpha, buf_r, buf_g, buf_b, buf_a, 1.0))
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
# Draw a circle centred at (x, y) with the given radius.
|
499
|
+
def primitive_circle(x, y, radius, filled, antialias, red, green, blue, alpha, mixer)
|
500
|
+
buffer = primitive_buffer
|
501
|
+
|
502
|
+
radius = radius.round
|
503
|
+
return if radius < 1 ||
|
504
|
+
x + radius < 0 || x - radius >= buffer.w ||
|
505
|
+
y + radius < 0 || y - radius >= buffer.h
|
506
|
+
|
507
|
+
# Optimise for REPLACE_MIXER, which doesn't need source pixel info.
|
508
|
+
if mixer == REPLACE_MIXER
|
509
|
+
plot = Proc.new do |px, py, i|
|
510
|
+
buffer[px, py] = buffer.map_rgba(*mixer.call(red, green, blue, alpha, 0, 0, 0, 0, i))
|
511
|
+
end
|
512
|
+
else
|
513
|
+
plot = Proc.new do |px, py, i|
|
514
|
+
buf_r, buf_g, buf_b, buf_a = buffer.get_rgba(buffer[px, py])
|
515
|
+
buffer[px, py] = buffer.map_rgba(*mixer.call(red, green, blue, alpha, buf_r, buf_g, buf_b, buf_a, i))
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
# Xiaolin Wu's circle algorithm, with extra stuff. Graphics Gems II, part IX, chapter 9.
|
520
|
+
plot.call x + radius, y, 1.0
|
521
|
+
plot.call x - radius, y, 1.0
|
522
|
+
plot.call x, y + radius, 1.0
|
523
|
+
plot.call x, y - radius, 1.0
|
524
|
+
|
525
|
+
if filled
|
526
|
+
# Fill the centre and cardinal directions before iterating.
|
527
|
+
plot.call x, y, 1.0
|
528
|
+
((x + 1)..(x + radius - 1)).each { |fill_x| plot.call fill_x, y, 1.0 }
|
529
|
+
((y + 1)..(y + radius - 1)).each { |fill_y| plot.call x, fill_y, 1.0 }
|
530
|
+
((x - radius + 1)..(x - 1)).each { |fill_x| plot.call fill_x, y, 1.0 }
|
531
|
+
((y - radius + 1)..(y - 1)).each { |fill_y| plot.call x, fill_y, 1.0 }
|
532
|
+
end
|
533
|
+
|
534
|
+
i = radius
|
535
|
+
j = 0
|
536
|
+
t = 0
|
537
|
+
|
538
|
+
# Stop two steps short so we can join the octants ourselves.
|
539
|
+
until i - 2 <= j
|
540
|
+
j += 1
|
541
|
+
radius2_j2_diff = Math.sqrt(radius * radius - j * j)
|
542
|
+
d = radius2_j2_diff.ceil - radius2_j2_diff
|
543
|
+
inv_d = 1.0 - d
|
544
|
+
# Graphics Gems II seems to get this wrong by writing the reverse condition.
|
545
|
+
i -= 1 unless d > t
|
546
|
+
|
547
|
+
if antialias
|
548
|
+
plot.call x + i, y + j, inv_d
|
549
|
+
plot.call x + j, y + i, inv_d
|
550
|
+
plot.call x - j, y + i, inv_d
|
551
|
+
plot.call x - i, y + j, inv_d
|
552
|
+
plot.call x - i, y - j, inv_d
|
553
|
+
plot.call x - j, y - i, inv_d
|
554
|
+
plot.call x + j, y - i, inv_d
|
555
|
+
plot.call x + i, y - j, inv_d
|
556
|
+
else
|
557
|
+
plot.call x + i, y + j, 1.0
|
558
|
+
plot.call x + j, y + i, 1.0
|
559
|
+
plot.call x - j, y + i, 1.0
|
560
|
+
plot.call x - i, y + j, 1.0
|
561
|
+
plot.call x - i, y - j, 1.0
|
562
|
+
plot.call x - j, y - i, 1.0
|
563
|
+
plot.call x + j, y - i, 1.0
|
564
|
+
plot.call x + i, y - j, 1.0
|
565
|
+
end
|
566
|
+
|
567
|
+
if filled
|
568
|
+
((x + j )..(x + i - 1)).each { |fill_x| plot.call fill_x, y + j, 1.0 }
|
569
|
+
((y + j + 1)..(y + i - 1)).each { |fill_y| plot.call x + j, fill_y, 1.0 }
|
570
|
+
((y + j + 1)..(y + i - 1)).each { |fill_y| plot.call x - j, fill_y, 1.0 }
|
571
|
+
((x - i + 1)..(x - j )).each { |fill_x| plot.call fill_x, y + j, 1.0 }
|
572
|
+
((x - i + 1)..(x - j )).each { |fill_x| plot.call fill_x, y - j, 1.0 }
|
573
|
+
((y - i + 1)..(y - j - 1)).each { |fill_y| plot.call x - j, fill_y, 1.0 }
|
574
|
+
((y - i + 1)..(y - j - 1)).each { |fill_y| plot.call x + j, fill_y, 1.0 }
|
575
|
+
((x + j )..(x + i - 1)).each { |fill_x| plot.call fill_x, y - j, 1.0 }
|
576
|
+
elsif antialias
|
577
|
+
# Anti-aliased drawing needs its inner pixel if no filling is happening.
|
578
|
+
plot.call x + i - 1, y + j, d
|
579
|
+
plot.call x + j, y + i - 1, d
|
580
|
+
plot.call x - j, y + i - 1, d
|
581
|
+
plot.call x - i + 1, y + j, d
|
582
|
+
plot.call x - i + 1, y - j, d
|
583
|
+
plot.call x - j, y - i + 1, d
|
584
|
+
plot.call x + j, y - i + 1, d
|
585
|
+
plot.call x + i - 1, y - j, d
|
586
|
+
end
|
587
|
+
|
588
|
+
t = d
|
589
|
+
end
|
590
|
+
|
591
|
+
# plot final octant meeting points relative to octants 1, 3, 5 & 7
|
592
|
+
j += 1
|
593
|
+
radius2_j2_diff = Math.sqrt(radius * radius - j * j)
|
594
|
+
d = radius2_j2_diff.ceil - radius2_j2_diff
|
595
|
+
inv_d = 1.0 - d
|
596
|
+
i -= 1 unless d > t
|
597
|
+
if antialias
|
598
|
+
plot.call x + i, y + j, inv_d
|
599
|
+
plot.call x - j, y + i, inv_d
|
600
|
+
plot.call x - i, y - j, inv_d
|
601
|
+
plot.call x + j, y - i, inv_d
|
602
|
+
else
|
603
|
+
plot.call x + i, y + j, 1.0
|
604
|
+
plot.call x - j, y + i, 1.0
|
605
|
+
plot.call x - i, y - j, 1.0
|
606
|
+
plot.call x + j, y - i, 1.0
|
607
|
+
end
|
608
|
+
|
609
|
+
end
|
610
|
+
|
611
|
+
end
|
612
|
+
|
613
|
+
end
|