From 05b21ace680d65336219a74cd87696eb157f7e84 Mon Sep 17 00:00:00 2001 From: sluicebox Date: Wed, 4 Dec 2019 09:06:35 -0800 Subject: SCI32: Implement VMD Censorship Blobs Phantasmagoria 1's censorship mode is now supported Trac #11229 --- engines/sci/engine/kernel.h | 4 ++ engines/sci/engine/kernel_tables.h | 9 ++--- engines/sci/engine/kvideo.cpp | 26 +++++++++++++ engines/sci/graphics/video32.cpp | 80 ++++++++++++++++++++++++++++++++++++-- engines/sci/graphics/video32.h | 47 +++++++++++++++++++++- 5 files changed, 157 insertions(+), 9 deletions(-) (limited to 'engines/sci') diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index f88afccfbe..cbbfaefc45 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -463,6 +463,10 @@ reg_t kPlayVMDIgnorePalettes(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDStartBlob(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDStopBlobs(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDAddBlob(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDDeleteBlob(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDSetPlane(EngineState *s, int argc, reg_t *argv); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index 77b442eb39..3d8c256ff9 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -468,11 +468,10 @@ static const SciKernelMapSubEntry kPlayVMD_subops[] = { { SIG_SINCE_SCI21, 10, MAP_CALL(PlayVMDGetStatus), "", NULL }, { SIG_SINCE_SCI21, 14, MAP_CALL(PlayVMDPlayUntilEvent), "i(i)(i)", NULL }, { SIG_SINCE_SCI21, 16, MAP_CALL(PlayVMDShowCursor), "i", NULL }, - // TODO: implement blob subops to pixelate Phant1 videos when censored mode is enabled - { SIG_SINCE_SCI21, 17, MAP_EMPTY(PlayVMDStartBlob), "", NULL }, - { SIG_SINCE_SCI21, 18, MAP_EMPTY(PlayVMDStopBlobs), "", NULL }, - { SIG_SINCE_SCI21, 19, MAP_EMPTY(PlayVMDAddBlob), "iiiii", NULL }, - { SIG_SINCE_SCI21, 20, MAP_EMPTY(PlayVMDDeleteBlob), "i", NULL }, + { SIG_SINCE_SCI21, 17, MAP_CALL(PlayVMDStartBlob), "", NULL }, + { SIG_SINCE_SCI21, 18, MAP_CALL(PlayVMDStopBlobs), "", NULL }, + { SIG_SINCE_SCI21, 19, MAP_CALL(PlayVMDAddBlob), "iiiii", NULL }, + { SIG_SINCE_SCI21, 20, MAP_CALL(PlayVMDDeleteBlob), "i", NULL }, { SIG_SINCE_SCI21, 21, MAP_CALL(PlayVMDSetBlackoutArea), "iiii", NULL }, { SIG_SINCE_SCI21, 23, MAP_CALL(PlayVMDRestrictPalette), "ii", NULL }, { SIG_SCI3, 27, MAP_CALL(PlayVMDSetPlane), "i(i)", NULL }, diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp index 03ed698f17..72762a668e 100644 --- a/engines/sci/engine/kvideo.cpp +++ b/engines/sci/engine/kvideo.cpp @@ -435,6 +435,32 @@ reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } +reg_t kPlayVMDStartBlob(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getVMDPlayer().deleteBlobs(); + return NULL_REG; +} + +reg_t kPlayVMDStopBlobs(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getVMDPlayer().deleteBlobs(); + return NULL_REG; +} + +reg_t kPlayVMDAddBlob(EngineState *s, int argc, reg_t *argv) { + int16 squareSize = argv[0].toSint16(); + int16 top = argv[1].toSint16(); + int16 left = argv[2].toSint16(); + int16 bottom = argv[3].toSint16(); + int16 right = argv[4].toSint16(); + int16 blobNumber = g_sci->_video32->getVMDPlayer().addBlob(squareSize, top, left, bottom, right); + return make_reg(0, blobNumber); +} + +reg_t kPlayVMDDeleteBlob(EngineState *s, int argc, reg_t *argv) { + int16 blobNumber = argv[0].toSint16(); + g_sci->_video32->getVMDPlayer().deleteBlob(blobNumber); + return SIGNAL_REG; +} + reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv) { const int16 scriptWidth = g_sci->_gfxFrameout->getScriptWidth(); const int16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight(); diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp index 7133be5bf4..567660d97a 100644 --- a/engines/sci/graphics/video32.cpp +++ b/engines/sci/graphics/video32.cpp @@ -126,7 +126,9 @@ VideoPlayer::EventFlags VideoPlayer::playUntilEvent(const EventFlags flags, cons EventFlags stopFlag = kEventFlagNone; for (;;) { - g_sci->sleep(MIN(_decoder->getTimeToNextFrame(), maxSleepMs)); + if (!_needsUpdate) { + g_sci->sleep(MIN(_decoder->getTimeToNextFrame(), maxSleepMs)); + } const Graphics::Surface *nextFrame = nullptr; // If a decoder needs more than one update per loop, this means we are @@ -140,9 +142,20 @@ VideoPlayer::EventFlags VideoPlayer::playUntilEvent(const EventFlags flags, cons } // Some frames may contain only audio and/or palette data; this occurs - // with Duck videos and is not an error + // with Duck videos and is not an error. + // If _needsUpdate has been set but it's not time to render the next frame + // then the current frame is rendered again. This reduces the delay between + // a script adding or removing censorship blobs and the screen reflecting + // this upon resuming playback. if (nextFrame) { renderFrame(*nextFrame); + _currentFrame = nextFrame; + _needsUpdate = false; + } else if (_needsUpdate) { + if (_currentFrame) { + renderFrame(*_currentFrame); + } + _needsUpdate = false; } // Event checks must only happen *after* the decoder is updated (1) and @@ -647,6 +660,9 @@ VMDPlayer::IOStatus VMDPlayer::close() { _planeIsOwned = true; _priority = 0; _drawRect = Common::Rect(); + _blobs.clear(); + _needsUpdate = false; + _currentFrame = nullptr; return kIOSuccess; } @@ -756,6 +772,43 @@ VMDPlayer::EventFlags VMDPlayer::checkForEvent(const EventFlags flags) { return kEventFlagNone; } +int16 VMDPlayer::addBlob(int16 blockSize, int16 top, int16 left, int16 bottom, int16 right) { + if (_blobs.size() >= kMaxBlobs) { + return -1; + } + + int16 blobNumber = 0; + Common::List::iterator prevBlobIterator = _blobs.begin(); + for (; prevBlobIterator != _blobs.end(); ++prevBlobIterator, ++blobNumber) { + if (blobNumber < prevBlobIterator->blobNumber) { + break; + } + } + + Blob blob = { blobNumber, blockSize, top, left, bottom, right }; + _blobs.insert(prevBlobIterator, blob); + + _needsUpdate = true; + return blobNumber; +} + +void VMDPlayer::deleteBlobs() { + if (!_blobs.empty()) { + _blobs.clear(); + _needsUpdate = true; + } +} + +void VMDPlayer::deleteBlob(int16 blobNumber) { + for (Common::List::iterator b = _blobs.begin(); b != _blobs.end(); ++b) { + if (b->blobNumber == blobNumber) { + _blobs.erase(b); + _needsUpdate = true; + break; + } + } +} + void VMDPlayer::initOverlay() { // Composited videos forced through the overlay renderer (due to HQ video // mode) still need to occlude whatever is behind them in the renderer (as @@ -977,7 +1030,28 @@ void VMDPlayer::renderFrame(const Graphics::Surface &nextFrame) const { if (_isComposited) { renderComposited(); } else { - renderOverlay(nextFrame); + if (_blobs.empty()) { + renderOverlay(nextFrame); + } else { + Graphics::Surface censoredFrame; + censoredFrame.create(nextFrame.w, nextFrame.h, nextFrame.format); + censoredFrame.copyFrom(nextFrame); + drawBlobs(censoredFrame); + renderOverlay(censoredFrame); + censoredFrame.free(); + } + } +} + +void VMDPlayer::drawBlobs(Graphics::Surface& frame) const { + for (Common::List::const_iterator blob = _blobs.begin(); blob != _blobs.end(); ++blob) { + for (int16 blockLeft = blob->left; blockLeft < blob->right; blockLeft += blob->blockSize) { + for (int16 blockTop = blob->top; blockTop < blob->bottom; blockTop += blob->blockSize) { + byte color = *(byte *)frame.getBasePtr(blockLeft, blockTop); + Common::Rect block(blockLeft, blockTop, blockLeft + blob->blockSize, blockTop + blob->blockSize); + frame.fillRect(block, color); + } + } } } diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h index cbfbe7dfa7..1fad807ac2 100644 --- a/engines/sci/graphics/video32.h +++ b/engines/sci/graphics/video32.h @@ -69,7 +69,9 @@ public: VideoPlayer(EventManager *eventMan, Video::VideoDecoder *decoder = nullptr) : _eventMan(eventMan), - _decoder(decoder) + _decoder(decoder), + _needsUpdate(false), + _currentFrame(nullptr) #ifdef USE_RGB_COLOR , _hqVideoMode(false) @@ -171,6 +173,18 @@ protected: */ Common::Rect _drawRect; + /** + * If true, playUntilEvent() will immediately render a frame. + * Used by VMDPlayer when censorship blobs are added or removed in Phant1 + * in order to immediately update the screen upon resuming playback. + */ + bool _needsUpdate; + + /** + * Current frame rendered by playUntilEvent() + */ + const Graphics::Surface* _currentFrame; + #ifdef USE_RGB_COLOR /** * Whether or not the player is currently in high-quality video rendering @@ -614,6 +628,37 @@ private: * Whether or not the mouse cursor should be shown during playback. */ bool _showCursor; + +#pragma mark - +#pragma mark VMDPlayer - Censorship blobs +public: + /** + * Censorship blobs are pixelated rectangles which are added and removed by + * game scripts. Phant1 is the only game known to use this and always sets a + * blockSize of 10. Each block's color comes from the pixel in the upper left + * corner of the block's location. + */ + int16 addBlob(int16 blockSize, int16 top, int16 left, int16 bottom, int16 right); + void deleteBlobs(); + void deleteBlob(int16 blobNumber); + +private: + enum { + kMaxBlobs = 10 + }; + + struct Blob { + int16 blobNumber; + int16 blockSize; + int16 top; + int16 left; + int16 bottom; + int16 right; + }; + + Common::List _blobs; + + void drawBlobs(Graphics::Surface& frame) const; }; #pragma mark - -- cgit v1.2.3