$(document).ready(function() {
var bitsyData = {};
var palette = {
background: {
red: 62,
green: 43,
blue: 32,
},
tile: {
red: 208,
green: 112,
blue: 56,
},
sprite: {
red: 229,
green: 92,
blue: 68,
}
};
var room = [];
var tiles = [];
var croptions = {
url: 'https://i.imgur.com/ThQZ94v.jpg',
viewport: {width: 128, height: 128, type: 'square'},
boundary: {width: 256, height: 256},
zoom: 0
}
var $croppie = $('#croppie');
var croppie = $croppie.croppie(croptions);
function colourDifference(colour1, colour2) {
difference = {};
_.each(['red', 'green', 'blue'], function(key) {
difference[key] = Math.abs(colour1[key] - colour2[key]);
});
// sum rgb differences
return _.reduce(difference, function(sum, n) {
return sum + n;
}, 0);
}
function zeroPad(input, desiredLength) {
while (input.length < desiredLength) {
input = "0" + input;
}
return input;
}
function colourToHex(colour) {
return '#' + zeroPad(Number(colour.red ).toString(16), 2)
+ zeroPad(Number(colour.green).toString(16), 2)
+ zeroPad(Number(colour.blue ).toString(16), 2);
}
function hexToColour(hex) {
var rgb = hex.match(/[\da-fA-F]{2}/g);
return {
red: parseInt(rgb[0], 16),
green: parseInt(rgb[1], 16),
blue: parseInt(rgb[2], 16),
};
}
function getClosestColour(initialColour, colourOptions) {
// ditch sprite colour as we're not using it atm
delete colourOptions.sprite;
_.each(palette, function(colour, name) {
colourOptions[name].name = name;
colourOptions[name].difference = colourDifference(initialColour, colour);
});
// lowest difference (closest) wins
return _.first(_.sortBy(colourOptions, 'difference'));
}
function handleBitsyGameData() {
bitsyData = {};
var input = $('#bitsy-data').val();
// get palettes
var palettes = input.match(/PAL (.*)\s(NAME (.*)\s)?([0-9,]*[\s]){3}/g);
bitsyData.palettes = {};
_.each(palettes, function(palette, n) {
var thisPalette = {};
var name = "";
if (palette.match(/NAME (.+)\n/)) {
name = palette.match(/NAME (.+)\n/)[0].replace('NAME ', '');
} else if (palette.match(/PAL (\d+)\n/)) {
name = palette.match(/PAL (\d+)\n/)[0].replace("PAL", "palette");
}
var colours = palette.match(/\d+,\d+,\d+/g);
colours = _.map(colours, function(colour) {
var rgb = colour.split(',');
return {red: rgb[0], green: rgb[1], blue: rgb[2]};
});
bitsyData.palettes[name] = {
sortOrder: n,
background: colours[0],
tile: colours[1],
sprite: colours[2],
}
});
// get tiles
bitsyData.tiles = {};
var tiles = input.match(/TIL (.*)\n([01]{8}\n){8}/g);
for (var i = 0; i < tiles.length; i++) {
var name = tiles[i].match(/TIL .*/)[0].replace('TIL ', '');
tiles[i] = tiles[i].replace(/TIL .*\n/, '');
var bitmap = tiles[i].match(/[01]/g);
bitsyData.tiles[name] = {
name: name,
bitmap: _.chunk(bitmap, 8)
};
}
// set palette to first imported palette and redraw
palette = _.first(_.sortBy(bitsyData.palettes, 'sortOrder'));
renderResult();
// update palette picker
$('tr.palette').remove();
_.each(bitsyData.palettes, function(palette, name) {
$('#palette tbody').append(
'
'
+ ' | '
+ ' | '
+ ' | '
+ ' | '
+ ' | '
+ '
'
);
});
$('tr.palette input[type="radio"]').eq(0).trigger('click');
}
function readFile(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$croppie.croppie('bind', {
url: e.target.result,
zoom: 0
});
}
reader.readAsDataURL(input.files[0]);
}
}
var renderResult = _.debounce(function() {
$croppie.croppie('result', {
type: 'rawcanvas',
size: 'viewport'
}).then(function (result) {
var imageData = result.getContext('2d').getImageData(0, 0, 128, 128);
var rawData = imageData.data;
var monochrome = [];
brightnessAdjustment = parseFloat($('#brightness').val());
// for each pixel
for (var i = 0; i < rawData.length; i += 4) {
// this brightness adjustment is pretty crude but whatever
var pixel = {
red: Math.min(rawData[i ] + brightnessAdjustment, 255),
green: Math.min(rawData[i + 1] + brightnessAdjustment, 255),
blue: Math.min(rawData[i + 2] + brightnessAdjustment, 255),
};
var targetColour = getClosestColour(pixel, palette);
if (targetColour.name === "background") {
monochrome.push(0);
} else { // tile
monochrome.push(1)
}
rawData[i ] = targetColour.red;
rawData[i + 1] = targetColour.green;
rawData[i + 2] = targetColour.blue;
rawData[i + 3] = 255;
}
// split monochrome bitmap into equal chunks for easier x:y access
monochrome = _.chunk(monochrome, 128);
document.getElementById('preview').getContext('2d').putImageData(imageData, 0, 0);
// tiled output
room = [];
_.times(16, function(tileY) {
_.times(16, function(tileX) {
// make pseudo-tile from monochrome bitmap
var pseudoTile = [];
_.times(8, function(y) {
pseudoTile.push(
_.slice(monochrome[(tileY * 8) + y], (tileX * 8), (tileX * 8) + 8)
);
})
var tilesForMatch = bitsyData.tiles;
_.each(tilesForMatch, function(tile) {
tile.match = 0;
_.each(tile.bitmap, function(row, y) {
_.each(row, function(pixel, x) {
if (parseInt(pixel) === parseInt(pseudoTile[y][x])) {
tile.match++;
}
});
});
});
var bestMatch = _.first(_.sortBy(tilesForMatch, 'match'));
// if best match is under threshold
// turn pseudo-tile into a real tile and add it to the tile data
room.push(bestMatch.name);
});
});
room = _.chunk(room, 16);
// write room to output
imageData = document.getElementById("room-output").getContext('2d').getImageData(0, 0, 128, 128);
rawData = imageData.data;
_.each(room, function(row, tileY) {
_.each(row, function(tileName, tileX) {
if (_.get(bitsyData, 'tiles.' + tileName + '.bitmap')) {
_.each(bitsyData.tiles[tileName].bitmap, function(row, y) {
_.each(row, function(pixel, x) {
var position = (((tileY * 8) + y) * 128) + ((tileX * 8) + x);
position *= 4; // 4 values (rgba) per pixel
if (parseInt(pixel) === 1) { // ?! wtf
pixelColour = palette.background;
} else {
pixelColour = palette.tile;
}
rawData[position ] = pixelColour.red;
rawData[position + 1] = pixelColour.green;
rawData[position + 2] = pixelColour.blue;
rawData[position + 3] = 255;
});
});
}
});
});
document.getElementById('room-output').getContext('2d').putImageData(imageData, 0, 0);
});
}, 30);
$croppie.on('update', renderResult);
// make this not debounced but called every n milliseconds
$('#brightness').on('change', renderResult);
$('#brightness').on('dblclick', function() {
$(this).val(0);
renderResult();
});
$('label[for="brightness"]').on('click touchdown', function() {
$('#brightness').trigger('dblclick');
});
$('#bitsy-data').on('change blur keyup', handleBitsyGameData);
handleBitsyGameData();
$('#imageUpload').on('change', function () {
readFile(this);
});
$('#palette input').on('change', function() {
// if this is a colour input, update the palette
if ($(this).attr('type') === 'color') {
palette[$(this).attr('name')] = hexToColour($(this).val());
}
// if this is a radio button, pick this palette
if ($(this).attr('type') === 'radio') {
palette.background = hexToColour($(this).closest('.palette').find('input[type="color"][name="background"]').val());
palette.tile = hexToColour($(this).closest('.palette').find('input[type="color"][name="tile"]').val());
}
renderResult();
});
});