mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 14:18:37 +00:00
432 lines
12 KiB
C++
432 lines
12 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
// Based on the TrueMotion 1 decoder by Alex Beregszaszi & Mike Melanson in FFmpeg
|
|
|
|
#include "common/scummsys.h"
|
|
#include "image/codecs/truemotion1.h"
|
|
|
|
#ifdef IMAGE_CODECS_TRUEMOTION1_H
|
|
|
|
#include "image/codecs/truemotion1data.h"
|
|
#include "common/stream.h"
|
|
#include "common/textconsole.h"
|
|
#include "common/rect.h"
|
|
#include "common/util.h"
|
|
|
|
namespace Image {
|
|
|
|
enum {
|
|
FLAG_SPRITE = (1 << 5),
|
|
FLAG_KEYFRAME = (1 << 4),
|
|
FLAG_INTERFRAME = (1 << 3),
|
|
FLAG_INTERPOLATED = (1 << 2)
|
|
};
|
|
|
|
enum {
|
|
ALGO_NOP = 0,
|
|
ALGO_RGB16V = 1,
|
|
ALGO_RGB16H = 2,
|
|
ALGO_RGB24H = 3
|
|
};
|
|
|
|
// these are the various block sizes that can occupy a 4x4 block
|
|
enum {
|
|
BLOCK_2x2 = 0,
|
|
BLOCK_2x4 = 1,
|
|
BLOCK_4x2 = 2,
|
|
BLOCK_4x4 = 3
|
|
};
|
|
|
|
// { valid for metatype }, algorithm, num of deltas, vert res, horiz res
|
|
struct CompressionType {
|
|
int algorithm;
|
|
int blockWidth; // vres
|
|
int blockHeight; // hres
|
|
int blockType;
|
|
};
|
|
|
|
static const CompressionType compressionTypes[17] = {
|
|
{ ALGO_NOP, 0, 0, 0 },
|
|
|
|
{ ALGO_RGB16V, 4, 4, BLOCK_4x4 },
|
|
{ ALGO_RGB16H, 4, 4, BLOCK_4x4 },
|
|
{ ALGO_RGB16V, 4, 2, BLOCK_4x2 },
|
|
{ ALGO_RGB16H, 4, 2, BLOCK_4x2 },
|
|
|
|
{ ALGO_RGB16V, 2, 4, BLOCK_2x4 },
|
|
{ ALGO_RGB16H, 2, 4, BLOCK_2x4 },
|
|
{ ALGO_RGB16V, 2, 2, BLOCK_2x2 },
|
|
{ ALGO_RGB16H, 2, 2, BLOCK_2x2 },
|
|
|
|
{ ALGO_NOP, 4, 4, BLOCK_4x4 },
|
|
{ ALGO_RGB24H, 4, 4, BLOCK_4x4 },
|
|
{ ALGO_NOP, 4, 2, BLOCK_4x2 },
|
|
{ ALGO_RGB24H, 4, 2, BLOCK_4x2 },
|
|
|
|
{ ALGO_NOP, 2, 4, BLOCK_2x4 },
|
|
{ ALGO_RGB24H, 2, 4, BLOCK_2x4 },
|
|
{ ALGO_NOP, 2, 2, BLOCK_2x2 },
|
|
{ ALGO_RGB24H, 2, 2, BLOCK_2x2 }
|
|
};
|
|
|
|
TrueMotion1Decoder::TrueMotion1Decoder() {
|
|
_surface = 0;
|
|
_vertPred = 0;
|
|
|
|
_buf = _mbChangeBits = _indexStream = 0;
|
|
_lastDeltaset = _lastVectable = -1;
|
|
}
|
|
|
|
TrueMotion1Decoder::~TrueMotion1Decoder() {
|
|
if (_surface) {
|
|
_surface->free();
|
|
delete _surface;
|
|
}
|
|
|
|
delete[] _vertPred;
|
|
}
|
|
|
|
void TrueMotion1Decoder::selectDeltaTables(int deltaTableIndex) {
|
|
if (deltaTableIndex > 3)
|
|
return;
|
|
|
|
for (byte i = 0; i < 8; i++) {
|
|
_ydt[i] = ydts[deltaTableIndex][i];
|
|
_cdt[i] = cdts[deltaTableIndex][i];
|
|
|
|
// Y skinny deltas need to be halved for some reason; maybe the
|
|
// skinny Y deltas should be modified
|
|
// Drop the lsb before dividing by 2-- net effect: round down
|
|
// when dividing a negative number (e.g., -3/2 = -2, not -1)
|
|
_ydt[i] &= 0xFFFE;
|
|
_ydt[i] /= 2;
|
|
}
|
|
}
|
|
|
|
int TrueMotion1Decoder::makeYdt16Entry(int p1, int p2) {
|
|
#ifdef SCUMM_BIG_ENDIAN
|
|
// Swap the values on BE systems. FFmpeg does this too.
|
|
SWAP<int>(p1, p2);
|
|
#endif
|
|
|
|
int lo = _ydt[p1];
|
|
lo += (lo << 6) + (lo << 11);
|
|
int hi = _ydt[p2];
|
|
hi += (hi << 6) + (hi << 11);
|
|
return lo + (hi << 16);
|
|
}
|
|
|
|
int TrueMotion1Decoder::makeCdt16Entry(int p1, int p2) {
|
|
int b = _cdt[p2];
|
|
int r = _cdt[p1] << 11;
|
|
int lo = b + r;
|
|
return lo + (lo << 16);
|
|
}
|
|
|
|
void TrueMotion1Decoder::genVectorTable16(const byte *selVectorTable) {
|
|
memset(&_yPredictorTable, 0, sizeof(PredictorTableEntry) * 1024);
|
|
memset(&_cPredictorTable, 0, sizeof(PredictorTableEntry) * 1024);
|
|
|
|
for (int i = 0; i < 1024; i += 4) {
|
|
int len = *selVectorTable++ / 2;
|
|
for (int j = 0; j < len; j++) {
|
|
byte deltaPair = *selVectorTable++;
|
|
_yPredictorTable[i + j].color = makeYdt16Entry(deltaPair >> 4, deltaPair & 0xf);
|
|
_cPredictorTable[i + j].color = makeCdt16Entry(deltaPair >> 4, deltaPair & 0xf);
|
|
}
|
|
|
|
_yPredictorTable[i + (len - 1)].getNextIndex = true;
|
|
_cPredictorTable[i + (len - 1)].getNextIndex = true;
|
|
}
|
|
}
|
|
|
|
void TrueMotion1Decoder::decodeHeader(Common::SeekableReadStream &stream) {
|
|
_buf = new byte[stream.size()];
|
|
stream.read(_buf, stream.size());
|
|
|
|
byte headerBuffer[128]; // logical maximum size of the header
|
|
const byte *selVectorTable;
|
|
|
|
_header.headerSize = ((_buf[0] >> 5) | (_buf[0] << 3)) & 0x7f;
|
|
|
|
if (_buf[0] < 0x10)
|
|
error("Invalid TrueMotion1 header size %d", _header.headerSize);
|
|
|
|
// unscramble the header bytes with a XOR operation
|
|
memset(headerBuffer, 0, 128);
|
|
for (int i = 1; i < _header.headerSize; i++)
|
|
headerBuffer[i - 1] = _buf[i] ^ _buf[i + 1];
|
|
|
|
_header.compression = headerBuffer[0];
|
|
_header.deltaset = headerBuffer[1];
|
|
_header.vectable = headerBuffer[2];
|
|
_header.ysize = READ_LE_UINT16(&headerBuffer[3]);
|
|
_header.xsize = READ_LE_UINT16(&headerBuffer[5]);
|
|
_header.checksum = READ_LE_UINT16(&headerBuffer[7]);
|
|
_header.version = headerBuffer[9];
|
|
_header.headerType = headerBuffer[10];
|
|
_header.flags = headerBuffer[11];
|
|
_header.control = headerBuffer[12];
|
|
|
|
if (!_vertPred) {
|
|
// there is a vertical predictor for each pixel in a line; each vertical
|
|
// predictor is 0 to start with
|
|
_vertPred = new uint32[_header.xsize];
|
|
}
|
|
|
|
if (!_surface) {
|
|
_surface = new Graphics::Surface();
|
|
_surface->create(_header.xsize, _header.ysize, getPixelFormat());
|
|
}
|
|
|
|
// There is 1 change bit per 4 pixels, so each change byte represents
|
|
// 32 pixels; divide width by 4 to obtain the number of change bits and
|
|
// then round up to the nearest byte.
|
|
_mbChangeBitsRowSize = ((_header.xsize >> 2) + 7) >> 3;
|
|
|
|
// Version 2
|
|
if (_header.version >= 2) {
|
|
if (_header.headerType > 3) {
|
|
error("Invalid header type %d", _header.headerType);
|
|
} else if (_header.headerType == 2 || _header.headerType == 3) {
|
|
_flags = _header.flags;
|
|
if (!(_flags & FLAG_INTERFRAME))
|
|
_flags |= FLAG_KEYFRAME;
|
|
} else
|
|
_flags = FLAG_KEYFRAME;
|
|
} else // Version 1
|
|
_flags = FLAG_KEYFRAME;
|
|
|
|
if (_flags & FLAG_SPRITE) {
|
|
error("SPRITE frame found, please report the sample to the developers");
|
|
} else if (_header.headerType < 2 && _header.xsize < 213 && _header.ysize >= 176) {
|
|
_flags |= FLAG_INTERPOLATED;
|
|
error("INTERPOLATION selected, please report the sample to the developers");
|
|
}
|
|
|
|
if (_header.compression >= 17)
|
|
error("Invalid TrueMotion1 compression type %d", _header.compression);
|
|
|
|
if (_header.deltaset != _lastDeltaset || _header.vectable != _lastVectable)
|
|
selectDeltaTables(_header.deltaset);
|
|
|
|
if ((_header.compression & 1) && _header.headerType)
|
|
selVectorTable = pc_tbl2;
|
|
else if (_header.vectable < 4)
|
|
selVectorTable = tables[_header.vectable - 1];
|
|
else
|
|
error("Invalid vector table id %d", _header.vectable);
|
|
|
|
if (_header.deltaset != _lastDeltaset || _header.vectable != _lastVectable)
|
|
genVectorTable16(selVectorTable);
|
|
|
|
// set up pointers to the other key data chunks
|
|
_mbChangeBits = _buf + _header.headerSize;
|
|
|
|
if (_flags & FLAG_KEYFRAME) {
|
|
// no change bits specified for a keyframe; only index bytes
|
|
_indexStream = _mbChangeBits;
|
|
} else {
|
|
// one change bit per 4x4 block
|
|
_indexStream = _mbChangeBits + _mbChangeBitsRowSize * (_header.ysize >> 2);
|
|
}
|
|
|
|
_indexStreamSize = stream.size() - (_indexStream - _buf);
|
|
|
|
_lastDeltaset = _header.deltaset;
|
|
_lastVectable = _header.vectable;
|
|
_blockWidth = compressionTypes[_header.compression].blockWidth;
|
|
_blockHeight = compressionTypes[_header.compression].blockHeight;
|
|
_blockType = compressionTypes[_header.compression].blockType;
|
|
}
|
|
|
|
#define GET_NEXT_INDEX() \
|
|
do { \
|
|
if (indexStreamIndex >= _indexStreamSize) \
|
|
error("TrueMotion1 decoder went out of bounds"); \
|
|
index = _indexStream[indexStreamIndex++] * 4; \
|
|
} while (0) \
|
|
|
|
#define APPLY_C_PREDICTOR() \
|
|
predictor_pair = _cPredictorTable[index].color; \
|
|
horizPred += predictor_pair; \
|
|
if (_cPredictorTable[index].getNextIndex) { \
|
|
GET_NEXT_INDEX(); \
|
|
if (!index) { \
|
|
GET_NEXT_INDEX(); \
|
|
predictor_pair = _cPredictorTable[index].color; \
|
|
horizPred += predictor_pair * 5; \
|
|
if (_cPredictorTable[index].getNextIndex) \
|
|
GET_NEXT_INDEX(); \
|
|
else \
|
|
index++; \
|
|
} \
|
|
} else \
|
|
index++
|
|
|
|
#define APPLY_Y_PREDICTOR() \
|
|
predictor_pair = _yPredictorTable[index].color; \
|
|
horizPred += predictor_pair; \
|
|
if (_yPredictorTable[index].getNextIndex) { \
|
|
GET_NEXT_INDEX(); \
|
|
if (!index) { \
|
|
GET_NEXT_INDEX(); \
|
|
predictor_pair = _yPredictorTable[index].color; \
|
|
horizPred += predictor_pair * 5; \
|
|
if (_yPredictorTable[index].getNextIndex) \
|
|
GET_NEXT_INDEX(); \
|
|
else \
|
|
index++; \
|
|
} \
|
|
} else \
|
|
index++
|
|
|
|
#define OUTPUT_PIXEL_PAIR() \
|
|
*currentPixelPair = *vertPred + horizPred; \
|
|
*vertPred++ = *currentPixelPair++
|
|
|
|
void TrueMotion1Decoder::decode16() {
|
|
uint32 predictor_pair;
|
|
bool keyframe = _flags & FLAG_KEYFRAME;
|
|
int indexStreamIndex = 0;
|
|
|
|
// these variables are for managing the main index stream
|
|
int index;
|
|
|
|
// clean out the line buffer
|
|
memset(_vertPred, 0, _header.xsize * 4);
|
|
|
|
GET_NEXT_INDEX();
|
|
|
|
for (int y = 0; y < _header.ysize; y++) {
|
|
// re-init variables for the next line iteration
|
|
uint32 horizPred = 0;
|
|
uint32 *currentPixelPair = (uint32 *)_surface->getBasePtr(0, y);
|
|
uint32 *vertPred = _vertPred;
|
|
int mbChangeIndex = 0;
|
|
byte mbChangeByte = _mbChangeBits[mbChangeIndex++];
|
|
byte mbChangeByteMask = 1;
|
|
|
|
for (int pixelsLeft = _header.xsize; pixelsLeft > 0; pixelsLeft -= 4) {
|
|
if (keyframe || (mbChangeByte & mbChangeByteMask) == 0) {
|
|
switch (y & 3) {
|
|
case 0:
|
|
// if macroblock width is 2, apply C-Y-C-Y; else
|
|
// apply C-Y-Y
|
|
if (_blockWidth == 2) {
|
|
APPLY_C_PREDICTOR();
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
APPLY_C_PREDICTOR();
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
} else {
|
|
APPLY_C_PREDICTOR();
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
}
|
|
break;
|
|
case 1:
|
|
case 3:
|
|
// always apply 2 Y predictors on these iterations
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
break;
|
|
case 2:
|
|
// this iteration might be C-Y-C-Y, Y-Y, or C-Y-Y
|
|
// depending on the macroblock type
|
|
if (_blockType == BLOCK_2x2) {
|
|
APPLY_C_PREDICTOR();
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
APPLY_C_PREDICTOR();
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
} else if (_blockType == BLOCK_4x2) {
|
|
APPLY_C_PREDICTOR();
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
} else {
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
APPLY_Y_PREDICTOR();
|
|
OUTPUT_PIXEL_PAIR();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
// skip (copy) four pixels, but reassign the horizontal
|
|
// predictor
|
|
*vertPred++ = *currentPixelPair++;
|
|
horizPred = *currentPixelPair - *vertPred;
|
|
*vertPred++ = *currentPixelPair++;
|
|
}
|
|
|
|
if (!keyframe) {
|
|
mbChangeByteMask <<= 1;
|
|
|
|
// next byte
|
|
if (!mbChangeByteMask) {
|
|
mbChangeByte = _mbChangeBits[mbChangeIndex++];
|
|
mbChangeByteMask = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// next change row
|
|
if (((y + 1) & 3) == 0)
|
|
_mbChangeBits += _mbChangeBitsRowSize;
|
|
}
|
|
}
|
|
|
|
const Graphics::Surface *TrueMotion1Decoder::decodeFrame(Common::SeekableReadStream &stream) {
|
|
decodeHeader(stream);
|
|
|
|
if (compressionTypes[_header.compression].algorithm == ALGO_NOP) {
|
|
delete[] _buf;
|
|
return 0;
|
|
}
|
|
|
|
if (compressionTypes[_header.compression].algorithm == ALGO_RGB24H) {
|
|
warning("Unhandled TrueMotion1 24bpp frame");
|
|
delete[] _buf;
|
|
return 0;
|
|
} else
|
|
decode16();
|
|
|
|
delete[] _buf;
|
|
|
|
return _surface;
|
|
}
|
|
|
|
} // End of namespace Image
|
|
|
|
#endif
|