class 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
- AVG
- FULL
- GRAY
-
Color
Types: - GRAYA
- HALF
- INDEXED
- NONE
-
Filter Types:
- PAETH
- RGB
- RGBA
- SIGNATURE
- SUB
- UP
- VERSION
Public Class Methods
Source
# File lib/png/pie.rb, line 7 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
Source
# File lib/png/reader.rb, line 44 def self.check_crc type, data, crc return true if (type + data).png_crc == crc raise ArgumentError, "Invalid CRC encountered in #{type} chunk" end
Source
# File lib/png.rb, line 147 def self.chunk type, data = "" [data.size, type, data, (type + data).png_crc].pack("Na*a*N") end
Creates a PNG
chunk of type type
that contains data
.
Source
# File lib/png/reader.rb, line 10 def self.load png, metadata_only = 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, metadata_only return [width, height, bit_depth] if metadata_only 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
Source
# File lib/png/reader.rb, line 5 def self.load_file path, metadata_only = false file = File.open(path, "rb") { |f| f.read } self.load file, metadata_only end
Source
# File lib/png.rb, line 154 def initialize canvas @height = canvas.height @width = canvas.width @bits = 8 @data = canvas.data end
Creates a new PNG
object using canvas
Source
# File lib/png/reader.rb, line 130 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
Source
# File lib/png/pie.rb, line 20 def self.pie_chart(diameter, pct_green, good_color = PNG::Color::Green, bad_color = PNG::Color::Red) diameter += 1 if diameter.even? radius = (diameter / 2.0).to_i pct_in_deg = FULL * pct_green canvas = PNG::Canvas.new(diameter, diameter) (-radius..radius).each do |x| (-radius..radius).each do |y| magnitude = Math.sqrt(x*x + y*y) next if magnitude > radius 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 canvas 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'
Source
# File lib/png/reader.rb, line 64 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
Source
# File lib/png/reader.rb, line 49 def self.read_IHDR data, metadata_only = false width, height, bit_depth, color_type, *rest = data.unpack "N2C5" unless metadata_only 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
Source
# File lib/png/reader.rb, line 32 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 data end
Public Instance Methods
Source
# File lib/png.rb, line 139 def png_join @data.map { |row| "\0".b + row.map(&:values).join }.join end
Source
# File lib/png.rb, line 164 def save path File.open path, "wb" do |f| f.write to_blob end end
Writes the PNG
to path
.
Source
# File lib/png.rb, line 173 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
Raw PNG
data