MOHAWK: Remap bitmaps not to use undefined colors

The Spanish version of Myst has bitmaps that use palette indices in the system
reserved range. Affected pixels previously used colors from the Windows system
palette instead of the bitmap's own palette, resulting in visual glitches.

Bitmaps are now remapped to the screen palette which is made of the Windows
reserved palette and part of the bitmap palette. The original engine used GDI's
StretchDIBits with DIB_RGB_COLORS to achieve the same result.

Fixes #7153.
This commit is contained in:
Bastien Bouclet 2016-06-26 07:20:21 +02:00
parent cedcdbc48d
commit 3391c726cf
5 changed files with 81 additions and 28 deletions

View File

@ -34,8 +34,8 @@
#include "graphics/wincursor.h"
#ifdef ENABLE_MYST
#include "mohawk/bitmap.h"
#include "mohawk/myst.h"
#include "mohawk/myst_graphics.h"
#endif
namespace Mohawk {
@ -86,11 +86,9 @@ void DefaultCursorManager::setCursor(uint16 id) {
#ifdef ENABLE_MYST
MystCursorManager::MystCursorManager(MohawkEngine_Myst *vm) : _vm(vm) {
_bmpDecoder = new MystBitmap();
}
MystCursorManager::~MystCursorManager() {
delete _bmpDecoder;
}
void MystCursorManager::showCursor() {
@ -111,17 +109,18 @@ void MystCursorManager::setCursor(uint16 id) {
return;
}
// Both Myst and Myst ME use the "MystBitmap" format for cursor images.
MohawkSurface *mhkSurface = _bmpDecoder->decodeImage(_vm->getResource(ID_WDIB, id));
Graphics::Surface *surface = mhkSurface->getSurface();
Common::SeekableReadStream *clrcStream = _vm->getResource(ID_CLRC, id);
uint16 hotspotX = clrcStream->readUint16LE();
uint16 hotspotY = clrcStream->readUint16LE();
delete clrcStream;
// Both Myst and Myst ME use the "MystBitmap" format for cursor images.
MohawkSurface *mhkSurface = _vm->_gfx->findImage(id);
Graphics::Surface *surface = mhkSurface->getSurface();
// Myst ME stores some cursors as 24bpp images instead of 8bpp
if (surface->format.bytesPerPixel == 1) {
CursorMan.replaceCursor(surface->getPixels(), surface->w, surface->h, hotspotX, hotspotY, 0);
CursorMan.replaceCursor(surface->getPixels(), surface->w, surface->h, hotspotX, hotspotY, 255);
// We're using the screen palette for the original game, but we need
// to use this for any 8bpp cursor in ME.
@ -133,7 +132,6 @@ void MystCursorManager::setCursor(uint16 id) {
}
_vm->_needsUpdate = true;
delete mhkSurface;
}
void MystCursorManager::setDefaultCursor() {

View File

@ -102,7 +102,6 @@ enum {
};
class MohawkEngine_Myst;
class MystBitmap;
// The cursor manager for Myst
// Uses WDIB + CLRC resources
@ -119,7 +118,6 @@ public:
private:
MohawkEngine_Myst *_vm;
MystBitmap *_bmpDecoder;
};
#endif // ENABLE_MYST

View File

@ -74,6 +74,10 @@ public:
// Free all surfaces in the cache
void clearCache();
// findImage will search the cache to find the image.
// If not found, it will call decodeImage to get a new one.
MohawkSurface *findImage(uint16 id);
void preloadImage(uint16 image);
virtual void setPalette(uint16 id);
void copyAnimImageToScreen(uint16 image, int left = 0, int top = 0);
@ -85,10 +89,6 @@ public:
protected:
void copyAnimImageSectionToScreen(MohawkSurface *image, Common::Rect src, Common::Rect dest);
// findImage will search the cache to find the image.
// If not found, it will call decodeImage to get a new one.
MohawkSurface *findImage(uint16 id);
// decodeImage will always return a new image.
virtual MohawkSurface *decodeImage(uint16 id) = 0;
virtual Common::Array<MohawkSurface *> decodeImages(uint16 id);

View File

@ -47,8 +47,7 @@ MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : GraphicsManager(), _vm(vm) {
} else {
// Paletted
initGraphics(_viewport.width(), _viewport.height(), true);
setBasePalette();
setPaletteToScreen();
clearScreenPalette();
}
_pixelFormat = _vm->_system->getScreenFormat();
@ -86,7 +85,7 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) {
bool isPict = false;
if (_vm->getFeatures() & GF_ME) {
if ((_vm->getFeatures() & GF_ME) && dataStream->size() > 512 + 10 + 4) {
// Here we detect whether it's really a PICT or a WDIB. Since a MystBitmap
// would be compressed, there's no way to detect for the BM without a hack.
// So, we search for the PICT version opcode for detection.
@ -109,8 +108,11 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) {
} else {
mhkSurface = _bmpDecoder->decodeImage(dataStream);
if (_vm->getFeatures() & GF_ME)
if (_vm->getFeatures() & GF_ME) {
mhkSurface->convertToTrueColor();
} else {
remapSurfaceToSystemPalette(mhkSurface);
}
}
assert(mhkSurface);
@ -204,7 +206,7 @@ void MystGraphics::copyImageSectionToBackBuffer(uint16 image, Common::Rect src,
if (!(_vm->getFeatures() & GF_ME)) {
// Make sure the palette is set
assert(mhkSurface->getPalette());
memcpy(_palette + 10 * 3, mhkSurface->getPalette() + 10 * 3, (256 - 10 * 2) * 3);
memcpy(_palette, mhkSurface->getPalette(), 256 * 3);
setPaletteToScreen();
}
}
@ -703,10 +705,10 @@ void MystGraphics::clearScreenPalette() {
_vm->_system->getPaletteManager()->setPalette(palette, 0, 256);
}
void MystGraphics::setBasePalette() {
void MystGraphics::remapSurfaceToSystemPalette(MohawkSurface *mhkSurface) {
// Entries [0, 9] of the palette
static const byte lowPalette[] = {
0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00,
0x80, 0x00, 0x00,
0x00, 0x80, 0x00,
0x80, 0x80, 0x00,
@ -729,15 +731,68 @@ void MystGraphics::setBasePalette() {
0x00, 0x00, 0xFF,
0xFF, 0x00, 0xFF,
0x00, 0xFF, 0xFF,
0x00, 0x00, 0x00
0xFF, 0xFF, 0xFF
};
// Note that 0 and 255 are different from normal Windows.
// Myst seems to hack that to white, resp. black (probably for Mac compat).
byte *originalPalette = mhkSurface->getPalette();
memcpy(_palette, lowPalette, sizeof(lowPalette));
memset(_palette + sizeof(lowPalette), 0, sizeof(_palette) - sizeof(lowPalette) - sizeof(highPalette));
memcpy(_palette + sizeof(_palette) - sizeof(highPalette), highPalette, sizeof(highPalette));
// The target palette is made of the Windows reserved palette, and colors 10 to 245
// of the bitmap palette. Entries 0 to 9 and 246 to 255 of the bitmap palette are
// discarded.
byte targetPalette[256 * 3];
memcpy(targetPalette, lowPalette, sizeof(lowPalette));
memcpy(targetPalette + sizeof(lowPalette), originalPalette + sizeof(lowPalette), sizeof(_palette) - sizeof(lowPalette) - sizeof(highPalette));
memcpy(targetPalette + sizeof(_palette) - sizeof(highPalette), highPalette, sizeof(highPalette));
// Remap the discarded entries from the bitmap palette using the target palette.
byte lowColorMap[ARRAYSIZE(lowPalette) / 3];
byte highColorMap[ARRAYSIZE(highPalette) / 3];
for (uint i = 0; i < ARRAYSIZE(lowColorMap); i++) {
uint colorIndex = 3 * i;
byte red = originalPalette[colorIndex + 0];
byte green = originalPalette[colorIndex + 1];
byte blue = originalPalette[colorIndex + 2];
lowColorMap[i] = getColorIndex(targetPalette, red, green, blue);
}
for (uint i = 0; i < ARRAYSIZE(highColorMap); i++) {
uint colorIndex = 3 * (i + 246);
byte red = originalPalette[colorIndex + 0];
byte green = originalPalette[colorIndex + 1];
byte blue = originalPalette[colorIndex + 2];
highColorMap[i] = getColorIndex(targetPalette, red, green, blue);
}
// Replace the original palette with the target palette
memcpy(originalPalette, targetPalette, sizeof(targetPalette));
// Remap the pixel data to the target palette
Graphics::Surface *surface = mhkSurface->getSurface();
byte *pixels = (byte *) surface->getPixels();
for (int i = 0; i < surface->w * surface->h; i++) {
if (pixels[i] < ARRAYSIZE(lowColorMap)) {
pixels[i] = lowColorMap[pixels[i]];
} else if (pixels[i] >= 246) {
pixels[i] = highColorMap[pixels[i] - 246];
}
}
}
byte MystGraphics::getColorIndex(const byte *palette, byte red, byte green, byte blue) {
for (uint i = 0; i < 256; i++) {
if (palette[(3 * i) + 0] == red && palette[(3 * i) + 1] == green && palette[(3 * i) + 2] == blue) {
return i;
}
}
// GDI actually chooses the nearest color if no exact match is found,
// but this should not happen in Myst
debug(1, "Color (%d, %d, %d) not in target palette", red, green, blue);
return 0;
}
void MystGraphics::setPaletteToScreen() {

View File

@ -56,7 +56,6 @@ public:
void fadeFromBlack();
void clearScreenPalette();
void setBasePalette();
void setPaletteToScreen();
const byte *getPalette() const { return _palette; }
@ -86,6 +85,9 @@ private:
void transitionSlideToBottom(Common::Rect rect, uint16 steps, uint16 delay);
void transitionPartialToRight(Common::Rect rect, uint32 width, uint32 steps);
void transitionPartialToLeft(Common::Rect rect, uint32 width, uint32 steps);
void remapSurfaceToSystemPalette(MohawkSurface *mhkSurface);
byte getColorIndex(const byte *palette, byte red, byte green, byte blue);
};
} // End of namespace Mohawk