TargaReader.st
author Claus Gittinger <cg@exept.de>
Sun, 29 Jan 2017 02:26:51 +0100
changeset 3853 5a78ffcf69de
parent 1848 864ca2cd4e71
child 3855 1db7742d33ad
child 3998 f38061cec746
permissions -rw-r--r--
#FEATURE by cg class: TypeConverter changed: #timeOfClass:withFormat:orDefault:language:

"
 COPYRIGHT (c) 1994 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

"{ Package: 'stx:libview2' }"

ImageReader subclass:#TargaReader
	instanceVariableNames:'orientation bytesPerRow bytesPerPixel'
	classVariableNames:''
	poolDictionaries:''
	category:'Graphics-Images-Readers'
!

!TargaReader class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1994 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    this class provides methods for loading targa-file (tga) images.

    Limitations: 
        not fully tested (I only had a few targa files to check things)
        only supports 8,24 and 32 bits/pixel formats; alpha channel is ignored
        Image saving not supported

    I had two tga files to test this code with - it may not work with
    other targa files (it certainly does not work with 1/16 bit images).

    Suggestions: adapt & use the pbmplus library here.

    [See also:]
        Image Form Icon
        BlitImageReader FaceReader GIFReader JPEGReader PBMReader PCXReader 
        ST80FormReader SunRasterReader TIFFReader WindowsIconReader 
        XBMReader XPMReader XWDReader
"
!

format
"
    Shorts etc. are lsb.

    offset:

        1       lenID           byte
        2       hasColorMap     byte
        3       imageType       byte
                         1   MapRGB  
                         2   RawRGB   
                         3   RawMono   
                         9   MapEnCode 
                         10  RawEnCode 
        4, 5    cmapOffset      short
        6, 7    cmapLength      short
        8       cmapEntrySize   short
        9,10    xOrg            short
        11,12   yOrg            short

        13,14   width 
        15,16   height 
        17      depth 
        18      flags
                        0000 xxxx  attribute-bits-per-pixel
                        0000 0001  greysc
                        0000 0010  colour
                        0000 0011  mapped
                        0000 0100  rleEncoded
                        0000 1000  interlaced
                        00xx 0000  origin (0 -> lower-left / 1 -> l-r / 2 -> u-l / 3 -> u-r)
                        xx00 0000  interleave (0 -> none / 1 -> odd/even / 2 ->4-fould / 3 reserved)

        19..    len             lenID-bytes
        ..      colorMap        cmapLength*3 bytes iff hasColorMap ~~ 0
"
! !

!TargaReader class methodsFor:'initialization'!

initialize
    "tell Image-class, that a new fileReader is present
     for the '.tga' extension."

    MIMETypes defineImageType:'image/x-targa' suffix:'tga' reader:self.

    "Modified: 27.6.1997 / 18:39:43 / cg"
! !

!TargaReader class methodsFor:'testing'!

isValidImageFile:aFileName
    "return true, if aFileName contains a targa-file image"

    |aStream w h depth flags imageType ok|

    ok := true.

    aStream := self streamReadingFile:aFileName.
    aStream isNil ifTrue:[^ false].
    aStream binary.

    aStream next.   "lenID"
    aStream next.   "hasColorMap"
    imageType := aStream next.  
    aStream skip:2. "cmapOffset"
    aStream skip:2. "cmapLength"
    aStream next.   "cmapEntrySize"
    aStream skip:2. "xOrg"
    aStream skip:2. "yOrg"

    w := aStream nextShortMSB:false.
    h := aStream nextShortMSB:false.

    depth := aStream next.
    flags := aStream next.

    "/ MapRGB == 1
    "/ RawRGB == 2
    "/ RawMono == 3
    "/ MapEnCode == 9
    "/ RawEnCode == 10

    (#(1 2 3 9 10) includes:imageType) ifFalse:[
        "/ 'TargaReader [warning]: unsupported imageType: ' errorPrint. imageType errorPrintCR.
        ok := false
    ] ifTrue:[
        (#(8 "16" 24 32) includes:depth) ifFalse:[
            ok := false
        ].
    ].
    aStream close. 
    ^ ok

    "
     TargaReader isValidImageFile:'bitmaps/test.tga'    
     TargaReader isValidImageFile:'bitmaps/garfield.gif'  
    "

    "Modified: 21.4.1997 / 20:46:52 / cg"
! !

!TargaReader methodsFor:'private-reading'!

