mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-11 11:45:21 +00:00
bcf6054c3a
These are flagged by GCC if -Wduplicated-branches is enabled.
410 lines
13 KiB
JavaScript
410 lines
13 KiB
JavaScript
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
// This file re-generates 'larryScale_generated.cpp'.
|
|
// To run it, install Node 8.0+, then run 'node larryScale_generator.js'.
|
|
|
|
process.on("unhandledRejection", (err) => {
|
|
console.error(err);
|
|
});
|
|
|
|
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, but one is added to avoid any compiler warnings.';
|
|
return `${comment}\nswitch (${variableName}) {\ndefault: ${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 });
|
|
if (lineUpscaleCode === fillUpscaleCode) {
|
|
return `${lineUpscaleCode}\nbreak;`;
|
|
} else {
|
|
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`);
|