aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/gui_options.cpp1
-rw-r--r--common/gui_options.h1
-rw-r--r--engines/sci/detection.cpp10
-rw-r--r--engines/sci/detection_tables.h5
-rw-r--r--engines/sci/graphics/celobj32.cpp107
-rw-r--r--engines/sci/sci.h1
-rw-r--r--graphics/larryScale.cpp406
-rw-r--r--graphics/larryScale.h82
-rw-r--r--graphics/larryScale_generated.cpp639
-rw-r--r--graphics/larryScale_generator.js402
-rw-r--r--graphics/module.mk1
11 files changed, 1630 insertions, 25 deletions
diff --git a/common/gui_options.cpp b/common/gui_options.cpp
index 00de6ec01c..06b6c20a61 100644
--- a/common/gui_options.cpp
+++ b/common/gui_options.cpp
@@ -84,6 +84,7 @@ const struct GameOpt {
// "gameOption10" would be invalid here because it contains "gameOption1"
{ GUIO_GAMEOPTIONS10, "gameOptionA" },
{ GUIO_GAMEOPTIONS11, "gameOptionB" },
+ { GUIO_GAMEOPTIONS12, "gameOptionC" },
{ GUIO_NONE, nullptr }
};
diff --git a/common/gui_options.h b/common/gui_options.h
index 0813ff1216..14d4cb9175 100644
--- a/common/gui_options.h
+++ b/common/gui_options.h
@@ -76,6 +76,7 @@
#define GUIO_GAMEOPTIONS9 "\060"
#define GUIO_GAMEOPTIONS10 "\061"
#define GUIO_GAMEOPTIONS11 "\062"
+#define GUIO_GAMEOPTIONS12 "\063"
#define GUIO0() (GUIO_NONE)
#define GUIO1(a) (a)
diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index 9a70429e47..b0d78f753d 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -437,6 +437,16 @@ static const ADExtraGuiOptionsMap optionsList[] = {
#endif
{
+ GAMEOPTION_LARRYSCALE,
+ {
+ _s("Use high-quality \"LarryScale\" cel scaling"),
+ _s("Use special cartoon scaler for drawing character sprites"),
+ "enable_larryscale",
+ true
+ }
+ },
+
+ {
GAMEOPTION_PREFER_DIGITAL_SFX,
{
_s("Prefer digital sound effects"),
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index e9fba208e4..b908d5b964 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -2733,11 +2733,12 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#define GUIO_LSL7_DEMO GUIO3(GUIO_NOASPECT, \
GUIO_NOMIDI, \
GUIO_NOLAUNCHLOAD)
-#define GUIO_LSL7 GUIO5(GUIO_NOASPECT, \
+#define GUIO_LSL7 GUIO6(GUIO_NOASPECT, \
GUIO_NOMIDI, \
GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
GAMEOPTION_ORIGINAL_SAVELOAD, \
- GAMEOPTION_HQ_VIDEO)
+ GAMEOPTION_HQ_VIDEO, \
+ GAMEOPTION_LARRYSCALE)
// Larry 7 - English DOS Demo (provided by richiefs in bug report #2670691)
// SCI interpreter version 2.100.002
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index fb7abe8a77..f11c75ab2b 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -31,6 +31,8 @@
#include "sci/graphics/text32.h"
#include "sci/engine/workarounds.h"
#include "sci/util.h"
+#include "graphics/larryScale.h"
+#include "common/config-manager.h"
namespace Sci {
#pragma mark CelScaler
@@ -154,6 +156,9 @@ struct SCALER_Scale {
#endif
const byte *_row;
READER _reader;
+ // If _sourceBuffer is set, it contains the full (possibly scaled) source
+ // image and takes precedence over _reader.
+ Common::SharedPtr<Buffer> _sourceBuffer;
int16 _x;
static int16 _valuesX[kCelScalerTableSize];
static int16 _valuesY[kCelScalerTableSize];
@@ -167,7 +172,8 @@ struct SCALER_Scale {
// The maximum width of the scaled object may not be as wide as the source
// data it requires if downscaling, so just always make the reader
// decompress an entire line of source data when scaling
- _reader(celObj, celObj._width) {
+ _reader(celObj, celObj._width),
+ _sourceBuffer() {
#ifndef NDEBUG
assert(_minX <= _maxX);
#endif
@@ -196,43 +202,98 @@ struct SCALER_Scale {
const CelScalerTable &table = CelObj::_scaler->getScalerTable(scaleX, scaleY);
- if (g_sci->_gfxFrameout->getScriptWidth() == kLowResX) {
- const int16 unscaledX = (scaledPosition.x / scaleX).toInt();
- if (FLIP) {
- const int lastIndex = celObj._width - 1;
- for (int16 x = targetRect.left; x < targetRect.right; ++x) {
- _valuesX[x] = lastIndex - (table.valuesX[x] - unscaledX);
+ const bool useLarryScale = ConfMan.getBool("enable_larryscale");
+ if (useLarryScale) {
+ // LarryScale is an alternative, high-quality cel scaler implemented
+ // for ScummVM. Due to the nature of smooth upscaling, it does *not*
+ // respect the global scaling pattern. Instead, it simply scales the
+ // cel to the extent of targetRect.
+
+ class Copier: public Graphics::RowReader, public Graphics::RowWriter {
+ READER &_souceReader;
+ Buffer &_targetBuffer;
+ public:
+ Copier(READER& souceReader, Buffer& targetBuffer) :
+ _souceReader(souceReader),
+ _targetBuffer(targetBuffer) {}
+ const Graphics::LarryScaleColor* readRow(int y) {
+ return _souceReader.getRow(y);
}
- } else {
- for (int16 x = targetRect.left; x < targetRect.right; ++x) {
- _valuesX[x] = table.valuesX[x] - unscaledX;
+ void writeRow(int y, const Graphics::LarryScaleColor* row) {
+ memcpy(_targetBuffer.getBasePtr(0, y), row, _targetBuffer.w);
}
+ };
+
+ // Scale the cel using LarryScale and write it to _sourceBuffer
+ // scaledImageRect is not necessarily identical to targetRect
+ // because targetRect may be cropped to render only a segment.
+ Common::Rect scaledImageRect(
+ scaledPosition.x,
+ scaledPosition.y,
+ scaledPosition.x + (celObj._width * scaleX).toInt(),
+ scaledPosition.y + (celObj._height * scaleY).toInt());
+ _sourceBuffer = Common::SharedPtr<Buffer>(new Buffer(), Graphics::SurfaceDeleter());
+ _sourceBuffer->create(
+ scaledImageRect.width(), scaledImageRect.height(),
+ Graphics::PixelFormat::createFormatCLUT8());
+ Copier copier(_reader, *_sourceBuffer);
+ Graphics::larryScale(
+ celObj._width, celObj._height, celObj._skipColor, copier,
+ scaledImageRect.width(), scaledImageRect.height(), copier);
+
+ // Set _valuesX and _valuesY to reference the scaled image without additional scaling
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ const int16 unsafeValue = FLIP
+ ? scaledImageRect.right - x - 1
+ : x - scaledImageRect.left;
+ _valuesX[x] = CLIP<int16>(unsafeValue, 0, scaledImageRect.width() - 1);
}
-
- const int16 unscaledY = (scaledPosition.y / scaleY).toInt();
for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
- _valuesY[y] = table.valuesY[y] - unscaledY;
+ const int16 unsafeValue = y - scaledImageRect.top;
+ _valuesY[y] = CLIP<int16>(unsafeValue, 0, scaledImageRect.height() - 1);
}
} else {
- if (FLIP) {
- const int lastIndex = celObj._width - 1;
- for (int16 x = targetRect.left; x < targetRect.right; ++x) {
- _valuesX[x] = lastIndex - table.valuesX[x - scaledPosition.x];
+ const bool useGlobalScaling = g_sci->_gfxFrameout->getScriptWidth() == kLowResX;
+ if (useGlobalScaling) {
+ const int16 unscaledX = (scaledPosition.x / scaleX).toInt();
+ if (FLIP) {
+ const int lastIndex = celObj._width - 1;
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = lastIndex - (table.valuesX[x] - unscaledX);
+ }
+ } else {
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = table.valuesX[x] - unscaledX;
+ }
+ }
+
+ const int16 unscaledY = (scaledPosition.y / scaleY).toInt();
+ for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
+ _valuesY[y] = table.valuesY[y] - unscaledY;
}
} else {
- for (int16 x = targetRect.left; x < targetRect.right; ++x) {
- _valuesX[x] = table.valuesX[x - scaledPosition.x];
+ if (FLIP) {
+ const int lastIndex = celObj._width - 1;
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = lastIndex - table.valuesX[x - scaledPosition.x];
+ }
+ } else {
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = table.valuesX[x - scaledPosition.x];
+ }
}
- }
- for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
- _valuesY[y] = table.valuesY[y - scaledPosition.y];
+ for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
+ _valuesY[y] = table.valuesY[y - scaledPosition.y];
+ }
}
}
}
inline void setTarget(const int16 x, const int16 y) {
- _row = _reader.getRow(_valuesY[y]);
+ _row = _sourceBuffer
+ ? static_cast<const byte *>( _sourceBuffer->getBasePtr(0, _valuesY[y]))
+ : _reader.getRow(_valuesY[y]);
_x = x;
assert(_x >= _minX && _x <= _maxX);
}
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index faa153221a..6245d186c6 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -58,6 +58,7 @@ namespace Sci {
#define GAMEOPTION_ENABLE_BLACK_LINED_VIDEO GUIO_GAMEOPTIONS9
#define GAMEOPTION_HQ_VIDEO GUIO_GAMEOPTIONS10
#define GAMEOPTION_ENABLE_CENSORING GUIO_GAMEOPTIONS11
+#define GAMEOPTION_LARRYSCALE GUIO_GAMEOPTIONS12
struct EngineState;
class Vocabulary;
diff --git a/graphics/larryScale.cpp b/graphics/larryScale.cpp
new file mode 100644
index 0000000000..c4b2f6e88e
--- /dev/null
+++ b/graphics/larryScale.cpp
@@ -0,0 +1,406 @@
+/* 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 "larryScale.h"
+#include <cassert>
+#include "common/array.h"
+#include <cstring>
+
+namespace Graphics {
+
+typedef LarryScaleColor Color;
+
+const int kMargin = 2;
+
+// A bitmap that has a margin of `kMargin` pixels all around it.
+// Allows fast access without time-consuming bounds checking.
+template<typename T>
+class MarginedBitmap {
+ int _width;
+ int _height;
+ int _stride;
+ Common::Array<T> _buffer;
+ T *_origin;
+
+public:
+ MarginedBitmap(int width, int height, T marginValue) :
+ _width(width),
+ _height(height),
+ _stride(width + 2 * kMargin),
+ _buffer(_stride * (height + 2 * kMargin)),
+ _origin(calculateOrigin())
+ {
+ fillMargin(marginValue);
+ }
+
+ // We need a custom copy constructor.
+ // Otherwise, _origin would point to the original buffer.
+ MarginedBitmap(const MarginedBitmap &rhs) :
+ _width(rhs._width),
+ _height(rhs._height),
+ _stride(rhs._stride),
+ _buffer(rhs._buffer),
+ _origin(calculateOrigin())
+ {}
+
+ // We need a custom assignment operator.
+ // Otherwise, _origin would point to the original buffer.
+ MarginedBitmap &operator =(const MarginedBitmap &rhs) {
+ _width = rhs._width;
+ _height = rhs._height;
+ _stride = rhs._stride;
+ _buffer = rhs._buffer;
+ _origin = calculateOrigin();
+ return this;
+ }
+
+ int getWidth() const { return _width; }
+ int getHeight() const { return _height; }
+ int getStride() const { return _stride; }
+ const T *getOrigin() const { return _origin; }
+ T *getOrigin() { return _origin; }
+
+ const T *getPointerTo(int x, int y) const {
+ return _origin + y * _stride + x;
+ }
+ T *getPointerTo(int x, int y) {
+ return _origin + y * _stride + x;
+ }
+
+ T get(int x, int y) const {
+ return _origin[y * _stride + x];
+ }
+ void set(int x, int y, T value) {
+ _origin[y * _stride + x] = value;
+ }
+
+ void fillMargin(T value);
+
+private:
+ T *calculateOrigin() {
+ return _buffer.data() + kMargin * _stride + kMargin;
+ }
+};
+
+template<typename T>
+void MarginedBitmap<T>::fillMargin(T value) {
+ T * const data = getOrigin();
+ const int stride = getStride();
+
+ // Fill top margin
+ for (int y = -kMargin; y < 0; ++y) {
+ for (int x = -kMargin; x < _width + kMargin; ++x) {
+ data[y * stride + x] = value;
+ }
+ }
+
+ // Fill sideways margins
+ for (int y = 0; y < _height; ++y) {
+ for (int x = -kMargin; x < 0; ++x) {
+ data[y * stride + x] = value;
+ }
+ for (int x = _width; x < _width + kMargin; ++x) {
+ data[y * stride + x] = value;
+ }
+ }
+
+ // Fill bottom margin
+ for (int y = _height; y < _height + kMargin; ++y) {
+ for (int x = -kMargin; x < _width + kMargin; ++x) {
+ data[y * stride + x] = value;
+ }
+ }
+}
+
+MarginedBitmap<Color> createMarginedBitmap(int width, int height, Color marginColor, RowReader &rowReader) {
+ MarginedBitmap<Color> result(width, height, marginColor);
+ for (int y = 0; y < height; ++y) {
+ memcpy(result.getPointerTo(0, y), rowReader.readRow(y), width * sizeof(Color));
+ }
+ return result;
+}
+
+class MarginedBitmapWriter : public RowWriter {
+ MarginedBitmap<Color> &_target;
+public:
+ explicit MarginedBitmapWriter(MarginedBitmap<Color> &target)
+ : _target(target) {}
+
+ void writeRow(int y, const LarryScaleColor *row) {
+ memcpy(_target.getPointerTo(0, y), row, _target.getWidth() * sizeof(Color));
+ }
+};
+
+inline bool isLinePixel(const MarginedBitmap<Color> &src, int x, int y) {
+#define EQUALS(xOffset, yOffset) (src.get(x + xOffset, y + yOffset) == pixel)
+
+ const Color pixel = src.get(x, y);
+
+ // Single pixels are fills
+ if (!EQUALS(-1, -1) && !EQUALS(0, -1) && !EQUALS(1, -1) && !EQUALS(1, 0) && !EQUALS(1, 1) && !EQUALS(0, 1) && !EQUALS(-1, 1) && !EQUALS(-1, 0)) {
+ return false;
+ }
+
+ // 2x2 blocks are fills
+ if (EQUALS(0, -1) && EQUALS(1, -1) && EQUALS(1, 0)) return false;
+ if (EQUALS(1, 0) && EQUALS(1, 1) && EQUALS(0, 1)) return false;
+ if (EQUALS(0, 1) && EQUALS(-1, 1) && EQUALS(-1, 0)) return false;
+ if (EQUALS(-1, 0) && EQUALS(-1, -1) && EQUALS(0, -1)) return false;
+
+ // A pixel adjacent to a 2x2 block is a fill.
+ if (EQUALS(-1, -1) && EQUALS(0, -1) && EQUALS(-1, -2) && EQUALS(0, -2)) return false;
+ if (EQUALS(0, -1) && EQUALS(1, -1) && EQUALS(0, -2) && EQUALS(1, -2)) return false;
+ if (EQUALS(1, -1) && EQUALS(1, 0) && EQUALS(2, -1) && EQUALS(2, 0)) return false;
+ if (EQUALS(1, 0) && EQUALS(1, 1) && EQUALS(2, 0) && EQUALS(2, 1)) return false;
+ if (EQUALS(1, 1) && EQUALS(0, 1) && EQUALS(1, 2) && EQUALS(0, 2)) return false;
+ if (EQUALS(0, 1) && EQUALS(-1, 1) && EQUALS(0, 2) && EQUALS(-1, 2)) return false;
+ if (EQUALS(-1, 1) && EQUALS(-1, 0) && EQUALS(-2, 1) && EQUALS(-2, 0)) return false;
+ if (EQUALS(-1, 0) && EQUALS(-1, -1) && EQUALS(-2, 0) && EQUALS(-2, -1)) return false;
+
+ // Everything else is part of a line
+ return true;
+
+#undef EQUALS
+}
+
+MarginedBitmap<bool> createMarginedLinePixelsBitmap(const MarginedBitmap<Color> &src) {
+ MarginedBitmap<bool> result(src.getWidth(), src.getHeight(), false);
+ for (int y = 0; y < src.getHeight(); ++y) {
+ for (int x = 0; x < src.getWidth(); ++x) {
+ result.set(x, y, isLinePixel(src, x, y));
+ }
+ }
+ return result;
+}
+
+void scaleDown(
+ const MarginedBitmap<Color> &src,
+ Color transparentColor,
+ int dstWidth, int dstHeight,
+ RowWriter &rowWriter
+) {
+ assert(src.getWidth() > 0);
+ assert(src.getHeight() > 0);
+ assert(dstWidth > 0 && dstWidth <= src.getWidth());
+ assert(dstHeight > 0 && dstHeight <= src.getHeight());
+
+ Common::Array<Color> dstRow(dstWidth);
+ for (int dstY = 0; dstY < dstHeight; ++dstY) {
+ const int srcY1 = dstY * src.getHeight() / dstHeight;
+ const int srcY2 = (dstY + 1) * src.getHeight() / dstHeight;
+
+ for (int dstX = 0; dstX < dstWidth; ++dstX) {
+ const int srcX1 = dstX * src.getWidth() / dstWidth;
+ const int srcX2 = (dstX + 1) * src.getWidth() / dstWidth;
+
+ const int blockPixelCount = (srcX2 - srcX1) * (srcY2 - srcY1);
+ if (blockPixelCount <= 4) {
+ // Downscaling to 50% or more. Prefer line pixels.
+ Color bestLineColor = 0;
+ int linePixelCount = 0;
+ for (int srcY = srcY1; srcY < srcY2; ++srcY) {
+ for (int srcX = srcX1; srcX < srcX2; ++srcX) {
+ const bool colorIsFromLine = isLinePixel(src, srcX, srcY);
+ if (colorIsFromLine) {
+ bestLineColor = src.get(srcX, srcY);
+ ++linePixelCount;
+ }
+ }
+ }
+ const bool sufficientLinePixels = linePixelCount * 2 >= blockPixelCount;
+ const Color resultColor = sufficientLinePixels
+ ? bestLineColor
+ : src.get(srcX1, srcY1);
+ dstRow[dstX] = resultColor;
+ } else {
+ // Downscaling significantly. Prefer outline pixels.
+ Color bestColor = src.get(srcX1, srcY1);
+ for (int srcY = srcY1; srcY < srcY2; ++srcY) {
+ for (int srcX = srcX1; srcX < srcX2; ++srcX) {
+ const Color pixelColor = src.get(srcX, srcY);
+ const bool isOutlinePixel = pixelColor != transparentColor && (
+ src.get(srcX - 1, srcY) == transparentColor
+ || src.get(srcX + 1, srcY) == transparentColor
+ || src.get(srcX, srcY - 1) == transparentColor
+ || src.get(srcX, srcY + 1) == transparentColor
+ );
+ if (isOutlinePixel) {
+ bestColor = pixelColor;
+ goto foundOutlinePixel;
+ }
+ }
+ }
+ foundOutlinePixel:
+ dstRow[dstX] = bestColor;
+ }
+ }
+ rowWriter.writeRow(dstY, dstRow.data());
+ }
+}
+
+// An equality matrix is a combination of eight Boolean flags indicating whether
+// each of the surrounding pixels has the same color as the central pixel.
+//
+// +------+------+------+
+// | 0x02 | 0x04 | 0x08 |
+// +------+------+------+
+// | 0x01 | Ref. | 0x10 |
+// +------+------+------+
+// | 0x80 | 0x40 | 0x20 |
+// +------+------+------+
+typedef byte EqualityMatrix;
+
+EqualityMatrix getEqualityMatrix(const Color *pixel, int stride) {
+#define EQUALS(x, y) (pixel[y * stride + x] == *pixel)
+
+ return (EQUALS(-1, 0) ? 0x01 : 0x00)
+ | (EQUALS(-1, -1) ? 0x02 : 0x00)
+ | (EQUALS(0, -1) ? 0x04 : 0x00)
+ | (EQUALS(1, -1) ? 0x08 : 0x00)
+ | (EQUALS(1, 0) ? 0x10 : 0x00)
+ | (EQUALS(1, 1) ? 0x20 : 0x00)
+ | (EQUALS(0, 1) ? 0x40 : 0x00)
+ | (EQUALS(-1, 1) ? 0x80 : 0x00);
+
+#undef EQUALS
+}
+
+// scapeUp() requires generated functions
+#include "larryScale_generated.cpp"
+
+void scaleUp(
+ const MarginedBitmap<Color> &src,
+ int dstWidth, int dstHeight,
+ RowWriter &rowWriter
+) {
+ const int srcWidth = src.getWidth();
+ const int srcHeight = src.getHeight();
+
+ assert(srcWidth > 0);
+ assert(srcHeight > 0);
+ assert(dstWidth >= srcWidth && dstWidth <= 2 * src.getWidth());
+ assert(dstHeight >= srcHeight && dstHeight <= 2 * src.getHeight());
+
+ const MarginedBitmap<bool> linePixels = createMarginedLinePixelsBitmap(src);
+ Common::Array<Color> topDstRow(dstWidth);
+ Common::Array<Color> bottomDstRow(dstWidth);
+ for (int srcY = 0; srcY < src.getHeight(); ++srcY) {
+ const int dstY1 = srcY * dstHeight / src.getHeight();
+ const int dstY2 = (srcY + 1) * dstHeight / src.getHeight();
+ const int dstBlockHeight = dstY2 - dstY1;
+
+ for (int srcX = 0; srcX < src.getWidth(); ++srcX) {
+ const int dstX1 = srcX * dstWidth / src.getWidth();
+ const int dstX2 = (srcX + 1) * dstWidth / src.getWidth();
+ const int dstBlockWidth = dstX2 - dstX1;
+
+ if (dstBlockWidth == 1) {
+ if (dstBlockHeight == 1) {
+ // 1x1
+ topDstRow[dstX1] = src.get(srcX, srcY);
+ } else {
+ // 1x2
+ Color &top = topDstRow[dstX1];
+ Color &bottom = bottomDstRow[dstX1];
+ scalePixelTo1x2(src, linePixels, srcX, srcY, top, bottom);
+ }
+ } else {
+ if (dstBlockHeight == 1) {
+ // 2x1
+ Color &left = topDstRow[dstX1];
+ Color &right = topDstRow[dstX1 + 1];
+ scalePixelTo2x1(src, linePixels, srcX, srcY, left, right);
+ } else {
+ // 2x2
+ Color &topLeft = topDstRow[dstX1];
+ Color &topRight = topDstRow[dstX1 + 1];
+ Color &bottomLeft = bottomDstRow[dstX1];
+ Color &bottomRight = bottomDstRow[dstX1 + 1];
+ scalePixelTo2x2(src, linePixels, srcX, srcY, topLeft, topRight, bottomLeft, bottomRight);
+ }
+ }
+ }
+ rowWriter.writeRow(dstY1, topDstRow.data());
+ if (dstBlockHeight == 2) {
+ rowWriter.writeRow(dstY1 + 1, bottomDstRow.data());
+ }
+ }
+}
+
+void copyRows(int height, RowReader &rowReader, RowWriter &rowWriter) {
+ for (int y = 0; y < height; ++y) {
+ rowWriter.writeRow(y, rowReader.readRow(y));
+ }
+}
+
+void larryScale(
+ const MarginedBitmap<Color> &src,
+ Color transparentColor,
+ int dstWidth, int dstHeight,
+ RowWriter &rowWriter
+) {
+ const int srcWidth = src.getWidth();
+ const int srcHeight = src.getHeight();
+
+ if (
+ (dstWidth > srcWidth && dstHeight < srcHeight) // Upscaling along x axis, downscaling along y axis
+ || (dstWidth < srcWidth && dstHeight > srcHeight) // Downscaling along x axis, upscaling along y axis
+ || (dstWidth > 2 * srcWidth) // Upscaling to more than 200% along x axis
+ || (dstHeight > 2 * srcHeight) // Upscaling to more than 200% along y axis
+ ) {
+ // We can't handle these cases with a single upscale.
+ // Let's do an intermediate scale.
+ const int tmpWidth = CLIP(dstWidth, srcWidth, 2 * srcWidth);
+ const int tmpHeight = CLIP(dstHeight, srcHeight, 2 * srcHeight);
+ MarginedBitmap<Color> tmp(tmpWidth, tmpHeight, transparentColor);
+ MarginedBitmapWriter writer = MarginedBitmapWriter(tmp);
+ larryScale(src, transparentColor, tmpWidth, tmpHeight, writer);
+ larryScale(tmp, transparentColor, dstWidth, dstHeight, rowWriter);
+ } else if (dstWidth > srcWidth || dstHeight > srcHeight) {
+ // Upscaling to no more than 200%
+ scaleUp(src, dstWidth, dstHeight, rowWriter);
+ } else {
+ // Downscaling
+ scaleDown(src, transparentColor, dstWidth, dstHeight, rowWriter);
+ }
+}
+
+void larryScale(
+ int srcWidth, int srcHeight,
+ Color transparentColor,
+ RowReader &rowReader,
+ int dstWidth, int dstHeight,
+ RowWriter &rowWriter
+) {
+ // Select the appropriate scaler
+ if (srcWidth <= 0 || srcHeight <= 0 || dstWidth <= 0 || dstHeight <= 0) {
+ // Nothing to do
+ } else if (dstWidth == srcWidth && dstHeight == srcHeight) {
+ copyRows(srcHeight, rowReader, rowWriter);
+ } else {
+ const MarginedBitmap<Color> src =
+ createMarginedBitmap(srcWidth, srcHeight, transparentColor, rowReader);
+ larryScale(src, transparentColor, dstWidth, dstHeight, rowWriter);
+ }
+}
+
+}
diff --git a/graphics/larryScale.h b/graphics/larryScale.h
new file mode 100644
index 0000000000..0248cdb062
--- /dev/null
+++ b/graphics/larryScale.h
@@ -0,0 +1,82 @@
+/* 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.
+*
+*/
+
+#ifndef GRAPHICS_LARRYSCALE_H
+#define GRAPHICS_LARRYSCALE_H
+
+#include "common/scummsys.h"
+
+namespace Graphics {
+
+/*
+
+LarryScale is a bitmap scaler for cartoon images.
+
+Features:
+
+* Supports arbitrary scaling factors along both axes (but looks best between 50% and 200%).
+* When downscaling: preserves fine details.
+* When upscaling: keeps lines smooth, introducing very few block artifacts.
+* Does not introduce additional colors.
+
+Limitations:
+
+* Lines must not be anti-aliased and should be only one pixel wide.
+* Fills should be flat without gradients.
+
+*/
+
+typedef byte LarryScaleColor;
+
+class RowReader {
+public:
+ virtual const LarryScaleColor *readRow(int y) = 0;
+ virtual ~RowReader() {}
+};
+
+class RowWriter {
+public:
+ virtual void writeRow(int y, const LarryScaleColor *row) = 0;
+ virtual ~RowWriter() {}
+};
+
+/**
+ * @param srcWidth The width, in pixels, of the original image
+ * @param srcHeight The height, in pixels, of the original image
+ * @param transparentColor The transparent color. Used for outline detection.
+ * @param rowReader An object with a callback method for reading the lines of the original
+ * image
+ * @param dstWidth The width, in pixels, of the scaled target image
+ * @param dstHeight The height, in pixels, of the scaled target image
+ * @param rowWriter An object with a callback method accepting the lines of the target image
+ */
+void larryScale(
+ int srcWidth, int srcHeight,
+ LarryScaleColor transparentColor,
+ RowReader &rowReader,
+ int dstWidth, int dstHeight,
+ RowWriter &rowWriter
+);
+
+}
+
+#endif
diff --git a/graphics/larryScale_generated.cpp b/graphics/larryScale_generated.cpp
new file mode 100644
index 0000000000..f98764848b
--- /dev/null
+++ b/graphics/larryScale_generated.cpp
@@ -0,0 +1,639 @@
+/* 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.
+*
+*/
+
+// This file was generated by larryScale_generator.js.
+// Do not edit directly! Instead, edit the generator script and run it.
+
+inline void scalePixelTo2x2(
+ const MarginedBitmap<Color> &src,
+ const MarginedBitmap<bool> &linePixels,
+ int x, int y,
+ // Out parameters
+ Color &topLeft, Color &topRight, Color &bottomLeft, Color &bottomRight
+) {
+ const Color pixel = src.get(x, y);
+ const EqualityMatrix matrix = getEqualityMatrix(src.getPointerTo(x, y), src.getStride());
+
+ // Note: There is a case label for every possible value, so we don't need a default label.
+ switch (matrix) {
+ case 0x00 /*⠀⠂⠀*/: case 0x07 /*⠃⠃⠀*/: case 0x17 /*⠃⠃⠂*/: case 0x1c /*⠀⠃⠃*/: case 0x1d /*⠂⠃⠃*/: case 0x1e /*⠁⠃⠃*/: case 0x1f /*⠃⠃⠃*/: case 0x27 /*⠃⠃⠄*/:
+ case 0x2f /*⠃⠃⠅*/: case 0x37 /*⠃⠃⠆*/: case 0x3c /*⠀⠃⠇*/: case 0x3d /*⠂⠃⠇*/: case 0x3e /*⠁⠃⠇*/: case 0x3f /*⠃⠃⠇*/: case 0x47 /*⠃⠇⠀*/: case 0x4f /*⠃⠇⠁*/:
+ case 0x50 /*⠀⠆⠂*/: case 0x51 /*⠂⠆⠂*/: case 0x52 /*⠁⠆⠂*/: case 0x54 /*⠀⠇⠂*/: case 0x55 /*⠂⠇⠂*/: case 0x57 /*⠃⠇⠂*/: case 0x5c /*⠀⠇⠃*/: case 0x5d /*⠂⠇⠃*/:
+ case 0x5e /*⠁⠇⠃*/: case 0x5f /*⠃⠇⠃*/: case 0x67 /*⠃⠇⠄*/: case 0x6f /*⠃⠇⠅*/: case 0x70 /*⠀⠆⠆*/: case 0x71 /*⠂⠆⠆*/: case 0x72 /*⠁⠆⠆*/: case 0x73 /*⠃⠆⠆*/:
+ case 0x74 /*⠀⠇⠆*/: case 0x75 /*⠂⠇⠆*/: case 0x76 /*⠁⠇⠆*/: case 0x77 /*⠃⠇⠆*/: case 0x78 /*⠀⠆⠇*/: case 0x79 /*⠂⠆⠇*/: case 0x7a /*⠁⠆⠇*/: case 0x7b /*⠃⠆⠇*/:
+ case 0x7c /*⠀⠇⠇*/: case 0x7d /*⠂⠇⠇*/: case 0x7e /*⠁⠇⠇*/: case 0x7f /*⠃⠇⠇*/: case 0x97 /*⠇⠃⠂*/: case 0x9c /*⠄⠃⠃*/: case 0x9d /*⠆⠃⠃*/: case 0x9e /*⠅⠃⠃*/:
+ case 0x9f /*⠇⠃⠃*/: case 0xa7 /*⠇⠃⠄*/: case 0xaa /*⠅⠂⠅*/: case 0xaf /*⠇⠃⠅*/: case 0xb7 /*⠇⠃⠆*/: case 0xbc /*⠄⠃⠇*/: case 0xbd /*⠆⠃⠇*/: case 0xbe /*⠅⠃⠇*/:
+ case 0xbf /*⠇⠃⠇*/: case 0xc1 /*⠆⠆⠀*/: case 0xc5 /*⠆⠇⠀*/: case 0xc7 /*⠇⠇⠀*/: case 0xc9 /*⠆⠆⠁*/: case 0xcb /*⠇⠆⠁*/: case 0xcd /*⠆⠇⠁*/: case 0xcf /*⠇⠇⠁*/:
+ case 0xd1 /*⠆⠆⠂*/: case 0xd3 /*⠇⠆⠂*/: case 0xd5 /*⠆⠇⠂*/: case 0xd7 /*⠇⠇⠂*/: case 0xd9 /*⠆⠆⠃*/: case 0xdb /*⠇⠆⠃*/: case 0xdc /*⠄⠇⠃*/: case 0xdd /*⠆⠇⠃*/:
+ case 0xde /*⠅⠇⠃*/: case 0xdf /*⠇⠇⠃*/: case 0xe5 /*⠆⠇⠄*/: case 0xe7 /*⠇⠇⠄*/: case 0xe9 /*⠆⠆⠅*/: case 0xeb /*⠇⠆⠅*/: case 0xed /*⠆⠇⠅*/: case 0xef /*⠇⠇⠅*/:
+ case 0xf0 /*⠄⠆⠆*/: case 0xf1 /*⠆⠆⠆*/: case 0xf2 /*⠅⠆⠆*/: case 0xf3 /*⠇⠆⠆*/: case 0xf4 /*⠄⠇⠆*/: case 0xf5 /*⠆⠇⠆*/: case 0xf6 /*⠅⠇⠆*/: case 0xf7 /*⠇⠇⠆*/:
+ case 0xf8 /*⠄⠆⠇*/: case 0xf9 /*⠆⠆⠇*/: case 0xfa /*⠅⠆⠇*/: case 0xfb /*⠇⠆⠇*/: case 0xfc /*⠄⠇⠇*/: case 0xfd /*⠆⠇⠇*/: case 0xfe /*⠅⠇⠇*/: case 0xff /*⠇⠇⠇*/:
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ break;
+ case 0xa4 /*⠄⠃⠄*/:
+ topLeft = bottomLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x29 /*⠂⠂⠅*/:
+ topLeft = topRight = bottomRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ break;
+ case 0x92 /*⠅⠂⠂*/: case 0x94 /*⠄⠃⠂*/:
+ topLeft = topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ break;
+ case 0x44 /*⠀⠇⠀*/: case 0x45 /*⠂⠇⠀*/:
+ topLeft = bottomLeft = pixel;
+ topRight = bottomRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x11 /*⠂⠂⠂*/: case 0x15 /*⠂⠃⠂*/:
+ topLeft = topRight = pixel;
+ bottomLeft = bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ break;
+ case 0x49 /*⠂⠆⠁*/: case 0x4a /*⠁⠆⠁*/:
+ topLeft = topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x53 /*⠃⠆⠂*/: case 0x56 /*⠁⠇⠂*/: case 0x59 /*⠂⠆⠃*/: case 0x5a /*⠁⠆⠃*/: case 0x5b /*⠃⠆⠃*/: case 0xd2 /*⠅⠆⠂*/: case 0xd4 /*⠄⠇⠂*/: case 0xd6 /*⠅⠇⠂*/:
+ case 0xda /*⠅⠆⠃*/:
+ if (linePixels.get(x, y)) {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0xa8 /*⠄⠂⠅*/:
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = bottomLeft = bottomRight = pixel;
+ break;
+ case 0x8a /*⠅⠂⠁*/: case 0x8f /*⠇⠃⠁*/:
+ topLeft = topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x2a /*⠁⠂⠅*/:
+ topLeft = topRight = bottomRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ break;
+ case 0xa2 /*⠅⠂⠄*/: case 0xe3 /*⠇⠆⠄*/:
+ topLeft = bottomLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x25 /*⠂⠃⠄*/:
+ topLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ break;
+ case 0xa6 /*⠅⠃⠄*/: case 0xac /*⠄⠃⠅*/: case 0xae /*⠅⠃⠅*/:
+ if (linePixels.get(x, y)) {
+ topLeft = bottomLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x13 /*⠃⠂⠂*/: case 0x19 /*⠂⠂⠃*/: case 0x1b /*⠃⠂⠃*/: case 0x31 /*⠂⠂⠆*/: case 0x33 /*⠃⠂⠆*/: case 0x35 /*⠂⠃⠆*/: case 0x39 /*⠂⠂⠇*/: case 0x3b /*⠃⠂⠇*/:
+ case 0x91 /*⠆⠂⠂*/: case 0x93 /*⠇⠂⠂*/: case 0x95 /*⠆⠃⠂*/: case 0x99 /*⠆⠂⠃*/: case 0x9b /*⠇⠂⠃*/: case 0xb1 /*⠆⠂⠆*/: case 0xb3 /*⠇⠂⠆*/: case 0xb5 /*⠆⠃⠆*/:
+ case 0xb9 /*⠆⠂⠇*/: case 0xbb /*⠇⠂⠇*/:
+ if (linePixels.get(x, y)) {
+ topLeft = topRight = pixel;
+ bottomLeft = bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x96 /*⠅⠃⠂*/: case 0x9a /*⠅⠂⠃*/: case 0xb2 /*⠅⠂⠆*/: case 0xb4 /*⠄⠃⠆*/: case 0xb6 /*⠅⠃⠆*/: case 0xba /*⠅⠂⠇*/:
+ if (linePixels.get(x, y)) {
+ topLeft = topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x4b /*⠃⠆⠁*/: case 0x69 /*⠂⠆⠅*/: case 0x6a /*⠁⠆⠅*/: case 0x6b /*⠃⠆⠅*/: case 0xca /*⠅⠆⠁*/: case 0xea /*⠅⠆⠅*/:
+ if (linePixels.get(x, y)) {
+ topLeft = topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x2b /*⠃⠂⠅*/: case 0xa9 /*⠆⠂⠅*/: case 0xab /*⠇⠂⠅*/:
+ if (linePixels.get(x, y)) {
+ topLeft = topRight = bottomRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x46 /*⠁⠇⠀*/: case 0x4c /*⠀⠇⠁*/: case 0x4d /*⠂⠇⠁*/: case 0x4e /*⠁⠇⠁*/: case 0x64 /*⠀⠇⠄*/: case 0x65 /*⠂⠇⠄*/: case 0x66 /*⠁⠇⠄*/: case 0x6c /*⠀⠇⠅*/:
+ case 0x6d /*⠂⠇⠅*/: case 0x6e /*⠁⠇⠅*/: case 0xc4 /*⠄⠇⠀*/: case 0xc6 /*⠅⠇⠀*/: case 0xcc /*⠄⠇⠁*/: case 0xce /*⠅⠇⠁*/: case 0xe4 /*⠄⠇⠄*/: case 0xe6 /*⠅⠇⠄*/:
+ case 0xec /*⠄⠇⠅*/: case 0xee /*⠅⠇⠅*/:
+ if (linePixels.get(x, y)) {
+ topLeft = bottomLeft = pixel;
+ topRight = bottomRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x24 /*⠀⠃⠄*/:
+ topLeft = !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = bottomRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ break;
+ case 0x04 /*⠀⠃⠀*/: case 0x84 /*⠄⠃⠀*/: case 0x87 /*⠇⠃⠀*/:
+ topLeft = bottomLeft = pixel;
+ topRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x90 /*⠄⠂⠂*/:
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : pixel;
+ bottomLeft = bottomRight = pixel;
+ break;
+ case 0x21 /*⠂⠂⠄*/: case 0xe1 /*⠆⠆⠄*/:
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomLeft = bottomRight = pixel;
+ break;
+ case 0x48 /*⠀⠆⠁*/:
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = bottomRight = pixel;
+ bottomLeft = !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ break;
+ case 0x01 /*⠂⠂⠀*/: case 0x09 /*⠂⠂⠁*/: case 0x0f /*⠃⠃⠁*/:
+ topLeft = topRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x10 /*⠀⠂⠂*/: case 0x12 /*⠁⠂⠂*/: case 0x14 /*⠀⠃⠂*/:
+ topLeft = topRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ break;
+ case 0x40 /*⠀⠆⠀*/: case 0x41 /*⠂⠆⠀*/: case 0x42 /*⠁⠆⠀*/: case 0xc3 /*⠇⠆⠀*/:
+ topLeft = bottomLeft = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0xd8 /*⠄⠆⠃*/:
+ if (linePixels.get(x, y)) {
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = bottomLeft = bottomRight = pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0xa0 /*⠄⠂⠄*/:
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomLeft = bottomRight = pixel;
+ break;
+ case 0x0a /*⠁⠂⠁*/:
+ topLeft = topRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x08 /*⠀⠂⠁*/: case 0x80 /*⠄⠂⠀*/: case 0x88 /*⠄⠂⠁*/:
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x28 /*⠀⠂⠅*/:
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = bottomRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ break;
+ case 0x02 /*⠁⠂⠀*/: case 0x20 /*⠀⠂⠄*/: case 0x22 /*⠁⠂⠄*/:
+ topLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ break;
+ case 0x82 /*⠅⠂⠀*/:
+ topLeft = bottomLeft = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x2d /*⠂⠃⠅*/: case 0xa5 /*⠆⠃⠄*/: case 0xad /*⠆⠃⠅*/:
+ if (linePixels.get(x, y)) {
+ topLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x05 /*⠂⠃⠀*/:
+ topLeft = pixel;
+ topRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x68 /*⠀⠆⠅*/: case 0xc8 /*⠄⠆⠁*/: case 0xe8 /*⠄⠆⠅*/:
+ if (linePixels.get(x, y)) {
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x26 /*⠁⠃⠄*/: case 0x2c /*⠀⠃⠅*/: case 0x2e /*⠁⠃⠅*/:
+ if (linePixels.get(x, y)) {
+ topLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0xc0 /*⠄⠆⠀*/:
+ if (linePixels.get(x, y)) {
+ topLeft = bottomLeft = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x18 /*⠀⠂⠃*/: case 0x1a /*⠁⠂⠃*/: case 0x30 /*⠀⠂⠆*/: case 0x32 /*⠁⠂⠆*/: case 0x36 /*⠁⠃⠆*/: case 0x38 /*⠀⠂⠇*/: case 0x3a /*⠁⠂⠇*/:
+ if (linePixels.get(x, y)) {
+ topLeft = topRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x06 /*⠁⠃⠀*/:
+ if (linePixels.get(x, y)) {
+ topLeft = bottomLeft = pixel;
+ topRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x98 /*⠄⠂⠃*/: case 0xb0 /*⠄⠂⠆*/: case 0xb8 /*⠄⠂⠇*/:
+ if (linePixels.get(x, y)) {
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x58 /*⠀⠆⠃*/: case 0xd0 /*⠄⠆⠂*/:
+ if (linePixels.get(x, y)) {
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : pixel;
+ bottomLeft = !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ bottomRight = pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x16 /*⠁⠃⠂*/: case 0x34 /*⠀⠃⠆*/:
+ if (linePixels.get(x, y)) {
+ topLeft = !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ topRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = bottomRight = pixel;
+ }
+ break;
+ case 0x60 /*⠀⠆⠄*/: case 0x62 /*⠁⠆⠄*/: case 0x63 /*⠃⠆⠄*/: case 0xc2 /*⠅⠆⠀*/: case 0xe0 /*⠄⠆⠄*/: case 0xe2 /*⠅⠆⠄*/:
+ if (linePixels.get(x, y)) {
+ topLeft = bottomLeft = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = bottomLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ }
+ break;
+ case 0x23 /*⠃⠂⠄*/: case 0xa1 /*⠆⠂⠄*/: case 0xa3 /*⠇⠂⠄*/:
+ if (linePixels.get(x, y)) {
+ topLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ } else {
+ topLeft = bottomLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ }
+ break;
+ case 0x0b /*⠃⠂⠁*/: case 0x81 /*⠆⠂⠀*/: case 0x83 /*⠇⠂⠀*/: case 0x89 /*⠆⠂⠁*/: case 0x8b /*⠇⠂⠁*/:
+ if (linePixels.get(x, y)) {
+ topLeft = topRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ }
+ break;
+ case 0x03 /*⠃⠂⠀*/:
+ if (linePixels.get(x, y)) {
+ topLeft = topRight = pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = bottomLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ }
+ break;
+ case 0x0c /*⠀⠃⠁*/: case 0x0e /*⠁⠃⠁*/: case 0x86 /*⠅⠃⠀*/: case 0x8c /*⠄⠃⠁*/: case 0x8e /*⠅⠃⠁*/:
+ if (linePixels.get(x, y)) {
+ topLeft = bottomLeft = pixel;
+ topRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ }
+ break;
+ case 0x0d /*⠂⠃⠁*/: case 0x85 /*⠆⠃⠀*/: case 0x8d /*⠆⠃⠁*/:
+ if (linePixels.get(x, y)) {
+ topLeft = pixel;
+ topRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomLeft = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = topRight = bottomLeft = pixel;
+ bottomRight = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ }
+ break;
+ case 0x43 /*⠃⠆⠀*/: case 0x61 /*⠂⠆⠄*/:
+ if (linePixels.get(x, y)) {
+ topLeft = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottomLeft = pixel;
+ bottomRight = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ topLeft = bottomLeft = bottomRight = pixel;
+ topRight = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ }
+ break;
+ }
+}
+
+inline void scalePixelTo2x1(
+ const MarginedBitmap<Color> &src,
+ const MarginedBitmap<bool> &linePixels,
+ int x, int y,
+ // Out parameters
+ Color &left, Color &right
+) {
+ const Color pixel = src.get(x, y);
+ const EqualityMatrix matrix = getEqualityMatrix(src.getPointerTo(x, y), src.getStride());
+
+ // Note: There is a case label for every possible value, so we don't need a default label.
+ switch (matrix) {
+ case 0x00 /*⠀⠂⠀*/: case 0x01 /*⠂⠂⠀*/: case 0x02 /*⠁⠂⠀*/: case 0x07 /*⠃⠃⠀*/: case 0x08 /*⠀⠂⠁*/: case 0x09 /*⠂⠂⠁*/: case 0x0a /*⠁⠂⠁*/: case 0x0f /*⠃⠃⠁*/:
+ case 0x10 /*⠀⠂⠂*/: case 0x11 /*⠂⠂⠂*/: case 0x12 /*⠁⠂⠂*/: case 0x14 /*⠀⠃⠂*/: case 0x15 /*⠂⠃⠂*/: case 0x17 /*⠃⠃⠂*/: case 0x1c /*⠀⠃⠃*/: case 0x1d /*⠂⠃⠃*/:
+ case 0x1e /*⠁⠃⠃*/: case 0x1f /*⠃⠃⠃*/: case 0x20 /*⠀⠂⠄*/: case 0x21 /*⠂⠂⠄*/: case 0x22 /*⠁⠂⠄*/: case 0x25 /*⠂⠃⠄*/: case 0x27 /*⠃⠃⠄*/: case 0x29 /*⠂⠂⠅*/:
+ case 0x2a /*⠁⠂⠅*/: case 0x2f /*⠃⠃⠅*/: case 0x37 /*⠃⠃⠆*/: case 0x3c /*⠀⠃⠇*/: case 0x3d /*⠂⠃⠇*/: case 0x3e /*⠁⠃⠇*/: case 0x3f /*⠃⠃⠇*/: case 0x47 /*⠃⠇⠀*/:
+ case 0x49 /*⠂⠆⠁*/: case 0x4a /*⠁⠆⠁*/: case 0x4f /*⠃⠇⠁*/: case 0x50 /*⠀⠆⠂*/: case 0x51 /*⠂⠆⠂*/: case 0x52 /*⠁⠆⠂*/: case 0x54 /*⠀⠇⠂*/: case 0x55 /*⠂⠇⠂*/:
+ case 0x57 /*⠃⠇⠂*/: case 0x5c /*⠀⠇⠃*/: case 0x5d /*⠂⠇⠃*/: case 0x5e /*⠁⠇⠃*/: case 0x5f /*⠃⠇⠃*/: case 0x67 /*⠃⠇⠄*/: case 0x6f /*⠃⠇⠅*/: case 0x70 /*⠀⠆⠆*/:
+ case 0x71 /*⠂⠆⠆*/: case 0x72 /*⠁⠆⠆*/: case 0x73 /*⠃⠆⠆*/: case 0x74 /*⠀⠇⠆*/: case 0x75 /*⠂⠇⠆*/: case 0x76 /*⠁⠇⠆*/: case 0x77 /*⠃⠇⠆*/: case 0x78 /*⠀⠆⠇*/:
+ case 0x79 /*⠂⠆⠇*/: case 0x7a /*⠁⠆⠇*/: case 0x7b /*⠃⠆⠇*/: case 0x7c /*⠀⠇⠇*/: case 0x7d /*⠂⠇⠇*/: case 0x7e /*⠁⠇⠇*/: case 0x7f /*⠃⠇⠇*/: case 0x80 /*⠄⠂⠀*/:
+ case 0x88 /*⠄⠂⠁*/: case 0x8a /*⠅⠂⠁*/: case 0x8f /*⠇⠃⠁*/: case 0x90 /*⠄⠂⠂*/: case 0x92 /*⠅⠂⠂*/: case 0x94 /*⠄⠃⠂*/: case 0x97 /*⠇⠃⠂*/: case 0x9c /*⠄⠃⠃*/:
+ case 0x9d /*⠆⠃⠃*/: case 0x9e /*⠅⠃⠃*/: case 0x9f /*⠇⠃⠃*/: case 0xa0 /*⠄⠂⠄*/: case 0xa2 /*⠅⠂⠄*/: case 0xa4 /*⠄⠃⠄*/: case 0xa7 /*⠇⠃⠄*/: case 0xa8 /*⠄⠂⠅*/:
+ case 0xaa /*⠅⠂⠅*/: case 0xaf /*⠇⠃⠅*/: case 0xb7 /*⠇⠃⠆*/: case 0xbc /*⠄⠃⠇*/: case 0xbd /*⠆⠃⠇*/: case 0xbe /*⠅⠃⠇*/: case 0xbf /*⠇⠃⠇*/: case 0xc1 /*⠆⠆⠀*/:
+ case 0xc5 /*⠆⠇⠀*/: case 0xc7 /*⠇⠇⠀*/: case 0xc9 /*⠆⠆⠁*/: case 0xcb /*⠇⠆⠁*/: case 0xcd /*⠆⠇⠁*/: case 0xcf /*⠇⠇⠁*/: case 0xd1 /*⠆⠆⠂*/: case 0xd3 /*⠇⠆⠂*/:
+ case 0xd5 /*⠆⠇⠂*/: case 0xd7 /*⠇⠇⠂*/: case 0xd9 /*⠆⠆⠃*/: case 0xdb /*⠇⠆⠃*/: case 0xdc /*⠄⠇⠃*/: case 0xdd /*⠆⠇⠃*/: case 0xde /*⠅⠇⠃*/: case 0xdf /*⠇⠇⠃*/:
+ case 0xe1 /*⠆⠆⠄*/: case 0xe3 /*⠇⠆⠄*/: case 0xe5 /*⠆⠇⠄*/: case 0xe7 /*⠇⠇⠄*/: case 0xe9 /*⠆⠆⠅*/: case 0xeb /*⠇⠆⠅*/: case 0xed /*⠆⠇⠅*/: case 0xef /*⠇⠇⠅*/:
+ case 0xf0 /*⠄⠆⠆*/: case 0xf1 /*⠆⠆⠆*/: case 0xf2 /*⠅⠆⠆*/: case 0xf3 /*⠇⠆⠆*/: case 0xf4 /*⠄⠇⠆*/: case 0xf5 /*⠆⠇⠆*/: case 0xf6 /*⠅⠇⠆*/: case 0xf7 /*⠇⠇⠆*/:
+ case 0xf8 /*⠄⠆⠇*/: case 0xf9 /*⠆⠆⠇*/: case 0xfa /*⠅⠆⠇*/: case 0xfb /*⠇⠆⠇*/: case 0xfc /*⠄⠇⠇*/: case 0xfd /*⠆⠇⠇*/: case 0xfe /*⠅⠇⠇*/: case 0xff /*⠇⠇⠇*/:
+ left = right = pixel;
+ break;
+ case 0x44 /*⠀⠇⠀*/: case 0x45 /*⠂⠇⠀*/:
+ left = pixel;
+ right = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x03 /*⠃⠂⠀*/: case 0x0b /*⠃⠂⠁*/: case 0x13 /*⠃⠂⠂*/: case 0x18 /*⠀⠂⠃*/: case 0x19 /*⠂⠂⠃*/: case 0x1a /*⠁⠂⠃*/: case 0x1b /*⠃⠂⠃*/: case 0x23 /*⠃⠂⠄*/:
+ case 0x26 /*⠁⠃⠄*/: case 0x2b /*⠃⠂⠅*/: case 0x2c /*⠀⠃⠅*/: case 0x2d /*⠂⠃⠅*/: case 0x2e /*⠁⠃⠅*/: case 0x30 /*⠀⠂⠆*/: case 0x31 /*⠂⠂⠆*/: case 0x32 /*⠁⠂⠆*/:
+ case 0x33 /*⠃⠂⠆*/: case 0x35 /*⠂⠃⠆*/: case 0x36 /*⠁⠃⠆*/: case 0x38 /*⠀⠂⠇*/: case 0x39 /*⠂⠂⠇*/: case 0x3a /*⠁⠂⠇*/: case 0x3b /*⠃⠂⠇*/: case 0x4b /*⠃⠆⠁*/:
+ case 0x53 /*⠃⠆⠂*/: case 0x56 /*⠁⠇⠂*/: case 0x59 /*⠂⠆⠃*/: case 0x5a /*⠁⠆⠃*/: case 0x5b /*⠃⠆⠃*/: case 0x68 /*⠀⠆⠅*/: case 0x69 /*⠂⠆⠅*/: case 0x6a /*⠁⠆⠅*/:
+ case 0x6b /*⠃⠆⠅*/: case 0x81 /*⠆⠂⠀*/: case 0x83 /*⠇⠂⠀*/: case 0x89 /*⠆⠂⠁*/: case 0x8b /*⠇⠂⠁*/: case 0x91 /*⠆⠂⠂*/: case 0x93 /*⠇⠂⠂*/: case 0x95 /*⠆⠃⠂*/:
+ case 0x96 /*⠅⠃⠂*/: case 0x98 /*⠄⠂⠃*/: case 0x99 /*⠆⠂⠃*/: case 0x9a /*⠅⠂⠃*/: case 0x9b /*⠇⠂⠃*/: case 0xa1 /*⠆⠂⠄*/: case 0xa3 /*⠇⠂⠄*/: case 0xa5 /*⠆⠃⠄*/:
+ case 0xa6 /*⠅⠃⠄*/: case 0xa9 /*⠆⠂⠅*/: case 0xab /*⠇⠂⠅*/: case 0xac /*⠄⠃⠅*/: case 0xad /*⠆⠃⠅*/: case 0xae /*⠅⠃⠅*/: case 0xb0 /*⠄⠂⠆*/: case 0xb1 /*⠆⠂⠆*/:
+ case 0xb2 /*⠅⠂⠆*/: case 0xb3 /*⠇⠂⠆*/: case 0xb4 /*⠄⠃⠆*/: case 0xb5 /*⠆⠃⠆*/: case 0xb6 /*⠅⠃⠆*/: case 0xb8 /*⠄⠂⠇*/: case 0xb9 /*⠆⠂⠇*/: case 0xba /*⠅⠂⠇*/:
+ case 0xbb /*⠇⠂⠇*/: case 0xc8 /*⠄⠆⠁*/: case 0xca /*⠅⠆⠁*/: case 0xd2 /*⠅⠆⠂*/: case 0xd4 /*⠄⠇⠂*/: case 0xd6 /*⠅⠇⠂*/: case 0xd8 /*⠄⠆⠃*/: case 0xda /*⠅⠆⠃*/:
+ case 0xe8 /*⠄⠆⠅*/: case 0xea /*⠅⠆⠅*/:
+ if (linePixels.get(x, y)) {
+ left = right = pixel;
+ } else {
+ left = right = pixel;
+ }
+ break;
+ case 0x48 /*⠀⠆⠁*/:
+ left = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ right = pixel;
+ break;
+ case 0x40 /*⠀⠆⠀*/: case 0x41 /*⠂⠆⠀*/: case 0x42 /*⠁⠆⠀*/: case 0xc3 /*⠇⠆⠀*/:
+ left = pixel;
+ right = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x24 /*⠀⠃⠄*/:
+ left = !linePixels.get(x - 1, y) ? src.get(x - 1, y) : !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ right = pixel;
+ break;
+ case 0x04 /*⠀⠃⠀*/: case 0x05 /*⠂⠃⠀*/: case 0x84 /*⠄⠃⠀*/: case 0x87 /*⠇⠃⠀*/:
+ left = pixel;
+ right = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ break;
+ case 0x46 /*⠁⠇⠀*/: case 0x4c /*⠀⠇⠁*/: case 0x4d /*⠂⠇⠁*/: case 0x4e /*⠁⠇⠁*/: case 0x64 /*⠀⠇⠄*/: case 0x65 /*⠂⠇⠄*/: case 0x66 /*⠁⠇⠄*/: case 0x6c /*⠀⠇⠅*/:
+ case 0x6d /*⠂⠇⠅*/: case 0x6e /*⠁⠇⠅*/: case 0xc4 /*⠄⠇⠀*/: case 0xc6 /*⠅⠇⠀*/: case 0xcc /*⠄⠇⠁*/: case 0xce /*⠅⠇⠁*/: case 0xe4 /*⠄⠇⠄*/: case 0xe6 /*⠅⠇⠄*/:
+ case 0xec /*⠄⠇⠅*/: case 0xee /*⠅⠇⠅*/:
+ if (linePixels.get(x, y)) {
+ left = pixel;
+ right = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ left = right = pixel;
+ }
+ break;
+ case 0x28 /*⠀⠂⠅*/:
+ left = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ right = pixel;
+ break;
+ case 0x82 /*⠅⠂⠀*/:
+ left = pixel;
+ right = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ break;
+ case 0x43 /*⠃⠆⠀*/: case 0x60 /*⠀⠆⠄*/: case 0x61 /*⠂⠆⠄*/: case 0x62 /*⠁⠆⠄*/: case 0x63 /*⠃⠆⠄*/: case 0xc0 /*⠄⠆⠀*/: case 0xc2 /*⠅⠆⠀*/: case 0xe0 /*⠄⠆⠄*/:
+ case 0xe2 /*⠅⠆⠄*/:
+ if (linePixels.get(x, y)) {
+ left = pixel;
+ right = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ left = right = pixel;
+ }
+ break;
+ case 0x16 /*⠁⠃⠂*/: case 0x34 /*⠀⠃⠆*/:
+ if (linePixels.get(x, y)) {
+ left = !linePixels.get(x - 1, y) ? src.get(x - 1, y) : !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ right = pixel;
+ } else {
+ left = right = pixel;
+ }
+ break;
+ case 0x06 /*⠁⠃⠀*/: case 0x0c /*⠀⠃⠁*/: case 0x0d /*⠂⠃⠁*/: case 0x0e /*⠁⠃⠁*/: case 0x85 /*⠆⠃⠀*/: case 0x86 /*⠅⠃⠀*/: case 0x8c /*⠄⠃⠁*/: case 0x8d /*⠆⠃⠁*/:
+ case 0x8e /*⠅⠃⠁*/:
+ if (linePixels.get(x, y)) {
+ left = pixel;
+ right = !linePixels.get(x + 1, y) ? src.get(x + 1, y) : !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ } else {
+ left = right = pixel;
+ }
+ break;
+ case 0x58 /*⠀⠆⠃*/: case 0xd0 /*⠄⠆⠂*/:
+ if (linePixels.get(x, y)) {
+ left = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ right = pixel;
+ } else {
+ left = right = pixel;
+ }
+ break;
+ }
+}
+
+inline void scalePixelTo1x2(
+ const MarginedBitmap<Color> &src,
+ const MarginedBitmap<bool> &linePixels,
+ int x, int y,
+ // Out parameters
+ Color &top, Color &bottom
+) {
+ const Color pixel = src.get(x, y);
+ const EqualityMatrix matrix = getEqualityMatrix(src.getPointerTo(x, y), src.getStride());
+
+ // Note: There is a case label for every possible value, so we don't need a default label.
+ switch (matrix) {
+ case 0x00 /*⠀⠂⠀*/: case 0x02 /*⠁⠂⠀*/: case 0x04 /*⠀⠃⠀*/: case 0x07 /*⠃⠃⠀*/: case 0x08 /*⠀⠂⠁*/: case 0x17 /*⠃⠃⠂*/: case 0x1c /*⠀⠃⠃*/: case 0x1d /*⠂⠃⠃*/:
+ case 0x1e /*⠁⠃⠃*/: case 0x1f /*⠃⠃⠃*/: case 0x20 /*⠀⠂⠄*/: case 0x22 /*⠁⠂⠄*/: case 0x24 /*⠀⠃⠄*/: case 0x25 /*⠂⠃⠄*/: case 0x27 /*⠃⠃⠄*/: case 0x28 /*⠀⠂⠅*/:
+ case 0x29 /*⠂⠂⠅*/: case 0x2a /*⠁⠂⠅*/: case 0x2f /*⠃⠃⠅*/: case 0x37 /*⠃⠃⠆*/: case 0x3c /*⠀⠃⠇*/: case 0x3d /*⠂⠃⠇*/: case 0x3e /*⠁⠃⠇*/: case 0x3f /*⠃⠃⠇*/:
+ case 0x40 /*⠀⠆⠀*/: case 0x41 /*⠂⠆⠀*/: case 0x42 /*⠁⠆⠀*/: case 0x44 /*⠀⠇⠀*/: case 0x45 /*⠂⠇⠀*/: case 0x47 /*⠃⠇⠀*/: case 0x48 /*⠀⠆⠁*/: case 0x49 /*⠂⠆⠁*/:
+ case 0x4a /*⠁⠆⠁*/: case 0x4f /*⠃⠇⠁*/: case 0x50 /*⠀⠆⠂*/: case 0x51 /*⠂⠆⠂*/: case 0x52 /*⠁⠆⠂*/: case 0x54 /*⠀⠇⠂*/: case 0x55 /*⠂⠇⠂*/: case 0x57 /*⠃⠇⠂*/:
+ case 0x5c /*⠀⠇⠃*/: case 0x5d /*⠂⠇⠃*/: case 0x5e /*⠁⠇⠃*/: case 0x5f /*⠃⠇⠃*/: case 0x67 /*⠃⠇⠄*/: case 0x6f /*⠃⠇⠅*/: case 0x70 /*⠀⠆⠆*/: case 0x71 /*⠂⠆⠆*/:
+ case 0x72 /*⠁⠆⠆*/: case 0x73 /*⠃⠆⠆*/: case 0x74 /*⠀⠇⠆*/: case 0x75 /*⠂⠇⠆*/: case 0x76 /*⠁⠇⠆*/: case 0x77 /*⠃⠇⠆*/: case 0x78 /*⠀⠆⠇*/: case 0x79 /*⠂⠆⠇*/:
+ case 0x7a /*⠁⠆⠇*/: case 0x7b /*⠃⠆⠇*/: case 0x7c /*⠀⠇⠇*/: case 0x7d /*⠂⠇⠇*/: case 0x7e /*⠁⠇⠇*/: case 0x7f /*⠃⠇⠇*/: case 0x80 /*⠄⠂⠀*/: case 0x82 /*⠅⠂⠀*/:
+ case 0x84 /*⠄⠃⠀*/: case 0x87 /*⠇⠃⠀*/: case 0x88 /*⠄⠂⠁*/: case 0x8a /*⠅⠂⠁*/: case 0x8f /*⠇⠃⠁*/: case 0x92 /*⠅⠂⠂*/: case 0x94 /*⠄⠃⠂*/: case 0x97 /*⠇⠃⠂*/:
+ case 0x9c /*⠄⠃⠃*/: case 0x9d /*⠆⠃⠃*/: case 0x9e /*⠅⠃⠃*/: case 0x9f /*⠇⠃⠃*/: case 0xa2 /*⠅⠂⠄*/: case 0xa4 /*⠄⠃⠄*/: case 0xa7 /*⠇⠃⠄*/: case 0xa8 /*⠄⠂⠅*/:
+ case 0xaa /*⠅⠂⠅*/: case 0xaf /*⠇⠃⠅*/: case 0xb7 /*⠇⠃⠆*/: case 0xbc /*⠄⠃⠇*/: case 0xbd /*⠆⠃⠇*/: case 0xbe /*⠅⠃⠇*/: case 0xbf /*⠇⠃⠇*/: case 0xc1 /*⠆⠆⠀*/:
+ case 0xc3 /*⠇⠆⠀*/: case 0xc5 /*⠆⠇⠀*/: case 0xc7 /*⠇⠇⠀*/: case 0xc9 /*⠆⠆⠁*/: case 0xcb /*⠇⠆⠁*/: case 0xcd /*⠆⠇⠁*/: case 0xcf /*⠇⠇⠁*/: case 0xd1 /*⠆⠆⠂*/:
+ case 0xd3 /*⠇⠆⠂*/: case 0xd5 /*⠆⠇⠂*/: case 0xd7 /*⠇⠇⠂*/: case 0xd9 /*⠆⠆⠃*/: case 0xdb /*⠇⠆⠃*/: case 0xdc /*⠄⠇⠃*/: case 0xdd /*⠆⠇⠃*/: case 0xde /*⠅⠇⠃*/:
+ case 0xdf /*⠇⠇⠃*/: case 0xe3 /*⠇⠆⠄*/: case 0xe5 /*⠆⠇⠄*/: case 0xe7 /*⠇⠇⠄*/: case 0xe9 /*⠆⠆⠅*/: case 0xeb /*⠇⠆⠅*/: case 0xed /*⠆⠇⠅*/: case 0xef /*⠇⠇⠅*/:
+ case 0xf0 /*⠄⠆⠆*/: case 0xf1 /*⠆⠆⠆*/: case 0xf2 /*⠅⠆⠆*/: case 0xf3 /*⠇⠆⠆*/: case 0xf4 /*⠄⠇⠆*/: case 0xf5 /*⠆⠇⠆*/: case 0xf6 /*⠅⠇⠆*/: case 0xf7 /*⠇⠇⠆*/:
+ case 0xf8 /*⠄⠆⠇*/: case 0xf9 /*⠆⠆⠇*/: case 0xfa /*⠅⠆⠇*/: case 0xfb /*⠇⠆⠇*/: case 0xfc /*⠄⠇⠇*/: case 0xfd /*⠆⠇⠇*/: case 0xfe /*⠅⠇⠇*/: case 0xff /*⠇⠇⠇*/:
+ top = bottom = pixel;
+ break;
+ case 0x11 /*⠂⠂⠂*/: case 0x15 /*⠂⠃⠂*/:
+ top = pixel;
+ bottom = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ break;
+ case 0x06 /*⠁⠃⠀*/: case 0x0c /*⠀⠃⠁*/: case 0x0e /*⠁⠃⠁*/: case 0x23 /*⠃⠂⠄*/: case 0x26 /*⠁⠃⠄*/: case 0x2b /*⠃⠂⠅*/: case 0x2c /*⠀⠃⠅*/: case 0x2d /*⠂⠃⠅*/:
+ case 0x2e /*⠁⠃⠅*/: case 0x46 /*⠁⠇⠀*/: case 0x4b /*⠃⠆⠁*/: case 0x4c /*⠀⠇⠁*/: case 0x4d /*⠂⠇⠁*/: case 0x4e /*⠁⠇⠁*/: case 0x53 /*⠃⠆⠂*/: case 0x56 /*⠁⠇⠂*/:
+ case 0x59 /*⠂⠆⠃*/: case 0x5a /*⠁⠆⠃*/: case 0x5b /*⠃⠆⠃*/: case 0x60 /*⠀⠆⠄*/: case 0x62 /*⠁⠆⠄*/: case 0x63 /*⠃⠆⠄*/: case 0x64 /*⠀⠇⠄*/: case 0x65 /*⠂⠇⠄*/:
+ case 0x66 /*⠁⠇⠄*/: case 0x68 /*⠀⠆⠅*/: case 0x69 /*⠂⠆⠅*/: case 0x6a /*⠁⠆⠅*/: case 0x6b /*⠃⠆⠅*/: case 0x6c /*⠀⠇⠅*/: case 0x6d /*⠂⠇⠅*/: case 0x6e /*⠁⠇⠅*/:
+ case 0x86 /*⠅⠃⠀*/: case 0x8c /*⠄⠃⠁*/: case 0x8e /*⠅⠃⠁*/: case 0x96 /*⠅⠃⠂*/: case 0x98 /*⠄⠂⠃*/: case 0x9a /*⠅⠂⠃*/: case 0xa1 /*⠆⠂⠄*/: case 0xa3 /*⠇⠂⠄*/:
+ case 0xa5 /*⠆⠃⠄*/: case 0xa6 /*⠅⠃⠄*/: case 0xa9 /*⠆⠂⠅*/: case 0xab /*⠇⠂⠅*/: case 0xac /*⠄⠃⠅*/: case 0xad /*⠆⠃⠅*/: case 0xae /*⠅⠃⠅*/: case 0xb0 /*⠄⠂⠆*/:
+ case 0xb2 /*⠅⠂⠆*/: case 0xb4 /*⠄⠃⠆*/: case 0xb6 /*⠅⠃⠆*/: case 0xb8 /*⠄⠂⠇*/: case 0xba /*⠅⠂⠇*/: case 0xc0 /*⠄⠆⠀*/: case 0xc2 /*⠅⠆⠀*/: case 0xc4 /*⠄⠇⠀*/:
+ case 0xc6 /*⠅⠇⠀*/: case 0xc8 /*⠄⠆⠁*/: case 0xca /*⠅⠆⠁*/: case 0xcc /*⠄⠇⠁*/: case 0xce /*⠅⠇⠁*/: case 0xd2 /*⠅⠆⠂*/: case 0xd4 /*⠄⠇⠂*/: case 0xd6 /*⠅⠇⠂*/:
+ case 0xd8 /*⠄⠆⠃*/: case 0xda /*⠅⠆⠃*/: case 0xe0 /*⠄⠆⠄*/: case 0xe2 /*⠅⠆⠄*/: case 0xe4 /*⠄⠇⠄*/: case 0xe6 /*⠅⠇⠄*/: case 0xe8 /*⠄⠆⠅*/: case 0xea /*⠅⠆⠅*/:
+ case 0xec /*⠄⠇⠅*/: case 0xee /*⠅⠇⠅*/:
+ if (linePixels.get(x, y)) {
+ top = bottom = pixel;
+ } else {
+ top = bottom = pixel;
+ }
+ break;
+ case 0x90 /*⠄⠂⠂*/:
+ top = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ bottom = pixel;
+ break;
+ case 0x21 /*⠂⠂⠄*/: case 0xe1 /*⠆⠆⠄*/:
+ top = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottom = pixel;
+ break;
+ case 0x10 /*⠀⠂⠂*/: case 0x12 /*⠁⠂⠂*/: case 0x14 /*⠀⠃⠂*/:
+ top = pixel;
+ bottom = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ break;
+ case 0x01 /*⠂⠂⠀*/: case 0x05 /*⠂⠃⠀*/: case 0x09 /*⠂⠂⠁*/: case 0x0f /*⠃⠃⠁*/:
+ top = pixel;
+ bottom = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0x13 /*⠃⠂⠂*/: case 0x19 /*⠂⠂⠃*/: case 0x1b /*⠃⠂⠃*/: case 0x31 /*⠂⠂⠆*/: case 0x33 /*⠃⠂⠆*/: case 0x35 /*⠂⠃⠆*/: case 0x39 /*⠂⠂⠇*/: case 0x3b /*⠃⠂⠇*/:
+ case 0x91 /*⠆⠂⠂*/: case 0x93 /*⠇⠂⠂*/: case 0x95 /*⠆⠃⠂*/: case 0x99 /*⠆⠂⠃*/: case 0x9b /*⠇⠂⠃*/: case 0xb1 /*⠆⠂⠆*/: case 0xb3 /*⠇⠂⠆*/: case 0xb5 /*⠆⠃⠆*/:
+ case 0xb9 /*⠆⠂⠇*/: case 0xbb /*⠇⠂⠇*/:
+ if (linePixels.get(x, y)) {
+ top = pixel;
+ bottom = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : pixel;
+ } else {
+ top = bottom = pixel;
+ }
+ break;
+ case 0x0a /*⠁⠂⠁*/:
+ top = pixel;
+ bottom = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ break;
+ case 0xa0 /*⠄⠂⠄*/:
+ top = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottom = pixel;
+ break;
+ case 0x16 /*⠁⠃⠂*/: case 0x18 /*⠀⠂⠃*/: case 0x1a /*⠁⠂⠃*/: case 0x30 /*⠀⠂⠆*/: case 0x32 /*⠁⠂⠆*/: case 0x34 /*⠀⠃⠆*/: case 0x36 /*⠁⠃⠆*/: case 0x38 /*⠀⠂⠇*/:
+ case 0x3a /*⠁⠂⠇*/:
+ if (linePixels.get(x, y)) {
+ top = pixel;
+ bottom = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ } else {
+ top = bottom = pixel;
+ }
+ break;
+ case 0x43 /*⠃⠆⠀*/: case 0x61 /*⠂⠆⠄*/:
+ if (linePixels.get(x, y)) {
+ top = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ bottom = pixel;
+ } else {
+ top = bottom = pixel;
+ }
+ break;
+ case 0x58 /*⠀⠆⠃*/: case 0xd0 /*⠄⠆⠂*/:
+ if (linePixels.get(x, y)) {
+ top = !linePixels.get(x, y - 1) ? src.get(x, y - 1) : !linePixels.get(x - 1, y) ? src.get(x - 1, y) : pixel;
+ bottom = pixel;
+ } else {
+ top = bottom = pixel;
+ }
+ break;
+ case 0x03 /*⠃⠂⠀*/: case 0x0b /*⠃⠂⠁*/: case 0x0d /*⠂⠃⠁*/: case 0x81 /*⠆⠂⠀*/: case 0x83 /*⠇⠂⠀*/: case 0x85 /*⠆⠃⠀*/: case 0x89 /*⠆⠂⠁*/: case 0x8b /*⠇⠂⠁*/:
+ case 0x8d /*⠆⠃⠁*/:
+ if (linePixels.get(x, y)) {
+ top = pixel;
+ bottom = !linePixels.get(x, y + 1) ? src.get(x, y + 1) : !linePixels.get(x + 1, y) ? src.get(x + 1, y) : pixel;
+ } else {
+ top = bottom = pixel;
+ }
+ break;
+ }
+}
diff --git a/graphics/larryScale_generator.js b/graphics/larryScale_generator.js
new file mode 100644
index 0000000000..8df7b01746
--- /dev/null
+++ b/graphics/larryScale_generator.js
@@ -0,0 +1,402 @@
+/* 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.
+*
+*/
+
+// This file re-generates 'larryScale_generated.cpp'.
+// To run it, install Node 8.0+, then run 'node larryScale_generator.js'.
+
+const fs = require('fs');
+
+// Compass directions
+const Direction = {
+ W: 0,
+ NW: 1,
+ N: 2,
+ NE: 3,
+ E: 4,
+ SE: 5,
+ S: 6,
+ SW: 7,
+
+ sanitize(direction) {
+ return ((direction % 8) + 8) % 8;
+ }
+};
+
+function getVector(direction) {
+ switch (direction) {
+ case Direction.W: return [-1, 0];
+ case Direction.NW: return [-1, -1];
+ case Direction.N: return [0, -1];
+ case Direction.NE: return [1, -1];
+ case Direction.E: return [1, 0];
+ case Direction.SE: return [1, 1];
+ case Direction.S: return [0, 1];
+ case Direction.SW: return [-1, 1];
+ default:
+ throw new Error(`Invalid direction: ${direction}`);
+ }
+}
+
+// An equality matrix is a combination of eight Boolean flags indicating whether
+// each of the surrounding pixels has the same color as the central pixel.
+//
+// +-----------+-----------+-----------+
+// | NW = 0x02 | N = 0x04 | NE = 0x08 |
+// +-----------+-----------+-----------+
+// | W = 0x01 | Reference | E = 0x10 |
+// +-----------+-----------+-----------+
+// | SW = 0x80 | S = 0x40 | SE = 0x20 |
+// +-----------+-----------+-----------+
+class EqualityMatrix {
+ constructor(value) {
+ this.value = value;
+ }
+
+ get(direction) {
+ const mask = 0x01 << Direction.sanitize(direction);
+ return (this.value & mask) != 0;
+ }
+
+ set(direction, flag) {
+ const mask = 0x01 << Direction.sanitize(direction);
+ this.value = this.value & ~mask | (flag ? mask : 0x00);
+ }
+
+ get w() { return this.get(Direction.W); }
+ set w(flag) { this.set(Direction.W, flag); }
+
+ get nw() { return this.get(Direction.NW); }
+ set nw(flag) { this.set(Direction.NW, flag); }
+
+ get n() { return this.get(Direction.N); }
+ set n(flag) { this.set(Direction.N, flag); }
+
+ get ne() { return this.get(Direction.NE); }
+ set ne(flag) { this.set(Direction.NE, flag); }
+
+ get e() { return this.get(Direction.E); }
+ set e(flag) { this.set(Direction.E, flag); }
+
+ get se() { return this.get(Direction.SE); }
+ set se(flag) { this.set(Direction.SE, flag); }
+
+ get s() { return this.get(Direction.S); }
+ set s(flag) { this.set(Direction.S, flag); }
+
+ get sw() { return this.get(Direction.SW); }
+ set sw(flag) { this.set(Direction.SW, flag); }
+
+ toBraille() {
+ return getBrailleColumn(this.nw, this.w, this.sw)
+ + getBrailleColumn(this.n, true, this.s)
+ + getBrailleColumn(this.ne, this.e, this.se);
+ }
+}
+
+function getBrailleColumn(top, middle, bottom) {
+ const codepoint = 0x2800 | (top ? 1 : 0) | (middle ? 2 : 0) | (bottom ? 4 : 0);
+ return String.fromCodePoint(codepoint);
+}
+
+function indent(string, tabCount = 1) {
+ const indentation = '\t'.repeat(tabCount);
+ return string
+ .split(/\r?\n/)
+ .map(s => indentation + s)
+ .join('\n');
+}
+
+function toHex(number, minLength = 2) {
+ const hex = number.toString(16);
+ const padding = '0'.repeat(Math.max(minLength - hex.length, 0));
+ return `0x${padding}${hex}`;
+}
+
+function generateCaseLabel(matrix) {
+ return `case ${toHex(matrix.value)} /*${matrix.toBraille()}*/:`
+}
+
+function generateCaseBlock(matrixes, body) {
+ const maxLabelsPerLine = 8;
+ const labels = matrixes
+ .map(generateCaseLabel)
+ .reduce((a, b, index) => a + ((index % maxLabelsPerLine === 0) ? '\n' : '\t') + b);
+ return `${labels}\n${indent(body)}`;
+}
+
+function generateSwitchBlock(variableName, getCaseBody) {
+ const matrixesByBody = new Map();
+ for (let value = 0; value <= 0xFF; value++) {
+ const matrix = new EqualityMatrix(value);
+ const body = getCaseBody(matrix);
+ if (!matrixesByBody.has(body)) {
+ matrixesByBody.set(body, []);
+ }
+ matrixesByBody.get(body).push(matrix);
+ }
+ const orderedPairs = [...matrixesByBody.entries()]
+ // For readability: order cases by increasing code length
+ .sort((a, b) => a[0].length - b[0].length);
+ const switchStatements = orderedPairs
+ .map(([body, matrixes]) => generateCaseBlock(matrixes, body))
+ .join('\n');
+ const comment = '// Note: There is a case label for every possible value, so we don\'t need a default label.';
+ return `${comment}\nswitch (${variableName}) {\n${switchStatements}\n}`;
+}
+
+const PixelType = {
+ // Pixel is part of a line
+ LINE: 'line',
+ // Pixel is part of a fill
+ FILL: 'fill',
+ // Pixel is part of a line *or* a fill
+ INDETERMINATE: 'indeterminate'
+};
+
+function getPixelType(matrix) {
+ // Single pixels are fills
+ if (matrix.value === 0) return PixelType.FILL;
+
+ // 2x2 blocks are fills
+ if (
+ (matrix.n && matrix.ne && matrix.e)
+ || (matrix.e && matrix.se && matrix.s)
+ || (matrix.s && matrix.sw && matrix.w)
+ || (matrix.w && matrix.nw && matrix.n)
+ ) return PixelType.FILL;
+
+ // A pixel adjacent to a 2x2 block is a fill.
+ // This requires reading out of the matrix, so we can't be sure.
+ if (
+ (matrix.n && matrix.ne)
+ || (matrix.ne && matrix.e)
+ || (matrix.e && matrix.se)
+ || (matrix.se && matrix.s)
+ || (matrix.s && matrix.sw)
+ || (matrix.sw && matrix.w)
+ || (matrix.w && matrix.nw)
+ || (matrix.nw && matrix.n)
+ ) return PixelType.INDETERMINATE;
+
+ // Everything else is part of a line
+ return PixelType.LINE;
+}
+
+function isPowerOfTwo(number) {
+ return Math.log2(number) % 1 === 0;
+}
+
+// Upscales a line pixel to 2x2.
+// Returns a 4-element array of Booleans in order top-left, top-right, bottom-left, bottom-right.
+// Each Boolean indicates whether the upscaled pixel should be filled with the original color.
+function getLineUpscaleFlags(matrix) {
+ // The rules for upscaling lines are *not* symmetrical but biased toward the left
+
+ // Special rules for upscaling smooth angled lines
+ switch (matrix.value) {
+ case 0x34 /*⠀⠃⠆*/:
+ return [false, true, false, false]; // [ ▀]
+ case 0x58 /*⠀⠆⠃*/:
+ return [false, false, false, true]; // [ ▄]
+ case 0x43 /*⠃⠆⠀*/:
+ return [false, false, true, false]; // [▄ ]
+
+ case 0x61 /*⠂⠆⠄*/:
+ return [false, false, true, false]; // [▄ ]
+ case 0x16 /*⠁⠃⠂*/:
+ return [false, true, false, false]; // [ ▀]
+ case 0xD0 /*⠄⠆⠂*/:
+ return [false, false, false, true]; // [ ▄]
+
+ case 0x24 /*⠀⠃⠄*/:
+ case 0x48 /*⠀⠆⠁*/:
+ return [false, true, false, true]; // [ █]
+
+ case 0x21 /*⠂⠂⠄*/:
+ case 0x90 /*⠄⠂⠂*/:
+ return [false, false, true, true]; // [▄▄]
+
+ case 0x50 /*⠀⠆⠂*/:
+ return [true, true, true, false]; // [█▀]
+ }
+
+ // Generic rules for upscaling lines
+
+ // Ignore diagonals next to fully-adjacent pixels
+ matrix = new EqualityMatrix(matrix.value);
+ if (matrix.w) {
+ matrix.sw = matrix.nw = false;
+ }
+ if (matrix.n) {
+ matrix.nw = matrix.ne = false;
+ }
+ if (matrix.e) {
+ matrix.ne = matrix.se = false;
+ }
+ if (matrix.s) {
+ matrix.se = matrix.sw = false;
+ }
+
+ // Mirror single lines
+ if (isPowerOfTwo(matrix.value)) {
+ matrix.value |= (matrix.value << 4) | (matrix.value >> 4);
+ }
+
+ return [
+ matrix.w || matrix.nw || matrix.n,
+ matrix.ne || matrix.e,
+ matrix.s || matrix.sw,
+ matrix.se
+ ];
+}
+
+// Upscales a fill pixel to 2x2.
+// Same result format as getLineUpscaleFlags.
+function getFillUpscaleFlags(matrix) {
+ // The rules for upscaling fills are *not* symmetrical but biased toward the top-left
+
+ // Special rules for upscaling cornered fills
+ switch (matrix.value) {
+ case 0xE1 /*⠆⠆⠄*/:
+ return [false, false, true, true]; // [▄▄]
+ case 0x0F /*⠃⠃⠁*/:
+ return [true, true, false, false]; // [▀▀]
+ case 0xC3 /*⠇⠆⠀*/:
+ case 0x87 /*⠇⠃⠀*/:
+ return [true, false, true, false]; // [█ ]
+ }
+
+ // Generic rules for upscaling fills
+ if (!matrix.s && !matrix.se && !matrix.e && (matrix.sw || matrix.ne)) {
+ return [true, true, true, false]; // [█▀]
+ } else if (!matrix.n && !matrix.ne && !matrix.e && (matrix.nw || matrix.se)) {
+ return [true, false, true, true]; // [█▄]
+ } else {
+ return [true, true, true, true]; // [██]
+ }
+}
+
+function formatOffset(number) {
+ if (number < 0) {
+ return ` - ${-number}`;
+ }
+ if (number > 0) {
+ return ` + ${number}`;
+ }
+ return '';
+}
+
+function generatePixelUpscaleCode(matrix, flags, pixelRecords, { generateBreak = true } = {}) {
+ const targetsByValue = new Map();
+ function addAssignment(param, value) {
+ if (targetsByValue.has(value)) {
+ targetsByValue.get(value).push(param);
+ } else {
+ targetsByValue.set(value, [param]);
+ }
+ }
+ for (const pixelRecord of pixelRecords) {
+ const param = pixelRecord.param;
+ const useSourceColor = flags
+ .filter((flag, index) => pixelRecord.flagIndexes.includes(index))
+ .some(flag => flag);
+ if (useSourceColor) {
+ addAssignment(param, 'pixel');
+ } else {
+ const sourceDirections = pixelRecord.sourceDirections
+ .filter(d => !matrix.get(d));
+ const value = sourceDirections
+ .filter(d => !matrix.get(d)) // We don't want to get our own color
+ .map(d => {
+ const vector = getVector(d);
+ const otherValueCode = `src.get(x${formatOffset(vector[0])}, y${formatOffset(vector[1])})`;
+ return `!linePixels.get(x${formatOffset(vector[0])}, y${formatOffset(vector[1])}) ? ${otherValueCode} : `;
+ })
+ .join('') + 'pixel';
+ addAssignment(param, value);
+ }
+ }
+
+ return [...targetsByValue.entries()]
+ .map(([value, targets]) => [...targets, value].join(' = ') + ';')
+ .concat(generateBreak ? ['break;'] : [])
+ .join('\n');
+}
+
+function generateScalePixelFunction(width, height, pixelRecords) {
+ const params = pixelRecords
+ .map((pixelRecord, index) => `Color &${pixelRecord.param}`)
+ .join(', ');
+ const header =
+ `inline void scalePixelTo${width}x${height}(\n\tconst MarginedBitmap<Color> &src,\n\tconst MarginedBitmap<bool> &linePixels,\n\tint x, int y,\n\t// Out parameters\n\t${params}\n)`;
+ const prefix =
+ 'const Color pixel = src.get(x, y);\n'
+ + 'const EqualityMatrix matrix = getEqualityMatrix(src.getPointerTo(x, y), src.getStride());';
+ const switchBlock = generateSwitchBlock('matrix', matrix => {
+ const pixelType = getPixelType(matrix);
+ switch (pixelType) {
+ case PixelType.LINE:
+ return generatePixelUpscaleCode(matrix, getLineUpscaleFlags(matrix), pixelRecords);
+ case PixelType.FILL:
+ return generatePixelUpscaleCode(matrix, getFillUpscaleFlags(matrix), pixelRecords);
+ case PixelType.INDETERMINATE:
+ const lineUpscaleCode = generatePixelUpscaleCode(matrix, getLineUpscaleFlags(matrix), pixelRecords, { generateBreak: false });
+ const fillUpscaleCode = generatePixelUpscaleCode(matrix, getFillUpscaleFlags(matrix), pixelRecords, { generateBreak: false });
+ return `if (linePixels.get(x, y)) {\n${indent(lineUpscaleCode)}\n} else {\n${indent(fillUpscaleCode)}\n}\nbreak;`;
+ }
+ });
+ return `${header} {\n${indent(prefix)}\n\n${indent(switchBlock)}\n}`;
+}
+
+function generateScalePixelTo2x2() {
+ const pixelRecords = [
+ { param: 'topLeft', flagIndexes: [0], sourceDirections: [Direction.N, Direction.W] },
+ { param: 'topRight', flagIndexes: [1], sourceDirections: [Direction.N, Direction.E] },
+ { param: 'bottomLeft', flagIndexes: [2], sourceDirections: [Direction.S, Direction.W] },
+ { param: 'bottomRight', flagIndexes: [3], sourceDirections: [Direction.S, Direction.E] }
+ ];
+ return generateScalePixelFunction(2, 2, pixelRecords);
+}
+
+function generateScalePixelTo2x1() {
+ const pixelRecords = [
+ { param: 'left', flagIndexes: [0, 2], sourceDirections: [Direction.N, Direction.W, Direction.S] },
+ { param: 'right', flagIndexes: [1, 3], sourceDirections: [Direction.N, Direction.E, Direction.S] }
+ ];
+ return generateScalePixelFunction(2, 1, pixelRecords);
+}
+
+function generateScalePixelTo1x2() {
+ const pixelRecords = [
+ { param: 'top', flagIndexes: [0, 1], sourceDirections: [Direction.N, Direction.W, Direction.E] },
+ { param: 'bottom', flagIndexes: [2, 3], sourceDirections: [Direction.S, Direction.W, Direction.E] }
+ ];
+ return generateScalePixelFunction(1, 2, pixelRecords);
+}
+
+const generators = [generateScalePixelTo2x2, generateScalePixelTo2x1, generateScalePixelTo1x2];
+const generatedFunctions = generators
+ .map(generator => generator())
+ .join('\n\n');
+const legalese = fs.readFileSync(__filename, 'utf8').match(/\/\*[\s\S]*?\*\//)[0];
+const headerComment = '// This file was generated by larryScale_generator.js.\n// Do not edit directly! Instead, edit the generator script and run it.'
+fs.writeFileSync('./larryScale_generated.cpp', `${legalese}\n\n${headerComment}\n\n${generatedFunctions}\n`);
diff --git a/graphics/module.mk b/graphics/module.mk
index dbe5a3b084..6076768ba8 100644
--- a/graphics/module.mk
+++ b/graphics/module.mk
@@ -12,6 +12,7 @@ MODULE_OBJS := \
fonts/newfont.o \
fonts/ttf.o \
fonts/winfont.o \
+ larryScale.o \
maccursor.o \
macgui/macfontmanager.o \
macgui/macmenu.o \