diff options
Diffstat (limited to 'engines/adl/graphics.h')
-rw-r--r-- | engines/adl/graphics.h | 553 |
1 files changed, 531 insertions, 22 deletions
diff --git a/engines/adl/graphics.h b/engines/adl/graphics.h index 38dc2b25aa..ad56281598 100644 --- a/engines/adl/graphics.h +++ b/engines/adl/graphics.h @@ -24,34 +24,44 @@ #define ADL_GRAPHICS_H #include "common/rect.h" +#include "common/stream.h" -namespace Common { -class SeekableReadStream; -} +#include "adl/display.h" namespace Adl { -class Display; - -// Used in hires1 class GraphicsMan { public: - GraphicsMan(Display &display) : _bounds(280, 160), _display(display) { } virtual ~GraphicsMan() { } // Applesoft BASIC HLINE - void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const; + virtual void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const = 0; // Applesoft BASIC DRAW - void drawShape(Common::ReadStream &shape, Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const; - - virtual void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos); - void clearScreen() const; - void putPixel(const Common::Point &p, byte color) const; + virtual void drawShape(Common::ReadStream &shape, Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const = 0; + virtual void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) = 0; + virtual void clearScreen() const = 0; void setBounds(const Common::Rect &r) { _bounds = r; } protected: - Display &_display; Common::Rect _bounds; +}; + +// Used in hires1 +template <class T> +class GraphicsMan_v1 : public GraphicsMan { +public: + using GraphicsMan::_bounds; + + GraphicsMan_v1<T>(T &display) : _display(display) { this->setBounds(Common::Rect(280, 160)); } + + virtual void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const override; + virtual void drawShape(Common::ReadStream &shape, Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const override; + virtual void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) override; + virtual void clearScreen() const override; + +protected: + T &_display; + void putPixel(const Common::Point &p, byte color) const; private: void drawShapePixel(Common::Point &p, byte color, byte bits, byte quadrant) const; @@ -59,38 +69,537 @@ private: }; // Used in hires0 and hires2-hires4 -class GraphicsMan_v2 : public GraphicsMan { +template <class T> +class GraphicsMan_v2 : public GraphicsMan_v1<T> { public: - GraphicsMan_v2(Display &display) : GraphicsMan(display), _color(0) { } + using GraphicsMan::_bounds; + using GraphicsMan_v1<T>::_display; + using GraphicsMan_v1<T>::drawLine; + using GraphicsMan_v1<T>::putPixel; + + GraphicsMan_v2<T>(T &display) : GraphicsMan_v1<T>(display), _color(0) { } void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos); protected: bool canFillAt(const Common::Point &p, const bool stopBit = false); void fillRow(Common::Point p, const byte pattern, const bool stopBit = false); + byte getPatternColor(const Common::Point &p, byte pattern); private: + static bool readByte(Common::SeekableReadStream &pic, byte &b); + bool readPoint(Common::SeekableReadStream &pic, Common::Point &p); void drawCorners(Common::SeekableReadStream &pic, bool yFirst); void drawRelativeLines(Common::SeekableReadStream &pic); void drawAbsoluteLines(Common::SeekableReadStream &pic); + void fill(Common::SeekableReadStream &pic); virtual void fillRowLeft(Common::Point p, const byte pattern, const bool stopBit); virtual void fillAt(Common::Point p, const byte pattern); - void fill(Common::SeekableReadStream &pic); - byte getClearColor() const { return 0xff; } + virtual byte getClearColor() const override { return 0xff; } byte _color; Common::Point _offset; }; // Used in hires5, hires6 and gelfling (possibly others as well) -class GraphicsMan_v3 : public GraphicsMan_v2 { +template <class T> +class GraphicsMan_v3 : public GraphicsMan_v2<T> { public: - GraphicsMan_v3(Display &display) : GraphicsMan_v2(display) { } + using GraphicsMan::_bounds; + using GraphicsMan_v1<T>::_display; + using GraphicsMan_v2<T>::canFillAt; + using GraphicsMan_v2<T>::fillRow; + using GraphicsMan_v2<T>::getPatternColor; + + GraphicsMan_v3<T>(T &display) : GraphicsMan_v2<T>(display) { } private: - void fillRowLeft(Common::Point p, const byte pattern, const bool stopBit); - void fillAt(Common::Point p, const byte pattern); + virtual void fillRowLeft(Common::Point p, const byte pattern, const bool stopBit) override; + virtual void fillAt(Common::Point p, const byte pattern) override; }; +template <class T> +void GraphicsMan_v1<T>::clearScreen() const { + _display.setMode(Display::kModeMixed); + _display.clear(getClearColor()); +} + +// Draws a four-connected line +template <class T> +void GraphicsMan_v1<T>::drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const { + int16 deltaX = p2.x - p1.x; + int8 xStep = 1; + + if (deltaX < 0) { + deltaX = -deltaX; + xStep = -1; + } + + int16 deltaY = p2.y - p1.y; + int8 yStep = -1; + + if (deltaY > 0) { + deltaY = -deltaY; + yStep = 1; + } + + Common::Point p(p1); + int16 steps = deltaX - deltaY + 1; + int16 err = deltaX + deltaY; + + while (true) { + putPixel(p, color); + + if (--steps == 0) + return; + + if (err < 0) { + p.y += yStep; + err += deltaX; + } else { + p.x += xStep; + err += deltaY; + } + } +} + +template <class T> +void GraphicsMan_v1<T>::putPixel(const Common::Point &p, byte color) const { + if (_bounds.contains(p)) + _display.putPixel(p, color); +} + +template <class T> +void GraphicsMan_v1<T>::drawShapePixel(Common::Point &p, byte color, byte bits, byte quadrant) const { + if (bits & 4) + putPixel(p, color); + + bits += quadrant; + + if (bits & 1) + p.x += (bits & 2 ? -1 : 1); + else + p.y += (bits & 2 ? 1 : -1); +} + +template <class T> +void GraphicsMan_v1<T>::drawShape(Common::ReadStream &corners, Common::Point &pos, byte rotation, byte scaling, byte color) const { + const byte stepping[] = { + 0xff, 0xfe, 0xfa, 0xf4, 0xec, 0xe1, 0xd4, 0xc5, + 0xb4, 0xa1, 0x8d, 0x78, 0x61, 0x49, 0x31, 0x18, + 0xff + }; + + byte quadrant = rotation >> 4; + rotation &= 0xf; + byte xStep = stepping[rotation]; + byte yStep = stepping[(rotation ^ 0xf) + 1] + 1; + + while (true) { + byte b = corners.readByte(); + + if (corners.eos() || corners.err()) + error("Error reading corners"); + + if (b == 0) + return; + + do { + byte xFrac = 0x80; + byte yFrac = 0x80; + for (uint j = 0; j < scaling; ++j) { + if (xFrac + xStep + 1 > 255) + drawShapePixel(pos, color, b, quadrant); + xFrac += xStep + 1; + if (yFrac + yStep > 255) + drawShapePixel(pos, color, b, quadrant + 1); + yFrac += yStep; + } + b >>= 3; + } while (b != 0); + } +} + +template <class T> +void GraphicsMan_v1<T>::drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) { + byte x, y; + bool bNewLine = false; + byte oldX = 0, oldY = 0; + while (1) { + x = pic.readByte(); + y = pic.readByte(); + + if (pic.err() || pic.eos()) + error("Error reading picture"); + + if (x == 0xff && y == 0xff) + return; + + if (x == 0 && y == 0) { + bNewLine = true; + continue; + } + + x += pos.x; + y += pos.y; + + if (y > 160) + y = 160; + + if (bNewLine) { + putPixel(Common::Point(x, y), 0x7f); + bNewLine = false; + } else { + drawLine(Common::Point(oldX, oldY), Common::Point(x, y), 0x7f); + } + + oldX = x; + oldY = y; + } +} + +template <class T> +bool GraphicsMan_v2<T>::readByte(Common::SeekableReadStream &pic, byte &b) { + b = pic.readByte(); + + if (pic.eos() || pic.err()) + error("Error reading picture"); + + if (b >= 0xe0) { + pic.seek(-1, SEEK_CUR); + return false; + } + + return true; +} + +template <class T> +bool GraphicsMan_v2<T>::readPoint(Common::SeekableReadStream &pic, Common::Point &p) { + byte b; + + if (!readByte(pic, b)) + return false; + + p.x = b + _offset.x; + p.x <<= 1; + + if (!readByte(pic, b)) + return false; + + p.y = b + _offset.y; + + return true; +} + +template <class T> +byte GraphicsMan_v2<T>::getPatternColor(const Common::Point &p, byte pattern) { + const byte fillPatterns[][4] = { + { 0x00, 0x00, 0x00, 0x00 }, + { 0x80, 0x80, 0x80, 0x80 }, + { 0xff, 0xff, 0xff, 0xff }, + { 0x7f, 0x7f, 0x7f, 0x7f }, + { 0x2a, 0x55, 0x2a, 0x55 }, + { 0xaa, 0xd5, 0xaa, 0xd5 }, + { 0x55, 0x2a, 0x55, 0x2a }, + { 0xd5, 0xaa, 0xd5, 0xaa }, + { 0x33, 0x66, 0x4c, 0x19 }, + { 0xb3, 0xe6, 0xcc, 0x99 }, + { 0x22, 0x44, 0x08, 0x11 }, + { 0xa2, 0xc4, 0x88, 0x91 }, + { 0x11, 0x22, 0x44, 0x08 }, + { 0x91, 0xa2, 0xc4, 0x88 }, + { 0x6e, 0x5d, 0x3b, 0x77 }, + { 0xee, 0xdd, 0xbb, 0xf7 }, + { 0x5d, 0x3b, 0x77, 0x6e }, + { 0xdd, 0xbb, 0xf7, 0xee }, + { 0x66, 0x4c, 0x19, 0x33 }, + { 0xe6, 0xcc, 0x99, 0xb3 }, + { 0x33, 0x66, 0x4c, 0x19 }, + { 0xb3, 0xe6, 0xcc, 0x99 } + }; + + if (pattern >= ARRAYSIZE(fillPatterns)) + error("Invalid fill pattern %i encountered in picture", pattern); + + byte offset = (p.y & 1) << 1; + offset += (p.x / 7) & 3; + + return fillPatterns[pattern][offset % sizeof(fillPatterns[0])]; +} + +template <class T> +void GraphicsMan_v2<T>::drawCorners(Common::SeekableReadStream &pic, bool yFirst) { + Common::Point p; + + if (!readPoint(pic, p)) + return; + + if (yFirst) + goto doYStep; + + while (true) { + byte b; + int16 n; + + if (!readByte(pic, b)) + return; + + n = b + _offset.x; + + putPixel(p, _color); + + n <<= 1; + drawLine(p, Common::Point(n, p.y), _color); + p.x = n; + +doYStep: + if (!readByte(pic, b)) + return; + + n = b + _offset.y; + + putPixel(p, _color); + drawLine(p, Common::Point(p.x, n), _color); + + putPixel(Common::Point(p.x + 1, p.y), _color); + drawLine(Common::Point(p.x + 1, p.y), Common::Point(p.x + 1, n), _color); + + p.y = n; + } +} + +template <class T> +void GraphicsMan_v2<T>::drawRelativeLines(Common::SeekableReadStream &pic) { + Common::Point p1; + + if (!readPoint(pic, p1)) + return; + + putPixel(p1, _color); + + while (true) { + Common::Point p2(p1); + + byte n; + + if (!readByte(pic, n)) + return; + + byte h = (n & 0x70) >> 4; + byte l = n & 7; + + if (n & 0x80) + p2.x -= (h << 1); + else + p2.x += (h << 1); + + if (n & 8) + p2.y -= l; + else + p2.y += l; + + drawLine(p1, p2, _color); + p1 = p2; + } +} + +template <class T> +void GraphicsMan_v2<T>::drawAbsoluteLines(Common::SeekableReadStream &pic) { + Common::Point p1; + + if (!readPoint(pic, p1)) + return; + + putPixel(p1, _color); + + while (true) { + Common::Point p2; + + if (!readPoint(pic, p2)) + return; + + drawLine(p1, p2, _color); + p1 = p2; + } +} + +template <class T> +bool GraphicsMan_v2<T>::canFillAt(const Common::Point &p, const bool stopBit) { + return _display.getPixelBit(p) != stopBit && _display.getPixelBit(Common::Point(p.x + 1, p.y)) != stopBit; +} + +template <class T> +void GraphicsMan_v2<T>::fillRowLeft(Common::Point p, const byte pattern, const bool stopBit) { + byte color = getPatternColor(p, pattern); + + while (--p.x >= _bounds.left) { + if ((p.x % 7) == 6) { + color = getPatternColor(p, pattern); + _display.setPixelPalette(p, color); + } + if (_display.getPixelBit(p) == stopBit) + break; + _display.setPixelBit(p, color); + } +} + +template <class T> +void GraphicsMan_v2<T>::fillRow(Common::Point p, const byte pattern, const bool stopBit) { + // Set pixel at p and palette + byte color = getPatternColor(p, pattern); + _display.setPixelPalette(p, color); + _display.setPixelBit(p, color); + + // Fill left of p + fillRowLeft(p, pattern, stopBit); + + // Fill right of p + while (++p.x < _bounds.right) { + if ((p.x % 7) == 0) { + color = getPatternColor(p, pattern); + // Palette is set before the first bit is tested + _display.setPixelPalette(p, color); + } + if (_display.getPixelBit(p) == stopBit) + break; + _display.setPixelBit(p, color); + } +} + +template <class T> +void GraphicsMan_v2<T>::fillAt(Common::Point p, const byte pattern) { + const bool stopBit = !_display.getPixelBit(p); + + // Move up into the open space above p + while (--p.y >= _bounds.top && canFillAt(p, stopBit)) {} + + // Then fill by moving down + while (++p.y < _bounds.bottom && canFillAt(p, stopBit)) + fillRow(p, pattern, stopBit); +} + +template <class T> +void GraphicsMan_v2<T>::fill(Common::SeekableReadStream &pic) { + byte pattern; + + if (!readByte(pic, pattern)) + return; + + while (true) { + Common::Point p; + + if (!readPoint(pic, p)) + return; + + if (_bounds.contains(p)) + fillAt(p, pattern); + } +} + +template <class T> +void GraphicsMan_v2<T>::drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) { + // NOTE: The original engine only resets the color for overlays. As a result, room + // pictures that draw without setting a color or clearing the screen, will use the + // last color set by the previous picture. We assume this is unintentional and do + // not copy this behavior. + _color = 0; + _offset = pos; + + while (true) { + byte opcode = pic.readByte(); + + if (pic.eos() || pic.err()) + error("Error reading picture"); + + switch (opcode) { + case 0xe0: + drawCorners(pic, false); + break; + case 0xe1: + drawCorners(pic, true); + break; + case 0xe2: + drawRelativeLines(pic); + break; + case 0xe3: + drawAbsoluteLines(pic); + break; + case 0xe4: + fill(pic); + break; + case 0xe5: + this->clearScreen(); + _color = 0x00; + break; + case 0xf0: + _color = 0x00; + break; + case 0xf1: + _color = 0x2a; + break; + case 0xf2: + _color = 0x55; + break; + case 0xf3: + _color = 0x7f; + break; + case 0xf4: + _color = 0x80; + break; + case 0xf5: + _color = 0xaa; + break; + case 0xf6: + _color = 0xd5; + break; + case 0xf7: + _color = 0xff; + break; + case 0xff: + return; + default: + if (opcode >= 0xe0) + error("Invalid pic opcode %02x", opcode); + else + warning("Expected pic opcode, but found data byte %02x", opcode); + } + } +} + +template <class T> +void GraphicsMan_v3<T>::fillRowLeft(Common::Point p, const byte pattern, const bool stopBit) { + byte color = getPatternColor(p, pattern); + + while (--p.x >= _bounds.left) { + // In this version, when moving left, it no longer sets the palette first + if (!_display.getPixelBit(p)) + return; + if ((p.x % 7) == 6) { + color = getPatternColor(p, pattern); + _display.setPixelPalette(p, color); + } + _display.setPixelBit(p, color); + } +} + +template <class T> +void GraphicsMan_v3<T>::fillAt(Common::Point p, const byte pattern) { + // If the row at p cannot be filled, we do nothing + if (!canFillAt(p)) + return; + + fillRow(p, pattern); + + Common::Point q(p); + + // Fill up from p + while (--q.y >= _bounds.top && canFillAt(q)) + fillRow(q, pattern); + + // Fill down from p + while (++p.y < _bounds.bottom && canFillAt(p)) + fillRow(p, pattern); +} + } // End of namespace Adl #endif |