Skip to content

Instantly share code, notes, and snippets.

@tkojitu
Last active May 30, 2017 06:05
Show Gist options
  • Save tkojitu/f8a58593d0e6f2a66f55aad2baafaade to your computer and use it in GitHub Desktop.
Save tkojitu/f8a58593d0e6f2a66f55aad2baafaade to your computer and use it in GitHub Desktop.
require 'stringio'
module Bmp
class BmpObject
def initialize(file_header, info_header, image_data)
@file_header = file_header
@info_header = info_header
@image_data = image_data
end
attr_reader :file_header, :info_header
attr_accessor :image_data
def to_s
StringIO.open do |io|
io.puts(@file_header)
io.puts(@info_header)
return io.string
end
end
end
class FileHeader
def initialize(type, size, reserved1, reserved2, offbits)
@type = type
@size = size
@reserved1 = reserved1
@reserved2 = reserved2
@offbits = offbits
end
attr_reader :type, :size, :reserved1, :reserved2, :offbits
def to_s
StringIO.open do |io|
io.puts("=== FileHeader ===")
io.printf("type: %s\n", type)
io.printf("size: %d\n", size)
io.printf("reserved1: %d\n", reserved1)
io.printf("reserved2: %d\n", reserved2)
io.printf("offbits: %d\n", offbits)
return io.string
end
end
end
class InfoHeader
def initialize(size, width, height, planes, bit_count, compression,
size_image, x_pix_per_meter, y_pix_per_meter,
clr_used, cir_important)
@size = size
@width = width
@height = height
@planes = planes
@bit_count = bit_count
@compression = compression
@size_image = size_image
@x_pix_per_meter = x_pix_per_meter
@y_pix_per_meter = y_pix_per_meter
@clr_used = clr_used
@cir_important = cir_important
end
attr_reader :size, :width, :height, :planes, :bit_count, :compression,
:size_image, :x_pix_per_meter, :y_pix_per_meter, :clr_used,
:cir_important
def to_s
StringIO.open do |io|
io.puts("=== InfoHeader ===")
io.printf("size: %d\n", size)
io.printf("width: %d\n", width)
io.printf("height: %d\n", height)
io.printf("planes: %d\n", planes)
io.printf("bit_count: %d\n", bit_count)
io.printf("compression: %d\n", compression)
io.printf("size_image: %d\n", size_image)
io.printf("x_pix_per_meter: %d\n", x_pix_per_meter)
io.printf("y_pix_per_meter: %d\n", y_pix_per_meter)
io.printf("clr_used: %d\n", clr_used)
io.printf("cir_important: %d\n", cir_important)
return io.string
end
end
end
class ImageData
def initialize(pixels)
@pixels = pixels
end
attr_reader :pixels
def count_pixels
count = 0
pixels.each do |row|
row.each do |col|
count += 1 if col
end
end
return count
end
def width
return @pixels[0].size
end
def height
return @pixels.size
end
def get(x, y)
return nil if x < 0 || y < 0
return nil if !@pixels[y]
return @pixels[y][x]
end
def set(x, y, pixel)
return if x < 0 || y < 0
@pixels[y][x] = pixel
end
def dup
pixels = Array.new(height){|i| Array.new(width)}
image = ImageData.new(pixels)
height.times do |y|
width.times do |x|
image.set(x, y, get(x, y).dup)
end
end
return image
end
def block(x, y, r)
return nil if x < 0 || x >= width || y < 0 || y >= height
ret = []
((y - r)..(y + r)).each do |yy|
((x - r)..(x + r)).each do |xx|
ret << get(xx, yy)
end
end
ret.delete_if{|e| e.nil?}
return ret
end
end
class Pixel
def initialize(blue, green, red)
@blue = blue
@green = green
@red = red
end
attr_reader :blue, :green, :red
def dup
return Pixel.new(blue, green, red)
end
def set(blue, green, red)
@blue = blue
@green = green
@red = red
end
end
def self.parse(filename)
return Parser.new.parse(filename)
end
class Parser
def parse(filename)
File.open(filename, "rb:ASCII-8BIT:ASCII-8BIT") do |input|
binary = input.read
file_header = parse_file_header(binary)
return nil unless file_header
info_header = parse_info_header(binary)
return nil unless info_header
image_data = parse_image_data(binary, file_header, info_header)
return nil unless image_data
return BmpObject.new(file_header, info_header, image_data)
end
end
def parse_file_header(binary)
return nil if binary.size < file_header_size
type = binary[0..1]
size = binary[2..5].unpack("V")[0]
reserved1 = binary[6..7].unpack("v")[0]
reserved2 = binary[8..9].unpack("v")[0]
offbits = binary[10..13].unpack("V")[0]
return FileHeader.new(type, size, reserved1, reserved2, offbits)
end
def file_header_size
return 14
end
def parse_info_header(binary)
return nil if binary.size < header_size
size = binary[14..17].unpack("V")[0]
width = binary[18..21].unpack("V")[0]
height = binary[22..25].unpack("V")[0]
planes = binary[26..27].unpack("v")[0]
bit_count = binary[28..29].unpack("v")[0]
compression = binary[30..33].unpack("V")[0]
size_image = binary[34..37].unpack("V")[0]
x_pix_per_meter = binary[38..41].unpack("V")[0]
y_pix_per_meter = binary[42..45].unpack("V")[0]
clr_used = binary[46..49].unpack("V")[0]
cir_important = binary[50..53].unpack("V")[0]
return InfoHeader.new(size, width, height, planes, bit_count, compression,
size_image, x_pix_per_meter, y_pix_per_meter,
clr_used, cir_important)
end
def header_size
return 54
end
def parse_image_data(binary, file_header, info_header)
pixels = Array.new(info_header.height){|i| Array.new(info_header.width)}
i = file_header.offbits
info_header.height.times do |h|
info_header.width.times do |w|
break if i >= binary.size
b = binary[i..i].unpack("C")[0]
i += 1
break if i >= binary.size
g = binary[i..i].unpack("C")[0]
i += 1
break if i >= binary.size
r = binary[i..i].unpack("C")[0]
i += 1
pixels[h][w] = Pixel.new(b, g, r)
end
end
return ImageData.new(pixels)
end
end
def self.serialize(bmp, filename)
File.open(filename, "wb:ASCII-8BIT:ASCII-8BIT") do |output|
Serializer.new.serialize(bmp, output)
end
end
class Serializer
def serialize(bmp, output)
serialize_file_header(output, bmp)
serialize_info_header(output, bmp)
serialize_image_data(output, bmp)
end
def serialize_file_header(output, bmp)
output.print(bmp.file_header.type)
output.print([bmp.file_header.size].pack("V"))
output.print([bmp.file_header.reserved1].pack("v"))
output.print([bmp.file_header.reserved2].pack("v"))
output.print([bmp.file_header.offbits].pack("V"))
end
def serialize_info_header(output, bmp)
output.print([bmp.info_header.size].pack("V"))
output.print([bmp.info_header.width].pack("V"))
output.print([bmp.info_header.height].pack("V"))
output.print([bmp.info_header.planes].pack("v"))
output.print([bmp.info_header.bit_count].pack("v"))
output.print([bmp.info_header.compression].pack("V"))
output.print([bmp.info_header.size_image].pack("V"))
output.print([bmp.info_header.x_pix_per_meter].pack("V"))
output.print([bmp.info_header.y_pix_per_meter].pack("V"))
output.print([bmp.info_header.clr_used].pack("V"))
output.print([bmp.info_header.cir_important].pack("V"))
end
def serialize_image_data(output, bmp)
bmp.image_data.pixels.each do |row|
row.each do |col|
output.print([col.blue, col.green, col.red].pack("C*"))
end
end
end
end
class BoxFilter
def filter(image_data)
out = image_data.dup
image_data.height.times do |y|
image_data.width.times do |x|
block = image_data.block(x, y, 2)
out.get(x, y).set(*average(block))
end
end
return out
end
def average(block)
blue = block.inject(0){|sum, p| sum + p.blue} / block.size
green = block.inject(0){|sum, p| sum + p.green} / block.size
red = block.inject(0){|sum, p| sum + p.red} / block.size
return [blue, green, red]
end
end
end
bmp = Bmp.parse(ARGV[0])
puts(bmp)
boxed = Bmp::BoxFilter.new.filter(bmp.image_data)
bmp.image_data = boxed
Bmp.serialize(bmp, ARGV[0] + ".bmp")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment