/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ // 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(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