* Final version of the IFF parsing code.

* Refactored ILBMDecoder usage from disk code.

svn-id: r41458
This commit is contained in:
Nicola Mettifogo 2009-06-12 05:03:18 +00:00
parent f5b2e69522
commit 5fccc0f98d
8 changed files with 423 additions and 334 deletions

View file

@ -0,0 +1,121 @@
#include "parallaction/disk.h"
#include "parallaction/graphics.h"
#include "parallaction/iff.h"
namespace Parallaction {
void ILBMLoader::setupBuffer(uint32 w, uint32 h) {
_intBuffer = 0;
switch (_bodyMode) {
case BODYMODE_SURFACE:
if (!_surf) {
_surf = new Graphics::Surface;
assert(_surf);
}
_surf->create(w, h, 1);
_mode = ILBMDecoder::ILBM_UNPACK_PLANES;
_intBuffer = (byte*)_surf->pixels;
break;
case BODYMODE_MASKBUFFER:
if (!_maskBuffer) {
_maskBuffer = new MaskBuffer;
assert(_maskBuffer);
}
_maskBuffer->create(w, h);
_mode = ILBMDecoder::ILBM_2_PACK_PLANES;
_intBuffer = _maskBuffer->data;
break;
case BODYMODE_PATHBUFFER:
if (!_pathBuffer) {
_pathBuffer = new PathBuffer;
assert(_pathBuffer);
}
_pathBuffer->create(w, h);
_mode = ILBMDecoder::ILBM_1_PACK_PLANES;
_intBuffer = _pathBuffer->data;
break;
default:
error("Invalid bodyMode '%i' for ILBMLoader", _bodyMode);
break;
}
}
bool ILBMLoader::callback(IFFChunk &chunk) {
switch (chunk._type) {
case ID_BMHD:
_decoder.loadHeader(chunk._stream);
break;
case ID_CMAP:
if (_palette) {
chunk._stream->read(_palette, chunk._size);
}
break;
case ID_CRNG:
if (_crng) {
PaletteFxRange *ptr = &_crng[_numCRNG];
chunk._stream->read((byte*)ptr, chunk._size);
ptr->_timer = FROM_BE_16(ptr->_timer);
ptr->_step = FROM_BE_16(ptr->_step);
ptr->_flags = FROM_BE_16(ptr->_flags);
++_numCRNG;
}
break;
case ID_BODY:
setupBuffer(_decoder._header.width, _decoder._header.height);
assert(_intBuffer);
_decoder.loadBitmap(_mode, _intBuffer, chunk._stream);
return true; // stop the parser
}
return false;
}
void ILBMLoader::load(Common::ReadStream *in, bool disposeStream) {
IFFParser parser(in, disposeStream);
Common::Functor1Mem< IFFChunk&, bool, ILBMLoader > c(this, &ILBMLoader::callback);
parser.parse(c);
}
ILBMLoader::ILBMLoader(uint32 bodyMode, byte *palette, PaletteFxRange *crng) {
_bodyMode = bodyMode;
_surf = 0;
_maskBuffer = 0;
_pathBuffer = 0;
_palette = palette;
_crng = crng;
_numCRNG = 0;
}
ILBMLoader::ILBMLoader(Graphics::Surface *surf, byte *palette, PaletteFxRange *crng) {
_bodyMode = ILBMLoader::BODYMODE_SURFACE;
_surf = surf;
_palette = palette;
_crng = crng;
_numCRNG = 0;
}
ILBMLoader::ILBMLoader(MaskBuffer *buffer) {
_bodyMode = ILBMLoader::BODYMODE_MASKBUFFER;
_maskBuffer = buffer;
_palette = 0;
_crng = 0;
_numCRNG = 0;
}
ILBMLoader::ILBMLoader(PathBuffer *buffer) {
_bodyMode = ILBMLoader::BODYMODE_PATHBUFFER;
_pathBuffer = buffer;
_palette = 0;
_crng = 0;
_numCRNG = 0;
}
}

View file

