Parent

Namespace

Class Index [+]

Quicksearch

PNG

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

Example

  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'

TODO:

+ Get everything orinted entirely on [x,y,h,w] with x,y origin being

  bottom left.

Constants

FULL
HALF
VERSION
SIGNATURE
GRAY

Color Types:

RGB
INDEXED
GRAYA
RGBA
NONE

Filter Types:

SUB
UP
AVG
PAETH

Public Class Methods

angle(x, y) click to toggle source
    # 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
check_crc(type, data, crc) click to toggle source
    # 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
chunk(type, data="") click to toggle source

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
load(png, metadata_only = false) click to toggle source
    # 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
load_file(path, metadata_only = false) click to toggle source
    # 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
new(canvas) click to toggle source

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
paeth(a, b, c) click to toggle source
     # 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
pie_chart(diameter, pct_green, good_color=PNG::Color::Green, bad_color=PNG::Color::Red) click to toggle source

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
read_IDAT(data, bit_depth, color_type, canvas) click to toggle source
     # 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
read_IHDR(data, metadata_only = false) click to toggle source
    # 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
read_chunk(expected_type, png) click to toggle source
    # 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

Public Instance Methods

png_join() click to toggle source
     # File lib/png.rb, line 155
155:     def png_join
156:       @data.map { |row| "\00"" + row.map { |p| p.values }.join }.join
157:     end
save(path) click to toggle source

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
to_blob() click to toggle source

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.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.