diff options
-rw-r--r-- | common/gui_options.cpp | 1 | ||||
-rw-r--r-- | common/gui_options.h | 1 | ||||
-rw-r--r-- | engines/sci/detection.cpp | 10 | ||||
-rw-r--r-- | engines/sci/detection_tables.h | 5 | ||||
-rw-r--r-- | engines/sci/graphics/celobj32.cpp | 107 | ||||
-rw-r--r-- | engines/sci/sci.h | 1 | ||||
-rw-r--r-- | graphics/larryScale.cpp | 406 | ||||
-rw-r--r-- | graphics/larryScale.h | 82 | ||||
-rw-r--r-- | graphics/larryScale_generated.cpp | 639 | ||||
-rw-r--r-- | graphics/larryScale_generator.js | 402 | ||||
-rw-r--r-- | graphics/module.mk | 1 |
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 \ |