aboutsummaryrefslogtreecommitdiff
path: root/engines/sci
diff options
context:
space:
mode:
authorsluicebox2019-12-04 09:06:35 -0800
committersluicebox2019-12-04 09:11:21 -0800
commit05b21ace680d65336219a74cd87696eb157f7e84 (patch)
treeb9dfd3ee22c83ee9fd6211a058a05b5c4dd3dafc /engines/sci
parentda9c7d3d5bb5b64f026d20eabfa8f3cb4297867b (diff)
downloadscummvm-rg350-05b21ace680d65336219a74cd87696eb157f7e84.tar.gz
scummvm-rg350-05b21ace680d65336219a74cd87696eb157f7e84.tar.bz2
scummvm-rg350-05b21ace680d65336219a74cd87696eb157f7e84.zip
SCI32: Implement VMD Censorship Blobs
Phantasmagoria 1's censorship mode is now supported Trac #11229
Diffstat (limited to 'engines/sci')
-rw-r--r--engines/sci/engine/kernel.h4
-rw-r--r--engines/sci/engine/kernel_tables.h9
-rw-r--r--engines/sci/engine/kvideo.cpp26
-rw-r--r--engines/sci/graphics/video32.cpp80
-rw-r--r--engines/sci/graphics/video32.h47
5 files changed, 157 insertions, 9 deletions
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<Blob>::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<Blob>::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<Blob>::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<Blob> _blobs;
+
+ void drawBlobs(Graphics::Surface& frame) const;
};
#pragma mark -