From 085ec30b49fedb032bd9b09cb5dc1729ffc54c0f Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 20 Aug 2017 11:15:42 -0500 Subject: IMAGE: Add support for Indeo4 transparency plane This is used by TITANIC for most of the furniture in the SGT stateroom and Titania's parts. --- image/codecs/indeo/indeo.cpp | 24 +++-- image/codecs/indeo/indeo.h | 3 +- image/codecs/indeo4.cpp | 251 +++++++++++++++++++++++++++++++++++++------ image/codecs/indeo4.h | 9 +- 4 files changed, 240 insertions(+), 47 deletions(-) diff --git a/image/codecs/indeo/indeo.cpp b/image/codecs/indeo/indeo.cpp index f420069139..6b6e3557fe 100644 --- a/image/codecs/indeo/indeo.cpp +++ b/image/codecs/indeo/indeo.cpp @@ -491,6 +491,8 @@ IndeoDecoderBase::~IndeoDecoderBase() { IVIPlaneDesc::freeBuffers(_ctx._planes); if (_ctx._mbVlc._custTab._table) _ctx._mbVlc._custTab.freeVlc(); + if (_ctx._transVlc._custTab._table) + _ctx._transVlc._custTab.freeVlc(); delete _ctx._pFrame; } @@ -575,11 +577,23 @@ int IndeoDecoderBase::decodeIndeoFrame() { outputPlane(&_ctx._planes[2], frame->_data[1], frame->_linesize[1]); outputPlane(&_ctx._planes[1], frame->_data[2], frame->_linesize[2]); + // Merge the planes into the final surface + Graphics::Surface s = _surface->getSubArea(Common::Rect(0, 0, _surface->w, _surface->h)); + YUVToRGBMan.convert410(&s, Graphics::YUVToRGBManager::kScaleITU, + frame->_data[0], frame->_data[1], frame->_data[2], frame->_width, frame->_height, + frame->_width, frame->_width); + + if (_ctx._hasTransp) + decodeTransparency(); + // If the bidirectional mode is enabled, next I and the following P // frame will be sent together. Unfortunately the approach below seems // to be the only way to handle the B-frames mode. // That's exactly the same Intel decoders do. if (_ctx._isIndeo4 && _ctx._frameType == IVI4_FRAMETYPE_INTRA) { + // TODO: It appears from the reference decoder that this should be + // aligning GetBits to a 32-bit boundary before reading again? + int left; // skip version string @@ -595,19 +609,9 @@ int IndeoDecoderBase::decodeIndeoFrame() { } } - // Merge the planes into the final surface - Graphics::Surface s = _surface->getSubArea(Common::Rect(0, 0, _surface->w, _surface->h)); - YUVToRGBMan.convert410(&s, Graphics::YUVToRGBManager::kScaleITU, - frame->_data[0], frame->_data[1], frame->_data[2], frame->_width, frame->_height, - frame->_width, frame->_width); - // Free the now un-needed frame data frame->freeFrame(); - // If there's any transparency data, decode it - if (_ctx._hasTransp) - decodeTransparency(); - return 0; } diff --git a/image/codecs/indeo/indeo.h b/image/codecs/indeo/indeo.h index dcb7330318..962d06817d 100644 --- a/image/codecs/indeo/indeo.h +++ b/image/codecs/indeo/indeo.h @@ -398,6 +398,7 @@ public: IVIHuffTab _mbVlc; ///< current macroblock table descriptor IVIHuffTab _blkVlc; ///< current block table descriptor + IVIHuffTab _transVlc; ///< current transparency table descriptor uint8 _rvmapSel; bool _inImf; @@ -566,7 +567,7 @@ protected: /** * Decodes optional transparency data within Indeo frames */ - virtual void decodeTransparency() {} + virtual int decodeTransparency() { return -1; } /** * Decodes the Indeo frame from the bit reader already diff --git a/image/codecs/indeo4.cpp b/image/codecs/indeo4.cpp index ead1d3a814..40f86f8fe2 100644 --- a/image/codecs/indeo4.cpp +++ b/image/codecs/indeo4.cpp @@ -26,7 +26,9 @@ * written, produced, and directed by Alan Smithee */ +#include "common/debug.h" #include "common/memstream.h" +#include "common/rect.h" #include "common/textconsole.h" #include "graphics/yuv_to_rgb.h" #include "image/codecs/indeo4.h" @@ -595,52 +597,233 @@ int Indeo4Decoder::decodeMbInfo(IVIBandDesc *band, IVITile *tile) { return 0; } -void Indeo4Decoder::decodeTransparency() { - // FIXME: Since I don't currently know how to decode the transparency layer, - // I'm currently doing a hack where I take the color of the top left corner, - // and mark the range of pixels of that color from the start and end of - // each line as transparent - assert(_surface->format.bytesPerPixel == 4); - byte r, g, b; +int Indeo4Decoder::decodeRLETransparency(VLC_TYPE (*table)[2]) { + const uint32 startPos = _ctx._gb->pos(); + + _ctx._gb->align(); + + bool runIsOpaque = _ctx._gb->getBit(); + bool nextRunIsOpaque = !runIsOpaque; + + const uint32 opacityMask = 0xFF << _pixelFormat.aShift; + + uint32 *pixel = (uint32 *)_surface->getPixels(); + const int surfacePixelPitch = _surface->pitch / _surface->format.bytesPerPixel; + const int surfacePadding = surfacePixelPitch - _surface->w; + const uint32 *endOfVisibleRow = pixel + _surface->w; + const uint32 *endOfVisibleArea = pixel + surfacePixelPitch * _surface->h - surfacePadding; + + const int codecAlignedWidth = (_surface->w + 31) & ~31; + const int codecPaddingSize = codecAlignedWidth - _surface->w; + + int numPixelsToRead = codecAlignedWidth * _surface->h; + int numPixelsToSkip = 0; + while (numPixelsToRead > 0) { + int value = _ctx._gb->getVLC2<1>(table, IVI_VLC_BITS); + + if (value == -1) { + warning("Transparency VLC code read failed"); + return -1; + } + + if (value == 0) { + value = 255; + nextRunIsOpaque = runIsOpaque; + } + + numPixelsToRead -= value; + + debugN(9, "%d%s ", value, runIsOpaque ? "O" : "T"); + + // The rest of the transparency data must be consumed but it will not + // participate in writing any more pixels + if (pixel == endOfVisibleArea) { + debug(5, "Indeo4: Done writing transparency, but still need to consume %d pixels", numPixelsToRead + value); + continue; + } + + // If a run ends in the padding area of a row, the next run needs to + // be partially consumed by the remaining pixels of the padding area + if (numPixelsToSkip) { + value -= numPixelsToSkip; + if (value < 0) { + numPixelsToSkip = -value; + value = 0; + } else { + numPixelsToSkip = 0; + } + } + + while (value > 0) { + if (runIsOpaque) { + *pixel = *pixel | opacityMask; + } else { + *pixel = *pixel & ~opacityMask; + } + + --value; + ++pixel; + + if (pixel == endOfVisibleRow) { + pixel += surfacePadding; + endOfVisibleRow += surfacePixelPitch; + value -= codecPaddingSize; + + if (value < 0) { + numPixelsToSkip = -value; + break; + } + + if (pixel == endOfVisibleArea) { + break; + } + } + } + + runIsOpaque = nextRunIsOpaque; + nextRunIsOpaque = !runIsOpaque; + } + + debugN(9, "\n"); + + if (numPixelsToRead != 0) { + warning("Wrong number of transparency pixels read; delta = %d", numPixelsToRead); + } + + _ctx._gb->align(); + + return (_ctx._gb->pos() - startPos) / 8; +} + +int Indeo4Decoder::decodeTransparency() { + if (_ctx._gb->getBits(2) != 3 || _ctx._gb->getBits(3) != 0) { + warning("Invalid transparency marker"); + return -1; + } + + Common::Rect drawRect; + + for (int numRects = _ctx._gb->getBits(8); numRects; --numRects) { + const int x1 = _ctx._gb->getBits(16); + const int y1 = _ctx._gb->getBits(16); + const int x2 = x1 + _ctx._gb->getBits(16); + const int y2 = y1 + _ctx._gb->getBits(16); + drawRect.extend(Common::Rect(x1, y1, x2, y2)); + } + + debug(4, "Indeo4: Transparency rect is (%d, %d, %d, %d)", drawRect.left, drawRect.top, drawRect.right, drawRect.bottom); + + if (_ctx._gb->getBit()) { /* @350 */ + /* @358 */ + int unknown = (_ctx._gb->getBits(8) << 16) | (_ctx._gb->getBits(8) << 8) | (_ctx._gb->getBits(8)); + debug(4, "Indeo4: Unknown is %08x", unknown); + /* @477 */ + // This unknown value gets written out to IVIPicture.field_f8 and does + // not seem to have any obvious effect on the transparency rendering + } + + if (_ctx._gb->getBit() == 0) { /* @4D9 */ + warning("Invalid transparency band?"); + return -1; + } + + IVIHuffDesc huffDesc; + + const int numHuffRows = huffDesc._numRows = _ctx._gb->getBits(4); + if (numHuffRows == 0 || numHuffRows > IVI_VLC_BITS - 1) { + warning("Invalid codebook row count %d", numHuffRows); + return -1; + } + + for (int i = 0; i < numHuffRows; ++i) { + huffDesc._xBits[i] = _ctx._gb->getBits(4); + } + + /* @5E2 */ + _ctx._gb->align(); + + IVIHuffTab &huffTable = _ctx._transVlc; + + if (huffDesc.huffDescCompare(&huffTable._custDesc) || !huffTable._custTab._table) { + if (huffTable._custTab._table) { + huffTable._custTab.freeVlc(); + } + + huffTable._custDesc = huffDesc; + huffTable._tabSel = 7; + huffTable._tab = &huffTable._custTab; + if (huffTable._custDesc.createHuffFromDesc(huffTable._tab, false)) { + // reset faulty description + huffTable._custDesc._numRows = 0; + warning("Error while initializing transparency VLC table"); + return -1; + } + } + + // FIXME: The transparency plane can be split, though it is not clear if + // this is in scalability mode, local decoding mode, or both. This adds + // complexity to the implementation, so avoid supporting unless it turns out + // to actually be necessary for correct decoding of game videos. + assert(!_ctx._isScalable); + assert(!_ctx._usesTiling); if (_surface->format.aBits() == 0) { // Surface is 4 bytes per pixel, but only RGB. So promote the // surface to full RGBA, and convert all the existing pixels - Graphics::PixelFormat oldFormat = _pixelFormat; _pixelFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); - _surface->format = _pixelFormat; + _surface->convertToInPlace(_pixelFormat); + } - for (int y = 0; y < _surface->h; ++y) { - uint32 *lineP = (uint32 *)_surface->getBasePtr(0, y); - for (int x = 0; x < _surface->w; ++x, ++lineP) { - oldFormat.colorToRGB(*lineP, r, g, b); - *lineP = _pixelFormat.ARGBToColor(0xff, r, g, b); - } + assert(_surface->format.bytesPerPixel == 4); + assert((_surface->pitch % 4) == 0); + + const uint32 startByte = _ctx._gb->pos() / 8; + + /* @68D */ + const bool useFillTransparency = _ctx._gb->getBit(); + if (useFillTransparency) { + /* @6F2 */ + const bool runIsOpaque = _ctx._gb->getBit(); + if (!runIsOpaque) { + // It should only be necessary to draw transparency here since the + // data from the YUV planes gets drawn to the output surface on each + // frame, which resets the surface pixels to be fully opaque + _surface->fillRect(Common::Rect(_surface->w, _surface->h), 0); } + + // No alignment here } else { - // Working on a frame when the surface is already RGBA. In which case, - // start of by defaulting all pixels of the frame to fully opaque - for (int y = 0; y < _surface->h; ++y) { - uint32 *lineP = (uint32 *)_surface->getBasePtr(0, y); - for (int x = 0; x < _surface->w; ++x, ++lineP) - *lineP |= 0xff; - } - } + /* @7BF */ + const bool hasDataSize = _ctx._gb->getBit(); + if (hasDataSize) { /* @81A */ + /* @822 */ + int expectedSize = _ctx._gb->getBits(8); + if (expectedSize == 0xFF) { + expectedSize = _ctx._gb->getBits(24); + } - // Use the top-left pixel as the key color, and figure out the - // equivalent value as fully transparent - uint32 keyColor = *(const uint32 *)_surface->getPixels(); - uint32 transColor = keyColor & ~0xff; + expectedSize -= ((_ctx._gb->pos() + 7) / 8) - startByte; - for (int y = 0; y < _surface->h; ++y) { - uint32 *startP = (uint32 *)_surface->getBasePtr(0, y); - uint32 *endP = (uint32 *)_surface->getBasePtr(_surface->w - 1, y); + const int bytesRead = decodeRLETransparency(huffTable._tab->_table); + if (bytesRead == -1) { + // A more specific warning should have been emitted already + return -1; + } else if (bytesRead != expectedSize) { + warning("Mismatched read %u != %u", bytesRead, expectedSize); + return -1; + } + } else { + /* @95B */ + if (decodeRLETransparency(huffTable._tab->_table) == -1) { + warning("Transparency data read failure"); + return -1; + } + } - while (startP <= endP && *startP == keyColor) - *startP++ = transColor; - while (endP > startP && *endP == keyColor) - *endP-- = transColor; + _ctx._gb->align(); } + + return 0; } int Indeo4Decoder::scaleTileSize(int defSize, int sizeFactor) { diff --git a/image/codecs/indeo4.h b/image/codecs/indeo4.h index 8b7fdab3f9..2f3fa8d816 100644 --- a/image/codecs/indeo4.h +++ b/image/codecs/indeo4.h @@ -91,9 +91,14 @@ protected: virtual int decodeMbInfo(IVIBandDesc *band, IVITile *tile); /** - * Decodes optional transparency data within Indeo frames + * Decodes huffman + RLE-coded transparency data within Indeo4 frames */ - virtual void decodeTransparency(); + int decodeRLETransparency(VLC_TYPE (*table)[2]); + + /** + * Decodes optional transparency data within Indeo4 frames + */ + virtual int decodeTransparency(); private: int scaleTileSize(int defSize, int sizeFactor); -- cgit v1.2.3