png 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -0,0 +1,73 @@
1
+ # encoding: BINARY
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
@@ -1,4 +1,4 @@
1
- #!/usr/local/bin/ruby -w
1
+ # encoding: BINARY
2
2
 
3
3
  require 'png'
4
4
 
@@ -0,0 +1,142 @@
1
+ # encoding: BINARY
2
+
3
+ require 'png'
4
+ require 'enumerator'
5
+
6
+ class PNG
7
+ def self.load_file path, metadata_only = false
8
+ file = File.open(path, 'rb') { |f| f.read }
9
+ self.load file, metadata_only
10
+ end
11
+
12
+ def self.load png, metadata_only = false
13
+ png = png.dup
14
+ signature = png.slice! 0, 8
15
+ raise ArgumentError, 'Invalid PNG signature' unless signature == SIGNATURE
16
+
17
+ ihdr = read_chunk 'IHDR', png
18
+
19
+ bit_depth, color_type, width, height = read_IHDR ihdr, metadata_only
20
+
21
+ return [width, height, bit_depth] if metadata_only
22
+
23
+ canvas = PNG::Canvas.new width, height
24
+
25
+ type = png.slice(4, 4).unpack('a4').first
26
+ read_chunk type, png if type == 'iCCP' # Ignore color profile
27
+
28
+ read_IDAT read_chunk('IDAT', png), bit_depth, color_type, canvas
29
+ read_chunk 'IEND', png
30
+
31
+ canvas
32
+ end
33
+
34
+ def self.read_chunk expected_type, png
35
+ size, type = png.slice!(0, 8).unpack 'Na4'
36
+ data, crc = png.slice!(0, size + 4).unpack "a#{size}N"
37
+
38
+ check_crc type, data, crc
39
+
40
+ raise ArgumentError, "Expected #{expected_type} chunk, not #{type}" unless
41
+ type == expected_type
42
+
43
+ return data
44
+ end
45
+
46
+ def self.check_crc type, data, crc
47
+ return true if (type + data).png_crc == crc
48
+ raise ArgumentError, "Invalid CRC encountered in #{type} chunk"
49
+ end
50
+
51
+ def self.read_IHDR data, metadata_only = false
52
+ width, height, bit_depth, color_type, *rest = data.unpack 'N2C5'
53
+
54
+ unless metadata_only then
55
+ raise ArgumentError, "Wrong bit depth: #{bit_depth}" unless
56
+ bit_depth == 8
57
+ raise ArgumentError, "Wrong color type: #{color_type}" unless
58
+ color_type == RGBA or color_type = RGB
59
+ raise ArgumentError, "Unsupported options: #{rest.inspect}" unless
60
+ rest == [0, 0, 0]
61
+ end
62
+
63
+ return bit_depth, color_type, width, height
64
+ end
65
+
66
+ def self.read_IDAT data, bit_depth, color_type, canvas
67
+ data = Zlib::Inflate.inflate(data).unpack 'C*'
68
+
69
+ pixel_size = color_type == RGBA ? 4 : 3
70
+
71
+ height = canvas.height
72
+ scanline_length = pixel_size * canvas.width + 1 # for filter
73
+
74
+ row = canvas.height - 1
75
+ until data.empty? do
76
+ row_data = data.slice! 0, scanline_length
77
+
78
+ filter = row_data.shift
79
+ case filter
80
+ when NONE then
81
+ when SUB then
82
+ row_data.each_with_index do |byte, index|
83
+ left = index < pixel_size ? 0 : row_data[index - pixel_size]
84
+ row_data[index] = (byte + left) % 256
85
+ end
86
+ when UP then
87
+ row_data.each_with_index do |byte, index|
88
+ col = index / pixel_size
89
+ upper = row == 0 ? 0 : canvas[col, row + 1].values[index % pixel_size]
90
+ row_data[index] = (upper + byte) % 256
91
+ end
92
+ when AVG then
93
+ row_data.each_with_index do |byte, index|
94
+ col = index / pixel_size
95
+ upper = row == 0 ? 0 : canvas[col, row + 1].values[index % pixel_size]
96
+ left = index < pixel_size ? 0 : row_data[index - pixel_size]
97
+
98
+ row_data[index] = (byte + ((left + upper)/2).floor) % 256
99
+ end
100
+ when PAETH then
101
+ left = upper = upper_left = nil
102
+ row_data.each_with_index do |byte, index|
103
+ col = index / pixel_size
104
+
105
+ left = index < pixel_size ? 0 : row_data[index - pixel_size]
106
+ if row == height then
107
+ upper = upper_left = 0
108
+ else
109
+ upper = canvas[col, row + 1].values[index % pixel_size]
110
+ upper_left = col == 0 ? 0 :
111
+ canvas[col - 1, row + 1].values[index % pixel_size]
112
+ end
113
+
114
+ paeth = paeth left, upper, upper_left
115
+ row_data[index] = (byte + paeth) % 256
116
+ end
117
+ else
118
+ raise ArgumentError, "invalid filter algorithm #{filter}"
119
+ end
120
+
121
+ col = 0
122
+ row_data.each_slice pixel_size do |slice|
123
+ slice << 0xFF if pixel_size == 3
124
+ canvas[col, row] = PNG::Color.new(*slice)
125
+ col += 1
126
+ end
127
+
128
+ row -= 1
129
+ end
130
+ end
131
+
132
+ def self.paeth a, b, c # left, above, upper left
133
+ p = a + b - c
134
+ pa = (p - a).abs
135
+ pb = (p - b).abs
136
+ pc = (p - c).abs
137
+
138
+ return a if pa <= pb && pa <= pc
139
+ return b if pb <= pc
140
+ c
141
+ end
142
+ end
@@ -1,43 +1,37 @@
1
- require 'test/unit'
1
+ dir = File.expand_path "~/.ruby_inline"
2
+ if test ?d, dir then
3
+ require 'fileutils'
4
+ puts "nuking #{dir}"
5
+ # force removal, Windoze is bitching at me, something to hunt later...
6
+ FileUtils.rm_r dir, :force => true
7
+ end
8
+
9
+ require 'minitest/autorun'
2
10
  require 'rubygems'
