png 1.0.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.
- data/Manifest.txt +5 -0
- data/README +28 -0
- data/Rakefile +56 -0
- data/example/lines.rb +27 -0
- data/lib/png.rb +288 -0
- metadata +50 -0
data/Manifest.txt
ADDED
data/README
ADDED
@@ -0,0 +1,28 @@
|
|
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
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
|
7
|
+
$VERBOSE = nil
|
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'
|
16
|
+
|
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
|
44
|
+
|
45
|
+
desc 'Build Gem'
|
46
|
+
Rake::GemPackageTask.new spec do |pkg|
|
47
|
+
pkg.need_tar = true
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'Clean up'
|
51
|
+
task :clean => [ :clobber_rdoc, :clobber_package ]
|
52
|
+
|
53
|
+
desc 'Clean up'
|
54
|
+
task :clobber => [ :clean ]
|
55
|
+
|
56
|
+
# vim: syntax=Ruby
|
data/example/lines.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
require 'png'
|
4
|
+
|
5
|
+
canvas = PNG::Canvas.new 1024, 1024, PNG::Color::Black
|
6
|
+
|
7
|
+
#canvas.each do |x, y|
|
8
|
+
# case x
|
9
|
+
# when y then
|
10
|
+
# canvas.point(x, y, Color::Black)
|
11
|
+
# when 50 then
|
12
|
+
# canvas.point(x, y, Color::Background)
|
13
|
+
# end
|
14
|
+
# canvas.point(x, y, Color::Green) if y = 200
|
15
|
+
#end
|
16
|
+
|
17
|
+
canvas.line 50, 50, 100, 50, PNG::Color::Blue
|
18
|
+
canvas.line 50, 50, 50, 100, PNG::Color::Blue
|
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
|
22
|
+
canvas.line 0, 200, 200, 0, PNG::Color::Black
|
23
|
+
|
24
|
+
png = PNG.new canvas
|
25
|
+
png.save 'blah.png'
|
26
|
+
`open blah.png`
|
27
|
+
|
data/lib/png.rb
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
class String # :nodoc:
|
4
|
+
|
5
|
+
##
|
6
|
+
# Calculates a CRC using the algorithm in the PNG specification.
|
7
|
+
|
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
|
18
|
+
end
|
19
|
+
|
20
|
+
c = 0xffffffff
|
21
|
+
each_byte do |b|
|
22
|
+
c = @@crc[(c^b) & 0xff] ^ (c >> 8)
|
23
|
+
end
|
24
|
+
return c ^ 0xffffffff
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# A pure Ruby Portable Network Graphics (PNG) writer.
|
31
|
+
#
|
32
|
+
# http://www.libpng.org/pub/png/spec/1.2/
|
33
|
+
#
|
34
|
+
# PNG supports:
|
35
|
+
# + 8 bit truecolor PNGs
|
36
|
+
#
|
37
|
+
# PNG does not support:
|
38
|
+
# + any other color depth
|
39
|
+
# + extra data chunks
|
40
|
+
# + filters
|
41
|
+
#
|
42
|
+
# = Example
|
43
|
+
#
|
44
|
+
# require 'png'
|
45
|
+
#
|
46
|
+
# canvas = PNG::Canvas.new 200, 200
|
47
|
+
# canvas[100, 100] = PNG::Color::Black
|
48
|
+
# canvas.line 50, 50, 100, 50, PNG::Color::Blue
|
49
|
+
# png = PNG.new canvas
|
50
|
+
# png.save 'blah.png'
|
51
|
+
|
52
|
+
class PNG
|
53
|
+
|
54
|
+
##
|
55
|
+
# Creates a PNG chunk of type +type+ that contains +data+.
|
56
|
+
|
57
|
+
def self.chunk(type, data="")
|
58
|
+
[data.size, type, data, (type + data).png_crc].pack("Na*a*N")
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Creates a new PNG object using +canvas+
|
63
|
+
|
64
|
+
def initialize(canvas)
|
65
|
+
@height = canvas.height
|
66
|
+
@width = canvas.width
|
67
|
+
@bits = 8
|
68
|
+
@data = canvas.data
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Writes the PNG to +path+.
|
73
|
+
|
74
|
+
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
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# RGBA colors
|
88
|
+
|
89
|
+
class Color
|
90
|
+
|
91
|
+
attr_reader :values
|
92
|
+
|
93
|
+
##
|
94
|
+
# Creates a new color with values +red+, +green+, +blue+, and +alpha+.
|
95
|
+
|
96
|
+
def initialize(red, green, blue, alpha)
|
97
|
+
@values = [red, green, blue, alpha]
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Transparent white
|
102
|
+
|
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
|
115
|
+
|
116
|
+
##
|
117
|
+
# Red component
|
118
|
+
|
119
|
+
def r; @values[0]; end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Green component
|
123
|
+
|
124
|
+
def g; @values[1]; end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Blue component
|
128
|
+
|
129
|
+
def b; @values[2]; end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Alpha transparency component
|
133
|
+
|
134
|
+
def a; @values[3]; end
|
135
|
+
|
136
|
+
##
|
137
|
+
# Blends +color+ into this color returning a new blended color.
|
138
|
+
|
139
|
+
def blend(color)
|
140
|
+
return Color.new((r * (0xFF - color.a) + color.r * color.a) >> 8,
|
141
|
+
(g * (0xFF - color.a) + color.g * color.a) >> 8,
|
142
|
+
(b * (0xFF - color.a) + color.b * color.a) >> 8,
|
143
|
+
(a * (0xFF - color.a) + color.a * color.a) >> 8)
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Returns a new color with an alpha value adjusted by +i+.
|
148
|
+
|
149
|
+
def intensity(i)
|
150
|
+
return Color.new(r,b,g,(a*i) >> 8)
|
151
|
+
end
|
152
|
+
|
153
|
+
def inspect # :nodoc:
|
154
|
+
"#<%s %02x %02x %02x %02x>" % [self.class, *@values]
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# PNG canvas
|
161
|
+
|
162
|
+
class Canvas
|
163
|
+
|
164
|
+
##
|
165
|
+
# Height of the canvas
|
166
|
+
|
167
|
+
attr_reader :height
|
168
|
+
|
169
|
+
##
|
170
|
+
# Width of the canvas
|
171
|
+
|
172
|
+
attr_reader :width
|
173
|
+
|
174
|
+
##
|
175
|
+
# Raw data
|
176
|
+
|
177
|
+
attr_reader :data
|
178
|
+
|
179
|
+
def initialize(height, width, background = Color::White)
|
180
|
+
@height = height
|
181
|
+
@width = width
|
182
|
+
@data = Array.new(@width) { |x| Array.new(@height) { background } }
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Retrieves the color of the pixel at (+x+, +y+).
|
187
|
+
|
188
|
+
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]
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Sets the color of the pixel at (+x+, +y+) to +color+.
|
196
|
+
|
197
|
+
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
|
201
|
+
end
|
202
|
+
|
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
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Blends +color+ onto the color at point (+x+, +y+).
|
216
|
+
|
217
|
+
def point(x, y, color)
|
218
|
+
self[x,y] = self[x,y].blend(color)
|
219
|
+
end
|
220
|
+
|
221
|
+
##
|
222
|
+
# Draws a line using Xiaolin Wu's antialiasing technique.
|
223
|
+
#
|
224
|
+
# http://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm
|
225
|
+
|
226
|
+
def line(x0, y0, x1, y1, color)
|
227
|
+
dx = x1 - x0
|
228
|
+
sx = dx < 0 ? -1 : 1
|
229
|
+
dx *= sx # TODO: abs?
|
230
|
+
dy = y1 - y0
|
231
|
+
|
232
|
+
# 'easy' cases
|
233
|
+
if dy == 0 then
|
234
|
+
Range.new(*[x0,x1].sort).each do |x|
|
235
|
+
point(x, y0, color)
|
236
|
+
end
|
237
|
+
return
|
238
|
+
end
|
239
|
+
|
240
|
+
if dx == 0 then
|
241
|
+
(y0..y1).each do |y|
|
242
|
+
point(x0, y, color)
|
243
|
+
end
|
244
|
+
return
|
245
|
+
end
|
246
|
+
|
247
|
+
if dx == dy then
|
248
|
+
Range.new(*[x0,x1].sort).each do |x|
|
249
|
+
point(x, y0, color)
|
250
|
+
y0 += 1
|
251
|
+
end
|
252
|
+
return
|
253
|
+
end
|
254
|
+
|
255
|
+
# main loop
|
256
|
+
point(x0, y0, color)
|
257
|
+
e_acc = 0
|
258
|
+
if dy > dx then # vertical displacement
|
259
|
+
e = (dx << 16) / dy
|
260
|
+
(y0...y1-1).each do |i|
|
261
|
+
e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
|
262
|
+
x0 = x0 + sx if (e_acc <= e_acc_temp)
|
263
|
+
w = 0xFF-(e_acc >> 8)
|
264
|
+
point(x0, y0, color.intensity(w))
|
265
|
+
y0 = y0 + 1
|
266
|
+
point(x0 + sx, y0, color.intensity(0xFF-w))
|
267
|
+
end
|
268
|
+
point(x1, y1, color)
|
269
|
+
return
|
270
|
+
end
|
271
|
+
|
272
|
+
# horizontal displacement
|
273
|
+
e = (dy << 16) / dx
|
274
|
+
(x0...(x1-sx)).each do |i|
|
275
|
+
e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
|
276
|
+
y0 += 1 if (e_acc <= e_acc_temp)
|
277
|
+
w = 0xFF-(e_acc >> 8)
|
278
|
+
point(x0, y0, color.intensity(w))
|
279
|
+
x0 += sx
|
280
|
+
point(x0, y0 + 1, color.intensity(0xFF-w))
|
281
|
+
end
|
282
|
+
point(x1, y1, color)
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.99
|
3
|
+
specification_version: 1
|
4
|
+
name: png
|
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
|
9
|
+
require_paths:
|
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.
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Ryan Davis
|
31
|
+
files:
|
32
|
+
- Manifest.txt
|
33
|
+
- README
|
34
|
+
- Rakefile
|
35
|
+
- example/lines.rb
|
36
|
+
- lib/png.rb
|
37
|
+
test_files: []
|
38
|
+
|
39
|
+
rdoc_options: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
executables: []
|
44
|
+
|
45
|
+
extensions: []
|
46
|
+
|
47
|
+
requirements: []
|
48
|
+
|
49
|
+
dependencies: []
|
50
|
+
|