aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/graphics
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/graphics')
-rw-r--r--engines/sci/graphics/animate.cpp92
-rw-r--r--engines/sci/graphics/animate.h10
-rw-r--r--engines/sci/graphics/cache.cpp4
-rw-r--r--engines/sci/graphics/cache.h2
-rw-r--r--engines/sci/graphics/celobj32.cpp1151
-rw-r--r--engines/sci/graphics/celobj32.h607
-rw-r--r--engines/sci/graphics/compare.cpp82
-rw-r--r--engines/sci/graphics/compare.h3
-rw-r--r--engines/sci/graphics/controls16.cpp2
-rw-r--r--engines/sci/graphics/controls32.cpp912
-rw-r--r--engines/sci/graphics/controls32.h469
-rw-r--r--engines/sci/graphics/cursor.cpp12
-rw-r--r--engines/sci/graphics/cursor.h14
-rw-r--r--engines/sci/graphics/frameout.cpp2193
-rw-r--r--engines/sci/graphics/frameout.h571
-rw-r--r--engines/sci/graphics/helpers.h134
-rw-r--r--engines/sci/graphics/lists32.h192
-rw-r--r--engines/sci/graphics/maciconbar.cpp2
-rw-r--r--engines/sci/graphics/menu.cpp12
-rw-r--r--engines/sci/graphics/paint.cpp44
-rw-r--r--engines/sci/graphics/paint16.h4
-rw-r--r--engines/sci/graphics/paint32.cpp167
-rw-r--r--engines/sci/graphics/paint32.h44
-rw-r--r--engines/sci/graphics/palette.cpp160
-rw-r--r--engines/sci/graphics/palette.h37
-rw-r--r--engines/sci/graphics/palette32.cpp841
-rw-r--r--engines/sci/graphics/palette32.h411
-rw-r--r--engines/sci/graphics/picture.cpp6
-rw-r--r--engines/sci/graphics/picture.h3
-rw-r--r--engines/sci/graphics/plane32.cpp951
-rw-r--r--engines/sci/graphics/plane32.h531
-rw-r--r--engines/sci/graphics/portrait.cpp4
-rw-r--r--engines/sci/graphics/ports.cpp6
-rw-r--r--engines/sci/graphics/remap.cpp99
-rw-r--r--engines/sci/graphics/remap.h (renamed from engines/sci/graphics/paint.h)44
-rw-r--r--engines/sci/graphics/remap32.cpp468
-rw-r--r--engines/sci/graphics/remap32.h400
-rw-r--r--engines/sci/graphics/screen.cpp282
-rw-r--r--engines/sci/graphics/screen.h315
-rw-r--r--engines/sci/graphics/screen_item32.cpp743
-rw-r--r--engines/sci/graphics/screen_item32.h326
-rw-r--r--engines/sci/graphics/text16.cpp65
-rw-r--r--engines/sci/graphics/text32.cpp880
-rw-r--r--engines/sci/graphics/text32.h464
-rw-r--r--engines/sci/graphics/video32.cpp415
-rw-r--r--engines/sci/graphics/video32.h312
-rw-r--r--engines/sci/graphics/view.cpp47
-rw-r--r--engines/sci/graphics/view.h3
48 files changed, 12631 insertions, 1905 deletions
diff --git a/engines/sci/graphics/animate.cpp b/engines/sci/graphics/animate.cpp
index 7957ed6a55..98278397b7 100644
--- a/engines/sci/graphics/animate.cpp
+++ b/engines/sci/graphics/animate.cpp
@@ -28,6 +28,7 @@
#include "sci/sci.h"
#include "sci/event.h"
#include "sci/engine/kernel.h"
+#include "sci/engine/script_patches.h"
#include "sci/engine/state.h"
#include "sci/engine/selector.h"
#include "sci/engine/vm.h"
@@ -44,8 +45,8 @@
namespace Sci {
-GfxAnimate::GfxAnimate(EngineState *state, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions)
- : _s(state), _cache(cache), _ports(ports), _paint16(paint16), _screen(screen), _palette(palette), _cursor(cursor), _transitions(transitions) {
+GfxAnimate::GfxAnimate(EngineState *state, ScriptPatcher *scriptPatcher, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions)
+ : _s(state), _scriptPatcher(scriptPatcher), _cache(cache), _ports(ports), _paint16(paint16), _screen(screen), _palette(palette), _cursor(cursor), _transitions(transitions) {
init();
}
@@ -55,16 +56,77 @@ GfxAnimate::~GfxAnimate() {
void GfxAnimate::init() {
_lastCastData.clear();
- _ignoreFastCast = false;
- // fastCast object is not found in any SCI games prior SCI1
- if (getSciVersion() <= SCI_VERSION_01)
- _ignoreFastCast = true;
- // Also if fastCast object exists at gamestartup, we can assume that the interpreter doesnt do kAnimate aborts
- // (found in Larry 1)
- if (getSciVersion() > SCI_VERSION_0_EARLY) {
- if (!_s->_segMan->findObjectByName("fastCast").isNull())
- _ignoreFastCast = true;
+ _fastCastEnabled = false;
+ if (getSciVersion() == SCI_VERSION_1_1) {
+ // Seems to have been available for all SCI1.1 games
+ _fastCastEnabled = true;
+ } else if (getSciVersion() >= SCI_VERSION_1_EARLY) {
+ // fastCast only exists for some games between SCI1 early and SCI1 late
+ // Try to detect it by code signature
+ // It's extremely important, that we only enable it for games that actually need it
+ if (detectFastCast()) {
+ _fastCastEnabled = true;
+ }
+ }
+}
+
+// Signature for fastCast detection
+static const uint16 fastCastSignature[] = {
+ SIG_MAGICDWORD,
+ 0x35, 0x00, // ldi 00
+ 0xa1, 84, // sag global[84d]
+ SIG_END
+};
+
+// Fast cast in games:
+
+// SCI1 Early:
+// KQ5 - no fastcast, LSL1 (demo) - no fastcast, Mixed Up Fairy Tales - *has fastcast*, XMas Card 1990 - no fastcast,
+// SQ4Floppy - no fastcast, Mixed Up Mother Goose - no fastcast
+//
+// SCI1 Middle:
+// LSL5 demo - no fastfast, Conquest of the Longbow demo - no fastcast, LSL1 - no fastcast,
+// Astro Chicken II - no fastcast
+//
+// SCI1 Late:
+// Castle of Dr. Brain demo - has fastcast, Castle of Dr. Brain - has fastcast,
+// Conquests of the Longbow - has fastcast, Space Quest 1 EGA - has fastcast,
+// King's Quest 5 multilingual - *NO* fastcast, Police Quest 3 demo - *NO* fastcast,
+// LSL5 multilingual - has fastcast, Police Quest 3 - has fastcast,
+// EcoQuest 1 - has fastcast, Mixed Up Fairy Tales demo - has fastcast,
+// Space Quest 4 multilingual - *NO* fastcast
+//
+// SCI1.1
+// Quest for Glory 3 demo - has fastcast, Police Quest 1 - hast fastcast, Quest for Glory 1 - has fastcast
+// Laura Bow 2 Floppy - has fastcast, Mixed Up Mother Goose - has fastcast, Quest for Glory 3 - has fastcast
+// Island of Dr. Brain - has fastcast, King's Quest 6 - has fastcast, Space Quest 5 - has fastcast
+// Hoyle 4 - has fastcast, Laura Bow 2 CD - has fastcast, Freddy Pharkas CD - has fastcast
+bool GfxAnimate::detectFastCast() {
+ SegManager *segMan = _s->_segMan;
+ const reg_t gameVMObject = g_sci->getGameObject();
+ reg_t gameSuperVMObject = segMan->getObject(gameVMObject)->getSuperClassSelector();
+ uint32 magicDWord = 0; // platform-specific BE/LE for performance
+ int magicDWordOffset = 0;
+
+ if (gameSuperVMObject.isNull()) {
+ gameSuperVMObject = gameVMObject; // Just in case. According to sci.cpp this may happen in KQ5CD, when loading saved games before r54510
+ }
+
+ Script *objectScript = segMan->getScript(gameSuperVMObject.getSegment());
+ byte *scriptData = const_cast<byte *>(objectScript->getBuf(0));
+ uint32 scriptSize = objectScript->getBufSize();
+
+ _scriptPatcher->calculateMagicDWordAndVerify("fast cast detection", fastCastSignature, true, magicDWord, magicDWordOffset);
+
+ // Signature is found for multilingual King's Quest 5 too, but it looks as if the fast cast global is never set
+ // within that game. Which means even though we detect it as having the capability, it's never actually used.
+ // The original multilingual KQ5 interpreter did have this feature disabled.
+ // Sierra probably used latest system scripts and that's why we detect it.
+ if (_scriptPatcher->findSignature(magicDWord, magicDWordOffset, fastCastSignature, "fast cast detection", scriptData, scriptSize) >= 0) {
+ // Signature found, game seems to use fast cast for kAnimate
+ return true;
}
+ return false;
}
void GfxAnimate::disposeLastCast() {
@@ -80,12 +142,14 @@ bool GfxAnimate::invoke(List *list, int argc, reg_t *argv) {
while (curNode) {
curObject = curNode->value;
- if (!_ignoreFastCast) {
+ if (_fastCastEnabled) {
// Check if the game has a fastCast object set
// if we don't abort kAnimate processing, at least in kq5 there will be animation cels drawn into speech boxes.
if (!_s->variables[VAR_GLOBAL][84].isNull()) {
- if (!strcmp(_s->_segMan->getObjectName(_s->variables[VAR_GLOBAL][84]), "fastCast"))
- return false;
+ // This normally points to an object called "fastCast",
+ // but for example in Eco Quest 1 it may also point to an object called "EventHandler" (see bug #5170)
+ // Original SCI only checked, if this global was not 0.
+ return false;
}
}
diff --git a/engines/sci/graphics/animate.h b/engines/sci/graphics/animate.h
index 6c1822c903..ac7078093c 100644
--- a/engines/sci/graphics/animate.h
+++ b/engines/sci/graphics/animate.h
@@ -87,9 +87,13 @@ class GfxView;
*/
class GfxAnimate {
public:
- GfxAnimate(EngineState *state, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions);
+ GfxAnimate(EngineState *state, ScriptPatcher *scriptPatcher, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions);
virtual ~GfxAnimate();
+ bool isFastCastEnabled() {
+ return _fastCastEnabled;
+ }
+
void disposeLastCast();
bool invoke(List *list, int argc, reg_t *argv);
void makeSortedList(List *list);
@@ -110,6 +114,7 @@ public:
private:
void init();
+ bool detectFastCast();
void addToPicSetPicNotValid();
void animateShowPic();
@@ -119,6 +124,7 @@ private:
void setNsRect(GfxView *view, AnimateList::iterator it);
EngineState *_s;
+ ScriptPatcher *_scriptPatcher;
GfxCache *_cache;
GfxPorts *_ports;
GfxPaint16 *_paint16;
@@ -130,7 +136,7 @@ private:
AnimateList _list;
AnimateArray _lastCastData;
- bool _ignoreFastCast;
+ bool _fastCastEnabled;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/cache.cpp b/engines/sci/graphics/cache.cpp
index 59af8334eb..fb1f557ad6 100644
--- a/engines/sci/graphics/cache.cpp
+++ b/engines/sci/graphics/cache.cpp
@@ -102,8 +102,4 @@ int16 GfxCache::kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo) {
return getView(viewId)->getCelCount(loopNo);
}
-byte GfxCache::kernelViewGetColorAtCoordinate(GuiResourceId viewId, int16 loopNo, int16 celNo, int16 x, int16 y) {
- return getView(viewId)->getColorAtCoordinate(loopNo, celNo, x, y);
-}
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/cache.h b/engines/sci/graphics/cache.h
index 33fa4fe399..61952718a9 100644
--- a/engines/sci/graphics/cache.h
+++ b/engines/sci/graphics/cache.h
@@ -49,8 +49,6 @@ public:
int16 kernelViewGetLoopCount(GuiResourceId viewId);
int16 kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo);
- byte kernelViewGetColorAtCoordinate(GuiResourceId viewId, int16 loopNo, int16 celNo, int16 x, int16 y);
-
private:
void purgeFontCache();
void purgeViewCache();
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
new file mode 100644
index 0000000000..48de054a31
--- /dev/null
+++ b/engines/sci/graphics/celobj32.cpp
@@ -0,0 +1,1151 @@
+/* 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.
+ *
+ */
+
+#include "sci/resource.h"
+#include "sci/engine/seg_manager.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
+#include "sci/graphics/text32.h"
+
+namespace Sci {
+#pragma mark CelScaler
+
+CelScaler *CelObj::_scaler = nullptr;
+
+void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ for (int i = 0; i < ARRAYSIZE(_scaleTables); ++i) {
+ if (_scaleTables[i].scaleX == scaleX && _scaleTables[i].scaleY == scaleY) {
+ _activeIndex = i;
+ return;
+ }
+ }
+
+ int i = 1 - _activeIndex;
+ _activeIndex = i;
+ CelScalerTable &table = _scaleTables[i];
+
+ if (table.scaleX != scaleX) {
+ assert(screenWidth <= ARRAYSIZE(table.valuesX));
+ buildLookupTable(table.valuesX, scaleX, screenWidth);
+ table.scaleX = scaleX;
+ }
+
+ if (table.scaleY != scaleY) {
+ assert(screenHeight <= ARRAYSIZE(table.valuesY));
+ buildLookupTable(table.valuesY, scaleY, screenHeight);
+ table.scaleY = scaleY;
+ }
+}
+
+void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) {
+ int value = 0;
+ int remainder = 0;
+ int num = ratio.getNumerator();
+ for (int i = 0; i < size; ++i) {
+ *table++ = value;
+ remainder += ratio.getDenominator();
+ if (remainder >= num) {
+ value += remainder / num;
+ remainder %= num;
+ }
+ }
+}
+
+const CelScalerTable *CelScaler::getScalerTable(const Ratio &scaleX, const Ratio &scaleY) {
+ activateScaleTables(scaleX, scaleY);
+ return &_scaleTables[_activeIndex];
+}
+
+#pragma mark -
+#pragma mark CelObj
+bool CelObj::_drawBlackLines = false;
+
+void CelObj::init() {
+ CelObj::deinit();
+ _drawBlackLines = false;
+ _nextCacheId = 1;
+ _scaler = new CelScaler();
+ _cache = new CelCache;
+ _cache->resize(100);
+}
+
+void CelObj::deinit() {
+ delete _scaler;
+ _scaler = nullptr;
+ if (_cache != nullptr) {
+ for (CelCache::iterator it = _cache->begin(); it != _cache->end(); ++it) {
+ delete it->celObj;
+ }
+ }
+ delete _cache;
+ _cache = nullptr;
+}
+
+#pragma mark -
+#pragma mark CelObj - Scalers
+
+template<bool FLIP, typename READER>
+struct SCALER_NoScale {
+#ifndef NDEBUG
+ const byte *_rowEdge;
+#endif
+ const byte *_row;
+ READER _reader;
+ const int16 _lastIndex;
+ const int16 _sourceX;
+ const int16 _sourceY;
+
+ SCALER_NoScale(const CelObj &celObj, const int16 maxWidth, const Common::Point &scaledPosition) :
+ _row(nullptr),
+ _reader(celObj, FLIP ? celObj._width : maxWidth),
+ _lastIndex(celObj._width - 1),
+ _sourceX(scaledPosition.x),
+ _sourceY(scaledPosition.y) {}
+
+ inline void setTarget(const int16 x, const int16 y) {
+ _row = _reader.getRow(y - _sourceY);
+
+ if (FLIP) {
+#ifndef NDEBUG
+ _rowEdge = _row - 1;
+#endif
+ _row += _lastIndex - (x - _sourceX);
+ assert(_row > _rowEdge);
+ } else {
+#ifndef NDEBUG
+ _rowEdge = _row + _lastIndex + 1;
+#endif
+ _row += x - _sourceX;
+ assert(_row < _rowEdge);
+ }
+ }
+
+ inline byte read() {
+ assert(_row != _rowEdge);
+
+ if (FLIP) {
+ return *_row--;
+ } else {
+ return *_row++;
+ }
+ }
+};
+
+template<bool FLIP, typename READER>
+struct SCALER_Scale {
+#ifndef NDEBUG
+ int16 _maxX;
+#endif
+ const byte *_row;
+ READER _reader;
+ int16 _x;
+ static int16 _valuesX[1024];
+ static int16 _valuesY[1024];
+
+ SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) :
+ _row(nullptr),
+#ifndef NDEBUG
+ _maxX(targetRect.right - 1),
+#endif
+ // The maximum width of the scaled object may not be as
+ // wide as the source data it requires if downscaling,
+ // so just always make the reader decompress an entire
+ // line of source data when scaling
+ _reader(celObj, celObj._width) {
+ // In order for scaling ratios to apply equally across objects that
+ // start at different positions on the screen (like the cels of a
+ // picture), the pixels that are read from the source bitmap must all
+ // use the same pattern of division. In other words, cels must follow
+ // a global scaling pattern as if they were always drawn starting at an
+ // even multiple of the scaling ratio, even if they are not.
+ //
+ // To get the correct source pixel when reading out through the scaler,
+ // the engine creates a lookup table for each axis that translates
+ // directly from target positions to the indexes of source pixels using
+ // the global cadence for the given scaling ratio.
+ //
+ // Note, however, that not all games use the global scaling mode.
+ //
+ // SQ6 definitely uses the global scaling mode (an easy visual
+ // comparison is to leave Implants N' Stuff and then look at Roger);
+ // Torin definitely does not (scaling subtitle backgrounds will cause it
+ // to attempt a read out of bounds and crash). They are both SCI
+ // "2.1mid" games, so currently the common denominator looks to be that
+ // games which use global scaling are the ones that use low-resolution
+ // script coordinates too.
+
+ const CelScalerTable *table = CelObj::_scaler->getScalerTable(scaleX, scaleY);
+
+ if (g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth == kLowResX) {
+ const int16 unscaledX = (scaledPosition.x / scaleX).toInt();
+ if (FLIP) {
+ int lastIndex = celObj._width - 1;
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = lastIndex - (table->valuesX[x] - unscaledX);
+ }
+ } else {
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = table->valuesX[x] - unscaledX;
+ }
+ }
+
+ const int16 unscaledY = (scaledPosition.y / scaleY).toInt();
+ for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
+ _valuesY[y] = table->valuesY[y] - unscaledY;
+ }
+ } else {
+ if (FLIP) {
+ int lastIndex = celObj._width - 1;
+ for (int16 x = 0; x < targetRect.width(); ++x) {
+ _valuesX[targetRect.left + x] = lastIndex - table->valuesX[x];
+ }
+ } else {
+ for (int16 x = 0; x < targetRect.width(); ++x) {
+ _valuesX[targetRect.left + x] = table->valuesX[x];
+ }
+ }
+
+ for (int16 y = 0; y < targetRect.height(); ++y) {
+ _valuesY[targetRect.top + y] = table->valuesY[y];
+ }
+ }
+ }
+
+ inline void setTarget(const int16 x, const int16 y) {
+ _row = _reader.getRow(_valuesY[y]);
+ _x = x;
+ assert(_x >= 0 && _x <= _maxX);
+ }
+
+ inline byte read() {
+ assert(_x >= 0 && _x <= _maxX);
+ return _row[_valuesX[_x++]];
+ }
+};
+
+template<bool FLIP, typename READER>
+int16 SCALER_Scale<FLIP, READER>::_valuesX[1024];
+template<bool FLIP, typename READER>
+int16 SCALER_Scale<FLIP, READER>::_valuesY[1024];
+
+#pragma mark -
+#pragma mark CelObj - Resource readers
+
+struct READER_Uncompressed {
+private:
+#ifndef NDEBUG
+ const int16 _sourceHeight;
+#endif
+ byte *_pixels;
+ const int16 _sourceWidth;
+
+public:
+ READER_Uncompressed(const CelObj &celObj, const int16) :
+#ifndef NDEBUG
+ _sourceHeight(celObj._height),
+#endif
+ _sourceWidth(celObj._width) {
+ byte *resource = celObj.getResPointer();
+ _pixels = resource + READ_SCI11ENDIAN_UINT32(resource + celObj._celHeaderOffset + 24);
+ }
+
+ inline const byte *getRow(const int16 y) const {
+ assert(y >= 0 && y < _sourceHeight);
+ return _pixels + y * _sourceWidth;
+ }
+};
+
+struct READER_Compressed {
+private:
+ byte *_resource;
+ byte _buffer[1024];
+ uint32 _controlOffset;
+ uint32 _dataOffset;
+ uint32 _uncompressedDataOffset;
+ int16 _y;
+ const int16 _sourceHeight;
+ const uint8 _transparentColor;
+ const int16 _maxWidth;
+
+public:
+ READER_Compressed(const CelObj &celObj, const int16 maxWidth) :
+ _resource(celObj.getResPointer()),
+ _y(-1),
+ _sourceHeight(celObj._height),
+ _transparentColor(celObj._transparentColor),
+ _maxWidth(maxWidth) {
+ assert(maxWidth <= celObj._width);
+
+ byte *celHeader = _resource + celObj._celHeaderOffset;
+ _dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
+ _uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
+ _controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
+ }
+
+ inline const byte *getRow(const int16 y) {
+ assert(y >= 0 && y < _sourceHeight);
+ if (y != _y) {
+ // compressed data segment for row
+ byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4);
+
+ // uncompressed data segment for row
+ byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4);
+
+ uint8 length;
+ for (int16 i = 0; i < _maxWidth; i += length) {
+ byte controlByte = *row++;
+ length = controlByte;
+
+ // Run-length encoded
+ if (controlByte & 0x80) {
+ length &= 0x3F;
+ assert(i + length < (int)sizeof(_buffer));
+
+ // Fill with skip color
+ if (controlByte & 0x40) {
+ memset(_buffer + i, _transparentColor, length);
+ // Next value is fill color
+ } else {
+ memset(_buffer + i, *literal, length);
+ ++literal;
+ }
+ // Uncompressed
+ } else {
+ assert(i + length < (int)sizeof(_buffer));
+ memcpy(_buffer + i, literal, length);
+ literal += length;
+ }
+ }
+ _y = y;
+ }
+
+ return _buffer;
+ }
+};
+
+#pragma mark -
+#pragma mark CelObj - Remappers
+
+/**
+ * Pixel mapper for a CelObj with transparent pixels and no
+ * remapping data.
+ */
+struct MAPPER_NoMD {
+ inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
+ if (pixel != skipColor) {
+ *target = pixel;
+ }
+ }
+};
+
+/**
+ * Pixel mapper for a CelObj with no transparent pixels and
+ * no remapping data.
+ */
+struct MAPPER_NoMDNoSkip {
+ inline void draw(byte *target, const byte pixel, const uint8) const {
+ *target = pixel;
+ }
+};
+
+/**
+ * Pixel mapper for a CelObj with transparent pixels,
+ * remapping data, and remapping enabled.
+ */
+struct MAPPER_Map {
+ inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
+ if (pixel != skipColor) {
+ // NOTE: For some reason, SSCI never checks if the source
+ // pixel is *above* the range of remaps.
+ if (pixel < g_sci->_gfxRemap32->getStartColor()) {
+ *target = pixel;
+ } else if (g_sci->_gfxRemap32->remapEnabled(pixel)) {
+ *target = g_sci->_gfxRemap32->remapColor(pixel, *target);
+ }
+ }
+ }
+};
+
+/**
+ * Pixel mapper for a CelObj with transparent pixels,
+ * remapping data, and remapping disabled.
+ */
+struct MAPPER_NoMap {
+ inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
+ // NOTE: For some reason, SSCI never checks if the source
+ // pixel is *above* the range of remaps.
+ if (pixel != skipColor && pixel < g_sci->_gfxRemap32->getStartColor()) {
+ *target = pixel;
+ }
+ }
+};
+
+void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const {
+ const Common::Point &scaledPosition = screenItem._scaledPosition;
+ const Ratio &scaleX = screenItem._ratioX;
+ const Ratio &scaleY = screenItem._ratioY;
+ _drawBlackLines = screenItem._drawBlackLines;
+
+ if (_remap) {
+ // NOTE: In the original code this check was `g_Remap_numActiveRemaps && _remap`,
+ // but since we are already in a `_remap` branch, there is no reason to check it
+ // again
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipMap(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipMap(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlip(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlip(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlip(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlip(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncomp(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDraw(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ }
+ } else {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_transparent) {
+ if (_drawMirrored) {
+ drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawUncompHzFlipNoMDNoSkip(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipNoMDNoSkip(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ }
+
+ _drawBlackLines = false;
+}
+
+void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) {
+ _drawMirrored = mirrorX;
+ draw(target, screenItem, targetRect);
+}
+
+void CelObj::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
+ _drawMirrored = mirrorX;
+ Ratio square;
+ drawTo(target, targetRect, scaledPosition, square, square);
+}
+
+void CelObj::drawTo(Buffer &target, Common::Rect const &targetRect, Common::Point const &scaledPosition, Ratio const &scaleX, Ratio const &scaleY) const {
+ if (_remap) {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipMap(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipMap(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ }
+}
+
+uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const {
+ if (mirrorX) {
+ x = _width - x - 1;
+ }
+
+ if (_compressionType == kCelCompressionNone) {
+ READER_Uncompressed reader(*this, x + 1);
+ return reader.getRow(y)[x];
+ } else {
+ READER_Compressed reader(*this, x + 1);
+ return reader.getRow(y)[x];
+ }
+}
+
+void CelObj::submitPalette() const {
+ if (_hunkPaletteOffset) {
+ HunkPalette palette(getResPointer() + _hunkPaletteOffset);
+ g_sci->_gfxPalette32->submit(palette);
+ }
+}
+
+#pragma mark -
+#pragma mark CelObj - Caching
+
+int CelObj::_nextCacheId = 1;
+CelCache *CelObj::_cache = nullptr;
+
+int CelObj::searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const {
+ *nextInsertIndex = -1;
+ int oldestId = _nextCacheId + 1;
+ int oldestIndex = 0;
+
+ for (int i = 0, len = _cache->size(); i < len; ++i) {
+ CelCacheEntry &entry = (*_cache)[i];
+
+ if (entry.celObj == nullptr) {
+ if (*nextInsertIndex == -1) {
+ *nextInsertIndex = i;
+ }
+ } else if (entry.celObj->_info == celInfo) {
+ entry.id = ++_nextCacheId;
+ return i;
+ } else if (oldestId > entry.id) {
+ oldestId = entry.id;
+ oldestIndex = i;
+ }
+ }
+
+ if (*nextInsertIndex == -1) {
+ *nextInsertIndex = oldestIndex;
+ }
+
+ return -1;
+}
+
+void CelObj::putCopyInCache(const int cacheIndex) const {
+ if (cacheIndex == -1) {
+ error("Invalid cache index");
+ }
+
+ CelCacheEntry &entry = (*_cache)[cacheIndex];
+
+ if (entry.celObj != nullptr) {
+ delete entry.celObj;
+ }
+
+ entry.celObj = duplicate();
+ entry.id = ++_nextCacheId;
+}
+
+#pragma mark -
+#pragma mark CelObj - Drawing
+
+template<typename MAPPER, typename SCALER, bool DRAW_BLACK_LINES>
+struct RENDERER {
+ MAPPER &_mapper;
+ SCALER &_scaler;
+ const uint8 _skipColor;
+
+ RENDERER(MAPPER &mapper, SCALER &scaler, const uint8 skipColor) :
+ _mapper(mapper),
+ _scaler(scaler),
+ _skipColor(skipColor) {}
+
+ inline void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ byte *targetPixel = (byte *)target.getPixels() + target.screenWidth * targetRect.top + targetRect.left;
+
+ const int16 skipStride = target.screenWidth - targetRect.width();
+ const int16 targetWidth = targetRect.width();
+ const int16 targetHeight = targetRect.height();
+ for (int16 y = 0; y < targetHeight; ++y) {
+ if (DRAW_BLACK_LINES && (y % 2) == 0) {
+ memset(targetPixel, 0, targetWidth);
+ targetPixel += targetWidth + skipStride;
+ continue;
+ }
+
+ _scaler.setTarget(targetRect.left, targetRect.top + y);
+
+ for (int16 x = 0; x < targetWidth; ++x) {
+ _mapper.draw(targetPixel++, _scaler.read(), _skipColor);
+ }
+
+ targetPixel += skipStride;
+ }
+ }
+};
+
+template<typename MAPPER, typename SCALER>
+void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+
+ MAPPER mapper;
+ SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width(), scaledPosition);
+ RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _transparentColor);
+ renderer.draw(target, targetRect, scaledPosition);
+}
+
+template<typename MAPPER, typename SCALER>
+void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const {
+
+ MAPPER mapper;
+ SCALER scaler(*this, targetRect, scaledPosition, scaleX, scaleY);
+ if (_drawBlackLines) {
+ RENDERER<MAPPER, SCALER, true> renderer(mapper, scaler, _transparentColor);
+ renderer.draw(target, targetRect, scaledPosition);
+ } else {
+ RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _transparentColor);
+ renderer.draw(target, targetRect, scaledPosition);
+ }
+}
+
+void dummyFill(Buffer &target, const Common::Rect &targetRect) {
+ target.fillRect(targetRect, 250);
+}
+
+void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMap, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMap, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMap, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMap, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ if (_drawMirrored) {
+ render<MAPPER_NoMap, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_NoMap, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
+}
+
+void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ if (_drawMirrored) {
+ render<MAPPER_NoMap, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_NoMap, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
+}
+
+void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_Map, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_Map, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_Map, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_Map, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ if (_drawMirrored) {
+ render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_Map, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
+}
+
+void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ if (_drawMirrored) {
+ render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_Map, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
+}
+
+void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMD, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMD, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMD, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMDNoSkip, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMD, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMDNoSkip, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ if (_drawMirrored)
+ render<MAPPER_NoMD, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ else
+ render<MAPPER_NoMD, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+}
+
+void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ if (_drawMirrored) {
+ render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_NoMD, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
+}
+
+#pragma mark -
+#pragma mark CelObjView
+
+CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
+ _info.type = kCelTypeView;
+ _info.resourceId = viewId;
+ _info.loopNo = loopNo;
+ _info.celNo = celNo;
+ _mirrorX = false;
+ _compressionType = kCelCompressionInvalid;
+ _transparent = true;
+
+ int cacheInsertIndex;
+ int cacheIndex = searchCache(_info, &cacheInsertIndex);
+ if (cacheIndex != -1) {
+ CelCacheEntry &entry = (*_cache)[cacheIndex];
+ const CelObjView *const cachedCelObj = dynamic_cast<CelObjView *>(entry.celObj);
+ if (cachedCelObj == nullptr) {
+ error("Expected a CelObjView in cache slot %d", cacheIndex);
+ }
+ *this = *cachedCelObj;
+ entry.id = ++_nextCacheId;
+ return;
+ }
+
+ // TODO: The next code should be moved to a common file that
+ // generates view resource metadata for both SCI16 and SCI32
+ // implementations
+
+ Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
+
+ // NOTE: SCI2.1/SQ6 just silently returns here.
+ if (!resource) {
+ warning("View resource %d not loaded", viewId);
+ return;
+ }
+
+ byte *data = resource->data;
+
+ _scaledWidth = READ_SCI11ENDIAN_UINT16(data + 14);
+ _scaledHeight = READ_SCI11ENDIAN_UINT16(data + 16);
+
+ if (_scaledWidth == 0 || _scaledHeight == 0) {
+ byte sizeFlag = data[5];
+ if (sizeFlag == 0) {
+ _scaledWidth = kLowResX;
+ _scaledHeight = kLowResY;
+ } else if (sizeFlag == 1) {
+ _scaledWidth = 640;
+ _scaledHeight = 480;
+ } else if (sizeFlag == 2) {
+ _scaledWidth = 640;
+ _scaledHeight = 400;
+ }
+ }
+
+ uint16 loopCount = data[2];
+ if (_info.loopNo >= loopCount) {
+ _info.loopNo = loopCount - 1;
+ }
+
+ // NOTE: This is the actual check, in the actual location,
+ // from SCI engine.
+ if (loopNo < 0) {
+ error("Loop is less than 0!");
+ }
+
+ const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data);
+ const uint8 loopHeaderSize = data[12];
+ const uint8 viewHeaderFieldSize = 2;
+
+ byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo);
+
+ if ((int8)loopHeader[0] != -1) {
+ if (loopHeader[1] == 1) {
+ _mirrorX = true;
+ }
+
+ loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * (int8)loopHeader[0]);
+ }
+
+ uint8 celCount = loopHeader[2];
+ if (_info.celNo >= celCount) {
+ _info.celNo = celCount - 1;
+ }
+
+ _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 8);
+ _celHeaderOffset = READ_SCI11ENDIAN_UINT32(loopHeader + 12) + (data[13] * _info.celNo);
+
+ byte *celHeader = data + _celHeaderOffset;
+
+ _width = READ_SCI11ENDIAN_UINT16(celHeader);
+ _height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
+ _displace.x = _width / 2 - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
+ _displace.y = _height - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6) - 1;
+ _transparentColor = celHeader[8];
+ _compressionType = (CelCompressionType)celHeader[9];
+
+ if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
+ error("Compression type not supported - V: %d L: %d C: %d", _info.resourceId, _info.loopNo, _info.celNo);
+ }
+
+ if (celHeader[10] & 128) {
+ // NOTE: This is correct according to SCI2.1/SQ6/DOS;
+ // the engine re-reads the byte value as a word value
+ uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
+ _transparent = flags & 1 ? true : false;
+ _remap = flags & 2 ? true : false;
+ } else if (_compressionType == kCelCompressionNone) {
+ _remap = analyzeUncompressedForRemap();
+ } else {
+ _remap = analyzeForRemap();
+ }
+
+ putCopyInCache(cacheInsertIndex);
+}
+
+bool CelObjView::analyzeUncompressedForRemap() const {
+ byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+ for (int i = 0; i < _width * _height; ++i) {
+ const byte pixel = pixels[i];
+ if (
+ pixel >= g_sci->_gfxRemap32->getStartColor() &&
+ pixel <= g_sci->_gfxRemap32->getEndColor() &&
+ pixel != _transparentColor
+ ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CelObjView::analyzeForRemap() const {
+ READER_Compressed reader(*this, _width);
+ for (int y = 0; y < _height; y++) {
+ const byte *curRow = reader.getRow(y);
+ for (int x = 0; x < _width; x++) {
+ const byte pixel = curRow[x];
+ if (
+ pixel >= g_sci->_gfxRemap32->getStartColor() &&
+ pixel <= g_sci->_gfxRemap32->getEndColor() &&
+ pixel != _transparentColor
+ ) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void CelObjView::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY) {
+ _drawMirrored = mirrorX;
+ drawTo(target, targetRect, scaledPosition, scaleX, scaleY);
+}
+
+CelObjView *CelObjView::duplicate() const {
+ return new CelObjView(*this);
+}
+
+byte *CelObjView::getResPointer() const {
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false);
+ if (resource == nullptr) {
+ error("Failed to load view %d from resource manager", _info.resourceId);
+ }
+ return resource->data;
+}
+
+#pragma mark -
+#pragma mark CelObjPic
+
+CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
+ _info.type = kCelTypePic;
+ _info.resourceId = picId;
+ _info.loopNo = 0;
+ _info.celNo = celNo;
+ _mirrorX = false;
+ _compressionType = kCelCompressionInvalid;
+ _transparent = true;
+ _remap = false;
+
+ int cacheInsertIndex;
+ int cacheIndex = searchCache(_info, &cacheInsertIndex);
+ if (cacheIndex != -1) {
+ CelCacheEntry &entry = (*_cache)[cacheIndex];
+ const CelObjPic *const cachedCelObj = dynamic_cast<CelObjPic *>(entry.celObj);
+ if (cachedCelObj == nullptr) {
+ error("Expected a CelObjPic in cache slot %d", cacheIndex);
+ }
+ *this = *cachedCelObj;
+ entry.id = ++_nextCacheId;
+ return;
+ }
+
+ Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false);
+
+ // NOTE: SCI2.1/SQ6 just silently returns here.
+ if (!resource) {
+ warning("Pic resource %d not loaded", picId);
+ return;
+ }
+
+ byte *data = resource->data;
+
+ _celCount = data[2];
+
+ if (_info.celNo >= _celCount) {
+ error("Cel number %d greater than cel count %d", _info.celNo, _celCount);
+ }
+
+ _celHeaderOffset = READ_SCI11ENDIAN_UINT16(data) + (READ_SCI11ENDIAN_UINT16(data + 4) * _info.celNo);
+ _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 6);
+
+ byte *celHeader = data + _celHeaderOffset;
+
+ _width = READ_SCI11ENDIAN_UINT16(celHeader);
+ _height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
+ _displace.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
+ _displace.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6);
+ _transparentColor = celHeader[8];
+ _compressionType = (CelCompressionType)celHeader[9];
+ _priority = READ_SCI11ENDIAN_UINT16(celHeader + 36);
+ _relativePosition.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 38);
+ _relativePosition.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 40);
+
+ uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10);
+ uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12);
+
+ if (sizeFlag2) {
+ _scaledWidth = sizeFlag1;
+ _scaledHeight = sizeFlag2;
+ } else if (sizeFlag1 == 0) {
+ _scaledWidth = kLowResX;
+ _scaledHeight = kLowResY;
+ } else if (sizeFlag1 == 1) {
+ _scaledWidth = 640;
+ _scaledHeight = 480;
+ } else if (sizeFlag1 == 2) {
+ _scaledWidth = 640;
+ _scaledHeight = 400;
+ }
+
+ if (celHeader[10] & 128) {
+ // NOTE: This is correct according to SCI2.1/SQ6/DOS;
+ // the engine re-reads the byte value as a word value
+ uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
+ _transparent = flags & 1 ? true : false;
+ _remap = flags & 2 ? true : false;
+ } else {
+ _transparent = _compressionType != kCelCompressionNone ? true : analyzeUncompressedForSkip();
+
+ if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
+ error("Compression type not supported - P: %d C: %d", picId, celNo);
+ }
+ }
+
+ putCopyInCache(cacheInsertIndex);
+}
+
+bool CelObjPic::analyzeUncompressedForSkip() const {
+ byte *resource = getResPointer();
+ byte *pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24);
+ for (int i = 0; i < _width * _height; ++i) {
+ uint8 pixel = pixels[i];
+ if (pixel == _transparentColor) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
+ Ratio square;
+ _drawMirrored = mirrorX;
+ drawTo(target, targetRect, scaledPosition, square, square);
+}
+
+CelObjPic *CelObjPic::duplicate() const {
+ return new CelObjPic(*this);
+}
+
+byte *CelObjPic::getResPointer() const {
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false);
+ if (resource == nullptr) {
+ error("Failed to load pic %d from resource manager", _info.resourceId);
+ }
+ return resource->data;
+}
+
+#pragma mark -
+#pragma mark CelObjMem
+
+CelObjMem::CelObjMem(const reg_t bitmapObject) {
+ _info.type = kCelTypeMem;
+ _info.bitmap = bitmapObject;
+ _mirrorX = false;
+ _compressionType = kCelCompressionNone;
+ _celHeaderOffset = 0;
+ _transparent = true;
+
+ BitmapResource bitmap(bitmapObject);
+ _width = bitmap.getWidth();
+ _height = bitmap.getHeight();
+ _displace = bitmap.getDisplace();
+ _transparentColor = bitmap.getSkipColor();
+ _scaledWidth = bitmap.getScaledWidth();
+ _scaledHeight = bitmap.getScaledHeight();
+ _hunkPaletteOffset = bitmap.getHunkPaletteOffset();
+ _remap = bitmap.getRemap();
+}
+
+CelObjMem *CelObjMem::duplicate() const {
+ return new CelObjMem(*this);
+}
+
+byte *CelObjMem::getResPointer() const {
+ return g_sci->getEngineState()->_segMan->getHunkPointer(_info.bitmap);
+}
+
+#pragma mark -
+#pragma mark CelObjColor
+
+CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) {
+ _info.type = kCelTypeColor;
+ _info.color = color;
+ _displace.x = 0;
+ _displace.y = 0;
+ _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ _hunkPaletteOffset = 0;
+ _mirrorX = false;
+ _remap = false;
+ _width = width;
+ _height = height;
+}
+
+void CelObjColor::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX) {
+ // TODO: The original engine sets this flag but why? One cannot
+ // draw a solid color mirrored.
+ _drawMirrored = mirrorX;
+ draw(target, targetRect);
+}
+void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX) {
+ error("Unsupported method");
+}
+void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect) const {
+ target.fillRect(targetRect, _info.color);
+}
+
+CelObjColor *CelObjColor::duplicate() const {
+ return new CelObjColor(*this);
+}
+
+byte *CelObjColor::getResPointer() const {
+ error("Unsupported method");
+}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h
new file mode 100644
index 0000000000..eb6ce3a3c9
--- /dev/null
+++ b/engines/sci/graphics/celobj32.h
@@ -0,0 +1,607 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_CELOBJ32_H
+#define SCI_GRAPHICS_CELOBJ32_H
+
+#include "common/rational.h"
+#include "common/rect.h"
+#include "sci/resource.h"
+#include "sci/engine/vm_types.h"
+
+namespace Sci {
+typedef Common::Rational Ratio;
+
+// SCI32 has four different coordinate systems:
+// 1. low resolution, 2. game/script resolution,
+// 3. text/bitmap resolution, 4. screen resolution
+//
+// In CelObj, these values are used when there is
+// no baked in resolution of cels.
+//
+// In ScreenItem, it is used when deciding which
+// path to take to calculate dimensions.
+enum {
+ kLowResX = 320,
+ kLowResY = 200
+};
+
+enum CelType {
+ kCelTypeView = 0,
+ kCelTypePic = 1,
+ kCelTypeMem = 2,
+ kCelTypeColor = 3
+};
+
+enum CelCompressionType {
+ kCelCompressionNone = 0,
+ kCelCompressionRLE = 138,
+ kCelCompressionInvalid = 1000
+};
+
+/**
+ * A CelInfo32 object describes the basic properties of a
+ * cel object.
+ */
+struct CelInfo32 {
+ /**
+ * The type of the cel object.
+ */
+ CelType type;
+
+ /**
+ * For cel objects that draw from resources, the ID of
+ * the resource to load.
+ */
+ GuiResourceId resourceId;
+
+ /**
+ * For CelObjView, the loop number to draw from the
+ * view resource.
+ */
+ int16 loopNo;
+
+ /**
+ * For CelObjView and CelObjPic, the cel number to draw
+ * from the view or pic resource.
+ */
+ int16 celNo;
+
+ /**
+ * For CelObjMem, a segment register pointing to a heap
+ * resource containing headered bitmap data.
+ */
+ reg_t bitmap;
+
+ /**
+ * For CelObjColor, the fill color.
+ */
+ uint8 color;
+
+ // NOTE: In at least SCI2.1/SQ6, color is left
+ // uninitialised.
+ CelInfo32() :
+ type(kCelTypeMem),
+ resourceId(0),
+ loopNo(0),
+ celNo(0),
+ bitmap(NULL_REG) {}
+
+ // NOTE: This is the equivalence criteria used by
+ // CelObj::searchCache in at least SCI2.1/SQ6. Notably,
+ // it does not check the color field.
+ inline bool operator==(const CelInfo32 &other) {
+ return (
+ type == other.type &&
+ resourceId == other.resourceId &&
+ loopNo == other.loopNo &&
+ celNo == other.celNo &&
+ bitmap == other.bitmap
+ );
+ }
+
+ inline bool operator!=(const CelInfo32 &other) {
+ return !(*this == other);
+ }
+};
+
+class CelObj;
+struct CelCacheEntry {
+ /**
+ * A monotonically increasing cache ID used to identify
+ * the least recently used item in the cache for
+ * replacement.
+ */
+ int id;
+ CelObj *celObj;
+ CelCacheEntry() : id(0), celObj(nullptr) {}
+};
+
+typedef Common::Array<CelCacheEntry> CelCache;
+
+#pragma mark -
+#pragma mark CelScaler
+
+struct CelScalerTable {
+ /**
+ * A lookup table of indexes that should be used to find
+ * the correct column to read from the source bitmap
+ * when drawing a scaled version of the source bitmap.
+ */
+ int valuesX[1024];
+
+ /**
+ * The ratio used to generate the x-values.
+ */
+ Ratio scaleX;
+
+ /**
+ * A lookup table of indexes that should be used to find
+ * the correct row to read from a source bitmap when
+ * drawing a scaled version of the source bitmap.
+ */
+ int valuesY[1024];
+
+ /**
+ * The ratio used to generate the y-values.
+ */
+ Ratio scaleY;
+};
+
+class CelScaler {
+ /**
+ * Cached scale tables.
+ */
+ CelScalerTable _scaleTables[2];
+
+ /**
+ * The index of the most recently used scale table.
+ */
+ int _activeIndex;
+
+ /**
+ * Activates a scale table for the given X and Y ratios.
+ * If there is no table that matches the given ratios,
+ * the least most recently used table will be replaced
+ * and activated.
+ */
+ void activateScaleTables(const Ratio &scaleX, const Ratio &scaleY);
+
+ /**
+ * Builds a pixel lookup table in `table` for the given
+ * ratio. The table will be filled up to the specified
+ * size, which should be large enough to draw across the
+ * entire target buffer.
+ */
+ void buildLookupTable(int *table, const Ratio &ratio, const int size);
+
+public:
+ CelScaler() :
+ _scaleTables(),
+ _activeIndex(0) {
+ CelScalerTable &table = _scaleTables[0];
+ table.scaleX = Ratio();
+ table.scaleY = Ratio();
+ for (int i = 0; i < ARRAYSIZE(table.valuesX); ++i) {
+ table.valuesX[i] = i;
+ table.valuesY[i] = i;
+ }
+ for (int i = 1; i < ARRAYSIZE(_scaleTables); ++i) {
+ _scaleTables[i] = _scaleTables[0];
+ }
+ }
+
+ /**
+ * Retrieves scaler tables for the given X and Y ratios.
+ */
+ const CelScalerTable *getScalerTable(const Ratio &scaleX, const Ratio &scaleY);
+};
+
+#pragma mark -
+#pragma mark CelObj
+
+class ScreenItem;
+/**
+ * A cel object is the lowest-level rendering primitive in
+ * the SCI engine and draws itself directly to a target
+ * pixel buffer.
+ */
+class CelObj {
+protected:
+ /**
+ * When true, every second line of the cel will be
+ * rendered as a black line.
+ *
+ * @see ScreenItem::_drawBlackLines
+ * @note Using a static member because otherwise this
+ * would otherwise need to be copied down through
+ * several calls. (SSCI did similar, using a global
+ * variable.)
+ */
+ static bool _drawBlackLines;
+
+ /**
+ * When true, this cel will be horizontally mirrored
+ * when it is drawn. This is an internal flag that is
+ * set by draw methods based on the combination of the
+ * cel's `_mirrorX` property and the owner screen item's
+ * `_mirrorX` property.
+ */
+ bool _drawMirrored;
+
+public:
+ static CelScaler *_scaler;
+
+ /**
+ * The basic identifying information for this cel. This
+ * information effectively acts as a composite key for
+ * a cel object, and any cel object can be recreated
+ * from this data alone.
+ */
+ CelInfo32 _info;
+
+ /**
+ * The offset to the cel header for this cel within the
+ * raw resource data.
+ */
+ uint32 _celHeaderOffset;
+
+ /**
+ * The offset to the embedded palette for this cel
+ * within the raw resource data.
+ */
+ uint32 _hunkPaletteOffset;
+
+ /**
+ * The natural dimensions of the cel.
+ */
+ uint16 _width, _height;
+
+ /**
+ * TODO: Documentation
+ */
+ Common::Point _displace;
+
+ /**
+ * The dimensions of the original coordinate system for
+ * the cel. Used to scale cels from their native size
+ * to the correct size on screen.
+ *
+ * @note This is set to scriptWidth/Height for
+ * CelObjColor. For other cel objects, the value comes
+ * from the raw resource data. For text bitmaps, this is
+ * the width/height of the coordinate system used to
+ * generate the text, which also defaults to
+ * scriptWidth/Height but seems to typically be changed
+ * to more closely match the native screen resolution.
+ */
+ uint16 _scaledWidth, _scaledHeight;
+
+ /**
+ * The skip (transparent) color for the cel. When
+ * compositing, any pixels matching this color will not
+ * be copied to the buffer.
+ */
+ uint8 _transparentColor;
+
+ /**
+ * Whether or not this cel has any transparent regions.
+ * This is used for optimised drawing of non-transparent
+ * cels.
+ */
+ bool _transparent; // TODO: probably "skip"?
+
+ /**
+ * The compression type for the pixel data for this cel.
+ */
+ CelCompressionType _compressionType;
+
+ /**
+ * Whether or not this cel should be palette-remapped?
+ */
+ bool _remap;
+
+ /**
+ * If true, the cel contains pre-mirrored picture data.
+ * This value comes directly from the resource data and
+ * is XORed with the `_mirrorX` property of the owner
+ * screen item when rendering.
+ */
+ bool _mirrorX;
+
+ /**
+ * Initialises static CelObj members.
+ */
+ static void init();
+
+ /**
+ * Frees static CelObj members.
+ */
+ static void deinit();
+
+ virtual ~CelObj() {};
+
+ /**
+ * Draws the cel to the target buffer using the priority
+ * and positioning information from the given screen
+ * item. The mirroring of the cel will be unchanged from
+ * any previous call to draw.
+ */
+ void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const;
+
+ /**
+ * Draws the cel to the target buffer using the priority
+ * and positioning information from the given screen
+ * item and the given mirror flag.
+ *
+ * @note In SCI engine, this function was a virtual
+ * function, but CelObjView, CelObjPic, and CelObjMem
+ * all used the same function and the compiler
+ * deduplicated the copies; we deduplicate the source by
+ * putting the implementation on CelObj instead of
+ * copying it to 3/4 of the subclasses.
+ */
+ virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX);
+
+ /**
+ * Draws the cel to the target buffer using the
+ * positioning and mirroring information from the
+ * provided arguments.
+ *
+ * @note In SCI engine, this function was a virtual
+ * function, but CelObjView, CelObjPic, and CelObjMem
+ * all used the same function and the compiler
+ * deduplicated the copies; we deduplicate the source by
+ * putting the implementation on CelObj instead of
+ * copying it to 3/4 of the subclasses.
+ */
+ virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX);
+
+ /**
+ * Draws the cel to the target buffer using the given
+ * position and scaling parameters. The mirroring of the
+ * cel will be unchanged from any previous call to draw.
+ */
+ void drawTo(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const;
+
+ /**
+ * Creates a copy of this cel on the free store and
+ * returns a pointer to the new object. The new cel will
+ * point to a shared copy of bitmap/resource data.
+ */
+ virtual CelObj *duplicate() const = 0;
+
+ /**
+ * Retrieves a pointer to the raw resource data for this
+ * cel. This method cannot be used with a CelObjColor.
+ */
+ virtual byte *getResPointer() const = 0;
+
+ /**
+ * Reads the pixel at the given coordinates. This method
+ * is valid only for CelObjView and CelObjPic.
+ */
+ virtual uint8 readPixel(uint16 x, uint16 y, bool mirrorX) const;
+
+ /**
+ * Submits the palette from this cel to the palette
+ * manager for integration into the master screen
+ * palette.
+ */
+ void submitPalette() const;
+
+#pragma mark -
+#pragma mark CelObj - Drawing
+private:
+ template<typename MAPPER, typename SCALER>
+ void render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+
+ template<typename MAPPER, typename SCALER>
+ void render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const;
+
+ void drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+
+ void drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ // NOTE: The original includes versions of the above functions with priority parameters, which were not actually used in SCI32
+
+ void drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ // NOTE: The original includes versions of the above functions with priority parameters, which were not actually used in SCI32
+
+#pragma mark -
+#pragma mark CelObj - Caching
+protected:
+ /**
+ * A monotonically increasing cache ID used to identify
+ * the least recently used item in the cache for
+ * replacement.
+ */
+ static int _nextCacheId;
+
+ /**
+ * A cache of cel objects used to avoid reinitialisation
+ * overhead for cels with the same CelInfo32.
+ */
+ // NOTE: At least SQ6 uses a fixed cache size of 100.
+ static CelCache *_cache;
+
+ /**
+ * Searches the cel cache for a CelObj matching the
+ * provided CelInfo32. If not found, -1 is returned.
+ * nextInsertIndex will receive the index of the oldest
+ * item in the cache, which can be used to replace
+ * the oldest item with a newer item.
+ */
+ int searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const;
+
+ /**
+ * Puts a copy of this CelObj into the cache at the
+ * given cache index.
+ */
+ void putCopyInCache(int index) const;
+};
+
+#pragma mark -
+#pragma mark CelObjView
+
+/**
+ * A CelObjView is the drawing primitive for a View type
+ * resource. Each CelObjView corresponds to a single cel
+ * within a single loop of a view.
+ */
+class CelObjView : public CelObj {
+private:
+ /**
+ * Analyses resources without baked-in remap flags
+ * to determine whether or not they should be remapped.
+ */
+ bool analyzeUncompressedForRemap() const;
+
+ /**
+ * Analyses compressed resources without baked-in remap
+ * flags to determine whether or not they should be
+ * remapped.
+ */
+ bool analyzeForRemap() const;
+
+public:
+ CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo);
+ virtual ~CelObjView() override {};
+
+ using CelObj::draw;
+
+ /**
+ * Draws the cel to the target buffer using the
+ * positioning, mirroring, and scaling information from
+ * the provided arguments.
+ */
+ void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY);
+
+ virtual CelObjView *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjPic
+
+/**
+ * A CelObjPic is the drawing primitive for a Picture type
+ * resource. Each CelObjPic corresponds to a single cel
+ * within a picture.
+ */
+class CelObjPic : public CelObj {
+private:
+ /**
+ * Analyses uncompressed resources without baked-in skip
+ * flags to determine whether or not they can use fast
+ * blitting.
+ */
+ bool analyzeUncompressedForSkip() const;
+
+public:
+ /**
+ * The number of cels in the original picture resource.
+ */
+ uint8 _celCount;
+
+ /**
+ * The position of this cel relative to the top-left
+ * corner of the picture.
+ */
+ Common::Point _relativePosition;
+
+ /**
+ * The z-buffer priority for this cel. Higher prorities
+ * are drawn on top of lower priorities.
+ */
+ int16 _priority;
+
+ CelObjPic(const GuiResourceId pictureId, const int16 celNo);
+ virtual ~CelObjPic() override {};
+
+ using CelObj::draw;
+ virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) override;
+
+ virtual CelObjPic *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjMem
+
+/**
+ * A CelObjMem is the drawing primitive for arbitrary
+ * bitmaps generated in memory. Generated bitmaps in SCI32
+ * include text & vector drawings and per-pixel screen
+ * transitions like dissolves.
+ */
+class CelObjMem : public CelObj {
+public:
+ CelObjMem(const reg_t bitmap);
+ virtual ~CelObjMem() override {};
+
+ virtual CelObjMem *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjColor
+
+/**
+ * A CelObjColor is the drawing primitive for fast,
+ * low-memory, flat color fills.
+ */
+class CelObjColor : public CelObj {
+public:
+ CelObjColor(const uint8 color, const int16 width, const int16 height);
+ virtual ~CelObjColor() override {};
+
+ using CelObj::draw;
+ /**
+ * Block fills the target buffer with the cel color.
+ */
+ void draw(Buffer &target, const Common::Rect &targetRect) const;
+ virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX) override;
+ virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) override;
+
+ virtual CelObjColor *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp
index 3c2285a470..130416ff60 100644
--- a/engines/sci/graphics/compare.cpp
+++ b/engines/sci/graphics/compare.cpp
@@ -67,7 +67,7 @@ uint16 GfxCompare::isOnControl(uint16 screenMask, const Common::Rect &rect) {
return result;
}
-reg_t GfxCompare::canBeHereCheckRectList(reg_t checkObject, const Common::Rect &checkRect, List *list) {
+reg_t GfxCompare::canBeHereCheckRectList(const reg_t checkObject, const Common::Rect &checkRect, const List *list, const uint16 signalFlags) const {
reg_t curAddress = list->first;
Node *curNode = _segMan->lookupNode(curAddress);
reg_t curObject;
@@ -78,7 +78,7 @@ reg_t GfxCompare::canBeHereCheckRectList(reg_t checkObject, const Common::Rect &
curObject = curNode->value;
if (curObject != checkObject) {
signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
- if (!(signal & (kSignalIgnoreActor | kSignalRemoveView | kSignalNoUpdate))) {
+ if (!(signal & signalFlags)) {
curRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft));
curRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop));
curRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight));
@@ -112,11 +112,6 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
GfxView *view = NULL;
Common::Rect celRect(0, 0);
GuiResourceId viewId = (GuiResourceId)readSelectorValue(_segMan, objectReference, SELECTOR(view));
-
- // HACK: Ignore invalid views for now (perhaps unimplemented text views?)
- if (viewId == 0xFFFF) // invalid view
- return;
-
int16 loopNo = readSelectorValue(_segMan, objectReference, SELECTOR(loop));
int16 celNo = readSelectorValue(_segMan, objectReference, SELECTOR(cel));
int16 x = (int16)readSelectorValue(_segMan, objectReference, SELECTOR(x));
@@ -126,26 +121,8 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
z = (int16)readSelectorValue(_segMan, objectReference, SELECTOR(z));
view = _cache->getView(viewId);
-
-#ifdef ENABLE_SCI32
- if (view->isSci2Hires())
- view->adjustToUpscaledCoordinates(y, x);
- else if (getSciVersion() == SCI_VERSION_2_1)
- _coordAdjuster->fromScriptToDisplay(y, x);
-#endif
-
view->getCelRect(loopNo, celNo, x, y, z, celRect);
-#ifdef ENABLE_SCI32
- if (view->isSci2Hires()) {
- view->adjustBackUpscaledCoordinates(celRect.top, celRect.left);
- view->adjustBackUpscaledCoordinates(celRect.bottom, celRect.right);
- } else if (getSciVersion() == SCI_VERSION_2_1) {
- _coordAdjuster->fromDisplayToScript(celRect.top, celRect.left);
- _coordAdjuster->fromDisplayToScript(celRect.bottom, celRect.right);
- }
-#endif
-
if (lookupSelector(_segMan, objectReference, SELECTOR(nsTop), NULL, NULL) == kSelectorVariable) {
setNSRect(objectReference, celRect);
}
@@ -153,32 +130,65 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
reg_t GfxCompare::kernelCanBeHere(reg_t curObject, reg_t listReference) {
Common::Rect checkRect;
- Common::Rect adjustedRect;
- uint16 signal, controlMask;
uint16 result;
checkRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft));
checkRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop));
checkRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight));
checkRect.bottom = readSelectorValue(_segMan, curObject, SELECTOR(brBottom));
+ uint16 signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
if (!checkRect.isValidRect()) { // can occur in Iceman and Mother Goose - HACK? TODO: is this really occuring in sierra sci? check this
warning("kCan(t)BeHere - invalid rect %d, %d -> %d, %d", checkRect.left, checkRect.top, checkRect.right, checkRect.bottom);
return NULL_REG; // this means "can be here"
}
- adjustedRect = _coordAdjuster->onControl(checkRect);
-
- signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
- controlMask = readSelectorValue(_segMan, curObject, SELECTOR(illegalBits));
+ Common::Rect adjustedRect = _coordAdjuster->onControl(checkRect);
+ uint16 controlMask = readSelectorValue(_segMan, curObject, SELECTOR(illegalBits));
result = isOnControl(GFX_SCREEN_MASK_CONTROL, adjustedRect) & controlMask;
if ((!result) && (signal & (kSignalIgnoreActor | kSignalRemoveView)) == 0) {
List *list = _segMan->lookupList(listReference);
if (!list)
error("kCanBeHere called with non-list as parameter");
- return canBeHereCheckRectList(curObject, checkRect, list);
+ return canBeHereCheckRectList(curObject, checkRect, list, kSignalIgnoreActor | kSignalRemoveView | kSignalNoUpdate);
+ }
+
+ return make_reg(0, result);
+}
+
+reg_t GfxCompare::kernelCantBeHere32(const reg_t curObject, const reg_t listReference) const {
+ // Most of SCI32 graphics code converts rects from the VM to exclusive
+ // rects before operating on them, but this call leverages SCI16 engine
+ // code that operates on inclusive rects, so the rect's bottom-right
+ // point is not modified like in other SCI32 kernel calls
+ Common::Rect checkRect;
+
+ // At least LSL6 hires passes invalid rectangles which trigger the
+ // isValidRect assertion in the Rect constructor; this is avoided by
+ // assigning the properties after construction and then testing the
+ // rect for validity ourselves here. SSCI does not care about whether
+ // or not the rects are valid
+ checkRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft));
+ checkRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop));
+ checkRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight));
+ checkRect.bottom = readSelectorValue(_segMan, curObject, SELECTOR(brBottom));
+ if (!checkRect.isValidRect()) {
+ return make_reg(0, 0);
+ }
+
+ uint16 result = 0;
+ uint16 signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
+ const uint16 signalFlags = kSignalIgnoreActor | kSignalHidden;
+
+ if ((signal & signalFlags) == 0) {
+ List *list = _segMan->lookupList(listReference);
+ if (!list) {
+ error("kCantBeHere called with non-list as parameter");
+ }
+ result = !canBeHereCheckRectList(curObject, checkRect, list, signalFlags).isNull();
}
+
return make_reg(0, result);
}
@@ -201,15 +211,9 @@ void GfxCompare::kernelBaseSetter(reg_t object) {
GuiResourceId viewId = readSelectorValue(_segMan, object, SELECTOR(view));
int16 loopNo = readSelectorValue(_segMan, object, SELECTOR(loop));
int16 celNo = readSelectorValue(_segMan, object, SELECTOR(cel));
-
- // HACK: Ignore invalid views for now (perhaps unimplemented text views?)
- if (viewId == 0xFFFF) // invalid view
- return;
-
uint16 scaleSignal = 0;
- if (getSciVersion() >= SCI_VERSION_1_1) {
+ if (getSciVersion() >= SCI_VERSION_1_1)
scaleSignal = readSelectorValue(_segMan, object, SELECTOR(scaleSignal));
- }
Common::Rect celRect;
diff --git a/engines/sci/graphics/compare.h b/engines/sci/graphics/compare.h
index 88b44aeeb1..c7005980d0 100644
--- a/engines/sci/graphics/compare.h
+++ b/engines/sci/graphics/compare.h
@@ -40,6 +40,7 @@ public:
uint16 kernelOnControl(byte screenMask, const Common::Rect &rect);
void kernelSetNowSeen(reg_t objectReference);
reg_t kernelCanBeHere(reg_t curObject, reg_t listReference);
+ reg_t kernelCantBeHere32(const reg_t curObject, const reg_t listReference) const;
bool kernelIsItSkip(GuiResourceId viewId, int16 loopNo, int16 celNo, Common::Point position);
void kernelBaseSetter(reg_t object);
Common::Rect getNSRect(reg_t object);
@@ -58,7 +59,7 @@ private:
* *different* from checkObject, has a brRect which is contained inside
* checkRect.
*/
- reg_t canBeHereCheckRectList(reg_t checkObject, const Common::Rect &checkRect, List *list);
+ reg_t canBeHereCheckRectList(const reg_t checkObject, const Common::Rect &checkRect, const List *list, const uint16 signalFlags) const;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/controls16.cpp b/engines/sci/graphics/controls16.cpp
index e2e250cf9d..b4bd92699a 100644
--- a/engines/sci/graphics/controls16.cpp
+++ b/engines/sci/graphics/controls16.cpp
@@ -151,7 +151,7 @@ void GfxControls16::kernelTexteditChange(reg_t controlObject, reg_t eventObject)
Common::Rect rect;
if (textReference.isNull())
- error("kEditControl called on object that doesnt have a text reference");
+ error("kEditControl called on object that doesn't have a text reference");
text = _segMan->getString(textReference);
uint16 oldCursorPos = cursorPos;
diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp
index 90b5cd558c..6b91bb4679 100644
--- a/engines/sci/graphics/controls32.cpp
+++ b/engines/sci/graphics/controls32.cpp
@@ -21,11 +21,14 @@
*/
#include "common/system.h"
-
+#include "common/translation.h"
+#include "gui/message.h"
#include "sci/sci.h"
+#include "sci/console.h"
#include "sci/event.h"
#include "sci/engine/kernel.h"
#include "sci/engine/seg_manager.h"
+#include "sci/engine/state.h"
#include "sci/graphics/cache.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/controls32.h"
@@ -34,171 +37,808 @@
#include "sci/graphics/text32.h"
namespace Sci {
+GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text) :
+ _segMan(segMan),
+ _gfxCache(cache),
+ _gfxText32(text),
+ _overwriteMode(false),
+ _nextCursorFlashTick(0),
+ // SSCI used a memory handle for a ScrollWindow object
+ // as ID. We use a simple numeric handle instead.
+ _nextScrollWindowId(10000) {}
-GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text)
- : _segMan(segMan), _cache(cache), _text(text) {
+GfxControls32::~GfxControls32() {
+ ScrollWindowMap::iterator it;
+ for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it)
+ delete it->_value;
}
-GfxControls32::~GfxControls32() {
+#pragma mark -
+#pragma mark Garbage collection
+
+Common::Array<reg_t> GfxControls32::listObjectReferences() {
+ Common::Array<reg_t> ret;
+ ScrollWindowMap::const_iterator it;
+ for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it)
+ ret.push_back(it->_value->getBitmap());
+
+ return ret;
}
-void GfxControls32::kernelTexteditChange(reg_t controlObject) {
- SciEvent curEvent;
- uint16 maxChars = 40; //readSelectorValue(_segMan, controlObject, SELECTOR(max)); // TODO
- reg_t textReference = readSelector(_segMan, controlObject, SELECTOR(text));
- GfxFont *font = _cache->getFont(readSelectorValue(_segMan, controlObject, SELECTOR(font)));
- Common::String text;
- uint16 textSize;
- bool textChanged = false;
- bool textAddChar = false;
- Common::Rect rect;
+#pragma mark -
+#pragma mark Text input control
+
+reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
+ SegManager *segMan = _segMan;
+
+ TextEditor editor;
+ reg_t textObject = readSelector(_segMan, controlObject, SELECTOR(text));
+ editor.text = _segMan->getString(textObject);
+ editor.foreColor = readSelectorValue(_segMan, controlObject, SELECTOR(fore));
+ editor.backColor = readSelectorValue(_segMan, controlObject, SELECTOR(back));
+ editor.skipColor = readSelectorValue(_segMan, controlObject, SELECTOR(skip));
+ editor.fontId = readSelectorValue(_segMan, controlObject, SELECTOR(font));
+ editor.maxLength = readSelectorValue(_segMan, controlObject, SELECTOR(width));
+ editor.bitmap = readSelector(_segMan, controlObject, SELECTOR(bitmap));
+ editor.cursorCharPosition = 0;
+ editor.cursorIsDrawn = false;
+ editor.borderColor = readSelectorValue(_segMan, controlObject, SELECTOR(borderColor));
+
+ reg_t titleObject = readSelector(_segMan, controlObject, SELECTOR(title));
+
+ int16 titleHeight = 0;
+ GuiResourceId titleFontId = readSelectorValue(_segMan, controlObject, SELECTOR(titleFont));
+ if (!titleObject.isNull()) {
+ GfxFont *titleFont = _gfxCache->getFont(titleFontId);
+ titleHeight += _gfxText32->scaleUpHeight(titleFont->getHeight()) + 1;
+ if (editor.borderColor != -1) {
+ titleHeight += 2;
+ }
+ }
+
+ int16 width = 0;
+ int16 height = titleHeight;
+
+ GfxFont *editorFont = _gfxCache->getFont(editor.fontId);
+ height += _gfxText32->scaleUpHeight(editorFont->getHeight()) + 1;
+ _gfxText32->setFont(editor.fontId);
+ int16 emSize = _gfxText32->getCharWidth('M', true);
+ width += editor.maxLength * emSize + 1;
+ if (editor.borderColor != -1) {
+ width += 4;
+ height += 2;
+ }
- if (textReference.isNull())
- error("kEditControl called on object that doesnt have a text reference");
- text = _segMan->getString(textReference);
+ Common::Rect editorPlaneRect(width, height);
+ editorPlaneRect.translate(readSelectorValue(_segMan, controlObject, SELECTOR(x)), readSelectorValue(_segMan, controlObject, SELECTOR(y)));
- // TODO: Finish this
- warning("kEditText ('%s')", text.c_str());
- return;
+ reg_t planeObj = readSelector(_segMan, controlObject, SELECTOR(plane));
+ Plane *sourcePlane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
+ if (sourcePlane == nullptr) {
+ error("Could not find plane %04x:%04x", PRINT_REG(planeObj));
+ }
+ editorPlaneRect.translate(sourcePlane->_gameRect.left, sourcePlane->_gameRect.top);
- uint16 cursorPos = 0;
- //uint16 oldCursorPos = cursorPos;
- bool captureEvents = true;
- EventManager* eventMan = g_sci->getEventManager();
+ editor.textRect = Common::Rect(2, titleHeight + 2, width - 1, height - 1);
+ editor.width = width;
- while (captureEvents) {
- curEvent = g_sci->getEventManager()->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK);
+ if (editor.bitmap.isNull()) {
+ TextAlign alignment = (TextAlign)readSelectorValue(_segMan, controlObject, SELECTOR(mode));
- if (curEvent.type == SCI_EVENT_NONE) {
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
+ if (titleObject.isNull()) {
+ bool dimmed = readSelectorValue(_segMan, controlObject, SELECTOR(dimmed));
+ editor.bitmap = _gfxText32->createFontBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, dimmed, true);
} else {
- textSize = text.size();
+ error("Titled bitmaps are not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+ }
+ }
+
+ drawCursor(editor);
+
+ Plane *plane = new Plane(editorPlaneRect, kPlanePicTransparent);
+ plane->changePic();
+ g_sci->_gfxFrameout->addPlane(*plane);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = editor.bitmap;
- switch (curEvent.type) {
- case SCI_EVENT_MOUSE_PRESS:
- // TODO: Implement mouse support for cursor change
+ ScreenItem *screenItem = new ScreenItem(plane->_object, celInfo, Common::Point(), ScaleInfo());
+ plane->_screenItemList.add(screenItem);
+
+ // frameOut must be called after the screen item is
+ // created, and before it is updated at the end of the
+ // event loop, otherwise it has both created and updated
+ // flags set which crashes the engine (it runs updates
+ // before creations)
+ g_sci->_gfxFrameout->frameOut(true);
+
+ EventManager *eventManager = g_sci->getEventManager();
+ bool clearTextOnInput = true;
+ bool textChanged = false;
+ for (;;) {
+ // We peek here because the last event needs to be allowed to
+ // dispatch a second time to the normal event handling system.
+ // In the actual engine, the event is always consumed and then
+ // the last event just gets posted back to the event manager for
+ // reprocessing, but instead, we only remove the event from the
+ // queue *after* we have determined it is not a defocusing event
+ const SciEvent event = eventManager->getSciEvent(SCI_EVENT_ANY | SCI_EVENT_PEEK);
+
+ bool focused = true;
+ // Original engine did not have a QUIT event but we have to handle it
+ if (event.type == SCI_EVENT_QUIT) {
+ focused = false;
+ break;
+ } else if (event.type == SCI_EVENT_MOUSE_PRESS && !editorPlaneRect.contains(event.mousePosSci)) {
+ focused = false;
+ } else if (event.type == SCI_EVENT_KEYBOARD) {
+ switch (event.character) {
+ case SCI_KEY_ESC:
+ case SCI_KEY_UP:
+ case SCI_KEY_DOWN:
+ case SCI_KEY_TAB:
+ case SCI_KEY_SHIFT_TAB:
+ case SCI_KEY_ENTER:
+ focused = false;
break;
- case SCI_EVENT_KEYBOARD:
- switch (curEvent.data) {
- case SCI_KEY_BACKSPACE:
- if (cursorPos > 0) {
- cursorPos--; text.deleteChar(cursorPos);
- textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_DELETE:
- if (cursorPos < textSize) {
- text.deleteChar(cursorPos);
- textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_HOME: // HOME
- cursorPos = 0; textChanged = true;
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_END: // END
- cursorPos = textSize; textChanged = true;
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_LEFT: // LEFT
- if (cursorPos > 0) {
- cursorPos--; textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_RIGHT: // RIGHT
- if (cursorPos + 1 <= textSize) {
- cursorPos++; textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case 3: // returned in SCI1 late and newer when Control - C is pressed
- if (curEvent.modifiers & SCI_KEYMOD_CTRL) {
- // Control-C erases the whole line
- cursorPos = 0; text.clear();
- textChanged = true;
+ }
+ }
+
+ if (!focused) {
+ break;
+ }
+
+ // Consume the event now that we know it is not one of the
+ // defocusing events above
+ if (event.type != SCI_EVENT_NONE)
+ eventManager->getSciEvent(SCI_EVENT_ANY);
+
+ // NOTE: In the original engine, the font and bitmap were
+ // reset here on each iteration through the loop, but it
+ // doesn't seem like this should be necessary since
+ // control is not yielded back to the VM until input is
+ // received, which means there is nothing that could modify
+ // the GfxText32's state with a different font in the
+ // meantime
+
+ bool shouldDeleteChar = false;
+ bool shouldRedrawText = false;
+ uint16 lastCursorPosition = editor.cursorCharPosition;
+ if (event.type == SCI_EVENT_KEYBOARD) {
+ switch (event.character) {
+ case SCI_KEY_LEFT:
+ clearTextOnInput = false;
+ if (editor.cursorCharPosition > 0) {
+ --editor.cursorCharPosition;
+ }
+ break;
+
+ case SCI_KEY_RIGHT:
+ clearTextOnInput = false;
+ if (editor.cursorCharPosition < editor.text.size()) {
+ ++editor.cursorCharPosition;
+ }
+ break;
+
+ case SCI_KEY_HOME:
+ clearTextOnInput = false;
+ editor.cursorCharPosition = 0;
+ break;
+
+ case SCI_KEY_END:
+ clearTextOnInput = false;
+ editor.cursorCharPosition = editor.text.size();
+ break;
+
+ case SCI_KEY_INSERT:
+ clearTextOnInput = false;
+ // Redrawing also changes the cursor rect to
+ // reflect the new insertion mode
+ shouldRedrawText = true;
+ _overwriteMode = !_overwriteMode;
+ break;
+
+ case SCI_KEY_DELETE:
+ clearTextOnInput = false;
+ if (editor.cursorCharPosition < editor.text.size()) {
+ shouldDeleteChar = true;
+ }
+ break;
+
+ case SCI_KEY_BACKSPACE:
+ clearTextOnInput = false;
+ shouldDeleteChar = true;
+ if (editor.cursorCharPosition > 0) {
+ --editor.cursorCharPosition;
+ }
+ break;
+
+ case SCI_KEY_ETX:
+ editor.text.clear();
+ editor.cursorCharPosition = 0;
+ shouldRedrawText = true;
+ break;
+
+ default: {
+ if (event.character >= 20 && event.character < 257) {
+ if (clearTextOnInput) {
+ clearTextOnInput = false;
+ editor.text.clear();
}
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_UP:
- case SCI_KEY_DOWN:
- case SCI_KEY_ENTER:
- case SCI_KEY_ESC:
- case SCI_KEY_TAB:
- case SCI_KEY_SHIFT_TAB:
- captureEvents = false;
- break;
- default:
- if ((curEvent.modifiers & SCI_KEYMOD_CTRL) && curEvent.data == 99) {
- // Control-C in earlier SCI games (SCI0 - SCI1 middle)
- // Control-C erases the whole line
- cursorPos = 0; text.clear();
- textChanged = true;
- } else if (curEvent.data > 31 && curEvent.data < 256 && textSize < maxChars) {
- // insert pressed character
- textAddChar = true;
- textChanged = true;
+
+ if (
+ (_overwriteMode && editor.cursorCharPosition < editor.maxLength) ||
+ (editor.text.size() < editor.maxLength && _gfxText32->getCharWidth(event.character, true) + _gfxText32->getStringWidth(editor.text) < editor.textRect.width())
+ ) {
+ if (_overwriteMode && editor.cursorCharPosition < editor.text.size()) {
+ editor.text.setChar(event.character, editor.cursorCharPosition);
+ } else {
+ editor.text.insertChar(event.character, editor.cursorCharPosition);
+ }
+
+ ++editor.cursorCharPosition;
+ shouldRedrawText = true;
}
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
}
- break;
+ }
}
}
- if (textChanged) {
- rect = g_sci->_gfxCompare->getNSRect(controlObject);
+ if (shouldDeleteChar) {
+ shouldRedrawText = true;
+ if (editor.cursorCharPosition < editor.text.size()) {
+ editor.text.deleteChar(editor.cursorCharPosition);
+ }
+ }
- if (textAddChar) {
- const char *textPtr = text.c_str();
+ if (shouldRedrawText) {
+ eraseCursor(editor);
+ _gfxText32->erase(editor.textRect, true);
+ _gfxText32->drawTextBox(editor.text);
+ drawCursor(editor);
+ textChanged = true;
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ } else if (editor.cursorCharPosition != lastCursorPosition) {
+ eraseCursor(editor);
+ drawCursor(editor);
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ } else {
+ flashCursor(editor);
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ }
- // We check if we are really able to add the new char
- uint16 textWidth = 0;
- while (*textPtr)
- textWidth += font->getCharWidth((byte)*textPtr++);
- textWidth += font->getCharWidth(curEvent.data);
+ g_sci->_gfxFrameout->frameOut(true);
+ g_sci->getSciDebugger()->onFrame();
+ g_sci->_gfxFrameout->throttle();
+ }
- // Does it fit?
- if (textWidth >= rect.width()) {
- return;
- }
+ g_sci->_gfxFrameout->deletePlane(*plane);
+ if (readSelectorValue(segMan, controlObject, SELECTOR(frameOut))) {
+ g_sci->_gfxFrameout->frameOut(true);
+ }
- text.insertChar(curEvent.data, cursorPos++);
+ _segMan->freeHunkEntry(editor.bitmap);
- // Note: the following checkAltInput call might make the text
- // too wide to fit, but SSCI fails to check that too.
- }
+ if (textChanged) {
+ editor.text.trim();
+ SciString *string = _segMan->lookupString(textObject);
+ string->fromString(editor.text);
+ }
- reg_t hunkId = readSelector(_segMan, controlObject, SELECTOR(bitmap));
- Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(controlObject);
- //texteditCursorErase(); // TODO: Cursor
+ return make_reg(0, textChanged);
+}
+
+void GfxControls32::drawCursor(TextEditor &editor) {
+ if (!editor.cursorIsDrawn) {
+ editor.cursorRect.left = editor.textRect.left + _gfxText32->getTextWidth(editor.text, 0, editor.cursorCharPosition);
- // Write back string
- _segMan->strcpy(textReference, text.c_str());
- // Modify the buffer and show it
- _text->createTextBitmap(controlObject, 0, 0, hunkId);
+ const int16 scaledFontHeight = _gfxText32->scaleUpHeight(_gfxText32->_font->getHeight());
- _text->drawTextBitmap(0, 0, nsRect, controlObject);
- //texteditCursorDraw(rect, text.c_str(), cursorPos); // TODO: Cursor
- g_system->updateScreen();
+ // NOTE: The original code branched on borderColor here but
+ // the two branches appeared to be identical, differing only
+ // because the compiler decided to be differently clever
+ // when optimising multiplication in each branch
+ if (_overwriteMode) {
+ editor.cursorRect.top = editor.textRect.top;
+ editor.cursorRect.setHeight(scaledFontHeight);
} else {
- // TODO: Cursor
- /*
- if (g_system->getMillis() >= _texteditBlinkTime) {
- _paint16->invertRect(_texteditCursorRect);
- _paint16->bitsShow(_texteditCursorRect);
- _texteditCursorVisible = !_texteditCursorVisible;
- texteditSetBlinkTime();
- }
- */
+ editor.cursorRect.top = editor.textRect.top + scaledFontHeight - 1;
+ editor.cursorRect.setHeight(1);
+ }
+
+ const char currentChar = editor.cursorCharPosition < editor.text.size() ? editor.text[editor.cursorCharPosition] : ' ';
+ editor.cursorRect.setWidth(_gfxText32->getCharWidth(currentChar, true));
+
+ _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
+
+ editor.cursorIsDrawn = true;
+ }
+
+ _nextCursorFlashTick = g_sci->getTickCount() + 30;
+}
+
+void GfxControls32::eraseCursor(TextEditor &editor) {
+ if (editor.cursorIsDrawn) {
+ _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
+ editor.cursorIsDrawn = false;
+ }
+
+ _nextCursorFlashTick = g_sci->getTickCount() + 30;
+}
+
+void GfxControls32::flashCursor(TextEditor &editor) {
+ if (g_sci->getTickCount() > _nextCursorFlashTick) {
+ _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
+
+ editor.cursorIsDrawn = !editor.cursorIsDrawn;
+ _nextCursorFlashTick = g_sci->getTickCount() + 30;
+ }
+}
+
+#pragma mark -
+#pragma mark Scrollable window control
+
+ScrollWindow::ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) :
+ _gfxText32(segMan, g_sci->_gfxCache),
+ _maxNumEntries(maxNumEntries),
+ _firstVisibleChar(0),
+ _topVisibleLine(0),
+ _lastVisibleChar(0),
+ _bottomVisibleLine(0),
+ _numLines(0),
+ _numVisibleLines(0),
+ _plane(plane),
+ _foreColor(defaultForeColor),
+ _backColor(defaultBackColor),
+ _borderColor(defaultBorderColor),
+ _fontId(defaultFontId),
+ _alignment(defaultAlignment),
+ _visible(false),
+ _position(position),
+ _screenItem(nullptr),
+ _nextEntryId(1) {
+
+ _entries.reserve(maxNumEntries);
+
+ _gfxText32.setFont(_fontId);
+ _pointSize = _gfxText32._font->getHeight();
+
+ const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Common::Rect bitmapRect(gameRect);
+ mulinc(bitmapRect, Ratio(_gfxText32._scaledWidth, scriptWidth), Ratio(_gfxText32._scaledHeight, scriptHeight));
+
+ _textRect.left = 2;
+ _textRect.top = 2;
+ _textRect.right = bitmapRect.width() - 2;
+ _textRect.bottom = bitmapRect.height() - 2;
+
+ uint8 skipColor = 0;
+ while (skipColor == _foreColor || skipColor == _backColor) {
+ skipColor++;
+ }
+
+ assert(bitmapRect.width() > 0 && bitmapRect.height() > 0);
+ _bitmap = _gfxText32.createFontBitmap(bitmapRect.width(), bitmapRect.height(), _textRect, "", _foreColor, _backColor, skipColor, _fontId, _alignment, _borderColor, false, false);
+
+ debugC(1, kDebugLevelGraphics, "New ScrollWindow: textRect size: %d x %d, bitmap: %04x:%04x", _textRect.width(), _textRect.height(), PRINT_REG(_bitmap));
+}
+
+ScrollWindow::~ScrollWindow() {
+ // _gfxText32._bitmap will get GCed once ScrollWindow is gone.
+ // _screenItem will be deleted by GfxFrameout
+}
+
+Ratio ScrollWindow::where() const {
+ return Ratio(_topVisibleLine, MAX(_numLines, 1));
+}
+
+void ScrollWindow::show() {
+ if (_visible) {
+ return;
+ }
+
+ if (_screenItem == nullptr) {
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = _bitmap;
+
+ _screenItem = new ScreenItem(_plane, celInfo, _position, ScaleInfo());
+ }
+
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane);
+ plane->_screenItemList.add(_screenItem);
+
+ _visible = true;
+}
+
+void ScrollWindow::hide() {
+ if (!_visible) {
+ return;
+ }
+
+ g_sci->_gfxFrameout->deleteScreenItem(*_screenItem, _plane);
+ _screenItem = nullptr;
+ g_sci->_gfxFrameout->frameOut(true);
+
+ _visible = false;
+}
+
+reg_t ScrollWindow::add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) {
+ if (_entries.size() == _maxNumEntries) {
+ ScrollWindowEntry removedEntry = _entries.remove_at(0);
+ _text.erase(0, removedEntry.text.size());
+ // `_firstVisibleChar` will be reset shortly if
+ // `scrollTo` is true, so there is no reason to
+ // update it
+ if (!scrollTo) {
+ _firstVisibleChar -= removedEntry.text.size();
}
+ }
+
+ _entries.push_back(ScrollWindowEntry());
+ ScrollWindowEntry &entry = _entries.back();
+
+ // NOTE: In SSCI the line ID was a memory handle for the
+ // string of this line. We use a numeric ID instead.
+ entry.id = make_reg(0, _nextEntryId++);
+
+ if (_nextEntryId > _maxNumEntries) {
+ _nextEntryId = 1;
+ }
+
+ // NOTE: In SSCI this was updated after _text was
+ // updated, which meant there was an extra unnecessary
+ // subtraction operation (subtracting `entry.text` size)
+ if (scrollTo) {
+ _firstVisibleChar = _text.size();
+ }
+
+ fillEntry(entry, text, fontId, foreColor, alignment);
+ _text += entry.text;
+
+ computeLineIndices();
+ update(true);
+
+ return entry.id;
+}
+
+void ScrollWindow::fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment) {
+ entry.alignment = alignment;
+ entry.foreColor = foreColor;
+ entry.fontId = fontId;
+
+ Common::String formattedText;
+
+ // NB: There are inconsistencies here.
+ // If there is a multi-line entry with non-default properties, and it
+ // is only partially displayed, it may not be displayed right, since the
+ // property directives are only added to the first line.
+ // (Verified by trying this in SSCI SQ6 with a custom ScrollWindowAdd call.)
+ //
+ // The converse is also a potential issue (but unverified), where lines
+ // with properties -1 can inherit properties from the previously rendered
+ // line instead of the defaults.
+
+ // NOTE: SSCI added "|s<lineIndex>|" here, but |s| is
+ // not a valid control code, so it just always ended up
+ // getting skipped
+ if (entry.fontId != -1) {
+ formattedText += Common::String::format("|f%d|", entry.fontId);
+ }
+ if (entry.foreColor != -1) {
+ formattedText += Common::String::format("|c%d|", entry.foreColor);
+ }
+ if (entry.alignment != -1) {
+ formattedText += Common::String::format("|a%d|", entry.alignment);
+ }
+ formattedText += text;
+ entry.text = formattedText;
+}
+
+reg_t ScrollWindow::modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) {
+
+ EntriesList::iterator it = _entries.begin();
+ uint firstCharLocation = 0;
+ for ( ; it != _entries.end(); ++it) {
+ if (it->id == id) {
+ break;
+ }
+ firstCharLocation += it->text.size();
+ }
+
+ if (it == _entries.end()) {
+ return make_reg(0, 0);
+ }
+
+ ScrollWindowEntry &entry = *it;
+
+ uint oldTextLength = entry.text.size();
+
+ fillEntry(entry, text, fontId, foreColor, alignment);
+ _text.replace(firstCharLocation, oldTextLength, entry.text);
+
+ if (scrollTo) {
+ _firstVisibleChar = firstCharLocation;
+ }
+
+ computeLineIndices();
+ update(true);
+
+ return entry.id;
+}
+
+void ScrollWindow::upArrow() {
+ if (_topVisibleLine == 0) {
+ return;
+ }
+
+ _topVisibleLine--;
+ _bottomVisibleLine--;
+
+ if (_bottomVisibleLine - _topVisibleLine + 1 < _numVisibleLines) {
+ _bottomVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
+
+ _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
+
+ Common::String lineText(_text.c_str() + _startsOfLines[_topVisibleLine], _text.c_str() + _startsOfLines[_topVisibleLine + 1] - 1);
+
+ debugC(3, kDebugLevelGraphics, "ScrollWindow::upArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str());
+
+ _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollUp);
+
+ if (_visible) {
+ assert(_screenItem);
+
+ _screenItem->update();
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+}
+
+void ScrollWindow::downArrow() {
+ if (_topVisibleLine + 1 >= _numLines) {
+ return;
+ }
+
+ _topVisibleLine++;
+ _bottomVisibleLine++;
+
+ if (_bottomVisibleLine + 1 >= _numLines) {
+ _bottomVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
+
+ _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
+
+ Common::String lineText;
+ if (_bottomVisibleLine - _topVisibleLine + 1 == _numVisibleLines) {
+ lineText = Common::String(_text.c_str() + _startsOfLines[_bottomVisibleLine], _text.c_str() + _startsOfLines[_bottomVisibleLine + 1] - 1);
+ } else {
+ // scroll in empty string
+ }
+
+ debugC(3, kDebugLevelGraphics, "ScrollWindow::downArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str());
+
+
+ _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollDown);
+
+ if (_visible) {
+ assert(_screenItem);
+
+ _screenItem->update();
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+}
+
+void ScrollWindow::go(const Ratio location) {
+ const int line = (location * _numLines).toInt();
+ if (line < 0 || line > _numLines) {
+ error("Index is Out of Range in ScrollWindow");
+ }
+
+ _firstVisibleChar = _startsOfLines[line];
+ update(true);
+
+ // HACK:
+ // It usually isn't possible to set _topVisibleLine >= _numLines, and so
+ // update() doesn't. However, in this case we should set _topVisibleLine
+ // past the end. This is clearly visible in Phantasmagoria when dragging
+ // the slider in the About dialog to the very end. The slider ends up lower
+ // than where it can be moved by scrolling down with the arrows.
+ if (location.isOne()) {
+ _topVisibleLine = _numLines;
+ }
+}
+
+void ScrollWindow::home() {
+ if (_firstVisibleChar == 0) {
+ return;
+ }
+
+ _firstVisibleChar = 0;
+ update(true);
+}
+
+void ScrollWindow::end() {
+ if (_bottomVisibleLine + 1 >= _numLines) {
+ return;
+ }
+
+ int line = _numLines - _numVisibleLines;
+ if (line < 0) {
+ line = 0;
+ }
+ _firstVisibleChar = _startsOfLines[line];
+ update(true);
+}
+
+void ScrollWindow::pageUp() {
+ if (_topVisibleLine == 0) {
+ return;
+ }
+
+ _topVisibleLine -= _numVisibleLines;
+ if (_topVisibleLine < 0) {
+ _topVisibleLine = 0;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ update(true);
+}
+
+void ScrollWindow::pageDown() {
+ if (_topVisibleLine + 1 >= _numLines) {
+ return;
+ }
+
+ _topVisibleLine += _numVisibleLines;
+ if (_topVisibleLine + 1 >= _numLines) {
+ _topVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ update(true);
+}
+
+void ScrollWindow::computeLineIndices() {
+ _gfxText32.setFont(_fontId);
+ // NOTE: Unlike SSCI, foreColor and alignment are not
+ // set since these properties do not affect the width of
+ // lines
+
+ if (_gfxText32._font->getHeight() != _pointSize) {
+ error("Illegal font size font = %d pointSize = %d, should be %d.", _fontId, _gfxText32._font->getHeight(), _pointSize);
+ }
+
+ Common::Rect lineRect(0, 0, _textRect.width(), _pointSize + 3);
+
+ _startsOfLines.clear();
+
+ // NOTE: The original engine had a 1000-line limit; we
+ // do not enforce any limit
+ for (uint charIndex = 0; charIndex < _text.size(); ) {
+ _startsOfLines.push_back(charIndex);
+ charIndex += _gfxText32.getTextCount(_text, charIndex, lineRect, false);
+ }
+
+ _numLines = _startsOfLines.size();
+
+ _startsOfLines.push_back(_text.size());
+
+ _lastVisibleChar = _gfxText32.getTextCount(_text, 0, _fontId, _textRect, false) - 1;
+
+ _bottomVisibleLine = 0;
+ while (
+ _bottomVisibleLine < _numLines - 1 &&
+ _startsOfLines[_bottomVisibleLine + 1] < _lastVisibleChar
+ ) {
+ ++_bottomVisibleLine;
+ }
+
+ _numVisibleLines = _bottomVisibleLine + 1;
+}
+
+void ScrollWindow::update(const bool doFrameOut) {
+ _topVisibleLine = 0;
+ while (
+ _topVisibleLine < _numLines - 1 &&
+ _firstVisibleChar >= _startsOfLines[_topVisibleLine + 1]
+ ) {
+ ++_topVisibleLine;
+ }
+
+ _bottomVisibleLine = _topVisibleLine + _numVisibleLines - 1;
+ if (_bottomVisibleLine >= _numLines) {
+ _bottomVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+
+ if (_bottomVisibleLine >= 0) {
+ _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
+ } else {
+ _lastVisibleChar = -1;
+ }
+
+ _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
+
+ _gfxText32.erase(_textRect, false);
+ _gfxText32.drawTextBox(_visibleText);
+
+ if (_visible) {
+ assert(_screenItem);
+
+ _screenItem->update();
+ if (doFrameOut) {
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+ }
+}
+
+reg_t GfxControls32::makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) {
+
+ ScrollWindow *scrollWindow = new ScrollWindow(_segMan, gameRect, position, planeObj, defaultForeColor, defaultBackColor, defaultFontId, defaultAlignment, defaultBorderColor, maxNumEntries);
+
+ const uint16 id = _nextScrollWindowId++;
+ _scrollWindows[id] = scrollWindow;
+ return make_reg(0, id);
+}
+
+ScrollWindow *GfxControls32::getScrollWindow(const reg_t id) {
+ ScrollWindowMap::iterator it;
+ it = _scrollWindows.find(id.toUint16());
+ if (it == _scrollWindows.end())
+ error("Invalid ScrollWindow ID");
+
+ return it->_value;
+}
+
+void GfxControls32::destroyScrollWindow(const reg_t id) {
+ ScrollWindow *scrollWindow = getScrollWindow(id);
+ scrollWindow->hide();
+ _scrollWindows.erase(id.getOffset());
+ delete scrollWindow;
+}
+
+#pragma mark -
+#pragma mark Message box
+
+int16 GfxControls32::showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue) {
+ GUI::MessageDialog dialog(message, okLabel, altLabel);
+ return (dialog.runModal() == GUI::kMessageOK) ? okValue : altValue;
+}
+
+reg_t GfxControls32::kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style) {
+ if (g_engine) {
+ g_engine->pauseEngine(true);
+ }
+
+ int16 result;
+
+ switch (style & 0xF) {
+ case kMessageBoxOK:
+ result = showMessageBox(message, _("OK"), NULL, 1, 1);
+ break;
+ case kMessageBoxYesNo:
+ result = showMessageBox(message, _("Yes"), _("No"), 6, 7);
+ break;
+ default:
+ error("Unsupported MessageBox style 0x%x", style & 0xF);
+ }
+
+ if (g_engine) {
+ g_engine->pauseEngine(false);
+ }
- textAddChar = false;
- textChanged = false;
- g_sci->sleep(10);
- } // while
+ return make_reg(0, result);
}
} // End of namespace Sci
diff --git a/engines/sci/graphics/controls32.h b/engines/sci/graphics/controls32.h
index 5af7c20f16..460b0b5625 100644
--- a/engines/sci/graphics/controls32.h
+++ b/engines/sci/graphics/controls32.h
@@ -23,12 +23,387 @@
#ifndef SCI_GRAPHICS_CONTROLS32_H
#define SCI_GRAPHICS_CONTROLS32_H
+#include "sci/graphics/text32.h"
+
namespace Sci {
class GfxCache;
class GfxScreen;
class GfxText32;
+enum MessageBoxStyle {
+ kMessageBoxOK = 0x0,
+ kMessageBoxYesNo = 0x4
+};
+
+struct TextEditor {
+ /**
+ * The bitmap where the editor is rendered.
+ */
+ reg_t bitmap;
+
+ /**
+ * The width of the editor, in bitmap pixels.
+ */
+ int16 width;
+
+ /**
+ * The text in the editor.
+ */
+ Common::String text;
+
+ /**
+ * The rect where text should be drawn into the editor,
+ * in bitmap pixels.
+ */
+ Common::Rect textRect;
+
+ /**
+ * The color of the border. -1 indicates no border.
+ */
+ int16 borderColor;
+
+ /**
+ * The text color.
+ */
+ uint8 foreColor;
+
+ /**
+ * The background color.
+ */
+ uint8 backColor;
+
+ /**
+ * The transparent color.
+ */
+ uint8 skipColor;
+
+ /**
+ * The font used to render the text in the editor.
+ */
+ GuiResourceId fontId;
+
+ /**
+ * The current position of the cursor within the editor.
+ */
+ uint16 cursorCharPosition;
+
+ /**
+ * Whether or not the cursor is currently drawn to the
+ * screen.
+ */
+ bool cursorIsDrawn;
+
+ /**
+ * The rectangle for drawing the input cursor, in bitmap
+ * pixels.
+ */
+ Common::Rect cursorRect;
+
+ /**
+ * The maximum allowed text length, in characters.
+ */
+ uint16 maxLength;
+};
+
+/**
+ * A single block of text written to a ScrollWindow.
+ */
+struct ScrollWindowEntry {
+ /**
+ * ID of the line. In SSCI this was actually a memory
+ * handle for the string of this line. We use a simple
+ * numeric ID instead.
+ */
+ reg_t id;
+
+ /**
+ * The alignment to use when rendering this line of
+ * text. If -1, the default alignment from the
+ * corresponding ScrollWindow will be used.
+ */
+ TextAlign alignment;
+
+ /**
+ * The color to use to render this line of text. If -1,
+ * the default foreground color from the corresponding
+ * ScrollWindow will be used.
+ */
+ int16 foreColor;
+
+ /**
+ * The font to use to render this line of text. If -1,
+ * the default font from the corresponding ScrollWindow
+ * will be used.
+ */
+ GuiResourceId fontId;
+
+ /**
+ * The text.
+ */
+ Common::String text;
+};
+
+class ScreenItem;
+
+/**
+ * A scrollable text window.
+ */
+class ScrollWindow {
+public:
+ ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries);
+ ~ScrollWindow();
+
+ /**
+ * Adds a new text entry to the window. If `fontId`,
+ * `foreColor`, or `alignment` are `-1`, the
+ * ScrollWindow's default values will be used.
+ */
+ reg_t add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo);
+
+ /**
+ * Modifies an existing text entry with the given ID. If
+ * `fontId`, `foreColor`, or `alignment` are `-1`, the
+ * ScrollWindow's default values will be used.
+ */
+ reg_t modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo);
+
+ /**
+ * Shows the ScrollWindow if it is not already visible.
+ */
+ void show();
+
+ /**
+ * Hides the ScrollWindow if it is currently visible.
+ */
+ void hide();
+
+ /**
+ * Gets the number of lines that the content of a
+ * ScrollWindow is scrolled upward, as a ratio of the
+ * total number of lines of content.
+ */
+ Ratio where() const;
+
+ /**
+ * Scrolls the window to a specific location.
+ */
+ void go(const Ratio location);
+
+ /**
+ * Scrolls the window to the top.
+ */
+ void home();
+
+ /**
+ * Scrolls the window to the bottom.
+ */
+ void end();
+
+ /**
+ * Scrolls the window up one line.
+ */
+ void upArrow();
+
+ /**
+ * Scrolls the window down one line.
+ */
+ void downArrow();
+
+ /**
+ * Scrolls the window up by one page.
+ */
+ void pageUp();
+
+ /**
+ * Scrolls the window down by one page.
+ */
+ void pageDown();
+
+ /**
+ * Gets a reference to the in-memory bitmap that
+ * is used to render the text in the ScrollWindow.
+ */
+ const reg_t getBitmap() const { return _bitmap; }
+
+private:
+ typedef Common::Array<ScrollWindowEntry> EntriesList;
+
+ /**
+ * A convenience function that fills a
+ * ScrollWindowEntry's properties.
+ */
+ void fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment);
+
+ /**
+ * Rescans the entire text of the ScrollWindow when an
+ * entry is added or modified, calculating the character
+ * offsets of all line endings, the total number of
+ * lines of text, the height of the viewport (in lines
+ * of text), the last character visible in the viewport
+ * (assuming the viewport is scrolled to the top), and
+ * the line index of the bottommost visible line
+ * (assuming the viewport is scrolled to the top).
+ */
+ void computeLineIndices();
+
+ /**
+ * Calculates which text is visible within the
+ * ScrollWindow's viewport and renders the text to the
+ * internal bitmap.
+ *
+ * If `doFrameOut` is true, the screen will be refreshed
+ * immediately instead of waiting for the next call to
+ * `kFrameOut`.
+ */
+ void update(const bool doFrameOut);
+
+ /**
+ * The text renderer.
+ */
+ GfxText32 _gfxText32;
+
+ /**
+ * The individual text entries added to the
+ * ScrollWindow.
+ */
+ EntriesList _entries;
+
+ /**
+ * The maximum number of entries allowed. Once this
+ * limit is reached, the oldest entry will be removed
+ * when a new entry is added.
+ */
+ uint _maxNumEntries;
+
+ /**
+ * A mapping from a line index to the line's character
+ * offset in `_text`.
+ */
+ Common::Array<int> _startsOfLines;
+
+ /**
+ * All text added to the window.
+ */
+ Common::String _text;
+
+ /**
+ * Text that is within the viewport of the ScrollWindow.
+ */
+ Common::String _visibleText;
+
+ /**
+ * The offset of the first visible character in `_text`.
+ */
+ int _firstVisibleChar;
+
+ /**
+ * The index of the line that is at the top of the
+ * viewport.
+ */
+ int _topVisibleLine;
+
+ /**
+ * The index of the last visible character in `_text`,
+ * or -1 if there is no text.
+ */
+ int _lastVisibleChar;
+
+ /**
+ * The index of the line that is at the bottom of the
+ * viewport, or -1 if there is no text.
+ */
+ int _bottomVisibleLine;
+
+ /**
+ * The total number of lines in the backbuffer. This
+ * number may be higher than the total number of entries
+ * if an entry contains newlines.
+ */
+ int _numLines;
+
+ /**
+ * The number of lines that are currently visible in the
+ * text area of the window.
+ */
+ int _numVisibleLines;
+
+ /**
+ * The plane in which the ScrollWindow should be
+ * rendered.
+ */
+ reg_t _plane;
+
+ /**
+ * The default text color.
+ */
+ uint8 _foreColor;
+
+ /**
+ * The default background color of the text bitmap.
+ */
+ uint8 _backColor;
+
+ /**
+ * The default border color of the text bitmap. If -1,
+ * the viewport will have no border.
+ */
+ int16 _borderColor;
+
+ /**
+ * The default font used for rendering text into the
+ * ScrollWindow.
+ */
+ GuiResourceId _fontId;
+
+ /**
+ * The default text alignment used for rendering text
+ * into the ScrollWindow.
+ */
+ TextAlign _alignment;
+
+ /**
+ * The visibility of the ScrollWindow.
+ */
+ bool _visible;
+
+ /**
+ * The dimensions of the text box inside the font
+ * bitmap, in text-system coordinates.
+ */
+ Common::Rect _textRect;
+
+ /**
+ * The top-left corner of the ScrollWindow's screen
+ * item, in game script coordinates, relative to the
+ * parent plane.
+ */
+ Common::Point _position;
+
+ /**
+ * The height of the default font in screen pixels. All
+ * fonts rendered into the ScrollWindow must have this
+ * same height.
+ */
+ uint8 _pointSize;
+
+ /**
+ * The bitmap used to render text.
+ */
+ reg_t _bitmap;
+
+ /**
+ * A monotonically increasing ID used to identify
+ * text entries added to the ScrollWindow.
+ */
+ uint16 _nextEntryId;
+
+ /**
+ * The ScrollWindow's screen item.
+ */
+ ScreenItem *_screenItem;
+};
+
/**
* Controls class, handles drawing of controls in SCI32 (SCI2, SCI2.1, SCI3) games
*/
@@ -37,12 +412,98 @@ public:
GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text);
~GfxControls32();
- void kernelTexteditChange(reg_t controlObject);
-
private:
SegManager *_segMan;
- GfxCache *_cache;
- GfxText32 *_text;
+ GfxCache *_gfxCache;
+ GfxText32 *_gfxText32;
+
+#pragma mark -
+#pragma mark Garbage collection
+public:
+ Common::Array<reg_t> listObjectReferences();
+
+#pragma mark -
+#pragma mark Text input control
+public:
+ reg_t kernelEditText(const reg_t controlObject);
+
+private:
+ /**
+ * If true, typing will overwrite text that already
+ * exists at the text cursor's current position.
+ */
+ bool _overwriteMode;
+
+ /**
+ * The tick at which the text cursor should be toggled
+ * by `flashCursor`.
+ */
+ uint32 _nextCursorFlashTick;
+
+ /**
+ * Draws the text cursor for the given editor.
+ */
+ void drawCursor(TextEditor &editor);
+
+ /**
+ * Erases the text cursor for the given editor.
+ */
+ void eraseCursor(TextEditor &editor);
+
+ /**
+ * Toggles the text cursor for the given editor to be
+ * either drawn or erased.
+ */
+ void flashCursor(TextEditor &editor);
+
+#pragma mark -
+#pragma mark Scrollable window control
+public:
+ /**
+ * Creates a new scrollable window and returns the ID
+ * for the new window, which is used by game scripts to
+ * interact with scrollable windows.
+ */
+ reg_t makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries);
+
+ /**
+ * Gets a registered ScrollWindow instance by ID.
+ */
+ ScrollWindow *getScrollWindow(const reg_t id);
+
+ /**
+ * Destroys the scroll window with the given ID.
+ */
+ void destroyScrollWindow(const reg_t id);
+
+private:
+ typedef Common::HashMap<uint16, ScrollWindow *> ScrollWindowMap;
+
+ /**
+ * Monotonically increasing ID used to identify
+ * ScrollWindow instances.
+ */
+ uint16 _nextScrollWindowId;
+
+ /**
+ * A lookup table for registered ScrollWindow instances.
+ */
+ ScrollWindowMap _scrollWindows;
+
+#pragma mark -
+#pragma mark Message box
+public:
+ /**
+ * Displays an OS-level message dialog.
+ */
+ reg_t kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style);
+
+private:
+ /**
+ * Convenience function for creating and showing a
+ * message box.
+ */
+ int16 showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp
index 1a58de073c..f5dd473959 100644
--- a/engines/sci/graphics/cursor.cpp
+++ b/engines/sci/graphics/cursor.cpp
@@ -336,6 +336,9 @@ void GfxCursor::setPosition(Common::Point pos) {
&& ((workaround->newPositionX == pos.x) && (workaround->newPositionY == pos.y))) {
EngineState *s = g_sci->getEngineState();
s->_cursorWorkaroundActive = true;
+ // At least on OpenPandora it seems that the cursor is actually set, but a bit afterwards
+ // touch screen controls will overwrite the position. More information see kGetEvent in kevent.cpp.
+ s->_cursorWorkaroundPosCount = 5; // should be enough for OpenPandora
s->_cursorWorkaroundPoint = pos;
s->_cursorWorkaroundRect = Common::Rect(workaround->rectLeft, workaround->rectTop, workaround->rectRight, workaround->rectBottom);
return;
@@ -453,6 +456,15 @@ void GfxCursor::kernelClearZoomZone() {
void GfxCursor::kernelSetZoomZone(byte multiplier, Common::Rect zone, GuiResourceId viewNum, int loopNum, int celNum, GuiResourceId picNum, byte zoomColor) {
kernelClearZoomZone();
+ // This function is a stub in the Mac version of Freddy Pharkas.
+ // This function was only used in two games (LB2 and Pharkas), but there
+ // was no version of LB2 for the Macintosh platform.
+ // CHECKME: This wasn't verified against disassembly, one might want
+ // to check against it, in case there's some leftover code in the stubbed
+ // function (although it does seem that this was completely removed).
+ if (g_sci->getPlatform() == Common::kPlatformMacintosh)
+ return;
+
_zoomMultiplier = multiplier;
if (_zoomMultiplier != 1 && _zoomMultiplier != 2 && _zoomMultiplier != 4)
diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h
index c2d7998eb3..5125469cfe 100644
--- a/engines/sci/graphics/cursor.h
+++ b/engines/sci/graphics/cursor.h
@@ -25,6 +25,8 @@
#include "common/array.h"
#include "common/hashmap.h"
+#include "sci/sci.h"
+#include "sci/graphics/helpers.h"
namespace Sci {
@@ -77,8 +79,18 @@ public:
*/
void kernelSetMoveZone(Common::Rect zone);
- void kernelClearZoomZone();
+ /**
+ * Creates a dynamic zoom cursor, that is used to zoom on specific parts of the screen,
+ * using a separate larger picture. This was only used by two SCI1.1 games, Laura Bow 2
+ * (for examining the glyphs), and Freddy Pharkas (for examining the prescription with
+ * the whisky glass).
+ *
+ * In the Mac version of Freddy Pharkas, this was removed completely, and the scene has
+ * been redesigned to work without this functionality. There was no version of LB2 for
+ * the Macintosh platform.
+ */
void kernelSetZoomZone(byte multiplier, Common::Rect zone, GuiResourceId viewNum, int loopNum, int celNum, GuiResourceId picNum, byte zoomColor);
+ void kernelClearZoomZone();
void kernelSetPos(Common::Point pos);
void kernelMoveCursor(Common::Point pos);
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index ccc362dc37..a7899b8d89 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -21,9 +21,10 @@
*/
#include "common/algorithm.h"
+#include "common/config-manager.h"
#include "common/events.h"
#include "common/keyboard.h"
-#include "common/list_intern.h"
+#include "common/list.h"
#include "common/str.h"
#include "common/system.h"
#include "common/textconsole.h"
@@ -41,867 +42,1717 @@
#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/font.h"
-#include "sci/graphics/view.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/paint32.h"
-#include "sci/graphics/palette.h"
-#include "sci/graphics/picture.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/remap32.h"
+#include "sci/graphics/screen_item32.h"
#include "sci/graphics/text32.h"
#include "sci/graphics/frameout.h"
#include "sci/video/robot_decoder.h"
namespace Sci {
-// TODO/FIXME: This is all guesswork
-
-enum SciSpeciaPlanelPictureCodes {
- kPlaneTranslucent = 0xfffe, // -2
- kPlanePlainColored = 0xffff // -1
+static int dissolveSequences[2][20] = {
+ /* SCI2.1early- */ { 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080 },
+ /* SCI2.1mid+ */ { 0, 0, 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080, 73728, 132096, 466944 }
+};
+static int16 divisionsDefaults[2][16] = {
+ /* SCI2.1early- */ { 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 40, 40, 101, 101 },
+ /* SCI2.1mid+ */ { 1, 20, 20, 20, 20, 10, 10, 10, 10, 20, 20, 6, 10, 101, 101, 2 }
};
+static int16 unknownCDefaults[2][16] = {
+ /* SCI2.1early- */ { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 0, 0, 0, 0 },
+ /* SCI2.1mid+ */ { 0, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 0, 0, 7, 7, 0 }
+};
+
+GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette) :
+ _isHiRes(false),
+ _palette(palette),
+ _resMan(resMan),
+ _screen(screen),
+ _segMan(segMan),
+ _benchmarkingFinished(false),
+ _throttleFrameOut(true),
+ _showStyles(nullptr),
+ _throttleState(0),
+ // TODO: Stop using _gfxScreen
+ _currentBuffer(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr),
+ _remapOccurred(false),
+ _frameNowVisible(false),
+ _screenRect(screen->getDisplayWidth(), screen->getDisplayHeight()),
+ _overdrawThreshold(0),
+ _palMorphIsOn(false) {
+
+ _currentBuffer.setPixels(calloc(1, screen->getDisplayWidth() * screen->getDisplayHeight()));
+
+ for (int i = 0; i < 236; i += 2) {
+ _styleRanges[i] = 0;
+ _styleRanges[i + 1] = -1;
+ }
+ for (int i = 236; i < ARRAYSIZE(_styleRanges); ++i) {
+ _styleRanges[i] = 0;
+ }
+
+ // TODO: Make hires detection work uniformly across all SCI engine
+ // versions (this flag is normally passed by SCI::MakeGraphicsMgr
+ // to the GraphicsMgr constructor depending upon video configuration,
+ // so should be handled upstream based on game configuration instead
+ // of here)
+ if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _resMan->detectHires()) {
+ _isHiRes = true;
+ }
-GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette *palette, GfxPaint32 *paint32)
- : _segMan(segMan), _resMan(resMan), _cache(cache), _screen(screen), _palette(palette), _paint32(paint32) {
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ _dissolveSequenceSeeds = dissolveSequences[0];
+ _defaultDivisions = divisionsDefaults[0];
+ _defaultUnknownC = unknownCDefaults[0];
+ } else {
+ _dissolveSequenceSeeds = dissolveSequences[1];
+ _defaultDivisions = divisionsDefaults[1];
+ _defaultUnknownC = unknownCDefaults[1];
+ }
+ switch (g_sci->getGameId()) {
+ case GID_HOYLE5:
+ case GID_GK2:
+ case GID_LIGHTHOUSE:
+ case GID_LSL7:
+ case GID_PHANTASMAGORIA2:
+ case GID_PQSWAT:
+ case GID_TORIN:
+ case GID_RAMA:
+ _currentBuffer.scriptWidth = 640;
+ _currentBuffer.scriptHeight = 480;
+ break;
+ default:
+ // default script width for other games is 320x200
+ break;
+ }
+
+ // TODO: Nothing in the renderer really uses this. Currently,
+ // the cursor renderer does, and kLocalToGlobal/kGlobalToLocal
+ // do, but in the real engine (1) the cursor is handled in
+ // frameOut, and (2) functions do a very simple lookup of the
+ // plane and arithmetic with the plane's gameRect. In
+ // principle, CoordAdjuster could be reused for
+ // convertGameRectToPlaneRect, but it is not super clear yet
+ // what the benefit would be to do that.
_coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster;
- _curScrollText = -1;
- _showScrollText = false;
- _maxScrollTexts = 0;
+
+ // TODO: Script resolution is hard-coded per game;
+ // also this must be set or else the engine will crash
+ _coordAdjuster->setScriptsResolution(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight);
}
GfxFrameout::~GfxFrameout() {
clear();
+ CelObj::deinit();
+ free(_currentBuffer.getPixels());
}
+void GfxFrameout::run() {
+ CelObj::init();
+ Plane::init();
+ ScreenItem::init();
+
+ // NOTE: This happens in SCI::InitPlane in the actual engine,
+ // and is a background fill plane to ensure hidden planes
+ // (planes with a priority of -1) are never drawn
+ Plane *initPlane = new Plane(Common::Rect(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight));
+ initPlane->_priority = 0;
+ _planes.add(initPlane);
+}
+
+// SCI32 actually did not clear anything at all it seems on restore. The scripts actually cleared up
+// planes + screen items right before restoring. And after restoring they sync'd its internal planes list
+// as well.
void GfxFrameout::clear() {
- deletePlaneItems(NULL_REG);
_planes.clear();
- deletePlanePictures(NULL_REG);
- clearScrollTexts();
-}
-
-void GfxFrameout::clearScrollTexts() {
- _scrollTexts.clear();
- _curScrollText = -1;
-}
-
-void GfxFrameout::addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace) {
- //reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow);
- // HACK: We set the container dimensions manually
- reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow, 480, 70);
- ScrollTextEntry textEntry;
- textEntry.bitmapHandle = bitmapHandle;
- textEntry.kWindow = kWindow;
- textEntry.x = x;
- textEntry.y = y;
- if (!replace || _scrollTexts.size() == 0) {
- if (_scrollTexts.size() > _maxScrollTexts) {
- _scrollTexts.remove_at(0);
- _curScrollText--;
- }
- _scrollTexts.push_back(textEntry);
- _curScrollText++;
- } else {
- _scrollTexts.pop_back();
- _scrollTexts.push_back(textEntry);
- }
+ _visiblePlanes.clear();
+ _showList.clear();
}
-void GfxFrameout::showCurrentScrollText() {
- if (!_showScrollText || _curScrollText < 0)
+// This is what Game::restore does, only needed when our ScummVM dialogs are patched in
+// It actually does one pass before actual restore deleting screen items + planes
+// And after restore it does another pass adding screen items + planes.
+// Attention: at least Space Quest 6's option plane seems to stay in memory right from the start and is not re-created.
+void GfxFrameout::syncWithScripts(bool addElements) {
+ EngineState *engineState = g_sci->getEngineState();
+ SegManager *segMan = engineState->_segMan;
+
+ // In case original save/restore dialogs are active, don't do anything
+ if (ConfMan.getBool("originalsaveload"))
return;
- uint16 size = (uint16)_scrollTexts.size();
- if (size > 0) {
- assert(_curScrollText < size);
- ScrollTextEntry textEntry = _scrollTexts[_curScrollText];
- g_sci->_gfxText32->drawScrollTextBitmap(textEntry.kWindow, textEntry.bitmapHandle, textEntry.x, textEntry.y);
- }
-}
-
-extern void showScummVMDialog(const Common::String &message);
-
-void GfxFrameout::kernelAddPlane(reg_t object) {
- PlaneEntry newPlane;
-
- if (_planes.empty()) {
- // There has to be another way for sierra sci to do this or maybe script resolution is compiled into
- // interpreter (TODO)
- uint16 scriptWidth = readSelectorValue(_segMan, object, SELECTOR(resX));
- uint16 scriptHeight = readSelectorValue(_segMan, object, SELECTOR(resY));
-
- // Phantasmagoria 2 doesn't specify a script width/height
- if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
- scriptWidth = 640;
- scriptHeight = 480;
- }
-
- assert(scriptWidth > 0 && scriptHeight > 0);
- _coordAdjuster->setScriptsResolution(scriptWidth, scriptHeight);
- }
-
- // Import of QfG character files dialog is shown in QFG4.
- // Display additional popup information before letting user use it.
- // For the SCI0-SCI1.1 version of this, check kDrawControl().
- if (g_sci->inQfGImportRoom() && !strcmp(_segMan->getObjectName(object), "DSPlane")) {
- showScummVMDialog("Characters saved inside ScummVM are shown "
- "automatically. Character files saved in the original "
- "interpreter need to be put inside ScummVM's saved games "
- "directory and a prefix needs to be added depending on which "
- "game it was saved in: 'qfg1-' for Quest for Glory 1, 'qfg2-' "
- "for Quest for Glory 2, 'qfg3-' for Quest for Glory 3. "
- "Example: 'qfg2-thief.sav'.");
- }
-
- newPlane.object = object;
- newPlane.priority = readSelectorValue(_segMan, object, SELECTOR(priority));
- newPlane.lastPriority = -1; // hidden
- newPlane.planeOffsetX = 0;
- newPlane.planeOffsetY = 0;
- newPlane.pictureId = kPlanePlainColored;
- newPlane.planePictureMirrored = false;
- newPlane.planeBack = 0;
- _planes.push_back(newPlane);
-
- kernelUpdatePlane(object);
-}
-
-void GfxFrameout::kernelUpdatePlane(reg_t object) {
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- // Read some information
- it->priority = readSelectorValue(_segMan, object, SELECTOR(priority));
- GuiResourceId lastPictureId = it->pictureId;
- it->pictureId = readSelectorValue(_segMan, object, SELECTOR(picture));
- if (lastPictureId != it->pictureId) {
- // picture got changed, load new picture
- deletePlanePictures(object);
- // Draw the plane's picture if it's not a translucent/plane colored frame
- if ((it->pictureId != kPlanePlainColored) && (it->pictureId != kPlaneTranslucent)) {
- // SQ6 gives us a bad picture number for the control menu
- if (_resMan->testResource(ResourceId(kResourceTypePic, it->pictureId)))
- addPlanePicture(object, it->pictureId, 0);
- }
- }
- it->planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top));
- it->planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left));
- it->planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom));
- it->planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right));
-
- _coordAdjuster->fromScriptToDisplay(it->planeRect.top, it->planeRect.left);
- _coordAdjuster->fromScriptToDisplay(it->planeRect.bottom, it->planeRect.right);
-
- // We get negative left in kq7 in scrolling rooms
- if (it->planeRect.left < 0) {
- it->planeOffsetX = -it->planeRect.left;
- it->planeRect.left = 0;
- } else {
- it->planeOffsetX = 0;
- }
+ // Get planes list object
+ reg_t planesListObject = engineState->variables[VAR_GLOBAL][10];
+ reg_t planesListElements = readSelector(segMan, planesListObject, SELECTOR(elements));
- if (it->planeRect.top < 0) {
- it->planeOffsetY = -it->planeRect.top;
- it->planeRect.top = 0;
- } else {
- it->planeOffsetY = 0;
- }
+ List *planesList = segMan->lookupList(planesListElements);
+ reg_t planesNodeObject = planesList->first;
- // We get bad plane-bottom in sq6
- if (it->planeRect.right > _screen->getWidth())
- it->planeRect.right = _screen->getWidth();
- if (it->planeRect.bottom > _screen->getHeight())
- it->planeRect.bottom = _screen->getHeight();
-
- it->planeClipRect = Common::Rect(it->planeRect.width(), it->planeRect.height());
- it->upscaledPlaneRect = it->planeRect;
- it->upscaledPlaneClipRect = it->planeClipRect;
- if (_screen->getUpscaledHires()) {
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.top, it->upscaledPlaneRect.left);
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.bottom, it->upscaledPlaneRect.right);
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.top, it->upscaledPlaneClipRect.left);
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.bottom, it->upscaledPlaneClipRect.right);
- }
+ // Go through all elements of planes::elements
+ while (!planesNodeObject.isNull()) {
+ Node *planesNode = segMan->lookupNode(planesNodeObject);
+ reg_t planeObject = planesNode->value;
+
+ if (addElements) {
+ // Add this plane object
+ kernelAddPlane(planeObject);
+ }
- it->planePictureMirrored = readSelectorValue(_segMan, object, SELECTOR(mirrored));
- it->planeBack = readSelectorValue(_segMan, object, SELECTOR(back));
+ reg_t planeCastsObject = readSelector(segMan, planeObject, SELECTOR(casts));
+ reg_t setListElements = readSelector(segMan, planeCastsObject, SELECTOR(elements));
- sortPlanes();
+ // Now go through all elements of plane::casts::elements
+ List *planeCastsList = segMan->lookupList(setListElements);
+ reg_t planeCastsNodeObject = planeCastsList->first;
- // Update the items in the plane
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
- if (object == itemPlane) {
- kernelUpdateScreenItem((*listIterator)->object);
+ while (!planeCastsNodeObject.isNull()) {
+ Node *castsNode = segMan->lookupNode(planeCastsNodeObject);
+ reg_t castsObject = castsNode->value;
+
+ reg_t castsListElements = readSelector(segMan, castsObject, SELECTOR(elements));
+
+ List *castsList = segMan->lookupList(castsListElements);
+ reg_t castNodeObject = castsList->first;
+
+ while (!castNodeObject.isNull()) {
+ Node *castNode = segMan->lookupNode(castNodeObject);
+ reg_t castObject = castNode->value;
+
+ // read selector "-info-" of this object
+ // TODO: Seems to have been changed for SCI3
+ // Do NOT use getInfoSelector in here. SCI3 games did not use infoToa, but an actual selector.
+ // Maybe that selector is just a straight copy, but it needs to get verified/checked.
+ uint16 castInfoSelector = readSelectorValue(segMan, castObject, SELECTOR(_info_));
+
+ if (castInfoSelector & kInfoFlagViewInserted) {
+ if (addElements) {
+ // Flag set, so add this screen item
+ kernelAddScreenItem(castObject);
+ } else {
+ // Flag set, so delete this screen item
+ kernelDeleteScreenItem(castObject);
+ }
}
+
+ castNodeObject = castNode->succ;
}
- return;
+ planeCastsNodeObject = castsNode->succ;
+ }
+
+ if (!addElements) {
+ // Delete this plane object
+ kernelDeletePlane(planeObject);
}
+
+ planesNodeObject = planesNode->succ;
}
- error("kUpdatePlane called on plane that wasn't added before");
}
-void GfxFrameout::kernelDeletePlane(reg_t object) {
- deletePlaneItems(object);
- deletePlanePictures(object);
+#pragma mark -
+#pragma mark Benchmarking
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- _planes.erase(it);
- Common::Rect planeRect;
- planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top));
- planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left));
- planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom));
- planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right));
+bool GfxFrameout::checkForFred(const reg_t object) {
+ const int16 viewId = readSelectorValue(_segMan, object, SELECTOR(view));
+ const SciGameId gameId = g_sci->getGameId();
- _coordAdjuster->fromScriptToDisplay(planeRect.top, planeRect.left);
- _coordAdjuster->fromScriptToDisplay(planeRect.bottom, planeRect.right);
+ if (gameId == GID_QFG4 && viewId == 9999) {
+ return true;
+ }
- // Blackout removed plane rect
- _paint32->fillRect(planeRect, 0);
- return;
- }
+ if (gameId != GID_QFG4 && viewId == -556) {
+ return true;
}
+
+ if (Common::String(_segMan->getObjectName(object)) == "fred") {
+ return true;
+ }
+
+ return false;
}
-void GfxFrameout::addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY) {
- if (pictureId == kPlanePlainColored || pictureId == kPlaneTranslucent) // sanity check
- return;
+#pragma mark -
+#pragma mark Screen items
- PlanePictureEntry newPicture;
- newPicture.object = object;
- newPicture.pictureId = pictureId;
- newPicture.picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false);
- newPicture.startX = startX;
- newPicture.startY = startY;
- newPicture.pictureCels = 0;
- _planePictures.push_back(newPicture);
+void GfxFrameout::addScreenItem(ScreenItem &screenItem) const {
+ Plane *plane = _planes.findByObject(screenItem._plane);
+ if (plane == nullptr) {
+ error("GfxFrameout::addScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
+ }
+ plane->_screenItemList.add(&screenItem);
}
-void GfxFrameout::deletePlanePictures(reg_t object) {
- PlanePictureList::iterator it = _planePictures.begin();
+void GfxFrameout::updateScreenItem(ScreenItem &screenItem) const {
+ // TODO: In SCI3+ this will need to go through Plane
+// Plane *plane = _planes.findByObject(screenItem._plane);
+// if (plane == nullptr) {
+// error("GfxFrameout::updateScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
+// }
- while (it != _planePictures.end()) {
- if (it->object == object || object.isNull()) {
- delete it->pictureCels;
- delete it->picture;
- it = _planePictures.erase(it);
- } else {
- ++it;
- }
+ screenItem.update();
+}
+
+void GfxFrameout::deleteScreenItem(ScreenItem &screenItem) {
+ Plane *plane = _planes.findByObject(screenItem._plane);
+ if (plane == nullptr) {
+ error("GfxFrameout::deleteScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
}
+ if (plane->_screenItemList.findByObject(screenItem._object) == nullptr) {
+ error("GfxFrameout::deleteScreenItem: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItem._object), PRINT_REG(screenItem._plane));
+ }
+ deleteScreenItem(screenItem, *plane);
}
-// Provides the same functionality as kGraph(DrawLine)
-reg_t GfxFrameout::addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) {
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- PlaneLineEntry line;
- line.hunkId = _segMan->allocateHunkEntry("PlaneLine()", 1); // we basically use this for a unique ID
- line.startPoint = startPoint;
- line.endPoint = endPoint;
- line.color = color;
- line.priority = priority;
- line.control = control;
- it->lines.push_back(line);
- return line.hunkId;
- }
+void GfxFrameout::deleteScreenItem(ScreenItem &screenItem, Plane &plane) {
+ if (screenItem._created == 0) {
+ screenItem._created = 0;
+ screenItem._updated = 0;
+ screenItem._deleted = getScreenCount();
+ } else {
+ plane._screenItemList.erase(&screenItem);
+ plane._screenItemList.pack();
}
+}
- return NULL_REG;
+void GfxFrameout::deleteScreenItem(ScreenItem &screenItem, const reg_t planeObject) {
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("GfxFrameout::deleteScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(screenItem._object));
+ }
+ deleteScreenItem(screenItem, *plane);
}
-void GfxFrameout::updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) {
- // Check if we're asked to update a line that was never added
- if (hunkId.isNull())
- return;
+void GfxFrameout::kernelAddScreenItem(const reg_t object) {
+ // The "fred" object is used to test graphics performance;
+ // it is impacted by framerate throttling, so disable the
+ // throttling when this item is on the screen for the
+ // performance check to pass.
+ if (!_benchmarkingFinished && _throttleFrameOut && checkForFred(object)) {
+ _throttleFrameOut = false;
+ }
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
- if (it2->hunkId == hunkId) {
- it2->startPoint = startPoint;
- it2->endPoint = endPoint;
- it2->color = color;
- it2->priority = priority;
- it2->control = control;
- return;
- }
- }
- }
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+
+ _segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted);
+
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kAddScreenItem: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(object));
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem != nullptr) {
+ screenItem->update(object);
+ } else {
+ screenItem = new ScreenItem(object);
+ plane->_screenItemList.add(screenItem);
}
}
-void GfxFrameout::deletePlaneLine(reg_t object, reg_t hunkId) {
- // Check if we're asked to delete a line that was never added (happens during the intro of LSL6)
- if (hunkId.isNull())
- return;
+void GfxFrameout::kernelUpdateScreenItem(const reg_t object) {
+ const reg_t magnifierObject = readSelector(_segMan, object, SELECTOR(magnifier));
+ if (magnifierObject.isNull()) {
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kUpdateScreenItem: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(object));
+ }
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
- if (it2->hunkId == hunkId) {
- _segMan->freeHunkEntry(hunkId);
- it2 = it->lines.erase(it2);
- return;
- }
- }
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem == nullptr) {
+ error("kUpdateScreenItem: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject));
}
+
+ screenItem->update(object);
+ } else {
+ error("Magnifier view is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
}
}
-// Adapted from GfxAnimate::applyGlobalScaling()
-void GfxFrameout::applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight) {
- // Global scaling uses global var 2 and some other stuff to calculate scaleX/scaleY
- int16 maxScale = readSelectorValue(_segMan, itemEntry->object, SELECTOR(maxScale));
- int16 maxCelHeight = (maxScale * celHeight) >> 7;
- reg_t globalVar2 = g_sci->getEngineState()->variables[VAR_GLOBAL][2]; // current room object
- int16 vanishingY = readSelectorValue(_segMan, globalVar2, SELECTOR(vanishingY));
+void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
+ // The "fred" object is used to test graphics performance;
+ // it is impacted by framerate throttling, so disable the
+ // throttling when this item is on the screen for the
+ // performance check to pass.
+ if (!_benchmarkingFinished && checkForFred(object)) {
+ _benchmarkingFinished = true;
+ _throttleFrameOut = true;
+ }
- int16 fixedPortY = planeRect.bottom - vanishingY;
- int16 fixedEntryY = itemEntry->y - vanishingY;
- if (!fixedEntryY)
- fixedEntryY = 1;
+ _segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted);
- if ((celHeight == 0) || (fixedPortY == 0))
- error("global scaling panic");
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ return;
+ }
- itemEntry->scaleY = (maxCelHeight * fixedEntryY) / fixedPortY;
- itemEntry->scaleY = (itemEntry->scaleY * maxScale) / celHeight;
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem == nullptr) {
+ return;
+ }
- // Make sure that the calculated value is sane
- if (itemEntry->scaleY < 1 /*|| itemEntry->scaleY > 128*/)
- itemEntry->scaleY = 128;
+ deleteScreenItem(*screenItem, *plane);
+}
- itemEntry->scaleX = itemEntry->scaleY;
+#pragma mark -
+#pragma mark Planes
- // and set objects scale selectors
- //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleX), itemEntry->scaleX);
- //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleY), itemEntry->scaleY);
+void GfxFrameout::kernelAddPlane(const reg_t object) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane != nullptr) {
+ plane->update(object);
+ updatePlane(*plane);
+ } else {
+ plane = new Plane(object);
+ addPlane(*plane);
+ }
}
-void GfxFrameout::kernelAddScreenItem(reg_t object) {
- // Ignore invalid items
- if (!_segMan->isObject(object)) {
- warning("kernelAddScreenItem: Attempt to add an invalid object (%04x:%04x)", PRINT_REG(object));
- return;
+void GfxFrameout::kernelUpdatePlane(const reg_t object) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane == nullptr) {
+ error("kUpdatePlane: Plane %04x:%04x not found", PRINT_REG(object));
}
- FrameoutEntry *itemEntry = new FrameoutEntry();
- memset(itemEntry, 0, sizeof(FrameoutEntry));
- itemEntry->object = object;
- itemEntry->givenOrderNr = _screenItems.size();
- itemEntry->visible = true;
- _screenItems.push_back(itemEntry);
+ plane->update(object);
+ updatePlane(*plane);
+}
- kernelUpdateScreenItem(object);
+void GfxFrameout::kernelDeletePlane(const reg_t object) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane == nullptr) {
+ error("kDeletePlane: Plane %04x:%04x not found", PRINT_REG(object));
+ }
+
+ if (plane->_created) {
+ // NOTE: The original engine calls some `AbortPlane` function that
+ // just ends up doing this anyway so we skip the extra indirection
+ _planes.erase(plane);
+ } else {
+ plane->_created = 0;
+ plane->_deleted = g_sci->_gfxFrameout->getScreenCount();
+ }
}
-void GfxFrameout::kernelUpdateScreenItem(reg_t object) {
- // Ignore invalid items
- if (!_segMan->isObject(object)) {
- warning("kernelUpdateScreenItem: Attempt to update an invalid object (%04x:%04x)", PRINT_REG(object));
- return;
+void GfxFrameout::deletePlane(Plane &planeToFind) {
+ Plane *plane = _planes.findByObject(planeToFind._object);
+ if (plane == nullptr) {
+ error("deletePlane: Plane %04x:%04x not found", PRINT_REG(planeToFind._object));
}
- FrameoutEntry *itemEntry = findScreenItem(object);
- if (!itemEntry) {
- warning("kernelUpdateScreenItem: invalid object %04x:%04x", PRINT_REG(object));
- return;
+ if (plane->_created) {
+ _planes.erase(plane);
+ } else {
+ plane->_created = 0;
+ plane->_moved = 0;
+ plane->_deleted = getScreenCount();
+ }
+}
+
+void GfxFrameout::kernelMovePlaneItems(const reg_t object, const int16 deltaX, const int16 deltaY, const bool scrollPics) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane == nullptr) {
+ error("kMovePlaneItems: Plane %04x:%04x not found", PRINT_REG(object));
}
- itemEntry->viewId = readSelectorValue(_segMan, object, SELECTOR(view));
- itemEntry->loopNo = readSelectorValue(_segMan, object, SELECTOR(loop));
- itemEntry->celNo = readSelectorValue(_segMan, object, SELECTOR(cel));
- itemEntry->x = readSelectorValue(_segMan, object, SELECTOR(x));
- itemEntry->y = readSelectorValue(_segMan, object, SELECTOR(y));
- itemEntry->z = readSelectorValue(_segMan, object, SELECTOR(z));
- itemEntry->priority = readSelectorValue(_segMan, object, SELECTOR(priority));
- if (readSelectorValue(_segMan, object, SELECTOR(fixPriority)) == 0)
- itemEntry->priority = itemEntry->y;
+ plane->scrollScreenItems(deltaX, deltaY, scrollPics);
- itemEntry->signal = readSelectorValue(_segMan, object, SELECTOR(signal));
- itemEntry->scaleSignal = readSelectorValue(_segMan, object, SELECTOR(scaleSignal));
+ for (ScreenItemList::iterator it = plane->_screenItemList.begin(); it != plane->_screenItemList.end(); ++it) {
+ ScreenItem &screenItem = **it;
- if (itemEntry->scaleSignal & kScaleSignalDoScaling32) {
- itemEntry->scaleX = readSelectorValue(_segMan, object, SELECTOR(scaleX));
- itemEntry->scaleY = readSelectorValue(_segMan, object, SELECTOR(scaleY));
+ // If object is a number, the screen item from the
+ // engine, not a script, and should be ignored
+ if (screenItem._object.isNumber()) {
+ continue;
+ }
+
+ if (deltaX != 0) {
+ writeSelectorValue(_segMan, screenItem._object, SELECTOR(x), readSelectorValue(_segMan, screenItem._object, SELECTOR(x)) + deltaX);
+ }
+
+ if (deltaY != 0) {
+ writeSelectorValue(_segMan, screenItem._object, SELECTOR(y), readSelectorValue(_segMan, screenItem._object, SELECTOR(y)) + deltaY);
+ }
+ }
+}
+
+int16 GfxFrameout::kernelGetHighPlanePri() {
+ return _planes.getTopSciPlanePriority();
+}
+
+void GfxFrameout::addPlane(Plane &plane) {
+ if (_planes.findByObject(plane._object) == nullptr) {
+ plane.clipScreenRect(_screenRect);
+ _planes.add(&plane);
} else {
- itemEntry->scaleX = 128;
- itemEntry->scaleY = 128;
+ plane._deleted = 0;
+ if (plane._created == 0) {
+ plane._moved = g_sci->_gfxFrameout->getScreenCount();
+ }
+ _planes.sort();
}
- itemEntry->visible = true;
+}
+
+void GfxFrameout::updatePlane(Plane &plane) {
+ // NOTE: This assertion comes from SCI engine code.
+ assert(_planes.findByObject(plane._object) == &plane);
- // Check if the entry can be hidden
- if (lookupSelector(_segMan, object, SELECTOR(visible), NULL, NULL) != kSelectorNone)
- itemEntry->visible = readSelectorValue(_segMan, object, SELECTOR(visible));
+ Plane *visiblePlane = _visiblePlanes.findByObject(plane._object);
+ plane.sync(visiblePlane, _screenRect);
+ // NOTE: updateScreenRect was originally called a second time here,
+ // but it is already called at the end of the Plane::Update call
+ // in the original engine anyway.
+
+ _planes.sort();
}
-void GfxFrameout::kernelDeleteScreenItem(reg_t object) {
- FrameoutEntry *itemEntry = findScreenItem(object);
- // If the item could not be found, it may already have been deleted
- if (!itemEntry)
- return;
+#pragma mark -
+#pragma mark Pics
+
+void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 x, const int16 y, const bool mirrorX) {
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kAddPicAt: Plane %04x:%04x not found", PRINT_REG(planeObject));
+ }
+ plane->addPic(pictureId, Common::Point(x, y), mirrorX);
+}
+
+#pragma mark -
+#pragma mark Rendering
+
+void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) {
+// TODO: Robot
+// if (_robot != nullptr) {
+// _robot.doRobot();
+// }
+
+ // NOTE: The original engine allocated these as static arrays of 100
+ // pointers to ScreenItemList / RectList
+ ScreenItemListList screenItemLists;
+ EraseListList eraseLists;
+
+ screenItemLists.resize(_planes.size());
+ eraseLists.resize(_planes.size());
+
+ if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
+ remapMarkRedraw();
+ }
+
+ calcLists(screenItemLists, eraseLists, eraseRect);
+
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
+ }
+
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
+ }
+ }
+
+ _remapOccurred = _palette->updateForFrame();
+
+ // NOTE: SCI engine set this to false on each loop through the
+ // planelist iterator below. Since that is a waste, we only set
+ // it once.
+ _frameNowVisible = false;
+
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
+ }
+
+// TODO: Robot
+// if (_robot != nullptr) {
+// _robot->frameAlmostVisible();
+// }
+
+ _palette->updateHardware(!shouldShowBits);
+
+ if (shouldShowBits) {
+ showBits();
+ }
- _screenItems.remove(itemEntry);
- delete itemEntry;
+ _frameNowVisible = true;
+
+// TODO: Robot
+// if (_robot != nullptr) {
+// robot->frameNowVisible();
+// }
}
-void GfxFrameout::deletePlaneItems(reg_t planeObject) {
- FrameoutList::iterator listIterator = _screenItems.begin();
+/**
+ * Determines the parts of `r` that aren't overlapped by `other`.
+ * Returns -1 if `r` and `other` have no intersection.
+ * Returns number of returned parts (in `outRects`) otherwise.
+ * (In particular, this returns 0 if `r` is contained in `other`.)
+ */
+int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) {
+ if (!r.intersects(other)) {
+ return -1;
+ }
+
+ int splitCount = 0;
+ if (r.top < other.top) {
+ Common::Rect &t = outRects[splitCount++];
+ t = r;
+ t.bottom = other.top;
+ r.top = other.top;
+ }
+
+ if (r.bottom > other.bottom) {
+ Common::Rect &t = outRects[splitCount++];
+ t = r;
+ t.top = other.bottom;
+ r.bottom = other.bottom;
+ }
+
+ if (r.left < other.left) {
+ Common::Rect &t = outRects[splitCount++];
+ t = r;
+ t.right = other.left;
+ r.left = other.left;
+ }
+
+ if (r.right > other.right) {
+ Common::Rect &t = outRects[splitCount++];
+ t = r;
+ t.left = other.right;
+ }
- while (listIterator != _screenItems.end()) {
- bool objectMatches = false;
- if (!planeObject.isNull()) {
- reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
- objectMatches = (planeObject == itemPlane);
+ return splitCount;
+}
+
+/**
+ * Determines the parts of `middleRect` that aren't overlapped
+ * by `showRect`, optimised for contiguous memory writes.
+ * Returns -1 if `middleRect` and `showRect` have no intersection.
+ * Returns number of returned parts (in `outRects`) otherwise.
+ * (In particular, this returns 0 if `middleRect` is contained
+ * in `other`.)
+ *
+ * `middleRect` is modified directly to extend into the upper
+ * and lower rects.
+ */
+int splitRectsForRender(Common::Rect &middleRect, const Common::Rect &showRect, Common::Rect(&outRects)[2]) {
+ if (!middleRect.intersects(showRect)) {
+ return -1;
+ }
+
+ const int16 minLeft = MIN(middleRect.left, showRect.left);
+ const int16 maxRight = MAX(middleRect.right, showRect.right);
+
+ int16 upperLeft, upperTop, upperRight, upperMaxTop;
+ if (middleRect.top < showRect.top) {
+ upperLeft = middleRect.left;
+ upperTop = middleRect.top;
+ upperRight = middleRect.right;
+ upperMaxTop = showRect.top;
+ }
+ else {
+ upperLeft = showRect.left;
+ upperTop = showRect.top;
+ upperRight = showRect.right;
+ upperMaxTop = middleRect.top;
+ }
+
+ int16 lowerLeft, lowerRight, lowerBottom, lowerMinBottom;
+ if (middleRect.bottom > showRect.bottom) {
+ lowerLeft = middleRect.left;
+ lowerRight = middleRect.right;
+ lowerBottom = middleRect.bottom;
+ lowerMinBottom = showRect.bottom;
+ } else {
+ lowerLeft = showRect.left;
+ lowerRight = showRect.right;
+ lowerBottom = showRect.bottom;
+ lowerMinBottom = middleRect.bottom;
+ }
+
+ int splitCount = 0;
+ middleRect.left = minLeft;
+ middleRect.top = upperMaxTop;
+ middleRect.right = maxRight;
+ middleRect.bottom = lowerMinBottom;
+
+ if (upperTop != upperMaxTop) {
+ Common::Rect &upperRect = outRects[0];
+ upperRect.left = upperLeft;
+ upperRect.top = upperTop;
+ upperRect.right = upperRight;
+ upperRect.bottom = upperMaxTop;
+
+ // Merge upper rect into middle rect if possible
+ if (upperRect.left == middleRect.left && upperRect.right == middleRect.right) {
+ middleRect.top = upperRect.top;
} else {
- objectMatches = true;
+ ++splitCount;
}
+ }
+
+ if (lowerBottom != lowerMinBottom) {
+ Common::Rect &lowerRect = outRects[splitCount];
+ lowerRect.left = lowerLeft;
+ lowerRect.top = lowerMinBottom;
+ lowerRect.right = lowerRight;
+ lowerRect.bottom = lowerBottom;
- if (objectMatches) {
- FrameoutEntry *itemEntry = *listIterator;
- listIterator = _screenItems.erase(listIterator);
- delete itemEntry;
+ // Merge lower rect into middle rect if possible
+ if (lowerRect.left == middleRect.left && lowerRect.right == middleRect.right) {
+ middleRect.bottom = lowerRect.bottom;
} else {
- ++listIterator;
+ ++splitCount;
}
}
+
+ assert(splitCount <= 2);
+ return splitCount;
}
-FrameoutEntry *GfxFrameout::findScreenItem(reg_t object) {
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- FrameoutEntry *itemEntry = *listIterator;
- if (itemEntry->object == object)
- return itemEntry;
+// NOTE: The third rectangle parameter is only ever given a non-empty rect
+// by VMD code, via `frameOut`
+void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect) {
+ RectList eraseList;
+ Common::Rect outRects[4];
+ int deletedPlaneCount = 0;
+ bool addedToEraseList = false;
+ bool foundTransparentPlane = false;
+
+ if (!eraseRect.isEmpty()) {
+ addedToEraseList = true;
+ eraseList.add(eraseRect);
}
- return NULL;
-}
+ PlaneList::size_type planeCount = _planes.size();
+ for (PlaneList::size_type outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) {
+ const Plane *outerPlane = _planes[outerPlaneIndex];
+ const Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object);
-int16 GfxFrameout::kernelGetHighPlanePri() {
- sortPlanes();
- return readSelectorValue(g_sci->getEngineState()->_segMan, _planes.back().object, SELECTOR(priority));
-}
+ // NOTE: SSCI only ever checks for kPlaneTypeTransparent here, even
+ // though kPlaneTypeTransparentPicture is also a transparent plane
+ if (outerPlane->_type == kPlaneTypeTransparent) {
+ foundTransparentPlane = true;
+ }
-void GfxFrameout::kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY) {
- addPlanePicture(planeObj, pictureId, pictureX, pictureY);
-}
+ if (outerPlane->_deleted) {
+ if (visiblePlane != nullptr && !visiblePlane->_screenRect.isEmpty()) {
+ eraseList.add(visiblePlane->_screenRect);
+ addedToEraseList = true;
+ }
+ ++deletedPlaneCount;
+ } else if (visiblePlane != nullptr && outerPlane->_moved) {
+ // _moved will be decremented in the final loop through the planes,
+ // at the end of this function
+
+ {
+ const int splitCount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects);
+ if (splitCount) {
+ if (splitCount == -1 && !visiblePlane->_screenRect.isEmpty()) {
+ eraseList.add(visiblePlane->_screenRect);
+ } else {
+ for (int i = 0; i < splitCount; ++i) {
+ eraseList.add(outRects[i]);
+ }
+ }
+ addedToEraseList = true;
+ }
+ }
+
+ if (!outerPlane->_redrawAllCount) {
+ const int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects);
+ if (splitCount) {
+ for (int i = 0; i < splitCount; ++i) {
+ eraseList.add(outRects[i]);
+ }
+ addedToEraseList = true;
+ }
+ }
+ }
-bool sortHelper(const FrameoutEntry* entry1, const FrameoutEntry* entry2) {
- if (entry1->priority == entry2->priority) {
- if (entry1->y == entry2->y)
- return (entry1->givenOrderNr < entry2->givenOrderNr);
- return (entry1->y < entry2->y);
+ if (addedToEraseList) {
+ for (RectList::size_type rectIndex = 0; rectIndex < eraseList.size(); ++rectIndex) {
+ const Common::Rect &rect = *eraseList[rectIndex];
+ for (int innerPlaneIndex = planeCount - 1; innerPlaneIndex >= 0; --innerPlaneIndex) {
+ const Plane &innerPlane = *_planes[innerPlaneIndex];
+
+ if (
+ !innerPlane._deleted &&
+ innerPlane._type != kPlaneTypeTransparent &&
+ innerPlane._screenRect.intersects(rect)
+ ) {
+ if (!innerPlane._redrawAllCount) {
+ eraseLists[innerPlaneIndex].add(innerPlane._screenRect.findIntersectingRect(rect));
+ }
+
+ const int splitCount = splitRects(rect, innerPlane._screenRect, outRects);
+ for (int i = 0; i < splitCount; ++i) {
+ eraseList.add(outRects[i]);
+ }
+
+ eraseList.erase_at(rectIndex);
+ break;
+ }
+ }
+ }
+
+ eraseList.pack();
+ }
}
- return (entry1->priority < entry2->priority);
-}
-bool planeSortHelper(const PlaneEntry &entry1, const PlaneEntry &entry2) {
- if (entry1.priority < 0)
- return true;
+ // clean up deleted planes
+ if (deletedPlaneCount) {
+ for (int planeIndex = planeCount - 1; planeIndex >= 0; --planeIndex) {
+ Plane *plane = _planes[planeIndex];
+
+ if (plane->_deleted) {
+ --plane->_deleted;
+ if (plane->_deleted <= 0) {
+ const int visiblePlaneIndex = _visiblePlanes.findIndexByObject(plane->_object);
+ if (visiblePlaneIndex != -1) {
+ _visiblePlanes.remove_at(visiblePlaneIndex);
+ }
- if (entry2.priority < 0)
- return false;
+ _planes.remove_at(planeIndex);
+ eraseLists.remove_at(planeIndex);
+ drawLists.remove_at(planeIndex);
+ }
- return entry1.priority < entry2.priority;
-}
+ if (--deletedPlaneCount <= 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Some planes may have been deleted, so re-retrieve count
+ planeCount = _planes.size();
+
+ for (PlaneList::size_type outerIndex = 0; outerIndex < planeCount; ++outerIndex) {
+ // "outer" just refers to the outer loop
+ Plane &outerPlane = *_planes[outerIndex];
+ if (outerPlane._priorityChanged) {
+ --outerPlane._priorityChanged;
+
+ const Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane._object);
+ if (visibleOuterPlane == nullptr) {
+ warning("calcLists could not find visible plane for %04x:%04x", PRINT_REG(outerPlane._object));
+ continue;
+ }
-void GfxFrameout::sortPlanes() {
- // First, remove any invalid planes
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end();) {
- if (!_segMan->isObject(it->object))
- it = _planes.erase(it);
- else
- it++;
+ eraseList.add(outerPlane._screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
+
+ for (int innerIndex = (int)planeCount - 1; innerIndex >= 0; --innerIndex) {
+ // "inner" just refers to the inner loop
+ const Plane &innerPlane = *_planes[innerIndex];
+ const Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane._object);
+
+ const RectList::size_type rectCount = eraseList.size();
+ for (RectList::size_type rectIndex = 0; rectIndex < rectCount; ++rectIndex) {
+ const int splitCount = splitRects(*eraseList[rectIndex], innerPlane._screenRect, outRects);
+ if (splitCount == 0) {
+ if (visibleInnerPlane != nullptr) {
+ // same priority, or relative priority between inner/outer changed
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) {
+ if (outerPlane._priority <= innerPlane._priority) {
+ eraseLists[innerIndex].add(*eraseList[rectIndex]);
+ } else {
+ eraseLists[outerIndex].add(*eraseList[rectIndex]);
+ }
+ }
+ }
+
+ eraseList.erase_at(rectIndex);
+ } else if (splitCount != -1) {
+ for (int i = 0; i < splitCount; ++i) {
+ eraseList.add(outRects[i]);
+ }
+
+ if (visibleInnerPlane != nullptr) {
+ // same priority, or relative priority between inner/outer changed
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) {
+ *eraseList[rectIndex] = outerPlane._screenRect.findIntersectingRect(innerPlane._screenRect);
+
+ if (outerPlane._priority <= innerPlane._priority) {
+ eraseLists[innerIndex].add(*eraseList[rectIndex]);
+ } else {
+ eraseLists[outerIndex].add(*eraseList[rectIndex]);
+ }
+ }
+ }
+ eraseList.erase_at(rectIndex);
+ }
+ }
+ eraseList.pack();
+ }
+ }
}
- // Sort the rest of them
- Common::sort(_planes.begin(), _planes.end(), planeSortHelper);
-}
+ for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ Plane &plane = *_planes[planeIndex];
+ Plane *visiblePlane = _visiblePlanes.findByObject(plane._object);
-void GfxFrameout::showVideo() {
- bool skipVideo = false;
- RobotDecoder *videoDecoder = g_sci->_robotDecoder;
- uint16 x = videoDecoder->getPos().x;
- uint16 y = videoDecoder->getPos().y;
- uint16 screenWidth = _screen->getWidth();
- uint16 screenHeight = _screen->getHeight();
- uint16 outputWidth;
- uint16 outputHeight;
+ if (!plane._screenRect.isEmpty()) {
+ if (plane._redrawAllCount) {
+ plane.redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ } else {
+ if (visiblePlane == nullptr) {
+ error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane._object));
+ }
- if (videoDecoder->hasDirtyPalette())
- g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
+ plane.calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ }
+ } else {
+ plane.decrementScreenItemArrayCounts(visiblePlane, false);
+ }
- while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) {
- if (videoDecoder->needsUpdate()) {
- const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
- if (frame) {
- // We need to clip here
- // At least Phantasmagoria shows a 640x390 video on a 630x450 screen during the intro
- outputWidth = frame->w > screenWidth ? screenWidth : frame->w;
- outputHeight = frame->h > screenHeight ? screenHeight : frame->h;
- g_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, outputWidth, outputHeight);
+ if (plane._moved) {
+ // the work for handling moved/resized planes was already done
+ // earlier in the function, we are just cleaning up now
+ --plane._moved;
+ }
+
+ if (plane._created) {
+ _visiblePlanes.add(new Plane(plane));
+ --plane._created;
+ } else if (plane._updated) {
+ *visiblePlane = plane;
+ --plane._updated;
+ }
+ }
- if (videoDecoder->hasDirtyPalette())
- g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
+ // NOTE: SSCI only looks for kPlaneTypeTransparent, not
+ // kPlaneTypeTransparentPicture
+ if (foundTransparentPlane) {
+ for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {
+ if (_planes[i]->_type == kPlaneTypeTransparent) {
+ _planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]);
+ }
+ }
+
+ if (_planes[planeIndex]->_type == kPlaneTypeTransparent) {
+ for (int i = (int)planeIndex - 1; i >= 0; --i) {
+ _planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]);
+ }
+
+ if (eraseLists[planeIndex].size() > 0) {
+ error("Transparent plane's erase list not absorbed");
+ }
+ }
- g_system->updateScreen();
+ for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {
+ if (_planes[i]->_type == kPlaneTypeTransparent) {
+ _planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]);
+ }
}
}
+ }
+}
+
+void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) {
+ if (plane._type != kPlaneTypeColored) {
+ return;
+ }
- Common::Event event;
- while (g_system->getEventManager()->pollEvent(event)) {
- if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP)
- skipVideo = true;
+ const RectList::size_type eraseListSize = eraseList.size();
+ for (RectList::size_type i = 0; i < eraseListSize; ++i) {
+ mergeToShowList(*eraseList[i], _showList, _overdrawThreshold);
+ _currentBuffer.fillRect(*eraseList[i], plane._back);
+ }
+}
+
+void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) {
+ const DrawList::size_type drawListSize = screenItemList.size();
+ for (DrawList::size_type i = 0; i < drawListSize; ++i) {
+ const DrawItem &drawItem = *screenItemList[i];
+ mergeToShowList(drawItem.rect, _showList, _overdrawThreshold);
+ const ScreenItem &screenItem = *drawItem.screenItem;
+ // TODO: Remove
+// debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), PRINT_RECT(drawItem.rect));
+ CelObj &celObj = *screenItem._celObj;
+ celObj.draw(_currentBuffer, screenItem, drawItem.rect, screenItem._mirrorX ^ celObj._mirrorX);
+ }
+}
+
+void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) {
+ RectList mergeList;
+ Common::Rect merged;
+ mergeList.add(drawRect);
+
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ bool didMerge = false;
+ const Common::Rect &r1 = *mergeList[i];
+ if (!r1.isEmpty()) {
+ for (RectList::size_type j = 0; j < showList.size(); ++j) {
+ const Common::Rect &r2 = *showList[j];
+ if (!r2.isEmpty()) {
+ merged = r1;
+ merged.extend(r2);
+
+ int difference = merged.width() * merged.height();
+ difference -= r1.width() * r1.height();
+ difference -= r2.width() * r2.height();
+ if (r1.intersects(r2)) {
+ const Common::Rect overlap = r1.findIntersectingRect(r2);
+ difference += overlap.width() * overlap.height();
+ }
+
+ if (difference <= overdrawThreshold) {
+ mergeList.erase_at(i);
+ showList.erase_at(j);
+ mergeList.add(merged);
+ didMerge = true;
+ break;
+ } else {
+ Common::Rect outRects[2];
+ int splitCount = splitRectsForRender(*mergeList[i], *showList[j], outRects);
+ if (splitCount != -1) {
+ mergeList.add(*mergeList[i]);
+ mergeList.erase_at(i);
+ showList.erase_at(j);
+ didMerge = true;
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ if (didMerge) {
+ showList.pack();
+ }
}
+ }
- g_system->delayMillis(10);
+ mergeList.pack();
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ showList.add(*mergeList[i]);
}
}
-void GfxFrameout::createPlaneItemList(reg_t planeObject, FrameoutList &itemList) {
- // Copy screen items of the current frame to the list of items to be drawn
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
- if (planeObject == itemPlane) {
- kernelUpdateScreenItem((*listIterator)->object); // TODO: Why is this necessary?
- itemList.push_back(*listIterator);
+void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) {
+ Palette sourcePalette(_palette->getNextPalette());
+ alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
+
+ int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16();
+
+ Common::Rect rect(_screen->getDisplayWidth(), _screen->getDisplayHeight());
+ _showList.add(rect);
+ showBits();
+
+ // NOTE: The original engine allocated these as static arrays of 100
+ // pointers to ScreenItemList / RectList
+ ScreenItemListList screenItemLists;
+ EraseListList eraseLists;
+
+ screenItemLists.resize(_planes.size());
+ eraseLists.resize(_planes.size());
+
+ if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
+ remapMarkRedraw();
+ }
+
+ calcLists(screenItemLists, eraseLists);
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
+ }
+
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
}
}
- for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
- if (pictureIt->object == planeObject) {
- GfxPicture *planePicture = pictureIt->picture;
- // Allocate memory for picture cels
- pictureIt->pictureCels = new FrameoutEntry[planePicture->getSci32celCount()];
+ _remapOccurred = _palette->updateForFrame();
+ _frameNowVisible = false;
- // Add following cels to the itemlist
- FrameoutEntry *picEntry = pictureIt->pictureCels;
- int planePictureCels = planePicture->getSci32celCount();
- for (int pictureCelNr = 0; pictureCelNr < planePictureCels; pictureCelNr++) {
- picEntry->celNo = pictureCelNr;
- picEntry->object = NULL_REG;
- picEntry->picture = planePicture;
- picEntry->y = planePicture->getSci32celY(pictureCelNr);
- picEntry->x = planePicture->getSci32celX(pictureCelNr);
- picEntry->picStartX = pictureIt->startX;
- picEntry->picStartY = pictureIt->startY;
- picEntry->visible = true;
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
+ }
- picEntry->priority = planePicture->getSci32celPriority(pictureCelNr);
+ Palette nextPalette(_palette->getNextPalette());
- itemList.push_back(picEntry);
- picEntry++;
+ if (prevRoom < 1000) {
+ for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+ if (styleRanges[i] == -1 || styleRanges[i] == 0) {
+ sourcePalette.colors[i] = nextPalette.colors[i];
+ sourcePalette.colors[i].used = true;
+ }
+ }
+ } else {
+ for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+ // TODO: Limiting range 72 to 103 is NOT present in every game
+ if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) {
+ sourcePalette.colors[i] = nextPalette.colors[i];
+ sourcePalette.colors[i].used = true;
}
}
}
- // Now sort our itemlist
- Common::sort(itemList.begin(), itemList.end(), sortHelper);
+ _palette->submit(sourcePalette);
+ _palette->updateFFrame();
+ _palette->updateHardware();
+ alterVmap(nextPalette, sourcePalette, 1, _styleRanges);
+
+ if (showStyle && showStyle->type != kShowStyleUnknown) {
+// TODO: SCI2.1mid transition effects
+// processEffects();
+ warning("Transition %d not implemented!", showStyle->type);
+ } else {
+ showBits();
+ }
+
+ _frameNowVisible = true;
+
+ for (PlaneList::iterator plane = _planes.begin(); plane != _planes.end(); ++plane) {
+ (*plane)->_redrawAllCount = getScreenCount();
+ }
+
+ if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
+ remapMarkRedraw();
+ }
+
+ calcLists(screenItemLists, eraseLists);
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
+ }
+
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
+ }
+ }
+
+ _remapOccurred = _palette->updateForFrame();
+ // NOTE: During this second loop, `_frameNowVisible = false` is
+ // inside the next loop in SCI2.1mid
+ _frameNowVisible = false;
+
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
+ }
+
+ _palette->submit(nextPalette);
+ _palette->updateFFrame();
+ _palette->updateHardware(false);
+ showBits();
+
+ _frameNowVisible = true;
}
-bool GfxFrameout::isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY) {
- // Out of view horizontally (sanity checks)
- int16 pictureCelStartX = itemEntry->picStartX + itemEntry->x;
- int16 pictureCelEndX = pictureCelStartX + itemEntry->picture->getSci32celWidth(itemEntry->celNo);
- int16 planeStartX = planeOffsetX;
- int16 planeEndX = planeStartX + planeRect.width();
- if (pictureCelEndX < planeStartX)
- return true;
- if (pictureCelStartX > planeEndX)
- return true;
+void GfxFrameout::showBits() {
+ for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
+ Common::Rect rounded(**rect);
+ // NOTE: SCI engine used BR-inclusive rects so used slightly
+ // different masking here to ensure that the width of rects
+ // was always even.
+ rounded.left &= ~1;
+ rounded.right = (rounded.right + 1) & ~1;
+
+ // TODO:
+ // _cursor->GonnaPaint(rounded);
+ }
- // Out of view vertically (sanity checks)
- int16 pictureCelStartY = itemEntry->picStartY + itemEntry->y;
- int16 pictureCelEndY = pictureCelStartY + itemEntry->picture->getSci32celHeight(itemEntry->celNo);
- int16 planeStartY = planeOffsetY;
- int16 planeEndY = planeStartY + planeRect.height();
- if (pictureCelEndY < planeStartY)
- return true;
- if (pictureCelStartY > planeEndY)
- return true;
+ // TODO:
+ // _cursor->PaintStarting();
- return false;
+ for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
+ Common::Rect rounded(**rect);
+ // NOTE: SCI engine used BR-inclusive rects so used slightly
+ // different masking here to ensure that the width of rects
+ // was always even.
+ rounded.left &= ~1;
+ rounded.right = (rounded.right + 1) & ~1;
+
+ byte *sourceBuffer = (byte *)_currentBuffer.getPixels() + rounded.top * _currentBuffer.screenWidth + rounded.left;
+
+ g_system->copyRectToScreen(sourceBuffer, _currentBuffer.screenWidth, rounded.left, rounded.top, rounded.width(), rounded.height());
+ }
+
+ // TODO:
+ // _cursor->DonePainting();
+
+ _showList.clear();
}
-void GfxFrameout::drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored) {
- int16 pictureOffsetX = planeOffsetX;
- int16 pictureX = itemEntry->x;
- if ((planeOffsetX) || (itemEntry->picStartX)) {
- if (planeOffsetX <= itemEntry->picStartX) {
- pictureX += itemEntry->picStartX - planeOffsetX;
- pictureOffsetX = 0;
- } else {
- pictureOffsetX = planeOffsetX - itemEntry->picStartX;
+void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges) {
+ uint8 clut[256];
+
+ for (int paletteIndex = 0; paletteIndex < ARRAYSIZE(palette1.colors); ++paletteIndex) {
+ int outerR = palette1.colors[paletteIndex].r;
+ int outerG = palette1.colors[paletteIndex].g;
+ int outerB = palette1.colors[paletteIndex].b;
+
+ if (styleRanges[paletteIndex] == style) {
+ int minDiff = 262140;
+ int minDiffIndex = paletteIndex;
+
+ for (int i = 0; i < 236; ++i) {
+ if (styleRanges[i] != style) {
+ int r = palette1.colors[i].r;
+ int g = palette1.colors[i].g;
+ int b = palette1.colors[i].b;
+ int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b);
+ if (diffSquared < minDiff) {
+ minDiff = diffSquared;
+ minDiffIndex = i;
+ }
+ }
+ }
+
+ clut[paletteIndex] = minDiffIndex;
}
- }
- int16 pictureOffsetY = planeOffsetY;
- int16 pictureY = itemEntry->y;
- if ((planeOffsetY) || (itemEntry->picStartY)) {
- if (planeOffsetY <= itemEntry->picStartY) {
- pictureY += itemEntry->picStartY - planeOffsetY;
- pictureOffsetY = 0;
- } else {
- pictureOffsetY = planeOffsetY - itemEntry->picStartY;
+ if (style == 1 && styleRanges[paletteIndex] == 0) {
+ int minDiff = 262140;
+ int minDiffIndex = paletteIndex;
+
+ for (int i = 0; i < 236; ++i) {
+ int r = palette2.colors[i].r;
+ int g = palette2.colors[i].g;
+ int b = palette2.colors[i].b;
+
+ int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b);
+ if (diffSquared < minDiff) {
+ minDiff = diffSquared;
+ minDiffIndex = i;
+ }
+ }
+
+ clut[paletteIndex] = minDiffIndex;
}
}
- itemEntry->picture->drawSci32Vga(itemEntry->celNo, pictureX, itemEntry->y, pictureOffsetX, pictureOffsetY, planePictureMirrored);
- // warning("picture cel %d %d", itemEntry->celNo, itemEntry->priority);
+ // NOTE: This is currBuffer->ptr in SCI engine
+ byte *pixels = (byte *)_currentBuffer.getPixels();
+
+ for (int pixelIndex = 0, numPixels = _currentBuffer.screenWidth * _currentBuffer.screenHeight; pixelIndex < numPixels; ++pixelIndex) {
+ byte currentValue = pixels[pixelIndex];
+ int8 styleRangeValue = styleRanges[currentValue];
+ if (styleRangeValue == -1 && styleRangeValue == style) {
+ currentValue = pixels[pixelIndex] = clut[currentValue];
+ // NOTE: In original engine this assignment happens outside of the
+ // condition, but if the branch is not followed the value is just
+ // going to be the same as it was before
+ styleRangeValue = styleRanges[currentValue];
+ }
+
+ if (
+ (styleRangeValue == 1 && styleRangeValue == style) ||
+ (styleRangeValue == 0 && style == 1)
+ ) {
+ pixels[pixelIndex] = clut[currentValue];
+ }
+ }
}
-void GfxFrameout::kernelFrameout() {
- if (g_sci->_robotDecoder->isVideoLoaded()) {
- showVideo();
+void GfxFrameout::kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor) {
+ if (toColor > fromColor) {
return;
}
- _palette->palVaryUpdate();
-
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); it++) {
- reg_t planeObject = it->object;
+ for (int i = fromColor; i < toColor; ++i) {
+ _styleRanges[i] = 0;
+ }
+}
- // Draw any plane lines, if they exist
- // These are drawn on invisible planes as well. (e.g. "invisiblePlane" in LSL6 hires)
- // FIXME: Lines aren't always drawn (e.g. when the narrator speaks in LSL6 hires).
- // Perhaps something is painted over them?
- for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
- Common::Point startPoint = it2->startPoint;
- Common::Point endPoint = it2->endPoint;
- _coordAdjuster->kernelLocalToGlobal(startPoint.x, startPoint.y, it->object);
- _coordAdjuster->kernelLocalToGlobal(endPoint.x, endPoint.y, it->object);
- _screen->drawLine(startPoint, endPoint, it2->color, it2->priority, it2->control);
+inline ShowStyleEntry * GfxFrameout::findShowStyleForPlane(const reg_t planeObj) const {
+ ShowStyleEntry *entry = _showStyles;
+ while (entry != nullptr) {
+ if (entry->plane == planeObj) {
+ break;
}
+ entry = entry->next;
+ }
- int16 planeLastPriority = it->lastPriority;
+ return entry;
+}
- // Update priority here, sq6 sets it w/o UpdatePlane
- int16 planePriority = it->priority = readSelectorValue(_segMan, planeObject, SELECTOR(priority));
+inline ShowStyleEntry *GfxFrameout::deleteShowStyleInternal(ShowStyleEntry *const showStyle) {
+ ShowStyleEntry *lastEntry = nullptr;
- it->lastPriority = planePriority;
- if (planePriority < 0) { // Plane currently not meant to be shown
- // If plane was shown before, delete plane rect
- if (planePriority != planeLastPriority)
- _paint32->fillRect(it->planeRect, 0);
- continue;
+ for (ShowStyleEntry *testEntry = _showStyles; testEntry != nullptr; testEntry = testEntry->next) {
+ if (testEntry == showStyle) {
+ break;
}
+ lastEntry = testEntry;
+ }
- // There is a race condition lurking in SQ6, which causes the game to hang in the intro, when teleporting to Polysorbate LX.
- // Since I first wrote the patch, the race has stopped occurring for me though.
- // I'll leave this for investigation later, when someone can reproduce.
- //if (it->pictureId == kPlanePlainColored) // FIXME: This is what SSCI does, and fixes the intro of LSL7, but breaks the dialogs in GK1 (adds black boxes)
- if (it->pictureId == kPlanePlainColored && (it->planeBack || g_sci->getGameId() != GID_GK1))
- _paint32->fillRect(it->planeRect, it->planeBack);
+ if (lastEntry == nullptr) {
+ _showStyles = showStyle->next;
+ lastEntry = _showStyles;
+ } else {
+ lastEntry->next = showStyle->next;
+ }
- _coordAdjuster->pictureSetDisplayArea(it->planeRect);
- // Invoking drewPicture() with an invalid picture ID in SCI32 results in
- // invalidating the palVary palette when a palVary effect is active. This
- // is quite obvious in QFG4, where the day time palette is incorrectly
- // shown when exiting the caves, and the correct night time palette
- // flashes briefly each time that kPalVaryInit is called.
- if (it->pictureId != 0xFFFF)
- _palette->drewPicture(it->pictureId);
+ delete[] showStyle->fadeColorRanges;
+ delete showStyle;
- FrameoutList itemList;
+ // TODO: Verify that this is the correct entry to return
+ // for the loop in processShowStyles to work correctly
+ return lastEntry;
+}
- createPlaneItemList(planeObject, itemList);
+// TODO: 10-argument version is only in SCI3; argc checks are currently wrong for this version
+// and need to be fixed in future
+// TODO: SQ6 does not use 'priority' (exists since SCI2) or 'blackScreen' (exists since SCI3);
+// check to see if other versions use or if they are just always ignored
+void GfxFrameout::kernelSetShowStyle(const uint16 argc, const reg_t planeObj, const ShowStyleType type, const int16 seconds, const int16 back, const int16 priority, const int16 animate, const int16 frameOutNow, reg_t pFadeArray, int16 divisions, const int16 blackScreen) {
+
+ bool hasDivisions = false;
+ bool hasFadeArray = false;
+
+ // KQ7 2.0b uses a mismatched version of the Styler script (SCI2.1early script
+ // for SCI2.1mid engine), so the calls it makes to kSetShowStyle are wrong and
+ // put `divisions` where `pFadeArray` is supposed to be
+ if (getSciVersion() == SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() == GID_KQ7) {
+ hasDivisions = argc > 7;
+ hasFadeArray = false;
+ divisions = argc > 7 ? pFadeArray.toSint16() : -1;
+ pFadeArray = NULL_REG;
+ } else if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ hasDivisions = argc > 7;
+ hasFadeArray = false;
+ } else if (getSciVersion() < SCI_VERSION_3) {
+ hasDivisions = argc > 8;
+ hasFadeArray = argc > 7;
+ } else {
+ hasDivisions = argc > 9;
+ hasFadeArray = argc > 8;
+ }
- for (FrameoutList::iterator listIterator = itemList.begin(); listIterator != itemList.end(); listIterator++) {
- FrameoutEntry *itemEntry = *listIterator;
+ bool isFadeUp;
+ int16 color;
+ if (back != -1) {
+ isFadeUp = false;
+ color = back;
+ } else {
+ isFadeUp = true;
+ color = 0;
+ }
- if (!itemEntry->visible)
- continue;
+ if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && type == 15) || type > 15) {
+ error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj));
+ }
- if (itemEntry->object.isNull()) {
- // Picture cel data
- _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x);
- _coordAdjuster->fromScriptToDisplay(itemEntry->picStartY, itemEntry->picStartX);
+ Plane *plane = _planes.findByObject(planeObj);
+ if (plane == nullptr) {
+ error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj));
+ }
- if (!isPictureOutOfView(itemEntry, it->planeRect, it->planeOffsetX, it->planeOffsetY))
- drawPicture(itemEntry, it->planeOffsetX, it->planeOffsetY, it->planePictureMirrored);
- } else {
- GfxView *view = (itemEntry->viewId != 0xFFFF) ? _cache->getView(itemEntry->viewId) : NULL;
- int16 dummyX = 0;
-
- if (view && view->isSci2Hires()) {
- view->adjustToUpscaledCoordinates(itemEntry->y, itemEntry->x);
- view->adjustToUpscaledCoordinates(itemEntry->z, dummyX);
- } else if (getSciVersion() >= SCI_VERSION_2_1) {
- _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x);
- _coordAdjuster->fromScriptToDisplay(itemEntry->z, dummyX);
- }
+ bool createNewEntry = true;
+ ShowStyleEntry *entry = findShowStyleForPlane(planeObj);
+ if (entry != nullptr) {
+ // TODO: SCI2.1early has different criteria for show style reuse
+ bool useExisting = true;
- // Adjust according to current scroll position
- itemEntry->x -= it->planeOffsetX;
- itemEntry->y -= it->planeOffsetY;
-
- uint16 useInsetRect = readSelectorValue(_segMan, itemEntry->object, SELECTOR(useInsetRect));
- if (useInsetRect) {
- itemEntry->celRect.top = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inTop));
- itemEntry->celRect.left = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inLeft));
- itemEntry->celRect.bottom = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inBottom));
- itemEntry->celRect.right = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inRight));
- if (view && view->isSci2Hires()) {
- view->adjustToUpscaledCoordinates(itemEntry->celRect.top, itemEntry->celRect.left);
- view->adjustToUpscaledCoordinates(itemEntry->celRect.bottom, itemEntry->celRect.right);
- }
- itemEntry->celRect.translate(itemEntry->x, itemEntry->y);
- // TODO: maybe we should clip the cels rect with this, i'm not sure
- // the only currently known usage is game menu of gk1
- } else if (view) {
- // Process global scaling, if needed.
- // TODO: Seems like SCI32 always processes global scaling for scaled objects
- // TODO: We can only process symmetrical scaling for now (i.e. same value for scaleX/scaleY)
- if ((itemEntry->scaleSignal & kScaleSignalDoScaling32) &&
- !(itemEntry->scaleSignal & kScaleSignalDisableGlobalScaling32) &&
- (itemEntry->scaleX == itemEntry->scaleY) &&
- itemEntry->scaleX != 128)
- applyGlobalScaling(itemEntry, it->planeRect, view->getHeight(itemEntry->loopNo, itemEntry->celNo));
-
- if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
- view->getCelRect(itemEntry->loopNo, itemEntry->celNo,
- itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect);
- else
- view->getCelScaledRect(itemEntry->loopNo, itemEntry->celNo,
- itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->scaleX,
- itemEntry->scaleY, itemEntry->celRect);
-
- Common::Rect nsRect = itemEntry->celRect;
- // Translate back to actual coordinate within scrollable plane
- nsRect.translate(it->planeOffsetX, it->planeOffsetY);
-
- if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
- // HACK: Some (?) objects in Phantasmagoria 2 have no NS rect. Skip them for now.
- // TODO: Remove once we figure out how Phantasmagoria 2 draws objects on screen.
- if (lookupSelector(_segMan, itemEntry->object, SELECTOR(nsLeft), NULL, NULL) != kSelectorVariable)
- continue;
- }
+ if (useExisting) {
+ useExisting = entry->divisions == (hasDivisions ? divisions : _defaultDivisions[type]) && entry->unknownC == _defaultUnknownC[type];
+ }
- if (view && view->isSci2Hires()) {
- view->adjustBackUpscaledCoordinates(nsRect.top, nsRect.left);
- view->adjustBackUpscaledCoordinates(nsRect.bottom, nsRect.right);
- g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
- } else if (getSciVersion() >= SCI_VERSION_2_1 && _resMan->detectHires()) {
- _coordAdjuster->fromDisplayToScript(nsRect.top, nsRect.left);
- _coordAdjuster->fromDisplayToScript(nsRect.bottom, nsRect.right);
- g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
- }
+ if (useExisting) {
+ createNewEntry = false;
+ isFadeUp = true;
+ entry->currentStep = 0;
+ } else {
+ isFadeUp = true;
+ color = entry->color;
+ deleteShowStyleInternal(entry/*, true*/);
+ entry = nullptr;
+ }
+ }
- // TODO: For some reason, the top left nsRect coordinates get
- // swapped in the GK1 inventory screen, investigate why.
- // This is also needed for GK1 rooms 710 and 720 (catacombs, inner and
- // outer circle), for handling the tiles and talking to Wolfgang.
- // HACK: Fix the coordinates by explicitly setting them here for GK1.
- // Also check bug #6729, for another case where this is needed.
- if (g_sci->getGameId() == GID_GK1)
- g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
+ if (type > 0) {
+ if (createNewEntry) {
+ entry = new ShowStyleEntry;
+ // NOTE: SCI2.1 engine tests if allocation returned a null pointer
+ // but then only avoids setting currentStep if this is so. Since
+ // this is a nonsensical approach, we do not do that here
+ entry->currentStep = 0;
+ entry->unknownC = _defaultUnknownC[type];
+ entry->processed = false;
+ entry->divisions = hasDivisions ? divisions : _defaultDivisions[type];
+ entry->plane = planeObj;
+
+ entry->fadeColorRanges = nullptr;
+ if (hasFadeArray) {
+ // NOTE: SCI2.1mid engine does no check to verify that an array is
+ // successfully retrieved, and SegMan will cause a fatal error
+ // if we try to use a memory segment that is not an array
+ SciArray<reg_t> *table = _segMan->lookupArray(pFadeArray);
+
+ uint32 rangeCount = table->getSize();
+ entry->fadeColorRangesCount = rangeCount;
+
+ // NOTE: SCI engine code always allocates memory even if the range
+ // table has no entries, but this does not really make sense, so
+ // we avoid the allocation call in this case
+ if (rangeCount > 0) {
+ entry->fadeColorRanges = new uint16[rangeCount];
+ for (size_t i = 0; i < rangeCount; ++i) {
+ entry->fadeColorRanges[i] = table->getValue(i).toUint16();
+ }
}
+ } else {
+ entry->fadeColorRangesCount = 0;
+ }
+ }
- // Don't attempt to draw sprites that are outside the visible
- // screen area. An example is the random people walking in
- // Jackson Square in GK1.
- if (itemEntry->celRect.bottom < 0 || itemEntry->celRect.top >= _screen->getDisplayHeight() ||
- itemEntry->celRect.right < 0 || itemEntry->celRect.left >= _screen->getDisplayWidth())
- continue;
-
- Common::Rect clipRect, translatedClipRect;
- clipRect = itemEntry->celRect;
-
- if (view && view->isSci2Hires()) {
- clipRect.clip(it->upscaledPlaneClipRect);
- translatedClipRect = clipRect;
- translatedClipRect.translate(it->upscaledPlaneRect.left, it->upscaledPlaneRect.top);
- } else {
- // QFG4 passes invalid rectangles when a battle is starting
- if (!clipRect.isValidRect())
- continue;
- clipRect.clip(it->planeClipRect);
- translatedClipRect = clipRect;
- translatedClipRect.translate(it->planeRect.left, it->planeRect.top);
- }
+ // NOTE: The original engine had no nullptr check and would just crash
+ // if it got to here
+ if (entry == nullptr) {
+ error("Cannot edit non-existing ShowStyle entry");
+ }
+
+ entry->fadeUp = isFadeUp;
+ entry->color = color;
+ entry->nextTick = g_sci->getTickCount();
+ entry->type = type;
+ entry->animate = animate;
+ entry->delay = (seconds * 60 + entry->divisions - 1) / entry->divisions;
+
+ if (entry->delay == 0) {
+ if (entry->fadeColorRanges != nullptr) {
+ delete[] entry->fadeColorRanges;
+ }
+ delete entry;
+ error("ShowStyle has no duration");
+ }
- if (view) {
- if (!clipRect.isEmpty()) {
- if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
- view->draw(itemEntry->celRect, clipRect, translatedClipRect,
- itemEntry->loopNo, itemEntry->celNo, 255, 0, view->isSci2Hires());
- else
- view->drawScaled(itemEntry->celRect, clipRect, translatedClipRect,
- itemEntry->loopNo, itemEntry->celNo, 255, itemEntry->scaleX, itemEntry->scaleY);
+ if (frameOutNow) {
+ Common::Rect frameOutRect(0, 0);
+ frameOut(false, frameOutRect);
+ }
+
+ if (createNewEntry) {
+ // TODO: Implement SCI2.1early and SCI3
+ entry->next = _showStyles;
+ _showStyles = entry;
+ }
+ }
+}
+
+// NOTE: Different version of SCI engine support different show styles
+// SCI2 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12, 13, 14
+// SCI2.1 implements 0, 1/2/3/4/5/6/7/8/9/10/11/12/15, 13, 14
+// SCI3 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12/15, 13, 14
+// TODO: Sierra code needs to be replaced with code that uses the
+// computed entry->delay property instead of just counting divisors,
+// as the latter is machine-speed-dependent and leads to wrong
+// transition speeds
+void GfxFrameout::processShowStyles() {
+ uint32 now = g_sci->getTickCount();
+
+ bool continueProcessing;
+
+ // TODO: Change to bool? Engine uses inc to set the value to true,
+ // but there does not seem to be any reason to actually count how
+ // many times it was set
+ int doFrameOut;
+ do {
+ continueProcessing = false;
+ doFrameOut = 0;
+ ShowStyleEntry *showStyle = _showStyles;
+ while (showStyle != nullptr) {
+ bool retval = false;
+
+ if (!showStyle->animate) {
+ ++doFrameOut;
+ }
+
+ if (showStyle->nextTick < now || !showStyle->animate) {
+ // TODO: Different versions of SCI use different processors!
+ // This is the SQ6/KQ7/SCI2.1mid table.
+ switch (showStyle->type) {
+ case kShowStyleNone: {
+ retval = processShowStyleNone(showStyle);
+ break;
+ }
+ case kShowStyleHShutterOut:
+ case kShowStyleVShutterOut:
+ case kShowStyleWipeLeft:
+ case kShowStyleWipeUp:
+ case kShowStyleIrisOut:
+ case kShowStyleHShutterIn:
+ case kShowStyleVShutterIn:
+ case kShowStyleWipeRight:
+ case kShowStyleWipeDown:
+ case kShowStyleIrisIn:
+ case kShowStyle11:
+ case kShowStyle12:
+ case kShowStyleUnknown: {
+ retval = processShowStyleMorph(showStyle);
+ break;
+ }
+ case kShowStyleFadeOut: {
+ retval = processShowStyleFade(-1, showStyle);
+ break;
+ }
+ case kShowStyleFadeIn: {
+ retval = processShowStyleFade(1, showStyle);
+ break;
}
}
+ }
- // Draw text, if it exists
- if (lookupSelector(_segMan, itemEntry->object, SELECTOR(text), NULL, NULL) == kSelectorVariable) {
- g_sci->_gfxText32->drawTextBitmap(itemEntry->x, itemEntry->y, it->planeRect, itemEntry->object);
- }
+ if (!retval) {
+ continueProcessing = true;
}
+
+ if (retval && showStyle->processed) {
+ showStyle = deleteShowStyleInternal(showStyle);
+ } else {
+ showStyle = showStyle->next;
+ }
+ }
+
+ if (g_engine->shouldQuit()) {
+ return;
+ }
+
+ if (doFrameOut) {
+ frameOut(true);
+
+ // TODO: Transitions without the “animate” flag are too
+ // fast, but the throttle value is arbitrary. Someone on
+ // real hardware probably needs to test what the actual
+ // speed of these transitions should be
+ throttle();
+ }
+ } while(continueProcessing && doFrameOut);
+}
+
+bool GfxFrameout::processShowStyleNone(ShowStyleEntry *const showStyle) {
+ if (showStyle->fadeUp) {
+ _palette->setFade(100, 0, 255);
+ } else {
+ _palette->setFade(0, 0, 255);
+ }
+
+ showStyle->processed = true;
+ return true;
+}
+
+bool GfxFrameout::processShowStyleMorph(ShowStyleEntry *const showStyle) {
+ palMorphFrameOut(_styleRanges, showStyle);
+ showStyle->processed = true;
+ return true;
+}
+
+// TODO: Normalise use of 'entry' vs 'showStyle'
+bool GfxFrameout::processShowStyleFade(const int direction, ShowStyleEntry *const showStyle) {
+ bool unchanged = true;
+ if (showStyle->currentStep < showStyle->divisions) {
+ int percent;
+ if (direction <= 0) {
+ percent = showStyle->divisions - showStyle->currentStep - 1;
+ } else {
+ percent = showStyle->currentStep;
}
- for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
- if (pictureIt->object == planeObject) {
- delete[] pictureIt->pictureCels;
- pictureIt->pictureCels = 0;
+ percent *= 100;
+ percent /= showStyle->divisions - 1;
+
+ if (showStyle->fadeColorRangesCount > 0) {
+ for (int i = 0, len = showStyle->fadeColorRangesCount; i < len; i += 2) {
+ _palette->setFade(percent, showStyle->fadeColorRanges[i], showStyle->fadeColorRanges[i + 1]);
}
+ } else {
+ _palette->setFade(percent, 0, 255);
}
+
+ ++showStyle->currentStep;
+ showStyle->nextTick += showStyle->delay;
+ unchanged = false;
}
- showCurrentScrollText();
+ if (showStyle->currentStep >= showStyle->divisions && unchanged) {
+ if (direction > 0) {
+ showStyle->processed = true;
+ }
- _screen->copyToScreen();
+ return true;
+ }
- g_sci->getEngineState()->_throttleTrigger = true;
+ return false;
}
-void GfxFrameout::printPlaneList(Console *con) {
- for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) {
- PlaneEntry p = *it;
- Common::String curPlaneName = _segMan->getObjectName(p.object);
- Common::Rect r = p.upscaledPlaneRect;
- Common::Rect cr = p.upscaledPlaneClipRect;
-
- con->debugPrintf("%04x:%04x (%s): prio %d, lastprio %d, offsetX %d, offsetY %d, pic %d, mirror %d, back %d\n",
- PRINT_REG(p.object), curPlaneName.c_str(),
- (int16)p.priority, (int16)p.lastPriority,
- p.planeOffsetX, p.planeOffsetY, p.pictureId,
- p.planePictureMirrored, p.planeBack);
- con->debugPrintf(" rect: (%d, %d, %d, %d), clip rect: (%d, %d, %d, %d)\n",
- r.left, r.top, r.right, r.bottom,
- cr.left, cr.top, cr.right, cr.bottom);
-
- if (p.pictureId != 0xffff && p.pictureId != 0xfffe) {
- con->debugPrintf("Pictures:\n");
-
- for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
- if (pictureIt->object == p.object) {
- con->debugPrintf(" Picture %d: x %d, y %d\n", pictureIt->pictureId, pictureIt->startX, pictureIt->startY);
- }
- }
+void GfxFrameout::kernelFrameOut(const bool shouldShowBits) {
+ if (_showStyles != nullptr) {
+ processShowStyles();
+ } else if (_palMorphIsOn) {
+ palMorphFrameOut(_styleRanges, nullptr);
+ _palMorphIsOn = false;
+ } else {
+// TODO: Window scroll
+// if (g_PlaneScroll) {
+// processScrolls();
+// }
+
+ frameOut(shouldShowBits);
+ }
+
+ throttle();
+}
+
+void GfxFrameout::throttle() {
+ if (_throttleFrameOut) {
+ uint8 throttleTime;
+ if (_throttleState == 2) {
+ throttleTime = 17;
+ _throttleState = 0;
+ } else {
+ throttleTime = 16;
+ ++_throttleState;
}
+
+ g_sci->getEngineState()->speedThrottler(throttleTime);
+ g_sci->getEngineState()->_throttleTrigger = true;
+ }
+}
+
+#pragma mark -
+#pragma mark Mouse cursor
+
+reg_t GfxFrameout::kernelIsOnMe(const reg_t object, const Common::Point &position, bool checkPixel) const {
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+ Plane *plane = _visiblePlanes.findByObject(planeObject);
+ if (plane == nullptr) {
+ return make_reg(0, 0);
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem == nullptr) {
+ return make_reg(0, 0);
}
+
+ // NOTE: The original engine passed a copy of the ScreenItem into isOnMe
+ // as a hack around the fact that the screen items in `_visiblePlanes`
+ // did not have their `_celObj` pointers cleared when their CelInfo was
+ // updated by `Plane::decrementScreenItemArrayCounts`. We handle this
+ // this more intelligently by clearing `_celObj` in the copy assignment
+ // operator, which is only ever called by `decrementScreenItemArrayCounts`
+ // anyway.
+ return make_reg(0, isOnMe(*screenItem, *plane, position, checkPixel));
}
-void GfxFrameout::printPlaneItemList(Console *con, reg_t planeObject) {
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- FrameoutEntry *e = *listIterator;
- reg_t itemPlane = readSelector(_segMan, e->object, SELECTOR(plane));
+bool GfxFrameout::isOnMe(const ScreenItem &screenItem, const Plane &plane, const Common::Point &position, const bool checkPixel) const {
- if (planeObject == itemPlane) {
- Common::String curItemName = _segMan->getObjectName(e->object);
- Common::Rect icr = e->celRect;
- GuiResourceId picId = e->picture ? e->picture->getResourceId() : 0;
+ Common::Point scaledPosition(position);
+ mulru(scaledPosition, Ratio(_currentBuffer.screenWidth, _currentBuffer.scriptWidth), Ratio(_currentBuffer.screenHeight, _currentBuffer.scriptHeight));
+ scaledPosition.x += plane._planeRect.left;
+ scaledPosition.y += plane._planeRect.top;
+
+ if (!screenItem._screenRect.contains(scaledPosition)) {
+ return false;
+ }
- con->debugPrintf("%d: %04x:%04x (%s), view %d, loop %d, cel %d, x %d, y %d, z %d, "
- "signal %d, scale signal %d, scaleX %d, scaleY %d, rect (%d, %d, %d, %d), "
- "pic %d, picX %d, picY %d, visible %d\n",
- e->givenOrderNr, PRINT_REG(e->object), curItemName.c_str(),
- e->viewId, e->loopNo, e->celNo, e->x, e->y, e->z,
- e->signal, e->scaleSignal, e->scaleX, e->scaleY,
- icr.left, icr.top, icr.right, icr.bottom,
- picId, e->picStartX, e->picStartY, e->visible);
+ if (checkPixel) {
+ CelObj &celObj = screenItem.getCelObj();
+
+ bool mirrorX = screenItem._mirrorX ^ celObj._mirrorX;
+
+ scaledPosition.x -= screenItem._scaledPosition.x;
+ scaledPosition.y -= screenItem._scaledPosition.y;
+
+ mulru(scaledPosition, Ratio(celObj._scaledWidth, _currentBuffer.screenWidth), Ratio(celObj._scaledHeight, _currentBuffer.screenHeight));
+
+ if (screenItem._scale.signal != kScaleSignalNone && screenItem._scale.x && screenItem._scale.y) {
+ scaledPosition.x = scaledPosition.x * 128 / screenItem._scale.x;
+ scaledPosition.y = scaledPosition.y * 128 / screenItem._scale.y;
}
+
+ uint8 pixel = celObj.readPixel(scaledPosition.x, scaledPosition.y, mirrorX);
+ return pixel != celObj._transparentColor;
+ }
+
+ return true;
+}
+
+void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const {
+ const reg_t planeObject = readSelector(_segMan, screenItemObject, SELECTOR(plane));
+
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kSetNowSeen: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(screenItemObject));
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject);
+ if (screenItem == nullptr) {
+ error("kSetNowSeen: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItemObject), PRINT_REG(planeObject));
+ }
+
+ Common::Rect result = screenItem->getNowSeenRect(*plane);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsLeft), result.left);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsTop), result.top);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsRight), result.right - 1);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsBottom), result.bottom - 1);
+}
+
+void GfxFrameout::remapMarkRedraw() {
+ for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) {
+ Plane *p = *it;
+ p->remapMarkRedraw();
+ }
+}
+
+#pragma mark -
+#pragma mark Debugging
+
+void GfxFrameout::printPlaneListInternal(Console *con, const PlaneList &planeList) const {
+ for (PlaneList::const_iterator it = planeList.begin(); it != planeList.end(); ++it) {
+ Plane *p = *it;
+ p->printDebugInfo(con);
}
}
+void GfxFrameout::printPlaneList(Console *con) const {
+ printPlaneListInternal(con, _planes);
+}
+
+void GfxFrameout::printVisiblePlaneList(Console *con) const {
+ printPlaneListInternal(con, _visiblePlanes);
+}
+
+void GfxFrameout::printPlaneItemListInternal(Console *con, const ScreenItemList &screenItemList) const {
+ ScreenItemList::size_type i = 0;
+ for (ScreenItemList::const_iterator sit = screenItemList.begin(); sit != screenItemList.end(); sit++) {
+ ScreenItem *screenItem = *sit;
+ con->debugPrintf("%2d: ", i++);
+ screenItem->printDebugInfo(con);
+ }
+}
+
+void GfxFrameout::printPlaneItemList(Console *con, const reg_t planeObject) const {
+ Plane *p = _planes.findByObject(planeObject);
+
+ if (p == nullptr) {
+ con->debugPrintf("Plane does not exist");
+ return;
+ }
+
+ printPlaneItemListInternal(con, p->_screenItemList);
+}
+
+void GfxFrameout::printVisiblePlaneItemList(Console *con, const reg_t planeObject) const {
+ Plane *p = _visiblePlanes.findByObject(planeObject);
+
+ if (p == nullptr) {
+ con->debugPrintf("Plane does not exist");
+ return;
+ }
+
+ printPlaneItemListInternal(con, p->_screenItemList);
+}
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index e0c60f92c1..99658ede6a 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -23,157 +23,488 @@
#ifndef SCI_GRAPHICS_FRAMEOUT_H
#define SCI_GRAPHICS_FRAMEOUT_H
-namespace Sci {
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/screen_item32.h"
-class GfxPicture;
+namespace Sci {
+// TODO: Don't do this this way
+int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]);
-struct PlaneLineEntry {
- reg_t hunkId;
- Common::Point startPoint;
- Common::Point endPoint;
- byte color;
- byte priority;
- byte control;
+// TODO: Verify display styles and adjust names appropriately for
+// types 1 through 12 & 15 (others are correct)
+// Names should be:
+// * VShutterIn, VShutterOut
+// * HShutterIn, HShutterOut
+// * WipeLeft, WipeRight, WipeDown, WipeUp
+// * PixelDissolve
+// * ShutDown and Kill? (and Plain and Fade?)
+enum ShowStyleType /* : uint8 */ {
+ kShowStyleNone = 0,
+ kShowStyleHShutterOut = 1,
+ kShowStyleHShutterIn = 2,
+ kShowStyleVShutterOut = 3,
+ kShowStyleVShutterIn = 4,
+ kShowStyleWipeLeft = 5,
+ kShowStyleWipeRight = 6,
+ kShowStyleWipeUp = 7,
+ kShowStyleWipeDown = 8,
+ kShowStyleIrisOut = 9,
+ kShowStyleIrisIn = 10,
+ kShowStyle11 = 11,
+ kShowStyle12 = 12,
+ kShowStyleFadeOut = 13,
+ kShowStyleFadeIn = 14,
+ // TODO: Only in SCI3
+ kShowStyleUnknown = 15
};
-typedef Common::List<PlaneLineEntry> PlaneLineList;
-
-struct PlaneEntry {
- reg_t object;
- int16 priority;
- int16 lastPriority;
- int16 planeOffsetX;
- int16 planeOffsetY;
- GuiResourceId pictureId;
- Common::Rect planeRect;
- Common::Rect planeClipRect;
- Common::Rect upscaledPlaneRect;
- Common::Rect upscaledPlaneClipRect;
- bool planePictureMirrored;
- byte planeBack;
- PlaneLineList lines;
-};
+/**
+ * Show styles represent transitions applied to draw planes.
+ * One show style per plane can be active at a time.
+ */
+struct ShowStyleEntry {
+ /**
+ * The ID of the plane this show style belongs to.
+ * In SCI2.1mid (at least SQ6), per-plane transitions
+ * were removed and a single plane ID is used.
+ */
+ reg_t plane;
-typedef Common::List<PlaneEntry> PlaneList;
-
-struct FrameoutEntry {
- uint16 givenOrderNr;
- reg_t object;
- GuiResourceId viewId;
- int16 loopNo;
- int16 celNo;
- int16 x, y, z;
- int16 priority;
- uint16 signal;
- uint16 scaleSignal;
- int16 scaleX;
- int16 scaleY;
- Common::Rect celRect;
- GfxPicture *picture;
- int16 picStartX;
- int16 picStartY;
- bool visible;
-};
+ /**
+ * The type of the transition.
+ */
+ ShowStyleType type;
-typedef Common::List<FrameoutEntry *> FrameoutList;
+ // TODO: This name is probably incorrect
+ bool fadeUp;
-struct PlanePictureEntry {
- reg_t object;
- int16 startX;
- int16 startY;
- GuiResourceId pictureId;
- GfxPicture *picture;
- FrameoutEntry *pictureCels; // temporary
-};
+ /**
+ * The number of steps for the show style.
+ */
+ int16 divisions;
-typedef Common::List<PlanePictureEntry> PlanePictureList;
+ // NOTE: This property exists from SCI2 through at least
+ // SCI2.1mid but is never used in the actual processing
+ // of the styles?
+ int unknownC;
-struct ScrollTextEntry {
- reg_t bitmapHandle;
- reg_t kWindow;
- uint16 x;
- uint16 y;
-};
+ /**
+ * The color used by transitions that draw CelObjColor
+ * screen items. -1 for transitions that do not draw
+ * screen items.
+ */
+ int16 color;
+
+ // TODO: Probably uint32
+ // TODO: This field probably should be used in order to
+ // provide time-accurate processing of show styles. In the
+ // actual SCI engine (at least 2–2.1mid) it appears that
+ // style transitions are drawn “as fast as possible”, one
+ // step per loop, even though this delay field exists
+ int delay;
+
+ // TODO: Probably bool, but never seems to be true?
+ int animate;
+
+ /**
+ * The wall time at which the next step of the animation
+ * should execute.
+ */
+ uint32 nextTick;
-typedef Common::Array<ScrollTextEntry> ScrollTextList;
+ /**
+ * During playback of the show style, the current step
+ * (out of divisions).
+ */
+ int currentStep;
-enum ViewScaleSignals32 {
- kScaleSignalDoScaling32 = 0x0001, // enables scaling when drawing that cel (involves scaleX and scaleY)
- kScaleSignalUnk1 = 0x0002, // unknown
- kScaleSignalDisableGlobalScaling32 = 0x0004
+ /**
+ * The next show style.
+ */
+ ShowStyleEntry *next;
+
+ /**
+ * Whether or not this style has finished running and
+ * is ready for disposal.
+ */
+ bool processed;
+
+ //
+ // Engine specific properties for SCI2.1mid through SCI3
+ //
+
+ /**
+ * The number of entries in the fadeColorRanges array.
+ */
+ uint8 fadeColorRangesCount;
+
+ /**
+ * A pointer to an dynamically sized array of palette
+ * indexes, in the order [ fromColor, toColor, ... ].
+ * Only colors within this range are transitioned.
+ */
+ uint16 *fadeColorRanges;
};
-class GfxCache;
+typedef Common::Array<DrawList> ScreenItemListList;
+typedef Common::Array<RectList> EraseListList;
+
class GfxCoordAdjuster32;
-class GfxPaint32;
-class GfxPalette;
class GfxScreen;
/**
- * Frameout class, kFrameout and relevant functions for SCI32 games
+ * Frameout class, kFrameout and relevant functions for SCI32 games.
+ * Roughly equivalent to GraphicsMgr in the actual SCI engine.
*/
class GfxFrameout {
+private:
+ bool _isHiRes;
+ GfxCoordAdjuster32 *_coordAdjuster;
+ GfxPalette32 *_palette;
+ ResourceManager *_resMan;
+ GfxScreen *_screen;
+ SegManager *_segMan;
+
public:
- GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette *palette, GfxPaint32 *paint32);
+ GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette);
~GfxFrameout();
- void kernelAddPlane(reg_t object);
- void kernelUpdatePlane(reg_t object);
- void kernelDeletePlane(reg_t object);
- void applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight);
- void kernelAddScreenItem(reg_t object);
- void kernelUpdateScreenItem(reg_t object);
- void kernelDeleteScreenItem(reg_t object);
- void deletePlaneItems(reg_t planeObject);
- FrameoutEntry *findScreenItem(reg_t object);
- int16 kernelGetHighPlanePri();
- void kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY);
- void kernelFrameout();
-
- void addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY = 0);
- void deletePlanePictures(reg_t object);
- reg_t addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control);
- void updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control);
- void deletePlaneLine(reg_t object, reg_t hunkId);
void clear();
+ void syncWithScripts(bool addElements); // this is what Game::restore does, only needed when our ScummVM dialogs are patched in
+ void run();
- // Scroll text functions
- void addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace);
- void showCurrentScrollText();
- void initScrollText(uint16 maxItems) { _maxScrollTexts = maxItems; }
- void clearScrollTexts();
- void firstScrollText() { if (_scrollTexts.size() > 0) _curScrollText = 0; }
- void lastScrollText() { if (_scrollTexts.size() > 0) _curScrollText = _scrollTexts.size() - 1; }
- void prevScrollText() { if (_curScrollText > 0) _curScrollText--; }
- void nextScrollText() { if (_curScrollText + 1 < (uint16)_scrollTexts.size()) _curScrollText++; }
- void toggleScrollText(bool show) { _showScrollText = show; }
+#pragma mark -
+#pragma mark Benchmarking
+private:
+ /**
+ * Optimization to avoid the more expensive object name
+ * comparision on every call to kAddScreenItem and
+ * kRemoveScreenItem.
+ */
+ bool _benchmarkingFinished;
- void printPlaneList(Console *con);
- void printPlaneItemList(Console *con, reg_t planeObject);
+ /**
+ * Whether or not calls to kFrameOut should be framerate
+ * limited to 60fps.
+ */
+ bool _throttleFrameOut;
+ /**
+ * Determines whether or not a screen item is the "Fred"
+ * object.
+ */
+ bool checkForFred(const reg_t object);
+
+#pragma mark -
+#pragma mark Screen items
private:
- void showVideo();
- void createPlaneItemList(reg_t planeObject, FrameoutList &itemList);
- bool isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY);
- void drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored);
+ void remapMarkRedraw();
- SegManager *_segMan;
- ResourceManager *_resMan;
- GfxCoordAdjuster32 *_coordAdjuster;
- GfxCache *_cache;
- GfxPalette *_palette;
- GfxScreen *_screen;
- GfxPaint32 *_paint32;
+public:
+ /**
+ * Adds a screen item.
+ */
+ void addScreenItem(ScreenItem &screenItem) const;
+
+ /**
+ * Updates a screen item.
+ */
+ void updateScreenItem(ScreenItem &screenItem) const;
- FrameoutList _screenItems;
+ /**
+ * Deletes a screen item.
+ */
+ void deleteScreenItem(ScreenItem &screenItem);
+
+ /**
+ * Deletes a screen item from the given plane.
+ */
+ void deleteScreenItem(ScreenItem &screenItem, Plane &plane);
+
+ /**
+ * Deletes a screen item from the given plane.
+ */
+ void deleteScreenItem(ScreenItem &screenItem, const reg_t plane);
+
+ void kernelAddScreenItem(const reg_t object);
+ void kernelUpdateScreenItem(const reg_t object);
+ void kernelDeleteScreenItem(const reg_t object);
+ void kernelSetNowSeen(const reg_t screenItemObject) const;
+
+#pragma mark -
+#pragma mark Planes
+private:
+ /**
+ * The list of planes (i.e. layers) that have been added
+ * to the screen.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
PlaneList _planes;
- PlanePictureList _planePictures;
- ScrollTextList _scrollTexts;
- int16 _curScrollText;
- bool _showScrollText;
- uint16 _maxScrollTexts;
- void sortPlanes();
+ /**
+ * Updates an existing plane with properties from the
+ * given VM object.
+ */
+ void updatePlane(Plane &plane);
+
+public:
+ /**
+ * Creates and adds a new plane to the plane list, or
+ * cancels deletion and updates an already-existing
+ * plane if a plane matching the given plane VM object
+ * already exists within the current plane list.
+ *
+ * @note This method is on Screen in SCI engine, but it
+ * is only ever called on `GraphicsMgr.screen`.
+ */
+ void addPlane(Plane &plane);
+
+ /**
+ * Deletes a plane within the current plane list.
+ *
+ * @note This method is on Screen in SCI engine, but it
+ * is only ever called on `GraphicsMgr.screen`.
+ */
+ void deletePlane(Plane &plane);
+
+ const PlaneList &getPlanes() const {
+ return _planes;
+ }
+ const PlaneList &getVisiblePlanes() const {
+ return _visiblePlanes;
+ }
+ void kernelAddPlane(const reg_t object);
+ void kernelUpdatePlane(const reg_t object);
+ void kernelDeletePlane(const reg_t object);
+ void kernelMovePlaneItems(const reg_t object, const int16 deltaX, const int16 deltaY, const bool scrollPics);
+ int16 kernelGetHighPlanePri();
+
+#pragma mark -
+#pragma mark Pics
+public:
+ void kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 pictureX, const int16 pictureY, const bool mirrorX);
+
+#pragma mark -
+
+ // TODO: Remap-related?
+ void kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor);
+
+#pragma mark -
+#pragma mark Transitions
+private:
+ int *_dissolveSequenceSeeds;
+ int16 *_defaultDivisions;
+ int16 *_defaultUnknownC;
+
+ /**
+ * TODO: Documentation
+ */
+ ShowStyleEntry *_showStyles;
+
+ inline ShowStyleEntry *findShowStyleForPlane(const reg_t planeObj) const;
+ inline ShowStyleEntry *deleteShowStyleInternal(ShowStyleEntry *const showStyle);
+ void processShowStyles();
+ bool processShowStyleNone(ShowStyleEntry *showStyle);
+ bool processShowStyleMorph(ShowStyleEntry *showStyle);
+ bool processShowStyleFade(const int direction, ShowStyleEntry *showStyle);
+
+public:
+ // NOTE: This signature is taken from SCI3 Phantasmagoria 2
+ // and is valid for all implementations of SCI32
+ void kernelSetShowStyle(const uint16 argc, const reg_t planeObj, const ShowStyleType type, const int16 seconds, const int16 direction, const int16 priority, const int16 animate, const int16 frameOutNow, reg_t pFadeArray, int16 divisions, const int16 blackScreen);
+
+#pragma mark -
+#pragma mark Rendering
+private:
+ /**
+ * State tracker to provide more accurate 60fps
+ * video throttling.
+ */
+ uint8 _throttleState;
+
+ /**
+ * TODO: Documentation
+ */
+ int8 _styleRanges[256];
+
+ /**
+ * The internal display pixel buffer. During frameOut,
+ * this buffer is drawn into according to the draw and
+ * erase rects calculated by `calcLists`, then drawn out
+ * to the hardware surface according to the `_showList`
+ * rects (which are also calculated by `calcLists`).
+ */
+ Buffer _currentBuffer;
+
+ /**
+ * When true, a change to the remap zone in the palette
+ * has occurred and screen items with remap data need to
+ * be redrawn.
+ */
+ bool _remapOccurred;
+
+ /**
+ * Whether or not the data in the current buffer is what
+ * is visible to the user. During rendering updates,
+ * this flag is set to false.
+ */
+ bool _frameNowVisible;
+
+ /**
+ * TODO: Document
+ * TODO: Depending upon if the engine ever modifies this
+ * rect, it may be stupid to store it separately instead
+ * of just getting width/height from GfxScreen.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ Common::Rect _screenRect;
+
+ /**
+ * A list of rectangles, in display coordinates, that
+ * represent portions of the internal screen buffer that
+ * should be drawn to the hardware display surface.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ RectList _showList;
+
+ /**
+ * The amount of extra overdraw that is acceptable when
+ * merging two show list rectangles together into a
+ * single larger rectangle.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ int _overdrawThreshold;
+
+ /**
+ * A list of planes that are currently drawn to the
+ * hardware display surface. Used to calculate
+ * differences in plane properties between the last
+ * frame and current frame.
+ *
+ * @note This field is on `GraphicsMgr.visibleScreen` in
+ * SCI engine.
+ */
+ PlaneList _visiblePlanes;
+
+ /**
+ * Calculates the location and dimensions of dirty rects
+ * over the entire screen for rendering the next frame.
+ * The draw and erase lists in `drawLists` and
+ * `eraseLists` each represent one plane on the screen.
+ * The optional `eraseRect` argument allows a specific
+ * area of the screen to be erased.
+ */
+ void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect = Common::Rect());
+
+ /**
+ * Erases the areas in the given erase list from the
+ * visible screen buffer by filling them with the color
+ * from the corresponding plane. This is an optimisation
+ * for colored-type planes only; other plane types have
+ * to be redrawn from pixel data.
+ */
+ void drawEraseList(const RectList &eraseList, const Plane &plane);
+
+ /**
+ * Draws all screen items from the given draw list to
+ * the visible screen buffer.
+ */
+ void drawScreenItemList(const DrawList &screenItemList);
+
+ /**
+ * Adds a new rectangle to the list of regions to write
+ * out to the hardware. The provided rect may be merged
+ * into an existing rectangle to reduce the number of
+ * blit operations.
+ */
+ void mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold);
+
+ /**
+ * TODO: Documentation
+ */
+ void palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle);
+
+ /**
+ * Writes the internal frame buffer out to hardware and
+ * clears the show list.
+ */
+ void showBits();
+
+public:
+ /**
+ * Whether palMorphFrameOut should be used instead of
+ * frameOut for rendering. Used by kMorphOn to
+ * explicitly enable palMorphFrameOut for one frame.
+ */
+ bool _palMorphIsOn;
+
+ inline const Buffer &getCurrentBuffer() const {
+ return _currentBuffer;
+ }
+
+ void kernelFrameOut(const bool showBits);
+
+ /**
+ * Throttles the engine as necessary to maintain
+ * 60fps output.
+ */
+ void throttle();
+
+ /**
+ * Updates the internal screen buffer for the next
+ * frame. If `shouldShowBits` is true, also sends the
+ * buffer to hardware. If `eraseRect` is non-empty,
+ * it is added to the erase list for this frame.
+ */
+ void frameOut(const bool shouldShowBits, const Common::Rect &eraseRect = Common::Rect());
+
+ /**
+ * Modifies the raw pixel data for the next frame with
+ * new palette indexes based on matched style ranges.
+ */
+ void alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges);
+
+ // NOTE: This function is used within ScreenItem subsystem and assigned
+ // to various booleanish fields that seem to represent the state of the
+ // screen item (created, updated, deleted). In GK1/DOS, Phant1/m68k,
+ // SQ6/DOS, SQ6/Win, and Phant2/Win, this function simply returns 1. If
+ // you know of any game/environment where this function returns some
+ // value other than 1, or if you used to work at Sierra and can explain
+ // why this is a thing (and if anyone needs to care about it), please
+ // open a ticket!!
+ inline int getScreenCount() const {
+ return 1;
+ };
+
+#pragma mark -
+#pragma mark Mouse cursor
+private:
+ /**
+ * Determines whether or not the point given by
+ * `position` is inside of the given screen item.
+ */
+ bool isOnMe(const ScreenItem &screenItem, const Plane &plane, const Common::Point &position, const bool checkPixel) const;
+
+public:
+ reg_t kernelIsOnMe(const reg_t object, const Common::Point &position, const bool checkPixel) const;
+
+#pragma mark -
+#pragma mark Debugging
+public:
+ void printPlaneList(Console *con) const;
+ void printVisiblePlaneList(Console *con) const;
+ void printPlaneListInternal(Console *con, const PlaneList &planeList) const;
+ void printPlaneItemList(Console *con, const reg_t planeObject) const;
+ void printVisiblePlaneItemList(Console *con, const reg_t planeObject) const;
+ void printPlaneItemListInternal(Console *con, const ScreenItemList &screenItemList) const;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index 4889f12bd2..3fcc83c5e2 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -26,6 +26,11 @@
#include "common/endian.h" // for READ_LE_UINT16
#include "common/rect.h"
#include "common/serializer.h"
+#ifdef ENABLE_SCI32
+#include "common/rational.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+#endif
#include "sci/engine/vm_types.h"
namespace Sci {
@@ -45,6 +50,9 @@ typedef int16 TextAlignment;
#define PORTS_FIRSTWINDOWID 2
#define PORTS_FIRSTSCRIPTWINDOWID 3
+#ifdef ENABLE_SCI32
+#define PRINT_RECT(x) (x).left,(x).top,(x).right,(x).bottom
+#endif
struct Port {
uint16 id;
@@ -118,9 +126,120 @@ struct Window : public Port, public Common::Serializable {
}
};
+#ifdef ENABLE_SCI32
+/**
+ * Multiplies a rectangle by two ratios with default
+ * rounding. Modifies the rect directly.
+ */
+inline void mul(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+ rect.left = (rect.left * ratioX).toInt();
+ rect.top = (rect.top * ratioY).toInt();
+ rect.right = (rect.right * ratioX).toInt();
+ rect.bottom = (rect.bottom * ratioY).toInt();
+}
+
+/**
+ * Multiplies a rectangle by two ratios with default
+ * rounding. Modifies the rect directly. Uses inclusive
+ * rectangle rounding.
+ */
+inline void mulinc(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+ rect.left = (rect.left * ratioX).toInt();
+ rect.top = (rect.top * ratioY).toInt();
+ rect.right = ((rect.right - 1) * ratioX).toInt() + 1;
+ rect.bottom = ((rect.bottom - 1) * ratioY).toInt() + 1;
+}
+
+/**
+ * Multiplies a number by a rational number, rounding up to
+ * the nearest whole number.
+ */
+inline int mulru(const int value, const Common::Rational &ratio, const int extra = 0) {
+ int num = (value + extra) * ratio.getNumerator();
+ int result = num / ratio.getDenominator();
+ if (num > ratio.getDenominator() && num % ratio.getDenominator()) {
+ ++result;
+ }
+ return result - extra;
+}
+
+/**
+ * Multiplies a point by two rational numbers for X and Y,
+ * rounding up to the nearest whole number. Modifies the
+ * point directly.
+ */
+inline void mulru(Common::Point &point, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+ point.x = mulru(point.x, ratioX);
+ point.y = mulru(point.y, ratioY);
+}
+
+/**
+ * Multiplies a point by two rational numbers for X and Y,
+ * rounding up to the nearest whole number. Modifies the
+ * rect directly.
+ */
+inline void mulru(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY, const int extra) {
+ rect.left = mulru(rect.left, ratioX);
+ rect.top = mulru(rect.top, ratioY);
+ rect.right = mulru(rect.right - 1, ratioX, extra) + 1;
+ rect.bottom = mulru(rect.bottom - 1, ratioY, extra) + 1;
+}
+
+struct Buffer : public Graphics::Surface {
+ uint16 screenWidth;
+ uint16 screenHeight;
+ uint16 scriptWidth;
+ uint16 scriptHeight;
+
+ Buffer() :
+ screenWidth(0),
+ screenHeight(0),
+ scriptWidth(320),
+ scriptHeight(200) {}
+
+ Buffer(const uint16 width, const uint16 height, uint8 *const pix) :
+ screenWidth(width),
+ screenHeight(height),
+ // TODO: These values are not correct for all games. Script
+ // dimensions were hard-coded per game in the original
+ // interpreter. Search all games for their internal script
+ // dimensions and set appropriately. (This code does not
+ // appear to exist at all in SCI3, which uses 640x480.)
+ scriptWidth(320),
+ scriptHeight(200) {
+ init(width, height, width, pix, Graphics::PixelFormat::createFormatCLUT8());
+ }
+
+ void clear(const uint8 value) {
+ memset(pixels, value, w * h);
+ }
+
+ inline uint8 *getAddress(const uint16 x, const uint16 y) {
+ return (uint8 *)getBasePtr(x, y);
+ }
+
+ inline uint8 *getAddressSimRes(const uint16 x, const uint16 y) {
+ return (uint8*)pixels + (y * w * screenHeight / scriptHeight) + (x * screenWidth / scriptWidth);
+ }
+
+ bool isNull() {
+ return pixels == nullptr;
+ }
+};
+#endif
+
struct Color {
byte used;
byte r, g, b;
+
+#ifdef ENABLE_SCI32
+ bool operator==(const Color &other) const {
+ return used == other.used && r == other.r && g == other.g && b == other.b;
+ }
+ inline bool operator!=(const Color &other) const {
+ return !operator==(other);
+ }
+#endif
};
struct Palette {
@@ -128,6 +247,21 @@ struct Palette {
uint32 timestamp;
Color colors[256];
byte intensity[256];
+
+#ifdef ENABLE_SCI32
+ bool operator==(const Palette &other) const {
+ for (int i = 0; i < ARRAYSIZE(colors); ++i) {
+ if (colors[i] != other.colors[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ inline bool operator!=(const Palette &other) const {
+ return !(*this == other);
+ }
+#endif
};
struct PalSchedule {
diff --git a/engines/sci/graphics/lists32.h b/engines/sci/graphics/lists32.h
new file mode 100644
index 0000000000..4f74c77325
--- /dev/null
+++ b/engines/sci/graphics/lists32.h
@@ -0,0 +1,192 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_LISTS32_H
+#define SCI_GRAPHICS_LISTS32_H
+
+#include "common/array.h"
+
+namespace Sci {
+
+/**
+ * StablePointerArray holds pointers in a fixed-size array
+ * that maintains position of erased items until `pack` is
+ * called. It is used by DrawList, RectList, and
+ * ScreenItemList. StablePointerArray takes ownership of
+ * all pointers that are passed to it and deletes them when
+ * calling `erase` or when destroying the
+ * StablePointerArray.
+ */
+template<class T, uint N>
+class StablePointerArray {
+ uint _size;
+ T *_items[N];
+
+public:
+ typedef T **iterator;
+ typedef T *const *const_iterator;
+ typedef T *value_type;
+ typedef uint size_type;
+
+ StablePointerArray() : _size(0), _items() {}
+ StablePointerArray(const StablePointerArray &other) : _size(other._size) {
+ for (size_type i = 0; i < _size; ++i) {
+ if (other._items[i] == nullptr) {
+ _items[i] = nullptr;
+ } else {
+ _items[i] = new T(*other._items[i]);
+ }
+ }
+ }
+ ~StablePointerArray() {
+ for (size_type i = 0; i < _size; ++i) {
+ delete _items[i];
+ }
+ }
+
+ void operator=(const StablePointerArray &other) {
+ clear();
+ _size = other._size;
+ for (size_type i = 0; i < _size; ++i) {
+ if (other._items[i] == nullptr) {
+ _items[i] = nullptr;
+ } else {
+ _items[i] = new T(*other._items[i]);
+ }
+ }
+ }
+
+ T *const &operator[](size_type index) const {
+ assert(index < _size);
+ return _items[index];
+ }
+
+ T *&operator[](size_type index) {
+ assert(index < _size);
+ return _items[index];
+ }
+
+ /**
+ * Adds a new pointer to the array.
+ */
+ void add(T *item) {
+ assert(_size < N);
+ _items[_size++] = item;
+ }
+
+ iterator begin() {
+ return _items;
+ }
+
+ const_iterator begin() const {
+ return _items;
+ }
+
+ void clear() {
+ for (size_type i = 0; i < _size; ++i) {
+ delete _items[i];
+ _items[i] = nullptr;
+ }
+
+ _size = 0;
+ }
+
+ iterator end() {
+ return _items + _size;
+ }
+
+ const_iterator end() const {
+ return _items + _size;
+ }
+
+ /**
+ * Erases the object pointed to by the given iterator.
+ */
+ void erase(T *item) {
+ for (iterator it = begin(); it != end(); ++it) {
+ if (*it == item) {
+ delete *it;
+ *it = nullptr;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Erases the object pointed to by the given iterator.
+ */
+ void erase(iterator &it) {
+ assert(it >= _items && it < _items + _size);
+ delete *it;
+ *it = nullptr;
+ }
+
+ /**
+ * Erases the object pointed to at the given index.
+ */
+ void erase_at(size_type index) {
+ assert(index < _size);
+
+ delete _items[index];
+ _items[index] = nullptr;
+ }
+
+ /**
+ * Removes freed pointers from the pointer list.
+ */
+ size_type pack() {
+ iterator freePtr = begin();
+ size_type newSize = 0;
+
+ for (iterator it = begin(), last = end(); it != last; ++it) {
+ if (*it != nullptr) {
+ *freePtr = *it;
+ ++freePtr;
+ ++newSize;
+ }
+ }
+
+ _size = newSize;
+ return newSize;
+ }
+
+ /**
+ * The number of populated slots in the array. The size
+ * of the array will only go down once `pack` is called.
+ */
+ size_type size() const {
+ return _size;
+ }
+};
+
+template<typename T>
+class FindByObject {
+ const reg_t &_object;
+public:
+ FindByObject(const reg_t &object) : _object(object) {}
+ bool operator()(const T entry) const {
+ return entry && entry->_object == _object;
+ }
+};
+
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/graphics/maciconbar.cpp b/engines/sci/graphics/maciconbar.cpp
index 8e2e12b7bd..e10b9fddbf 100644
--- a/engines/sci/graphics/maciconbar.cpp
+++ b/engines/sci/graphics/maciconbar.cpp
@@ -234,7 +234,7 @@ void GfxMacIconBar::remapColors(Graphics::Surface *surf, const byte *palette) {
byte g = palette[color * 3 + 1];
byte b = palette[color * 3 + 2];
- *pixels++ = g_sci->_gfxPalette->findMacIconBarColor(r, g, b);
+ *pixels++ = g_sci->_gfxPalette16->findMacIconBarColor(r, g, b);
}
}
diff --git a/engines/sci/graphics/menu.cpp b/engines/sci/graphics/menu.cpp
index 8e8c1d64c2..eb3f5888c7 100644
--- a/engines/sci/graphics/menu.cpp
+++ b/engines/sci/graphics/menu.cpp
@@ -424,8 +424,12 @@ reg_t GfxMenu::kernelSelect(reg_t eventObject, bool pauseSound) {
default:
while (itemIterator != itemEnd) {
itemEntry = *itemIterator;
+ // Sierra actually did not check the modifier, they only checked the ascii code
+ // Which is why for example pressing Ctrl-I and Shift-Ctrl-I both brought up the inventory in QfG1
+ // We still check the modifier, but we need to isolate the lower byte, because of a keyboard
+ // driver bug (see engine/kevent.cpp / kGetEvent)
if (itemEntry->keyPress == keyPress &&
- itemEntry->keyModifier == keyModifier &&
+ itemEntry->keyModifier == (keyModifier & 0xFF) &&
itemEntry->enabled)
break;
itemIterator++;
@@ -743,7 +747,7 @@ GuiMenuItemEntry *GfxMenu::interactiveWithKeyboard() {
// - sierra didn't wrap around when changing item id
// - sierra allowed item id to be 0, which didn't make any sense
do {
- switch (curEvent.data) {
+ switch (curEvent.character) {
case SCI_KEY_ESC:
_curMenuId = curItemEntry->menuId; _curItemId = curItemEntry->id;
return NULL;
@@ -772,10 +776,10 @@ GuiMenuItemEntry *GfxMenu::interactiveWithKeyboard() {
newMenuId = newItemEntry->menuId; newItemId = newItemEntry->id;
// if we do this step again because of a separator line -> don't repeat left/right, but go down
- switch (curEvent.data) {
+ switch (curEvent.character) {
case SCI_KEY_LEFT:
case SCI_KEY_RIGHT:
- curEvent.data = SCI_KEY_DOWN;
+ curEvent.character = SCI_KEY_DOWN;
}
}
} while (newItemEntry->separatorLine);
diff --git a/engines/sci/graphics/paint.cpp b/engines/sci/graphics/paint.cpp
deleted file mode 100644
index 482b81aff1..0000000000
--- a/engines/sci/graphics/paint.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/* 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.
- *
- */
-
-#include "graphics/primitives.h"
-
-#include "sci/sci.h"
-#include "sci/engine/state.h"
-#include "sci/engine/selector.h"
-#include "sci/graphics/paint.h"
-
-namespace Sci {
-
-GfxPaint::GfxPaint() {
-}
-
-GfxPaint::~GfxPaint() {
-}
-
-void GfxPaint::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) {
-}
-
-void GfxPaint::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) {
-}
-
-} // End of namespace Sci
diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h
index 955cfdec8f..317388b2df 100644
--- a/engines/sci/graphics/paint16.h
+++ b/engines/sci/graphics/paint16.h
@@ -23,8 +23,6 @@
#ifndef SCI_GRAPHICS_PAINT16_H
#define SCI_GRAPHICS_PAINT16_H
-#include "sci/graphics/paint.h"
-
namespace Sci {
class GfxPorts;
@@ -36,7 +34,7 @@ class GfxView;
/**
* Paint16 class, handles painting/drawing for SCI16 (SCI0-SCI1.1) games
*/
-class GfxPaint16 : public GfxPaint {
+class GfxPaint16 {
public:
GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio);
~GfxPaint16();
diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp
index a210a469f1..74eb1629d0 100644
--- a/engines/sci/graphics/paint32.cpp
+++ b/engines/sci/graphics/paint32.cpp
@@ -20,49 +20,162 @@
*
*/
-#include "sci/sci.h"
-#include "sci/engine/state.h"
-#include "sci/engine/selector.h"
-#include "sci/graphics/coordadjuster.h"
-#include "sci/graphics/cache.h"
+#include "graphics/primitives.h"
+#include "sci/engine/seg_manager.h"
#include "sci/graphics/paint32.h"
-#include "sci/graphics/font.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/view.h"
-#include "sci/graphics/screen.h"
-#include "sci/graphics/palette.h"
+#include "sci/graphics/text32.h"
namespace Sci {
-GfxPaint32::GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette)
- : _resMan(resMan), _coordAdjuster(coordAdjuster), _screen(screen), _palette(palette) {
+GfxPaint32::GfxPaint32(SegManager *segMan) :
+ _segMan(segMan) {}
+
+reg_t GfxPaint32::kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) {
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kAddLine: Plane %04x:%04x not found", PRINT_REG(planeObject));
+ }
+
+ Common::Rect gameRect;
+ BitmapResource bitmap = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = bitmap.getObject();
+ // SSCI stores the line color on `celInfo`, even though
+ // this is not a `kCelTypeColor`, as a hack so that
+ // `kUpdateLine` can get the originally used color
+ celInfo.color = color;
+
+ ScreenItem *screenItem = new ScreenItem(planeObject, celInfo, Common::Rect(startPoint.x, startPoint.y, startPoint.x + bitmap.getWidth(), startPoint.y + bitmap.getHeight()));
+ screenItem->_priority = priority;
+ screenItem->_fixedPriority = true;
+
+ plane->_screenItemList.add(screenItem);
+
+ return screenItem->_object;
+}
+
+void GfxPaint32::kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) {
+
+ Common::Rect gameRect;
+ BitmapResource bitmap = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect);
+
+ _segMan->freeHunkEntry(screenItem->_celInfo.bitmap);
+ screenItem->_celInfo.bitmap = bitmap.getObject();
+ screenItem->_celInfo.color = color;
+ screenItem->_position = startPoint;
+ screenItem->_priority = priority;
+ screenItem->update();
}
-GfxPaint32::~GfxPaint32() {
+void GfxPaint32::kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject) {
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject);
+ if (plane == nullptr) {
+ return;
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject);
+ if (screenItem == nullptr) {
+ return;
+ }
+
+ _segMan->freeHunkEntry(screenItem->_celInfo.bitmap);
+ g_sci->_gfxFrameout->deleteScreenItem(*screenItem, *plane);
}
-void GfxPaint32::fillRect(Common::Rect rect, byte color) {
- int16 y, x;
- Common::Rect clipRect = rect;
+void GfxPaint32::plotter(int x, int y, int color, void *data) {
+ LineProperties &properties = *static_cast<LineProperties *>(data);
+ byte *pixels = properties.bitmap->getPixels();
+
+ const uint32 index = properties.bitmap->getWidth() * y + x;
+
+ if (index < properties.bitmap->getDataSize()) {
+ if (properties.solid) {
+ pixels[index] = (uint8)color;
+ return;
+ }
+
+ if (properties.horizontal && x != properties.lastAddress) {
+ properties.lastAddress = x;
+ ++properties.patternIndex;
+ } else if (!properties.horizontal && y != properties.lastAddress) {
+ properties.lastAddress = y;
+ ++properties.patternIndex;
+ }
- clipRect.clip(_screen->getWidth(), _screen->getHeight());
+ if (properties.pattern[properties.patternIndex]) {
+ pixels[index] = (uint8)color;
+ }
- for (y = clipRect.top; y < clipRect.bottom; y++) {
- for (x = clipRect.left; x < clipRect.right; x++) {
- _screen->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, color, 0, 0);
+ if (properties.patternIndex == ARRAYSIZE(properties.pattern)) {
+ properties.patternIndex = 0;
}
+ } else {
+ warning("GfxPaint32::plotter: Attempted to write out of bounds (%u >= %u)", index, properties.bitmap->getDataSize());
}
}
-void GfxPaint32::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) {
- GfxPicture *picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false);
+BitmapResource GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, uint16 pattern, uint8 thickness, Common::Rect &outRect) {
+ const uint8 skipColor = color != 250 ? 250 : 0;
- picture->draw(animationNr, mirroredFlag, addToFlag, EGApaletteNo);
- delete picture;
-}
+ // Thickness is expected to be 2n+1
+ thickness = ((MAX((uint8)1, thickness) - 1) | 1);
+ const uint8 halfThickness = thickness >> 1;
+
+ outRect.left = (startPoint.x < endPoint.x ? startPoint.x : endPoint.x) - halfThickness;
+ outRect.top = (startPoint.y < endPoint.y ? startPoint.y : endPoint.y) - halfThickness;
+ outRect.right = (startPoint.x > endPoint.x ? startPoint.x : endPoint.x) + halfThickness + 1;
+ outRect.bottom = (startPoint.y > endPoint.y ? startPoint.y : endPoint.y) + halfThickness + 1;
+
+ BitmapResource bitmap(_segMan, outRect.width(), outRect.height(), skipColor, 0, 0, g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, 0, false);
+
+ byte *pixels = bitmap.getPixels();
+ memset(pixels, skipColor, bitmap.getWidth() * bitmap.getHeight());
+
+ LineProperties properties;
+ properties.bitmap = &bitmap;
+
+ switch (style) {
+ case kLineStyleSolid:
+ pattern = 0xFFFF;
+ properties.solid = true;
+ break;
+ case kLineStyleDashed:
+ pattern = 0xFF00;
+ properties.solid = false;
+ break;
+ case kLineStylePattern:
+ properties.solid = pattern == 0xFFFF;
+ break;
+ }
+
+ const Common::Rect drawRect(
+ startPoint.x - outRect.left,
+ startPoint.y - outRect.top,
+ endPoint.x - outRect.left,
+ endPoint.y - outRect.top
+ );
-void GfxPaint32::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) {
- _screen->drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, color, priority, control);
+ if (!properties.solid) {
+ for (int i = 0; i < ARRAYSIZE(properties.pattern); ++i) {
+ properties.pattern[i] = (pattern & 0x8000);
+ pattern <<= 1;
+ }
+
+ properties.patternIndex = 0;
+ properties.horizontal = ABS(drawRect.right - drawRect.left) > ABS(drawRect.bottom - drawRect.top);
+ properties.lastAddress = properties.horizontal ? drawRect.left : drawRect.top;
+ }
+
+ if (thickness <= 1) {
+ Graphics::drawLine(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, color, plotter, &properties);
+ } else {
+ Graphics::drawThickLine2(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, thickness, color, plotter, &properties);
+ }
+
+ return bitmap;
}
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/paint32.h b/engines/sci/graphics/paint32.h
index e7a3ec256d..6d5a957fcd 100644
--- a/engines/sci/graphics/paint32.h
+++ b/engines/sci/graphics/paint32.h
@@ -23,30 +23,48 @@
#ifndef SCI_GRAPHICS_PAINT32_H
#define SCI_GRAPHICS_PAINT32_H
-#include "sci/graphics/paint.h"
-
namespace Sci {
+class BitmapResource;
+class Plane;
+class ScreenItem;
+class SegManager;
-class GfxPorts;
+enum LineStyle {
+ kLineStyleSolid,
+ kLineStyleDashed,
+ kLineStylePattern
+};
/**
* Paint32 class, handles painting/drawing for SCI32 (SCI2+) games
*/
-class GfxPaint32 : public GfxPaint {
+class GfxPaint32 {
public:
- GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette);
- ~GfxPaint32();
+ GfxPaint32(SegManager *segMan);
- void fillRect(Common::Rect rect, byte color);
+private:
+ SegManager *_segMan;
- void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo);
- void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control);
+#pragma mark -
+#pragma mark Line drawing
+public:
+ reg_t kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness);
+ void kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness);
+ void kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject);
private:
- ResourceManager *_resMan;
- GfxCoordAdjuster *_coordAdjuster;
- GfxScreen *_screen;
- GfxPalette *_palette;
+ typedef struct {
+ BitmapResource *bitmap;
+ bool pattern[16];
+ uint8 patternIndex;
+ bool solid;
+ bool horizontal;
+ int lastAddress;
+ } LineProperties;
+
+ static void plotter(int x, int y, int color, void *data);
+
+ BitmapResource makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness, Common::Rect &outRect);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp
index 59abef5550..1514ad838f 100644
--- a/engines/sci/graphics/palette.cpp
+++ b/engines/sci/graphics/palette.cpp
@@ -32,6 +32,7 @@
#include "sci/graphics/cache.h"
#include "sci/graphics/maciconbar.h"
#include "sci/graphics/palette.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/view.h"
@@ -86,10 +87,6 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen)
_macClut = 0;
loadMacIconBarPalette();
-#ifdef ENABLE_SCI32
- _clutTable = 0;
-#endif
-
switch (_resMan->getViewType()) {
case kViewEga:
_totalScreenColors = 16;
@@ -107,9 +104,6 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen)
default:
error("GfxPalette: Unknown view type");
}
-
- _remapOn = false;
- resetRemapping();
}
GfxPalette::~GfxPalette() {
@@ -117,10 +111,6 @@ GfxPalette::~GfxPalette() {
palVaryRemoveTimer();
delete[] _macClut;
-
-#ifdef ENABLE_SCI32
- unloadClut();
-#endif
}
bool GfxPalette::isMerging() {
@@ -144,7 +134,7 @@ void GfxPalette::setDefault() {
#define SCI_PAL_FORMAT_CONSTANT 1
#define SCI_PAL_FORMAT_VARIABLE 0
-void GfxPalette::createFromData(byte *data, int bytesLeft, Palette *paletteOut) {
+void GfxPalette::createFromData(byte *data, int bytesLeft, Palette *paletteOut) const {
int palFormat = 0;
int palOffset = 0;
int palColorStart = 0;
@@ -318,7 +308,7 @@ void GfxPalette::set(Palette *newPalette, bool force, bool forceRealMerge) {
uint32 systime = _sysPalette.timestamp;
if (force || newPalette->timestamp != systime) {
- // SCI1.1+ doesnt do real merging anymore, but simply copying over the used colors from other palettes
+ // SCI1.1+ doesn't do real merging anymore, but simply copying over the used colors from other palettes
// There are some games with inbetween SCI1.1 interpreters, use real merging for them (e.g. laura bow 2 demo)
if ((forceRealMerge) || (_useMerging))
_sysPaletteChanged |= merge(newPalette, force, forceRealMerge);
@@ -344,79 +334,6 @@ void GfxPalette::set(Palette *newPalette, bool force, bool forceRealMerge) {
}
}
-byte GfxPalette::remapColor(byte remappedColor, byte screenColor) {
- assert(_remapOn);
- if (_remappingType[remappedColor] == kRemappingByRange)
- return _remappingByRange[screenColor];
- else if (_remappingType[remappedColor] == kRemappingByPercent)
- return _remappingByPercent[screenColor];
- else
- error("remapColor(): Color %d isn't remapped", remappedColor);
-
- return 0; // should never reach here
-}
-
-void GfxPalette::resetRemapping() {
- _remapOn = false;
- _remappingPercentToSet = 0;
-
- for (int i = 0; i < 256; i++) {
- _remappingType[i] = kRemappingNone;
- _remappingByPercent[i] = i;
- _remappingByRange[i] = i;
- }
-}
-
-void GfxPalette::setRemappingPercent(byte color, byte percent) {
- _remapOn = true;
-
- // We need to defer the setup of the remapping table every time the screen
- // palette is changed, so that kernelFindColor() can find the correct
- // colors. Set it once here, in case the palette stays the same and update
- // it on each palette change by copySysPaletteToScreen().
- _remappingPercentToSet = percent;
-
- for (int i = 0; i < 256; i++) {
- byte r = _sysPalette.colors[i].r * _remappingPercentToSet / 100;
- byte g = _sysPalette.colors[i].g * _remappingPercentToSet / 100;
- byte b = _sysPalette.colors[i].b * _remappingPercentToSet / 100;
- _remappingByPercent[i] = kernelFindColor(r, g, b);
- }
-
- _remappingType[color] = kRemappingByPercent;
-}
-
-void GfxPalette::setRemappingPercentGray(byte color, byte percent) {
- _remapOn = true;
-
- // We need to defer the setup of the remapping table every time the screen
- // palette is changed, so that kernelFindColor() can find the correct
- // colors. Set it once here, in case the palette stays the same and update
- // it on each palette change by copySysPaletteToScreen().
- _remappingPercentToSet = percent;
-
- // Note: This is not what the original does, but the results are the same visually
- for (int i = 0; i < 256; i++) {
- byte rComponent = (byte)(_sysPalette.colors[i].r * _remappingPercentToSet * 0.30 / 100);
- byte gComponent = (byte)(_sysPalette.colors[i].g * _remappingPercentToSet * 0.59 / 100);
- byte bComponent = (byte)(_sysPalette.colors[i].b * _remappingPercentToSet * 0.11 / 100);
- byte luminosity = rComponent + gComponent + bComponent;
- _remappingByPercent[i] = kernelFindColor(luminosity, luminosity, luminosity);
- }
-
- _remappingType[color] = kRemappingByPercent;
-}
-
-void GfxPalette::setRemappingRange(byte color, byte from, byte to, byte base) {
- _remapOn = true;
-
- for (int i = from; i <= to; i++) {
- _remappingByRange[i] = i + base;
- }
-
- _remappingType[color] = kRemappingByRange;
-}
-
bool GfxPalette::insert(Palette *newPalette, Palette *destPalette) {
bool paletteChanged = false;
@@ -597,15 +514,8 @@ void GfxPalette::copySysPaletteToScreen() {
}
}
- // Check if we need to reset remapping by percent with the new colors.
- if (_remappingPercentToSet) {
- for (int i = 0; i < 256; i++) {
- byte r = _sysPalette.colors[i].r * _remappingPercentToSet / 100;
- byte g = _sysPalette.colors[i].g * _remappingPercentToSet / 100;
- byte b = _sysPalette.colors[i].b * _remappingPercentToSet / 100;
- _remappingByPercent[i] = kernelFindColor(r, g, b);
- }
- }
+ if (g_sci->_gfxRemap16)
+ g_sci->_gfxRemap16->updateRemapping();
g_system->getPaletteManager()->setPalette(bpal, 0, 256);
}
@@ -658,7 +568,7 @@ bool GfxPalette::kernelAnimate(byte fromColor, byte toColor, int speed) {
Color col;
//byte colorNr;
int16 colorCount;
- uint32 now = g_system->getMillis() * 60 / 1000;
+ uint32 now = g_sci->getTickCount();
// search for sheduled animations with the same 'from' value
// schedule animation...
@@ -983,7 +893,7 @@ void GfxPalette::palVaryProcess(int signal, bool setPalette) {
_palVaryResourceId = -1;
// Calculate inbetween palette
- Sci::Color inbetween;
+ Color inbetween;
int16 color;
for (int colorNr = 1; colorNr < 255; colorNr++) {
inbetween.used = _sysPalette.colors[colorNr].used;
@@ -994,7 +904,7 @@ void GfxPalette::palVaryProcess(int signal, bool setPalette) {
color = _palVaryTargetPalette.colors[colorNr].b - _palVaryOriginPalette.colors[colorNr].b;
inbetween.b = ((color * _palVaryStep) / 64) + _palVaryOriginPalette.colors[colorNr].b;
- if (memcmp(&inbetween, &_sysPalette.colors[colorNr], sizeof(Sci::Color))) {
+ if (memcmp(&inbetween, &_sysPalette.colors[colorNr], sizeof(Color))) {
_sysPalette.colors[colorNr] = inbetween;
_sysPaletteChanged = true;
}
@@ -1087,58 +997,4 @@ bool GfxPalette::colorIsFromMacClut(byte index) {
return index != 0 && _macClut && (_macClut[index * 3] != 0 || _macClut[index * 3 + 1] != 0 || _macClut[index * 3 + 2] != 0);
}
-#ifdef ENABLE_SCI32
-
-bool GfxPalette::loadClut(uint16 clutId) {
- // loadClut() will load a color lookup table from a clu file and set
- // the palette found in the file. This is to be used with Phantasmagoria 2.
-
- unloadClut();
-
- Common::String filename = Common::String::format("%d.clu", clutId);
- Common::File clut;
-
- if (!clut.open(filename) || clut.size() != 0x10000 + 236 * 3)
- return false;
-
- // Read in the lookup table
- // It maps each RGB565 color to a palette index
- _clutTable = new byte[0x10000];
- clut.read(_clutTable, 0x10000);
-
- Palette pal;
- memset(&pal, 0, sizeof(Palette));
-
- // Setup 1:1 mapping
- for (int i = 0; i < 256; i++) {
- pal.mapping[i] = i;
- }
-
- // Now load in the palette
- for (int i = 1; i <= 236; i++) {
- pal.colors[i].used = 1;
- pal.colors[i].r = clut.readByte();
- pal.colors[i].g = clut.readByte();
- pal.colors[i].b = clut.readByte();
- }
-
- set(&pal, true);
- setOnScreen();
- return true;
-}
-
-byte GfxPalette::matchClutColor(uint16 color) {
- // Match a color in RGB565 format to a palette index based on the loaded CLUT
- assert(_clutTable);
- return _clutTable[color];
-}
-
-void GfxPalette::unloadClut() {
- // This will only unload the actual table, but not reset any palette
- delete[] _clutTable;
- _clutTable = 0;
-}
-
-#endif
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h
index 500a45eccf..2178de8a91 100644
--- a/engines/sci/graphics/palette.h
+++ b/engines/sci/graphics/palette.h
@@ -35,12 +35,6 @@ class GfxScreen;
#define SCI_PALETTE_MATCH_PERFECT 0x8000
#define SCI_PALETTE_MATCH_COLORMASK 0xFF
-enum ColorRemappingType {
- kRemappingNone = 0,
- kRemappingByRange = 1,
- kRemappingByPercent = 2
-};
-
/**
* Palette class, handles palette operations like changing intensity, setting up the palette, merging different palettes
*/
@@ -53,7 +47,7 @@ public:
bool isUsing16bitColorMatch();
void setDefault();
- void createFromData(byte *data, int bytesLeft, Palette *paletteOut);
+ void createFromData(byte *data, int bytesLeft, Palette *paletteOut) const;
bool setAmiga();
void modifyAmigaPalette(byte *data);
void setEGA();
@@ -64,15 +58,6 @@ public:
void getSys(Palette *pal);
uint16 getTotalColorCount() const { return _totalScreenColors; }
- void resetRemapping();
- void setRemappingPercent(byte color, byte percent);
- void setRemappingPercentGray(byte color, byte percent);
- void setRemappingRange(byte color, byte from, byte to, byte base);
- bool isRemapped(byte color) const {
- return _remapOn && (_remappingType[color] != kRemappingNone);
- }
- byte remapColor(byte remappedColor, byte screenColor);
-
void setOnScreen();
void copySysPaletteToScreen();
@@ -104,19 +89,13 @@ public:
Palette _sysPalette;
- virtual void saveLoadWithSerializer(Common::Serializer &s);
+ void saveLoadWithSerializer(Common::Serializer &s);
void palVarySaveLoadPalette(Common::Serializer &s, Palette *palette);
byte findMacIconBarColor(byte r, byte g, byte b);
bool colorIsFromMacClut(byte index);
-#ifdef ENABLE_SCI32
- bool loadClut(uint16 clutId);
- byte matchClutColor(uint16 color);
- void unloadClut();
-#endif
-
-private:
+protected:
void palVaryInit();
void palVaryInstallTimer();
void palVaryRemoveTimer();
@@ -144,18 +123,8 @@ private:
int _palVarySignal;
uint16 _totalScreenColors;
- bool _remapOn;
- ColorRemappingType _remappingType[256];
- byte _remappingByPercent[256];
- byte _remappingByRange[256];
- uint16 _remappingPercentToSet;
-
void loadMacIconBarPalette();
byte *_macClut;
-
-#ifdef ENABLE_SCI32
- byte *_clutTable;
-#endif
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
new file mode 100644
index 0000000000..2a98c237b0
--- /dev/null
+++ b/engines/sci/graphics/palette32.cpp
@@ -0,0 +1,841 @@
+/* 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.
+ *
+ */
+
+#include "common/file.h"
+#include "common/system.h"
+#include "graphics/palette.h"
+
+#include "sci/sci.h"
+#include "sci/event.h"
+#include "sci/resource.h"
+#include "sci/util.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
+#include "sci/graphics/screen.h"
+
+namespace Sci {
+
+#pragma mark HunkPalette
+
+HunkPalette::HunkPalette(byte *rawPalette) :
+ _version(0),
+ // NOTE: The header size in palettes is garbage. In at least KQ7 2.00b and
+ // Phant1, the 999.pal sets this value to 0. In most other palettes it is
+ // set to 14, but the *actual* size of the header structure used in SSCI is
+ // 13, which is reflected by `kHunkPaletteHeaderSize`.
+ // _headerSize(rawPalette[0]),
+ _numPalettes(rawPalette[10]),
+ _data(nullptr) {
+ assert(_numPalettes == 0 || _numPalettes == 1);
+ if (_numPalettes) {
+ _data = rawPalette;
+ _version = getEntryHeader().version;
+ }
+}
+
+void HunkPalette::setVersion(const uint32 version) {
+ if (_numPalettes != _data[10]) {
+ error("Invalid HunkPalette");
+ }
+
+ if (_numPalettes) {
+ const EntryHeader header = getEntryHeader();
+ if (header.version != _version) {
+ error("Invalid HunkPalette");
+ }
+
+ WRITE_SCI11ENDIAN_UINT32(getPalPointer() + kEntryVersionOffset, version);
+ _version = version;
+ }
+}
+
+const HunkPalette::EntryHeader HunkPalette::getEntryHeader() const {
+ const byte *const data = getPalPointer();
+
+ EntryHeader header;
+ header.startColor = data[10];
+ header.numColors = READ_SCI11ENDIAN_UINT16(data + 14);
+ header.used = data[16];
+ header.sharedUsed = data[17];
+ header.version = READ_SCI11ENDIAN_UINT32(data + kEntryVersionOffset);
+
+ return header;
+}
+
+const Palette HunkPalette::toPalette() const {
+ Palette outPalette;
+
+ for (int16 i = 0; i < ARRAYSIZE(outPalette.colors); ++i) {
+ outPalette.colors[i].used = false;
+ outPalette.colors[i].r = 0;
+ outPalette.colors[i].g = 0;
+ outPalette.colors[i].b = 0;
+ }
+
+ if (_numPalettes) {
+ const EntryHeader header = getEntryHeader();
+ byte *data = getPalPointer() + kEntryHeaderSize;
+
+ int16 end = header.startColor + header.numColors;
+ assert(end <= 256);
+
+ if (header.sharedUsed) {
+ for (int16 i = header.startColor; i < end; ++i) {
+ outPalette.colors[i].used = header.used;
+ outPalette.colors[i].r = *data++;
+ outPalette.colors[i].g = *data++;
+ outPalette.colors[i].b = *data++;
+ }
+ } else {
+ for (int16 i = header.startColor; i < end; ++i) {
+ outPalette.colors[i].used = *data++;
+ outPalette.colors[i].r = *data++;
+ outPalette.colors[i].g = *data++;
+ outPalette.colors[i].b = *data++;
+ }
+ }
+ }
+
+ return outPalette;
+}
+
+
+#pragma mark -
+#pragma mark GfxPalette32
+
+GfxPalette32::GfxPalette32(ResourceManager *resMan)
+ : _resMan(resMan),
+ // Palette versioning
+ _version(1),
+ _needsUpdate(false),
+ _currentPalette(),
+ _sourcePalette(),
+ _nextPalette(),
+ // Clut
+ _clutTable(nullptr),
+ // Palette varying
+ _varyStartPalette(nullptr),
+ _varyTargetPalette(nullptr),
+ _varyFromColor(0),
+ _varyToColor(255),
+ _varyLastTick(0),
+ _varyTime(0),
+ _varyDirection(0),
+ _varyTargetPercent(0),
+ _varyNumTimesPaused(0),
+ // Palette cycling
+ _cyclers(),
+ _cycleMap() {
+ _varyPercent = _varyTargetPercent;
+ for (int i = 0, len = ARRAYSIZE(_fadeTable); i < len; ++i) {
+ _fadeTable[i] = 100;
+ }
+
+ loadPalette(999);
+}
+
+GfxPalette32::~GfxPalette32() {
+#ifdef ENABLE_SCI3_GAMES
+ unloadClut();
+#endif
+ varyOff();
+ cycleAllOff();
+}
+
+inline void mergePaletteInternal(Palette *const to, const Palette *const from) {
+ // The last color is always white, so it is not copied.
+ // (Some palettes try to set the last color, which causes
+ // churning in the palettes when they are merged)
+ for (int i = 0, len = ARRAYSIZE(to->colors) - 1; i < len; ++i) {
+ if (from->colors[i].used) {
+ to->colors[i] = from->colors[i];
+ }
+ }
+}
+
+void GfxPalette32::submit(const Palette &palette) {
+ const Palette oldSourcePalette(_sourcePalette);
+ mergePaletteInternal(&_sourcePalette, &palette);
+
+ if (!_needsUpdate && _sourcePalette != oldSourcePalette) {
+ ++_version;
+ _needsUpdate = true;
+ }
+}
+
+void GfxPalette32::submit(HunkPalette &hunkPalette) {
+ if (hunkPalette.getVersion() == _version) {
+ return;
+ }
+
+ const Palette oldSourcePalette(_sourcePalette);
+ const Palette palette = hunkPalette.toPalette();
+ mergePaletteInternal(&_sourcePalette, &palette);
+
+ if (!_needsUpdate && oldSourcePalette != _sourcePalette) {
+ ++_version;
+ _needsUpdate = true;
+ }
+
+ hunkPalette.setVersion(_version);
+}
+
+bool GfxPalette32::loadPalette(const GuiResourceId resourceId) {
+ Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
+
+ if (!palResource) {
+ return false;
+ }
+
+ HunkPalette palette(palResource->data);
+ submit(palette);
+ return true;
+}
+
+int16 GfxPalette32::matchColor(const uint8 r, const uint8 g, const uint8 b) {
+ int16 bestIndex = 0;
+ int bestDifference = 0xFFFFF;
+ int difference;
+
+ for (int i = 0, channelDifference; i < g_sci->_gfxRemap32->getStartColor(); ++i) {
+ difference = _currentPalette.colors[i].r - r;
+ difference *= difference;
+ if (bestDifference <= difference) {
+ continue;
+ }
+
+ channelDifference = _currentPalette.colors[i].g - g;
+ difference += channelDifference * channelDifference;
+ if (bestDifference <= difference) {
+ continue;
+ }
+
+ channelDifference = _currentPalette.colors[i].b - b;
+ difference += channelDifference * channelDifference;
+ if (bestDifference <= difference) {
+ continue;
+ }
+ bestDifference = difference;
+ bestIndex = i;
+ }
+
+ return bestIndex;
+}
+
+bool GfxPalette32::updateForFrame() {
+ applyAll();
+ _needsUpdate = false;
+ return g_sci->_gfxRemap32->remapAllTables(_nextPalette != _currentPalette);
+}
+
+void GfxPalette32::updateFFrame() {
+ for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
+ _nextPalette.colors[i] = _sourcePalette.colors[i];
+ }
+ _needsUpdate = false;
+ g_sci->_gfxRemap32->remapAllTables(_nextPalette != _currentPalette);
+}
+
+void GfxPalette32::updateHardware(const bool updateScreen) {
+ if (_currentPalette == _nextPalette) {
+ return;
+ }
+
+ byte bpal[3 * 256];
+
+ for (int i = 0; i < ARRAYSIZE(_currentPalette.colors) - 1; ++i) {
+ _currentPalette.colors[i] = _nextPalette.colors[i];
+
+ // NOTE: If the brightness option in the user configuration file is set,
+ // SCI engine adjusts palette brightnesses here by mapping RGB values to values
+ // in some hard-coded brightness tables. There is no reason on modern hardware
+ // to implement this, unless it is discovered that some game uses a non-standard
+ // brightness setting by default
+
+ // All color entries MUST be copied, not just "used" entries, otherwise
+ // uninitialised memory from bpal makes its way into the system palette.
+ // This would not normally be a problem, except that games sometimes use
+ // unused palette entries. e.g. Phant1 title screen references palette
+ // entries outside its own palette, so will render garbage colors where
+ // the game expects them to be black
+ bpal[i * 3 ] = _currentPalette.colors[i].r;
+ bpal[i * 3 + 1] = _currentPalette.colors[i].g;
+ bpal[i * 3 + 2] = _currentPalette.colors[i].b;
+ }
+
+ // The last color must always be white
+ bpal[255 * 3 ] = 255;
+ bpal[255 * 3 + 1] = 255;
+ bpal[255 * 3 + 2] = 255;
+
+ g_system->getPaletteManager()->setPalette(bpal, 0, 256);
+ if (updateScreen) {
+ g_sci->getEventManager()->updateScreen();
+ }
+}
+
+void GfxPalette32::applyAll() {
+ applyVary();
+ applyCycles();
+ applyFade();
+}
+
+#pragma mark -
+#pragma mark Colour look-up
+
+#ifdef ENABLE_SCI3_GAMES
+bool GfxPalette32::loadClut(uint16 clutId) {
+ // loadClut() will load a color lookup table from a clu file and set
+ // the palette found in the file. This is to be used with Phantasmagoria 2.
+
+ unloadClut();
+
+ Common::String filename = Common::String::format("%d.clu", clutId);
+ Common::File clut;
+
+ if (!clut.open(filename) || clut.size() != 0x10000 + 236 * 3)
+ return false;
+
+ // Read in the lookup table
+ // It maps each RGB565 color to a palette index
+ _clutTable = new byte[0x10000];
+ clut.read(_clutTable, 0x10000);
+
+ Palette pal;
+ memset(&pal, 0, sizeof(Palette));
+
+ // Setup 1:1 mapping
+ for (int i = 0; i < 256; i++) {
+ pal.mapping[i] = i;
+ }
+
+ // Now load in the palette
+ for (int i = 1; i <= 236; i++) {
+ pal.colors[i].used = 1;
+ pal.colors[i].r = clut.readByte();
+ pal.colors[i].g = clut.readByte();
+ pal.colors[i].b = clut.readByte();
+ }
+
+ set(&pal, true);
+ setOnScreen();
+ return true;
+}
+
+byte GfxPalette32::matchClutColor(uint16 color) {
+ // Match a color in RGB565 format to a palette index based on the loaded CLUT
+ assert(_clutTable);
+ return _clutTable[color];
+}
+
+void GfxPalette32::unloadClut() {
+ // This will only unload the actual table, but not reset any palette
+ delete[] _clutTable;
+ _clutTable = nullptr;
+}
+#endif
+
+#pragma mark -
+#pragma mark Varying
+
+inline Palette GfxPalette32::getPaletteFromResourceInternal(const GuiResourceId resourceId) const {
+ Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
+
+ if (!palResource) {
+ error("Could not load vary palette %d", resourceId);
+ }
+
+ HunkPalette rawPalette(palResource->data);
+ return rawPalette.toPalette();
+}
+
+inline void GfxPalette32::setVaryTimeInternal(const int16 percent, const int time) {
+ _varyLastTick = g_sci->getTickCount();
+ if (!time || _varyPercent == percent) {
+ _varyDirection = 0;
+ _varyTargetPercent = _varyPercent = percent;
+ } else {
+ _varyTime = time / (percent - _varyPercent);
+ _varyTargetPercent = percent;
+
+ if (_varyTime > 0) {
+ _varyDirection = 1;
+ } else if (_varyTime < 0) {
+ _varyDirection = -1;
+ _varyTime = -_varyTime;
+ } else {
+ _varyDirection = 0;
+ _varyTargetPercent = _varyPercent = percent;
+ }
+ }
+}
+
+void GfxPalette32::kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor) {
+ Palette palette = getPaletteFromResourceInternal(paletteId);
+ setVary(&palette, percent, time, fromColor, toColor);
+}
+
+void GfxPalette32::kernelPalVaryMergeTarget(GuiResourceId paletteId) {
+ Palette palette = getPaletteFromResourceInternal(paletteId);
+ mergeTarget(&palette);
+}
+
+void GfxPalette32::kernelPalVarySetTarget(GuiResourceId paletteId) {
+ Palette palette = getPaletteFromResourceInternal(paletteId);
+ setTarget(&palette);
+}
+
+void GfxPalette32::kernelPalVarySetStart(GuiResourceId paletteId) {
+ Palette palette = getPaletteFromResourceInternal(paletteId);
+ setStart(&palette);
+}
+
+void GfxPalette32::kernelPalVaryMergeStart(GuiResourceId paletteId) {
+ Palette palette = getPaletteFromResourceInternal(paletteId);
+ mergeStart(&palette);
+}
+
+void GfxPalette32::kernelPalVaryPause(bool pause) {
+ if (pause) {
+ varyPause();
+ } else {
+ varyOn();
+ }
+}
+
+void GfxPalette32::setVary(const Palette *const target, const int16 percent, const int time, const int16 fromColor, const int16 toColor) {
+ setTarget(target);
+ setVaryTimeInternal(percent, time);
+
+ if (fromColor > -1) {
+ _varyFromColor = fromColor;
+ }
+ if (toColor > -1) {
+ assert(toColor < 256);
+ _varyToColor = toColor;
+ }
+}
+
+void GfxPalette32::setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate) {
+ if (_varyTargetPalette != nullptr) {
+ setVaryTimeInternal(percent, time);
+ }
+
+ // This looks like a mistake in the actual SCI engine (both SQ6 and Lighthouse);
+ // the values are always hardcoded to -1 in kPalVary, so this code can never
+ // actually be executed
+ if (fromColor > -1) {
+ _varyFromColor = fromColor;
+ }
+ if (fromColorAlternate > -1) {
+ _varyFromColor = fromColorAlternate;
+ }
+}
+
+int16 GfxPalette32::getVaryPercent() const {
+ return ABS(_varyPercent);
+}
+
+void GfxPalette32::varyOff() {
+ _varyNumTimesPaused = 0;
+ _varyPercent = _varyTargetPercent = 0;
+ _varyFromColor = 0;
+ _varyToColor = 255;
+ _varyDirection = 0;
+
+ if (_varyTargetPalette != nullptr) {
+ delete _varyTargetPalette;
+ _varyTargetPalette = nullptr;
+ }
+
+ if (_varyStartPalette != nullptr) {
+ delete _varyStartPalette;
+ _varyStartPalette = nullptr;
+ }
+}
+
+void GfxPalette32::mergeTarget(const Palette *const palette) {
+ if (_varyTargetPalette != nullptr) {
+ mergePaletteInternal(_varyTargetPalette, palette);
+ } else {
+ _varyTargetPalette = new Palette(*palette);
+ }
+}
+
+void GfxPalette32::varyPause() {
+ _varyDirection = 0;
+ ++_varyNumTimesPaused;
+}
+
+void GfxPalette32::varyOn() {
+ if (_varyNumTimesPaused > 0) {
+ --_varyNumTimesPaused;
+ }
+
+ if (_varyTargetPalette != nullptr && _varyNumTimesPaused == 0 && _varyPercent != _varyTargetPercent) {
+ if (_varyTime == 0) {
+ _varyPercent = _varyTargetPercent;
+ } else if (_varyTargetPercent < _varyPercent) {
+ _varyDirection = -1;
+ } else {
+ _varyDirection = 1;
+ }
+ }
+}
+
+void GfxPalette32::setVaryTime(const int time) {
+ if (_varyTargetPalette == nullptr) {
+ return;
+ }
+
+ setVaryTimeInternal(_varyTargetPercent, time);
+}
+
+void GfxPalette32::setTarget(const Palette *const palette) {
+ if (_varyTargetPalette != nullptr) {
+ delete _varyTargetPalette;
+ }
+
+ _varyTargetPalette = new Palette(*palette);
+}
+
+void GfxPalette32::setStart(const Palette *const palette) {
+ if (_varyStartPalette != nullptr) {
+ delete _varyStartPalette;
+ }
+
+ _varyStartPalette = new Palette(*palette);
+}
+
+void GfxPalette32::mergeStart(const Palette *const palette) {
+ if (_varyStartPalette != nullptr) {
+ mergePaletteInternal(_varyStartPalette, palette);
+ } else {
+ _varyStartPalette = new Palette(*palette);
+ }
+}
+
+void GfxPalette32::applyVary() {
+ while (g_sci->getTickCount() - _varyLastTick > (uint32)_varyTime && _varyDirection != 0) {
+ _varyLastTick += _varyTime;
+
+ if (_varyPercent == _varyTargetPercent) {
+ _varyDirection = 0;
+ }
+
+ _varyPercent += _varyDirection;
+ }
+
+ if (_varyPercent == 0 || _varyTargetPalette == nullptr) {
+ for (int i = 0, len = ARRAYSIZE(_nextPalette.colors); i < len; ++i) {
+ if (_varyStartPalette != nullptr && i >= _varyFromColor && i <= _varyToColor) {
+ _nextPalette.colors[i] = _varyStartPalette->colors[i];
+ } else {
+ _nextPalette.colors[i] = _sourcePalette.colors[i];
+ }
+ }
+ } else {
+ for (int i = 0, len = ARRAYSIZE(_nextPalette.colors); i < len; ++i) {
+ if (i >= _varyFromColor && i <= _varyToColor) {
+ Color targetColor = _varyTargetPalette->colors[i];
+ Color sourceColor;
+
+ if (_varyStartPalette != nullptr) {
+ sourceColor = _varyStartPalette->colors[i];
+ } else {
+ sourceColor = _sourcePalette.colors[i];
+ }
+
+ Color computedColor;
+
+ int color;
+ color = targetColor.r - sourceColor.r;
+ computedColor.r = ((color * _varyPercent) / 100) + sourceColor.r;
+ color = targetColor.g - sourceColor.g;
+ computedColor.g = ((color * _varyPercent) / 100) + sourceColor.g;
+ color = targetColor.b - sourceColor.b;
+ computedColor.b = ((color * _varyPercent) / 100) + sourceColor.b;
+ computedColor.used = sourceColor.used;
+
+ _nextPalette.colors[i] = computedColor;
+ }
+ else {
+ _nextPalette.colors[i] = _sourcePalette.colors[i];
+ }
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark Cycling
+
+inline void GfxPalette32::clearCycleMap(const uint16 fromColor, const uint16 numColorsToClear) {
+ bool *mapEntry = _cycleMap + fromColor;
+ const bool *lastEntry = _cycleMap + numColorsToClear;
+ while (mapEntry < lastEntry) {
+ *mapEntry++ = false;
+ }
+}
+
+inline void GfxPalette32::setCycleMap(const uint16 fromColor, const uint16 numColorsToSet) {
+ bool *mapEntry = _cycleMap + fromColor;
+ const bool *lastEntry = _cycleMap + numColorsToSet;
+ while (mapEntry < lastEntry) {
+ if (*mapEntry != false) {
+ error("Cycles intersect");
+ }
+ *mapEntry++ = true;
+ }
+}
+
+inline PalCycler *GfxPalette32::getCycler(const uint16 fromColor) {
+ const int numCyclers = ARRAYSIZE(_cyclers);
+
+ for (int cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) {
+ PalCycler *cycler = _cyclers[cyclerIndex];
+ if (cycler != nullptr && cycler->fromColor == fromColor) {
+ return cycler;
+ }
+ }
+
+ return nullptr;
+}
+
+inline void doCycleInternal(PalCycler *cycler, const int16 speed) {
+ int16 currentCycle = cycler->currentCycle;
+ const uint16 numColorsToCycle = cycler->numColorsToCycle;
+
+ if (cycler->direction == 0) {
+ currentCycle = (currentCycle - (speed % numColorsToCycle)) + numColorsToCycle;
+ } else {
+ currentCycle = currentCycle + speed;
+ }
+
+ cycler->currentCycle = (uint8) (currentCycle % numColorsToCycle);
+}
+
+void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay) {
+ assert(fromColor < toColor);
+
+ int cyclerIndex;
+ const int numCyclers = ARRAYSIZE(_cyclers);
+
+ PalCycler *cycler = getCycler(fromColor);
+
+ if (cycler != nullptr) {
+ clearCycleMap(fromColor, cycler->numColorsToCycle);
+ } else {
+ for (cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) {
+ if (_cyclers[cyclerIndex] == nullptr) {
+ cycler = new PalCycler;
+ _cyclers[cyclerIndex] = cycler;
+ break;
+ }
+ }
+ }
+
+ // SCI engine overrides the first oldest cycler that it finds where
+ // “oldest” is determined by the difference between the tick and now
+ if (cycler == nullptr) {
+ const uint32 now = g_sci->getTickCount();
+ uint32 minUpdateDelta = 0xFFFFFFFF;
+
+ for (cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) {
+ PalCycler *candidate = _cyclers[cyclerIndex];
+
+ const uint32 updateDelta = now - candidate->lastUpdateTick;
+ if (updateDelta < minUpdateDelta) {
+ minUpdateDelta = updateDelta;
+ cycler = candidate;
+ }
+ }
+
+ clearCycleMap(cycler->fromColor, cycler->numColorsToCycle);
+ }
+
+ const uint16 numColorsToCycle = 1 + ((uint8) toColor) - fromColor;
+ cycler->fromColor = (uint8) fromColor;
+ cycler->numColorsToCycle = (uint8) numColorsToCycle;
+ cycler->currentCycle = (uint8) fromColor;
+ cycler->direction = direction < 0 ? PalCycleBackward : PalCycleForward;
+ cycler->delay = delay;
+ cycler->lastUpdateTick = g_sci->getTickCount();
+ cycler->numTimesPaused = 0;
+
+ setCycleMap(fromColor, numColorsToCycle);
+}
+
+void GfxPalette32::doCycle(const uint8 fromColor, const int16 speed) {
+ PalCycler *cycler = getCycler(fromColor);
+ if (cycler != nullptr) {
+ cycler->lastUpdateTick = g_sci->getTickCount();
+ doCycleInternal(cycler, speed);
+ }
+}
+
+void GfxPalette32::cycleOn(const uint8 fromColor) {
+ PalCycler *cycler = getCycler(fromColor);
+ if (cycler != nullptr && cycler->numTimesPaused > 0) {
+ --cycler->numTimesPaused;
+ }
+}
+
+void GfxPalette32::cyclePause(const uint8 fromColor) {
+ PalCycler *cycler = getCycler(fromColor);
+ if (cycler != nullptr) {
+ ++cycler->numTimesPaused;
+ }
+}
+
+void GfxPalette32::cycleAllOn() {
+ for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
+ PalCycler *cycler = _cyclers[i];
+ if (cycler != nullptr && cycler->numTimesPaused > 0) {
+ --cycler->numTimesPaused;
+ }
+ }
+}
+
+void GfxPalette32::cycleAllPause() {
+ // NOTE: The original engine did not check for null pointers in the
+ // palette cyclers pointer array.
+ for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
+ PalCycler *cycler = _cyclers[i];
+ if (cycler != nullptr) {
+ // This seems odd, because currentCycle is 0..numColorsPerCycle,
+ // but fromColor is 0..255. When applyAllCycles runs, the values
+ // end up back in range
+ cycler->currentCycle = cycler->fromColor;
+ }
+ }
+
+ applyAllCycles();
+
+ for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
+ PalCycler *cycler = _cyclers[i];
+ if (cycler != nullptr) {
+ ++cycler->numTimesPaused;
+ }
+ }
+}
+
+void GfxPalette32::cycleOff(const uint8 fromColor) {
+ for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
+ PalCycler *cycler = _cyclers[i];
+ if (cycler != nullptr && cycler->fromColor == fromColor) {
+ clearCycleMap(fromColor, cycler->numColorsToCycle);
+ delete cycler;
+ _cyclers[i] = nullptr;
+ break;
+ }
+ }
+}
+
+void GfxPalette32::cycleAllOff() {
+ for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
+ PalCycler *cycler = _cyclers[i];
+ if (cycler != nullptr) {
+ clearCycleMap(cycler->fromColor, cycler->numColorsToCycle);
+ delete cycler;
+ _cyclers[i] = nullptr;
+ }
+ }
+}
+
+void GfxPalette32::applyAllCycles() {
+ Color paletteCopy[256];
+ memcpy(paletteCopy, _nextPalette.colors, sizeof(Color) * 256);
+
+ for (int cyclerIndex = 0, numCyclers = ARRAYSIZE(_cyclers); cyclerIndex < numCyclers; ++cyclerIndex) {
+ PalCycler *cycler = _cyclers[cyclerIndex];
+ if (cycler != nullptr) {
+ cycler->currentCycle = (uint8) ((((int) cycler->currentCycle) + 1) % cycler->numColorsToCycle);
+ // Disassembly was not fully evaluated to verify this is exactly the same
+ // as the code from applyCycles, but it appeared to be at a glance
+ for (int j = 0; j < cycler->numColorsToCycle; j++) {
+ _nextPalette.colors[cycler->fromColor + j] = paletteCopy[cycler->fromColor + (cycler->currentCycle + j) % cycler->numColorsToCycle];
+ }
+ }
+ }
+}
+
+void GfxPalette32::applyCycles() {
+ Color paletteCopy[256];
+ memcpy(paletteCopy, _nextPalette.colors, sizeof(Color) * 256);
+
+ for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
+ PalCycler *cycler = _cyclers[i];
+ if (cycler == nullptr) {
+ continue;
+ }
+
+ if (cycler->delay != 0 && cycler->numTimesPaused == 0) {
+ while ((cycler->delay + cycler->lastUpdateTick) < g_sci->getTickCount()) {
+ doCycleInternal(cycler, 1);
+ cycler->lastUpdateTick += cycler->delay;
+ }
+ }
+
+ for (int j = 0; j < cycler->numColorsToCycle; j++) {
+ _nextPalette.colors[cycler->fromColor + j] = paletteCopy[cycler->fromColor + (cycler->currentCycle + j) % cycler->numColorsToCycle];
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark Fading
+
+// NOTE: There are some game scripts (like SQ6 Sierra logo and main menu) that call
+// setFade with numColorsToFade set to 256, but other parts of the engine like
+// processShowStyleNone use 255 instead of 256. It is not clear if this is because
+// the last palette entry is intentionally left unmodified, or if this is a bug
+// in the engine. It certainly seems confused because all other places that accept
+// color ranges typically receive values in the range of 0–255.
+void GfxPalette32::setFade(uint16 percent, uint8 fromColor, uint16 numColorsToFade) {
+ if (fromColor > numColorsToFade) {
+ return;
+ }
+
+ assert(numColorsToFade <= ARRAYSIZE(_fadeTable));
+
+ for (int i = fromColor; i < numColorsToFade; i++)
+ _fadeTable[i] = percent;
+}
+
+void GfxPalette32::fadeOff() {
+ setFade(100, 0, 256);
+}
+
+void GfxPalette32::applyFade() {
+ for (int i = 0; i < ARRAYSIZE(_fadeTable); ++i) {
+ if (_fadeTable[i] == 100)
+ continue;
+
+ Color &color = _nextPalette.colors[i];
+
+ color.r = MIN(255, (uint16)color.r * _fadeTable[i] / 100);
+ color.g = MIN(255, (uint16)color.g * _fadeTable[i] / 100);
+ color.b = MIN(255, (uint16)color.b * _fadeTable[i] / 100);
+ }
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
new file mode 100644
index 0000000000..dc2158022f
--- /dev/null
+++ b/engines/sci/graphics/palette32.h
@@ -0,0 +1,411 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_PALETTE32_H
+#define SCI_GRAPHICS_PALETTE32_H
+
+#include "sci/graphics/palette.h"
+
+namespace Sci {
+
+/**
+ * HunkPalette represents a raw palette resource
+ * read from disk.
+ */
+class HunkPalette {
+public:
+ HunkPalette(byte *rawPalette);
+
+ /**
+ * Gets the version of the palette.
+ */
+ uint32 getVersion() const { return _version; }
+
+ /**
+ * Sets the version of the palette.
+ */
+ void setVersion(const uint32 version);
+
+ /**
+ * Converts the hunk palette to a standard
+ * palette.
+ */
+ const Palette toPalette() const;
+
+private:
+ enum {
+ /**
+ * The size of the HunkPalette header.
+ */
+ kHunkPaletteHeaderSize = 13,
+
+ /**
+ * The size of a palette entry header.
+ */
+ kEntryHeaderSize = 22,
+
+ /**
+ * The offset of the hunk palette version
+ * within the palette entry header.
+ */
+ kEntryVersionOffset = 18
+ };
+
+ /**
+ * The header for a palette inside the
+ * HunkPalette.
+ */
+ struct EntryHeader {
+ /**
+ * The start color.
+ */
+ uint8 startColor;
+
+ /**
+ * The number of palette colors in this
+ * entry.
+ */
+ uint16 numColors;
+
+ /**
+ * The default `used` flag.
+ */
+ bool used;
+
+ /**
+ * Whether or not all palette entries
+ * share the same `used` value in
+ * `defaultFlag`.
+ */
+ bool sharedUsed;
+
+ /**
+ * The palette version.
+ */
+ uint32 version;
+ };
+
+ /**
+ * The version number from the last time this
+ * palette was submitted to GfxPalette32.
+ */
+ uint32 _version;
+
+ /**
+ * The number of palettes stored in the hunk
+ * palette. In SCI32 games this is always 1.
+ */
+ uint8 _numPalettes;
+
+ /**
+ * The raw palette data for this hunk palette.
+ */
+ byte *_data;
+
+ /**
+ * Returns a struct that describes the palette
+ * held by this HunkPalette. The entry header
+ * is reconstructed on every call from the raw
+ * palette data.
+ */
+ const EntryHeader getEntryHeader() const;
+
+ /**
+ * Returns a pointer to the palette data within
+ * the hunk palette.
+ */
+ byte *getPalPointer() const {
+ return _data + kHunkPaletteHeaderSize + (2 * _numPalettes);
+ }
+};
+
+enum PalCyclerDirection {
+ PalCycleBackward = 0,
+ PalCycleForward = 1
+};
+
+struct PalCycler {
+ /**
+ * The color index of the palette cycler. This value is effectively used as the ID for the
+ * cycler.
+ */
+ uint8 fromColor;
+
+ /**
+ * The number of palette slots which are cycled by the palette cycler.
+ */
+ uint16 numColorsToCycle;
+
+ /**
+ * The position of the cursor in its cycle.
+ */
+ uint8 currentCycle;
+
+ /**
+ * The direction of the cycler.
+ */
+ PalCyclerDirection direction;
+
+ /**
+ * The cycle tick at the last time the cycler’s currentCycle was updated.
+ * 795 days of game time ought to be enough for everyone? :)
+ */
+ uint32 lastUpdateTick;
+
+ /**
+ * The amount of time in ticks each cycle should take to complete. In other words,
+ * the higher the delay, the slower the cycle animation. If delay is 0, the cycler
+ * does not automatically cycle and needs to be pumped manually with DoCycle.
+ */
+ int16 delay;
+
+ /**
+ * The number of times this cycler has been paused.
+ */
+ uint16 numTimesPaused;
+};
+
+class GfxPalette32 {
+public:
+ GfxPalette32(ResourceManager *resMan);
+ ~GfxPalette32();
+
+private:
+ ResourceManager *_resMan;
+
+ /**
+ * The palette revision version. Increments once per game
+ * loop that changes the source palette.
+ */
+ uint32 _version;
+
+ /**
+ * Whether or not the hardware palette needs updating.
+ */
+ bool _needsUpdate;
+
+ /**
+ * The currently displayed palette.
+ */
+ Palette _currentPalette;
+
+ /**
+ * The unmodified source palette loaded by kPalette. Additional
+ * palette entries may be mixed into the source palette by
+ * CelObj objects, which contain their own palettes.
+ */
+ Palette _sourcePalette;
+
+ /**
+ * The palette to be used when the hardware is next updated.
+ * On update, _nextPalette is transferred to _currentPalette.
+ */
+ Palette _nextPalette;
+
+ bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const;
+ Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const;
+
+public:
+ void saveLoadWithSerializer(Common::Serializer &s);
+ inline const Palette &getNextPalette() const { return _nextPalette; };
+ inline const Palette &getCurrentPalette() const { return _currentPalette; };
+
+ /**
+ * Loads a palette into GfxPalette32 with the given resource
+ * ID.
+ */
+ bool loadPalette(const GuiResourceId resourceId);
+
+ /**
+ * Finds the nearest color in the current palette matching the
+ * given RGB value.
+ */
+ int16 matchColor(const uint8 r, const uint8 g, const uint8 b);
+
+ /**
+ * Submits a palette to display. Entries marked as “used” in the
+ * submitted palette are merged into the existing entries of
+ * _sourcePalette.
+ */
+ void submit(const Palette &palette);
+ void submit(HunkPalette &palette);
+
+ bool updateForFrame();
+ void updateFFrame();
+ void updateHardware(const bool updateScreen = true);
+ void applyAll();
+
+#pragma mark -
+#pragma mark Color look-up
+private:
+ /**
+ * An optional lookup table used to remap RGB565 colors to a palette
+ * index. Used by Phantasmagoria 2 in 8-bit color environments.
+ */
+ byte *_clutTable;
+
+public:
+ bool loadClut(uint16 clutId);
+ byte matchClutColor(uint16 color);
+ void unloadClut();
+
+#pragma mark -
+#pragma mark Varying
+private:
+ /**
+ * An optional palette used to describe the source colors used
+ * in a palette vary operation. If this palette is not specified,
+ * sourcePalette is used instead.
+ */
+ Palette *_varyStartPalette;
+
+ /**
+ * An optional palette used to describe the target colors used
+ * in a palette vary operation.
+ */
+ Palette *_varyTargetPalette;
+
+ /**
+ * The minimum palette index that has been varied from the
+ * source palette. 0–255
+ */
+ uint8 _varyFromColor;
+
+ /**
+ * The maximum palette index that is has been varied from the
+ * source palette. 0-255
+ */
+ uint8 _varyToColor;
+
+ /**
+ * The tick at the last time the palette vary was updated.
+ */
+ uint32 _varyLastTick;
+
+ /**
+ * The amount of time to elapse, in ticks, between each cycle
+ * of a palette vary animation.
+ */
+ int _varyTime;
+
+ /**
+ * The direction of change: -1, 0, or 1.
+ */
+ int16 _varyDirection;
+
+ /**
+ * The amount, in percent, that the vary color is currently
+ * blended into the source color.
+ */
+ int16 _varyPercent;
+
+ /**
+ * The target amount that a vary color will be blended into
+ * the source color.
+ */
+ int16 _varyTargetPercent;
+
+ /**
+ * The number of time palette varying has been paused.
+ */
+ uint16 _varyNumTimesPaused;
+
+public:
+ void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
+ void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
+ void kernelPalVarySetTarget(const GuiResourceId paletteId);
+ void kernelPalVarySetStart(const GuiResourceId paletteId);
+ void kernelPalVaryMergeStart(const GuiResourceId paletteId);
+ void kernelPalVaryPause(bool pause);
+
+ void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
+ void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate);
+ int16 getVaryPercent() const;
+ void varyOff();
+ void mergeTarget(const Palette *const palette);
+ void varyPause();
+ void varyOn();
+ void setVaryTime(const int time);
+ void setTarget(const Palette *const palette);
+ void setStart(const Palette *const palette);
+ void mergeStart(const Palette *const palette);
+ void setVaryTimeInternal(const int16 percent, const int time);
+ void applyVary();
+
+#pragma mark -
+#pragma mark Cycling
+private:
+ // SQ6 defines 10 cyclers
+ PalCycler *_cyclers[10];
+
+ /**
+ * The cycle map is used to detect overlapping cyclers.
+ * According to SCI engine code, when two cyclers overlap,
+ * a fatal error has occurred and the engine will display
+ * an error and then exit.
+ *
+ * The cycle map is also by the color remapping system to
+ * avoid attempting to remap to palette entries that are
+ * cycling (so won't be the expected color once the cycler
+ * runs again).
+ */
+ bool _cycleMap[256];
+ inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
+ inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear);
+ inline PalCycler *getCycler(uint16 fromColor);
+
+public:
+ void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay);
+ void doCycle(const uint8 fromColor, const int16 speed);
+ void cycleOn(const uint8 fromColor);
+ void cyclePause(const uint8 fromColor);
+ void cycleAllOn();
+ void cycleAllPause();
+ void cycleOff(const uint8 fromColor);
+ void cycleAllOff();
+ void applyAllCycles();
+ void applyCycles();
+ inline const bool *getCycleMap() const { return _cycleMap; }
+
+#pragma mark -
+#pragma mark Fading
+private:
+ /**
+ * The fade table records the expected intensity level of each pixel
+ * in the palette that will be displayed on the next frame.
+ */
+ uint16 _fadeTable[256];
+
+public:
+ /**
+ * Sets the intensity level for a range of palette
+ * entries. An intensity of zero indicates total
+ * darkness. Intensity may be set to over 100 percent.
+ */
+ void setFade(const uint16 percent, const uint8 fromColor, const uint16 toColor);
+ void fadeOff();
+ void applyFade();
+};
+
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp
index d7ef84dc1e..2eab391afd 100644
--- a/engines/sci/graphics/picture.cpp
+++ b/engines/sci/graphics/picture.cpp
@@ -209,12 +209,12 @@ void GfxPicture::drawSci32Vga(int16 celNo, int16 drawX, int16 drawY, int16 pictu
}
// Header
- // [headerSize:WORD] [celCount:BYTE] [Unknown:BYTE] [Unknown:WORD] [paletteOffset:DWORD] [Unknown:DWORD]
+ // 0[headerSize:WORD] 2[celCount:BYTE] 3[Unknown:BYTE] 4[celHeaderSize:WORD] 6[paletteOffset:DWORD] 10[Unknown:WORD] 12[Unknown:WORD]
// cel-header follow afterwards, each is 42 bytes
// Cel-Header
- // [width:WORD] [height:WORD] [displaceX:WORD] [displaceY:WORD] [clearColor:BYTE] [compressed:BYTE]
+ // 0[width:WORD] 2[height:WORD] 4[displaceX:WORD] 6[displaceY:WORD] 8[clearColor:BYTE] 9[compressed:BYTE]
// offset 10-23 is unknown
- // [rleOffset:DWORD] [literalOffset:DWORD] [Unknown:WORD] [Unknown:WORD] [priority:WORD] [relativeXpos:WORD] [relativeYpos:WORD]
+ // 24[rleOffset:DWORD] 28[literalOffset:DWORD] 32[Unknown:WORD] 34[Unknown:WORD] 36[priority:WORD] 38[relativeXpos:WORD] 40[relativeYpos:WORD]
cel_headerPos += 42 * celNo;
diff --git a/engines/sci/graphics/picture.h b/engines/sci/graphics/picture.h
index 2404f99b41..942fa0f107 100644
--- a/engines/sci/graphics/picture.h
+++ b/engines/sci/graphics/picture.h
@@ -38,6 +38,9 @@ enum {
class GfxPorts;
class GfxScreen;
class GfxPalette;
+class GfxCoordAdjuster;
+class ResourceManager;
+class Resource;
/**
* Picture class, handles loading and displaying of picture resources
diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
new file mode 100644
index 0000000000..aa629e4081
--- /dev/null
+++ b/engines/sci/graphics/plane32.cpp
@@ -0,0 +1,951 @@
+/* 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.
+ *
+ */
+
+#include "sci/console.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/selector.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/lists32.h"
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/remap32.h"
+#include "sci/graphics/screen.h"
+#include "sci/graphics/screen_item32.h"
+
+namespace Sci {
+#pragma mark DrawList
+void DrawList::add(ScreenItem *screenItem, const Common::Rect &rect) {
+ DrawItem *drawItem = new DrawItem;
+ drawItem->screenItem = screenItem;
+ drawItem->rect = rect;
+ DrawListBase::add(drawItem);
+}
+
+#pragma mark -
+#pragma mark Plane
+uint16 Plane::_nextObjectId = 20000;
+
+Plane::Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId) :
+_pictureId(pictureId),
+_mirrored(false),
+_type(kPlaneTypeColored),
+_back(0),
+_priorityChanged(0),
+_object(make_reg(0, _nextObjectId++)),
+_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_moved(0),
+_gameRect(gameRect) {
+ convertGameRectToPlaneRect();
+ _priority = MAX(10000, g_sci->_gfxFrameout->getPlanes().getTopPlanePriority() + 1);
+ setType();
+ _screenRect = _planeRect;
+}
+
+Plane::Plane(reg_t object) :
+_type(kPlaneTypeColored),
+_priorityChanged(false),
+_object(object),
+_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_moved(0) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+ _vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX));
+ _vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
+
+ _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+ _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ convertGameRectToPlaneRect();
+
+ _back = readSelectorValue(segMan, object, SELECTOR(back));
+ _priority = readSelectorValue(segMan, object, SELECTOR(priority));
+ _pictureId = readSelectorValue(segMan, object, SELECTOR(picture));
+ setType();
+
+ _mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored));
+ _screenRect = _planeRect;
+ changePic();
+}
+
+Plane::Plane(const Plane &other) :
+_pictureId(other._pictureId),
+_mirrored(other._mirrored),
+_type(other._type),
+_back(other._back),
+_object(other._object),
+_priority(other._priority),
+_planeRect(other._planeRect),
+_gameRect(other._gameRect),
+_screenRect(other._screenRect),
+_screenItemList(other._screenItemList) {}
+
+void Plane::operator=(const Plane &other) {
+ _gameRect = other._gameRect;
+ _planeRect = other._planeRect;
+ _vanishingPoint = other._vanishingPoint;
+ _pictureId = other._pictureId;
+ _type = other._type;
+ _mirrored = other._mirrored;
+ _priority = other._priority;
+ _back = other._back;
+ _screenRect = other._screenRect;
+ _priorityChanged = other._priorityChanged;
+}
+
+void Plane::init() {
+ _nextObjectId = 20000;
+}
+
+void Plane::convertGameRectToPlaneRect() {
+ 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;
+
+ const Ratio ratioX = Ratio(screenWidth, scriptWidth);
+ const Ratio ratioY = Ratio(screenHeight, scriptHeight);
+
+ _planeRect = _gameRect;
+ mulru(_planeRect, ratioX, ratioY, 1);
+}
+
+void Plane::printDebugInfo(Console *con) const {
+ Common::String name;
+
+ if (_object.isNumber()) {
+ name = "-scummvm-";
+ } else {
+ name = g_sci->getEngineState()->_segMan->getObjectName(_object);
+ }
+
+ con->debugPrintf("%04x:%04x (%s): type %d, prio %d, pic %d, mirror %d, back %d\n",
+ PRINT_REG(_object),
+ name.c_str(),
+ _type,
+ _priority,
+ _pictureId,
+ _mirrored,
+ _back
+ );
+ con->debugPrintf(" game rect: (%d, %d, %d, %d), plane rect: (%d, %d, %d, %d)\n screen rect: (%d, %d, %d, %d)\n",
+ PRINT_RECT(_gameRect),
+ PRINT_RECT(_planeRect),
+ PRINT_RECT(_screenRect)
+ );
+ con->debugPrintf(" # screen items: %d\n", _screenItemList.size());
+}
+
+#pragma mark -
+#pragma mark Plane - Pic
+
+void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) {
+
+ uint16 celCount = 1000;
+ bool transparent = true;
+ for (uint16 celNo = 0; celNo < celCount; ++celNo) {
+ CelObjPic *celObj = new CelObjPic(pictureId, celNo);
+ if (celCount == 1000) {
+ celCount = celObj->_celCount;
+ }
+ if (!celObj->_transparent) {
+ transparent = false;
+ }
+
+ ScreenItem *screenItem = new ScreenItem(_object, celObj->_info);
+ screenItem->_pictureId = pictureId;
+ screenItem->_mirrorX = mirrorX;
+ screenItem->_priority = celObj->_priority;
+ screenItem->_fixedPriority = true;
+ if (position != nullptr) {
+ screenItem->_position = *position + celObj->_relativePosition;
+ } else {
+ screenItem->_position = celObj->_relativePosition;
+ }
+ _screenItemList.add(screenItem);
+
+ delete screenItem->_celObj;
+ screenItem->_celObj = celObj;
+ }
+ _type = transparent ? kPlaneTypeTransparentPicture : kPlaneTypePicture;
+}
+
+void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) {
+ deletePic(pictureId);
+ addPicInternal(pictureId, &position, mirrorX);
+ // NOTE: In SCI engine this method returned the pictureId of the
+ // plane, but this return value was never used
+}
+
+void Plane::changePic() {
+ _pictureChanged = false;
+
+ if (_type != kPlaneTypePicture && _type != kPlaneTypeTransparentPicture) {
+ return;
+ }
+
+ addPicInternal(_pictureId, nullptr, _mirrored);
+}
+
+void Plane::deletePic(const GuiResourceId pictureId) {
+ for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+ ScreenItem *screenItem = *it;
+ if (screenItem->_pictureId == pictureId) {
+ screenItem->_created = 0;
+ screenItem->_updated = 0;
+ screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount();
+ }
+ }
+}
+
+void Plane::deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId) {
+ deletePic(oldPictureId);
+ _pictureId = newPictureId;
+}
+
+void Plane::deleteAllPics() {
+ for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+ ScreenItem *screenItem = *it;
+ if (screenItem != nullptr && screenItem->_celInfo.type == kCelTypePic) {
+ if (screenItem->_created == 0) {
+ screenItem->_created = 0;
+ screenItem->_updated = 0;
+ screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount();
+ } else {
+ _screenItemList.erase(it);
+ }
+ }
+ }
+
+ _screenItemList.pack();
+}
+
+#pragma mark -
+#pragma mark Plane - Rendering
+
+void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const {
+ const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1;
+ const PlaneList::size_type planeCount = planeList.size();
+
+ for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
+ for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) {
+ if (
+ planeList[j]->_type != kPlaneTypeTransparent &&
+ planeList[j]->_type != kPlaneTypeTransparentPicture
+ ) {
+ Common::Rect outRects[4];
+ int splitCount = splitRects(drawList[i]->rect, planeList[j]->_screenRect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ drawList.add(drawList[i]->screenItem, outRects[splitCount]);
+ }
+
+ drawList.erase_at(i);
+ break;
+ }
+ }
+ }
+ }
+ drawList.pack();
+}
+
+void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const {
+ const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1;
+ const PlaneList::size_type planeCount = planeList.size();
+
+ for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+ for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) {
+ if (
+ planeList[j]->_type != kPlaneTypeTransparent &&
+ planeList[j]->_type != kPlaneTypeTransparentPicture
+ ) {
+ Common::Rect outRects[4];
+ int splitCount = splitRects(*eraseList[i], planeList[j]->_screenRect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ eraseList.add(outRects[splitCount]);
+ }
+
+ eraseList.erase_at(i);
+ break;
+ }
+ }
+ }
+ }
+ eraseList.pack();
+}
+
+void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ const ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size();
+
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ // Items can be added to ScreenItemList and we don't want to process
+ // those new items, but the list also can grow smaller, so we need
+ // to check that we are still within the upper bound of the list and
+ // quit if we aren't any more
+ if (i >= _screenItemList.size()) {
+ break;
+ }
+
+ ScreenItem *item = _screenItemList[i];
+ if (item == nullptr) {
+ continue;
+ }
+
+ // NOTE: The original engine used an array without bounds checking
+ // so could just get the visible screen item directly; we need to
+ // verify that the index is actually within the valid range for
+ // the visible plane before accessing the item to avoid a range
+ // error.
+ const ScreenItem *visibleItem = nullptr;
+ if (i < visiblePlaneItemCount) {
+ visibleItem = visiblePlane._screenItemList[i];
+ }
+
+ // Keep erase rects for this screen item from drawing outside
+ // of its owner plane
+ Common::Rect visibleItemScreenRect;
+ if (visibleItem != nullptr) {
+ visibleItemScreenRect = visibleItem->_screenRect;
+ visibleItemScreenRect.clip(_screenRect);
+ }
+
+ if (item->_deleted) {
+ // Add item's rect to erase list
+ if (
+ visibleItem != nullptr &&
+ !visibleItemScreenRect.isEmpty()
+ ) {
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ mergeToRectList(visibleItemScreenRect, eraseList);
+ } else {
+ eraseList.add(visibleItemScreenRect);
+ }
+ }
+ }
+
+ if (!item->_created && !item->_updated) {
+ continue;
+ }
+
+ item->calcRects(*this);
+ const Common::Rect itemScreenRect(item->_screenRect);
+
+ if (item->_created) {
+ // Add item to draw list
+ if(!itemScreenRect.isEmpty()) {
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ drawList.add(item, itemScreenRect);
+ mergeToRectList(itemScreenRect, eraseList);
+ } else {
+ drawList.add(item, itemScreenRect);
+ }
+ }
+ } else {
+ // Add old rect to erase list, new item to draw list
+
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ // If item and visibleItem don't overlap...
+ if (itemScreenRect.isEmpty() ||
+ visibleItem == nullptr ||
+ visibleItemScreenRect.isEmpty() ||
+ !visibleItemScreenRect.intersects(itemScreenRect)
+ ) {
+ // ...add item to draw list, and old rect to erase list...
+ if (!itemScreenRect.isEmpty()) {
+ drawList.add(item, itemScreenRect);
+ mergeToRectList(itemScreenRect, eraseList);
+ }
+ if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) {
+ mergeToRectList(visibleItemScreenRect, eraseList);
+ }
+ } else {
+ // ...otherwise, add bounding box of old+new to erase list,
+ // and item to draw list
+ Common::Rect extendedScreenRect = visibleItemScreenRect;
+ extendedScreenRect.extend(itemScreenRect);
+
+ drawList.add(item, itemScreenRect);
+ mergeToRectList(extendedScreenRect, eraseList);
+ }
+ } else {
+ // If no active remaps, just add item to draw list and old rect
+ // to erase list
+
+ // TODO: SCI3 update rects for VMD?
+ if (!itemScreenRect.isEmpty()) {
+ drawList.add(item, itemScreenRect);
+ }
+ if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) {
+ eraseList.add(visibleItemScreenRect);
+ }
+ }
+ }
+ }
+
+ // Remove parts of eraselist/drawlist that are covered by other planes
+ breakEraseListByPlanes(eraseList, planeList);
+ breakDrawListByPlanes(drawList, planeList);
+
+ // We store the current size of the drawlist, as we want to loop
+ // over the currently inserted entries later.
+ DrawList::size_type drawListSizePrimary = drawList.size();
+ const RectList::size_type eraseListCount = eraseList.size();
+
+ // TODO: Figure out which games need which rendering method
+ if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"
+ _screenItemList.sort();
+ bool pictureDrawn = false;
+ bool screenItemDrawn = false;
+
+ for (RectList::size_type i = 0; i < eraseListCount; ++i) {
+ const Common::Rect &rect = *eraseList[i];
+
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ ScreenItem *item = _screenItemList[j];
+
+ if (item == nullptr) {
+ continue;
+ }
+
+ if (rect.intersects(item->_screenRect)) {
+ const Common::Rect intersection = rect.findIntersectingRect(item->_screenRect);
+ if (!item->_deleted) {
+ if (pictureDrawn) {
+ if (item->_celInfo.type == kCelTypePic) {
+ if (screenItemDrawn || item->_celInfo.celNo == 0) {
+ mergeToDrawList(j, intersection, drawList);
+ }
+ } else {
+ if (!item->_updated && !item->_created) {
+ mergeToDrawList(j, intersection, drawList);
+ }
+ screenItemDrawn = true;
+ }
+ } else {
+ if (!item->_updated && !item->_created) {
+ mergeToDrawList(j, intersection, drawList);
+ }
+ if (item->_celInfo.type == kCelTypePic) {
+ pictureDrawn = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ _screenItemList.unsort();
+ } else {
+ // Add all items overlapping the erase list to the draw list
+ for (RectList::size_type i = 0; i < eraseListCount; ++i) {
+ const Common::Rect &rect = *eraseList[i];
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ ScreenItem *item = _screenItemList[j];
+ if (
+ item != nullptr &&
+ !item->_created && !item->_updated && !item->_deleted &&
+ rect.intersects(item->_screenRect)
+ ) {
+ drawList.add(item, rect.findIntersectingRect(item->_screenRect));
+ }
+ }
+ }
+ }
+
+ if (g_sci->_gfxRemap32->getRemapCount() == 0) {
+ // Add all items that overlap with items in the drawlist and have higher
+ // priority.
+
+ // We only loop over "primary" items in the draw list, skipping
+ // those that were added because of the erase list in the previous loop,
+ // or those to be added in this loop.
+ for (DrawList::size_type i = 0; i < drawListSizePrimary; ++i) {
+ const DrawItem *drawListEntry = nullptr;
+ if (i < drawList.size()) {
+ drawListEntry = drawList[i];
+ }
+
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ ScreenItem *newItem = nullptr;
+ if (j < _screenItemList.size()) {
+ newItem = _screenItemList[j];
+ }
+
+ if (
+ drawListEntry != nullptr && newItem != nullptr &&
+ !newItem->_created && !newItem->_updated && !newItem->_deleted
+ ) {
+ const ScreenItem *drawnItem = drawListEntry->screenItem;
+
+ if (
+ (newItem->_priority > drawnItem->_priority || (newItem->_priority == drawnItem->_priority && newItem->_object > drawnItem->_object)) &&
+ drawListEntry->rect.intersects(newItem->_screenRect)
+ ) {
+ mergeToDrawList(j, drawListEntry->rect.findIntersectingRect(newItem->_screenRect), drawList);
+ }
+ }
+ }
+ }
+ }
+
+ decrementScreenItemArrayCounts(&visiblePlane, false);
+}
+
+void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) {
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ ScreenItem *item = _screenItemList[i];
+
+ if (item != nullptr) {
+ // update item in visiblePlane if item is updated
+ if (
+ item->_updated ||
+ (
+ forceUpdate &&
+ visiblePlane != nullptr &&
+ visiblePlane->_screenItemList.findByObject(item->_object) != nullptr
+ )
+ ) {
+ *visiblePlane->_screenItemList[i] = *item;
+ }
+
+ if (item->_updated) {
+ item->_updated--;
+ }
+
+ // create new item in visiblePlane if item was added
+ if (item->_created) {
+ item->_created--;
+ if (visiblePlane != nullptr) {
+ visiblePlane->_screenItemList.add(new ScreenItem(*item));
+ }
+ }
+
+ // delete item from both planes if it was deleted
+ if (item->_deleted) {
+ item->_deleted--;
+ if (!item->_deleted) {
+ if (visiblePlane != nullptr && visiblePlane->_screenItemList.findByObject(item->_object) != nullptr) {
+ visiblePlane->_screenItemList.erase_at(i);
+ }
+ _screenItemList.erase_at(i);
+ }
+ }
+ }
+ }
+
+ _screenItemList.pack();
+ if (visiblePlane != nullptr) {
+ visiblePlane->_screenItemList.pack();
+ }
+}
+
+void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const {
+ const RectList::size_type higherEraseCount = higherEraseList.size();
+
+ if (_type == kPlaneTypeTransparent || _type == kPlaneTypeTransparentPicture) {
+ for (RectList::size_type i = 0; i < higherEraseCount; ++i) {
+ const Common::Rect &r = *higherEraseList[i];
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
+ }
+ }
+ }
+ } else {
+ for (RectList::size_type i = 0; i < higherEraseCount; ++i) {
+ Common::Rect r = *higherEraseList[i];
+ if (r.intersects(_screenRect)) {
+ r.clip(_screenRect);
+ mergeToRectList(r, eraseList);
+
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
+ }
+ }
+
+ Common::Rect outRects[4];
+ const Common::Rect &r2 = *higherEraseList[i];
+ int splitCount = splitRects(r2, r, outRects);
+ if (splitCount > 0) {
+ while (splitCount--) {
+ higherEraseList.add(outRects[splitCount]);
+ }
+ }
+ higherEraseList.erase_at(i);
+ }
+ }
+
+ higherEraseList.pack();
+ }
+}
+
+void Plane::filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const {
+ const DrawList::size_type lowerDrawCount = lowerDrawList.size();
+ for (DrawList::size_type i = 0; i < lowerDrawCount; ++i) {
+ const Common::Rect &r = lowerDrawList[i]->rect;
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
+ }
+ }
+ }
+}
+
+void Plane::filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const {
+ const RectList::size_type lowerEraseCount = lowerEraseList.size();
+ for (RectList::size_type i = 0; i < lowerEraseCount; ++i) {
+ const Common::Rect &r = *lowerEraseList[i];
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
+ }
+ }
+ }
+}
+
+void Plane::mergeToDrawList(const ScreenItemList::size_type index, const Common::Rect &rect, DrawList &drawList) const {
+ RectList mergeList;
+ ScreenItem &item = *_screenItemList[index];
+ Common::Rect r = item._screenRect;
+ r.clip(rect);
+ mergeList.add(r);
+
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ r = *mergeList[i];
+
+ const DrawList::size_type drawCount = drawList.size();
+ for (DrawList::size_type j = 0; j < drawCount; ++j) {
+ const DrawItem &drawItem = *drawList[j];
+ if (item._object == drawItem.screenItem->_object) {
+ if (drawItem.rect.contains(r)) {
+ mergeList.erase_at(i);
+ break;
+ }
+
+ Common::Rect outRects[4];
+ int splitCount = splitRects(r, drawItem.rect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
+ }
+
+ mergeList.erase_at(i);
+
+ // proceed to the next rect
+ r = *mergeList[++i];
+ }
+ }
+ }
+ }
+
+ mergeList.pack();
+
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ drawList.add(&item, *mergeList[i]);
+ }
+}
+
+void Plane::mergeToRectList(const Common::Rect &rect, RectList &eraseList) const {
+ RectList mergeList;
+ Common::Rect r;
+ mergeList.add(rect);
+
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ r = *mergeList[i];
+
+ const RectList::size_type eraseCount = eraseList.size();
+ for (RectList::size_type j = 0; j < eraseCount; ++j) {
+ const Common::Rect &eraseRect = *eraseList[j];
+ if (eraseRect.contains(r)) {
+ mergeList.erase_at(i);
+ break;
+ }
+
+ Common::Rect outRects[4];
+ int splitCount = splitRects(r, eraseRect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
+ }
+
+ mergeList.erase_at(i);
+
+ // proceed to the next rect
+ r = *mergeList[++i];
+ }
+ }
+ }
+
+ mergeList.pack();
+
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ eraseList.add(*mergeList[i]);
+ }
+}
+
+void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ ScreenItem *screenItem = _screenItemList[i];
+ if (screenItem != nullptr && !screenItem->_deleted) {
+ screenItem->calcRects(*this);
+ if (!screenItem->_screenRect.isEmpty()) {
+ mergeToDrawList(i, screenItem->_screenRect, drawList);
+ }
+ }
+ }
+
+ eraseList.clear();
+
+ if (!_screenRect.isEmpty() && _type != kPlaneTypePicture && _type != kPlaneTypeOpaque) {
+ eraseList.add(_screenRect);
+ }
+ breakEraseListByPlanes(eraseList, planeList);
+ breakDrawListByPlanes(drawList, planeList);
+ --_redrawAllCount;
+ decrementScreenItemArrayCounts(visiblePlane, true);
+}
+
+void Plane::setType() {
+ switch (_pictureId) {
+ case kPlanePicColored:
+ _type = kPlaneTypeColored;
+ break;
+ case kPlanePicTransparent:
+ _type = kPlaneTypeTransparent;
+ break;
+ case kPlanePicOpaque:
+ _type = kPlaneTypeOpaque;
+ break;
+ case kPlanePicTransparentPicture:
+ _type = kPlaneTypeTransparentPicture;
+ break;
+ default:
+ if (_type != kPlaneTypeTransparentPicture) {
+ _type = kPlaneTypePicture;
+ }
+ break;
+ }
+}
+
+void Plane::sync(const Plane *other, const Common::Rect &screenRect) {
+ if (other == nullptr) {
+ if (_pictureChanged) {
+ deleteAllPics();
+ setType();
+ changePic();
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ } else {
+ setType();
+ }
+ } else {
+ if (
+ _planeRect.top != other->_planeRect.top ||
+ _planeRect.left != other->_planeRect.left ||
+ _planeRect.right > other->_planeRect.right ||
+ _planeRect.bottom > other->_planeRect.bottom
+ ) {
+ // the plane moved or got larger
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ _moved = g_sci->_gfxFrameout->getScreenCount();
+ } else if (_planeRect != other->_planeRect) {
+ // the plane got smaller
+ _moved = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ if (_priority != other->_priority) {
+ _priorityChanged = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ if (_pictureId != other->_pictureId || _mirrored != other->_mirrored || _pictureChanged) {
+ deleteAllPics();
+ setType();
+ changePic();
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ if (_back != other->_back) {
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ }
+ }
+
+ _deleted = 0;
+ if (_created == 0) {
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ convertGameRectToPlaneRect();
+ _screenRect = _planeRect;
+ // NOTE: screenRect originally was retrieved through globals
+ // instead of being passed into the function
+ clipScreenRect(screenRect);
+}
+
+void Plane::update(const reg_t object) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+ _vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX));
+ _vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
+ _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+ _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ convertGameRectToPlaneRect();
+
+ _priority = readSelectorValue(segMan, object, SELECTOR(priority));
+ GuiResourceId pictureId = readSelectorValue(segMan, object, SELECTOR(picture));
+ if (_pictureId != pictureId) {
+ _pictureId = pictureId;
+ _pictureChanged = true;
+ }
+
+ _mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored));
+ _back = readSelectorValue(segMan, object, SELECTOR(back));
+}
+
+void Plane::scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool scrollPics) {
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+
+ for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+ if (*it != nullptr) {
+ ScreenItem &screenItem = **it;
+ if (!screenItem._deleted && (screenItem._celInfo.type != kCelTypePic || scrollPics)) {
+ screenItem._position.x += deltaX;
+ screenItem._position.y += deltaY;
+ }
+ }
+ }
+}
+
+void Plane::remapMarkRedraw() {
+ ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ ScreenItem *screenItem = _screenItemList[i];
+ if (
+ screenItem != nullptr &&
+ !screenItem->_deleted && !screenItem->_created &&
+ screenItem->getCelObj()._remap
+ ) {
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark PlaneList
+
+void PlaneList::add(Plane *plane) {
+ for (iterator it = begin(); it != end(); ++it) {
+ if ((*it)->_priority > plane->_priority) {
+ insert(it, plane);
+ return;
+ }
+ }
+
+ push_back(plane);
+}
+
+void PlaneList::clear() {
+ for (iterator it = begin(); it != end(); ++it) {
+ delete *it;
+ }
+
+ PlaneListBase::clear();
+}
+
+void PlaneList::erase(Plane *plane) {
+ for (iterator it = begin(); it != end(); ++it) {
+ if (*it == plane) {
+ erase(it);
+ break;
+ }
+ }
+}
+
+PlaneList::iterator PlaneList::erase(iterator it) {
+ delete *it;
+ return PlaneListBase::erase(it);
+}
+
+int PlaneList::findIndexByObject(const reg_t object) const {
+ for (size_type i = 0; i < size(); ++i) {
+ if ((*this)[i] != nullptr && (*this)[i]->_object == object) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+Plane *PlaneList::findByObject(const reg_t object) const {
+ const_iterator planeIt = Common::find_if(begin(), end(), FindByObject<Plane *>(object));
+
+ if (planeIt == end()) {
+ return nullptr;
+ }
+
+ return *planeIt;
+}
+
+int16 PlaneList::getTopPlanePriority() const {
+ if (size() > 0) {
+ return (*this)[size() - 1]->_priority;
+ }
+
+ return 0;
+}
+
+int16 PlaneList::getTopSciPlanePriority() const {
+ int16 priority = 0;
+
+ for (const_iterator it = begin(); it != end(); ++it) {
+ if ((*it)->_priority >= 10000) {
+ break;
+ }
+
+ priority = (*it)->_priority;
+ }
+
+ return priority;
+}
+
+void PlaneList::remove_at(size_type index) {
+ delete PlaneListBase::remove_at(index);
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h
new file mode 100644
index 0000000000..3981a2b319
--- /dev/null
+++ b/engines/sci/graphics/plane32.h
@@ -0,0 +1,531 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_PLANE32_H
+#define SCI_GRAPHICS_PLANE32_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "sci/engine/vm_types.h"
+#include "sci/graphics/helpers.h"
+#include "sci/graphics/lists32.h"
+#include "sci/graphics/screen_item32.h"
+
+namespace Sci {
+enum PlaneType {
+ kPlaneTypeColored = 0,
+ kPlaneTypePicture = 1,
+ kPlaneTypeTransparent = 2,
+ kPlaneTypeOpaque = 3,
+ kPlaneTypeTransparentPicture = 4
+};
+
+enum PlanePictureCodes {
+ // NOTE: Any value at or below 65531 means the plane
+ // is a kPlaneTypePicture.
+ kPlanePic = 65531,
+ kPlanePicTransparentPicture = 65532,
+ kPlanePicOpaque = 65533,
+ kPlanePicTransparent = 65534,
+ kPlanePicColored = 65535
+};
+
+#pragma mark -
+#pragma mark RectList
+
+typedef StablePointerArray<Common::Rect, 200> RectListBase;
+class RectList : public RectListBase {
+public:
+ void add(const Common::Rect &rect) {
+ RectListBase::add(new Common::Rect(rect));
+ }
+};
+
+#pragma mark -
+#pragma mark DrawList
+
+struct DrawItem {
+ /**
+ * The screen item to draw.
+ */
+ ScreenItem *screenItem;
+
+ /**
+ * The target rectangle of the draw operation.
+ */
+ Common::Rect rect;
+
+ inline bool operator<(const DrawItem &other) const {
+ return *screenItem < *other.screenItem;
+ }
+};
+
+typedef StablePointerArray<DrawItem, 250> DrawListBase;
+class DrawList : public DrawListBase {
+private:
+ inline static bool sortHelper(const DrawItem *a, const DrawItem *b) {
+ return *a < *b;
+ }
+public:
+ void add(ScreenItem *screenItem, const Common::Rect &rect);
+ inline void sort() {
+ pack();
+ Common::sort(begin(), end(), sortHelper);
+ }
+};
+
+class PlaneList;
+
+#pragma mark -
+#pragma mark Plane
+
+/**
+ * A plane is a grouped layer of screen items.
+ */
+class Plane {
+private:
+ /**
+ * A serial used for planes that are generated inside
+ * the graphics engine, rather than the interpreter.
+ */
+ static uint16 _nextObjectId;
+
+ /**
+ * For planes that are used to render picture data, the
+ * resource ID of the picture to be displayed. This
+ * value may also be one of the special
+ * PlanePictureCodes, in which case the plane becomes a
+ * non-picture plane.
+ */
+ GuiResourceId _pictureId;
+
+ /**
+ * Whether or not the contents of picture planes should
+ * be drawn horizontally mirrored. Only applies to
+ * planes of type kPlaneTypePicture.
+ */
+ bool _mirrored;
+
+ /**
+ * Whether the picture ID for this plane has changed.
+ * This flag is set when the plane is created or updated
+ * from a VM object, and is cleared when the plane is
+ * synchronised to another plane (which calls
+ * changePic).
+ */
+ bool _pictureChanged;
+
+ /**
+ * Converts the dimensions of the game rect used by
+ * scripts to the dimensions of the plane rect used to
+ * render content to the screen. Coordinates with
+ * remainders are rounded up to the next whole pixel.
+ */
+ void convertGameRectToPlaneRect();
+
+ /**
+ * Sets the type of the plane according to its assigned
+ * picture resource ID.
+ */
+ void setType();
+
+public:
+ /**
+ * The type of the plane.
+ */
+ PlaneType _type;
+
+ /**
+ * The color to use when erasing the plane. Only
+ * applies to planes of type kPlaneTypeColored.
+ */
+ byte _back;
+
+ /**
+ * Whether the priority of this plane has changed.
+ * This flag is set when the plane is updated from
+ * another plane and cleared when draw list calculation
+ * occurs.
+ */
+ int _priorityChanged;
+
+ /**
+ * A handle to the VM object corresponding to this
+ * plane. Some planes are generated purely within the
+ * graphics engine and have a numeric object value.
+ */
+ reg_t _object;
+
+ /**
+ * The rendering priority of the plane. Higher
+ * priorities are drawn above lower priorities.
+ */
+ int16 _priority;
+
+ /**
+ * Whether or not all screen items in this plane should
+ * be redrawn on the next frameout, instead of just
+ * the screen items marked as updated. This is set when
+ * visual changes to the plane itself are made that
+ * affect the rendering of the entire plane, and cleared
+ * once those changes are rendered by `redrawAll`.
+ */
+ int _redrawAllCount;
+
+ /**
+ * Flags indicating the state of the plane.
+ * - `created` is set when the plane is first created,
+ * either from a VM object or from within the engine
+ * itself
+ * - `updated` is set when the plane is updated from
+ * another plane and the two planes' `planeRect`s do
+ * not match
+ * - `deleted` is set when the plane is deleted by a
+ * kernel call
+ * - `moved` is set when the plane has been moved or
+ * resized
+ */
+ int _created, _updated, _deleted, _moved;
+
+ /**
+ * The vanishing point for the plane. Used when
+ * automatically calculating the correct scaling of the
+ * plane's screen items according to their position.
+ */
+ Common::Point _vanishingPoint;
+
+ /**
+ * The position & dimensions of the plane in screen
+ * coordinates. This rect is not clipped to the screen,
+ * so may include coordinates that are offscreen.
+ */
+ Common::Rect _planeRect;
+
+ /**
+ * The position & dimensions of the plane in game script
+ * coordinates.
+ */
+ Common::Rect _gameRect;
+
+ /**
+ * The position & dimensions of the plane in screen
+ * coordinates. This rect is clipped to the screen.
+ */
+ Common::Rect _screenRect;
+
+ /**
+ * The list of screen items grouped within this plane.
+ */
+ ScreenItemList _screenItemList;
+
+public:
+ /**
+ * Initialises static Plane members.
+ */
+ static void init();
+
+ // NOTE: This constructor signature originally did not accept a
+ // picture ID, but some calls to construct planes with this signature
+ // immediately set the picture ID and then called setType again, so
+ // it made more sense to just make the picture ID a parameter instead.
+ Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId = kPlanePicColored);
+
+ Plane(const reg_t object);
+
+ Plane(const Plane &other);
+
+ void operator=(const Plane &other);
+
+ inline bool operator<(const Plane &other) const {
+ if (_priority < other._priority) {
+ return true;
+ }
+
+ if (_priority == other._priority) {
+ return _object < other._object;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clips the screen rect of this plane to fit within the
+ * given screen rect.
+ */
+ inline void clipScreenRect(const Common::Rect &screenRect) {
+ // LSL6 hires creates planes with invalid rects; SSCI does not
+ // care about this, but `Common::Rect::clip` does, so we need to
+ // check whether or not the rect is actually valid before clipping
+ // and only clip valid rects
+ if (_screenRect.isValidRect() && _screenRect.intersects(screenRect)) {
+ _screenRect.clip(screenRect);
+ } else {
+ _screenRect.left = 0;
+ _screenRect.top = 0;
+ _screenRect.right = 0;
+ _screenRect.bottom = 0;
+ }
+ }
+
+ void printDebugInfo(Console *con) const;
+
+ /**
+ * Compares the properties of the current plane against
+ * the properties of the `other` plane (which is the
+ * corresponding plane from the visible plane list) to
+ * discover which properties have been changed on this
+ * plane by a call to `update(reg_t)`.
+ *
+ * @note This method was originally called UpdatePlane
+ * in SCI engine.
+ */
+ void sync(const Plane *other, const Common::Rect &screenRect);
+
+ /**
+ * Updates the plane to match the state of the plane
+ * object from the virtual machine.
+ *
+ * @note This method was originally called UpdatePlane
+ * in SCI engine.
+ */
+ void update(const reg_t object);
+
+ /**
+ * Modifies the position of all non-pic screen items
+ * by the given delta. If `scrollPics` is true, pic
+ * items are also repositioned.
+ */
+ void scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool scrollPics);
+
+#pragma mark -
+#pragma mark Plane - Pic
+private:
+ /**
+ * Adds all cels from the specified picture resource to
+ * the plane as screen items. If a position is provided,
+ * the screen items will be given that position;
+ * otherwise, the default relative positions for each
+ * cel will be taken from the picture resource data.
+ */
+ inline void addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX);
+
+ /**
+ * Marks all screen items to be deleted that are within
+ * this plane and match the given picture ID.
+ */
+ void deletePic(const GuiResourceId pictureId);
+
+ /**
+ * Marks all screen items to be deleted that are within
+ * this plane and match the given picture ID, then sets
+ * the picture ID of the plane to the new picture ID
+ * without adding any screen items.
+ */
+ void deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId);
+
+ /**
+ * Marks all screen items to be deleted that are within
+ * this plane and are picture cels.
+ */
+ void deleteAllPics();
+
+public:
+ /**
+ * Marks all existing screen items matching the current
+ * picture to be deleted, then adds all cels from the
+ * new picture resource to the plane at the given
+ * position.
+ */
+ void addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX);
+
+ /**
+ * If the plane is a picture plane, re-adds all cels
+ * from its picture resource to the plane. Otherwise,
+ * just clears the _pictureChanged flag.
+ */
+ void changePic();
+
+#pragma mark -
+#pragma mark Plane - Rendering
+private:
+ /**
+ * Splits all rects in the given draw list at the edges
+ * of all higher-priority, non-transparent, intersecting
+ * planes.
+ */
+ void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const;
+
+ /**
+ * Splits all rects in the given erase list at the
+ * edges of higher-priority, non-transparent,
+ * intersecting planes.
+ */
+ void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const;
+
+ /**
+ * Adds the screen item at `index` into `drawList`,
+ * ensuring it is only drawn within the bounds of
+ * `rect`. If an existing draw list entry exists
+ * for this screen item, it will be modified.
+ * Otherwise, a new entry will be added.
+ */
+ void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const;
+
+ /**
+ * Merges `rect` with an existing rect in `eraseList`,
+ * if possible. Otherwise, adds the rect as a new entry
+ * to `eraseList`.
+ */
+ void mergeToRectList(const Common::Rect &rect, RectList &eraseList) const;
+
+public:
+ /**
+ * Calculates the location and dimensions of dirty rects
+ * of the screen items in this plane and adds them to
+ * the given draw and erase lists, and synchronises this
+ * plane's list of screen items to the given visible
+ * plane.
+ */
+ void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
+
+ /**
+ * Synchronises changes to screen items from the current
+ * plane to the visible plane and deletes screen items
+ * from the current plane that have been marked as
+ * deleted. If `forceUpdate` is true, all screen items
+ * on the visible plane will be updated, even if they
+ * are not marked as having changed.
+ */
+ void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate);
+
+ /**
+ * This method is called from the highest priority plane
+ * to the lowest priority plane.
+ *
+ * Adds screen items from this plane to the draw list
+ * that must be redrawn because they intersect entries
+ * in the `higherEraseList`.
+ *
+ * If this plane is opaque, all intersecting erase rects
+ * in `lowerEraseList` are removed, as they would be
+ * completely overwritten by the contents of this plane.
+ *
+ * If this plane is transparent, erase rects from the
+ * `lowerEraseList` are added to the erase list for this
+ * plane, so that lower planes.
+ *
+ * @param drawList The draw list for this plane.
+ * @param eraseList The erase list for this plane.
+ * @param higherEraseList The erase list for a plane
+ * above this plane.
+ */
+ void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const;
+
+ /**
+ * This method is called from the lowest priority plane
+ * to the highest priority plane.
+ *
+ * Adds screen items from this plane to the draw list
+ * that must be drawn because the lower plane is being
+ * redrawn and potentially transparent screen items
+ * from this plane would draw over the lower priority
+ * plane's screen items.
+ *
+ * This method applies only to transparent planes.
+ *
+ * @param drawList The draw list for this plane.
+ * @param eraseList The erase list for a plane below
+ * this plane.
+ */
+ void filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const;
+
+ /**
+ * This method is called from the lowest priority plane
+ * to the highest priority plane.
+ *
+ * Adds screen items from this plane to the draw list
+ * that must be drawn because the lower plane is being
+ * redrawn and potentially transparent screen items
+ * from this plane would draw over the lower priority
+ * plane's screen items.
+ *
+ * This method applies only to transparent planes.
+ *
+ * @param drawList The draw list for this plane.
+ * @param lowerDrawList The draw list for a plane below
+ * this plane.
+ */
+ void filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const;
+
+ /**
+ * Updates all of the plane's non-deleted screen items
+ * and adds them to the given draw and erase lists.
+ */
+ void redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
+
+ void remapMarkRedraw();
+};
+
+#pragma mark -
+#pragma mark PlaneList
+
+typedef Common::Array<Plane *> PlaneListBase;
+class PlaneList : public PlaneListBase {
+private:
+ inline static bool sortHelper(const Plane *a, const Plane *b) {
+ return *a < *b;
+ }
+
+ using PlaneListBase::push_back;
+
+public:
+ // A method for finding the index of a plane inside a
+ // PlaneList is used because entries in the main plane
+ // list and visible plane list of GfxFrameout are
+ // synchronised by index
+ int findIndexByObject(const reg_t object) const;
+ Plane *findByObject(const reg_t object) const;
+
+ /**
+ * Gets the priority of the top plane in the plane list.
+ */
+ int16 getTopPlanePriority() const;
+
+ /**
+ * Gets the priority of the top plane in the plane list
+ * created by a game script.
+ */
+ int16 getTopSciPlanePriority() const;
+
+ void add(Plane *plane);
+ void clear();
+ iterator erase(iterator it);
+ void erase(Plane *plane);
+ inline void sort() {
+ Common::sort(begin(), end(), sortHelper);
+ }
+ void remove_at(size_type index);
+};
+
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/graphics/portrait.cpp b/engines/sci/graphics/portrait.cpp
index cb425f3be9..045a923569 100644
--- a/engines/sci/graphics/portrait.cpp
+++ b/engines/sci/graphics/portrait.cpp
@@ -316,7 +316,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
g_sci->getEngineState()->wait(1);
curEvent = _event->getSciEvent(SCI_EVENT_ANY);
if (curEvent.type == SCI_EVENT_MOUSE_PRESS ||
- (curEvent.type == SCI_EVENT_KEYBOARD && curEvent.data == SCI_KEY_ESC) ||
+ (curEvent.type == SCI_EVENT_KEYBOARD && curEvent.character == SCI_KEY_ESC) ||
g_sci->getEngineState()->abortScriptProcessing == kAbortQuitGame ||
g_sci->getEngineState()->_delayedRestoreGame)
userAbort = true;
@@ -339,7 +339,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
g_sci->getEngineState()->wait(1);
curEvent = _event->getSciEvent(SCI_EVENT_ANY);
if (curEvent.type == SCI_EVENT_MOUSE_PRESS ||
- (curEvent.type == SCI_EVENT_KEYBOARD && curEvent.data == SCI_KEY_ESC) ||
+ (curEvent.type == SCI_EVENT_KEYBOARD && curEvent.character == SCI_KEY_ESC) ||
g_sci->getEngineState()->abortScriptProcessing == kAbortQuitGame)
userAbort = true;
curPosition = _audio->getAudioPosition();
diff --git a/engines/sci/graphics/ports.cpp b/engines/sci/graphics/ports.cpp
index bcc991081e..0d00ce01e6 100644
--- a/engines/sci/graphics/ports.cpp
+++ b/engines/sci/graphics/ports.cpp
@@ -717,8 +717,10 @@ void GfxPorts::kernelGraphAdjustPriority(int top, int bottom) {
}
byte GfxPorts::kernelCoordinateToPriority(int16 y) {
- if (y < _priorityTop)
- return _priorityBands[_priorityTop];
+ if (y < 0) // Sierra did not check this, we do for safety reasons
+ return _priorityBands[0];
+ // do NOT check for _priorityTop in here. Sierra never did that and it would cause
+ // at least priority issues in room 54 of lsl2 (airplane)
if (y > _priorityBottom)
return _priorityBands[_priorityBottom];
return _priorityBands[y];
diff --git a/engines/sci/graphics/remap.cpp b/engines/sci/graphics/remap.cpp
new file mode 100644
index 0000000000..2abf03ea29
--- /dev/null
+++ b/engines/sci/graphics/remap.cpp
@@ -0,0 +1,99 @@
+/* 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.
+ *
+ */
+
+#include "sci/sci.h"
+#include "sci/graphics/palette.h"
+#include "sci/graphics/remap.h"
+#include "sci/graphics/screen.h"
+
+namespace Sci {
+
+GfxRemap::GfxRemap(GfxPalette *palette)
+ : _palette(palette) {
+ _remapOn = false;
+ resetRemapping();
+}
+
+byte GfxRemap::remapColor(byte remappedColor, byte screenColor) {
+ assert(_remapOn);
+ if (_remappingType[remappedColor] == kRemapByRange)
+ return _remappingByRange[screenColor];
+ else if (_remappingType[remappedColor] == kRemapByPercent)
+ return _remappingByPercent[screenColor];
+ else
+ error("remapColor(): Color %d isn't remapped", remappedColor);
+
+ return 0; // should never reach here
+}
+
+void GfxRemap::resetRemapping() {
+ _remapOn = false;
+ _remappingPercentToSet = 0;
+
+ for (int i = 0; i < 256; i++) {
+ _remappingType[i] = kRemapNone;
+ _remappingByPercent[i] = i;
+ _remappingByRange[i] = i;
+ }
+}
+
+void GfxRemap::setRemappingPercent(byte color, byte percent) {
+ _remapOn = true;
+
+ // We need to defer the setup of the remapping table every time the screen
+ // palette is changed, so that kernelFindColor() can find the correct
+ // colors. Set it once here, in case the palette stays the same and update
+ // it on each palette change by copySysPaletteToScreen().
+ _remappingPercentToSet = percent;
+
+ for (int i = 0; i < 256; i++) {
+ byte r = _palette->_sysPalette.colors[i].r * _remappingPercentToSet / 100;
+ byte g = _palette->_sysPalette.colors[i].g * _remappingPercentToSet / 100;
+ byte b = _palette->_sysPalette.colors[i].b * _remappingPercentToSet / 100;
+ _remappingByPercent[i] = _palette->kernelFindColor(r, g, b);
+ }
+
+ _remappingType[color] = kRemapByPercent;
+}
+
+void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) {
+ _remapOn = true;
+
+ for (int i = from; i <= to; i++) {
+ _remappingByRange[i] = i + base;
+ }
+
+ _remappingType[color] = kRemapByRange;
+}
+
+void GfxRemap::updateRemapping() {
+ // Check if we need to reset remapping by percent with the new colors.
+ if (_remappingPercentToSet) {
+ for (int i = 0; i < 256; i++) {
+ byte r = _palette->_sysPalette.colors[i].r * _remappingPercentToSet / 100;
+ byte g = _palette->_sysPalette.colors[i].g * _remappingPercentToSet / 100;
+ byte b = _palette->_sysPalette.colors[i].b * _remappingPercentToSet / 100;
+ _remappingByPercent[i] = _palette->kernelFindColor(r, g, b);
+ }
+ }
+}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/paint.h b/engines/sci/graphics/remap.h
index b2277131d5..98177f6d19 100644
--- a/engines/sci/graphics/paint.h
+++ b/engines/sci/graphics/remap.h
@@ -20,20 +20,48 @@
*
*/
-#ifndef SCI_GRAPHICS_PAINT_H
-#define SCI_GRAPHICS_PAINT_H
+#ifndef SCI_GRAPHICS_REMAP_H
+#define SCI_GRAPHICS_REMAP_H
+
+#include "common/array.h"
+#include "common/serializer.h"
namespace Sci {
-class GfxPaint {
+class GfxScreen;
+
+/**
+ * This class handles color remapping for the QFG4 demo.
+ */
+class GfxRemap {
+private:
+ enum ColorRemappingType {
+ kRemapNone = 0,
+ kRemapByRange = 1,
+ kRemapByPercent = 2
+ };
+
public:
- GfxPaint();
- virtual ~GfxPaint();
+ GfxRemap(GfxPalette *_palette);
- virtual void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo);
- virtual void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control);
-};
+ void resetRemapping();
+ void setRemappingPercent(byte color, byte percent);
+ void setRemappingRange(byte color, byte from, byte to, byte base);
+ bool isRemapped(byte color) const {
+ return _remapOn && (_remappingType[color] != kRemapNone);
+ }
+ byte remapColor(byte remappedColor, byte screenColor);
+ void updateRemapping();
+
+private:
+ GfxPalette *_palette;
+ bool _remapOn;
+ ColorRemappingType _remappingType[256];
+ byte _remappingByPercent[256];
+ byte _remappingByRange[256];
+ uint16 _remappingPercentToSet;
+};
} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/remap32.cpp b/engines/sci/graphics/remap32.cpp
new file mode 100644
index 0000000000..d5a2362f14
--- /dev/null
+++ b/engines/sci/graphics/remap32.cpp
@@ -0,0 +1,468 @@
+/* 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.
+ *
+ */
+
+#include "sci/sci.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
+
+namespace Sci {
+
+#pragma mark SingleRemap
+
+void SingleRemap::reset() {
+ _lastPercent = 100;
+ _lastGray = 0;
+
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+ for (uint i = 0; i < remapStartColor; ++i) {
+ const Color &color = currentPalette.colors[i];
+ _remapColors[i] = i;
+ _originalColors[i] = color;
+ _originalColorsChanged[i] = true;
+ _idealColors[i] = color;
+ _idealColorsChanged[i] = false;
+ _matchDistances[i] = 0;
+ }
+}
+
+bool SingleRemap::update() {
+ switch (_type) {
+ case kRemapNone:
+ break;
+ case kRemapByRange:
+ return updateRange();
+ case kRemapByPercent:
+ return updateBrightness();
+ case kRemapToGray:
+ return updateSaturation();
+ case kRemapToPercentGray:
+ return updateSaturationAndBrightness();
+ default:
+ error("Illegal remap type %d", _type);
+ }
+
+ return false;
+}
+
+bool SingleRemap::updateRange() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ bool updated = false;
+
+ for (uint i = 0; i < remapStartColor; ++i) {
+ uint8 targetColor;
+ if (_from <= i && i <= _to) {
+ targetColor = i + _delta;
+ } else {
+ targetColor = i;
+ }
+
+ if (_remapColors[i] != targetColor) {
+ updated = true;
+ _remapColors[i] = targetColor;
+ }
+
+ _originalColorsChanged[i] = true;
+ }
+
+ return updated;
+}
+
+bool SingleRemap::updateBrightness() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette();
+ for (uint i = 1; i < remapStartColor; ++i) {
+ Color color(nextPalette.colors[i]);
+
+ if (_originalColors[i] != color) {
+ _originalColorsChanged[i] = true;
+ _originalColors[i] = color;
+ }
+
+ if (_percent != _lastPercent || _originalColorsChanged[i]) {
+ // NOTE: SSCI checked if percent was over 100 and only
+ // then clipped values, but we always unconditionally
+ // ensure the result is in the correct range
+ color.r = MIN(255, (uint16)color.r * _percent / 100);
+ color.g = MIN(255, (uint16)color.g * _percent / 100);
+ color.b = MIN(255, (uint16)color.b * _percent / 100);
+
+ if (_idealColors[i] != color) {
+ _idealColorsChanged[i] = true;
+ _idealColors[i] = color;
+ }
+ }
+ }
+
+ const bool updated = apply();
+ Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+ Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+ _lastPercent = _percent;
+ return updated;
+}
+
+bool SingleRemap::updateSaturation() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+ for (uint i = 1; i < remapStartColor; ++i) {
+ Color color(currentPalette.colors[i]);
+ if (_originalColors[i] != color) {
+ _originalColorsChanged[i] = true;
+ _originalColors[i] = color;
+ }
+
+ if (_gray != _lastGray || _originalColorsChanged[i]) {
+ const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100;
+
+ color.r = MIN(255, color.r - ((color.r - luminosity) * _gray / 100));
+ color.g = MIN(255, color.g - ((color.g - luminosity) * _gray / 100));
+ color.b = MIN(255, color.b - ((color.b - luminosity) * _gray / 100));
+
+ if (_idealColors[i] != color) {
+ _idealColorsChanged[i] = true;
+ _idealColors[i] = color;
+ }
+ }
+ }
+
+ const bool updated = apply();
+ Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+ Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+ _lastGray = _gray;
+ return updated;
+}
+
+bool SingleRemap::updateSaturationAndBrightness() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+ for (uint i = 1; i < remapStartColor; i++) {
+ Color color(currentPalette.colors[i]);
+ if (_originalColors[i] != color) {
+ _originalColorsChanged[i] = true;
+ _originalColors[i] = color;
+ }
+
+ if (_percent != _lastPercent || _gray != _lastGray || _originalColorsChanged[i]) {
+ const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100;
+
+ color.r = MIN(255, color.r - ((color.r - luminosity) * _gray) / 100);
+ color.g = MIN(255, color.g - ((color.g - luminosity) * _gray) / 100);
+ color.b = MIN(255, color.b - ((color.b - luminosity) * _gray) / 100);
+
+ if (_idealColors[i] != color) {
+ _idealColorsChanged[i] = true;
+ _idealColors[i] = color;
+ }
+ }
+ }
+
+ const bool updated = apply();
+ Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+ Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+ _lastPercent = _percent;
+ _lastGray = _gray;
+ return updated;
+}
+
+bool SingleRemap::apply() {
+ const GfxRemap32 *const gfxRemap32 = g_sci->_gfxRemap32;
+ const uint8 remapStartColor = gfxRemap32->getStartColor();
+
+ // Blocked colors are not allowed to be used as target
+ // colors for the remap
+ bool blockedColors[236];
+ Common::fill(blockedColors, blockedColors + remapStartColor, false);
+
+ const bool *const paletteCycleMap = g_sci->_gfxPalette32->getCycleMap();
+
+ const int16 blockedRangeCount = gfxRemap32->getBlockedRangeCount();
+ if (blockedRangeCount) {
+ const uint8 blockedRangeStart = gfxRemap32->getBlockedRangeStart();
+ Common::fill(blockedColors + blockedRangeStart, blockedColors + blockedRangeStart + blockedRangeCount, true);
+ }
+
+ for (uint i = 0; i < remapStartColor; ++i) {
+ if (paletteCycleMap[i]) {
+ blockedColors[i] = true;
+ }
+ }
+
+ // NOTE: SSCI did a loop over colors here to create a
+ // new array of updated, unblocked colors, but then
+ // never used it
+
+ bool updated = false;
+ for (uint i = 1; i < remapStartColor; ++i) {
+ int distance;
+
+ if (!_idealColorsChanged[i] && !_originalColorsChanged[_remapColors[i]]) {
+ continue;
+ }
+
+ if (
+ _idealColorsChanged[i] &&
+ _originalColorsChanged[_remapColors[i]] &&
+ _matchDistances[i] < 100 &&
+ colorDistance(_idealColors[i], _originalColors[_remapColors[i]]) <= _matchDistances[i]
+ ) {
+ continue;
+ }
+
+ const int16 bestColor = matchColor(_idealColors[i], _matchDistances[i], distance, blockedColors);
+
+ if (bestColor != -1 && _remapColors[i] != bestColor) {
+ updated = true;
+ _remapColors[i] = bestColor;
+ _matchDistances[i] = distance;
+ }
+ }
+
+ return updated;
+}
+
+int SingleRemap::colorDistance(const Color &a, const Color &b) const {
+ int channelDistance = a.r - b.r;
+ int distance = channelDistance * channelDistance;
+ channelDistance = a.g - b.g;
+ distance += channelDistance * channelDistance;
+ channelDistance = a.b - b.b;
+ distance += channelDistance * channelDistance;
+ return distance;
+}
+
+int16 SingleRemap::matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const {
+ int16 bestIndex = -1;
+ int bestDistance = 0xFFFFF;
+ int distance = minimumDistance;
+ const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette();
+
+ for (uint i = 0, channelDistance; i < g_sci->_gfxRemap32->getStartColor(); ++i) {
+ if (blockedIndexes[i]) {
+ continue;
+ }
+
+ distance = nextPalette.colors[i].r - color.r;
+ distance *= distance;
+ if (bestDistance <= distance) {
+ continue;
+ }
+ channelDistance = nextPalette.colors[i].g - color.g;
+ distance += channelDistance * channelDistance;
+ if (bestDistance <= distance) {
+ continue;
+ }
+ channelDistance = nextPalette.colors[i].b - color.b;
+ distance += channelDistance * channelDistance;
+ if (bestDistance <= distance) {
+ continue;
+ }
+ bestDistance = distance;
+ bestIndex = i;
+ }
+
+ // This value is only valid if the last index to
+ // perform a distance calculation was the best index
+ outDistance = distance;
+ return bestIndex;
+}
+
+#pragma mark -
+#pragma mark GfxRemap32
+
+GfxRemap32::GfxRemap32() :
+ _needsUpdate(false),
+ _blockedRangeStart(0),
+ _blockedRangeCount(0),
+ _remapStartColor(236),
+ _numActiveRemaps(0) {
+ // The `_remapStartColor` seems to always be 236 in SSCI,
+ // but if it is ever changed then the various C-style
+ // member arrays hard-coded to 236 need to be changed to
+ // match the highest possible value of `_remapStartColor`
+ assert(_remapStartColor == 236);
+
+ if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) {
+ _remaps.resize(9);
+ } else {
+ _remaps.resize(19);
+ }
+
+ _remapEndColor = _remapStartColor + _remaps.size() - 1;
+}
+
+void GfxRemap32::remapOff(const uint8 color) {
+ if (color == 0) {
+ remapAllOff();
+ return;
+ }
+
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapOff: %d out of remap range", color);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+ singleRemap._type = kRemapNone;
+ --_numActiveRemaps;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapAllOff() {
+ for (uint i = 0, len = _remaps.size(); i < len; ++i) {
+ _remaps[i]._type = kRemapNone;
+ }
+
+ _numActiveRemaps = 0;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapByRange: %d out of remap range", color);
+ return;
+ }
+
+ if (from < 0) {
+ warning("GfxRemap32::remapByRange: attempt to remap negative color %d", from);
+ return;
+ }
+
+ if (to >= _remapStartColor) {
+ warning("GfxRemap32::remapByRange: attempt to remap into the remap zone at %d", to);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._from = from;
+ singleRemap._to = to;
+ singleRemap._delta = delta;
+ singleRemap._type = kRemapByRange;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapByPercent(const uint8 color, const int16 percent) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapByPercent: %d out of remap range", color);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._percent = percent;
+ singleRemap._type = kRemapByPercent;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapToGray(const uint8 color, const int8 gray) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapToGray: %d out of remap range", color);
+ return;
+ }
+
+ if (gray < 0 || gray > 100) {
+ error("RemapToGray percent out of range; gray = %d", gray);
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._gray = gray;
+ singleRemap._type = kRemapToGray;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapToPercentGray(const uint8 color, const int16 gray, const int16 percent) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapToPercentGray: %d out of remap range", color);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._percent = percent;
+ singleRemap._gray = gray;
+ singleRemap._type = kRemapToPercentGray;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::blockRange(const uint8 from, const int16 count) {
+ _blockedRangeStart = from;
+ _blockedRangeCount = count;
+}
+
+bool GfxRemap32::remapAllTables(const bool paletteUpdated) {
+ if (!_needsUpdate && !paletteUpdated) {
+ return false;
+ }
+
+ bool updated = false;
+
+ for (SingleRemapsList::iterator it = _remaps.begin(); it != _remaps.end(); ++it) {
+ if (it->_type != kRemapNone) {
+ updated |= it->update();
+ }
+ }
+
+ _needsUpdate = false;
+ return updated;
+}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/remap32.h b/engines/sci/graphics/remap32.h
new file mode 100644
index 0000000000..5f629d733e
--- /dev/null
+++ b/engines/sci/graphics/remap32.h
@@ -0,0 +1,400 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_REMAP32_H
+#define SCI_GRAPHICS_REMAP32_H
+
+#include "common/algorithm.h"
+#include "common/array.h"
+#include "common/scummsys.h"
+#include "sci/graphics/helpers.h"
+
+namespace Sci {
+class GfxPalette32;
+
+enum RemapType {
+ kRemapNone = 0,
+ kRemapByRange = 1,
+ kRemapByPercent = 2,
+ kRemapToGray = 3,
+ kRemapToPercentGray = 4
+};
+
+#pragma mark -
+#pragma mark SingleRemap
+
+/**
+ * SingleRemap objects each manage one remapping operation.
+ */
+class SingleRemap {
+public:
+ SingleRemap() : _type(kRemapNone) {}
+
+ /**
+ * The type of remap.
+ */
+ RemapType _type;
+
+ /**
+ * The first color that should be shifted by a range
+ * remap.
+ */
+ uint8 _from;
+
+ /**
+ * The last color that should be shifted a range remap.
+ */
+ uint8 _to;
+
+ /**
+ * The direction and amount that the colors should be
+ * shifted in a range remap.
+ */
+ int16 _delta;
+
+ /**
+ * The difference in brightness that should be
+ * applied by a brightness (percent) remap.
+ *
+ * This value may be be greater than 100, in
+ * which case the color will be oversaturated.
+ */
+ int16 _percent;
+
+ /**
+ * The amount of desaturation that should be
+ * applied by a saturation (gray) remap, where
+ * 0 is full saturation and 100 is full
+ * desaturation.
+ */
+ uint8 _gray;
+
+ /**
+ * The final array used by CelObj renderers to composite
+ * remapped pixels to the screen buffer.
+ *
+ * Here is how it works:
+ *
+ * The source bitmap being rendered will have pixels
+ * within the remap range (236-245 or 236-254), and the
+ * target buffer will have colors in the non-remapped
+ * range (0-235).
+ *
+ * To arrive at the correct color, first the source
+ * pixel is used to look up the correct SingleRemap for
+ * that pixel. Then, the final composited color is
+ * looked up in this array using the target's pixel
+ * color. In other words,
+ * `target = _remaps[remapEndColor - source].remapColors[target]`.
+ */
+ uint8 _remapColors[236];
+
+ /**
+ * Resets this SingleRemap's color information to
+ * default values.
+ */
+ void reset();
+
+ /**
+ * Recalculates and reapplies remap colors to the
+ * `_remapColors` array.
+ */
+ bool update();
+
+private:
+ /**
+ * The previous brightness value. Used to
+ * determine whether or not targetColors needs
+ * to be updated.
+ */
+ int16 _lastPercent;
+
+ /**
+ * The previous saturation value. Used to
+ * determine whether or not targetColors needs
+ * to be updated.
+ */
+ uint8 _lastGray;
+
+ /**
+ * The colors from the current GfxPalette32 palette
+ * before this SingleRemap is applied.
+ */
+ Color _originalColors[236];
+
+ /**
+ * Map of colors that changed in `_originalColors`
+ * when this SingleRemap was updated. This map is
+ * transient and gets reset to `false` after the
+ * SingleRemap finishes updating.
+ */
+ bool _originalColorsChanged[236];
+
+ /**
+ * The ideal target RGB color values for each generated
+ * remap color.
+ */
+ Color _idealColors[236];
+
+ /**
+ * Map of colors that changed in `_idealColors` when
+ * this SingleRemap was updated. This map is transient
+ * and gets reset to `false` after the SingleRemap
+ * finishes applying.
+ */
+ bool _idealColorsChanged[236];
+
+ /**
+ * When applying a SingleRemap, finding an appropriate
+ * color in the palette is the responsibility of a
+ * distance function. Once a match is found, the
+ * distance of that match is stored here so that the
+ * next time the SingleRemap is applied, it can check
+ * the distance from the previous application and avoid
+ * triggering an expensive redraw of the entire screen
+ * if the new palette value only changed slightly.
+ */
+ int _matchDistances[236];
+
+ /**
+ * Computes the final target values for a range remap
+ * and applies them directly to the `_remaps` map.
+ *
+ * @note Was ByRange in SSCI.
+ */
+ bool updateRange();
+
+ /**
+ * Computes the intermediate target values for a
+ * brightness remap and applies them indirectly via
+ * the `apply` method.
+ *
+ * @note Was ByPercent in SSCI.
+ */
+ bool updateBrightness();
+
+ /**
+ * Computes the intermediate target values for a
+ * saturation remap and applies them indirectly via
+ * the `apply` method.
+ *
+ * @note Was ToGray in SSCI.
+ */
+ bool updateSaturation();
+
+ /**
+ * Computes the intermediate target values for a
+ * saturation + brightness bitmap and applies them
+ * indirectly via the `apply` method.
+ *
+ * @note Was ToPercentGray in SSCI.
+ */
+ bool updateSaturationAndBrightness();
+
+ /**
+ * Computes and applies the final values to the
+ * `_remaps` map.
+ *
+ * @note In SSCI, a boolean array of changed values
+ * was passed into this method, but this was done by
+ * creating arrays on the stack in the caller. Instead
+ * of doing this, we simply add another member property
+ * `_idealColorsChanged` and use that instead.
+ */
+ bool apply();
+
+ /**
+ * Calculates the square distance of two colors.
+ *
+ * @note In SSCI this method is Rgb24::Dist, but it is
+ * only used by SingleRemap.
+ */
+ int colorDistance(const Color &a, const Color &b) const;
+
+ /**
+ * Finds the closest index in the next palette matching
+ * the given RGB color. Returns -1 if no match can be
+ * found that is closer than `minimumDistance`.
+ *
+ * @note In SSCI, this method is SOLPalette::Match, but
+ * this particular signature is only used by
+ * SingleRemap.
+ */
+ int16 matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const;
+};
+
+#pragma mark -
+#pragma mark GfxRemap32
+
+/**
+ * This class provides color remapping support for SCI32
+ * games.
+ */
+class GfxRemap32 : public Common::Serializable {
+public:
+ GfxRemap32();
+
+ void saveLoadWithSerializer(Common::Serializer &s);
+
+ inline uint8 getRemapCount() const { return _numActiveRemaps; }
+ inline uint8 getStartColor() const { return _remapStartColor; }
+ inline uint8 getEndColor() const { return _remapEndColor; }
+ inline uint8 getBlockedRangeStart() const { return _blockedRangeStart; }
+ inline int16 getBlockedRangeCount() const { return _blockedRangeCount; }
+
+ /**
+ * Turns off remapping of the given color. If `color` is
+ * 0, all remaps are turned off.
+ */
+ void remapOff(const uint8 color);
+
+ /**
+ * Turns off all color remaps.
+ */
+ void remapAllOff();
+
+ /**
+ * Configures a SingleRemap for the remap color `color`.
+ * The SingleRemap will shift palette colors between
+ * `from` and `to` (inclusive) by `delta` palette
+ * entries when the remap is applied.
+ */
+ void remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta);
+
+ /**
+ * Configures a SingleRemap for the remap color `color`
+ * to modify the brightness of remapped colors by
+ * `percent`.
+ */
+ void remapByPercent(const uint8 color, const int16 percent);
+
+ /**
+ * Configures a SingleRemap for the remap color `color`
+ * to modify the saturation of remapped colors by
+ * `gray`.
+ */
+ void remapToGray(const uint8 color, const int8 gray);
+
+ /**
+ * Configures a SingleRemap for the remap color `color`
+ * to modify the brightness of remapped colors by
+ * `percent`, and saturation of remapped colors by
+ * `gray`.
+ */
+ void remapToPercentGray(const uint8 color, const int16 gray, const int16 percent);
+
+ /**
+ * Prevents GfxRemap32 from using the given range of
+ * palette entries as potential remap targets.
+ *
+ * @NOTE Was DontMapToRange in SSCI.
+ */
+ void blockRange(const uint8 from, const int16 count);
+
+ /**
+ * Determines whether or not the given color has an
+ * active remapper. If it does not, it is treated as a
+ * skip color and the pixel is not drawn.
+ *
+ * @note SSCI uses a boolean array to decide whether a
+ * a pixel is remapped, but it is possible to get the
+ * same information from `_remaps`, as this function
+ * does.
+ * Presumably, the separate array was created for
+ * performance reasons, since this is called a lot in
+ * the most critical section of the renderer.
+ */
+ inline bool remapEnabled(uint8 color) const {
+ const uint8 index = _remapEndColor - color;
+ assert(index < _remaps.size());
+ return (_remaps[index]._type != kRemapNone);
+ }
+
+ /**
+ * Calculates the correct color for a target by looking
+ * up the target color in the SingleRemap that controls
+ * the given sourceColor. If there is no remap for the
+ * given color, it will be treated as a skip color.
+ */
+ inline uint8 remapColor(const uint8 sourceColor, const uint8 targetColor) const {
+ const uint8 index = _remapEndColor - sourceColor;
+ assert(index < _remaps.size());
+ const SingleRemap &singleRemap = _remaps[index];
+ assert(singleRemap._type != kRemapNone);
+ return singleRemap._remapColors[targetColor];
+ }
+
+ /**
+ * Updates all active remaps in response to a palette
+ * change or a remap settings change.
+ *
+ * `paletteChanged` is true if the next palette in
+ * GfxPalette32 has been previously modified by other
+ * palette operations.
+ */
+ bool remapAllTables(const bool paletteUpdated);
+
+private:
+ typedef Common::Array<SingleRemap> SingleRemapsList;
+
+ /**
+ * The first index of the remap area in the system
+ * palette.
+ */
+ const uint8 _remapStartColor;
+
+ /**
+ * The last index of the remap area in the system
+ * palette.
+ */
+ uint8 _remapEndColor;
+
+ /**
+ * The number of currently active remaps.
+ */
+ uint8 _numActiveRemaps;
+
+ /**
+ * The list of SingleRemaps.
+ */
+ SingleRemapsList _remaps;
+
+ /**
+ * If true, indicates that one or more SingleRemaps were
+ * reconfigured and all remaps need to be recalculated.
+ */
+ bool _needsUpdate;
+
+ /**
+ * The first color that is blocked from being used as a
+ * remap target color.
+ */
+ uint8 _blockedRangeStart;
+
+ /**
+ * The size of the range of blocked colors. If zero,
+ * all colors are potential targets for remapping.
+ */
+ int16 _blockedRangeCount;
+};
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp
index ca5b5b3b8c..c977a93817 100644
--- a/engines/sci/graphics/screen.cpp
+++ b/engines/sci/graphics/screen.cpp
@@ -50,12 +50,14 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
// to provide that under DOS as well, but as gk1/floppy does support
// upscaled hires scriptswise, but doesn't actually have the hires content
// we need to limit it to platform windows.
- if (g_sci->getPlatform() == Common::kPlatformWindows) {
+ if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) {
if (g_sci->getGameId() == GID_KQ6)
_upscaledHires = GFX_SCREEN_UPSCALED_640x440;
#ifdef ENABLE_SCI32
if (g_sci->getGameId() == GID_GK1)
_upscaledHires = GFX_SCREEN_UPSCALED_640x480;
+ if (g_sci->getGameId() == GID_PQ4)
+ _upscaledHires = GFX_SCREEN_UPSCALED_640x480;
#endif
}
@@ -212,36 +214,6 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
error("Unknown SCI1.1 Mac game");
} else
initGraphics(_displayWidth, _displayHeight, _displayWidth > 320);
-
- // Initialize code pointers
- _vectorAdjustCoordinatePtr = &GfxScreen::vectorAdjustCoordinateNOP;
- _vectorAdjustLineCoordinatesPtr = &GfxScreen::vectorAdjustLineCoordinatesNOP;
- _vectorIsFillMatchPtr = &GfxScreen::vectorIsFillMatchNormal;
- _vectorPutPixelPtr = &GfxScreen::putPixelNormal;
- _vectorPutLinePixelPtr = &GfxScreen::putPixel;
- _vectorGetPixelPtr = &GfxScreen::getPixelNormal;
- _putPixelPtr = &GfxScreen::putPixelNormal;
- _getPixelPtr = &GfxScreen::getPixelNormal;
-
- switch (_upscaledHires) {
- case GFX_SCREEN_UPSCALED_480x300:
- _vectorAdjustCoordinatePtr = &GfxScreen::vectorAdjustCoordinate480x300Mac;
- _vectorAdjustLineCoordinatesPtr = &GfxScreen::vectorAdjustLineCoordinates480x300Mac;
- // vectorPutPixel -> we already adjust coordinates for vector code, that's why we can set pixels directly
- // vectorGetPixel -> see vectorPutPixel
- _vectorPutLinePixelPtr = &GfxScreen::vectorPutLinePixel480x300Mac;
- _putPixelPtr = &GfxScreen::putPixelAllUpscaled;
- _getPixelPtr = &GfxScreen::getPixelUpscaled;
- break;
- case GFX_SCREEN_UPSCALED_640x400:
- case GFX_SCREEN_UPSCALED_640x440:
- case GFX_SCREEN_UPSCALED_640x480:
- _vectorPutPixelPtr = &GfxScreen::putPixelDisplayUpscaled;
- _putPixelPtr = &GfxScreen::putPixelDisplayUpscaled;
- break;
- case GFX_SCREEN_UPSCALED_DISABLED:
- break;
- }
}
GfxScreen::~GfxScreen() {
@@ -268,17 +240,26 @@ void GfxScreen::copyToScreen() {
}
void GfxScreen::copyFromScreen(byte *buffer) {
- // TODO this ignores the pitch
Graphics::Surface *screen = g_system->lockScreen();
- memcpy(buffer, screen->getPixels(), _displayPixels);
+
+ if (screen->pitch == _displayWidth) {
+ memcpy(buffer, screen->getPixels(), _displayPixels);
+ } else {
+ const byte *src = (const byte *)screen->getPixels();
+ uint height = _displayHeight;
+
+ while (height--) {
+ memcpy(buffer, src, _displayWidth);
+ buffer += _displayWidth;
+ src += screen->pitch;
+ }
+ }
+
g_system->unlockScreen();
}
void GfxScreen::kernelSyncWithFramebuffer() {
- // TODO this ignores the pitch
- Graphics::Surface *screen = g_system->lockScreen();
- memcpy(_displayScreen, screen->getPixels(), _displayPixels);
- g_system->unlockScreen();
+ copyFromScreen(_displayScreen);
}
void GfxScreen::copyRectToScreen(const Common::Rect &rect) {
@@ -323,40 +304,68 @@ byte GfxScreen::getDrawingMask(byte color, byte prio, byte control) {
return flag;
}
-void GfxScreen::vectorAdjustCoordinateNOP(int16 *x, int16 *y) {
+void GfxScreen::vectorAdjustLineCoordinates(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_480x300: {
+ int16 displayLeft = (*left * 3) / 2;
+ int16 displayRight = (*right * 3) / 2;
+ int16 displayTop = (*top * 3) / 2;
+ int16 displayBottom = (*bottom * 3) / 2;
+
+ if (displayLeft < displayRight) {
+ // one more pixel to the left, one more pixel to the right
+ if (displayLeft > 0)
+ vectorPutLinePixel(displayLeft - 1, displayTop, drawMask, color, priority, control);
+ vectorPutLinePixel(displayRight + 1, displayBottom, drawMask, color, priority, control);
+ } else if (displayLeft > displayRight) {
+ if (displayRight > 0)
+ vectorPutLinePixel(displayRight - 1, displayBottom, drawMask, color, priority, control);
+ vectorPutLinePixel(displayLeft + 1, displayTop, drawMask, color, priority, control);
+ }
+ *left = displayLeft;
+ *top = displayTop;
+ *right = displayRight;
+ *bottom = displayBottom;
+ break;
+ }
+ default:
+ break;
+ }
}
-void GfxScreen::vectorAdjustCoordinate480x300Mac(int16 *x, int16 *y) {
- *x = _upscaledWidthMapping[*x];
- *y = _upscaledHeightMapping[*y];
+// This is called from vector drawing to put a pixel at a certain location
+void GfxScreen::vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ if (_upscaledHires == GFX_SCREEN_UPSCALED_480x300) {
+ vectorPutLinePixel480x300(x, y, drawMask, color, priority, control);
+ return;
+ }
+
+ // For anything else forward to the regular putPixel
+ putPixel(x, y, drawMask, color, priority, control);
}
-void GfxScreen::vectorAdjustLineCoordinatesNOP(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
+// Special 480x300 Mac putPixel for vector line drawing, also draws an additional pixel below the actual one
+void GfxScreen::vectorPutLinePixel480x300(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ int offset = y * _width + x;
+
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ // also set pixel below actual pixel
+ _visualScreen[offset] = color;
+ _visualScreen[offset + _width] = color;
+ _displayScreen[offset] = color;
+ _displayScreen[offset + _displayWidth] = color;
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
+ _priorityScreen[offset] = priority;
+ _priorityScreen[offset + _width] = priority;
+ }
+ if (drawMask & GFX_SCREEN_MASK_CONTROL) {
+ _controlScreen[offset] = control;
+ _controlScreen[offset + _width] = control;
+ }
}
-void GfxScreen::vectorAdjustLineCoordinates480x300Mac(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
- int16 displayLeft = _upscaledWidthMapping[*left];
- int16 displayRight = _upscaledWidthMapping[*right];
- int16 displayTop = _upscaledHeightMapping[*top];
- int16 displayBottom = _upscaledHeightMapping[*bottom];
-
- if (displayLeft < displayRight) {
- // one more pixel to the left, one more pixel to the right
- if (displayLeft > 0)
- vectorPutLinePixel(displayLeft - 1, displayTop, drawMask, color, priority, control);
- vectorPutLinePixel(displayRight + 1, displayBottom, drawMask, color, priority, control);
- } else if (displayLeft > displayRight) {
- if (displayRight > 0)
- vectorPutLinePixel(displayRight - 1, displayBottom, drawMask, color, priority, control);
- vectorPutLinePixel(displayLeft + 1, displayTop, drawMask, color, priority, control);
- }
- *left = displayLeft;
- *top = displayTop;
- *right = displayRight;
- *bottom = displayBottom;
-}
-
-byte GfxScreen::vectorIsFillMatchNormal(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA) {
+byte GfxScreen::vectorIsFillMatch(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA) {
int offset = y * _width + x;
byte match = 0;
@@ -386,132 +395,6 @@ byte GfxScreen::vectorIsFillMatchNormal(int16 x, int16 y, byte screenMask, byte
return match;
}
-// Special 480x300 Mac putPixel for vector line drawing, also draws an additional pixel below the actual one
-void GfxScreen::vectorPutLinePixel480x300Mac(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- int offset = y * _width + x;
-
- if (drawMask & GFX_SCREEN_MASK_VISUAL) {
- _visualScreen[offset] = color;
- _visualScreen[offset + _width] = color;
- _displayScreen[offset] = color;
- // also set pixel below actual pixel
- _displayScreen[offset + _displayWidth] = color;
- }
- if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
- _priorityScreen[offset] = priority;
- _priorityScreen[offset + _width] = priority;
- }
- if (drawMask & GFX_SCREEN_MASK_CONTROL) {
- _controlScreen[offset] = control;
- _controlScreen[offset + _width] = control;
- }
-}
-
-// Directly sets a pixel on various screens, display is not upscaled
-void GfxScreen::putPixelNormal(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- int offset = y * _width + x;
-
- if (drawMask & GFX_SCREEN_MASK_VISUAL) {
- _visualScreen[offset] = color;
- _displayScreen[offset] = color;
- }
- if (drawMask & GFX_SCREEN_MASK_PRIORITY)
- _priorityScreen[offset] = priority;
- if (drawMask & GFX_SCREEN_MASK_CONTROL)
- _controlScreen[offset] = control;
-}
-
-// Directly sets a pixel on various screens, display IS upscaled
-void GfxScreen::putPixelDisplayUpscaled(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- int offset = y * _width + x;
-
- if (drawMask & GFX_SCREEN_MASK_VISUAL) {
- _visualScreen[offset] = color;
- putScaledPixelOnScreen(_displayScreen, x, y, color);
- }
- if (drawMask & GFX_SCREEN_MASK_PRIORITY)
- _priorityScreen[offset] = priority;
- if (drawMask & GFX_SCREEN_MASK_CONTROL)
- _controlScreen[offset] = control;
-}
-
-// Directly sets a pixel on various screens, ALL screens ARE upscaled
-void GfxScreen::putPixelAllUpscaled(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- if (drawMask & GFX_SCREEN_MASK_VISUAL) {
- putScaledPixelOnScreen(_visualScreen, x, y, color);
- putScaledPixelOnScreen(_displayScreen, x, y, color);
- }
- if (drawMask & GFX_SCREEN_MASK_PRIORITY)
- putScaledPixelOnScreen(_priorityScreen, x, y, priority);
- if (drawMask & GFX_SCREEN_MASK_CONTROL)
- putScaledPixelOnScreen(_controlScreen, x, y, control);
-}
-
-/**
- * This is used to put font pixels onto the screen - we adjust differently, so that we won't
- * do triple pixel lines in any case on upscaled hires. That way the font will not get distorted
- * Sierra SCI didn't do this
- */
-void GfxScreen::putFontPixel(int16 startingY, int16 x, int16 y, byte color) {
- int16 actualY = startingY + y;
- if (_fontIsUpscaled) {
- // Do not scale ourselves, but put it on the display directly
- putPixelOnDisplay(x, actualY, color);
- } else {
- int offset = actualY * _width + x;
-
- _visualScreen[offset] = color;
- switch (_upscaledHires) {
- case GFX_SCREEN_UPSCALED_DISABLED:
- _displayScreen[offset] = color;
- break;
- case GFX_SCREEN_UPSCALED_640x400:
- case GFX_SCREEN_UPSCALED_640x440:
- case GFX_SCREEN_UPSCALED_640x480: {
- // to 1-> 4 pixels upscaling for all of those, so that fonts won't look weird
- int displayOffset = (_upscaledHeightMapping[startingY] + y * 2) * _displayWidth + x * 2;
- _displayScreen[displayOffset] = color;
- _displayScreen[displayOffset + 1] = color;
- displayOffset += _displayWidth;
- _displayScreen[displayOffset] = color;
- _displayScreen[displayOffset + 1] = color;
- break;
- }
- default:
- putScaledPixelOnScreen(_displayScreen, x, actualY, color);
- break;
- }
- }
-}
-
-/**
- * This will just change a pixel directly on displayscreen. It is supposed to be
- * only used on upscaled-Hires games where hires content needs to get drawn ONTO
- * the upscaled display screen (like japanese fonts, hires portraits, etc.).
- */
-void GfxScreen::putPixelOnDisplay(int16 x, int16 y, byte color) {
- int offset = y * _displayWidth + x;
- _displayScreen[offset] = color;
-}
-
-//void GfxScreen::putScaledPixelOnDisplay(int16 x, int16 y, byte color) {
-//}
-
-void GfxScreen::putScaledPixelOnScreen(byte *screen, int16 x, int16 y, byte data) {
- int displayOffset = _upscaledHeightMapping[y] * _displayWidth + _upscaledWidthMapping[x];
- int heightOffsetBreak = (_upscaledHeightMapping[y + 1] - _upscaledHeightMapping[y]) * _displayWidth;
- int heightOffset = 0;
- int widthOffsetBreak = _upscaledWidthMapping[x + 1] - _upscaledWidthMapping[x];
- do {
- int widthOffset = 0;
- do {
- screen[displayOffset + heightOffset + widthOffset] = data;
- widthOffset++;
- } while (widthOffset != widthOffsetBreak);
- heightOffset += _displayWidth;
- } while (heightOffset != heightOffsetBreak);
-}
-
/**
* Sierra's Bresenham line drawing.
* WARNING: Do not replace this with Graphics::drawLine(), as this causes issues
@@ -593,16 +476,6 @@ void GfxScreen::putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, u
commonFont->drawChar(displayPtr, chr, _displayWidth, 1, color, 0, -1, -1);
}
-byte GfxScreen::getPixelNormal(byte *screen, int16 x, int16 y) {
- return screen[y * _width + x];
-}
-
-byte GfxScreen::getPixelUpscaled(byte *screen, int16 x, int16 y) {
- int16 mappedX = _upscaledWidthMapping[x];
- int16 mappedY = _upscaledHeightMapping[y];
- return screen[mappedY * _width + mappedX];
-}
-
int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) {
int byteCount = sizeof(rect) + sizeof(mask);
int pixels = rect.width() * rect.height();
@@ -613,7 +486,7 @@ int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) {
} else {
int rectHeight = _upscaledHeightMapping[rect.bottom] - _upscaledHeightMapping[rect.top];
int rectWidth = _upscaledWidthMapping[rect.right] - _upscaledWidthMapping[rect.left];
- byteCount += rectHeight * rect.width() * rectWidth; // _displayScreen (upscaled hires)
+ byteCount += rectHeight * rectWidth; // _displayScreen (upscaled hires)
}
}
if (mask & GFX_SCREEN_MASK_PRIORITY) {
@@ -627,7 +500,6 @@ int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) {
error("bitsGetDataSize() called w/o being in upscaled hires mode");
byteCount += pixels; // _displayScreen (coordinates actually are given to us for hires displayScreen)
}
-
return byteCount;
}
@@ -794,7 +666,7 @@ void GfxScreen::dither(bool addToFlag) {
*displayPtr = color;
break;
default:
- putScaledPixelOnScreen(_displayScreen, x, y, color);
+ putScaledPixelOnDisplay(x, y, color);
break;
}
*visualPtr = color;
@@ -826,7 +698,7 @@ void GfxScreen::dither(bool addToFlag) {
*displayPtr = ditheredColor;
break;
default:
- putScaledPixelOnScreen(_displayScreen, x, y, ditheredColor);
+ putScaledPixelOnDisplay(x, y, ditheredColor);
break;
}
color = ((x^y) & 1) ? color >> 4 : color & 0x0F;
diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h
index 1c946ef02f..65416252f6 100644
--- a/engines/sci/graphics/screen.h
+++ b/engines/sci/graphics/screen.h
@@ -76,6 +76,11 @@ public:
byte getColorWhite() { return _colorWhite; }
byte getColorDefaultVectorData() { return _colorDefaultVectorData; }
+#ifdef ENABLE_SCI32
+ byte *getDisplayScreen() { return _displayScreen; }
+ byte *getPriorityScreen() { return _priorityScreen; }
+#endif
+
void clearForRestoreGame();
void copyToScreen();
void copyFromScreen(byte *buffer);
@@ -84,51 +89,16 @@ public:
void copyDisplayRectToScreen(const Common::Rect &rect);
void copyRectToScreen(const Common::Rect &rect, int16 x, int16 y);
- // calls to code pointers
- void inline vectorAdjustCoordinate (int16 *x, int16 *y) {
- (this->*_vectorAdjustCoordinatePtr)(x, y);
- }
- void inline vectorAdjustLineCoordinates (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
- (this->*_vectorAdjustLineCoordinatesPtr)(left, top, right, bottom, drawMask, color, priority, control);
- }
- byte inline vectorIsFillMatch (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA) {
- return (this->*_vectorIsFillMatchPtr)(x, y, screenMask, t_color, t_pri, t_con, isEGA);
- }
- void inline vectorPutPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- (this->*_vectorPutPixelPtr)(x, y, drawMask, color, priority, control);
- }
- void inline vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- (this->*_vectorPutLinePixelPtr)(x, y, drawMask, color, priority, control);
- }
- byte inline vectorGetVisual(int16 x, int16 y) {
- return (this->*_vectorGetPixelPtr)(_visualScreen, x, y);
- }
- byte inline vectorGetPriority(int16 x, int16 y) {
- return (this->*_vectorGetPixelPtr)(_priorityScreen, x, y);
- }
- byte inline vectorGetControl(int16 x, int16 y) {
- return (this->*_vectorGetPixelPtr)(_controlScreen, x, y);
- }
-
-
- void inline putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- (this->*_putPixelPtr)(x, y, drawMask, color, priority, control);
- }
+ // Vector drawing
+private:
+ void vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ void vectorPutLinePixel480x300(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- byte inline getVisual(int16 x, int16 y) {
- return (this->*_getPixelPtr)(_visualScreen, x, y);
- }
- byte inline getPriority(int16 x, int16 y) {
- return (this->*_getPixelPtr)(_priorityScreen, x, y);
- }
- byte inline getControl(int16 x, int16 y) {
- return (this->*_getPixelPtr)(_controlScreen, x, y);
- }
+public:
+ void vectorAdjustLineCoordinates(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
+ byte vectorIsFillMatch(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA);
byte getDrawingMask(byte color, byte prio, byte control);
- //void putPixel(int16 x, int16 y, byte drawMask, byte color, byte prio, byte control);
- void putFontPixel(int16 startingY, int16 x, int16 y, byte color);
- void putPixelOnDisplay(int16 x, int16 y, byte color);
void drawLine(Common::Point startPoint, Common::Point endPoint, byte color, byte prio, byte control);
void drawLine(int16 left, int16 top, int16 right, int16 bottom, byte color, byte prio, byte control) {
drawLine(Common::Point(left, top), Common::Point(right, bottom), color, prio, control);
@@ -206,8 +176,8 @@ private:
byte *_controlScreen;
/**
- * This screen is the one that is actually displayed to the user. It may be
- * 640x400 for japanese SCI1 games. SCI0 games may be undithered in here.
+ * This screen is the one, where pixels are copied out of into the frame buffer.
+ * It may be 640x400 for japanese SCI1 games. SCI0 games may be undithered in here.
* Only read from this buffer for Save/ShowBits usage.
*/
byte *_displayScreen;
@@ -215,8 +185,8 @@ private:
ResourceManager *_resMan;
/**
- * Pointer to the currently active screen (changing it only required for
- * debug purposes).
+ * Pointer to the currently active screen (changing only required for
+ * debug purposes, to show for example the priority screen).
*/
byte *_activeScreen;
@@ -239,38 +209,241 @@ private:
*/
bool _fontIsUpscaled;
- // dynamic code
- void (GfxScreen::*_vectorAdjustCoordinatePtr) (int16 *x, int16 *y);
- void vectorAdjustCoordinateNOP (int16 *x, int16 *y);
- void vectorAdjustCoordinate480x300Mac (int16 *x, int16 *y);
- void (GfxScreen::*_vectorAdjustLineCoordinatesPtr) (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
- void vectorAdjustLineCoordinatesNOP (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
- void vectorAdjustLineCoordinates480x300Mac (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
-
- byte (GfxScreen::*_vectorIsFillMatchPtr) (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA);
- byte vectorIsFillMatchNormal (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA);
- byte vectorIsFillMatch480x300Mac (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA);
+ // pixel related code, in header so that it can be inlined for performance
+public:
+ void putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ if (_upscaledHires == GFX_SCREEN_UPSCALED_480x300) {
+ putPixel480x300(x, y, drawMask, color, priority, control);
+ return;
+ }
+
+ // Set pixel for visual, priority and control map directly, those are not upscaled
+ int offset = y * _width + x;
+
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ _visualScreen[offset] = color;
+
+ int displayOffset = 0;
+
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_DISABLED:
+ displayOffset = offset;
+ _displayScreen[displayOffset] = color;
+ break;
+
+ case GFX_SCREEN_UPSCALED_640x400:
+ case GFX_SCREEN_UPSCALED_640x440:
+ case GFX_SCREEN_UPSCALED_640x480:
+ putScaledPixelOnDisplay(x, y, color);
+ break;
+ default:
+ break;
+ }
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
+ _priorityScreen[offset] = priority;
+ }
+ if (drawMask & GFX_SCREEN_MASK_CONTROL) {
+ _controlScreen[offset] = control;
+ }
+ }
- void (GfxScreen::*_vectorPutPixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- void vectorPutPixel480x300Mac (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ void putPixel480x300(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ int offset = ((y * 3) / 2 * _width) + ((x * 3) / 2);
+
+ // All maps are upscaled
+ // TODO: figure out, what Sierra exactly did on Mac for these games
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ putPixel480x300Worker(x, y, offset, _visualScreen, color);
+ putPixel480x300Worker(x, y, offset, _displayScreen, color);
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
+ putPixel480x300Worker(x, y, offset, _priorityScreen, priority);
+ }
+ if (drawMask & GFX_SCREEN_MASK_CONTROL) {
+ putPixel480x300Worker(x, y, offset, _controlScreen, control);
+ }
+ }
+ void putPixel480x300Worker(int16 x, int16 y, int offset, byte *screen, byte byteToSet) {
+ screen[offset] = byteToSet;
+ if (x & 1)
+ screen[offset + 1] = byteToSet;
+ if (y & 1)
+ screen[offset + _width] = byteToSet;
+ if ((x & 1) && (y & 1))
+ screen[offset + _width + 1] = byteToSet;
+ }
- void (GfxScreen::*_vectorPutLinePixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- void vectorPutLinePixel480x300Mac (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ // This is called from vector drawing to put a pixel at a certain location
+ void vectorPutPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_640x400:
+ case GFX_SCREEN_UPSCALED_640x440:
+ case GFX_SCREEN_UPSCALED_640x480:
+ // For regular upscaled modes forward to the regular putPixel
+ putPixel(x, y, drawMask, color, priority, control);
+ return;
+ break;
+
+ default:
+ break;
+ }
+
+ // For non-upscaled mode and 480x300 Mac put pixels directly
+ int offset = y * _width + x;
+
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ _visualScreen[offset] = color;
+ _displayScreen[offset] = color;
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
+ _priorityScreen[offset] = priority;
+ }
+ if (drawMask & GFX_SCREEN_MASK_CONTROL) {
+ _controlScreen[offset] = control;
+ }
+ }
- byte (GfxScreen::*_vectorGetPixelPtr) (byte *screen, int16 x, int16 y);
+ /**
+ * This will just change a pixel directly on displayscreen. It is supposed to be
+ * only used on upscaled-Hires games where hires content needs to get drawn ONTO
+ * the upscaled display screen (like japanese fonts, hires portraits, etc.).
+ */
+ void putPixelOnDisplay(int16 x, int16 y, byte color) {
+ int offset = y * _displayWidth + x;
+ _displayScreen[offset] = color;
+ }
- void (GfxScreen::*_putPixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- void putPixelNormal (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- void putPixelDisplayUpscaled (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- void putPixelAllUpscaled (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ // Upscales a pixel and puts it on display screen only
+ void putScaledPixelOnDisplay(int16 x, int16 y, byte color) {
+ int displayOffset = 0;
+
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_640x400:
+ displayOffset = (y * 2) * _displayWidth + x * 2; // straight 1 pixel -> 2 mapping
+
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ _displayScreen[displayOffset + _displayWidth] = color;
+ _displayScreen[displayOffset + _displayWidth + 1] = color;
+ break;
+
+ case GFX_SCREEN_UPSCALED_640x440: {
+ int16 startY = (y * 11) / 5;
+ int16 endY = ((y + 1) * 11) / 5;
+ displayOffset = (startY * _displayWidth) + x * 2;
+
+ for (int16 curY = startY; curY < endY; curY++) {
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ displayOffset += _displayWidth;
+ }
+ break;
+ }
+ case GFX_SCREEN_UPSCALED_640x480: {
+ int16 startY = (y * 12) / 5;
+ int16 endY = ((y + 1) * 12) / 5;
+ displayOffset = (startY * _displayWidth) + x * 2;
+
+ for (int16 curY = startY; curY < endY; curY++) {
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ displayOffset += _displayWidth;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
- byte (GfxScreen::*_getPixelPtr) (byte *screen, int16 x, int16 y);
- byte getPixelNormal (byte *screen, int16 x, int16 y);
- byte getPixelUpscaled (byte *screen, int16 x, int16 y);
+ /**
+ * This is used to put font pixels onto the screen - we adjust differently, so that we won't
+ * do triple pixel lines in any case on upscaled hires. That way the font will not get distorted
+ * Sierra SCI didn't do this
+ */
+ void putFontPixel(int16 startingY, int16 x, int16 y, byte color) {
+ int16 actualY = startingY + y;
+ if (_fontIsUpscaled) {
+ // Do not scale ourselves, but put it on the display directly
+ putPixelOnDisplay(x, actualY, color);
+ } else {
+ int offset = actualY * _width + x;
+
+ _visualScreen[offset] = color;
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_DISABLED:
+ _displayScreen[offset] = color;
+ break;
+ case GFX_SCREEN_UPSCALED_640x400:
+ case GFX_SCREEN_UPSCALED_640x440:
+ case GFX_SCREEN_UPSCALED_640x480: {
+ // to 1-> 4 pixels upscaling for all of those, so that fonts won't look weird
+ int displayOffset = (_upscaledHeightMapping[startingY] + y * 2) * _displayWidth + x * 2;
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ displayOffset += _displayWidth;
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ break;
+ }
+ default:
+ putScaledPixelOnDisplay(x, actualY, color);
+ break;
+ }
+ }
+ }
- // pixel helper
- void putScaledPixelOnScreen(byte *screen, int16 x, int16 y, byte color);
+ byte getPixel(byte *screen, int16 x, int16 y) {
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_480x300: {
+ int offset = ((y * 3) / 2) * _width + ((y * 3) / 2);
+
+ return screen[offset];
+ break;
+ }
+ default:
+ break;
+ }
+ return screen[y * _width + x];
+ }
+
+ byte getVisual(int16 x, int16 y) {
+ return getPixel(_visualScreen, x, y);
+ }
+ byte getPriority(int16 x, int16 y) {
+ return getPixel(_priorityScreen, x, y);
+ }
+ byte getControl(int16 x, int16 y) {
+ return getPixel(_controlScreen, x, y);
+ }
+
+ // Vector related public code - in here, so that it can be inlined
+ byte vectorGetPixel(byte *screen, int16 x, int16 y) {
+ return screen[y * _width + x];
+ }
+
+ byte vectorGetVisual(int16 x, int16 y) {
+ return vectorGetPixel(_visualScreen, x, y);
+ }
+ byte vectorGetPriority(int16 x, int16 y) {
+ return vectorGetPixel(_priorityScreen, x, y);
+ }
+ byte vectorGetControl(int16 x, int16 y) {
+ return vectorGetPixel(_controlScreen, x, y);
+ }
+
+ void vectorAdjustCoordinate(int16 *x, int16 *y) {
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_480x300:
+ *x = (*x * 3) / 2;
+ *y = (*y * 3) / 2;
+ break;
+ default:
+ break;
+ }
+ }
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp
new file mode 100644
index 0000000000..7383dc222e
--- /dev/null
+++ b/engines/sci/graphics/screen_item32.cpp
@@ -0,0 +1,743 @@
+/* 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.
+ *
+ */
+
+#include "sci/console.h"
+#include "sci/resource.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/selector.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/screen_item32.h"
+#include "sci/graphics/view.h"
+
+namespace Sci {
+#pragma mark ScreenItem
+
+uint16 ScreenItem::_nextObjectId = 20000;
+
+ScreenItem::ScreenItem(const reg_t object) :
+_celObj(nullptr),
+_object(object),
+_pictureId(-1),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_mirrorX(false),
+_drawBlackLines(false) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+
+ setFromObject(segMan, object, true, true);
+ _plane = readSelector(segMan, object, SELECTOR(plane));
+}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo) :
+_plane(plane),
+_useInsetRect(false),
+_z(0),
+_celInfo(celInfo),
+_celObj(nullptr),
+_fixedPriority(false),
+_position(0, 0),
+_object(make_reg(0, _nextObjectId++)),
+_pictureId(-1),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_mirrorX(false),
+_drawBlackLines(false) {}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect) :
+_plane(plane),
+_useInsetRect(false),
+_z(0),
+_celInfo(celInfo),
+_celObj(nullptr),
+_fixedPriority(false),
+_position(rect.left, rect.top),
+_object(make_reg(0, _nextObjectId++)),
+_pictureId(-1),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_mirrorX(false),
+_drawBlackLines(false) {
+ if (celInfo.type == kCelTypeColor) {
+ _insetRect = rect;
+ }
+}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo) :
+_plane(plane),
+_scale(scaleInfo),
+_useInsetRect(false),
+_z(0),
+_celInfo(celInfo),
+_celObj(nullptr),
+_fixedPriority(false),
+_position(position),
+_object(make_reg(0, _nextObjectId++)),
+_pictureId(-1),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_mirrorX(false),
+_drawBlackLines(false) {}
+
+ScreenItem::ScreenItem(const ScreenItem &other) :
+_plane(other._plane),
+_scale(other._scale),
+_useInsetRect(other._useInsetRect),
+_celInfo(other._celInfo),
+_celObj(nullptr),
+_object(other._object),
+_mirrorX(other._mirrorX),
+_scaledPosition(other._scaledPosition),
+_screenRect(other._screenRect),
+_drawBlackLines(other._drawBlackLines) {
+ if (other._useInsetRect) {
+ _insetRect = other._insetRect;
+ }
+}
+
+void ScreenItem::operator=(const ScreenItem &other) {
+ // NOTE: The original engine did not check for differences in `_celInfo`
+ // to clear `_celObj` here; instead, it unconditionally set `_celInfo`,
+ // didn't clear `_celObj`, and did hacky stuff in `kIsOnMe` to avoid
+ // testing a mismatched `_celObj`. See `GfxFrameout::kernelIsOnMe` for
+ // more detail.
+ if (_celInfo != other._celInfo) {
+ _celInfo = other._celInfo;
+ delete _celObj;
+ _celObj = nullptr;
+ }
+
+ _screenRect = other._screenRect;
+ _mirrorX = other._mirrorX;
+ _useInsetRect = other._useInsetRect;
+ if (other._useInsetRect) {
+ _insetRect = other._insetRect;
+ }
+ _scale = other._scale;
+ _scaledPosition = other._scaledPosition;
+ _drawBlackLines = other._drawBlackLines;
+}
+
+ScreenItem::~ScreenItem() {
+ delete _celObj;
+}
+
+void ScreenItem::init() {
+ _nextObjectId = 20000;
+}
+
+void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap) {
+ _position.x = readSelectorValue(segMan, object, SELECTOR(x));
+ _position.y = readSelectorValue(segMan, object, SELECTOR(y));
+ _scale.x = readSelectorValue(segMan, object, SELECTOR(scaleX));
+ _scale.y = readSelectorValue(segMan, object, SELECTOR(scaleY));
+ _scale.max = readSelectorValue(segMan, object, SELECTOR(maxScale));
+ _scale.signal = (ScaleSignals32)(readSelectorValue(segMan, object, SELECTOR(scaleSignal)) & 3);
+
+ if (updateCel) {
+ _celInfo.resourceId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(view));
+ _celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
+ _celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel));
+
+ if (_celInfo.resourceId <= kPlanePic) {
+ // TODO: Enhance GfxView or ResourceManager to allow
+ // metadata for resources to be retrieved once, from a
+ // single location
+ Resource *view = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _celInfo.resourceId), false);
+ if (!view) {
+ error("Failed to load resource %d", _celInfo.resourceId);
+ }
+
+ // NOTE: +2 because the header size field itself is excluded from
+ // the header size in the data
+ const uint16 headerSize = READ_SCI11ENDIAN_UINT16(view->data) + 2;
+ const uint8 loopCount = view->data[2];
+ const uint8 loopSize = view->data[12];
+
+ if (_celInfo.loopNo >= loopCount) {
+ const int maxLoopNo = loopCount - 1;
+ _celInfo.loopNo = maxLoopNo;
+ writeSelectorValue(segMan, object, SELECTOR(loop), maxLoopNo);
+ }
+
+ byte *loopData = view->data + headerSize + (_celInfo.loopNo * loopSize);
+ const int8 seekEntry = loopData[0];
+ if (seekEntry != -1) {
+ loopData = view->data + headerSize + (seekEntry * loopSize);
+ }
+ const uint8 celCount = loopData[2];
+ if (_celInfo.celNo >= celCount) {
+ const int maxCelNo = celCount - 1;
+ _celInfo.celNo = maxCelNo;
+ writeSelectorValue(segMan, object, SELECTOR(cel), maxCelNo);
+ }
+ }
+ }
+
+ if (updateBitmap) {
+ const reg_t bitmap = readSelector(segMan, object, SELECTOR(bitmap));
+ if (!bitmap.isNull()) {
+ _celInfo.bitmap = bitmap;
+ _celInfo.type = kCelTypeMem;
+ } else {
+ _celInfo.bitmap = NULL_REG;
+ _celInfo.type = kCelTypeView;
+ }
+ }
+
+ if (updateCel || updateBitmap) {
+ delete _celObj;
+ _celObj = nullptr;
+ }
+
+ if (readSelectorValue(segMan, object, SELECTOR(fixPriority))) {
+ _fixedPriority = true;
+ _priority = readSelectorValue(segMan, object, SELECTOR(priority));
+ } else {
+ _fixedPriority = false;
+ writeSelectorValue(segMan, object, SELECTOR(priority), _position.y);
+ }
+
+ _z = readSelectorValue(segMan, object, SELECTOR(z));
+ _position.y -= _z;
+
+ if (readSelectorValue(segMan, object, SELECTOR(useInsetRect))) {
+ _useInsetRect = true;
+ _insetRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _insetRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _insetRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+ _insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ } else {
+ _useInsetRect = false;
+ }
+
+ segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewVisible);
+}
+
+void ScreenItem::calcRects(const Plane &plane) {
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ const CelObj &celObj = getCelObj();
+
+ Common::Rect celRect(celObj._width, celObj._height);
+ if (_useInsetRect) {
+ if (_insetRect.intersects(celRect)) {
+ _insetRect.clip(celRect);
+ } else {
+ _insetRect = Common::Rect();
+ }
+ } else {
+ _insetRect = celRect;
+ }
+
+ Ratio scaleX, scaleY;
+
+ if (_scale.signal & kScaleSignalDoScaling32) {
+ if (_scale.signal & kScaleSignalUseVanishingPoint) {
+ int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
+ scaleX = Ratio(num, 128);
+ scaleY = Ratio(num, 128);
+ } else {
+ scaleX = Ratio(_scale.x, 128);
+ scaleY = Ratio(_scale.y, 128);
+ }
+ }
+
+ if (scaleX.getNumerator() && scaleY.getNumerator()) {
+ _screenItemRect = _insetRect;
+
+ const Ratio celToScreenX(screenWidth, celObj._scaledWidth);
+ const Ratio celToScreenY(screenHeight, celObj._scaledHeight);
+
+ // Cel may use a coordinate system that is not the same size as the
+ // script coordinate system (usually this means high-resolution
+ // pictures with low-resolution scripts)
+ if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) {
+ // high resolution coordinates
+
+ if (_useInsetRect) {
+ const Ratio scriptToCelX(celObj._scaledWidth, scriptWidth);
+ const Ratio scriptToCelY(celObj._scaledHeight, scriptHeight);
+ mulru(_screenItemRect, scriptToCelX, scriptToCelY, 0);
+
+ if (_screenItemRect.intersects(celRect)) {
+ _screenItemRect.clip(celRect);
+ } else {
+ _screenItemRect = Common::Rect();
+ }
+ }
+
+ int displaceX = celObj._displace.x;
+ int displaceY = celObj._displace.y;
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ displaceX = celObj._width - celObj._displace.x - 1;
+ }
+
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ // Different games use a different cel scaling mode, but the
+ // difference isn't consistent across SCI versions; instead,
+ // it seems to be related to an update that happened during
+ // SCI2.1mid where games started using hi-resolution game
+ // scripts
+ if (scriptWidth == kLowResX) {
+ mulinc(_screenItemRect, scaleX, scaleY);
+ } else {
+ _screenItemRect.left = (_screenItemRect.left * scaleX).toInt();
+ _screenItemRect.top = (_screenItemRect.top * scaleY).toInt();
+
+ if (scaleX.getNumerator() > scaleX.getDenominator()) {
+ _screenItemRect.right = (_screenItemRect.right * scaleX).toInt();
+ } else {
+ _screenItemRect.right = ((_screenItemRect.right - 1) * scaleX).toInt() + 1;
+ }
+
+ if (scaleY.getNumerator() > scaleY.getDenominator()) {
+ _screenItemRect.bottom = (_screenItemRect.bottom * scaleY).toInt();
+ } else {
+ _screenItemRect.bottom = ((_screenItemRect.bottom - 1) * scaleY).toInt() + 1;
+ }
+ }
+
+ displaceX = (displaceX * scaleX).toInt();
+ displaceY = (displaceY * scaleY).toInt();
+ }
+
+ mulinc(_screenItemRect, celToScreenX, celToScreenY);
+ displaceX = (displaceX * celToScreenX).toInt();
+ displaceY = (displaceY * celToScreenY).toInt();
+
+ const Ratio scriptToScreenX = Ratio(screenWidth, scriptWidth);
+ const Ratio scriptToScreenY = Ratio(screenHeight, scriptHeight);
+
+ if (/* TODO: dword_C6288 */ false && _celInfo.type == kCelTypePic) {
+ _scaledPosition.x = _position.x;
+ _scaledPosition.y = _position.y;
+ } else {
+ _scaledPosition.x = (_position.x * scriptToScreenX).toInt() - displaceX;
+ _scaledPosition.y = (_position.y * scriptToScreenY).toInt() - displaceY;
+ }
+
+ _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y);
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type == kCelTypePic) {
+ Common::Rect temp(_insetRect);
+
+ if (!scaleX.isOne()) {
+ mulinc(temp, scaleX, Ratio());
+ }
+
+ mulinc(temp, celToScreenX, Ratio());
+
+ CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj);
+ if (celObjPic == nullptr) {
+ error("Expected a CelObjPic");
+ }
+ temp.translate((celObjPic->_relativePosition.x * scriptToScreenX).toInt() - displaceX, 0);
+
+ // TODO: This is weird.
+ int deltaX = plane._planeRect.width() - temp.right - 1 - temp.left;
+
+ _scaledPosition.x += deltaX;
+ _screenItemRect.translate(deltaX, 0);
+ }
+
+ _scaledPosition.x += plane._planeRect.left;
+ _scaledPosition.y += plane._planeRect.top;
+ _screenItemRect.translate(plane._planeRect.left, plane._planeRect.top);
+
+ _ratioX = scaleX * celToScreenX;
+ _ratioY = scaleY * celToScreenY;
+ } else {
+ // low resolution coordinates
+
+ int displaceX = celObj._displace.x;
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ displaceX = celObj._width - celObj._displace.x - 1;
+ }
+
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ mulinc(_screenItemRect, scaleX, scaleY);
+ // TODO: This was in the original code, baked into the
+ // multiplication though it is not immediately clear
+ // why this is the only one that reduces the BR corner
+ _screenItemRect.right -= 1;
+ _screenItemRect.bottom -= 1;
+ }
+
+ _scaledPosition.x = _position.x - (displaceX * scaleX).toInt();
+ _scaledPosition.y = _position.y - (celObj._displace.y * scaleY).toInt();
+ _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y);
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type == kCelTypePic) {
+ Common::Rect temp(_insetRect);
+
+ if (!scaleX.isOne()) {
+ mulinc(temp, scaleX, Ratio());
+ temp.right -= 1;
+ }
+
+ CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj);
+ if (celObjPic == nullptr) {
+ error("Expected a CelObjPic");
+ }
+ temp.translate(celObjPic->_relativePosition.x - (displaceX * scaleX).toInt(), celObjPic->_relativePosition.y - (celObj._displace.y * scaleY).toInt());
+
+ // TODO: This is weird.
+ int deltaX = plane._gameRect.width() - temp.right - 1 - temp.left;
+
+ _scaledPosition.x += deltaX;
+ _screenItemRect.translate(deltaX, 0);
+ }
+
+ _scaledPosition.x += plane._gameRect.left;
+ _scaledPosition.y += plane._gameRect.top;
+ _screenItemRect.translate(plane._gameRect.left, plane._gameRect.top);
+
+ if (celObj._scaledWidth != screenWidth || celObj._scaledHeight != screenHeight) {
+ mulru(_scaledPosition, celToScreenX, celToScreenY);
+ mulru(_screenItemRect, celToScreenX, celToScreenY, 1);
+ }
+
+ _ratioX = scaleX * celToScreenX;
+ _ratioY = scaleY * celToScreenY;
+ }
+
+ _screenRect = _screenItemRect;
+
+ if (_screenRect.intersects(plane._screenRect)) {
+ _screenRect.clip(plane._screenRect);
+ } else {
+ _screenRect.right = 0;
+ _screenRect.bottom = 0;
+ _screenRect.left = 0;
+ _screenRect.top = 0;
+ }
+
+ if (!_fixedPriority) {
+ _priority = _z + _position.y;
+ }
+ } else {
+ _screenRect.left = 0;
+ _screenRect.top = 0;
+ _screenRect.right = 0;
+ _screenRect.bottom = 0;
+ }
+}
+
+CelObj &ScreenItem::getCelObj() const {
+ if (_celObj == nullptr) {
+ switch (_celInfo.type) {
+ case kCelTypeView:
+ _celObj = new CelObjView(_celInfo.resourceId, _celInfo.loopNo, _celInfo.celNo);
+ break;
+ case kCelTypePic:
+ error("Internal error, pic screen item with no cel.");
+ break;
+ case kCelTypeMem:
+ _celObj = new CelObjMem(_celInfo.bitmap);
+ break;
+ case kCelTypeColor:
+ _celObj = new CelObjColor(_celInfo.color, _insetRect.width(), _insetRect.height());
+ break;
+ }
+ }
+
+ return *_celObj;
+}
+
+void ScreenItem::printDebugInfo(Console *con) const {
+ con->debugPrintf("%04x:%04x (%s), prio %d, x %d, y %d, z: %d, scaledX: %d, scaledY: %d flags: %d\n",
+ _object.getSegment(), _object.getOffset(),
+ g_sci->getEngineState()->_segMan->getObjectName(_object),
+ _priority,
+ _position.x,
+ _position.y,
+ _z,
+ _scaledPosition.x,
+ _scaledPosition.y,
+ _created | (_updated << 1) | (_deleted << 2)
+ );
+ con->debugPrintf(" screen rect (%d, %d, %d, %d)\n", PRINT_RECT(_screenRect));
+ if (_useInsetRect) {
+ con->debugPrintf(" inset rect: (%d, %d, %d, %d)\n", PRINT_RECT(_insetRect));
+ }
+
+ Common::String celType;
+ switch (_celInfo.type) {
+ case kCelTypePic:
+ celType = "pic";
+ break;
+ case kCelTypeView:
+ celType = "view";
+ break;
+ case kCelTypeColor:
+ celType = "color";
+ break;
+ case kCelTypeMem:
+ celType = "mem";
+ break;
+ }
+
+ con->debugPrintf(" type: %s, res %d, loop %d, cel %d, bitmap %04x:%04x, color: %d\n",
+ celType.c_str(),
+ _celInfo.resourceId,
+ _celInfo.loopNo,
+ _celInfo.celNo,
+ PRINT_REG(_celInfo.bitmap),
+ _celInfo.color
+ );
+ if (_celObj != nullptr) {
+ con->debugPrintf(" width %d, height %d, scaledWidth %d, scaledHeight %d\n",
+ _celObj->_width,
+ _celObj->_height,
+ _celObj->_scaledWidth,
+ _celObj->_scaledHeight
+ );
+ }
+}
+
+void ScreenItem::update(const reg_t object) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+
+ const GuiResourceId view = readSelectorValue(segMan, object, SELECTOR(view));
+ const int16 loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
+ const int16 celNo = readSelectorValue(segMan, object, SELECTOR(cel));
+
+ const bool updateCel = (
+ _celInfo.resourceId != view ||
+ _celInfo.loopNo != loopNo ||
+ _celInfo.celNo != celNo
+ );
+
+ const bool updateBitmap = !readSelector(segMan, object, SELECTOR(bitmap)).isNull();
+
+ setFromObject(segMan, object, updateCel, updateBitmap);
+
+ if (!_created) {
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ _deleted = 0;
+}
+
+void ScreenItem::update() {
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane);
+ if (plane == nullptr) {
+ error("ScreenItem::update: Invalid plane %04x:%04x", PRINT_REG(_plane));
+ }
+
+ if (plane->_screenItemList.findByObject(_object) == nullptr) {
+ error("ScreenItem::update: %04x:%04x not in plane %04x:%04x", PRINT_REG(_object), PRINT_REG(_plane));
+ }
+
+ if (!_created) {
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+ _deleted = 0;
+
+ delete _celObj;
+ _celObj = nullptr;
+}
+
+Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
+ CelObj &celObj = getCelObj();
+
+ Common::Rect celObjRect(celObj._width, celObj._height);
+ Common::Rect nsRect;
+
+ if (_useInsetRect) {
+ if (_insetRect.intersects(celObjRect)) {
+ nsRect = _insetRect;
+ nsRect.clip(celObjRect);
+ } else {
+ nsRect = Common::Rect();
+ }
+ } else {
+ nsRect = celObjRect;
+ }
+
+ const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Ratio scaleX, scaleY;
+ if (_scale.signal & kScaleSignalDoScaling32) {
+ if (_scale.signal & kScaleSignalUseVanishingPoint) {
+ int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
+ scaleX = Ratio(num, 128);
+ scaleY = Ratio(num, 128);
+ } else {
+ scaleX = Ratio(_scale.x, 128);
+ scaleY = Ratio(_scale.y, 128);
+ }
+ }
+
+ if (scaleX.getNumerator() == 0 || scaleY.getNumerator() == 0) {
+ return Common::Rect();
+ }
+
+ int16 displaceX = celObj._displace.x;
+ int16 displaceY = celObj._displace.y;
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ displaceX = celObj._width - displaceX - 1;
+ }
+
+ if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) {
+ // high resolution coordinates
+
+ if (_useInsetRect) {
+ Ratio scriptToCelX(celObj._scaledWidth, scriptWidth);
+ Ratio scriptToCelY(celObj._scaledHeight, scriptHeight);
+ mulru(nsRect, scriptToCelX, scriptToCelY, 0);
+
+ if (nsRect.intersects(celObjRect)) {
+ nsRect.clip(celObjRect);
+ } else {
+ nsRect = Common::Rect();
+ }
+ }
+
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ // Different games use a different cel scaling mode, but the
+ // difference isn't consistent across SCI versions; instead,
+ // it seems to be related to an update that happened during
+ // SCI2.1mid where games started using hi-resolution game
+ // scripts
+ if (scriptWidth == kLowResX) {
+ mulinc(nsRect, scaleX, scaleY);
+ // TODO: This was in the original code, baked into the
+ // multiplication though it is not immediately clear
+ // why this is the only one that reduces the BR corner
+ nsRect.right -= 1;
+ nsRect.bottom -= 1;
+ } else {
+ nsRect.left = (nsRect.left * scaleX).toInt();
+ nsRect.top = (nsRect.top * scaleY).toInt();
+
+ if (scaleX.getNumerator() > scaleX.getDenominator()) {
+ nsRect.right = (nsRect.right * scaleX).toInt();
+ } else {
+ nsRect.right = ((nsRect.right - 1) * scaleX).toInt() + 1;
+ }
+
+ if (scaleY.getNumerator() > scaleY.getDenominator()) {
+ nsRect.bottom = (nsRect.bottom * scaleY).toInt();
+ } else {
+ nsRect.bottom = ((nsRect.bottom - 1) * scaleY).toInt() + 1;
+ }
+ }
+ }
+
+ Ratio celToScriptX(scriptWidth, celObj._scaledWidth);
+ Ratio celToScriptY(scriptHeight, celObj._scaledHeight);
+
+ displaceX = (displaceX * scaleX * celToScriptX).toInt();
+ displaceY = (displaceY * scaleY * celToScriptY).toInt();
+
+ mulinc(nsRect, celToScriptX, celToScriptY);
+ nsRect.translate(_position.x - displaceX, _position.y - displaceY);
+ } else {
+ // low resolution coordinates
+
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ mulinc(nsRect, scaleX, scaleY);
+ // TODO: This was in the original code, baked into the
+ // multiplication though it is not immediately clear
+ // why this is the only one that reduces the BR corner
+ nsRect.right -= 1;
+ nsRect.bottom -= 1;
+ }
+
+ displaceX = (displaceX * scaleX).toInt();
+ displaceY = (displaceY * scaleY).toInt();
+ nsRect.translate(_position.x - displaceX, _position.y - displaceY);
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ nsRect.translate(plane._gameRect.width() - nsRect.width(), 0);
+ }
+ }
+
+ return nsRect;
+}
+
+#pragma mark -
+#pragma mark ScreenItemList
+ScreenItem *ScreenItemList::findByObject(const reg_t object) const {
+ const_iterator screenItemIt = Common::find_if(begin(), end(), FindByObject<ScreenItem *>(object));
+
+ if (screenItemIt == end()) {
+ return nullptr;
+ }
+
+ return *screenItemIt;
+}
+void ScreenItemList::sort() {
+ if (size() < 2) {
+ return;
+ }
+
+ for (size_type i = 0; i < size(); ++i) {
+ _unsorted[i] = i;
+ }
+
+ for (size_type i = size() - 1; i > 0; --i) {
+ bool swap = false;
+
+ for (size_type j = 0; j < i; ++j) {
+ value_type &a = operator[](j);
+ value_type &b = operator[](j + 1);
+
+ if (a == nullptr || *a > *b) {
+ SWAP(a, b);
+ SWAP(_unsorted[j], _unsorted[j + 1]);
+ swap = true;
+ }
+ }
+
+ if (!swap) {
+ break;
+ }
+ }
+}
+void ScreenItemList::unsort() {
+ if (size() < 2) {
+ return;
+ }
+
+ for (size_type i = 0; i < size(); ++i) {
+ while (_unsorted[i] != i) {
+ SWAP(operator[](_unsorted[i]), operator[](i));
+ SWAP(_unsorted[_unsorted[i]], _unsorted[i]);
+ }
+ }
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h
new file mode 100644
index 0000000000..3d9d5ef3d7
--- /dev/null
+++ b/engines/sci/graphics/screen_item32.h
@@ -0,0 +1,326 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_SCREEN_ITEM32_H
+#define SCI_GRAPHICS_SCREEN_ITEM32_H
+
+#include "common/rect.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/lists32.h"
+
+namespace Sci {
+
+enum ScaleSignals32 {
+ kScaleSignalNone = 0,
+ kScaleSignalDoScaling32 = 1, // enables scaling when drawing that cel (involves scaleX and scaleY)
+ kScaleSignalUseVanishingPoint = 2,
+ // TODO: Is this actually a thing? I have not seen it and
+ // the original engine masks &3 where it uses scale signals.
+ kScaleSignalDisableGlobalScaling32 = 4
+};
+
+struct ScaleInfo {
+ int x, y, max;
+ ScaleSignals32 signal;
+ ScaleInfo() : x(128), y(128), max(100), signal(kScaleSignalNone) {}
+};
+
+class CelObj;
+class Plane;
+class SegManager;
+
+#pragma mark -
+#pragma mark ScreenItem
+
+/**
+ * A ScreenItem is the engine-side representation of a
+ * game script View.
+ */
+class ScreenItem {
+private:
+ /**
+ * A serial used for screen items that are generated
+ * inside the graphics engine, rather than the
+ * interpreter.
+ */
+ static uint16 _nextObjectId;
+
+public:
+ /**
+ * The parent plane of this screen item.
+ */
+ reg_t _plane;
+
+ /**
+ * Scaling data used to calculate the final screen
+ * dimensions of the screen item as well as the scaling
+ * ratios used when drawing the item to screen.
+ */
+ ScaleInfo _scale;
+
+private:
+ /**
+ * The position & dimensions of the screen item in
+ * screen coordinates. This rect includes the offset
+ * of the parent plane, but is not clipped to the
+ * screen, so may include coordinates that are
+ * offscreen.
+ */
+ Common::Rect _screenItemRect;
+
+ /**
+ * If true, the `_insetRect` rectangle will be used
+ * when calculating the dimensions of the screen item
+ * instead of the cel's intrinsic width and height.
+ *
+ * In other words, using an inset rect means that
+ * the cel is cropped to the dimensions given in
+ * `_insetRect`.
+ */
+ bool _useInsetRect;
+
+ /**
+ * The cropping rectangle used when `_useInsetRect`
+ * is true.
+ *
+ * `_insetRect` is also used to describe the fill
+ * rectangle of a screen item with a CelObjColor
+ * cel.
+ */
+ Common::Rect _insetRect;
+
+ /**
+ * The z-index of the screen item in pseudo-3D space.
+ * Higher values are drawn on top of lower values.
+ */
+ int _z;
+
+ /**
+ * Sets the common properties of a screen item that must
+ * be set both during creation and update of a screen
+ * item.
+ */
+ void setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap);
+
+public:
+ /**
+ * A descriptor for the cel object represented by the
+ * screen item.
+ */
+ CelInfo32 _celInfo;
+
+ /**
+ * The cel object used to actually render the screen
+ * item. This member is populated by calling
+ * `getCelObj`.
+ */
+ mutable CelObj *_celObj;
+
+ /**
+ * If set, the priority for this screen item is fixed
+ * in place. Otherwise, the priority of the screen item
+ * is calculated from its y-position + z-index.
+ */
+ bool _fixedPriority;
+
+ /**
+ * The rendering priority of the screen item, relative
+ * only to the other screen items within the same plane.
+ * Higher priorities are drawn above lower priorities.
+ */
+ int16 _priority;
+
+ /**
+ * The top-left corner of the screen item, in game
+ * script coordinates, relative to the parent plane.
+ */
+ Common::Point _position;
+
+ /**
+ * The associated View script object that was
+ * used to create the ScreenItem, or a numeric
+ * value in the case of a ScreenItem that was
+ * generated outside of the VM.
+ */
+ reg_t _object;
+
+ /**
+ * For screen items representing picture resources,
+ * the resource ID of the picture.
+ */
+ GuiResourceId _pictureId;
+
+ /**
+ * Flags indicating the state of the screen item.
+ * - `created` is set when the screen item is first
+ * created, either from a VM object or from within the
+ * engine itself
+ * - `updated` is set when `created` is not already set
+ * and the screen item is updated from a VM object
+ * - `deleted` is set by the parent plane, if the parent
+ * plane is a pic type and its picture resource ID has
+ * changed
+ */
+ int _created, _updated, _deleted;
+
+ /**
+ * For screen items that represent picture cels, this
+ * value is set to match the `_mirrorX` property of the
+ * parent plane and indicates that the cel should be
+ * drawn horizontally mirrored. For final drawing, it is
+ * XORed with the `_mirrorX` property of the cel object.
+ * The cel object's `_mirrorX` property comes from the
+ * resource data itself.
+ */
+ bool _mirrorX;
+
+ /**
+ * The scaling ratios to use when drawing this screen
+ * item. These values are calculated according to the
+ * scale info whenever the screen item is updated.
+ */
+ Ratio _ratioX, _ratioY;
+
+ /**
+ * The top-left corner of the screen item, in screen
+ * coordinates.
+ */
+ Common::Point _scaledPosition;
+
+ /**
+ * The position & dimensions of the screen item in
+ * screen coordinates. This rect includes the offset of
+ * the parent plane and is clipped to the screen.
+ */
+ Common::Rect _screenRect;
+
+ /**
+ * Whether or not the screen item should be drawn
+ * with black lines drawn every second line. This is
+ * used when pixel doubling videos to improve apparent
+ * sharpness at the cost of your eyesight.
+ */
+ bool _drawBlackLines;
+
+ /**
+ * Initialises static Plane members.
+ */
+ static void init();
+
+ ScreenItem(const reg_t screenItem);
+ ScreenItem(const reg_t plane, const CelInfo32 &celInfo);
+ ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect);
+ ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo);
+ ScreenItem(const ScreenItem &other);
+ ~ScreenItem();
+ void operator=(const ScreenItem &);
+
+ inline bool operator<(const ScreenItem &other) const {
+ if (_priority < other._priority) {
+ return true;
+ }
+
+ if (_priority == other._priority) {
+ if (_position.y + _z < other._position.y + other._z) {
+ return true;
+ }
+
+ if (_position.y + _z == other._position.y + other._z) {
+ return _object < other._object;
+ }
+ }
+
+ return false;
+ }
+
+ inline bool operator>(const ScreenItem &other) const {
+ if (_priority > other._priority) {
+ return true;
+ }
+
+ if (_priority == other._priority) {
+ if (_position.y + _z > other._position.y + other._z) {
+ return true;
+ }
+
+ if (_position.y + _z == other._position.y + other._z) {
+ return _object > other._object;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Calculates the dimensions and scaling parameters for
+ * the screen item, using the given plane as the parent
+ * plane for screen rect positioning.
+ *
+ * @note This method was called Update in SCI engine.
+ */
+ void calcRects(const Plane &plane);
+
+ /**
+ * Retrieves the corresponding cel object for this
+ * screen item. If a cel object does not already exist,
+ * one will be created and assigned.
+ */
+ CelObj &getCelObj() const;
+
+ void printDebugInfo(Console *con) const;
+
+ /**
+ * Updates the properties of the screen item from a
+ * VM object.
+ */
+ void update(const reg_t object);
+
+ /**
+ * Updates the properties of the screen item for one not belonging
+ * to a VM object. Originally GraphicsMgr::UpdateScreenItem.
+ */
+ void update();
+
+ /**
+ * Gets the "now seen" rect for the screen item, which
+ * represents the current size and position of the
+ * screen item on the screen in script coordinates.
+ */
+ Common::Rect getNowSeenRect(const Plane &plane) const;
+};
+
+#pragma mark -
+#pragma mark ScreenItemList
+
+typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase;
+class ScreenItemList : public ScreenItemListBase {
+private:
+ size_type _unsorted[250];
+
+public:
+ ScreenItem *findByObject(const reg_t object) const;
+ void sort();
+ void unsort();
+};
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/graphics/text16.cpp b/engines/sci/graphics/text16.cpp
index f463dff4b1..b0f2c52791 100644
--- a/engines/sci/graphics/text16.cpp
+++ b/engines/sci/graphics/text16.cpp
@@ -83,8 +83,7 @@ void GfxText16::ClearChar(int16 chr) {
}
// This internal function gets called as soon as a '|' is found in a text. It
-// will process the encountered code and set new font/set color. We only support
-// one-digit codes currently, don't know if multi-digit codes are possible.
+// will process the encountered code and set new font/set color.
// Returns textcode character count.
int16 GfxText16::CodeProcessing(const char *&text, GuiResourceId orgFontId, int16 orgPenColor, bool doingDrawing) {
const char *textCode = text;
@@ -99,10 +98,8 @@ int16 GfxText16::CodeProcessing(const char *&text, GuiResourceId orgFontId, int1
// c -> sets textColor to current port pen color
// cX -> sets textColor to _textColors[X-1]
curCode = textCode[0];
- curCodeParm = textCode[1];
- if (Common::isDigit(curCodeParm)) {
- curCodeParm -= '0';
- } else {
+ curCodeParm = strtol(textCode+1, NULL, 10);
+ if (!Common::isDigit(textCode[1])) {
curCodeParm = -1;
}
switch (curCode) {
@@ -144,12 +141,41 @@ int16 GfxText16::CodeProcessing(const char *&text, GuiResourceId orgFontId, int1
}
// Has actually punctuation and characters in it, that may not be the first in a line
+// SCI1 didn't check for exclamation nor question marks, us checking for those too shouldn't be bad
static const uint16 text16_shiftJIS_punctuation[] = {
+ 0x4181, 0x4281, 0x7681, 0x7881, 0x4981, 0x4881, 0
+};
+
+// Table from Quest for Glory 1 PC-98 (SCI01)
+// has pronunciation and small combining form characters on top (details right after this table)
+static const uint16 text16_shiftJIS_punctuation_SCI01[] = {
0x9F82, 0xA182, 0xA382, 0xA582, 0xA782, 0xC182, 0xE182, 0xE382, 0xE582, 0xEC82, 0x4083, 0x4283,
0x4483, 0x4683, 0x4883, 0x6283, 0x8383, 0x8583, 0x8783, 0x8E83, 0x9583, 0x9683, 0x5B81, 0x4181,
0x4281, 0x7681, 0x7881, 0x4981, 0x4881, 0
};
+// Police Quest 2 (SCI0) only checked for: 0x4181, 0x4281, 0x7681, 0x7881, 0x4981, 0x4881
+// Castle of Dr. Brain/King's Quest 5/Space Quest 4 (SCI1) only checked for: 0x4181, 0x4281, 0x7681, 0x7881
+
+// SCI0/SCI01/SCI1:
+// 0x4181 -> comma, 0x4281 -> period / full stop
+// 0x7681 -> ending quotation mark, 0x7881 -> secondary quotation mark
+
+// SCI0/SCI01:
+// 0x4981 -> exclamation mark, 0x4881 -> question mark
+
+// SCI01 (Quest for Glory only):
+// 0x9F82, 0xA182, 0xA382, 0xA582, 0xA782 -> specifies vowel part of prev. hiragana char or pronunciation/extension of vowel
+// 0xC182 -> pronunciation
+// 0xE182, 0xE382, 0xE582, 0xEC82 -> small combining form of hiragana
+// 0x4083, 0x4283, 0x4483, 0x4683, 0x4883 -> small combining form of katagana
+// 0x6283 -> glottal stop / sokuon
+// 0x8383, 0x8583 0x8783, 0x8E83 -> small combining form of katagana
+// 0x9583 -> combining form
+// 0x9683 -> abbreviation for the kanji (ka), the counter for months, places or provisions
+// 0x5b81 -> low line / underscore (full width)
+
+
// return max # of chars to fit maxwidth with full words, does not include
// breaking space
// Also adjusts text pointer to the new position for the caller
@@ -201,9 +227,10 @@ int16 GfxText16::GetLongest(const char *&textPtr, int16 maxWidth, GuiResourceId
}
// it's meant to pass through here
case 0xA:
- case 0x9781: // this one is used by SQ4/japanese as line break as well
+ case 0x9781: // this one is used by SQ4/japanese as line break as well (was added for SCI1/PC98)
curCharCount++; textPtr++;
if (curChar > 0xFF) {
+ // skip another byte in case char is double-byte (PC-98)
curCharCount++; textPtr++;
}
// and it's also meant to pass through here
@@ -261,17 +288,27 @@ int16 GfxText16::GetLongest(const char *&textPtr, int16 maxWidth, GuiResourceId
// But it also checked, if the current character is not inside a punctuation table and it even
// went backwards in case it found multiple ones inside that table.
+ // Note: PQ2 PC-98 only went back 1 character and not multiple ones
uint nonBreakingPos = 0;
+ const uint16 *punctuationTable;
+
+ if (getSciVersion() != SCI_VERSION_01) {
+ punctuationTable = text16_shiftJIS_punctuation;
+ } else {
+ // Quest for Glory 1 PC-98 only
+ punctuationTable = text16_shiftJIS_punctuation_SCI01;
+ }
+
while (1) {
// Look up if character shouldn't be the first on a new line
nonBreakingPos = 0;
- while (text16_shiftJIS_punctuation[nonBreakingPos]) {
- if (text16_shiftJIS_punctuation[nonBreakingPos] == curChar)
+ while (punctuationTable[nonBreakingPos]) {
+ if (punctuationTable[nonBreakingPos] == curChar)
break;
nonBreakingPos++;
}
- if (!text16_shiftJIS_punctuation[nonBreakingPos]) {
+ if (!punctuationTable[nonBreakingPos]) {
// character is fine
break;
}
@@ -285,6 +322,14 @@ int16 GfxText16::GetLongest(const char *&textPtr, int16 maxWidth, GuiResourceId
error("Non double byte while seeking back");
curChar |= (*(const byte *)(textPtr + 1)) << 8;
}
+
+ if (curChar == 0x4081) {
+ // Skip over alphabetic double-byte space
+ // This was introduced for SCI1
+ // Happens in Castle of Dr. Brain PC-98 in room 120, when looking inside the mirror
+ // (game mentions Mixed Up Fairy Tales and uses English letters for that)
+ textPtr += 2;
+ }
}
// We split the word in that case
diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp
index 56ce73e8fa..277e6e93d0 100644
--- a/engines/sci/graphics/text32.cpp
+++ b/engines/sci/graphics/text32.cpp
@@ -29,363 +29,691 @@
#include "sci/engine/selector.h"
#include "sci/engine/state.h"
#include "sci/graphics/cache.h"
+#include "sci/graphics/celobj32.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/font.h"
+#include "sci/graphics/frameout.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/text32.h"
namespace Sci {
-#define BITMAP_HEADER_SIZE 46
+int16 GfxText32::_defaultFontId = 0;
+int16 GfxText32::_scaledWidth = 0;
+int16 GfxText32::_scaledHeight = 0;
+
+GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) :
+ _segMan(segMan),
+ _cache(fonts),
+ // Not a typo, the original engine did not initialise height, only width
+ _width(0),
+ _text(""),
+ _bitmap(NULL_REG) {
+ _fontId = _defaultFontId;
+ _font = _cache->getFont(_defaultFontId);
+
+ if (_scaledWidth == 0) {
+ // initialize the statics
+ _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ }
+ }
-#define SCI_TEXT32_ALIGNMENT_RIGHT -1
-#define SCI_TEXT32_ALIGNMENT_CENTER 1
-#define SCI_TEXT32_ALIGNMENT_LEFT 0
+reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling) {
-GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen)
- : _segMan(segMan), _cache(fonts), _screen(screen) {
-}
+ _borderColor = borderColor;
+ _text = text;
+ _textRect = rect;
+ _width = width;
+ _height = height;
+ _foreColor = foreColor;
+ _backColor = backColor;
+ _skipColor = skipColor;
+ _alignment = alignment;
+ _dimmed = dimmed;
-GfxText32::~GfxText32() {
-}
+ setFont(fontId);
-reg_t GfxText32::createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
- return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk);
+ if (doScaling) {
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
-}
-reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
- reg_t stringObject = readSelector(_segMan, textObject, SELECTOR(text));
- // The object in the text selector of the item can be either a raw string
- // or a Str object. In the latter case, we need to access the object's data
- // selector to get the raw string.
- if (_segMan->isHeapObject(stringObject))
- stringObject = readSelector(_segMan, stringObject, SELECTOR(data));
+ Ratio scaleX(_scaledWidth, scriptWidth);
+ Ratio scaleY(_scaledHeight, scriptHeight);
- Common::String text = _segMan->getString(stringObject);
+ _width = (_width * scaleX).toInt();
+ _height = (_height * scaleY).toInt();
+ mulinc(_textRect, scaleX, scaleY);
+ }
- return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk);
-}
+ // _textRect represents where text is drawn inside the
+ // bitmap; clipRect is the entire bitmap
+ Common::Rect bitmapRect(_width, _height);
-reg_t GfxText32::createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
- // HACK: The character offsets of the up and down arrow buttons are off by one
- // in GK1, for some unknown reason. Fix them here.
- if (text.size() == 1 && (text[0] == 29 || text[0] == 30)) {
- text.setChar(text[0] + 1, 0);
- }
- GuiResourceId fontId = readSelectorValue(_segMan, textObject, SELECTOR(font));
- GfxFont *font = _cache->getFont(fontId);
- bool dimmed = readSelectorValue(_segMan, textObject, SELECTOR(dimmed));
- int16 alignment = readSelectorValue(_segMan, textObject, SELECTOR(mode));
- uint16 foreColor = readSelectorValue(_segMan, textObject, SELECTOR(fore));
- uint16 backColor = readSelectorValue(_segMan, textObject, SELECTOR(back));
-
- Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(textObject);
- uint16 width = nsRect.width() + 1;
- uint16 height = nsRect.height() + 1;
-
- // Limit rectangle dimensions, if requested
- if (maxWidth > 0)
- width = maxWidth;
- if (maxHeight > 0)
- height = maxHeight;
-
- // Upscale the coordinates/width if the fonts are already upscaled
- if (_screen->fontIsUpscaled()) {
- width = width * _screen->getDisplayWidth() / _screen->getWidth();
- height = height * _screen->getDisplayHeight() / _screen->getHeight();
+ if (_textRect.intersects(bitmapRect)) {
+ _textRect.clip(bitmapRect);
+ } else {
+ _textRect = Common::Rect();
}
- int entrySize = width * height + BITMAP_HEADER_SIZE;
- reg_t memoryId = NULL_REG;
- if (prevHunk.isNull()) {
- memoryId = _segMan->allocateHunkEntry("TextBitmap()", entrySize);
+ BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false);
+ _bitmap = bitmap.getObject();
- // Scroll text objects have no bitmap selector!
- ObjVarRef varp;
- if (lookupSelector(_segMan, textObject, SELECTOR(bitmap), &varp, NULL) == kSelectorVariable)
- writeSelector(_segMan, textObject, SELECTOR(bitmap), memoryId);
- } else {
- memoryId = prevHunk;
+ erase(bitmapRect, false);
+
+ if (_borderColor > -1) {
+ drawFrame(bitmapRect, 1, _borderColor, false);
}
- byte *memoryPtr = _segMan->getHunkPointer(memoryId);
- if (prevHunk.isNull())
- memset(memoryPtr, 0, BITMAP_HEADER_SIZE);
+ drawTextBox();
+ return _bitmap;
+}
- byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
- memset(bitmap, backColor, width * height);
+reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed) {
+ _borderColor = borderColor;
+ _text = text;
+ _textRect = rect;
+ _foreColor = foreColor;
+ _dimmed = dimmed;
- // Save totalWidth, totalHeight
- WRITE_LE_UINT16(memoryPtr, width);
- WRITE_LE_UINT16(memoryPtr + 2, height);
+ setFont(fontId);
- int16 charCount = 0;
- uint16 curX = 0, curY = 0;
- const char *txt = text.c_str();
- int16 textWidth, textHeight, totalHeight = 0, offsetX = 0, offsetY = 0;
- uint16 start = 0;
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
- // Calculate total text height
- while (*txt) {
- charCount = GetLongest(txt, width, font);
- if (charCount == 0)
- break;
+ mulinc(_textRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight));
- Width(txt, 0, (int16)strlen(txt), fontId, textWidth, textHeight, true);
+ CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo);
+ _skipColor = view._transparentColor;
+ _width = view._width * _scaledWidth / view._scaledWidth;
+ _height = view._height * _scaledHeight / view._scaledHeight;
- totalHeight += textHeight;
- txt += charCount;
- while (*txt == ' ')
- txt++; // skip over breaking spaces
+ Common::Rect bitmapRect(_width, _height);
+ if (_textRect.intersects(bitmapRect)) {
+ _textRect.clip(bitmapRect);
+ } else {
+ _textRect = Common::Rect();
}
- txt = text.c_str();
-
- // Draw text in buffer
- while (*txt) {
- charCount = GetLongest(txt, width, font);
- if (charCount == 0)
- break;
- Width(txt, start, charCount, fontId, textWidth, textHeight, true);
-
- switch (alignment) {
- case SCI_TEXT32_ALIGNMENT_RIGHT:
- offsetX = width - textWidth;
- break;
- case SCI_TEXT32_ALIGNMENT_CENTER:
- // Center text both horizontally and vertically
- offsetX = (width - textWidth) / 2;
- offsetY = (height - totalHeight) / 2;
- break;
- case SCI_TEXT32_ALIGNMENT_LEFT:
- offsetX = 0;
- break;
-
- default:
- warning("Invalid alignment %d used in TextBox()", alignment);
- }
+ BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false);
+ _bitmap = bitmap.getObject();
+
+ // NOTE: The engine filled the bitmap pixels with 11 here, which is silly
+ // because then it just erased the bitmap using the skip color. So we don't
+ // fill the bitmap redundantly here.
+
+ _backColor = _skipColor;
+ erase(bitmapRect, false);
+ _backColor = backColor;
+
+ view.draw(bitmap.getBuffer(), bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight));
- byte curChar;
-
- for (int i = 0; i < charCount; i++) {
- curChar = txt[i];
-
- switch (curChar) {
- case 0x0A:
- case 0x0D:
- case 0:
- break;
- case 0x7C:
- warning("Code processing isn't implemented in SCI32");
- break;
- default:
- font->drawToBuffer(curChar, curY + offsetY, curX + offsetX, foreColor, dimmed, bitmap, width, height);
- curX += font->getCharWidth(curChar);
- break;
+ if (_backColor != skipColor && _foreColor != skipColor) {
+ erase(_textRect, false);
+ }
+
+ if (text.size() > 0) {
+ if (_foreColor == skipColor) {
+ error("TODO: Implement transparent text");
+ } else {
+ if (borderColor != -1) {
+ drawFrame(bitmapRect, 1, _borderColor, false);
}
- }
- curX = 0;
- curY += font->getHeight();
- txt += charCount;
- while (*txt == ' ')
- txt++; // skip over breaking spaces
+ drawTextBox();
+ }
}
- return memoryId;
+ return _bitmap;
}
-void GfxText32::disposeTextBitmap(reg_t hunkId) {
- _segMan->freeHunkEntry(hunkId);
+void GfxText32::setFont(const GuiResourceId fontId) {
+ // NOTE: In SCI engine this calls FontMgr::BuildFontTable and then a font
+ // table is built on the FontMgr directly; instead, because we already have
+ // font resources, this code just grabs a font out of GfxCache.
+ if (fontId != _fontId) {
+ _fontId = fontId == -1 ? _defaultFontId : fontId;
+ _font = _cache->getFont(_fontId);
+ }
}
-void GfxText32::drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject) {
- reg_t hunkId = readSelector(_segMan, textObject, SELECTOR(bitmap));
- drawTextBitmapInternal(x, y, planeRect, textObject, hunkId);
+void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling) {
+ Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
+
+ byte *bitmap = _segMan->getHunkPointer(_bitmap);
+ byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28) + rect.top * _width + rect.left;
+
+ // NOTE: Not fully disassembled, but this should be right
+ int16 rectWidth = targetRect.width();
+ int16 sidesHeight = targetRect.height() - size * 2;
+ int16 centerWidth = rectWidth - size * 2;
+ int16 stride = _width - rectWidth;
+
+ for (int16 y = 0; y < size; ++y) {
+ memset(pixels, color, rectWidth);
+ pixels += _width;
+ }
+ for (int16 y = 0; y < sidesHeight; ++y) {
+ for (int16 x = 0; x < size; ++x) {
+ *pixels++ = color;
+ }
+ pixels += centerWidth;
+ for (int16 x = 0; x < size; ++x) {
+ *pixels++ = color;
+ }
+ pixels += stride;
+ }
+ for (int16 y = 0; y < size; ++y) {
+ memset(pixels, color, rectWidth);
+ pixels += _width;
+ }
}
-void GfxText32::drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y) {
- /*reg_t plane = readSelector(_segMan, textObject, SELECTOR(plane));
- Common::Rect planeRect;
- planeRect.top = readSelectorValue(_segMan, plane, SELECTOR(top));
- planeRect.left = readSelectorValue(_segMan, plane, SELECTOR(left));
- planeRect.bottom = readSelectorValue(_segMan, plane, SELECTOR(bottom));
- planeRect.right = readSelectorValue(_segMan, plane, SELECTOR(right));
+void GfxText32::drawChar(const char charIndex) {
+ byte *bitmap = _segMan->getHunkPointer(_bitmap);
+ byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28);
- drawTextBitmapInternal(x, y, planeRect, textObject, hunkId);*/
+ _font->drawToBuffer(charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height);
+ _drawPosition.x += _font->getCharWidth(charIndex);
+}
- // HACK: we pretty much ignore the plane rect and x, y...
- drawTextBitmapInternal(0, 0, Common::Rect(20, 390, 600, 460), textObject, hunkId);
+uint16 GfxText32::getCharWidth(const char charIndex, const bool doScaling) const {
+ uint16 width = _font->getCharWidth(charIndex);
+ if (doScaling) {
+ width = scaleUpWidth(width);
+ }
+ return width;
}
-void GfxText32::drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId) {
- int16 backColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(back));
- // Sanity check: Check if the hunk is set. If not, either the game scripts
- // didn't set it, or an old saved game has been loaded, where it wasn't set.
- if (hunkId.isNull())
+void GfxText32::drawTextBox() {
+ if (_text.size() == 0) {
return;
+ }
- // Negative coordinates indicate that text shouldn't be displayed
- if (x < 0 || y < 0)
- return;
+ const char *text = _text.c_str();
+ const char *sourceText = text;
+ int16 textRectWidth = _textRect.width();
+ _drawPosition.y = _textRect.top;
+ uint charIndex = 0;
- byte *memoryPtr = _segMan->getHunkPointer(hunkId);
+ if (g_sci->getGameId() == GID_SQ6 || g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+ if (getLongest(&charIndex, textRectWidth) == 0) {
+ error("DrawTextBox GetLongest=0");
+ }
+ }
- if (!memoryPtr) {
- // Happens when restoring in some SCI32 games (e.g. SQ6).
- // Commented out to reduce console spam
- //warning("Attempt to draw an invalid text bitmap");
- return;
+ charIndex = 0;
+ uint nextCharIndex = 0;
+ while (*text != '\0') {
+ _drawPosition.x = _textRect.left;
+
+ uint length = getLongest(&nextCharIndex, textRectWidth);
+ int16 textWidth = getTextWidth(charIndex, length);
+
+ if (_alignment == kTextAlignCenter) {
+ _drawPosition.x += (textRectWidth - textWidth) / 2;
+ } else if (_alignment == kTextAlignRight) {
+ _drawPosition.x += textRectWidth - textWidth;
+ }
+
+ drawText(charIndex, length);
+ charIndex = nextCharIndex;
+ text = sourceText + charIndex;
+ _drawPosition.y += _font->getHeight();
}
+}
- byte *surface = memoryPtr + BITMAP_HEADER_SIZE;
+void GfxText32::drawTextBox(const Common::String &text) {
+ _text = text;
+ drawTextBox();
+}
+
+void GfxText32::drawText(const uint index, uint length) {
+ assert(index + length <= _text.size());
+
+ // NOTE: This draw loop implementation is somewhat different than the
+ // implementation in the actual engine, but should be accurate. Primarily
+ // the changes revolve around eliminating some extra temporaries and
+ // fixing the logic to match.
+ const char *text = _text.c_str() + index;
+ while (length-- > 0) {
+ char currentChar = *text++;
+
+ if (currentChar == '|') {
+ const char controlChar = *text++;
+ --length;
- int curByte = 0;
- int16 skipColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(skip));
- uint16 textX = planeRect.left + x;
- uint16 textY = planeRect.top + y;
- // Get totalWidth, totalHeight
- uint16 width = READ_LE_UINT16(memoryPtr);
- uint16 height = READ_LE_UINT16(memoryPtr + 2);
+ if (length == 0) {
+ return;
+ }
+
+ if (controlChar == 'a' || controlChar == 'c' || controlChar == 'f') {
+ uint16 value = 0;
+
+ while (length > 0) {
+ const char valueChar = *text;
+ if (valueChar < '0' || valueChar > '9') {
+ break;
+ }
+
+ ++text;
+ --length;
+ value = 10 * value + (valueChar - '0');
+ }
+
+ if (length == 0) {
+ return;
+ }
+
+ if (controlChar == 'a') {
+ _alignment = (TextAlign)value;
+ } else if (controlChar == 'c') {
+ _foreColor = value;
+ } else if (controlChar == 'f') {
+ setFont(value);
+ }
+ }
- // Upscale the coordinates/width if the fonts are already upscaled
- if (_screen->fontIsUpscaled()) {
- textX = textX * _screen->getDisplayWidth() / _screen->getWidth();
- textY = textY * _screen->getDisplayHeight() / _screen->getHeight();
+ while (length > 0 && *text != '|') {
+ ++text;
+ --length;
+ }
+ if (length > 0) {
+ ++text;
+ --length;
+ }
+ } else {
+ drawChar(currentChar);
+ }
+ }
+}
+
+void GfxText32::invertRect(const reg_t bitmap, int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling) {
+ Common::Rect targetRect = rect;
+ if (doScaling) {
+ bitmapStride = bitmapStride * _scaledWidth / g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ targetRect = scaleRect(rect);
+ }
+
+ byte *bitmapData = _segMan->getHunkPointer(bitmap);
+
+ // NOTE: SCI code is super weird here; it seems to be trying to look at the
+ // entire size of the bitmap including the header, instead of just the pixel
+ // data size. We just look at the pixel size. This function generally is an
+ // odd duck since the stride dimension for a bitmap is built in to the bitmap
+ // header, so perhaps it was once an unheadered bitmap format and this
+ // function was never updated to match? Or maybe they exploit the
+ // configurable stride length somewhere else to do stair stepping inverts...
+ uint32 invertSize = targetRect.height() * bitmapStride + targetRect.width();
+ uint32 bitmapSize = READ_SCI11ENDIAN_UINT32(bitmapData + 12);
+
+ if (invertSize >= bitmapSize) {
+ error("InvertRect too big: %u >= %u", invertSize, bitmapSize);
}
- bool translucent = (skipColor == -1 && backColor == -1);
+ // NOTE: Actual engine just added the bitmap header size hardcoded here
+ byte *pixel = bitmapData + READ_SCI11ENDIAN_UINT32(bitmapData + 28) + bitmapStride * targetRect.top + targetRect.left;
+
+ int16 stride = bitmapStride - targetRect.width();
+ int16 targetHeight = targetRect.height();
+ int16 targetWidth = targetRect.width();
+
+ for (int16 y = 0; y < targetHeight; ++y) {
+ for (int16 x = 0; x < targetWidth; ++x) {
+ if (*pixel == foreColor) {
+ *pixel = backColor;
+ } else if (*pixel == backColor) {
+ *pixel = foreColor;
+ }
- for (int curY = 0; curY < height; curY++) {
- for (int curX = 0; curX < width; curX++) {
- byte pixel = surface[curByte++];
- if ((!translucent && pixel != skipColor && pixel != backColor) ||
- (translucent && pixel != 0xFF))
- _screen->putFontPixel(textY, curX + textX, curY, pixel);
+ ++pixel;
}
+
+ pixel += stride;
}
}
-int16 GfxText32::GetLongest(const char *text, int16 maxWidth, GfxFont *font) {
- uint16 curChar = 0;
- int16 maxChars = 0, curCharCount = 0;
- uint16 width = 0;
-
- while (width <= maxWidth) {
- curChar = (*(const byte *)text++);
-
- switch (curChar) {
- // We need to add 0xD, 0xA and 0xD 0xA to curCharCount and then exit
- // which means, we split text like
- // 'Mature, experienced software analyst available.' 0xD 0xA
- // 'Bug installation a proven speciality. "No version too clean."' (normal game text, this is from lsl2)
- // and 0xA '-------' 0xA (which is the official sierra subtitle separator)
- // Sierra did it the same way.
- case 0xD:
- // Check, if 0xA is following, if so include it as well
- if ((*(const unsigned char *)text) == 0xA)
- curCharCount++;
- // it's meant to pass through here
- case 0xA:
- curCharCount++;
- // and it's also meant to pass through here
- case 0:
- return curCharCount;
- case ' ':
- maxChars = curCharCount; // return count up to (but not including) breaking space
- break;
+uint GfxText32::getLongest(uint *charIndex, const int16 width) {
+ assert(width > 0);
+
+ uint testLength = 0;
+ uint length = 0;
+
+ const uint initialCharIndex = *charIndex;
+
+ // The index of the next word after the last word break
+ uint lastWordBreakIndex = *charIndex;
+
+ const char *text = _text.c_str() + *charIndex;
+
+ char currentChar;
+ while ((currentChar = *text++) != '\0') {
+ // NOTE: In the original engine, the font, color, and alignment were
+ // reset here to their initial values
+
+ // The text to render contains a line break; stop at the line break
+ if (currentChar == '\r' || currentChar == '\n') {
+ // Skip the rest of the line break if it is a Windows-style
+ // \r\n or non-standard \n\r
+ // NOTE: In the original engine, the `text` pointer had not been
+ // advanced yet so the indexes used to access characters were
+ // one higher
+ if (
+ (currentChar == '\r' && text[0] == '\n') ||
+ (currentChar == '\n' && text[0] == '\r' && text[1] != '\n')
+ ) {
+ ++*charIndex;
+ }
+
+ // We are at the end of a line but the last word in the line made
+ // it too wide to fit in the text area; return up to the previous
+ // word
+ if (length && getTextWidth(initialCharIndex, testLength) > width) {
+ *charIndex = lastWordBreakIndex;
+ return length;
+ }
+
+ // Skip the line break and return all text seen up to now
+ // NOTE: In original engine, the font, color, and alignment were
+ // reset, then getTextWidth was called to use its side-effects to
+ // set font, color, and alignment according to the text from
+ // `initialCharIndex` to `testLength`
+ ++*charIndex;
+ return testLength;
+ } else if (currentChar == ' ') {
+ // The last word in the line made it too wide to fit in the text area;
+ // return up to the previous word, then collapse the whitespace
+ // between that word and its next sibling word into the line break
+ if (getTextWidth(initialCharIndex, testLength) > width) {
+ *charIndex = lastWordBreakIndex;
+ const char *nextChar = _text.c_str() + lastWordBreakIndex;
+ while (*nextChar++ == ' ') {
+ ++*charIndex;
+ }
+
+ // NOTE: In original engine, the font, color, and alignment were
+ // set here to the values that were seen at the last space character
+ return length;
+ }
+
+ // NOTE: In the original engine, the values of _fontId, _foreColor,
+ // and _alignment were stored for use in the return path mentioned
+ // just above here
+
+ // We found a word break that was within the text area, memorise it
+ // and continue processing. +1 on the character index because it has
+ // not been incremented yet so currently points to the word break
+ // and not the word after the break
+ length = testLength;
+ lastWordBreakIndex = *charIndex + 1;
+ }
+
+ // In the middle of a line, keep processing
+ ++*charIndex;
+ ++testLength;
+
+ // NOTE: In the original engine, the font, color, and alignment were
+ // reset here to their initial values
+
+ // The text to render contained no word breaks yet but is already too
+ // wide for the text area; just split the word in half at the point
+ // where it overflows
+ if (length == 0 && getTextWidth(initialCharIndex, testLength) > width) {
+ *charIndex = --testLength + lastWordBreakIndex;
+ return testLength;
}
- if (width + font->getCharWidth(curChar) > maxWidth)
- break;
- width += font->getCharWidth(curChar);
- curCharCount++;
}
- return maxChars;
+ // The complete text to render was a single word, or was narrower than
+ // the text area, so return the entire line
+ if (length == 0 || getTextWidth(initialCharIndex, testLength) <= width) {
+ // NOTE: In original engine, the font, color, and alignment were
+ // reset, then getTextWidth was called to use its side-effects to
+ // set font, color, and alignment according to the text from
+ // `initialCharIndex` to `testLength`
+ return testLength;
+ }
+
+ // The last word in the line made it wider than the text area, so return
+ // up to the penultimate word
+ *charIndex = lastWordBreakIndex;
+ return length;
}
-void GfxText32::kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) {
- Common::Rect rect(0, 0, 0, 0);
- Size(rect, text, font, maxWidth);
- *textWidth = rect.width();
- *textHeight = rect.height();
+int16 GfxText32::getTextWidth(const uint index, uint length) const {
+ int16 width = 0;
+
+ const char *text = _text.c_str() + index;
+
+ GfxFont *font = _font;
+
+ char currentChar = *text++;
+ while (length > 0 && currentChar != '\0') {
+ // Control codes are in the format `|<code><value>|`
+ if (currentChar == '|') {
+ // NOTE: Original engine code changed the global state of the
+ // FontMgr here upon encountering any color, alignment, or
+ // font control code.
+ // To avoid requiring all callers to manually restore these
+ // values on every call, we ignore control codes other than
+ // font change (since alignment and color do not change the
+ // width of characters), and simply update the font pointer
+ // on stack instead of the member property font.
+ currentChar = *text++;
+ --length;
+
+ if (length > 0 && currentChar == 'f') {
+ GuiResourceId fontId = 0;
+ do {
+ currentChar = *text++;
+ --length;
+
+ fontId = fontId * 10 + currentChar - '0';
+ } while (length > 0 && *text >= '0' && *text <= '9');
+
+ if (length > 0) {
+ font = _cache->getFont(fontId);
+ }
+ }
+
+ // Forward through any more unknown control character data
+ while (length > 0 && *text != '|') {
+ ++text;
+ --length;
+ }
+ if (length > 0) {
+ ++text;
+ --length;
+ }
+ } else {
+ width += font->getCharWidth(currentChar);
+ }
+
+ if (length > 0) {
+ currentChar = *text++;
+ --length;
+ }
+ }
+
+ return width;
}
-void GfxText32::StringWidth(const char *str, GuiResourceId fontId, int16 &textWidth, int16 &textHeight) {
- Width(str, 0, (int16)strlen(str), fontId, textWidth, textHeight, true);
+int16 GfxText32::getTextWidth(const Common::String &text, const uint index, const uint length) {
+ _text = text;
+ return scaleUpWidth(getTextWidth(index, length));
}
-void GfxText32::Width(const char *text, int16 from, int16 len, GuiResourceId fontId, int16 &textWidth, int16 &textHeight, bool restoreFont) {
- byte curChar;
- textWidth = 0; textHeight = 0;
-
- GfxFont *font = _cache->getFont(fontId);
-
- if (font) {
- text += from;
- while (len--) {
- curChar = (*(const byte *)text++);
- switch (curChar) {
- case 0x0A:
- case 0x0D:
- textHeight = MAX<int16> (textHeight, font->getHeight());
- break;
- case 0x7C:
- warning("Code processing isn't implemented in SCI32");
- break;
- default:
- textHeight = MAX<int16> (textHeight, font->getHeight());
- textWidth += font->getCharWidth(curChar);
- break;
+Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, bool doScaling) {
+ // NOTE: Like most of the text rendering code, this function was pretty
+ // weird in the original engine. The initial result rectangle was actually
+ // a 1x1 rectangle (0, 0, 0, 0), which was then "fixed" after the main
+ // text size loop finished running by subtracting 1 from the right and
+ // bottom edges. Like other functions in SCI32, this has been converted
+ // to use exclusive rects with inclusive rounding.
+
+ Common::Rect result;
+
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ maxWidth = maxWidth * _scaledWidth / scriptWidth;
+
+ _text = text;
+
+ if (maxWidth >= 0) {
+ if (maxWidth == 0) {
+ maxWidth = _scaledWidth * 3 / 5;
+ }
+
+ result.right = maxWidth;
+
+ int16 textWidth = 0;
+ if (_text.size() > 0) {
+ const char *rawText = _text.c_str();
+ const char *sourceText = rawText;
+ uint charIndex = 0;
+ uint nextCharIndex = 0;
+ while (*rawText != '\0') {
+ uint length = getLongest(&nextCharIndex, result.width());
+ textWidth = MAX(textWidth, getTextWidth(charIndex, length));
+ charIndex = nextCharIndex;
+ rawText = sourceText + charIndex;
+ // TODO: Due to getLongest and getTextWidth not having side
+ // effects, it is possible that the currently loaded font's
+ // height is wrong for this line if it was changed inline
+ result.bottom += _font->getHeight();
}
}
+
+ if (textWidth < maxWidth) {
+ result.right = textWidth;
+ }
+ } else {
+ result.right = getTextWidth(0, 10000);
+
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ result.bottom = 0;
+ } else {
+ // NOTE: In the original engine code, the bottom was not decremented
+ // by 1, which means that the rect was actually a pixel taller than
+ // the height of the font. This was not the case in the other branch,
+ // which decremented the bottom by 1 at the end of the loop.
+ result.bottom = _font->getHeight() + 1;
+ }
+ }
+
+ if (doScaling) {
+ // NOTE: The original engine code also scaled top/left but these are
+ // always zero so there is no reason to do that.
+ result.right = ((result.right - 1) * scriptWidth + _scaledWidth - 1) / _scaledWidth + 1;
+ result.bottom = ((result.bottom - 1) * scriptHeight + _scaledHeight - 1) / _scaledHeight + 1;
}
+
+ return result;
}
-int16 GfxText32::Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth) {
- int16 charCount;
- int16 maxTextWidth = 0, textWidth;
- int16 totalHeight = 0, textHeight;
+void GfxText32::erase(const Common::Rect &rect, const bool doScaling) {
+ Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
+
+ BitmapResource bitmap(_bitmap);
+ bitmap.getBuffer().fillRect(targetRect, _backColor);
+}
- // Adjust maxWidth if we're using an upscaled font
- if (_screen->fontIsUpscaled())
- maxWidth = maxWidth * _screen->getDisplayWidth() / _screen->getWidth();
+int16 GfxText32::getStringWidth(const Common::String &text) {
+ return getTextWidth(text, 0, 10000);
+}
- rect.top = rect.left = 0;
- GfxFont *font = _cache->getFont(fontId);
+int16 GfxText32::getTextCount(const Common::String &text, const uint index, const Common::Rect &textRect, const bool doScaling) {
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
- if (maxWidth < 0) { // force output as single line
- StringWidth(text, fontId, textWidth, textHeight);
- rect.bottom = textHeight;
- rect.right = textWidth;
+ Common::Rect scaledRect(textRect);
+ if (doScaling) {
+ mulinc(scaledRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight));
+ }
+
+ Common::String oldText = _text;
+ _text = text;
+
+ uint charIndex = index;
+ int16 maxWidth = scaledRect.width();
+ int16 lineCount = (scaledRect.height() - 2) / _font->getHeight();
+ while (lineCount--) {
+ getLongest(&charIndex, maxWidth);
+ }
+
+ _text = oldText;
+ return charIndex - index;
+}
+
+int16 GfxText32::getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling) {
+ setFont(fontId);
+ return getTextCount(text, index, textRect, doScaling);
+}
+
+void GfxText32::scrollLine(const Common::String &lineText, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir) {
+ BitmapResource bmr(_bitmap);
+ byte *pixels = bmr.getPixels();
+
+ int h = _font->getHeight();
+
+ if (dir == kScrollUp) {
+ // Scroll existing text down
+ for (int i = 0; i < (numLines - 1) * h; ++i) {
+ int y = _textRect.top + numLines * h - i - 1;
+ memcpy(pixels + y * _width + _textRect.left,
+ pixels + (y - h) * _width + _textRect.left,
+ _textRect.width());
+ }
} else {
- // rect.right=found widest line with RTextWidth and GetLongest
- // rect.bottom=num. lines * GetPointSize
- rect.right = (maxWidth ? maxWidth : 192);
- const char *curPos = text;
- while (*curPos) {
- charCount = GetLongest(curPos, rect.right, font);
- if (charCount == 0)
- break;
- Width(curPos, 0, charCount, fontId, textWidth, textHeight, false);
- maxTextWidth = MAX(textWidth, maxTextWidth);
- totalHeight += textHeight;
- curPos += charCount;
- while (*curPos == ' ')
- curPos++; // skip over breaking spaces
+ // Scroll existing text up
+ for (int i = 0; i < (numLines - 1) * h; ++i) {
+ int y = _textRect.top + i;
+ memcpy(pixels + y * _width + _textRect.left,
+ pixels + (y + h) * _width + _textRect.left,
+ _textRect.width());
}
- rect.bottom = totalHeight;
- rect.right = maxWidth ? maxWidth : MIN(rect.right, maxTextWidth);
}
- // Adjust the width/height if we're using an upscaled font
- // for the scripts
- if (_screen->fontIsUpscaled()) {
- rect.right = rect.right * _screen->getWidth() / _screen->getDisplayWidth();
- rect.bottom = rect.bottom * _screen->getHeight() / _screen->getDisplayHeight();
+ Common::Rect lineRect = _textRect;
+
+ if (dir == kScrollUp) {
+ lineRect.bottom = lineRect.top + h;
+ } else {
+ // It is unclear to me what the purpose of this bottom++ is.
+ // It does not seem to be the usual inc/exc issue.
+ lineRect.top += (numLines - 1) * h;
+ lineRect.bottom++;
+ }
+
+ erase(lineRect, false);
+
+ _drawPosition.x = _textRect.left;
+ _drawPosition.y = _textRect.top;
+ if (dir == kScrollDown) {
+ _drawPosition.y += (numLines - 1) * h;
+ }
+
+ _foreColor = color;
+ _alignment = align;
+ //int fc = _foreColor;
+
+ setFont(fontId);
+
+ _text = lineText;
+ int16 textWidth = getTextWidth(0, lineText.size());
+
+ if (_alignment == kTextAlignCenter) {
+ _drawPosition.x += (_textRect.width() - textWidth) / 2;
+ } else if (_alignment == kTextAlignRight) {
+ _drawPosition.x += _textRect.width() - textWidth;
}
- return rect.right;
+ //_foreColor = fc;
+ //setFont(fontId);
+
+ drawText(0, lineText.size());
}
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h
index 7ba7df50e4..a61760dd87 100644
--- a/engines/sci/graphics/text32.h
+++ b/engines/sci/graphics/text32.h
@@ -23,34 +23,462 @@
#ifndef SCI_GRAPHICS_TEXT32_H
#define SCI_GRAPHICS_TEXT32_H
+#include "sci/engine/state.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/helpers.h"
+
namespace Sci {
+enum TextAlign {
+ kTextAlignDefault = -1,
+ kTextAlignLeft = 0,
+ kTextAlignCenter = 1,
+ kTextAlignRight = 2
+};
+
+enum ScrollDirection {
+ kScrollUp,
+ kScrollDown
+};
+
+enum BitmapFlags {
+ kBitmapRemap = 2
+};
+
+#define BITMAP_PROPERTY(size, property, offset)\
+inline uint##size get##property() const {\
+ return READ_SCI11ENDIAN_UINT##size(_bitmap + (offset));\
+}\
+inline void set##property(uint##size value) {\
+ WRITE_SCI11ENDIAN_UINT##size(_bitmap + (offset), (value));\
+}
+
/**
- * Text32 class, handles text calculation and displaying of text for SCI2, SCI21 and SCI3 games
+ * A convenience class for creating and modifying in-memory
+ * bitmaps.
*/
-class GfxText32 {
+class BitmapResource {
+ byte *_bitmap;
+ reg_t _object;
+ Buffer _buffer;
+
+ /**
+ * Gets the size of the bitmap header for the current
+ * engine version.
+ */
+ static inline uint16 getBitmapHeaderSize() {
+ // TODO: These values are accurate for each engine, but there may be no reason
+ // to not simply just always use size 40, since SCI2.1mid does not seem to
+ // actually store any data above byte 40, and SCI2 did not allow bitmaps with
+ // scaling resolutions other than the default (320x200). Perhaps SCI3 used
+ // the extra bytes, or there is some reason why they tried to align the header
+ // size with other headers like pic headers?
+// uint32 bitmapHeaderSize;
+// if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+// bitmapHeaderSize = 46;
+// } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) {
+// bitmapHeaderSize = 40;
+// } else {
+// bitmapHeaderSize = 36;
+// }
+// return bitmapHeaderSize;
+ return 46;
+ }
+
+ /**
+ * Gets the byte size of a bitmap with the given width
+ * and height.
+ */
+ static inline uint32 getBitmapSize(const uint16 width, const uint16 height) {
+ return width * height + getBitmapHeaderSize();
+ }
+
public:
- GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen);
- ~GfxText32();
- reg_t createTextBitmap(reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG);
- reg_t createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG);
- void drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject);
- void drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y);
- void disposeTextBitmap(reg_t hunkId);
- int16 GetLongest(const char *text, int16 maxWidth, GfxFont *font);
+ /**
+ * Create a bitmap resource for an existing bitmap.
+ * Ownership of the bitmap is retained by the caller.
+ */
+ inline BitmapResource(reg_t bitmap) :
+ _bitmap(g_sci->getEngineState()->_segMan->getHunkPointer(bitmap)),
+ _object(bitmap) {
+ if (_bitmap == nullptr || getUncompressedDataOffset() != getBitmapHeaderSize()) {
+ error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap));
+ }
- void kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight);
+ _buffer = Buffer(getWidth(), getHeight(), getPixels());
+ }
-private:
- reg_t createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t hunkId);
- void drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId);
- int16 Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth);
- void Width(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight, bool restoreFont);
- void StringWidth(const char *str, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight);
+ /**
+ * Allocates and initialises a new bitmap in the given
+ * segment manager.
+ */
+ inline BitmapResource(SegManager *segMan, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool remap) {
+ _object = segMan->allocateHunkEntry("Bitmap()", getBitmapSize(width, height));
+ _bitmap = segMan->getHunkPointer(_object);
+
+ const uint16 bitmapHeaderSize = getBitmapHeaderSize();
+
+ setWidth(width);
+ setHeight(height);
+ setDisplace(Common::Point(displaceX, displaceY));
+ setSkipColor(skipColor);
+ _bitmap[9] = 0;
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 10, 0);
+ setRemap(remap);
+ setDataSize(width * height);
+ WRITE_SCI11ENDIAN_UINT32(_bitmap + 16, 0);
+ setHunkPaletteOffset(hunkPaletteOffset);
+ setDataOffset(bitmapHeaderSize);
+ setUncompressedDataOffset(bitmapHeaderSize);
+ setControlOffset(0);
+ setScaledWidth(scaledWidth);
+ setScaledHeight(scaledHeight);
+
+ _buffer = Buffer(getWidth(), getHeight(), getPixels());
+ }
+
+ inline reg_t getObject() const {
+ return _object;
+ }
+
+ inline Buffer &getBuffer() {
+ return _buffer;
+ }
+
+ BITMAP_PROPERTY(16, Width, 0);
+ BITMAP_PROPERTY(16, Height, 2);
+
+ inline Common::Point getDisplace() const {
+ return Common::Point(
+ (int16)READ_SCI11ENDIAN_UINT16(_bitmap + 4),
+ (int16)READ_SCI11ENDIAN_UINT16(_bitmap + 6)
+ );
+ }
+
+ inline void setDisplace(const Common::Point &displace) {
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 4, (uint16)displace.x);
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 6, (uint16)displace.y);
+ }
+ inline uint8 getSkipColor() const {
+ return _bitmap[8];
+ }
+
+ inline void setSkipColor(const uint8 skipColor) {
+ _bitmap[8] = skipColor;
+ }
+
+ inline bool getRemap() const {
+ return READ_SCI11ENDIAN_UINT16(_bitmap + 10) & kBitmapRemap;
+ }
+
+ inline void setRemap(const bool remap) {
+ uint16 flags = READ_SCI11ENDIAN_UINT16(_bitmap + 10);
+ if (remap) {
+ flags |= kBitmapRemap;
+ } else {
+ flags &= ~kBitmapRemap;
+ }
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 10, flags);
+ }
+
+ BITMAP_PROPERTY(32, DataSize, 12);
+
+ inline uint32 getHunkPaletteOffset() const {
+ return READ_SCI11ENDIAN_UINT32(_bitmap + 20);
+ }
+
+ inline void setHunkPaletteOffset(uint32 hunkPaletteOffset) {
+ if (hunkPaletteOffset) {
+ hunkPaletteOffset += getBitmapHeaderSize();
+ }
+
+ WRITE_SCI11ENDIAN_UINT32(_bitmap + 20, hunkPaletteOffset);
+ }
+
+ BITMAP_PROPERTY(32, DataOffset, 24);
+
+ // NOTE: This property is used as a "magic number" for
+ // validating that a block of memory is a valid bitmap,
+ // and so is always set to the size of the header.
+ BITMAP_PROPERTY(32, UncompressedDataOffset, 28);
+
+ // NOTE: This property always seems to be zero
+ BITMAP_PROPERTY(32, ControlOffset, 32);
+
+ inline uint16 getScaledWidth() const {
+ if (getDataOffset() >= 40) {
+ return READ_SCI11ENDIAN_UINT16(_bitmap + 36);
+ }
+
+ // SCI2 bitmaps did not have scaling ability
+ return 320;
+ }
+
+ inline void setScaledWidth(uint16 scaledWidth) {
+ if (getDataOffset() >= 40) {
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 36, scaledWidth);
+ }
+ }
+
+ inline uint16 getScaledHeight() const {
+ if (getDataOffset() >= 40) {
+ return READ_SCI11ENDIAN_UINT16(_bitmap + 38);
+ }
+
+ // SCI2 bitmaps did not have scaling ability
+ return 200;
+ }
+
+ inline void setScaledHeight(uint16 scaledHeight) {
+ if (getDataOffset() >= 40) {
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 38, scaledHeight);
+ }
+ }
+
+ inline byte *getPixels() {
+ return _bitmap + getUncompressedDataOffset();
+ }
+};
+
+class GfxFont;
+
+/**
+ * This class handles text calculation and rendering for
+ * SCI32 games. The text calculation system in SCI32 is
+ * nearly the same as SCI16, which means this class behaves
+ * similarly. Notably, GfxText32 maintains drawing
+ * parameters across multiple calls.
+ */
+class GfxText32 {
+private:
SegManager *_segMan;
GfxCache *_cache;
- GfxScreen *_screen;
+
+ /**
+ * The resource ID of the default font used by the game.
+ *
+ * @todo Check all SCI32 games to learn what their
+ * default font is.
+ */
+ static int16 _defaultFontId;
+
+ /**
+ * The width and height of the currently active text
+ * bitmap, in text-system coordinates.
+ *
+ * @note These are unsigned in the actual engine.
+ */
+ int16 _width, _height;
+
+ /**
+ * The color used to draw text.
+ */
+ uint8 _foreColor;
+
+ /**
+ * The background color of the text box.
+ */
+ uint8 _backColor;
+
+ /**
+ * The transparent color of the text box. Used when
+ * compositing the bitmap onto the screen.
+ */
+ uint8 _skipColor;
+
+ /**
+ * The rect where the text is drawn within the bitmap.
+ * This rect is clipped to the dimensions of the bitmap.
+ */
+ Common::Rect _textRect;
+
+ /**
+ * The text being drawn to the currently active text
+ * bitmap.
+ */
+ Common::String _text;
+
+ /**
+ * The font being used to draw the text.
+ */
+ GuiResourceId _fontId;
+
+ /**
+ * The color of the text box border.
+ */
+ int16 _borderColor;
+
+ /**
+ * TODO: Document
+ */
+ bool _dimmed;
+
+ /**
+ * The text alignment for the drawn text.
+ */
+ TextAlign _alignment;
+
+ /**
+ * The position of the text draw cursor.
+ */
+ Common::Point _drawPosition;
+
+ void drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling);
+
+ void drawChar(const char charIndex);
+ void drawText(const uint index, uint length);
+
+ /**
+ * Gets the length of the longest run of text available
+ * within the currently loaded text, starting from the
+ * given `charIndex` and running for up to `maxWidth`
+ * pixels. Returns the number of characters that can be
+ * written, and mutates the value pointed to by
+ * `charIndex` to point to the index of the next
+ * character to render.
+ */
+ uint getLongest(uint *charIndex, const int16 maxWidth);
+
+ /**
+ * Gets the pixel width of a substring of the currently
+ * loaded text, without scaling.
+ */
+ int16 getTextWidth(const uint index, uint length) const;
+
+ inline Common::Rect scaleRect(const Common::Rect &rect) {
+ Common::Rect scaledRect(rect);
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ Ratio scaleX(_scaledWidth, scriptWidth);
+ Ratio scaleY(_scaledHeight, scriptHeight);
+ mulinc(scaledRect, scaleX, scaleY);
+ return scaledRect;
+ }
+
+public:
+ GfxText32(SegManager *segMan, GfxCache *fonts);
+
+ /**
+ * The memory handle of the currently active bitmap.
+ */
+ reg_t _bitmap;
+
+ /**
+ * The size of the x-dimension of the coordinate system
+ * used by the text renderer. Static since it was global in SSCI.
+ */
+ static int16 _scaledWidth;
+
+ /**
+ * The size of the y-dimension of the coordinate system
+ * used by the text renderer. Static since it was global in SSCI.
+ */
+ static int16 _scaledHeight;
+
+ /**
+ * The currently active font resource used to write text
+ * into the bitmap.
+ *
+ * @note SCI engine builds the font table directly
+ * inside of FontMgr; we use GfxFont instead.
+ */
+ GfxFont *_font;
+
+ /**
+ * Creates a plain font bitmap with a flat color
+ * background.
+ */
+ reg_t createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, TextAlign alignment, const int16 borderColor, bool dimmed, const bool doScaling);
+
+ /**
+ * Creates a font bitmap with a view background.
+ */
+ reg_t createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed);
+
+ inline int scaleUpWidth(int value) const {
+ const int scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ return (value * scriptWidth + _scaledWidth - 1) / _scaledWidth;
+ }
+
+ inline int scaleUpHeight(int value) const {
+ const int scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ return (value * scriptHeight + _scaledHeight - 1) / _scaledHeight;
+ }
+
+ /**
+ * Draws the text to the bitmap.
+ */
+ void drawTextBox();
+
+ /**
+ * Draws the given text to the bitmap.
+ *
+ * @note The original engine holds a reference to a
+ * shared string which lets the text be updated from
+ * outside of the font manager. Instead, we give this
+ * extra signature to send the text to draw.
+ *
+ * TODO: Use shared string instead?
+ */
+ void drawTextBox(const Common::String &text);
+
+ /**
+ * Erases the given rect by filling with the background
+ * color.
+ */
+ void erase(const Common::Rect &rect, const bool doScaling);
+
+ void invertRect(const reg_t bitmap, const int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling);
+
+ /**
+ * Sets the font to be used for rendering and
+ * calculation of text dimensions.
+ */
+ void setFont(const GuiResourceId fontId);
+
+ /**
+ * Gets the width of a character.
+ */
+ uint16 getCharWidth(const char charIndex, const bool doScaling) const;
+
+ /**
+ * Retrieves the width and height of a block of text.
+ */
+ Common::Rect getTextSize(const Common::String &text, const int16 maxWidth, bool doScaling);
+
+ /**
+ * Gets the pixel width of a substring of the currently
+ * loaded text, with scaling.
+ */
+ int16 getTextWidth(const Common::String &text, const uint index, const uint length);
+
+ /**
+ * Retrieves the width of a line of text.
+ */
+ int16 getStringWidth(const Common::String &text);
+
+ /**
+ * Gets the number of characters of `text`, starting
+ * from `index`, that can be safely rendered into
+ * `textRect`.
+ */
+ int16 getTextCount(const Common::String &text, const uint index, const Common::Rect &textRect, const bool doScaling);
+
+ /**
+ * Gets the number of characters of `text`, starting
+ * from `index`, that can be safely rendered into
+ * `textRect` using the given font.
+ */
+ int16 getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling);
+
+ /**
+ * Scroll up/down one line. `numLines` is the number of the lines in the
+ * textarea, and `textLine` contains the text to draw as the newly
+ * visible line. Originally FontMgr::DrawOneLine and FontMgr::UpOneLine.
+ */
+ void scrollLine(const Common::String &textLine, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
new file mode 100644
index 0000000000..dd841f5b4c
--- /dev/null
+++ b/engines/sci/graphics/video32.cpp
@@ -0,0 +1,415 @@
+/* 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.
+ *
+ */
+
+#include "audio/mixer.h"
+#include "common/config-manager.h"
+#include "sci/console.h"
+#include "sci/event.h"
+#include "sci/graphics/cursor.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/text32.h"
+#include "sci/graphics/video32.h"
+#include "sci/sci.h"
+#include "video/coktel_decoder.h"
+
+namespace Sci {
+
+#pragma mark VMDPlayer
+
+VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) :
+ _segMan(segMan),
+ _eventMan(eventMan),
+ _decoder(new Video::AdvancedVMDDecoder(Audio::Mixer::kSFXSoundType)),
+
+ _isOpen(false),
+ _isInitialized(false),
+ _yieldInterval(0),
+ _lastYieldedFrameNo(0),
+
+ _plane(nullptr),
+ _screenItem(nullptr),
+ _planeIsOwned(true),
+ _priority(0),
+ _doublePixels(false),
+ _stretchVertical(false),
+ _blackLines(false),
+ _leaveScreenBlack(false),
+ _leaveLastFrame(false),
+
+ _blackoutPlane(nullptr),
+
+ _startColor(0),
+ _endColor(255),
+ _blackPalette(false),
+
+ _boostPercent(100),
+ _boostStartColor(0),
+ _boostEndColor(255),
+
+ _showCursor(false) {}
+
+VMDPlayer::~VMDPlayer() {
+ close();
+ delete _decoder;
+}
+
+#pragma mark -
+#pragma mark VMDPlayer - Playback
+
+VMDPlayer::IOStatus VMDPlayer::open(const Common::String &fileName, const OpenFlags flags) {
+ if (_isOpen) {
+ error("Attempted to play %s, but another VMD was loaded", fileName.c_str());
+ }
+
+ if (_decoder->loadFile(fileName)) {
+ if (flags & kOpenFlagMute) {
+ _decoder->setVolume(0);
+ }
+ _isOpen = true;
+ return kIOSuccess;
+ } else {
+ return kIOError;
+ }
+}
+
+void VMDPlayer::init(const int16 x, const int16 y, const PlayFlags flags, const int16 boostPercent, const int16 boostStartColor, const int16 boostEndColor) {
+ _x = getSciVersion() >= SCI_VERSION_3 ? x : (x & ~1);
+ _y = y;
+ _doublePixels = flags & kPlayFlagDoublePixels;
+ _blackLines = ConfMan.getBool("enable_black_lined_video") && (flags & kPlayFlagBlackLines);
+ _boostPercent = 100 + (flags & kPlayFlagBoost ? boostPercent : 0);
+ _boostStartColor = CLIP<int16>(boostStartColor, 0, 255);
+ _boostEndColor = CLIP<int16>(boostEndColor, 0, 255);
+ _leaveScreenBlack = flags & kPlayFlagLeaveScreenBlack;
+ _leaveLastFrame = flags & kPlayFlagLeaveLastFrame;
+ _blackPalette = flags & kPlayFlagBlackPalette;
+ _stretchVertical = flags & kPlayFlagStretchVertical;
+}
+
+VMDPlayer::IOStatus VMDPlayer::close() {
+ if (!_isOpen) {
+ return kIOSuccess;
+ }
+
+ _decoder->close();
+ _isOpen = false;
+ _isInitialized = false;
+
+ if (!_planeIsOwned && _screenItem != nullptr) {
+ g_sci->_gfxFrameout->deleteScreenItem(*_screenItem);
+ _screenItem = nullptr;
+ } else if (_plane != nullptr) {
+ g_sci->_gfxFrameout->deletePlane(*_plane);
+ _plane = nullptr;
+ }
+
+ if (!_leaveLastFrame && _leaveScreenBlack) {
+ // This call *actually* deletes the plane/screen item
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+
+ if (_blackoutPlane != nullptr) {
+ g_sci->_gfxFrameout->deletePlane(*_blackoutPlane);
+ _blackoutPlane = nullptr;
+ }
+
+ if (!_leaveLastFrame && !_leaveScreenBlack) {
+ // This call *actually* deletes the blackout plane
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+
+ if (!_showCursor) {
+ g_sci->_gfxCursor->kernelShow();
+ }
+
+ _lastYieldedFrameNo = 0;
+ _planeIsOwned = true;
+ _priority = 0;
+ return kIOSuccess;
+}
+
+VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval) {
+ assert(lastFrameNo >= -1);
+
+ const int32 maxFrameNo = (int32)(_decoder->getFrameCount() - 1);
+
+ if ((flags & kEventFlagToFrame) && lastFrameNo > 0) {
+ _decoder->setEndFrame(MIN((int32)lastFrameNo, maxFrameNo));
+ } else {
+ _decoder->setEndFrame(maxFrameNo);
+ }
+
+ if (flags & kEventFlagYieldToVM) {
+ _yieldInterval = 3;
+ if (yieldInterval == -1 && !(flags & kEventFlagToFrame)) {
+ _yieldInterval = lastFrameNo;
+ } else if (yieldInterval != -1) {
+ _yieldInterval = MIN((int32)yieldInterval, maxFrameNo);
+ }
+ } else {
+ _yieldInterval = maxFrameNo;
+ }
+
+ return playUntilEvent(flags);
+}
+
+VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
+ // Flushing all the keyboard and mouse events out of the event manager to
+ // avoid letting any events queued from before the video started from
+ // accidentally activating an event callback
+ for (;;) {
+ const SciEvent event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_MOUSE_PRESS | SCI_EVENT_MOUSE_RELEASE | SCI_EVENT_QUIT);
+ if (event.type == SCI_EVENT_NONE) {
+ break;
+ } else if (event.type == SCI_EVENT_QUIT) {
+ return kEventFlagEnd;
+ }
+ }
+
+ _decoder->pauseVideo(false);
+
+ if (flags & kEventFlagReverse) {
+ // NOTE: This flag may not work properly since SSCI does not care
+ // if a video has audio, but the VMD decoder does.
+ const bool success = _decoder->setReverse(true);
+ assert(success);
+ _decoder->setVolume(0);
+ }
+
+ if (!_isInitialized) {
+ _isInitialized = true;
+
+ if (!_showCursor) {
+ g_sci->_gfxCursor->kernelHide();
+ }
+
+ Common::Rect vmdRect(_x,
+ _y,
+ _x + _decoder->getWidth(),
+ _y + _decoder->getHeight());
+ ScaleInfo vmdScaleInfo;
+
+ if (!_blackoutRect.isEmpty() && _planeIsOwned) {
+ _blackoutPlane = new Plane(_blackoutRect);
+ g_sci->_gfxFrameout->addPlane(*_blackoutPlane);
+ }
+
+ if (_doublePixels) {
+ vmdScaleInfo.x = 256;
+ vmdScaleInfo.y = 256;
+ vmdScaleInfo.signal = kScaleSignalDoScaling32;
+ vmdRect.right += vmdRect.width();
+ vmdRect.bottom += vmdRect.height();
+ } else if (_stretchVertical) {
+ vmdScaleInfo.y = 256;
+ vmdScaleInfo.signal = kScaleSignalDoScaling32;
+ vmdRect.bottom += vmdRect.height();
+ }
+
+ 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;
+
+ BitmapResource vmdBitmap(_segMan, vmdRect.width(), vmdRect.height(), 255, 0, 0, screenWidth, screenHeight, 0, false);
+
+ if (screenWidth != scriptWidth || screenHeight != scriptHeight) {
+ mulru(vmdRect, Ratio(scriptWidth, screenWidth), Ratio(scriptHeight, screenHeight), 1);
+ }
+
+ CelInfo32 vmdCelInfo;
+ vmdCelInfo.bitmap = vmdBitmap.getObject();
+ _decoder->setSurfaceMemory(vmdBitmap.getPixels(), vmdBitmap.getWidth(), vmdBitmap.getHeight(), 1);
+
+ if (_planeIsOwned) {
+ _x = 0;
+ _y = 0;
+ _plane = new Plane(vmdRect, kPlanePicColored);
+ if (_priority) {
+ _plane->_priority = _priority;
+ }
+ g_sci->_gfxFrameout->addPlane(*_plane);
+ _screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(), vmdScaleInfo);
+ } else {
+ _screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(_x, _y), vmdScaleInfo);
+ if (_priority) {
+ _screenItem->_priority = _priority;
+ }
+ }
+
+ if (_blackLines) {
+ _screenItem->_drawBlackLines = true;
+ }
+
+ // NOTE: There was code for positioning the screen item using insetRect
+ // here, but none of the game scripts seem to use this functionality.
+
+ g_sci->_gfxFrameout->addScreenItem(*_screenItem);
+
+ _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();
+ }
+
+ const int currentFrameNo = _decoder->getCurFrame();
+
+ if (_yieldInterval > 0 &&
+ currentFrameNo != _lastYieldedFrameNo &&
+ (currentFrameNo % _yieldInterval) == 0
+ ) {
+ _lastYieldedFrameNo = currentFrameNo;
+ stopFlag = kEventFlagYieldToVM;
+ break;
+ }
+
+ 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;
+ if (getSciVersion() < SCI_VERSION_3) {
+ while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)),
+ event.type != SCI_EVENT_NONE) {
+ if (event.character == SCI_KEY_ESC) {
+ stop = true;
+ break;
+ }
+ }
+ } else {
+ stop = (event.character == SCI_KEY_ESC);
+ }
+
+ 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;
+ }
+ }
+
+ _decoder->pauseVideo(true);
+ return stopFlag;
+}
+
+#pragma mark -
+#pragma mark VMDPlayer - Rendering
+
+void VMDPlayer::renderFrame() const {
+ // This writes directly to the CelObjMem we already created,
+ // so no need to take its return value
+ _decoder->decodeNextFrame();
+
+ // NOTE: Normally this would write a hunk palette at the end of the
+ // video bitmap that CelObjMem would read out and submit, but instead
+ // we are just submitting it directly here because the decoder exposes
+ // this information a little bit differently than the one in SSCI
+ const bool dirtyPalette = _decoder->hasDirtyPalette();
+ if (dirtyPalette) {
+ Palette palette;
+ palette.timestamp = g_sci->getTickCount();
+ for (uint16 i = 0; i < _startColor; ++i) {
+ palette.colors[i].used = false;
+ }
+ for (uint16 i = _endColor; i < 256; ++i) {
+ palette.colors[i].used = false;
+ }
+ if (_blackPalette) {
+ for (uint16 i = _startColor; i <= _endColor; ++i) {
+ palette.colors[i].r = palette.colors[i].g = palette.colors[i].b = 0;
+ palette.colors[i].used = true;
+ }
+ } else {
+ fillPalette(palette);
+ }
+
+ g_sci->_gfxPalette32->submit(palette);
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+
+ if (_blackPalette) {
+ fillPalette(palette);
+ g_sci->_gfxPalette32->submit(palette);
+ g_sci->_gfxPalette32->updateForFrame();
+ g_sci->_gfxPalette32->updateHardware();
+ }
+ } else {
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->getSciDebugger()->onFrame();
+ g_sci->_gfxFrameout->frameOut(true);
+ g_sci->_gfxFrameout->throttle();
+ }
+}
+
+void VMDPlayer::fillPalette(Palette &palette) const {
+ const byte *vmdPalette = _decoder->getPalette() + _startColor * 3;
+ for (uint16 i = _startColor; i <= _endColor; ++i) {
+ int16 r = *vmdPalette++;
+ int16 g = *vmdPalette++;
+ int16 b = *vmdPalette++;
+
+ if (_boostPercent != 100 && i >= _boostStartColor && i <= _boostEndColor) {
+ r = CLIP<int16>(r * _boostPercent / 100, 0, 255);
+ g = CLIP<int16>(g * _boostPercent / 100, 0, 255);
+ b = CLIP<int16>(b * _boostPercent / 100, 0, 255);
+ }
+
+ palette.colors[i].r = r;
+ palette.colors[i].g = g;
+ palette.colors[i].b = b;
+ palette.colors[i].used = true;
+ }
+}
+
+#pragma mark -
+#pragma mark VMDPlayer - Palette
+
+void VMDPlayer::restrictPalette(const uint8 startColor, const int16 endColor) {
+ _startColor = startColor;
+ // At least GK2 sends 256 as the end color, which is wrong,
+ // but works in the original engine as the storage size is 4 bytes
+ // and used values are clamped to 0-255
+ _endColor = MIN((int16)255, endColor);
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h
new file mode 100644
index 0000000000..7033f7c647
--- /dev/null
+++ b/engines/sci/graphics/video32.h
@@ -0,0 +1,312 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_VIDEO32_H
+#define SCI_GRAPHICS_VIDEO32_H
+
+namespace Video { class AdvancedVMDDecoder; }
+namespace Sci {
+class Plane;
+class ScreenItem;
+class SegManager;
+
+#pragma mark VMDPlayer
+
+/**
+ * VMDPlayer is used to play VMD videos.
+ */
+class VMDPlayer {
+public:
+ enum OpenFlags {
+ kOpenFlagNone = 0,
+ kOpenFlagMute = 1
+ };
+
+ enum IOStatus {
+ kIOSuccess = 0,
+ kIOError = 0xFFFF
+ };
+
+ enum PlayFlags {
+ kPlayFlagNone = 0,
+ kPlayFlagDoublePixels = 1,
+ kPlayFlagBlackLines = 4,
+ kPlayFlagBoost = 0x10,
+ kPlayFlagLeaveScreenBlack = 0x20,
+ kPlayFlagLeaveLastFrame = 0x40,
+ kPlayFlagBlackPalette = 0x80,
+ kPlayFlagStretchVertical = 0x100
+ };
+
+ enum EventFlags {
+ kEventFlagNone = 0,
+ kEventFlagEnd = 1,
+ kEventFlagEscapeKey = 2,
+ kEventFlagMouseDown = 4,
+ kEventFlagHotRectangle = 8,
+ kEventFlagToFrame = 0x10,
+ kEventFlagYieldToVM = 0x20,
+ kEventFlagReverse = 0x80
+ };
+
+ VMDPlayer(SegManager *segMan, EventManager *eventMan);
+ ~VMDPlayer();
+
+private:
+ SegManager *_segMan;
+ EventManager *_eventMan;
+ Video::AdvancedVMDDecoder *_decoder;
+
+#pragma mark -
+#pragma mark VMDPlayer - Playback
+public:
+ /**
+ * Opens a stream to a VMD resource.
+ */
+ IOStatus open(const Common::String &fileName, const OpenFlags flags);
+
+ /**
+ * Initializes the VMD rendering parameters for the
+ * current VMD. This must be called after `open`.
+ */
+ void init(const int16 x, const int16 y, const PlayFlags flags, const int16 boostPercent, const int16 boostStartColor, const int16 boostEndColor);
+
+ /**
+ * Stops playback and closes the currently open VMD stream.
+ */
+ IOStatus close();
+
+ // NOTE: Was WaitForEvent in SSCI
+ EventFlags kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval);
+
+private:
+ /**
+ * Whether or not a VMD stream has been opened with
+ * `open`.
+ */
+ bool _isOpen;
+
+ /**
+ * Whether or not a VMD player has been initialised
+ * with `init`.
+ */
+ bool _isInitialized;
+
+ /**
+ * For VMDs played with the `kEventFlagYieldToVM` flag,
+ * the number of frames that should be rendered until
+ * yielding back to the SCI VM.
+ */
+ int32 _yieldInterval;
+
+ /**
+ * For VMDs played with the `kEventFlagYieldToVM` flag,
+ * the last frame when control of the main thread was
+ * yielded back to the SCI VM.
+ */
+ int _lastYieldedFrameNo;
+
+ /**
+ * Plays the VMD until an event occurs (e.g. user
+ * presses escape, clicks, etc.).
+ */
+ EventFlags playUntilEvent(const EventFlags flags);
+
+#pragma mark -
+#pragma mark VMDPlayer - Rendering
+private:
+ /**
+ * The location of the VMD plane, in game script
+ * coordinates.
+ */
+ int16 _x, _y;
+
+ /**
+ * The plane where the VMD will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * The screen item representing the VMD surface.
+ */
+ ScreenItem *_screenItem;
+
+ // TODO: planeIsOwned and priority are used in SCI3+ only
+
+ /**
+ * If true, the plane for this VMD was set
+ * externally and is not owned by this VMDPlayer.
+ */
+ bool _planeIsOwned;
+
+ /**
+ * The screen priority of the video.
+ * @see ScreenItem::_priority
+ */
+ int _priority;
+
+ /**
+ * Whether or not the video should be pixel doubled.
+ */
+ bool _doublePixels;
+
+ /**
+ * Whether or not the video should be pixel doubled
+ * vertically only.
+ */
+ bool _stretchVertical;
+
+ /**
+ * Whether or not black lines should be rendered
+ * across the video.
+ */
+ bool _blackLines;
+
+ /**
+ * Whether or not the playback area of the VMD
+ * should be left black at the end of playback.
+ */
+ bool _leaveScreenBlack;
+
+ /**
+ * Whether or not the area of the VMD should be left
+ * displaying the final frame of the video.
+ */
+ bool _leaveLastFrame;
+
+ /**
+ * Renders a frame of video to the output bitmap.
+ */
+ void renderFrame() const;
+
+ /**
+ * Fills the given palette with RGB values from
+ * the VMD palette, applying brightness boost if
+ * it is enabled.
+ */
+ void fillPalette(Palette &palette) const;
+
+#pragma mark -
+#pragma mark VMDPlayer - Blackout
+public:
+ /**
+ * Sets the area of the screen that should be blacked out
+ * during VMD playback.
+ */
+ void setBlackoutArea(const Common::Rect &rect) { _blackoutRect = rect; }
+
+private:
+ /**
+ * The dimensions of the blackout plane.
+ */
+ Common::Rect _blackoutRect;
+
+ /**
+ * An optional plane that will be used to black out
+ * areas of the screen outside of the VMD surface.
+ */
+ Plane *_blackoutPlane;
+
+#pragma mark -
+#pragma mark VMDPlayer - Palette
+public:
+ /**
+ * Restricts use of the system palette by VMD playback to
+ * the given range of palette indexes.
+ */
+ void restrictPalette(const uint8 startColor, const int16 endColor);
+
+private:
+ /**
+ * The first color in the system palette that the VMD
+ * can write to.
+ */
+ uint8 _startColor;
+
+ /**
+ * The last color in the system palette that the VMD
+ * can write to.
+ */
+ uint8 _endColor;
+
+ /**
+ * If true, video frames are rendered after a blank
+ * palette is submitted to the palette manager,
+ * which is then restored after the video pixels
+ * have already been rendered.
+ */
+ bool _blackPalette;
+
+#pragma mark -
+#pragma mark VMDPlayer - Brightness boost
+private:
+ /**
+ * The amount of brightness boost for the video.
+ * Values above 100 increase brightness; values below
+ * 100 reduce it.
+ */
+ int16 _boostPercent;
+
+ /**
+ * The first color in the palette that should be
+ * brightness boosted.
+ */
+ uint8 _boostStartColor;
+
+ /**
+ * The last color in the palette that should be
+ * brightness boosted.
+ */
+ uint8 _boostEndColor;
+
+#pragma mark -
+#pragma mark VMDPlayer - Mouse cursor
+public:
+ /**
+ * Sets whether or not the mouse cursor should be drawn.
+ * This does not have any effect during playback, but can
+ * be used to prevent the mouse cursor from being shown
+ * again after the video has finished.
+ */
+ void setShowCursor(const bool shouldShow) { _showCursor = shouldShow; }
+
+private:
+ /**
+ * Whether or not the mouse cursor should be shown
+ * during playback.
+ */
+ bool _showCursor;
+};
+
+class Video32 {
+public:
+ Video32(SegManager *segMan, EventManager *eventMan) :
+ _VMDPlayer(segMan, eventMan) {}
+
+ VMDPlayer &getVMDPlayer() { return _VMDPlayer; }
+
+private:
+ VMDPlayer _VMDPlayer;
+};
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp
index da61ecf4c3..1939e66179 100644
--- a/engines/sci/graphics/view.cpp
+++ b/engines/sci/graphics/view.cpp
@@ -25,6 +25,7 @@
#include "sci/engine/state.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/palette.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/view.h"
@@ -356,7 +357,7 @@ void GfxView::initData(GuiResourceId resourceId) {
for (loopNo = 0; loopNo < _loopCount; loopNo++)
for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++)
_screen->adjustBackUpscaledCoordinates(_loop[loopNo].cel[celNo].scriptWidth, _loop[loopNo].cel[celNo].scriptHeight, _sci2ScaleRes);
- } else if (getSciVersion() == SCI_VERSION_2_1) {
+ } else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) {
for (loopNo = 0; loopNo < _loopCount; loopNo++)
for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++)
_coordAdjuster->fromDisplayToScript(_loop[loopNo].cel[celNo].scriptHeight, _loop[loopNo].cel[celNo].scriptWidth);
@@ -833,19 +834,6 @@ void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const
bitmap += (clipRect.top - rect.top) * celWidth + (clipRect.left - rect.left);
- // WORKAROUND: EcoQuest French and German draw the fish and anemone sprites
- // with priority 15 in scene 440. Afterwards, a dialog is shown on top of
- // these sprites with priority 15 as well. This is undefined behavior
- // actually, as the sprites and dialog share the same priority, so in our
- // implementation the sprites get drawn incorrectly on top of the dialog.
- // Perhaps this worked by mistake in SSCI because of subtle differences in
- // how sprites are drawn. We compensate for this by resetting the priority
- // of all sprites that have a priority of 15 in scene 440 to priority 14,
- // so that the speech bubble can be drawn correctly on top of them. Fixes
- // bug #3040625.
- if (g_sci->getGameId() == GID_ECOQUEST && g_sci->getEngineState()->currentRoomNumber() == 440 && priority == 15)
- priority = 14;
-
if (!_EGAmapping) {
for (y = 0; y < height; y++, bitmap += celWidth) {
for (x = 0; x < width; x++) {
@@ -855,12 +843,11 @@ void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const
const int y2 = clipRectTranslated.top + y;
if (!upscaledHires) {
if (priority >= _screen->getPriority(x2, y2)) {
- if (!_palette->isRemapped(palette->mapping[color])) {
- _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0);
- } else {
- byte remappedColor = _palette->remapColor(palette->mapping[color], _screen->getVisual(x2, y2));
- _screen->putPixel(x2, y2, drawMask, remappedColor, priority, 0);
- }
+ byte outputColor = palette->mapping[color];
+ // SCI16 remapping (QFG4 demo)
+ if (g_sci->_gfxRemap16 && g_sci->_gfxRemap16->isRemapped(outputColor))
+ outputColor = g_sci->_gfxRemap16->remapColor(outputColor, _screen->getVisual(x2, y2));
+ _screen->putPixel(x2, y2, drawMask, outputColor, priority, 0);
}
} else {
// UpscaledHires means view is hires and is supposed to
@@ -970,12 +957,11 @@ void GfxView::drawScaled(const Common::Rect &rect, const Common::Rect &clipRect,
const int x2 = clipRectTranslated.left + x;
const int y2 = clipRectTranslated.top + y;
if (color != clearKey && priority >= _screen->getPriority(x2, y2)) {
- if (!_palette->isRemapped(palette->mapping[color])) {
- _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0);
- } else {
- byte remappedColor = _palette->remapColor(palette->mapping[color], _screen->getVisual(x2, y2));
- _screen->putPixel(x2, y2, drawMask, remappedColor, priority, 0);
- }
+ byte outputColor = palette->mapping[color];
+ // SCI16 remapping (QFG4 demo)
+ if (g_sci->_gfxRemap16 && g_sci->_gfxRemap16->isRemapped(outputColor))
+ outputColor = g_sci->_gfxRemap16->remapColor(outputColor, _screen->getVisual(x2, y2));
+ _screen->putPixel(x2, y2, drawMask, outputColor, priority, 0);
}
}
}
@@ -989,13 +975,4 @@ void GfxView::adjustBackUpscaledCoordinates(int16 &y, int16 &x) {
_screen->adjustBackUpscaledCoordinates(y, x, _sci2ScaleRes);
}
-byte GfxView::getColorAtCoordinate(int16 loopNo, int16 celNo, int16 x, int16 y) {
- const CelInfo *celInfo = getCelInfo(loopNo, celNo);
- const byte *bitmap = getBitmap(loopNo, celNo);
- const int16 celWidth = celInfo->width;
-
- bitmap += (celWidth * y);
- return bitmap[x];
-}
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h
index d8803db208..96b48c0477 100644
--- a/engines/sci/graphics/view.h
+++ b/engines/sci/graphics/view.h
@@ -55,6 +55,7 @@ struct LoopInfo {
class GfxScreen;
class GfxPalette;
+class Resource;
/**
* View class, handles loading of view resources and drawing contained cels to screen
@@ -85,8 +86,6 @@ public:
void adjustToUpscaledCoordinates(int16 &y, int16 &x);
void adjustBackUpscaledCoordinates(int16 &y, int16 &x);
- byte getColorAtCoordinate(int16 loopNo, int16 celNo, int16 x, int16 y);
-
private:
void initData(GuiResourceId resourceId);
void unpackCel(int16 loopNo, int16 celNo, byte *outPtr, uint32 pixelCount);