png 1.0.0 → 1.1.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
+
@@ -1,5 +1,10 @@
1
+ History.txt
1
2
  Manifest.txt
2
- README
3
+ README.txt
3
4
  Rakefile
4
5
  example/lines.rb
6
+ example/profile.rb
7
+ example/profile_lines.rb
5
8
  lib/png.rb
9
+ lib/png/pie.rb
10
+ 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.
data/Rakefile CHANGED
@@ -1,56 +1,17 @@
1
- require 'rubygems'
2
- require 'rake'
3
- require 'rake/testtask'
4
- require 'rake/rdoctask'
5
- require 'rake/gempackagetask'
1
+ require 'hoe'
2
+ require './lib/png.rb'
6
3
 
7
- $VERBOSE = nil
4
+ Hoe.new 'png', PNG::VERSION do |s|
5
+ s.rubyforge_name = 'seattlerb'
6
+ s.author = ['Ryan Davis', 'Eric Hodel']
7
+ s.email = 'support@zenspider.com'
8
8
 
9
- spec = Gem::Specification.new do |s|
10
- s.name = 'png'
11
- s.version = '1.0.0'
12
- s.summary = 'A pure ruby PNG library'
13
- s.description = 'png allows you to write a PNG file without any C libraries.'
14
- s.author = 'Ryan Davis'
15
- s.email = 'ryand-ruby@zenspider.com'
9
+ s.summary = 'An almost-pure-ruby PNG library'
10
+ s.description = s.paragraphs_of('README.txt', 3..7).join("\n\n")
16
11
 
17
- s.has_rdoc = true
18
- s.files = File.read('Manifest.txt').split($/)
19
- s.require_path = 'lib'
20
- end
21
-
22
- desc 'Run tests'
23
- task :default => [ :test ]
24
-
25
- Rake::TestTask.new('test') do |t|
26
- t.libs << 'test'
27
- t.pattern = 'test/test_*.rb'
28
- t.verbose = true
29
- end
30
-
31
- desc 'Update Manifest.txt'
32
- task :update_manifest do
33
- sh "find . -type f | sed -e 's%./%%' | egrep -v 'svn|swp|~' | egrep -v '^(doc|pkg)/' | sort > Manifest.txt"
34
- end
35
-
36
- desc 'Generate RDoc'
37
- Rake::RDocTask.new :rdoc do |rd|
38
- rd.rdoc_dir = 'doc'
39
- rd.rdoc_files.add 'lib', 'README'
40
- rd.main = 'README'
41
- rd.options << '-d' if `which dot` =~ /\/dot/
42
- rd.options << '-t png'
43
- end
12
+ s.changes = s.paragraphs_of('History.txt', 0..1).join("\n\n")
44
13
 
45
- desc 'Build Gem'
46
- Rake::GemPackageTask.new spec do |pkg|
47
- pkg.need_tar = true
14
+ s.extra_deps << ['RubyInline', '>= 3.5.0']
48
15
  end
49
16
 
50
- desc 'Clean up'
51
- task :clean => [ :clobber_rdoc, :clobber_package ]
52
-
53
- desc 'Clean up'
54
- task :clobber => [ :clean ]
55
-
56
17
  # vim: syntax=Ruby
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'png'
4
4
 
5
- canvas = PNG::Canvas.new 1024, 1024, PNG::Color::Black
5
+ canvas = PNG::Canvas.new 1024, 1024, PNG::Color::White
6
6
 
7
7
  #canvas.each do |x, y|
8
8
  # case x
@@ -17,9 +17,10 @@ canvas = PNG::Canvas.new 1024, 1024, PNG::Color::Black
17
17
  canvas.line 50, 50, 100, 50, PNG::Color::Blue
18
18
  canvas.line 50, 50, 50, 100, PNG::Color::Blue
19
19
  canvas.line 100, 50, 150, 100, PNG::Color::Blue
20
- canvas.line 100, 50, 125, 100, PNG::Color::Green # currently wrong
21
- canvas.line 100, 50, 200, 75, PNG::Color::Green # currently wrong
20
+ canvas.line 100, 50, 125, 100, PNG::Color::Green
21
+ canvas.line 100, 50, 200, 75, PNG::Color::Green
22
22
  canvas.line 0, 200, 200, 0, PNG::Color::Black
23
+ canvas.line 0, 200, 150, 0, PNG::Color::Red
23
24
 
24
25
  png = PNG.new canvas
25
26
  png.save '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
+
data/lib/png.rb CHANGED
@@ -1,33 +1,56 @@
1
+ begin; require 'rubygems'; rescue LoadError; end
1
2
  require 'zlib'
3
+ require 'inline'
2
4
 
3
- class String # :nodoc:
5
+ class String # :nodoc: # ZenTest SKIP
4
6
 
5
7
  ##
6
8
  # Calculates a CRC using the algorithm in the PNG specification.
7
9
 
8
- def png_crc
9
- unless defined? @@crc then
10
- @@crc = Array.new(256)
11
- 256.times do |n|
12
- c = n
13
- 8.times do
14
- c = (c & 1 == 1) ? 0xedb88320 ^ (c >> 1) : c >> 1
15
- end
16
- @@crc[n] = c
17
- end
10
+ inline do |builder|
11
+ if RUBY_VERSION < "1.8.6" then
12
+ builder.prefix <<-EOM
13
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
14
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
15
+ EOM
18
16
  end
19
17
 