3
11
  require 'png'
12
+ require 'png/reader'
4
13
  require 'png/pie'
5
14
 
6
- class TestPng < Test::Unit::TestCase
7
-
15
+ class TestPng < MiniTest::Unit::TestCase
8
16
  def setup
9
17
  @canvas = PNG::Canvas.new 5, 10, PNG::Color::White
10
18
  @png = PNG.new @canvas
11
19
 
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
20
  @blob = <<-EOF.unpack('m*').first
19
21
  iVBORw0KGgoAAAANSUhEUgAAAAUAAAAKCAYAAAB8OZQwAAAAD0lEQVR4nGP4
20
22
  jwUwDGVBALuJxzlQugpEAAAAAElFTkSuQmCC
21
23
  EOF
22
24
  end
23
25
 
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
26
  def test_class_chunk
39
27
  chunk = PNG.chunk 'IHDR', [10, 10, 8, 6, 0, 0, 0 ].pack('N2C5')
40
- assert_equal @IHDR_chunk, chunk
28
+
29
+ header_crc = "\2152\317\275"
30
+ header_data = "\000\000\000\n\000\000\000\n\b\006\000\000\000"
31
+ header_length = "\000\000\000\r"
32
+ header_chunk = "#{header_length}IHDR#{header_data}#{header_crc}"
33
+
34
+ assert_equal header_chunk, chunk
41
35
  end
42
36
 
43
37
  def test_class_chunk_empty
@@ -53,17 +47,104 @@ jwUwDGVBALuJxzlQugpEAAAAAElFTkSuQmCC
53
47
  def test_save