handleImageOrientation
    |rowIdx startIdx endIdx t|

    orientation == #topLeft ifTrue:[
        ^ self.
    ].

    orientation == #topRight ifTrue:[
        "/ flip horizontal
        rowIdx := 1.
        1 to:height do:[:row |
            startIdx := rowIdx.
            endIdx := rowIdx + bytesPerRow - bytesPerPixel.
            1 to:width//2 do:[:x |
                0 to:bytesPerPixel-1 do:[:c |
                    t := data at:startIdx+c.
                    data at:startIdx+c put:(data at:endIdx+c).
                    data at:endIdx+c put:t.
                ].
                startIdx := startIdx + bytesPerPixel.
                endIdx := endIdx - bytesPerPixel.
            ].
            rowIdx := rowIdx + width.
        ].
        ^ self.
    ].

    orientation == #bottomLeft ifTrue:[
        "/ flip vertical
        startIdx := 1.
        endIdx := 1 + ((height - 1) * bytesPerRow).
        t := ByteArray new:bytesPerRow.
        1 to:height//2 do:[:row |
            t replaceFrom:1 to:bytesPerRow with:data startingAt:startIdx.
            data replaceFrom:startIdx to:startIdx+bytesPerRow-1 with:data startingAt:endIdx.
            data replaceFrom:endIdx to:endIdx+bytesPerRow-1 with:t startingAt:1.
            startIdx := startIdx + bytesPerRow.
            endIdx := endIdx - bytesPerRow
        ].
        ^ self.
    ].

    'TargaReader [warning]: unsupported orientation: ' errorPrint. orientation errorPrintCR.
!

read24
    "read a 24 bit/pixel targa-image"

    |totalBytes|

    totalBytes := width * height * 3.
    data := ByteArray new:totalBytes.
    inStream nextBytes:totalBytes into:data.

    "
     mhmh - pixel-byte order is blue-green-red
     swap blue & red bytes
    "
    self class swap:totalBytes bytesFromRGB_to_BGR_in:data.

    photometric := #rgb.
    samplesPerPixel := 3.
    bitsPerSample := #(8 8 8).
!

read24RLE
    "read an 8 bit/pixel rle encoded targa-image"

    |total count dstIndex code n r g b|

    data := ByteArray new:((total := width * height * 3)).
    count := 0.
    dstIndex := 1.
    [count < total] whileTrue:[
        code := inStream nextByte.
        n := (code bitAnd:16r7F) + 1.
        (code bitAnd:16r80) ~~ 0 ifTrue:[
            b := inStream nextByte.
            g := inStream nextByte.
            r := inStream nextByte.
            n timesRepeat:[
                data at:dstIndex put:r.
                data at:dstIndex+1 put:g.
                data at:dstIndex+2 put:b.
                dstIndex := dstIndex + 3
            ].
        ] ifFalse:[
            n timesRepeat:[
                b := inStream nextByte.
                g := inStream nextByte.
                r := inStream nextByte.
                data at:dstIndex put:r.
                data at:dstIndex+1 put:g.
                data at:dstIndex+2 put:b.
                dstIndex := dstIndex + 3
            ]
        ].
        count := count + (n * 3).
    ].

    photometric := #rgb.
    samplesPerPixel := 3.
    bitsPerSample := #(8 8 8).

    "Modified: 21.4.1997 / 20:21:12 / cg"
    "Created: 21.4.1997 / 20:43:23 / cg"
!

read32
    "read a 32 bit/pixel targa-image; skip alpha channel (for now)"

    |totalBytes remainingBytes dstIndex a r g b|

    totalBytes := width * height * 3.
    data := ByteArray new:totalBytes.

    dstIndex := 1.
    remainingBytes := totalBytes.
    [remainingBytes > 0] whileTrue:[
        b := inStream nextByte.
        g := inStream nextByte.
        r := inStream nextByte.
        a := inStream nextByte.   "/ alpha - skipped
        data at:dstIndex   put:r.
        data at:dstIndex+1 put:g.
        data at:dstIndex+2 put:b.
        dstIndex := dstIndex + 3.
        remainingBytes := remainingBytes - 3.
    ].

    photometric := #rgb.
    samplesPerPixel := 3.
    bitsPerSample := #(8 8 8).

    "
     TargaReader fromFile:'/phys/exept//unsaved1/pd_stuff/graphic/3d_engines/crystal_space/CS/plugins/video/canvas/sdl/img/move.tga'
     TargaReader fromFile:'/phys/exept/unsaved2/smalltalk/Squeak/croquet/Croquet/Content/Cisco/2520/Textures/2520caseback.tga'
     TargaReader fromFile:'/phys/exept/unsaved2/smalltalk/Squeak/croquet/Croquet/Content/Cisco/2520/Textures/2520dimm1front.tga'
    "