20
- c = 0xffffffff
21
- each_byte do |b|
22
- c = @@crc[(c^b) & 0xff] ^ (c >> 8)
23
- end
24
- return c ^ 0xffffffff
18
+ builder.c <<-EOM
19
+ unsigned long png_crc() {
20
+ static unsigned long crc[256];
21
+ static char crc_table_computed = 0;
22
+
23
+ if (! crc_table_computed) {
24
+ unsigned long c;
25
+ int n, k;
26
+
27
+ for (n = 0; n < 256; n++) {
28
+ c = (unsigned long) n;
29
+ for (k = 0; k < 8; k++) {
30
+ c = (c & 1) ? 0xedb88320L ^ (c >> 1) : c >> 1;
31
+ }
32
+ crc[n] = c;
33
+ }
34
+ crc_table_computed = 1;
35
+ }
36
+
37
+ unsigned long c = 0xffffffff;
38
+ unsigned len = RSTRING_LEN(self);
39
+ char * s = StringValuePtr(self);
40
+ unsigned i;
41
+
42
+ for (i = 0; i < len; i++) {
43
+ c = crc[(c ^ s[i]) & 0xff] ^ (c >> 8);
44
+ }
45
+
46
+ return c ^ 0xffffffff;
47
+ }
48
+ EOM
25
49
  end
26
-
27
50
  end
28
51
 
29
52
  ##
30
- # A pure Ruby Portable Network Graphics (PNG) writer.
53
+ # An almost-pure-ruby Portable Network Graphics (PNG) writer.
31
54
  #
32
55
  # http://www.libpng.org/pub/png/spec/1.2/
33
56
  #
@@ -42,7 +65,7 @@ end
42
65
  # = Example
43
66
  #
44
67
  # require 'png'
45
- #
68
+ #
46
69
  # canvas = PNG::Canvas.new 200, 200
47
70
  # canvas[100, 100] = PNG::Color::Black
48
71
  # canvas.line 50, 50, 100, 50, PNG::Color::Blue
@@ -51,6 +74,46 @@ end
51
74
 
52
75
  class PNG
53
76
 
77
+ VERSION = '1.1.0'
78
+ SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack("C*")
79
+
80
+ inline do |builder|
81
+ if RUBY_VERSION < "1.8.6" then
82
+ builder.prefix <<-EOM
83
+ #define RARRAY_PTR(s) (RARRAY(s)->ptr)
84
+ #define RARRAY_LEN(s) (RARRAY(s)->len)
85
+ EOM
86
+ end
87
+
88
+ # C equivalent of:
89
+ # @data.map { |row| "\0" + row.map { |p| p.values }.join }.join
90
+ builder.c <<-EOM
91
+ VALUE png_join() {
92
+ int i, j;
93
+ VALUE data = rb_iv_get(self, "@data");
94
+ unsigned int data_len = RARRAY_LEN(data);
95
+ unsigned int row_len = RARRAY_LEN(RARRAY_PTR(data)[0]);
96
+ unsigned long size = data_len * (1 + (row_len * 4));
97
+ char * result = malloc(size);
98
+ unsigned long idx = 0;
99
+ for (i = 0; i < data_len; i++) {
100
+ VALUE row = RARRAY_PTR(data)[i];
101
+ result[idx++] = 0;
102
+ for (j = 0; j < row_len; j++) {
103
+ VALUE color = RARRAY_PTR(row)[j];
104
+ VALUE values = rb_iv_get(color, "@values");
105
+ char * value = StringValuePtr(values);
106
+ result[idx++] = value[0];
107
+ result[idx++] = value[1];
108
+ result[idx++] = value[2];
109
+ result[idx++] = value[3];
110
+ }
111
+ }
112
+ return rb_str_new(result, size);
113
+ }
114
+ EOM
115
+ end
116
+
54
117
  ##
55
118
  # Creates a PNG chunk of type +type+ that contains +data+.
56
119
 
@@ -58,6 +121,116 @@ class PNG
58
121
  [data.size, type, data, (type + data).png_crc].pack("Na*a*N")
59
122
  end
60
123
 
