/* 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. * * Additional copyright for this file: * Copyright (C) 1995 Presto Studios, Inc. * * 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 3 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, see . * */ #include "common/config-manager.h" #include "common/fs.h" #include "common/str-array.h" #include "common/system.h" #include "common/compression/unzip.h" #include "graphics/cursorman.h" #include "graphics/font.h" #include "graphics/paletteman.h" #include "graphics/surface.h" #include "graphics/wincursor.h" #include "graphics/fonts/ttf.h" #include "image/bmp.h" #include "buried/buried.h" #include "buried/graphics.h" #include "buried/window.h" namespace Buried { GraphicsManager::GraphicsManager(BuriedEngine *vm) : _vm(vm) { _curCursor = kCursorNone; _mouseMoved = false; _needsErase = false; setCursor(kCursorArrow); CursorMan.showMouse(true); _screen = new Graphics::Surface(); _screen->create(640, 480, g_system->getScreenFormat()); if (_vm->isTrueColor()) { // No palette to deal with _palette = nullptr; } else { // Grab the palette from our EXE bitmap _palette = createDefaultPalette(); // Then apply it. The only time we'll use this call even. g_system->getPaletteManager()->setPalette(_palette, 0, 256); } } GraphicsManager::~GraphicsManager() { _screen->free(); delete _screen; delete[] _palette; } Graphics::Font *GraphicsManager::createFont(int size, bool bold) const { // MS Gothic for the Japanese version // Arial or Arial Bold for everything else if (_vm->getLanguage() == Common::JA_JPN) return createMSGothicFont(size, bold); return createArialFont(size, bold); } Graphics::Font *GraphicsManager::createArialFont(int size, bool bold) const { const char *defaultBaseName = bold ? "arialbd.ttf" : "arial.ttf"; Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(defaultBaseName); // Map the heights needed to point sizes if (bold) { if (size != 20) error("Unhandled Arial Bold height %d", size); size = 12; } else { switch (size) { case 12: case 13: size = 7; break; case 14: size = 8; break; default: error("Unhandled Arial height %d", size); } } // TODO: Make the monochrome mode optional // Win3.1 obviously only had raster fonts, but BIT Win3.1 will render // with the TrueType font on Win7/Win8 (at least) // FIXME: The font is slightly off from the original... need to check. Sizes are right though! Graphics::Font *font; if (stream) { font = Graphics::loadTTFFont(stream, DisposeAfterUse::YES, size, Graphics::kTTFSizeModeCharacter, 96, 96, _vm->isTrueColor() ? Graphics::kTTFRenderModeLight : Graphics::kTTFRenderModeMonochrome); } else { const char *fname; if (bold) fname = "LiberationSans-Bold.ttf"; else fname = "LiberationSans-Regular.ttf"; font = Graphics::loadTTFFontFromArchive(fname, size, Graphics::kTTFSizeModeCharacter, 96, 96, _vm->isTrueColor() ? Graphics::kTTFRenderModeLight : Graphics::kTTFRenderModeMonochrome); } if (!font) error("Failed to load Arial%s font", bold ? " Bold" : ""); return font; } void GraphicsManager::toggleCursor(bool show) { CursorMan.showMouse(show); } Cursor GraphicsManager::setCursor(Cursor newCursor) { // Don't set the cursor again if (newCursor == _curCursor) return _curCursor; Cursor oldCursor = _curCursor; Graphics::Cursor *cursor = nullptr; Graphics::WinCursorGroup *cursorGroup = nullptr; if (newCursor == kCursorArrow) { cursor = Graphics::makeDefaultWinCursor(); } else if (newCursor == kCursorWait) { cursor = Graphics::makeBusyWinCursor(); } else { cursorGroup = _vm->getCursorGroup(newCursor); if (!cursorGroup) return kCursorNone; cursor = cursorGroup->cursors[0].cursor; } if (!cursor) error("Failed to find cursor %d", newCursor); CursorMan.replaceCursor(cursor); if (cursorGroup) delete cursorGroup; else delete cursor; _curCursor = newCursor; return oldCursor; } Graphics::Surface *GraphicsManager::getBitmap(uint32 bitmapID) { Common::SeekableReadStream *stream = _vm->getBitmapStream(bitmapID); if (!stream) error("Could not find bitmap %d", bitmapID); Graphics::Surface *surface = getBitmap(stream); if (!surface) error("Failed to decode bitmap %d", bitmapID); return surface; } Graphics::Surface *GraphicsManager::getBitmap(const Common::Path &fileName, bool required) { Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName); if (!stream) { if (required) error("Could not find bitmap '%s'", fileName.toString(Common::Path::kNativeSeparator).c_str()); return nullptr; } Graphics::Surface *surface = getBitmap(stream); if (!surface) { if (required) error("Failed to decode bitmap '%s'", fileName.toString(Common::Path::kNativeSeparator).c_str()); return nullptr; } return surface; } Graphics::Surface *GraphicsManager::getBitmap(Common::SeekableReadStream *stream) { Image::BitmapDecoder decoder; if (!decoder.loadStream(*stream)) { delete stream; return nullptr; } delete stream; // Convert to the screen format, if required if (decoder.getSurface()->format != g_system->getScreenFormat()) { assert(_vm->isTrueColor()); return decoder.getSurface()->convertTo(g_system->getScreenFormat(), decoder.getPalette().data(), decoder.getPalette().size()); } // Remap the palette, if required if (!_vm->isTrueColor() && memcmp(decoder.getPalette().data() + 3, getDefaultPalette() + 3, 256 - 6) != 0) return remapPalettedFrame(decoder.getSurface(), decoder.getPalette().data()); // Just copy the frame Graphics::Surface *surface = new Graphics::Surface(); surface->copyFrom(*decoder.getSurface()); return surface; } uint32 GraphicsManager::getColor(byte r, byte g, byte b) { if (_vm->isTrueColor()) return g_system->getScreenFormat().RGBToColor(r, g, b); // Find the best match color int diff = 0x7FFFFFFF; byte best = 0; for (uint i = 0; i < 256 && diff > 0; i++) { int rDiff = (int)_palette[i * 3] - (int)r; int gDiff = (int)_palette[i * 3 + 1] - (int)g; int bDiff = (int)_palette[i * 3 + 2] - (int)b; int curDiff = rDiff * rDiff + gDiff * gDiff + bDiff * bDiff; if (curDiff < diff) { best = i; diff = curDiff; } } return best; } void GraphicsManager::invalidateRect(const Common::Rect &rect, bool erase) { if (_dirtyRect.isEmpty()) _dirtyRect = rect; else _dirtyRect.extend(rect); _needsErase |= erase; } void GraphicsManager::updateScreen(bool drawWindows) { bool shouldUpdateScreen = _mouseMoved; _mouseMoved = false; if (!_dirtyRect.isEmpty()) { // Draw the main window, which will draw its children if (drawWindows) _vm->_mainWindow->updateWindow(); // Copy just that rect g_system->copyRectToScreen(_screen->getBasePtr(_dirtyRect.left, _dirtyRect.top), _screen->pitch, _dirtyRect.left, _dirtyRect.top, _dirtyRect.width(), _dirtyRect.height()); // Empty out the dirty rect _dirtyRect = Common::Rect(); // Definitely update shouldUpdateScreen = true; } if (shouldUpdateScreen) g_system->updateScreen(); _needsErase = false; } void GraphicsManager::blit(const Graphics::Surface *surface, int x, int y) { assert(surface->format.bytesPerPixel == _screen->format.bytesPerPixel); for (int i = 0; i < surface->h; i++) memcpy(_screen->getBasePtr(x, y + i), surface->getBasePtr(0, i), surface->w * surface->format.bytesPerPixel); } void GraphicsManager::blit(const Graphics::Surface *surface, int x, int y, uint width, uint height) { assert(surface->format.bytesPerPixel == _screen->format.bytesPerPixel); for (uint i = 0; i < height; i++) memcpy(_screen->getBasePtr(x, y + i), surface->getBasePtr(0, i), width * surface->format.bytesPerPixel); } void GraphicsManager::blit(const Graphics::Surface *surface, const Common::Rect &srcRect, const Common::Rect &dstRect) { assert(surface->format.bytesPerPixel == _screen->format.bytesPerPixel); uint width = MIN(srcRect.width(), dstRect.width()); uint height = MIN(srcRect.height(), dstRect.height()); for (uint i = 0; i < height; i++) memcpy(_screen->getBasePtr(dstRect.left, dstRect.top + i), surface->getBasePtr(srcRect.left, srcRect.top + i), width * surface->format.bytesPerPixel); } void GraphicsManager::fillRect(const Common::Rect &rect, uint32 color) { _screen->fillRect(rect, color); } void GraphicsManager::keyBlit(Graphics::Surface *dst, int xDst, int yDst, int w, int h, const Graphics::Surface *src, uint xSrc, uint ySrc, uint32 transColor) { assert(dst->format.bytesPerPixel == src->format.bytesPerPixel); w = MIN(src->w, w); h = MIN(src->h, h); Common::Rect srcRect(xSrc, ySrc, xSrc + w, ySrc + h); Common::Rect dstRect(xDst, yDst, xDst + w, yDst + h); if (dst->clip(srcRect, dstRect)) dst->copyRectToSurfaceWithKey(*src, dstRect.left, dstRect.top, srcRect, transColor); } void GraphicsManager::keyBlit(Graphics::Surface *dst, int xDst, int yDst, int w, int h, const Graphics::Surface *src, uint xSrc, uint ySrc, byte rTrans, byte gTrans, byte bTrans) { if (_vm->isTrueColor()) { keyBlit(dst, xDst, yDst, w, h, src, xSrc, ySrc, getColor(rTrans, gTrans, bTrans)); } else { // Find the palette index of the color int paletteIndex = -1; for (int i = 0; i < 256; i++) { if (_palette[i * 3] == rTrans && _palette[i * 3 + 1] == gTrans && _palette[i * 3 + 2] == bTrans) { paletteIndex = i; break; } } assert(paletteIndex >= 0); keyBlit(dst, xDst, yDst, w, h, src, xSrc, ySrc, paletteIndex); } } void GraphicsManager::opaqueTransparentBlit(Graphics::Surface *dst, int xDst, int yDst, int w, int h, const Graphics::Surface *src, uint xSrc, uint ySrc, int opacityValue, byte rTrans, byte gTrans, byte bTrans) { if (_vm->isTrueColor()) { uint32 transColor = getColor(rTrans, gTrans, bTrans); for (int y = 0; y < h; y++) { if (y + yDst < dst->h && y + yDst >= 0) { for (int x = 0; x < w; x++) { if (x + xDst < dst->w && x + xDst >= 0) { uint32 srcColor; if (src->format.bytesPerPixel == 2) srcColor = *((const uint16 *)src->getBasePtr(x + xSrc, y + ySrc)); else srcColor = *((const uint32 *)src->getBasePtr(x + xSrc, y + ySrc)); if (srcColor == transColor) continue; int srcCycles, dstCycles; switch (opacityValue) { case 50: srcCycles = 1; dstCycles = 3; break; case 85: srcCycles = 17; dstCycles = 3; break; default: srcCycles = 1; dstCycles = 0; break; } byte rSrc, gSrc, bSrc; g_system->getScreenFormat().colorToRGB(srcColor, rSrc, gSrc, bSrc); uint32 dstColor; if (dst->format.bytesPerPixel == 2) dstColor = *((uint16 *)dst->getBasePtr(x + xDst, y + yDst)); else dstColor = *((uint32 *)dst->getBasePtr(x + xDst, y + yDst)); byte rDst, gDst, bDst; g_system->getScreenFormat().colorToRGB(dstColor, rDst, gDst, bDst); byte r = (((int)rSrc * srcCycles) + ((int)rDst * dstCycles)) / (srcCycles + dstCycles); byte g = (((int)gSrc * srcCycles) + ((int)gDst * dstCycles)) / (srcCycles + dstCycles); byte b = (((int)bSrc * srcCycles) + ((int)bDst * dstCycles)) / (srcCycles + dstCycles); uint32 color = g_system->getScreenFormat().RGBToColor(r, g, b); if (dst->format.bytesPerPixel == 2) *((uint16 *)dst->getBasePtr(x + xDst, y + yDst)) = color; else *((uint32 *)dst->getBasePtr(x + xDst, y + yDst)) = color; } } } } } else { keyBlit(dst, xDst, yDst, w, h, src, xSrc, ySrc, rTrans, gTrans, bTrans); } } bool GraphicsManager::checkPointAgainstMaskedBitmap(const Graphics::Surface *bitmap, int x, int y, const Common::Point &point, byte rTrans, byte gTrans, byte bTrans) { if (_vm->isTrueColor()) { uint32 transColor = getColor(rTrans, gTrans, bTrans); uint32 color; if (bitmap->format.bytesPerPixel == 2) color = *((const uint16 *)bitmap->getBasePtr(point.x - x, point.y - y)); else color = *((const uint32 *)bitmap->getBasePtr(point.x - x, point.y - y)); return transColor != color; } else { // Find the palette index of the color int paletteIndex = -1; for (int i = 0; i < 256; i++) { if (_palette[i * 3] == rTrans && _palette[i * 3 + 1] == gTrans && _palette[i * 3 + 2] == bTrans) { paletteIndex = i; break; } } assert(paletteIndex >= 0); return *((const byte *)bitmap->getBasePtr(point.x - x, point.y - y)) != paletteIndex; } } byte *GraphicsManager::createDefaultPalette() const { Common::SeekableReadStream *stream = _vm->getBitmapStream(700); if (!stream) error("Couldn't find bitmap 700"); stream->skip(14); if (stream->readUint16LE() != 8) error("Trying to load palette from non-8bpp image 700"); stream->skip(16); uint32 colorsUsed = stream->readUint32LE(); if (colorsUsed != 0 && colorsUsed != 256) error("Bitmap 700 is missing a full palette"); stream->skip(4); byte *palette = new byte[256 * 3]; byte *ptr = palette; for (uint32 i = 0; i < 256; i++) { ptr[2] = stream->readByte(); ptr[1] = stream->readByte(); ptr[0] = stream->readByte(); stream->readByte(); ptr += 3; } delete stream; // Make sure the first entry is black and the last is white palette[0 * 3] = palette[0 * 3 + 1] = palette[0 * 3 + 2] = 0x00; palette[255 * 3] = palette[255 * 3 + 1] = palette[255 * 3 + 2] = 0xFF; return palette; } Graphics::Surface *GraphicsManager::remapPalettedFrame(const Graphics::Surface *frame, const byte *palette) { // This is pretty much the same as the Cinepak one // It seems to work for the one video I know that needs it (SWLOGO.BTV) // TODO: Merge some of this with getColor() byte palMap[256]; const byte *screenPal = getDefaultPalette(); for (int i = 0; i < 256; i++) { int r = palette[i * 3]; int g = palette[i * 3 + 1]; int b = palette[i * 3 + 2]; int diff = 0x7FFFFFFF; byte result = 0; for (int j = 0; j < 256; j++) { int bDiff = b - (int)screenPal[j * 3 + 2]; int curDiffB = diff - (bDiff * bDiff); if (curDiffB > 0) { int gDiff = g - (int)screenPal[j * 3 + 1]; int curDiffG = curDiffB - (gDiff * gDiff); if (curDiffG > 0) { int rDiff = r - (int)screenPal[j * 3]; int curDiffR = curDiffG - (rDiff * rDiff); if (curDiffR > 0) { diff -= curDiffR; result = j; if (diff == 0) break; } } } } palMap[i] = result; } Graphics::Surface *convertedSurface = new Graphics::Surface(); convertedSurface->create(frame->w, frame->h, frame->format); for (int y = 0; y < frame->h; y++) { for (int x = 0; x < frame->w; x++) *((byte *)convertedSurface->getBasePtr(x, y)) = palMap[*((const byte *)frame->getBasePtr(x, y))]; } return convertedSurface; } int GraphicsManager::computeHPushOffset(int speed) { switch (speed) { case 3: return 432; case 2: return 72; case 1: return 36; case 0: return 12; } return 432; } int GraphicsManager::computeVPushOffset(int speed) { switch (speed) { case 3: return 189; case 2: return 63; case 1: return 21; case 0: return 7; } return 189; } void GraphicsManager::crossBlit(Graphics::Surface *dst, int xDst, int yDst, uint w, uint h, const Graphics::Surface *src, uint xSrc, uint ySrc) { assert(dst->format.bytesPerPixel == src->format.bytesPerPixel); for (uint y = 0; y < h; y++) memcpy(dst->getBasePtr(xDst, yDst + y), src->getBasePtr(xSrc, ySrc + y), w * src->format.bytesPerPixel); } Graphics::Font *GraphicsManager::createMSGothicFont(int size, bool bold) const { switch (size) { case 10: case 11: size = 8; break; case 12: size = 9; break; case 20: size = 16; break; default: error("Unknown MS Gothic font size %d", size); } Graphics::Font *font; // Try to see if the user supplied a font Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember("msgothic.ttc"); // TODO: Fake a bold version if (stream) { // Force monochrome, since the original uses the bitmap glyphs in the font font = Graphics::loadTTFFont(stream, DisposeAfterUse::YES, size, Graphics::kTTFSizeModeCharacter, 96, 96, Graphics::kTTFRenderModeMonochrome); } else { font = Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, Graphics::kTTFSizeModeCharacter, 96, 96, Graphics::kTTFRenderModeMonochrome); } if (!font) error("Failed to load MS Gothic font"); return font; } void GraphicsManager::renderText(Graphics::Surface *dst, Graphics::Font *font, const Common::String &text, int x, int y, int w, int h, uint32 color, int lineHeight, TextAlign textAlign, bool centerVertically) { if (text.empty()) return; // Convert to UTF-32 for drawing. Choose the codepage based on the language. Common::CodePage srcFormat = (_vm->getLanguage() == Common::JA_JPN) ? Common::kWindows932 : Common::kWindows1252; Common::U32String convString = text.decode(srcFormat); renderText(dst, font, convString, x, y, w, h, color, lineHeight, textAlign, centerVertically); } void GraphicsManager::renderText(Graphics::Surface *dst, Graphics::Font *font, const Common::U32String &text, int x, int y, int w, int h, uint32 color, int lineHeight, TextAlign textAlign, bool centerVertically) { if (text.empty()) return; Common::U32StringArray lines; font->wordWrapText(text, w, lines); Graphics::TextAlign align = Graphics::kTextAlignLeft; switch (textAlign) { case kTextAlignLeft: align = Graphics::kTextAlignLeft; break; case kTextAlignCenter: align = Graphics::kTextAlignCenter; break; case kTextAlignRight: align = Graphics::kTextAlignRight; break; } if (centerVertically) y += (h - (lines.size() * lineHeight)) / 2; // Why is this needed? Dunno, but I guess Windows adds one row of space on the top y++; for (uint32 i = 0; i < lines.size(); i++) { font->drawString(dst, lines[i], x, y, w, color, align); y += lineHeight; } } void GraphicsManager::drawEllipse(const Common::Rect &rect, uint32 color) { // HACK: This just hardcodes the sizes of the rows of the ellipses // for the one thing in the game that needs it. static const int rows7[7] = { 7, 13, 15, 15, 15, 13, 7 }; static const int rows10[10] = { 7, 11, 13, 15, 15, 15, 15, 13, 11, 7 }; static const int rows12[12] = { 7, 11, 13, 13, 15, 15, 15, 15, 13, 13, 11, 7 }; static const int rows15[15] = { 5, 9, 11, 13, 13, 15, 15, 15, 15, 15, 13, 13, 11, 9, 5 }; const int *table = nullptr; switch (rect.height()) { case 7: table = rows7; break; case 10: table = rows10; break; case 12: table = rows12; break; case 15: table = rows15; break; } assert(table); for (int y = 0; y < rect.height(); y++) { int width = table[y]; int x = rect.left + (rect.width() - width) / 2; _screen->hLine(x, y + rect.top, x + width, color); } } TempCursorChange::TempCursorChange(Cursor cursor) { _prevCursor = static_cast(g_engine)->_gfx->setCursor(cursor); } TempCursorChange::~TempCursorChange() { static_cast(g_engine)->_gfx->setCursor(_prevCursor); } } // End of namespace Buried