[Scummvm-git-logs] scummvm master -> 32824ea83f05ee28be5816c30ff14ba57b84ade1

waltervn walter at vanniftrik-it.nl
Mon Aug 12 02:27:44 CEST 2019


This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
d5c7e9d207 ADL: Refactor Display class
32824ea83f ADL: Improve color accuracy


Commit: d5c7e9d2073c54ed284e1aa4e532f36bf68c3818
    https://github.com/scummvm/scummvm/commit/d5c7e9d2073c54ed284e1aa4e532f36bf68c3818
Author: Walter van Niftrik (walter at scummvm.org)
Date: 2019-08-11T23:36:27+02:00

Commit Message:
ADL: Refactor Display class

Changed paths:
    engines/adl/adl.cpp
    engines/adl/adl_v2.cpp
    engines/adl/console.cpp
    engines/adl/display.cpp
    engines/adl/display.h
    engines/adl/display_a2.cpp
    engines/adl/display_a2.h
    engines/adl/hires1.cpp
    engines/adl/hires4.cpp
    engines/adl/hires5.cpp
    engines/adl/hires6.cpp


diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp
index 2379ecd..baf14ea 100644
--- a/engines/adl/adl.cpp
+++ b/engines/adl/adl.cpp
@@ -286,7 +286,7 @@ byte AdlEngine::inputKey(bool showCursor) const {
 		if (_inputScript && !_scriptPaused)
 			return _display->asciiToNative('\r');
 
-		_display->copyTextSurface();
+		_display->renderText();
 		g_system->delayMillis(16);
 	}
 
