allow for creating new tiles
(does not yet add to game data)
This commit is contained in:
parent
e2d5d33ac7
commit
948fccb5a3
|
@ -1,6 +1,7 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// todo define things like 16x16, 128x128 etc. as constants?
|
// todo define things like 16x16, 128x128 etc. as constants?
|
||||||
// also script debounce/throttle times
|
// also script debounce/throttle times
|
||||||
|
const animationTime = 400; // defined in bitsy.js
|
||||||
|
|
||||||
var bitsyData = {};
|
var bitsyData = {};
|
||||||
|
|
||||||
|
@ -27,6 +28,8 @@ $(document).ready(function() {
|
||||||
|
|
||||||
var tiles = [];
|
var tiles = [];
|
||||||
|
|
||||||
|
var tileMatchThreshold = 0;
|
||||||
|
|
||||||
var croptions = {
|
var croptions = {
|
||||||
url: 'https://i.imgur.com/ThQZ94v.jpg',
|
url: 'https://i.imgur.com/ThQZ94v.jpg',
|
||||||
viewport: {width: 128, height: 128, type: 'square'},
|
viewport: {width: 128, height: 128, type: 'square'},
|
||||||
|
@ -87,6 +90,19 @@ $(document).ready(function() {
|
||||||
return _.first(_.sortBy(colourOptions, 'difference'));
|
return _.first(_.sortBy(colourOptions, 'difference'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function newTileName() {
|
||||||
|
var tileNames = _.map(bitsyData.tiles, 'name');
|
||||||
|
|
||||||
|
var i = 1; // start with 1 as 0 is an implicit tile
|
||||||
|
|
||||||
|
while (tileNames.indexOf(i.toString(36)) > -1) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// base 36 = 0-9a-z
|
||||||
|
return i.toString(36);
|
||||||
|
}
|
||||||
|
|
||||||
function handleBitsyGameData() {
|
function handleBitsyGameData() {
|
||||||
bitsyData = {};
|
bitsyData = {};
|
||||||
|
|
||||||
|
@ -127,14 +143,15 @@ $(document).ready(function() {
|
||||||
|
|
||||||
// get tiles
|
// get tiles
|
||||||
|
|
||||||
bitsyData.tiles = {};
|
bitsyData.tiles = [];
|
||||||
|
|
||||||
// tile 0 (background colour only) is implicit in bitsy rather than being stored in the game data
|
// tile 0 (background colour only) is implicit in bitsy rather than being stored in the game data
|
||||||
// so, make our own version
|
// so, make our own version
|
||||||
bitsyData.tiles[0] = {
|
bitsyData.tiles.push({
|
||||||
name: 0,
|
name: "0",
|
||||||
bitmap: _.chunk(_.split(_.repeat(0, 64), ''), 8)
|
bitmap: _.chunk(_.times(64, _.constant(0)), 8),
|
||||||
};
|
new: false // this could also be used to stop it from being added to the game data, wooo
|
||||||
|
});
|
||||||
|
|
||||||
// todo: handle animated tiles properly instead of discarding the second animation frame
|
// todo: handle animated tiles properly instead of discarding the second animation frame
|
||||||
var tiles = input.match(/TIL (.*)\n([01]{8}\n){8}(>\n([01]{8}\n){8})?/g); // everything after > is an optional second animation frame
|
var tiles = input.match(/TIL (.*)\n([01]{8}\n){8}(>\n([01]{8}\n){8})?/g); // everything after > is an optional second animation frame
|
||||||
|
@ -144,9 +161,14 @@ $(document).ready(function() {
|
||||||
|
|
||||||
tile = tile.replace(/TIL .*\n/, '');
|
tile = tile.replace(/TIL .*\n/, '');
|
||||||
|
|
||||||
var bitmap = tile.match(/[01]/g);
|
var bitmap = _.map(tile.match(/[01]/g), _.toInteger);
|
||||||
|
|
||||||
var newTile = {name: name};
|
var newTile = {
|
||||||
|
name: name,
|
||||||
|
new: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo make this agnostic? i.e. tile.frames = _.chunk(bitmap, 64)
|
||||||
|
|
||||||
if (bitmap.length === 64) { // normal tile
|
if (bitmap.length === 64) { // normal tile
|
||||||
newTile.bitmap = _.chunk(bitmap, 8);
|
newTile.bitmap = _.chunk(bitmap, 8);
|
||||||
|
@ -155,7 +177,7 @@ $(document).ready(function() {
|
||||||
newTile.secondAnimationFrame = _.chunk(_.takeRight(bitmap, 64), 8);
|
newTile.secondAnimationFrame = _.chunk(_.takeRight(bitmap, 64), 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
bitsyData.tiles[name] = newTile;
|
bitsyData.tiles.push(newTile);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (_.find(bitsyData.palettes, {'id': palette.id})) {
|
if (_.find(bitsyData.palettes, {'id': palette.id})) {
|
||||||
|
@ -172,7 +194,7 @@ $(document).ready(function() {
|
||||||
palette = _.first(_.sortBy(bitsyData.palettes, 'id'));
|
palette = _.first(_.sortBy(bitsyData.palettes, 'id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderResult();
|
renderDebounced();
|
||||||
|
|
||||||
// update palette picker
|
// update palette picker
|
||||||
$('tr.palette').remove();
|
$('tr.palette').remove();
|
||||||
|
@ -210,7 +232,7 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var renderResult = _.debounce(function() {
|
function render() {
|
||||||
$croppie.croppie('result', {
|
$croppie.croppie('result', {
|
||||||
type: 'rawcanvas',
|
type: 'rawcanvas',
|
||||||
size: 'viewport'
|
size: 'viewport'
|
||||||
|
@ -232,10 +254,10 @@ $(document).ready(function() {
|
||||||
|
|
||||||
var targetColour = getClosestColour(pixel, palette);
|
var targetColour = getClosestColour(pixel, palette);
|
||||||
|
|
||||||
if (targetColour.name === "background") { // ?! why is this reversed?
|
if (targetColour.name === "background") {
|
||||||
monochrome.push(1);
|
monochrome.push(0);
|
||||||
} else { // tile
|
} else { // tile
|
||||||
monochrome.push(0)
|
monochrome.push(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
rawData[i ] = targetColour.red;
|
rawData[i ] = targetColour.red;
|
||||||
|
@ -262,23 +284,16 @@ $(document).ready(function() {
|
||||||
pseudoTile.push(
|
pseudoTile.push(
|
||||||
_.slice(monochrome[(tileY * 8) + y], (tileX * 8), (tileX * 8) + 8)
|
_.slice(monochrome[(tileY * 8) + y], (tileX * 8), (tileX * 8) + 8)
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
var tilesForMatch = bitsyData.tiles;
|
var tilesForMatch = bitsyData.tiles;
|
||||||
|
|
||||||
_.each(tilesForMatch, function(tile) {
|
// if we want to always create new tiles, don't bother trying to check matches
|
||||||
tile.match = 0;
|
if (tileMatchThreshold < 64) {
|
||||||
|
_.each(tilesForMatch, function(tile) {
|
||||||
|
tile.match = 0;
|
||||||
|
|
||||||
_.each(tile.bitmap, function(row, y) {
|
_.each(tile.bitmap, function(row, y) {
|
||||||
_.each(row, function(pixel, x) {
|
|
||||||
if (parseInt(pixel) === parseInt(pseudoTile[y][x])) {
|
|
||||||
tile.match++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tile.secondAnimationFrame) {
|
|
||||||
_.each(tile.secondAnimationFrame, function(row, y) {
|
|
||||||
_.each(row, function(pixel, x) {
|
_.each(row, function(pixel, x) {
|
||||||
if (parseInt(pixel) === parseInt(pseudoTile[y][x])) {
|
if (parseInt(pixel) === parseInt(pseudoTile[y][x])) {
|
||||||
tile.match++;
|
tile.match++;
|
||||||
|
@ -286,62 +301,100 @@ $(document).ready(function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
tile.match /= 2;
|
if (tile.secondAnimationFrame) {
|
||||||
}
|
_.each(tile.secondAnimationFrame, function(row, y) {
|
||||||
});
|
_.each(row, function(pixel, x) {
|
||||||
|
if (parseInt(pixel) === parseInt(pseudoTile[y][x])) {
|
||||||
|
tile.match++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
var bestMatch = _.first(_.sortBy(tilesForMatch, 'match'));
|
tile.match /= 2;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// if best match is under threshold
|
// what if there are several equally good matches?
|
||||||
|
// find highest match amount and find all of them
|
||||||
|
var bestMatchAmount = _.last(_.sortBy(tilesForMatch, ['match'])).match;
|
||||||
|
var bestMatches = _.filter(tilesForMatch, {'match': bestMatchAmount});
|
||||||
|
|
||||||
|
// sort by name in ascending order
|
||||||
|
// earlier names are preferable
|
||||||
|
var bestMatch = _.first(_.sortBy(bestMatches, 'name'));
|
||||||
|
|
||||||
|
if (tileMatchThreshold === 64 || bestMatch.match <= tileMatchThreshold) {
|
||||||
// turn pseudo-tile into a real tile and add it to the tile data
|
// turn pseudo-tile into a real tile and add it to the tile data
|
||||||
|
|
||||||
room.push(bestMatch.name);
|
var name = newTileName();
|
||||||
|
|
||||||
|
bitsyData.tiles.push({
|
||||||
|
name: name,
|
||||||
|
bitmap: pseudoTile,
|
||||||
|
new: true
|
||||||
|
});
|
||||||
|
|
||||||
|
room.push(name);
|
||||||
|
|
||||||
|
// issue with this approach:
|
||||||
|
// what if a tile we add late in the loop is a better match for an earlier "good enough" match?
|
||||||
|
// this would also cause different results if the user were to add the same room several times
|
||||||
|
// we could keep iterating until the room no longer changes
|
||||||
|
} else {
|
||||||
|
room.push(bestMatch.name);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
room = _.chunk(room, 16);
|
room = _.chunk(room, 16);
|
||||||
|
|
||||||
// write room to output
|
// write room to output
|
||||||
|
|
||||||
imageData = document.getElementById("room-output").getContext('2d').getImageData(0, 0, 128, 128);
|
imageData = document.getElementById("room-output").getContext('2d').getImageData(0, 0, 128, 128);
|
||||||
rawData = imageData.data;
|
rawData = imageData.data;
|
||||||
|
|
||||||
_.each(room, function(row, tileY) {
|
_.each(room, function(row, tileY) {
|
||||||
_.each(row, function(tileName, tileX) {
|
_.each(row, function(tileName, tileX) {
|
||||||
if (_.get(bitsyData, 'tiles.' + tileName + '.bitmap')) {
|
var tile = _.find(bitsyData.tiles, {'name' : tileName});
|
||||||
_.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
|
_.each(tile.bitmap, function(row, y) {
|
||||||
|
_.each(row, function(pixel, x) {
|
||||||
|
var position = (((tileY * 8) + y) * 128) + ((tileX * 8) + x);
|
||||||
|
|
||||||
if (parseInt(pixel) === 0) {
|
position *= 4; // 4 values (rgba) per pixel
|
||||||
var pixelColour = palette.background;
|
|
||||||
} else {
|
|
||||||
var pixelColour = palette.tile;
|
|
||||||
}
|
|
||||||
|
|
||||||
rawData[position ] = pixelColour.red;
|
var pixelColour = {};
|
||||||
rawData[position + 1] = pixelColour.green;
|
|
||||||
rawData[position + 2] = pixelColour.blue;
|
switch(parseInt(pixel)) {
|
||||||
rawData[position + 3] = 255;
|
case 0: pixelColour = palette.background; break;
|
||||||
});
|
case 1: pixelColour = palette.tile; break;
|
||||||
|
default: console.log("error");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
document.getElementById('room-output').getContext('2d').putImageData(imageData, 0, 0);
|
||||||
});
|
});
|
||||||
}, 30);
|
}
|
||||||
|
|
||||||
$croppie.on('update', renderResult);
|
var renderDebounced = _.debounce(render, 30);
|
||||||
|
|
||||||
// make this not debounced but called every n milliseconds
|
$croppie.on('update', renderDebounced);
|
||||||
$('#brightness').on('change', renderResult);
|
|
||||||
|
$('#brightness').on('change', renderDebounced);
|
||||||
|
|
||||||
$('#brightness').on('dblclick', function() {
|
$('#brightness').on('dblclick', function() {
|
||||||
$(this).val(0);
|
$(this).val(0);
|
||||||
renderResult();
|
|
||||||
|
renderDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('label[for="brightness"]').on('click touchdown', function() {
|
$('label[for="brightness"]').on('click touchdown', function() {
|
||||||
|
@ -375,7 +428,20 @@ $(document).ready(function() {
|
||||||
// sprite colour is not currently used
|
// sprite colour is not currently used
|
||||||
}
|
}
|
||||||
|
|
||||||
renderResult();
|
renderDebounced();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('change', '#threshold', function() {
|
||||||
|
var newValue = parseInt($(this).val());
|
||||||
|
|
||||||
|
if (newValue < tileMatchThreshold) {
|
||||||
|
// set tiles back to default
|
||||||
|
bitsyData.tiles = _.filter(bitsyData.tiles, ['new', false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
tileMatchThreshold = newValue;
|
||||||
|
|
||||||
|
renderDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#save').on('click touchend', function() {
|
$('#save').on('click touchend', function() {
|
||||||
|
|
|
@ -74,10 +74,12 @@ input {
|
||||||
background-color: #d3cbd0;
|
background-color: #d3cbd0;
|
||||||
color: #594a54;
|
color: #594a54;
|
||||||
}
|
}
|
||||||
#brightness,
|
#brightness {
|
||||||
#threshold {
|
|
||||||
width: 256px;
|
width: 256px;
|
||||||
}
|
}
|
||||||
|
#threshold {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
#brightness + label,
|
#brightness + label,
|
||||||
#threshold + label {
|
#threshold + label {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
|
@ -95,13 +95,20 @@ textarea, input {
|
||||||
color: @dark;
|
color: @dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#brightness {
|
||||||
|
width: 256px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#threshold {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
#brightness, #threshold {
|
#brightness, #threshold {
|
||||||
+ label{
|
+ label{
|
||||||
.centre;
|
.centre;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo make this match the croppie slider or vice versa
|
// todo make this match the croppie slider or vice versa
|
||||||
width: 256px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#imageUpload {
|
#imageUpload {
|
||||||
|
|
|
@ -5229,4 +5229,4 @@ DLG ITM_0
|
||||||
You found a nice warm cup of tea
|
You found a nice warm cup of tea
|
||||||
|
|
||||||
VAR a
|
VAR a
|
||||||
42</textarea></div><div class="section" id="image"><h2>image</h2><div id="croppie"></div><input id="imageUpload" type="file" accepts="image/*"></div><div class="section" id="palette"><h2>palette</h2><form id="palettes"><table><tbody></tbody></table></form></div><div class="section" id="crop"><h2>preview</h2><canvas id="preview" width="128" height="128"></canvas><input id="brightness" type="range" min="-255" max="255" value="0"><label for="brightness">brightness</label></div><div class="section" id="output"><h2>output</h2><canvas id="room-output" width="128" height="128"></canvas><input id="roomName" type="text" placeholder="room name"><button id="save">write to game data</button></div></body></html>
|
42</textarea></div><div class="section" id="image"><h2>image</h2><div id="croppie"></div><input id="imageUpload" type="file" accepts="image/*"></div><div class="section" id="palette"><h2>palette</h2><form id="palettes"><table><tbody></tbody></table></form></div><div class="section" id="crop"><h2>preview</h2><canvas id="preview" width="128" height="128"></canvas><input id="brightness" type="range" min="-255" max="255" value="0"><label for="brightness">brightness</label><br><br><label>never </label><input id="threshold" type="range" min="0" max="64" value="0"><label> always</label><br><label for="threshold">create new tiles</label></div><div class="section" id="output"><h2>output</h2><canvas id="room-output" width="128" height="128"></canvas><input id="roomName" type="text" placeholder="room name"><button id="save">write to game data</button></div></body></html>
|
15
index.pug
15
index.pug
|
@ -58,6 +58,16 @@ html
|
||||||
|
|
||||||
label(for="brightness") brightness
|
label(for="brightness") brightness
|
||||||
|
|
||||||
|
br
|
||||||
|
br
|
||||||
|
|
||||||
|
label never
|
||||||
|
input#threshold(type="range" min=0 max=64 value=0)
|
||||||
|
label always
|
||||||
|
br
|
||||||
|
label(for="threshold") create new tiles
|
||||||
|
br
|
||||||
|
|
||||||
//- to do
|
//- to do
|
||||||
input#dithering(type="checkbox")
|
input#dithering(type="checkbox")
|
||||||
label(for="dithering") dithering
|
label(for="dithering") dithering
|
||||||
|
@ -76,11 +86,6 @@ html
|
||||||
button#save write to game data
|
button#save write to game data
|
||||||
|
|
||||||
//- to do
|
//- to do
|
||||||
input#threshold(type="range" min=0 max=64)
|
|
||||||
br
|
|
||||||
label(for="threshold")
|
|
||||||
span.left use existing tiles
|
|
||||||
span.right create new tiles
|
|
||||||
|
|
||||||
//-label favour broad strokes | favour details
|
//-label favour broad strokes | favour details
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,8 @@ if (src.match(/^(file|https)?:\/\/|^\/\//)) {
|
||||||
|
|
||||||
## to do
|
## to do
|
||||||
|
|
||||||
* allow user to save output as image, or tweet it :) *user can currently right-click -> Save As but the 128x128 size is not great*
|
* allow user to save output as image, or tweet it :)
|
||||||
|
* *user can currently right-click -> Save As but the 128x128 size is not great*
|
||||||
* create new tiles based on image
|
* create new tiles based on image
|
||||||
* only add unique new tiles
|
* only add unique new tiles
|
||||||
* don't write the 0 tile (implicit background-only tile)
|
* don't write the 0 tile (implicit background-only tile)
|
||||||
|
@ -64,4 +65,7 @@ if (src.match(/^(file|https)?:\/\/|^\/\//)) {
|
||||||
* allow user to draw to canvas
|
* allow user to draw to canvas
|
||||||
* do a 'branching tree' approach to finding the closest tile? i.e. create a 1x1, 2x2, 4x4 version of each tile, so all the broadly darker tiles will sit under '0' and lighter tiles under '1', then tiles that are lighter at the top will sit under '1100', etc... I'm not sure how much more effective this will be or whatever it will give better/faster results but it's worth a try
|
* do a 'branching tree' approach to finding the closest tile? i.e. create a 1x1, 2x2, 4x4 version of each tile, so all the broadly darker tiles will sit under '0' and lighter tiles under '1', then tiles that are lighter at the top will sit under '1100', etc... I'm not sure how much more effective this will be or whatever it will give better/faster results but it's worth a try
|
||||||
* give heavier weighting to edge pixels when finding a matching tile? (thanks Mark)
|
* give heavier weighting to edge pixels when finding a matching tile? (thanks Mark)
|
||||||
* apply grid lines to preview?
|
* apply grid lines to preview
|
||||||
|
* optionally add inverted versions of existing tiles if they are a better match
|
||||||
|
* make new tiles out of fragments of existing tiles instead of directly copying from bitmap
|
||||||
|
* allow user to zoom out so the image is letterboxed/windowboxed/etc.
|
||||||
|
|
Loading…
Reference in New Issue