[Scummvm-cvs-logs] scummvm master -> 65d94d3fb3e0a06219982cc0a8fb885c35b893b9

m-kiewitz m_kiewitz at users.sourceforge.net
Mon Jun 8 16:41:31 CEST 2015


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
65d94d3fb3 SHERLOCK: 3DO cel decoding support


Commit: 65d94d3fb3e0a06219982cc0a8fb885c35b893b9
    https://github.com/scummvm/scummvm/commit/65d94d3fb3e0a06219982cc0a8fb885c35b893b9
Author: Martin Kiewitz (m_kiewitz at users.sourceforge.net)
Date: 2015-06-08T16:41:41+02:00

Commit Message:
SHERLOCK: 3DO cel decoding support

Changed paths:
    engines/sherlock/resources.cpp
    engines/sherlock/resources.h
    engines/sherlock/scalpel/scalpel.cpp



diff --git a/engines/sherlock/resources.cpp b/engines/sherlock/resources.cpp
index ab5c6ab..cc45756 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 44da810..795918b 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 abca299..35ef664 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;
 }
 






More information about the Scummvm-git-logs mailing list