Lars H, 2008-07-27: Some four years after the binary image compression challenge was issued, I was implementing the HashLife algorithm, which has been described as doing "compression in both space and time". So I though I'd check how good it was at compressing images that are not Life patterns. In the case of the cat.gif image, it turned out to be the best so far (not counting entries that used gzip for post-processing)!
The idea is to first encode the image as a quadtree, then serialise that quadtree in a bit-compact manner. The first part is a minor variation on code in HashLife; I even used the exact same find procedure:
proc find {nw ne sw se} { variable hash set key [list $nw $ne $sw $se] if {![info exists hash($key)]} then { variable heap set hash($key) [llength $heap] switch -- [lindex $heap $nw 0] "cell" { lappend heap [list 2node $nw $ne $sw $se] } "2node" { lappend heap [list 4node $nw $ne $sw $se $hash($key)] } default { lappend heap [list bignode $nw $ne $sw $se $hash($key)] } } return $hash($key) } proc encode_photo {name {side -1} {w 0} {h 0} {x 0} {y 0}} { if {$side<1} then { set w [image width $name] set h [image height $name] set side 1 while {$side<$w || $side<$h} {incr side $side} } if {$side > 1} then { set half [expr {$side/2}] return [find [encode_photo $name $half $w $h $x $y]\ [encode_photo $name $half $w $h [expr {$x+$half}] $y]\ [encode_photo $name $half $w $h $x [expr {$y+$half}]]\ [encode_photo $name $half $w $h [expr {$x+$half}] [expr {$y+$half}]] ] } if {$x>=$w || $y>=$h} then {return 0} if {[lindex [$name get $x $y] 0]} then {return 0} else {return 1} }
The resulting quadtree, whose root is returned by this encode_photo, has the following properties:
Hence the tree can be encoded as follows:
Bit-fiddling is generally awkward, so I only coded two procedures to compute how many are needed.
proc estimate_bits {root} { set sum 4 set last 2 set bpn 8 while {2*$last<$root} { set sum [expr {$sum + $bpn*$last}] incr bpn 4 incr last $last } expr {$sum + $bpn*($root-$last)} } proc bytelength {image} { set ::heap {{cell 0} {cell 1}} array unset ::hash set root [encode_photo $image] format {Last node %d; %d bytes} $root [expr {([estimate_bits $root]+7)/8}] }
For an actual image encoding format, this would have to be equipped with some way of determining the non-padded dimensions of the image, but that is negligible.
Some approaches for further improving the compression are:
But I've got other stuff to do…