diff options
Diffstat (limited to 'engines/zvision/video/rlf_decoder.cpp')
-rw-r--r-- | engines/zvision/video/rlf_decoder.cpp | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/engines/zvision/video/rlf_decoder.cpp b/engines/zvision/video/rlf_decoder.cpp new file mode 100644 index 0000000000..3bbf22edff --- /dev/null +++ b/engines/zvision/video/rlf_decoder.cpp @@ -0,0 +1,313 @@ +/* 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. +* +*/ + +#include "common/scummsys.h" + +#include "zvision/video/rlf_decoder.h" + +#include "common/str.h" +#include "common/file.h" +#include "common/textconsole.h" +#include "common/debug.h" +#include "common/endian.h" + +namespace ZVision { + +RLFDecoder::~RLFDecoder() { + close(); +} + +bool RLFDecoder::loadStream(Common::SeekableReadStream *stream) { + close(); + + // Check if the stream is valid + if (stream && !stream->err() && stream->readUint32BE() == MKTAG('F', 'E', 'L', 'R')) { + addTrack(new RLFVideoTrack(stream)); + return true; + } else { + return false; + } +} + +RLFDecoder::RLFVideoTrack::RLFVideoTrack(Common::SeekableReadStream *stream) + : _readStream(stream), + _lastFrameRead(0), + _frameCount(0), + _width(0), + _height(0), + _frameTime(0), + _frames(0), + _displayedFrame(-1), + _frameBufferByteSize(0) { + + if (!readHeader()) { + warning("Not a RLF animation file. Wrong magic number"); + return; + } + + _currentFrameBuffer.create(_width, _height, getPixelFormat()); + _frameBufferByteSize = _width * _height * sizeof(uint16); + + _frames = new Frame[_frameCount]; + + // Read in each frame + for (uint i = 0; i < _frameCount; ++i) { + _frames[i] = readNextFrame(); + } +} + +RLFDecoder::RLFVideoTrack::~RLFVideoTrack() { + for (uint i = 0; i < _frameCount; ++i) { + delete[] _frames[i].encodedData; + } + delete[] _frames; + delete _readStream; + _currentFrameBuffer.free(); +} + +bool RLFDecoder::RLFVideoTrack::readHeader() { + // Read the header + _readStream->readUint32LE(); // Size1 + _readStream->readUint32LE(); // Unknown1 + _readStream->readUint32LE(); // Unknown2 + _frameCount = _readStream->readUint32LE(); // Frame count + + // Since we don't need any of the data, we can just seek right to the + // entries we need rather than read in all the individual entries. + _readStream->seek(136, SEEK_CUR); + + //// Read CIN header + //_readStream->readUint32BE(); // Magic number FNIC + //_readStream->readUint32LE(); // Size2 + //_readStream->readUint32LE(); // Unknown3 + //_readStream->readUint32LE(); // Unknown4 + //_readStream->readUint32LE(); // Unknown5 + //_readStream->seek(0x18, SEEK_CUR); // VRLE + //_readStream->readUint32LE(); // LRVD + //_readStream->readUint32LE(); // Unknown6 + //_readStream->seek(0x18, SEEK_CUR); // HRLE + //_readStream->readUint32LE(); // ELHD + //_readStream->readUint32LE(); // Unknown7 + //_readStream->seek(0x18, SEEK_CUR); // HKEY + //_readStream->readUint32LE(); // ELRH + + //// Read MIN info header + //_readStream->readUint32BE(); // Magic number FNIM + //_readStream->readUint32LE(); // Size3 + //_readStream->readUint32LE(); // OEDV + //_readStream->readUint32LE(); // Unknown8 + //_readStream->readUint32LE(); // Unknown9 + //_readStream->readUint32LE(); // Unknown10 + _width = _readStream->readUint32LE(); // Width + _height = _readStream->readUint32LE(); // Height + + // Read time header + _readStream->readUint32BE(); // Magic number EMIT + _readStream->readUint32LE(); // Size4 + _readStream->readUint32LE(); // Unknown11 + _frameTime = _readStream->readUint32LE() / 10; // Frame time in microseconds + + return true; +} + +RLFDecoder::RLFVideoTrack::Frame RLFDecoder::RLFVideoTrack::readNextFrame() { + RLFDecoder::RLFVideoTrack::Frame frame; + + _readStream->readUint32BE(); // Magic number MARF + uint32 size = _readStream->readUint32LE(); // Size + _readStream->readUint32LE(); // Unknown1 + _readStream->readUint32LE(); // Unknown2 + uint32 type = _readStream->readUint32BE(); // Either ELHD or ELRH + uint32 headerSize = _readStream->readUint32LE(); // Offset from the beginning of this frame to the frame data. Should always be 28 + _readStream->readUint32LE(); // Unknown3 + + frame.encodedSize = size - headerSize; + frame.encodedData = new int8[frame.encodedSize]; + _readStream->read(frame.encodedData, frame.encodedSize); + + if (type == MKTAG('E', 'L', 'H', 'D')) { + frame.type = Masked; + } else if (type == MKTAG('E', 'L', 'R', 'H')) { + frame.type = Simple; + _completeFrames.push_back(_lastFrameRead); + } else { + warning("Frame %u doesn't have type that can be decoded", _lastFrameRead); + } + + _lastFrameRead++; + return frame; +} + +bool RLFDecoder::RLFVideoTrack::seek(const Audio::Timestamp &time) { + uint frame = getFrameAtTime(time); + assert(frame < _frameCount); + + if ((uint)_displayedFrame == frame) + return true; + + int closestFrame = _displayedFrame; + int distance = (int)frame - closestFrame; + + if (distance < 0) { + for (uint i = 0; i < _completeFrames.size(); ++i) { + if (_completeFrames[i] > frame) + break; + closestFrame = _completeFrames[i]; + } + } else { + for (uint i = 0; i < _completeFrames.size(); ++i) { + int newDistance = (int)frame - (int)(_completeFrames[i]); + if (newDistance < 0) + break; + if (newDistance < distance) { + closestFrame = _completeFrames[i]; + distance = newDistance; + } + } + } + + for (uint i = closestFrame; i < frame; ++i) { + applyFrameToCurrent(i); + } + + _displayedFrame = frame - 1; + + return true; +} + +const Graphics::Surface *RLFDecoder::RLFVideoTrack::decodeNextFrame() { + if (_displayedFrame >= (int)_frameCount) + return NULL; + + _displayedFrame++; + applyFrameToCurrent(_displayedFrame); + + return &_currentFrameBuffer; +} + +void RLFDecoder::RLFVideoTrack::applyFrameToCurrent(uint frameNumber) { + if (_frames[frameNumber].type == Masked) { + decodeMaskedRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize); + } else if (_frames[frameNumber].type == Simple) { + decodeSimpleRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize); + } +} + +void RLFDecoder::RLFVideoTrack::decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const { + uint32 sourceOffset = 0; + uint32 destOffset = 0; + int16 numberOfCopy = 0; + + while (sourceOffset < sourceSize) { + int8 numberOfSamples = source[sourceOffset]; + sourceOffset++; + + // If numberOfSamples is negative, the next abs(numberOfSamples) samples should + // be copied directly from source to dest + if (numberOfSamples < 0) { + numberOfCopy = -numberOfSamples; + + while (numberOfCopy > 0) { + if (sourceOffset + 1 >= sourceSize) { + return; + } else if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + WRITE_UINT16(dest + destOffset, READ_LE_UINT16(source + sourceOffset)); + + sourceOffset += 2; + destOffset += 2; + numberOfCopy--; + } + + // If numberOfSamples is >= 0, move destOffset forward ((numberOfSamples * 2) + 2) + // This function assumes the dest buffer has been memset with 0's. + } else { + if (sourceOffset + 1 >= sourceSize) { + return; + } else if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + destOffset += (numberOfSamples * 2) + 2; + } + } +} + +void RLFDecoder::RLFVideoTrack::decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const { + uint32 sourceOffset = 0; + uint32 destOffset = 0; + int16 numberOfCopy = 0; + + while (sourceOffset < sourceSize) { + int8 numberOfSamples = source[sourceOffset]; + sourceOffset++; + + // If numberOfSamples is negative, the next abs(numberOfSamples) samples should + // be copied directly from source to dest + if (numberOfSamples < 0) { + numberOfCopy = -numberOfSamples; + + while (numberOfCopy > 0) { + if (sourceOffset + 1 >= sourceSize) { + return; + } else if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + WRITE_UINT16(dest + destOffset, READ_LE_UINT16(source + sourceOffset)); + + sourceOffset += 2; + destOffset += 2; + numberOfCopy--; + } + + // If numberOfSamples is >= 0, copy one sample from source to the + // next (numberOfSamples + 2) dest spots + } else { + if (sourceOffset + 1 >= sourceSize) { + return; + } + + uint16 sampleColor = READ_LE_UINT16(source + sourceOffset); + sourceOffset += 2; + + numberOfCopy = numberOfSamples + 2; + while (numberOfCopy > 0) { + if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + WRITE_UINT16(dest + destOffset, sampleColor); + destOffset += 2; + numberOfCopy--; + } + } + } +} + +} // End of namespace ZVision |