@@ -730,7 +730,7 @@ void AdlEngine::gameLoop() {
 }
 
 Common::Error AdlEngine::run() {
-	_display = new Display_A2();
+	_display = Display_A2_create();
 	_console = new Console(this);
 	_display->init();
 
@@ -964,7 +964,7 @@ Common::Error AdlEngine::saveGameState(int slot, const Common::String &desc) {
 	uint32 playTime = getTotalPlayTime();
 	outFile->writeUint32BE(playTime);
 
-	_display->saveThumbnail(*outFile);
+	Graphics::saveThumbnail(*outFile);
 	saveState(*outFile);
 	outFile->finalize();
 
@@ -1280,7 +1280,7 @@ int AdlEngine::o_restart(ScriptEnv &e) {
 	if (input.size() == 0 || input[0] != _display->asciiToNative('N')) {
 		_isRestarting = true;
 		_graphics->clearScreen();
-		_display->copyGfxSurface();
+		_display->renderGraphics();
 		_display->printString(_strings.pressReturn);
 		initState();
 		_display->printAsciiString(_strings.lineFeeds);
diff --git a/engines/adl/adl_v2.cpp b/engines/adl/adl_v2.cpp
index 81ff145..e30502e 100644
--- a/engines/adl/adl_v2.cpp
+++ b/engines/adl/adl_v2.cpp
@@ -112,7 +112,7 @@ void AdlEngine_v2::checkTextOverflow(char c) {
 
 void AdlEngine_v2::handleTextOverflow() {
 	_linesPrinted = 0;
-	_display->copyTextSurface();
+	_display->renderText();
 
 	if (_inputScript) {
 		// Set pause flag to activate regular behaviour of delay and inputKey
@@ -185,7 +185,7 @@ void AdlEngine_v2::printString(const Common::String &str) {
 
 	checkTextOverflow(returnChar);
 	_display->printChar(returnChar);
-	_display->copyTextSurface();
+	_display->renderText();
 }
 
 void AdlEngine_v2::drawItem(Item &item, const Common::Point &pos) {
@@ -263,7 +263,7 @@ void AdlEngine_v2::showRoom() {
 	if (!_state.isDark)
 		drawItems();
 
-	_display->copyGfxSurface();
+	_display->renderGraphics();
 	printString(_roomData.description);
 }
 
diff --git a/engines/adl/console.cpp b/engines/adl/console.cpp
index 876389c..4567265 100644
--- a/engines/adl/console.cpp
+++ b/engines/adl/console.cpp
@@ -182,8 +182,8 @@ void Console::prepareGame() {
 	_engine->_graphics->clearScreen();
 	_engine->loadRoom(_engine->_state.room);
 	_engine->showRoom();
-	_engine->_display->copyTextSurface();
-	_engine->_display->copyGfxSurface();
+	_engine->_display->renderText();
+	_engine->_display->renderGraphics();
 }
 
 bool Console::Cmd_Region(int argc, const char **argv) {
diff --git a/engines/adl/display.cpp b/engines/adl/display.cpp
index 736b094..d715944 100644
--- a/engines/adl/display.cpp
+++ b/engines/adl/display.cpp
@@ -25,27 +25,12 @@
 #include "common/str.h"
 #include "common/system.h"
 
-#include "graphics/surface.h"
-
 #include "adl/display.h"
 
 namespace Adl {
 
 Display::~Display() {
 	delete[] _textBuf;
-	_textSurface->free();
-	delete _textSurface;
-
-	_gfxSurface->free();
-	delete _gfxSurface;
-}
-
-void Display::createSurfaces(uint gfxWidth, uint gfxHeight, uint splitHeight) {
-	_gfxSurface = new Graphics::Surface;
-	_gfxSurface->create(gfxWidth, gfxHeight, Graphics::PixelFormat::createFormatCLUT8());
-	_textSurface = new Graphics::Surface;
-	_textSurface->create(gfxWidth, gfxHeight, Graphics::PixelFormat::createFormatCLUT8());
-	_splitHeight = splitHeight;
 }
 
 void Display::createTextBuffer(uint textWidth, uint textHeight) {
@@ -60,31 +45,9 @@ void Display::setMode(Display::Mode mode) {
 	_mode = mode;
 
 	if (_mode == Display::kModeText || _mode == Display::kModeMixed)
-		copyTextSurface();
+		renderText();
 	if (_mode == Display::kModeGraphics || _mode == Display::kModeMixed)
-		copyGfxSurface();
-}
-
-void Display::copyTextSurface() {
-	updateTextSurface();
-
-	if (_mode == Display::kModeText)
-		g_system->copyRectToScreen(_textSurface->getPixels(), _textSurface->pitch, 0, 0, _textSurface->w, _textSurface->h);
-	else if (_mode == Display::kModeMixed)
-		g_system->copyRectToScreen(_textSurface->getBasePtr(0, _textSurface->h - _splitHeight), _textSurface->pitch, 0, _textSurface->h - _splitHeight, _textSurface->w, _splitHeight);
-
-	g_system->updateScreen();
-}
-
-void Display::copyGfxSurface() {
-	updateGfxSurface();
-
-	if (_mode == kModeGraphics)
-		g_system->copyRectToScreen(_gfxSurface->getPixels(), _gfxSurface->pitch, 0, 0, _gfxSurface->w, _gfxSurface->h);
-	else if (_mode == kModeMixed)
-		g_system->copyRectToScreen(_gfxSurface->getPixels(), _gfxSurface->pitch, 0, 0, _gfxSurface->w, _gfxSurface->h - _splitHeight);
-
-	g_system->updateScreen();
+		renderGraphics();
 }
 
 void Display::home() {
@@ -116,7 +79,7 @@ void Display::printString(const Common::String &str) {
 	for (c = str.begin(); c != str.end(); ++c)
 		printChar(*c);
 
-	copyTextSurface();
+	renderText();
 }
 
 void Display::printAsciiString(const Common::String &str) {
@@ -124,7 +87,7 @@ void Display::printAsciiString(const Common::String &str) {
 	for (c = str.begin(); c != str.end(); ++c)
 		printChar(asciiToNative(*c));
 
-	copyTextSurface();
+	renderText();
 }
 
 void Display::setCharAtCursor(byte c) {
diff --git a/engines/adl/display.h b/engines/adl/display.h
index a92993e..0c43d65 100644
--- a/engines/adl/display.h
+++ b/engines/adl/display.h
@@ -26,15 +26,10 @@
 #include "common/types.h"
 
 namespace Common {
-class WriteStream;
 class String;
 struct Point;
 }
 
-namespace Graphics {
-struct Surface;
-}
-
 namespace Adl {
 
 class Display {
@@ -48,10 +43,9 @@ public:
 	virtual ~Display();
 
 	virtual void init() = 0;
-	virtual bool saveThumbnail(Common::WriteStream &out) = 0;
 	void setMode(Mode mode);
-	void copyTextSurface();
-	void copyGfxSurface();
+	virtual void renderText() = 0;
+	virtual void renderGraphics() = 0;
 
 	virtual char asciiToNative(char c) const = 0;
 	virtual void printChar(char c) = 0;
@@ -68,23 +62,13 @@ public:
 	void scrollUp();
 
 protected:
-	Display() :	_textBuf(nullptr), _textSurface(nullptr), _gfxSurface(nullptr), _cursorPos(0),
-	            _mode(kModeText), _splitHeight(0), _textWidth(0), _textHeight(0) { }
+	Display() :	_textBuf(nullptr), _cursorPos(0), _mode(kModeText), _textWidth(0), _textHeight(0) { }
 
-	void createSurfaces(uint gfxWidth, uint gfxHeight, uint splitHeight);
 	void createTextBuffer(uint textWidth, uint textHeight);
 
 	byte *_textBuf;
-	Graphics::Surface *_textSurface;
-	Graphics::Surface *_gfxSurface;
 	uint _cursorPos;
-
-private:
-	virtual void updateTextSurface() = 0;
-	virtual void updateGfxSurface() = 0;
-
 	Mode _mode;
-	uint _splitHeight;
 	uint _textWidth;
 	uint _textHeight;
 };
diff --git a/engines/adl/display_a2.cpp b/engines/adl/display_a2.cpp
index 3a62e33..e738e77 100644
--- a/engines/adl/display_a2.cpp
+++ b/engines/adl/display_a2.cpp
@@ -101,183 +101,6 @@ static const byte font[64][5] = {
 	{ 0x00, 0x82, 0x44, 0x28, 0x10 }, { 0x04, 0x02, 0xb2, 0x0a, 0x04 }  // >?
 };
 
-Display_A2::Display_A2() : _showCursor(false) {
-	initGraphics(Display_A2::kGfxWidth * 2, Display_A2::kGfxHeight * 2);
-}
-
-Display_A2::~Display_A2() {
-	delete[] _frameBuf;
-
-	if (_font) {
-		_font->free();
-		delete _font;
-	}
-}
-
-void Display_A2::init() {
-	_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);
-
-	// We need 2x scaling to properly render the half-pixel shift
-	// of the second palette
-	createSurfaces(Display_A2::kGfxWidth * 2, Display_A2::kGfxHeight * 2, 64);
-	createTextBuffer(Display_A2::kTextWidth, Display_A2::kTextHeight);
-
-	_frameBuf = new byte[Display_A2::kGfxSize];
-	memset(_frameBuf, 0, Display_A2::kGfxSize);
-
-	createFont();
-
-	_startMillis = g_system->getMillis();
-}
-
-bool Display_A2::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_A2::loadFrameBuffer(Common::ReadStream &stream, byte *dst) {
-	for (uint j = 0; j < 8; ++j) {
-		for (uint i = 0; i < 8; ++i) {
-			stream.read(dst, Display_A2::kGfxPitch);
-			dst += Display_A2::kGfxPitch * 64;
-			stream.read(dst, Display_A2::kGfxPitch);
-			dst += Display_A2::kGfxPitch * 64;
-			stream.read(dst, Display_A2::kGfxPitch);
-			stream.readUint32LE();
-			stream.readUint32LE();
-			dst -= Display_A2::kGfxPitch * 120;
-		}
-		dst -= Display_A2::kGfxPitch * 63;
-	}
-
-	if (stream.eos() || stream.err())
-		error("Failed to read frame buffer");
-}
-
-void Display_A2::loadFrameBuffer(Common::ReadStream &stream) {
-	loadFrameBuffer(stream, _frameBuf);
-}
-
-void Display_A2::putPixel(const Common::Point &p, byte color) {
-	byte offset = p.x / 7;
-	byte mask = 0x80 | (1 << (p.x % 7));
-
-	// Since white and black are in both palettes, we leave
-	// the palette bit alone
-	if ((color & 0x7f) == 0x7f || (color & 0x7f) == 0)
-		mask &= 0x7f;
-
-	// Adjust colors starting with bits '01' or '10' for
-	// odd offsets
-	if (offset & 1) {
-		byte c = color << 1;
-		if (c >= 0x40 && c < 0xc0)
-			color ^= 0x7f;
-	}
-
-	writeFrameBuffer(p, color, mask);
-}
-
-void Display_A2::setPixelByte(const Common::Point &p, byte color) {
-	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
-
-	_frameBuf[p.y * Display_A2::kGfxPitch + p.x / 7] = color;
-}
-
-void Display_A2::setPixelBit(const Common::Point &p, byte color) {
-	writeFrameBuffer(p, color, 1 << (p.x % 7));
-}
-
-void Display_A2::setPixelPalette(const Common::Point &p, byte color) {
-	writeFrameBuffer(p, color, 0x80);
-}
-
-byte Display_A2::getPixelByte(const Common::Point &p) const {
-	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
-
-	return _frameBuf[p.y * Display_A2::kGfxPitch + p.x / 7];
-}
-
-bool Display_A2::getPixelBit(const Common::Point &p) const {
-	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
-
-	byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
-	return *b & (1 << (p.x % 7));
-}
-
-void Display_A2::clear(byte color) {
-	byte val = 0;
-
-	byte c = color << 1;
-	if (c >= 0x40 && c < 0xc0)
-		val = 0x7f;
-
-	for (uint i = 0; i < Display_A2::kGfxSize; ++i) {
-		_frameBuf[i] = color;
-		color ^= val;
-	}
-}
-
-// FIXME: This does not currently update the surfaces
-void Display_A2::printChar(char c) {
-	if (c == Display_A2::asciiToNative('\r'))
-		_cursorPos = (_cursorPos / Display_A2::kTextWidth + 1) * Display_A2::kTextWidth;
-	else if (c == Display_A2::asciiToNative('\a')) {
-		copyTextSurface();
-		static_cast<AdlEngine *>(g_engine)->bell();
-	} else if ((byte)c < 0x80 || (byte)c >= 0xa0) {
-		setCharAtCursor(c);
-		++_cursorPos;
-	}
-
-	if (_cursorPos == Display_A2::kTextWidth * Display_A2::kTextHeight)
-		scrollUp();
-}
-
-void Display_A2::showCursor(bool enable) {
-	_showCursor = enable;
-}
-
-void Display_A2::writeFrameBuffer(const Common::Point &p, byte color, byte mask) {
-	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
-
-	byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
-	color ^= *b;
-	color &= mask;
-	*b ^= color;
-}
-
-void Display_A2::showScanlines(bool enable) {
-	byte pal[COLOR_PALETTE_ENTRIES * 3];
-
-	g_system->getPaletteManager()->grabPalette(pal, 0, COLOR_PALETTE_ENTRIES);
-
-	if (enable) {
-		for (uint i = 0; i < ARRAYSIZE(pal); ++i)
-			pal[i] = pal[i] * (100 - SCANLINE_OPACITY) / 100;
-	}
-
-	g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES);
-}
 
 static byte processColorBits(uint16 &bits, bool &odd, bool secondPal) {
 	byte color = 0;
@@ -395,7 +218,82 @@ static void copyEvenSurfaceRows(Graphics::Surface &surf) {
 	}
 }
 
-void Display_A2::updateGfxSurface() {
+class Display_A2_Monitor : public Display_A2 {
+public:
+	Display_A2_Monitor();
+	~Display_A2_Monitor();
+
+	enum {
+		kSplitHeight = 64
+	};
+
+	void init() override;
+	void renderText() override;
+	void renderGraphics() override;
+
+private:
+	void updateTextSurface();
+	void updateGfxSurface();
+	void drawChar(byte c, int x, int y);
+	void createFont();
+	void showScanlines(bool enable);
+	void createSurfaces(uint gfxWidth, uint gfxHeight);
+
+	Graphics::Surface *_textSurface;
+	Graphics::Surface *_gfxSurface;
+	Graphics::Surface *_font;
+	bool _scanlines;
+	bool _monochrome;
+};
+
+Display_A2_Monitor::Display_A2_Monitor() :
+		_textSurface(nullptr),
+		_gfxSurface(nullptr),
+		_font(nullptr),
+		_scanlines(false),
+		_monochrome(false) { }
+
+Display_A2_Monitor::~Display_A2_Monitor() {
+	if (_font) {
+		_font->free();
+		delete _font;
+	}
+
+	_textSurface->free();
+	delete _textSurface;
+
+	_gfxSurface->free();
+	delete _gfxSurface;
+}
+
+void Display_A2_Monitor::createSurfaces(uint gfxWidth, uint gfxHeight) {
+	_gfxSurface = new Graphics::Surface;
+	_gfxSurface->create(gfxWidth, gfxHeight, Graphics::PixelFormat::createFormatCLUT8());
+	_textSurface = new Graphics::Surface;
+	_textSurface->create(gfxWidth, gfxHeight, Graphics::PixelFormat::createFormatCLUT8());
+}
+
+void Display_A2_Monitor::init() {
+	Display_A2::init();
+
+	// We need 2x scaling to properly render the half-pixel shift
+	// of the second palette
+	createSurfaces(Display_A2::kGfxWidth * 2, Display_A2::kGfxHeight * 2);
+
+	_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);
+
+	createFont();
+
+	showScanlines(_scanlines);
+}
+
+void Display_A2_Monitor::updateGfxSurface() {
 	byte *src = _frameBuf;
 	byte *dst = (byte *)_gfxSurface->getPixels();
 
@@ -411,7 +309,7 @@ void Display_A2::updateGfxSurface() {
 	copyEvenSurfaceRows(*_gfxSurface);
 }
 
-void Display_A2::updateTextSurface() {
+void Display_A2_Monitor::updateTextSurface() {
 	for (uint row = 0; row < 24; ++row)
 		for (uint col = 0; col < Display_A2::kTextWidth; ++col) {
 			uint charPos = row * Display_A2::kTextWidth + col;
@@ -424,11 +322,7 @@ void Display_A2::updateTextSurface() {
 			r.translate(((c & 0x3f) % 16) * 7 * 2, (c & 0x3f) / 16 * 8 * 2);
 
 			if (!(c & 0x80)) {
-				// Blink text. We subtract _startMillis to make this compatible
-				// with the event recorder, which returns offsetted values on
-				// playback.
-				const uint32 millisPassed = g_system->getMillis() - _startMillis;
-				if (!(c & 0x40) || ((millisPassed / 270) & 1))
+				if (!(c & 0x40) || ((g_system->getMillis() / 270) & 1))
 					r.translate(0, 4 * 8 * 2);
 			}
 
@@ -436,7 +330,29 @@ void Display_A2::updateTextSurface() {
 		}
 }
 
-void Display_A2::drawChar(byte c, int x, int y) {
+void Display_A2_Monitor::renderText() {
+	updateTextSurface();
+
+	if (_mode == Display::kModeText)
+		g_system->copyRectToScreen(_textSurface->getPixels(), _textSurface->pitch, 0, 0, _textSurface->w, _textSurface->h);
+	else if (_mode == Display::kModeMixed)
+		g_system->copyRectToScreen(_textSurface->getBasePtr(0, _textSurface->h - kSplitHeight), _textSurface->pitch, 0, _textSurface->h - kSplitHeight, _textSurface->w, kSplitHeight);
+
+	g_system->updateScreen();
+}
+
+void Display_A2_Monitor::renderGraphics() {
+	updateGfxSurface();
+
+	if (_mode == kModeGraphics)
+		g_system->copyRectToScreen(_gfxSurface->getPixels(), _gfxSurface->pitch, 0, 0, _gfxSurface->w, _gfxSurface->h);
+	else if (_mode == kModeMixed)
+		g_system->copyRectToScreen(_gfxSurface->getPixels(), _gfxSurface->pitch, 0, 0, _gfxSurface->w, _gfxSurface->h - kSplitHeight);
+
+	g_system->updateScreen();
+}
+
+void Display_A2_Monitor::drawChar(byte c, int x, int y) {
 	byte *buf = (byte *)_font->getPixels() + y * _font->pitch + x;
 
 	for (uint row = 0; row < 8; ++row) {
@@ -451,7 +367,7 @@ void Display_A2::drawChar(byte c, int x, int y) {
 	}
 }
 
-void Display_A2::createFont() {
+void Display_A2_Monitor::createFont() {
 	_font = new Graphics::Surface;
 	_font->create(16 * 7 * 2, 4 * 8 * 2 * 2, Graphics::PixelFormat::createFormatCLUT8());
 
@@ -474,4 +390,146 @@ void Display_A2::createFont() {
 	copyEvenSurfaceRows(*_font);
 }
 
+void Display_A2_Monitor::showScanlines(bool enable) {
+	byte pal[COLOR_PALETTE_ENTRIES * 3];
+
+	g_system->getPaletteManager()->grabPalette(pal, 0, COLOR_PALETTE_ENTRIES);
+
+	if (enable) {
+		for (uint i = 0; i < ARRAYSIZE(pal); ++i)
+			pal[i] = pal[i] * (100 - SCANLINE_OPACITY) / 100;
+	}
+
+	g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES);
+}
+
+Display_A2::Display_A2() : _frameBuf(nullptr), _showCursor(false) {
+	initGraphics(Display_A2::kGfxWidth * 2, Display_A2::kGfxHeight * 2);
+}
+
+Display_A2::~Display_A2() {
+	delete[] _frameBuf;
+}
+
+void Display_A2::init() {
+	createTextBuffer(Display_A2::kTextWidth, Display_A2::kTextHeight);
+
+	_frameBuf = new byte[Display_A2::kGfxSize];
+	memset(_frameBuf, 0, Display_A2::kGfxSize);
+}
+
+void Display_A2::loadFrameBuffer(Common::ReadStream &stream, byte *dst) {
+	for (uint j = 0; j < 8; ++j) {
+		for (uint i = 0; i < 8; ++i) {
+			stream.read(dst, Display_A2::kGfxPitch);
+			dst += Display_A2::kGfxPitch * 64;
+			stream.read(dst, Display_A2::kGfxPitch);
+			dst += Display_A2::kGfxPitch * 64;
+			stream.read(dst, Display_A2::kGfxPitch);
+			stream.readUint32LE();
+			stream.readUint32LE();
+			dst -= Display_A2::kGfxPitch * 120;
+		}
+		dst -= Display_A2::kGfxPitch * 63;
+	}
+
+	if (stream.eos() || stream.err())
+		error("Failed to read frame buffer");
+}
+
+void Display_A2::loadFrameBuffer(Common::ReadStream &stream) {
+	loadFrameBuffer(stream, _frameBuf);
+}
+
+void Display_A2::putPixel(const Common::Point &p, byte color) {
+	byte offset = p.x / 7;
+	byte mask = 0x80 | (1 << (p.x % 7));
+
+	// Since white and black are in both palettes, we leave
+	// the palette bit alone
+	if ((color & 0x7f) == 0x7f || (color & 0x7f) == 0)
+		mask &= 0x7f;
+
+	// Adjust colors starting with bits '01' or '10' for
+	// odd offsets
+	if (offset & 1) {
+		byte c = color << 1;
+		if (c >= 0x40 && c < 0xc0)
+			color ^= 0x7f;
+	}
+
+	writeFrameBuffer(p, color, mask);
+}
+
+void Display_A2::setPixelByte(const Common::Point &p, byte color) {
+	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
+
+	_frameBuf[p.y * Display_A2::kGfxPitch + p.x / 7] = color;
+}
+
+void Display_A2::setPixelBit(const Common::Point &p, byte color) {
+	writeFrameBuffer(p, color, 1 << (p.x % 7));
+}
+
+void Display_A2::setPixelPalette(const Common::Point &p, byte color) {
+	writeFrameBuffer(p, color, 0x80);
+}
+
+byte Display_A2::getPixelByte(const Common::Point &p) const {
+	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
+
+	return _frameBuf[p.y * Display_A2::kGfxPitch + p.x / 7];
+}
+
+bool Display_A2::getPixelBit(const Common::Point &p) const {
+	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
+
+	byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
+	return *b & (1 << (p.x % 7));
+}
+
+void Display_A2::clear(byte color) {
+	byte val = 0;
+
+	byte c = color << 1;
+	if (c >= 0x40 && c < 0xc0)
+		val = 0x7f;
+
+	for (uint i = 0; i < Display_A2::kGfxSize; ++i) {
+		_frameBuf[i] = color;
+		color ^= val;
+	}
+}
+
+// FIXME: This does not currently update the surfaces
+void Display_A2::printChar(char c) {
+	if (c == Display_A2::asciiToNative('\r'))
+		_cursorPos = (_cursorPos / Display_A2::kTextWidth + 1) * Display_A2::kTextWidth;
+	else if (c == Display_A2::asciiToNative('\a')) {
+		renderText();
+		static_cast<AdlEngine *>(g_engine)->bell();
+	} else if ((byte)c < 0x80 || (byte)c >= 0xa0) {
+		setCharAtCursor(c);
+		++_cursorPos;
+	}
+
+	if (_cursorPos == Display_A2::kTextWidth * Display_A2::kTextHeight)
+		scrollUp();
+}
+
+void Display_A2::showCursor(bool enable) {
+	_showCursor = enable;
+}
+
+void Display_A2::writeFrameBuffer(const Common::Point &p, byte color, byte mask) {
+	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
+
+	byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
+	color ^= *b;
+	color &= mask;
+	*b ^= color;
+}
+
+Display_A2 *Display_A2_create() { return new Display_A2_Monitor(); }
+
 } // End of namespace Adl
diff --git a/engines/adl/display_a2.h b/engines/adl/display_a2.h
index 52c7970..4e84fac 100644
--- a/engines/adl/display_a2.h
+++ b/engines/adl/display_a2.h
@@ -42,7 +42,6 @@ public:
 	};
 
 	void init() override;
-	bool saveThumbnail(Common::WriteStream &out) override;
 
 	// Graphics
 	uint getGfxWidth() const { return kGfxWidth; }
@@ -63,23 +62,18 @@ public:
 	void printChar(char c) override;
 	void showCursor(bool enable) override;
 
+protected:
+	byte *_frameBuf;
+	bool _showCursor;
+
 private:
 	void writeFrameBuffer(const Common::Point &p, byte color, byte mask);
-	void updateTextSurface() override;
-	void updateGfxSurface() override;
-
-	void showScanlines(bool enable);
-	void drawChar(byte c, int x, int y);
-	void createFont();
 
-	byte *_frameBuf;
-	bool _scanlines;
-	bool _monochrome;
-	Graphics::Surface *_font;
-	bool _showCursor;
-	uint32 _startMillis;
+	virtual void showScanlines(bool enable) { };
 };
 
+Display_A2 *Display_A2_create();
+
 } // End of namespace Adl
 
 #endif
diff --git a/engines/adl/hires1.cpp b/engines/adl/hires1.cpp
index 169a0ea..9c448b8 100644
--- a/engines/adl/hires1.cpp
+++ b/engines/adl/hires1.cpp
@@ -156,7 +156,7 @@ void HiRes1Engine::runIntro() {
 		stream->seek(IDI_HR1_OFS_LOGO_0);
 		_display->setMode(Display::kModeGraphics);
 		static_cast<Display_A2 *>(_display)->loadFrameBuffer(*stream);
-		_display->copyGfxSurface();
+		_display->renderGraphics();
 
 		if (getGameVersion() == GAME_VER_HR1_PD) {
 			// Only the PD version shows a title screen during the load
@@ -239,7 +239,7 @@ void HiRes1Engine::runIntro() {
 	stream.reset(_files->createReadStream(IDS_HR1_EXE_1));
 	stream->seek(0x1800);
 	static_cast<Display_A2 *>(_display)->loadFrameBuffer(*stream);
-	_display->copyGfxSurface();
+	_display->renderGraphics();
 
 	_display->setMode(Display::kModeMixed);
 
@@ -479,7 +479,7 @@ void HiRes1Engine::showRoom() {
 		drawItems();
 	}
 
-	_display->copyGfxSurface();
+	_display->renderGraphics();
 	_messageDelay = false;
 	printString(_roomData.description);
 	_messageDelay = true;
diff --git a/engines/adl/hires4.cpp b/engines/adl/hires4.cpp
index 5171685..a449953 100644
--- a/engines/adl/hires4.cpp
+++ b/engines/adl/hires4.cpp
@@ -134,7 +134,7 @@ void HiRes4Engine::putSpace(uint x, uint y) const {
 
 	_display->moveCursorTo(Common::Point(x, y));
 	_display->printChar(' ');
-	_display->copyTextSurface();
+	_display->renderText();
 	delay(2);
 }
 
@@ -167,7 +167,7 @@ void HiRes4Engine::drawText(const Common::String &str, Common::SeekableReadStrea
 		drawChar(c, shapeTable, pos);
 		drawChar(98, shapeTable, pos);
 
-		_display->copyGfxSurface();
+		_display->renderGraphics();
 		delay(15);
 	}
 }
@@ -223,7 +223,7 @@ void HiRes4Engine::runIntroAdvise(Common::SeekableReadStream &menu) {
 			_display->printAsciiString(left);
 			_display->moveCursorTo(Common::Point(19, y));
 			_display->printAsciiString(right);
-			_display->copyTextSurface();
+			_display->renderText();
 			delay(35);
 		} while (x != backupText[i].size() / 2);
 
@@ -245,7 +245,7 @@ void HiRes4Engine::runIntroAdvise(Common::SeekableReadStream &menu) {
 
 		_display->moveCursorTo(Common::Point(32, 18));
 		_display->printChar(_display->asciiToNative(cursor[cursorIdx]));
-		_display->copyTextSurface();
+		_display->renderText();
 		g_system->delayMillis(25);
 		cursorIdx = (cursorIdx + 1) % cursor.size();
 	}
@@ -269,7 +269,7 @@ void HiRes4Engine::runIntroLogo(Common::SeekableReadStream &ms2) {
 			if (x % 7 == 6)
 				display->setPixelPalette(Common::Point(x, y), p);
 		}
-		display->copyGfxSurface();
+		display->renderGraphics();
 
 		if (shouldQuit()) {
 			delete[] logo;
@@ -288,7 +288,7 @@ void HiRes4Engine::runIntroLogo(Common::SeekableReadStream &ms2) {
 			for (p.x = 0; p.x < (int)width; p.x += 7)
 				display->setPixelByte(Common::Point(p.x, p.y - 1), display->getPixelByte(p));
 
-		display->copyGfxSurface();
+		display->renderGraphics();
 
 		Tones tone;
 		tone.push_back(Tone(kClock / 2.0 / ((i * 4 + 1) * 10.0 + 10.0), 12.5));
@@ -314,13 +314,13 @@ void HiRes4Engine::runIntroTitle(Common::SeekableReadStream &menu, Common::Seeka
 	// Draw "TM" with lines
 	_graphics->drawLine(Common::Point(200, 170), Common::Point(200, 174), 0x7f);
 	_graphics->drawLine(Common::Point(198, 170), Common::Point(202, 170), 0x7f);
-	_display->copyGfxSurface();
+	_display->renderGraphics();
 	delay(7);
 	_graphics->drawLine(Common::Point(204, 170), Common::Point(204, 174), 0x7f);
 	_graphics->drawLine(Common::Point(204, 170), Common::Point(207, 173), 0x7f);
 	_graphics->drawLine(Common::Point(207, 173), Common::Point(209, 170), 0x7f);
 	_graphics->drawLine(Common::Point(209, 170), Common::Point(209, 174), 0x7f);
-	_display->copyGfxSurface();
+	_display->renderGraphics();
 	delay(7);
 
 	titleString = readStringAt(menu, 0x46c);
diff --git a/engines/adl/hires5.cpp b/engines/adl/hires5.cpp
index b98dfc5..b9b51c6 100644
--- a/engines/adl/hires5.cpp
+++ b/engines/adl/hires5.cpp
@@ -97,7 +97,7 @@ void HiRes5Engine::drawLight(uint index, byte color) const {
 		for (int xDelta = 0; xDelta < 7; ++xDelta)
 			display->putPixel(Common::Point(xCoord[index] + xDelta, yCoord + yDelta), color);
 
-	display->copyGfxSurface();
+	display->renderGraphics();
 }
 
 void HiRes5Engine::animateLights() const {
@@ -248,7 +248,7 @@ void HiRes5Engine::runIntro() {
 
 	display->setMode(Display::kModeGraphics);
 	display->loadFrameBuffer(*stream);
-	display->copyGfxSurface();
+	display->renderGraphics();
 
 	inputKey();
 
diff --git a/engines/adl/hires6.cpp b/engines/adl/hires6.cpp
index da5b465..53db92d 100644
--- a/engines/adl/hires6.cpp
+++ b/engines/adl/hires6.cpp
@@ -200,11 +200,11 @@ void HiRes6Engine::runIntro() {
 
 	display->setMode(Display::kModeGraphics);
 	display->loadFrameBuffer(*stream);
-	display->copyGfxSurface();
+	display->renderGraphics();
 	delay(256 * 8609 / 1000);
 
 	display->loadFrameBuffer(*stream);
-	display->copyGfxSurface();
+	display->renderGraphics();
 	delay(256 * 8609 / 1000);
 
 	display->loadFrameBuffer(*stream);
@@ -220,7 +220,7 @@ void HiRes6Engine::runIntro() {
 
 	delete files;
 
-	display->copyGfxSurface();
+	display->renderGraphics();
 	display->home();
 	display->setMode(Display::kModeMixed);
 	display->moveCursorTo(Common::Point(0, 21));
@@ -328,7 +328,7 @@ void HiRes6Engine::showRoom() {
 	if (!_state.isDark)
 		drawItems();
 
-	_display->copyGfxSurface();
+	_display->renderGraphics();
 	setVar(2, 0xff);
 	printString(_roomData.description);
 }


Commit: 32824ea83f05ee28be5816c30ff14ba57b84ade1
    https://github.com/scummvm/scummvm/commit/32824ea83f05ee28be5816c30ff14ba57b84ade1
Author: Walter van Niftrik (walter at scummvm.org)
Date: 2019-08-12T02:18:34+02:00

Commit Message:
ADL: Improve color accuracy

This adds two new display modes to replace the old one. One is a
16-color mode and the other does TV "emulation" based on code in
AppleWin. Both of these modes should deliver more accurate colors,
including NTSC artifact colors.

Changed paths:
    engines/adl/configure.engine
    engines/adl/detection.cpp
    engines/adl/display_a2.cpp
    engines/adl/display_a2.h


diff --git a/engines/adl/configure.engine b/engines/adl/configure.engine
index 8abee75..c138b64 100644
--- a/engines/adl/configure.engine
+++ b/engines/adl/configure.engine
@@ -1,3 +1,3 @@
 # This file is included from the main "configure" script
 # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine adl "ADL" yes "" "" "highres"
+add_engine adl "ADL" yes "" "" "16bit highres"
diff --git a/engines/adl/detection.cpp b/engines/adl/detection.cpp
index 52807ca..45cd73c 100644
--- a/engines/adl/detection.cpp
+++ b/engines/adl/detection.cpp
@@ -41,13 +41,25 @@ namespace Adl {
 #define GAMEOPTION_COLOR_DEFAULT_OFF GUIO_GAMEOPTIONS1
 #define GAMEOPTION_SCANLINES         GUIO_GAMEOPTIONS2
 #define GAMEOPTION_COLOR_DEFAULT_ON  GUIO_GAMEOPTIONS3
+#define GAMEOPTION_NTSC              GUIO_GAMEOPTIONS4
+#define GAMEOPTION_MONO_TEXT         GUIO_GAMEOPTIONS5
 
 static const ADExtraGuiOptionsMap optionsList[] = {
 	{
+		GAMEOPTION_NTSC,
+		{
+			_s("TV emulation"),
+			_s("Emulate composite output to an NTSC TV"),
+			"ntsc",
+			true
+		}
+	},
+
+	{
 		GAMEOPTION_COLOR_DEFAULT_OFF,
 		{
-			_s("Color mode"),
-			_s("Use color graphics"),
+			_s("Color graphics"),
+			_s("Use color graphics instead of monochrome"),
 			"color",
 			false
 		}
@@ -56,8 +68,8 @@ static const ADExtraGuiOptionsMap optionsList[] = {
 	{
 		GAMEOPTION_COLOR_DEFAULT_ON,
 		{
-			_s("Color mode"),
-			_s("Use color graphics"),
+			_s("Color graphics"),
+			_s("Use color graphics instead of monochrome"),
 			"color",
 			true
 		}
@@ -66,16 +78,29 @@ static const ADExtraGuiOptionsMap optionsList[] = {
 	{
 		GAMEOPTION_SCANLINES,
 		{
-			_s("Scanlines"),
 			_s("Show scanlines"),
+			_s("Darken every other scanline to mimic the look of a CRT"),
 			"scanlines",
 			false
 		}
 	},
 
+	{
+		GAMEOPTION_MONO_TEXT,
+		{
+			_s("Always use sharp monochrome text"),
+			_s("Do not emulate NTSC artifacts for text"),
+			"monotext",
+			true
+		}
+	},
+
 	AD_EXTRA_GUI_OPTIONS_TERMINATOR
 };
 
+#define DEFAULT_OPTIONS GUIO4(GAMEOPTION_NTSC, GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_MONO_TEXT, GAMEOPTION_SCANLINES)
+#define MH_OPTIONS GUIO4(GAMEOPTION_NTSC, GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_MONO_TEXT, GAMEOPTION_SCANLINES)
+
 static const PlainGameDescriptor adlGames[] = {
 	{ "hires0", "Hi-Res Adventure #0: Mission Asteroid" },
 	{ "hires1", "Hi-Res Adventure #1: Mystery House" },
@@ -105,7 +130,7 @@ static const AdlGameDescription gameFileDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_SCANLINES)
+			MH_OPTIONS
 		},
 		GAME_TYPE_HIRES1,
 		GAME_VER_HR1_SIMI
@@ -121,7 +146,7 @@ static const AdlGameDescription gameFileDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_SCANLINES)
+			MH_OPTIONS
 		},
 		GAME_TYPE_HIRES1,
 		GAME_VER_HR1_COARSE
@@ -138,7 +163,7 @@ static const AdlGameDescription gameFileDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_SCANLINES)
+			MH_OPTIONS
 		},
 		GAME_TYPE_HIRES1,
 		GAME_VER_HR1_PD
@@ -157,7 +182,7 @@ static const AdlGameDescription gameDiskDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_SCANLINES)
+			MH_OPTIONS
 		},
 		GAME_TYPE_HIRES1,
 		GAME_VER_HR1_COARSE
@@ -172,7 +197,7 @@ static const AdlGameDescription gameDiskDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_SCANLINES)
+			MH_OPTIONS
 		},
 		GAME_TYPE_HIRES1,
 		GAME_VER_HR1_PD
@@ -187,7 +212,7 @@ static const AdlGameDescription gameDiskDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
+			DEFAULT_OPTIONS
 		},
 		GAME_TYPE_HIRES2,
 		GAME_VER_NONE
@@ -202,7 +227,7 @@ static const AdlGameDescription gameDiskDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
+			DEFAULT_OPTIONS
 		},
 		GAME_TYPE_HIRES0,
 		GAME_VER_NONE
@@ -217,7 +242,7 @@ static const AdlGameDescription gameDiskDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
+			DEFAULT_OPTIONS
 		},
 		GAME_TYPE_HIRES3,
 		GAME_VER_NONE
@@ -233,7 +258,7 @@ static const AdlGameDescription gameDiskDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
+			DEFAULT_OPTIONS
 		},
 		GAME_TYPE_HIRES4,
 		GAME_VER_NONE
@@ -277,7 +302,7 @@ static const AdlGameDescription gameDiskDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
+			DEFAULT_OPTIONS
 		},
 		GAME_TYPE_HIRES5,
 		GAME_VER_NONE
@@ -295,7 +320,7 @@ static const AdlGameDescription gameDiskDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
+			DEFAULT_OPTIONS
 		},
 		GAME_TYPE_HIRES6,
 		GAME_VER_NONE
@@ -313,7 +338,7 @@ static const AdlGameDescription gameDiskDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformApple2,
 			ADGF_NO_FLAGS,
-			GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
+			DEFAULT_OPTIONS
 		},
 		GAME_TYPE_HIRES6,
 		GAME_VER_NONE
diff --git a/engines/adl/display_a2.cpp b/engines/adl/display_a2.cpp
index e738e77..0471b66 100644
--- a/engines/adl/display_a2.cpp
+++ b/engines/adl/display_a2.cpp
@@ -20,11 +20,18 @@
  *
  */
 
+// Based on AppleWin's code for NTSC emulation and its RGB Monitor palette
+// Copyright (C) 2010-2011, William S Simms
+// Copyright (C) 2014-2016, Michael Pohoreski, Tom Charlesworth
+// Licensed under GPLv2+
+
 #include "common/stream.h"
 #include "common/rect.h"
 #include "common/system.h"
 #include "common/str.h"
 #include "common/config-manager.h"
+#include "common/math.h"
+#include "common/memstream.h"
 
 #include "graphics/surface.h"
 #include "graphics/palette.h"
@@ -37,376 +44,457 @@
 
 namespace Adl {
 
-#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
-};
+#define NTSC_REMOVE_BLACK_GHOSTING
+// #define NTSC_REMOVE_WHITE_RINGING
 
-// Opacity of the optional scanlines (percentage)
-#define SCANLINE_OPACITY 75
+// Uppercase-only Apple II font (manually created).
+const byte Display_A2::_font[64][8] = {
+	{ 0x00, 0x1c, 0x22, 0x2a, 0x3a, 0x1a, 0x02, 0x3c }, { 0x00, 0x08, 0x14, 0x22, 0x22, 0x3e, 0x22, 0x22 }, // @A
+	{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x22, 0x22, 0x1e }, { 0x00, 0x1c, 0x22, 0x02, 0x02, 0x02, 0x22, 0x1c }, // BC
+	{ 0x00, 0x1e, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1e }, { 0x00, 0x3e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x3e }, // DE
+	{ 0x00, 0x3e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x02 }, { 0x00, 0x3c, 0x02, 0x02, 0x02, 0x32, 0x22, 0x3c }, // FG
+	{ 0x00, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22 }, { 0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c }, // HI
+	{ 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x1c }, { 0x00, 0x22, 0x12, 0x0a, 0x06, 0x0a, 0x12, 0x22 }, // JK
+	{ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x3e }, { 0x00, 0x22, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x22 }, // LM
+	{ 0x00, 0x22, 0x22, 0x26, 0x2a, 0x32, 0x22, 0x22 }, { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c }, // NO
+	{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x02 }, { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x2a, 0x12, 0x2c }, // PQ
+	{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x0a, 0x12, 0x22 }, { 0x00, 0x1c, 0x22, 0x02, 0x1c, 0x20, 0x22, 0x1c }, // RS
+	{ 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 }, { 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c }, // TU
+	{ 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x14, 0x08 }, { 0x00, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x36, 0x22 }, // VW
+	{ 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22 }, { 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08 }, // XY
+	{ 0x00, 0x3e, 0x20, 0x10, 0x08, 0x04, 0x02, 0x3e }, { 0x00, 0x3e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x3e }, // Z[
+	{ 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, { 0x00, 0x3e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3e }, // \]
+	{ 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e }, // ^_
+	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08 }, //  !
+	{ 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x14, 0x14, 0x3e, 0x14, 0x3e, 0x14, 0x14 }, // "#
+	{ 0x00, 0x08, 0x3c, 0x0a, 0x1c, 0x28, 0x1e, 0x08 }, { 0x00, 0x06, 0x26, 0x10, 0x08, 0x04, 0x32, 0x30 }, // $%
+	{ 0x00, 0x04, 0x0a, 0x0a, 0x04, 0x2a, 0x12, 0x2c }, { 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00 }, // &'
+	{ 0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08 }, { 0x00, 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08 }, // ()
+	{ 0x00, 0x08, 0x2a, 0x1c, 0x08, 0x1c, 0x2a, 0x08 }, { 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00 }, // *+
+	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x04 }, { 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00 }, // ,-
+	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }, { 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, // ./
+	{ 0x00, 0x1c, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x1c }, { 0x00, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x1c }, // 01
+	{ 0x00, 0x1c, 0x22, 0x20, 0x18, 0x04, 0x02, 0x3e }, { 0x00, 0x3e, 0x20, 0x10, 0x18, 0x20, 0x22, 0x1c }, // 23
+	{ 0x00, 0x10, 0x18, 0x14, 0x12, 0x3e, 0x10, 0x10 }, { 0x00, 0x3e, 0x02, 0x1e, 0x20, 0x20, 0x22, 0x1c }, // 45
+	{ 0x00, 0x38, 0x04, 0x02, 0x1e, 0x22, 0x22, 0x1c }, { 0x00, 0x3e, 0x20, 0x10, 0x08, 0x04, 0x04, 0x04 }, // 67
+	{ 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c }, { 0x00, 0x1c, 0x22, 0x22, 0x3c, 0x20, 0x10, 0x0e }, // 89
+	{ 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x04 }, // :;
+	{ 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10 }, { 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00 }, // <=
+	{ 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04 }, { 0x00, 0x1c, 0x22, 0x10, 0x08, 0x08, 0x00, 0x08 }  // >?
+};
 
-// Corresponding color in second palette
-#define PAL2(X) ((X) | 0x04)
+struct LineDoubleBright {
+	static uint8 blend(uint8 c1, uint8 c2) {
+		return c1;
+	}
+};
 
-// Alternate color for odd pixel rows (for scanlines)
-#define ALTCOL(X) ((X) | 0x08)
+struct LineDoubleDim {
+	static uint8 blend(uint8 c1, uint8 c2) {
+		return (c1 >> 1) + (c1 >> 2);
+	}
+};
 
-// Green monochrome palette
-#define MONO_PALETTE_ENTRIES 2
-static const byte monoPalette[MONO_PALETTE_ENTRIES * 3] = {
-	0x00, 0x00, 0x00,
-	0x00, 0xc0, 0x01
+struct BlendBright {
+	static uint8 blend(uint8 c1, uint8 c2) {
+		return (c1 + c2) >> 1;
+	}
 };
 
-// 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 }  // >?
+struct BlendDim {
+	static uint8 blend(uint8 c1, uint8 c2) {
+		// AppleWin does c1 >>= 2; return (c1 < c2 ? c2 - c1 : 0);
+		// I think the following looks a lot better:
+		return ((c1 + c2) >> 2) + ((c1 + c2) >> 3);
+	}
 };
 
+static const uint kColorPhases = 4;
+// All PixelWriters have been adjusted to have 3 pixels of "pre-render" that
+// will be cut off when blitting to the screen
+static const uint kPreRender = 3;
 
-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;
+template<typename ColorType, typename T>
+class PixelWriter {
+public:
+	PixelWriter() : _ptr(nullptr), _format(g_system->getScreenFormat()), _phase(0), _window(0) { }
+
+	void setupWrite(ColorType *dest) {
+		_ptr = dest;
+		_phase = 3;
+		_window = 0;
 	}
 
-	if (secondPal)
-		color = PAL2(color);
+	void writePixels(uint bits) {
+		for (uint b = 0; b < 14; ++b) {
+			_window <<= 1;
+			_window |= bits & 1;
+			bits >>= 1;
+			*_ptr++ = static_cast<T *>(this)->getColor();
+			_phase = (_phase + 1) & 3;
+		}
+	}
 
-	odd = !odd;
-	bits >>= 1;
+protected:
+	ColorType *_ptr;
+	Graphics::PixelFormat _format;
+	uint _phase;
+	uint _window;
+};
 
-	return color;
-}
+template<typename ColorType>
+class PixelWriterColor : public PixelWriter<ColorType, PixelWriterColor<ColorType> > {
+public:
+	static const uint kColors = 16;
+	typedef LineDoubleBright BlendRegular;
+	typedef LineDoubleDim BlendScanlines;
+
+	PixelWriterColor() {
+		const byte palette[kColors][3] = {
+			{ 0x00, 0x00, 0x00 }, { 0x9d, 0x09, 0x66 }, { 0x2a, 0x2a, 0xe5 }, { 0xc7, 0x34, 0xff },
+			{ 0x00, 0x80, 0x00 }, { 0x80, 0x80, 0x80 }, { 0x0d, 0xa1, 0xff }, { 0xaa, 0xaa, 0xff },
+			{ 0x55, 0x55, 0x00 }, { 0xf2, 0x5e, 0x00 }, { 0xc0, 0xc0, 0xc0 }, { 0xff, 0x89, 0xe5 },
+			{ 0x38, 0xcb, 0x00 }, { 0xd5, 0xd5, 0x1a }, { 0x62, 0xf6, 0x99 }, { 0xff, 0xff, 0xff }
+		};
+
+		for (uint pattern = 0; pattern < kColors; ++pattern) {
+			uint color = ((pattern & 1) << 3) | ((pattern & 2) << 1) | ((pattern & 4) >> 1) | ((pattern & 8) >> 3);
+
+			for (uint phase = 0; phase < kColorPhases; ++phase) {
+				_colors[phase][pattern] = this->_format.RGBToColor(palette[color][0], palette[color][1], palette[color][2]);
+				color = ((color & 8) >> 3) | ((color << 1) & 0x0f);
+			}
+		}
+	}
 
-static void renderPixelRowColor(byte *dst, byte *src) {
-	uint16 bits = (src[0] & 0x7f) << 1;
-	byte pal = src[0] >> 7;
+	// >> 2 to synchronize rendering output with NTSC
+	ColorType getColor() { return _colors[this->_phase][(this->_window >> 2) & (kColors - 1)]; }
 
-	if (pal != 0)
-		*dst++ = 0;
+private:
+	ColorType _colors[kColorPhases][kColors];
+};
 
-	bool odd = false;
+template<typename ColorType, uint8 R, uint8 G, uint8 B>
+class PixelWriterMono : public PixelWriter<ColorType, PixelWriterMono<ColorType, R, G, B> > {
+public:
+	static const uint kColors = 2;
 
-	for (uint i = 0; i < Display_A2::kGfxPitch; ++i) {
-		if (i != Display_A2::kGfxPitch - 1) {
-			bits |= (src[i + 1] & 0x7f) << 8;
-			pal |= (src[i + 1] >> 7) << 1;
-		}
+	typedef LineDoubleBright BlendRegular;
+	typedef LineDoubleDim BlendScanlines;
 
-		// 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;
-		}
+	PixelWriterMono() {
+		_colors[0] = this->_format.RGBToColor(0, 0, 0);
+		_colors[1] = this->_format.RGBToColor(R, G, B);
+	}
 
-		// 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);
-		}
+	ColorType getColor() { return _colors[(this->_window >> 3) & (kColors - 1)]; }
 
-		pal >>= 1;
-	}
-}
+private:
+	ColorType _colors[kColors];
+};
 
-static void renderPixelRowMono(byte *dst, byte *src) {
-	byte pal = src[0] >> 7;
+static double filterChroma(double z) {
+	static double x[3] = {0, 0, 0};
+	static double y[3] = {0, 0, 0};
 
-	if (pal != 0)
-		*dst++ = 0;
+	x[0] = x[1];
+	x[1] = x[2];
+	x[2] = z / 7.438011255;
 
-	for (uint i = 0; i < Display_A2::kGfxPitch; ++i) {
-		if (i != Display_A2::kGfxPitch - 1)
-			pal |= (src[i + 1] >> 7) << 1;
+	y[0] = y[1];
+	y[1] = y[2];
+	y[2] = -x[0] + x[2] + (-0.7318893645 * y[0]) + (1.2336442711 * y[1]);
 
-		for (uint j = 0; j < 6; ++j) {
-			bool color = src[i] & (1 << j);
-			*dst++ = color;
-			*dst++ = color;
-		}
+	return y[2];
+}
 
-		bool color = src[i] & (1 << 6);
+static double filterLuma(double z) {
+	static double x[3] = {0, 0, 0};
+	static double y[3] = {0, 0, 0};
 
-		*dst++ = color;
+	x[0] = x[1];
+	x[1] = x[2];
+	x[2] = z / 13.71331570;
 
-		switch (pal) {
-			case 0x0:
-			case 0x3:
-				*dst++ = color;
-				break;
-			case 0x2:
-				*dst++ = color;
-				*dst++ = color;
-		}
+	y[0] = y[1];
+	y[1] = y[2];
+	y[2] = x[0] + x[2] + (2.f * x[1]) + (-0.3961075449 * y[0]) + (1.1044202472 * y[1]);
 
-		pal >>= 1;
-	}
+	return y[2];
 }
 
-static void copyEvenSurfaceRows(Graphics::Surface &surf) {
-	byte *src = (byte *)surf.getPixels();
+static double filterSignal(double z) {
+	static double x[3] = {0, 0, 0};
+	static double y[3] = {0, 0, 0};
 
-	for (uint y = 0; y < surf.h / 2u; ++y) {
-		byte *dst = src + surf.pitch;
-		for (uint x = 0; x < surf.w; ++x)
-			dst[x] = ALTCOL(src[x]);
-		src += surf.pitch * 2;
-	}
+	x[0] = x[1];
+	x[1] = x[2];
+	x[2] = z / 7.614490548;
+
+	y[0] = y[1];
+	y[1] = y[2];
+	y[2] = x[0] + x[2] + (2.0 * x[1]) + (-0.2718798058 * y[0]) + (0.7465656072 * y[1]);
+
+	return y[2];
 }
 
-class Display_A2_Monitor : public Display_A2 {
+template<typename ColorType>
+class PixelWriterColorNTSC : public PixelWriter<ColorType, PixelWriterColorNTSC<ColorType> > {
 public:
-	Display_A2_Monitor();
-	~Display_A2_Monitor();
-
-	enum {
-		kSplitHeight = 64
-	};
+	static const uint kColors = 4096;
+
+	typedef BlendBright BlendRegular;
+	typedef BlendDim BlendScanlines;
+
+	PixelWriterColorNTSC() {
+		for (uint phase = 0; phase < kColorPhases; ++phase) {
+			double phi = Common::deg2rad(phase * 90.0 + 45.0);
+			for (uint s = 0; s < kColors; ++s) {
+				uint t = s;
+				double y;
+				double i = 0.0;
+				double q = 0.0;
+
+				for (uint n = 0; n < 12; ++n) {
+					double z = (double)(0 != (t & 0x800));
+					t = t << 1;
+
+					for (uint k = 0; k < 2; k++ ) {
+						const double zz = filterSignal(z);
+						double c = filterChroma(zz);
+						y = filterLuma(zz - c);
+
+						c = c * 2.0;
+						i = i + (c * cos(phi) - i) / 8.0;
+						q = q + (c * sin(phi) - q) / 8.0;
+
+						phi += Common::deg2rad(45.0);
+					}
+				}
+
+				// YIQ to RGB
+				const double r64 = y + (0.956 * i) + (0.621 * q);
+				const double g64 = y + (-0.272 * i) + (-0.647 * q);
+				const double b64 = y + (-1.105 * i) + (1.702 * q);
+
+				uint8 r = CLIP(r64, 0.0, 1.0) * 255;
+				uint8 g = CLIP(g64, 0.0, 1.0) * 255;
+				uint8 b = CLIP(b64, 0.0, 1.0) * 255;
+
+	#ifdef NTSC_REMOVE_WHITE_RINGING
+				if ((s & 0xf) == 15) {
+					// white
+					r = 255;
+					g = 255;
+					b = 255;
+				}
+	#endif			
+
+	#ifdef NTSC_REMOVE_BLACK_GHOSTING
+				if ((s & 0xf) == 0) {
+					// Black
+					r = 0;
+					g = 0;
+					b = 0;
+				}
+	#endif
+
+				_colors[phase][s] = this->_format.RGBToColor(r, g, b);
+			}
+		}
+	}
 
-	void init() override;
-	void renderText() override;
-	void renderGraphics() override;
+	ColorType getColor() { return _colors[this->_phase][(this->_window >> 1) & (kColors - 1)]; }
 
 private:
-	void updateTextSurface();
-	void updateGfxSurface();
-	void drawChar(byte c, int x, int y);
-	void createFont();
-	void showScanlines(bool enable);
-	void createSurfaces(uint gfxWidth, uint gfxHeight);
-
-	Graphics::Surface *_textSurface;
-	Graphics::Surface *_gfxSurface;
-	Graphics::Surface *_font;
-	bool _scanlines;
-	bool _monochrome;
+	ColorType _colors[kColorPhases][kColors];
 };
 
-Display_A2_Monitor::Display_A2_Monitor() :
-		_textSurface(nullptr),
-		_gfxSurface(nullptr),
-		_font(nullptr),
-		_scanlines(false),
-		_monochrome(false) { }
-
-Display_A2_Monitor::~Display_A2_Monitor() {
-	if (_font) {
-		_font->free();
-		delete _font;
-	}
+template<typename ColorType>
+class PixelWriterMonoNTSC : public PixelWriter<ColorType, PixelWriterMonoNTSC<ColorType> > {
+public:
+	static const uint kColors = 4096;
 
-	_textSurface->free();
-	delete _textSurface;
+	typedef BlendBright BlendRegular;
+	typedef BlendDim BlendScanlines;
 
-	_gfxSurface->free();
-	delete _gfxSurface;
-}
+	PixelWriterMonoNTSC() {
+		for (uint s = 0; s < kColors; ++s) {
+			uint t = s;
+			double y;
 
-void Display_A2_Monitor::createSurfaces(uint gfxWidth, uint gfxHeight) {
-	_gfxSurface = new Graphics::Surface;
-	_gfxSurface->create(gfxWidth, gfxHeight, Graphics::PixelFormat::createFormatCLUT8());
-	_textSurface = new Graphics::Surface;
-	_textSurface->create(gfxWidth, gfxHeight, Graphics::PixelFormat::createFormatCLUT8());
-}
+			for (uint n = 0; n < 12; ++n) {
+				double z = (double)(0 != (t & 0x800));
+				t = t << 1;
 
-void Display_A2_Monitor::init() {
-	Display_A2::init();
+				for (uint k = 0; k < 2; k++ ) {
+					const double zz = filterSignal(z);
+					double c = filterChroma(zz);
+					y = filterLuma(zz - c);
+				}
+			}
 
-	// We need 2x scaling to properly render the half-pixel shift
-	// of the second palette
-	createSurfaces(Display_A2::kGfxWidth * 2, Display_A2::kGfxHeight * 2);
+			const uint8 brightness = CLIP(y, 0.0, 1.0) * 255;
+			_colors[s] = this->_format.RGBToColor(brightness, brightness, brightness);
+		}
+	}
 
-	_monochrome = !ConfMan.getBool("color");
-	_scanlines = ConfMan.getBool("scanlines");
+	ColorType getColor() { return _colors[(this->_window >> 1) & (kColors - 1)]; }
 
-	if (_monochrome)
-		g_system->getPaletteManager()->setPalette(monoPalette, 0, MONO_PALETTE_ENTRIES);
-	else
-		g_system->getPaletteManager()->setPalette(colorPalette, 0, COLOR_PALETTE_ENTRIES);
+private:
+	ColorType _colors[kColors];
+};
 
-	createFont();
+template<typename ColorType, typename GfxWriter, typename TextWriter>
+class DisplayImpl_A2 : public Display_A2 {
+public:
+	DisplayImpl_A2();
+	~DisplayImpl_A2();
 
-	showScanlines(_scanlines);
-}
+	void renderText() override;
+	void renderGraphics() override;
 
-void Display_A2_Monitor::updateGfxSurface() {
-	byte *src = _frameBuf;
-	byte *dst = (byte *)_gfxSurface->getPixels();
+private:
+	enum {
+		kRenderBufWidth = (kGfxPitch + 1) * 14, // one extra chunk to account for pre-render
+		kRenderBufHeight = (kGfxHeight * 2) + 1 // one extra line to simplify scanline mixing
+	};
 
-	for (uint i = 0; i < Display_A2::kGfxHeight; ++i) {
-		if (_monochrome)
-			renderPixelRowMono(dst, src);
-		else
-			renderPixelRowColor(dst, src);
-		src += Display_A2::kGfxPitch;
-		dst += _gfxSurface->pitch * 2;
-	}
+	template<typename BlendFunc>
+	void blendScanlines(uint yStart, uint yEnd);
 
-	copyEvenSurfaceRows(*_gfxSurface);
-}
+	template<typename Reader, typename Writer>
+	void render(Writer &writer);
 
-void Display_A2_Monitor::updateTextSurface() {
-	for (uint row = 0; row < 24; ++row)
-		for (uint col = 0; col < Display_A2::kTextWidth; ++col) {
-			uint charPos = row * Display_A2::kTextWidth + col;
-			char c = _textBuf[row * Display_A2::kTextWidth + col];
+	ColorType *_renderBuf;
+	uint16 _doublePixelMasks[128];
 
-			if (charPos == _cursorPos && _showCursor)
-				c = (c & 0x3f) | 0x40;
+	GfxWriter _writerColor;
+	TextWriter _writerMono;
+};
 
-			Common::Rect r(7 * 2, 8 * 2);
-			r.translate(((c & 0x3f) % 16) * 7 * 2, (c & 0x3f) / 16 * 8 * 2);
+template<typename ColorType, typename GfxWriter, typename TextWriter>
+DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::DisplayImpl_A2() : _doublePixelMasks() {
+	_renderBuf = new ColorType[kRenderBufHeight * kRenderBufWidth]();
 
-			if (!(c & 0x80)) {
-				if (!(c & 0x40) || ((g_system->getMillis() / 270) & 1))
-					r.translate(0, 4 * 8 * 2);
-			}
+	for (uint8 val = 0; val < ARRAYSIZE(_doublePixelMasks); ++val)
+		for (uint8 mask = 0; mask < 7; mask++)
+			if (val & (1 << mask))
+				_doublePixelMasks[val] |= 3 << (mask * 2);
+}
 
-			_textSurface->copyRectToSurface(*_font, col * 7 * 2, row * 8 * 2, r);
-		}
+template<typename ColorType, typename GfxWriter, typename TextWriter>
+DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::~DisplayImpl_A2() {
+	delete[] _renderBuf;
 }
 
-void Display_A2_Monitor::renderText() {
-	updateTextSurface();
+template<typename ColorType, typename GfxWriter, typename TextWriter>
+template<typename Reader, typename Writer>
+void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::render(Writer &writer) {
+	uint startY = Reader::getStartY(this);
+	const uint endY = Reader::getEndY(this);
 
-	if (_mode == Display::kModeText)
-		g_system->copyRectToScreen(_textSurface->getPixels(), _textSurface->pitch, 0, 0, _textSurface->w, _textSurface->h);
-	else if (_mode == Display::kModeMixed)
-		g_system->copyRectToScreen(_textSurface->getBasePtr(0, _textSurface->h - kSplitHeight), _textSurface->pitch, 0, _textSurface->h - kSplitHeight, _textSurface->w, kSplitHeight);
+	ColorType *ptr = _renderBuf + startY * kRenderBufWidth * 2;
 
-	g_system->updateScreen();
-}
+	for (uint y = startY; y < endY; ++y) {
+		uint16 lastBit = 0;
 
-void Display_A2_Monitor::renderGraphics() {
-	updateGfxSurface();
+		writer.setupWrite(ptr);
 
-	if (_mode == kModeGraphics)
-		g_system->copyRectToScreen(_gfxSurface->getPixels(), _gfxSurface->pitch, 0, 0, _gfxSurface->w, _gfxSurface->h);
-	else if (_mode == kModeMixed)
-		g_system->copyRectToScreen(_gfxSurface->getPixels(), _gfxSurface->pitch, 0, 0, _gfxSurface->w, _gfxSurface->h - kSplitHeight);
+		for (uint x = 0; x < kGfxPitch; ++x) {
+			const uint8 m = Reader::getBits(this, y, x);
 
-	g_system->updateScreen();
-}
+			uint16 bits = _doublePixelMasks[m & 0x7F];
 
-void Display_A2_Monitor::drawChar(byte c, int x, int y) {
-	byte *buf = (byte *)_font->getPixels() + y * _font->pitch + x;
+			if (m & 0x80)
+				bits = (bits << 1) | lastBit;
 
-	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;
-			}
+			lastBit = (bits >> 13) & 1;
+
+			writer.writePixels(bits);
 		}
 
-		buf += 2 * _font->pitch;
+		// Because of the pre-render, we need to feed
+		// in some more bits to get the full picture
+		writer.writePixels(0);
+
+		// The odd lines will be filled in later, so skip a line
+		ptr += 2 * kRenderBufWidth;
 	}
-}
 
-void Display_A2_Monitor::createFont() {
-	_font = new Graphics::Surface;
-	_font->create(16 * 7 * 2, 4 * 8 * 2 * 2, Graphics::PixelFormat::createFormatCLUT8());
+	if (_enableScanlines)
+		blendScanlines<typename Writer::BlendScanlines>(startY, endY);
+	else
+		blendScanlines<typename Writer::BlendRegular>(startY, endY);
 
-	for (uint i = 0; i < 4; ++i)
-		for (uint j = 0; j < 16; ++j)
-			drawChar(i * 16 + j, j * 7 * 2, i * 8 * 2);
+	// For the NTSC modes we need to redo the scanline that blends with our first line
+	if (GfxWriter::kColors == 4096 && startY > 0) {
+		--startY;
+		if (_enableScanlines)
+			blendScanlines<typename GfxWriter::BlendScanlines>(startY, startY + 1);
+		else
+			blendScanlines<typename GfxWriter::BlendRegular>(startY, startY + 1);
+	}
 
-	// Create inverted font
-	byte *buf = (byte *)_font->getPixels();
-	byte *bufInv = buf + (_font->h / 2) * _font->pitch;
+	g_system->copyRectToScreen(_renderBuf + startY * 2 * kRenderBufWidth + kPreRender, kRenderBufWidth * sizeof(ColorType), 0, startY * 2, kGfxWidth * 2, (endY - startY) * 2);
+	g_system->updateScreen();
+}
 
-	for (uint row = 0; row < _font->h / 2u; row += 2) {
-		for (uint col = 0; col < _font->w; ++col)
-			bufInv[col] = (buf[col] ? 0 : 1);
+template<typename ColorType, typename GfxWriter, typename TextWriter>
+template<typename BlendType>
+void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::blendScanlines(uint yStart, uint yEnd) {
+	const Graphics::PixelFormat rgbFormat = g_system->getScreenFormat();
 
-		buf += _font->pitch * 2;
-		bufInv += _font->pitch * 2;
-	}
+	// Note: this reads line yEnd * 2 of _renderBuf!
+	for (uint y = yStart; y < yEnd; ++y) {
+		ColorType *buf = &_renderBuf[y * 2 * kRenderBufWidth];
+		for (uint x = 0; x < kRenderBufWidth; ++x) {
+			const ColorType color1 = buf[x];
+			const ColorType color2 = buf[2 * kRenderBufWidth + x];
 
-	copyEvenSurfaceRows(*_font);
-}
+			uint8 r1, g1, b1, r2, g2, b2;
 
-void Display_A2_Monitor::showScanlines(bool enable) {
-	byte pal[COLOR_PALETTE_ENTRIES * 3];
+			rgbFormat.colorToRGB(color1, r1, g1, b1);
+			rgbFormat.colorToRGB(color2, r2, g2, b2);
 
-	g_system->getPaletteManager()->grabPalette(pal, 0, COLOR_PALETTE_ENTRIES);
+			const uint8 r3 = BlendType::blend(r1, r2);
+			const uint8 g3 = BlendType::blend(g1, g2);
+			const uint8 b3 = BlendType::blend(b1, b2);
 
-	if (enable) {
-		for (uint i = 0; i < ARRAYSIZE(pal); ++i)
-			pal[i] = pal[i] * (100 - SCANLINE_OPACITY) / 100;
+			buf[kRenderBufWidth + x] = rgbFormat.RGBToColor(r3, g3, b3);
+		}
 	}
+}
+
+template<typename ColorType, typename GfxWriter, typename TextWriter>
+void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::renderText() {
+	if (_mode == kModeGraphics)
+		return;
 
-	g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES);
+	_blink = (g_system->getMillis() / 270) & 1;
+
+	if (_mode == kModeMixed && _enableColor && !_enableMonoText)
+		render<TextReader>(_writerColor);
+	else
+		render<TextReader>(_writerMono);
 }
 
-Display_A2::Display_A2() : _frameBuf(nullptr), _showCursor(false) {
-	initGraphics(Display_A2::kGfxWidth * 2, Display_A2::kGfxHeight * 2);
+template<typename ColorType, typename GfxWriter, typename TextWriter>
+void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::renderGraphics() {
+	if (_mode == kModeText)
+		return;
+
+	render<GfxReader>(_writerColor);
 }
 
+Display_A2::Display_A2() :
+		_frameBuf(nullptr),
+		_showCursor(false),
+		_enableColor(false),
+		_enableScanlines(false),
+		_enableMonoText(false),
+		_blink(false) { }
+
 Display_A2::~Display_A2() {
 	delete[] _frameBuf;
 }
@@ -414,11 +502,14 @@ Display_A2::~Display_A2() {
 void Display_A2::init() {
 	createTextBuffer(Display_A2::kTextWidth, Display_A2::kTextHeight);
 
-	_frameBuf = new byte[Display_A2::kGfxSize];
-	memset(_frameBuf, 0, Display_A2::kGfxSize);
+	_frameBuf = new byte[Display_A2::kGfxSize]();
+
+	_enableColor = ConfMan.getBool("color");
+	_enableScanlines = ConfMan.getBool("scanlines");
+	_enableMonoText = ConfMan.getBool("monotext");
 }
 
-void Display_A2::loadFrameBuffer(Common::ReadStream &stream, byte *dst) {
+void Display_A2::loadFrameBuffer(Common::ReadStream &stream, byte *dst) const {
 	for (uint j = 0; j < 8; ++j) {
 		for (uint i = 0; i < 8; ++i) {
 			stream.read(dst, Display_A2::kGfxPitch);
@@ -442,7 +533,7 @@ void Display_A2::loadFrameBuffer(Common::ReadStream &stream) {
 }
 
 void Display_A2::putPixel(const Common::Point &p, byte color) {
-	byte offset = p.x / 7;
+	const byte offset = p.x / 7;
 	byte mask = 0x80 | (1 << (p.x % 7));
 
 	// Since white and black are in both palettes, we leave
@@ -484,14 +575,14 @@ byte Display_A2::getPixelByte(const Common::Point &p) const {
 bool Display_A2::getPixelBit(const Common::Point &p) const {
 	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
 
-	byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
+	const byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
 	return *b & (1 << (p.x % 7));
 }
 
 void Display_A2::clear(byte color) {
 	byte val = 0;
 
-	byte c = color << 1;
+	const byte c = color << 1;
 	if (c >= 0x40 && c < 0xc0)
 		val = 0x7f;
 
@@ -530,6 +621,47 @@ void Display_A2::writeFrameBuffer(const Common::Point &p, byte color, byte mask)
 	*b ^= color;
 }
 
-Display_A2 *Display_A2_create() { return new Display_A2_Monitor(); }
+template<typename ColorType>
+static Display_A2 *Display_A2_create_helper() {
+	const bool ntsc = ConfMan.getBool("ntsc");
+	const bool color = ConfMan.getBool("color");
+	const bool monotext = ConfMan.getBool("monotext");
+
+	typedef PixelWriterMono<ColorType, 0xff, 0xff, 0xff> PixelWriterMonoWhite;
+	typedef PixelWriterMono<ColorType, 0x00, 0xc0, 0x00> PixelWriterMonoGreen;
+
+	if (ntsc) {
+		if (color) {
+			if (monotext)
+				return new DisplayImpl_A2<ColorType, PixelWriterColorNTSC<ColorType>, PixelWriterMonoWhite>;
+			else
+				return new DisplayImpl_A2<ColorType, PixelWriterColorNTSC<ColorType>, PixelWriterMonoNTSC<ColorType> >;
+		} else {
+			if (monotext)
+				return new DisplayImpl_A2<ColorType, PixelWriterMonoNTSC<ColorType>, PixelWriterMonoWhite>;
+			else
+				return new DisplayImpl_A2<ColorType, PixelWriterMonoNTSC<ColorType>, PixelWriterMonoNTSC<ColorType> >;
+		}
+	}
+
+	if (color)
+		return new DisplayImpl_A2<ColorType, PixelWriterColor<ColorType>, PixelWriterMonoWhite>;
+	else
+		return new DisplayImpl_A2<ColorType, PixelWriterMonoGreen, PixelWriterMonoGreen>;
+}
+
+Display_A2 *Display_A2_create() {
+	initGraphics(Display_A2::kGfxWidth * 2, Display_A2::kGfxHeight * 2, new Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0));
+	debugN(1, "Initialized graphics with format: %s\n", g_system->getScreenFormat().toString().c_str());
+
+	const uint bpp = g_system->getScreenFormat().bytesPerPixel;
+
+	if (bpp == 4)
+		return Display_A2_create_helper<uint32>();
+	else if (bpp == 2)
+		return Display_A2_create_helper<uint16>();
+	else
+		error("Graphics format uses %d bytes per pixel", bpp);		
+}
 
 } // End of namespace Adl
diff --git a/engines/adl/display_a2.h b/engines/adl/display_a2.h
index 4e84fac..8de3a77 100644
--- a/engines/adl/display_a2.h
+++ b/engines/adl/display_a2.h
@@ -36,9 +36,10 @@ public:
 		kGfxWidth = 280,
 		kGfxHeight = 192,
 		kGfxPitch = kGfxWidth / 7,
-		kGfxSize = kGfxWidth * kGfxHeight,
+		kGfxSize = kGfxPitch * kGfxHeight,
 		kTextWidth = 40,
-		kTextHeight = 24
+		kTextHeight = 24,
+		kSplitHeight = 32
 	};
 
 	void init() override;
@@ -47,7 +48,7 @@ public:
 	uint getGfxWidth() const { return kGfxWidth; }
 	uint getGfxHeight() const { return kGfxHeight; }
 	uint getGfxPitch() const { return kGfxPitch; }
-	void loadFrameBuffer(Common::ReadStream &stream, byte *dst);
+	void loadFrameBuffer(Common::ReadStream &stream, byte *dst) const ;
 	void loadFrameBuffer(Common::ReadStream &stream);
 	void putPixel(const Common::Point &p, byte color);
 	void setPixelByte(const Common::Point &p, byte color);
@@ -63,13 +64,60 @@ public:
 	void showCursor(bool enable) override;
 
 protected:
+	class TextReader {
+	public:
+		static uint16 getBits(const Display_A2 *display, uint y, uint x) {
+			const uint charPos = (y >> 3) * kTextWidth + x;
+			byte m = display->_textBuf[charPos];
+
+			if (display->_showCursor && charPos == display->_cursorPos)
+				m = (m & 0x3f) | 0x40;
+
+			byte b = _font[m & 0x3f][y % 8];
+
+			if (!(m & 0x80) && (!(m & 0x40) || display->_blink))
+				b = ~b;
+
+			return b & 0x7f;
+		}
+
+		static uint8 getStartY(const Display_A2 *display) {
+			if (display->_mode == kModeText)
+				return 0;
+			else
+				return kGfxHeight - kSplitHeight;
+		}
+
+		static uint8 getEndY(const Display_A2 *display) { return kGfxHeight; }
+	};
+
+	class GfxReader {
+	public:
+		static uint16 getBits(const Display_A2 *display, uint y, uint x) {
+			return display->_frameBuf[y * kGfxPitch + x];
+		}
+
+		static uint8 getStartY(const Display_A2 *display) { return 0; }
+
+		static uint8 getEndY(const Display_A2 *display) {
+			if (display->_mode == kModeGraphics)
+				return kGfxHeight;
+			else
+				return kGfxHeight - kSplitHeight;
+		}
+	};
+
 	byte *_frameBuf;
 	bool _showCursor;
+	bool _enableColor;
+	bool _enableScanlines;
+	bool _enableMonoText;
+	bool _blink;
 
 private:
 	void writeFrameBuffer(const Common::Point &p, byte color, byte mask);
 
-	virtual void showScanlines(bool enable) { };
+	static const byte _font[64][8];
 };
 
 Display_A2 *Display_A2_create();





More information about the Scummvm-git-logs mailing list