124
+ def self.load(png)
125
+ png = png.dup
126
+ signature = png.slice! 0, 8
127
+ raise ArgumentError, 'Invalid PNG signature' unless signature == SIGNATURE
128
+
129
+ type, data = read_chunk png
130
+
131
+ raise ArgumentError, 'Invalid PNG, no IHDR chunk' unless type == 'IHDR'
132
+
133
+ canvas = read_IHDR data
134
+ type, data = read_chunk png
135
+ read_IDAT data, canvas
136
+ type, data = read_chunk png
137
+ raise 'oh no! IEND not next? crashing and burning!' unless type == 'IEND'
138
+
139
+ new canvas
140
+ end
141
+
142
+ def self.check_crc(type, data, crc)
143
+ return true if (type + data).png_crc == crc
144
+ raise ArgumentError, "Invalid CRC encountered in #{type} chunk"
145
+ end
146
+
147
+ def self.paeth(a, b, c) # left, above, upper left
148
+ p = a + b - c
149
+ pa = (p - a).abs
150
+ pb = (p - b).abs
151
+ pc = (p - c).abs
152
+
153
+ return a if pa <= pb && pa <= pc
154
+ return b if pb <= pc
155
+ c
156
+ end
157
+
158
+ def self.read_chunk(png)
159
+ size, type = png.slice!(0, 8).unpack 'Na4'
160
+ data, crc = png.slice!(0, size + 4).unpack "a#{size}N"
161
+
162
+ check_crc type, data, crc
163
+
164
+ return type, data
165
+ end
166
+
167
+ def self.read_IDAT(data, canvas)
168
+ data = Zlib::Inflate.inflate(data).unpack 'C*'
169
+ scanline_length = 4 * canvas.width + 1 # for filter
170
+ row = 0
171
+ until data.empty? do
172
+ row_data = data.slice! 0, scanline_length
173
+ filter = row_data.shift
174
+ case filter
175
+ when 0 then # None
176
+ when 1 then # Sub
177
+ row_data.each_with_index do |byte, index|
178
+ left = index < 4 ? 0 : row_data[index - 4]
179
+ row_data[index] = (byte + left) % 256
180
+ #p [byte, left, row_data[index]]
181
+ end
182
+ when 2 then # Up
183
+ row_data.each_with_index do |byte, index|
184
+ col = index / 4
185
+ upper = row == 0 ? 0 : canvas[col, row - 1].values[index % 4]
186
+ row_data[index] = (upper + byte) % 256
187
+ end
188
+ when 3 then # Average
189
+ row_data.each_with_index do |byte, index|
190
+ col = index / 4
191
+ upper = row == 0 ? 0 : canvas[col, row - 1].values[index % 4]
192
+ left = index < 4 ? 0 : row_data[index - 4]
193
+
194
+ row_data[index] = (byte + ((left + upper)/2).floor) % 256
195
+ end
196
+ when 4 then # Paeth
197
+ left = upper = upper_left = nil
198
+ row_data.each_with_index do |byte, index|
199
+ col = index / 4
200
+
201
+ left = index < 4 ? 0 : row_data[index - 4]
202
+ if row == 0 then
203
+ upper = upper_left = 0
204
+ else
205
+ upper = canvas[col, row - 1].values[index % 4]
206
+ upper_left = col == 0 ? 0 :
207
+ canvas[col - 1, row - 1].values[index % 4]
208
+ end
209
+
210
+ paeth = paeth left, upper, upper_left
211
+ row_data[index] = (byte + paeth) % 256
212
+ #p [byte, paeth, row_data[index]]
213
+ end
214
+ else
215
+ raise ArgumentError, "Invalid filter algorithm #{filter}"
216
+ end
217
+
218
+ col = 0
219
+ row_data.each_slice 4 do |slice|
220
+ canvas[col, row] = PNG::Color.new(*slice)
221
+ col += 1
222
+ end
223
+
224
+ row += 1
225
+ end
226
+ end
227
+
228
+ def self.read_IHDR(data)
229
+ width, height, *rest = data.unpack 'N2C5'
230
+ raise ArgumentError, 'unsupported PNG file' unless rest == [8, 6, 0, 0, 0]
231
+ return PNG::Canvas.new(height, width)
232
+ end
233
+
61
234
  ##
62
235
  # Creates a new PNG object using +canvas+
63
236
 
@@ -72,15 +245,22 @@ class PNG
72
245
  # Writes the PNG to +path+.
73
246
 
74
247
  def save(path)
75
- File.open(path, "w") do |f|
76
- f.write [137, 80, 78, 71, 13, 10, 26, 10].pack("C*") # PNG signature
77
- f.write PNG.chunk('IHDR',
78
- [ @height, @width, @bits, 6, 0, 0, 0 ].pack("N2C5"))
79
- # 0 == filter type code "none"
80
- data = @data.map { |row| [0] + row.map { |p| p.values } }.flatten
81
- f.write PNG.chunk('IDAT', Zlib::Deflate.deflate(data.pack("C*"), 9))
82
- f.write PNG.chunk('IEND', '')
83
- end
248
+ File.open path, 'wb' do |f| f.write to_blob end
249
+ end
250
+
251
+ ##
252
+ # Raw PNG data
253
+
254
+ def to_blob
255
+ blob = []
256
+
257
+ blob << SIGNATURE
258
+ blob << PNG.chunk('IHDR', [@width, @height, @bits, 6, 0, 0, 0 ].pack("N2C5"))
259
+ # 0 == filter type code "none"
260
+ data = self.png_join
261
+ blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(data))
262
+ blob << PNG.chunk('IEND', '')
263
+ blob.join
84
264
  end
85
265
 
86
266
  ##
@@ -90,28 +270,42 @@ class PNG
90
270
 
91
271
  attr_reader :values
92
272
 
273
+ def self.from str, name = nil
274
+ str = "%08x" % str if Integer === str
275
+ colors = str.scan(/[\da-f][\da-f]/i).map { |n| n.hex }
276
+ colors << name
277
+ self.new(*colors)
278
+ end
279
+
93
280
  ##
94
281
  # Creates a new color with values +red+, +green+, +blue+, and +alpha+.
95
282
 
96
- def initialize(red, green, blue, alpha)
97
- @values = [red, green, blue, alpha]
283
+ def initialize(red, green, blue, alpha, name = nil)
284
+ @values = "%c%c%c%c" % [red, green, blue, alpha]
285
+ @name = name
98
286
  end
99
287
 
100
288
  ##
101
289
  # Transparent white
102
290
 
103
- Background = Color.new 0xFF, 0xFF, 0xFF, 0x00
104
-
105
- White = Color.new 0xFF, 0xFF, 0xFF, 0xFF
106
- Black = Color.new 0x00, 0x00, 0x00, 0xFF
107
- Gray = Color.new 0x7F, 0x7F, 0x7F, 0xFF
108
-
109
- Red = Color.new 0xFF, 0x00, 0x00, 0xFF
110
- Orange = Color.new 0xFF, 0xA5, 0x00, 0xFF
111
- Yellow = Color.new 0xFF, 0xFF, 0x00, 0xFF
112
- Green = Color.new 0x00, 0xFF, 0x00, 0xFF
113
- Blue = Color.new 0x00, 0x00, 0xFF, 0xFF
114
- Purple = Color.new 0XFF, 0x00, 0xFF, 0xFF
291
+ Background = Color.from 0x00000000, "Transparent"
292
+ Black = Color.from 0x000000FF, "Black"
293
+ Blue = Color.from 0x0000FFFF, "Blue"
294
+ Brown = Color.from 0x996633FF, "Brown"
295
+ Bubblegum = Color.from 0xFF66FFFF, "Bubblegum"
296
+ Cyan = Color.from 0x00FFFFFF, "Cyan"
297
+ Gray = Color.from 0x7F7F7FFF, "Gray"
298
+ Green = Color.from 0x00FF00FF, "Green"
299
+ Magenta = Color.from 0xFF00FFFF, "Magenta"
300
+ Orange = Color.from 0xFF7F00FF, "Orange"
301
+ Purple = Color.from 0x7F007FFF, "Purple"
302
+ Red = Color.from 0xFF0000FF, "Red"
303
+ White = Color.from 0xFFFFFFFF, "White"
304
+ Yellow = Color.from 0xFFFF00FF, "Yellow"
305
+
306
+ def ==(other) # :nodoc:
307
+ self.class === other and other.values == values
308
+ end
115
309
 
116
310
  ##
117
311
  # Red component
@@ -147,13 +341,37 @@ class PNG
147
341
  # Returns a new color with an alpha value adjusted by +i+.
148
342
 
149
343
  def intensity(i)
150
- return Color.new(r,b,g,(a*i) >> 8)
344
+ return Color.new(r,g,b,(a*i) >> 8)
151
345
  end
152
346
 
153
347
  def inspect # :nodoc:
154
- "#<%s %02x %02x %02x %02x>" % [self.class, *@values]
348
+ if @name then
349
+ "#<%s %s>" % [self.class, @name]
350
+ else
351
+ "#<%s %02x %02x %02x %02x>" % [self.class, r, g, b, a]
352
+ end
155
353
  end
156
354
 
355
+ ##
356
+ # An ASCII representation of this color, almost suitable for making ASCII
357
+ # art!
358
+
359
+ def to_ascii
360
+ return ' ' if a == 0x00
361
+ brightness = (((r + g + b) / 3) * a) / 0xFF
362
+ return '00' if brightness >= 0xc0
363
+ return '++' if brightness >= 0x7F
364
+ return ',,' if brightness >= 0x40
365
+ return '..'
366
+ end
367
+
368
+ def to_s
369
+ if @name then
370
+ @name
371
+ else
372
+ super
373
+ end
374
+ end
157
375
  end
158
376
 
159
377
  ##
@@ -176,39 +394,32 @@ class PNG
176
394
 
177
395
  attr_reader :data
178
396
 
179
- def initialize(height, width, background = Color::White)
180
- @height = height
397
+ def initialize(width, height, background = Color::Background)
181
398
  @width = width
182
- @data = Array.new(@width) { |x| Array.new(@height) { background } }
399
+ @height = height
400
+ @data = Array.new(@height) { |x| Array.new(@width, background) }
183
401
  end
184
402
 
185
403
  ##
186
404
  # Retrieves the color of the pixel at (+x+, +y+).
187
405
 
188
406
  def [](x, y)
189
- raise "bad x value #{x} >= #{@height}" if x >= @height
190
- raise "bad y value #{y} >= #{@width}" if y >= @width
191
- @data[y][x]
407
+ raise "bad x value #{x} >= #{@width}" if x >= @width
408
+ raise "bad y value #{y} >= #{@height}" if y >= @height
409
+ @data[@height-y-1][x]
192
410
  end
193
411
 
194
412
  ##
195
413
  # Sets the color of the pixel at (+x+, +y+) to +color+.
196
414
 
197
415
  def []=(x, y, color)
198
- raise "bad x value #{x} >= #{@height}" if x >= @height
199
- raise "bad y value #{y} >= #{@width}" if y >= @width
200
- @data[y][x] = color
416
+ raise "bad x value #{x} >= #{@width}" if x >= @width
417
+ raise "bad y value #{y} >= #{@height}" if y >= @height
418
+ @data[@height-y-1][x] = color
201
419
  end
202
420
 
203
- ##
204
- # Iterates over each pixel in the canvas.
205
-
206
- def each
207
- @data.each_with_index do |row, y|
208
- row.each_with_index do |pixel, x|
209
- yield x, y, color
210
- end
211
- end
421
+ def inspect # :nodoc:
422
+ '#<%s %dx%d>' % [self.class, @width, @height]
212
423
  end
213
424
 
214
425
  ##
@@ -224,9 +435,10 @@ class PNG
224
435
  # http://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm
225
436
 
226
437
  def line(x0, y0, x1, y1, color)
438
+ y0, y1, x0, x1 = y1, y0, x1, x0 if y0 > y1
227
439
  dx = x1 - x0
228
440
  sx = dx < 0 ? -1 : 1
229
- dx *= sx # TODO: abs?
441
+ dx *= sx
230
442
  dy = y1 - y0
231
443
 
232
444
  # 'easy' cases
@@ -245,7 +457,7 @@ class PNG
245
457
  end
246
458
 
247
459
  if dx == dy then
248
- Range.new(*[x0,x1].sort).each do |x|
460
+ x0.step(x1, sx) do |x|
249
461
  point(x, y0, color)
250
462
  y0 += 1
251
463
  end
@@ -259,7 +471,7 @@ class PNG
259
471
  e = (dx << 16) / dy
260
472
  (y0...y1-1).each do |i|
261
473
  e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
