When I wrote this, I only focussed on compression speed so sometimes it doesn't compress very well.
How does it work?
It uses RLE compression with a fixed 5 pixel pattern ABCDE that can be repeated up to NNN times. Result is sent in a single byte ABCDENNN so NNN can't be greater than 7 (111).
Each line must end with 0x80.
If only white points remain on a line, I don't store them: they're replaced by 0x80.
Finally, if there are more than 70 consecutive points of the same color on a line, I do like that:
Some speed results (on a 600 Mhz Crusoe with 192 MB Ram) :
courier12.gif : 892 bytes (done in 0.23 seconds) times12i.gif : 890 bytes (done in 0.21 seconds) castle.gif : 15288 bytes (done in 1.85 seconds) ouster.gif : 2612 bytes (done in 0.30 seconds) cat.gif : this one fails actually :( oldeng16.gif : 1976 bytes (done in 0.30 seconds)
The script (updated to work without TkImg) :
package require Tk wm withdraw . # You only need to run this proc to test : proc Go { giffile } { set tps [time "gif2rle $giffile"] rle2gif [file rootname $giffile].rle set match [photo'eq rle gif] set tps [expr round([lindex $tps 0] / 10000.0) / 100.0] set rlesz [file size [file rootname $giffile].rle] if { $match } { puts "Done with succes. Results for $giffile :" puts "Start size : [file size $giffile] bytes" puts "Compressed size : $rlesz bytes (done in $tps seconds)" } else { puts "Error : " gif write [file rootname $giffile]-rle.gif puts "$giffile does not match [file rootname $giffile]-rle.gif" } } proc photo'eq {im1 im2} { set h [image height $im1] if {[image height $im2] != $h} {return 0} set w [image width $im1] if {[image width $im2] != $w} {return 0} for {set y 0} {$y<$h} {incr y} { for {set x 0} {$x<$w} {incr x} { if {[$im1 get $x $y] ne [$im2 get $x $y]} {return 0} } } return 1 } proc rle2gif { fichier } { set fin [open $fichier r] fconfigure $fin -encoding binary -translation binary set data [read $fin] close $fin image create photo gif gif put white -to 0 0 $::largeur $::hauteur set ln 0 foreach ligne [split $data \x80] { set segblanc 0 set segnoir 0 set lg 0 foreach octet [split $ligne {}] { if { $segblanc } { incr lg [expr [scan $octet %c] * 5] set segblanc 0 } elseif { $segnoir } { set qte [expr [scan $octet %c] * 5] set ori $lg incr lg $qte gif put black -to $ori $ln $lg $ln set segnoir 0 } elseif {[string match \x10 $octet]} { set segblanc 1 } elseif {[string match \x20 $octet]} { set segnoir 1 } else { foreach p [pixel $octet] { if {$p} { gif put black -to $lg $ln } incr lg } } } incr ln } } proc gif2rle { fichier } { image create photo rle -file $fichier set data [ascii rle] set RES "" foreach ligne $data { append RES [compress $ligne] } set fout [open [file rootname $fichier].rle w] fconfigure $fout -encoding binary -translation binary puts -nonewline $fout $RES close $fout } proc compress { ligne } { if {[string first 1 $ligne] == -1} { return \x80 } else { set PAT "" set occ 1 set RES "" while {[string length $ligne]} { if {[string first 1 $ligne] == -1} { set ligne "" } elseif {[string first 1 $ligne] > 70} { if {[string length $PAT]} { append RES [octet $PAT $occ] } set PAT "" set occ 1 append RES \x10 set ind [expr [string first 1 $ligne]-([string first 1 $ligne]%5)] if { $ind == 640 } { set ind 635 } elseif { $ind > 1275 } { set ind 1275 } set ligne [string range $ligne $ind end] eval append RES \\x[format %x [expr int($ind / 5)]] } elseif {[string first 0 $ligne] > 70} { if {[string length $PAT]} { append RES [octet $PAT $occ] } set PAT "" set occ 1 append RES \x20 set ind [expr [string first 0 $ligne]-([string first 0 $ligne]%5)] if { $ind == 640 } { set ind 639 } elseif { $ind > 1275 } { set ind 1275 } set ligne [string range $ligne $ind end] eval append RES \\x[format %x [expr int($ind / 5)]] } else { set pat [string range $ligne 0 4] set ligne [string range $ligne 5 end] if {[string match $pat $PAT] && $occ < 7} { incr occ } else { if {[string length $PAT]} { append RES [octet $PAT $occ] } set PAT $pat set occ 1 } } } if {[string length $PAT]} { append RES [octet $PAT $occ] } if {[string length $RES]} { return ${RES}\x80 } } } proc ascii { image } { set res "" set ::largeur [image width $image] if {[expr $::largeur % 5]} { set fl "[string repeat 0 [expr 5 - ( $::largeur % 5)]] " } else { set fl " " } set ::hauteur [image height $image] foreach ligne [$image data] { foreach p $ligne { if { "$p" eq "#000000" } { append res 1 } else { append res 0 } } append res $fl } return $res } proc octet { pat occ } { switch $occ \ 1 "set N 001" 2 "set N 010" 3 "set N 011" 4 "set N 100" \ 5 "set N 101" 6 "set N 110" 7 "set N 111" return [binary format B8 ${pat}${N}] } proc pixel { octet } { binary scan $octet B8 res set pat [string range $res 0 4] set occ [string range $res 5 7] switch $occ \ 001 "set N 1" 010 "set N 2" 011 "set N 3" 100 "set N 4" \ 101 "set N 5" 110 "set N 6" 111 "set N 7" return [split [string repeat $pat $N] {}] }