aslakhellesoy-png 1.2.0

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