262
- x0 = x0 + sx if (e_acc <= e_acc_temp)
474
+ x0 = x0 + sx if (e_acc <= e_acc_temp)
263
475
  w = 0xFF-(e_acc >> 8)
264
476
  point(x0, y0, color.intensity(w))
265
477
  y0 = y0 + 1
@@ -271,7 +483,7 @@ class PNG
271
483
 
272
484
  # horizontal displacement
273
485
  e = (dy << 16) / dx
274
- (x0...(x1-sx)).each do |i|
486
+ (dx - 1).downto(0) do |i|
275
487
  e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
276
488
  y0 += 1 if (e_acc <= e_acc_temp)
277
489
  w = 0xFF-(e_acc >> 8)
@@ -282,7 +494,23 @@ class PNG
282
494
  point(x1, y1, color)
283
495
  end
284
496
 
285
- end
497
+ ##
498
+ # Returns an ASCII representation of this image
286
499
 
287
- end
500
+ def to_s
501
+ image = []
502
+ scale = (@width / 39) + 1
503
+
504
+ @data.each_with_index do |row, x|
505
+ next if x % scale != 0
506
+ row.each_with_index do |color, y|
507
+ next if y % scale != 0
508
+ image << color.to_ascii
509
+ end
510
+ image << "\n"
511
+ end
288
512
 