54
48
  path = "blah.png"
55
49
  @png.save(path)
56
- assert_equal @blob, File.read(path)
50
+ file = File.open(path, 'rb') { |f| f.read }
51
+ assert_equal @blob, file
57
52
  ensure
58
53
  assert_equal 1, File.unlink(path)
59
54
  end
60
55
 
61
- class TestCanvas < Test::Unit::TestCase
56
+ end
57
+
58
+ class TestCanvas < MiniTest::Unit::TestCase
62
59
 
63
60
  def setup
64
61
  @canvas = PNG::Canvas.new 5, 10, PNG::Color::White
65
62
  end
66
63
 
64
+ def test_composite_default
65
+ canvas1, canvas2 = util_composite_canvases
66
+
67
+ canvas1.composite canvas2, 1, 1
68
+
69
+ expected = " xxxxxxxx
70
+ xxxxxxxx
71
+ xx..xxxx
72
+ ..xxxxxx
73
+ ".gsub(/ /, '')
74
+
75
+ assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
76
+ end
77
+
78
+ def test_composite_underlay
79
+ canvas1, canvas2 = util_composite_canvases
80
+
81
+ canvas1.composite canvas2, 1, 1, :add
82
+
83
+ expected = " xxxxxxxx
84
+ xxxx..xx
85
+ xx00xxxx
86
+ ..xxxxxx
87
+ ".gsub(/ /, '')
88
+
89
+ assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
90
+ end
91
+
92
+ def test_composite_overlay
93
+ canvas1, canvas2 = util_composite_canvases
94
+
95
+ canvas1.composite canvas2, 1, 1, :overlay
96
+
97
+ expected = " xxxxxxxx
98
+ xxxx..xx
99
+ xx..xxxx
100
+ ..xxxxxx
101
+ ".gsub(/ /, '')
102
+
103
+ assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
104
+ end
105
+
106
+ def test_composite_blend
107
+ canvas1, canvas2 = util_composite_canvases
108
+
109
+ canvas1.composite canvas2, 1, 1, :blend
110
+
111
+ expected = " xxxxxxxx
112
+ xxxx..xx
113
+ xx,,xxxx
114
+ ..xxxxxx
115
+ ".gsub(/ /, '')
116
+
117
+ assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
118
+ end
119
+
120
+ def test_composite_bad_style
121
+ canvas1, canvas2 = util_composite_canvases
122
+
123
+ assert_raises RuntimeError do
124
+ canvas1.composite canvas2, 1, 1, :bad
125
+ end
126
+ end
127
+
128
+ def test_extract
129
+ canvas1, _ = util_composite_canvases
130
+
131
+ expected = " xxxxxxxx
132
+ xxxx..xx
133
+ xx00xxxx
134
+ ..xxxxxx
135
+ ".gsub(/ /, '')
136
+
137
+ assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
138
+
139
+ canvas2 = canvas1.extract(1, 1, 2, 2)
140
+
141
+ expected = " xx..
142
+ 00xx
143
+ ".gsub(/ /, '')
144
+
145
+ assert_equal expected, canvas2.to_s.gsub(/ /, 'x')
146
+ end
147
+
67
148
  def test_index
68
149
  assert_equal PNG::Color::White, @canvas[1, 2]
69
150
  assert_same @canvas[1, 2], @canvas.data[1][2]
@@ -162,7 +243,7 @@ class TestCanvas < Test::Unit::TestCase
162
243
  end
163
244
 
164
245
  def test_point
165
- assert_equal PNG::Color.new(0xfe, 0x00, 0xfe, 0xfe),
246
+ assert_equal PNG::Color.new(0xff, 0x7f, 0xff, 0xff),
166
247
  @canvas.point(0, 0, PNG::Color::Magenta)
167
248
  # flunk "this doesn't test ANYTHING"
168
249
  end
@@ -171,15 +252,15 @@ class TestCanvas < Test::Unit::TestCase
171
252
  @canvas.line 0, 9, 4, 0, PNG::Color::Black
172
253
 
173
254
  expected = <<-EOF
174
- ..00000000
175
255
  ,,00000000
176
- 00,,000000
256
+ ,,00000000
257
+ ,,,,000000
177
258
  00..000000
178
- 00++++0000
259
+ 00,,,,0000
179
260
  0000..0000
180
- 0000++++00
261
+ 0000,,,,00
181
262
  000000..00
182
- 000000,,00
263
+ 000000,,,,
183
264
  00000000..
184
265
  EOF
185
266
 
@@ -190,25 +271,21 @@ class TestCanvas < Test::Unit::TestCase
190
271
  @canvas.line 0, 0, 4, 9, PNG::Color::Black
191
272
 
192
273
  expected = <<-EOF
193
- 00000000..
194
274
  00000000,,
195
- 000000,,00
275
+ 00000000,,
276
+ 000000,,,,
196
277
  000000..00
197
- 0000++++00
278
+ 0000,,,,00
198
279
  0000..0000
199
- 00++++0000
280
+ 00,,,,0000
200
281
  00..000000
201
- 00,,000000
282
+ ,,,,000000
202
283
  ..00000000
203
284
  EOF
204
285
 
205
286
  assert_equal expected, @canvas.to_s
206
287
  end
207
288
 
208
- def util_ascii_art(width, height)
209
- (("0" * width * 2) + "\n") * height
210
- end
211
-
212
289
  def test_to_s_normal
213
290
  @canvas = PNG::Canvas.new 5, 10, PNG::Color::White
214
291
  expected = util_ascii_art(5, 10)
@@ -233,31 +310,39 @@ class TestCanvas < Test::Unit::TestCase
233
310
  assert_equal expected, @canvas.to_s
234
311
  end
235
312
 
236
- # def test_class_read_chunk
237
- # type, data = PNG.read_chunk @IHDR_chunk
313
+ def util_composite_canvases
314
+ canvas1 = PNG::Canvas.new 4, 4
315
+ canvas1[0, 0] = PNG::Color::Black
316
+ canvas1[1, 1] = PNG::Color::White
317
+ canvas1[2, 2] = PNG::Color::Black
238
318
 
239
- # assert_equal 'IHDR', type
240
- # assert_equal @IHDR_data, data
241
- # end
319
+ expected = " xxxxxxxx
320
+ xxxx..xx
321
+ xx00xxxx
322
+ ..xxxxxx
323
+ ".gsub(/ /, '')
242
324
 
243
- # def test_class_read_IDAT
244
- # canvas = PNG::Canvas.new 10, 10, PNG::Color::White
325
+ assert_equal expected, canvas1.to_s.gsub(/ /, 'x')
245
326
 
246
- # data = "x\332c\370O$`\030UH_\205\000#\373\216\200"
247
327
 
248
- # PNG.read_IDAT data, canvas
328
+ canvas2 = PNG::Canvas.new 2, 2
329
+ canvas2[0, 0] = PNG::Color::Black
249
330
 
250
- # assert_equal @blob, PNG.new(canvas).to_blob
251
- # end
331
+ expected = " xxxx
332
+ ..xx
333
+ ".gsub(/ /, '')
252
334
 
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
335
+ assert_equal expected, canvas2.to_s.gsub(/ /, 'x')
336
+
337
+ return canvas1, canvas2
338
+ end
339
+
340
+ def util_ascii_art(width, height)
341
+ (("0" * width * 2) + "\n") * height
342
+ end
258
343
  end
259
344
 
260
- class TestPng::TestColor < Test::Unit::TestCase
345
+ class TestPng::TestColor < MiniTest::Unit::TestCase
261
346
  def setup
262
347
  @color = PNG::Color.new 0x01, 0x02, 0x03, 0x04
