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
170 171 172 173 174 175 |
# File 'lib/png.rb', line 170 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
46 47 48 49 |
# File 'lib/png/reader.rb', line 46 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
.
163 164 165 |
# File 'lib/png.rb', line 163 def self.chunk(type, data="") [data.size, type, data, (type + data).png_crc].pack("Na*a*N") end |
.load(png, metadata_only = false) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/png/reader.rb', line 12 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
7 8 9 10 |
# File 'lib/png/reader.rb', line 7 def self.load_file path, = false file = File.open(path, 'rb') { |f| f.read } self.load file, end |
.paeth(a, b, c) ⇒ Object
left, above, upper left
132 133 134 135 136 137 138 139 140 141 |
# File 'lib/png/reader.rb', line 132 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
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
34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/png/reader.rb', line 34 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
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 128 129 130 |
# File 'lib/png/reader.rb', line 66 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
51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/png/reader.rb', line 51 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
.
180 181 182 183 184 |
# File 'lib/png.rb', line 180 def save(path) File.open path, 'wb' do |f| f.write to_blob end end |
#to_blob ⇒ Object
Raw PNG data
189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/png.rb', line 189 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 |