Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * TGA is a fairly dead standard, however in the video industry
 * it is still used a little for test patterns and the like.
 *
 * Thus we ignore any alpha channels, and colour mapped images.
 */

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <ctype.h>
#include "imagefile.h"

enum {
        HdrLen = 18,
};

typedef struct {
        int idlen;              /* length of string after header */
        int cmaptype;           /* 1 =>  datatype = 1 => colourmapped */
        int datatype;           /* see below */
        int cmaporigin;         /* index of first entry in colour map */
        int cmaplen;            /* length of olour map */
        int cmapbpp;            /* bips per pixel of colour map: 16, 24, or 32 */
        int xorigin;            /* source image origin */
        int yorigin;
        int width;
        int height;
        int bpp;                /* bits per pixel of image: 16, 24, or 32 */
        int descriptor;
        uchar *cmap;            /* colour map (optional) */
} Tga;

/*
 * descriptor:
 * d0-3 = number of attribute bits per pixel
 * d4   = reserved, always zero
 * d6-7 = origin: 0=lower left, 1=upper left, 2=lower right, 3=upper right
 * d8-9 = interleave: 0=progressive, 1=2 way, 3 = 4 way, 4 = reserved.
 */

char *datatype[] = {
        [0]     "No image data",
        [1]     "color mapped",
        [2]     "RGB",
        [3]     "B&W",
        [9]     "RLE color-mapped",
        [10]    "RLE RGB",
        [11]    "Compressed B&W",
        [32]    "Compressed color",
        [33]    "Quadtree compressed color",
};

static int
Bgeti(Biobuf *bp)
{
        int x, y;

        if((x = Bgetc(bp)) < 0)
                return -1;
        if((y = Bgetc(bp)) < 0)
                return -1;
        return (y<<8)|x;
}

static Tga *
rdhdr(Biobuf *bp)
{
        int n;
        Tga *h;

        if((h = malloc(sizeof(Tga))) == nil)
                return nil;
        if((h->idlen = Bgetc(bp)) == -1)
                return nil;
        if((h->cmaptype = Bgetc(bp)) == -1)
                return nil;
        if((h->datatype = Bgetc(bp)) == -1)
                return nil;
        if((h->cmaporigin = Bgeti(bp)) == -1)
                return nil;
        if((h->cmaplen = Bgeti(bp)) == -1)
                return nil;
        if((h->cmapbpp = Bgetc(bp)) == -1)
                return nil;
        if((h->xorigin = Bgeti(bp)) == -1)
                return nil;
        if((h->yorigin = Bgeti(bp)) == -1)
                return nil;
        if((h->width = Bgeti(bp)) == -1)
                return nil;
        if((h->height = Bgeti(bp)) == -1)
                return nil;
        if((h->bpp = Bgetc(bp)) == -1)
                return nil;
        if((h->descriptor = Bgetc(bp)) == -1)
                return nil;

        /* skip over ID, usually empty anyway */
        if(Bseek(bp, h->idlen, 1) < 0){
                free(h);
                return nil;
        }

        if(h->cmaptype == 0){
                h->cmap = 0;
                return h;
        }

        n = (h->cmapbpp/8)*h->cmaplen;
        if((h->cmap = malloc(n)) == nil){
                free(h);
                return nil;
        }
        if(Bread(bp, h->cmap, n) != n){
                free(h);
                free(h->cmap);
                return nil;
        }
        return h;
}

static int
luma(Biobuf *bp, uchar *l, int num)
{
        return Bread(bp, l, num);
}

static int
luma_rle(Biobuf *bp, uchar *l, int num)
{
        uchar len;
        int i, got;

        for(got = 0; got < num; got += len){
                if(Bread(bp, &len, 1) != 1)
                        break;
                if(len & 0x80){
                        len &= 0x7f;
                        len += 1;       /* run of zero is meaningless */
                        if(luma(bp, l, 1) != 1)
                                break;
                        for(i = 0; i < len && got < num; i++)
                                l[i+1] = *l;
                }
                else{
                        len += 1;       /* raw block of zero is meaningless */
                        if(luma(bp, l, len) != len)
                                break;
                }
                l += len;
        }
        return got;
}


static int
rgba(Biobuf *bp, int bpp, uchar *r, uchar *g, uchar *b, int num)
{
        int i;
        uchar x, y, buf[4];

        switch(bpp){
        case 16:
                for(i = 0; i < num; i++){
                        if(Bread(bp, buf, 2) != 2)
                                break;
                        x = buf[0];
                        y = buf[1];
                        *b++ = (x&0x1f)<<3;
                        *g++ = ((y&0x03)<<6) | ((x&0xe0)>>2);
                        *r++ = (y&0x1f)<<3;
                }
                break;
        case 24:
                for(i = 0; i < num; i++){
                        if(Bread(bp, buf, 3) != 3)
                                break;
                        *b++ = buf[0];
                        *g++ = buf[1];
                        *r++ = buf[2];
                }
                break;
        case 32:
                for(i = 0; i < num; i++){
                        if(Bread(bp, buf, 4) != 4)
                                break;
                        *b++ = buf[0];
                        *g++ = buf[1];
                        *r++ = buf[2];
                }
                break;
        default:
                i = 0;
                break;
        }
        return i;
}

