Last active
May 30, 2017 06:05
-
-
Save tkojitu/f8a58593d0e6f2a66f55aad2baafaade to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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