263
348
  end
@@ -295,10 +380,16 @@ class TestPng::TestColor < Test::Unit::TestCase
295
380
  end
296
381
 
297
382
  def test_blend
298
- c1 = @color
299
- c2 = PNG::Color.new 0xFF, 0xFE, 0xFD, 0xFC
383
+ # c1 = @color
384
+ # c2 = PNG::Color.new 0xFF, 0xFE, 0xFD, 0xFC
385
+
386
+ # assert_equal PNG::Color.new(0xFB, 0xFA, 0xF9, 0xF8), c1.blend(c2)
387
+
388
+ c1 = PNG::Color::White
389
+ c2 = PNG::Color::Black
300
390
 
301
- assert_equal PNG::Color.new(0xfb, 0xfa, 0xf9, 0xf8), c1.blend(c2)
391
+ assert_equal PNG::Color::Gray, c2.blend(c1)
392
+ assert_equal PNG::Color::Gray, c1.blend(c2)
302
393
  end
303
394
 
304
395
  def test_intensity
@@ -313,6 +404,21 @@ class TestPng::TestColor < Test::Unit::TestCase
313
404
  assert_equal "#<PNG::Color Red>", PNG::Color::Red.inspect
314
405
  end
315
406
 
407
+ def test_pipe
408
+ b = PNG::Color::Black
409
+ w = PNG::Color::White
410
+ t = PNG::Color::Background
411
+
412
+ # first non-transparent
413
+ assert_equal b, b | t
414
+ assert_equal b, t | b
415
+
416
+ assert_equal b, b | w
417
+ assert_equal w, w | b
418
+
419
+ assert_equal t, t | t
420
+ end
421
+
316
422
  def test_to_ascii
317
423
  assert_equal '00', PNG::Color::White.to_ascii, "white"
318
424
  assert_equal '++', PNG::Color::Yellow.to_ascii, "yellow"
@@ -325,7 +431,7 @@ class TestPng::TestColor < Test::Unit::TestCase
325
431
  assert_equal '00', PNG::Color.new(255,255,255,255).to_ascii
326
432
  assert_equal '00', PNG::Color.new(255,255,255,192).to_ascii
327
433
  assert_equal '++', PNG::Color.new(255,255,255,191).to_ascii
328
- assert_equal '++', PNG::Color.new(255,255,255,127).to_ascii
434
+ assert_equal ',,', PNG::Color.new(255,255,255,127).to_ascii
329
435
  assert_equal ',,', PNG::Color.new(255,255,255,126).to_ascii
330
436
  assert_equal ',,', PNG::Color.new(255,255,255, 64).to_ascii
331
437
  assert_equal '..', PNG::Color.new(255,255,255, 63).to_ascii
@@ -342,18 +448,22 @@ class TestPng::TestColor < Test::Unit::TestCase
342
448
  assert_equal '#<PNG::Color:0xXXXXXX>', obj.to_s.sub(/0x[0-9a-f]+/, '0xXXXXXX')
343
449
  end
344
450
 
451
+ def test_equals2
452
+ assert_equal PNG::Color.new(255,255,255, 0), PNG::Color.new(255,255,255, 0)
453
+ end
454
+
455
+ def test_hash
456
+ a = PNG::Color.new(255,255,255, 0)
457
+ b = PNG::Color.new(255,255,255, 0)
458
+ assert_equal a.hash, b.hash
459
+ end
460
+
345
461
  # def test_values
346
462
  # raise NotImplementedError, 'Need to write test_values'
347
463
  # end
348
464
  end
349
465
 
350
- end
351
-
352
- class TestPng::TestPie < Test::Unit::TestCase
353
- def setup
354
-
355
- end
356
-
466
+ class TestPng::TestPie < MiniTest::Unit::TestCase
357
467
  def test_pie_chart_odd
358
468
  expected =
359
469
  [" .. ",