Merge pull request #1141 from DanielSWolf/larryscale

SCI: High-quality "LarryScale" cel scaler for LSL7
This commit is contained in:
Thierry Crozat 2018-06-03 17:41:47 +01:00 committed by GitHub
commit 4229e2642e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1632 additions and 27 deletions

View File

@ -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 }
};

View File

@ -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)

View File

@ -436,6 +436,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,
{

View File

@ -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

View File

@ -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);
}
} else {
for (int16 x = targetRect.left; x < targetRect.right; ++x) {
_valuesX[x] = 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.
const int16 unscaledY = (scaledPosition.y / scaleY).toInt();
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);
}
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);
}
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);
}

View File

@ -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;

406
graphics/larryScale.cpp Normal file
View File

@ -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);
}
}
}

82
graphics/larryScale.h Normal file
View File

@ -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

View File

@ -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;
}
}

View File

@ -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`);

View File

@ -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 \