scummvm/engines/grim/bitmap.cpp

560 lines
14 KiB
C++
Raw Normal View History

2012-01-06 23:29:45 +01:00
/* ResidualVM - A 3D game interpreter
*
2012-01-06 23:29:45 +01:00
* ResidualVM is the legal property of its developers, whose names
2011-04-16 14:12:44 +02:00
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
2006-04-02 14:20:45 +00:00
*
* 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 2
* of the License, or (at your option) any later version.
2014-04-05 18:18:42 +02:00
*
* This program is distributed in the hope that it will be useful,
2006-04-02 14:20:45 +00:00
* 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.
2014-04-05 18:18:42 +02:00
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2006-04-02 14:20:45 +00:00
*
*/
2003-08-15 18:00:22 +00:00
#include "common/endian.h"
#include "graphics/colormasks.h"
#include "graphics/pixelbuffer.h"
2014-04-05 18:18:42 +02:00
#include "image/tga.h"
#include "engines/grim/savegame.h"
#include "engines/grim/debug.h"
#include "engines/grim/bitmap.h"
#include "engines/grim/resource.h"
#include "engines/grim/gfx_base.h"
2009-05-25 06:49:57 +00:00
namespace Grim {
static bool decompress_codec3(const char *compressed, char *result, int maxBytes);
2003-08-15 18:00:22 +00:00
2014-05-29 15:35:46 -07:00
Common::HashMap<Common::String, BitmapData *> *BitmapData::_bitmaps = nullptr;
2011-03-21 17:18:04 +01:00
BitmapData *BitmapData::getBitmapData(const Common::String &fname) {
Common::String str(fname);
if (_bitmaps && _bitmaps->contains(str)) {
BitmapData *b = (*_bitmaps)[str];
++b->_refCount;
return b;
}
BitmapData *b = new BitmapData(fname);
if (!_bitmaps) {
_bitmaps = new Common::HashMap<Common::String, BitmapData *>();
}
(*_bitmaps)[str] = b;
return b;
}
BitmapData::BitmapData(const Common::String &fname) {
_fname = fname;
_refCount = 1;
2014-05-29 15:35:46 -07:00
_data = nullptr;
_loaded = false;
_keepData = true;
// Initialize members to avoid warnings:
_numImages = 0;
_width = 0;
_height = 0;
_x = 0;
_y = 0;
_format = 0;
_numTex = 0;
_bpp = 0;
_colorFormat = 0;
2014-05-29 15:35:46 -07:00
_texIds = nullptr;
_hasTransparency = 0;
2014-05-29 15:35:46 -07:00
_texc = nullptr;
2014-05-29 15:35:46 -07:00
_verts = nullptr;
_layers = nullptr;
_numCoords = 0;
_numVerts = 0;
_numLayers = 0;
2014-05-29 15:35:46 -07:00
_userData = nullptr;
}
void BitmapData::load() {
if (_loaded) {
return;
}
Common::SeekableReadStream *data = g_resourceloader->openNewStreamFile(_fname.c_str());
uint32 tag = data->readUint32BE();
switch(tag) {
case(MKTAG('B','M',' ',' ')): //Grim bitmap
loadGrimBm(data);
break;
case(MKTAG('T','I','L','0')): // MI4 bitmap
loadTile(data);
break;
default:
if (!loadTGA(data)) // Try to load as TGA.
Debug::error(Debug::Bitmaps, "Invalid magic loading bitmap");
break;
}
delete data;
_loaded = true;
}
bool BitmapData::loadGrimBm(Common::SeekableReadStream *data) {
uint32 tag2 = data->readUint32BE();
if (tag2 != (MKTAG('F','\0','\0','\0')))
return false;
int codec = data->readUint32LE();
data->readUint32LE(); //_paletteIncluded
_numImages = data->readUint32LE();
_x = data->readUint32LE();
_y = data->readUint32LE();
data->readUint32LE(); //_transparentColor
_format = data->readUint32LE();
_bpp = data->readUint32LE();
// uint32 redBits = data->readUint32LE();
// uint32 greenBits = data->readUint32LE();
// uint32 blueBits = data->readUint32LE();
// uint32 redShift = data->readUint32LE();
// uint32 greenShift = data->readUint32LE();
// uint32 blueShift = data->readUint32LE();
// Hardcode the format, since the values saved in the files are garbage for some, like "ha_0_elvos.zbm".
Graphics::PixelFormat pixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
data->seek(128, SEEK_SET);
_width = data->readUint32LE();
_height = data->readUint32LE();
_colorFormat = BM_RGB565;
_hasTransparency = false;
_data = new Graphics::PixelBuffer[_numImages];
data->seek(0x80, SEEK_SET);
for (int i = 0; i < _numImages; i++) {
data->seek(8, SEEK_CUR);
_data[i].create(pixelFormat, _width * _height, DisposeAfterUse::YES);
if (codec == 0) {
uint32 dsize = _bpp / 8 * _width * _height;
data->read(_data[i].getRawBuffer(), dsize);
} else if (codec == 3) {
int compressed_len = data->readUint32LE();
char *compressed = new char[compressed_len];
data->read(compressed, compressed_len);
bool success = decompress_codec3(compressed, (char *)_data[i].getRawBuffer(), _bpp / 8 * _width * _height);
delete[] compressed;
if (!success)
warning(".. when loading image %s.\n", _fname.c_str());
} else
Debug::error(Debug::Bitmaps, "Unknown image codec in BitmapData ctor!");
#ifdef SCUMM_BIG_ENDIAN
2012-01-27 18:57:06 +01:00
if (_format == 1) {
uint16 *d = (uint16 *)_data[i].getRawBuffer();
for (int j = 0; j < _width * _height; ++j) {
2012-01-27 18:57:06 +01:00
d[j] = SWAP_BYTES_16(d[j]);
}
2012-01-27 18:57:06 +01:00
}
#endif
}
// Initially, no GPU-side textures created. the createBitmap
// function will allocate some if necessary (and successful)
_numTex = 0;
2014-05-29 15:35:46 -07:00
_texIds = nullptr;
g_driver->createBitmap(this);
return true;
}
BitmapData::BitmapData(const Graphics::PixelBuffer &buf, int w, int h, const char *fname) : _fname(fname) {
_refCount = 1;
Debug::debug(Debug::Bitmaps, "New bitmap loaded: %s\n", fname);
_numImages = 1;
_x = 0;
_y = 0;
_width = w;
_height = h;
_format = 1;
_numTex = 0;
2014-05-29 15:35:46 -07:00
_texIds = nullptr;
_bpp = buf.getFormat().bytesPerPixel * 8;
_hasTransparency = false;
_colorFormat = BM_RGB565;
_data = new Graphics::PixelBuffer[_numImages];
_data[0].create(buf.getFormat(), w * h, DisposeAfterUse::YES);
_data[0].copyBuffer(0, w * h, buf);
_loaded = true;
_keepData = true;
2014-05-29 15:35:46 -07:00
_userData = nullptr;
_texc = nullptr;
_verts = nullptr;
_layers = nullptr;
g_driver->createBitmap(this);
}
BitmapData::BitmapData() :
_numImages(0), _width(0), _height(0), _x(0), _y(0), _format(0), _numTex(0),
2014-05-29 15:35:46 -07:00
_bpp(0), _colorFormat(0), _texIds(nullptr), _hasTransparency(false), _data(nullptr),
_refCount(1), _loaded(false), _keepData(false), _texc(nullptr), _verts(nullptr),
_layers(nullptr), _numCoords(0), _numVerts(0), _numLayers(0), _userData(nullptr) {
}
BitmapData::~BitmapData() {
_keepData = false;
if (_loaded) {
g_driver->destroyBitmap(this);
}
freeData();
if (_bitmaps) {
if (_bitmaps->contains(_fname)) {
_bitmaps->erase(_fname);
}
if (_bitmaps->empty()) {
delete _bitmaps;
2014-05-29 15:35:46 -07:00
_bitmaps = nullptr;
}
}
2014-06-20 10:59:37 -07:00
delete[] _texc;
delete[] _layers;
delete[] _verts;
}
void BitmapData::freeData() {
if (!_keepData && _data) {
for (int i = 0; i < _numImages; ++i) {
_data[i].free();
}
delete[] _data;
2014-05-29 15:35:46 -07:00
_data = nullptr;
}
}
bool BitmapData::loadTGA(Common::SeekableReadStream *data) {
2014-04-05 18:18:42 +02:00
Image::TGADecoder dec;
2013-07-03 20:55:42 +02:00
bool success = dec.loadStream(*data);
2013-07-03 20:55:42 +02:00
if (!success)
return false;
2013-07-03 20:55:42 +02:00
const Graphics::Surface *origSurf = dec.getSurface();
Graphics::PixelFormat pixelFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24);
Graphics::Surface *surf = origSurf->convertTo(pixelFormat);
2013-07-03 20:55:42 +02:00
_width = surf->w;
_height = surf->h;
_format = 1;
_x = _y = 0;
_bpp = 4;
_colorFormat = BM_RGBA;
_numImages = 1;
_data = new Graphics::PixelBuffer[1];
2013-10-13 11:30:34 +02:00
_data[0].set(pixelFormat, (unsigned char *)surf->getPixels());
2013-07-03 20:55:42 +02:00
g_driver->createBitmap(this);
2013-07-03 20:55:42 +02:00
freeData();
delete surf;
return true;
}
bool BitmapData::loadTile(Common::SeekableReadStream *o) {
#ifdef ENABLE_MONKEY4
2011-05-09 04:37:51 +08:00
_x = 0;
_y = 0;
2011-05-14 17:42:37 -07:00
_format = 1;
o->seek(0, SEEK_SET);
2011-05-09 04:37:51 +08:00
2012-04-12 02:54:10 +02:00
/*uint32 id = */o->readUint32LE();
2011-05-09 04:37:51 +08:00
// Should check that we actually HAVE a TIL
2012-04-12 02:54:10 +02:00
uint32 bmoffset = o->readUint32LE();
2012-11-18 09:17:21 -08:00
_numCoords = o->readUint32LE();
2012-11-26 01:57:07 +01:00
_numLayers = o->readUint32LE();
2012-11-18 09:17:21 -08:00
_numVerts = o->readUint32LE();
// skip some 0
o->seek(16, SEEK_CUR);
_texc = new float[_numCoords * 4];
for (uint32 i = 0; i < _numCoords * 4; ++i) {
char f[4];
o->read(f, 4);
_texc[i] = get_float(f);
2012-11-18 09:17:21 -08:00
}
2012-11-26 01:57:07 +01:00
_layers = new Layer[_numLayers];
for (uint32 i = 0; i < _numLayers; ++i) {
_layers[i]._offset = o->readUint32LE();
_layers[i]._numImages = o->readUint32LE();
2012-11-18 09:17:21 -08:00
}
_verts = new Vert[_numVerts];
for (uint32 i = 0; i < _numVerts; ++i) {
_verts[i]._texid = o->readUint32LE();
_verts[i]._pos = o->readUint32LE();
_verts[i]._verts = o->readUint32LE();
}
o->seek(16, SEEK_CUR);
2011-05-09 01:21:48 +02:00
int numSubImages = o->readUint32LE();
2011-05-09 04:37:51 +08:00
char **data = new char *[numSubImages];
2011-05-09 04:37:51 +08:00
o->seek(16, SEEK_CUR);
_bpp = o->readUint32LE();
o->seek(bmoffset + 128);
_width = o->readUint32LE();
_height = o->readUint32LE();
o->seek(-8, SEEK_CUR);
2014-07-24 21:16:16 -04:00
int size = 4 * _width * _height;
2011-05-09 01:21:48 +02:00
for (int i = 0; i < numSubImages; ++i) {
data[i] = new char[size];
2011-05-09 04:37:51 +08:00
o->seek(8, SEEK_CUR);
if (_bpp == 16) {
2014-07-24 21:16:16 -04:00
uint32 *d = (uint32 *)data[i];
for (int j = 0; j < _width * _height; ++j) {
2014-07-24 21:16:16 -04:00
uint16 p = o->readUint16LE();
// These values are shifted left by 3 so that they saturate the color channel
uint8 b = (p & 0x7C00) >> 7;
uint8 g = (p & 0x03E0) >> 2;
uint8 r = (p & 0x001F) << 3;
uint8 a = (p & 0x8000) ? 0xFF : 0x00;
// Recombine the color components into a 32 bit RGB value
uint32 tmp = (r << 24) | (g << 16) | (b << 8) | a;
WRITE_BE_UINT32(&d[j], tmp);
}
} else if (_bpp == 32) {
uint32 *d = (uint32 *)data[i];
for (int j = 0; j < _width * _height; ++j) {
2014-07-24 21:16:16 -04:00
o->read(&(d[j]), 4);
}
}
2011-05-09 04:37:51 +08:00
}
2014-07-24 21:16:16 -04:00
_bpp = 32;
2011-05-16 13:16:45 +08:00
2014-07-24 21:16:16 -04:00
Graphics::PixelFormat pixelFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24);
_colorFormat = BM_RGBA;
2011-05-09 04:37:51 +08:00
2012-11-18 09:17:21 -08:00
_width = 256;
_height = 256;
_numImages = numSubImages;
_data = new Graphics::PixelBuffer[_numImages];
2012-11-18 09:17:21 -08:00
for (int i = 0; i < _numImages; ++i) {
_data[i].create(pixelFormat, _width * _height, DisposeAfterUse::YES);
_data[i].set(pixelFormat, (byte *)data[i]);
}
delete[] data;
2011-05-09 04:37:51 +08:00
g_driver->createBitmap(this);
#endif // ENABLE_MONKEY4
return true;
2011-05-09 04:37:51 +08:00
}
const Graphics::PixelBuffer &BitmapData::getImageData(int num) const {
assert(num >= 0);
assert(num < _numImages);
2011-05-12 11:08:45 +02:00
return _data[num];
}
// Bitmap
Bitmap::Bitmap(const Common::String &fname) {
_data = BitmapData::getBitmapData(fname);
_currImage = 1;
}
Bitmap::Bitmap(const Graphics::PixelBuffer &buf, int w, int h, const char *fname) {
_data = new BitmapData(buf, w, h, fname);
_currImage = 1;
}
Bitmap::Bitmap() {
_data = new BitmapData();
_currImage = 0;
}
Bitmap *Bitmap::create(const Common::String &filename) {
if (!SearchMan.hasFile(filename)) {
warning("Could not find bitmap %s", filename.c_str());
2014-05-29 15:35:46 -07:00
return nullptr;
}
Bitmap *b = new Bitmap(filename);
return b;
}
void Bitmap::saveState(SaveGame *state) const {
state->writeString(getFilename());
state->writeLESint32(getActiveImage());
}
void Bitmap::restoreState(SaveGame *state) {
freeData();
Common::String fname = state->readString();
_data = BitmapData::getBitmapData(fname);
_currImage = state->readLESint32();
}
void Bitmap::draw() {
_data->load();
if (_currImage == 0)
return;
g_driver->drawBitmap(this, _data->_x, _data->_y);
}
void Bitmap::draw(int x, int y) {
_data->load();
2004-12-10 07:26:03 +00:00
if (_currImage == 0)
return;
g_driver->drawBitmap(this, x, y, _data->_numLayers - 1);
2003-08-15 18:00:22 +00:00
}
void Bitmap::drawLayer(uint32 layer) {
_data->load();
if (_currImage == 0)
return;
g_driver->drawBitmap(this, _data->_x, _data->_y, layer);
}
void Bitmap::setActiveImage(int n) {
assert(n >= 0);
_data->load();
if ((n - 1) >= _data->_numImages) {
warning("Bitmap::setActiveImage: no anim image: %d. (%s)", n, _data->_fname.c_str());
} else {
_currImage = n;
}
}
2012-02-12 17:06:23 +01:00
int Bitmap::getNumImages() const {
_data->load();
return _data->_numImages;
}
void Bitmap::freeData() {
--_data->_refCount;
if (_data->_refCount < 1) {
delete _data;
2014-05-29 15:35:46 -07:00
_data = nullptr;
}
}
Bitmap::~Bitmap() {
freeData();
2003-08-15 18:00:22 +00:00
}
2011-05-16 09:51:06 +02:00
const Graphics::PixelFormat &Bitmap::getPixelFormat(int num) const {
return getData(num).getFormat();
}
2003-08-15 18:00:22 +00:00
void BitmapData::convertToColorFormat(int num, const Graphics::PixelFormat &format) {
if (_data[num].getFormat() == format) {
return;
}
Graphics::PixelBuffer dst(format, _width * _height, DisposeAfterUse::NO);
for (int i = 0; i < _width * _height; ++i) {
if (_data[num].getValueAt(i) == 0xf81f) { //transparency
dst.setPixelAt(i, 0xf81f);
} else {
dst.setPixelAt(i, _data[num]);
}
}
_data[num].free();
_data[num] = dst;
}
void BitmapData::convertToColorFormat(const Graphics::PixelFormat &format) {
for (int i = 0; i < _numImages; ++i) {
convertToColorFormat(i, format);
}
}
2004-02-21 15:36:31 +00:00
#define GET_BIT do { bit = bitstr_value & 1; \
bitstr_len--; \
bitstr_value >>= 1; \
if (bitstr_len == 0) { \
bitstr_value = READ_LE_UINT16(compressed); \
bitstr_len = 16; \
compressed += 2; \
} \
} while (0)
2003-08-15 18:00:22 +00:00
static bool decompress_codec3(const char *compressed, char *result, int maxBytes) {
int bitstr_value = READ_LE_UINT16(compressed);
int bitstr_len = 16;
2003-08-15 18:00:22 +00:00
compressed += 2;
bool bit;
int byteIndex = 0;
for (;;) {
GET_BIT;
if (bit == 1) {
if (byteIndex >= maxBytes) {
warning("Buffer overflow when decoding image: decompress_codec3 walked past the input buffer!");
return false;
} else {
*result++ = *compressed++;
}
++byteIndex;
} else {
GET_BIT;
int copy_len, copy_offset;
if (bit == 0) {
GET_BIT;
copy_len = 2 * bit;
GET_BIT;
copy_len += bit + 3;
2012-04-03 16:58:04 -07:00
copy_offset = *(const uint8 *)(compressed++) - 0x100;
} else {
2012-04-03 16:58:04 -07:00
copy_offset = (*(const uint8 *)(compressed) | (*(const uint8 *)(compressed + 1) & 0xf0) << 4) - 0x1000;
copy_len = (*(const uint8 *)(compressed + 1) & 0xf) + 3;
compressed += 2;
if (copy_len == 3) {
2012-04-03 16:58:04 -07:00
copy_len = *(const uint8 *)(compressed++) + 1;
if (copy_len == 1)
return true;
}
}
2004-12-09 23:55:43 +00:00
while (copy_len > 0) {
if (byteIndex >= maxBytes) {
warning("Buffer overflow when decoding image: decompress_codec3 walked past the input buffer!");
return false;
} else {
assert(byteIndex + copy_offset >= 0);
assert(byteIndex + copy_offset < maxBytes);
*result = result[copy_offset];
result++;
}
++byteIndex;
2004-12-09 23:55:43 +00:00
copy_len--;
}
}
2003-08-15 18:00:22 +00:00
}
return true;
2003-08-15 18:00:22 +00:00
}
2009-05-25 06:49:57 +00:00
} // end of namespace Grim