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.
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