ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

truemotion1.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 // Based on the TrueMotion 1 decoder by Alex Beregszaszi & Mike Melanson in FFmpeg
00024 
00025 #include "common/scummsys.h"
00026 #include "image/codecs/truemotion1.h"
00027 
00028 #ifdef IMAGE_CODECS_TRUEMOTION1_H
00029 
00030 #include "image/codecs/truemotion1data.h"
00031 #include "common/stream.h"
00032 #include "common/textconsole.h"
00033 #include "common/rect.h"
00034 #include "common/util.h"
00035 
00036 namespace Image {
00037 
00038 enum {
00039     FLAG_SPRITE = (1 << 5),
00040     FLAG_KEYFRAME = (1 << 4),
00041     FLAG_INTERFRAME = (1 << 3),
00042     FLAG_INTERPOLATED = (1 << 2)
00043 };
00044 
00045 enum {
00046     ALGO_NOP = 0,
00047     ALGO_RGB16V = 1,
00048     ALGO_RGB16H = 2,
00049     ALGO_RGB24H = 3
00050 };
00051 
00052 // these are the various block sizes that can occupy a 4x4 block
00053 enum {
00054     BLOCK_2x2 = 0,
00055     BLOCK_2x4 = 1,
00056     BLOCK_4x2 = 2,
00057     BLOCK_4x4 = 3
00058 };
00059 
00060 // { valid for metatype }, algorithm, num of deltas, vert res, horiz res
00061 struct CompressionType {
00062     int algorithm;
00063     int blockWidth; // vres
00064     int blockHeight; // hres
00065     int blockType;
00066 };
00067 
00068 static const CompressionType compressionTypes[17] = {
00069     { ALGO_NOP, 0, 0, 0 },
00070 
00071     { ALGO_RGB16V, 4, 4, BLOCK_4x4 },
00072     { ALGO_RGB16H, 4, 4, BLOCK_4x4 },
00073     { ALGO_RGB16V, 4, 2, BLOCK_4x2 },
00074     { ALGO_RGB16H, 4, 2, BLOCK_4x2 },
00075 
00076     { ALGO_RGB16V, 2, 4, BLOCK_2x4 },
00077     { ALGO_RGB16H, 2, 4, BLOCK_2x4 },
00078     { ALGO_RGB16V, 2, 2, BLOCK_2x2 },
00079     { ALGO_RGB16H, 2, 2, BLOCK_2x2 },
00080 
00081     { ALGO_NOP, 4, 4, BLOCK_4x4 },
00082     { ALGO_RGB24H, 4, 4, BLOCK_4x4 },
00083     { ALGO_NOP, 4, 2, BLOCK_4x2 },
00084     { ALGO_RGB24H, 4, 2, BLOCK_4x2 },
00085 
00086     { ALGO_NOP, 2, 4, BLOCK_2x4 },
00087     { ALGO_RGB24H, 2, 4, BLOCK_2x4 },
00088     { ALGO_NOP, 2, 2, BLOCK_2x2 },
00089     { ALGO_RGB24H, 2, 2, BLOCK_2x2 }
00090 };
00091 
00092 TrueMotion1Decoder::TrueMotion1Decoder() {
00093     _surface = 0;
00094     _vertPred = 0;
00095 
00096     _buf = _mbChangeBits = _indexStream = 0;
00097     _lastDeltaset = _lastVectable = -1;
00098 }
00099 
00100 TrueMotion1Decoder::~TrueMotion1Decoder() {
00101     if (_surface) {
00102         _surface->free();
00103         delete _surface;
00104     }
00105 
00106     delete[] _vertPred;
00107 }
00108 
00109 void TrueMotion1Decoder::selectDeltaTables(int deltaTableIndex) {
00110     if (deltaTableIndex > 3)
00111         return;
00112 
00113     for (byte i = 0; i < 8; i++) {
00114         _ydt[i] = ydts[deltaTableIndex][i];
00115         _cdt[i] = cdts[deltaTableIndex][i];
00116 
00117         // Y skinny deltas need to be halved for some reason; maybe the
00118         // skinny Y deltas should be modified
00119         // Drop the lsb before dividing by 2-- net effect: round down
00120         // when dividing a negative number (e.g., -3/2 = -2, not -1)
00121         _ydt[i] &= 0xFFFE;
00122         _ydt[i] /= 2;
00123     }
00124 }
00125 
00126 int TrueMotion1Decoder::makeYdt16Entry(int p1, int p2) {
00127 #ifdef SCUMM_BIG_ENDIAN
00128     // Swap the values on BE systems. FFmpeg does this too.
00129     SWAP<int>(p1, p2);
00130 #endif
00131 
00132     int lo = _ydt[p1];
00133     lo += (lo << 6) + (lo << 11);
00134     int hi = _ydt[p2];
00135     hi += (hi << 6) + (hi << 11);
00136     return lo + (hi << 16);
00137 }
00138 
00139 int TrueMotion1Decoder::makeCdt16Entry(int p1, int p2) {
00140     int b = _cdt[p2];
00141     int r = _cdt[p1] << 11;
00142     int lo = b + r;
00143     return lo + (lo << 16);
00144 }
00145 
00146 void TrueMotion1Decoder::genVectorTable16(const byte *selVectorTable) {
00147     memset(&_yPredictorTable, 0, sizeof(PredictorTableEntry) * 1024);
00148     memset(&_cPredictorTable, 0, sizeof(PredictorTableEntry) * 1024);
00149 
00150     for (int i = 0; i < 1024; i += 4) {
00151         int len = *selVectorTable++ / 2;
00152         for (int j = 0; j < len; j++) {
00153             byte deltaPair = *selVectorTable++;
00154             _yPredictorTable[i + j].color = makeYdt16Entry(deltaPair >> 4, deltaPair & 0xf);
00155             _cPredictorTable[i + j].color = makeCdt16Entry(deltaPair >> 4, deltaPair & 0xf);
00156         }
00157 
00158         _yPredictorTable[i + (len - 1)].getNextIndex = true;
00159         _cPredictorTable[i + (len - 1)].getNextIndex = true;
00160     }
00161 }
00162 
00163 void TrueMotion1Decoder::decodeHeader(Common::SeekableReadStream &stream) {
00164     _buf = new byte[stream.size()];
00165     stream.read(_buf, stream.size());
00166 
00167     byte headerBuffer[128];  // logical maximum size of the header
00168     const byte *selVectorTable;
00169 
00170     _header.headerSize = ((_buf[0] >> 5) | (_buf[0] << 3)) & 0x7f;
00171 
00172     if (_buf[0] < 0x10)
00173         error("Invalid TrueMotion1 header size %d", _header.headerSize);
00174 
00175     // unscramble the header bytes with a XOR operation
00176     memset(headerBuffer, 0, 128);
00177     for (int i = 1; i < _header.headerSize; i++)
00178         headerBuffer[i - 1] = _buf[i] ^ _buf[i + 1];
00179 
00180     _header.compression = headerBuffer[0];
00181     _header.deltaset = headerBuffer[1];
00182     _header.vectable = headerBuffer[2];
00183     _header.ysize = READ_LE_UINT16(&headerBuffer[3]);
00184     _header.xsize = READ_LE_UINT16(&headerBuffer[5]);
00185     _header.checksum = READ_LE_UINT16(&headerBuffer[7]);
00186     _header.version = headerBuffer[9];
00187     _header.headerType = headerBuffer[10];
00188     _header.flags = headerBuffer[11];
00189     _header.control = headerBuffer[12];
00190 
00191     if (!_vertPred) {
00192         // there is a vertical predictor for each pixel in a line; each vertical
00193         // predictor is 0 to start with
00194         _vertPred = new uint32[_header.xsize];
00195     }
00196 
00197     if (!_surface) {
00198         _surface = new Graphics::Surface();
00199         _surface->create(_header.xsize, _header.ysize, getPixelFormat());
00200     }
00201 
00202     // There is 1 change bit per 4 pixels, so each change byte represents
00203     // 32 pixels; divide width by 4 to obtain the number of change bits and
00204     // then round up to the nearest byte.
00205     _mbChangeBitsRowSize = ((_header.xsize >> 2) + 7) >> 3;
00206 
00207     // Version 2
00208     if (_header.version >= 2) {
00209         if (_header.headerType > 3) {
00210             error("Invalid header type %d", _header.headerType);
00211         } else if (_header.headerType == 2 || _header.headerType == 3) {
00212             _flags = _header.flags;
00213             if (!(_flags & FLAG_INTERFRAME))
00214                 _flags |= FLAG_KEYFRAME;
00215         } else
00216             _flags = FLAG_KEYFRAME;
00217     } else // Version 1
00218         _flags = FLAG_KEYFRAME;
00219 
00220     if (_flags & FLAG_SPRITE) {
00221         error("SPRITE frame found, please report the sample to the developers");
00222     } else if (_header.headerType < 2 && _header.xsize < 213 && _header.ysize >= 176) {
00223         _flags |= FLAG_INTERPOLATED;
00224         error("INTERPOLATION selected, please report the sample to the developers");
00225     }
00226 
00227     if (_header.compression >= 17)
00228         error("Invalid TrueMotion1 compression type %d", _header.compression);
00229 
00230     if (_header.deltaset != _lastDeltaset || _header.vectable != _lastVectable)
00231         selectDeltaTables(_header.deltaset);
00232 
00233     if ((_header.compression & 1) && _header.headerType)
00234         selVectorTable = pc_tbl2;
00235     else if (_header.vectable < 4)
00236         selVectorTable = tables[_header.vectable - 1];
00237     else
00238         error("Invalid vector table id %d", _header.vectable);
00239 
00240     if (_header.deltaset != _lastDeltaset || _header.vectable != _lastVectable)
00241         genVectorTable16(selVectorTable);
00242 
00243     // set up pointers to the other key data chunks
00244     _mbChangeBits = _buf + _header.headerSize;
00245 
00246     if (_flags & FLAG_KEYFRAME) {
00247         // no change bits specified for a keyframe; only index bytes
00248         _indexStream = _mbChangeBits;
00249     } else {
00250         // one change bit per 4x4 block
00251         _indexStream = _mbChangeBits + _mbChangeBitsRowSize * (_header.ysize >> 2);
00252     }
00253 
00254     _indexStreamSize = stream.size() - (_indexStream - _buf);
00255 
00256     _lastDeltaset = _header.deltaset;
00257     _lastVectable = _header.vectable;
00258     _blockWidth = compressionTypes[_header.compression].blockWidth;
00259     _blockHeight = compressionTypes[_header.compression].blockHeight;
00260     _blockType = compressionTypes[_header.compression].blockType;
00261 }
00262 
00263 #define GET_NEXT_INDEX() \
00264 do { \
00265     if (indexStreamIndex >= _indexStreamSize) \
00266         error("TrueMotion1 decoder went out of bounds"); \
00267     index = _indexStream[indexStreamIndex++] * 4; \
00268 } while (0) \
00269 
00270 #define APPLY_C_PREDICTOR() \
00271     predictor_pair = _cPredictorTable[index].color; \
00272     horizPred += predictor_pair; \
00273     if (_cPredictorTable[index].getNextIndex) { \
00274         GET_NEXT_INDEX(); \
00275         if (!index) { \
00276             GET_NEXT_INDEX(); \
00277             predictor_pair = _cPredictorTable[index].color; \
00278             horizPred += predictor_pair * 5; \
00279             if (_cPredictorTable[index].getNextIndex) \
00280                 GET_NEXT_INDEX(); \
00281             else \
00282                 index++; \
00283         } \
00284     } else \
00285         index++
00286 
00287 #define APPLY_Y_PREDICTOR() \
00288     predictor_pair = _yPredictorTable[index].color; \
00289     horizPred += predictor_pair; \
00290     if (_yPredictorTable[index].getNextIndex) { \
00291         GET_NEXT_INDEX(); \
00292         if (!index) { \
00293             GET_NEXT_INDEX(); \
00294             predictor_pair = _yPredictorTable[index].color; \
00295             horizPred += predictor_pair * 5; \
00296             if (_yPredictorTable[index].getNextIndex) \
00297                 GET_NEXT_INDEX(); \
00298             else \
00299                 index++; \
00300         } \
00301     } else \
00302         index++
00303 
00304 #define OUTPUT_PIXEL_PAIR() \
00305     *currentPixelPair = *vertPred + horizPred; \
00306     *vertPred++ = *currentPixelPair++
00307 
00308 void TrueMotion1Decoder::decode16() {
00309     uint32 predictor_pair;
00310     bool keyframe = _flags & FLAG_KEYFRAME;
00311     int indexStreamIndex = 0;
00312 
00313     // these variables are for managing the main index stream
00314     int index;
00315 
00316     // clean out the line buffer
00317     memset(_vertPred, 0, _header.xsize * 4);
00318 
00319     GET_NEXT_INDEX();
00320 
00321     for (int y = 0; y < _header.ysize; y++) {
00322         // re-init variables for the next line iteration
00323         uint32 horizPred = 0;
00324         uint32 *currentPixelPair = (uint32 *)_surface->getBasePtr(0, y);
00325         uint32 *vertPred = _vertPred;
00326         int mbChangeIndex = 0;
00327         byte mbChangeByte = _mbChangeBits[mbChangeIndex++];
00328         byte mbChangeByteMask = 1;
00329 
00330         for (int pixelsLeft = _header.xsize; pixelsLeft > 0; pixelsLeft -= 4) {
00331             if (keyframe || (mbChangeByte & mbChangeByteMask) == 0) {
00332                 switch (y & 3) {
00333                 case 0:
00334                     // if macroblock width is 2, apply C-Y-C-Y; else
00335                     // apply C-Y-Y
00336                     if (_blockWidth == 2) {
00337                         APPLY_C_PREDICTOR();
00338                         APPLY_Y_PREDICTOR();
00339                         OUTPUT_PIXEL_PAIR();
00340                         APPLY_C_PREDICTOR();
00341                         APPLY_Y_PREDICTOR();
00342                         OUTPUT_PIXEL_PAIR();
00343                     } else {
00344                         APPLY_C_PREDICTOR();
00345                         APPLY_Y_PREDICTOR();
00346                         OUTPUT_PIXEL_PAIR();
00347                         APPLY_Y_PREDICTOR();
00348                         OUTPUT_PIXEL_PAIR();
00349                     }
00350                     break;
00351                 case 1:
00352                 case 3:
00353                     // always apply 2 Y predictors on these iterations
00354                     APPLY_Y_PREDICTOR();
00355                     OUTPUT_PIXEL_PAIR();
00356                     APPLY_Y_PREDICTOR();
00357                     OUTPUT_PIXEL_PAIR();
00358                     break;
00359                 case 2:
00360                     // this iteration might be C-Y-C-Y, Y-Y, or C-Y-Y
00361                     // depending on the macroblock type
00362                     if (_blockType == BLOCK_2x2) {
00363                         APPLY_C_PREDICTOR();
00364                         APPLY_Y_PREDICTOR();
00365                         OUTPUT_PIXEL_PAIR();
00366                         APPLY_C_PREDICTOR();
00367                         APPLY_Y_PREDICTOR();
00368                         OUTPUT_PIXEL_PAIR();
00369                     } else if (_blockType == BLOCK_4x2) {
00370                         APPLY_C_PREDICTOR();
00371                         APPLY_Y_PREDICTOR();
00372                         OUTPUT_PIXEL_PAIR();
00373                         APPLY_Y_PREDICTOR();
00374                         OUTPUT_PIXEL_PAIR();
00375                     } else {
00376                         APPLY_Y_PREDICTOR();
00377                         OUTPUT_PIXEL_PAIR();
00378                         APPLY_Y_PREDICTOR();
00379                         OUTPUT_PIXEL_PAIR();
00380                     }
00381                     break;
00382                 }
00383             } else {
00384                 // skip (copy) four pixels, but reassign the horizontal
00385                 // predictor
00386                 *vertPred++ = *currentPixelPair++;
00387                 horizPred = *currentPixelPair - *vertPred;
00388                 *vertPred++ = *currentPixelPair++;
00389             }
00390 
00391             if (!keyframe) {
00392                 mbChangeByteMask <<= 1;
00393 
00394                 // next byte
00395                 if (!mbChangeByteMask) {
00396                     mbChangeByte = _mbChangeBits[mbChangeIndex++];
00397                     mbChangeByteMask = 1;
00398                 }
00399             }
00400         }
00401 
00402         // next change row
00403         if (((y + 1) & 3) == 0)
00404             _mbChangeBits += _mbChangeBitsRowSize;
00405     }
00406 }
00407 
00408 const Graphics::Surface *TrueMotion1Decoder::decodeFrame(Common::SeekableReadStream &stream) {
00409     decodeHeader(stream);
00410 
00411     if (compressionTypes[_header.compression].algorithm == ALGO_NOP) {
00412         delete[] _buf;
00413         return 0;
00414     }
00415 
00416     if (compressionTypes[_header.compression].algorithm == ALGO_RGB24H) {
00417         warning("Unhandled TrueMotion1 24bpp frame");
00418         delete[] _buf;
00419         return 0;
00420     } else
00421         decode16();
00422 
00423     delete[] _buf;
00424 
00425     return _surface;
00426 }
00427 
00428 } // End of namespace Image
00429 
00430 #endif


Generated on Sat Mar 16 2019 05:01:57 for ResidualVM by doxygen 1.7.1
curved edge   curved edge