aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Kiewitz2015-06-08 16:41:41 +0200
committerMartin Kiewitz2015-06-08 16:41:41 +0200
commit65d94d3fb3e0a06219982cc0a8fb885c35b893b9 (patch)
tree8a8cf204201896139d3cf19b9e548f56c07d9157
parent9e964b52b1c6d8711f690cf50af9675449fca094 (diff)
downloadscummvm-rg350-65d94d3fb3e0a06219982cc0a8fb885c35b893b9.tar.gz
scummvm-rg350-65d94d3fb3e0a06219982cc0a8fb885c35b893b9.tar.bz2
scummvm-rg350-65d94d3fb3e0a06219982cc0a8fb885c35b893b9.zip
SHERLOCK: 3DO cel decoding support
-rw-r--r--engines/sherlock/resources.cpp387
-rw-r--r--engines/sherlock/resources.h14
-rw-r--r--engines/sherlock/scalpel/scalpel.cpp39
3 files changed, 370 insertions, 70 deletions
diff --git a/engines/sherlock/resources.cpp b/engines/sherlock/resources.cpp
index ab5c6ab56f..cc45756588 100644
--- a/engines/sherlock/resources.cpp
+++ b/engines/sherlock/resources.cpp
@@ -602,18 +602,18 @@ ImageFile3DO::~ImageFile3DO() {
void ImageFile3DO::load(Common::SeekableReadStream &stream, bool animImages) {
uint32 headerId = stream.readUint32BE();
- // Sekk back to the start
- stream.seek(0);
+ assert(!stream.eos());
+
+ // Seek back to the start
+ stream.seek(-4, SEEK_CUR);
// Identify type of file
switch (headerId) {
case MKTAG('C', 'C', 'B', ' '):
- // .cel file (title1a.cel etc.)
- load3DOCelFile(stream);
- break;
-
case MKTAG('A', 'N', 'I', 'M'):
- // 3DO animation file (walk.anim)
+ case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel
+ // 3DO .cel (title1a.cel, etc.) or animation file (walk.anim)
+ load3DOCelFile(stream);
break;
default:
@@ -667,91 +667,346 @@ void ImageFile3DO::loadAnimationFile(Common::SeekableReadStream &stream, bool an
// Load data for frame and decompress it
byte *data = new byte[compressedSize];
+ assert(data);
stream.read(data, compressedSize);
- decompressAnimationFrame(&stream, frame, data);
+
+ // always 16 bits per pixel (RGB555)
+ decompress3DOCelFrame(frame, data, 16, NULL);
+
delete[] data;
push_back(frame);
}
}
-// Decompresses an animation frame of a .3DA file
-// note: the .3DA files seem to use an "in memory" format, which uses the same compression technique
-// as regular 3DO cel files, but every scanline is started with a length UINT16 and each scanline
-// also starts on UINT32 boundaries
-void ImageFile3DO::decompressAnimationFrame(Common::SeekableReadStream *stream, ImageFrame &frame, const byte *src) {
+static byte imagefile3DO_cel_bitsPerPixelLookupTable[8] = {
+ 0, 1, 2, 4, 6, 8, 16, 0
+};
+
+// Reads a 3DO .cel/.anim file
+void ImageFile3DO::load3DOCelFile(Common::SeekableReadStream &stream) {
+ int32 chunkStartPos = 0;
+ uint32 chunkTag = 0;
+ uint32 chunkSize = 0;
+ byte *chunkDataPtr = NULL;
+
+ ImageFrame imageFrame;
+
+ // ANIM chunk (animation header for animation files)
+ bool animFound = false;
+ uint32 animVersion = 0;
+ uint32 animType = 0;
+ uint32 animFrameCount = 1; // we expect 1 frame without an ANIM header
+ // CCB chunk (cel control block)
+ bool ccbFound = false;
+ uint32 ccbVersion = 0;
+ uint32 ccbFlags = 0;
+ bool ccbFlags_compressed = false;
+ uint16 ccbPPMP0 = 0;
+ uint16 ccbPPMP1 = 0;
+ uint32 ccbPRE0 = 0;
+ byte ccbPRE0_bitsPerPixel = 0;
+ uint32 ccbPRE1 = 0;
+ uint32 ccbWidth = 0;
+ uint32 ccbHeight = 0;
+ // pixel lookup table
+ bool plutFound = false;
+ uint32 plutCount = 0;
+ ImageFile3DOPixelLookupTable plutRGBlookupTable;
+
+ memset(&plutRGBlookupTable, 0, sizeof(plutRGBlookupTable));
+
+ while (!stream.err() && !stream.eos()) {
+ chunkStartPos = stream.pos();
+ chunkTag = stream.readUint32BE();
+ chunkSize = stream.readUint32BE();
+
+ if (chunkSize < 8)
+ error("load3DOCelFile: Invalid chunk size");
+
+ uint32 dataSize = chunkSize - 8;
+
+ if (stream.eos() || stream.err())
+ break;
+
+ switch (chunkTag) {
+ case MKTAG('A', 'N', 'I', 'M'):
+ // animation header
+ assert(dataSize >= 24);
+
+ if (animFound)
+ error("load3DOCelFile: multiple ANIM chunks not supported");
+
+ animFound = true;
+ animVersion = stream.readUint32BE();
+ animType = stream.readUint32BE();
+ animFrameCount = stream.readUint32BE();
+ // UINT32 - framerate (0x2000 in walk.anim???)
+ // UINT32 - starting frame (0 for walk.anim)
+ // UINT32 - number of loops (0 for walk.anim)
+
+ if (animVersion != 0)
+ error("load3DOCelFile: Unsupported animation file version");
+ if (animType != 1)
+ error("load3DOCelFile: Only single CCB animation files are supported");
+ break;
+
+ case MKTAG('C', 'C', 'B', ' '):
+ // CEL control block
+ assert(dataSize >= 72);
+
+ if (ccbFound)
+ error("load3DOCelFile: multiple CCB chunks not supported");
+
+ ccbFound = true;
+ ccbVersion = stream.readUint32BE();
+ ccbFlags = stream.readUint32BE();
+ stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware
+ stream.skip(8 * 4); // skip over 8 offset fields
+ ccbPPMP0 = stream.readUint16BE();
+ ccbPPMP1 = stream.readUint16BE();
+ ccbPRE0 = stream.readUint32BE();
+ ccbPRE1 = stream.readUint32BE();
+ ccbWidth = stream.readUint32BE();
+ ccbHeight = stream.readUint32BE();
+
+ if (ccbVersion != 0)
+ error("load3DOCelFile: Unsupported CCB version");
+
+ if (ccbFlags & 0x200) // bit 9
+ ccbFlags_compressed = true;
+
+ // PRE0 first 3 bits define how many bits per encoded pixel are used
+ ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07];
+ if (!ccbPRE0_bitsPerPixel)
+ error("load3DOCelFile: Invalid CCB PRE0 bits per pixel");
+ break;
+
+ case MKTAG('P', 'L', 'U', 'T'):
+ // pixel lookup table
+ // optional, not required for at least 16-bit pixel data
+ assert(dataSize >= 6);
+
+ if (!ccbFound)
+ error("load3DOCelFile: PLUT chunk found without CCB chunk");
+ if (plutFound)
+ error("load3DOCelFile: multiple PLUT chunks currently not supported");
+
+ plutFound = true;
+ plutCount = stream.readUint32BE();
+ // table follows, each entry is 16bit RGB555
+ assert(dataSize >= 4 + (plutCount * 2)); // security check
+ assert(plutCount <= 256); // security check
+
+ for (uint32 plutColor = 0; plutColor < plutCount; plutColor++) {
+ plutRGBlookupTable.pixelColor[plutColor] = stream.readUint16BE();
+ }
+ break;
+
+ case MKTAG('X', 'T', 'R', 'A'):
+ // Unknown contents, occurs right before PDAT
+ break;
+
+ case MKTAG('P', 'D', 'A', 'T'):
+ // pixel data for one frame
+ // may be compressed or uncompressed pixels
+
+ if (ccbPRE0_bitsPerPixel != 16) {
+ // We require a pixel lookup table in case bits-per-pixel is lower than 16
+ if (!plutFound)
+ error("load3DOCelFile: bits per pixel < 16, but no pixel lookup table was found");
+ } else {
+ // But we don't like it in case bits-per-pixel is 16 and we find one
+ if (plutFound)
+ error("load3DOCelFile: bits per pixel == 16, but pixel lookup table was found as well");
+ }
+ // read data into memory
+ chunkDataPtr = new byte[dataSize];
+ assert(chunkDataPtr);
+
+ stream.read(chunkDataPtr, dataSize);
+
+ // Set up frame
+ imageFrame._width = ccbWidth;
+ imageFrame._height = ccbHeight;
+ imageFrame._paletteBase = 0;
+ imageFrame._offset.x = 0;
+ imageFrame._offset.y = 0;
+ imageFrame._rleEncoded = ccbFlags_compressed;
+ imageFrame._size = 0;
+
+ // Decompress/copy this frame
+ if (!plutFound) {
+ decompress3DOCelFrame(imageFrame, chunkDataPtr, ccbPRE0_bitsPerPixel, NULL);
+ } else {
+ decompress3DOCelFrame(imageFrame, chunkDataPtr, ccbPRE0_bitsPerPixel, &plutRGBlookupTable);
+ }
+
+ delete[] chunkDataPtr;
+
+ push_back(imageFrame);
+ break;
+
+ case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel
+ // unknown contents
+ break;
+
+ default:
+ error("Unsupported '%s' chunk in 3DO cel file", tag2str(chunkTag));
+ }
+
+ // Seek to end of chunk
+ stream.seek(chunkStartPos + chunkSize);
+ }
+}
+
+static uint16 imagefile3DO_cel_bitsMask[17] = {
+ 0,
+ 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
+ 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
+};
+
+// gets [bitCount] bits from dataPtr, going from MSB to LSB
+inline uint16 ImageFile3DO::celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft) {
+ byte resultBitsLeft = bitCount;
+ uint16 result = 0;
+ byte currentByte = *dataPtr;
+
+ // Get bits of current byte
+ while (resultBitsLeft) {
+ if (resultBitsLeft < dataBitsLeft) {
+ // we need less than we have left
+ result |= (currentByte >> (dataBitsLeft - resultBitsLeft)) & imagefile3DO_cel_bitsMask[resultBitsLeft];
+ dataBitsLeft -= resultBitsLeft;
+ resultBitsLeft = 0;
+
+ } else {
+ // we need as much as we have left or more
+ resultBitsLeft -= dataBitsLeft;
+ result |= (currentByte & imagefile3DO_cel_bitsMask[dataBitsLeft]) << resultBitsLeft;
+
+ // Go to next byte
+ dataPtr++;
+ currentByte = *dataPtr; dataBitsLeft = 8;
+ }
+ }
+ return result;
+}
+
+// decompress/copy 3DO cel data
+void ImageFile3DO::decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable) {
frame._frame.create(frame._width, frame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
uint16 *dest = (uint16 *)frame._frame.getPixels();
Common::fill(dest, dest + frame._width * frame._height, 0);
- const byte *srcSeeker = src;
-
- // CEL compression
int frameHeightLeft = frame._height;
int frameWidthLeft = frame._width;
+ uint16 pixelCount = 0;
+ uint16 pixel = 0;
- while (frameHeightLeft > 0) {
- frameWidthLeft = frame._width;
+ if (bitsPerPixel == 16) {
+ // Must not use pixel lookup table on 16-bits-per-pixel data
+ assert(!pixelLookupTable);
+ }
- uint16 dwordSize = READ_BE_UINT16(srcSeeker);
- dwordSize += 2;
- uint16 lineByteSize = dwordSize * 4;
+ if (frame._rleEncoded) {
+ // compressed
+ const byte *srcLineStart = dataPtr;
+ const byte *srcLineData = dataPtr;
+ byte srcLineDataBitsLeft = 0;
+ uint16 lineDWordSize = 0;
+ byte compressionType = 0;
+ byte compressionPixels = 0;
+
+ while (frameHeightLeft > 0) {
+ frameWidthLeft = frame._width;
+
+ if (bitsPerPixel >= 8) {
+ lineDWordSize = READ_BE_UINT16(srcLineStart);
+ srcLineData = srcLineStart + 2;
+ } else {
+ lineDWordSize = *srcLineStart;
+ srcLineData = srcLineStart + 1;
+ }
+ srcLineDataBitsLeft = 8;
- // debug
- //warning("offset %d: decoding line, size %d, bytesize %d", srcSeeker - src, dwordSize, lineByteSize);
+ lineDWordSize += 2;
+ uint16 lineByteSize = lineDWordSize * 4; // calculate compressed data size in bytes for current line
- const byte *srcLine = srcSeeker + 2; // start at 3rd byte
- while (frameWidthLeft > 0) {
- byte compressionByte = *srcLine++;
- byte compressionType = compressionByte >> 6; // upper 2 bits == type
- byte compressionPixels = (compressionByte & 0x3F) + 1; // lower 6 bits == length (0 = 1 pixel)
- uint16 pixelCount;
- uint16 pixel;
+ // debug
+ //warning("offset %d: decoding line, size %d, bytesize %d", srcSeeker - src, dwordSize, lineByteSize);
- if (!compressionType) // end of line
- break;
+ while (frameWidthLeft > 0) {
+ // get 2 bits -> compressionType
+ // get 6 bits -> pixel count (0 = 1 pixel)
+ compressionType = celGetBits(srcLineData, 2, srcLineDataBitsLeft);
+ // 6 bits == length (0 = 1 pixel)
+ compressionPixels = celGetBits(srcLineData, 6, srcLineDataBitsLeft) + 1;
- switch(compressionType) {
- case 1: // simple copy
- for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
- pixel = READ_BE_UINT16(srcLine); srcLine += 2;
- *dest++ = convertPixel(pixel);
- }
- break;
- case 2: // transparent
- for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
- *dest++ = 0;
- }
- break;
- case 3: // duplicate pixels
- pixel = READ_BE_UINT16(srcLine); srcLine += 2;
- pixel = convertPixel(pixel);
- for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
- *dest++ = pixel;
+ if (!compressionType) // end of line
+ break;
+
+ switch(compressionType) {
+ case 1: // simple copy
+ for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
+ pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
+ if (pixelLookupTable) {
+ pixel = pixelLookupTable->pixelColor[pixel];
+ }
+ *dest++ = convertPixel(pixel);
+ }
+ break;
+ case 2: // transparent
+ for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
+ *dest++ = 0;
+ }
+ break;
+ case 3: // duplicate pixels
+ pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
+ if (pixelLookupTable) {
+ pixel = pixelLookupTable->pixelColor[pixel];
+ }
+ pixel = convertPixel(pixel);
+ for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
+ *dest++ = pixel;
+ }
+ break;
+ default:
+ break;
}
- break;
- default:
- break;
+ frameWidthLeft -= compressionPixels;
}
- frameWidthLeft -= compressionPixels;
- }
- assert(frameWidthLeft >= 0);
+ assert(frameWidthLeft >= 0);
- if (frameWidthLeft > 0) {
- // still pixels left? skip them
- dest += frameWidthLeft;
- }
+ if (frameWidthLeft > 0) {
+ // still pixels left? skip them
+ dest += frameWidthLeft;
+ }
- frameHeightLeft--;
+ frameHeightLeft--;
- // Seek to next line start
- srcSeeker += lineByteSize;
- }
-}
+ // Seek to next line start
+ srcLineStart += lineByteSize;
+ }
+ } else {
+ // uncompressed
+ byte dataBitsLeft = 8;
+
+ while (frameHeightLeft > 0) {
+ frameWidthLeft = frame._width;
+ while (frameWidthLeft > 0) {
+ pixel = celGetBits(dataPtr, bitsPerPixel, dataBitsLeft);
+ if (pixelLookupTable) {
+ pixel = pixelLookupTable->pixelColor[pixel];
+ }
+ *dest++ = convertPixel(pixel);
-void ImageFile3DO::load3DOCelFile(Common::SeekableReadStream &stream) {
- warning("3DO-cel file loader currently missing");
+ frameWidthLeft--;
+ }
+ frameHeightLeft--;
+ }
+ }
}
} // End of namespace Sherlock
diff --git a/engines/sherlock/resources.h b/engines/sherlock/resources.h
index 44da810d29..795918be41 100644
--- a/engines/sherlock/resources.h
+++ b/engines/sherlock/resources.h
@@ -222,6 +222,10 @@ public:
static void setVm(SherlockEngine *vm);
};
+struct ImageFile3DOPixelLookupTable {
+ uint16 pixelColor[256];
+};
+
class ImageFile3DO : public Common::Array<ImageFrame> {
private:
static SherlockEngine *_vm;
@@ -241,15 +245,17 @@ private:
*/
void load3DOCelFile(Common::SeekableReadStream &stream);
+ inline uint16 celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft);
+
/**
- * Load animation graphics file
+ * Decompress a single frame of a 3DO cel file
*/
- void loadAnimationFile(Common::SeekableReadStream &stream, bool animImages);
+ void decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable);
/**
- * Decompress a single frame for the sprite
+ * Load animation graphics file
*/
- void decompressAnimationFrame(Common::SeekableReadStream *stream, ImageFrame &frame, const byte *src);
+ void loadAnimationFile(Common::SeekableReadStream &stream, bool animImages);
public:
ImageFile3DO(const Common::String &name, bool animImages = false);
diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp
index abca2997f6..35ef6648b0 100644
--- a/engines/sherlock/scalpel/scalpel.cpp
+++ b/engines/sherlock/scalpel/scalpel.cpp
@@ -533,9 +533,48 @@ bool ScalpelEngine::showCityCutscene3DO() {
bool finished = _animation->play3DO("26open1", true, 1, 255, 2);
+ if (finished) {
+ // TODO: Both of these should actually fade into the screen
+ _screen->_backBuffer2.blitFrom(*_screen);
+
+ // "London, England"
+ ImageFile3DO titleImage_London("title2a.cel");
+
+ _screen->transBlitFromUnscaled3DO(titleImage_London[0]._frame, Common::Point(10, 11));
+ finished = _events->delay(1000, true);
+
+ if (finished) {
+ // "November, 1888"
+ ImageFile3DO titleImage_November("title2b.cel");
+
+ _screen->transBlitFromUnscaled3DO(titleImage_November[0]._frame, Common::Point(101, 102));
+ finished = _events->delay(5000, true);
+
+ if (finished) {
+ _screen->blitFrom(_screen->_backBuffer2);
+ }
+ }
+ }
+
if (finished)
finished = _animation->play3DO("26open2", true, 1, 0, 2);
+ if (finished) {
+ // "Sherlock Holmes" (title)
+ ImageFile3DO titleImage_SherlockHolmesTitle("title1ab.cel");
+
+ _screen->transBlitFromUnscaled3DO(titleImage_SherlockHolmesTitle[0]._frame, Common::Point(34, 21));
+ finished = _events->delay(500, true);
+
+ // Title should fade in, Copyright should be displayed a bit after that
+ if (finished) {
+ //ImageFile3DO titleImage_Copyright("title1c.cel");
+
+ //_screen->transBlitFromUnscaled3DO(titleImage_Copyright[0]._frame, Common::Point(4, 190));
+ finished = _events->delay(3500, true);
+ }
+ // Title is supposed to get faded away after that
+ }
return finished;
}