Class: PNG
- Inherits:
-
Object
- Object
- PNG
- Defined in:
- lib/png.rb,
lib/png/pie.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'
Defined Under Namespace
Constant Summary collapse
- VERSION =
'1.1.0'
- SIGNATURE =
[137, 80, 78, 71, 13, 10, 26, 10].pack("C*")
- 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) ⇒ 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(png) ⇒ Object
- .read_IDAT(data, canvas) ⇒ Object
- .read_IHDR(data) ⇒ 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
237 238 239 240 241 242 |
# File 'lib/png.rb', line 237 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
142 143 144 145 |
# File 'lib/png.rb', line 142 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
.
120 121 122 |
# File 'lib/png.rb', line 120 def self.chunk(type, data="") [data.size, type, data, (type + data).png_crc].pack("Na*a*N") end |
.load(png) ⇒ Object
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/png.rb', line 124 def self.load(png) png = png.dup signature = png.slice! 0, 8 raise ArgumentError, 'Invalid PNG signature' unless signature == SIGNATURE type, data = read_chunk png raise ArgumentError, 'Invalid PNG, no IHDR chunk' unless type == 'IHDR' canvas = read_IHDR data type, data = read_chunk png read_IDAT data, canvas type, data = read_chunk png raise 'oh no! IEND not next? crashing and burning!' unless type == 'IEND' new canvas end |
.paeth(a, b, c) ⇒ Object
left, above, upper left
147 148 149 150 151 152 153 154 155 156 |
# File 'lib/png.rb', line 147 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(png) ⇒ Object
158 159 160 161 162 163 164 165 |
# File 'lib/png.rb', line 158 def self.read_chunk(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 return type, data end |
.read_IDAT(data, canvas) ⇒ Object
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/png.rb', line 167 def self.read_IDAT(data, canvas) data = Zlib::Inflate.inflate(data).unpack 'C*' scanline_length = 4 * canvas.width + 1 # for filter row = 0 until data.empty? do row_data = data.slice! 0, scanline_length filter = row_data.shift case filter when 0 then # None when 1 then # Sub row_data.each_with_index do |byte, index| left = index < 4 ? 0 : row_data[index - 4] row_data[index] = (byte + left) % 256 #p [byte, left, row_data[index]] end when 2 then # Up row_data.each_with_index do |byte, index| col = index / 4 upper = row == 0 ? 0 : canvas[col, row - 1].values[index % 4] row_data[index] = (upper + byte) % 256 end when 3 then # Average row_data.each_with_index do |byte, index| col = index / 4 upper = row == 0 ? 0 : canvas[col, row - 1].values[index % 4] left = index < 4 ? 0 : row_data[index - 4] row_data[index] = (byte + ((left + upper)/2).floor) % 256 end when 4 then # Paeth left = upper = upper_left = nil row_data.each_with_index do |byte, index| col = index / 4 left = index < 4 ? 0 : row_data[index - 4] if row == 0 then upper = upper_left = 0 else upper = canvas[col, row - 1].values[index % 4] upper_left = col == 0 ? 0 : canvas[col - 1, row - 1].values[index % 4] end paeth = paeth left, upper, upper_left row_data[index] = (byte + paeth) % 256 #p [byte, paeth, row_data[index]] end else raise ArgumentError, "Invalid filter algorithm #{filter}" end col = 0 row_data.each_slice 4 do |slice| canvas[col, row] = PNG::Color.new(*slice) col += 1 end row += 1 end end |
Instance Method Details
#save(path) ⇒ Object
Writes the PNG to path
.
247 248 249 |
# File 'lib/png.rb', line 247 def save(path) File.open path, 'wb' do |f| f.write to_blob end end |
#to_blob ⇒ Object
Raw PNG data
254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/png.rb', line 254 def to_blob blob = [] blob << SIGNATURE blob << PNG.chunk('IHDR', [@width, @height, @bits, 6, 0, 0, 0 ].pack("N2C5")) # 0 == filter type code "none" data = self.png_join blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(data)) blob << PNG.chunk('IEND', '') blob.join end |