scummvm/engines/sword2/animation.cpp
Colin Snover 432fd522d2 ENGINES: Remove default1x scaler flag
This flag is removed for a few reasons:

* Engines universally set this flag to true for widths > 320,
  which made it redundant everywhere;
* This flag functioned primarily as a "force 1x scaler" flag,
  since its behaviour was almost completely undocumented and users
  would need to figure out that they'd need an explicit non-default
  scaler set to get a scaler to operate at widths > 320;
* (Most importantly) engines should not be in the business of
  deciding how the backend may choose to render its virtual screen.
  The choice of rendering behaviour belongs to the user, and the
  backend, in that order.

A nearby future commit restores the default1x scaler behaviour in
the SDL backend code for the moment, but in the future it is my
hope that there will be a better configuration UI to allow users
to specify how they want scaling to work for high resolutions.
2017-10-07 12:30:29 -05:00

480 lines
14 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/file.h"
#include "common/mutex.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/resman.h"
#include "sword2/sound.h"
#include "sword2/screen.h"
#include "sword2/animation.h"
#include "graphics/palette.h"
#include "gui/message.h"
#ifdef USE_MPEG2
#include "video/avi_decoder.h"
#endif
#ifdef USE_ZLIB
#include "video/dxa_decoder.h"
#endif
#include "video/smk_decoder.h"
#include "video/psx_decoder.h"
#include "engines/util.h"
namespace Sword2 {
///////////////////////////////////////////////////////////////////////////////
// Basic movie player
///////////////////////////////////////////////////////////////////////////////
MoviePlayer::MoviePlayer(Sword2Engine *vm, OSystem *system, Video::VideoDecoder *decoder, DecoderType decoderType)
: _vm(vm), _system(system) {
_decoderType = decoderType;
_decoder = decoder;
_white = 255;
_black = 0;
}
MoviePlayer::~MoviePlayer() {
delete _decoder;
}
/**
* Plays an animated cutscene.
* @param id the id of the file
*/
bool MoviePlayer::load(const char *name) {
// This happens when quitting during the "eye" cutscene.
if (_vm->shouldQuit())
return false;
_textSurface = NULL;
Common::String filename;
switch (_decoderType) {
case kVideoDecoderDXA:
filename = Common::String::format("%s.dxa", name);
break;
case kVideoDecoderSMK:
filename = Common::String::format("%s.smk", name);
break;
case kVideoDecoderPSX:
filename = Common::String::format("%s.str", name);
break;
case kVideoDecoderMP2:
filename = Common::String::format("%s.mp2", name);
break;
}
// Need to switch to true color for PSX/MP2 videos
if (_decoderType == kVideoDecoderPSX || _decoderType == kVideoDecoderMP2)
initGraphics(g_system->getWidth(), g_system->getHeight(), nullptr);
if (!_decoder->loadFile(filename)) {
// Go back to 8bpp color
if (_decoderType == kVideoDecoderPSX || _decoderType == kVideoDecoderMP2)
initGraphics(g_system->getWidth(), g_system->getHeight());
return false;
}
// For DXA/MP2, also add the external sound file
if (_decoderType == kVideoDecoderDXA || _decoderType == kVideoDecoderMP2)
_decoder->addStreamFileTrack(name);
_decoder->start();
return true;
}
void MoviePlayer::play(MovieText *movieTexts, uint32 numMovieTexts, uint32 leadIn, uint32 leadOut) {
_leadOutFrame = _decoder->getFrameCount();
if (_leadOutFrame > 60)
_leadOutFrame -= 60;
_movieTexts = movieTexts;
_numMovieTexts = numMovieTexts;
_currentMovieText = 0;
_leadOut = leadOut;
if (leadIn)
_vm->_sound->playMovieSound(leadIn, kLeadInSound);
bool terminated = !playVideo();
closeTextObject(_currentMovieText, NULL, 0);
if (terminated) {
_vm->_sound->stopMovieSounds();
_vm->_sound->stopSpeech();
}
// Need to jump back to paletted color
if (_decoderType == kVideoDecoderPSX || _decoderType == kVideoDecoderMP2)
initGraphics(640, 480);
}
void MoviePlayer::openTextObject(uint32 index) {
MovieText *text = &_movieTexts[index];
// Pull out the text line to get the official text number (for WAV id)
uint32 res = text->_textNumber / SIZE;
uint32 localText = text->_textNumber & 0xffff;
// Open text resource and get the line
byte *textData = _vm->fetchTextLine(_vm->_resman->openResource(res), localText);
text->_speechId = READ_LE_UINT16(textData);
// Is it speech or subtitles, or both?
// If we want subtitles, or there was no sound
if (_vm->getSubtitles() || !text->_speechId) {
text->_textMem = _vm->_fontRenderer->makeTextSprite(textData + 2, 600, 255, _vm->_speechFontId, 1);
}
_vm->_resman->closeResource(res);
if (text->_textMem) {
FrameHeader frame;
frame.read(text->_textMem);
text->_textSprite.x = 320 - frame.width / 2;
text->_textSprite.y = 440 - frame.height;
text->_textSprite.w = frame.width;
text->_textSprite.h = frame.height;
text->_textSprite.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION;
text->_textSprite.data = text->_textMem + FrameHeader::size();
text->_textSprite.isText = true;
_vm->_screen->createSurface(&text->_textSprite, &_textSurface);
_textX = 320 - text->_textSprite.w / 2;
_textY = 420 - text->_textSprite.h;
}
}
void MoviePlayer::closeTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch) {
if (index < _numMovieTexts) {
MovieText *text = &_movieTexts[index];
free(text->_textMem);
text->_textMem = NULL;
if (_textSurface) {
if (screen) {
// If the frame doesn't cover the entire
// screen, we have to erase the subtitles
// manually.
int frameWidth = _decoder->getWidth();
int frameHeight = _decoder->getHeight();
if (_decoderType == kVideoDecoderPSX)
frameHeight *= 2;
int frameX = (_system->getWidth() - frameWidth) / 2;
int frameY = (_system->getHeight() - frameHeight) / 2;
uint32 black = getBlackColor();
for (int y = 0; y < text->_textSprite.h; y++) {
if (_textY + y < frameY || _textY + y >= frameY + frameHeight) {
screen->hLine(_textX, _textY + y, _textX + text->_textSprite.w, black);
} else {
if (frameX > _textX)
screen->hLine(_textX, _textY + y, frameX, black);
if (frameX + frameWidth < _textX + text->_textSprite.w)
screen->hLine(frameX + frameWidth, _textY + y, text->_textSprite.w, black);
}
}
}
_vm->_screen->deleteSurface(_textSurface);
_textSurface = NULL;
}
}
}
#define PUT_PIXEL(c) \
switch (screen->format.bytesPerPixel) { \
case 1: \
*dst = (c); \
break; \
case 2: \
WRITE_UINT16(dst, (c)); \
break; \
case 4: \
WRITE_UINT32(dst, (c)); \
break; \
}
void MoviePlayer::drawTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch) {
MovieText *text = &_movieTexts[index];
uint32 white = getWhiteColor();
uint32 black = getBlackColor();
if (text->_textMem && _textSurface) {
byte *src = text->_textSprite.data;
uint16 width = text->_textSprite.w;
uint16 height = text->_textSprite.h;
// Resize text sprites for PSX version
byte *psxSpriteBuffer = 0;
if (Sword2Engine::isPsx()) {
height *= 2;
psxSpriteBuffer = (byte *)malloc(width * height);
Screen::resizePsxSprite(psxSpriteBuffer, src, width, height);
src = psxSpriteBuffer;
}
for (int y = 0; y < height; y++) {
byte *dst = (byte *)screen->getBasePtr(_textX, _textY + y);
for (int x = 0; x < width; x++) {
if (src[x] == 1) {
PUT_PIXEL(black);
} else if (src[x] == 255) {
PUT_PIXEL(white);
}
dst += screen->format.bytesPerPixel;
}
src += width;
}
// Free buffer used to resize psx sprite
if (Sword2Engine::isPsx())
free(psxSpriteBuffer);
}
}
#undef PUT_PIXEL
void MoviePlayer::performPostProcessing(Graphics::Surface *screen, uint16 pitch) {
MovieText *text;
int frame = _decoder->getCurFrame();
if (_currentMovieText < _numMovieTexts) {
text = &_movieTexts[_currentMovieText];
} else {
text = NULL;
}
if (text && frame == text->_startFrame) {
if ((_vm->getSubtitles() || !text->_speechId) && _currentMovieText < _numMovieTexts) {
openTextObject(_currentMovieText);
}
}
if (text && frame >= text->_startFrame) {
if (text->_speechId && !text->_played && _vm->_sound->amISpeaking() == RDSE_QUIET) {
text->_played = true;
_vm->_sound->playCompSpeech(text->_speechId, 16, 0);
}
if (frame < text->_endFrame) {
drawTextObject(_currentMovieText, screen, pitch);
} else {
closeTextObject(_currentMovieText, screen, pitch);
_currentMovieText++;
}
}
if (_leadOut && _decoder->getCurFrame() == _leadOutFrame) {
_vm->_sound->playMovieSound(_leadOut, kLeadOutSound);
}
}
bool MoviePlayer::playVideo() {
uint16 x = (g_system->getWidth() - _decoder->getWidth()) / 2;
uint16 y = (g_system->getHeight() - _decoder->getHeight()) / 2;
while (!_vm->shouldQuit() && !_decoder->endOfVideo()) {
if (_decoder->needsUpdate()) {
const Graphics::Surface *frame = _decoder->decodeNextFrame();
if (frame) {
if (_decoderType == kVideoDecoderPSX)
drawFramePSX(frame);
else
_vm->_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, frame->w, frame->h);
}
if (_decoder->hasDirtyPalette()) {
_vm->_system->getPaletteManager()->setPalette(_decoder->getPalette(), 0, 256);
uint32 maxWeight = 0;
uint32 minWeight = 0xFFFFFFFF;
uint32 weight;
byte r, g, b;
const byte *palette = _decoder->getPalette();
for (int i = 0; i < 256; i++) {
r = *palette++;
g = *palette++;
b = *palette++;
weight = 3 * r * r + 6 * g * g + 2 * b * b;
if (weight >= maxWeight) {
maxWeight = weight;
_white = i;
}
if (weight <= minWeight) {
minWeight = weight;
_black = i;
}
}
}
Graphics::Surface *screen = _vm->_system->lockScreen();
performPostProcessing(screen, screen->pitch);
_vm->_system->unlockScreen();
_vm->_system->updateScreen();
}
Common::Event event;
while (_vm->_system->getEventManager()->pollEvent(event))
if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP)
return false;
_vm->_system->delayMillis(10);
}
return !_vm->shouldQuit();
}
uint32 MoviePlayer::getBlackColor() {
return (_decoderType == kVideoDecoderPSX || _decoderType == kVideoDecoderMP2) ? g_system->getScreenFormat().RGBToColor(0x00, 0x00, 0x00) : _black;
}
uint32 MoviePlayer::getWhiteColor() {
return (_decoderType == kVideoDecoderPSX || _decoderType == kVideoDecoderMP2) ? g_system->getScreenFormat().RGBToColor(0xFF, 0xFF, 0xFF) : _white;
}
void MoviePlayer::drawFramePSX(const Graphics::Surface *frame) {
// The PSX videos have half resolution
Graphics::Surface scaledFrame;
scaledFrame.create(frame->w, frame->h * 2, frame->format);
for (int y = 0; y < scaledFrame.h; y++)
memcpy(scaledFrame.getBasePtr(0, y), frame->getBasePtr(0, y / 2), scaledFrame.w * scaledFrame.format.bytesPerPixel);
uint16 x = (g_system->getWidth() - scaledFrame.w) / 2;
uint16 y = (g_system->getHeight() - scaledFrame.h) / 2;
_vm->_system->copyRectToScreen(scaledFrame.getPixels(), scaledFrame.pitch, x, y, scaledFrame.w, scaledFrame.h);
scaledFrame.free();
}
///////////////////////////////////////////////////////////////////////////////
// Factory function for creating the appropriate cutscene player
///////////////////////////////////////////////////////////////////////////////
MoviePlayer *makeMoviePlayer(const char *name, Sword2Engine *vm, OSystem *system, uint32 frameCount) {
Common::String filename;
filename = Common::String::format("%s.str", name);
if (Common::File::exists(filename)) {
#ifdef USE_RGB_COLOR
Video::VideoDecoder *psxDecoder = new Video::PSXStreamDecoder(Video::PSXStreamDecoder::kCD2x, frameCount);
return new MoviePlayer(vm, system, psxDecoder, kVideoDecoderPSX);
#else
GUI::MessageDialog dialog(_("PSX cutscenes found but ScummVM has been built without RGB color support"), _("OK"));
dialog.runModal();
return NULL;
#endif
}
filename = Common::String::format("%s.smk", name);
if (Common::File::exists(filename)) {
Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder();
return new MoviePlayer(vm, system, smkDecoder, kVideoDecoderSMK);
}
filename = Common::String::format("%s.dxa", name);
if (Common::File::exists(filename)) {
#ifdef USE_ZLIB
Video::DXADecoder *dxaDecoder = new Video::DXADecoder();
return new MoviePlayer(vm, system, dxaDecoder, kVideoDecoderDXA);
#else
GUI::MessageDialog dialog(_("DXA cutscenes found but ScummVM has been built without zlib"), _("OK"));
dialog.runModal();
return NULL;
#endif
}
// Old MPEG2 cutscenes
filename = Common::String::format("%s.mp2", name);
if (Common::File::exists(filename)) {
#ifdef USE_MPEG2
// HACK: Old ScummVM builds ignored the AVI frame rate field and forced the video
// to be played back at 12fps.
Video::AVIDecoder *aviDecoder = new Video::AVIDecoder(12);
return new MoviePlayer(vm, system, aviDecoder, kVideoDecoderMP2);
#else
GUI::MessageDialog dialog(_("MPEG-2 cutscenes found but ScummVM has been built without MPEG-2 support"), _("OK"));
dialog.runModal();
return NULL;
#endif
}
// The demo tries to play some cutscenes that aren't there, so make those warnings more discreet.
// In addition, some of the later re-releases of the game don't have the "eye" Virgin logo movie.
if (!vm->_logic->readVar(DEMO) && strcmp(name, "eye") != 0) {
Common::String buf = Common::String::format(_("Cutscene '%s' not found"), name);
GUI::MessageDialog dialog(buf, _("OK"));
dialog.runModal();
} else
warning("Cutscene '%s' not found", name);
return NULL;
}
} // End of namespace Sword2