diff options
Diffstat (limited to 'engines/adl/display.cpp')
-rw-r--r-- | engines/adl/display.cpp | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/engines/adl/display.cpp b/engines/adl/display.cpp new file mode 100644 index 0000000000..6342504bc3 --- /dev/null +++ b/engines/adl/display.cpp @@ -0,0 +1,520 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/stream.h" +#include "common/rect.h" +#include "common/system.h" +#include "common/str.h" +#include "common/config-manager.h" + +#include "graphics/surface.h" +#include "graphics/palette.h" +#include "graphics/thumbnail.h" + +#include "engines/util.h" + +#include "adl/display.h" + +namespace Adl { + +// This implements the Apple II "Hi-Res" display mode + +#define DISPLAY_PITCH (DISPLAY_WIDTH / 7) +#define DISPLAY_SIZE (DISPLAY_PITCH * DISPLAY_HEIGHT) + +#define TEXT_WIDTH 40 +#define TEXT_HEIGHT 24 +#define TEXT_BUF_SIZE (TEXT_WIDTH * TEXT_HEIGHT) + +#define COLOR_PALETTE_ENTRIES 8 +static const byte colorPalette[COLOR_PALETTE_ENTRIES * 3] = { + 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, + 0xc7, 0x34, 0xff, + 0x38, 0xcb, 0x00, + 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, + 0x0d, 0xa1, 0xff, + 0xf2, 0x5e, 0x00 +}; + +// Corresponding color in second palette +#define PAL2(X) ((X) | 0x04) + +// Alternate color for odd pixel rows (for scanlines) +#define ALTCOL(X) ((X) | 0x08) + +// Green monochrome palette +#define MONO_PALETTE_ENTRIES 2 +static const byte monoPalette[MONO_PALETTE_ENTRIES * 3] = { + 0x00, 0x00, 0x00, + 0x00, 0xc0, 0x01 +}; + +// Uppercase-only Apple II font (manually created). +static const byte font[64][5] = { + { 0x7c, 0x82, 0xba, 0xb2, 0x9c }, { 0xf8, 0x24, 0x22, 0x24, 0xf8 }, // @A + { 0xfe, 0x92, 0x92, 0x92, 0x6c }, { 0x7c, 0x82, 0x82, 0x82, 0x44 }, // BC + { 0xfe, 0x82, 0x82, 0x82, 0x7c }, { 0xfe, 0x92, 0x92, 0x92, 0x82 }, // DE + { 0xfe, 0x12, 0x12, 0x12, 0x02 }, { 0x7c, 0x82, 0x82, 0xa2, 0xe2 }, // FG + { 0xfe, 0x10, 0x10, 0x10, 0xfe }, { 0x00, 0x82, 0xfe, 0x82, 0x00 }, // HI + { 0x40, 0x80, 0x80, 0x80, 0x7e }, { 0xfe, 0x10, 0x28, 0x44, 0x82 }, // JK + { 0xfe, 0x80, 0x80, 0x80, 0x80 }, { 0xfe, 0x04, 0x18, 0x04, 0xfe }, // LM + { 0xfe, 0x08, 0x10, 0x20, 0xfe }, { 0x7c, 0x82, 0x82, 0x82, 0x7c }, // NO + { 0xfe, 0x12, 0x12, 0x12, 0x0c }, { 0x7c, 0x82, 0xa2, 0x42, 0xbc }, // PQ + { 0xfe, 0x12, 0x32, 0x52, 0x8c }, { 0x4c, 0x92, 0x92, 0x92, 0x64 }, // RS + { 0x02, 0x02, 0xfe, 0x02, 0x02 }, { 0x7e, 0x80, 0x80, 0x80, 0x7e }, // TU + { 0x3e, 0x40, 0x80, 0x40, 0x3e }, { 0xfe, 0x40, 0x30, 0x40, 0xfe }, // VW + { 0xc6, 0x28, 0x10, 0x28, 0xc6 }, { 0x06, 0x08, 0xf0, 0x08, 0x06 }, // XY + { 0xc2, 0xa2, 0x92, 0x8a, 0x86 }, { 0xfe, 0xfe, 0x82, 0x82, 0x82 }, // Z[ + { 0x04, 0x08, 0x10, 0x20, 0x40 }, { 0x82, 0x82, 0x82, 0xfe, 0xfe }, // \] + { 0x20, 0x10, 0x08, 0x10, 0x20 }, { 0x80, 0x80, 0x80, 0x80, 0x80 }, // ^_ + { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0xbe, 0x00, 0x00 }, // ! + { 0x00, 0x0e, 0x00, 0x0e, 0x00 }, { 0x28, 0xfe, 0x28, 0xfe, 0x28 }, // "# + { 0x48, 0x54, 0xfe, 0x54, 0x24 }, { 0x46, 0x26, 0x10, 0xc8, 0xc4 }, // $% + { 0x6c, 0x92, 0xac, 0x40, 0xa0 }, { 0x00, 0x00, 0x0e, 0x00, 0x00 }, // &' + { 0x38, 0x44, 0x82, 0x00, 0x00 }, { 0x00, 0x00, 0x82, 0x44, 0x38 }, // () + { 0x44, 0x28, 0xfe, 0x28, 0x44 }, { 0x10, 0x10, 0x7c, 0x10, 0x10 }, // *+ + { 0x00, 0x80, 0x60, 0x00, 0x00 }, { 0x10, 0x10, 0x10, 0x10, 0x10 }, // ,- + { 0x00, 0x00, 0x80, 0x00, 0x00 }, { 0x40, 0x20, 0x10, 0x08, 0x04 }, // ./ + { 0x7c, 0xa2, 0x92, 0x8a, 0x7c }, { 0x00, 0x84, 0xfe, 0x80, 0x00 }, // 01 + { 0xc4, 0xa2, 0x92, 0x92, 0x8c }, { 0x42, 0x82, 0x92, 0x9a, 0x66 }, // 23 + { 0x30, 0x28, 0x24, 0xfe, 0x20 }, { 0x4e, 0x8a, 0x8a, 0x8a, 0x72 }, // 45 + { 0x78, 0x94, 0x92, 0x92, 0x62 }, { 0x02, 0xe2, 0x12, 0x0a, 0x06 }, // 67 + { 0x6c, 0x92, 0x92, 0x92, 0x6c }, { 0x8c, 0x92, 0x92, 0x52, 0x3c }, // 89 + { 0x00, 0x00, 0x28, 0x00, 0x00 }, { 0x00, 0x80, 0x68, 0x00, 0x00 }, // :; + { 0x10, 0x28, 0x44, 0x82, 0x00 }, { 0x28, 0x28, 0x28, 0x28, 0x28 }, // <= + { 0x00, 0x82, 0x44, 0x28, 0x10 }, { 0x04, 0x02, 0xb2, 0x0a, 0x04 } // >? +}; + +Display::Display() : + _mode(DISPLAY_MODE_TEXT), + _cursorPos(0), + _showCursor(false) { + + initGraphics(DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 2, true); + + _monochrome = !ConfMan.getBool("color"); + _scanlines = ConfMan.getBool("scanlines"); + + if (_monochrome) + g_system->getPaletteManager()->setPalette(monoPalette, 0, MONO_PALETTE_ENTRIES); + else + g_system->getPaletteManager()->setPalette(colorPalette, 0, COLOR_PALETTE_ENTRIES); + + showScanlines(_scanlines); + + _frameBuf = new byte[DISPLAY_SIZE]; + memset(_frameBuf, 0, DISPLAY_SIZE); + _frameBufSurface = new Graphics::Surface; + // We need 2x scaling to properly render the half-pixel shift + // of the second palette + _frameBufSurface->create(DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 2, Graphics::PixelFormat::createFormatCLUT8()); + + _textBuf = new byte[TEXT_BUF_SIZE]; + memset(_textBuf, APPLECHAR(' '), TEXT_BUF_SIZE); + _textBufSurface = new Graphics::Surface; + // For ease of copying, also use 2x scaling here + _textBufSurface->create(DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 2, Graphics::PixelFormat::createFormatCLUT8()); + + createFont(); +} + +Display::~Display() { + delete[] _frameBuf; + _frameBufSurface->free(); + delete _frameBufSurface; + + delete[] _textBuf; + _textBufSurface->free(); + delete _textBufSurface; + + _font->free(); + delete _font; +} + +void Display::setMode(DisplayMode mode) { + _mode = mode; + + if (_mode == DISPLAY_MODE_TEXT || _mode == DISPLAY_MODE_MIXED) + updateTextScreen(); + if (_mode == DISPLAY_MODE_HIRES || _mode == DISPLAY_MODE_MIXED) + updateHiResScreen(); +} + +void Display::updateTextScreen() { + updateTextSurface(); + + if (_mode == DISPLAY_MODE_TEXT) + g_system->copyRectToScreen(_textBufSurface->getPixels(), _textBufSurface->pitch, 0, 0, _textBufSurface->w, _textBufSurface->h); + else if (_mode == DISPLAY_MODE_MIXED) + g_system->copyRectToScreen(_textBufSurface->getBasePtr(0, _textBufSurface->h - 4 * 8 * 2), _textBufSurface->pitch, 0, _textBufSurface->h - 4 * 8 * 2, _textBufSurface->w, 4 * 8 * 2); + + g_system->updateScreen(); +} + +void Display::updateHiResScreen() { + updateHiResSurface(); + + if (_mode == DISPLAY_MODE_HIRES) + g_system->copyRectToScreen(_frameBufSurface->getPixels(), _frameBufSurface->pitch, 0, 0, _frameBufSurface->w, _frameBufSurface->h); + else if (_mode == DISPLAY_MODE_MIXED) + g_system->copyRectToScreen(_frameBufSurface->getPixels(), _frameBufSurface->pitch, 0, 0, _frameBufSurface->w, _frameBufSurface->h - 4 * 8 * 2); + + g_system->updateScreen(); +} + +bool Display::saveThumbnail(Common::WriteStream &out) { + if (_scanlines) { + showScanlines(false); + g_system->updateScreen(); + } + + bool retval = Graphics::saveThumbnail(out); + + if (_scanlines) { + showScanlines(true); + g_system->updateScreen(); + } + + return retval; +} + +void Display::loadFrameBuffer(Common::ReadStream &stream) { + byte *dst = _frameBuf; + for (uint j = 0; j < 8; ++j) { + for (uint i = 0; i < 8; ++i) { + stream.read(dst, DISPLAY_PITCH); + dst += DISPLAY_PITCH * 64; + stream.read(dst, DISPLAY_PITCH); + dst += DISPLAY_PITCH * 64; + stream.read(dst, DISPLAY_PITCH); + stream.readUint32LE(); + stream.readUint32LE(); + dst -= DISPLAY_PITCH * 120; + } + dst -= DISPLAY_PITCH * 63; + } + + if (stream.eos() || stream.err()) + error("Failed to read frame buffer"); +} + +void Display::putPixel(const Common::Point &p, byte color) { + byte offset = p.x / 7; + + if (offset & 1) { + byte c = color << 1; + if (c >= 0x40 && c < 0xc0) + color ^= 0x7f; + } + + byte *b = _frameBuf + p.y * DISPLAY_PITCH + offset; + color ^= *b; + color &= 1 << (p.x % 7); + *b ^= color; +} + +void Display::clear(byte color) { + byte val = 0; + + byte c = color << 1; + if (c >= 0x40 && c < 0xc0) + val = 0x7f; + + for (uint i = 0; i < DISPLAY_SIZE; ++i) { + _frameBuf[i] = color; + color ^= val; + } +} + +void Display::home() { + memset(_textBuf, APPLECHAR(' '), TEXT_BUF_SIZE); + _cursorPos = 0; +} + +void Display::moveCursorForward() { + ++_cursorPos; + + if (_cursorPos >= TEXT_BUF_SIZE) + scrollUp(); +} + +void Display::moveCursorBackward() { + if (_cursorPos > 0) + --_cursorPos; +} + +void Display::moveCursorTo(const Common::Point &pos) { + _cursorPos = pos.y * TEXT_WIDTH + pos.x; + + if (_cursorPos >= TEXT_BUF_SIZE) + error("Cursor position (%i, %i) out of bounds", pos.x, pos.y); +} + +void Display::printString(const Common::String &str) { + Common::String::const_iterator c; + for (c = str.begin(); c != str.end(); ++c) { + byte b = *c; + + if (*c == APPLECHAR('\r')) + _cursorPos = (_cursorPos / TEXT_WIDTH + 1) * TEXT_WIDTH; + else if (b < 0x80 || b >= 0xa0) { + setCharAtCursor(b); + ++_cursorPos; + } + + if (_cursorPos == TEXT_BUF_SIZE) + scrollUp(); + } + + updateTextScreen(); +} + +void Display::printAsciiString(const Common::String &str) { + Common::String aStr; + + Common::String::const_iterator it; + for (it = str.begin(); it != str.end(); ++it) + aStr += APPLECHAR(*it); + + printString(aStr); +} + +void Display::setCharAtCursor(byte c) { + _textBuf[_cursorPos] = c; +} + +void Display::showCursor(bool enable) { + _showCursor = enable; +} + +void Display::showScanlines(bool enable) { + byte pal[COLOR_PALETTE_ENTRIES * 3] = { }; + + if (enable) + g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES); + else { + g_system->getPaletteManager()->grabPalette(pal, 0, COLOR_PALETTE_ENTRIES); + g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES); + } +} + +static byte processColorBits(uint16 &bits, bool &odd, bool secondPal) { + byte color = 0; + + switch (bits & 0x7) { + case 0x3: // 011 (white) + case 0x6: // 110 + case 0x7: // 111 + color = 1; + break; + case 0x2: // 010 (color) + color = 2 + odd; + break; + case 0x5: // 101 (color) + color = 2 + !odd; + } + + if (secondPal) + color = PAL2(color); + + odd = !odd; + bits >>= 1; + + return color; +} + +static void renderPixelRowColor(byte *dst, byte *src) { + uint16 bits = (src[0] & 0x7f) << 1; + byte pal = src[0] >> 7; + + if (pal != 0) + *dst++ = 0; + + bool odd = false; + + for (uint i = 0; i < DISPLAY_PITCH; ++i) { + if (i != DISPLAY_PITCH - 1) { + bits |= (src[i + 1] & 0x7f) << 8; + pal |= (src[i + 1] >> 7) << 1; + } + + // For the first 6 bits in the block we draw two pixels + for (uint j = 0; j < 6; ++j) { + byte color = processColorBits(bits, odd, pal & 1); + *dst++ = color; + *dst++ = color; + } + + // Last bit of the block, draw one, two or three pixels + byte color = processColorBits(bits, odd, pal & 1); + + // Draw the first pixel + *dst++ = color; + + switch (pal) { + case 0x0: + case 0x3: + // If palette stays the same, draw a second pixel + *dst++ = color; + break; + case 0x2: + // If we're moving from first to second palette, + // draw a second pixel, and a third in the second + // palette. + *dst++ = color; + *dst++ = PAL2(color); + } + + pal >>= 1; + } +} + +static void renderPixelRowMono(byte *dst, byte *src) { + byte pal = src[0] >> 7; + + if (pal != 0) + *dst++ = 0; + + for (uint i = 0; i < DISPLAY_PITCH; ++i) { + if (i != DISPLAY_PITCH - 1) + pal |= (src[i + 1] >> 7) << 1; + + for (uint j = 0; j < 6; ++j) { + bool color = src[i] & (1 << j); + *dst++ = color; + *dst++ = color; + } + + bool color = src[i] & (1 << 6); + + *dst++ = color; + + switch (pal) { + case 0x0: + case 0x3: + *dst++ = color; + break; + case 0x2: + *dst++ = color; + *dst++ = color; + } + + pal >>= 1; + } +} + +static void copyEvenSurfaceRows(Graphics::Surface &surf) { + byte *src = (byte *)surf.getPixels(); + + for (uint y = 0; y < surf.h / 2; ++y) { + byte *dst = src + surf.pitch; + for (uint x = 0; x < surf.w; ++x) + dst[x] = ALTCOL(src[x]); + src += surf.pitch * 2; + } +} + +void Display::updateHiResSurface() { + byte *src = _frameBuf; + byte *dst = (byte *)_frameBufSurface->getPixels(); + + for (uint i = 0; i < DISPLAY_HEIGHT; ++i) { + if (_monochrome) + renderPixelRowMono(dst, src); + else + renderPixelRowColor(dst, src); + src += DISPLAY_PITCH; + dst += _frameBufSurface->pitch * 2; + } + + copyEvenSurfaceRows(*_frameBufSurface); +} + +void Display::updateTextSurface() { + for (uint row = 0; row < 24; ++row) + for (uint col = 0; col < TEXT_WIDTH; ++col) { + uint charPos = row * TEXT_WIDTH + col; + char c = _textBuf[row * TEXT_WIDTH + col]; + + if (charPos == _cursorPos && _showCursor) + c = (c & 0x3f) | 0x40; + + Common::Rect r(7 * 2, 8 * 2); + r.translate(((c & 0x3f) % 16) * 7 * 2, (c & 0x3f) / 16 * 8 * 2); + + if (!(c & 0x80)) { + if (!(c & 0x40) || ((g_system->getMillis() / 270) & 1)) + r.translate(0, 4 * 8 * 2); + } + + _textBufSurface->copyRectToSurface(*_font, col * 7 * 2, row * 8 * 2, r); + } +} + +void Display::drawChar(byte c, int x, int y) { + byte *buf = (byte *)_font->getPixels() + y * _font->pitch + x; + + for (uint row = 0; row < 8; ++row) { + for (uint col = 1; col < 6; ++col) { + if (font[c][col - 1] & (1 << row)) { + buf[col * 2] = 1; + buf[col * 2 + 1] = 1; + } + } + + buf += 2 * _font->pitch; + } +} + +void Display::createFont() { + _font = new Graphics::Surface; + _font->create(16 * 7 * 2, 4 * 8 * 2 * 2, Graphics::PixelFormat::createFormatCLUT8()); + + for (uint i = 0; i < 4; ++i) + for (uint j = 0; j < 16; ++j) + drawChar(i * 16 + j, j * 7 * 2, i * 8 * 2); + + // Create inverted font + byte *buf = (byte *)_font->getPixels(); + byte *bufInv = buf + (_font->h / 2) * _font->pitch; + + for (uint row = 0; row < _font->h / 2; row += 2) { + for (uint col = 0; col < _font->w; ++col) + bufInv[col] = (buf[col] ? 0 : 1); + + buf += _font->pitch * 2; + bufInv += _font->pitch * 2; + } + + copyEvenSurfaceRows(*_font); +} + +void Display::scrollUp() { + memmove(_textBuf, _textBuf + TEXT_WIDTH, TEXT_BUF_SIZE - TEXT_WIDTH); + memset(_textBuf + TEXT_BUF_SIZE - TEXT_WIDTH, APPLECHAR(' '), TEXT_WIDTH); + if (_cursorPos >= TEXT_WIDTH) + _cursorPos -= TEXT_WIDTH; +} + +} // End of namespace Adl |