tea 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/COPYING +674 -0
  2. data/COPYING.LESSER +165 -0
  3. data/README.rdoc +93 -0
  4. data/doc/example/bitmap_draw.rb +21 -0
  5. data/doc/example/bitmap_load.rb +14 -0
  6. data/doc/example/bitmap_new.rb +19 -0
  7. data/doc/example/circle.rb +24 -0
  8. data/doc/example/circle_alpha.rb +45 -0
  9. data/doc/example/circle_alpha_bitmap.rb +51 -0
  10. data/doc/example/circle_bitmap.rb +18 -0
  11. data/doc/example/clip.rb +46 -0
  12. data/doc/example/event_app.rb +45 -0
  13. data/doc/example/event_keyboard.rb +43 -0
  14. data/doc/example/event_mouse.rb +85 -0
  15. data/doc/example/font_hello.rb +22 -0
  16. data/doc/example/font_word_wrap.rb +44 -0
  17. data/doc/example/grab.rb +28 -0
  18. data/doc/example/init.rb +10 -0
  19. data/doc/example/lines.rb +49 -0
  20. data/doc/example/lines_aa.rb +44 -0
  21. data/doc/example/lines_alpha.rb +33 -0
  22. data/doc/example/point.rb +26 -0
  23. data/doc/example/rect.rb +15 -0
  24. data/doc/example/rect_alpha.rb +75 -0
  25. data/doc/example/screen_set_mode.rb +18 -0
  26. data/doc/example/screen_update.rb +14 -0
  27. data/doc/example/sfont_hello.rb +22 -0
  28. data/doc/example/smile.png +0 -0
  29. data/doc/example/smile_bounce.rb +44 -0
  30. data/doc/example/smile_move.rb +58 -0
  31. data/doc/example/smile_move_2.rb +78 -0
  32. data/doc/example/sound.rb +101 -0
  33. data/doc/example/state_app.rb +33 -0
  34. data/doc/example/state_keyboard.rb +23 -0
  35. data/doc/example/state_mouse.rb +60 -0
  36. data/doc/key_constants.textile +129 -0
  37. data/doc/key_modifiers.textile +19 -0
  38. data/doc/reference.textile +421 -0
  39. data/lib/tea.rb +34 -0
  40. data/lib/tea/c_bitmap.rb +122 -0
  41. data/lib/tea/c_error.rb +11 -0
  42. data/lib/tea/c_font.rb +302 -0
  43. data/lib/tea/c_sound.rb +144 -0
  44. data/lib/tea/m_color.rb +50 -0
  45. data/lib/tea/m_event.rb +65 -0
  46. data/lib/tea/m_event_app.rb +96 -0
  47. data/lib/tea/m_event_dispatch.rb +54 -0
  48. data/lib/tea/m_event_keyboard.rb +311 -0
  49. data/lib/tea/m_event_mouse.rb +189 -0
  50. data/lib/tea/mix_blitting.rb +31 -0
  51. data/lib/tea/mix_clipping.rb +71 -0
  52. data/lib/tea/mix_grabbing.rb +86 -0
  53. data/lib/tea/mix_image_saving.rb +70 -0
  54. data/lib/tea/mix_primitive.rb +613 -0
  55. data/lib/tea/o_screen.rb +98 -0
  56. 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