Skip to content

Commit

Permalink
GRAPHICS: Rewrite PictDecoder's opcode handling
Browse files Browse the repository at this point in the history
In preparation for adding support for multiple CompressedQuickTime opcodes
  • Loading branch information
Matthew Hoops committed Oct 7, 2011
1 parent d5a763b commit 583bef2
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 83 deletions.
289 changes: 208 additions & 81 deletions graphics/pict.cpp
Expand Up @@ -45,10 +45,164 @@ PictDecoder::~PictDecoder() {
delete _jpeg;
}

#define OPCODE(a, b, c) _opcodes.push_back(PICTOpcode(a, &PictDecoder::b, c))

void PictDecoder::setupOpcodesCommon() {
OPCODE(0x0000, o_nop, "NOP");
OPCODE(0x0001, o_clip, "Clip");
OPCODE(0x0003, o_txFont, "TxFont");
OPCODE(0x0004, o_txFace, "TxFace");
OPCODE(0x0007, o_pnSize, "PnSize");
OPCODE(0x000D, o_txSize, "TxSize");
OPCODE(0x0010, o_txRatio, "TxRatio");
OPCODE(0x0011, o_versionOp, "VersionOp");
OPCODE(0x001E, o_nop, "DefHilite");
OPCODE(0x0028, o_longText, "LongText");
OPCODE(0x00A1, o_longComment, "LongComment");
OPCODE(0x00FF, o_opEndPic, "OpEndPic");
OPCODE(0x0C00, o_headerOp, "HeaderOp");
}

void PictDecoder::setupOpcodesNormal() {
setupOpcodesCommon();
OPCODE(0x0098, on_packBitsRect, "PackBitsRect");
OPCODE(0x009A, on_directBitsRect, "DirectBitsRect");
OPCODE(0x8200, on_compressedQuickTime, "CompressedQuickTime");
}

void PictDecoder::setupOpcodesQuickTime() {
setupOpcodesCommon();
OPCODE(0x0098, oq_packBitsRect, "PackBitsRect");
OPCODE(0x009A, oq_directBitsRect, "DirectBitsRect");
OPCODE(0x8200, oq_compressedQuickTime, "CompressedQuickTime");
}

#undef OPCODE

void PictDecoder::o_nop(Common::SeekableReadStream *) {
// Nothing to do
}

void PictDecoder::o_clip(Common::SeekableReadStream *stream) {
// Ignore
stream->skip(stream->readUint16BE() - 2);
}

void PictDecoder::o_txFont(Common::SeekableReadStream *stream) {
// Ignore
stream->readUint16BE();
}

void PictDecoder::o_txFace(Common::SeekableReadStream *stream) {
// Ignore
stream->readByte();
}

void PictDecoder::o_pnSize(Common::SeekableReadStream *stream) {
// Ignore
stream->readUint16BE();
stream->readUint16BE();
}

void PictDecoder::o_txSize(Common::SeekableReadStream *stream) {
// Ignore
stream->readUint16BE();
}

void PictDecoder::o_txRatio(Common::SeekableReadStream *stream) {
// Ignore
stream->readUint16BE();
stream->readUint16BE();
stream->readUint16BE();
stream->readUint16BE();
}

void PictDecoder::o_versionOp(Common::SeekableReadStream *stream) {
// We only support v2 extended
if (stream->readUint16BE() != 0x02FF)
error("Unknown PICT version");
}

void PictDecoder::o_longText(Common::SeekableReadStream *stream) {
// Ignore
stream->readUint16BE();
stream->readUint16BE();
stream->skip(stream->readByte());
}

void PictDecoder::o_longComment(Common::SeekableReadStream *stream) {
// Ignore
stream->readUint16BE();
stream->skip(stream->readUint16BE());
}

void PictDecoder::o_opEndPic(Common::SeekableReadStream *stream) {
// We've reached the end of the picture
_continueParsing = false;
}

void PictDecoder::o_headerOp(Common::SeekableReadStream *stream) {
// Read the basic header, but we don't really have to do anything with it
/* uint16 version = */ stream->readUint16BE();
stream->readUint16BE(); // Reserved
/* uint32 hRes = */ stream->readUint32BE();
/* uint32 vRes = */ stream->readUint32BE();
Common::Rect origResRect;
origResRect.top = stream->readUint16BE();
origResRect.left = stream->readUint16BE();
origResRect.bottom = stream->readUint16BE();
origResRect.right = stream->readUint16BE();
stream->readUint32BE(); // Reserved
}