@ -33,6 +33,7 @@
#include "common/file.h"
#include "graphics/surface.h"
#include "parallaction/iff.h"
@ -77,8 +78,37 @@ public:
virtual Table* loadTable(const char* name) = 0;
virtual Common::SeekableReadStream* loadMusic(const char* name) = 0;
virtual Common::SeekableReadStream* loadSound(const char* name) = 0;
virtual void loadMask(const char *name, MaskBuffer &buffer) { }
virtual void loadPath(const char *name, PathBuffer &buffer) { }
virtual MaskBuffer *loadMask(const char *name, uint32 w, uint32 h) { return 0; }
virtual PathBuffer *loadPath(const char *name, uint32 w, uint32 h) { return 0; }
};
struct PaletteFxRange;
struct ILBMLoader {
enum {
BODYMODE_SURFACE,
BODYMODE_MASKBUFFER,
BODYMODE_PATHBUFFER
};
uint32 _bodyMode;
Graphics::Surface *_surf;
MaskBuffer *_maskBuffer;
PathBuffer *_pathBuffer;
byte *_palette;
PaletteFxRange *_crng;
uint32 _mode;
byte* _intBuffer;
uint32 _numCRNG;
ILBMDecoder _decoder;
ILBMLoader(uint32 bodyMode, byte *palette = 0, PaletteFxRange *crng = 0);
ILBMLoader(Graphics::Surface *surf, byte *palette = 0, PaletteFxRange *crng = 0);
ILBMLoader(MaskBuffer *buffer);
ILBMLoader(PathBuffer *buffer);
bool callback(IFFChunk &chunk);
void setupBuffer(uint32 w, uint32 h);
void load(Common::ReadStream *in, bool disposeStream = false);
};
@ -235,8 +265,8 @@ public:
Table* loadTable(const char* name);
Common::SeekableReadStream* loadMusic(const char* name);
Common::SeekableReadStream* loadSound(const char* name);
void loadMask(const char *name, MaskBuffer &buffer);
void loadPath(const char *name, PathBuffer &buffer);
MaskBuffer *loadMask(const char *name, uint32 w, uint32 h);
PathBuffer *loadPath(const char *name, uint32 w, uint32 h);
};
class DosDemoDisk_br : public DosDisk_br {
@ -272,7 +302,7 @@ public:
GfxObj* loadObjects(const char *name, uint8 part = 0);
Common::SeekableReadStream* loadMusic(const char* name);
Common::SeekableReadStream* loadSound(const char* name);
void loadMask(const char *name, MaskBuffer &buffer);
MaskBuffer *loadMask(const char *name, uint32 w, uint32 h);
};
} // namespace Parallaction

View file

@ -331,32 +331,40 @@ void DosDisk_br::loadSlide(BackgroundInfo& info, const char *name) {
}
}
void DosDisk_br::loadMask(const char *name, MaskBuffer &buffer) {
MaskBuffer *DosDisk_br::loadMask(const char *name, uint32 w, uint32 h) {
if (!name) {
return;
return 0;
}
Common::SeekableReadStream *stream = openFile("msk/" + Common::String(name), ".msk");
// NOTE: info.width and info.height are only valid if the background graphics
// have already been loaded
buffer.bigEndian = false;
stream->read(buffer.data, buffer.size);
MaskBuffer *buffer = new MaskBuffer;
assert(buffer);
buffer->create(w, h);
buffer->bigEndian = false;
stream->read(buffer->data, buffer->size);
delete stream;
return buffer;
}
void DosDisk_br::loadPath(const char *name, PathBuffer &buffer) {
PathBuffer *DosDisk_br::loadPath(const char *name, uint32 w, uint32 h) {
if (!name) {
return;
return 0;
}
Common::SeekableReadStream *stream = openFile("pth/" + Common::String(name), ".pth");
// NOTE: info.width and info.height are only valid if the background graphics
// have already been loaded
buffer.bigEndian = false;
stream->read(buffer.data, buffer.size);
PathBuffer *buffer = new PathBuffer;
assert(buffer);
buffer->create(w, h);
buffer->bigEndian = false;
stream->read(buffer->data, buffer->size);
delete stream;
return buffer;
}
void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char *mask, const char* path) {
@ -380,18 +388,12 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char
}
if (mask) {
info._mask = new MaskBuffer;
info._mask->create(info.width, info.height);
loadMask(mask, *info._mask);
info._mask = loadMask(mask, info.width, info.height);
}
if (path) {
info._path = new PathBuffer;
info._path->create(info.width, info.height);
loadPath(path, *info._path);
info._path = loadPath(path, info.width, info.height);
}
return;
}
Table* DosDisk_br::loadTable(const char* name) {
@ -459,7 +461,7 @@ void AmigaDisk_br::adjustForPalette(Graphics::Surface &surf, int transparentColo
void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *filename) {
byte r,g,b;
byte *pal, *p;
byte *p;
Common::SeekableReadStream *stream;
uint i;
@ -488,20 +490,14 @@ void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *filename) {
}
stream = openFile("backs/" + Common::String(filename), ".bkg");
ILBMDecoder decoder(stream, true);
// TODO: encapsulate surface creation
info.bg.w = decoder.getWidth();
info.bg.h = decoder.getHeight();
info.bg.pitch = info.bg.w;
info.bg.bytesPerPixel = 1;
info.bg.pixels = decoder.getBitmap();
assert(info.bg.pixels);
byte pal[768];
ILBMLoader loader(&info.bg, pal);
loader.load(stream, true);
info.width = info.bg.w;
info.height = info.bg.h;
pal = decoder.getPalette();
p = pal;
for (i = 16; i < 32; i++) {
r = *p >> 2;
@ -516,8 +512,6 @@ void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *filename) {
// Overwrite the first color (transparent key) in the palette
info.palette.setEntry(0, pal[0] >> 2, pal[1] >> 2, pal[2] >> 0);
delete []pal;
// background data is drawn used the upper portion of the palette
adjustForPalette(info.bg);
}
@ -543,27 +537,24 @@ void finalpass(byte *buffer, uint32 size) {
}
}
void AmigaDisk_br::loadMask(const char *name, MaskBuffer &buffer) {
MaskBuffer *AmigaDisk_br::loadMask(const char *name, uint32 w, uint32 h) {
if (!name) {
return;
return 0;
}
debugC(1, kDebugDisk, "AmigaDisk_br::loadMask '%s'", name);
Common::SeekableReadStream *stream = tryOpenFile("msk/" + Common::String(name), ".msk");
if (!stream) {
return;
return 0;
}
ILBMDecoder decoder(stream, true);
ILBMLoader loader(ILBMLoader::BODYMODE_MASKBUFFER);
loader.load(stream, true);
// TODO: the buffer is allocated by the caller, so a copy here is
// unavoidable... a better solution would be inform the function
// of the size of the mask (the size in the mask file is not valid!)
byte *bitmap = decoder.getBitmap(2, true);
memcpy(buffer.data, bitmap, buffer.size);
finalpass(buffer.data, buffer.size);
buffer.bigEndian = true;
MaskBuffer *buffer = loader._maskBuffer;
buffer->bigEndian = true;
finalpass(buffer->data, buffer->size);
return buffer;
}
void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path) {
@ -573,18 +564,12 @@ void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const cha
loadBackground(info, name);
}
if (mask) {
info._mask = new MaskBuffer;
info._mask->create(info.width, info.height);
loadMask(mask, *info._mask);
info._mask = loadMask(mask, info.width, info.height);
}
if (path) {
info._path = new PathBuffer;
info._path->create(info.width, info.height);
loadPath(path, *info._path);
info._path = loadPath(path, info.width, info.height);
}
return;
}
void AmigaDisk_br::loadSlide(BackgroundInfo& info, const char *name) {
@ -596,21 +581,14 @@ GfxObj* AmigaDisk_br::loadStatic(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadStatic '%s'", name);
Common::String sName = name;
Common::SeekableReadStream *stream = openFile("ras/" + sName, ".ras");
ILBMDecoder decoder(stream, true);
Graphics::Surface* surf = new Graphics::Surface;
ILBMLoader loader(ILBMLoader::BODYMODE_SURFACE);
loader.load(stream, true);
Graphics::Surface* surf = loader._surf;
assert(surf);
// TODO: encapsulate surface creation
surf->w = decoder.getWidth();
surf->h = decoder.getHeight();
surf->pitch = surf->w;
surf->bytesPerPixel = 1;
surf->pixels = decoder.getBitmap();
assert(surf->pixels);
// Static pictures are drawn used the upper half of the palette: this must be
// done before shadow mask is applied. This way, only really transparent pixels
// will have zero as a color.
@ -741,15 +719,16 @@ GfxObj* AmigaDisk_br::loadObjects(const char *name, uint8 part) {
debugC(5, kDebugDisk, "AmigaDisk_br::loadObjects");
Common::SeekableReadStream *stream = openFile(name);
ILBMDecoder decoder(stream, true);
ILBMLoader loader(ILBMLoader::BODYMODE_SURFACE);
loader.load(stream, true);
uint16 max = objectsMax[part];
if (_vm->getFeatures() & GF_DEMO)
max = 72;
byte *data = new byte[max * 2601];
byte *srcPtr = decoder.getBitmap();
int w = decoder.getWidth();
byte *srcPtr = (byte*)loader._surf->getBasePtr(0,0);
int w = loader._surf->w;
// Convert to the expected display format
for (int i = 0; i < max; i++) {
@ -764,7 +743,7 @@ GfxObj* AmigaDisk_br::loadObjects(const char *name, uint8 part) {
dst += 51;
}
}
free(srcPtr);
delete loader._surf;
return new GfxObj(0, new Cnv(max, 51, 51, data, true));
}

View file

@ -900,56 +900,18 @@ void AmigaDisk_ns::buildMask(byte* buf) {
}
}
// TODO: extend the ILBMDecoder to return CRNG chunks and get rid of this BackgroundDecoder crap
class BackgroundDecoder : public ILBMDecoder {
public:
BackgroundDecoder(Common::SeekableReadStream *input, bool disposeStream = false) : ILBMDecoder(input, disposeStream) {
}
uint32 getCRNG(PaletteFxRange *ranges, uint32 num) {
assert(ranges);
uint32 size = _parser.getIFFBlockSize(ID_CRNG);
if (size == (uint32)-1) {
return 0;
}
uint32 count = MIN((uint32)(size / sizeof(PaletteFxRange)), num);
_parser.loadIFFBlock(ID_CRNG, ranges, count * sizeof(PaletteFxRange));
for (uint32 i = 0; i < count; ++i) {
ranges[i]._timer = FROM_BE_16(ranges[i]._timer);
ranges[i]._step = FROM_BE_16(ranges[i]._step);
ranges[i]._flags = FROM_BE_16(ranges[i]._flags);
}
return count;
}
};
void AmigaDisk_ns::loadBackground(BackgroundInfo& info, const char *name) {
PaletteFxRange ranges[6];
byte pal[768];
Common::SeekableReadStream *s = openFile(name);
BackgroundDecoder decoder(s, true);
PaletteFxRange ranges[6];
memset(ranges, 0, 6*sizeof(PaletteFxRange));
decoder.getCRNG(ranges, 6);
// TODO: encapsulate surface creation
info.bg.w = decoder.getWidth();
info.bg.h = decoder.getHeight();
info.bg.pitch = info.bg.w;
info.bg.bytesPerPixel = 1;
info.bg.pixels = decoder.getBitmap();
ILBMLoader loader(&info.bg, pal, ranges);
loader.load(s, true);
info.width = info.bg.w;
info.height = info.bg.h;
byte *pal = decoder.getPalette();
assert(pal);
byte *p = pal;
for (uint i = 0; i < 32; i++) {
byte r = *p >> 2;
@ -960,7 +922,6 @@ void AmigaDisk_ns::loadBackground(BackgroundInfo& info, const char *name) {
p++;
info.palette.setEntry(i, r, g, b);
}
delete []pal;
for (uint j = 0; j < 6; j++) {
info.setPaletteRange(j, ranges[j]);
@ -979,9 +940,9 @@ void AmigaDisk_ns::loadMask(BackgroundInfo& info, const char *name) {
return; // no errors if missing mask files: not every location has one
}
ILBMDecoder decoder(s, true);
byte *pal = decoder.getPalette();
assert(pal);
byte pal[768];
ILBMLoader loader(ILBMLoader::BODYMODE_MASKBUFFER, pal);
loader.load(s, true);
byte r, g, b;
for (uint i = 0; i < 4; i++) {
@ -990,14 +951,8 @@ void AmigaDisk_ns::loadMask(BackgroundInfo& info, const char *name) {
b = pal[i*3+2];
info.layers[i] = (((r << 4) & 0xF00) | (g & 0xF0) | (b >> 4)) & 0xFF;
}
delete []pal;
info._mask = new MaskBuffer;
info._mask->w = info.width;
info._mask->h = info.height;
info._mask->internalWidth = info.width >> 2;
info._mask->size = info._mask->internalWidth * info._mask->h;
info._mask->data = decoder.getBitmap(2, true);
info._mask = loader._maskBuffer;
}
void AmigaDisk_ns::loadPath(BackgroundInfo& info, const char *name) {
@ -1010,15 +965,10 @@ void AmigaDisk_ns::loadPath(BackgroundInfo& info, const char *name) {
return; // no errors if missing path files: not every location has one
}
ILBMDecoder decoder(s, true);
info._path = new PathBuffer;
info._path->create(info.width, info.height);
ILBMLoader loader(ILBMLoader::BODYMODE_PATHBUFFER);
loader.load(s, true);
info._path = loader._pathBuffer;
info._path->bigEndian = true;
byte *bitmap = decoder.getBitmap(1, true);
assert(bitmap);
memcpy(info._path->data, bitmap, info._path->size);
delete bitmap;
}
void AmigaDisk_ns::loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path) {

View file

@ -162,9 +162,7 @@ void BackgroundInfo::loadGfxObjMask(const char *name, GfxObj *obj) {
Common::Rect rect;
obj->getRect(0, rect);
MaskBuffer *buf = new MaskBuffer;
buf->create(rect.width(), rect.height());
_vm->_disk->loadMask(name, *buf);
MaskBuffer *buf = _vm->_disk->loadMask(name, rect.width(), rect.height());
obj->_maskId = addMaskPatch(buf);
obj->_hasMask = true;
@ -174,9 +172,7 @@ void BackgroundInfo::loadGfxObjPath(const char *name, GfxObj *obj) {
Common::Rect rect;
obj->getRect(0, rect);
PathBuffer *buf = new PathBuffer;
buf->create(rect.width(), rect.height());
_vm->_disk->loadPath(name, *buf);
PathBuffer *buf = _vm->_disk->loadPath(name, rect.width(), rect.height());
obj->_pathId = addPathPatch(buf);
obj->_hasPath = true;

View file

@ -32,34 +32,17 @@
namespace Parallaction {
void IFFParser::setInputStream(Common::SeekableReadStream *stream) {
destroy();
void IFFParser::setInputStream(Common::ReadStream *stream) {
assert(stream);
_stream = stream;
_startOffset = 0;
_endOffset = _stream->size();
_formChunk.setInputStream(stream);
_chunk.setInputStream(stream);
_formType = 0;
_formSize = (uint32)-1;
if (_stream->size() < 12) {
// this file is too small to be a valid IFF container
return;
_formChunk.readHeader();
if (_formChunk.id != ID_FORM) {
error("IFFParser input is not a FORM type IFF file");
}
if (_stream->readUint32BE() != ID_FORM) {
// no FORM header was found
return;
}
_formSize = _stream->readUint32BE();
_formType = _stream->readUint32BE();
}
void IFFParser::destroy() {
_stream = 0;
_startOffset = _endOffset = 0;
_formSize = _formChunk.size;
_formType = _formChunk.readUint32BE();
}
uint32 IFFParser::getFORMSize() const {
@ -70,171 +53,99 @@ Common::IFF_ID IFFParser::getFORMType() const {
return _formType;
}
uint32 IFFParser::moveToIFFBlock(Common::IFF_ID chunkName) {
uint32 size = (uint32)-1;
void IFFParser::parse(IFFCallback &callback) {
bool stop;
do {
_chunk.feed();
_formChunk.incBytesRead(_chunk.size);
_stream->seek(_startOffset + 0x0C);
while ((uint)_stream->pos() < _endOffset) {
uint32 chunk = _stream->readUint32BE();
uint32 size_temp = _stream->readUint32BE();
if (chunk != chunkName) {
_stream->seek((size_temp + 1) & (~1), SEEK_CUR);
assert((uint)_stream->pos() <= _endOffset);
} else {
size = size_temp;
if (_formChunk.hasReadAll()) {
break;
}
}
return size;
}
_formChunk.incBytesRead(8);
_chunk.readHeader();
uint32 IFFParser::getIFFBlockSize(Common::IFF_ID chunkName) {
uint32 size = moveToIFFBlock(chunkName);
return size;
}
// invoke the callback
Common::SubReadStream stream(&_chunk, _chunk.size);
IFFChunk chunk(_chunk.id, _chunk.size, &stream);
stop = callback(chunk);
bool IFFParser::loadIFFBlock(Common::IFF_ID chunkName, void *loadTo, uint32 ptrSize) {
uint32 chunkSize = moveToIFFBlock(chunkName);
if (chunkSize == (uint32)-1) {
return false;
}
uint32 loadSize = 0;
loadSize = MIN(ptrSize, chunkSize);
_stream->read(loadTo, loadSize);
return true;
}
Common::SeekableReadStream *IFFParser::getIFFBlockStream(Common::IFF_ID chunkName) {
uint32 chunkSize = moveToIFFBlock(chunkName);
if (chunkSize == (uint32)-1) {
return 0;
}
uint32 pos = _stream->pos();
return new Common::SeekableSubReadStream(_stream, pos, pos + chunkSize, false);
// eats up all the remaining data in the chunk
while (!stream.eos()) {
printf("attemping to eat data in chunk\n");
stream.readByte();
}
// ILBM decoder implementation
ILBMDecoder::ILBMDecoder(Common::SeekableReadStream *in, bool disposeStream) : _in(in), _disposeStream(disposeStream), _hasHeader(false), _bodySize((uint32)-1), _paletteSize((uint32)-1) {
assert(in);
_parser.setInputStream(in);
if (_parser.getFORMType() != ID_ILBM) {
return;
}
_hasHeader = _parser.loadIFFBlock(ID_BMHD, &_header, sizeof(_header));
if (!_hasHeader) {
return;
}
_header.width = TO_BE_16(_header.width);
_header.height = TO_BE_16(_header.height);
_paletteSize = _parser.getIFFBlockSize(ID_CMAP);
_bodySize = _parser.getIFFBlockSize(ID_BODY);
} while (!stop);
}
ILBMDecoder::~ILBMDecoder() {
if (_disposeStream) {
delete _in;
}
void ILBMDecoder::loadHeader(Common::ReadStream *stream) {
assert(stream);
stream->read(&_header, sizeof(_header));
_header.width = FROM_BE_16(_header.width);
_header.height = FROM_BE_16(_header.height);
_header.x = FROM_BE_16(_header.x);
_header.y = FROM_BE_16(_header.y);
_header.transparentColor = FROM_BE_16(_header.transparentColor);
_header.pageWidth = FROM_BE_16(_header.pageWidth);
_header.pageHeight = FROM_BE_16(_header.pageHeight);
}
uint32 ILBMDecoder::getWidth() {
assert(_hasHeader);
return _header.width;
}
uint32 ILBMDecoder::getHeight() {
assert(_hasHeader);
return _header.height;
}
uint32 ILBMDecoder::getNumColors() {
assert(_hasHeader);
return (1 << _header.depth);
}
byte *ILBMDecoder::getPalette() {
assert(_paletteSize != (uint32)-1);
byte *palette = new byte[_paletteSize];
assert(palette);
_parser.loadIFFBlock(ID_CMAP, palette, _paletteSize);
return palette;
}
byte *ILBMDecoder::getBitmap(uint32 numPlanes, bool packPlanes) {
assert(_bodySize != (uint32)-1);
void ILBMDecoder::loadBitmap(uint32 mode, byte *buffer, Common::ReadStream *stream) {
assert(stream);
uint32 numPlanes = MIN(mode & ILBM_UNPACK_PLANES, (uint32)_header.depth);
assert(numPlanes == 1 || numPlanes == 2 || numPlanes == 3 || numPlanes == 4 || numPlanes == 5 || numPlanes == 8);
numPlanes = MIN(numPlanes, (uint32)_header.depth);
if (numPlanes > 4) {
packPlanes = false;
bool packPixels = (mode & ILBM_PACK_PLANES) != 0;
if (numPlanes != 1 && numPlanes != 2 && numPlanes != 4) {
packPixels = false;
}
uint32 bitmapSize = _header.width * _header.height;
uint32 bitmapWidth = _header.width;
if (packPlanes) {
bitmapSize /= (8 / numPlanes);
bitmapWidth /= (8 / numPlanes);
uint32 outPitch = _header.width;
if (packPixels) {
outPitch /= (8 / numPlanes);
}
Common::SeekableReadStream *bodyStream = _parser.getIFFBlockStream(ID_BODY);
assert(bodyStream);
byte *bitmap = (byte*)calloc(bitmapSize, 1);
assert(bitmap);
byte *out = buffer;
switch (_header.pack) {
case 1: { // PackBits compressed bitmap
Graphics::PackBitsReadStream stream(*bodyStream);
byte *out = bitmap;
Graphics::PackBitsReadStream packStream(*stream);
// setup a buffer to hold enough data to build a line in the output
uint32 scanWidth = ((_header.width + 15)/16) << 1;
byte *scanBuffer = (byte*)malloc(scanWidth * _header.depth);
uint32 scanlineWidth = ((_header.width + 15)/16) << 1;
byte *scanline = new byte[scanlineWidth * _header.depth];
for (uint i = 0; i < _header.height; ++i) {
byte *s = scanBuffer;
byte *s = scanline;
for (uint32 j = 0; j < _header.depth; ++j) {
stream.read(s, scanWidth);
s += scanWidth;
packStream.read(s, scanlineWidth);
s += scanlineWidth;
}
planarToChunky(out, bitmapWidth, scanBuffer, scanWidth, numPlanes, packPlanes);
out += bitmapWidth;
planarToChunky(out, outPitch, scanline, scanlineWidth, numPlanes, packPixels);
out += outPitch;
}
free(scanBuffer);
delete []scanline;
break;
}
default:
// implement other compression types here!
error("only RLE compressed ILBM files are supported");
break;
}
delete bodyStream;
return bitmap;
}
void ILBMDecoder::planarToChunky(byte *out, uint32 width, byte *in, uint32 planeWidth, uint32 nPlanes, bool packPlanes) {
void ILBMDecoder::planarToChunky(byte *out, uint32 outPitch, byte *in, uint32 inWidth, uint32 nPlanes, bool packPlanes) {
byte pix, ofs, bit;
byte *s;
uint32 pixels = width;
uint32 pixels = outPitch;
if (packPlanes) {
pixels *= (8 / nPlanes);
}
@ -251,7 +162,7 @@ void ILBMDecoder::planarToChunky(byte *out, uint32 width, byte *in, uint32 plane
if (s[ofs] & bit) {
pix |= (1 << plane);
}
s += planeWidth;
s += inWidth;
}

View file

@ -27,39 +27,126 @@
#define PARALLACTION_IFF_H
#include "common/stream.h"
#include "common/func.h"
#include "common/iff_container.h" // for IFF chunk names
#include "graphics/iff.h" // for BMHD
// this IFF parser code is courtesy of the Kyra engine team ;)
namespace Parallaction {
/**
* Represents a IFF chunk available to client code.
*
* Client code must *not* deallocate _stream when done.
*/
struct IFFChunk {
Common::IFF_ID _type;
uint32 _size;
Common::ReadStream *_stream;
IFFChunk(Common::IFF_ID type, uint32 size, Common::ReadStream *stream) : _type(type), _size(size), _stream(stream) {
assert(_stream);
}
};
/**
* Parser for IFF containers.
*/
class IFFParser {
/**
* This private class implements IFF chunk navigation.
*/
class IFFChunkNav : public Common::ReadStream {
protected:
Common::ReadStream *_input;
uint32 _bytesRead;
public:
IFFParser() : _stream(0), _startOffset(0), _endOffset(0) {}
IFFParser(Common::SeekableReadStream *stream) : _stream(0), _startOffset(0), _endOffset(0) {
Common::IFF_ID id;
uint32 size;
IFFChunkNav() : _input(0) {
}
void setInputStream(Common::ReadStream *input) {
_input = input;
size = _bytesRead = 0;
}
void incBytesRead(uint32 inc) {
_bytesRead += inc;
if (_bytesRead > size) {
error("Chunk overread");
}
}
void readHeader() {
id = _input->readUint32BE();
size = _input->readUint32BE();
_bytesRead = 0;
}
bool hasReadAll() const {
return (size - _bytesRead) == 0;
}
void feed() {
if (size % 2) {
size++;
}
while (!hasReadAll()) {
readByte();
}
}
// Common::ReadStream implementation
bool eos() const { return _input->eos(); }
bool err() const { return _input->err(); }
void clearErr() { _input->clearErr(); }
uint32 read(void *dataPtr, uint32 dataSize) {
incBytesRead(dataSize);
return _input->read(dataPtr, dataSize);
}
};
IFFChunkNav _formChunk; //!< The root chunk of the file.
IFFChunkNav _chunk; //!< The current chunk.
Common::ReadStream *_stream;
bool _disposeStream;
void setInputStream(Common::ReadStream *stream);
public:
IFFParser(Common::ReadStream *stream, bool disposeStream = false) : _stream(stream), _disposeStream(stream) {
setInputStream(stream);
}
~IFFParser() { destroy(); }
~IFFParser() {
if (_disposeStream) {
delete _stream;
}
_stream = 0;
}
void setInputStream(Common::SeekableReadStream *stream);
operator bool() const { return (_startOffset != _endOffset) && _stream; }
uint32 getFORMSize() const;
/**
* Returns the IFF FORM type.
* @return the IFF FORM type of the stream, or 0 if FORM header is not found.
*/
Common::IFF_ID getFORMType() const;
uint32 getIFFBlockSize(Common::IFF_ID chunk);
bool loadIFFBlock(Common::IFF_ID chunk, void *loadTo, uint32 ptrSize);
Common::SeekableReadStream *getIFFBlockStream(Common::IFF_ID chunkName);
/**
* Returns the size of the data.
* @return the size of the data in file, or -1 if FORM header is not found.
*/
uint32 getFORMSize() const;
/**
* Callback type for the parser.
*/
typedef Common::Functor1< IFFChunk&, bool > IFFCallback;
/**
* Parse the IFF container, invoking the callback on each chunk encountered.
* The callback can interrupt the parsing by returning 'true'.
*/
void parse(IFFCallback &callback);
private:
void destroy();
uint32 moveToIFFBlock(Common::IFF_ID chunkName);
Common::SeekableReadStream *_stream;
uint32 _startOffset;
uint32 _endOffset;
uint32 _formSize;
Common::IFF_ID _formType;
};
@ -67,35 +154,49 @@ private:
class ILBMDecoder {
Common::SeekableReadStream *_in;
bool _disposeStream;
void planarToChunky(byte *out, uint32 width, byte *in, uint32 planeWidth, uint32 nPlanes, bool packPlanes);
protected:
IFFParser _parser;
struct ILBMDecoder {
/**
* ILBM header data, necessary for loadBitmap()
*/
Graphics::BMHD _header;
bool _hasHeader;
uint32 _bodySize;
uint32 _paletteSize;
/**
* Available decoding modes for loadBitmap().
*/
enum {
ILBM_UNPACK_PLANES = 0xFF, //!< Decode all bitplanes, and map 1 pixel to 1 byte.
ILBM_PACK_PLANES = 0x100, //!< Request unpacking, used as a mask with below options.
public:
ILBMDecoder(Common::SeekableReadStream *input, bool disposeStream = false);
ILBM_1_PLANES = 1, //!< Decode only the first bitplane, don't pack.
ILBM_1_PACK_PLANES = ILBM_1_PLANES | ILBM_PACK_PLANES, //!< Decode only the first bitplane, pack 8 pixels in 1 byte.
ILBM_2_PLANES = 2, //!< Decode first 2 bitplanes, don't pack.
ILBM_2_PACK_PLANES = ILBM_2_PLANES | ILBM_PACK_PLANES, //!< Decode first 2 bitplanes, pack 4 pixels in 1 byte.
ILBM_3_PLANES = 3, //!< Decode first 3 bitplanes, don't pack.
ILBM_4_PLANES = 4, //!< Decode first 4 bitplanes, don't pack.
ILBM_4_PACK_PLANES = ILBM_4_PLANES | ILBM_PACK_PLANES, //!< Decode first 4 bitplanes, pack 2 pixels in 1 byte.
ILBM_5_PLANES = 5, //!< Decode first 5 bitplanes, don't pack.
ILBM_8_PLANES = 8 //!< Decode all 8 bitplanes.
};
virtual ~ILBMDecoder();
/**
* Fills the _header member from the given stream.
*/
void loadHeader(Common::ReadStream *stream);
uint32 getWidth();
uint32 getHeight();
uint32 getNumColors();
byte *getPalette();
/**
* Loads and unpacks the ILBM bitmap data from the stream into the buffer.
* The functions assumes the buffer is large enough to contain all data.
* The caller controls how data should be packed by choosing mode from
* the enum above.
*/
void loadBitmap(uint32 mode, byte *buffer, Common::ReadStream *stream);
byte *getBitmap(uint32 numPlanes, bool packPlanes);
byte *getBitmap() {
assert(_hasHeader);
return getBitmap(_header.depth, false);
}
/**
* Converts from bitplanar to chunky representation. Intended for internal
* usage, but you can be (ab)use it from client code if you know what you
* are doing.
*/
void planarToChunky(byte *out, uint32 width, byte *in, uint32 planeWidth, uint32 nPlanes, bool packPlanes);
};

View file

@ -7,6 +7,7 @@ MODULE_OBJS := \
debug.o \
detection.o \
dialogue.o \
disk.o \
disk_br.o \
disk_ns.o \
exec.o \