!

read32RLE
    "read a 32 bit/pixel rle encoded targa-image; skip alpha channel (for now)"

    |total count dstIndex code n a r g b|

    data := ByteArray new:((total := width * height * 3)).
    count := 0.
    dstIndex := 1.
    [count < total] whileTrue:[
        code := inStream nextByte.
        n := (code bitAnd:16r7F) + 1.
        (code bitAnd:16r80) ~~ 0 ifTrue:[
            b := inStream nextByte.
            g := inStream nextByte.
            r := inStream nextByte.
            a := inStream nextByte.             "/ alpha - skipped
            n timesRepeat:[
                data at:dstIndex put:r.
                data at:dstIndex+1 put:g.
                data at:dstIndex+2 put:b.
                dstIndex := dstIndex + 3
            ].
        ] ifFalse:[
            n timesRepeat:[
                b := inStream nextByte.
                g := inStream nextByte.
                r := inStream nextByte.
                a := inStream nextByte.         "/ alpha - skipped
                data at:dstIndex put:r.
                data at:dstIndex+1 put:g.
                data at:dstIndex+2 put:b.
                dstIndex := dstIndex + 3
            ]
        ].
        count := count + (n * 3).
    ].

    photometric := #rgb.
    samplesPerPixel := 3.
    bitsPerSample := #(8 8 8).

    "Created: 21.4.1997 / 20:43:54 / cg"
    "Modified: 21.4.1997 / 20:45:49 / cg"
!

read8
    "read an 8 bit/pixel targa-image"

    data := ByteArray new:(width * height).
    inStream nextBytes:(data size) into:data.

    colorMap isNil ifTrue:[
        photometric := #blackIs0.
    ] ifFalse:[
        photometric := #palette.
    ].
    samplesPerPixel := 1.
    bitsPerSample := #(8).

    "Created: 21.4.1997 / 20:12:35 / cg"
    "Modified: 21.4.1997 / 20:39:02 / cg"
!

read8RLE
    "read an 8 bit/pixel rle encoded targa-image"

    |total count dstIndex code n byte|

    data := ByteArray new:((total := width * height)).
    count := 0.
    dstIndex := 1.
    [count < total] whileTrue:[
        code := inStream nextByte.
        n := (code bitAnd:16r7F) + 1.
        (code bitAnd:16r80) ~~ 0 ifTrue:[
            byte := inStream nextByte.
            data from:dstIndex to:dstIndex+n-1 put:byte.
        ] ifFalse:[
            inStream nextBytes:n into:data startingAt:dstIndex.
        ].
        count := count + n.
        dstIndex := dstIndex + n.
    ].

    photometric := #palette.
    samplesPerPixel := 1.
    bitsPerSample := #(8).

    "Created: 21.4.1997 / 20:19:46 / cg"
    "Modified: 21.4.1997 / 20:21:12 / cg"
!

readColorMap:cmapLength
    colorMap := Array new:cmapLength.
    1 to:cmapLength do:[:index |
        |r g b|

        b := inStream nextByte.
        g := inStream nextByte.
        r := inStream nextByte.
        (r isNil or:[g isNil or:[b isNil]]) ifTrue:[
            ^ self fileFormatError:('short file - probably not targa format').
        ].
        colorMap at:index put:(Color redByte:r greenByte:g blueByte:b).
    ].
    ^ colorMap
! !

!TargaReader methodsFor:'reading'!