void PictDecoder::on_packBitsRect(Common::SeekableReadStream *stream) {
// Unpack data (8bpp or lower)
unpackBitsRect(stream, true);
}

void PictDecoder::on_directBitsRect(Common::SeekableReadStream *stream) {
// Unpack data (16bpp or higher)
unpackBitsRect(stream, false);
}

void PictDecoder::on_compressedQuickTime(Common::SeekableReadStream *stream) {
// OK, here's the fun. We get to completely change how QuickDraw draws
// the data in PICT files.

// Swap out the opcodes to the new ones
_opcodes.clear();
setupOpcodesQuickTime();

// We'll decode the first QuickTime data from here, but the QuickTime-specific
// opcodes will take over from here on out. Normal opcodes, signing off.
decodeCompressedQuickTime(stream);
}

void PictDecoder::oq_packBitsRect(Common::SeekableReadStream *stream) {
// Skip any data here (8bpp or lower)
skipBitsRect(stream, true);
}

void PictDecoder::oq_directBitsRect(Common::SeekableReadStream *stream) {
// Skip any data here (16bpp or higher)
skipBitsRect(stream, false);
}

void PictDecoder::oq_compressedQuickTime(Common::SeekableReadStream *stream) {
// Just pass the data along
decodeCompressedQuickTime(stream);
}

