ref: ac7e6ac77f7eaa31be48c79a18b28f41e6ee9c22
dir: /i_tga.c/
#include "quakedef.h"
typedef struct Header Header;
typedef struct Footer Footer;
typedef struct Tga Tga;
struct Header {
int idlen;
int paltype;
int imgtype;
int palfirst;
int pallen;
int palbpp;
int ox;
int oy;
int w;
int h;
int bpp;
int flags;
};
struct Tga {
Header;
int alphasz;
int pxsz;
byte *pal;
bool hasalpha;
};
struct Footer {
u32int extoff;
u32int ddoff;
char sig[18];
};
enum {
FlagOriginRight = 1<<4,
FlagOriginTop = 1<<5,
FlagAlphaSizeMask = 0x0f,
ImgTypePaletted = 1,
ImgTypeTrueColor,
ImgTypeMonoChrome,
ImgTypeFlagRLE = 1<<3,
ExtAreaAttrTypeOffset = 0x1ee,
AttrTypeAlpha = 3,
AttrTypePremultipliedAlpha,
HeaderSize = 18,
FooterSize = 26,
};
static const char tga_sig[] = "TRUEVISION-XFILE.\0";
static void
w2rgb(int w, byte *r, byte *g, byte *b)
{
*b = (w>>0) & 31;
*b = (*b<<3) + (*b>>2);
*g = (w>>5) & 31;
*g = (*g<<3) + (*g>>2);
*r = (w>>10) & 31;
*r = (*r<<3) + (*r>>2);
}
static byte *
getpixel(Tga *h, pixel_t *out, byte *p, int *sz)
{
byte r, g, b, a;
int w;
if(*sz < h->pxsz){
werrstr("truncated?");
return nil;
}
r = g = b = a = 0xff;
switch(h->pxsz){
case 4:
if(h->hasalpha)
a = p[3];
/* fallthrough */
case 3:
b = p[0];
g = p[1];
r = p[2];
break;
case 2:
if(h->imgtype == ImgTypeMonoChrome){
r = g = b = p[0];
if(h->hasalpha)
a = p[1];
}else{
w = p[1]<<8 | p[0];
w2rgb(w, &r, &g, &b);
if(h->hasalpha && (w & (1<<15)) == 0)
a = 0;
}
break;
case 1:
if(h->pal != nil){
w = p[0];
if(w >= h->pallen){
werrstr("out of palette range: %d/%d", w, h->pallen);
return nil;
}
if(h->palbpp == 24){
w *= 3;
b = h->pal[w+0];
g = h->pal[w+1];
r = h->pal[w+2];
}else if(h->palbpp == 32){
w *= 4;
b = h->pal[w+0];
g = h->pal[w+1];
r = h->pal[w+2];
if(h->hasalpha)
a = h->pal[w+3];
}else if(h->palbpp == 16){
w *= 2;
w = h->pal[w+1]<<8 | h->pal[w+0];
w2rgb(w, &r, &g, &b);
}
}else{
r = g = b = p[0];
}
break;
}
if(a == 0)
*out = 0;
else
*out = a<<24 | r<<16 | g<<8 | b<<0;
*sz -= h->pxsz;
return p + h->pxsz;
}
int
TGA_Encode(byte **out, char *id, pixel_t *pix, int w, int h)
{
int sz, n, i;
byte *p;
n = id == nil ? 0 : strlen(id);
sz = HeaderSize + n + w*h*3;
*out = p = emalloc(sz);
*p++ = n; /* idlen */
*p++ = 0; /* paltype */
*p++ = ImgTypeTrueColor; /* imgtype */
*p++ = 0; *p++ = 0; /* palfirst */
*p++ = 0; *p++ = 0; /* pallen */
*p++ = 0; /* palbpp */
*p++ = 0; *p++ = 0; /* ox */
*p++ = 0; *p++ = 0; /* oy */
*p++ = w & 0xff; *p++ = w>>8; /* w */
*p++ = h & 0xff; *p++ = h>>8; /* h */
*p++ = 24; /* bpp */
*p++ = FlagOriginTop; /* flags */
memmove(p, id, n); p += n; /* id */
n = w * h;
for(i = 0; i < n; i++){
*p++ = pix[i] >> 0;
*p++ = pix[i] >> 8;
*p++ = pix[i] >> 16;
}
return sz;
}
qpic_t *
TGA_Decode(byte *in, int sz, bool *premult)
{
int i, n, cnt, x, y;
pixel_t pix;
byte *p, b;
qpic_t *q;
bool rle;
Footer f;
Tga h;
if(sz < HeaderSize){
werrstr("invalid size: %d", sz);
goto err;
}
h.idlen = *in++;
h.paltype = *in++;
h.imgtype = *in++;
h.palfirst = le16u(in);
h.pallen = le16u(in);
h.palbpp = *in++;
h.ox = le16u(in);
h.oy = le16u(in);
h.w = le16u(in);
h.h = le16u(in);
h.bpp = *in++;
h.flags = *in++;
sz -= HeaderSize;
if(h.idlen >= sz){
werrstr("invalid idlen: %d", h.idlen);
goto err;
}
in += h.idlen;
sz -= h.idlen;
if(h.w < 1 || h.h < 1){
werrstr("invalid size: %dx%d", h.w, h.h);
goto err;
}
rle = h.imgtype & ImgTypeFlagRLE;
h.imgtype &= 3;
h.alphasz = h.flags & FlagAlphaSizeMask;
if(h.alphasz != 0 && h.alphasz != 1 && h.alphasz != 8){
werrstr("invalid alpha size: %d", h.alphasz);
goto err;
}
*premult = false;
h.hasalpha =
(h.alphasz > 0 || h.bpp == 32) ||
(h.imgtype == ImgTypeMonoChrome && h.bpp == 16) ||
(h.imgtype == ImgTypePaletted && h.palbpp == 32);
h.pxsz = h.bpp >> 3;
if(sz > h.idlen + FooterSize){
p = in + sz - FooterSize;
f.extoff = le32u(p);
f.ddoff = le32u(p);
if(f.extoff != 0 && memcmp(p, tga_sig, sizeof(f.sig)) == 0){
f.extoff += ExtAreaAttrTypeOffset - HeaderSize;
if(f.extoff >= (u32int)sz){
werrstr("extoff out of range: %d", f.extoff);
goto err;
}
switch(in[f.extoff]){
case AttrTypeAlpha:
h.hasalpha = true;
break;
case AttrTypePremultipliedAlpha:
h.hasalpha = true;
*premult = true;
default:
h.hasalpha = false;
break;
}
}
}
if(
(h.imgtype == ImgTypePaletted && (
h.paltype != 1 ||
h.bpp != 8 ||
(h.palbpp != 15 && h.palbpp != 16 && h.palbpp != 24 && h.palbpp != 32))) ||
(h.imgtype == ImgTypeTrueColor && (
h.bpp != 32 && h.bpp != 16 && (h.bpp != 24 || h.hasalpha))) ||
(h.imgtype == ImgTypeMonoChrome && (
(h.bpp != 16 && h.hasalpha) ||
(h.bpp != 8 && !h.hasalpha)))){
werrstr("invalid/unsupported: imgtype=%d bpp=%d pal=[type=%d bpp=%d] hasalpha=%d",
h.imgtype, h.bpp, h.paltype, h.palbpp, h.hasalpha
);
goto err;
}
if(h.imgtype == ImgTypePaletted){
if(h.pallen <= h.palfirst){
werrstr("invalid palfirst/pallen: %d/%d", h.palfirst, h.pallen);
goto err;
}
h.pallen -= h.palfirst;
n = (h.palbpp+1) >> 3;
i = n*((int)h.palfirst + (int)h.pallen);
if(i >= sz){
werrstr("truncated?");
goto err;
}
h.pal = in + n*h.palfirst;
in += i;
sz -= i;
}else{
h.pal = nil;
}
q = Hunk_Alloc(sizeof(*q) + h.w*h.h*sizeof(pixel_t));
q->width = h.w;
q->height = h.h;
n = q->width * q->height;
if(rle){
for(i = 0; i < n && sz > 0;){
b = *in++;
sz--;
cnt = b;
if(cnt & (1<<7)){
cnt &= ~(1<<7);
if((in = getpixel(&h, &q->pixels[i++], in, &sz)) == nil)
goto err;
for(; cnt > 0 && i < n; cnt--, i++)
q->pixels[i] = q->pixels[i-1];
}else{
for(cnt++; cnt > 0 && i < n; cnt--, i++){
if((in = getpixel(&h, &q->pixels[i], in, &sz)) == nil)
goto err;
}
}
}
}else{
for(i = 0; i < n; i++){
if((in = getpixel(&h, &q->pixels[i], in, &sz)) == nil)
goto err;
}
}
if((h.flags & FlagOriginRight) != 0){
for(y = 0; y < q->height; y++){
for(x = 0; x < q->width/2; x++){
pix = q->pixels[x];
q->pixels[x] = q->pixels[q->width-x-1];
q->pixels[q->width-x-1] = pix;
}
}
}
if((h.flags & FlagOriginTop) == 0){
for(y = 0; y < q->height/2; y++){
for(x = 0; x < q->width; x++){
pix = q->pixels[y*q->width+x];
q->pixels[y*q->width+x] = q->pixels[(q->height-y-1)*q->width+x];
q->pixels[(q->height-y-1)*q->width+x] = pix;
}
}
}
return q;
err:
werrstr("TGA_Decode: %s", lerr());
return nil;
}