An almost-pure-ruby Portable Network Graphics (PNG) writer.
www.libpng.org/pub/png/spec/1.2/
PNG supports: + 8 bit truecolor PNGs
PNG does not support: + any other color depth + extra data chunks + filters
require 'png' canvas = PNG::Canvas.new 200, 200 canvas[100, 100] = PNG::Color::Black canvas.line 50, 50, 100, 50, PNG::Color::Blue png = PNG.new canvas png.save 'blah.png'
+ Get everything orinted entirely on [x,y,h,w] with x,y origin being
bottom left.
# File lib/png/pie.rb, line 9
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
# File lib/png/reader.rb, line 46
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
Creates a PNG chunk of type type that contains data.
# File lib/png.rb, line 163
163: def self.chunk(type, data="")
164: [data.size, type, data, (type + data).png_crc].pack("Na*a*N")
165: end
# File lib/png/reader.rb, line 12
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
# File lib/png/reader.rb, line 7
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
Creates a new PNG object using canvas
# File lib/png.rb, line 170
170: def initialize(canvas)
171: @height = canvas.height
172: @width = canvas.width
173: @bits = 8
174: @data = canvas.data
175: end
# File lib/png/reader.rb, line 132
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
Makes a pie chart you can pass to PNG.new:
png = PNG.new pie_chart(250, 0.30) png.save "pie.png" system 'open pie.png'
# File lib/png/pie.rb, line 22
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
# File lib/png/reader.rb, line 66
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
# File lib/png/reader.rb, line 51
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
# File lib/png/reader.rb, line 34
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
# File lib/png.rb, line 155
155: def png_join
156: @data.map { |row| "\00"" + row.map { |p| p.values }.join }.join
157: end
Writes the PNG to path.
# File lib/png.rb, line 180
180: def save(path)
181: File.open path, 'wb' do |f|
182: f.write to_blob
183: end
184: end
Raw PNG data
# File lib/png.rb, line 189
189: def to_blob
190: blob = []
191:
192: header = [@width, @height, @bits, RGBA, NONE, NONE, NONE]
193:
194: blob << SIGNATURE
195: blob << PNG.chunk('IHDR', header.pack("N2C5"))
196: blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(self.png_join))
197: blob << PNG.chunk('IEND', '')
198: blob.join
199: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.