aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/graphics
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/graphics')
-rw-r--r--engines/sci/graphics/celobj32.cpp257
-rw-r--r--engines/sci/graphics/celobj32.h34
-rw-r--r--engines/sci/graphics/compare.cpp20
-rw-r--r--engines/sci/graphics/controls32.cpp511
-rw-r--r--engines/sci/graphics/controls32.h390
-rw-r--r--engines/sci/graphics/cursor.h2
-rw-r--r--engines/sci/graphics/frameout.cpp584
-rw-r--r--engines/sci/graphics/frameout.h82
-rw-r--r--engines/sci/graphics/helpers.h8
-rw-r--r--engines/sci/graphics/paint.cpp44
-rw-r--r--engines/sci/graphics/paint.h39
-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.h10
-rw-r--r--engines/sci/graphics/palette32.cpp323
-rw-r--r--engines/sci/graphics/palette32.h171
-rw-r--r--engines/sci/graphics/plane32.cpp586
-rw-r--r--engines/sci/graphics/plane32.h166
-rw-r--r--engines/sci/graphics/remap.cpp297
-rw-r--r--engines/sci/graphics/remap.h107
-rw-r--r--engines/sci/graphics/remap32.cpp468
-rw-r--r--engines/sci/graphics/remap32.h405
-rw-r--r--engines/sci/graphics/screen_item32.cpp178
-rw-r--r--engines/sci/graphics/screen_item32.h64
-rw-r--r--engines/sci/graphics/text32.cpp150
-rw-r--r--engines/sci/graphics/text32.h63
-rw-r--r--engines/sci/graphics/video32.cpp415
-rw-r--r--engines/sci/graphics/video32.h312
-rw-r--r--engines/sci/graphics/view.h1
30 files changed, 4462 insertions, 1440 deletions
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index 693bc5f196..a3dcbbbab6 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -26,13 +26,12 @@
#include "sci/graphics/celobj32.h"
#include "sci/graphics/frameout.h"
#include "sci/graphics/palette32.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/remap.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/text32.h"
-#include "sci/graphics/view.h"
namespace Sci {
#pragma mark CelScaler
+
CelScaler *CelObj::_scaler = nullptr;
void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
@@ -84,9 +83,11 @@ const CelScalerTable *CelScaler::getScalerTable(const Ratio &scaleX, const Ratio
#pragma mark -
#pragma mark CelObj
+bool CelObj::_drawBlackLines = false;
void CelObj::init() {
CelObj::deinit();
+ _drawBlackLines = false;
_nextCacheId = 1;
_scaler = new CelScaler();
_cache = new CelCache;
@@ -120,6 +121,7 @@ struct SCALER_NoScale {
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),
@@ -166,6 +168,7 @@ struct SCALER_Scale {
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
@@ -175,34 +178,61 @@ struct SCALER_Scale {
// 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, the pixels that are
- // read from the source bitmap must all use the same pattern of
- // division. In other words, cels must follow the same scaling pattern
- // as if they were drawn starting at an even multiple of the scaling
- // ratio, even if they were not.
+ // 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);
- 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);
+ 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 {
- for (int16 x = targetRect.left; x < targetRect.right; ++x) {
- _valuesX[x] = table->valuesX[x] - unscaledX;
+ 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];
+ }
}
- }
- const int16 unscaledY = (scaledPosition.y / scaleY).toInt();
- for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
- _valuesY[y] = table->valuesY[y] - unscaledY;
+ for (int16 y = 0; y < targetRect.height(); ++y) {
+ _valuesY[targetRect.top + y] = table->valuesY[y];
+ }
}
}
@@ -321,6 +351,10 @@ public:
#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) {
@@ -328,29 +362,54 @@ struct MAPPER_NoMD {
}
}
};
+
+/**
+ * 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);
+ } 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`,
@@ -432,6 +491,8 @@ void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Re
}
}
}
+
+ _drawBlackLines = false;
}
void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) {
@@ -509,46 +570,42 @@ uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const {
void CelObj::submitPalette() const {
if (_hunkPaletteOffset) {
- Palette palette;
-
- byte *res = getResPointer();
- // NOTE: In SCI engine this uses HunkPalette::Init.
- // TODO: Use a better size value
- g_sci->_gfxPalette32->createFromData(res + _hunkPaletteOffset, 999999, &palette);
+ 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 = -1;
+ int oldestIndex = 0;
for (int i = 0, len = _cache->size(); i < len; ++i) {
CelCacheEntry &entry = (*_cache)[i];
- if (entry.celObj != nullptr) {
- if (entry.celObj->_info == celInfo) {
- entry.id = ++_nextCacheId;
- return i;
+ if (entry.celObj == nullptr) {
+ if (*nextInsertIndex == -1) {
+ *nextInsertIndex = i;
}
-
- if (oldestId > entry.id) {
- oldestId = entry.id;
- oldestIndex = i;
- }
- } else if (oldestIndex == -1) {
+ } else if (entry.celObj->_info == celInfo) {
+ entry.id = ++_nextCacheId;
+ return i;
+ } else if (oldestId > entry.id) {
+ oldestId = entry.id;
oldestIndex = i;
}
}
- // NOTE: Unlike the original SCI engine code, the out-param
- // here is only updated if there was not a cache hit.
- *nextInsertIndex = oldestIndex;
+ if (*nextInsertIndex == -1) {
+ *nextInsertIndex = oldestIndex;
+ }
+
return -1;
}
@@ -570,7 +627,7 @@ void CelObj::putCopyInCache(const int cacheIndex) const {
#pragma mark -
#pragma mark CelObj - Drawing
-template<typename MAPPER, typename SCALER>
+template<typename MAPPER, typename SCALER, bool DRAW_BLACK_LINES>
struct RENDERER {
MAPPER &_mapper;
SCALER &_scaler;
@@ -588,6 +645,12 @@ struct RENDERER {
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) {
@@ -604,7 +667,7 @@ void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common
MAPPER mapper;
SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width(), scaledPosition);
- RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor);
+ RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _transparentColor);
renderer.draw(target, targetRect, scaledPosition);
}
@@ -613,42 +676,45 @@ void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common
MAPPER mapper;
SCALER scaler(*this, targetRect, scaledPosition, scaleX, scaleY);
- RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor);
- renderer.draw(target, targetRect, scaledPosition);
-}
-
-void dummyFill(Buffer &target, const Common::Rect &targetRect) {
- target.fillRect(targetRect, 250);
+ 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 CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawHzFlip");
- dummyFill(target, targetRect);
+ 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 {
- debug("drawNoFlip");
- dummyFill(target, targetRect);
+ 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 {
- debug("drawUncompNoFlip");
- dummyFill(target, targetRect);
+ 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 {
- debug("drawUncompHzFlip");
- dummyFill(target, targetRect);
+ 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 {
- debug("scaleDraw");
- dummyFill(target, targetRect);
+ 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 {
- debug("scaleDrawUncomp");
- dummyFill(target, targetRect);
+ 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 {
@@ -668,17 +734,19 @@ void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect,
}
void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- if (_drawMirrored)
+ if (_drawMirrored) {
render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
- else
+ } 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)
+ if (_drawMirrored) {
render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
- else
+ } 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 {
@@ -713,14 +781,16 @@ void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &sca
}
void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- if (_drawMirrored)
+ if (_drawMirrored) {
render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
- else
+ } 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;
@@ -734,7 +804,11 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
int cacheIndex = searchCache(_info, &cacheInsertIndex);
if (cacheIndex != -1) {
CelCacheEntry &entry = (*_cache)[cacheIndex];
- *this = *dynamic_cast<CelObjView *>(entry.celObj);
+ 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;
}
@@ -759,8 +833,8 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
if (_scaledWidth == 0 || _scaledHeight == 0) {
byte sizeFlag = data[5];
if (sizeFlag == 0) {
- _scaledWidth = 320;
- _scaledHeight = 200;
+ _scaledWidth = kLowResX;
+ _scaledHeight = kLowResY;
} else if (sizeFlag == 1) {
_scaledWidth = 640;
_scaledHeight = 480;
@@ -834,8 +908,12 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
bool CelObjView::analyzeUncompressedForRemap() const {
byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
for (int i = 0; i < _width * _height; ++i) {
- byte pixel = pixels[i];
- if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) {
+ const byte pixel = pixels[i];
+ if (
+ pixel >= g_sci->_gfxRemap32->getStartColor() &&
+ pixel <= g_sci->_gfxRemap32->getEndColor() &&
+ pixel != _transparentColor
+ ) {
return true;
}
}
@@ -847,8 +925,12 @@ bool CelObjView::analyzeForRemap() const {
for (int y = 0; y < _height; y++) {
const byte *curRow = reader.getRow(y);
for (int x = 0; x < _width; x++) {
- byte pixel = curRow[x];
- if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) {
+ const byte pixel = curRow[x];
+ if (
+ pixel >= g_sci->_gfxRemap32->getStartColor() &&
+ pixel <= g_sci->_gfxRemap32->getEndColor() &&
+ pixel != _transparentColor
+ ) {
return true;
}
}
@@ -866,11 +948,16 @@ CelObjView *CelObjView::duplicate() const {
}
byte *CelObjView::getResPointer() const {
- return g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false)->data;
+ 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;
@@ -885,7 +972,11 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
int cacheIndex = searchCache(_info, &cacheInsertIndex);
if (cacheIndex != -1) {
CelCacheEntry &entry = (*_cache)[cacheIndex];
- *this = *dynamic_cast<CelObjPic *>(entry.celObj);
+ 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;
}
@@ -928,8 +1019,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
_scaledWidth = sizeFlag1;
_scaledHeight = sizeFlag2;
} else if (sizeFlag1 == 0) {
- _scaledWidth = 320;
- _scaledHeight = 200;
+ _scaledWidth = kLowResX;
+ _scaledHeight = kLowResY;
} else if (sizeFlag1 == 1) {
_scaledWidth = 640;
_scaledHeight = 480;
@@ -979,11 +1070,16 @@ CelObjPic *CelObjPic::duplicate() const {
}
byte *CelObjPic::getResPointer() const {
- return g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false)->data;
+ 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;
@@ -1013,6 +1109,7 @@ byte *CelObjMem::getResPointer() const {
#pragma mark -
#pragma mark CelObjColor
+
CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) {
_info.type = kCelTypeColor;
_info.color = color;
diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h
index 0bb4b03ae2..eb6ce3a3c9 100644
--- a/engines/sci/graphics/celobj32.h
+++ b/engines/sci/graphics/celobj32.h
@@ -31,6 +31,20 @@
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,
@@ -214,6 +228,18 @@ class ScreenItem;
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
@@ -474,7 +500,7 @@ private:
bool analyzeForRemap() const;
public:
- CelObjView(GuiResourceId viewId, int16 loopNo, int16 celNo);
+ CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo);
virtual ~CelObjView() override {};
using CelObj::draw;
@@ -525,7 +551,7 @@ public:
*/
int16 _priority;
- CelObjPic(GuiResourceId pictureId, int16 celNo);
+ CelObjPic(const GuiResourceId pictureId, const int16 celNo);
virtual ~CelObjPic() override {};
using CelObj::draw;
@@ -546,7 +572,7 @@ public:
*/
class CelObjMem : public CelObj {
public:
- CelObjMem(reg_t bitmap);
+ CelObjMem(const reg_t bitmap);
virtual ~CelObjMem() override {};
virtual CelObjMem *duplicate() const override;
@@ -562,7 +588,7 @@ public:
*/
class CelObjColor : public CelObj {
public:
- CelObjColor(uint8 color, int16 width, int16 height);
+ CelObjColor(const uint8 color, const int16 width, const int16 height);
virtual ~CelObjColor() override {};
using CelObj::draw;
diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp
index 729eeeaf81..130416ff60 100644
--- a/engines/sci/graphics/compare.cpp
+++ b/engines/sci/graphics/compare.cpp
@@ -162,12 +162,20 @@ reg_t GfxCompare::kernelCantBeHere32(const reg_t curObject, const reg_t listRefe
// 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(
- readSelectorValue(_segMan, curObject, SELECTOR(brLeft)),
- readSelectorValue(_segMan, curObject, SELECTOR(brTop)),
- readSelectorValue(_segMan, curObject, SELECTOR(brRight)),
- readSelectorValue(_segMan, curObject, SELECTOR(brBottom))
- );
+ 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));
diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp
index a877d8c276..f4e9d26df4 100644
--- a/engines/sci/graphics/controls32.cpp
+++ b/engines/sci/graphics/controls32.cpp
@@ -21,7 +21,8 @@
*/
#include "common/system.h"
-
+#include "common/translation.h"
+#include "gui/message.h"
#include "sci/sci.h"
#include "sci/console.h"
#include "sci/event.h"
@@ -41,7 +42,31 @@ GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *tex
_gfxCache(cache),
_gfxText32(text),
_overwriteMode(false),
- _nextCursorFlashTick(0) {}
+ _nextCursorFlashTick(0),
+ // SSCI used a memory handle for a ScrollWindow object
+ // as ID. We use a simple numeric handle instead.
+ _nextScrollWindowId(10000) {}
+
+GfxControls32::~GfxControls32() {
+ ScrollWindowMap::iterator it;
+ for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it)
+ delete it->_value;
+}
+
+#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;
+}
+
+#pragma mark -
+#pragma mark Text input control
reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
SegManager *segMan = _segMan;
@@ -90,7 +115,10 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
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));
+ sourcePlane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
+ if (sourcePlane == nullptr) {
+ error("Could not find plane %04x:%04x", PRINT_REG(planeObj));
+ }
}
editorPlaneRect.translate(sourcePlane->_gameRect.left, sourcePlane->_gameRect.top);
@@ -104,10 +132,7 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
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 {
- Common::String title = _segMan->getString(titleObject);
- int16 titleBackColor = readSelectorValue(_segMan, controlObject, SELECTOR(titleBack));
- int16 titleForeColor = readSelectorValue(_segMan, controlObject, SELECTOR(titleFore));
- editor.bitmap = _gfxText32->createTitledBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, title, titleForeColor, titleBackColor, titleFontId, true);
+ 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!");
}
}
@@ -169,7 +194,8 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
// Consume the event now that we know it is not one of the
// defocusing events above
- eventManager->getSciEvent(SCI_EVENT_ANY);
+ 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
@@ -287,8 +313,7 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
g_sci->_gfxFrameout->frameOut(true);
g_sci->getSciDebugger()->onFrame();
- g_sci->getEngineState()->speedThrottler(16);
- g_sci->getEngineState()->_throttleTrigger = true;
+ g_sci->_gfxFrameout->throttle();
}
g_sci->_gfxFrameout->deletePlane(*plane);
@@ -353,4 +378,470 @@ void GfxControls32::flashCursor(TextEditor &editor) {
_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);
+ }
+
+ return make_reg(0, result);
+}
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/controls32.h b/engines/sci/graphics/controls32.h
index 1bb7679ddd..460b0b5625 100644
--- a/engines/sci/graphics/controls32.h
+++ b/engines/sci/graphics/controls32.h
@@ -23,12 +23,19 @@
#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.
@@ -100,24 +107,403 @@ struct TextEditor {
};
/**
+ * 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
*/
class GfxControls32 {
public:
GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text);
-
- reg_t kernelEditText(const reg_t controlObject);
+ ~GfxControls32();
private:
SegManager *_segMan;
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.h b/engines/sci/graphics/cursor.h
index 8d125c45b3..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 {
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index 6454a1eb32..b5b320637f 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -42,15 +42,13 @@
#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/palette32.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/remap.h"
-#include "sci/graphics/text32.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"
@@ -69,15 +67,16 @@ static int16 unknownCDefaults[2][16] = {
/* 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, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) :
+GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette) :
_isHiRes(false),
- _cache(cache),
_palette(palette),
_resMan(resMan),
_screen(screen),
_segMan(segMan),
- _paint32(paint32),
+ _benchmarkingFinished(false),
+ _throttleFrameOut(true),
_showStyles(nullptr),
+ _throttleState(0),
// TODO: Stop using _gfxScreen
_currentBuffer(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr),
_remapOccurred(false),
@@ -116,6 +115,7 @@ GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAd
}
switch (g_sci->getGameId()) {
+ case GID_HOYLE5:
case GID_GK2:
case GID_LIGHTHOUSE:
case GID_LSL7:
@@ -255,9 +255,87 @@ void GfxFrameout::syncWithScripts(bool addElements) {
}
#pragma mark -
+#pragma mark Benchmarking
+
+bool GfxFrameout::checkForFred(const reg_t object) {
+ const int16 viewId = readSelectorValue(_segMan, object, SELECTOR(view));
+ const SciGameId gameId = g_sci->getGameId();
+
+ if (gameId == GID_QFG4 && viewId == 9999) {
+ return true;
+ }
+
+ if (gameId != GID_QFG4 && viewId == -556) {
+ return true;
+ }
+
+ if (Common::String(_segMan->getObjectName(object)) == "fred") {
+ return true;
+ }
+
+ return false;
+}
+
+#pragma mark -
#pragma mark Screen items
+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::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));
+// }
+
+ 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);
+}
+
+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();
+ }
+}
+
+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::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;
+ }
+
const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
_segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted);
@@ -297,6 +375,15 @@ void GfxFrameout::kernelUpdateScreenItem(const reg_t object) {
}
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;
+ }
+
_segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted);
const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
@@ -310,14 +397,7 @@ void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
return;
}
- if (screenItem->_created == 0) {
- screenItem->_created = 0;
- screenItem->_updated = 0;
- screenItem->_deleted = getScreenCount();
- } else {
- plane->_screenItemList.erase(screenItem);
- plane->_screenItemList.pack();
- }
+ deleteScreenItem(*screenItem, *plane);
}
#pragma mark -
@@ -446,7 +526,7 @@ void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pi
#pragma mark -
#pragma mark Rendering
-void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) {
+void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) {
// TODO: Robot
// if (_robot != nullptr) {
// _robot.doRobot();
@@ -464,7 +544,7 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect)
remapMarkRedraw();
}
- calcLists(screenItemLists, eraseLists, rect);
+ calcLists(screenItemLists, eraseLists, eraseRect);
for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
list->sort();
@@ -493,7 +573,7 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect)
// _robot->frameAlmostVisible();
// }
- _palette->updateHardware();
+ _palette->updateHardware(!shouldShowBits);
if (shouldShowBits) {
showBits();
@@ -507,130 +587,221 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect)
// }
}
-// Determine 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.)
+/**
+ * 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 count = 0;
+ int splitCount = 0;
if (r.top < other.top) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.bottom = other.top;
r.top = other.top;
}
if (r.bottom > other.bottom) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.top = other.bottom;
r.bottom = other.bottom;
}
if (r.left < other.left) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.right = other.left;
r.left = other.left;
}
if (r.right > other.right) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.left = other.right;
}
- return count;
+ return splitCount;
}
-void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) {
- RectList rectlist;
- Common::Rect outRects[4];
+/**
+ * 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 {
+ ++splitCount;
+ }
+ }
+ if (lowerBottom != lowerMinBottom) {
+ Common::Rect &lowerRect = outRects[splitCount];
+ lowerRect.left = lowerLeft;
+ lowerRect.top = lowerMinBottom;
+ lowerRect.right = lowerRight;
+ lowerRect.bottom = lowerBottom;
+
+ // Merge lower rect into middle rect if possible
+ if (lowerRect.left == middleRect.left && lowerRect.right == middleRect.right) {
+ middleRect.bottom = lowerRect.bottom;
+ } else {
+ ++splitCount;
+ }
+ }
+
+ assert(splitCount <= 2);
+ return splitCount;
+}
+
+// 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 addedToRectList = false;
- int planeCount = _planes.size();
+ bool addedToEraseList = false;
bool foundTransparentPlane = false;
- if (!calcRect.isEmpty()) {
- addedToRectList = true;
- rectlist.add(calcRect);
+ if (!eraseRect.isEmpty()) {
+ addedToEraseList = true;
+ eraseList.add(eraseRect);
}
- for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) {
- Plane *outerPlane = _planes[outerPlaneIndex];
+ 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);
+ // NOTE: SSCI only ever checks for kPlaneTypeTransparent here, even
+ // though kPlaneTypeTransparentPicture is also a transparent plane
if (outerPlane->_type == kPlaneTypeTransparent) {
foundTransparentPlane = true;
}
- Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object);
-
if (outerPlane->_deleted) {
- if (visiblePlane != nullptr) {
- if (!visiblePlane->_screenRect.isEmpty()) {
- addedToRectList = true;
- rectlist.add(visiblePlane->_screenRect);
- }
+ if (visiblePlane != nullptr && !visiblePlane->_screenRect.isEmpty()) {
+ eraseList.add(visiblePlane->_screenRect);
+ addedToEraseList = true;
}
++deletedPlaneCount;
- } else if (visiblePlane != nullptr) {
- if (outerPlane->_updated) {
- --outerPlane->_updated;
-
- int splitcount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects);
- if (splitcount) {
- if (splitcount == -1) {
- if (!visiblePlane->_screenRect.isEmpty()) {
- rectlist.add(visiblePlane->_screenRect);
- }
+ } 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) {
- rectlist.add(outRects[i]);
+ for (int i = 0; i < splitCount; ++i) {
+ eraseList.add(outRects[i]);
}
}
-
- addedToRectList = true;
+ addedToEraseList = true;
}
+ }
- if (!outerPlane->_redrawAllCount) {
- int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects);
- if (splitCount) {
- for (int i = 0; i < splitCount; ++i) {
- rectlist.add(outRects[i]);
- }
- addedToRectList = 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;
}
}
}
- if (addedToRectList) {
- for (RectList::iterator rect = rectlist.begin(); rect != rectlist.end(); ++rect) {
- for (int innerPlaneIndex = _planes.size() - 1; innerPlaneIndex >= 0; --innerPlaneIndex) {
- Plane *innerPlane = _planes[innerPlaneIndex];
-
- if (!innerPlane->_deleted && innerPlane->_type != kPlaneTypeTransparent && innerPlane->_screenRect.intersects(**rect)) {
- if (innerPlane->_redrawAllCount == 0) {
- eraseLists[innerPlaneIndex].add(innerPlane->_screenRect.findIntersectingRect(**rect));
+ 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));
}
- int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects);
+ const int splitCount = splitRects(rect, innerPlane._screenRect, outRects);
for (int i = 0; i < splitCount; ++i) {
- rectlist.add(outRects[i]);
+ eraseList.add(outRects[i]);
}
- rectlist.erase(rect);
+ eraseList.erase_at(rectIndex);
break;
}
}
}
- rectlist.pack();
+ eraseList.pack();
}
}
@@ -642,9 +813,9 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL
if (plane->_deleted) {
--plane->_deleted;
if (plane->_deleted <= 0) {
- PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
- if (visiblePlaneIt != _visiblePlanes.end()) {
- _visiblePlanes.erase(visiblePlaneIt);
+ const int visiblePlaneIndex = _visiblePlanes.findIndexByObject(plane->_object);
+ if (visiblePlaneIndex != -1) {
+ _visiblePlanes.remove_at(visiblePlaneIndex);
}
_planes.remove_at(planeIndex);
@@ -659,103 +830,114 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL
}
}
+ // Some planes may have been deleted, so re-retrieve count
planeCount = _planes.size();
- for (int outerIndex = 0; outerIndex < planeCount; ++outerIndex) {
- // "outer" just refers to the outer loop
- Plane *outerPlane = _planes[outerIndex];
- if (outerPlane->_priorityChanged) {
- --outerPlane->_priorityChanged;
- Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane->_object);
+ 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;
+ }
- rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
+ eraseList.add(outerPlane._screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
- for (int innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) {
+ for (int innerIndex = (int)planeCount - 1; innerIndex >= 0; --innerIndex) {
// "inner" just refers to the inner loop
- Plane *innerPlane = _planes[innerIndex];
- Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane->_object);
-
- int rectCount = rectlist.size();
- for (int rectIndex = 0; rectIndex < rectCount; ++rectIndex) {
- int splitCount = splitRects(*rectlist[rectIndex], _planes[innerIndex]->_screenRect, outRects);
+ 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(*rectlist[rectIndex]);
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) {
+ if (outerPlane._priority <= innerPlane._priority) {
+ eraseLists[innerIndex].add(*eraseList[rectIndex]);
} else {
- eraseLists[outerIndex].add(*rectlist[rectIndex]);
+ eraseLists[outerIndex].add(*eraseList[rectIndex]);
}
}
}
- rectlist.erase_at(rectIndex);
+ eraseList.erase_at(rectIndex);
} else if (splitCount != -1) {
for (int i = 0; i < splitCount; ++i) {
- rectlist.add(outRects[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) {
- *rectlist[rectIndex] = outerPlane->_screenRect.findIntersectingRect(innerPlane->_screenRect);
- if (outerPlane->_priority <= innerPlane->_priority) {
- eraseLists[innerIndex].add(*rectlist[rectIndex]);
- }
- else {
- eraseLists[outerIndex].add(*rectlist[rectIndex]);
+ 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]);
}
}
}
- rectlist.erase_at(rectIndex);
+ eraseList.erase_at(rectIndex);
}
}
- rectlist.pack();
+ eraseList.pack();
}
}
}
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- Plane *plane = _planes[planeIndex];
- Plane *visiblePlane = nullptr;
+ for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ Plane &plane = *_planes[planeIndex];
+ Plane *visiblePlane = _visiblePlanes.findByObject(plane._object);
- PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
- if (visiblePlaneIt != _visiblePlanes.end()) {
- visiblePlane = *visiblePlaneIt;
- }
+ 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 (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));
+ plane.calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
}
+ } else {
+ plane.decrementScreenItemArrayCounts(visiblePlane, false);
+ }
- plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ 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->_moved) {
- assert(visiblePlaneIt != _visiblePlanes.end());
- **visiblePlaneIt = *plane;
- --plane->_moved;
+ if (plane._created) {
+ _visiblePlanes.add(new Plane(plane));
+ --plane._created;
+ } else if (plane._updated) {
+ *visiblePlane = plane;
+ --plane._updated;
}
}
+ // NOTE: SSCI only looks for kPlaneTypeTransparent, not
+ // kPlaneTypeTransparentPicture
if (foundTransparentPlane) {
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- for (int i = planeIndex + 1; i < planeCount; ++i) {
+ 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 = planeIndex - 1; i >= 0; --i) {
+ for (int i = (int)planeIndex - 1; i >= 0; --i) {
_planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]);
}
@@ -764,7 +946,7 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL
}
}
- for (int i = planeIndex + 1; i < planeCount; ++i) {
+ for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {
if (_planes[i]->_type == kPlaneTypeTransparent) {
_planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]);
}
@@ -778,17 +960,19 @@ void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) {
return;
}
- for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) {
- mergeToShowList(**it, _showList, _overdrawThreshold);
- _currentBuffer.fillRect(**it, plane._back);
+ 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) {
- for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) {
- DrawItem &drawItem = **it;
+ 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);
- ScreenItem &screenItem = *drawItem.screenItem;
+ 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;
@@ -797,36 +981,65 @@ void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) {
}
void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) {
- Common::Rect merged(drawRect);
-
- bool didDelete = true;
- RectList::size_type count = showList.size();
- while (didDelete && count) {
- didDelete = false;
-
- for (RectList::size_type i = 0; i < count; ++i) {
- Common::Rect existing = *showList[i];
- Common::Rect candidate;
- candidate.left = MIN(merged.left, existing.left);
- candidate.top = MIN(merged.top, existing.top);
- candidate.right = MAX(merged.right, existing.right);
- candidate.bottom = MAX(merged.bottom, existing.bottom);
-
- if (candidate.height() * candidate.width() - merged.width() * merged.height() - existing.width() * existing.height() <= overdrawThreshold) {
- merged = candidate;
- showList.erase_at(i);
- didDelete = true;
+ 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;
+ }
+ }
+ }
}
- }
- count = showList.pack();
+ if (didMerge) {
+ showList.pack();
+ }
+ }
}
- showList.add(merged);
+ mergeList.pack();
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ showList.add(*mergeList[i]);
+ }
}
void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) {
- Palette sourcePalette(*_palette->getNextPalette());
+ Palette sourcePalette(_palette->getNextPalette());
alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16();
@@ -835,8 +1048,6 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
_showList.add(rect);
showBits();
- Common::Rect calcRect(0, 0);
-
// NOTE: The original engine allocated these as static arrays of 100
// pointers to ScreenItemList / RectList
ScreenItemListList screenItemLists;
@@ -849,7 +1060,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
remapMarkRedraw();
}
- calcLists(screenItemLists, eraseLists, calcRect);
+ calcLists(screenItemLists, eraseLists);
for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
list->sort();
}
@@ -868,7 +1079,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
drawScreenItemList(screenItemLists[i]);
}
- Palette nextPalette(*_palette->getNextPalette());
+ Palette nextPalette(_palette->getNextPalette());
if (prevRoom < 1000) {
for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
@@ -879,6 +1090,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
}
} 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;
@@ -909,7 +1121,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
remapMarkRedraw();
}
- calcLists(screenItemLists, eraseLists, calcRect);
+ calcLists(screenItemLists, eraseLists);
for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
list->sort();
}
@@ -932,16 +1144,12 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
_palette->submit(nextPalette);
_palette->updateFFrame();
- _palette->updateHardware();
+ _palette->updateHardware(false);
showBits();
_frameNowVisible = true;
}
-// TODO: What does the bit masking for the show rects do,
-// and does it cause an off-by-one error in rect calculations
-// since SOL_Rect is BR inclusive and Common::Rect is BR
-// exclusive?
void GfxFrameout::showBits() {
for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
Common::Rect rounded(**rect);
@@ -987,7 +1195,7 @@ void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, co
if (styleRanges[paletteIndex] == style) {
int minDiff = 262140;
- int minDiffIndex;
+ int minDiffIndex = paletteIndex;
for (int i = 0; i < 236; ++i) {
if (styleRanges[i] != style) {
@@ -1007,7 +1215,7 @@ void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, co
if (style == 1 && styleRanges[paletteIndex] == 0) {
int minDiff = 262140;
- int minDiffIndex;
+ int minDiffIndex = paletteIndex;
for (int i = 0; i < 236; ++i) {
int r = palette2.colors[i].r;
@@ -1308,6 +1516,10 @@ void GfxFrameout::processShowStyles() {
}
}
+ if (g_engine->shouldQuit()) {
+ return;
+ }
+
if (doFrameOut) {
frameOut(true);
@@ -1315,9 +1527,7 @@ void GfxFrameout::processShowStyles() {
// fast, but the throttle value is arbitrary. Someone on
// real hardware probably needs to test what the actual
// speed of these transitions should be
- EngineState *state = g_sci->getEngineState();
- state->speedThrottler(33);
- state->_throttleTrigger = true;
+ throttle();
}
} while(continueProcessing && doFrameOut);
}
@@ -1391,6 +1601,24 @@ void GfxFrameout::kernelFrameOut(const bool shouldShowBits) {
frameOut(shouldShowBits);
}
+
+ throttle();
+}
+
+void GfxFrameout::throttle() {
+ if (_throttleFrameOut) {
+ uint8 throttleTime;
+ if (_throttleState == 2) {
+ throttleTime = 16;
+ _throttleState = 0;
+ } else {
+ throttleTime = 17;
+ ++_throttleState;
+ }
+
+ g_sci->getEngineState()->speedThrottler(throttleTime);
+ g_sci->getEngineState()->_throttleTrigger = true;
+ }
}
#pragma mark -
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index 8ed95a00de..99658ede6a 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -149,10 +149,7 @@ struct ShowStyleEntry {
typedef Common::Array<DrawList> ScreenItemListList;
typedef Common::Array<RectList> EraseListList;
-class GfxCache;
class GfxCoordAdjuster32;
-class GfxPaint32;
-class GfxPalette;
class GfxScreen;
/**
@@ -162,16 +159,14 @@ class GfxScreen;
class GfxFrameout {
private:
bool _isHiRes;
- GfxCache *_cache;
GfxCoordAdjuster32 *_coordAdjuster;
GfxPalette32 *_palette;
ResourceManager *_resMan;
GfxScreen *_screen;
SegManager *_segMan;
- GfxPaint32 *_paint32;
public:
- GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32);
+ GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette);
~GfxFrameout();
void clear();
@@ -179,12 +174,58 @@ public:
void run();
#pragma mark -
+#pragma mark Benchmarking
+private:
+ /**
+ * Optimization to avoid the more expensive object name
+ * comparision on every call to kAddScreenItem and
+ * kRemoveScreenItem.
+ */
+ bool _benchmarkingFinished;
+
+ /**
+ * 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 deleteScreenItem(ScreenItem *screenItem, const reg_t plane);
void remapMarkRedraw();
public:
+ /**
+ * Adds a screen item.
+ */
+ void addScreenItem(ScreenItem &screenItem) const;
+
+ /**
+ * Updates a screen item.
+ */
+ void updateScreenItem(ScreenItem &screenItem) const;
+
+ /**
+ * 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);
@@ -278,6 +319,12 @@ public:
#pragma mark Rendering
private:
/**
+ * State tracker to provide more accurate 60fps
+ * video throttling.
+ */
+ uint8 _throttleState;
+
+ /**
* TODO: Documentation
*/
int8 _styleRanges[256];
@@ -292,7 +339,9 @@ private:
Buffer _currentBuffer;
/**
- * TODO: Documentation
+ * 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;
@@ -350,8 +399,10 @@ private:
* 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 &calcRect);
+ void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect = Common::Rect());
/**
* Erases the areas in the given erase list from the
@@ -395,18 +446,25 @@ public:
*/
bool _palMorphIsOn;
- inline Buffer &getCurrentBuffer() {
+ 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.
+ * 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 &rect = Common::Rect());
+ void frameOut(const bool shouldShowBits, const Common::Rect &eraseRect = Common::Rect());
/**
* Modifies the raw pixel data for the next frame with
diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index 19dddd74b8..3fcc83c5e2 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -191,6 +191,12 @@ struct Buffer : public Graphics::Surface {
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),
@@ -231,7 +237,7 @@ struct Color {
return used == other.used && r == other.r && g == other.g && b == other.b;
}
inline bool operator!=(const Color &other) const {
- return !(*this == other);
+ return !operator==(other);
}
#endif
};
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/paint.h b/engines/sci/graphics/paint.h
deleted file mode 100644
index b2277131d5..0000000000
--- a/engines/sci/graphics/paint.h
+++ /dev/null
@@ -1,39 +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.
- *
- */
-
-#ifndef SCI_GRAPHICS_PAINT_H
-#define SCI_GRAPHICS_PAINT_H
-
-namespace Sci {
-
-class GfxPaint {
-public:
- GfxPaint();
- virtual ~GfxPaint();
-
- 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);
-};
-
-} // End of namespace Sci
-
-#endif
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..7773b9f4e1 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 != kDefaultSkipColor ? kDefaultSkipColor : 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.h b/engines/sci/graphics/palette.h
index 7335dc59d0..2178de8a91 100644
--- a/engines/sci/graphics/palette.h
+++ b/engines/sci/graphics/palette.h
@@ -51,7 +51,7 @@ public:
bool setAmiga();
void modifyAmigaPalette(byte *data);
void setEGA();
- virtual void set(Palette *sciPal, bool force, bool forceRealMerge = false);
+ void set(Palette *sciPal, bool force, bool forceRealMerge = false);
bool insert(Palette *newPalette, Palette *destPalette);
bool merge(Palette *pFrom, bool force, bool forceRealMerge);
uint16 matchColor(byte r, byte g, byte b);
@@ -63,11 +63,11 @@ public:
void drewPicture(GuiResourceId pictureId);
- virtual bool kernelSetFromResource(GuiResourceId resourceId, bool force);
+ bool kernelSetFromResource(GuiResourceId resourceId, bool force);
void kernelSetFlag(uint16 fromColor, uint16 toColor, uint16 flag);
void kernelUnsetFlag(uint16 fromColor, uint16 toColor, uint16 flag);
void kernelSetIntensity(uint16 fromColor, uint16 toColor, uint16 intensity, bool setPalette);
- virtual int16 kernelFindColor(uint16 r, uint16 g, uint16 b);
+ int16 kernelFindColor(uint16 r, uint16 g, uint16 b);
bool kernelAnimate(byte fromColor, byte toColor, int speed);
void kernelAnimateSet();
reg_t kernelSave();
@@ -81,7 +81,7 @@ public:
int16 kernelPalVaryGetCurrentStep();
int16 kernelPalVaryChangeTarget(GuiResourceId resourceId);
void kernelPalVaryChangeTicks(uint16 ticks);
- virtual void kernelPalVaryPause(bool pause);
+ void kernelPalVaryPause(bool pause);
void kernelPalVaryDeinit();
void palVaryUpdate();
void palVaryPrepareForTransition();
@@ -89,7 +89,7 @@ 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);
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index 6844011675..2a98c237b0 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -27,19 +27,109 @@
#include "sci/sci.h"
#include "sci/event.h"
#include "sci/resource.h"
+#include "sci/util.h"
#include "sci/graphics/palette32.h"
-#include "sci/graphics/remap.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/screen.h"
namespace Sci {
-GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen)
- : GfxPalette(resMan, screen),
+#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),
- _versionUpdated(false),
- _sourcePalette(_sysPalette),
- _nextPalette(_sysPalette),
+ _needsUpdate(false),
+ _currentPalette(),
+ _sourcePalette(),
+ _nextPalette(),
// Clut
_clutTable(nullptr),
// Palette varying
@@ -55,106 +145,91 @@ GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen)
// Palette cycling
_cyclers(),
_cycleMap() {
- _varyPercent = _varyTargetPercent;
- for (int i = 0, len = ARRAYSIZE(_fadeTable); i < len; ++i) {
- _fadeTable[i] = 100;
- }
- // NOTE: In SCI engine, the palette manager constructor loads
- // the default palette, but in ScummVM this initialisation
- // is performed by SciEngine::run; see r49523 for details
+ _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) {
- for (int i = 0, len = ARRAYSIZE(to->colors); i < len; ++i) {
+ // 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];
}
}
}
-const Palette *GfxPalette32::getNextPalette() const {
- return &_nextPalette;
+void GfxPalette32::submit(const Palette &palette) {
+ const Palette oldSourcePalette(_sourcePalette);
+ mergePaletteInternal(&_sourcePalette, &palette);
+
+ if (!_needsUpdate && _sourcePalette != oldSourcePalette) {
+ ++_version;
+ _needsUpdate = true;
+ }
}
-void GfxPalette32::submit(Palette &palette) {
- // TODO: The resource manager in SCI32 retains raw data of palettes from
- // the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and
- // the version number for submitted palettes is set in the raw palette
- // data in memory as an int at an offset
- // `rawData + *rawData[0x0a] * 2 + 31`. However, ScummVM does not retain
- // resource data like this, so this versioning code, while accurate to
- // the original engine, does not do much.
- // (Hopefully this was an optimisation mechanism in SCI engine and not a
- // clever thing to keep the same palette submitted many times from
- // overwriting other palette entries.)
- if (palette.timestamp == _version) {
+void GfxPalette32::submit(HunkPalette &hunkPalette) {
+ if (hunkPalette.getVersion() == _version) {
return;
}
- Palette oldSourcePalette(_sourcePalette);
+ const Palette oldSourcePalette(_sourcePalette);
+ const Palette palette = hunkPalette.toPalette();
mergePaletteInternal(&_sourcePalette, &palette);
- if (!_versionUpdated && _sourcePalette != oldSourcePalette) {
+ if (!_needsUpdate && oldSourcePalette != _sourcePalette) {
++_version;
- _versionUpdated = true;
+ _needsUpdate = true;
}
- // Technically this information is supposed to be persisted through a
- // HunkPalette object; right now it would just be lost once the temporary
- // palette was destroyed.
- palette.timestamp = _version;
+ hunkPalette.setVersion(_version);
}
-bool GfxPalette32::kernelSetFromResource(GuiResourceId resourceId, bool force) {
- // TODO: In SCI32, palettes that come from resources come in as
- // HunkPalette objects, not SOLPalette objects. The HunkPalettes
- // have some extra persistence stuff associated with them, such that
- // when they are passed to GfxPalette32::submit, they would get the
- // version number of GfxPalette32 assigned to them.
- Palette palette;
+bool GfxPalette32::loadPalette(const GuiResourceId resourceId) {
+ Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
- if (createPaletteFromResourceInternal(resourceId, &palette)) {
- submit(palette);
- return true;
+ if (!palResource) {
+ return false;
}
- return false;
+ HunkPalette palette(palResource->data);
+ submit(palette);
+ return true;
}
-// In SCI32 engine this method is SOLPalette::Match(Rgb24 *)
-// and is called as PaletteMgr.Current().Match(color)
-int16 GfxPalette32::kernelFindColor(uint16 r, uint16 g, uint16 b) {
- // SQ6 SCI32 engine takes the 16-bit r, g, b arguments from the
- // VM and puts them into al, ah, dl. For compatibility, make sure
- // to discard any high bits here too
- r = r & 0xFF;
- g = g & 0xFF;
- b = b & 0xFF;
+int16 GfxPalette32::matchColor(const uint8 r, const uint8 g, const uint8 b) {
int16 bestIndex = 0;
int bestDifference = 0xFFFFF;
int difference;
- // SQ6 DOS really does check only the first 236 entries
- for (int i = 0, channelDifference; i < 236; ++i) {
- difference = _sysPalette.colors[i].r - r;
+ for (int i = 0, channelDifference; i < g_sci->_gfxRemap32->getStartColor(); ++i) {
+ difference = _currentPalette.colors[i].r - r;
difference *= difference;
if (bestDifference <= difference) {
continue;
}
- channelDifference = _sysPalette.colors[i].g - g;
+ channelDifference = _currentPalette.colors[i].g - g;
difference += channelDifference * channelDifference;
if (bestDifference <= difference) {
continue;
}
- channelDifference = _sysPalette.colors[i].b - b;
+ channelDifference = _currentPalette.colors[i].b - b;
difference += channelDifference * channelDifference;
if (bestDifference <= difference) {
continue;
@@ -166,99 +241,56 @@ int16 GfxPalette32::kernelFindColor(uint16 r, uint16 g, uint16 b) {
return bestIndex;
}
-// TODO: set is overridden for the time being to send palettes coming from
-// various draw methods like GfxPicture::drawSci32Vga and GfxView::draw to
-// _nextPalette instead of _sysPalette. In the SCI32 engine, CelObj palettes
-// (which are stored as Hunk palettes) are submitted by GraphicsMgr::FrameOut
-// to PaletteMgr::Submit by way of calls to CelObj::SubmitPalette.
-// GfxPalette::set is very similar to GfxPalette32::submit, except that SCI32
-// does not do any fancy best-fit merging and so does not accept arguments
-// like `force` and `forceRealMerge`.
-void GfxPalette32::set(Palette *newPalette, bool force, bool forceRealMerge) {
- submit(*newPalette);
-}
-
-// In SCI32 engine this method is SOLPalette::Match(Rgb24 *, int, int *, int *)
-// and is used by Remap
-// TODO: Anything that calls GfxPalette::matchColor(int, int, int) is going to
-// match using an algorithm from SCI16 engine right now. This needs to be
-// corrected in the future so either nothing calls
-// GfxPalette::matchColor(int, int, int), or it is fixed to match the other
-// SCI32 algorithms.
-int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable) {
- int16 bestIndex = -1;
- int bestDifference = 0xFFFFF;
- int difference = defaultDifference;
-
- // SQ6 DOS really does check only the first 236 entries
- for (int i = 0, channelDifference; i < 236; ++i) {
- if (matchTable[i] == 0) {
- continue;
- }
-
- difference = _sysPalette.colors[i].r - r;
- difference *= difference;
- if (bestDifference <= difference) {
- continue;
- }
- channelDifference = _sysPalette.colors[i].g - g;
- difference += channelDifference * channelDifference;
- if (bestDifference <= difference) {
- continue;
- }
- channelDifference = _sysPalette.colors[i].b - b;
- difference += channelDifference * channelDifference;
- if (bestDifference <= difference) {
- continue;
- }
- bestDifference = difference;
- bestIndex = i;
- }
-
- // NOTE: This value is only valid if the last index to
- // perform a difference calculation was the best index
- lastCalculatedDifference = difference;
- return bestIndex;
-}
-
bool GfxPalette32::updateForFrame() {
applyAll();
- _versionUpdated = false;
- return g_sci->_gfxRemap32->remapAllTables(_nextPalette != _sysPalette);
+ _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];
}
- _versionUpdated = false;
- g_sci->_gfxRemap32->remapAllTables(_nextPalette != _sysPalette);
+ _needsUpdate = false;
+ g_sci->_gfxRemap32->remapAllTables(_nextPalette != _currentPalette);
}
-void GfxPalette32::updateHardware() {
- if (_sysPalette == _nextPalette) {
+void GfxPalette32::updateHardware(const bool updateScreen) {
+ if (_currentPalette == _nextPalette) {
return;
}
byte bpal[3 * 256];
- for (int i = 0; i < ARRAYSIZE(_sysPalette.colors); ++i) {
- _sysPalette.colors[i] = _nextPalette.colors[i];
+ 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
- if (_sysPalette.colors[i].used) {
- bpal[i * 3 ] = _sysPalette.colors[i].r;
- bpal[i * 3 + 1] = _sysPalette.colors[i].g;
- bpal[i * 3 + 2] = _sysPalette.colors[i].b;
- }
+
+ // 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);
- g_sci->getEventManager()->updateScreen();
+ if (updateScreen) {
+ g_sci->getEventManager()->updateScreen();
+ }
}
void GfxPalette32::applyAll() {
@@ -270,6 +302,7 @@ void GfxPalette32::applyAll() {
#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.
@@ -319,27 +352,20 @@ void GfxPalette32::unloadClut() {
delete[] _clutTable;
_clutTable = nullptr;
}
+#endif
#pragma mark -
#pragma mark Varying
-inline bool GfxPalette32::createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const {
- Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, paletteId), false);
+inline Palette GfxPalette32::getPaletteFromResourceInternal(const GuiResourceId resourceId) const {
+ Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
if (!palResource) {
- return false;
+ error("Could not load vary palette %d", resourceId);
}
- createFromData(palResource->data, palResource->size, out);
- return true;
-}
-
-inline Palette GfxPalette32::getPaletteFromResourceInternal(const GuiResourceId paletteId) const {
- Palette palette;
- if (!createPaletteFromResourceInternal(paletteId, &palette)) {
- error("Could not load vary target %d", paletteId);
- }
- return palette;
+ HunkPalette rawPalette(palResource->data);
+ return rawPalette.toPalette();
}
inline void GfxPalette32::setVaryTimeInternal(const int16 percent, const int time) {
@@ -363,8 +389,6 @@ inline void GfxPalette32::setVaryTimeInternal(const int16 percent, const int tim
}
}
-// TODO: This gets called *a lot* in at least the first scene
-// of SQ6. Optimisation would not be the worst idea in the world.
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);
@@ -632,18 +656,15 @@ void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const in
// 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) {
- int maxUpdateDelta = -1;
- // Optimization: Unlike actual SCI (SQ6) engine, we call
- // getTickCount only once and store it, instead of calling it
- // twice on each iteration through the loop
const uint32 now = g_sci->getTickCount();
+ uint32 minUpdateDelta = 0xFFFFFFFF;
for (cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) {
PalCycler *candidate = _cyclers[cyclerIndex];
- const int32 updateDelta = now - candidate->lastUpdateTick;
- if (updateDelta >= maxUpdateDelta) {
- maxUpdateDelta = updateDelta;
+ const uint32 updateDelta = now - candidate->lastUpdateTick;
+ if (updateDelta < minUpdateDelta) {
+ minUpdateDelta = updateDelta;
cycler = candidate;
}
}
diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
index a5450776dc..dc2158022f 100644
--- a/engines/sci/graphics/palette32.h
+++ b/engines/sci/graphics/palette32.h
@@ -26,6 +26,118 @@
#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
@@ -72,28 +184,29 @@ struct PalCycler {
uint16 numTimesPaused;
};
-class GfxPalette32 : public GfxPalette {
+class GfxPalette32 {
public:
- GfxPalette32(ResourceManager *resMan, GfxScreen *screen);
+ GfxPalette32(ResourceManager *resMan);
~GfxPalette32();
private:
- // NOTE: currentPalette in SCI engine is called _sysPalette
- // here.
+ ResourceManager *_resMan;
/**
* The palette revision version. Increments once per game
- * loop that changes the source palette. TODO: Possibly
- * other areas also change _version, double-check once it
- * is all implemented.
+ * loop that changes the source palette.
*/
uint32 _version;
/**
- * Whether or not the palette manager version was updated
- * during this loop.
+ * Whether or not the hardware palette needs updating.
+ */
+ bool _needsUpdate;
+
+ /**
+ * The currently displayed palette.
*/
- bool _versionUpdated;
+ Palette _currentPalette;
/**
* The unmodified source palette loaded by kPalette. Additional
@@ -104,7 +217,7 @@ private:
/**
* The palette to be used when the hardware is next updated.
- * On update, _nextPalette is transferred to _sysPalette.
+ * On update, _nextPalette is transferred to _currentPalette.
*/
Palette _nextPalette;
@@ -112,28 +225,37 @@ private:
Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const;
public:
- virtual void saveLoadWithSerializer(Common::Serializer &s) override;
- const Palette *getNextPalette() const;
+ void saveLoadWithSerializer(Common::Serializer &s);
+ inline const Palette &getNextPalette() const { return _nextPalette; };
+ inline const Palette &getCurrentPalette() const { return _currentPalette; };
- bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;
- int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;
- void set(Palette *newPalette, bool force, bool forceRealMerge = false) override;
- int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);
+ /**
+ * 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(Palette &palette);
+ void submit(const Palette &palette);
+ void submit(HunkPalette &palette);
bool updateForFrame();
void updateFFrame();
- void updateHardware();
+ void updateHardware(const bool updateScreen = true);
void applyAll();
#pragma mark -
-#pragma mark color look-up
+#pragma mark Color look-up
private:
/**
* An optional lookup table used to remap RGB565 colors to a palette
@@ -213,7 +335,7 @@ public:
void kernelPalVarySetTarget(const GuiResourceId paletteId);
void kernelPalVarySetStart(const GuiResourceId paletteId);
void kernelPalVaryMergeStart(const GuiResourceId paletteId);
- virtual void kernelPalVaryPause(bool pause) override;
+ 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);
@@ -240,6 +362,11 @@ private:
* 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);
@@ -257,7 +384,7 @@ public:
void cycleAllOff();
void applyAllCycles();
void applyCycles();
- const bool *getCyclemap() { return _cycleMap; }
+ inline const bool *getCycleMap() const { return _cycleMap; }
#pragma mark -
#pragma mark Fading
diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
index d05e4f79e1..6b05b2d98a 100644
--- a/engines/sci/graphics/plane32.cpp
+++ b/engines/sci/graphics/plane32.cpp
@@ -27,7 +27,7 @@
#include "sci/graphics/frameout.h"
#include "sci/graphics/lists32.h"
#include "sci/graphics/plane32.h"
-#include "sci/graphics/remap.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/screen_item32.h"
@@ -45,10 +45,9 @@ void DrawList::add(ScreenItem *screenItem, const Common::Rect &rect) {
uint16 Plane::_nextObjectId = 20000;
Plane::Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId) :
-_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
-_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
_pictureId(pictureId),
_mirrored(false),
+_type(kPlaneTypeColored),
_back(0),
_priorityChanged(0),
_object(make_reg(0, _nextObjectId++)),
@@ -65,8 +64,7 @@ _gameRect(gameRect) {
}
Plane::Plane(reg_t object) :
-_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
-_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
+_type(kPlaneTypeColored),
_priorityChanged(false),
_object(object),
_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()),
@@ -97,8 +95,7 @@ _moved(0) {
Plane::Plane(const Plane &other) :
_pictureId(other._pictureId),
_mirrored(other._mirrored),
-_field_34(other._field_34), _field_38(other._field_38),
-_field_3C(other._field_3C), _field_40(other._field_40),
+_type(other._type),
_back(other._back),
_object(other._object),
_priority(other._priority),
@@ -116,11 +113,7 @@ void Plane::operator=(const Plane &other) {
_mirrored = other._mirrored;
_priority = other._priority;
_back = other._back;
- _width = other._width;
- _field_34 = other._field_34;
- _height = other._height;
_screenRect = other._screenRect;
- _field_3C = other._field_3C;
_priorityChanged = other._priorityChanged;
}
@@ -173,17 +166,21 @@ void Plane::printDebugInfo(Console *con) const {
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->_fixPriority = true;
+ screenItem->_fixedPriority = true;
if (position != nullptr) {
screenItem->_position = *position + celObj->_relativePosition;
} else {
@@ -194,6 +191,7 @@ void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *p
delete screenItem->_celObj;
screenItem->_celObj = celObj;
}
+ _type = transparent ? kPlaneTypeTransparentPicture : kPlaneTypePicture;
}
void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) {
@@ -206,7 +204,7 @@ void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position,
void Plane::changePic() {
_pictureChanged = false;
- if (_type != kPlaneTypePicture) {
+ if (_type != kPlaneTypePicture && _type != kPlaneTypeTransparentPicture) {
return;
}
@@ -249,17 +247,23 @@ void Plane::deleteAllPics() {
#pragma mark -
#pragma mark Plane - Rendering
+extern int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]);
+
void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const {
- int index = planeList.findIndexByObject(_object);
+ 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 = index + 1; j < planeList.size(); ++j) {
- if (planeList[j]->_type != kPlaneTypeTransparent) {
- Common::Rect ptr[4];
- int count = splitRects(drawList[i]->rect, planeList[j]->_screenRect, ptr);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- drawList.add(drawList[i]->screenItem, ptr[k]);
+ 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);
@@ -272,17 +276,20 @@ void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList
}
void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const {
- int index = planeList.findIndexByObject(_object);
+ 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 = index + 1; j < planeList.size(); ++j) {
- if (planeList[j]->_type != kPlaneTypeTransparent) {
- Common::Rect ptr[4];
-
- int count = splitRects(*eraseList[i], planeList[j]->_screenRect, ptr);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- eraseList.add(ptr[k]);
+ 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);
@@ -295,94 +302,109 @@ void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeLi
}
void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
- ScreenItemList::size_type planeItemCount = _screenItemList.size();
- ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size();
+ 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;
+ }
- for (ScreenItemList::size_type i = 0; i < planeItemCount; ++i) {
- ScreenItem *vitem = nullptr;
// 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) {
- vitem = visiblePlane._screenItemList[i];
+ visibleItem = visiblePlane._screenItemList[i];
}
- ScreenItem *item = _screenItemList[i];
- if (i < _screenItemList.size() && item != nullptr) {
- if (item->_deleted) {
- // add item's rect to erase list
- if (
- i < visiblePlane._screenItemList.size() &&
- vitem != nullptr &&
- !vitem->_screenRect.isEmpty()
- ) {
- if (g_sci->_gfxRemap32->getRemapCount()) {
- mergeToRectList(vitem->_screenRect, eraseList);
- } else {
- eraseList.add(vitem->_screenRect);
- }
- }
- } else if (item->_created) {
- // add item to draw list
- item->calcRects(*this);
-
- if(!item->_screenRect.isEmpty()) {
- if (g_sci->_gfxRemap32->getRemapCount()) {
- drawList.add(item, item->_screenRect);
- mergeToRectList(item->_screenRect, eraseList);
- } else {
- drawList.add(item, item->_screenRect);
- }
- }
- } else if (item->_updated) {
- // add old rect to erase list, new item to draw list
- item->calcRects(*this);
+ // 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()) {
- // if item and vitem don't overlap, ...
- if (item->_screenRect.isEmpty() ||
- i >= visiblePlaneItemCount ||
- vitem == nullptr ||
- vitem->_screenRect.isEmpty() ||
- !vitem->_screenRect.intersects(item->_screenRect)
- ) {
- // add item to draw list, and old rect to erase list
- if (!item->_screenRect.isEmpty()) {
- drawList.add(item, item->_screenRect);
- mergeToRectList(item->_screenRect, eraseList);
- }
- if (
- i < visiblePlaneItemCount &&
- vitem != nullptr &&
- !vitem->_screenRect.isEmpty()
- ) {
- mergeToRectList(vitem->_screenRect, eraseList);
- }
- } else {
- // otherwise, add bounding box of old+new to erase list,
- // and item to draw list
+ mergeToRectList(visibleItemScreenRect, eraseList);
+ } else {
+ eraseList.add(visibleItemScreenRect);
+ }
+ }
+ }
- // TODO: This was changed from disasm, verify please!
- Common::Rect extendedScreenRect = vitem->_screenRect;
- extendedScreenRect.extend(item->_screenRect);
+ if (!item->_created && !item->_updated) {
+ continue;
+ }
- drawList.add(item, item->_screenRect);
- mergeToRectList(extendedScreenRect, eraseList);
- }
+ 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 {
- // if no active remaps, just add item to draw list and old rect
- // to erase list
- if (!item->_screenRect.isEmpty()) {
- drawList.add(item, item->_screenRect);
+ 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 (
- i < visiblePlaneItemCount &&
- vitem != nullptr &&
- !vitem->_screenRect.isEmpty()
- ) {
- eraseList.add(vitem->_screenRect);
+ 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);
}
}
}
@@ -395,40 +417,44 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
// 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();
- if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"????
+ // TODO: Figure out which games need which rendering method
+ if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"
_screenItemList.sort();
- bool encounteredPic = false;
- bool v81 = false;
+ bool pictureDrawn = false;
+ bool screenItemDrawn = false;
- for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- const Common::Rect *rect = eraseList[i];
+ for (RectList::size_type i = 0; i < eraseListCount; ++i) {
+ const Common::Rect &rect = *eraseList[i];
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
ScreenItem *item = _screenItemList[j];
- if (j < _screenItemList.size() && item != nullptr) {
- if (rect->intersects(item->_screenRect)) {
- const Common::Rect intersection = rect->findIntersectingRect(item->_screenRect);
- if (!item->_deleted) {
- if (encounteredPic) {
- if (item->_celInfo.type == kCelTypePic) {
- if (v81 || item->_celInfo.celNo == 0) {
- drawList.add(item, intersection);
- }
- } else {
- if (!item->_updated && !item->_created) {
- drawList.add(item, intersection);
- }
- v81 = true;
+ 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) {
- drawList.add(item, intersection);
- }
- if (item->_celInfo.type == kCelTypePic) {
- encounteredPic = true;
+ mergeToDrawList(j, intersection, drawList);
}
+ screenItemDrawn = true;
+ }
+ } else {
+ if (!item->_updated && !item->_created) {
+ mergeToDrawList(j, intersection, drawList);
+ }
+ if (item->_celInfo.type == kCelTypePic) {
+ pictureDrawn = true;
}
}
}
@@ -438,22 +464,23 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
_screenItemList.unsort();
} else {
- // add all items overlapping the erase list to the draw list
- for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ // 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 &&
- eraseList[i]->intersects(item->_screenRect)
+ rect.intersects(item->_screenRect)
) {
- drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect));
+ drawList.add(item, rect.findIntersectingRect(item->_screenRect));
}
}
}
}
- if (g_sci->_gfxRemap32->getRemapCount() == 0) { // no remaps active?
+ if (g_sci->_gfxRemap32->getRemapCount() == 0) {
// Add all items that overlap with items in the drawlist and have higher
// priority.
@@ -461,23 +488,28 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
// 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) {
- DrawItem *dli = drawList[i];
+ const DrawItem *drawListEntry = nullptr;
+ if (i < drawList.size()) {
+ drawListEntry = drawList[i];
+ }
- for (ScreenItemList::size_type j = 0; j < planeItemCount; ++j) {
- ScreenItem *sli = _screenItemList[j];
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ ScreenItem *newItem = nullptr;
+ if (j < _screenItemList.size()) {
+ newItem = _screenItemList[j];
+ }
if (
- i < drawList.size() && dli != nullptr &&
- j < _screenItemList.size() && sli != nullptr &&
- !sli->_created && !sli->_updated && !sli->_deleted
+ drawListEntry != nullptr && newItem != nullptr &&
+ !newItem->_created && !newItem->_updated && !newItem->_deleted
) {
- ScreenItem *item = dli->screenItem;
+ const ScreenItem *drawnItem = drawListEntry->screenItem;
if (
- (sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) &&
- dli->rect.intersects(sli->_screenRect)
+ (newItem->_priority > drawnItem->_priority || (newItem->_priority == drawnItem->_priority && newItem->_object > drawnItem->_object)) &&
+ drawListEntry->rect.intersects(newItem->_screenRect)
) {
- drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect));
+ mergeToDrawList(j, drawListEntry->rect.findIntersectingRect(newItem->_screenRect), drawList);
}
}
}
@@ -485,14 +517,11 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
}
decrementScreenItemArrayCounts(&visiblePlane, false);
- _screenItemList.pack();
- visiblePlane._screenItemList.pack();
}
void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) {
- // The size of the screenItemList may change, so it is
- // critical to re-check the size on each iteration
- for (ScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
ScreenItem *item = _screenItemList[i];
if (item != nullptr) {
@@ -505,7 +534,7 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force
visiblePlane->_screenItemList.findByObject(item->_object) != nullptr
)
) {
- *visiblePlane->_screenItemList[i] = *_screenItemList[i];
+ *visiblePlane->_screenItemList[i] = *item;
}
if (item->_updated) {
@@ -524,175 +553,182 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force
if (item->_deleted) {
item->_deleted--;
if (!item->_deleted) {
- visiblePlane->_screenItemList.erase_at(i);
+ 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 &transparentEraseList) const {
- if (_type == kPlaneTypeTransparent) {
- for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
- const Common::Rect *r = transparentEraseList[i];
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
- if (item != nullptr) {
- if (r->intersects(item->_screenRect)) {
- mergeToDrawList(j, *r, drawList);
- }
+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 < transparentEraseList.size(); ++i) {
- Common::Rect *r = transparentEraseList[i];
- if (r->intersects(_screenRect)) {
- r->clip(_screenRect);
- mergeToRectList(*r, eraseList);
-
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
-
- if (item != nullptr) {
- if (r->intersects(item->_screenRect)) {
- mergeToDrawList(j, *r, drawList);
- }
+ 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 ptr[4];
- const Common::Rect *r2 = transparentEraseList[i];
- int count = splitRects(*r2, *r, ptr);
- for (int k = count - 1; k >= 0; --k) {
- transparentEraseList.add(ptr[k]);
+ 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]);
+ }
}
- transparentEraseList.erase_at(i);
+ higherEraseList.erase_at(i);
}
}
- transparentEraseList.pack();
+ higherEraseList.pack();
}
}
-void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const {
- for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
- const Common::Rect &r = drawList[i]->rect;
-
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
- if (item != nullptr) {
- if (r.intersects(item->_screenRect)) {
- mergeToDrawList(j, r, transparentDrawList);
- }
+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, RectList &eraseList) const {
- for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- const Common::Rect &r = *eraseList[i];
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
-
- if (item != nullptr) {
- if (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 rects;
-
- ScreenItem *item = _screenItemList[index];
- Common::Rect r = item->_screenRect;
+ RectList mergeList;
+ ScreenItem &item = *_screenItemList[index];
+ Common::Rect r = item._screenRect;
r.clip(rect);
- rects.add(r);
+ mergeList.add(r);
- for (RectList::size_type i = 0; i < rects.size(); ++i) {
- r = *rects[i];
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ r = *mergeList[i];
- for (DrawList::size_type j = 0; j < drawList.size(); ++j) {
- const DrawItem *drawitem = drawList[j];
- if (item->_object == drawitem->screenItem->_object) {
- if (drawitem->rect.contains(r)) {
- rects.erase_at(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];
- const int count = splitRects(r, drawitem->rect, outRects);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- rects.add(outRects[k]);
+ int splitCount = splitRects(r, drawItem.rect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
}
- rects.erase_at(i);
+ mergeList.erase_at(i);
// proceed to the next rect
- r = *rects[++i];
+ r = *mergeList[++i];
}
}
}
}
- rects.pack();
+ mergeList.pack();
- for (RectList::size_type i = 0; i < rects.size(); ++i) {
- drawList.add(item, *rects[i]);
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ drawList.add(&item, *mergeList[i]);
}
}
-void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const {
- RectList temp;
- temp.add(rect);
+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 < temp.size(); ++i) {
- Common::Rect r = *temp[i];
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ r = *mergeList[i];
- for (RectList::size_type j = 0; j < rectList.size(); ++j) {
- const Common::Rect *innerRect = rectList[j];
- if (innerRect->contains(r)) {
- temp.erase_at(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 out[4];
- const int count = splitRects(r, *innerRect, out);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- temp.add(out[k]);
+ Common::Rect outRects[4];
+ int splitCount = splitRects(r, eraseRect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
}
- temp.erase_at(i);
+ mergeList.erase_at(i);
// proceed to the next rect
- r = *temp[++i];
+ r = *mergeList[++i];
}
}
}
- temp.pack();
+ mergeList.pack();
- for (RectList::size_type i = 0; i < temp.size(); ++i) {
- rectList.add(*temp[i]);
+ 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) {
- for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) {
- if (*screenItemPtr != nullptr) {
- ScreenItem &screenItem = **screenItemPtr;
- if (!screenItem._deleted) {
- screenItem.calcRects(*this);
- if (!screenItem._screenRect.isEmpty()) {
- drawList.add(&screenItem, screenItem._screenRect);
- }
+ 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);
}
}
}
@@ -706,21 +742,27 @@ void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList
breakDrawListByPlanes(drawList, planeList);
--_redrawAllCount;
decrementScreenItemArrayCounts(visiblePlane, true);
- _screenItemList.pack();
- if (visiblePlane != nullptr) {
- visiblePlane->_screenItemList.pack();
- }
}
void Plane::setType() {
- if (_pictureId == kPlanePicOpaque) {
- _type = kPlaneTypeOpaque;
- } else if (_pictureId == kPlanePicTransparent) {
- _type = kPlaneTypeTransparent;
- } else if (_pictureId == kPlanePicColored) {
+ switch (_pictureId) {
+ case kPlanePicColored:
_type = kPlaneTypeColored;
- } else {
- _type = kPlaneTypePicture;
+ break;
+ case kPlanePicTransparent:
+ _type = kPlaneTypeTransparent;
+ break;
+ case kPlanePicOpaque:
+ _type = kPlaneTypeOpaque;
+ break;
+ case kPlanePicTransparentPicture:
+ _type = kPlaneTypeTransparentPicture;
+ break;
+ default:
+ if (_type != kPlaneTypeTransparentPicture) {
+ _type = kPlaneTypePicture;
+ }
+ break;
}
}
@@ -741,10 +783,12 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) {
_planeRect.right > other->_planeRect.right ||
_planeRect.bottom > other->_planeRect.bottom
) {
+ // the plane moved or got larger
_redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
- _updated = g_sci->_gfxFrameout->getScreenCount();
+ _moved = g_sci->_gfxFrameout->getScreenCount();
} else if (_planeRect != other->_planeRect) {
- _updated = g_sci->_gfxFrameout->getScreenCount();
+ // the plane got smaller
+ _moved = g_sci->_gfxFrameout->getScreenCount();
}
if (_priority != other->_priority) {
@@ -765,12 +809,10 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) {
_deleted = 0;
if (_created == 0) {
- _moved = g_sci->_gfxFrameout->getScreenCount();
+ _updated = g_sci->_gfxFrameout->getScreenCount();
}
convertGameRectToPlaneRect();
- _width = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
- _height = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
_screenRect = _planeRect;
// NOTE: screenRect originally was retrieved through globals
// instead of being passed into the function
@@ -813,18 +855,22 @@ void Plane::scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool
}
void Plane::remapMarkRedraw() {
- for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) {
- if (*screenItemPtr != nullptr) {
- ScreenItem &screenItem = **screenItemPtr;
- if (screenItem.getCelObj()._remap && !screenItem._deleted && !screenItem._created) {
- screenItem._updated = g_sci->_gfxFrameout->getScreenCount();
- }
+ 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) {
diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h
index 770a6fa445..3981a2b319 100644
--- a/engines/sci/graphics/plane32.h
+++ b/engines/sci/graphics/plane32.h
@@ -32,19 +32,21 @@
namespace Sci {
enum PlaneType {
- kPlaneTypeColored = 0,
- kPlaneTypePicture = 1,
- kPlaneTypeTransparent = 2,
- kPlaneTypeOpaque = 3
+ kPlaneTypeColored = 0,
+ kPlaneTypePicture = 1,
+ kPlaneTypeTransparent = 2,
+ kPlaneTypeOpaque = 3,
+ kPlaneTypeTransparentPicture = 4
};
enum PlanePictureCodes {
- // NOTE: Any value at or below 65532 means the plane
+ // NOTE: Any value at or below 65531 means the plane
// is a kPlaneTypePicture.
- kPlanePic = 65532,
- kPlanePicOpaque = 65533,
- kPlanePicTransparent = 65534,
- kPlanePicColored = 65535
+ kPlanePic = 65531,
+ kPlanePicTransparentPicture = 65532,
+ kPlanePicOpaque = 65533,
+ kPlanePicTransparent = 65534,
+ kPlanePicColored = 65535
};
#pragma mark -
@@ -62,7 +64,14 @@ public:
#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 {
@@ -101,16 +110,6 @@ private:
static uint16 _nextObjectId;
/**
- * The dimensions of the plane, in game script
- * coordinates.
- * TODO: These are never used and are always
- * scriptWidth x scriptHeight in SCI engine? The actual
- * dimensions of the plane are always in
- * gameRect/planeRect.
- */
- int16 _width, _height;
-
- /**
* 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
@@ -135,10 +134,6 @@ private:
*/
bool _pictureChanged;
- // TODO: Are these ever actually used?
- int _field_34, _field_38; // probably a point or ratio
- int _field_3C, _field_40; // probably a point or ratio
-
/**
* Converts the dimensions of the game rect used by
* scripts to the dimensions of the plane rect used to
@@ -155,6 +150,11 @@ private:
public:
/**
+ * The type of the plane.
+ */
+ PlaneType _type;
+
+ /**
* The color to use when erasing the plane. Only
* applies to planes of type kPlaneTypeColored.
*/
@@ -191,8 +191,6 @@ public:
*/
int _redrawAllCount;
- PlaneType _type;
-
/**
* Flags indicating the state of the plane.
* - `created` is set when the plane is first created,
@@ -203,16 +201,15 @@ public:
* not match
* - `deleted` is set when the plane is deleted by a
* kernel call
- * - `moved` is set when the plane is synchronised from
- * another plane and is not already in the "created"
- * state
+ * - `moved` is set when the plane has been moved or
+ * resized
*/
int _created, _updated, _deleted, _moved;
/**
* The vanishing point for the plane. Used when
- * calculating the correct scaling of the plane's screen
- * items according to their position.
+ * automatically calculating the correct scaling of the
+ * plane's screen items according to their position.
*/
Common::Point _vanishingPoint;
@@ -275,7 +272,11 @@ public:
* given screen rect.
*/
inline void clipScreenRect(const Common::Rect &screenRect) {
- if (_screenRect.intersects(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;
@@ -368,42 +369,33 @@ public:
private:
/**
* Splits all rects in the given draw list at the edges
- * of all non-transparent planes above the current
- * plane.
+ * of all higher-priority, non-transparent, intersecting
+ * planes.
*/
void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const;
/**
- * Splits all rects in the given erase list rects at the
- * edges of all non-transparent planes above the current
- * plane.
+ * 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;
/**
- * 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);
-
- /**
- * Merges the screen item from this plane at the given
- * index into the given draw list, clipped to the given
- * rect. TODO: Finish documenting
+ * 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;
/**
- * Adds the given rect into the given rect list,
- * merging it with other rects already inside the list,
- * if possible, to avoid overdraw. TODO: Finish
- * documenting
+ * 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 &rectList) const;
+ void mergeToRectList(const Common::Rect &rect, RectList &eraseList) const;
public:
/**
@@ -416,19 +408,73 @@ public:
void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
/**
- * TODO: Documentation
+ * 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 &transparentEraseList) const;
+ void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const;
/**
- * TODO: Documentation
+ * 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, RectList &eraseList) const;
+ void filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const;
/**
- * TODO: Documentation
+ * 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 &transparentDrawList, const DrawList &drawList) const;
+ void filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const;
/**
* Updates all of the plane's non-deleted screen items
diff --git a/engines/sci/graphics/remap.cpp b/engines/sci/graphics/remap.cpp
index e331eaf971..2abf03ea29 100644
--- a/engines/sci/graphics/remap.cpp
+++ b/engines/sci/graphics/remap.cpp
@@ -21,31 +21,23 @@
*/
#include "sci/sci.h"
-#include "sci/resource.h"
#include "sci/graphics/palette.h"
-#include "sci/graphics/palette32.h"
#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
namespace Sci {
-#pragma mark -
-#pragma mark SCI16 remapping (QFG4 demo)
-
GfxRemap::GfxRemap(GfxPalette *palette)
: _palette(palette) {
_remapOn = false;
resetRemapping();
}
-GfxRemap::~GfxRemap() {
-}
-
byte GfxRemap::remapColor(byte remappedColor, byte screenColor) {
assert(_remapOn);
- if (_remappingType[remappedColor] == kRemappingByRange)
+ if (_remappingType[remappedColor] == kRemapByRange)
return _remappingByRange[screenColor];
- else if (_remappingType[remappedColor] == kRemappingByPercent)
+ else if (_remappingType[remappedColor] == kRemapByPercent)
return _remappingByPercent[screenColor];
else
error("remapColor(): Color %d isn't remapped", remappedColor);
@@ -58,7 +50,7 @@ void GfxRemap::resetRemapping() {
_remappingPercentToSet = 0;
for (int i = 0; i < 256; i++) {
- _remappingType[i] = kRemappingNone;
+ _remappingType[i] = kRemapNone;
_remappingByPercent[i] = i;
_remappingByRange[i] = i;
}
@@ -80,7 +72,7 @@ void GfxRemap::setRemappingPercent(byte color, byte percent) {
_remappingByPercent[i] = _palette->kernelFindColor(r, g, b);
}
- _remappingType[color] = kRemappingByPercent;
+ _remappingType[color] = kRemapByPercent;
}
void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) {
@@ -90,7 +82,7 @@ void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) {
_remappingByRange[i] = i + base;
}
- _remappingType[color] = kRemappingByRange;
+ _remappingType[color] = kRemapByRange;
}
void GfxRemap::updateRemapping() {
@@ -104,283 +96,4 @@ void GfxRemap::updateRemapping() {
}
}
}
-
-#pragma mark -
-#pragma mark SCI32 remapping
-
-#ifdef ENABLE_SCI32
-
-GfxRemap32::GfxRemap32(GfxPalette32 *palette) : _palette(palette) {
- for (int i = 0; i < REMAP_COLOR_COUNT; i++)
- _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
- _noMapStart = _noMapCount = 0;
- _update = false;
- _remapCount = 0;
-
- // The remap range was 245 - 254 in SCI2, but was changed to 235 - 244 in SCI21 middle
- _remapEndColor = (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) ? 244 : 254;
-}
-
-void GfxRemap32::remapOff(byte color) {
- if (!color) {
- for (int i = 0; i < REMAP_COLOR_COUNT; i++)
- _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
-
- _remapCount = 0;
- } else {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- const byte index = _remapEndColor - color;
- _remaps[index] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
- _remapCount--;
- }
-
- _update = true;
-}
-
-void GfxRemap32::setRemappingRange(byte color, byte from, byte to, byte base) {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- _remaps[_remapEndColor - color] = RemapParams(from, to, base, 0, 100, kRemappingByRange);
- initColorArrays(_remapEndColor - color);
- _remapCount++;
- _update = true;
-}
-
-void GfxRemap32::setRemappingPercent(byte color, byte percent) {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, 0, percent, kRemappingByPercent);
- initColorArrays(_remapEndColor - color);
- _remapCount++;
- _update = true;
-}
-
-void GfxRemap32::setRemappingToGray(byte color, byte gray) {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, 100, kRemappingToGray);
- initColorArrays(_remapEndColor - color);
- _remapCount++;
- _update = true;
-}
-
-void GfxRemap32::setRemappingToPercentGray(byte color, byte gray, byte percent) {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, percent, kRemappingToPercentGray);
- initColorArrays(_remapEndColor - color);
- _remapCount++;
- _update = true;
-}
-
-void GfxRemap32::setNoMatchRange(byte from, byte count) {
- _noMapStart = from;
- _noMapCount = count;
-}
-
-bool GfxRemap32::remapEnabled(byte color) const {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- const byte index = _remapEndColor - color;
- return (_remaps[index].type != kRemappingNone);
-}
-
-byte GfxRemap32::remapColor(byte color, byte target) {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- const byte index = _remapEndColor - color;
- if (_remaps[index].type != kRemappingNone)
- return _remaps[index].remap[target];
- else
- return target;
-}
-
-void GfxRemap32::initColorArrays(byte index) {
- Palette *curPalette = &_palette->_sysPalette;
- RemapParams *curRemap = &_remaps[index];
-
- memcpy(curRemap->curColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color));
- memcpy(curRemap->targetColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color));
-}
-
-bool GfxRemap32::updateRemap(byte index, bool palChanged) {
- int result;
- RemapParams *curRemap = &_remaps[index];
- const Palette *curPalette = &_palette->_sysPalette;
- const Palette *nextPalette = _palette->getNextPalette();
- bool changed = false;
-
- if (!_update && !palChanged)
- return false;
-
- Common::fill(_targetChanged, _targetChanged + NON_REMAPPED_COLOR_COUNT, false);
-
- switch (curRemap->type) {
- case kRemappingNone:
- return false;
- case kRemappingByRange:
- for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) {
- if (curRemap->from <= i && i <= curRemap->to)
- result = i + curRemap->base;
- else
- result = i;
-
- if (curRemap->remap[i] != result) {
- changed = true;
- curRemap->remap[i] = result;
- }
-
- curRemap->colorChanged[i] = true;
- }
- return changed;
- case kRemappingByPercent:
- for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
- // NOTE: This method uses nextPalette instead of curPalette
- Color color = nextPalette->colors[i];
-
- if (curRemap->curColor[i] != color) {
- curRemap->colorChanged[i] = true;
- curRemap->curColor[i] = color;
- }
-
- if (curRemap->percent != curRemap->oldPercent || curRemap->colorChanged[i]) {
- byte red = CLIP<byte>(color.r * curRemap->percent / 100, 0, 255);
- byte green = CLIP<byte>(color.g * curRemap->percent / 100, 0, 255);
- byte blue = CLIP<byte>(color.b * curRemap->percent / 100, 0, 255);
- byte used = curRemap->targetColor[i].used;
-
- Color newColor = { used, red, green, blue };
- if (curRemap->targetColor[i] != newColor) {
- _targetChanged[i] = true;
- curRemap->targetColor[i] = newColor;
- }
- }
- }
-
- changed = applyRemap(index);
- Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
- curRemap->oldPercent = curRemap->percent;
- return changed;
- case kRemappingToGray:
- for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
- Color color = curPalette->colors[i];
-
- if (curRemap->curColor[i] != color) {
- curRemap->colorChanged[i] = true;
- curRemap->curColor[i] = color;
- }
-
- if (curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) {
- byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8;
- byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255);
- byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255);
- byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255);
- byte used = curRemap->targetColor[i].used;
-
- Color newColor = { used, red, green, blue };
- if (curRemap->targetColor[i] != newColor) {
- _targetChanged[i] = true;
- curRemap->targetColor[i] = newColor;
- }
- }
- }
-
- changed = applyRemap(index);
- Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
- curRemap->oldGray = curRemap->gray;
- return changed;
- case kRemappingToPercentGray:
- for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
- Color color = curPalette->colors[i];
-
- if (curRemap->curColor[i] != color) {
- curRemap->colorChanged[i] = true;
- curRemap->curColor[i] = color;
- }
-
- if (curRemap->percent != curRemap->oldPercent || curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) {
- byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8;
- lumosity = lumosity * curRemap->percent / 100;
- byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255);
- byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255);
- byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255);
- byte used = curRemap->targetColor[i].used;
-
- Color newColor = { used, red, green, blue };
- if (curRemap->targetColor[i] != newColor) {
- _targetChanged[i] = true;
- curRemap->targetColor[i] = newColor;
- }
- }
- }
-
- changed = applyRemap(index);
- Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
- curRemap->oldPercent = curRemap->percent;
- curRemap->oldGray = curRemap->gray;
- return changed;
- default:
- return false;
- }
-}
-
-static int colorDistance(Color a, Color b) {
- int rDiff = (a.r - b.r) * (a.r - b.r);
- int gDiff = (a.g - b.g) * (a.g - b.g);
- int bDiff = (a.b - b.b) * (a.b - b.b);
- return rDiff + gDiff + bDiff;
-}
-
-bool GfxRemap32::applyRemap(byte index) {
- RemapParams *curRemap = &_remaps[index];
- const bool *cycleMap = _palette->getCyclemap();
- bool unmappedColors[NON_REMAPPED_COLOR_COUNT];
- Color newColors[NON_REMAPPED_COLOR_COUNT];
- bool changed = false;
-
- Common::fill(unmappedColors, unmappedColors + NON_REMAPPED_COLOR_COUNT, false);
- if (_noMapCount)
- Common::fill(unmappedColors + _noMapStart, unmappedColors + _noMapStart + _noMapCount, true);
-
- for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) {
- if (cycleMap[i])
- unmappedColors[i] = true;
- }
-
- int curColor = 0;
- for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
- if (curRemap->colorChanged[i] && !unmappedColors[i])
- newColors[curColor++] = curRemap->curColor[i];
- }
-
- for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
- Color targetColor = curRemap->targetColor[i];
- bool colorChanged = curRemap->colorChanged[curRemap->remap[i]];
-
- if (!_targetChanged[i] && !colorChanged)
- continue;
-
- if (_targetChanged[i] && colorChanged)
- if (curRemap->distance[i] < 100 && colorDistance(targetColor, curRemap->curColor[curRemap->remap[i]]) <= curRemap->distance[i])
- continue;
-
- int diff = 0;
- int16 result = _palette->matchColor(targetColor.r, targetColor.g, targetColor.b, curRemap->distance[i], diff, unmappedColors);
- if (result != -1 && curRemap->remap[i] != result) {
- changed = true;
- curRemap->remap[i] = result;
- curRemap->distance[i] = diff;
- }
- }
-
- return changed;
-}
-
-bool GfxRemap32::remapAllTables(bool palChanged) {
- bool changed = false;
-
- for (int i = 0; i < REMAP_COLOR_COUNT; i++) {
- changed |= updateRemap(i, palChanged);
- }
-
- _update = false;
- return changed;
-}
-
-#endif
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/remap.h b/engines/sci/graphics/remap.h
index d012568f7f..98177f6d19 100644
--- a/engines/sci/graphics/remap.h
+++ b/engines/sci/graphics/remap.h
@@ -24,42 +24,36 @@
#define SCI_GRAPHICS_REMAP_H
#include "common/array.h"
-#include "sci/graphics/helpers.h"
+#include "common/serializer.h"
namespace Sci {
class GfxScreen;
-enum ColorRemappingType {
- kRemappingNone = 0,
- kRemappingByRange = 1,
- kRemappingByPercent = 2,
- kRemappingToGray = 3,
- kRemappingToPercentGray = 4
-};
-
-#define REMAP_COLOR_COUNT 9
-#define NON_REMAPPED_COLOR_COUNT 236
-
/**
- * Remap class, handles color remapping
+ * This class handles color remapping for the QFG4 demo.
*/
class GfxRemap {
+private:
+ enum ColorRemappingType {
+ kRemapNone = 0,
+ kRemapByRange = 1,
+ kRemapByPercent = 2
+ };
+
public:
GfxRemap(GfxPalette *_palette);
- ~GfxRemap();
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] != kRemappingNone);
+ return _remapOn && (_remappingType[color] != kRemapNone);
}
byte remapColor(byte remappedColor, byte screenColor);
void updateRemapping();
private:
- GfxScreen *_screen;
GfxPalette *_palette;
bool _remapOn;
@@ -68,87 +62,6 @@ private:
byte _remappingByRange[256];
uint16 _remappingPercentToSet;
};
-
-#ifdef ENABLE_SCI32
-
-struct RemapParams {
- byte from;
- byte to;
- byte base;
- byte gray;
- byte oldGray;
- byte percent;
- byte oldPercent;
- ColorRemappingType type;
- Color curColor[256];
- Color targetColor[256];
- byte distance[256];
- byte remap[256];
- bool colorChanged[256];
-
- RemapParams() {
- from = to = base = gray = oldGray = percent = oldPercent = 0;
- type = kRemappingNone;
-
- // curColor and targetColor are initialized in GfxRemap32::initColorArrays
- memset(curColor, 0, 256 * sizeof(Color));
- memset(targetColor, 0, 256 * sizeof(Color));
- memset(distance, 0, 256);
- for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)
- remap[i] = i;
- Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true);
- }
-
- RemapParams(byte from_, byte to_, byte base_, byte gray_, byte percent_, ColorRemappingType type_) {
- from = from_;
- to = to_;
- base = base_;
- gray = oldGray = gray_;
- percent = oldPercent = percent_;
- type = type_;
-
- // curColor and targetColor are initialized in GfxRemap32::initColorArrays
- memset(curColor, 0, 256 * sizeof(Color));
- memset(targetColor, 0, 256 * sizeof(Color));
- memset(distance, 0, 256);
- for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)
- remap[i] = i;
- Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true);
- }
-};
-
-class GfxRemap32 {
-public:
- GfxRemap32(GfxPalette32 *palette);
- ~GfxRemap32() {}
-
- void remapOff(byte color);
- void setRemappingRange(byte color, byte from, byte to, byte base);
- void setRemappingPercent(byte color, byte percent);
- void setRemappingToGray(byte color, byte gray);
- void setRemappingToPercentGray(byte color, byte gray, byte percent);
- void setNoMatchRange(byte from, byte count);
- bool remapEnabled(byte color) const;
- byte remapColor(byte color, byte target);
- bool remapAllTables(bool palChanged);
- int getRemapCount() const { return _remapCount; }
- int getStartColor() const { return _remapEndColor - REMAP_COLOR_COUNT + 1; }
- int getEndColor() const { return _remapEndColor; }
-private:
- GfxPalette32 *_palette;
- RemapParams _remaps[REMAP_COLOR_COUNT];
- bool _update;
- byte _noMapStart, _noMapCount;
- bool _targetChanged[NON_REMAPPED_COLOR_COUNT];
- byte _remapEndColor;
- int _remapCount;
-
- void initColorArrays(byte index);
- bool applyRemap(byte index);
- bool updateRemap(byte index, bool palChanged);
-};
-#endif
-
} // 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..1b9628c7be
--- /dev/null
+++ b/engines/sci/graphics/remap32.h
@@ -0,0 +1,405 @@
+/* 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;
+ // At least KQ7 DOS uses remap colors that are outside the valid remap
+ // range; in these cases, just treat those pixels as skip pixels (which
+ // is how they would be treated in SSCI)
+ if (index >= _remaps.size()) {
+ return false;
+ }
+ 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_item32.cpp b/engines/sci/graphics/screen_item32.cpp
index c3fdbb6845..7383dc222e 100644
--- a/engines/sci/graphics/screen_item32.cpp
+++ b/engines/sci/graphics/screen_item32.cpp
@@ -42,7 +42,8 @@ _pictureId(-1),
_created(g_sci->_gfxFrameout->getScreenCount()),
_updated(0),
_deleted(0),
-_mirrorX(false) {
+_mirrorX(false),
+_drawBlackLines(false) {
SegManager *segMan = g_sci->getEngineState()->_segMan;
setFromObject(segMan, object, true, true);
@@ -55,14 +56,15 @@ _useInsetRect(false),
_z(0),
_celInfo(celInfo),
_celObj(nullptr),
-_fixPriority(false),
+_fixedPriority(false),
_position(0, 0),
_object(make_reg(0, _nextObjectId++)),
_pictureId(-1),
_created(g_sci->_gfxFrameout->getScreenCount()),
_updated(0),
_deleted(0),
-_mirrorX(false) {}
+_mirrorX(false),
+_drawBlackLines(false) {}
ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect) :
_plane(plane),
@@ -70,14 +72,15 @@ _useInsetRect(false),
_z(0),
_celInfo(celInfo),
_celObj(nullptr),
-_fixPriority(false),
+_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) {
+_mirrorX(false),
+_drawBlackLines(false) {
if (celInfo.type == kCelTypeColor) {
_insetRect = rect;
}
@@ -90,14 +93,15 @@ _useInsetRect(false),
_z(0),
_celInfo(celInfo),
_celObj(nullptr),
-_fixPriority(false),
+_fixedPriority(false),
_position(position),
_object(make_reg(0, _nextObjectId++)),
_pictureId(-1),
_created(g_sci->_gfxFrameout->getScreenCount()),
_updated(0),
_deleted(0),
-_mirrorX(false) {}
+_mirrorX(false),
+_drawBlackLines(false) {}
ScreenItem::ScreenItem(const ScreenItem &other) :
_plane(other._plane),
@@ -108,7 +112,8 @@ _celObj(nullptr),
_object(other._object),
_mirrorX(other._mirrorX),
_scaledPosition(other._scaledPosition),
-_screenRect(other._screenRect) {
+_screenRect(other._screenRect),
+_drawBlackLines(other._drawBlackLines) {
if (other._useInsetRect) {
_insetRect = other._insetRect;
}
@@ -134,6 +139,7 @@ void ScreenItem::operator=(const ScreenItem &other) {
}
_scale = other._scale;
_scaledPosition = other._scaledPosition;
+ _drawBlackLines = other._drawBlackLines;
}
ScreenItem::~ScreenItem() {
@@ -209,10 +215,10 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo
}
if (readSelectorValue(segMan, object, SELECTOR(fixPriority))) {
- _fixPriority = true;
+ _fixedPriority = true;
_priority = readSelectorValue(segMan, object, SELECTOR(priority));
} else {
- _fixPriority = false;
+ _fixedPriority = false;
writeSelectorValue(segMan, object, SELECTOR(priority), _position.y);
}
@@ -273,7 +279,9 @@ void ScreenItem::calcRects(const Plane &plane) {
// 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 != scriptWidth || celObj._scaledHeight != scriptHeight) {
+ if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) {
+ // high resolution coordinates
+
if (_useInsetRect) {
const Ratio scriptToCelX(celObj._scaledWidth, scriptWidth);
const Ratio scriptToCelY(celObj._scaledHeight, scriptHeight);
@@ -294,7 +302,30 @@ void ScreenItem::calcRects(const Plane &plane) {
}
if (!scaleX.isOne() || !scaleY.isOne()) {
- mulinc(_screenItemRect, scaleX, scaleY);
+ // 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();
}
@@ -326,6 +357,9 @@ void ScreenItem::calcRects(const Plane &plane) {
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.
@@ -342,6 +376,8 @@ void ScreenItem::calcRects(const Plane &plane) {
_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;
@@ -369,6 +405,9 @@ void ScreenItem::calcRects(const Plane &plane) {
}
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.
@@ -402,7 +441,7 @@ void ScreenItem::calcRects(const Plane &plane) {
_screenRect.top = 0;
}
- if (!_fixPriority) {
+ if (!_fixedPriority) {
_priority = _z + _position.y;
}
} else {
@@ -509,8 +548,25 @@ void ScreenItem::update(const reg_t object) {
_deleted = 0;
}
-// TODO: This code is quite similar to calcRects, so try to deduplicate
-// if possible
+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();
@@ -518,10 +574,7 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
Common::Rect nsRect;
if (_useInsetRect) {
- // TODO: This is weird. Checking to see if the inset rect is
- // fully inside the bounds of the celObjRect, and then
- // clipping to the celObjRect, is pretty useless.
- if (_insetRect.right > 0 && _insetRect.bottom > 0 && _insetRect.left < celObj._width && _insetRect.top < celObj._height) {
+ if (_insetRect.intersects(celObjRect)) {
nsRect = _insetRect;
nsRect.clip(celObjRect);
} else {
@@ -557,16 +610,15 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
displaceX = celObj._width - displaceX - 1;
}
- if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) {
+ 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);
- // TODO: This is weird. Checking to see if the inset rect is
- // fully inside the bounds of the celObjRect, and then
- // clipping to the celObjRect, is pretty useless.
- if (nsRect.right > 0 && nsRect.bottom > 0 && nsRect.left < celObj._width && nsRect.top < celObj._height) {
+ if (nsRect.intersects(celObjRect)) {
nsRect.clip(celObjRect);
} else {
nsRect = Common::Rect();
@@ -574,12 +626,34 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
}
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;
+ // 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);
@@ -591,6 +665,8 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
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
@@ -624,23 +700,43 @@ ScreenItem *ScreenItemList::findByObject(const reg_t object) const {
return *screenItemIt;
}
void ScreenItemList::sort() {
- // TODO: SCI engine used _unsorted as an array of indexes into the
- // list itself and then performed the same swap operations on the
- // _unsorted array as the _storage array during sorting, but the
- // only reason to do this would be if some of the pointers in the
- // list were replaced so the pointer values themselves couldn’t
- // simply be recorded and then restored later. It is not yet
- // verified whether this simplification of the sort/unsort is
- // safe.
+ if (size() < 2) {
+ return;
+ }
+
for (size_type i = 0; i < size(); ++i) {
- _unsorted[i] = (*this)[i];
+ _unsorted[i] = i;
}
- Common::sort(begin(), end(), sortHelper);
+ 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) {
- (*this)[i] = _unsorted[i];
+ while (_unsorted[i] != i) {
+ SWAP(operator[](_unsorted[i]), operator[](i));
+ SWAP(_unsorted[_unsorted[i]], _unsorted[i]);
+ }
}
}
diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h
index 977d80ebad..3d9d5ef3d7 100644
--- a/engines/sci/graphics/screen_item32.h
+++ b/engines/sci/graphics/screen_item32.h
@@ -64,12 +64,12 @@ private:
*/
static uint16 _nextObjectId;
+public:
/**
* The parent plane of this screen item.
*/
reg_t _plane;
-public:
/**
* Scaling data used to calculate the final screen
* dimensions of the screen item as well as the scaling
@@ -88,15 +88,23 @@ private:
Common::Rect _screenItemRect;
/**
- * TODO: Document
+ * 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;
/**
- * TODO: Documentation
- * The insetRect is also used to describe the fill
- * rectangle of a screen item that is drawn using
- * CelObjColor.
+ * 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;
@@ -132,7 +140,7 @@ public:
* in place. Otherwise, the priority of the screen item
* is calculated from its y-position + z-index.
*/
- bool _fixPriority;
+ bool _fixedPriority;
/**
* The rendering priority of the screen item, relative
@@ -172,7 +180,7 @@ public:
* plane is a pic type and its picture resource ID has
* changed
*/
- int _created, _updated, _deleted; // ?
+ int _created, _updated, _deleted;
/**
* For screen items that represent picture cels, this
@@ -206,6 +214,14 @@ public:
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();
@@ -236,6 +252,24 @@ public:
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
@@ -261,6 +295,12 @@ public:
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.
@@ -273,12 +313,10 @@ public:
typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase;
class ScreenItemList : public ScreenItemListBase {
- static bool inline sortHelper(const ScreenItem *a, const ScreenItem *b) {
- return *a < *b;
- }
-public:
- ScreenItem *_unsorted[250];
+private:
+ size_type _unsorted[250];
+public:
ScreenItem *findByObject(const reg_t object) const;
void sort();
void unsort();
diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp
index 99ffc6e328..277e6e93d0 100644
--- a/engines/sci/graphics/text32.cpp
+++ b/engines/sci/graphics/text32.cpp
@@ -39,29 +39,28 @@
namespace Sci {
int16 GfxText32::_defaultFontId = 0;
+int16 GfxText32::_scaledWidth = 0;
+int16 GfxText32::_scaledHeight = 0;
GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) :
_segMan(segMan),
_cache(fonts),
- _scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
- _scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
// Not a typo, the original engine did not initialise height, only width
_width(0),
_text(""),
- _field_20(0),
- _field_2C(2),
- _field_30(0),
- _field_34(0),
- _field_38(0),
- _field_3C(0),
_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;
+ }
}
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) {
- _field_22 = 0;
_borderColor = borderColor;
_text = text;
_textRect = rect;
@@ -111,7 +110,6 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect
}
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) {
- _field_22 = 0;
_borderColor = borderColor;
_text = text;
_textRect = rect;
@@ -123,7 +121,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &
int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
- int borderSize = 1;
mulinc(_textRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight));
CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo);
@@ -140,7 +137,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &
BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false);
_bitmap = bitmap.getObject();
- Buffer buffer(_width, _height, bitmap.getPixels());
// 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
@@ -150,7 +146,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &
erase(bitmapRect, false);
_backColor = backColor;
- view.draw(buffer, bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight));
+ view.draw(bitmap.getBuffer(), bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight));
if (_backColor != skipColor && _foreColor != skipColor) {
erase(_textRect, false);
@@ -161,7 +157,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &
error("TODO: Implement transparent text");
} else {
if (borderColor != -1) {
- drawFrame(bitmapRect, borderSize, _borderColor, false);
+ drawFrame(bitmapRect, 1, _borderColor, false);
}
drawTextBox();
@@ -171,11 +167,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &
return _bitmap;
}
-reg_t GfxText32::createTitledBitmap(const int16 width, const int16 height, const Common::Rect &textRect, const Common::String &text, const int16 foreColor, const int16 backColor, const int16 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, Common::String &title, const int16 titleForeColor, const int16 titleBackColor, const GuiResourceId titleFontId, const bool doScaling) {
- warning("TODO: createTitledBitmap incomplete !");
- return createFontBitmap(width, height, textRect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, false, doScaling);
-}
-
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
@@ -244,8 +235,11 @@ void GfxText32::drawTextBox() {
int16 textRectWidth = _textRect.width();
_drawPosition.y = _textRect.top;
uint charIndex = 0;
- if (getLongest(&charIndex, textRectWidth) == 0) {
- error("DrawTextBox GetLongest=0");
+
+ if (g_sci->getGameId() == GID_SQ6 || g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+ if (getLongest(&charIndex, textRectWidth) == 0) {
+ error("DrawTextBox GetLongest=0");
+ }
}
charIndex = 0;
@@ -324,6 +318,10 @@ void GfxText32::drawText(const uint index, uint length) {
++text;
--length;
}
+ if (length > 0) {
+ ++text;
+ --length;
+ }
} else {
drawChar(currentChar);
}
@@ -511,7 +509,7 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const {
--length;
fontId = fontId * 10 + currentChar - '0';
- } while (length > 0 && currentChar >= '0' && currentChar <= '9');
+ } while (length > 0 && *text >= '0' && *text <= '9');
if (length > 0) {
font = _cache->getFont(fontId);
@@ -519,7 +517,11 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const {
}
// Forward through any more unknown control character data
- while (length > 0 && currentChar != '|') {
+ while (length > 0 && *text != '|') {
+ ++text;
+ --length;
+ }
+ if (length > 0) {
++text;
--length;
}
@@ -527,8 +529,10 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const {
width += font->getCharWidth(currentChar);
}
- currentChar = *text++;
- --length;
+ if (length > 0) {
+ currentChar = *text++;
+ --length;
+ }
}
return width;
@@ -558,12 +562,7 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth,
if (maxWidth >= 0) {
if (maxWidth == 0) {
- // TODO: This was hardcoded to 192, but guessing
- // that it was originally 60% of the scriptWidth
- // before the compiler took over.
- // Verify this by looking at a game that uses a
- // scriptWidth other than 320, like LSL7
- maxWidth = _scaledWidth * (scriptWidth * 0.6) / scriptWidth;
+ maxWidth = _scaledWidth * 3 / 5;
}
result.right = maxWidth;
@@ -591,11 +590,16 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth,
}
} else {
result.right = getTextWidth(0, 10000);
- // 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 (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) {
@@ -611,14 +615,8 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth,
void GfxText32::erase(const Common::Rect &rect, const bool doScaling) {
Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
- byte *bitmap = _segMan->getHunkPointer(_bitmap);
- byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28);
-
- // NOTE: There is an extra optimisation within the SCI code to
- // do a single memset if the scaledRect is the same size as
- // the bitmap, not implemented here.
- Buffer buffer(_width, _height, pixels);
- buffer.fillRect(targetRect, _backColor);
+ BitmapResource bitmap(_bitmap);
+ bitmap.getBuffer().fillRect(targetRect, _backColor);
}
int16 GfxText32::getStringWidth(const Common::String &text) {
@@ -653,5 +651,69 @@ int16 GfxText32::getTextCount(const Common::String &text, const uint index, cons
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 {
+ // 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());
+ }
+ }
+
+ 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;
+ }
+
+ //_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 5768ea0c59..f57433f42d 100644
--- a/engines/sci/graphics/text32.h
+++ b/engines/sci/graphics/text32.h
@@ -23,21 +23,33 @@
#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 {
- kTextAlignLeft = 0,
- kTextAlignCenter = 1,
- kTextAlignRight = 2
+ kTextAlignDefault = -1,
+ kTextAlignLeft = 0,
+ kTextAlignCenter = 1,
+ kTextAlignRight = 2
+};
+
+enum ScrollDirection {
+ kScrollUp,
+ kScrollDown
};
enum BitmapFlags {
kBitmapRemap = 2
};
+enum {
+ kDefaultSkipColor = 250
+};
+
#define BITMAP_PROPERTY(size, property, offset)\
inline uint##size get##property() const {\
return READ_SCI11ENDIAN_UINT##size(_bitmap + (offset));\
@@ -53,6 +65,7 @@ inline void set##property(uint##size value) {\
class BitmapResource {
byte *_bitmap;
reg_t _object;
+ Buffer _buffer;
/**
* Gets the size of the bitmap header for the current
@@ -96,6 +109,8 @@ public:
if (_bitmap == nullptr || getUncompressedDataOffset() != getBitmapHeaderSize()) {
error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap));
}
+
+ _buffer = Buffer(getWidth(), getHeight(), getPixels());
}
/**
@@ -103,7 +118,6 @@ public:
* 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);
@@ -124,12 +138,18 @@ public:
setControlOffset(0);
setScaledWidth(scaledWidth);
setScaledHeight(scaledHeight);
+
+ _buffer = Buffer(getWidth(), getHeight(), getPixels());
}
- reg_t getObject() const {
+ inline reg_t getObject() const {
return _object;
}
+ inline Buffer &getBuffer() {
+ return _buffer;
+ }
+
BITMAP_PROPERTY(16, Width, 0);
BITMAP_PROPERTY(16, Height, 2);
@@ -173,7 +193,7 @@ public:
return READ_SCI11ENDIAN_UINT32(_bitmap + 20);
}
- void setHunkPaletteOffset(uint32 hunkPaletteOffset) {
+ inline void setHunkPaletteOffset(uint32 hunkPaletteOffset) {
if (hunkPaletteOffset) {
hunkPaletteOffset += getBitmapHeaderSize();
}
@@ -304,17 +324,6 @@ private:
*/
TextAlign _alignment;
- int16 _field_20;
-
- /**
- * TODO: Document
- */
- int16 _field_22;
-
- int _field_2C, _field_30, _field_34, _field_38;
-
- int16 _field_3C;
-
/**
* The position of the text draw cursor.
*/
@@ -362,15 +371,15 @@ public:
/**
* The size of the x-dimension of the coordinate system
- * used by the text renderer.
+ * used by the text renderer. Static since it was global in SSCI.
*/
- int16 _scaledWidth;
+ static int16 _scaledWidth;
/**
* The size of the y-dimension of the coordinate system
- * used by the text renderer.
+ * used by the text renderer. Static since it was global in SSCI.
*/
- int16 _scaledHeight;
+ static int16 _scaledHeight;
/**
* The currently active font resource used to write text
@@ -392,11 +401,6 @@ public:
*/
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);
- /**
- * Creates a font bitmap with a title.
- */
- reg_t createTitledBitmap(const int16 width, const int16 height, const Common::Rect &textRect, const Common::String &text, const int16 foreColor, const int16 backColor, const int16 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, Common::String &title, const int16 titleForeColor, const int16 titleBackColor, const GuiResourceId titleFontId, const bool doScaling);
-
inline int scaleUpWidth(int value) const {
const int scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
return (value * scriptWidth + _scaledWidth - 1) / _scaledWidth;
@@ -472,6 +476,13 @@ public:
* `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.h b/engines/sci/graphics/view.h
index 91590208c1..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