XPM Pixmap Formatting in Pure TCL

Exactly what it says on the tin. The procedure below will take a nested list of pixels in hex notation and format the list into a valid XPM pixmap. This pixmap can be saved to disk as a regular text file and displayed using the Img extension's pixmap type. Neither Tk nor Img is required to use the formatter.

Also included is a procedure to convert a Tk photo image to pixmap data. Tk is required to use this procedure, although Img is not as long as you are using a format supported by vanilla Tk. Transparency is supported, so if your source photo has transparency, so will the resulting pixmap.

Return data from both procedures is a string containing the pixmap. This can be saved in a variable and written to disk or loaded into a Tk (Img) image with:

set pixmapname [image create pixmap -data $returndata]

and updated with:

$pixmapname configure -data $newdata

Some sample data has been supplied to demonstrate the formatting needed for the nested list used as input.

##=========================================
## photo2xpm 
##
## Convert Tk photo image to XPM data. 
## Transparency is supported and handled
## automatically.
##
## Uses the image handle as the XPM name.
##
##=========================================
## Arguments:
## tki - Tk photo image handle 
##=========================================

proc photo2xpm {tki} {
        
        if {![string match *$tki* [image names]] || ![string match [image type $tki] "photo"]} {
                error "Valid Tk photo image handle required." ; return 1
        }
        
        set idata {}
        set w [image width $tki] ; set h [image height $tki]
        
        for { set y 0 } { $y < $h } { incr y } {
                set bline {}
                for { set x 0 } { $x < $w} { incr x } {
                        if {[$tki transparency get $x $y]} {
                                lappend bline "#00NONE"
                        } else {
                                lappend bline [format "#%02x%02x%02x" {*}[$tki get $x $y]]
                        }        
                }
                lappend idata $bline
        }                
        
        return [xpm.data $tki $idata]
         
}

##=========================================
## xpm.data 
##
## Format a nested list of pixels into 
## valid XPM data. Data can be loaded into
## a Tk image of type 'pixmap' using the
## Img extension or saved to disk. 
##
## The data supplied to the argument 'pixels'
## should be formated as a list of lists, with
## each sublist representing a row of pixels
## in the image. Rows must be of the same 
## length or the data may be corrupted.  
##
## Colors are specified in 8-bit hex. For
## transparent pixels, the color code 
## "#00NONE" should be used. 
##
## A few checks are done to ensure that 
## the proc has been given valid data. 
## These are not exhaustive and errors still
## may occur if bad data is supplied. 
##
## A substitution formula based on the total 
## number of unique colors is used to 
## designate character codes. This helps keep
## file size as small as possible while still
## allowing for the full 8-bit range to be
## expressed. 
##
## Output has been tested and works with the
## Img 'pixmap' type and with Gimp, including
## transparency. 
##
##=========================================
## Arguments: 
## name - name for XPM data object
## pixels - nested list of pixels
##=========================================

proc xpm.data {name pixels} {

        set data "/* XPM */"
        append data \n "static char *const " $name \[\] " = " \{ \n \"
        
        set test [lindex [lindex $pixels 0] 0]
        if {[string length $test] ne 7 || ![string equal [string index $test 0] \#]} {
                error "8-bit hex color codes required." ; return 1
        }
                
        set colors {}
        foreach row $pixels {
                foreach pixel $row {
                        if {![string equal $pixel "#00NONE"]} 
                        {lappend colors $pixel}
                }
        }
        set colors [lsort -unique $colors]
        
        set h [llength $pixels]
        set w [llength [lindex $pixels 0]]
        
        set count [llength $colors]

        set fmt "%0" ; append fmt [string length $count] "s"        

        append data "$w $h [expr {$count + 1}] [string length $count]" \" "," \n  
        
        set trans [string map {0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j} 0] 
        append data \" "$trans c None " \" "," \n            
        
        set idx 1
        foreach color $colors {
                set tag [string map {0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j} [format $fmt $idx]]         
                append data \" $tag " c " $color \" "," \n        
                set lookup($color) $tag
                incr idx
        }

        foreach row $pixels {
                append data \"
                foreach pixel $row {
                        if {[string equal $pixel "#00NONE"]} {
                                append data $trans
                        } else {
                                append data $lookup($pixel)
                        }
                }
                append data \" "," \n
        }
        
        append data \}\;

        return $data

}


#### Test data 

proc testdata {} {
        
        set data {}
        lappend data [list #FFFFFF #000000 #FFFFFF #000000]
        lappend data [list #000000 #FFFFFF #000000 #FFFFFF] 
        lappend data [list #FFFFFF #000000 #FFFFFF #000000]
        lappend data [list #FFFFFF #00NONE #00NONE #000000]
        lappend data [list #FFFFFF #00NONE #00NONE #000000]
        lappend data [list #000000 #FFFFFF #000000 #FFFFFF] 
        lappend data [list #FFFFFF #000000 #FFFFFF #000000]
        lappend data [list #FFFFFF #000000 #FFFFFF #000000]                
        
        return $data
                
}