259 lines
6.8 KiB
C++
259 lines
6.8 KiB
C++
#include "global.h"
|
|
#include "RageSurface_Load_BMP.h"
|
|
#include "RageFile.h"
|
|
#include "RageUtil.h"
|
|
#include "RageLog.h"
|
|
#include "RageSurface.h"
|
|
|
|
/* Tested with http://entropymine.com/jason/bmpsuite/. */
|
|
|
|
enum
|
|
{
|
|
COMP_BI_RGB = 0,
|
|
COMP_BI_RLE4, /* unsupported */
|
|
COMP_BI_RLE8, /* unsupported */
|
|
COMP_BI_BITFIELDS
|
|
};
|
|
|
|
struct NotBMP: public RageException { NotBMP(): RageException("not a BMP") { } };
|
|
struct FatalError: public RageException { FatalError(const CString &str): RageException(str) { } };
|
|
struct UnexpectedEOF: public RageException { UnexpectedEOF(): RageException("Unexpected EOF") { } };
|
|
|
|
|
|
static void ReadBytes( RageFile &f, void *buf, int size )
|
|
{
|
|
int ret = f.Read( buf, size );
|
|
if( ret == -1 )
|
|
throw FatalError( f.GetError() );
|
|
|
|
if( ret < size )
|
|
throw UnexpectedEOF();
|
|
}
|
|
|
|
static uint8_t read_8( RageFile &f )
|
|
{
|
|
uint8_t val;
|
|
ReadBytes( f, &val, sizeof(uint8_t) );
|
|
return val;
|
|
}
|
|
|
|
static uint16_t read_le16( RageFile &f )
|
|
{
|
|
uint16_t val;
|
|
ReadBytes( f, &val, sizeof(uint16_t) );
|
|
return Swap16LE( val );
|
|
}
|
|
|
|
static uint32_t read_le32( RageFile &f )
|
|
{
|
|
uint32_t val;
|
|
ReadBytes( f, &val, sizeof(uint32_t) );
|
|
return Swap32LE( val );
|
|
}
|
|
|
|
static RageSurface *LoadBMP( RageFile &f, RageSurface *&ret )
|
|
{
|
|
char magic[2];
|
|
ReadBytes( f, magic, 2 );
|
|
if( magic[0] != 'B' || magic[1] != 'M' )
|
|
throw NotBMP();
|
|
|
|
read_le32( f ); /* file size */
|
|
read_le32( f ); /* unused */
|
|
uint32_t iDataOffset = read_le32( f );
|
|
uint32_t iHeaderSize = read_le32( f );
|
|
|
|
uint32_t iWidth, iHeight, iPlanes, iBPP, iCompression = COMP_BI_RGB, iColors = 0;
|
|
if( iHeaderSize == 12 )
|
|
{
|
|
/* OS/2 format */
|
|
iWidth = read_le16( f );
|
|
iHeight = read_le16( f );
|
|
iPlanes = read_le16( f );
|
|
iBPP = read_le16( f );
|
|
}
|
|
else if( iHeaderSize == 40 )
|
|
{
|
|
iWidth = read_le32( f );
|
|
iHeight = read_le32( f );
|
|
iPlanes = read_le16( f );
|
|
iBPP = read_le16( f );
|
|
iCompression = read_le32( f );
|
|
read_le32( f ); /* bitmap size */
|
|
read_le32( f ); /* horiz resolution */
|
|
read_le32( f ); /* vert resolution */
|
|
iColors = read_le32( f );
|
|
read_le32( f ); /* "important" colors */
|
|
}
|
|
else
|
|
throw FatalError( ssprintf( "expected header size of 40, got %u", iHeaderSize ) );
|
|
|
|
if( iBPP <= 8 && iColors == 0 )
|
|
iColors = 1 << iBPP;
|
|
if( iPlanes != 1 )
|
|
throw FatalError( ssprintf( "expected one plane, got %u", iPlanes ) );
|
|
if( iBPP != 1 && iBPP != 4 && iBPP != 8 && iBPP != 16 && iBPP != 24 && iBPP != 32 )
|
|
throw FatalError( ssprintf( "unsupported bpp %u", iBPP ) );
|
|
if( iCompression != COMP_BI_RGB && iCompression != COMP_BI_BITFIELDS )
|
|
throw FatalError( ssprintf( "unsupported compression %u", iCompression ) );
|
|
|
|
if( iCompression == COMP_BI_BITFIELDS && iBPP <= 8 )
|
|
throw FatalError( ssprintf( "BI_BITFIELDS unexpected with bpp %u", iBPP ) );
|
|
|
|
int iFileBPP = iBPP;
|
|
iBPP = max( iBPP, 8u );
|
|
|
|
int Rmask = 0, Gmask = 0, Bmask = 0, Amask = 0;
|
|
switch( iBPP )
|
|
{
|
|
case 16:
|
|
Rmask = Swap16LE( 0x7C00 );
|
|
Gmask = Swap16LE( 0x03E0 );
|
|
Bmask = Swap16LE( 0x001F );
|
|
break;
|
|
case 24:
|
|
Rmask = Swap24LE( 0xFF0000 );
|
|
Gmask = Swap24LE( 0x00FF00 );
|
|
Bmask = Swap24LE( 0x0000FF );
|
|
break;
|
|
case 32:
|
|
Rmask = Swap32LE( 0x00FF0000 );
|
|
Gmask = Swap32LE( 0x0000FF00 );
|
|
Bmask = Swap32LE( 0x000000FF );
|
|
break;
|
|
}
|
|
|
|
if( iCompression == COMP_BI_BITFIELDS )
|
|
{
|
|
Rmask = read_le32( f );
|
|
Gmask = read_le32( f );
|
|
Bmask = read_le32( f );
|
|
}
|
|
|
|
RageSurface *img = CreateSurface( iWidth, iHeight, iBPP, Rmask, Gmask, Bmask, Amask );
|
|
|
|
try { /* file reading may throw */
|
|
if( iBPP == 8 )
|
|
{
|
|
RageSurfaceColor Palette[256];
|
|
ZERO( Palette );
|
|
|
|
if( iColors > 256 )
|
|
throw FatalError( ssprintf( "unexpected colors %i", iColors ) );
|
|
|
|
for( unsigned i = 0; i < iColors; ++i )
|
|
{
|
|
Palette[i].b = read_8( f );
|
|
Palette[i].g = read_8( f );
|
|
Palette[i].r = read_8( f );
|
|
Palette[i].a = 0xFF;
|
|
/* Windows BMP palettes are padded to 32bpp. */
|
|
if( iHeaderSize == 40 )
|
|
read_8( f );
|
|
}
|
|
|
|
memcpy( img->fmt.palette->colors, Palette, sizeof(Palette) );
|
|
}
|
|
|
|
int iFilePitch = iFileBPP * iWidth; // in bits
|
|
iFilePitch = (iFilePitch+7) / 8; // in bytes: round up
|
|
iFilePitch = (iFilePitch+3) & ~3; // round up a multiple of 4
|
|
|
|
{
|
|
int ret = f.Seek( iDataOffset );
|
|
if( ret == -1 )
|
|
throw FatalError( f.GetError() );
|
|
if( ret != (int) iDataOffset )
|
|
throw UnexpectedEOF();
|
|
}
|
|
|
|
for( int y = (int) iHeight-1; y >= 0; --y )
|
|
{
|
|
uint8_t *pRow = img->pixels + img->pitch*y;
|
|
CString buf;
|
|
|
|
f.Read( buf, iFilePitch );
|
|
|
|
/* Expand 1- and 4-bits to 8-bits. */
|
|
if( iFileBPP == 1 )
|
|
{
|
|
for( unsigned x = 0; x < iWidth; ++x )
|
|
{
|
|
int iByteNo = x >> 3;
|
|
int iBitNo = 7-(x&7);
|
|
int iBit = 1 << iBitNo;
|
|
pRow[x] = !!(buf[iByteNo] & iBit);
|
|
}
|
|
}
|
|
else if( iFileBPP == 4 )
|
|
{
|
|
for( unsigned x = 0; x < iWidth; ++x )
|
|
{
|
|
if( (x & 1) == 0 )
|
|
pRow[x] = buf[x/2] & 0x0F;
|
|
else
|
|
pRow[x] = (buf[x/2] >> 4) & 0x0F;
|
|
}
|
|
}
|
|
else
|
|
memcpy( pRow, buf.data(), img->pitch );
|
|
}
|
|
} catch(...) {
|
|
delete img;
|
|
throw;
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
RageSurfaceUtils::OpenResult RageSurface_Load_BMP( const CString &sPath, RageSurface *&ret, bool bHeaderOnly, CString &error )
|
|
{
|
|
RageFile f;
|
|
|
|
if( !f.Open( sPath ) )
|
|
{
|
|
error = f.GetError();
|
|
return RageSurfaceUtils::OPEN_FATAL_ERROR;
|
|
}
|
|
|
|
try {
|
|
ret = LoadBMP( f, ret );
|
|
} catch( const NotBMP & ) {
|
|
error = "not a BMP";
|
|
return RageSurfaceUtils::OPEN_UNKNOWN_FILE_FORMAT;
|
|
} catch( const FatalError &err ) {
|
|
error = err.what();
|
|
return RageSurfaceUtils::OPEN_FATAL_ERROR;
|
|
} catch( const UnexpectedEOF & ) {
|
|
error = "unexpected end of file";
|
|
return RageSurfaceUtils::OPEN_FATAL_ERROR;
|
|
}
|
|
|
|
return RageSurfaceUtils::OPEN_OK;
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) 2004 Glenn Maynard
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, and/or sell copies of the Software, and to permit persons to
|
|
* whom the Software is furnished to do so, provided that the above
|
|
* copyright notice(s) and this permission notice appear in all copies of
|
|
* the Software and that both the above copyright notice(s) and this
|
|
* permission notice appear in supporting documentation.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
|
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
|
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
* PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|