aslakhellesoy-png 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *** 1.1.0 / 2007-03-26
2
+
3
+ + 4 major enhancements:
4
+ + Fixed and incorporated Dominik Barathon's optimizations.
5
+ + Wrote inline methods for png_crc and png_join. Now about 15x faster overall.
6
+ + Basic PNG loading.
7
+ + Reoriented x/y origin to bottom left. This will break things!
8
+ + 3 minor enhancements:
9
+ + Awesome ascii art patches from Tom Werner: Canvas#inpsect, Canvas#to_s, Color#to_ascii.
10
+ + Switched to Hoe.
11
+ + PNG.pie_chart from png/pie.
12
+ + 1 bug fix:
13
+ + Fixed bug in PNG::Canvas#each.
14
+
15
+ *** 1.0.0 / 2006-09-31
16
+
17
+ + Birthday!
18
+
@@ -0,0 +1,13 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ example/lines.rb
6
+ example/profile.rb
7
+ example/profile_lines.rb
8
+ lib/png.rb
9
+ lib/png/default_font.png
10
+ lib/png/font.rb
11
+ lib/png/pie.rb
12
+ lib/png/reader.rb
13
+ test/test_png.rb
@@ -0,0 +1,65 @@
1
+ = PNG
2
+
3
+ * http://seattlerb.rubyforge.org/
4
+
5
+ == DESCRIPTION
6
+
7
+ PNG is an almost-pure-ruby PNG library. It lets you write a PNG
8
+ without any C libraries.
9
+
10
+ == FEATURES
11
+
12
+ * Very simple interface.
13
+ * Outputs simple PNG files with ease.
14
+ * Basic PNG reader as well (someday it might do compositing and the like!).
15
+ * Almost pure ruby, does require a compiler.
16
+
17
+ == SYNOPSYS
18
+
19
+ require 'png'
20
+
21
+ canvas = PNG::Canvas.new 200, 200
22
+
23
+ # Set a point to a color
24
+ canvas[100, 100] = PNG::Color::Black
25
+
26
+ # draw an anti-aliased line
27
+ canvas.line 50, 50, 100, 50, PNG::Color::Blue
28
+
29
+ png = PNG.new canvas
30
+ png.save 'blah.png'
31
+
32
+ == REQUIREMENTS
33
+
34
+ + C compiler
35
+ + RubyInline
36
+ + Hoe
37
+
38
+ == INSTALL
39
+
40
+ + sudo gem install -y png
41
+
42
+ == LICENSE
43
+
44
+ (The MIT License)
45
+
46
+ Copyright (c) 2006-2007 Ryan Davis, Eric Hodel, Zen Spider Software
47
+
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of this software and associated documentation files (the
50
+ "Software"), to deal in the Software without restriction, including
51
+ without limitation the rights to use, copy, modify, merge, publish,
52
+ distribute, sublicense, and/or sell copies of the Software, and to
53
+ permit persons to whom the Software is furnished to do so, subject to
54
+ the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be
57
+ included in all copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
60
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
61
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
62
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
63
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
64
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
65
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ $: << "../../RubyInline/dev/lib"
2
+
3
+ require 'hoe'
4
+ require './lib/png.rb'
5
+
6
+ Hoe.add_include_dirs("../../RubyInline/dev/lib",
7
+ "lib")
8
+
9
+ Hoe.new 'png', PNG::VERSION do |png|
10
+ png.rubyforge_name = 'seattlerb'
11
+
12
+ png.developer('Ryan Davis', 'ryand-ruby@zenspider.com')
13
+ png.developer('Eric Hodel', 'drbrain@segment7.net')
14
+
15
+ png.clean_globs << File.expand_path("~/.ruby_inline")
16
+ png.extra_deps << ['RubyInline', '>= 3.5.0']
17
+ end
18
+
19
+ # vim: syntax=Ruby
@@ -0,0 +1,20 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ require 'png'
4
+ require 'png/font'
5
+
6
+ canvas = PNG::Canvas.new 201, 201, PNG::Color::White
7
+
8
+ canvas.line 50, 50, 100, 50, PNG::Color::Blue
9
+ canvas.line 50, 50, 50, 100, PNG::Color::Blue
10
+ canvas.line 100, 50, 150, 100, PNG::Color::Blue
11
+ canvas.line 100, 50, 125, 100, PNG::Color::Green
12
+ canvas.line 100, 50, 200, 75, PNG::Color::Green
13
+ canvas.line 0, 200, 200, 0, PNG::Color::Black
14
+ canvas.line 0, 200, 150, 0, PNG::Color::Red
15
+
16
+ canvas.annotate 'Hello World', 10, 10
17
+
18
+ png = PNG.new canvas
19
+ png.save 'blah.png'
20
+ `open blah.png`
@@ -0,0 +1,16 @@
1
+ require 'png'
2
+
3
+ class PNGProfile
4
+
5
+ def draw
6
+ canvas = PNG::Canvas.new 400, 400
7
+ png = PNG.new canvas
8
+ png.to_blob
9
+ end
10
+
11
+ end
12
+
13
+ pp = PNGProfile.new
14
+
15
+ 10.times do pp.draw end
16
+
@@ -0,0 +1,37 @@
1
+ require 'png'
2
+
3
+ class PNGProfileLine
4
+
5
+ COLORS = [
6
+ PNG::Color::Red,
7
+ PNG::Color::Orange,
8
+ PNG::Color::Yellow,
9
+ PNG::Color::Green,
10
+ PNG::Color::Blue,
11
+ PNG::Color::Purple,
12
+ ]
13
+
14
+ def draw
15
+ line = 0
16
+ canvas = PNG::Canvas.new 100, 100
17
+
18
+ 0.step 99, 10 do |x|
19
+ canvas.line x, 0, 99 - x, 99, COLORS[line % 6]
20
+ line += 1
21
+ end
22
+
23
+ 0.step 99, 10 do |y|
24
+ canvas.line 0, y, 99, y, COLORS[line % 6]
25
+ line += 1
26
+ end
27
+
28
+ canvas
29
+ end
30
+
31
+ end
32
+
33
+ ppl = PNGProfileLine.new
34
+
35
+ 5.times do ppl.draw end
36
+ #PNG.new(ppl.draw).save 'x.png'
37
+
@@ -0,0 +1,584 @@
1
+ require 'rubygems'
2
+ require 'zlib'
3
+ require 'inline'
4
+
5
+ class String # :nodoc: # ZenTest SKIP
6
+ inline do |builder|
7
+ raise CompilationError if defined?(JRUBY_VERSION)
8
+ if RUBY_VERSION < "1.8.6" then
9
+ builder.prefix <<-EOM
10
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
11
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
12
+ EOM
13
+ end
14
+
15
+ builder.c <<-EOM
16
+ unsigned long png_crc() {
17
+ static unsigned long crc[256];
18
+ static char crc_table_computed = 0;
19
+
20
+ if (! crc_table_computed) {
21
+ unsigned long c;
22
+ int n, k;
23
+
24
+ for (n = 0; n < 256; n++) {
25
+ c = (unsigned long) n;
26
+ for (k = 0; k < 8; k++) {
27
+ c = (c & 1) ? 0xedb88320L ^ (c >> 1) : c >> 1;
28
+ }
29
+ crc[n] = c;
30
+ }
31
+ crc_table_computed = 1;
32
+ }
33
+
34
+ unsigned long c = 0xffffffff;
35
+ unsigned len = RSTRING_LEN(self);
36
+ char * s = StringValuePtr(self);
37
+ unsigned i;
38
+
39
+ for (i = 0; i < len; i++) {
40
+ c = crc[(c ^ s[i]) & 0xff] ^ (c >> 8);
41
+ }
42
+
43
+ return c ^ 0xffffffff;
44
+ }
45
+ EOM
46
+ end
47
+ rescue CompilationError
48
+ unless defined? @@crc then
49
+ @@crc = Array.new(256)
50
+ 256.times do |n|
51
+ c = n
52
+ 8.times do
53
+ c = (c & 1 == 1) ? 0xedb88320 ^ (c >> 1) : c >> 1
54
+ end
55
+ @@crc[n] = c
56
+ end
57
+ end
58
+
59
+ ##
60
+ # Calculates a CRC using the algorithm in the PNG specification.
61
+
62
+ def png_crc()
63
+ c = 0xffffffff
64
+ each_byte do |b|
65
+ c = @@crc[(c^b) & 0xff] ^ (c >> 8)
66
+ end
67
+ return c ^ 0xffffffff
68
+ end
69
+ end
70
+
71
+ ##
72
+ # An almost-pure-ruby Portable Network Graphics (PNG) writer.
73
+ #
74
+ # http://www.libpng.org/pub/png/spec/1.2/
75
+ #
76
+ # PNG supports:
77
+ # + 8 bit truecolor PNGs
78
+ #
79
+ # PNG does not support:
80
+ # + any other color depth
81
+ # + extra data chunks
82
+ # + filters
83
+ #
84
+ # = Example
85
+ #
86
+ # require 'png'
87
+ #
88
+ # canvas = PNG::Canvas.new 200, 200
89
+ # canvas[100, 100] = PNG::Color::Black
90
+ # canvas.line 50, 50, 100, 50, PNG::Color::Blue
91
+ # png = PNG.new canvas
92
+ # png.save 'blah.png'
93
+ #
94
+ # = TODO:
95
+ #
96
+ # + Get everything orinted entirely on [x,y,h,w] with x,y origin being
97
+ # bottom left.
98
+
99
+ class PNG
100
+ VERSION = '1.2.0'
101
+ SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack("C*")
102
+
103
+ # Color Types:
104
+ GRAY = 0 # DEPTH = 1,2,4,8,16
105
+ RGB = 2 # DEPTH = 8,16
106
+ INDEXED = 3 # DEPTH = 1,2,4,8
107
+ GRAYA = 4 # DEPTH = 8,16
108
+ RGBA = 6 # DEPTH = 8,16
109
+
110
+ # Filter Types:
111
+ NONE = 0
112
+ SUB = 1
113
+ UP = 2
114
+ AVG = 3
115
+ PAETH = 4
116
+
117
+ begin
118
+ inline do |builder|
119
+ raise CompilationError if defined?(JRUBY_VERSION)
120
+ if RUBY_VERSION < "1.8.6" then
121
+ builder.prefix <<-EOM
122
+ #define RARRAY_PTR(s) (RARRAY(s)->ptr)
123
+ #define RARRAY_LEN(s) (RARRAY(s)->len)
124
+ EOM
125
+ end
126
+
127
+ builder.c <<-EOM
128
+ VALUE png_join() {
129
+ int i, j;
130
+ VALUE data = rb_iv_get(self, "@data");
131
+ unsigned int data_len = RARRAY_LEN(data);
132
+ unsigned int row_len = RARRAY_LEN(RARRAY_PTR(data)[0]);
133
+ unsigned long size = data_len * (1 + (row_len * 4));
134
+ char * result = malloc(size);
135
+ unsigned long idx = 0;
136
+ for (i = 0; i < data_len; i++) {
137
+ VALUE row = RARRAY_PTR(data)[i];
138
+ result[idx++] = 0;
139
+ for (j = 0; j < row_len; j++) {
140
+ VALUE color = RARRAY_PTR(row)[j];
141
+ VALUE values = rb_iv_get(color, "@values");
142
+ char * value = StringValuePtr(values);
143
+ result[idx++] = value[0];
144
+ result[idx++] = value[1];
145
+ result[idx++] = value[2];
146
+ result[idx++] = value[3];
147
+ }
148
+ }
149
+ return rb_str_new(result, size);
150
+ }
151
+ EOM
152
+ end
153
+ rescue CompilationError
154
+ def png_join
155
+ @data.map { |row| "\0" + row.map { |p| p.values }.join }.join
156
+ end
157
+ end
158
+
159
+ ##
160
+ # Creates a PNG chunk of type +type+ that contains +data+.
161
+
162
+ def self.chunk(type, data="")
163
+ [data.size, type, data, (type + data).png_crc].pack("Na*a*N")
164
+ end
165
+
166
+ ##
167
+ # Creates a new PNG object using +canvas+
168
+
169
+ def initialize(canvas)
170
+ @height = canvas.height
171
+ @width = canvas.width
172
+ @bits = 8
173
+ @data = canvas.data
174
+ end
175
+
176
+ ##
177
+ # Writes the PNG to +path+.
178
+
179
+ def save(path)
180
+ File.open path, 'wb' do |f|
181
+ f.write to_blob
182
+ end
183
+ end
184
+
185
+ ##
186
+ # Raw PNG data
187
+
188
+ def to_blob
189
+ blob = []
190
+
191
+ header = [@width, @height, @bits, RGBA, NONE, NONE, NONE]
192
+
193
+ blob << SIGNATURE
194
+ blob << PNG.chunk('IHDR', header.pack("N2C5"))
195
+ blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(self.png_join))
196
+ blob << PNG.chunk('IEND', '')
197
+ blob.join
198
+ end
199
+
200
+ ##
201
+ # A 32 bit RGBA color. Can be created from RGB or RGBA via #new,
202
+ # numeric value or hex string via #from, or HSV via #from_hsv.
203
+
204
+ class Color
205
+
206
+ MAX=255
207
+
208
+ attr_reader :values
209
+
210
+ ##
211
+ # Create a new color from a string or integer value. Can take an
212
+ # optional name as well.
213
+
214
+ def self.from str, name = nil
215
+ str = "%08x" % str if Integer === str
216
+ colors = str.scan(/[\da-f][\da-f]/i).map { |n| n.hex }
217
+ colors << name
218
+ self.new(*colors)
219
+ end
220
+
221
+ ##
222
+ # Creates a new color with values +red+, +green+, +blue+, and +alpha+.
223
+
224
+ def initialize red, green, blue, alpha = MAX, name = nil
225
+ @values = "%c%c%c%c" % [red, green, blue, alpha]
226
+ @name = name
227
+ end
228
+
229
+ ##
230
+ # Transparent white
231
+
232
+ Background = Color.from 0x00000000, "Transparent"
233
+ Black = Color.from 0x000000FF, "Black"
234
+ Blue = Color.from 0x0000FFFF, "Blue"
235
+ Brown = Color.from 0x996633FF, "Brown"
236
+ Bubblegum = Color.from 0xFF66FFFF, "Bubblegum"
237
+ Cyan = Color.from 0x00FFFFFF, "Cyan"
238
+ Gray = Color.from 0x7F7F7FFF, "Gray"
239
+ Green = Color.from 0x00FF00FF, "Green"
240
+ Magenta = Color.from 0xFF00FFFF, "Magenta"
241
+ Orange = Color.from 0xFF7F00FF, "Orange"
242
+ Purple = Color.from 0x7F007FFF, "Purple"
243
+ Red = Color.from 0xFF0000FF, "Red"
244
+ White = Color.from 0xFFFFFFFF, "White"
245
+ Yellow = Color.from 0xFFFF00FF, "Yellow"
246
+
247
+ def == other # :nodoc:
248
+ self.class === other and other.values == values
249
+ end
250
+
251
+ alias :eql? :==
252
+
253
+ ##
254
+ # "Bitwise or" as applied to colors. Background color is
255
+ # considered false.
256
+
257
+ def | o
258
+ self == Background ? o : self
259
+ end
260
+
261
+ def hash # :nodoc:
262
+ self.values.hash
263
+ end
264
+
265
+ ##
266
+ # Return an array of RGB
267
+
268
+ def rgb # TODO: rgba?
269
+ return @values[0], @values[1], @values[2]
270
+ end
271
+
272
+ ##
273
+ # Red component
274
+
275
+ def r; @values[0]; end
276
+
277
+ ##
278
+ # Green component
279
+
280
+ def g; @values[1]; end
281
+
282
+ ##
283
+ # Blue component
284
+
285
+ def b; @values[2]; end
286
+
287
+ ##
288
+ # Alpha transparency component
289
+
290
+ def a; @values[3]; end
291
+
292
+ ##
293
+ # Blends +color+ into this color returning a new blended color.
294
+
295
+ def blend color
296
+ return Color.new(((r + color.r) / 2), ((g + color.g) / 2),
297
+ ((b + color.b) / 2), ((a + color.a) / 2))
298
+ end
299
+
300
+ ##
301
+ # Returns a new color with an alpha value adjusted by +i+.
302
+
303
+ def intensity i
304
+ return Color.new(r,g,b,(a*i) >> 8)
305
+ end
306
+
307
+ def inspect # :nodoc:
308
+ if @name then
309
+ "#<%s %s>" % [self.class, @name]
310
+ else
311
+ "#<%s %02x %02x %02x %02x>" % [self.class, r, g, b, a]
312
+ end
313
+ end
314
+
315
+ ##
316
+ # An ASCII representation of this color, almost suitable for making ASCII
317
+ # art!
318
+
319
+ def to_ascii
320
+ return ' ' if a == 0x00
321
+ brightness = (((r + g + b) / 3) * a) / 0xFF
322
+
323
+ %w(.. ,, ++ 00)[brightness / 64]
324
+ end
325
+
326
+ def to_s # :nodoc:
327
+ if @name then
328
+ @name
329
+ else
330
+ super
331
+ end
332
+ end
333
+
334
+ ##
335
+ # Creates a new RGB color from HSV equivalent values.
336
+
337
+ def self.from_hsv h, s, v
338
+ r = g = b = v # gray
339
+ unless s == 0.0 then
340
+ h += 255 if h < 0
341
+ h = h / 255.0 * 6.0
342
+ s = s / 255.0
343
+ v = v / 255.0
344
+ i = h.floor
345
+ f = h - i
346
+ p = v * (1 - (s))
347
+ q = v * (1 - (s * (f)))
348
+ w = v * (1 - (s * (1-f)))
349
+ r, g, b = case i
350
+ when 0,6 then
351
+ [ v, w, p ]
352
+ when 1 then
353
+ [ q, v, p ]
354
+ when 2 then
355
+ [ p, v, w ]
356
+ when 3 then
357
+ [ p, q, v ]
358
+ when 4 then
359
+ [ w, p, v ]
360
+ when 5 then
361
+ [ v, p, q ]
362
+ else
363
+ raise [h, s, v, i, f, p, q, w].inspect
364
+ end
365
+ end
366
+ self.new((r * 255).round, (g * 255).round, (b * 255).round)
367
+ end
368
+
369
+ ##
370
+ # Returns HSV equivalent of the current color.
371
+
372
+ def to_hsv # errors = 54230 out of 255^3 are off by about 1 on r, g, or b
373
+ rgb = self.rgb
374
+ r, g, b = rgb
375
+ h, s, v = 0, 0, rgb.max
376
+
377
+ return h, s, v if v == 0
378
+
379
+ range = v - rgb.min
380
+ s = 255 * range / v
381
+
382
+ return h, s, v if s == 0
383
+
384
+ h = case v
385
+ when r then
386
+ 0x00 + 43 * (g - b) / range # 43 = 1/4 of 360 scaled to 255
387
+ when g then
388
+ 0x55 + 43 * (b - r) / range
389
+ else
390
+ 0xAA + 43 * (r - g) / range
391
+ end
392
+
393
+ return h.round, s.round, v.round
394
+ end
395
+ end # Color
396
+
397
+ ##
398
+ # A canvas used for drawing images. Origin is 0, 0 in the bottom
399
+ # left corner.
400
+
401
+ class Canvas
402
+
403
+ ##
404
+ # Height of the canvas
405
+
406
+ attr_reader :height
407
+
408
+ ##
409
+ # Width of the canvas
410
+
411
+ attr_reader :width
412
+
413
+ ##
414
+ # Raw data
415
+
416
+ attr_reader :data
417
+
418
+ def initialize width, height, background = Color::Background
419
+ @width = width
420
+ @height = height
421
+ @data = Array.new(@height) { |x| Array.new(@width, background) }
422
+ end
423
+
424
+ ##
425
+ # Retrieves the color of the pixel at (+x+, +y+).
426
+
427
+ def [] x, y
428
+ raise "bad x value #{x} >= #{@width}" if x >= @width
429
+ raise "bad y value #{y} >= #{@height}" if y >= @height
430
+ @data[@height-y-1][x]
431
+ end
432
+
433
+ ##
434
+ # Sets the color of the pixel at (+x+, +y+) to +color+.
435
+
436
+ def []= x, y, color
437
+ raise "bad x value #{x} >= #{@width}" if x >= @width
438
+ raise "bad y value #{y} >= #{@height}" if y >= @height
439
+ raise "bad color #{color.inspect}" unless color.kind_of? PNG::Color
440
+ @data[@height-y-1][x] = color
441
+ end
442
+
443
+ ##
444
+ # Composites another canvas onto self at the given (bottom left) coordinates.
445
+
446
+ def composite canvas, x, y, style = :overwrite
447
+ canvas.each do |x1, y1, color|
448
+ case style
449
+ when :overwrite then
450
+ self[x+x1, y+y1] = color
451
+ when :add, :underlay then
452
+ self[x+x1, y+y1] = self[x+x1, y+y1] | color
453
+ when :overlay then
454
+ self[x+x1, y+y1] = color | self[x+x1, y+y1]
455
+ when :blend then
456
+ self.point x+x1, y+y1, color
457
+ else
458
+ raise "unknown style for composite: #{style.inspect}"
459
+ end
460
+ end
461
+ end
462
+
463
+ ##
464
+ # Iterates over the canvas yielding x, y, and color.
465
+
466
+ def each
467
+ data.reverse.each_with_index do |row, y|
468
+ row.each_with_index do |color, x|
469
+ yield x, y, color
470
+ end
471
+ end
472
+ end
473
+
474
+ ##
475
+ # Create a new canvas copying a region of the current canvas
476
+
477
+ def extract x0, y0, x1, y1
478
+ canvas = Canvas.new(x1-x0+1, y1-y0+1)
479
+
480
+ (x0..x1).each_with_index do |x2, x3|
481
+ (y0..y1).each_with_index do |y2, y3|
482
+ canvas[x3, y3] = self[x2, y2]
483
+ end
484
+ end
485
+
486
+ canvas
487
+ end
488
+
489
+ def inspect # :nodoc:
490
+ '#<%s %dx%d>' % [self.class, @width, @height]
491
+ end
492
+
493
+ ##
494
+ # Blends +color+ onto the color at point (+x+, +y+).
495
+
496
+ def point(x, y, color)
497
+ self[x,y] = self[x,y].blend(color)
498
+ end
499
+
500
+ ##
501
+ # Draws a line using Xiaolin Wu's antialiasing technique.
502
+ #
503
+ # http://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm
504
+
505
+ def line(x0, y0, x1, y1, color)
506
+ y0, y1, x0, x1 = y1, y0, x1, x0 if y0 > y1
507
+ dx = x1 - x0
508
+ sx = dx < 0 ? -1 : 1
509
+ dx *= sx
510
+ dy = y1 - y0
511
+
512
+ # 'easy' cases
513
+ if dy == 0 then
514
+ Range.new(*[x0,x1].sort).each do |x|
515
+ point(x, y0, color)
516
+ end
517
+ return
518
+ end
519
+
520
+ if dx == 0 then
521
+ (y0..y1).each do |y|
522
+ point(x0, y, color)
523
+ end
524
+ return
525
+ end
526
+
527
+ if dx == dy then
528
+ x0.step(x1, sx) do |x|
529
+ point(x, y0, color)
530
+ y0 += 1
531
+ end
532
+ return
533
+ end
534
+
535
+ # main loop
536
+ point(x0, y0, color)
537
+ e_acc = 0
538
+ if dy > dx then # vertical displacement
539
+ e = (dx << 16) / dy
540
+ (y0...y1-1).each do |i|
541
+ e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
542
+ x0 = x0 + sx if (e_acc <= e_acc_temp)
543
+ w = 0xFF-(e_acc >> 8)
544
+ point(x0, y0, color.intensity(w))
545
+ y0 = y0 + 1
546
+ point(x0 + sx, y0, color.intensity(0xFF-w))
547
+ end
548
+ point(x1, y1, color)
549
+ return
550
+ end
551
+
552
+ # horizontal displacement
553
+ e = (dy << 16) / dx
554
+ (dx - 1).downto(0) do |i|
555
+ e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
556
+ y0 += 1 if (e_acc <= e_acc_temp)
557
+ w = 0xFF-(e_acc >> 8)
558
+ point(x0, y0, color.intensity(w))
559
+ x0 += sx
560
+ point(x0, y0 + 1, color.intensity(0xFF-w))
561
+ end
562
+ point(x1, y1, color)
563
+ end
564
+
565
+ ##
566
+ # Returns an ASCII representation of this image
567
+
568
+ def to_s
569
+ image = []
570
+ scale = (@width / 39) + 1
571
+
572
+ @data.each_with_index do |row, x|
573
+ next if x % scale != 0
574
+ row.each_with_index do |color, y|
575
+ next if y % scale != 0
576
+ image << color.to_ascii
577
+ end
578
+ image << "\n"
579
+ end
580
+
581
+ return image.join
582
+ end
583
+ end # Canvas
584
+ end