If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle
See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// lib_image.cpp
#include "library.h"
/*
==============
L_ScaleImage
Horribly slow scaling of a texture.
Basically, this function puts a grid over the output, then scales
this grid to the input. The grid is of size stepx/stepy.
A single pixel in the output is created by adding the color values
from the input pixels corresponding to the output pixel.
This doesn't result in anti-aliasing when an image is enlarged.
==============
*/
void L_ScaleImage(byte *out, int outw, int outh, const byte *in, int inw, int inh)
{
byte *dst;
const byte *src;
float r, g, b, a;
float srcx, srcy;
float stepx, stepy;
int x, y;
// Com_DPrintf("Scaling %i/%i to %i/%i\n", inw, inh, outw, outh);
dst = out;
stepx = (float)inw / outw;
stepy = (float)inh / outh;
for(y = 0, srcy = 0; y < outh; y++, srcy += stepy) {
for(x = 0, srcx = 0; x < outw; x++, srcx += stepx) {
int sx, sy;
float areaw, areah, f;
float areatotal;
r = g = b = a = 0;
areatotal = 0;
sy = (int)srcy;
areah = 1 - (srcy - sy);
do {
f = (sy + 1) - (srcy + stepy);
if (f > 0)
areah -= f;
sx = (int)srcx;
areaw = 1 - (srcx - sx);
src = in + 4 * (sy*inw + sx);
do {
f = (sx + 1) - (srcx + stepx);
if (f > 0)
areaw -= f;
areaw *= areah;
r += src[0] * areaw;
g += src[1] * areaw;
b += src[2] * areaw;
a += src[3] * areaw;
areatotal += areaw;
areaw = 1.0;
sx++;
src += 4;
} while(sx < srcx + stepx);
areah = 1.0;
sy++;
} while(sy < srcy + stepy);
dst[0] = (byte)(r / areatotal);
dst[1] = (byte)(g / areatotal);
dst[2] = (byte)(b / areatotal);
dst[3] = (byte)(a / areatotal);
dst += 4;
}
}
}
/*
==============
L_ImageBorder
Add a one-pixel border with zero alpha to the image (color is equal to
nearest). Used for sub-pixel accurate rendering.
==============
*/
u32 *L_ImageBorder(const u32 *pixels, const int *size)
{
u32 *buf;
int newsize[2];
int x, y;
u32 *dst;
const u32 *src;
newsize[0] = size[0]+2;
newsize[1] = size[1]+2;
buf = (u32 *)L_Malloc(4 * newsize[0]*newsize[1], TAG_IMAGE);
// copy the actual image
for(y = 0; y < size[1]; y++) {
dst = &buf[(y+1) * newsize[0] + 1];
src = &pixels[y * size[0]];
memcpy(dst, src, 4 * size[0]);
}
// now fill in the borders
for(x = 0; x < size[0]; x++) {
buf[x+1] = pixels[x] & 0xffffff;
buf[(newsize[1]-1) * newsize[0] + (x+1)] =
pixels[(size[1]-1) * size[0] + x] & 0xffffff;
}
for(y = 0; y < size[1]; y++) {
buf[(y+1) * newsize[0]] = pixels[y * size[0]] & 0xffffff;
buf[(y+2) * newsize[0] - 1] = pixels[(y+1) * size[0] - 1] & 0xffffff;
}
// fixup the corners
buf[0] = pixels[0] & 0xffffff;
buf[newsize[0]-1] = pixels[size[0]-1] & 0xffffff;
buf[(newsize[1]-1)*newsize[0]] = pixels[(size[1]-1)*size[0]] & 0xffffff;
buf[newsize[1]*newsize[0] - 1] = pixels[size[1]*size[0] - 1] & 0xffffff;
return buf;
}
/*
==============
L_ImageBitmask
Allocate and build a bitmask for the given image, based on the alpha part
==============
*/
byte *L_ImageBitmask(const byte *pixels, const int *size)
{
byte *bitmask;
byte *dst;
const byte *scan;
int x, y;
int bytes;
bytes = (size[0] + 7) / 8;
bitmask = (byte *)L_Malloc(bytes * size[1], TAG_IMAGE);
memset(bitmask, 0, bytes * size[1]);
scan = pixels + 3;
dst = bitmask;
for(y = 0; y < size[1]; y++) {
for(x = 0; x < size[0]; x++, scan += 4) {
if (*scan >= 128)
dst[x >> 3] |= 1 << (x & 7);
}
dst += bytes;
}
return bitmask;
}
/*
==============================================================================
PCX FILE FORMAT
==============================================================================
*/
typedef struct {
byte magic;
byte version;
byte encoding;
byte bitsperpixel;
short xmin;
short ymin;
short xmax;
short ymax;
short hdpi;
short vdpi;
byte colormap[48];
byte reserved;
byte nplanes;
short bytesperline;
short paletteinfo; // ignore
short hscreensize; // ignore
short vscreensize; // ignore
byte filler[54];
} pcx_header_t;
#define PCX_MAGIC 10
#define PCX_VERSION 5
#define PCX_RLE 1
#define PCX_PAL_MAGIC 12
/*
==============
LoadPCX
Loads a .pcx file into memory. Returns 0 on success, or an error message.
==============
*/
void LoadPCX(const char *fname, int *sizes, byte **ppal, byte **pdata)
{
byte decodebuf[640];
pcx_header_t *hdr;
byte *pal;
int bytes;
byte *pic;
int line;
int i;
*pdata = 0;
*ppal = 0;
pic = 0;
try
{
LFileRead fr;
// Load the file
fr.Open(fname);
hdr = (pcx_header_t *)fr.Data(sizeof(pcx_header_t));
// fixup endianness
hdr->xmin = LittleShort(hdr->xmin);
hdr->xmax = LittleShort(hdr->xmax);
hdr->ymin = LittleShort(hdr->ymin);
hdr->ymax = LittleShort(hdr->ymax);
hdr->bytesperline = LittleShort(hdr->bytesperline);
// check validity of file
if (hdr->magic != PCX_MAGIC)
throw LError("%s: not a PCX file", fname);
// check format
if (hdr->version != PCX_VERSION || hdr->encoding != PCX_RLE)
throw LError("%s: unsupported format", fname);
if (hdr->bitsperpixel != 8 || hdr->nplanes != 1)
throw LError("%s: only plain 256-color PCX supported", fname);
// figure out width and height
sizes[0] = hdr->xmax - hdr->xmin + 1;
sizes[1] = hdr->ymax - hdr->ymin + 1;
if (sizes[0] > 640)
throw LError("%s: image is too big", fname);
if (hdr->bytesperline > sizeof(decodebuf))
throw LError("%s hates you", fname);
// decode line by line
pic = (byte *)L_Malloc(sizes[0] * sizes[1], TAG_IMAGE);
line = 0;
while(line < sizes[1]) {
bytes = 0;
while(bytes < hdr->bytesperline) {
byte c = fr.Byte();
byte fill;
if ((c & 0xC0) != 0xC0) {
decodebuf[bytes++] = c;
continue;
}
c &= ~0xC0;
if (bytes + c > hdr->bytesperline)
throw LError("%s: RLE malformed", fname);
fill = fr.Byte();
memset(&decodebuf[bytes], fill, c);
bytes += c;
}
memcpy(pic + (line * sizes[0]), decodebuf, sizes[0]);
line++;
}
// load the palette
fr.SetFilePos(fr.length-769);
if (fr.Byte() != PCX_PAL_MAGIC)
throw LError("%s: no palette", fname);
pal = (byte *)L_Malloc(256 * 4, TAG_IMAGE);
for(i = 0; i < 256; i++) {
pal[i*4 + 0] = fr.Byte();
pal[i*4 + 1] = fr.Byte();
pal[i*4 + 2] = fr.Byte();
pal[i*4 + 3] = 255;
}
}
catch(...)
{
if (pic)
L_Free(pic);
throw;
}
*ppal = pal;
*pdata = pic;
return;
}
/*
==============================================================================
TGA FILE FORMAT
==============================================================================
*/
#pragma pack(1)
typedef struct tga_header_s {
byte idlength;
byte paltype;
byte imgtype;
u16 pal_start; // first present palette entry
u16 pal_length; // number of palette entries
byte pal_size; // size of palette entry, in bits
s16 img_xorigin;
s16 img_yorigin;
u16 img_width;
u16 img_height;
byte img_bitsperpixel;
byte img_descriptor;
} tga_header_t;
#pragma pack()
/*
==============
LoadTGA
Loads a targa file into memory. Returns 0, or a string with an error message.
*palpha is set to true if the loaded image contains non-opaque pixels.
==============
*/
void LoadTGA(const char *fname, int *sizes, byte **ppal, byte **pdata, bool *palpha)
{
tga_header_t *hdr;
byte *palette;
byte *data;
byte *dst, *src;
int i;
int pixbytes;
int x, y, xstep, ystep, xcount, ycount;
*ppal = 0;
*pdata = 0;
*palpha = false;
palette = 0;
data = 0;
try
{
LFileRead fr;
// Open the file
fr.Open(fname);
// Process the header
hdr = (tga_header_t *)fr.Data(sizeof(tga_header_t));
hdr->pal_start = LittleShort(hdr->pal_start);
hdr->pal_length = LittleShort(hdr->pal_length);
hdr->img_width = LittleShort(hdr->img_width);
hdr->img_height = LittleShort(hdr->img_height);
sizes[0] = hdr->img_width;
sizes[1] = hdr->img_height;
if (hdr->imgtype != 1 && hdr->imgtype != 2)
throw LError("%s: only uncompressed palette and true-color TGAs supported",
fname);
fr.SetFilePos(fr.filepos + hdr->idlength);
// Load the palette
if (hdr->paltype)
{
if (hdr->paltype != 1)
throw LError("%s: unsupported palette type", fname);
if (hdr->pal_start + hdr->pal_length > 256)
throw LError("%s: palette is too long", fname);
if (hdr->pal_size != 24 && hdr->pal_size != 32)
throw LError("%s: only RGB and RGBA palettes are supported", fname);
palette = (byte *)L_Malloc(256 * 4, TAG_IMAGE);
for(i = 0, dst = palette; i < 256; i++, dst += 4) {
if (i < hdr->pal_start || i >= hdr->pal_start + hdr->pal_length) {
dst[0] = dst[1] = dst[2] = 0;
dst[3] = 255;
continue;
}
if (hdr->pal_size == 24) {
src = (byte *)fr.Data(3);
dst[0] = src[2];
dst[1] = src[1];
dst[2] = src[0];
dst[3] = 255;
} else {
src = (byte *)fr.Data(4);
dst[0] = src[2];
dst[1] = src[1];
dst[2] = src[0];
dst[3] = src[3]; // ???
if (dst[3] < 255)
*palpha = true;
}
}
}
// Load the actual image data
if (palette) {
pixbytes = 1;
if (hdr->img_bitsperpixel != 8)
throw LError("%s: paletted TGA should have 8 bpp", fname);
} else {
pixbytes = 4;
if (hdr->img_bitsperpixel != 24 && hdr->img_bitsperpixel != 32)
throw LError("%s: unsupported true-color mode", fname);
}
data = (byte *)L_Malloc(hdr->img_width * hdr->img_height * pixbytes, TAG_IMAGE);
if (hdr->img_descriptor & 0x20) { // start from top bit
y = 0;
ystep = 1;
} else {
y = hdr->img_height-1;
ystep = -1;
}
ycount = hdr->img_height;
while(ycount--) {
if (hdr->img_descriptor & 0x10) { // start from right bit
x = hdr->img_width-1;
xstep = -1;
} else {
x = 0;
xstep = 1;
}
xcount = hdr->img_width;
while(xcount--) {
dst = data + (y*hdr->img_width + x)*pixbytes;
if (palette) {
dst[0] = fr.Byte();
} else if (hdr->img_bitsperpixel == 24) {
src = (byte *)fr.Data(3);
dst[0] = src[2];
dst[1] = src[1];
dst[2] = src[0];
dst[3] = 255;
} else {
src = (byte *)fr.Data(4);
dst[0] = src[2];
dst[1] = src[1];
dst[2] = src[0];
dst[3] = src[3]; // ???
if (dst[3] < 255)
*palpha = true;
}
x += xstep;
}
y += ystep;
}
}
catch(...)
{
if (palette)
L_Free(palette);
if (data)
L_Free(data);
throw;
}
*pdata = data;
*ppal = palette;
return;
}
/*
==============================================================================
BMP FILE FORMAT
==============================================================================
*/
#pragma pack(1)
typedef struct {
short magic;
unsigned size;
unsigned reserved;
unsigned offbits;
unsigned magic2;
int width;
int height;
short planes;
short bitcount;
unsigned compression;
unsigned sizeimage;
signed xpelspermeter;
signed ypelspermeter;
unsigned clrused;
unsigned clrimportant;
} bmp_header_t;
#pragma pack()
#define BMP_MAGIC 0x4D42
#define BMP_MAGIC2 40
/*
==============
LoadBMP
Loads true-color (24 bit) .bmps. Returns 0 on success, or an error
message string.
==============
*/
void LoadBMP(const char *fname, int *sizes, byte **ppal, byte **pdata)
{
bmp_header_t *hdr;
int offset;
int bytesperline;
byte *pic;
byte *dest, *src;
bool reverse;
int w, h;
int line;
*ppal = 0;
*pdata = 0;
pic = 0;
try
{
LFileRead fr;
// Load the file
fr.Open(fname);
// Process the header
hdr = (bmp_header_t *)fr.Data(sizeof(bmp_header_t));
hdr->magic = LittleShort(hdr->magic);
hdr->size = LittleInt(hdr->size);
hdr->offbits = LittleInt(hdr->offbits);
hdr->magic2 = LittleInt(hdr->magic2);
hdr->width = LittleInt(hdr->width);
hdr->height = LittleInt(hdr->height);
hdr->planes = LittleShort(hdr->planes);
hdr->bitcount = LittleShort(hdr->bitcount);
// check validity of file
if (hdr->magic != BMP_MAGIC || hdr->magic2 != BMP_MAGIC2)
throw LError("%s: not a valid BMP", fname);
// check format
if (hdr->compression || hdr->planes != 1 || hdr->bitcount != 24)
throw LError("%s: unsupported format", fname);
// check width and height
w = hdr->width;
h = hdr->height;
reverse = false;
if (h < 0)
h = -h;
else
reverse = true;
if (w > 640 || h > 480)
throw LError("%s: image is too big", fname);
bytesperline = (w*3 + 3) & ~3;
// copy into a buffer
pic = (byte *)L_Malloc(4 * w * h, TAG_IMAGE);
line = 0;
dest = pic;
while(line < h) {
int x;
if (reverse)
offset = hdr->offbits + (h - line - 1) * bytesperline;
else
offset = hdr->offbits + line * bytesperline;
fr.SetFilePos(offset);
for(x = 0; x < w; x++) {
src = (byte *)fr.Data(3);
dest[0] = src[2];
dest[1] = src[1];
dest[2] = src[0];
dest[3] = 255;
dest += 4;
offset += 3;
}
line++;
}
}
catch(...)
{
if (pic)
L_Free(pic);
throw;
}
*pdata = pic;
sizes[0] = w;
sizes[1] = h;
return;
}
/*
==============
WriteBMP
Writes the given image to a .bmp file. Alpha channel information is
dropped if alpha is set to false
==============
*/
void WriteBMP(const char *fname, const int *size, const byte *pal, const byte *data,
bool alpha)
{
LFileWrite fw;
const byte *src;
byte quad[4], fill[3];
int bpp, bpl, pad;
int i, x, y;
if (pal)
bpp = 8;
else if (alpha)
bpp = 32;
else
bpp = 24;
// Write the bitmap header
fw.Short(BMP_MAGIC);
fw.Integer(0); // size of bitmap file, fill in later
fw.Integer(0); // reserved
fw.Integer(0); // offset to image data
fw.Integer(BMP_MAGIC2);
fw.Integer(size[0]);
fw.Integer(size[1]); // apparently, some editors don't understand negative heights
fw.Short(1); // planes
fw.Short(bpp);
fw.Integer(0); // compression
fw.Integer(0); // image size, in bytes
fw.Integer(1000); // pixels per meter
fw.Integer(1000);
fw.Integer(0); // clrused
fw.Integer(0); // clrimportant
// Write the palette
if (pal) {
for(i = 0, src = pal; i < 256; i++, src += 4) {
quad[0] = src[2];
quad[1] = src[1];
quad[2] = src[0];
if (alpha)
quad[3] = src[3];
else
quad[3] = 255;
fw.Data(quad, 4);
}
}
// Fixup image offset
fw.Integer(fw.filepos, 10);
// Write the image data
bpl = bpp * size[0] / 8;
pad = (-bpl) & 3;
fill[0] = fill[1] = fill[2] = 0;
if (pal) {
y = size[1];
while(y--) {
src = data + y*size[0];
fw.Data(src, bpl);
fw.Data(fill, pad);
}
} else if (alpha) {
y = size[1];
while(y--) {
src = data + y*size[0]*4;
for(x = 0; x < size[0]; x++) {
quad[0] = src[2];
quad[1] = src[1];
quad[2] = src[0];
quad[3] = src[3];
fw.Data(quad, 4);
src += 4;
}
}
} else {
y = size[1];
while(y--) {
src = data + y*size[0]*4;
for(x = 0; x < size[0]; x++) {
quad[0] = src[2];
quad[1] = src[1];
quad[2] = src[0];
fw.Data(quad, 3);
src += 4;
}
fw.Data(fill, pad);
}
}
// Fix the image size
fw.Integer(fw.filepos, 2);
// Save the image to disk
fw.Write(fname);
}