diff options
Diffstat (limited to 'engines/sci/graphics')
-rw-r--r-- | engines/sci/graphics/video32.cpp | 390 | ||||
-rw-r--r-- | engines/sci/graphics/video32.h | 204 |
2 files changed, 591 insertions, 3 deletions
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp index dc2641c92a..ac4522c1a0 100644 --- a/engines/sci/graphics/video32.cpp +++ b/engines/sci/graphics/video32.cpp @@ -29,11 +29,397 @@ #include "sci/graphics/palette32.h" #include "sci/graphics/text32.h" #include "sci/graphics/video32.h" -#include "sci/sci.h" +#include "audio/mixer.h" // for Audio::Mixer::kSFXSoundType +#include "common/config-manager.h" // for ConfMan +#include "common/textconsole.h" // for error, warning +#include "common/system.h" +#include "engine.h" // for Engine, g_engine +#include "engines/util.h" +#include "graphics/scaler/scalebit.h" +#include "sci/console.h" // for Console +#include "sci/engine/state.h" // for EngineState +#include "sci/engine/vm_types.h" // for reg_t +#include "sci/event.h" // for SciEvent, EventManager, SCI_... +#include "sci/graphics/celobj32.h" // for CelInfo32 +#include "sci/graphics/cursor.h" // for GfxCursor +#include "sci/graphics/helpers.h" // for Color, Palette +#include "sci/graphics/palette32.h" // for GfxPalette32 +#include "sci/graphics/plane32.h" // for Plane, PlanePictureCodes::kP... +#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem, Scale... +#include "sci/graphics/text32.h" // for BitmapResource +#include "sci/video/seq_decoder.h" +#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion +#include "video/avi_decoder.h" #include "video/coktel_decoder.h" namespace Sci { +#pragma mark SEQPlayer + +SEQPlayer::SEQPlayer(SegManager *segMan) : + _segMan(segMan), + _decoder(nullptr), + _plane(nullptr), + _screenItem(nullptr) {} + +void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y) { + delete _decoder; + _decoder = new SEQDecoder(numTicks); + _decoder->loadFile(fileName); + + // NOTE: In the original engine, video was output directly to the hardware, + // bypassing the game's rendering engine. Instead of doing this, we use a + // mechanism that is very similar to that used by the VMD player, which + // allows the SEQ to be drawn into a bitmap ScreenItem and displayed using + // the normal graphics system. + _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, kLowResX, kLowResY, 0, false, false); + + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = _bitmap; + + _plane = new Plane(Common::Rect(kLowResX, kLowResY), kPlanePicColored); + g_sci->_gfxFrameout->addPlane(*_plane); + + // Normally we would use the x, y coordinates passed into the play function + // to position the screen item, but because the video frame bitmap is + // drawn in low-resolution coordinates, it gets automatically scaled up by + // the engine (pixel doubling with aspect ratio correction). As a result, + // the animation does not need the extra offsets from the game in order to + // be correctly positioned in the middle of the window, so we ignore them. + _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(0, 0), ScaleInfo()); + g_sci->_gfxFrameout->addScreenItem(*_screenItem); + g_sci->_gfxFrameout->frameOut(true); + _decoder->start(); + + while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) { + renderFrame(); + g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); + g_sci->getEngineState()->_throttleTrigger = true; + } + + _segMan->freeBitmap(_screenItem->_celInfo.bitmap); + g_sci->_gfxFrameout->deletePlane(*_plane); + g_sci->_gfxFrameout->frameOut(true); + _screenItem = nullptr; + _plane = nullptr; +} + +void SEQPlayer::renderFrame() const { + const Graphics::Surface *surface = _decoder->decodeNextFrame(); + + SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap); + bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h)); + + const bool dirtyPalette = _decoder->hasDirtyPalette(); + if (dirtyPalette) { + Palette palette; + const byte *rawPalette = _decoder->getPalette(); + for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) { + palette.colors[i].r = *rawPalette++; + palette.colors[i].g = *rawPalette++; + palette.colors[i].b = *rawPalette++; + palette.colors[i].used = true; + } + + g_sci->_gfxPalette32->submit(palette); + } + + g_sci->_gfxFrameout->updateScreenItem(*_screenItem); + g_sci->getSciDebugger()->onFrame(); + g_sci->_gfxFrameout->frameOut(true); +} + +#pragma mark - +#pragma mark AVIPlayer + +AVIPlayer::AVIPlayer(SegManager *segMan, EventManager *eventMan) : + _segMan(segMan), + _eventMan(eventMan), + _decoder(new Video::AVIDecoder(Audio::Mixer::kSFXSoundType)), + _scaleBuffer(nullptr), + _plane(nullptr), + _screenItem(nullptr), + _status(kAVINotOpen) {} + +AVIPlayer::~AVIPlayer() { + close(); + delete _decoder; +} + +AVIPlayer::IOStatus AVIPlayer::open(const Common::String &fileName) { + if (_status != kAVINotOpen) { + close(); + } + + if (!_decoder->loadFile(fileName)) { + return kIOFileNotFound; + } + + _status = kAVIOpen; + return kIOSuccess; +} + +AVIPlayer::IOStatus AVIPlayer::init1x(const int16 x, const int16 y, int16 width, int16 height) { + if (!width || !height) { + width = _decoder->getWidth(); + height = _decoder->getHeight(); + } + + // QFG4CD gives non-multiple-of-2 values for width and height, + // which would normally be OK except the source video is a pixel bigger + // in each dimension + width = (width + 1) & ~1; + height = (height + 1) & ~1; + + _drawRect.left = x; + _drawRect.top = y; + _drawRect.right = x + width; + _drawRect.bottom = y + height; + + // SCI2.1 uses init2x to draw a pixel-doubled AVI, but SCI2 has only the + // one play routine which automatically pixel-doubles in hi-res mode + if (getSciVersion() == SCI_VERSION_2) { + // NOTE: This is somewhat of a hack; credits.avi from GK1 is not + // rendered correctly in SSCI because it is a 640x480 video, but the + // game script gives the wrong dimensions. Since this is the only + // high-resolution AVI ever used, just set the draw rectangle to draw + // the entire screen + if (_decoder->getWidth() > 320) { + _drawRect.left = 0; + _drawRect.top = 0; + _drawRect.right = 320; + _drawRect.bottom = 200; + } + + // In hi-res mode, video will be pixel doubled, so the origin (which + // corresponds to the correct position without pixel doubling) needs to + // be corrected + if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() <= 320) { + _drawRect.left /= 2; + _drawRect.top /= 2; + } + } + + _pixelDouble = false; + init(); + + return kIOSuccess; +} + +AVIPlayer::IOStatus AVIPlayer::init2x(const int16 x, const int16 y) { + _drawRect.left = x; + _drawRect.top = y; + _drawRect.right = x + _decoder->getWidth() * 2; + _drawRect.bottom = y + _decoder->getHeight() * 2; + + _pixelDouble = true; + init(); + + return kIOSuccess; +} + +void AVIPlayer::init() { + int16 xRes; + int16 yRes; + + if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() > 320) { + xRes = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + yRes = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + } else { + xRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + yRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + } + + _plane = new Plane(_drawRect); + g_sci->_gfxFrameout->addPlane(*_plane); + + if (_decoder->getPixelFormat().bytesPerPixel == 1) { + _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, xRes, yRes, 0, false, false); + + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = _bitmap; + + _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(_drawRect.left, _drawRect.top), ScaleInfo()); + g_sci->_gfxFrameout->addScreenItem(*_screenItem); + g_sci->_gfxFrameout->frameOut(true); + } else { + const Buffer ¤tBuffer = g_sci->_gfxFrameout->getCurrentBuffer(); + const Graphics::PixelFormat format = _decoder->getPixelFormat(); + initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, g_sci->_gfxFrameout->_isHiRes, &format); + + if (_pixelDouble) { + const int16 width = _drawRect.width(); + const int16 height = _drawRect.height(); + _scaleBuffer = calloc(1, width * height * format.bytesPerPixel); + } + } +} + +AVIPlayer::IOStatus AVIPlayer::play(const int16 from, const int16 to, const int16, const bool async) { + if (from >= 0 && to > 0 && from <= to) { + _decoder->seekToFrame(from); + _decoder->setEndFrame(to); + } + + if (!async) { + renderVideo(); + } + + _status = kAVIPlaying; + return kIOSuccess; +} + +void AVIPlayer::renderVideo() const { + _decoder->start(); + while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) { + g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); + g_sci->getEngineState()->_throttleTrigger = true; + if (_decoder->needsUpdate()) { + renderFrame(); + } + } +} + +AVIPlayer::IOStatus AVIPlayer::close() { + if (_status == kAVINotOpen) { + return kIOSuccess; + } + + free(_scaleBuffer); + _scaleBuffer = nullptr; + + if (_decoder->getPixelFormat().bytesPerPixel != 1) { + const bool isHiRes = g_sci->_gfxFrameout->_isHiRes; + const Buffer ¤tBuffer = g_sci->_gfxFrameout->getCurrentBuffer(); + const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8(); + initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, isHiRes, &format); + } + + _decoder->close(); + _status = kAVINotOpen; + g_sci->_gfxFrameout->deletePlane(*_plane); + _plane = nullptr; + _screenItem = nullptr; + return kIOSuccess; +} + +AVIPlayer::IOStatus AVIPlayer::cue(const uint16 frameNo) { + if (!_decoder->seekToFrame(frameNo)) { + return kIOSeekFailed; + } + + _status = kAVIPaused; + return kIOSuccess; +} + +uint16 AVIPlayer::getDuration() const { + if (_status == kAVINotOpen) { + return 0; + } + + return _decoder->getFrameCount(); +} + +void AVIPlayer::renderFrame() const { + const Graphics::Surface *surface = _decoder->decodeNextFrame(); + + if (surface->format.bytesPerPixel == 1) { + SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap); + if (surface->w > bitmap.getWidth() || surface->h > bitmap.getHeight()) { + warning("Attempted to draw a video frame larger than the destination bitmap"); + return; + } + + bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h)); + + const bool dirtyPalette = _decoder->hasDirtyPalette(); + if (dirtyPalette) { + Palette palette; + const byte *rawPalette = _decoder->getPalette(); + for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) { + palette.colors[i].r = *rawPalette++; + palette.colors[i].g = *rawPalette++; + palette.colors[i].b = *rawPalette++; + palette.colors[i].used = true; + } + + g_sci->_gfxPalette32->submit(palette); + } + + g_sci->_gfxFrameout->updateScreenItem(*_screenItem); + g_sci->getSciDebugger()->onFrame(); + g_sci->_gfxFrameout->frameOut(true); + } else { + Common::Rect drawRect(_drawRect); + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + if (_pixelDouble) { + scale(2, _scaleBuffer, surface->pitch * 2, surface->getPixels(), surface->pitch, surface->format.bytesPerPixel, surface->w, surface->h); + g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, _drawRect.left, _drawRect.top, _drawRect.width(), _drawRect.height()); + } else { + mulinc(drawRect, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight)); + + g_system->copyRectToScreen(surface->getPixels(), surface->pitch, drawRect.left, drawRect.top, surface->w, surface->h); + } + } +} + +AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) { + _decoder->start(); + + EventFlags stopFlag = kEventFlagNone; + while (!g_engine->shouldQuit()) { + if (_decoder->endOfVideo()) { + stopFlag = kEventFlagEnd; + break; + } + + g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); + g_sci->getEngineState()->_throttleTrigger = true; + if (_decoder->needsUpdate()) { + renderFrame(); + } + + SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK); + if ((flags & kEventFlagMouseDown) && event.type == SCI_EVENT_MOUSE_PRESS) { + stopFlag = kEventFlagMouseDown; + break; + } + + event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK); + if ((flags & kEventFlagEscapeKey) && event.type == SCI_EVENT_KEYBOARD) { + bool stop = false; + while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)), + event.type != SCI_EVENT_NONE) { + if (event.character == SCI_KEY_ESC) { + stop = true; + break; + } + } + + if (stop) { + stopFlag = kEventFlagEscapeKey; + break; + } + } + + // TODO: Hot rectangles + if ((flags & kEventFlagHotRectangle) /* && event.type == SCI_EVENT_HOT_RECTANGLE */) { + warning("Hot rectangles not implemented in VMD player"); + stopFlag = kEventFlagHotRectangle; + break; + } + } + + return stopFlag; +} + +#pragma mark - #pragma mark VMDPlayer VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) : @@ -117,7 +503,7 @@ VMDPlayer::IOStatus VMDPlayer::close() { if (!_planeIsOwned && _screenItem != nullptr) { g_sci->_gfxFrameout->deleteScreenItem(*_screenItem); - g_sci->getEngineState()->_segMan->freeBitmap(_screenItem->_celInfo.bitmap); + _segMan->freeBitmap(_screenItem->_celInfo.bitmap); _screenItem = nullptr; } else if (_plane != nullptr) { g_sci->_gfxFrameout->deletePlane(*_plane); diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h index 7033f7c647..b0deba148a 100644 --- a/engines/sci/graphics/video32.h +++ b/engines/sci/graphics/video32.h @@ -23,12 +23,208 @@ #ifndef SCI_GRAPHICS_VIDEO32_H #define SCI_GRAPHICS_VIDEO32_H -namespace Video { class AdvancedVMDDecoder; } +#include "common/rect.h" // for Rect +#include "common/scummsys.h" // for int16, uint8, int32 +#include "common/str.h" // for String +#include "sci/video/robot_decoder.h" // for RobotDecoder + +namespace Video { +class AdvancedVMDDecoder; +class AVIDecoder; +} namespace Sci { class Plane; class ScreenItem; class SegManager; +class SEQDecoder; +struct Palette; +#pragma mark SEQPlayer + +/** + * SEQPlayer is used to play SEQ animations. + * Used by DOS versions of GK1 and QFG4CD. + */ +class SEQPlayer { +public: + SEQPlayer(SegManager *segMan); + + /** + * Plays a SEQ animation with the given + * file name, with each frame being displayed + * for `numTicks` ticks. + */ + void play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y); + +private: + SegManager *_segMan; + SEQDecoder *_decoder; + + /** + * The plane where the SEQ will be drawn. + */ + Plane *_plane; + + /** + * The screen item representing the SEQ surface. + */ + ScreenItem *_screenItem; + + /** + * The bitmap used to render video output. + */ + reg_t _bitmap; + + /** + * Renders a single frame of video. + */ + void renderFrame() const; +}; + +#pragma mark - +#pragma mark AVIPlayer + +/** + * AVIPlayer is used to play AVI videos. Used by + * Windows versions of GK1CD, KQ7, and QFG4CD. + */ +class AVIPlayer { +public: + enum IOStatus { + kIOSuccess = 0, + kIOFileNotFound = 2, + kIOSeekFailed = 12 + }; + + enum AVIStatus { + kAVINotOpen = 0, + kAVIOpen = 1, + kAVIPlaying = 2, + kAVIPaused = 3 + }; + + enum EventFlags { + kEventFlagNone = 0, + kEventFlagEnd = 1, + kEventFlagEscapeKey = 2, + kEventFlagMouseDown = 4, + kEventFlagHotRectangle = 8 + }; + + AVIPlayer(SegManager *segMan, EventManager *eventMan); + ~AVIPlayer(); + + /** + * Opens a stream to an AVI resource. + */ + IOStatus open(const Common::String &fileName); + + /** + * Initializes the AVI rendering parameters for the + * current AVI. This must be called after `open`. + */ + IOStatus init1x(const int16 x, const int16 y, const int16 width, const int16 height); + + /** + * Initializes the AVI rendering parameters for the + * current AVI, in pixel-doubling mode. This must + * be called after `open`. + */ + IOStatus init2x(const int16 x, const int16 y); + + /** + * Begins playback of the current AVI. + */ + IOStatus play(const int16 from, const int16 to, const int16 showStyle, const bool cue); + + /** + * Stops playback and closes the currently open AVI stream. + */ + IOStatus close(); + + /** + * Seeks the currently open AVI stream to the given frame. + */ + IOStatus cue(const uint16 frameNo); + + /** + * Returns the duration of the current video. + */ + uint16 getDuration() const; + + /** + * Plays the AVI until an event occurs (e.g. user + * presses escape, clicks, etc.). + */ + EventFlags playUntilEvent(const EventFlags flags); + +private: + typedef Common::HashMap<uint16, AVIStatus> StatusMap; + + SegManager *_segMan; + EventManager *_eventMan; + Video::AVIDecoder *_decoder; + + /** + * Playback status of the player. + */ + AVIStatus _status; + + /** + * The plane where the AVI will be drawn. + */ + Plane *_plane; + + /** + * The screen item representing the AVI surface, + * in 8bpp mode. In 24bpp mode, video is drawn + * directly to the screen. + */ + ScreenItem *_screenItem; + + /** + * The bitmap used to render video output in + * 8bpp mode. + */ + reg_t _bitmap; + + /** + * The rectangle where the video will be drawn, + * in game script coordinates. + */ + Common::Rect _drawRect; + + /** + * The scale buffer for pixel-doubled videos + * drawn in 24bpp mode. + */ + void *_scaleBuffer; + + /** + * In SCI2.1, whether or not the video should + * be pixel doubled for playback. + */ + bool _pixelDouble; + + /** + * Performs common initialisation for both + * scaled and unscaled videos. + */ + void init(); + + /** + * Renders video without event input until the + * video is complete. + */ + void renderVideo() const; + + /** + * Renders a single frame of video. + */ + void renderFrame() const; +}; + +#pragma mark - #pragma mark VMDPlayer /** @@ -300,11 +496,17 @@ private: class Video32 { public: Video32(SegManager *segMan, EventManager *eventMan) : + _SEQPlayer(segMan), + _AVIPlayer(segMan, eventMan), _VMDPlayer(segMan, eventMan) {} + SEQPlayer &getSEQPlayer() { return _SEQPlayer; } + AVIPlayer &getAVIPlayer() { return _AVIPlayer; } VMDPlayer &getVMDPlayer() { return _VMDPlayer; } private: + SEQPlayer _SEQPlayer; + AVIPlayer _AVIPlayer; VMDPlayer _VMDPlayer; }; } // End of namespace Sci |