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);
}