Keith Vetter 2005-09-22: For the same project which led to WAV Dump, I also had to delve into the file format of BMP files. It can be run either as a wish script or tclsh script. It displays the image header information, the palette (if there is one) and the first row of the image (which is actually the bottom row since the image is stored inverted).
##+########################################################################## # # bmpDump.tsh -- Dumps out metadata about .BMP files # by Keith Vetter, September 2005 # ############################################################################# set S(rows) 1 ;# Count of image rows to show proc DoBMP {iname} { set ::S(ch) [open $iname r] fconfigure $::S(ch) -translation binary FileHeader $iname ;# Read file header ImageHeader ;# Read image header DumpImageHeader $iname ;# Dump image header info ReadPixels ;# Dump pixel info Show "" } proc ReadInt {{size 32}} { ;# Reads raw integer from file array set BSCAN {32 i 16 s 8 c} set data [read $::S(ch) [expr {$size / 8}]] binary scan $data $BSCAN($size) val return $val } proc ReadRGBX {} { set data [read $::S(ch) 4] binary scan $data cccc b g r x return [format "%02X%02X%02X" [expr {$r & 0xFF}] [expr {$g & 0xFF}] [expr {$b & 0xFF}]] } proc FileHeader {fname} { set data [read $::S(ch) 2] if {$data ne "BM"} { ERROR "Bad file header: signature" } set len [ReadInt] set len2 [file size $fname] if {$len != $len2} { ERROR "Bad file header: file length"} set len [ReadInt] if {$len != 0} { ERROR "Bad file header: reserved fields"} set ::S(offBits) [ReadInt] } proc ImageHeader {} { global I array unset I set size [ReadInt] if {$size == 12} { ERROR "Cannot handle OS/2 bmp files" } set I(Width) [ReadInt] set I(Height) [ReadInt] set I(Planes) [ReadInt 16] set I(BitCount) [ReadInt 16] set I(Compression) [ReadInt] set I(SizeImage) [ReadInt] set I(xPelsPerMeter) [ReadInt] set I(yPelsPerMeter) [ReadInt] set I(ClrUsed) [ReadInt] set I(ClrImportant) [ReadInt] if {$I(Compression)} { ERROR "Cannot handle compressed images"} if {$I(BitCount) <= 8} ReadPalette } proc ReadPalette {} { set cnt [expr {int(pow(2,$::I(BitCount)))}] for {set i 0} {$i < $cnt} {incr i} { set ii [format "%d" $i] set ::I(palette,$ii) [ReadRGBX] } } proc DumpImageHeader {iname} { array set ZIP {0 none 1 rle8 2 rle4 3 bitfields} Show [file tail $iname] Show [string repeat "=" [string length [file tail $iname]]] Show [format " %-17s: %d" "Width" $::I(Width)] Show [format " %-17s: %d" "Height" $::I(Height)] Show [format " %-17s: %d" "Planes" $::I(Planes)] Show [format " %-17s: %d" "Bits/pixel" $::I(BitCount)] Show [format " %-17s: %s" "Compression" $ZIP($::I(Compression))] Show [format " %-17s: %d" "Image Size" $::I(SizeImage)] Show [format " %-17s: %dx%d" "Pixels/meter" $::I(xPelsPerMeter) $::I(yPelsPerMeter)] Show [format " %-17s: %d" "Colors Used" $::I(ClrUsed)] Show [format " %-17s: %d %s" "Essential Colors" $::I(ClrImportant) \ [expr {$::I(ClrImportant) > 0 ? "" : "(all)"}]] Show [format " %-17s: %s" "Palette" [DumpPalette]] Show [format " %-17s: %s" "Image" ""] } proc DumpPalette {} { if {$::I(BitCount) > 8} { return "none"} if {$::I(BitCount) == 1} { return [format "0: %s 1: %s" $::I(palette,0) $::I(palette,1)] } set result "\n" set cnt [expr {int(pow(2,$::I(BitCount)))}] set cols 6 for {set i 0} {$i < $cnt} {incr i} { append result [format " %3d: %s" $i $::I(palette,$i)] if {($i % $cols) == $cols-1} { append result "\n"} } return $result } proc ReadPixels {} { seek $::S(ch) $::S(offBits) set func "ReadPixels$::I(BitCount)" if {[info commands $func] eq {}} { ERROR "Cannot read pixels for bitCount $::I(BitCount)" 0 return } $func } proc ReadPixels1 {} { set bytes [expr {($::I(Width)+7)/8}] set bpr [expr {(((($::I(Width)+7)/8)+3)/4)*4}] for {set row 0} {$row < $::I(Height)} {incr row} { if {$row >= $::S(rows)} break set data [read $::S(ch) $bpr] binary scan $data c$bytes pixels set tmp {} foreach pixel $pixels { for {set shift 7} {$shift >= 0} {incr shift -1} { lappend tmp [expr {($pixel >> $shift) & 0x01}] } } set pixels [lrange $tmp 0 [expr {$::I(Width)-1}]] ShowRow $row $pixels } } proc ReadPixels4 {} { set bytes [expr {($::I(Width)+1)/2}] set bpr [expr {(((($::I(Width)+1)/2)+3)/4)*4}] for {set row 0} {$row < $::I(Height)} {incr row} { if {$row >= $::S(rows)} break set data [read $::S(ch) $bpr] binary scan $data c$bytes pixels set tmp {} foreach pixel $pixels { lappend tmp [expr {($pixel >> 4) & 0x0F}] lappend tmp [expr {$pixel & 0x0F}] } set pixels [lrange $tmp 0 [expr {$::I(Width)-1}]] ShowRow $row $pixels } } proc ReadPixels8 {} { set bpr [expr {(($::I(Width)+3)/4)*4}] for {set row 0} {$row < $::I(Height)} {incr row} { if {$row >= $::S(rows)} break set data [read $::S(ch) $bpr] binary scan $data c$::I(Width) pixels ShowRow $row $pixels } } proc ShowRow {row pixels} { ShowNNL [format " Row %2d: " [expr {$::I(Height)-$row-1}]] set w [expr {$::I(BitCount) == 8 ? 3 : 2}] foreach pixel $pixels { ShowNNL [format " %${w}d" [expr {$pixel & 0xFF}]] } Show "" } proc ReadPixels24 {} { set bpr [expr {4 * (($::I(Width) * 3 + 3) / 4)}] for {set row 0} {$row < $::I(Height)} {incr row} { if {$row >= $::S(rows)} break set data [read $::S(ch) $bpr] DisplayRow $row $data } } proc DisplayRow {row data} { ShowNNL " Row [expr {$::I(Height)-$row-1}]:" binary scan $data c* bgr set bgr [lrange $bgr 0 [expr {([llength $bgr] / 3)*3-1}]] ; list set last {} set cnt 0 foreach {b g r} $bgr { set pixel [list $r $g $b] if {$pixel eq $last} { if {$cnt == 0} {ShowNNL "*"} incr cnt } else { set rgb [format "%02X%02X%02X" [expr {$r & 0xFF}] [expr {$g & 0xFF}] [expr {$b & 0xFF}]] ShowNNL " $rgb" set last $pixel set cnt 0 } } Show "" } proc Show {line} { ShowNNL "$line\n" } proc ShowNNL {line} { if {[info exists ::tk_version] && [winfo exists .t]} { .t insert end $line .t see end } else { puts -nonewline $line } } proc ERROR {emsg {die 1}} { if {[info exists ::tk_version]} { tk_messageBox -icon error -message $emsg } else { puts stderr $emsg } if {$die} exit } ################################################################ ################################################################ if {[info exists ::tk_version]} { wm title . "BMP Dump" bind all <Key-F2> [list console show] pack [text .t -wrap word] -fill both -expand 1 } if {$argv eq {}} { catch {wm withdraw .} ERROR "usage: bmpdmp <bmp files>" } foreach arg $argv { regsub -all {\\} $arg {/} arg set files [glob -nocomplain $arg] if {$files eq {}} { set files $arg } foreach iname $files { DoBMP $iname } }