/* 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 "mohawk/myst.h" #include "mohawk/myst_graphics.h" #include "mohawk/resource.h" #include "common/substream.h" #include "common/system.h" #include "common/textconsole.h" #include "engines/util.h" #include "graphics/fonts/ttf.h" #include "graphics/fontman.h" #include "graphics/palette.h" #include "graphics/scaler.h" #include "image/pict.h" namespace Mohawk { MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : GraphicsManager(), _vm(vm), _menuFont(nullptr) { _bmpDecoder = new MystBitmap(); _viewport = Common::Rect(544, 332); if (_vm->getFeatures() & GF_ME) { // High color initGraphics(_viewport.width(), _viewport.height(), nullptr); if (_vm->_system->getScreenFormat().bytesPerPixel == 1) error("Myst ME requires greater than 256 colors to run"); } else { // Paletted initGraphics(_viewport.width(), _viewport.height()); clearScreenPalette(); } _pixelFormat = _vm->_system->getScreenFormat(); // Initialize our buffer _backBuffer = new Graphics::Surface(); _backBuffer->create(_vm->_system->getWidth(), _vm->_system->getHeight(), _pixelFormat); _mainMenuBackupScreen.reset(new Graphics::Surface()); _mainMenuBackupScreenThumbnail.reset(new Graphics::Surface()); _mainMenuBackupBackBuffer.reset(new Graphics::Surface()); if (_vm->getFeatures() & GF_25TH) { const char *menuFontName = "NotoSans-ExtraBold.ttf"; #ifdef USE_FREETYPE2 int fontSize; if (_vm->getLanguage() == Common::PL_POL) { fontSize = 11; // The Polish diacritics need significantly more space, so we use a smaller font } else { fontSize = 16; } Common::SeekableReadStream *fontStream = SearchMan.createReadStreamForMember(menuFontName); if (fontStream) { _menuFont = Graphics::loadTTFFont(*fontStream, fontSize); delete fontStream; } else #endif { warning("Unable to open the menu font file '%s'", menuFontName); } } } MystGraphics::~MystGraphics() { delete _bmpDecoder; _backBuffer->free(); delete _backBuffer; delete _menuFont; } MohawkSurface *MystGraphics::decodeImage(uint16 id) { // We need to grab the image from the current stack archive, however, we don't know // if it's a PICT or WDIB resource. If it's Myst ME it's most likely a PICT, and if it's // original it's definitely a WDIB. However, Myst ME throws us another curve ball in // that PICT resources can contain WDIB's instead of PICT's. Common::SeekableReadStream *dataStream = nullptr; if (_vm->getFeatures() & GF_ME && _vm->hasResource(ID_PICT, id)) { // The PICT resource exists. However, it could still contain a MystBitmap // instead of a PICT image... dataStream = _vm->getResource(ID_PICT, id); } else { // No PICT, so the WDIB must exist. Let's go grab it. dataStream = _vm->getResource(ID_WDIB, id); } bool isPict = false; 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. dataStream->seek(512 + 10); // 512 byte pict header isPict = (dataStream->readUint32BE() == 0x001102FF); dataStream->seek(0); } MohawkSurface *mhkSurface = nullptr; if (isPict) { Image::PICTDecoder pict; if (!pict.loadStream(*dataStream)) error("Could not decode Myst ME PICT %d", id); delete dataStream; mhkSurface = new MohawkSurface(pict.getSurface()->convertTo(_pixelFormat)); } else { mhkSurface = _bmpDecoder->decodeImage(dataStream); if (_vm->getFeatures() & GF_ME) { mhkSurface->convertToTrueColor(); } else { remapSurfaceToSystemPalette(mhkSurface); } } assert(mhkSurface); applyImagePatches(id, mhkSurface); return mhkSurface; } void MystGraphics::applyImagePatches(uint16 id, const MohawkSurface *mhkSurface) const { // In the English ME version of the game, the instructions found on Stoneship // to open the vault are incorrect. They are: // Turn every one of [these switches to the] "off" position. // They should be: // Turn every one of [these switches to the] "on" position. // // Here we stomp over the "off" with an "on". // The fixed image was provided by dafioram in bug Trac#10115. if (id == 2019 && _vm->getFeatures() & GF_ME && _vm->getLanguage() == Common::EN_ANY) { static const byte markerSwitchInstructionsFixPic[] = { 0x1d, 0x1c, 0x19, 0x19, 0x19, 0x19, 0x1c, 0x19, 0x19, 0x17, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1e, 0x1e, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1c, 0x19, 0x19, 0x19, 0x1c, 0x19, 0x19, 0x19, 0x1c, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1d, 0x1e, 0x16, 0x0d, 0x0e, 0x12, 0x19, 0x19, 0x17, 0x10, 0x06, 0x05, 0x19, 0x19, 0x19, 0x1e, 0x1e, 0x10, 0x13, 0x1c, 0x11, 0x0d, 0x19, 0x12, 0x09, 0x16, 0x04, 0x18, 0x18, 0x19, 0x1e, 0x1a, 0x03, 0x1b, 0x1c, 0x17, 0x02, 0x15, 0x13, 0x00, 0x19, 0x06, 0x18, 0x19, 0x18, 0x1e, 0x1e, 0x01, 0x1b, 0x1c, 0x1b, 0x02, 0x15, 0x13, 0x00, 0x19, 0x07, 0x0a, 0x19, 0x18, 0x1e, 0x1c, 0x0c, 0x0e, 0x14, 0x0c, 0x0c, 0x19, 0x0b, 0x00, 0x19, 0x00, 0x08, 0x19, 0x19, 0x1e, 0x1c, 0x19, 0x14, 0x0f, 0x0f, 0x14, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x17, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1c, 0x1c, 0x1e, 0x19, 0x19, 0x19, 0x17, 0x19, 0x19, 0x17, 0x19, 0x19, 0x19, 0x19, 0x19 }; static const byte markerSwitchInstructionsFixPal[] = { 0x00, 0x00, 0x00, 0x10, 0x08, 0x08, 0x18, 0x10, 0x10, 0x28, 0x10, 0x08, 0x20, 0x18, 0x18, 0x28, 0x20, 0x20, 0x38, 0x20, 0x10, 0x30, 0x28, 0x20, 0x38, 0x30, 0x28, 0x40, 0x38, 0x28, 0x48, 0x38, 0x28, 0x48, 0x40, 0x30, 0x50, 0x48, 0x38, 0x50, 0x48, 0x40, 0x60, 0x50, 0x38, 0x68, 0x58, 0x40, 0x68, 0x58, 0x48, 0x70, 0x60, 0x50, 0x78, 0x68, 0x50, 0x80, 0x70, 0x50, 0x80, 0x78, 0x60, 0x88, 0x80, 0x60, 0x98, 0x90, 0x70, 0xb0, 0xa0, 0x78, 0xb8, 0xa8, 0x8d, 0xb8, 0xa8, 0x90, 0xb8, 0xb0, 0x88, 0xc0, 0xb8, 0x90, 0xd8, 0xcc, 0x98, 0xd0, 0xe0, 0xc8, 0xf0, 0xe4, 0xc8 }; Graphics::Surface fixSurf; fixSurf.create(15, 11, Graphics::PixelFormat::createFormatCLUT8()); fixSurf.copyRectToSurface(markerSwitchInstructionsFixPic, fixSurf.w, 0, 0, fixSurf.w, fixSurf.h); fixSurf.convertToInPlace(_pixelFormat, markerSwitchInstructionsFixPal); mhkSurface->getSurface()->copyRectToSurface(fixSurf, 171, 208, Common::Rect(fixSurf.w, fixSurf.h)); fixSurf.free(); } } void MystGraphics::copyImageSectionToScreen(uint16 image, Common::Rect src, Common::Rect dest) { Graphics::Surface *surface = findImage(image)->getSurface(); // Make sure the image is bottom aligned in the dest rect dest.top = dest.bottom - MIN(surface->h, dest.height()); // Convert from bitmap coordinates to surface coordinates uint16 top = surface->h - (src.top + MIN(surface->h, dest.height())); // Do not draw the top pixels if the image is too tall if (dest.height() > _viewport.height()) top += dest.height() - _viewport.height(); // Clip the destination rect to the screen if (dest.right > _vm->_system->getWidth() || dest.bottom > _vm->_system->getHeight()) dest.debugPrint(4, "Clipping destination rect to the screen"); dest.right = CLIP(dest.right, 0, _vm->_system->getWidth()); dest.bottom = CLIP(dest.bottom, 0, _vm->_system->getHeight()); uint16 width = MIN(surface->w, dest.width()); uint16 height = MIN(surface->h, dest.height()); // Clamp Width and Height to within src surface dimensions if (src.left + width > surface->w) width = surface->w - src.left; if (src.top + height > surface->h) height = surface->h - src.top; debug(3, "MystGraphics::copyImageSectionToScreen()"); debug(3, "\tImage: %d", image); debug(3, "\tsrc.left: %d", src.left); debug(3, "\tsrc.top: %d", src.top); debug(3, "\tdest.left: %d", dest.left); debug(3, "\tdest.top: %d", dest.top); debug(3, "\twidth: %d", width); debug(3, "\theight: %d", height); _vm->_system->copyRectToScreen(surface->getBasePtr(src.left, top), surface->pitch, dest.left, dest.top, width, height); } void MystGraphics::copyImageSectionToBackBuffer(uint16 image, Common::Rect src, Common::Rect dest) { MohawkSurface *mhkSurface = findImage(image); Graphics::Surface *surface = mhkSurface->getSurface(); if (image == 2258 && _vm->getFeatures() & GF_ME) { // In Myst ME, the image for the open red page brother door // when the special lights are on does not have the correct width. // We work around this issue by tweaking the destination rectangle // so it renders at the correct position. // The original executable does the same hack. dest.left += 49; } // Make sure the image is bottom aligned in the dest rect dest.top = dest.bottom - MIN(surface->h, dest.height()); // Convert from bitmap coordinates to surface coordinates uint16 top = surface->h - (src.top + MIN(surface->h, dest.height())); // Do not draw the top pixels if the image is too tall if (dest.height() > _viewport.height()) { top += dest.height() - _viewport.height(); } // Clip the destination rect to the screen if (dest.right > _vm->_system->getWidth() || dest.bottom > _vm->_system->getHeight()) dest.debugPrint(4, "Clipping destination rect to the screen"); dest.right = CLIP(dest.right, 0, _vm->_system->getWidth()); dest.bottom = CLIP(dest.bottom, 0, _vm->_system->getHeight()); uint16 width = MIN(surface->w, dest.width()); uint16 height = MIN(surface->h, dest.height()); // Clamp Width and Height to within src surface dimensions if (src.left + width > surface->w) width = surface->w - src.left; if (src.top + height > surface->h) height = surface->h - src.top; debug(3, "MystGraphics::copyImageSectionToBackBuffer()"); debug(3, "\tImage: %d", image); debug(3, "\tsrc.left: %d", src.left); debug(3, "\tsrc.top: %d", src.top); debug(3, "\tdest.left: %d", dest.left); debug(3, "\tdest.top: %d", dest.top); debug(3, "\twidth: %d", width); debug(3, "\theight: %d", height); for (uint16 i = 0; i < height; i++) memcpy(_backBuffer->getBasePtr(dest.left, i + dest.top), surface->getBasePtr(src.left, top + i), width * surface->format.bytesPerPixel); if (!(_vm->getFeatures() & GF_ME)) { // Make sure the palette is set assert(mhkSurface->getPalette()); memcpy(_palette, mhkSurface->getPalette(), 256 * 3); setPaletteToScreen(); } } void MystGraphics::copyImageToScreen(uint16 image, Common::Rect dest) { copyImageSectionToScreen(image, Common::Rect(544, 333), dest); } void MystGraphics::copyImageToBackBuffer(uint16 image, Common::Rect dest) { copyImageSectionToBackBuffer(image, Common::Rect(544, 333), dest); } void MystGraphics::copyBackBufferToScreen(Common::Rect r) { r.clip(_viewport); _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(r.left, r.top), _backBuffer->pitch, r.left, r.top, r.width(), r.height()); } void MystGraphics::runTransition(TransitionType type, Common::Rect rect, uint16 steps, uint16 delay) { switch (type) { case kTransitionLeftToRight: { debugC(kDebugView, "Left to Right"); uint16 step = (rect.right - rect.left) / steps; Common::Rect area = rect; for (uint i = 0; i < steps; i++) { area.left = rect.left + step * i; area.right = area.left + step; copyBackBufferToScreen(area); _vm->wait(delay); } if (area.right < rect.right) { area.left = area.right; area.right = rect.right; copyBackBufferToScreen(area); } } break; case kTransitionRightToLeft: { debugC(kDebugView, "Right to Left"); uint16 step = (rect.right - rect.left) / steps; Common::Rect area = rect; for (uint i = 0; i < steps; i++) { area.right = rect.right - step * i; area.left = area.right - step; copyBackBufferToScreen(area); _vm->wait(delay); } if (area.left > rect.left) { area.right = area.left; area.left = rect.left; copyBackBufferToScreen(area); } } break; case kTransitionSlideToLeft: debugC(kDebugView, "Slide to left"); transitionSlideToLeft(rect, steps, delay); break; case kTransitionSlideToRight: debugC(kDebugView, "Slide to right"); transitionSlideToRight(rect, steps, delay); break; case kTransitionDissolve: { debugC(kDebugView, "Dissolve"); for (int16 step = 0; step < 8; step++) { transitionDissolve(rect, step); _vm->doFrame(); } } break; case kTransitionTopToBottom: { debugC(kDebugView, "Top to Bottom"); uint16 step = (rect.bottom - rect.top) / steps; Common::Rect area = rect; for (uint i = 0; i < steps; i++) { area.top = rect.top + step * i; area.bottom = area.top + step; copyBackBufferToScreen(area); _vm->wait(delay); } if (area.bottom < rect.bottom) { area.top = area.bottom; area.bottom = rect.bottom; copyBackBufferToScreen(area); } } break; case kTransitionBottomToTop: { debugC(kDebugView, "Bottom to Top"); uint16 step = (rect.bottom - rect.top) / steps; Common::Rect area = rect; for (uint i = 0; i < steps; i++) { area.bottom = rect.bottom - step * i; area.top = area.bottom - step; copyBackBufferToScreen(area); _vm->wait(delay); } if (area.top > rect.top) { area.bottom = area.top; area.top = rect.top; copyBackBufferToScreen(area); } } break; case kTransitionSlideToTop: debugC(kDebugView, "Slide to top"); transitionSlideToTop(rect, steps, delay); break; case kTransitionSlideToBottom: debugC(kDebugView, "Slide to bottom"); transitionSlideToBottom(rect, steps, delay); break; case kTransitionPartToRight: { debugC(kDebugView, "Partial left to right"); transitionPartialToRight(rect, 75, 3); } break; case kTransitionPartToLeft: { debugC(kDebugView, "Partial right to left"); transitionPartialToLeft(rect, 75, 3); } break; case kTransitionCopy: copyBackBufferToScreen(rect); break; default: error("Unknown transition %d", type); } } void MystGraphics::transitionDissolve(Common::Rect rect, uint step) { static const bool pattern[][4][4] = { { { true, false, false, false }, { false, false, false, false }, { false, false, true, false }, { false, false, false, false } }, { { false, false, true, false }, { false, false, false, false }, { true, false, false, false }, { false, false, false, false } }, { { false, false, false, false }, { false, true, false, false }, { false, false, false, false }, { false, false, false, true } }, { { false, false, false, false }, { false, false, false, true }, { false, false, false, false }, { false, true, false, false } }, { { false, false, false, false }, { false, false, true, false }, { false, true, false, false }, { false, false, false, false } }, { { false, true, false, false }, { false, false, false, false }, { false, false, false, false }, { false, false, true, false } }, { { false, false, false, false }, { true, false, false, false }, { false, false, false, true }, { false, false, false, false } }, { { false, false, false, true }, { false, false, false, false }, { false, false, false, false }, { true, false, false, false } } }; rect.clip(_viewport); Graphics::Surface *screen = _vm->_system->lockScreen(); for (uint16 y = rect.top; y < rect.bottom; y++) { const bool *linePattern = pattern[step][y % 4]; if (!linePattern[0] && !linePattern[1] && !linePattern[2] && !linePattern[3]) continue; for (uint16 x = rect.left; x < rect.right; x++) { if (linePattern[x % 4]) { switch (_pixelFormat.bytesPerPixel) { case 1: *((byte *)screen->getBasePtr(x, y)) = *((const byte *)_backBuffer->getBasePtr(x, y)); break; case 2: *((uint16 *)screen->getBasePtr(x, y)) = *((const uint16 *)_backBuffer->getBasePtr(x, y)); break; case 4: *((uint32 *)screen->getBasePtr(x, y)) = *((const uint32 *)_backBuffer->getBasePtr(x, y)); break; } } } } _vm->_system->unlockScreen(); } void MystGraphics::transitionSlideToLeft(Common::Rect rect, uint16 steps, uint16 delay) { rect.clip(_viewport); uint32 stepWidth = (rect.right - rect.left) / steps; Common::Rect srcRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom); Common::Rect dstRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom); for (uint step = 1; step <= steps; step++) { dstRect.right = dstRect.left + step * stepWidth; srcRect.left = srcRect.right - step * stepWidth; _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); _vm->wait(delay); } if (dstRect.right != rect.right) { copyBackBufferToScreen(rect); } } void MystGraphics::transitionSlideToRight(Common::Rect rect, uint16 steps, uint16 delay) { rect.clip(_viewport); uint32 stepWidth = (rect.right - rect.left) / steps; Common::Rect srcRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom); Common::Rect dstRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom); for (uint step = 1; step <= steps; step++) { dstRect.left = dstRect.right - step * stepWidth; srcRect.right = srcRect.left + step * stepWidth; _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); _vm->wait(delay); } if (dstRect.left != rect.left) { copyBackBufferToScreen(rect); } } void MystGraphics::transitionSlideToTop(Common::Rect rect, uint16 steps, uint16 delay) { rect.clip(_viewport); uint32 stepWidth = (rect.bottom - rect.top) / steps; Common::Rect srcRect = Common::Rect(rect.left, rect.bottom, rect.right, rect.bottom); Common::Rect dstRect = Common::Rect(rect.left, rect.top, rect.right, rect.top); for (uint step = 1; step <= steps; step++) { dstRect.bottom = dstRect.top + step * stepWidth; srcRect.top = srcRect.bottom - step * stepWidth; _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); _vm->wait(delay); } if (dstRect.bottom < rect.bottom) { copyBackBufferToScreen(rect); } } void MystGraphics::transitionSlideToBottom(Common::Rect rect, uint16 steps, uint16 delay) { rect.clip(_viewport); uint32 stepWidth = (rect.bottom - rect.top) / steps; Common::Rect srcRect = Common::Rect(rect.left, rect.top, rect.right, rect.top); Common::Rect dstRect = Common::Rect(rect.left, rect.bottom, rect.right, rect.bottom); for (uint step = 1; step <= steps; step++) { dstRect.top = dstRect.bottom - step * stepWidth; srcRect.bottom = srcRect.top + step * stepWidth; _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); _vm->wait(delay); } if (dstRect.top > rect.top) { copyBackBufferToScreen(rect); } } void MystGraphics::transitionPartialToRight(Common::Rect rect, uint32 width, uint32 steps) { rect.clip(_viewport); uint32 stepWidth = width / steps; Common::Rect srcRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom); Common::Rect dstRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom); for (uint step = 1; step <= steps; step++) { dstRect.right = dstRect.left + step * stepWidth; srcRect.left = srcRect.right - step * stepWidth; _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); _vm->doFrame(); } copyBackBufferToScreen(rect); } void MystGraphics::transitionPartialToLeft(Common::Rect rect, uint32 width, uint32 steps) { rect.clip(_viewport); uint32 stepWidth = width / steps; Common::Rect srcRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom); Common::Rect dstRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom); for (uint step = 1; step <= steps; step++) { dstRect.left = dstRect.right - step * stepWidth; srcRect.right = srcRect.left + step * stepWidth; _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); _vm->doFrame(); } copyBackBufferToScreen(rect); } void MystGraphics::drawRect(Common::Rect rect, RectState state) { rect.clip(_viewport); // Useful with debugging. Shows where hotspots are on the screen and whether or not they're active. if (!rect.isValidRect() || rect.width() == 0 || rect.height() == 0) return; Graphics::Surface *screen = _vm->_system->lockScreen(); if (state == kRectEnabled) screen->frameRect(rect, (_vm->getFeatures() & GF_ME) ? _pixelFormat.RGBToColor(0, 255, 0) : 250); else if (state == kRectUnreachable) screen->frameRect(rect, (_vm->getFeatures() & GF_ME) ? _pixelFormat.RGBToColor(0, 0, 255) : 252); else screen->frameRect(rect, (_vm->getFeatures() & GF_ME) ? _pixelFormat.RGBToColor(255, 0, 0) : 249); _vm->_system->unlockScreen(); } void MystGraphics::drawLine(const Common::Point &p1, const Common::Point &p2, uint32 color) { _backBuffer->drawLine(p1.x, p1.y, p2.x, p2.y, color); } void MystGraphics::fadeToBlack() { // This is only for the demo assert(!(_vm->getFeatures() & GF_ME)); // Linear fade in 64 steps for (int i = 63; i >= 0; i--) { byte palette[256 * 3]; byte *src = _palette; byte *dst = palette; for (uint j = 0; j < sizeof(palette); j++) *dst++ = *src++ * i / 64; _vm->_system->getPaletteManager()->setPalette(palette, 0, 256); _vm->doFrame(); } } void MystGraphics::fadeFromBlack() { // This is only for the demo assert(!(_vm->getFeatures() & GF_ME)); copyBackBufferToScreen(_viewport); // Linear fade in 64 steps for (int i = 0; i < 64; i++) { byte palette[256 * 3]; byte *src = _palette; byte *dst = palette; for (uint j = 0; j < sizeof(palette); j++) *dst++ = *src++ * i / 64; _vm->_system->getPaletteManager()->setPalette(palette, 0, 256); _vm->doFrame(); } // Set the full palette _vm->_system->getPaletteManager()->setPalette(_palette, 0, 256); } void MystGraphics::clearScreenPalette() { // Set the palette to all black byte palette[256 * 3]; memset(palette, 0, sizeof(palette)); _vm->_system->getPaletteManager()->setPalette(palette, 0, 256); } void MystGraphics::remapSurfaceToSystemPalette(MohawkSurface *mhkSurface) { // Entries [0, 9] of the palette static const byte lowPalette[] = { 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xDC, 0xC0, 0xA6, 0xCA, 0xF0 }; // Entries [246, 255] of the palette static const byte highPalette[] = { 0xFF, 0xFB, 0xF0, 0xA0, 0xA0, 0xA4, 0x80, 0x80, 0x80, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; byte *originalPalette = mhkSurface->getPalette(); // 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() { _vm->_system->getPaletteManager()->setPalette(_palette, 0, 256); } void MystGraphics::saveStateForMainMenu() { Graphics::Surface *screen = _vm->_system->lockScreen(); _mainMenuBackupScreen->copyFrom(*screen); _vm->_system->unlockScreen(); // Create a thumbnail of the screen that will be used when saving from the main menu createThumbnailFromScreen(_mainMenuBackupScreenThumbnail.get()); _mainMenuBackupBackBuffer->copyFrom(*_backBuffer); } void MystGraphics::restoreStateForMainMenu() { _vm->_system->copyRectToScreen(_mainMenuBackupScreen->getPixels(), _mainMenuBackupScreen->pitch, 0, 0, _mainMenuBackupScreen->w, _mainMenuBackupScreen->h); _backBuffer->copyFrom(*_mainMenuBackupBackBuffer); _mainMenuBackupScreen->free(); _mainMenuBackupScreenThumbnail->free(); _mainMenuBackupBackBuffer->free(); } Graphics::Surface *MystGraphics::getThumbnailForMainMenu() const { return _mainMenuBackupScreenThumbnail.get(); } void MystGraphics::drawText(uint16 image, const Common::U32String &text, const Common::Rect &dest, uint8 r, uint8 g, uint8 b, Graphics::TextAlign align, int16 deltaY) { MohawkSurface *mhkSurface = findImage(image); Graphics::Surface *surface = mhkSurface->getSurface(); const Graphics::Font *font = getMenuFont(); font->drawString(surface, text, dest.left, dest.top + deltaY, dest.width(), surface->format.RGBToColor(r, g, b), align); } Common::Rect MystGraphics::getTextBoundingBox(const Common::U32String &text, const Common::Rect &dest, Graphics::TextAlign align) { const Graphics::Font *font = getMenuFont(); return font->getBoundingBox(text, dest.left, dest.top, dest.width(), align); } const Graphics::Font *MystGraphics::getMenuFont() const { const Graphics::Font *font; if (_menuFont) { font = _menuFont; } else { font = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont); } return font; } void MystGraphics::replaceImageWithRect(uint16 destImage, uint16 sourceImage, const Common::Rect &sourceRect) { MohawkSurface *sourceSurface = findImage(sourceImage); const Graphics::Surface sourceArea = sourceSurface->getSurface()->getSubArea(sourceRect); Graphics::Surface *replacementSurface = new Graphics::Surface(); replacementSurface->copyFrom(sourceArea); MohawkSurface *destSurface = new MohawkSurface(replacementSurface, nullptr, 0, 0); addImageToCache(destImage, destSurface); } void MystGraphics::clearScreen() { if (_vm->getFeatures() & GF_ME) _vm->_system->fillScreen(_pixelFormat.RGBToColor(0, 0, 0)); else _vm->_system->fillScreen(0); } } // End of namespace Mohawk