static int
rgba_rle(Biobuf *bp, int bpp, uchar *r, uchar *g, uchar *b, int num)
{
        uchar len;
        int i, got;

        for(got = 0; got < num; got += len){
                if(Bread(bp, &len, 1) != 1)
                        break;
                if(len & 0x80){
                        len &= 0x7f;
                        len += 1;       /* run of zero is meaningless */
                        if(rgba(bp, bpp, r, g, b, 1) != 1)
                                break;
                        for(i = 0; i < len-1 && got < num; i++){
                                r[i+1] = *r;
                                g[i+1] = *g;
                                b[i+1] = *b;
                        }
                }
                else{
                        len += 1;       /* raw block of zero is meaningless */
                        if(rgba(bp, bpp, r, g, b, len) != len)
                                break;
                }
                r += len;
                g += len;
                b += len;
        }
        return got;
}

int
flip(Rawimage *ar)
{
        int w, h, c, l;
        uchar *t, *s, *d;

        w = Dx(ar->r);
        h = Dy(ar->r);
        if((t = malloc(w)) == nil){
                werrstr("ReadTGA: no memory - %r\n");
                return -1;
        }

        for(c = 0; c < ar->nchans; c++){
                s = ar->chans[c];
                d = ar->chans[c] + ar->chanlen - w;
                for(l = 0; l < (h/2); l++){
                        memcpy(t, s, w);
                        memcpy(s, d, w);
                        memcpy(d, t, w);
                        s += w;
                        d -= w;
                }
        }
        free(t);
        return 0;
}

int
reflect(Rawimage *ar)
{
        int w, h, c, l, p;
        uchar t, *sol, *eol, *s, *d;

        w = Dx(ar->r);
        h = Dy(ar->r);

        for(c = 0; c < ar->nchans; c++){
                sol = ar->chans[c];
                eol = ar->chans[c] +w -1;
                for(l = 0; l < h; l++){
                        s = sol;
                        d = eol;
                        for(p = 0; p < w/2; p++){
                                t = *s;
                                *s = *d;
                                *d = t;
                                s++;
                                d--;
                        }
                        sol += w;
                        eol += w;
                }
        }
        return 0;
}


Rawimage**
Breadtga(Biobuf *bp)
{
        Tga *h;
        int n, c, num;
        uchar *r, *g, *b;
        Rawimage *ar, **array;

        if((h = rdhdr(bp)) == nil){
                werrstr("ReadTGA: bad header %r");
                return nil;
        }

        if(0){
                fprint(2, "idlen=%d\n", h->idlen);
                fprint(2, "cmaptype=%d\n", h->cmaptype);
                fprint(2, "datatype=%s\n", datatype[h->datatype]);
                fprint(2, "cmaporigin=%d\n", h->cmaporigin);
                fprint(2, "cmaplen=%d\n", h->cmaplen);
                fprint(2, "cmapbpp=%d\n", h->cmapbpp);
                fprint(2, "xorigin=%d\n", h->xorigin);
                fprint(2, "yorigin=%d\n", h->yorigin);
                fprint(2, "width=%d\n", h->width);
                fprint(2, "height=%d\n", h->height);
                fprint(2, "bpp=%d\n", h->bpp);
                fprint(2, "descriptor=%d\n", h->descriptor);
        }

        array = nil;
        if((ar = calloc(sizeof(Rawimage), 1)) == nil){
                werrstr("ReadTGA: no memory - %r\n");
                goto Error;
        }

        if((array = calloc(sizeof(Rawimage *), 2)) == nil){
                werrstr("ReadTGA: no memory - %r\n");
                goto Error;
        }
        array[0] = ar;
        array[1] = nil;

        if(h->datatype == 3){
                ar->nchans = 1;
                ar->chandesc = CY;
        }
        else{
                ar->nchans = 3;
                ar->chandesc = CRGB;
        }

        ar->chanlen = h->width*h->height;
        ar->r = Rect(0, 0, h->width, h->height);
        for (c = 0; c < ar->nchans; c++)
                if ((ar->chans[c] = malloc(h->width*h->height)) == nil){
                        werrstr("ReadTGA: no memory - %r\n");
                        goto Error;
                }
        r = ar->chans[0];
        g = ar->chans[1];
        b = ar->chans[2];

        num = h->width*h->height;
        switch(h->datatype){
        case 2:
                if(rgba(bp, h->bpp, r, g, b, num) != num){
                        werrstr("ReadTGA: decode fail - %r\n");
                        goto Error;
                }
                break;
        case 3:
                if(luma(bp, r, num) != num){
                        werrstr("ReadTGA: decode fail - %r\n");
                        goto Error;
                }
                break;
        case 10:
                if((n = rgba_rle(bp, h->bpp, r, g, b, num)) != num){
                        werrstr("ReadTGA: decode fail (%d!=%d) - %r\n", n, num);
                        goto Error;
                }
                break;
        case 11:
                if(luma_rle(bp, r, num) != num){
                        werrstr("ReadTGA: decode fail - %r\n");
                        goto Error;
                }
                break;
        default:
                werrstr("ReadTGA: type=%d (%s) unsupported\n", h->datatype, datatype[h->datatype]);
                goto Error;     
        }

        if(h->xorigin != 0)
                reflect(ar);
        if(h->yorigin == 0)
                flip(ar);
        
        free(h->cmap);
        free(h);
        return array;
Error:

        if(ar)
                for (c = 0; c < ar->nchans; c++)
                        free(ar->chans[c]);
        free(ar);
        free(array);
        free(h->cmap);
        free(h);
        return nil;
}

Rawimage**
readtga(int fd)
{
        Rawimage * *a;
        Biobuf b;

        if (Binit(&b, fd, OREAD) < 0)
                return nil;
        a = Breadtga(&b);
        Bterm(&b);
        return a;
}