readImage
    "read a targa-image from aFileName. return the receiver (with all
     relevant instance variables set for the image) or nil on error"

    |depth flags lenID hasColorMap imageType 
     cmapOffset cmapLength cmapEntrySize xOrg yOrg rle|

    inStream binary.

    lenID := inStream next.
    hasColorMap := inStream next.
    imageType := inStream next.
    cmapOffset := inStream nextShortMSB:false.
    cmapLength := inStream nextShortMSB:false.
    cmapEntrySize := inStream next.
    xOrg := inStream nextShortMSB:false.
    yOrg := inStream nextShortMSB:false.

    width := inStream nextShortMSB:false.
    height := inStream nextShortMSB:false.
    depth := inStream next.
    (#(8 "16" 24 32) includes:depth) ifFalse:[
        ^ self fileFormatError:('unsupported depth: ', depth printString).
    ].
    depth == 32 ifTrue:[
        'TargaReader [info]: alpha channel ignored' infoPrintCR.
    ] ifFalse:[
        'TargaReader [info]: depth: ' infoPrint. depth infoPrintCR.
    ].

    "/ MapRGB == 1
    "/ RawRGB == 2
    "/ RawMono == 3
    "/ MapEnCode == 9
    "/ RawEnCode == 10

    (#(1 2 9 10) includes:imageType) ifFalse:[
        "/ 'TargaReader [warning]: unsupported imageType: ' errorPrint. imageType errorPrintCR.
        ^ self fileFormatError:('unsupported imageType: ', imageType printString).
    ].
    'TargaReader [info]: imageType: ' infoPrint. imageType infoPrintCR.

    self reportDimension.

    "/ flags:
    "/    0000 xxxx  attribute-bits-per-pixel
    "/    0000 0001  greysc
    "/    0000 0010  colour
    "/    0000 0011  mapped
    "/    0000 0100  rleEncoded
    "/    0000 1000  interlaced
    "/    00xx 0000  origin (0 -> lower-left / 1 -> l-r / 2 -> u-l / 3 -> u-r)
    "/    xx00 0000  interleave (0 -> none / 1 -> odd/even / 2 ->4-fould / 3 reserved)
    "/
    flags := inStream next.

    (flags bitAnd:2r11000000) ~~ 0 ifTrue:[
        ^ self fileFormatError:('unsupported interlace: ' , flags printString).
    ].

    rle := flags bitTest:2r000001000.
    flags := flags bitAnd:2r111110111.

    (flags bitAnd:2r00001111) ~~ 0 ifTrue:[
        ^ self fileFormatError:('unsupported flags: ' , flags printString).
    ].

    (flags bitAnd:2r00110000) == 16r20 ifTrue:[
        orientation := #topLeft
    ] ifFalse:[
        (flags bitAnd:2r00110000) == 16r30 ifTrue:[
            orientation := #topRight
        ] ifFalse:[
            (flags bitAnd:2r00110000) == 16r10 ifTrue:[
                orientation := #bottomRight
            ] ifFalse:[
                (flags bitAnd:2r00110000) == 0 ifTrue:[
                    orientation := #bottomLeft
                ]
            ]
        ]
    ].

    lenID ~~ 0 ifTrue:[
        inStream skip:lenID
    ].

    hasColorMap ~~ 0 ifTrue:[
        "/ read the colorMap
        colorMap := self readColorMap:cmapLength.
        'TargaReader [info]: has colorMap' infoPrintCR.
    ].

    depth == 32 ifTrue:[
        imageType == 2 ifTrue:[
"/            rle ifTrue:[self halt:'oops - should not happen'].
            self read32.
        ] ifFalse:[
"/            rle ifFalse:[self halt:'oops - should not happen'].
            self read32RLE.
        ].
        bytesPerRow := width*3.
        bytesPerPixel := 3.
    ].
    depth == 24 ifTrue:[
        imageType == 2 ifTrue:[
"/            rle ifTrue:[self halt:'oops - should not happen'].
            self read24.
        ] ifFalse:[
"/            rle ifFalse:[self halt:'oops - should not happen'].
            self read24RLE.
        ].
        bytesPerRow := width*3.
        bytesPerPixel := 3.
    ].
    depth == 8 ifTrue:[
        imageType == 1 ifTrue:[
"/            rle ifTrue:[self halt:'oops - should not happen'].
            self read8.
        ] ifFalse:[
"/            rle ifFalse:[self halt:'oops - should not happen'].
            self read8RLE
        ].
        bytesPerRow := width.
        bytesPerPixel := 1.
    ].

    self handleImageOrientation.

    "
     TargaReader fromFile:'bitmaps/test.tga' 
    "

    "Modified: / 7.9.1998 / 21:12:12 / cg"
    "Modified: / 13.10.1998 / 19:50:48 / ps"
! !

!TargaReader class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libview2/TargaReader.st,v 1.25 2003-11-19 15:38:53 cg Exp $'
! !

TargaReader initialize!