Version 1 of Binary image compression challenge - RS's entry

Updated 2004-09-06 09:31:47

if 0 {Richard Suchenwirth 2004-09-06 - Here's my take for the Binary image compression challenge, as proposed by KBK. It assumes that the colors of the binary image are #000000 (black) and #FFFFFF (white), and

  • turns the pixels into a bitstream,
  • converts that to a sequence of positive runlengths
  • encodes those compactly with Elias coding

Results (compressed size in bytes, test runtime on my 200MHz box):

  • courier12.gif - 585 in 5.1 sec
  • times12i.gif - 586 in 4.9 sec
  • castle.gif - 9993 in 345 sec :(
  • ouster.gif - 1613 in 19.6 sec
  • cat.gif - will take a while...

}

 proc Elias'encode img {
    set h [image height $img]
    set w [image width  $img]
    set bits ""
    foreach row [$img data] {
        foreach pixel $row {
             append bits [string equal $pixel #000000]
        }
     }
     set runs [map strlen [split'runs $bits]]
     binary format ssb* $h $w [Elias'gammas $runs]
 }
 proc Elias'decode data {
    binary scan $data ssb* h w ebits
    set data {}
    set bit 1
    set bits ""
    foreach run [Elias'decode'gammas $ebits] {
        append bits [string repeat $bit $run]
        while {[string length $bits]>=$w} {
            lappend data [bits2cols [string range $bits 0 [expr {$w-1}]]]
            set bits [string range $bits $w end]
        }
        set bit [expr {!$bit}]
    }
    set img [image create photo -width $w -height $h]
    $img put $data -to 0 0
    set img
 }
 proc Elias'test img {
    set data [Elias'encode $img]
    set img2 [Elias'decode $data]
    if {[$img data] ne [$img2 data]} {error "result not equal"}
    image delete $img2
    string length $data
 }
 proc bits2cols bits {
    set res {}
    foreach bit [split $bits ""] {
        lappend res [expr {$bit? "#FFFFFF" : "#000000"}]
    }
    set res
 }
 proc map {fn list} {
    set res {}
    foreach e $list {lappend res [$fn $e]}
    set res
 }
 interp alias {} strlen {} string length ;# to make it a one-worder

 proc split'runs bits {
    string map {01 "0 1"} [string map {10 "1 0"} $bits]
 }

#-- See Elias coding for explanations on these functions

 proc Elias'gamma int {
    set bits [int2bits $int]
    return [string repeat 0 [expr {[string length $bits]-1}]]$bits
 }
 proc Elias'gammas ints {
    set res ""
    foreach int $ints {append res [Elias'gamma $int]}
    set res
 }
 proc Elias'decode'gammas bits {
    set res {}
    while {$bits ne ""} {
        regexp ^(0*) $bits -> zeroes
        set length [expr {[string length $zeroes]*2}]
        lappend res [bits2int [string range $bits 0 $length]]
        set bits [string range $bits [incr length] end]
    }
    set res
 }
 proc bits2int bits {
    set res 0
    foreach bit [split $bits ""] {set res [expr {$res*2+$bit}]}
    set res
 }
 proc int2bits int {
    set res ""
    while {$int>0} {
       set res [expr {$int%2}]$res
       set int [expr {$int/2}]
    }
    set res
 }

Arts and crafts of Tcl-Tk programming