module BomRadarPng
BomRadarPng — decode indexed-colour BOM radar PNG frames (stdlib only). Public: decode_indices Tests: test/lib/bom_radar_image_test.rb
Public Instance Methods
Source
# File lib/bom_radar_png.rb, line 80 def apply_filter(filter, row, previous) case filter when 0 row when 1 row.each_with_index.map { |value, index| (value + (index.positive? ? row[index - 1] : 0)) % 256 } when 2 return row unless previous row.each_with_index.map { |value, index| (value + previous[index]) % 256 } else row end end
Source
# File lib/bom_radar_png.rb, line 12 def decode_indices(bytes) data = bytes.to_s.b raise ArgumentError, "not a PNG" unless data.start_with?("\x89PNG\r\n\x1a\n".b) width = nil height = nil bit_depth = nil color_type = nil palette = [] idat = +"".b pos = 8 while pos < data.bytesize length = data[pos, 4].unpack1("N") type = data[pos + 4, 4] chunk = data[pos + 8, length] pos += 12 + length case type when "IHDR" width, height, bit_depth, color_type = chunk.unpack("NNCC") when "PLTE" palette = chunk.bytes.each_slice(3).map { |rgb| rgb } when "IDAT" idat << chunk when "IEND" break end end raise ArgumentError, "unsupported PNG" unless width && height && color_type == 3 inflated = Zlib::Inflate.inflate(idat) grid = unpack_scanlines(inflated, width: width, height: height, bit_depth: bit_depth) { width: width, height: height, grid: grid, palette: palette } end
Source
# File lib/bom_radar_png.rb, line 67 def unpack_row(row_bytes, width:, bit_depth:) if bit_depth == 2 row_bytes.bytes.flat_map { |byte| [byte >> 6, (byte >> 4) & 3, (byte >> 2) & 3, byte & 3] }.first(width) elsif bit_depth == 4 nibbles = row_bytes.bytes.flat_map { |byte| [byte >> 4, byte & 0x0F] } nibbles.first(width) elsif bit_depth == 8 row_bytes.bytes.first(width) else raise ArgumentError, "unsupported bit depth #{bit_depth}" end end
Source
# File lib/bom_radar_png.rb, line 49 def unpack_scanlines(inflated, width:, height:, bit_depth:) bytes_per_pixel = (bit_depth * width / 8.0).ceil stride = 1 + bytes_per_pixel offset = 0 grid = [] height.times do filter = inflated.getbyte(offset) row_bytes = inflated.byteslice(offset + 1, bytes_per_pixel) offset += stride row = unpack_row(row_bytes, width: width, bit_depth: bit_depth) row = apply_filter(filter, row, grid.last) if grid.any? grid << row end grid end