Surface *PictDecoder::decodeImage(Common::SeekableReadStream *stream, byte *palette) {
assert(stream);

// Initialize opcodes to their normal state
_opcodes.clear();
setupOpcodesNormal();

_outputSurface = 0;
_continueParsing = true;
memset(_palette, 0, sizeof(_palette));

uint16 fileSize = stream->readUint16BE();

Expand All @@ -63,70 +217,41 @@ Surface *PictDecoder::decodeImage(Common::SeekableReadStream *stream, byte *pale
_imageRect.bottom = stream->readUint16BE();
_imageRect.right = stream->readUint16BE();
_imageRect.debugPrint(0, "PICT Rect:");
_isPaletted = false;

// NOTE: This is only a subset of the full PICT format.
// - Only V2 Images Supported
// - Only V2 (Extended) Images Supported
// - CompressedQuickTime (JPEG) compressed data is supported
// - DirectBitsRect/PackBitsRect compressed data is supported
for (uint32 opNum = 0; !stream->eos() && !stream->err() && stream->pos() < stream->size(); opNum++) {
for (uint32 opNum = 0; !stream->eos() && !stream->err() && stream->pos() < stream->size() && _continueParsing; opNum++) {
// PICT v2 opcodes are two bytes
uint16 opcode = stream->readUint16BE();
debug(2, "Found PICT opcode %04x", opcode);

if (opNum == 0 && opcode != 0x0011)
error ("Cannot find PICT version opcode");
error("Cannot find PICT version opcode");
else if (opNum == 1 && opcode != 0x0C00)
error ("Cannot find PICT header opcode");

if (opcode == 0x0000) { // Nop
stream->readUint16BE(); // Unknown
} else if (opcode == 0x0001) { // Clip
// Ignore
uint16 clipSize = stream->readUint16BE();
stream->seek(clipSize - 2, SEEK_CUR);
} else if (opcode == 0x0007) { // PnSize
// Ignore
stream->readUint16BE();
stream->readUint16BE();
} else if (opcode == 0x0011) { // VersionOp
uint16 version = stream->readUint16BE();
if (version != 0x02FF)
error ("Unknown PICT version");
} else if (opcode == 0x001E) { // DefHilite
// Ignore, Contains no Data
} else if (opcode == 0x0098) { // PackBitsRect
decodeDirectBitsRect(stream, true);
_isPaletted = true;
} else if (opcode == 0x009A) { // DirectBitsRect
decodeDirectBitsRect(stream, false);
} else if (opcode == 0x00A1) { // LongComment
stream->readUint16BE();
uint16 dataSize = stream->readUint16BE();
stream->seek(dataSize, SEEK_CUR);
} else if (opcode == 0x00FF) { // OpEndPic
stream->readUint16BE();
break;
} else if (opcode == 0x0C00) { // HeaderOp
/* uint16 version = */ stream->readUint16BE();
stream->readUint16BE(); // Reserved
/* uint32 hRes = */ stream->readUint32BE();
/* uint32 vRes = */ stream->readUint32BE();
Common::Rect origResRect;
origResRect.top = stream->readUint16BE();
origResRect.left = stream->readUint16BE();
origResRect.bottom = stream->readUint16BE();
origResRect.right = stream->readUint16BE();
stream->readUint32BE(); // Reserved
} else if (opcode == 0x8200) { // CompressedQuickTime
decodeCompressedQuickTime(stream);
break;
} else {
warning("Unknown PICT opcode %04x", opcode);
error("Cannot find PICT header opcode");

// Since opcodes are word-aligned, we need to mark our starting
// position here.
uint32 startPos = stream->pos();

for (uint32 i = 0; i < _opcodes.size(); i++) {
if (_opcodes[i].op == opcode) {
debug(4, "Running PICT opcode %04x '%s'", opcode, _opcodes[i].desc);
(this->*(_opcodes[i].proc))(stream);
break;
} else if (i == _opcodes.size() - 1) {
// Unknown opcode; attempt to continue forward
warning("Unknown PICT opcode %04x", opcode);
}
}

// Align
stream->skip((stream->pos() - startPos) & 1);
}

// If we got a palette throughout this nonsense, go and grab it
if (palette && _isPaletted)
if (palette)
memcpy(palette, _palette, 256 * 3);

return _outputSurface;
Expand Down Expand Up @@ -155,21 +280,18 @@ PictDecoder::PixMap PictDecoder::readPixMap(Common::SeekableReadStream *stream,
return pixMap;
}

struct DirectBitsRectData {
struct PackBitsRectData {
PictDecoder::PixMap pixMap;
Common::Rect srcRect;
Common::Rect dstRect;
uint16 mode;
};

void PictDecoder::decodeDirectBitsRect(Common::SeekableReadStream *stream, bool hasPalette) {
void PictDecoder::unpackBitsRect(Common::SeekableReadStream *stream, bool hasPalette) {
static const PixelFormat directBitsFormat16 = PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0);

// Clear the palette
memset(_palette, 0, sizeof(_palette));

DirectBitsRectData directBitsData;
directBitsData.pixMap = readPixMap(stream, !hasPalette);
PackBitsRectData packBitsData;
packBitsData.pixMap = readPixMap(stream, !hasPalette);

// Read in the palette if there is one present
if (hasPalette) {
Expand All @@ -186,47 +308,47 @@ void PictDecoder::decodeDirectBitsRect(Common::SeekableReadStream *stream, bool
}
}

directBitsData.srcRect.top = stream->readUint16BE();
directBitsData.srcRect.left = stream->readUint16BE();
directBitsData.srcRect.bottom = stream->readUint16BE();
directBitsData.srcRect.right = stream->readUint16BE();
directBitsData.dstRect.top = stream->readUint16BE();
directBitsData.dstRect.left = stream->readUint16BE();
directBitsData.dstRect.bottom = stream->readUint16BE();
directBitsData.dstRect.right = stream->readUint16BE();
directBitsData.mode = stream->readUint16BE();
packBitsData.srcRect.top = stream->readUint16BE();
packBitsData.srcRect.left = stream->readUint16BE();
packBitsData.srcRect.bottom = stream->readUint16BE();
packBitsData.srcRect.right = stream->readUint16BE();
packBitsData.dstRect.top = stream->readUint16BE();
packBitsData.dstRect.left = stream->readUint16BE();
packBitsData.dstRect.bottom = stream->readUint16BE();
packBitsData.dstRect.right = stream->readUint16BE();
packBitsData.mode = stream->readUint16BE();

uint16 width = directBitsData.srcRect.width();
uint16 height = directBitsData.srcRect.height();
uint16 width = packBitsData.srcRect.width();
uint16 height = packBitsData.srcRect.height();

byte bytesPerPixel = 0;

if (directBitsData.pixMap.pixelSize <= 8)
if (packBitsData.pixMap.pixelSize <= 8)
bytesPerPixel = 1;
else if (directBitsData.pixMap.pixelSize == 32)
else if (packBitsData.pixMap.pixelSize == 32)
bytesPerPixel = 3;
else
bytesPerPixel = directBitsData.pixMap.pixelSize / 8;
bytesPerPixel = packBitsData.pixMap.pixelSize / 8;

_outputSurface = new Graphics::Surface();
_outputSurface->create(width, height, (bytesPerPixel == 1) ? PixelFormat::createFormatCLUT8() : _pixelFormat);
byte *buffer = new byte[width * height * bytesPerPixel];

// Read in amount of data per row
for (uint16 i = 0; i < directBitsData.pixMap.bounds.height(); i++) {
for (uint16 i = 0; i < packBitsData.pixMap.bounds.height(); i++) {
// NOTE: Compression 0 is "default". The format in SCI games is packed when 0.
// In the future, we may need to have something to set the "default" packing
// format, but this is good for now.

if (directBitsData.pixMap.packType == 1 || directBitsData.pixMap.rowBytes < 8) { // Unpacked, Pad-Byte (on 24-bit)
if (packBitsData.pixMap.packType == 1 || packBitsData.pixMap.rowBytes < 8) { // Unpacked, Pad-Byte (on 24-bit)
// TODO: Finish this. Hasn't been needed (yet).
error("Unpacked DirectBitsRect data (padded)");
} else if (directBitsData.pixMap.packType == 2) { // Unpacked, No Pad-Byte (on 24-bit)
} else if (packBitsData.pixMap.packType == 2) { // Unpacked, No Pad-Byte (on 24-bit)
// TODO: Finish this. Hasn't been needed (yet).
error("Unpacked DirectBitsRect data (not padded)");
} else if (directBitsData.pixMap.packType == 0 || directBitsData.pixMap.packType > 2) { // Packed
uint16 byteCount = (directBitsData.pixMap.rowBytes > 250) ? stream->readUint16BE() : stream->readByte();
decodeDirectBitsLine(buffer + i * _outputSurface->w * bytesPerPixel, directBitsData.pixMap.rowBytes, stream->readStream(byteCount), directBitsData.pixMap.pixelSize, bytesPerPixel);
} else if (packBitsData.pixMap.packType == 0 || packBitsData.pixMap.packType > 2) { // Packed
uint16 byteCount = (packBitsData.pixMap.rowBytes > 250) ? stream->readUint16BE() : stream->readByte();
unpackBitsLine(buffer + i * _outputSurface->w * bytesPerPixel, packBitsData.pixMap.rowBytes, stream->readStream(byteCount), packBitsData.pixMap.pixelSize, bytesPerPixel);
}
}

Expand Down Expand Up @@ -264,7 +386,7 @@ void PictDecoder::decodeDirectBitsRect(Common::SeekableReadStream *stream, bool
delete[] buffer;
}

void PictDecoder::decodeDirectBitsLine(byte *out, uint32 length, Common::SeekableReadStream *data, byte bitsPerPixel, byte bytesPerPixel) {
void PictDecoder::unpackBitsLine(byte *out, uint32 length, Common::SeekableReadStream *data, byte bitsPerPixel, byte bytesPerPixel) {
uint32 dataDecoded = 0;
byte bytesPerDecode = (bytesPerPixel == 2) ? 2 : 1;

Expand Down Expand Up @@ -299,11 +421,16 @@ void PictDecoder::decodeDirectBitsLine(byte *out, uint32 length, Common::Seekabl
dataDecoded += length / 4;

if (length != dataDecoded)
warning("Mismatched DirectBits read (%d/%d)", dataDecoded, length);
warning("Mismatched PackBits read (%d/%d)", dataDecoded, length);

delete data;
}

void PictDecoder::skipBitsRect(Common::SeekableReadStream *stream, bool hasPalette) {
// TODO
error("TODO: PICT-QuickTime mode: skip PackBitsRect/DirectBitsRect");
}

void PictDecoder::outputPixelBuffer(byte *&out, byte value, byte bitsPerPixel) {
switch (bitsPerPixel) {
case 1:
Expand Down

0 comments on commit 583bef2

Please sign in to comment.