Harold Hausman:
#It only supports 24bit bmp files, and it even chokes on most of them
(If I was going to solve this quiz, I'd have done a BMP-reader.)
I had trouble installing the RMagick gem, so I went ahead and did a bmp rea=
der.
In retrospect, it probably would have a better use of my time to
figure out what was wrong with the install.
My biggest gotcha in writing the BMP reader was forgetting about the
padding bytes in the raw data. I made the mistake of doing my early
tests with an 80x80 square file, which worked fine without handling
padding. Oh, and unpack is weird. Why can't I extract bits and
nibbles as integers instead of strings?
---
# iview.rb
# Adam Shelly
# for ruby quiz #50
#usage iview file [screenwidth] [threshold] [disable_optimization]
# Displays BMP files as ascii art. resizes to screenwidth (default=3D80)
# threshold controls edge sensitivity. Lower for more detail, raise
for less. (default 500)
# File must be BMP.
# supports bit depths of 2,4,8,24
# maybe 16, but I didn't test any
# converts image to greyscale, resizes to screenwidth, does sobel edge
detection, displays edges as 'x'
# unless disable_optimization is non-nil, re-runs the edge detector
several times to improve pixel density
require 'Matrix'
class BMFile
def initialize filename
@f =3D File.open(filename, "rb")
end
def read size, spec
@f.sysread(size).unpack(spec)
end
def parse spec
rslt =3D spec.inject({}){|h,v| h[v.first]=3Dread(v.last,
v.last=3D=3D2?'S':'L').first; h}
rslt.each {|tag,v| puts "Read #{tag} =3D #{v}"} if $DEBUG
rslt
end
end
class BmpView
attr_reader :show
FileHeaderSpec =3D %w( type size reserved offset).zip [2,4,4,4]
InfoHeaderSpec =3D %w( size width height planes bpp compression
imgSize xppm yppm colorsUsed clrimp).zip [4,4,4,2,2,4,4,4,4,4,4]
def initialize filename, screenwidth =3D 80
@file =3D BMFile.new filename
file_h =3D @file.parse FileHeaderSpec
info_h =3D @file.parse InfoHeaderSpec
throw "#{filename} is Not a BMP file: #{file_h['type']}" if
file_h['type'] !=3D 19778 #=3D=3D'BM'
@width,@height =3D info_h['width'], info_h['height']
@bpp =3D info_h['bpp']
@size =3D (@width*@height*@bpp/8.0).ceil
@image =3D resize( load_data , screenwidth)
@show =3D edgefind @image
end
def greyscale a
(a[0]+a[1]+a[2])/3
end
def load_ctab #load color table, convert to greyscale right away.
@ctable =3D (0...2**@bpp).inject([]){|a,v|
a<<greyscale(@file.read(4,"C4"))}
end
def calc_padding
databits =3D @width*@bpp
paddingbits =3D (32-(databits%32)) %32
width, sparebytes =3D (databits+paddingbits)/8,paddingbits/8
end
def load_data
puts "loading data..."
pattern =3D {1=3D>"B8",4=3D>"H2",8=3D>"C1" ,16=3D>"L1",24=3D>"C3"}
throw "#{@file} is not a Valid BMP file" if !pattern[@bpp]
rowdata =3D[]
width,sparebytes =3D calc_padding
if @bpp < 24
load_ctab
@height.times do
line =3D @file.read(width, pattern[@bpp]*width)
line =3D line.map{|s| s.split(//)}.flatten if @bpp < 8
#separate all the values
sparebytes.times { line.pop } =20
#reject the padding
rowdata << line.map {|s| @ctable[s.to_i]}
end
else
@height.times do
rowdata << (0...@width).inject([]){|a,v|
a<< greyscale(@file.read(3, pattern[@bpp]))
}
sparebytes.times {@file.read(1,"C")} #slurp padding
end
end
Matrix.rows(rowdata.reverse,false)
end
def resize data, screenwidth
factor =3D (@width / screenwidth.to_f).ceil
@width /=3D factor; @height /=3D factor
newdata =3D Array.new(@height){ Array.new(@width) }
puts "Resizing by #{factor} to #{@width}x#{@height}... "
@height.times { |y|
@width.times { |x| sum =3D 0;
grid =3D data.minor(y*factor,factor,x*factor,factor)
grid.to_a.flatten.each{|e|sum+=3De} #take average over squa=
re
newdata[y][x]=3Dsum/factor
}
}
Matrix.rows(newdata,false)
end
def edgefind data
puts "Finding Edges..."
@output =3D ""
gx =3DMatrix.rows [[-1,0,1],[-2,0,2],[-1,0,1]] #sobel convolution =
kernels
gy =3D Matrix.rows [[1,2,1],[0,0,0],[-1,-2,-1]]
1.upto(@height-2) {|y|
1.upto(@width-2) {|x|
sumX,sumY =3D 0,0
v =3D data.minor((y-1)..(y+1),(x-1)..(x+1)).row_vectors
3.times do |i|
gx.row(i).each2(v
) {|a,b| sumX +=3D a*b}
gy.row(i).each2(v) {|a,b| sumY +=3D a*b}
end
@output +=3D ((sumX.abs+sumY.abs) > $threshold) ? 'x' : ' '
}
@output+=3D"\n"
}
@output
end
def optimize
8.times do #if it doesn't get better in 8 tries, give up...
density =3D @show.count('x') / (@width*@height).to_f
break if (0.12 .. 0.33) =3D=3D=3D density #rough heuristic
puts show,density,$threshold if $DEBUG
@show =3D edgefind @image
$threshold *=3D density / 0.2
end
end
end
$threshold =3D (ARGV[2]|| 500).to_i
b =3D BmpView.new(ARGV[0]||"ducky.bmp", ARGV[1]||80)
b.optimize unless ARGV[3]
puts b.show
---
$ ruby iview.rb ducky.bmp
loading data...
Resizing by 7 to 71x78...
Finding Edges...
xxxxxxxxxxxxxxxxx
xxxxxx xxxxx xxxxxx
xxxxx xx xxxxx
xxxx xxxxx
xxxx xxx
xxx xxxx
xxxx xx xxx
xxx x xxxx
xxx xxx
xxx xxx
xxx xxx
xxx xxx
xxxx xxx
xxx xxxx xxx
xxxxxxxxx xxx
xxxxxxxxxx xxx
xxxxxxx xxx xxx xxx
xxx xxxxxxx xxxxx xxx
xxx xxxxxx xxxxxxx xxx
xxx xxxxx xxxxxxxxx xxx
xxx xxxxx x xx xxx xxx
xx xxxxx xxxxxxxxx xxx
xxx xx xxxxxxxx xxx
xxx xxxxxxxxxx xxxxxxxx xxx
xxxxxxxxxxxxxxxxx xxxxxxx xxx
xxxxxxxxxxxxxxxxxxxxxxx xxx xxxxx
xxxxx xxxxxxxx xxxx xxxxxxxx
xxxx xxxxxx xxx xxxxxx xxxx
xxx xx xxxxx xxxx xxxxxx xxx
xxxx xxx xxx xxxx xxxxxxxx x xxx
xxx xxxxx xxxx xxxxxxxxxxxxxxxxxxxx xxxxx
xxx xxxx xxxx xxxxxxxxxxxxxxxxx x xxx
xxx x xxxx xxxxxxxxxxxxxxx xxxx
xxxxxx xxx xxxxxxxxx x xx
xxxxxxxxx xxxx xxx xxx xx
xxxxxxxxxxxxxxxxxx xxx xxxxxx x
xxxx xxxxxxxxxxxx xxx xxxxxx x
xxxx xxxxxx x x xx xxxxxxx x
xxxx xxxxxx xxxxxxxxxx x
xxxx xxxxx xxxxxxxxxx x
xxxx xxxxxx xxxxxxxxxx x
xxxx xxxxxx xxxxxxxxxxxxxxxx x
xxx xxxxxxx xxxxxxxxxxxxxxx x
xxx xxxxxxxxxxxxxxxxxxxx x x xx
xxx xxxxxxxxxxxxxxxxxx xx x xx
xxx xxxxxxxxxxxxxxxxx x x xxx
xxx xxxxxxxxxxxxxx x xxx
xx xxxx x xxx x xx xxxx
xx xx x xxx
xx xxxx
x x xxx
x x xxxx
x xxxx
x xxx
x xxxx
x xxxx
x xxxx
x xxxx
xx xxxxx
xx x xxxxx
xx xxxxx
xxx xxxxx
xxx xxxxx
xxxx xxxxx
xxx xxxxx
xxxx xxxxxxx
xxxx xxxxxx
xxxxx xxxxxx
xxxxx xxxxxxx
xxxxxx xxxxxxx
xxxxxxx xxxxxxxx
xxxxxxxx xxxxxxxx
xxxxxxxxx xxxxxxxx
xxxxxxxxxxxx xx xxxxxxxxx
xxxxxxxxxxx xxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxx
$ ruby iview.rb toumb.bmp
loading data...
Resizing by 7 to 77x108...
Finding Edges...
Finding Edges...
Finding Edges...
xx
xxx
xxx xx xx
xx xxx
xxxx
x x xx xx
xxxx xxxxx
xxxx xx xx
xxxx xxxx xxxxx
xxxxxxx xxxx x xxxx
xx x xx xxxxxxxx xxxx
x x xxxxxxxxx xx xx
x xx xxxxxxxxxx xxxxx
xx xx xx xxxxxxx xxxx
xx xx xx xxxxxxx x xx x
xxx xxxxxx xx xxx xxx xx x
xxx xxxxxxx xxxxxxxxxxxx xxxx xx xx
xxx xx xxxx xxxxxxxxxxxxxx xxxxxx xxxxx
xxx xx xxx xxxxxxx xxxxx xxxxxxxxx xxxx
xxx xx xxx xxxxx x xxxx xxx xxxx x xx
xx xx xxx xxxxxxxxx xxxxxx xxx xxxx
xx xx xxx xxxx xxx xxxxxx xxx xxxx
xx xxx xxx xxx xx xxxxx xxx xxxx
xx xxx xxx xxx xx xx xxxx xxxx
xxx xxxxxxx xxxxxxx xxxxx xxxx
x x xxxxxx xxxxxxx x xxx xxxx
xxx xxxxxx xxxxxxx x xxxx xxx
x xxxxxxxx xxxx x xxxx x x
x xx xxxxxxxx xxxx x xxxxxx xxxx
x xxxxxxxxxx xxx x x xxxxx xxxx
x xxxxxxxxxx xx x x xxxxx xxxx
xx x xxxxxxxxxxxx xxx x xx xxxxx xxxx
xxxxxx xxxx xxxxxx xxx x xxxx xx x x
xxxxxxxxxx xxxxxxxx xxxxx xxxx xx xxx
xxxx xxxxxx xxxxxx xxxx xxx xx xxx xxxx
xxxxx xxxxxx xxxxxx xxx xxxxxxxx xxx xxxx
xxx xxxxx xxxxxx xxxxxxxxxxxx xxxx xxxx
xxxxx x xx xxxxxx xxxxxxx xx x x x xx x x
xxxxxx xx xxxxxxxxx x xxxx x x xxxx xx x
xxxxxxxxx xx xxxxxxx xxx x xxxx x x
xxxxxx xxx xxxxxxx xxxx xxxxxxxxxx x x
xx xx xxx xxxxxxx xxxxxxxxxxxxxxxxx xxx
xxxxx xx xxxxxxx x xxx xxxxxx xxxx x x
x xxx x xxxxxxxxxx xxxxxxxxx x x
x xxx xxxxxxxx xxxxxxx xxxx xxxx x xx
x xxx xxxxxxx xxxxxx xxxxxxxxxx xxxxxxx
xxx xxx x xxxxxx xxxxxxxxxxxx x x xxx
xxx xxx x xxxxxxxxx xx xxxxxx
xxx xxx xxxxxxxx xxx xxxx
xx xx xxx xx x xx
xxx xxxxxx xxx xxx xx
xxxxxxxxxxxx x x xxx xx xx
xxxxxxxxxxx xx x xxxx xx x xx
xxxxxxx xx xxxxx x x xx
x xxxx xxx xxxxxx x xx
x xx x xxxxx xxx xx
x xx xxx xxx xx xx x xx
xxxxxx xx xxx xxxxx xx x xx
x xxxxxxxxx xx xxx xxx x xxxxx xx
xxxxxxxxxx x xx xxxxxx xxxx xx
xxxxx xxx xx xxxxx xxx x xx
xxxx xxx x xxx x xxx x xx
xxxxx xxx x xxx x xxx x x
xxxx xxx x xxx x xxxx x x
xxxxx x xxx xxx xxxxx x xxx
xxxxxx xxxxx xxx xxxxxx x x xx
xxx xx xxxxx xxx xxxxxxx x xxxx
xx xxx xxxxx xxx xxxxxx x xxxxx
xxx x xxxxx xxx xxxxx x xxxxx
xxxx x xxxxx xxx xxxxx xxx xxxx
xxxx xx xxxxxx xxx xxxx x xxxx
xxx x xxxxx xxx xxxx x xxxxxxx
xxx x xxxxx xxx xxxx x xxxxxx
xxx xxxx xxxx xxxxx xx xxxxxxxx
xx xxxxxxxx xxxxx xxx x xxx xx
xxx xx xxxxxx xxxxx xxxx xxx xx
xxx x xxxxx xxxxx xxxxx xxxxxx
xx x xx xx xxxxx xxxxx xxxxxx
xxx x xxxxx xxxxx xxxxxx xxxxxx
xx xxxx xxxx xxxxxxxxx xxxxxx
xxx xxxxx xxxxxxxxx xxxxx
xxx xxxxx xx xxxxxx xxxxx
xxx xxxxxx x xxx xxx xxxxx
xxx xxxxx xx xxxxxxx xxxxx
x xx xxxxxx xxx xxxxx xx xxxx
xxx xxxxx xxxxxxx xxxx xx
xxx xxxxxx xx xxxxx x xxxx
xx xxxxxx xxx xxxx xxxxxx
xxx xxxxxxxxx xxxxxxx xxxxxx
xxx xxxxxxxxxxxxx xxxxxxx xx xxx
xxxxxxxxxx xxxxx xxxx xx xx x x
xxxxxxxx xxxxxx xxxxxx x xxxx x
xxx xx xxx xx xxxx xxxxxx
xx xxx xx xxx xxx xxx
xx xxx xx x x xxx
xxxxxx xxx xx xx xx
xxxxxxx xxxxx x xxx
xxxxxx xxxxx xx
x xxxxx xxx xx xx
xx xxxxx xxxxxxxxxx
x xxx xxx xx x
x xxx x xxxxx x
x x xxxx xxx xxxxxxx
xxxxxxxx xxxx xxxxxxx
xxxxx xx xx xxx xxxx
xxxxx x xxxxxxx
-Adam