diff options
author | Martin Kiewitz | 2015-06-08 16:41:41 +0200 |
---|---|---|
committer | Martin Kiewitz | 2015-06-08 16:41:41 +0200 |
commit | 65d94d3fb3e0a06219982cc0a8fb885c35b893b9 (patch) | |
tree | 8a8cf204201896139d3cf19b9e548f56c07d9157 | |
parent | 9e964b52b1c6d8711f690cf50af9675449fca094 (diff) | |
download | scummvm-rg350-65d94d3fb3e0a06219982cc0a8fb885c35b893b9.tar.gz scummvm-rg350-65d94d3fb3e0a06219982cc0a8fb885c35b893b9.tar.bz2 scummvm-rg350-65d94d3fb3e0a06219982cc0a8fb885c35b893b9.zip |
SHERLOCK: 3DO cel decoding support
-rw-r--r-- | engines/sherlock/resources.cpp | 387 | ||||
-rw-r--r-- | engines/sherlock/resources.h | 14 | ||||
-rw-r--r-- | engines/sherlock/scalpel/scalpel.cpp | 39 |
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; } |