diff options
Diffstat (limited to 'engines/voyeur/animation.cpp')
-rw-r--r-- | engines/voyeur/animation.cpp | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/engines/voyeur/animation.cpp b/engines/voyeur/animation.cpp new file mode 100644 index 0000000000..62b37346da --- /dev/null +++ b/engines/voyeur/animation.cpp @@ -0,0 +1,501 @@ +/* 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 "voyeur/animation.h" +#include "voyeur/staticres.h" +#include "voyeur/voyeur.h" +#include "common/endian.h" +#include "common/memstream.h" +#include "common/system.h" +#include "audio/decoders/raw.h" +#include "graphics/surface.h" + +namespace Voyeur { + +// Number of audio frames to keep audio track topped up when playing back video +#define SOUND_FRAMES_READAHEAD 3 + +RL2Decoder::RL2Decoder(Audio::Mixer::SoundType soundType) : _soundType(soundType) { + _paletteStart = 0; + _fileStream = nullptr; + _soundFrameNumber = -1; + + _audioTrack = nullptr; + _videoTrack = nullptr; +} + +RL2Decoder::~RL2Decoder() { + close(); +} + +bool RL2Decoder::loadVideo(int videoId) { + Common::String filename = Common::String::format("%s.rl2", + ::Voyeur::SZ_FILENAMES[videoId * 2]); + return loadRL2File(filename, false); +} + +bool RL2Decoder::loadRL2File(const Common::String &file, bool palFlag) { + bool result = VideoDecoder::loadFile(file); + _paletteStart = palFlag ? 0 : 128; + return result; +} + +bool RL2Decoder::loadStream(Common::SeekableReadStream *stream) { + close(); + + // Load basic file information + _fileStream = stream; + _header.load(stream); + _paletteStart = 0; + + // Check RL2 magic number + if (!_header.isValid()) { + warning("RL2Decoder::loadStream(): attempted to load non-RL2 data (0x%08X)", _header._signature); + return false; + } + + // Add an audio track if sound is present + _audioTrack = nullptr; + if (_header._soundRate) { + _audioTrack = new RL2AudioTrack(_header, stream, _soundType); + addTrack(_audioTrack); + } + + // Create a video track + _videoTrack = new RL2VideoTrack(_header, _audioTrack, stream); + addTrack(_videoTrack); + + // Load the offset/sizes of the video's audio data + _soundFrames.reserve(_header._numFrames); + for (int frameNumber = 0; frameNumber < _header._numFrames; ++frameNumber) { + int offset = _header._frameOffsets[frameNumber]; + int size = _header._frameSoundSizes[frameNumber]; + + _soundFrames.push_back(SoundFrame(offset, size)); + } + + return true; +} + +const Common::List<Common::Rect> *RL2Decoder::getDirtyRects() const { + if (_videoTrack) + return _videoTrack->getDirtyRects(); + + return nullptr; +} + +void RL2Decoder::clearDirtyRects() { + if (_videoTrack) + _videoTrack->clearDirtyRects(); +} + +void RL2Decoder::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) { + if (_videoTrack) + _videoTrack->copyDirtyRectsToBuffer(dst, pitch); +} + +void RL2Decoder::readNextPacket() { + int frameNumber = getCurFrame(); + RL2AudioTrack *audioTrack = getRL2AudioTrack(); + + // Handle queueing sound data + if (_soundFrameNumber == -1) + _soundFrameNumber = (frameNumber == -1) ? 0 : frameNumber; + + while (audioTrack->numQueuedStreams() < SOUND_FRAMES_READAHEAD && + (_soundFrameNumber < (int)_soundFrames.size())) { + _fileStream->seek(_soundFrames[_soundFrameNumber]._offset); + audioTrack->queueSound(_fileStream, _soundFrames[_soundFrameNumber]._size); + ++_soundFrameNumber; + } +} + +bool RL2Decoder::seekIntern(const Audio::Timestamp &where) { + _soundFrameNumber = -1; + return VideoDecoder::seekIntern(where); +} + +void RL2Decoder::close() { + VideoDecoder::close(); + delete _fileStream; + _fileStream = nullptr; + _soundFrameNumber = -1; +} + +/*------------------------------------------------------------------------*/ + +RL2Decoder::SoundFrame::SoundFrame(int offset, int size) { + _offset = offset; + _size = size; +} + +/*------------------------------------------------------------------------*/ + +RL2Decoder::RL2FileHeader::RL2FileHeader() { + _frameOffsets = nullptr; + _frameSoundSizes = nullptr; + + _channels = 0; + _colorCount = 0; + _numFrames = 0; + _rate = 0; + _soundRate = 0; + _videoBase = 0; + _backSize = 0; + _signature = MKTAG('N', 'O', 'N', 'E'); + _form = 0; + _dataSize = 0; + _method = 0; + _defSoundSize = 0; +} + +RL2Decoder::RL2FileHeader::~RL2FileHeader() { + delete[] _frameOffsets; + delete[] _frameSoundSizes; +} + +void RL2Decoder::RL2FileHeader::load(Common::SeekableReadStream *stream) { + stream->seek(0); + + _form = stream->readUint32LE(); + _backSize = stream->readUint32LE(); + _signature = stream->readUint32BE(); + + if (!isValid()) + return; + + _dataSize = stream->readUint32LE(); + _numFrames = stream->readUint32LE(); + _method = stream->readUint16LE(); + _soundRate = stream->readUint16LE(); + _rate = stream->readUint16LE(); + _channels = stream->readUint16LE(); + _defSoundSize = stream->readUint16LE(); + _videoBase = stream->readUint16LE(); + _colorCount = stream->readUint32LE(); + assert(_colorCount <= 256); + + stream->read(_palette, 768); + + // Skip over background frame, if any, and the list of overall frame sizes (which we don't use) + stream->skip(_backSize + 4 * _numFrames); + + // Load frame offsets + delete[] _frameOffsets; + _frameOffsets = new uint32[_numFrames]; + for (int i = 0; i < _numFrames; ++i) + _frameOffsets[i] = stream->readUint32LE(); + + // Load the size of the sound portion of each frame + delete[] _frameSoundSizes; + _frameSoundSizes = new int[_numFrames]; + for (int i = 0; i < _numFrames; ++i) + _frameSoundSizes[i] = stream->readUint32LE() & 0xffff; +} + +bool RL2Decoder::RL2FileHeader::isValid() const { + return _signature == MKTAG('R','L','V','2') || _signature == MKTAG('R','L','V','3'); +} + +Common::Rational RL2Decoder::RL2FileHeader::getFrameRate() const { + return (_soundRate > 0) ? Common::Rational(_rate, _defSoundSize) : + Common::Rational(11025, 1103); +} + +/*------------------------------------------------------------------------*/ + +RL2Decoder::RL2VideoTrack::RL2VideoTrack(const RL2FileHeader &header, RL2AudioTrack *audioTrack, + Common::SeekableReadStream *stream): _header(header), _fileStream(stream) { + + _frameOffsets = nullptr; + + // Set up surfaces + _surface = new Graphics::Surface(); + _surface->create(320, 200, Graphics::PixelFormat::createFormatCLUT8()); + _backSurface = nullptr; + + _hasBackFrame = header._backSize != 0; + if (_hasBackFrame) + initBackSurface(); + + _videoBase = header._videoBase; + _dirtyPalette = header._colorCount > 0; + + _curFrame = -1; + _initialFrame = true; +} + +RL2Decoder::RL2VideoTrack::~RL2VideoTrack() { + // Free surfaces + _surface->free(); + delete _surface; + if (_backSurface) { + _backSurface->free(); + delete _backSurface; + } +} + +void RL2Decoder::RL2VideoTrack::initBackSurface() { + _backSurface = new Graphics::Surface(); + _backSurface->create(320, 200, Graphics::PixelFormat::createFormatCLUT8()); +} + +bool RL2Decoder::RL2VideoTrack::seek(const Audio::Timestamp &time) { + int frame = getFrameAtTime(time); + + if (frame < 0 || frame >= _header._numFrames) + return false; + + _curFrame = frame; + return true; +} + +uint16 RL2Decoder::RL2VideoTrack::getWidth() const { + return _surface->w; +} + +uint16 RL2Decoder::RL2VideoTrack::getHeight() const { + return _surface->h; +} + +Graphics::PixelFormat RL2Decoder::RL2VideoTrack::getPixelFormat() const { + return _surface->format; +} + +const Graphics::Surface *RL2Decoder::RL2VideoTrack::decodeNextFrame() { + if (_initialFrame && _hasBackFrame) { + // Read in the initial background frame + _fileStream->seek(0x324); + rl2DecodeFrameWithoutTransparency(0); + + Common::copy((byte *)_surface->getPixels(), (byte *)_surface->getPixels() + (320 * 200), + (byte *)_backSurface->getPixels()); + _dirtyRects.push_back(Common::Rect(0, 0, _surface->w, _surface->h)); + _initialFrame = false; + } + + // Move to the next frame data + _fileStream->seek(_header._frameOffsets[++_curFrame]); + + // If there's any sound data, pass it to the audio track + _fileStream->seek(_header._frameSoundSizes[_curFrame], SEEK_CUR); + + // Decode the graphic data using the appropriate method depending on whether the animation + // has a background or just raw frames without any background transparency + if (_backSurface) { + rl2DecodeFrameWithTransparency(_videoBase); + } else { + rl2DecodeFrameWithoutTransparency(_videoBase); + } + + return _surface; +} + +void RL2Decoder::RL2VideoTrack::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) { + for (Common::List<Common::Rect>::const_iterator it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + for (int y = (*it).top; y < (*it).bottom; ++y) { + const int x = (*it).left; + memcpy(dst + y * pitch + x, (byte *)_surface->getPixels() + y * getWidth() + x, (*it).right - x); + } + } + + clearDirtyRects(); +} + +void RL2Decoder::RL2VideoTrack::copyFrame(uint8 *data) { + memcpy((byte *)_surface->getPixels(), data, getWidth() * getHeight()); + + // Redraw + _dirtyRects.clear(); + _dirtyRects.push_back(Common::Rect(0, 0, getWidth(), getHeight())); +} + +void RL2Decoder::RL2VideoTrack::rl2DecodeFrameWithoutTransparency(int screenOffset) { + if (screenOffset == -1) + screenOffset = _videoBase; + int frameSize = _surface->w * _surface->h - screenOffset; + byte *destP = (byte *)_surface->getPixels(); + + // Main frame decode loop + byte nextByte; + for (;;) { + nextByte = _fileStream->readByte(); + + if (nextByte < 0x80) { + // Simple byte to copy to output + assert(frameSize > 0); + *destP++ = nextByte; + --frameSize; + } else if (nextByte > 0x80) { + // Lower 7 bits a run length for the following byte + int runLength = _fileStream->readByte(); + runLength = MIN(runLength, frameSize); + + Common::fill(destP, destP + runLength, nextByte & 0x7f); + destP += runLength; + frameSize -= runLength; + } else { + // Follow byte run length for zeroes. If zero, indicates end of image + int runLength = _fileStream->readByte(); + if (runLength == 0) + break; + + runLength = MIN(runLength, frameSize); + Common::fill(destP, destP + runLength, 0); + destP += runLength; + frameSize -= runLength; + } + } + + // If there's any remaining screen area, zero it out + byte *endP = (byte *)_surface->getPixels() + _surface->w * _surface->h; + if (destP != endP) + Common::fill(destP, endP, 0); +} + +void RL2Decoder::RL2VideoTrack::rl2DecodeFrameWithTransparency(int screenOffset) { + int frameSize = _surface->w * _surface->h - screenOffset; + byte *refP = (byte *)_backSurface->getPixels(); + byte *destP = (byte *)_surface->getPixels(); + + // If there's a screen offset, copy unchanged initial pixels from reference surface + if (screenOffset > 0) + Common::copy(refP, refP + screenOffset, destP); + + // Main decode loop + while (frameSize > 0) { + byte nextByte = _fileStream->readByte(); + + if (nextByte == 0) { + // Move one single byte from reference surface + assert(frameSize > 0); + destP[screenOffset] = refP[screenOffset]; + ++screenOffset; + --frameSize; + } else if (nextByte < 0x80) { + // Single 7-bit pixel to output (128-255) + assert(frameSize > 0); + destP[screenOffset] = nextByte | 0x80; + ++screenOffset; + --frameSize; + } else if (nextByte == 0x80) { + int runLength = _fileStream->readByte(); + if (runLength == 0) + return; + + // Run length of transparency (i.e. pixels to copy from reference frame) + runLength = MIN(runLength, frameSize); + + Common::copy(refP + screenOffset, refP + screenOffset + runLength, destP + screenOffset); + screenOffset += runLength; + frameSize -= runLength; + } else { + // Run length of a single pixel value + int runLength = _fileStream->readByte(); + runLength = MIN(runLength, frameSize); + + Common::fill(destP + screenOffset, destP + screenOffset + runLength, nextByte); + screenOffset += runLength; + frameSize -= runLength; + } + } + + // If there's a remaining section of the screen not covered, copy it from reference surface + if (screenOffset < (_surface->w * _surface->h)) + Common::copy(refP + screenOffset, refP + (_surface->w * _surface->h), destP + screenOffset); +} + +Graphics::Surface *RL2Decoder::RL2VideoTrack::getBackSurface() { + if (!_backSurface) + initBackSurface(); + + return _backSurface; +} + +/*------------------------------------------------------------------------*/ + +RL2Decoder::RL2AudioTrack::RL2AudioTrack(const RL2FileHeader &header, Common::SeekableReadStream *stream, Audio::Mixer::SoundType soundType): + _header(header), _soundType(soundType) { + // Create audio straem for the audio track + _audStream = Audio::makeQueuingAudioStream(_header._rate, _header._channels == 2); +} + +RL2Decoder::RL2AudioTrack::~RL2AudioTrack() { + delete _audStream; +} + +void RL2Decoder::RL2AudioTrack::queueSound(Common::SeekableReadStream *stream, int size) { + // Queue the sound data + byte *data = (byte *)malloc(size); + stream->read(data, size); + Common::MemoryReadStream *memoryStream = new Common::MemoryReadStream(data, size, + DisposeAfterUse::YES); + + _audStream->queueAudioStream(Audio::makeRawStream(memoryStream, _header._rate, + Audio::FLAG_UNSIGNED, DisposeAfterUse::YES), DisposeAfterUse::YES); +} + +Audio::AudioStream *RL2Decoder::RL2AudioTrack::getAudioStream() const { + return _audStream; +} + +void RL2Decoder::play(VoyeurEngine *vm, int resourceOffset, + byte *frames, byte *imgPos) { + vm->flipPageAndWait(); + int paletteStart = getPaletteStart(); + int paletteCount = getPaletteCount(); + + PictureResource videoFrame(getRL2VideoTrack()->getBackSurface()); + int picCtr = 0; + while (!vm->shouldQuit() && !endOfVideo() && !vm->_eventsManager->_mouseClicked) { + if (hasDirtyPalette()) { + const byte *palette = getPalette(); + + vm->_graphicsManager->setPalette128(palette, paletteStart, paletteCount); + } + + if (needsUpdate()) { + if (frames) { + // If reached a point where a new background is needed, load it + // and copy over to the video decoder + if (getCurFrame() >= READ_LE_UINT16(frames + picCtr * 4)) { + PictureResource *newPic = vm->_bVoy->boltEntry(0x302 + picCtr)._picResource; + Common::Point pt(READ_LE_UINT16(imgPos + 4 * picCtr) - 32, + READ_LE_UINT16(imgPos + 4 * picCtr + 2) - 20); + + vm->_graphicsManager->sDrawPic(newPic, &videoFrame, pt); + ++picCtr; + } + } + + // Decode the next frame and display + const Graphics::Surface *frame = decodeNextFrame(); + Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200, + (byte *)vm->_graphicsManager->_screenSurface.getPixels()); + } + + vm->_eventsManager->getMouseInfo(); + g_system->delayMillis(10); + } +} + +} // End of namespace Voyeur |