This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/engine/common/imagelib/img_bmp.c

484 lines
12 KiB
C
Raw Normal View History

2011-05-09 22:00:00 +02:00
/*
img_bmp.c - bmp format load & save
Copyright (C) 2007 Uncle Mike
This program 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 3 of the License, or
(at your option) any later version.
This program 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.
*/
2008-08-06 22:00:00 +02:00
2008-10-22 22:00:00 +02:00
#include "imagelib.h"
2016-08-12 23:00:00 +02:00
#include "mathlib.h"
2008-08-06 22:00:00 +02:00
/*
=============
Image_LoadBMP
=============
*/
2010-10-26 22:00:00 +02:00
qboolean Image_LoadBMP( const char *name, const byte *buffer, size_t filesize )
2008-08-06 22:00:00 +02:00
{
2009-08-29 22:00:00 +02:00
byte *buf_p, *pixbuf;
byte palette[256][4];
int i, columns, column, rows, row, bpp = 1;
2010-11-29 22:00:00 +01:00
int cbPalBytes = 0, padSize = 0, bps = 0;
2016-08-12 23:00:00 +02:00
int reflectivity[3] = { 0, 0, 0 };
2011-07-07 22:00:00 +02:00
qboolean load_qfont = false;
2008-08-06 22:00:00 +02:00
bmp_t bhdr;
2009-08-29 22:00:00 +02:00
if( filesize < sizeof( bhdr )) return false;
2008-08-06 22:00:00 +02:00
buf_p = (byte *)buffer;
bhdr.id[0] = *buf_p++;
bhdr.id[1] = *buf_p++; // move pointer
2010-11-21 22:00:00 +01:00
bhdr.fileSize = *(long *)buf_p; buf_p += 4;
bhdr.reserved0 = *(long *)buf_p; buf_p += 4;
bhdr.bitmapDataOffset = *(long *)buf_p; buf_p += 4;
bhdr.bitmapHeaderSize = *(long *)buf_p; buf_p += 4;
bhdr.width = *(long *)buf_p; buf_p += 4;
bhdr.height = *(long *)buf_p; buf_p += 4;
bhdr.planes = *(short *)buf_p; buf_p += 2;
bhdr.bitsPerPixel = *(short *)buf_p; buf_p += 2;
bhdr.compression = *(long *)buf_p; buf_p += 4;
bhdr.bitmapDataSize = *(long *)buf_p; buf_p += 4;
bhdr.hRes = *(long *)buf_p; buf_p += 4;
bhdr.vRes = *(long *)buf_p; buf_p += 4;
bhdr.colors = *(long *)buf_p; buf_p += 4;
bhdr.importantColors = *(long *)buf_p; buf_p += 4;
2009-08-29 22:00:00 +02:00
2008-08-06 22:00:00 +02:00
// bogus file header check
if( bhdr.reserved0 != 0 ) return false;
2010-07-06 22:00:00 +02:00
if( bhdr.planes != 1 ) return false;
2008-08-06 22:00:00 +02:00
2016-11-17 22:00:00 +01:00
if( memcmp( bhdr.id, "BM", 2 ))
2008-08-06 22:00:00 +02:00
{
MsgDev( D_ERROR, "Image_LoadBMP: only Windows-style BMP files supported (%s)\n", name );
return false;
}
2010-07-06 22:00:00 +02:00
if( bhdr.bitmapHeaderSize != 0x28 )
{
MsgDev( D_ERROR, "Image_LoadBMP: invalid header size %i\n", bhdr.bitmapHeaderSize );
return false;
}
2008-08-06 22:00:00 +02:00
// bogus info header check
if( bhdr.fileSize != filesize )
{
2012-06-25 22:00:00 +02:00
// Sweet Half-Life issues. splash.bmp have bogus filesize
2017-08-12 23:00:00 +02:00
MsgDev( D_REPORT, "Image_LoadBMP: %s have incorrect file size %i should be %i\n", name, filesize, bhdr.fileSize );
2008-08-06 22:00:00 +02:00
}
// bogus compression? Only non-compressed supported.
if( bhdr.compression != BI_RGB )
{
2009-08-29 22:00:00 +02:00
MsgDev( D_ERROR, "Image_LoadBMP: only uncompressed BMP files supported (%s)\n", name );
2008-08-06 22:00:00 +02:00
return false;
}
2010-03-06 22:00:00 +01:00
2010-07-06 22:00:00 +02:00
image.width = columns = bhdr.width;
image.height = rows = abs( bhdr.height );
2008-08-06 22:00:00 +02:00
2011-07-07 22:00:00 +02:00
if( !Image_ValidSize( name ))
2008-08-06 22:00:00 +02:00
return false;
2018-03-13 22:00:00 +01:00
// special case for loading qfont (menu font)
2018-03-11 22:00:00 +01:00
if( !Q_strncmp( name, "#XASH_SYSTEMFONT_001", 20 ))
2011-07-07 22:00:00 +02:00
{
// NOTE: same as system font we can use 4-bit bmps only
// step1: move main layer into alpha-channel (give grayscale from RED channel)
// step2: fill main layer with 255 255 255 color (white)
// step3: ????
// step4: PROFIT!!! (economy up to 150 kb for menu.dll final size)
image.flags |= IMAGE_HAS_ALPHA;
load_qfont = true;
}
2010-07-06 22:00:00 +02:00
if( bhdr.bitsPerPixel <= 8 )
2008-08-06 22:00:00 +02:00
{
2012-12-16 21:00:00 +01:00
// figure out how many entries are actually in the table
2009-08-29 22:00:00 +02:00
if( bhdr.colors == 0 )
{
bhdr.colors = 256;
cbPalBytes = (1 << bhdr.bitsPerPixel) * sizeof( RGBQUAD );
}
else cbPalBytes = bhdr.colors * sizeof( RGBQUAD );
2008-08-06 22:00:00 +02:00
}
2016-11-17 22:00:00 +01:00
memcpy( palette, buf_p, cbPalBytes );
2010-02-21 22:00:00 +01:00
2018-03-11 22:00:00 +01:00
// setup gradient alpha for player decal
if( !Q_strncmp( name, "#logo", 5 ))
{
for( i = 0; i < bhdr.colors; i++ )
palette[i][3] = i;
image.flags |= IMAGE_HAS_ALPHA;
}
2018-03-13 22:00:00 +01:00
if( Image_CheckFlag( IL_OVERVIEW ) && bhdr.bitsPerPixel == 8 )
2011-08-21 22:00:00 +02:00
{
// convert green background into alpha-layer, make opacity for all other entries
for( i = 0; i < bhdr.colors; i++ )
{
if( palette[i][0] == 0 && palette[i][1] == 255 && palette[i][2] == 0 )
{
palette[i][0] = palette[i][1] = palette[i][2] = palette[i][3] = 0;
image.flags |= IMAGE_HAS_ALPHA;
}
else palette[i][3] = 255;
}
}
2011-10-09 22:00:00 +02:00
if( Image_CheckFlag( IL_KEEP_8BIT ) && bhdr.bitsPerPixel == 8 )
2008-08-06 22:00:00 +02:00
{
2018-05-26 23:00:00 +02:00
pixbuf = image.palette = Mem_Malloc( host.imagepool, 1024 );
2018-03-11 22:00:00 +01:00
2009-08-29 22:00:00 +02:00
// bmp have a reversed palette colors
for( i = 0; i < bhdr.colors; i++ )
{
*pixbuf++ = palette[i][2];
*pixbuf++ = palette[i][1];
*pixbuf++ = palette[i][0];
*pixbuf++ = palette[i][3];
}
image.type = PF_INDEXED_32; // 32 bit palette
2008-08-06 22:00:00 +02:00
}
2009-08-29 22:00:00 +02:00
else
2008-08-06 22:00:00 +02:00
{
2010-02-21 22:00:00 +01:00
image.palette = NULL;
2009-08-29 22:00:00 +02:00
image.type = PF_RGBA_32;
bpp = 4;
2008-08-06 22:00:00 +02:00
}
2009-08-29 22:00:00 +02:00
buf_p += cbPalBytes;
2010-03-06 22:00:00 +01:00
image.size = image.width * image.height * bpp;
2018-05-26 23:00:00 +02:00
image.rgba = Mem_Malloc( host.imagepool, image.size );
2010-11-29 22:00:00 +01:00
bps = image.width * (bhdr.bitsPerPixel >> 3);
2010-07-06 22:00:00 +02:00
switch( bhdr.bitsPerPixel )
{
case 1:
padSize = (( 32 - ( bhdr.width % 32 )) / 8 ) % 4;
break;
case 4:
padSize = (( 8 - ( bhdr.width % 8 )) / 2 ) % 4;
break;
case 16:
padSize = ( 4 - ( image.width * 2 % 4 )) % 4;
break;
case 8:
case 24:
2010-11-29 22:00:00 +01:00
padSize = ( 4 - ( bps % 4 )) % 4;
2010-07-06 22:00:00 +02:00
break;
}
2008-08-06 22:00:00 +02:00
2009-08-29 22:00:00 +02:00
for( row = rows - 1; row >= 0; row-- )
2008-08-06 22:00:00 +02:00
{
2009-08-29 22:00:00 +02:00
pixbuf = image.rgba + (row * columns * bpp);
for( column = 0; column < columns; column++ )
2008-08-06 22:00:00 +02:00
{
2009-08-29 22:00:00 +02:00
byte red, green, blue, alpha;
word shortPixel;
2010-07-06 22:00:00 +02:00
int c, k, palIndex;
2009-08-29 22:00:00 +02:00
switch( bhdr.bitsPerPixel )
{
2010-07-06 22:00:00 +02:00
case 1:
alpha = *buf_p++;
column--; // ingnore main iterations
for( c = 0, k = 128; c < 8; c++, k >>= 1 )
{
2011-10-28 22:00:00 +02:00
red = green = blue = (!!(alpha & k) == 1 ? 0xFF : 0x00);
2010-07-06 22:00:00 +02:00
*pixbuf++ = red;
2011-10-28 22:00:00 +02:00
*pixbuf++ = green;
*pixbuf++ = blue;
2010-07-06 22:00:00 +02:00
*pixbuf++ = 0x00;
if( ++column == columns )
break;
}
break;
case 4:
alpha = *buf_p++;
palIndex = alpha >> 4;
2011-07-07 22:00:00 +02:00
if( load_qfont )
{
*pixbuf++ = red = 255;
*pixbuf++ = green = 255;
*pixbuf++ = blue = 255;
*pixbuf++ = palette[palIndex][2];
}
else
{
*pixbuf++ = red = palette[palIndex][2];
*pixbuf++ = green = palette[palIndex][1];
*pixbuf++ = blue = palette[palIndex][0];
*pixbuf++ = palette[palIndex][3];
}
2010-07-06 22:00:00 +02:00
if( ++column == columns ) break;
palIndex = alpha & 0x0F;
2011-07-07 22:00:00 +02:00
if( load_qfont )
{
*pixbuf++ = red = 255;
*pixbuf++ = green = 255;
*pixbuf++ = blue = 255;
*pixbuf++ = palette[palIndex][2];
}
else
{
*pixbuf++ = red = palette[palIndex][2];
*pixbuf++ = green = palette[palIndex][1];
*pixbuf++ = blue = palette[palIndex][0];
*pixbuf++ = palette[palIndex][3];
}
2010-07-06 22:00:00 +02:00
break;
2009-08-29 22:00:00 +02:00
case 8:
2010-03-07 22:00:00 +01:00
palIndex = *buf_p++;
red = palette[palIndex][2];
green = palette[palIndex][1];
blue = palette[palIndex][0];
alpha = palette[palIndex][3];
2010-07-06 22:00:00 +02:00
2011-10-09 22:00:00 +02:00
if( Image_CheckFlag( IL_KEEP_8BIT ))
2009-08-29 22:00:00 +02:00
{
*pixbuf++ = palIndex;
}
else
{
2010-03-07 22:00:00 +01:00
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alpha;
2009-08-29 22:00:00 +02:00
}
break;
case 16:
shortPixel = *(word *)buf_p, buf_p += 2;
*pixbuf++ = blue = (shortPixel & ( 31 << 10 )) >> 7;
*pixbuf++ = green = (shortPixel & ( 31 << 5 )) >> 2;
*pixbuf++ = red = (shortPixel & ( 31 )) << 3;
*pixbuf++ = 0xff;
break;
case 24:
blue = *buf_p++;
green = *buf_p++;
red = *buf_p++;
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 0xFF;
break;
case 32:
blue = *buf_p++;
green = *buf_p++;
red = *buf_p++;
alpha = *buf_p++;
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alpha;
2010-03-07 22:00:00 +01:00
if( alpha != 255 ) image.flags |= IMAGE_HAS_ALPHA;
2009-08-29 22:00:00 +02:00
break;
default:
MsgDev( D_ERROR, "Image_LoadBMP: illegal pixel_size (%s)\n", name );
Mem_Free( image.palette );
Mem_Free( image.rgba );
return false;
}
2016-08-12 23:00:00 +02:00
2017-03-23 22:00:00 +01:00
if( red != green || green != blue )
2009-08-29 22:00:00 +02:00
image.flags |= IMAGE_HAS_COLOR;
2016-08-12 23:00:00 +02:00
reflectivity[0] += red;
reflectivity[1] += green;
reflectivity[2] += blue;
2008-08-06 22:00:00 +02:00
}
2010-07-06 22:00:00 +02:00
buf_p += padSize; // actual only for 4-bit bmps
2008-08-06 22:00:00 +02:00
}
2016-08-12 23:00:00 +02:00
VectorDivide( reflectivity, ( image.width * image.height ), image.fogParams );
2010-02-21 22:00:00 +01:00
if( image.palette ) Image_GetPaletteBMP( image.palette );
2016-09-20 23:00:00 +02:00
image.depth = 1;
2009-08-29 22:00:00 +02:00
return true;
2008-08-06 22:00:00 +02:00
}
2010-10-26 22:00:00 +02:00
qboolean Image_SaveBMP( const char *name, rgbdata_t *pix )
2008-08-06 22:00:00 +02:00
{
file_t *pfile = NULL;
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER bmih;
2012-01-27 21:00:00 +01:00
size_t total_size, cur_size;
2011-08-21 22:00:00 +02:00
RGBQUAD rgrgbPalette[256];
2008-08-06 22:00:00 +02:00
dword cbBmpBits;
2012-01-27 21:00:00 +01:00
byte *clipbuf = NULL;
2011-02-15 22:00:00 +01:00
byte *pb, *pbBmpBits;
2011-08-21 22:00:00 +02:00
dword cbPalBytes = 0;
2008-08-06 22:00:00 +02:00
dword biTrueWidth;
2011-02-15 22:00:00 +01:00
int pixel_size;
int i, x, y;
2008-08-06 22:00:00 +02:00
2012-01-27 21:00:00 +01:00
if( FS_FileExists( name, false ) && !Image_CheckFlag( IL_ALLOW_OVERWRITE ) && !host.write_to_clipboard )
2008-08-08 22:00:00 +02:00
return false; // already existed
2008-08-06 22:00:00 +02:00
// bogus parameter check
2011-02-15 22:00:00 +01:00
if( !pix->buffer )
2008-08-06 22:00:00 +02:00
return false;
2011-02-15 22:00:00 +01:00
// get image description
2008-08-06 22:00:00 +02:00
switch( pix->type )
{
2011-08-21 22:00:00 +02:00
case PF_INDEXED_24:
case PF_INDEXED_32:
pixel_size = 1;
break;
2011-02-15 22:00:00 +01:00
case PF_RGB_24:
pixel_size = 3;
2008-08-06 22:00:00 +02:00
break;
2011-02-15 22:00:00 +01:00
case PF_RGBA_32:
pixel_size = 4;
break;
2008-08-06 22:00:00 +02:00
default:
2011-02-15 22:00:00 +01:00
MsgDev( D_ERROR, "Image_SaveBMP: unsupported image type %s\n", PFDesc[pix->type].name );
2008-08-06 22:00:00 +02:00
return false;
}
2012-01-27 21:00:00 +01:00
if( !host.write_to_clipboard )
{
pfile = FS_Open( name, "wb", false );
if( !pfile ) return false;
}
2008-10-27 22:00:00 +01:00
// NOTE: align transparency column will sucessfully removed
2008-08-06 22:00:00 +02:00
// after create sprite or lump image, it's just standard requiriments
biTrueWidth = ((pix->width + 3) & ~3);
2011-02-15 22:00:00 +01:00
cbBmpBits = biTrueWidth * pix->height * pixel_size;
2011-08-21 22:00:00 +02:00
if( pixel_size == 1 ) cbPalBytes = 256 * sizeof( RGBQUAD );
2008-08-06 22:00:00 +02:00
// Bogus file header check
bmfh.bfType = MAKEWORD( 'B', 'M' );
2011-08-21 22:00:00 +02:00
bmfh.bfSize = sizeof( bmfh ) + sizeof( bmih ) + cbBmpBits + cbPalBytes;
2008-08-06 22:00:00 +02:00
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
2011-08-21 22:00:00 +02:00
bmfh.bfOffBits = sizeof( bmfh ) + sizeof( bmih ) + cbPalBytes;
2008-08-06 22:00:00 +02:00
2012-01-27 21:00:00 +01:00
if( host.write_to_clipboard )
{
2012-03-24 21:00:00 +01:00
// NOTE: the cbPalBytes may be 0
2012-01-27 21:00:00 +01:00
total_size = sizeof( bmih ) + cbPalBytes + cbBmpBits;
clipbuf = Z_Malloc( total_size );
cur_size = 0;
}
else
{
// write header
FS_Write( pfile, &bmfh, sizeof( bmfh ));
}
2008-08-06 22:00:00 +02:00
// size of structure
2011-08-21 22:00:00 +02:00
bmih.biSize = sizeof( bmih );
2008-08-06 22:00:00 +02:00
bmih.biWidth = biTrueWidth;
bmih.biHeight = pix->height;
bmih.biPlanes = 1;
2011-08-21 22:00:00 +02:00
bmih.biBitCount = pixel_size * 8;
2008-08-06 22:00:00 +02:00
bmih.biCompression = BI_RGB;
2011-04-17 22:00:00 +02:00
bmih.biSizeImage = cbBmpBits;
2008-08-06 22:00:00 +02:00
bmih.biXPelsPerMeter = 0;
bmih.biYPelsPerMeter = 0;
2011-08-21 22:00:00 +02:00
bmih.biClrUsed = ( pixel_size == 1 ) ? 256 : 0;
2008-08-06 22:00:00 +02:00
bmih.biClrImportant = 0;
2012-01-27 21:00:00 +01:00
if( host.write_to_clipboard )
{
2016-11-17 22:00:00 +01:00
memcpy( clipbuf + cur_size, &bmih, sizeof( bmih ));
2012-01-27 21:00:00 +01:00
cur_size += sizeof( bmih );
}
else
{
// Write info header
FS_Write( pfile, &bmih, sizeof( bmih ));
}
2008-08-06 22:00:00 +02:00
2018-05-26 23:00:00 +02:00
pbBmpBits = Mem_Malloc( host.imagepool, cbBmpBits );
2008-08-06 22:00:00 +02:00
2011-08-21 22:00:00 +02:00
if( pixel_size == 1 )
{
pb = pix->palette;
// copy over used entries
for( i = 0; i < (int)bmih.biClrUsed; i++ )
{
rgrgbPalette[i].rgbRed = *pb++;
rgrgbPalette[i].rgbGreen = *pb++;
rgrgbPalette[i].rgbBlue = *pb++;
// bmp feature - can store 32-bit palette if present
// some viewers e.g. fimg.exe can show alpha-chanell for it
if( pix->type == PF_INDEXED_32 )
rgrgbPalette[i].rgbReserved = *pb++;
else rgrgbPalette[i].rgbReserved = 0;
}
2012-01-27 21:00:00 +01:00
if( host.write_to_clipboard )
{
2016-11-17 22:00:00 +01:00
memcpy( clipbuf + cur_size, rgrgbPalette, cbPalBytes );
2012-01-27 21:00:00 +01:00
cur_size += cbPalBytes;
}
else
{
// write palette
FS_Write( pfile, rgrgbPalette, cbPalBytes );
}
2011-08-21 22:00:00 +02:00
}
2008-08-06 22:00:00 +02:00
pb = pix->buffer;
2011-02-15 22:00:00 +01:00
for( y = 0; y < bmih.biHeight; y++ )
2008-08-06 22:00:00 +02:00
{
2011-02-15 22:00:00 +01:00
i = (bmih.biHeight - 1 - y ) * (bmih.biWidth);
2013-10-19 22:00:00 +02:00
for( x = 0; x < pix->width; x++ )
2011-02-15 22:00:00 +01:00
{
2011-08-21 22:00:00 +02:00
if( pixel_size == 1 )
{
// 8-bit
pbBmpBits[i] = pb[x];
}
else
{
// 24 bit
pbBmpBits[i*pixel_size+0] = pb[x*pixel_size+2];
pbBmpBits[i*pixel_size+1] = pb[x*pixel_size+1];
pbBmpBits[i*pixel_size+2] = pb[x*pixel_size+0];
}
2011-02-15 22:00:00 +01:00
if( pixel_size == 4 ) // write alpha channel
pbBmpBits[i*pixel_size+3] = pb[x*pixel_size+3];
i++;
}
2011-08-21 22:00:00 +02:00
2013-10-19 22:00:00 +02:00
pb += pix->width * pixel_size;
2008-08-06 22:00:00 +02:00
}
2012-01-27 21:00:00 +01:00
if( host.write_to_clipboard )
{
2016-11-17 22:00:00 +01:00
memcpy( clipbuf + cur_size, pbBmpBits, cbBmpBits );
2012-01-27 21:00:00 +01:00
cur_size += cbBmpBits;
Sys_SetClipboardData( clipbuf, total_size );
Z_Free( clipbuf );
}
else
{
// write bitmap bits (remainder of file)
FS_Write( pfile, pbBmpBits, cbBmpBits );
FS_Close( pfile );
}
2008-08-06 22:00:00 +02:00
Mem_Free( pbBmpBits );
return true;
}