513
+ return image.join
514
+ end
515
+ end
516
+ end
@@ -0,0 +1,47 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ require 'png'
4
+
5
+ class PNG
6
+ FULL = 360.0
7
+ HALF = FULL / 2
8
+
9
+ def self.angle(x, y)
10
+ return 0 if x == 0 and y == 0
11
+ rad_to_deg = 180.0 / Math::PI
12
+ (Math.atan2(-y, x) * rad_to_deg + 90) % 360
13
+ end
14
+
15
+ ##
16
+ # Makes a pie chart you can pass to PNG.new:
17
+ #
18
+ # png = PNG.new pie_chart(250, 0.30)
19
+ # png.save "pie.png"
20
+ # system 'open pie.png'
21
+
22
+ def self.pie_chart(diameter, pct_green,
23
+ good_color=PNG::Color::Green, bad_color=PNG::Color::Red)
24
+ diameter += 1 if diameter % 2 == 0
25
+ radius = (diameter / 2.0).to_i
26
+ pct_in_deg = FULL * pct_green
27
+ rad_to_deg = HALF / Math::PI
28
+
29
+ canvas = PNG::Canvas.new(diameter, diameter)
30
+
31
+ (-radius..radius).each do |x|
32
+ (-radius..radius).each do |y|
33
+ magnitude = Math.sqrt(x*x + y*y)
34
+ if magnitude <= radius then
35
+ angle = PNG.angle(x, y)
36
+ color = ((angle <= pct_in_deg) ? good_color : bad_color)
37
+
38
+ rx, ry = x+radius, y+radius
39
+
40
+ canvas[ rx, ry ] = color
41
+ end
42
+ end
43
+ end
44
+
45
+ canvas
46
+ end
47
+ end
@@ -0,0 +1,423 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'png'
4
+ require 'png/pie'
5
+
6
+ class TestPng < Test::Unit::TestCase
7
+
8
+ def setup
9
+ @canvas = PNG::Canvas.new 5, 10, PNG::Color::White
10
+ @png = PNG.new @canvas
11
+
12
+ @IHDR_length = "\000\000\000\r"
13
+ @IHDR_crc = "\2152\317\275"
14
+ @IHDR_crc_value = @IHDR_crc.unpack('N').first
15
+ @IHDR_data = "\000\000\000\n\000\000\000\n\b\006\000\000\000"
16
+ @IHDR_chunk = "#{@IHDR_length}IHDR#{@IHDR_data}#{@IHDR_crc}"
17
+
18
+ @blob = <<-EOF.unpack('m*').first
19
+ iVBORw0KGgoAAAANSUhEUgAAAAUAAAAKCAYAAAB8OZQwAAAAD0lEQVR4nGP4
20
+ jwUwDGVBALuJxzlQugpEAAAAAElFTkSuQmCC
21
+ EOF
22
+ end
23
+
24
+ def test_class_check_crc
25
+ assert PNG.check_crc('IHDR', @IHDR_data, @IHDR_crc_value)
26
+ end
27
+
28
+ def test_class_check_crc_exception
29
+ begin
30
+ PNG.check_crc('IHDR', @IHDR_data, @IHDR_crc_value + 1)
31
+ rescue ArgumentError => e
32
+ assert_equal "Invalid CRC encountered in IHDR chunk", e.message
33
+ else
34
+ flunk "exception wasn't raised"
35
+ end
36
+ end
37
+
38
+ def test_class_chunk
39
+ chunk = PNG.chunk 'IHDR', [10, 10, 8, 6, 0, 0, 0 ].pack('N2C5')
40
+ assert_equal @IHDR_chunk, chunk
41
+ end
42
+
43
+ def test_class_chunk_empty
44
+ chunk = PNG.chunk 'IHDR'
45
+ expected = "#{0.chr * 4}IHDR#{["IHDR".png_crc].pack 'N'}"
46
+ assert_equal expected, chunk
47
+ end
48
+
49
+ def test_to_blob
50
+ assert_equal @blob, @png.to_blob
51
+ end
52
+
53
+ def test_save
54
+ path = "blah.png"
55
+ @png.save(path)
56
+ assert_equal @blob, File.read(path)
57
+ ensure
58
+ assert_equal 1, File.unlink(path)
59
+ end
60
+
61
+ class TestCanvas < Test::Unit::TestCase
62
+
63
+ def setup
64
+ @canvas = PNG::Canvas.new 5, 10, PNG::Color::White
65
+ end
66
+
67
+ def test_index
68
+ assert_equal PNG::Color::White, @canvas[1, 2]
69
+ assert_same @canvas[1, 2], @canvas.data[1][2]
70
+ end
71
+
72
+ def test_index_tall
73
+ @canvas = PNG::Canvas.new 2, 4, PNG::Color::White
74
+ @canvas[ 0, 0] = PNG::Color::Black
75
+ @canvas[ 0, 3] = PNG::Color::Background
76
+ @canvas[ 1, 0] = PNG::Color::Yellow
77
+ @canvas[ 1, 3] = PNG::Color::Blue
78
+
79
+ expected = " ,,\n0000\n0000\n..++\n"
80
+
81
+ assert_equal expected, @canvas.to_s
82
+ end
83
+
84
+ def test_index_wide
85
+ @canvas = PNG::Canvas.new 4, 2, PNG::Color::White
86
+ @canvas[ 0, 0] = PNG::Color::Black
87
+ @canvas[ 3, 0] = PNG::Color::Background
88
+ @canvas[ 0, 1] = PNG::Color::Yellow
89
+ @canvas[ 3, 1] = PNG::Color::Blue
90
+
91
+ expected = "++0000,,\n..0000 \n"
92
+
93
+ assert_equal expected, @canvas.to_s
94
+ end
95
+
96
+ def test_index_bad_x
97
+ begin
98
+ @canvas[6, 1]
99
+ rescue => e
100
+ assert_equal "bad x value 6 >= 5", e.message
101
+ else
102
+ flunk "didn't raise"
103
+ end
104
+ end
105
+
106
+ def test_index_bad_y
107
+ begin
108
+ @canvas[1, 11]
109
+ rescue => e
110
+ assert_equal "bad y value 11 >= 10", e.message
111
+ else
112
+ flunk "didn't raise"
113
+ end
114
+ end
115
+
116
+ def test_index_equals
117
+ @canvas[1, 2] = PNG::Color::Red
118
+ assert_equal PNG::Color::Red, @canvas[1, 2]
119
+ assert_same @canvas[1, 2], @canvas.data[7][1]
120
+
121
+ expected = "
122
+ 0000000000
123
+ 0000000000
124
+ 0000000000
125
+ 0000000000
126
+ 0000000000
127
+ 0000000000
128
+ 0000000000
129
+ 00,,000000
130
+ 0000000000
131
+ 0000000000".strip + "\n"
132
+ actual = @canvas.to_s
133
+ assert_equal expected, actual
134
+ end
135
+
136
+ def test_index_equals_bad_x
137
+ begin
138
+ @canvas[6, 1] = PNG::Color::Red
139
+ rescue => e
140
+ assert_equal "bad x value 6 >= 5", e.message
141
+ else
142
+ flunk "didn't raise"
143
+ end
144
+ end
145
+
146
+ def test_index_equals_bad_y
147
+ begin
148
+ @canvas[1, 11] = PNG::Color::Red
149
+ rescue => e
150
+ assert_equal "bad y value 11 >= 10", e.message
151
+ else
152
+ flunk "didn't raise"
153
+ end
154
+ end
155
+
156
+ # def test_point
157
+ # raise NotImplementedError, 'Need to write test_point'
158
+ # end
159
+
160
+ def test_inspect
161
+ assert_equal "#<PNG::Canvas 5x10>", @canvas.inspect
162
+ end
163
+
164
+ def test_point
165
+ assert_equal PNG::Color.new(0xfe, 0x00, 0xfe, 0xfe),
166
+ @canvas.point(0, 0, PNG::Color::Magenta)
167
+ # flunk "this doesn't test ANYTHING"
168
+ end
169
+
170
+ def test_line
171
+ @canvas.line 0, 9, 4, 0, PNG::Color::Black
172
+
173
+ expected = <<-EOF
174
+ ..00000000
175
+ ,,00000000
176
+ 00,,000000
177
+ 00..000000
178
+ 00++++0000
179
+ 0000..0000
180
+ 0000++++00
181
+ 000000..00
182
+ 000000,,00
183
+ 00000000..
184
+ EOF
185
+
186
+ assert_equal expected, @canvas.to_s
187
+ end
188
+
189
+ def test_positive_slope_line
190
+ @canvas.line 0, 0, 4, 9, PNG::Color::Black
191
+
192
+ expected = <<-EOF
193
+ 00000000..
194
+ 00000000,,
195
+ 000000,,00
196
+ 000000..00
197
+ 0000++++00
198
+ 0000..0000
199
+ 00++++0000
200
+ 00..000000
201
+ 00,,000000
202
+ ..00000000
203
+ EOF
204
+
205
+ assert_equal expected, @canvas.to_s
206
+ end
207
+
208
+ def util_ascii_art(width, height)
209
+ (("0" * width * 2) + "\n") * height
210
+ end
211
+
212
+ def test_to_s_normal
213
+ @canvas = PNG::Canvas.new 5, 10, PNG::Color::White
214
+ expected = util_ascii_art(5, 10)
215
+ assert_equal expected, @canvas.to_s
216
+ end
217
+
218
+ def test_to_s_wide
219
+ @canvas = PNG::Canvas.new 250, 10, PNG::Color::White
220
+ expected = util_ascii_art(36, 2) # scaled
221
+ assert_equal expected, @canvas.to_s
222
+ end
223
+
224
+ def test_to_s_tall
225
+ @canvas = PNG::Canvas.new 10, 250, PNG::Color::White
226
+ expected = util_ascii_art(10, 250)
227
+ assert_equal expected, @canvas.to_s
228
+ end
229
+
230
+ def test_to_s_huge
231
+ @canvas = PNG::Canvas.new 250, 250, PNG::Color::White
232
+ expected = util_ascii_art(36, 36) # scaled
233
+ assert_equal expected, @canvas.to_s
234
+ end
235
+
236
+ # def test_class_read_chunk
237
+ # type, data = PNG.read_chunk @IHDR_chunk
238
+
239
+ # assert_equal 'IHDR', type
240
+ # assert_equal @IHDR_data, data
241
+ # end
242
+
243
+ # def test_class_read_IDAT
244
+ # canvas = PNG::Canvas.new 10, 10, PNG::Color::White
245
+
246
+ # data = "x\332c\370O$`\030UH_\205\000#\373\216\200"
247
+
248
+ # PNG.read_IDAT data, canvas
249
+
250
+ # assert_equal @blob, PNG.new(canvas).to_blob
251
+ # end
252
+
253
+ # def test_class_read_IHDR
254
+ # canvas = PNG.read_IHDR @IHDR_data
255
+ # assert_equal 10, canvas.width
256
+ # assert_equal 10, canvas.height
257
+ # end
258
+ end
259
+
260
+ class TestPng::TestColor < Test::Unit::TestCase
261
+ def setup
262
+ @color = PNG::Color.new 0x01, 0x02, 0x03, 0x04
263
+ end
264
+
265
+ def test_class_from_str
266
+ @color = PNG::Color.from "0x01020304"
267
+ test_r
268
+ test_g
269
+ test_b
270
+ test_a
271
+ end
272
+
273
+ def test_class_from_int
274
+ @color = PNG::Color.from 0x01020304
275
+ test_r
276
+ test_g
277
+ test_b
278
+ test_a
279
+ end
280
+
281
+ def test_r
282
+ assert_equal 0x01, @color.r
283
+ end
284
+
285
+ def test_g
286
+ assert_equal 0x02, @color.g
287
+ end
288
+
289
+ def test_b
290
+ assert_equal 0x03, @color.b
291
+ end
292
+
293
+ def test_a
294
+ assert_equal 0x04, @color.a
295
+ end
296
+
297
+ def test_blend
298
+ c1 = @color
299
+ c2 = PNG::Color.new 0xFF, 0xFE, 0xFD, 0xFC
300
+
301
+ assert_equal PNG::Color.new(0xfb, 0xfa, 0xf9, 0xf8), c1.blend(c2)
302
+ end
303
+
304
+ def test_intensity
305
+ assert_equal PNG::Color.new(0x01, 0x02, 0x03, 0x3c), @color.intensity(0xf00)
306
+ end
307
+
308
+ def test_inspect
309
+ assert_equal "#<PNG::Color 01 02 03 04>", @color.inspect
310
+ end
311
+
312
+ def test_inspect_name
313
+ assert_equal "#<PNG::Color Red>", PNG::Color::Red.inspect
314
+ end
315
+
316
+ def test_to_ascii
317
+ assert_equal '00', PNG::Color::White.to_ascii, "white"
318
+ assert_equal '++', PNG::Color::Yellow.to_ascii, "yellow"
319
+ assert_equal ',,', PNG::Color::Red.to_ascii, "red"
320
+ assert_equal '..', PNG::Color::Black.to_ascii, "black"
321
+ assert_equal ' ', PNG::Color::Background.to_ascii, "background"
322
+ end
323
+
324
+ def test_to_ascii_alpha
325
+ assert_equal '00', PNG::Color.new(255,255,255,255).to_ascii
326
+ assert_equal '00', PNG::Color.new(255,255,255,192).to_ascii
327
+ assert_equal '++', PNG::Color.new(255,255,255,191).to_ascii
328
+ assert_equal '++', PNG::Color.new(255,255,255,127).to_ascii
329
+ assert_equal ',,', PNG::Color.new(255,255,255,126).to_ascii
330
+ assert_equal ',,', PNG::Color.new(255,255,255, 64).to_ascii
331
+ assert_equal '..', PNG::Color.new(255,255,255, 63).to_ascii
332
+ assert_equal '..', PNG::Color.new(255,255,255, 1).to_ascii
333
+ assert_equal ' ', PNG::Color.new(255,255,255, 0).to_ascii
334
+ end
335
+
336
+ def test_to_s_name
337
+ assert_equal 'Red', PNG::Color::Red.to_s
338
+ end
339
+
340
+ def test_to_s
341
+ obj = PNG::Color.new(255,255,255, 0)
342
+ assert_equal '#<PNG::Color:0xXXXXXX>', obj.to_s.sub(/0x[0-9a-f]+/, '0xXXXXXX')
343
+ end
344
+
345
+ # def test_values
346
+ # raise NotImplementedError, 'Need to write test_values'
347
+ # end
348
+ end
349
+
350
+ end
351
+
352
+ class TestPng::TestPie < Test::Unit::TestCase
353
+ def setup
354
+
355
+ end
356
+
357
+ def test_pie_chart_odd
358
+ expected =
359
+ [" .. ",
360
+ " ,,,,,,........ ",
361
+ " ,,,,,,,,.......... ",
362
+ " ,,,,,,,,.......... ",
363
+ " ,,,,,,,,.......... ",
364
+ ",,,,,,,,,,............",
365
+ " ,,,,,,,,,,,,,,,,,, ",
366
+ " ,,,,,,,,,,,,,,,,,, ",
367
+ " ,,,,,,,,,,,,,,,,,, ",
368
+ " ,,,,,,,,,,,,,, ",
369
+ " ,, ",
370
+ nil].join("\n")
371
+
372
+ actual = PNG::pie_chart(11, 0.25, PNG::Color::Black, PNG::Color::Green)
373
+ assert_equal expected, actual.to_s
374
+ end
375
+
376
+ def test_pie_chart_even
377
+ expected =
378
+ [" .. ",
379
+ " ,,,,,,........ ",
380
+ " ,,,,,,,,.......... ",
381
+ " ,,,,,,,,.......... ",
382
+ " ,,,,,,,,.......... ",
383
+ ",,,,,,,,,,............",
384
+ " ,,,,,,,,,,,,,,,,,, ",
385
+ " ,,,,,,,,,,,,,,,,,, ",
386
+ " ,,,,,,,,,,,,,,,,,, ",
387
+ " ,,,,,,,,,,,,,, ",
388
+ " ,, ",
389
+ nil].join("\n")
390
+
391
+ actual = PNG::pie_chart(10, 0.25, PNG::Color::Black, PNG::Color::Green)
392
+ assert_equal expected, actual.to_s
393
+ end
394
+
395
+ def util_angle(expect, x, y)
396
+ actual = PNG.angle(x, y)
397
+ case expect
398
+ when Integer then
399
+ assert_equal(expect, actual,
400
+ "[#{x}, #{y}] should be == #{expect}, was #{actual}")
401
+ else
402
+ assert_in_delta(expect, actual, 0.5)
403
+ end
404
+ end
405
+
406
+ def test_math_is_hard_lets_go_shopping
407
+ util_angle 0, 0, 0
408
+ (25..500).step(25) do |n|
409
+ util_angle 0, 0, n
410
+ util_angle 90, n, 0
411
+ util_angle 180, 0, -n
412
+ util_angle 270, -n, 0
413
+ end
414
+
415
+ util_angle 359.5, -1, 250
416
+ util_angle 0.0, 0, 250
417
+ util_angle 0.5, 1, 250
418
+
419
+ util_angle 89.5, 250, 1
420
+ util_angle 90.0, 250, 0
421
+ util_angle 90.5, 250, -1
422
+ end
423
+ end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.99
2
+ rubygems_version: 0.9.0.9
3
3
  specification_version: 1
