gtkradiant/plugins/image/lbmlib.cpp

761 lines
18 KiB
C++

/*
Copyright (C) 1999-2007 id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.
This file is part of GtkRadiant.
GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
// lbmlib.c
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include "image.h"
#include "lbmlib.h"
#include "bmp.h"
#define LittleLong( a ) GINT32_FROM_LE( a )
#define LittleShort( a ) GINT16_FROM_LE( a )
#include <stdio.h>
#define Sys_Printf g_FuncTable.m_pfnSysPrintf
#define Sys_FPrintf g_FuncTable.m_pfnSysFPrintf
/*
============================================================================
LOAD PCX
============================================================================
*/
typedef struct
{
char manufacturer;
char version;
char encoding;
char bits_per_pixel;
unsigned short xmin, ymin, xmax, ymax;
unsigned short hres, vres;
unsigned char palette[48];
char reserved;
char color_planes;
unsigned short bytes_per_line;
unsigned short palette_type;
char filler[58];
unsigned char data; // unbounded
} pcx_t;
/*
============================================================================
TARGA IMAGE
============================================================================
*/
typedef struct _TargaHeader
{
unsigned char id_length, colormap_type, image_type;
unsigned short colormap_index, colormap_length;
unsigned char colormap_size;
unsigned short x_origin, y_origin, width, height;
unsigned char pixel_size, attributes;
} TargaHeader;
/*
=========================================================
BMP LOADING
=========================================================
*/
typedef struct
{
char id[2];
unsigned long fileSize;
unsigned long reserved0;
unsigned long bitmapDataOffset;
unsigned long bitmapHeaderSize;
unsigned long width;
unsigned long height;
unsigned short planes;
unsigned short bitsPerPixel;
unsigned long compression;
unsigned long bitmapDataSize;
unsigned long hRes;
unsigned long vRes;
unsigned long colors;
unsigned long importantColors;
unsigned char palette[256][4];
} BMPHeader_t;
static void LoadBMP( const char *name, byte ** pic, int *width, int *height ){
int columns, rows, numPixels;
byte *pixbuf;
int row, column;
byte *buf_p;
byte *buffer;
unsigned int length;
BMPHeader_t bmpHeader;
byte *bmpRGBA;
*pic = NULL;
//
// load the file
//
length = vfsLoadFile( (char *)name, (void **)&buffer, 0 );
if ( length == (unsigned int) -1 ) {
return;
}
buf_p = buffer;
bmpHeader.id[0] = *buf_p++;
bmpHeader.id[1] = *buf_p++;
bmpHeader.fileSize = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.reserved0 = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.bitmapDataOffset = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.bitmapHeaderSize = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.width = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.height = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.planes = LittleShort( *(short *) buf_p );
buf_p += 2;
bmpHeader.bitsPerPixel = LittleShort( *(short *) buf_p );
buf_p += 2;
bmpHeader.compression = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.bitmapDataSize = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.hRes = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.vRes = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.colors = LittleLong( *(long *) buf_p );
buf_p += 4;
bmpHeader.importantColors = LittleLong( *(long *) buf_p );
buf_p += 4;
memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) );
if ( bmpHeader.bitsPerPixel == 8 ) {
buf_p += 1024;
}
if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' ) {
Sys_Printf( "LoadBMP: only Windows-style BMP files supported (%s)\n", name );
return;
}
if ( bmpHeader.fileSize != length ) {
Sys_Printf( "LoadBMP: header size does not match file size (%d vs. %d) (%s)\n",
bmpHeader.fileSize, length, name );
return;
}
if ( bmpHeader.compression != 0 ) {
Sys_Printf( "LoadBMP: only uncompressed BMP files supported (%s)\n", name );
return;
}
if ( bmpHeader.bitsPerPixel < 8 ) {
Sys_Printf( "LoadBMP: monochrome and 4-bit BMP files not supported (%s)\n", name );
return;
}
columns = bmpHeader.width;
rows = bmpHeader.height;
if ( rows < 0 ) {
rows = -rows;
}
numPixels = columns * rows;
if ( width ) {
*width = columns;
}
if ( height ) {
*height = rows;
}
bmpRGBA = reinterpret_cast < unsigned char *>( g_malloc( numPixels * 4 ) );
*pic = bmpRGBA;
for ( row = rows - 1; row >= 0; row-- )
{
pixbuf = bmpRGBA + row * columns * 4;
for ( column = 0; column < columns; column++ )
{
unsigned char red, green, blue, alpha;
int palIndex;
unsigned short shortPixel;
switch ( bmpHeader.bitsPerPixel )
{
case 8:
palIndex = *buf_p++;
*pixbuf++ = bmpHeader.palette[palIndex][2];
*pixbuf++ = bmpHeader.palette[palIndex][1];
*pixbuf++ = bmpHeader.palette[palIndex][0];
*pixbuf++ = 0xff;
break;
case 16:
shortPixel = *(unsigned short *) pixbuf;
pixbuf += 2;
*pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7;
*pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2;
*pixbuf++ = ( shortPixel & ( 31 ) ) << 3;
*pixbuf++ = 0xff;
break;
case 24:
blue = *buf_p++;
green = *buf_p++;
red = *buf_p++;
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 255;
break;
case 32:
blue = *buf_p++;
green = *buf_p++;
red = *buf_p++;
alpha = *buf_p++;
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alpha;
break;
default:
Sys_Printf( "LoadBMP: illegal pixel_size '%d' in file '%s'\n", bmpHeader.bitsPerPixel,
name );
g_free( *pic );
*pic = NULL;
return;
break;
}
}
}
vfsFreeFile( buffer );
}
/*
=================================================================
PCX LOADING
=================================================================
*/
/*
==============
LoadPCX
==============
*/
/* RR2DO2 */
#define DECODEPCX( b, d, r ) d = *b++; if ( ( d & 0xC0 ) == 0xC0 ) {r = d & 0x3F; d = *b++; }else{r = 1; }
static void LoadPCX( const char *filename, byte **pic, byte **palette, int *width, int *height ){
byte *raw;
pcx_t *pcx;
int x, y, lsize;
int len;
int dataByte, runLength;
byte *out, *pix;
/* load the file */
len = vfsLoadFile( filename, (void **)&raw, 0 );
if ( len == -1 ) {
Error( "LoadPCX: Couldn't read %s", filename );
}
/* parse the PCX file */
pcx = (pcx_t *)raw;
raw = &pcx->data;
pcx->xmin = LittleShort( pcx->xmin );
pcx->ymin = LittleShort( pcx->ymin );
pcx->xmax = LittleShort( pcx->xmax );
pcx->ymax = LittleShort( pcx->ymax );
pcx->hres = LittleShort( pcx->hres );
pcx->vres = LittleShort( pcx->vres );
pcx->bytes_per_line = LittleShort( pcx->bytes_per_line );
pcx->palette_type = LittleShort( pcx->palette_type );
if ( pcx->manufacturer != 0x0a
|| pcx->version != 5
|| pcx->encoding != 1
|| pcx->bits_per_pixel != 8
|| pcx->xmax >= 640
|| pcx->ymax >= 480 ) {
Error( "Bad pcx file %s", filename );
}
if ( palette ) {
*palette = (byte *)malloc( 768 );
memcpy( *palette, (byte *)pcx + len - 768, 768 );
}
if ( width ) {
*width = pcx->xmax + 1;
}
if ( height ) {
*height = pcx->ymax + 1;
}
if ( !pic ) {
return;
}
out = (byte *)malloc( ( pcx->ymax + 1 ) * ( pcx->xmax + 1 ) );
if ( !out ) {
Error( "LoadPCX: couldn't allocate" );
}
*pic = out;
pix = out;
/* RR2DO2: pcx fix */
lsize = pcx->color_planes * pcx->bytes_per_line;
/* go scanline by scanline */
for ( y = 0; y <= pcx->ymax; y++, pix += pcx->xmax + 1 )
{
/* do a scanline */
for ( x = 0; x <= pcx->xmax; )
{
/* RR2DO2 */
DECODEPCX( raw, dataByte, runLength );
while ( runLength-- > 0 )
pix[ x++ ] = dataByte;
}
/* RR2DO2: discard any other data */
while ( x < lsize )
{
DECODEPCX( raw, dataByte, runLength );
x++;
}
while ( runLength-- > 0 )
x++;
}
/* validity check */
if ( raw - (byte *) pcx > len ) {
Error( "PCX file %s was malformed", filename );
}
free( pcx );
}
/*
==============
LoadPCX32
==============
*/
static void LoadPCX32( const char *filename, byte ** pic, int *width, int *height ){
byte *palette;
byte *pic8;
int i, c, p;
byte *pic32;
LoadPCX( filename, &pic8, &palette, width, height );
if ( !pic8 ) {
*pic = NULL;
return;
}
c = ( *width ) * ( *height );
pic32 = *pic = reinterpret_cast < unsigned char *>( g_malloc( 4 * c ) );
for ( i = 0; i < c; i++ )
{
p = pic8[i];
pic32[0] = palette[p * 3];
pic32[1] = palette[p * 3 + 1];
pic32[2] = palette[p * 3 + 2];
pic32[3] = 255;
pic32 += 4;
}
g_free( pic8 );
g_free( palette );
}
/*
=========================================================
TARGA LOADING
TTimo: added code to get rid of alphachannel from prefs or ignore it if completely empty
was required since Radiant is using alpha channel when binding the textures for proper curry operation
can be fully turned off from the prefs though
=========================================================
*/
/*
=============
LoadTGA
=============
*/
void LoadTGA( const char *name, byte ** pic, int *width, int *height ){
int columns, rows, numPixels;
byte *pixbuf;
int row, column;
byte *buf_p;
byte *buffer;
TargaHeader targa_header;
byte *targa_rgba;
*pic = NULL;
//
// load the file
//
int nLen = vfsLoadFile( (char *)name, (void **)&buffer, 0 );
if ( nLen == -1 ) {
return;
}
buf_p = buffer;
targa_header.id_length = *buf_p++;
targa_header.colormap_type = *buf_p++;
targa_header.image_type = *buf_p++;
targa_header.colormap_index = LittleShort( *(short *) buf_p );
buf_p += 2;
targa_header.colormap_length = LittleShort( *(short *) buf_p );
buf_p += 2;
targa_header.colormap_size = *buf_p++;
targa_header.x_origin = LittleShort( *(short *) buf_p );
buf_p += 2;
targa_header.y_origin = LittleShort( *(short *) buf_p );
buf_p += 2;
targa_header.width = LittleShort( *(short *) buf_p );
buf_p += 2;
targa_header.height = LittleShort( *(short *) buf_p );
buf_p += 2;
targa_header.pixel_size = *buf_p++;
targa_header.attributes = *buf_p++;
bool bAlphaOK = false;
if ( targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3 ) {
Sys_Printf( "LoadTGA: TGA type %d not supported\n", targa_header.image_type );
Sys_Printf( "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n" );
return;
}
if ( targa_header.colormap_type != 0 ) {
Sys_Printf( "LoadTGA: colormaps not supported\n" );
return;
}
if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 )
&& targa_header.image_type != 3 ) {
Sys_Printf( "LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n" );
return;
}
columns = targa_header.width;
rows = targa_header.height;
numPixels = columns * rows;
if ( width ) {
*width = columns;
}
if ( height ) {
*height = rows;
}
targa_rgba = reinterpret_cast < unsigned char *>( g_malloc( numPixels * 4 ) );
*pic = targa_rgba;
if ( targa_header.id_length != 0 ) {
buf_p += targa_header.id_length; // skip TARGA image comment
}
if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) {
// Uncompressed RGB or gray scale image
for ( row = rows - 1; row >= 0; row-- )
{
pixbuf = targa_rgba + row * columns * 4;
for ( column = 0; column < columns; column++ )
{
unsigned char red, green, blue, alphabyte;
switch ( targa_header.pixel_size )
{
case 8:
blue = *buf_p++;
green = blue;
red = blue;
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 255;
break;
case 24:
blue = *buf_p++;
green = *buf_p++;
red = *buf_p++;
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 255;
break;
case 32:
blue = *buf_p++;
green = *buf_p++;
red = *buf_p++;
alphabyte = *buf_p++;
// detect if the whole alpha channel is 0
if ( alphabyte != 0 ) {
bAlphaOK = true;
}
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
break;
default:
Sys_Printf( "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size,
name );
g_free( *pic );
*pic = NULL;
return;
break;
}
}
}
if ( !bAlphaOK ) {
if ( targa_header.pixel_size == 32 ) {
Sys_FPrintf( SYS_WRN, "WARNING: %s has empty alpha channel\n", name );
}
// disable the alpha value
for ( row = rows - 1; row >= 0; row-- )
{
pixbuf = targa_rgba + row * columns * 4;
for ( column = 0; column < columns; column++ )
{
// 32 bit
pixbuf += 3;
*pixbuf++ = 255;
}
}
}
}
else if ( targa_header.image_type == 10 ) { // Runlength encoded RGB images
unsigned char red, green, blue, alphabyte, packetHeader, packetSize, j;
red = 0;
green = 0;
blue = 0;
alphabyte = 0xff;
for ( row = rows - 1; row >= 0; row-- )
{
pixbuf = targa_rgba + row * columns * 4;
for ( column = 0; column < columns; )
{
packetHeader = *buf_p++;
packetSize = 1 + ( packetHeader & 0x7f );
if ( packetHeader & 0x80 ) { // run-length packet
switch ( targa_header.pixel_size )
{
case 24:
blue = *buf_p++;
green = *buf_p++;
red = *buf_p++;
alphabyte = 255;
break;
case 32:
blue = *buf_p++;
green = *buf_p++;
red = *buf_p++;
alphabyte = *buf_p++;
// detect if the whole alpha channel is 0
if ( alphabyte != 0 ) {
bAlphaOK = true;
}
break;
default:
Sys_Printf( "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size,
name );
g_free( *pic );
*pic = NULL;
return;
break;
}
for ( j = 0; j < packetSize; j++ )
{
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
column++;
if ( column == columns ) { // run spans across rows
column = 0;
if ( row > 0 ) {
row--;
}
else{
goto breakOut;
}
pixbuf = targa_rgba + row * columns * 4;
}
}
}
else
{ // non run-length packet
for ( j = 0; j < packetSize; j++ )
{
switch ( targa_header.pixel_size )
{
case 24:
blue = *buf_p++;
green = *buf_p++;
red = *buf_p++;
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 255;
break;
case 32:
blue = *buf_p++;
green = *buf_p++;
red = *buf_p++;
alphabyte = *buf_p++;
// detect if the whole alpha channel is 0
if ( alphabyte != 0 ) {
bAlphaOK = true;
}
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
break;
default:
Sys_Printf( "LoadTGA: illegal pixel_size '%d' in file '%s'\n",
targa_header.pixel_size, name );
g_free( *pic );
*pic = NULL;
return;
break;
}
column++;
if ( column == columns ) { // pixel packet run spans across rows
column = 0;
if ( row > 0 ) {
row--;
}
else{
goto breakOut;
}
pixbuf = targa_rgba + row * columns * 4;
}
}
}
}
breakOut:;
}
if ( !bAlphaOK ) {
if ( targa_header.pixel_size == 32 ) {
Sys_FPrintf( SYS_WRN, "WARNING: %s has empty alpha channel\n", name );
}
// disable the alpha value
for ( row = rows - 1; row >= 0; row-- )
{
pixbuf = targa_rgba + row * columns * 4;
for ( column = 0; column < columns; column++ )
{
// 32 bit
pixbuf += 3;
*pixbuf++ = 255;
}
}
}
}
// vertically flipped
if ( ( targa_header.attributes & ( 1 << 5 ) ) ) {
int flip;
for ( row = 0; row < .5f * rows; row++ )
{
for ( column = 0; column < columns; column++ )
{
flip = *( (int*)targa_rgba + row * columns + column );
*( (int*)targa_rgba + row * columns + column ) = *( (int*)targa_rgba + ( ( rows - 1 ) - row ) * columns + column );
*( (int*)targa_rgba + ( ( rows - 1 ) - row ) * columns + column ) = flip;
}
}
}
vfsFreeFile( buffer );
}
//===================================================================
/*
=================
LoadImage
Loads any of the supported image types into a cannonical
32 bit format.
=================
*/
void LoadImage( const char *name, byte ** pic, int *width, int *height ){
int len;
*pic = NULL;
*width = 0;
*height = 0;
len = strlen( name );
if ( len < 5 ) {
return;
}
if ( !g_ascii_strcasecmp( name + len - 4, ".tga" ) ) {
LoadTGA( name, pic, width, height );
}
else if ( !g_ascii_strcasecmp( name + len - 4, ".pcx" ) ) {
LoadPCX32( name, pic, width, height );
}
else if ( !g_ascii_strcasecmp( name + len - 4, ".bmp" ) ) {
LoadBMP( name, pic, width, height );
}
/*
else if (!g_ascii_strcasecmp (name + len - 4, ".jpg"))
{
LoadJPG (name, pic, width, height);
}
*/
}