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.
- data/History.txt +18 -0
- data/Manifest.txt +13 -0
- data/README.txt +65 -0
- data/Rakefile +19 -0
- data/example/lines.rb +20 -0
- data/example/profile.rb +16 -0
- data/example/profile_lines.rb +37 -0
- data/lib/png.rb +584 -0
- data/lib/png/default_font.png +0 -0
- data/lib/png/font.rb +73 -0
- data/lib/png/pie.rb +47 -0
- data/lib/png/reader.rb +139 -0
- data/test/test_png.rb +529 -0
- data/test/test_png_font.rb +77 -0
- data/test/test_png_reader.rb +87 -0
- metadata +94 -0
Binary file
|
data/lib/png/font.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
require 'png/reader'
|
4
|
+
|
5
|
+
##
|
6
|
+
# Implements a simple bitmap font by extracting letters from a PNG.
|
7
|
+
|
8
|
+
class PNG::Font
|
9
|
+
LETTERS = (('A'..'Z').to_a +
|
10
|
+
('a'..'z').to_a +
|
11
|
+
('0'..'9').to_a + [" "] * 16 +
|
12
|
+
'({[<!@#$%^&*?_+-=;,"/~>]})'.split(//))
|
13
|
+
|
14
|
+
attr_reader :height, :width, :canvas
|
15
|
+
|
16
|
+
def self.default
|
17
|
+
@@default ||= new(File.join(File.dirname(__FILE__), "default_font.png"))
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(png_file)
|
21
|
+
@canvas = PNG.load_file png_file
|
22
|
+
@height, @width = canvas.height / 4, canvas.width / 26
|
23
|
+
@cache = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def coordinates c
|
27
|
+
i = LETTERS.index c
|
28
|
+
|
29
|
+
raise ArgumentError, "Can't find #{c.inspect}" unless i
|
30
|
+
|
31
|
+
x = (i % 26) * width
|
32
|
+
y = (3 - (i / 26)) * height # start from the top (3rd row)
|
33
|
+
|
34
|
+
return x, y, x+width-1, y+height-1
|
35
|
+
end
|
36
|
+
|
37
|
+
def [] c
|
38
|
+
c = c.chr unless String === c
|
39
|
+
x0, y0, x1, y1 = coordinates c
|
40
|
+
|
41
|
+
@cache[c] ||= @canvas.extract(x0, y0, x1, y1)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class PNG::Canvas
|
46
|
+
##
|
47
|
+
# Write a string at [x, y] with font, optionally specifying a font,
|
48
|
+
# an alignment of :left, :center, or :right and the style to draw
|
49
|
+
# the annotation (see #composite).
|
50
|
+
#
|
51
|
+
# require 'png/font'
|
52
|
+
|
53
|
+
def annotate(string, x, y,
|
54
|
+
font = PNG::Font.default, align = :left, style = :overwrite)
|
55
|
+
case align
|
56
|
+
when :left then
|
57
|
+
# do nothing
|
58
|
+
when :center then
|
59
|
+
x -= string.length * font.width / 2
|
60
|
+
when :right then
|
61
|
+
x -= string.length * font.width
|
62
|
+
else
|
63
|
+
raise ArgumentError, "Unknown align: #{align.inspect}"
|
64
|
+
end
|
65
|
+
|
66
|
+
x_offset, width = 0, font.width
|
67
|
+
|
68
|
+
string.split(//).each do |char|
|
69
|
+
self.composite font[char], x + x_offset, y
|
70
|
+
x_offset += width
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/png/pie.rb
ADDED
@@ -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
|
data/lib/png/reader.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'png'
|
2
|
+
require 'enumerator'
|
3
|
+
|
4
|
+
class PNG
|
5
|
+
def self.load_file path, metadata_only = false
|
6
|
+
self.load File.read(path), metadata_only
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.load png, metadata_only = false
|
10
|
+
png = png.dup
|
11
|
+
signature = png.slice! 0, 8
|
12
|
+
raise ArgumentError, 'Invalid PNG signature' unless signature == SIGNATURE
|
13
|
+
|
14
|
+
ihdr = read_chunk 'IHDR', png
|
15
|
+
|
16
|
+
bit_depth, color_type, width, height = read_IHDR ihdr, metadata_only
|
17
|
+
|
18
|
+
return [width, height, bit_depth] if metadata_only
|
19
|
+
|
20
|
+
canvas = PNG::Canvas.new width, height
|
21
|
+
|
22
|
+
type = png.slice(4, 4).unpack('a4').first
|
23
|
+
read_chunk type, png if type == 'iCCP' # Ignore color profile
|
24
|
+
|
25
|
+
read_IDAT read_chunk('IDAT', png), bit_depth, color_type, canvas
|
26
|
+
read_chunk 'IEND', png
|
27
|
+
|
28
|
+
canvas
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.read_chunk expected_type, png
|
32
|
+
size, type = png.slice!(0, 8).unpack 'Na4'
|
33
|
+
data, crc = png.slice!(0, size + 4).unpack "a#{size}N"
|
34
|
+
|
35
|
+
check_crc type, data, crc
|
36
|
+
|
37
|
+
raise ArgumentError, "Expected #{expected_type} chunk, not #{type}" unless
|
38
|
+
type == expected_type
|
39
|
+
|
40
|
+
return data
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.check_crc type, data, crc
|
44
|
+
return true if (type + data).png_crc == crc
|
45
|
+
raise ArgumentError, "Invalid CRC encountered in #{type} chunk"
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.read_IHDR data, metadata_only = false
|
49
|
+
width, height, bit_depth, color_type, *rest = data.unpack 'N2C5'
|
50
|
+
|
51
|
+
unless metadata_only then
|
52
|
+
raise ArgumentError, "Wrong bit depth: #{bit_depth}" unless
|
53
|
+
bit_depth == 8
|
54
|
+
raise ArgumentError, "Wrong color type: #{color_type}" unless
|
55
|
+
color_type == RGBA or color_type = RGB
|
56
|
+
raise ArgumentError, "Unsupported options: #{rest.inspect}" unless
|
57
|
+
rest == [0, 0, 0]
|
58
|
+
end
|
59
|
+
|
60
|
+
return bit_depth, color_type, width, height
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.read_IDAT data, bit_depth, color_type, canvas
|
64
|
+
data = Zlib::Inflate.inflate(data).unpack 'C*'
|
65
|
+
|
66
|
+
pixel_size = color_type == RGBA ? 4 : 3
|
67
|
+
|
68
|
+
height = canvas.height
|
69
|
+
scanline_length = pixel_size * canvas.width + 1 # for filter
|
70
|
+
|
71
|
+
row = canvas.height - 1
|
72
|
+
until data.empty? do
|
73
|
+
row_data = data.slice! 0, scanline_length
|
74
|
+
|
75
|
+
filter = row_data.shift
|
76
|
+
case filter
|
77
|
+
when NONE then
|
78
|
+
when SUB then
|
79
|
+
row_data.each_with_index do |byte, index|
|
80
|
+
left = index < pixel_size ? 0 : row_data[index - pixel_size]
|
81
|
+
row_data[index] = (byte + left) % 256
|
82
|
+
end
|
83
|
+
when UP then
|
84
|
+
row_data.each_with_index do |byte, index|
|
85
|
+
col = index / pixel_size
|
86
|
+
upper = row == 0 ? 0 : canvas[col, row + 1].values[index % pixel_size]
|
87
|
+
row_data[index] = (upper + byte) % 256
|
88
|
+
end
|
89
|
+
when AVG then
|
90
|
+
row_data.each_with_index do |byte, index|
|
91
|
+
col = index / pixel_size
|
92
|
+
upper = row == 0 ? 0 : canvas[col, row + 1].values[index % pixel_size]
|
93
|
+
left = index < pixel_size ? 0 : row_data[index - pixel_size]
|
94
|
+
|
95
|
+
row_data[index] = (byte + ((left + upper)/2).floor) % 256
|
96
|
+
end
|
97
|
+
when PAETH then
|
98
|
+
left = upper = upper_left = nil
|
99
|
+
row_data.each_with_index do |byte, index|
|
100
|
+
col = index / pixel_size
|
101
|
+
|
102
|
+
left = index < pixel_size ? 0 : row_data[index - pixel_size]
|
103
|
+
if row == height then
|
104
|
+
upper = upper_left = 0
|
105
|
+
else
|
106
|
+
upper = canvas[col, row + 1].values[index % pixel_size]
|
107
|
+
upper_left = col == 0 ? 0 :
|
108
|
+
canvas[col - 1, row + 1].values[index % pixel_size]
|
109
|
+
end
|
110
|
+
|
111
|
+
paeth = paeth left, upper, upper_left
|
112
|
+
row_data[index] = (byte + paeth) % 256
|
113
|
+
end
|
114
|
+
else
|
115
|
+
raise ArgumentError, "invalid filter algorithm #{filter}"
|
116
|
+
end
|
117
|
+
|
118
|
+
col = 0
|
119
|
+
row_data.each_slice pixel_size do |slice|
|
120
|
+
slice << 0xFF if pixel_size == 3
|
121
|
+
canvas[col, row] = PNG::Color.new(*slice)
|
122
|
+
col += 1
|
123
|
+
end
|
124
|
+
|
125
|
+
row -= 1
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.paeth a, b, c # left, above, upper left
|
130
|
+
p = a + b - c
|
131
|
+
pa = (p - a).abs
|
132
|
+
pb = (p - b).abs
|
133
|
+
pc = (p - c).abs
|
134
|
+
|
135
|
+
return a if pa <= pb && pa <= pc
|
136
|
+
return b if pb <= pc
|
137
|
+
c
|
138
|
+
end
|
139
|
+
end
|
data/test/test_png.rb
ADDED
@@ -0,0 +1,529 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'png'
|
4
|
+
require 'png/reader'
|
5
|
+
require 'png/pie'
|
6
|
+
|
7
|
+
class TestPng < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@canvas = PNG::Canvas.new 5, 10, PNG::Color::White
|
11
|
+
@png = PNG.new @canvas
|
12
|
+
|
13
|
+
@blob = <<-EOF.unpack('m*').first
|
14
|
+
iVBORw0KGgoAAAANSUhEUgAAAAUAAAAKCAYAAAB8OZQwAAAAD0lEQVR4nGP4
|
15
|
+
jwUwDGVBALuJxzlQugpEAAAAAElFTkSuQmCC
|
16
|
+
EOF
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_class_chunk
|
20
|
+
chunk = PNG.chunk 'IHDR', [10, 10, 8, 6, 0, 0, 0 ].pack('N2C5')
|
21
|
+
|
22
|
+
header_crc = "\2152\317\275"
|
23
|
+
header_data = "\000\000\000\n\000\000\000\n\b\006\000\000\000"
|
24
|
+
header_length = "\000\000\000\r"
|
25
|
+
header_chunk = "#{header_length}IHDR#{header_data}#{header_crc}"
|
26
|
+
|
27
|
+
assert_equal header_chunk, chunk
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_class_chunk_empty
|
31
|
+
chunk = PNG.chunk 'IHDR'
|
32
|
+
expected = "#{0.chr * 4}IHDR#{["IHDR".png_crc].pack 'N'}"
|
33
|
+
assert_equal expected, chunk
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_to_blob
|
37
|
+
assert_equal @blob, @png.to_blob
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_save
|
41
|
+
path = "blah.png"
|
42
|
+
@png.save(path)
|
43
|
+
assert_equal @blob, File.read(path)
|
44
|
+
ensure
|
45
|
+
assert_equal 1, File.unlink(path)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
class TestCanvas < Test::Unit::TestCase
|
51
|
+
|
52
|
+
def setup
|
53
|
+
@canvas = PNG::Canvas.new 5, 10, PNG::Color::White
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_composite_default
|
57
|
+
canvas1, canvas2 = util_composite_canvases
|
58
|
+
|
59
|
+
canvas1.composite canvas2, 1, 1
|
60
|
+
|
61
|
+
expected = " xxxxxxxx
|
62
|
+
xxxxxxxx
|
63
|
+
xx..xxxx
|
64
|
+
..xxxxxx
|
65
|
+
".gsub(/ /, '')
|
66
|
+
|
67
|
+
assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_composite_underlay
|
71
|
+
canvas1, canvas2 = util_composite_canvases
|
72
|
+
|
73
|
+
canvas1.composite canvas2, 1, 1, :add
|
74
|
+
|
75
|
+
expected = " xxxxxxxx
|
76
|
+
xxxx..xx
|
77
|
+
xx00xxxx
|
78
|
+
..xxxxxx
|
79
|
+
".gsub(/ /, '')
|
80
|
+
|
81
|
+
assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_composite_overlay
|
85
|
+
canvas1, canvas2 = util_composite_canvases
|
86
|
+
|
87
|
+
canvas1.composite canvas2, 1, 1, :overlay
|
88
|
+
|
89
|
+
expected = " xxxxxxxx
|
90
|
+
xxxx..xx
|
91
|
+
xx..xxxx
|
92
|
+
..xxxxxx
|
93
|
+
".gsub(/ /, '')
|
94
|
+
|
95
|
+
assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_composite_blend
|
99
|
+
canvas1, canvas2 = util_composite_canvases
|
100
|
+
|
101
|
+
canvas1.composite canvas2, 1, 1, :blend
|
102
|
+
|
103
|
+
expected = " xxxxxxxx
|
104
|
+
xxxx..xx
|
105
|
+
xx,,xxxx
|
106
|
+
..xxxxxx
|
107
|
+
".gsub(/ /, '')
|
108
|
+
|
109
|
+
assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_composite_bad_style
|
113
|
+
canvas1, canvas2 = util_composite_canvases
|
114
|
+
|
115
|
+
assert_raises RuntimeError do
|
116
|
+
canvas1.composite canvas2, 1, 1, :bad
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_extract
|
121
|
+
canvas1, _ = util_composite_canvases
|
122
|
+
|
123
|
+
expected = " xxxxxxxx
|
124
|
+
xxxx..xx
|
125
|
+
xx00xxxx
|
126
|
+
..xxxxxx
|
127
|
+
".gsub(/ /, '')
|
128
|
+
|
129
|
+
assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
|
130
|
+
|
131
|
+
canvas2 = canvas1.extract(1, 1, 2, 2)
|
132
|
+
|
133
|
+
expected = " xx..
|
134
|
+
00xx
|
135
|
+
".gsub(/ /, '')
|
136
|
+
|
137
|
+
assert_equal expected, canvas2.to_s.gsub(/ /, 'x')
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_index
|
141
|
+
assert_equal PNG::Color::White, @canvas[1, 2]
|
142
|
+
assert_same @canvas[1, 2], @canvas.data[1][2]
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_index_tall
|
146
|
+
@canvas = PNG::Canvas.new 2, 4, PNG::Color::White
|
147
|
+
@canvas[ 0, 0] = PNG::Color::Black
|
148
|
+
@canvas[ 0, 3] = PNG::Color::Background
|
149
|
+
@canvas[ 1, 0] = PNG::Color::Yellow
|
150
|
+
@canvas[ 1, 3] = PNG::Color::Blue
|
151
|
+
|
152
|
+
expected = " ,,\n0000\n0000\n..++\n"
|
153
|
+
|
154
|
+
assert_equal expected, @canvas.to_s
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_index_wide
|
158
|
+
@canvas = PNG::Canvas.new 4, 2, PNG::Color::White
|
159
|
+
@canvas[ 0, 0] = PNG::Color::Black
|
160
|
+
@canvas[ 3, 0] = PNG::Color::Background
|
161
|
+
@canvas[ 0, 1] = PNG::Color::Yellow
|
162
|
+
@canvas[ 3, 1] = PNG::Color::Blue
|
163
|
+
|
164
|
+
expected = "++0000,,\n..0000 \n"
|
165
|
+
|
166
|
+
assert_equal expected, @canvas.to_s
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_index_bad_x
|
170
|
+
begin
|
171
|
+
@canvas[6, 1]
|
172
|
+
rescue => e
|
173
|
+
assert_equal "bad x value 6 >= 5", e.message
|
174
|
+
else
|
175
|
+
flunk "didn't raise"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_index_bad_y
|
180
|
+
begin
|
181
|
+
@canvas[1, 11]
|
182
|
+
rescue => e
|
183
|
+
assert_equal "bad y value 11 >= 10", e.message
|
184
|
+
else
|
185
|
+
flunk "didn't raise"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_index_equals
|
190
|
+
@canvas[1, 2] = PNG::Color::Red
|
191
|
+
assert_equal PNG::Color::Red, @canvas[1, 2]
|
192
|
+
assert_same @canvas[1, 2], @canvas.data[7][1]
|
193
|
+
|
194
|
+
expected = "
|
195
|
+
0000000000
|
196
|
+
0000000000
|
197
|
+
0000000000
|
198
|
+
0000000000
|
199
|
+
0000000000
|
200
|
+
0000000000
|
201
|
+
0000000000
|
202
|
+
00,,000000
|
203
|
+
0000000000
|
204
|
+
0000000000".strip + "\n"
|
205
|
+
actual = @canvas.to_s
|
206
|
+
assert_equal expected, actual
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_index_equals_bad_x
|
210
|
+
begin
|
211
|
+
@canvas[6, 1] = PNG::Color::Red
|
212
|
+
rescue => e
|
213
|
+
assert_equal "bad x value 6 >= 5", e.message
|
214
|
+
else
|
215
|
+
flunk "didn't raise"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_index_equals_bad_y
|
220
|
+
begin
|
221
|
+
@canvas[1, 11] = PNG::Color::Red
|
222
|
+
rescue => e
|
223
|
+
assert_equal "bad y value 11 >= 10", e.message
|
224
|
+
else
|
225
|
+
flunk "didn't raise"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# def test_point
|
230
|
+
# raise NotImplementedError, 'Need to write test_point'
|
231
|
+
# end
|
232
|
+
|
233
|
+
def test_inspect
|
234
|
+
assert_equal "#<PNG::Canvas 5x10>", @canvas.inspect
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_point
|
238
|
+
assert_equal PNG::Color.new(0xff, 0x7f, 0xff, 0xff),
|
239
|
+
@canvas.point(0, 0, PNG::Color::Magenta)
|
240
|
+
# flunk "this doesn't test ANYTHING"
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_line
|
244
|
+
@canvas.line 0, 9, 4, 0, PNG::Color::Black
|
245
|
+
|
246
|
+
expected = <<-EOF
|
247
|
+
,,00000000
|
248
|
+
,,00000000
|
249
|
+
,,,,000000
|
250
|
+
00..000000
|
251
|
+
00,,,,0000
|
252
|
+
0000..0000
|
253
|
+
0000,,,,00
|
254
|
+
000000..00
|
255
|
+
000000,,,,
|
256
|
+
00000000..
|
257
|
+
EOF
|
258
|
+
|
259
|
+
assert_equal expected, @canvas.to_s
|
260
|
+
end
|
261
|
+
|
262
|
+
def test_positive_slope_line
|
263
|
+
@canvas.line 0, 0, 4, 9, PNG::Color::Black
|
264
|
+
|
265
|
+
expected = <<-EOF
|
266
|
+
00000000,,
|
267
|
+
00000000,,
|
268
|
+
000000,,,,
|
269
|
+
000000..00
|
270
|
+
0000,,,,00
|
271
|
+
0000..0000
|
272
|
+
00,,,,0000
|
273
|
+
00..000000
|
274
|
+
,,,,000000
|
275
|
+
..00000000
|
276
|
+
EOF
|
277
|
+
|
278
|
+
assert_equal expected, @canvas.to_s
|
279
|
+
end
|
280
|
+
|
281
|
+
def test_to_s_normal
|
282
|
+
@canvas = PNG::Canvas.new 5, 10, PNG::Color::White
|
283
|
+
expected = util_ascii_art(5, 10)
|
284
|
+
assert_equal expected, @canvas.to_s
|
285
|
+
end
|
286
|
+
|
287
|
+
def test_to_s_wide
|
288
|
+
@canvas = PNG::Canvas.new 250, 10, PNG::Color::White
|
289
|
+
expected = util_ascii_art(36, 2) # scaled
|
290
|
+
assert_equal expected, @canvas.to_s
|
291
|
+
end
|
292
|
+
|
293
|
+
def test_to_s_tall
|
294
|
+
@canvas = PNG::Canvas.new 10, 250, PNG::Color::White
|
295
|
+
expected = util_ascii_art(10, 250)
|
296
|
+
assert_equal expected, @canvas.to_s
|
297
|
+
end
|
298
|
+
|
299
|
+
def test_to_s_huge
|
300
|
+
@canvas = PNG::Canvas.new 250, 250, PNG::Color::White
|
301
|
+
expected = util_ascii_art(36, 36) # scaled
|
302
|
+
assert_equal expected, @canvas.to_s
|
303
|
+
end
|
304
|
+
|
305
|
+
def util_composite_canvases
|
306
|
+
canvas1 = PNG::Canvas.new 4, 4
|
307
|
+
canvas1[0, 0] = PNG::Color::Black
|
308
|
+
canvas1[1, 1] = PNG::Color::White
|
309
|
+
canvas1[2, 2] = PNG::Color::Black
|
310
|
+
|
311
|
+
expected = " xxxxxxxx
|
312
|
+
xxxx..xx
|
313
|
+
xx00xxxx
|
314
|
+
..xxxxxx
|
315
|
+
".gsub(/ /, '')
|
316
|
+
|
317
|
+
assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
|
318
|
+
|
319
|
+
|
320
|
+
canvas2 = PNG::Canvas.new 2, 2
|
321
|
+
canvas2[0, 0] = PNG::Color::Black
|
322
|
+
|
323
|
+
expected = " xxxx
|
324
|
+
..xx
|
325
|
+
".gsub(/ /, '')
|
326
|
+
|
327
|
+
assert_equal expected, canvas2.to_s.gsub(/ /, 'x')
|
328
|
+
|
329
|
+
return canvas1, canvas2
|
330
|
+
end
|
331
|
+
|
332
|
+
def util_ascii_art(width, height)
|
333
|
+
(("0" * width * 2) + "\n") * height
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
class TestPng::TestColor < Test::Unit::TestCase
|
338
|
+
def setup
|
339
|
+
@color = PNG::Color.new 0x01, 0x02, 0x03, 0x04
|
340
|
+
end
|
341
|
+
|
342
|
+
def test_class_from_str
|
343
|
+
@color = PNG::Color.from "0x01020304"
|
344
|
+
test_r
|
345
|
+
test_g
|
346
|
+
test_b
|
347
|
+
test_a
|
348
|
+
end
|
349
|
+
|
350
|
+
def test_class_from_int
|
351
|
+
@color = PNG::Color.from 0x01020304
|
352
|
+
test_r
|
353
|
+
test_g
|
354
|
+
test_b
|
355
|
+
test_a
|
356
|
+
end
|
357
|
+
|
358
|
+
def test_r
|
359
|
+
assert_equal 0x01, @color.r
|
360
|
+
end
|
361
|
+
|
362
|
+
def test_g
|
363
|
+
assert_equal 0x02, @color.g
|
364
|
+
end
|
365
|
+
|
366
|
+
def test_b
|
367
|
+
assert_equal 0x03, @color.b
|
368
|
+
end
|
369
|
+
|
370
|
+
def test_a
|
371
|
+
assert_equal 0x04, @color.a
|
372
|
+
end
|
373
|
+
|
374
|
+
def test_blend
|
375
|
+
# c1 = @color
|
376
|
+
# c2 = PNG::Color.new 0xFF, 0xFE, 0xFD, 0xFC
|
377
|
+
|
378
|
+
# assert_equal PNG::Color.new(0xFB, 0xFA, 0xF9, 0xF8), c1.blend(c2)
|
379
|
+
|
380
|
+
c1 = PNG::Color::White
|
381
|
+
c2 = PNG::Color::Black
|
382
|
+
|
383
|
+
assert_equal PNG::Color::Gray, c2.blend(c1)
|
384
|
+
assert_equal PNG::Color::Gray, c1.blend(c2)
|
385
|
+
end
|
386
|
+
|
387
|
+
def test_intensity
|
388
|
+
assert_equal PNG::Color.new(0x01, 0x02, 0x03, 0x3c), @color.intensity(0xf00)
|
389
|
+
end
|
390
|
+
|
391
|
+
def test_inspect
|
392
|
+
assert_equal "#<PNG::Color 01 02 03 04>", @color.inspect
|
393
|
+
end
|
394
|
+
|
395
|
+
def test_inspect_name
|
396
|
+
assert_equal "#<PNG::Color Red>", PNG::Color::Red.inspect
|
397
|
+
end
|
398
|
+
|
399
|
+
def test_pipe
|
400
|
+
b = PNG::Color::Black
|
401
|
+
w = PNG::Color::White
|
402
|
+
t = PNG::Color::Background
|
403
|
+
|
404
|
+
# first non-transparent
|
405
|
+
assert_equal b, b | t
|
406
|
+
assert_equal b, t | b
|
407
|
+
|
408
|
+
assert_equal b, b | w
|
409
|
+
assert_equal w, w | b
|
410
|
+
|
411
|
+
assert_equal t, t | t
|
412
|
+
end
|
413
|
+
|
414
|
+
def test_to_ascii
|
415
|
+
assert_equal '00', PNG::Color::White.to_ascii, "white"
|
416
|
+
assert_equal '++', PNG::Color::Yellow.to_ascii, "yellow"
|
417
|
+
assert_equal ',,', PNG::Color::Red.to_ascii, "red"
|
418
|
+
assert_equal '..', PNG::Color::Black.to_ascii, "black"
|
419
|
+
assert_equal ' ', PNG::Color::Background.to_ascii, "background"
|
420
|
+
end
|
421
|
+
|
422
|
+
def test_to_ascii_alpha
|
423
|
+
assert_equal '00', PNG::Color.new(255,255,255,255).to_ascii
|
424
|
+
assert_equal '00', PNG::Color.new(255,255,255,192).to_ascii
|
425
|
+
assert_equal '++', PNG::Color.new(255,255,255,191).to_ascii
|
426
|
+
assert_equal ',,', PNG::Color.new(255,255,255,127).to_ascii
|
427
|
+
assert_equal ',,', PNG::Color.new(255,255,255,126).to_ascii
|
428
|
+
assert_equal ',,', PNG::Color.new(255,255,255, 64).to_ascii
|
429
|
+
assert_equal '..', PNG::Color.new(255,255,255, 63).to_ascii
|
430
|
+
assert_equal '..', PNG::Color.new(255,255,255, 1).to_ascii
|
431
|
+
assert_equal ' ', PNG::Color.new(255,255,255, 0).to_ascii
|
432
|
+
end
|
433
|
+
|
434
|
+
def test_to_s_name
|
435
|
+
assert_equal 'Red', PNG::Color::Red.to_s
|
436
|
+
end
|
437
|
+
|
438
|
+
def test_to_s
|
439
|
+
obj = PNG::Color.new(255,255,255, 0)
|
440
|
+
assert_equal '#<PNG::Color:0xXXXXXX>', obj.to_s.sub(/0x[0-9a-f]+/, '0xXXXXXX')
|
441
|
+
end
|
442
|
+
|
443
|
+
def test_equals2
|
444
|
+
assert_equal PNG::Color.new(255,255,255, 0), PNG::Color.new(255,255,255, 0)
|
445
|
+
end
|
446
|
+
|
447
|
+
def test_hash
|
448
|
+
a = PNG::Color.new(255,255,255, 0)
|
449
|
+
b = PNG::Color.new(255,255,255, 0)
|
450
|
+
assert_equal a.hash, b.hash
|
451
|
+
end
|
452
|
+
|
453
|
+
# def test_values
|
454
|
+
# raise NotImplementedError, 'Need to write test_values'
|
455
|
+
# end
|
456
|
+
end
|
457
|
+
|
458
|
+
class TestPng::TestPie < Test::Unit::TestCase
|
459
|
+
def setup
|
460
|
+
|
461
|
+
end
|
462
|
+
|
463
|
+
def test_pie_chart_odd
|
464
|
+
expected =
|
465
|
+
[" .. ",
|
466
|
+
" ,,,,,,........ ",
|
467
|
+
" ,,,,,,,,.......... ",
|
468
|
+
" ,,,,,,,,.......... ",
|
469
|
+
" ,,,,,,,,.......... ",
|
470
|
+
",,,,,,,,,,............",
|
471
|
+
" ,,,,,,,,,,,,,,,,,, ",
|
472
|
+
" ,,,,,,,,,,,,,,,,,, ",
|
473
|
+
" ,,,,,,,,,,,,,,,,,, ",
|
474
|
+
" ,,,,,,,,,,,,,, ",
|
475
|
+
" ,, ",
|
476
|
+
nil].join("\n")
|
477
|
+
|
478
|
+
actual = PNG::pie_chart(11, 0.25, PNG::Color::Black, PNG::Color::Green)
|
479
|
+
assert_equal expected, actual.to_s
|
480
|
+
end
|
481
|
+
|
482
|
+
def test_pie_chart_even
|
483
|
+
expected =
|
484
|
+
[" .. ",
|
485
|
+
" ,,,,,,........ ",
|
486
|
+
" ,,,,,,,,.......... ",
|
487
|
+
" ,,,,,,,,.......... ",
|
488
|
+
" ,,,,,,,,.......... ",
|
489
|
+
",,,,,,,,,,............",
|
490
|
+
" ,,,,,,,,,,,,,,,,,, ",
|
491
|
+
" ,,,,,,,,,,,,,,,,,, ",
|
492
|
+
" ,,,,,,,,,,,,,,,,,, ",
|
493
|
+
" ,,,,,,,,,,,,,, ",
|
494
|
+
" ,, ",
|
495
|
+
nil].join("\n")
|
496
|
+
|
497
|
+
actual = PNG::pie_chart(10, 0.25, PNG::Color::Black, PNG::Color::Green)
|
498
|
+
assert_equal expected, actual.to_s
|
499
|
+
end
|
500
|
+
|
501
|
+
def util_angle(expect, x, y)
|
502
|
+
actual = PNG.angle(x, y)
|
503
|
+
case expect
|
504
|
+
when Integer then
|
505
|
+
assert_equal(expect, actual,
|
506
|
+
"[#{x}, #{y}] should be == #{expect}, was #{actual}")
|
507
|
+
else
|
508
|
+
assert_in_delta(expect, actual, 0.5)
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def test_math_is_hard_lets_go_shopping
|
513
|
+
util_angle 0, 0, 0
|
514
|
+
(25..500).step(25) do |n|
|
515
|
+
util_angle 0, 0, n
|
516
|
+
util_angle 90, n, 0
|
517
|
+
util_angle 180, 0, -n
|
518
|
+
util_angle 270, -n, 0
|
519
|
+
end
|
520
|
+
|
521
|
+
util_angle 359.5, -1, 250
|
522
|
+
util_angle 0.0, 0, 250
|
523
|
+
util_angle 0.5, 1, 250
|
524
|
+
|
525
|
+
util_angle 89.5, 250, 1
|
526
|
+
util_angle 90.0, 250, 0
|
527
|
+
util_angle 90.5, 250, -1
|
528
|
+
end
|
529
|
+
end
|