Class: PNG
- Inherits:
-
Object
- Object
- PNG
- Defined in:
- lib/png.rb,
lib/png/pie.rb,
lib/png/reader.rb
Overview
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.
Defined Under Namespace
Constant Summary collapse
- VERSION =
'1.2.0'
- SIGNATURE =
[137, 80, 78, 71, 13, 10, 26, 10].pack("C*")
- GRAY =
Color Types:
0
- RGB =
DEPTH = 1,2,4,8,16
2
- INDEXED =
DEPTH = 8,16
3
- GRAYA =
DEPTH = 1,2,4,8
4
- RGBA =
DEPTH = 8,16
6
- NONE =
Filter Types:
0
- SUB =
1
- UP =
2
- AVG =
3
- PAETH =
4
- FULL =
360.0
- HALF =
FULL / 2
Class Method Summary collapse
- .angle(x, y) ⇒ Object
- .check_crc(type, data, crc) ⇒ Object
-
.chunk(type, data = "") ⇒ Object
Creates a PNG chunk of type
type
that containsdata
. - .load(png, metadata_only = false) ⇒ Object
- .load_file(path, metadata_only = false) ⇒ Object
-
.paeth(a, b, c) ⇒ Object
left, above, upper left.
-
.pie_chart(diameter, pct_green, good_color = PNG::Color::Green, bad_color = PNG::Color::Red) ⇒ Object
Makes a pie chart you can pass to PNG.new:.
- .read_chunk(expected_type, png) ⇒ Object
- .read_IDAT(data, bit_depth, color_type, canvas) ⇒ Object
- .read_IHDR(data, metadata_only = false) ⇒ Object
Instance Method Summary collapse
-
#initialize(canvas) ⇒ PNG
constructor
Creates a new PNG object using
canvas
. -
#save(path) ⇒ Object
Writes the PNG to
path
. -
#to_blob ⇒ Object
Raw PNG data.
Constructor Details
#initialize(canvas) ⇒ PNG
Creates a new PNG object using canvas
169 170 171 172 173 174 |
# File 'lib/png.rb', line 169 def initialize(canvas) @height = canvas.height @width = canvas.width @bits = 8 @data = canvas.data end |
Class Method Details
.angle(x, y) ⇒ Object
9 10 11 12 13 |
# File 'lib/png/pie.rb', line 9 def self.angle(x, y) return 0 if x == 0 and y == 0 rad_to_deg = 180.0 / Math::PI (Math.atan2(-y, x) * rad_to_deg + 90) % 360 end |
.check_crc(type, data, crc) ⇒ Object
43 44 45 46 |
# File 'lib/png/reader.rb', line 43 def self.check_crc type, data, crc return true if (type + data).png_crc == crc raise ArgumentError, "Invalid CRC encountered in #{type} chunk" end |
.chunk(type, data = "") ⇒ Object
Creates a PNG chunk of type type
that contains data
.
162 163 164 |
# File 'lib/png.rb', line 162 def self.chunk(type, data="") [data.size, type, data, (type + data).png_crc].pack("Na*a*N") end |
.load(png, metadata_only = false) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/png/reader.rb', line 9 def self.load png, = false png = png.dup signature = png.slice! 0, 8 raise ArgumentError, 'Invalid PNG signature' unless signature == SIGNATURE ihdr = read_chunk 'IHDR', png bit_depth, color_type, width, height = read_IHDR ihdr, return [width, height, bit_depth] if canvas = PNG::Canvas.new width, height type = png.slice(4, 4).unpack('a4').first read_chunk type, png if type == 'iCCP' # Ignore color profile read_IDAT read_chunk('IDAT', png), bit_depth, color_type, canvas read_chunk 'IEND', png canvas end |
.load_file(path, metadata_only = false) ⇒ Object
5 6 7 |
# File 'lib/png/reader.rb', line 5 def self.load_file path, = false self.load File.read(path), end |
.paeth(a, b, c) ⇒ Object
left, above, upper left
129 130 131 132 133 134 135 136 137 138 |
# File 'lib/png/reader.rb', line 129 def self.paeth a, b, c # left, above, upper left p = a + b - c pa = (p - a).abs pb = (p - b).abs pc = (p - c).abs return a if pa <= pb && pa <= pc return b if pb <= pc c end |
.pie_chart(diameter, pct_green, good_color = PNG::Color::Green, bad_color = PNG::Color::Red) ⇒ Object
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'
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/png/pie.rb', line 22 def self.pie_chart(diameter, pct_green, good_color=PNG::Color::Green, bad_color=PNG::Color::Red) diameter += 1 if diameter % 2 == 0 radius = (diameter / 2.0).to_i pct_in_deg = FULL * pct_green rad_to_deg = HALF / Math::PI canvas = PNG::Canvas.new(diameter, diameter) (-radius..radius).each do |x| (-radius..radius).each do |y| magnitude = Math.sqrt(x*x + y*y) if magnitude <= radius then angle = PNG.angle(x, y) color = ((angle <= pct_in_deg) ? good_color : bad_color) rx, ry = x+radius, y+radius canvas[ rx, ry ] = color end end end canvas end |
.read_chunk(expected_type, png) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/png/reader.rb', line 31 def self.read_chunk expected_type, png size, type = png.slice!(0, 8).unpack 'Na4' data, crc = png.slice!(0, size + 4).unpack "a#{size}N" check_crc type, data, crc raise ArgumentError, "Expected #{expected_type} chunk, not #{type}" unless type == expected_type return data end |
.read_IDAT(data, bit_depth, color_type, canvas) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/png/reader.rb', line 63 def self.read_IDAT data, bit_depth, color_type, canvas data = Zlib::Inflate.inflate(data).unpack 'C*' pixel_size = color_type == RGBA ? 4 : 3 height = canvas.height scanline_length = pixel_size * canvas.width + 1 # for filter row = canvas.height - 1 until data.empty? do row_data = data.slice! 0, scanline_length filter = row_data.shift case filter when NONE then when SUB then row_data.each_with_index do |byte, index| left = index < pixel_size ? 0 : row_data[index - pixel_size] row_data[index] = (byte + left) % 256 end when UP then row_data.each_with_index do |byte, index| col = index / pixel_size upper = row == 0 ? 0 : canvas[col, row + 1].values[index % pixel_size] row_data[index] = (upper + byte) % 256 end when AVG then row_data.each_with_index do |byte, index| col = index / pixel_size upper = row == 0 ? 0 : canvas[col, row + 1].values[index % pixel_size] left = index < pixel_size ? 0 : row_data[index - pixel_size] row_data[index] = (byte + ((left + upper)/2).floor) % 256 end when PAETH then left = upper = upper_left = nil row_data.each_with_index do |byte, index| col = index / pixel_size left = index < pixel_size ? 0 : row_data[index - pixel_size] if row == height then upper = upper_left = 0 else upper = canvas[col, row + 1].values[index % pixel_size] upper_left = col == 0 ? 0 : canvas[col - 1, row + 1].values[index % pixel_size] end paeth = paeth left, upper, upper_left row_data[index] = (byte + paeth) % 256 end else raise ArgumentError, "invalid filter algorithm #{filter}" end col = 0 row_data.each_slice pixel_size do |slice| slice << 0xFF if pixel_size == 3 canvas[col, row] = PNG::Color.new(*slice) col += 1 end row -= 1 end end |
.read_IHDR(data, metadata_only = false) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/png/reader.rb', line 48 def self.read_IHDR data, = false width, height, bit_depth, color_type, *rest = data.unpack 'N2C5' unless then raise ArgumentError, "Wrong bit depth: #{bit_depth}" unless bit_depth == 8 raise ArgumentError, "Wrong color type: #{color_type}" unless color_type == RGBA or color_type = RGB raise ArgumentError, "Unsupported options: #{rest.inspect}" unless rest == [0, 0, 0] end return bit_depth, color_type, width, height end |
Instance Method Details
#save(path) ⇒ Object
Writes the PNG to path
.
179 180 181 182 183 |
# File 'lib/png.rb', line 179 def save(path) File.open path, 'wb' do |f| f.write to_blob end end |
#to_blob ⇒ Object
Raw PNG data
188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/png.rb', line 188 def to_blob blob = [] header = [@width, @height, @bits, RGBA, NONE, NONE, NONE] blob << SIGNATURE blob << PNG.chunk('IHDR', header.pack("N2C5")) blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(self.png_join)) blob << PNG.chunk('IEND', '') blob.join end |