4
4
  name: png
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.0.0
7
- date: 2006-08-31 00:00:00 -07:00
8
- summary: A pure ruby PNG library
6
+ version: 1.1.0
7
+ date: 2007-03-26 00:00:00 -07:00
8
+ summary: An almost-pure-ruby PNG library
9
9
  require_paths:
10
10
  - lib
11
- email: ryand-ruby@zenspider.com
12
- homepage:
13
- rubyforge_project:
14
- description: png allows you to write a PNG file without any C libraries.
11
+ email: support@zenspider.com
12
+ homepage: http://www.zenspider.com/ZSS/Products/png/
13
+ rubyforge_project: seattlerb
14
+ description: "PNG is an almost-pure-ruby PNG library. It lets you write a PNG without any C libraries. == FEATURES * Very simple interface. * Outputs simple PNG files with ease. * Basic PNG reader as well (someday it might do compositing and the like!). * Almost pure ruby, does require a compiler. == SYNOPSYS require 'png' canvas = PNG::Canvas.new 200, 200 # Set a point to a color canvas[100, 100] = PNG::Color::Black # draw an anti-aliased line canvas.line 50, 50, 100, 50, PNG::Color::Blue png = PNG.new canvas png.save 'blah.png'"
15
15
  autorequire:
16
16
  default_executable:
17
17
  bindir: bin
@@ -28,14 +28,20 @@ cert_chain:
28
28
  post_install_message:
29
29
  authors:
30
30
  - Ryan Davis
31
+ - Eric Hodel
31
32
  files:
33
+ - History.txt
32
34
  - Manifest.txt
33
- - README
35
+ - README.txt
34
36
  - Rakefile
35
37
  - example/lines.rb
38
+ - example/profile.rb
39
+ - example/profile_lines.rb
36
40
  - lib/png.rb
37
- test_files: []
38
-
41
+ - lib/png/pie.rb
42
+ - test/test_png.rb
43
+ test_files:
44
+ - test/test_png.rb
39
45
  rdoc_options: []
40
46
 
41
47
  extra_rdoc_files: []
@@ -46,5 +52,22 @@ extensions: []
46
52
 
47
53
  requirements: []
48
54
 
49
- dependencies: []
50
-
55
+ dependencies:
56
+ - !ruby/object:Gem::Dependency
57
+ name: RubyInline
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Version::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 3.5.0
64
+ version:
65
+ - !ruby/object:Gem::Dependency
66
+ name: hoe
67
+ version_requirement:
68
+ version_requirements: !ruby/object:Gem::Version::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 1.2.0
73
+ version:
data/README DELETED
@@ -1,28 +0,0 @@
1
- = png
2
-
3
- == About
4
-
5
- png is a pure-ruby PNG library. It lets you write a PNG without any C
6
- libraries. Of course it is "slow".
7
-
8
- == Installing png
9
-
10
- Just install the gem:
11
-
12
- $ sudo gem install png
13
-
14
- == Using png
15
-
16
- require 'png'
17
-
18
- canvas = PNG::Canvas.new 200, 200
19
-
20
- # Set a point to a color
21
- canvas[100, 100] = PNG::Color::Black
22
-
23
- # draw an anti-aliased line
24
- canvas.line 50, 50, 100, 50, PNG::Color::Blue
25
-
26
- png = PNG.new canvas
27
- png.save 'blah.png'
28
-