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.
This commit is contained in:
parent
9512cf46b7
commit
085ec30b49
4 changed files with 242 additions and 49 deletions
|
@ -491,6 +491,8 @@ IndeoDecoderBase::~IndeoDecoderBase() {
|
||||||
IVIPlaneDesc::freeBuffers(_ctx._planes);
|
IVIPlaneDesc::freeBuffers(_ctx._planes);
|
||||||
if (_ctx._mbVlc._custTab._table)
|
if (_ctx._mbVlc._custTab._table)
|
||||||
_ctx._mbVlc._custTab.freeVlc();
|
_ctx._mbVlc._custTab.freeVlc();
|
||||||
|
if (_ctx._transVlc._custTab._table)
|
||||||
|
_ctx._transVlc._custTab.freeVlc();
|
||||||
|
|
||||||
delete _ctx._pFrame;
|
delete _ctx._pFrame;
|
||||||
}
|
}
|
||||||
|
@ -575,11 +577,23 @@ int IndeoDecoderBase::decodeIndeoFrame() {
|
||||||
outputPlane(&_ctx._planes[2], frame->_data[1], frame->_linesize[1]);
|
outputPlane(&_ctx._planes[2], frame->_data[1], frame->_linesize[1]);
|
||||||
outputPlane(&_ctx._planes[1], frame->_data[2], frame->_linesize[2]);
|
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
|
// If the bidirectional mode is enabled, next I and the following P
|
||||||
// frame will be sent together. Unfortunately the approach below seems
|
// frame will be sent together. Unfortunately the approach below seems
|
||||||
// to be the only way to handle the B-frames mode.
|
// to be the only way to handle the B-frames mode.
|
||||||
// That's exactly the same Intel decoders do.
|
// That's exactly the same Intel decoders do.
|
||||||
if (_ctx._isIndeo4 && _ctx._frameType == IVI4_FRAMETYPE_INTRA) {
|
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;
|
int left;
|
||||||
|
|
||||||
// skip version string
|
// 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
|
// Free the now un-needed frame data
|
||||||
frame->freeFrame();
|
frame->freeFrame();
|
||||||
|
|
||||||
// If there's any transparency data, decode it
|
|
||||||
if (_ctx._hasTransp)
|
|
||||||
decodeTransparency();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -398,6 +398,7 @@ public:
|
||||||
|
|
||||||
IVIHuffTab _mbVlc; ///< current macroblock table descriptor
|
IVIHuffTab _mbVlc; ///< current macroblock table descriptor
|
||||||
IVIHuffTab _blkVlc; ///< current block table descriptor
|
IVIHuffTab _blkVlc; ///< current block table descriptor
|
||||||
|
IVIHuffTab _transVlc; ///< current transparency table descriptor
|
||||||
|
|
||||||
uint8 _rvmapSel;
|
uint8 _rvmapSel;
|
||||||
bool _inImf;
|
bool _inImf;
|
||||||
|
@ -566,7 +567,7 @@ protected:
|
||||||
/**
|
/**
|
||||||
* Decodes optional transparency data within Indeo frames
|
* Decodes optional transparency data within Indeo frames
|
||||||
*/
|
*/
|
||||||
virtual void decodeTransparency() {}
|
virtual int decodeTransparency() { return -1; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes the Indeo frame from the bit reader already
|
* Decodes the Indeo frame from the bit reader already
|
||||||
|
|
|
@ -26,7 +26,9 @@
|
||||||
* written, produced, and directed by Alan Smithee
|
* written, produced, and directed by Alan Smithee
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "common/debug.h"
|
||||||
#include "common/memstream.h"
|
#include "common/memstream.h"
|
||||||
|
#include "common/rect.h"
|
||||||
#include "common/textconsole.h"
|
#include "common/textconsole.h"
|
||||||
#include "graphics/yuv_to_rgb.h"
|
#include "graphics/yuv_to_rgb.h"
|
||||||
#include "image/codecs/indeo4.h"
|
#include "image/codecs/indeo4.h"
|
||||||
|
@ -595,52 +597,233 @@ int Indeo4Decoder::decodeMbInfo(IVIBandDesc *band, IVITile *tile) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Indeo4Decoder::decodeTransparency() {
|
int Indeo4Decoder::decodeRLETransparency(VLC_TYPE (*table)[2]) {
|
||||||
// FIXME: Since I don't currently know how to decode the transparency layer,
|
const uint32 startPos = _ctx._gb->pos();
|
||||||
// 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
|
_ctx._gb->align();
|
||||||
// each line as transparent
|
|
||||||
assert(_surface->format.bytesPerPixel == 4);
|
bool runIsOpaque = _ctx._gb->getBit();
|
||||||
byte r, g, b;
|
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) {
|
if (_surface->format.aBits() == 0) {
|
||||||
// Surface is 4 bytes per pixel, but only RGB. So promote the
|
// Surface is 4 bytes per pixel, but only RGB. So promote the
|
||||||
// surface to full RGBA, and convert all the existing pixels
|
// 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);
|
_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) {
|
assert(_surface->format.bytesPerPixel == 4);
|
||||||
uint32 *lineP = (uint32 *)_surface->getBasePtr(0, y);
|
assert((_surface->pitch % 4) == 0);
|
||||||
for (int x = 0; x < _surface->w; ++x, ++lineP) {
|
|
||||||
oldFormat.colorToRGB(*lineP, r, g, b);
|
const uint32 startByte = _ctx._gb->pos() / 8;
|
||||||
*lineP = _pixelFormat.ARGBToColor(0xff, r, g, b);
|
|
||||||
|
/* @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 {
|
||||||
|
/* @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSize -= ((_ctx._gb->pos() + 7) / 8) - startByte;
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Working on a frame when the surface is already RGBA. In which case,
|
_ctx._gb->align();
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the top-left pixel as the key color, and figure out the
|
return 0;
|
||||||
// equivalent value as fully transparent
|
|
||||||
uint32 keyColor = *(const uint32 *)_surface->getPixels();
|
|
||||||
uint32 transColor = keyColor & ~0xff;
|
|
||||||
|
|
||||||
for (int y = 0; y < _surface->h; ++y) {
|
|
||||||
uint32 *startP = (uint32 *)_surface->getBasePtr(0, y);
|
|
||||||
uint32 *endP = (uint32 *)_surface->getBasePtr(_surface->w - 1, y);
|
|
||||||
|
|
||||||
while (startP <= endP && *startP == keyColor)
|
|
||||||
*startP++ = transColor;
|
|
||||||
while (endP > startP && *endP == keyColor)
|
|
||||||
*endP-- = transColor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Indeo4Decoder::scaleTileSize(int defSize, int sizeFactor) {
|
int Indeo4Decoder::scaleTileSize(int defSize, int sizeFactor) {
|
||||||
|
|
|
@ -91,9 +91,14 @@ protected:
|
||||||
virtual int decodeMbInfo(IVIBandDesc *band, IVITile *tile);
|
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:
|
private:
|
||||||
int scaleTileSize(int defSize, int sizeFactor);
|
int scaleTileSize(int defSize, int sizeFactor);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue