scummvm/engines/sci/graphics/robot.cpp
Lars Skovlund aa5e8e6c2b SCI/Robot: Simplify ::setPalette()
svn-id: r55498
2011-01-24 11:17:47 +00:00

307 lines
9.4 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 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.
*
* $URL$
* $Id$
*
*/
// fopen() and friends so we can dump frames and stuff to disk, for debugging
// #define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "sci/sci.h"
#include "sci/engine/state.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/robot.h"
#include "sci/graphics/palette.h"
#include "sci/sound/audio.h"
#include "graphics/surface.h"
#include "sound/audiostream.h"
#include "sound/mixer.h"
#include "common/file.h"
#include "common/system.h"
#include "common/memstream.h"
namespace Sci {
// Some non technical information on robot files, from an interview with
// Greg Tomko-Pavia of Sierra On-Line
// Taken from http://anthonylarme.tripod.com/phantas/phintgtp.html
//
// (...) What we needed was a way of playing video, but have it blend into
// normal room art instead of occupying its own rectangular area. Room art
// consists of a background pic overlaid with various animating cels
// (traditional lingo: sprites). The cels each have a priority that determines
// who is on top and who is behind in the drawing order. Cels are read from
// *.v56 files (another proprietary format). A Robot is video frames with
// transparent background including priority and x,y information. Thus, it is
// like a cel, except it comes from an RBT - not a v56. Because it blends into
// our graphics engine, it looks just like a part of the room. A RBT can move
// around the screen and go behind other objects. (...)
#ifdef ENABLE_SCI32
GfxRobot::GfxRobot(ResourceManager *resMan, GfxScreen *screen, GfxPalette *palette)
: _resMan(resMan), _screen(screen), _palette(palette) {
_resourceId = -1;
_x = _y = 0;
}
GfxRobot::~GfxRobot() {
delete[] _resourceData;
delete[] _imageStart;
delete[] _audioStart;
delete[] _audioLen;
}
void GfxRobot::init(GuiResourceId resourceId, uint16 x, uint16 y) {
char fileName[10];
uint32 fileSize;
// resourceId = 1305; // debug
_resourceId = resourceId;
_x = x;
_y = y;
_curFrame = 0;
sprintf(fileName, "%d.rbt", _resourceId);
Common::File robotFile;
if (robotFile.open(fileName)) {
_resourceData = new byte[robotFile.size()];
robotFile.read(_resourceData, robotFile.size());
fileSize = robotFile.size();
robotFile.close();
} else {
warning("Unable to open robot file %s", fileName);
return;
}
_frameCount = READ_LE_UINT16(_resourceData + 14);
// There is another value in the header, at offset 0x12, which
// equals this value plus 14 (audio header size) in the cases
// I've seen so far. Dunno which one to use, really.
_audioSize = READ_LE_UINT16(_resourceData + 60);
//_frameSize = READ_LE_UINT32(_resourceData + 34);
_hasSound = (_resourceData[25] != 0);
_palOffset = 60;
// Some robot files have sound, which doesn't start from frame 0
// (e.g. Phantasmagoria, robot 1305)
// Yes, strangely, the word at offset 10 is non-zero if it DOESN'T have a preload
// in that case, the word is equal to that value which would otherwise be stored
// in the audio header (i.e. _audioSize above)
if (_hasSound && READ_LE_UINT16(_resourceData + 10) == 0)
_palOffset += READ_LE_UINT32(_resourceData + 60) + 14;
getFrameOffsets();
assert(_imageStart[_frameCount] == fileSize);
setPalette();
debug("Robot %d, %d frames, sound: %s\n", resourceId, _frameCount, _hasSound ? "yes" : "no");
}
void GfxRobot::setPalette() {
byte *paletteData = _resourceData + _palOffset;
uint16 paletteSize = READ_LE_UINT16(_resourceData + 16);
Palette resourcePal;
byte robotPal[256 * 4];
int startIndex = READ_LE_UINT16(paletteData + 25);
int colorCount = READ_LE_UINT16(paletteData + 29);
warning("%d palette entries starting at %d", colorCount, startIndex);
_palette->createFromData(paletteData, paletteSize, &resourcePal);
for (int i = 0; i < 256; ++i) {
_savedPal[i * 4 + 0] = _palette->_sysPalette.colors[i].r;
_savedPal[i * 4 + 1] = _palette->_sysPalette.colors[i].g;
_savedPal[i * 4 + 2] = _palette->_sysPalette.colors[i].b;
_savedPal[i * 4 + 3] = 0;
}
memcpy(robotPal, _savedPal, sizeof(_savedPal));
for (int i = 0; i < colorCount; ++i) {
int index = i + startIndex;
robotPal[index * 4 + 0] = resourcePal.colors[index].r;
robotPal[index * 4 + 1] = resourcePal.colors[index].g;
robotPal[index * 4 + 2] = resourcePal.colors[index].b;
robotPal[index * 4 + 3] = 0;
}
g_system->setPalette(robotPal, 0, 256);
}
void GfxRobot::drawNextFrame() {
int width, height;
byte *pixels = assembleVideoFrame(_curFrame);
getFrameDimensions(_curFrame, width, height);
g_system->copyRectToScreen(pixels, width, _x, _y, width, height * getFrameScale(_curFrame) / 100);
g_system->updateScreen();
g_system->delayMillis(100);
delete[] pixels;
_curFrame++;
if (_curFrame == _frameCount) {
// End of robot video, restore palette
g_system->setPalette(_savedPal, 0, 256);
_resourceId = -1;
}
}
void GfxRobot::getFrameOffsets() {
int *audioEnd = new int[_frameCount];
int *videoEnd = new int[_frameCount];
for (int i = 0; i < _frameCount; ++i) {
videoEnd[i] = READ_LE_UINT16(_resourceData + _palOffset + 1200 + i * 2);
audioEnd[i] = READ_LE_UINT16(_resourceData + _palOffset + 1200 + _frameCount * 2 + i * 2);
}
int frameDataOffset = _palOffset + 0x4b0 + 0x400 + 0x200 + _frameCount * 4;
// Pad to nearest 2 kilobytes
if (frameDataOffset & 0x7ff)
frameDataOffset = (frameDataOffset & ~0x7ff)+0x800;
_imageStart = new uint32[_frameCount + 1];
_audioStart = new uint32[_frameCount];
_audioLen = new uint32[_frameCount];
_imageStart[0] = frameDataOffset;
// Plus one so we can assert on this in the calling routine
// The last one should point to end-of-file, unless I'm misunderstanding something
for (int i = 1; i < _frameCount + 1; ++i)
_imageStart[i] = _imageStart[i - 1] + audioEnd[i - 1];
for (int i = 0; i < _frameCount; ++i)
_audioStart[i] = _imageStart[i] + videoEnd[i];
for (int i = 0; i < _frameCount; ++i)
_audioLen[i] = _imageStart[i + 1] - _audioStart[i];
delete[] audioEnd;
delete[] videoEnd;
}
byte *GfxRobot::assembleVideoFrame(int frame) {
byte *videoData = _resourceData + _imageStart[frame];
int frameWidth = READ_LE_UINT16(videoData + 4);
int frameHeight = READ_LE_UINT16(videoData + 6);
int frameFragments = READ_LE_UINT16(videoData + 18);
int decompressedSize = 0;
DecompressorLZS lzs;
videoData = _resourceData + _imageStart[frame] + 24;
for (int i = 0; i < frameFragments; ++i) {
int fragmentCompressed = READ_LE_UINT32(videoData);
int fragmentUncompressed = READ_LE_UINT32(videoData + 4);
decompressedSize += fragmentUncompressed;
videoData += 10 + fragmentCompressed;
}
assert(decompressedSize == (frameWidth * frameHeight) * getFrameScale(frame) / 100);
byte *output = new byte[decompressedSize];
int assemblePtr = 0;
videoData = _resourceData + _imageStart[frame] + 24;
for (int i = 0; i < frameFragments; ++i) {
int fragmentCompressed = READ_LE_UINT32(videoData);
int fragmentUncompressed = READ_LE_UINT32(videoData + 4);
uint16 compressionType = READ_LE_UINT16(videoData + 8);
switch (compressionType) {
case 0: {
Common::MemoryReadStream compressedFrame(videoData + 10, fragmentCompressed, DisposeAfterUse::NO);
lzs.unpack(&compressedFrame, output + assemblePtr, fragmentCompressed, fragmentUncompressed);
assemblePtr += fragmentUncompressed;
break;
}
case 2: // Untested
memcpy(output + assemblePtr, videoData + 10, fragmentCompressed);
assemblePtr += fragmentUncompressed;
break;
}
videoData += 10 + fragmentCompressed;
}
assert(assemblePtr == decompressedSize);
// FILE *f = fopen("/tmp/flap", "w");
// fwrite(output, 1, decompressedSize, f);
// fclose(f);
// exit(1);
return output;
}
void GfxRobot::getFrameDimensions(int frame, int &width, int &height) {
byte *videoData = _resourceData + _imageStart[frame];
width = READ_LE_UINT16(videoData + 4);
height = READ_LE_UINT16(videoData + 6);
}
void GfxRobot::getFrameRect(int frame, Common::Rect &rect) {
byte *videoData = _resourceData + _imageStart[frame];
int x = READ_LE_UINT16(videoData + 8);
int y = READ_LE_UINT16(videoData + 10);
int w = READ_LE_UINT16(videoData + 12);
int h = READ_LE_UINT16(videoData + 14);
rect = Common::Rect(x, y, x + w, y + h);
}
int GfxRobot::getFrameScale(int frame) {
byte *videoData = _resourceData + _imageStart[frame];
return videoData[3];
}
void GfxRobot::playAudio() {
if (_hasSound) {
Audio::SoundHandle _audioHandle;
Audio::AudioStream *audioStream = g_sci->_audio->getRobotAudioStream(_resourceData);
g_system->getMixer()->playStream(Audio::Mixer::kSpeechSoundType, &_audioHandle, audioStream);
}
}
#endif
} // End of namespace Sci