TGA image reader

Reads TGA images into an easily manipulable format. Requires fishpool.easyOps 0.1, which can be found at http://www.fishpool.com/~setok/proj/easyOpers.tcl .

The idea would be to collect a whole series of these under the same namespace for managing different image formats.


    namespace import ::easyOps::*

    # Reads image from file 'filePath', interprets it as a TGA image and
    # returns it as a list that can be used with Tk photos:
    # A list of lists. Each list is one row of the image and contains 
    # pixels in the following format #RRGGBB, with R, G and B being HEX
    # values for red, green and blue, respectively.

    proc readData {filePath} {
        set imageFile [open [lindex $filePath 0] r]
        fconfigure $imageFile -translation binary -encoding binary
        set fileData [read $imageFile]
        close $imageFile

        array set header [getHeader $fileData]
        array set imageSpec [getImgSpec $fileData]

        # Pixels
        # :NOTE: We assume colour map is 0 for now (just deal with 
        # truecolour). 
        # Naughty naughty.

        set pixels [string range $fileData [+ 18 $header(idLength)] \
                        [+ 18 $header(idLength) \
                             [* $imageSpec(width) \
                                  $imageSpec(height) \
                                  [/ $imageSpec(pixDepth) 8]] -1]]

        # Check order to process pixels in. We always build the list from top 
        # to bottom, left to right.

        if {$imageSpec(rightLeft)} {
            # Image should be draw from right to left
            set startX $imageSpec(width)
            set endX -1
            set incrX -1
        } else {
            set startX 0
            set endX [- $imageSpec(width) 1]
            set incrX 1
        }

        if {$imageSpec(topDown)} {
            # Image should be drawn from top to bottom
            set startY 0
            set endY $imageSpec(height)
            set incrY 1
        } else {
            set startY [- $imageSpec(height) 1]
            set endY -1
            set incrY -1
        }

        set imageDat [list]
        for {set y $startY} {$y != $endY} {incr y $incrY} {
            set row [list]
            for {set x $startX} {$x != $endX} {incr x $incrX} {
                set idx [* [+ [* $y $imageSpec(width)] $x] 3]
                set rgbString [string range $pixels $idx [+ $idx 2]]
                binary scan $rgbString "ccc" b g r
                set r [& $r 0xFF]
                set g [& $g 0xFF]
                set b [& $b 0xFF]
                lappend row "#[format "%02x%02x%02x" $r $g $b]"
            }
            lappend imageDat $row
        }

        return $imageDat
    }


    ## Get image specification.
    ##
    ## 'imgData' contains the full TGA data, including headers.
    ##
    ## Returns key-value list with the following fields: xOrigin, yOrigin
    ## width, height, pixDepth, alpha, rightLeft, topDown.
    ##   xOrigin, yOrigin  Abolute co-ordinates for lower left corner.
    ##   width, height   Hm.. obvious.
    ##   pixDepth  Amount of bits per pixel.
    ##   attrBits   Number of attribute bits per pixel (f.ex. alpha).
    ##   rightLeft   If true, to be drawn from right to left.
    ##   topDown   If true, to be drawn from top to bottom.

    proc getImgSpec {imgData} {
        binary scan $imgData "@8 sssscc" imageSpec(xOrigin) \
            imageSpec(yOrigin) imageSpec(width) imageSpec(height) \
            imageSpec(pixDepth) \
            imageDesc

        set imageSpec(attrBits) [& $imageDesc 0x7]
        set imageSpec(rightLeft) [>> [& $imageDesc 0x8] 3]
        set imageSpec(topDown) [>> [& $imageDesc 0x16] 4]

        return [array get imageSpec]
    }


    ## Get image header. 
    ##
    ## 'imgData' contains the full TGA data, including headers.
    ##
    ## Returns key-value list with fields: idLength, colourMap, imageData,
    ## [compression, colourModel].
    ##    idLength   Length in bytes of ID field.
    ##    colourMap  If true, image uses a colour map.
    ##    imageData  If true, header contained image data.
    ##    compression  Compression model, either RLE or none.
    ##    colourModel  Model of colour. Either "mapped", "true-colour" or
    ##                 "black-and-white"
    ## 'compression' and 'colourModel' are only set if 'imageData' is true.

    proc getHeader {imgData} {
        set hdrData [string range $imgData 0 18]
        binary scan $hdrData "ccc" header(idLength) header(colourMap) \
            imageType 

        if {$imageType == 0} {
            # No image data present.

            set header(imageData) false
            return
        } else {
            set header(imageData) true
        }

        if {$imageType >= 1 && $imageType <= 3} {
            # No imagecompression
            set header(compression) none
        } else {
            set header(compression) RLE
        }

        switch -- $imageType {
            1 -
            9 {
                set header(colourModel) mapped
            } 
            2 - 
            10 {
                set header(colourModel) true-colour
            } 
            3 -
            11 {
                set header(colourModel) black-and-white
            }
        }

        return [array get header]
    }
 }