2018-03-15 15:34:59 +01:00
/ * S c u m m V M - G r a p h i c A d v e n t u r e E n g i n e
2021-12-26 18:47:58 +01:00
*
* 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/>.
*
* /
2018-03-15 15:34:59 +01:00
// This file re-generates 'larryScale_generated.cpp'.
// To run it, install Node 8.0+, then run 'node larryScale_generator.js'.
2022-01-12 08:26:39 +00:00
process . on ( "unhandledRejection" , ( err ) => {
console . error ( err ) ;
} ) ;
2018-03-15 15:34:59 +01:00
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' ) ;
2019-12-11 05:37:23 +00:00
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 } \n switch ( ${ variableName } ) { \n default: ${ switchStatements } \n } ` ;
2018-03-15 15:34:59 +01:00
}
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 \t const MarginedBitmap<Color> &src, \n \t const MarginedBitmap<bool> &linePixels, \n \t int 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 } ) ;
2022-01-12 08:26:39 +00:00
if ( lineUpscaleCode === fillUpscaleCode ) {
return ` ${ lineUpscaleCode } \n break; ` ;
} else {
return ` if (linePixels.get(x, y)) { \n ${ indent ( lineUpscaleCode ) } \n } else { \n ${ indent ( fillUpscaleCode ) } \n } \n break; ` ;
}
2018-03-15 15:34:59 +01:00
}
} ) ;
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 ` ) ;