Various changes
- added `/:paletteSlug/:resolution` functionality for localhost testing - created `currFile.sublayers` for *things that should zoom with the canvas layers* - `currFile.layers` now solely contains the canvas layers - added `getProjectData` to `FileManager`'s exported methods --- - added `FileManager.localStorageSave` (it's basically just: localStorage.setItem("lpe-cache",FileManager.getProjectData())) - added `FileManager.localStorageCheck` (it's basically just: `!!localStorage.getItem("lpe-cache")`) - added `FileManager.localStorageLoad` (it's basically just: `return localStorage.getItem("lpe-cache")`) - added `FileManager.localStorageReset` (for debugging purity) --- - calling `FileManager.localStorageSave()` on mouse up (we should stress test this) --- - changed lpe file format to `{canvasWidth:number,canvasHeight:number,selectedLayer:number,colors:[],layers:[]}` - added backward compatibility for the old lpe file format --- - added some canvas utility functions in `canvas_util` - added Unsettled's color similarity utility functions in `color_util2` --- - html boilerplate - wang tiles - - POC - tiny text boilerplate - POC - tiny text font scraper --- - WIP - added two optional url route parameters `/:paletteSlug/:resolution/:prefillWidth/:prefillBinaryStr` - WIP POC - hbs_parser.js (outputs tree data about hbs file relationships)
@ -24,12 +24,12 @@
|
|||||||
height: 400px;
|
height: 400px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
display: none;
|
display: none;
|
||||||
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.64);
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
#checkerboard {
|
#checkerboard {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.64);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pixel-canvas {
|
#pixel-canvas {
|
||||||
|
52
hbs_parser.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const HANDLEBARS = require('handlebars');
|
||||||
|
const sass = require('sass');
|
||||||
|
|
||||||
|
const result = sass.compile('./css/pixel-editor.scss');
|
||||||
|
fs.writeFileSync("./css/pixel-editor.css",result.css);
|
||||||
|
|
||||||
|
// fs.readFile('/css/pixel-editor.scss', function(err, scssFile) {
|
||||||
|
// compiler.compile(scssFile.toString(), function(err, css) {
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
const hbsArr = [
|
||||||
|
...fs.readdirSync("./views/").filter(n=>n.slice(-3)==="hbs").map(n=>"./views/" + n),
|
||||||
|
...fs.readdirSync("./views/components/").filter(n=>n.slice(-3)==="hbs").map(n=>"./views/components/" + n),
|
||||||
|
...fs.readdirSync("./views/logs/").filter(n=>n.slice(-3)==="hbs").map(n=>"./views/logs/" + n),
|
||||||
|
...fs.readdirSync("./views/popups/").filter(n=>n.slice(-3)==="hbs").map(n=>"./views/popups/" + n),
|
||||||
|
];
|
||||||
|
const HBS_STR_MAP = {};
|
||||||
|
const HBS_SPEC_MAP = {};
|
||||||
|
const HBS_TEMPLATE_MAP = {};
|
||||||
|
const HBS_META_DATA = hbsArr.reduce((r,filePath,i)=>{
|
||||||
|
|
||||||
|
const fileStr = fs.readFileSync(filePath,"utf8");
|
||||||
|
const sp0 = fileStr.split("{{> ").slice(1);
|
||||||
|
const partialArr = sp0.map(n=>n.split("}}")[0]);
|
||||||
|
const sp1 = fileStr.split("{{").slice(1);
|
||||||
|
|
||||||
|
if(sp0.length || sp1.length) {
|
||||||
|
const dblCurlsArr = sp1.map(n=>n.split("}}")[0]);
|
||||||
|
|
||||||
|
HBS_STR_MAP[filePath] = fileStr;
|
||||||
|
|
||||||
|
HBS_SPEC_MAP[filePath] = HANDLEBARS.precompile(fileStr);
|
||||||
|
// HBS_TEMPLATE_MAP[filePath] = HANDLEBARS.template(HBS_SPEC_MAP[filePath]);
|
||||||
|
|
||||||
|
r[filePath] = {
|
||||||
|
fileStr,
|
||||||
|
filePath,
|
||||||
|
dblCurlsArr,
|
||||||
|
partialArr
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
},{});
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
"./js/HBS_META_DATA.js",
|
||||||
|
"const HBS_META_DATA = " + JSON.stringify(HBS_META_DATA,null,4)
|
||||||
|
);
|
BIN
images/icons_14x14.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
images/le_button_18x19.png
Normal file
After Width: | Height: | Size: 301 B |
BIN
images/lospec_mock1.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
images/lospec_mock2.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
images/rotate_test_8x8_x16.png
Normal file
After Width: | Height: | Size: 859 B |
BIN
images/sked_tree_32x32.png
Normal file
After Width: | Height: | Size: 872 B |
BIN
images/sked_x1.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
images/sked_x10.png
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
images/test_8x8.png
Normal file
After Width: | Height: | Size: 149 B |
BIN
images/wang_tilesets_32x32.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
@ -30,7 +30,7 @@ class Color {
|
|||||||
this.hsv = Color.rgbToHsv(this.rgb);
|
this.hsv = Color.rgbToHsv(this.rgb);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error("Unsupported color mode " + fmt);
|
//console.error("Unsupported color mode " + fmt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ const ColorModule = (() => {
|
|||||||
if (newColorHex == colors[i].jscolor.toString()) {
|
if (newColorHex == colors[i].jscolor.toString()) {
|
||||||
//if the color isnt the one that has the picker currently open
|
//if the color isnt the one that has the picker currently open
|
||||||
if (!colors[i].parentElement.classList.contains('jscolor-active')) {
|
if (!colors[i].parentElement.classList.contains('jscolor-active')) {
|
||||||
//console.log('%cColor is duplicate', colorCheckingStyle);
|
//////console.log('%cColor is duplicate', colorCheckingStyle);
|
||||||
|
|
||||||
//show the duplicate color warning
|
//show the duplicate color warning
|
||||||
duplicateColorWarning.style.visibility = 'visible';
|
duplicateColorWarning.style.visibility = 'visible';
|
||||||
@ -251,7 +251,7 @@ const ColorModule = (() => {
|
|||||||
|
|
||||||
//loop through colors
|
//loop through colors
|
||||||
for (var i = 0; i < colors.length; i++) {
|
for (var i = 0; i < colors.length; i++) {
|
||||||
//console.log(color,'=',colors[i].jscolor.toString());
|
//////console.log(color,'=',colors[i].jscolor.toString());
|
||||||
|
|
||||||
if (color == colors[i].jscolor.toString()) {
|
if (color == colors[i].jscolor.toString()) {
|
||||||
//set color to the color button
|
//set color to the color button
|
||||||
@ -346,7 +346,7 @@ const ColorModule = (() => {
|
|||||||
* @param {*} paletteColors The colours of the palette
|
* @param {*} paletteColors The colours of the palette
|
||||||
*/
|
*/
|
||||||
function createColorPalette(paletteColors) {
|
function createColorPalette(paletteColors) {
|
||||||
console.log("creating palette");
|
////console.log("creating palette");
|
||||||
//remove current palette
|
//remove current palette
|
||||||
while (colorsMenu.childElementCount > 1)
|
while (colorsMenu.childElementCount > 1)
|
||||||
colorsMenu.children[0].remove();
|
colorsMenu.children[0].remove();
|
||||||
@ -358,7 +358,7 @@ const ColorModule = (() => {
|
|||||||
for (var i = 0; i < paletteColors.length; i++) {
|
for (var i = 0; i < paletteColors.length; i++) {
|
||||||
var newColor = new Color("hex", paletteColors[i]);
|
var newColor = new Color("hex", paletteColors[i]);
|
||||||
var newColorElement = ColorModule.addColor(newColor.hex);
|
var newColorElement = ColorModule.addColor(newColor.hex);
|
||||||
|
////console.log('newColor.hex === ',newColor.hex);
|
||||||
var newColRgb = newColor.rgb;
|
var newColRgb = newColor.rgb;
|
||||||
|
|
||||||
var lightestColorRgb = lightestColor.rgb;
|
var lightestColorRgb = lightestColor.rgb;
|
||||||
@ -382,6 +382,8 @@ const ColorModule = (() => {
|
|||||||
|
|
||||||
//set as current color
|
//set as current color
|
||||||
updateCurrentColor(darkestColor.hex);
|
updateCurrentColor(darkestColor.hex);
|
||||||
|
|
||||||
|
////console.log('getCurrentPalette() === ',getCurrentPalette());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates the palette with the colours used in all the layers
|
/** Creates the palette with the colours used in all the layers
|
||||||
@ -421,7 +423,7 @@ const ColorModule = (() => {
|
|||||||
//create palette from colors array
|
//create palette from colors array
|
||||||
createColorPalette(colorPaletteArray);
|
createColorPalette(colorPaletteArray);
|
||||||
|
|
||||||
console.log("Done 2");
|
////console.log("Done 2");
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCurrentColor(color, refLayer) {
|
function updateCurrentColor(color, refLayer) {
|
||||||
|
@ -696,7 +696,7 @@ const ColorPicker = (() => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log("How did you select the " + currentPickingMode + ", hackerman?");
|
////console.log("How did you select the " + currentPickingMode + ", hackerman?");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ const Dialogue = (() => {
|
|||||||
let currentOpenDialogue = "";
|
let currentOpenDialogue = "";
|
||||||
let dialogueOpen = true;
|
let dialogueOpen = true;
|
||||||
|
|
||||||
const popUpContainer = document.getElementById("pop-up-container");
|
const popUpContainer = document.getElementById("pop-up-container") ?? document.createElement("div");
|
||||||
const cancelButtons = popUpContainer.getElementsByClassName('close-button');
|
const cancelButtons = popUpContainer.getElementsByClassName('close-button');
|
||||||
|
|
||||||
Events.onCustom("esc-pressed", closeDialogue);
|
Events.onCustom("esc-pressed", closeDialogue);
|
||||||
@ -31,6 +31,9 @@ const Dialogue = (() => {
|
|||||||
* @param {*} trackEvent Should I track the GA event?
|
* @param {*} trackEvent Should I track the GA event?
|
||||||
*/
|
*/
|
||||||
function showDialogue (dialogueName, trackEvent) {
|
function showDialogue (dialogueName, trackEvent) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (typeof trackEvent === 'undefined') trackEvent = true;
|
if (typeof trackEvent === 'undefined') trackEvent = true;
|
||||||
|
|
||||||
// Updating currently open dialogue
|
// Updating currently open dialogue
|
||||||
@ -83,4 +86,4 @@ const Dialogue = (() => {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
console.log("Dialog: " + Dialogue);
|
////console.log("Dialog: " + Dialogue);
|
@ -9,8 +9,10 @@ const EditorState = (() => {
|
|||||||
return pixelEditorMode;
|
return pixelEditorMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchMode(newMode) {
|
function switchMode(newMode, skipConfirm = false) {
|
||||||
if (!firstFile && newMode == "Basic" && !confirm('Switching to basic mode will flatten all the visible layers. Are you sure you want to continue?')) {
|
////console.trace();
|
||||||
|
const switchText = 'Switching to basic mode will flatten all the visible layers. Are you sure you want to continue?';
|
||||||
|
if (!firstFile && newMode == "Basic" && !skipConfirm && !confirm(switchText)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//switch to advanced mode
|
//switch to advanced mode
|
||||||
|
@ -100,8 +100,9 @@ const Events = (() => {
|
|||||||
*/
|
*/
|
||||||
function on(event, elementId, functionCallback, ...args) {
|
function on(event, elementId, functionCallback, ...args) {
|
||||||
//if element provided is string, get the actual element
|
//if element provided is string, get the actual element
|
||||||
|
if(!elementId)return;
|
||||||
const element = Util.getElement(elementId);
|
const element = Util.getElement(elementId);
|
||||||
|
if(!element)return;
|
||||||
element.addEventListener(event,
|
element.addEventListener(event,
|
||||||
function (e) {
|
function (e) {
|
||||||
functionCallback(...args, e);
|
functionCallback(...args, e);
|
||||||
|
65
js/File.js
@ -2,11 +2,12 @@ class File {
|
|||||||
// Canvas, canvas state
|
// Canvas, canvas state
|
||||||
canvasSize = [];
|
canvasSize = [];
|
||||||
zoom = 7;
|
zoom = 7;
|
||||||
canvasView = document.getElementById("canvas-view");
|
canvasView = document.getElementById("canvas-view") ?? document.createElement("canvas");
|
||||||
inited = false;
|
inited = false;
|
||||||
|
|
||||||
// Layers
|
// Layers
|
||||||
layers = [];
|
layers = [];
|
||||||
|
sublayers = [];
|
||||||
currentLayer = undefined;
|
currentLayer = undefined;
|
||||||
VFXLayer = undefined;
|
VFXLayer = undefined;
|
||||||
TMPLayer = undefined;
|
TMPLayer = undefined;
|
||||||
@ -129,11 +130,9 @@ class File {
|
|||||||
|
|
||||||
// Save all imageDatas
|
// Save all imageDatas
|
||||||
for (let i=0; i<currFile.layers.length; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
if (currFile.layers[i].hasCanvas()) {
|
imageDatas.push(currFile.layers[i].context.getImageData(
|
||||||
imageDatas.push(currFile.layers[i].context.getImageData(
|
0, 0, currFile.canvasSize[0], currFile.canvasSize[1])
|
||||||
0, 0, currFile.canvasSize[0], currFile.canvasSize[1])
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saving the history only if I'm not already undoing or redoing
|
// Saving the history only if I'm not already undoing or redoing
|
||||||
@ -154,16 +153,20 @@ class File {
|
|||||||
currFile.canvasSize[1] = parseInt(currFile.canvasSize[1]) +
|
currFile.canvasSize[1] = parseInt(currFile.canvasSize[1]) +
|
||||||
this.rcBorders.top + this.rcBorders.bottom;
|
this.rcBorders.top + this.rcBorders.bottom;
|
||||||
|
|
||||||
console.trace();
|
////console.trace();
|
||||||
console.log(currFile.canvasSize);
|
////console.log(currFile.canvasSize);
|
||||||
|
|
||||||
// Resize the canvases
|
// Resize the canvases
|
||||||
for (let i=0; i<currFile.layers.length; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
currFile.layers[i].canvas.width = currFile.canvasSize[0];
|
currFile.layers[i].canvas.width = currFile.canvasSize[0];
|
||||||
currFile.layers[i].canvas.height = currFile.canvasSize[1];
|
currFile.layers[i].canvas.height = currFile.canvasSize[1];
|
||||||
|
|
||||||
currFile.layers[i].resize();
|
currFile.layers[i].resize();
|
||||||
}
|
}
|
||||||
|
for (let i=0; i<currFile.sublayers.length; i++) {
|
||||||
|
currFile.sublayers[i].canvas.width = currFile.canvasSize[0];
|
||||||
|
currFile.sublayers[i].canvas.height = currFile.canvasSize[1];
|
||||||
|
currFile.sublayers[i].resize();
|
||||||
|
}
|
||||||
|
|
||||||
// Regenerate the checkerboard
|
// Regenerate the checkerboard
|
||||||
currFile.checkerBoard.fillCheckerboard();
|
currFile.checkerBoard.fillCheckerboard();
|
||||||
@ -213,16 +216,14 @@ class File {
|
|||||||
|
|
||||||
// Putting all the data for each layer with the right offsets (decided by the pivot)
|
// Putting all the data for each layer with the right offsets (decided by the pivot)
|
||||||
for (let i=0; i<currFile.layers.length; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
if (currFile.layers[i].hasCanvas()) {
|
if (customData == undefined) {
|
||||||
if (customData == undefined) {
|
currFile.layers[i].context.putImageData(imageDatas[copiedDataIndex], leftOffset, topOffset);
|
||||||
currFile.layers[i].context.putImageData(imageDatas[copiedDataIndex], leftOffset, topOffset);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
currFile.layers[i].context.putImageData(customData[copiedDataIndex], 0, 0);
|
|
||||||
}
|
|
||||||
currFile.layers[i].updateLayerPreview();
|
|
||||||
copiedDataIndex++;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
currFile.layers[i].context.putImageData(customData[copiedDataIndex], 0, 0);
|
||||||
|
}
|
||||||
|
currFile.layers[i].updateLayerPreview();
|
||||||
|
copiedDataIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialogue.closeDialogue();
|
Dialogue.closeDialogue();
|
||||||
@ -246,7 +247,7 @@ class File {
|
|||||||
this.rcPivot = "topleft";
|
this.rcPivot = "topleft";
|
||||||
|
|
||||||
// Computing the min and max coordinates in which there's a non empty pixel
|
// Computing the min and max coordinates in which there's a non empty pixel
|
||||||
for (let i=1; i<currFile.layers.length - nAppLayers; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
let imageData = currFile.layers[i].context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
|
let imageData = currFile.layers[i].context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
|
||||||
let pixelPosition;
|
let pixelPosition;
|
||||||
|
|
||||||
@ -291,12 +292,10 @@ class File {
|
|||||||
|
|
||||||
// Saving the data
|
// Saving the data
|
||||||
for (let i=0; i<currFile.layers.length; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
if (currFile.layers[i].hasCanvas()) {
|
imageDatas.push(currFile.layers[i].context.getImageData(minX - 1, currFile.layers[i].canvasSize[1] - maxY, maxX-minX + 1, maxY-minY + 1));
|
||||||
imageDatas.push(currFile.layers[i].context.getImageData(minX - 1, currFile.layers[i].canvasSize[1] - maxY, maxX-minX + 1, maxY-minY + 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log("sx: " + borders.left + "dx: " + borders.right + "top: " + borders.top + "btm: " + borders.bottom);
|
//////console.log("sx: " + borders.left + "dx: " + borders.right + "top: " + borders.top + "btm: " + borders.bottom);
|
||||||
|
|
||||||
document.getElementById("rc-border-left").value = this.rcBorders.left;
|
document.getElementById("rc-border-left").value = this.rcBorders.left;
|
||||||
document.getElementById("rc-border-right").value = this.rcBorders.right;
|
document.getElementById("rc-border-right").value = this.rcBorders.right;
|
||||||
@ -430,11 +429,9 @@ class File {
|
|||||||
|
|
||||||
// Get all the image datas
|
// Get all the image datas
|
||||||
for (let i=0; i<currFile.layers.length; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
if (currFile.layers[i].hasCanvas()) {
|
rsImageDatas.push(currFile.layers[i].context.getImageData(
|
||||||
rsImageDatas.push(currFile.layers[i].context.getImageData(
|
0, 0, currFile.canvasSize[0], currFile.canvasSize[1])
|
||||||
0, 0, currFile.canvasSize[0], currFile.canvasSize[1])
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// event is null when the user is undoing
|
// event is null when the user is undoing
|
||||||
@ -450,13 +447,11 @@ class File {
|
|||||||
|
|
||||||
// Put the image datas on the new canvases
|
// Put the image datas on the new canvases
|
||||||
for (let i=0; i<currFile.layers.length; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
if (currFile.layers[i].hasCanvas()) {
|
currFile.layers[i].context.putImageData(
|
||||||
currFile.layers[i].context.putImageData(
|
this.resizeImageData(rsImageDatas[layerIndex], newWidth, newHeight, this.currentAlgo), 0, 0
|
||||||
this.resizeImageData(rsImageDatas[layerIndex], newWidth, newHeight, this.currentAlgo), 0, 0
|
);
|
||||||
);
|
currFile.layers[i].updateLayerPreview();
|
||||||
currFile.layers[i].updateLayerPreview();
|
layerIndex++;
|
||||||
layerIndex++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updating start values when I finish scaling the sprite
|
// Updating start values when I finish scaling the sprite
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// localStorage.setItem("lpe-cache",`{}`)
|
||||||
const FileManager = (() => {
|
const FileManager = (() => {
|
||||||
|
|
||||||
// Binding the browse holder change event to file loading
|
// Binding the browse holder change event to file loading
|
||||||
@ -6,6 +7,7 @@ const FileManager = (() => {
|
|||||||
|
|
||||||
Events.on('change', browseHolder, loadFile);
|
Events.on('change', browseHolder, loadFile);
|
||||||
Events.on('change', browsePaletteHolder, loadPalette);
|
Events.on('change', browsePaletteHolder, loadPalette);
|
||||||
|
Events.on("click", "save-project-confirm", saveProject);
|
||||||
|
|
||||||
function openSaveProjectWindow() {
|
function openSaveProjectWindow() {
|
||||||
//create name
|
//create name
|
||||||
@ -20,7 +22,6 @@ const FileManager = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Util.setValue('lpe-file-name', fileName);
|
Util.setValue('lpe-file-name', fileName);
|
||||||
Events.on("click", "save-project-confirm", saveProject);
|
|
||||||
Dialogue.showDialogue('save-project', false);
|
Dialogue.showDialogue('save-project', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ const FileManager = (() => {
|
|||||||
|
|
||||||
function saveProject() {
|
function saveProject() {
|
||||||
// Get name
|
// Get name
|
||||||
|
// debugger;
|
||||||
let fileName = Util.getValue("lpe-file-name") + ".lpe";
|
let fileName = Util.getValue("lpe-file-name") + ".lpe";
|
||||||
let selectedPalette = Util.getText('palette-button');
|
let selectedPalette = Util.getText('palette-button');
|
||||||
//set download link
|
//set download link
|
||||||
@ -55,6 +57,9 @@ const FileManager = (() => {
|
|||||||
|
|
||||||
if (typeof ga !== 'undefined')
|
if (typeof ga !== 'undefined')
|
||||||
ga('send', 'event', 'Pixel Editor Save', selectedPalette, currFile.canvasSize[0]+'/'+currFile.canvasSize[1]); /*global ga*/
|
ga('send', 'event', 'Pixel Editor Save', selectedPalette, currFile.canvasSize[0]+'/'+currFile.canvasSize[1]); /*global ga*/
|
||||||
|
|
||||||
|
|
||||||
|
LayerList.closeOptionsMenu(); // is this the right place for this?
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportProject() {
|
function exportProject() {
|
||||||
@ -117,7 +122,39 @@ const FileManager = (() => {
|
|||||||
//open file selection dialog
|
//open file selection dialog
|
||||||
document.getElementById('open-image-browse-holder').click();
|
document.getElementById('open-image-browse-holder').click();
|
||||||
}
|
}
|
||||||
|
function localStorageCheck() {
|
||||||
|
return !!localStorage.getItem("lpe-cache");
|
||||||
|
}
|
||||||
|
function localStorageSave() {
|
||||||
|
const lpeStr = getProjectData();
|
||||||
|
const lpe = JSON.parse(lpeStr);
|
||||||
|
//console.log('LPE saved === ',lpe);
|
||||||
|
if(lpe.colors.length < 1)lpe.colors.push("#000000");
|
||||||
|
if(!lpe.canvasWidth)lpe.canvasWidth = 16;
|
||||||
|
if(!lpe.canvasHeight)lpe.canvasHeight = 16;
|
||||||
|
localStorage.setItem("lpe-cache", JSON.stringify(lpe));
|
||||||
|
}
|
||||||
|
function localStorageReset() {
|
||||||
|
localStorage.setItem("lpe-cache", JSON.stringify({
|
||||||
|
"canvasWidth":16,
|
||||||
|
"canvasHeight":16,
|
||||||
|
"editorMode":"Advanced",
|
||||||
|
"colors":["#000000","#0b6082","#1d8425","#cc1919"],
|
||||||
|
"selectedLayer":0,
|
||||||
|
"layers":[
|
||||||
|
{"canvas":{},"context":{"mozImageSmoothingEnabled":false},"isSelected":true,"isVisible":true,"isLocked":false,"oldLayerName":null,"menuEntry":{},"id":"layer0","name":"Layer 0","src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAHZJREFUOE9jZKAQMFKon4EuBvxHcyWKpdhcgK4BpB+nS9ElYJqJ9hqyQpI1ozsNZABRNnMnNIEt+7qgjhGrBpgCWOCBFKJHN0gNTgOQFSPbhi5OlAHYEhpBL+DThO4tgoGGHB7YwgKvAbj8j+xCgi4glNkoNgAA3JApEbHObDkAAAAASUVORK5CYII="},
|
||||||
|
{"canvas":{},"context":{"mozImageSmoothingEnabled":false},"isSelected":false,"isVisible":true,"isLocked":false,"oldLayerName":null,"menuEntry":{},"id":"layer1","name":"Layer 1","src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAGNJREFUOE9jZKAQMCLrl21R/Q/iP665jSKOzw7qGgCyCeQKsl0AM4AUb2D1KymuoJ0BxHoDZ3QRG6V445uYsCBoACGvEExxhFxBlAH4XEHQAEKpkygDkFMoumuINgCWI9HDBAChJjwRzAXQUwAAAABJRU5ErkJggg=="},
|
||||||
|
{"canvas":{},"context":{"mozImageSmoothingEnabled":false},"isSelected":false,"isVisible":true,"isLocked":false,"oldLayerName":null,"menuEntry":{},"id":"layer2","name":"Layer 2","src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAGNJREFUOE9jZKAQMJKi/4yk5H+YepPnz8F68RqArAFdI4yP1QBsNuFyKYYBMM0wJxLyIlYDiNWMNQxALhimBuCKUoKBSChKcRpASCPOhESsRoIGEBuVKF4g1XaMhERqMgYZAACIaEgR0hnFxgAAAABJRU5ErkJggg=="}
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
function localStorageLoad() {
|
||||||
|
////console.log("loading from localStorage");
|
||||||
|
////console.log('JSON.parse(localStorage.getItem("lpe-cache") ?? "{}") === ',JSON.parse(localStorage.getItem("lpe-cache") ?? "{}"));
|
||||||
|
const lpe = JSON.parse(localStorage.getItem("lpe-cache") ?? "{}");
|
||||||
|
//console.log('LPE loaded === ',lpe);
|
||||||
|
return lpe;
|
||||||
|
}
|
||||||
function loadFile() {
|
function loadFile() {
|
||||||
let fileName = document.getElementById("open-image-browse-holder").value;
|
let fileName = document.getElementById("open-image-browse-holder").value;
|
||||||
// Getting the extension
|
// Getting the extension
|
||||||
@ -139,7 +176,6 @@ const FileManager = (() => {
|
|||||||
|
|
||||||
browseHolder.value = null;
|
browseHolder.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openFile() {
|
function openFile() {
|
||||||
//load file
|
//load file
|
||||||
var fileReader = new FileReader();
|
var fileReader = new FileReader();
|
||||||
@ -165,59 +201,88 @@ const FileManager = (() => {
|
|||||||
};
|
};
|
||||||
fileReader.readAsDataURL(browseHolder.files[0]);
|
fileReader.readAsDataURL(browseHolder.files[0]);
|
||||||
}
|
}
|
||||||
|
function openProject(lpeData) {
|
||||||
function openProject() {
|
|
||||||
let file = browseHolder.files[0];
|
|
||||||
let reader = new FileReader();
|
|
||||||
|
|
||||||
// Getting all the data
|
// Getting all the data
|
||||||
reader.readAsText(file, "UTF-8");
|
if(lpeData){
|
||||||
// Converting the data to a json object and creating a new pixel (see _newPixel.js for more)
|
_parseLPE(lpeData);
|
||||||
reader.onload = function (e) {
|
} else {
|
||||||
let dictionary = JSON.parse(e.target.result);
|
let file = uri ?? browseHolder.files[0];
|
||||||
Startup.newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], dictionary);
|
let reader = new FileReader();
|
||||||
|
reader.readAsText(file, "UTF-8");
|
||||||
for (let i=0; dictionary['color' + i] != null; i++) {
|
// Converting the data to a json object and creating a new pixel (see _newPixel.js for more)
|
||||||
ColorModule.addColor(dictionary['color'+i]);
|
reader.onload = function (e) {
|
||||||
|
let dictionary = JSON.parse(e.target.result);
|
||||||
|
_parseLPE(dictionary);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Removing the default colours
|
function _parseLPE(dictionary) {
|
||||||
ColorModule.deleteColor(ColorModule.getCurrentPalette()[0]);
|
Startup.newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], dictionary, true);
|
||||||
ColorModule.deleteColor(ColorModule.getCurrentPalette()[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function loadFromLPE(dictionary) {
|
||||||
|
ColorModule.resetPalette();
|
||||||
|
|
||||||
|
//console.log('dictionary === ',dictionary);
|
||||||
|
|
||||||
|
dictionary = FileManager.upgradeLPE(dictionary);
|
||||||
|
|
||||||
|
EditorState.switchMode(dictionary.editorMode ?? 'Advanced');
|
||||||
|
|
||||||
|
// I add every layer the file had in it
|
||||||
|
// dictionary.layers.forEach((layerData,i)=>{
|
||||||
|
// let layerImage = layerData.src;
|
||||||
|
// if (layerData != null) {
|
||||||
|
// // Setting id
|
||||||
|
// let createdLayer = LayerList.addLayer(layerData.id, false, layerData.name);
|
||||||
|
// // Setting name
|
||||||
|
// createdLayer.menuEntry.getElementsByTagName("p")[0].innerHTML = layerData.name;
|
||||||
|
|
||||||
|
// // Adding the image (I can do that because they're sorted by increasing z-index)
|
||||||
|
// let img = new Image();
|
||||||
|
// img.onload = function() {
|
||||||
|
// createdLayer.context.drawImage(img, 0, 0);
|
||||||
|
// createdLayer.updateLayerPreview();
|
||||||
|
// };
|
||||||
|
|
||||||
|
// img.src = layerImage;
|
||||||
|
|
||||||
|
// // Setting visibility and lock options
|
||||||
|
// if (!layerData.isVisible) {
|
||||||
|
// createdLayer.hide();
|
||||||
|
// }
|
||||||
|
// if (layerData.isLocked) {
|
||||||
|
// createdLayer.lock();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
if(dictionary.colors)ColorModule.createColorPalette(dictionary.colors);
|
||||||
|
}
|
||||||
function getProjectData() {
|
function getProjectData() {
|
||||||
// use a dictionary
|
// use a dictionary
|
||||||
let dictionary = {};
|
let dictionary = {};
|
||||||
// sorting layers by increasing z-index
|
// sorting layers by increasing z-index
|
||||||
let layersCopy = currFile.layers.slice();
|
let layersCopy = currFile.layers.filter(n=>!!n.menuEntry).slice();
|
||||||
layersCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1);
|
// layersCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1);
|
||||||
// save canvas size
|
|
||||||
dictionary['canvasWidth'] = currFile.canvasSize[0];
|
dictionary['canvasWidth'] = currFile.canvasSize[0];
|
||||||
dictionary['canvasHeight'] = currFile.canvasSize[1];
|
dictionary['canvasHeight'] = currFile.canvasSize[1];
|
||||||
// save editor mode
|
|
||||||
dictionary['editorMode'] = EditorState.getCurrentMode();
|
dictionary['editorMode'] = EditorState.getCurrentMode();
|
||||||
// save palette
|
dictionary.colors = [
|
||||||
for (let i=0; i<ColorModule.getCurrentPalette().length; i++) {
|
...ColorModule.getCurrentPalette()
|
||||||
dictionary["color" + i] = ColorModule.getCurrentPalette()[i];
|
];
|
||||||
}
|
dictionary.layers = layersCopy
|
||||||
|
.map((n,i)=>{
|
||||||
// save number of layers
|
//console.log('n.name === ',n.name);
|
||||||
dictionary["nLayers"] = layersCopy.length;
|
if(n.isSelected)dictionary.selectedLayer = i;
|
||||||
|
return {
|
||||||
// save layers
|
...n,
|
||||||
for (let i=0; i<layersCopy.length; i++) {
|
src: n.canvas.toDataURL(),
|
||||||
// Only saving the layers the user has access to (no vfx, tmp or checkerboard layers)
|
};
|
||||||
if (layersCopy[i].menuEntry != null) {
|
});
|
||||||
dictionary["layer" + i] = layersCopy[i];
|
|
||||||
dictionary["layer" + i + "ImageData"] = layersCopy[i].canvas.toDataURL();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(dictionary);
|
return JSON.stringify(dictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPalette() {
|
function loadPalette() {
|
||||||
if (browsePaletteHolder.files && browsePaletteHolder.files[0]) {
|
if (browsePaletteHolder.files && browsePaletteHolder.files[0]) {
|
||||||
//make sure file is allowed filetype
|
//make sure file is allowed filetype
|
||||||
@ -268,9 +333,45 @@ const FileManager = (() => {
|
|||||||
|
|
||||||
browsePaletteHolder.value = null;
|
browsePaletteHolder.value = null;
|
||||||
}
|
}
|
||||||
|
function upgradeLPE(dictionary) {
|
||||||
|
////console.log('dictionary === ',dictionary);
|
||||||
|
if(dictionary.color0 && !dictionary.colors) {
|
||||||
|
dictionary.colors = [];
|
||||||
|
let colorIdx = 0;
|
||||||
|
while(dictionary[`color${colorIdx}`]) {
|
||||||
|
dictionary.colors.push(dictionary[`color${colorIdx}`]);
|
||||||
|
delete dictionary[`color${colorIdx}`];
|
||||||
|
colorIdx++;
|
||||||
|
}
|
||||||
|
dictionary.layers = Object.keys(dictionary).reduce((r,k,i)=>{
|
||||||
|
if(k.slice(0,5) === "layer"){
|
||||||
|
if(dictionary[k].isSelected){
|
||||||
|
dictionary.selectedLayer = r.length;
|
||||||
|
}
|
||||||
|
r.push({
|
||||||
|
...dictionary[k],
|
||||||
|
src: dictionary[`${k}ImageData`]
|
||||||
|
});
|
||||||
|
|
||||||
|
delete dictionary[k];
|
||||||
|
delete dictionary[`${k}ImageData`];
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
},[]);
|
||||||
|
}
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
loadFromLPE,
|
||||||
|
getProjectData,
|
||||||
|
localStorageReset,
|
||||||
|
localStorageCheck,
|
||||||
|
localStorageSave,
|
||||||
|
localStorageLoad,
|
||||||
|
upgradeLPE,
|
||||||
saveProject,
|
saveProject,
|
||||||
|
openProject,
|
||||||
exportProject,
|
exportProject,
|
||||||
openPixelExportWindow,
|
openPixelExportWindow,
|
||||||
openSaveProjectWindow,
|
openSaveProjectWindow,
|
||||||
|
@ -38,12 +38,12 @@ const History = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function undo () {
|
function undo () {
|
||||||
console.log("undoing");
|
////console.log("undoing");
|
||||||
undoOrRedo('undo');
|
undoOrRedo('undo');
|
||||||
}
|
}
|
||||||
|
|
||||||
function redo () {
|
function redo () {
|
||||||
console.log("redoing");
|
////console.log("redoing");
|
||||||
undoOrRedo('redo');
|
undoOrRedo('redo');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,13 +186,13 @@ class HistoryState {
|
|||||||
this.nFlattened = nFlattened;
|
this.nFlattened = nFlattened;
|
||||||
|
|
||||||
this.undo = function() {
|
this.undo = function() {
|
||||||
for (let i=0; i<this.nFlattened - nAppLayers; i++) {
|
for (let i=0; i<this.nFlattened; i++) {
|
||||||
undo();
|
undo();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.redo = function() {
|
this.redo = function() {
|
||||||
for (let i=0; i<this.nFlattened - nAppLayers; i++) {
|
for (let i=0; i<this.nFlattened; i++) {
|
||||||
redo();
|
redo();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -241,7 +241,9 @@ class HistoryState {
|
|||||||
|
|
||||||
this.undo = function() {
|
this.undo = function() {
|
||||||
addedLayer.selectLayer();
|
addedLayer.selectLayer();
|
||||||
LayerList.deleteLayer(false);
|
if (currFile.layers.length != 4) {//TODO: repent and rebirth lol
|
||||||
|
LayerList.deleteLayer(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.redo = function() {
|
this.redo = function() {
|
||||||
@ -268,7 +270,9 @@ class HistoryState {
|
|||||||
|
|
||||||
this.redo = function() {
|
this.redo = function() {
|
||||||
this.deleted.selectLayer();
|
this.deleted.selectLayer();
|
||||||
LayerList.deleteLayer(false);
|
if (currFile.layers.length != 4) {//TODO: repent and rebirth lol
|
||||||
|
LayerList.deleteLayer(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,7 +319,7 @@ class HistoryState {
|
|||||||
this.index = index;
|
this.index = index;
|
||||||
|
|
||||||
this.undo = function() {
|
this.undo = function() {
|
||||||
if (currFile.layers.length - nAppLayers > this.index + 1) {
|
if (currFile.layers.length > this.index + 1) {
|
||||||
currFile.layers[this.index + 1].selectLayer();
|
currFile.layers[this.index + 1].selectLayer();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -412,7 +416,7 @@ class HistoryState {
|
|||||||
//find new color in palette and change it back to old color
|
//find new color in palette and change it back to old color
|
||||||
let colors = document.getElementsByClassName('color-button');
|
let colors = document.getElementsByClassName('color-button');
|
||||||
for (let i = 0; i < colors.length; i++) {
|
for (let i = 0; i < colors.length; i++) {
|
||||||
//console.log(newColorValue, '==', colors[i].jscolor.toString());
|
//////console.log(newColorValue, '==', colors[i].jscolor.toString());
|
||||||
if (newColorValue == colors[i].jscolor.toString()) {
|
if (newColorValue == colors[i].jscolor.toString()) {
|
||||||
colors[i].jscolor.fromString(oldColorValue);
|
colors[i].jscolor.fromString(oldColorValue);
|
||||||
break;
|
break;
|
||||||
@ -429,7 +433,7 @@ class HistoryState {
|
|||||||
//find old color in palette and change it back to new color
|
//find old color in palette and change it back to new color
|
||||||
let colors = document.getElementsByClassName('color-button');
|
let colors = document.getElementsByClassName('color-button');
|
||||||
for (let i = 0; i < colors.length; i++) {
|
for (let i = 0; i < colors.length; i++) {
|
||||||
//console.log(oldColorValue, '==', colors[i].jscolor.toString());
|
//////console.log(oldColorValue, '==', colors[i].jscolor.toString());
|
||||||
if (oldColorValue == colors[i].jscolor.toString()) {
|
if (oldColorValue == colors[i].jscolor.toString()) {
|
||||||
colors[i].jscolor.fromString(newColorValue);
|
colors[i].jscolor.fromString(newColorValue);
|
||||||
break;
|
break;
|
||||||
|
@ -163,7 +163,7 @@ const Input = (() => {
|
|||||||
spacePressed = true;
|
spacePressed = true;
|
||||||
break;
|
break;
|
||||||
case 46:
|
case 46:
|
||||||
console.log("Pressed del");
|
////console.log("Pressed del");
|
||||||
Events.emit("del");
|
Events.emit("del");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
200
js/LayerList.js
@ -1,46 +1,36 @@
|
|||||||
const LayerList = (() => {
|
const LayerList = (() => {
|
||||||
|
|
||||||
let layerList = document.getElementById("layers-menu");
|
let layerList = document.getElementById("layers-menu");
|
||||||
let layerListEntry = layerList.firstElementChild;
|
// let layerListEntry = layerList.firstElementChild;
|
||||||
|
let layerListEntry = document.getElementById("default-layer-list-item");
|
||||||
let renamingLayer = false;
|
let renamingLayer = false;
|
||||||
let dragStartLayer;
|
let dragStartLayer;
|
||||||
|
|
||||||
// Binding the right click menu
|
|
||||||
Events.on("mousedown", layerList, openOptionsMenu);
|
Events.on("mousedown", layerList, openOptionsMenu);
|
||||||
// Binding the add layer button to the right function
|
|
||||||
Events.on('click',"add-layer-button", addLayer, false);
|
Events.on('click',"add-layer-button", addLayer, false);
|
||||||
// Listening to the switch mode event so I can change the layout
|
Events.onCustom("switchedToAdvanced", showLayerList);
|
||||||
Events.onCustom("switchedToAdvanced", showMenu);
|
Events.onCustom("switchedToBasic", hideLayerList);
|
||||||
Events.onCustom("switchedToBasic", hideMenu);
|
|
||||||
|
|
||||||
// Making the layers list sortable
|
|
||||||
new Sortable(layerList, {
|
new Sortable(layerList, {
|
||||||
animation: 100,
|
animation: 100,
|
||||||
filter: ".layer-button",
|
filter: ".layer-button",
|
||||||
draggable: ".layers-menu-entry",
|
draggable: ".layers-menu-entry",
|
||||||
onStart: layerDragStart,
|
onStart: layerDragStart,
|
||||||
onEnd: layerDragDrop
|
onEnd: layerDragEnd
|
||||||
});
|
});
|
||||||
|
function showLayerList() {
|
||||||
function showMenu() {
|
|
||||||
layerList.style.display = "inline-block";
|
layerList.style.display = "inline-block";
|
||||||
document.getElementById('layer-button').style.display = 'inline-block';
|
document.getElementById('layer-button').style.display = 'inline-block';
|
||||||
}
|
}
|
||||||
function hideMenu() {
|
function hideLayerList() {
|
||||||
if (EditorState.documentCreated()) {
|
if (EditorState.documentCreated()) {
|
||||||
// Selecting the current layer
|
|
||||||
currFile.currentLayer.selectLayer();
|
currFile.currentLayer.selectLayer();
|
||||||
// Flatten the layers
|
|
||||||
flatten(true);
|
flatten(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
layerList.style.display = "none";
|
layerList.style.display = "none";
|
||||||
document.getElementById('layer-button').style.display = 'none';
|
document.getElementById('layer-button').style.display = 'none';
|
||||||
}
|
}
|
||||||
|
function addLayer(id, saveHistory = true, layerName) {
|
||||||
|
|
||||||
function addLayer(id, saveHistory = true) {
|
let index = currFile.layers.length;
|
||||||
// layers.length - 3
|
|
||||||
let index = currFile.layers.length - 3;
|
|
||||||
// Creating a new canvas
|
// Creating a new canvas
|
||||||
let newCanvas = document.createElement("canvas");
|
let newCanvas = document.createElement("canvas");
|
||||||
// Setting up the new canvas
|
// Setting up the new canvas
|
||||||
@ -49,12 +39,16 @@ const LayerList = (() => {
|
|||||||
newCanvas.style.zIndex = Layer.maxZIndex;
|
newCanvas.style.zIndex = Layer.maxZIndex;
|
||||||
newCanvas.classList.add("drawingCanvas");
|
newCanvas.classList.add("drawingCanvas");
|
||||||
|
|
||||||
if (!layerListEntry) return console.warn('skipping adding layer because no document');
|
if (!layerListEntry) return //console.warn('skipping adding layer because no document');
|
||||||
|
|
||||||
// Clone the default layer
|
// Clone the default layer
|
||||||
let toAppend = layerListEntry.cloneNode(true);
|
let toAppend = layerListEntry.cloneNode(true);
|
||||||
|
toAppend.style.display = "flex";
|
||||||
|
//console.log('toAppend === ',toAppend);
|
||||||
// Setting the default name for the layer
|
// Setting the default name for the layer
|
||||||
toAppend.getElementsByTagName('p')[0].innerHTML = "Layer " + Layer.layerCount;
|
const _layerName = layerName ?? "Layer " + currFile.layers.length;
|
||||||
|
//console.log('_layerName === ',_layerName);
|
||||||
|
toAppend.getElementsByTagName('p')[0].innerHTML = _layerName;
|
||||||
// Removing the selected class
|
// Removing the selected class
|
||||||
toAppend.classList.remove("selected-layer");
|
toAppend.classList.remove("selected-layer");
|
||||||
// Adding the layer to the list
|
// Adding the layer to the list
|
||||||
@ -65,10 +59,11 @@ const LayerList = (() => {
|
|||||||
newLayer.context.fillStyle = currFile.currentLayer.context.fillStyle;
|
newLayer.context.fillStyle = currFile.currentLayer.context.fillStyle;
|
||||||
newLayer.copyData(currFile.currentLayer);
|
newLayer.copyData(currFile.currentLayer);
|
||||||
|
|
||||||
currFile.layers.splice(index, 0, newLayer);
|
// currFile.layers.splice(index, 0, newLayer);
|
||||||
|
currFile.layers.push(newLayer);
|
||||||
|
|
||||||
// Insert it before the Add layer button
|
// Insert it before the Add layer button
|
||||||
layerList.insertBefore(toAppend, layerList.childNodes[0]);
|
layerList.insertBefore(toAppend, document.getElementById("add-layer-li"));
|
||||||
|
|
||||||
if (id != null && typeof(id) == "string") {
|
if (id != null && typeof(id) == "string") {
|
||||||
newLayer.setID(id);
|
newLayer.setID(id);
|
||||||
@ -76,11 +71,11 @@ const LayerList = (() => {
|
|||||||
// Basically "if I'm not adding a layer because redo() is telling meto do so", then I can save the history
|
// Basically "if I'm not adding a layer because redo() is telling meto do so", then I can save the history
|
||||||
if (saveHistory) {
|
if (saveHistory) {
|
||||||
new HistoryState().AddLayer(newLayer, index);
|
new HistoryState().AddLayer(newLayer, index);
|
||||||
|
FileManager.localStorageSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
return newLayer;
|
return newLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Merges topLayer onto belowLayer
|
/** Merges topLayer onto belowLayer
|
||||||
*
|
*
|
||||||
* @param {*} belowLayer The layer on the bottom of the layer stack
|
* @param {*} belowLayer The layer on the bottom of the layer stack
|
||||||
@ -97,7 +92,7 @@ const LayerList = (() => {
|
|||||||
toMergeImageData.data[i+2], toMergeImageData.data[i+3]
|
toMergeImageData.data[i+2], toMergeImageData.data[i+3]
|
||||||
];
|
];
|
||||||
|
|
||||||
let currentUnderlyingPixel = [
|
let currentUnderlyingPixel = [ //TODO: I'd be curious to know if this array slows this function down
|
||||||
belowImageData.data[i], belowImageData.data[i+1],
|
belowImageData.data[i], belowImageData.data[i+1],
|
||||||
belowImageData.data[i+2], belowImageData.data[i+3]
|
belowImageData.data[i+2], belowImageData.data[i+3]
|
||||||
];
|
];
|
||||||
@ -115,34 +110,54 @@ const LayerList = (() => {
|
|||||||
// Putting the top data into the belowdata
|
// Putting the top data into the belowdata
|
||||||
belowLayer.putImageData(toMergeImageData, 0, 0);
|
belowLayer.putImageData(toMergeImageData, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the z indexes of the layers when the user drops the layer in the menu
|
/** Sets the z indexes of the layers when the user drops the layer in the menu
|
||||||
*
|
*
|
||||||
* @param {*} event
|
* @param {*} event
|
||||||
*/
|
*/
|
||||||
function layerDragDrop(event) {
|
function layerDragEnd(event) {
|
||||||
let oldIndex = event.oldDraggableIndex;
|
// let oldIndex = event.oldDraggableIndex;
|
||||||
let newIndex = event.newDraggableIndex;
|
// let newIndex = event.newDraggableIndex;
|
||||||
|
|
||||||
let movedZIndex = dragStartLayer.canvas.style.zIndex;
|
// let movedZIndex = dragStartLayer.canvas.style.zIndex;
|
||||||
|
|
||||||
if (oldIndex > newIndex)
|
// if (oldIndex > newIndex)
|
||||||
{
|
// {
|
||||||
for (let i=newIndex; i<oldIndex; i++) {
|
// for (let i=newIndex; i<oldIndex; i++) {
|
||||||
getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i + 1].id).canvas.style.zIndex;
|
// getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i + 1].id).canvas.style.zIndex;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
else
|
// else
|
||||||
{
|
// {
|
||||||
for (let i=newIndex; i>oldIndex; i--) {
|
// for (let i=newIndex; i>oldIndex; i--) {
|
||||||
getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i - 1].id).canvas.style.zIndex;
|
// getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i - 1].id).canvas.style.zIndex;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
getLayerByID(layerList.children[oldIndex].id).canvas.style.zIndex = movedZIndex;
|
// getLayerByID(layerList.children[oldIndex].id).canvas.style.zIndex = movedZIndex;
|
||||||
Events.simulateMouseEvent(window, "mouseup");
|
Events.simulateMouseEvent(window, "mouseup");
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
const tempLayerCache = currFile.layers.reduce((r,n,i) => {
|
||||||
|
r[n.id] = n;
|
||||||
|
return r;
|
||||||
|
},{});
|
||||||
|
|
||||||
|
let selectedId;
|
||||||
|
const idArr = [...document.querySelectorAll(".layers-menu-entry")].map(elm => {
|
||||||
|
if([...elm.classList].includes("selected-layer")) {
|
||||||
|
selectedId = elm.id;
|
||||||
|
}
|
||||||
|
return elm.id;
|
||||||
|
});
|
||||||
|
let selectedIdx = idArr.indexOf(selectedId);
|
||||||
|
idArr.forEach((id,i)=>{
|
||||||
|
currFile.layers[i] = tempLayerCache[id];
|
||||||
|
currFile.layers[i].isSelected = i===selectedIdx;
|
||||||
|
});
|
||||||
|
|
||||||
|
FileManager.localStorageSave();
|
||||||
|
|
||||||
|
}
|
||||||
/** Saves the layer that is being moved when the dragging starts
|
/** Saves the layer that is being moved when the dragging starts
|
||||||
*
|
*
|
||||||
* @param {*} event
|
* @param {*} event
|
||||||
@ -150,21 +165,27 @@ const LayerList = (() => {
|
|||||||
function layerDragStart(event) {
|
function layerDragStart(event) {
|
||||||
dragStartLayer = getLayerByID(layerList.children[event.oldIndex].id);
|
dragStartLayer = getLayerByID(layerList.children[event.oldIndex].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds a layer given its id
|
|
||||||
function getLayerByID(id) {
|
function getLayerByID(id) {
|
||||||
|
//console.log(`getLayerByID(${id})`);
|
||||||
|
// for (let i=0; i<currFile.layers.length; i++) {
|
||||||
|
// if (currFile.layers[i].hasCanvas()) {
|
||||||
|
// if (currFile.layers[i].menuEntry.id == id) {
|
||||||
|
// return currFile.layers[i];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
let ret;
|
||||||
for (let i=0; i<currFile.layers.length; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
if (currFile.layers[i].hasCanvas()) {
|
if (currFile.layers[i].hasCanvas()) {
|
||||||
if (currFile.layers[i].menuEntry.id == id) {
|
if (currFile.layers[i].menuEntry.id == id) {
|
||||||
return currFile.layers[i];
|
ret = currFile.layers[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//console.log('ret === ',ret);
|
||||||
|
|
||||||
return null;
|
return ret ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds a layer given its name
|
|
||||||
function getLayerByName(name) {
|
function getLayerByName(name) {
|
||||||
for (let i=0; i<currFile.layers.length; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
if (currFile.layers[i].hasCanvas()) {
|
if (currFile.layers[i].hasCanvas()) {
|
||||||
@ -176,7 +197,6 @@ const LayerList = (() => {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function startRenamingLayer(event) {
|
function startRenamingLayer(event) {
|
||||||
let p = currFile.currentLayer.menuEntry.getElementsByTagName("p")[0];
|
let p = currFile.currentLayer.menuEntry.getElementsByTagName("p")[0];
|
||||||
|
|
||||||
@ -189,7 +209,6 @@ const LayerList = (() => {
|
|||||||
|
|
||||||
renamingLayer = true;
|
renamingLayer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function duplicateLayer(event, saveHistory = true) {
|
function duplicateLayer(event, saveHistory = true) {
|
||||||
function getMenuEntryIndex(list, entry) {
|
function getMenuEntryIndex(list, entry) {
|
||||||
for (let i=0; i<list.length; i++) {
|
for (let i=0; i<list.length; i++) {
|
||||||
@ -218,7 +237,7 @@ const LayerList = (() => {
|
|||||||
newCanvas.style.zIndex = parseInt(currFile.currentLayer.canvas.style.zIndex) + 2;
|
newCanvas.style.zIndex = parseInt(currFile.currentLayer.canvas.style.zIndex) + 2;
|
||||||
newCanvas.classList.add("drawingCanvas");
|
newCanvas.classList.add("drawingCanvas");
|
||||||
|
|
||||||
if (!layerListEntry) return console.warn('skipping adding layer because no document');
|
if (!layerListEntry) return //console.warn('skipping adding layer because no document');
|
||||||
|
|
||||||
// Clone the default layer
|
// Clone the default layer
|
||||||
let toAppend = currFile.currentLayer.menuEntry.cloneNode(true);
|
let toAppend = currFile.currentLayer.menuEntry.cloneNode(true);
|
||||||
@ -248,41 +267,52 @@ const LayerList = (() => {
|
|||||||
new HistoryState().DuplicateLayer(newLayer, currFile.currentLayer);
|
new HistoryState().DuplicateLayer(newLayer, currFile.currentLayer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function clearLayers() {
|
||||||
|
//console.log('currFile.layers === ',currFile.layers);
|
||||||
|
|
||||||
function deleteLayer(saveHistory = true) {
|
currFile.layers.forEach(()=>deleteLayer());
|
||||||
// Cannot delete all the layers
|
//console.log('currFile.layers.length === ',currFile.layers.length);
|
||||||
if (currFile.layers.length != 4) {
|
for(let i = 0; i < currFile.layers.length;i++){
|
||||||
let layerIndex = currFile.layers.indexOf(currFile.currentLayer);
|
const layer = currFile.layers[i];
|
||||||
let toDelete = currFile.layers[layerIndex];
|
//console.log('i === ', i);
|
||||||
let previousSibling = toDelete.menuEntry.previousElementSibling;
|
//console.log('layer === ',layer);
|
||||||
// Adding the ids to the unused ones
|
|
||||||
Layer.unusedIDs.push(toDelete.id);
|
|
||||||
|
|
||||||
// Selecting the next layer
|
|
||||||
if (layerIndex != (currFile.layers.length - 4)) {
|
|
||||||
currFile.layers[layerIndex + 1].selectLayer();
|
|
||||||
}
|
|
||||||
// or the previous one if the next one doesn't exist
|
|
||||||
else {
|
|
||||||
currFile.layers[layerIndex - 1].selectLayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deleting canvas and entry
|
|
||||||
toDelete.canvas.remove();
|
|
||||||
toDelete.menuEntry.remove();
|
|
||||||
|
|
||||||
// Removing the layer from the list
|
|
||||||
currFile.layers.splice(layerIndex, 1);
|
|
||||||
|
|
||||||
if (saveHistory) {
|
|
||||||
new HistoryState().DeleteLayer(toDelete, previousSibling, layerIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
function deleteLayer(saveHistory = true) {
|
||||||
|
//console.log('deleting layer: ', currFile.currentLayer.name, currFile.currentLayer);
|
||||||
|
//console.trace();
|
||||||
|
deleteLayerDirectly(currFile.currentLayer, saveHistory);
|
||||||
|
|
||||||
// Closing the menu
|
// Closing the menu
|
||||||
closeOptionsMenu();
|
closeOptionsMenu();
|
||||||
}
|
}
|
||||||
|
function deleteLayerDirectly(layer, saveHistory = true) {
|
||||||
|
let layerIndex = currFile.layers.indexOf(layer);
|
||||||
|
let toDelete = currFile.layers[layerIndex];
|
||||||
|
let previousSibling = toDelete.menuEntry.previousElementSibling;
|
||||||
|
// Adding the ids to the unused ones
|
||||||
|
Layer.unusedIDs.push(toDelete.id);
|
||||||
|
|
||||||
|
if(layer.isSelected) {
|
||||||
|
// Selecting the nearest layer
|
||||||
|
const nearestLayer = (currFile.layers[layerIndex + 1] ?? currFile.layers[layerIndex - 1]);
|
||||||
|
if(nearestLayer){
|
||||||
|
nearestLayer.selectLayer();
|
||||||
|
//console.log('changing to nearest layer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleting canvas and entry
|
||||||
|
toDelete.canvas.remove();
|
||||||
|
toDelete.menuEntry.remove();
|
||||||
|
|
||||||
|
// Removing the layer from the list
|
||||||
|
currFile.layers.splice(layerIndex, 1);
|
||||||
|
|
||||||
|
if (saveHistory) {
|
||||||
|
new HistoryState().DeleteLayer(toDelete, previousSibling, layerIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
function merge(saveHistory = true) {
|
function merge(saveHistory = true) {
|
||||||
// Saving the layer that should be merged
|
// Saving the layer that should be merged
|
||||||
let toMerge = currFile.currentLayer;
|
let toMerge = currFile.currentLayer;
|
||||||
@ -312,7 +342,6 @@ const LayerList = (() => {
|
|||||||
currFile.currentLayer.updateLayerPreview();
|
currFile.currentLayer.updateLayerPreview();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function flatten(onlyVisible) {
|
function flatten(onlyVisible) {
|
||||||
if (!onlyVisible) {
|
if (!onlyVisible) {
|
||||||
// Selecting the first layer
|
// Selecting the first layer
|
||||||
@ -366,7 +395,6 @@ const LayerList = (() => {
|
|||||||
currFile.currentLayer.updateLayerPreview();
|
currFile.currentLayer.updateLayerPreview();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openOptionsMenu(event) {
|
function openOptionsMenu(event) {
|
||||||
if (event.which == 3) {
|
if (event.which == 3) {
|
||||||
let selectedId;
|
let selectedId;
|
||||||
@ -385,21 +413,17 @@ const LayerList = (() => {
|
|||||||
getLayerByID(selectedId).selectLayer(false);
|
getLayerByID(selectedId).selectLayer(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeOptionsMenu(event) {
|
function closeOptionsMenu(event) {
|
||||||
Layer.layerOptions.style.visibility = "hidden";
|
Layer.layerOptions.style.visibility = "hidden";
|
||||||
currFile.currentLayer.rename();
|
currFile.currentLayer.rename();
|
||||||
renamingLayer = false;
|
renamingLayer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLayerListEntries() {
|
function getLayerListEntries() {
|
||||||
return layerList;
|
return layerList;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRenamingLayer() {
|
function isRenamingLayer() {
|
||||||
return renamingLayer;
|
return renamingLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addLayer,
|
addLayer,
|
||||||
mergeLayers,
|
mergeLayers,
|
||||||
@ -407,7 +431,9 @@ const LayerList = (() => {
|
|||||||
getLayerByName,
|
getLayerByName,
|
||||||
renameLayer: startRenamingLayer,
|
renameLayer: startRenamingLayer,
|
||||||
duplicateLayer,
|
duplicateLayer,
|
||||||
|
clearLayers,
|
||||||
deleteLayer,
|
deleteLayer,
|
||||||
|
deleteLayerDirectly,
|
||||||
merge,
|
merge,
|
||||||
flatten,
|
flatten,
|
||||||
closeOptionsMenu,
|
closeOptionsMenu,
|
||||||
|
@ -6,7 +6,7 @@ const PresetModule = (() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function instrumentPresetMenu() {
|
function instrumentPresetMenu() {
|
||||||
console.info("Initializing presets..");
|
//console.info("Initializing presets..");
|
||||||
// Add a button for all the presets available
|
// Add a button for all the presets available
|
||||||
const presetsMenu = document.getElementById('preset-menu');
|
const presetsMenu = document.getElementById('preset-menu');
|
||||||
Object.keys(presets).forEach((presetName,) => {
|
Object.keys(presets).forEach((presetName,) => {
|
||||||
@ -17,7 +17,7 @@ const PresetModule = (() => {
|
|||||||
presetsMenu.appendChild(button);
|
presetsMenu.appendChild(button);
|
||||||
|
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
console.log("Preset: " + presetName);
|
////console.log("Preset: " + presetName);
|
||||||
//change dimentions on new pixel form
|
//change dimentions on new pixel form
|
||||||
Util.setValue('size-width', presets[presetName].width);
|
Util.setValue('size-width', presets[presetName].width);
|
||||||
Util.setValue('size-height', presets[presetName].height);
|
Util.setValue('size-height', presets[presetName].height);
|
||||||
|
@ -14,7 +14,7 @@ const Settings = (() => {
|
|||||||
settingsFromCookie = Cookies.get('pixelEditorSettings');
|
settingsFromCookie = Cookies.get('pixelEditorSettings');
|
||||||
|
|
||||||
if(!settingsFromCookie) {
|
if(!settingsFromCookie) {
|
||||||
console.log('settings cookie not found');
|
////console.log('settings cookie not found');
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
switchToChangedColor: true,
|
switchToChangedColor: true,
|
||||||
@ -27,8 +27,8 @@ const Settings = (() => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log('settings cookie found');
|
////console.log('settings cookie found');
|
||||||
console.log(settingsFromCookie);
|
////console.log(settingsFromCookie);
|
||||||
|
|
||||||
settings = JSON.parse(settingsFromCookie);
|
settings = JSON.parse(settingsFromCookie);
|
||||||
}
|
}
|
||||||
|
169
js/Startup.js
@ -1,6 +1,8 @@
|
|||||||
const Startup = (() => {
|
const Startup = (() => {
|
||||||
let splashPostfix = '';
|
let splashPostfix = '';
|
||||||
|
|
||||||
|
let cacheIntervalIdx;
|
||||||
|
|
||||||
Events.on('click', 'create-button', create, false);
|
Events.on('click', 'create-button', create, false);
|
||||||
Events.on('click', 'create-button-splash', create, true);
|
Events.on('click', 'create-button-splash', create, true);
|
||||||
|
|
||||||
@ -15,7 +17,10 @@ const Startup = (() => {
|
|||||||
var height = Util.getValue('size-height' + splashPostfix);
|
var height = Util.getValue('size-height' + splashPostfix);
|
||||||
var selectedPalette = Util.getText('palette-button' + splashPostfix);
|
var selectedPalette = Util.getText('palette-button' + splashPostfix);
|
||||||
|
|
||||||
newPixel(width, height);
|
newPixel({
|
||||||
|
canvasWidth: width,
|
||||||
|
canvasHeight: height,
|
||||||
|
});
|
||||||
resetInput();
|
resetInput();
|
||||||
|
|
||||||
//track google event
|
//track google event
|
||||||
@ -25,15 +30,16 @@ const Startup = (() => {
|
|||||||
|
|
||||||
/** Creates a new, empty file
|
/** Creates a new, empty file
|
||||||
*
|
*
|
||||||
* @param {*} width Start width of the canvas
|
|
||||||
* @param {*} height Start height of the canvas
|
|
||||||
* @param {*} fileContent If fileContent != null, then the newPixel is being called from the open menu
|
* @param {*} fileContent If fileContent != null, then the newPixel is being called from the open menu
|
||||||
|
* @param {*} skipModeConfirm If skipModeConfirm == true, then the mode switching confirmation will be skipped
|
||||||
*/
|
*/
|
||||||
function newPixel (width, height, fileContent = null) {
|
function newPixel (fileContent = null, skipModeConfirm = false) {
|
||||||
|
//console.log('called newPixel');
|
||||||
|
//console.trace();
|
||||||
// The palette is empty, at the beginning
|
// The palette is empty, at the beginning
|
||||||
ColorModule.resetPalette();
|
ColorModule.resetPalette();
|
||||||
|
|
||||||
initLayers(width, height);
|
initLayers(fileContent);
|
||||||
initPalette();
|
initPalette();
|
||||||
|
|
||||||
// Closing the "New Pixel dialogue"
|
// Closing the "New Pixel dialogue"
|
||||||
@ -46,64 +52,91 @@ const Startup = (() => {
|
|||||||
|
|
||||||
// Now, if I opened an LPE file
|
// Now, if I opened an LPE file
|
||||||
if (fileContent != null) {
|
if (fileContent != null) {
|
||||||
loadFromLPE(fileContent);
|
FileManager.loadFromLPE(fileContent);
|
||||||
// Deleting the default layer
|
|
||||||
LayerList.deleteLayer(false);
|
|
||||||
// Selecting the new one
|
|
||||||
currFile.layers[1].selectLayer();
|
|
||||||
}
|
}
|
||||||
|
////console.log('ColorModule.getCurrentPalette() === ',ColorModule.getCurrentPalette());
|
||||||
|
|
||||||
EditorState.switchMode(EditorState.getCurrentMode());
|
EditorState.switchMode(EditorState.getCurrentMode(), skipModeConfirm);
|
||||||
// This is not the first Pixel anymore
|
// This is not the first Pixel anymore
|
||||||
EditorState.created();
|
EditorState.created();
|
||||||
}
|
|
||||||
|
|
||||||
function initLayers(width, height) {
|
////console.log('ColorModule.getCurrentPalette() === ',ColorModule.getCurrentPalette());
|
||||||
// Setting the general canvasSize
|
////console.trace();
|
||||||
|
}
|
||||||
|
function clearLayers() {
|
||||||
|
for(let i = 0; i < currFile.layers.length;i++) {
|
||||||
|
currFile.layers[i].delete(i);
|
||||||
|
}
|
||||||
|
for(let i = 0; i < currFile.sublayers.length;i++) {
|
||||||
|
currFile.sublayers[i].delete(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function initLayers(lpe) {
|
||||||
|
//console.group('called initLayers');
|
||||||
|
//console.log('currFile.layers === ',currFile.layers);
|
||||||
|
|
||||||
|
const width = lpe.canvasWidth;
|
||||||
|
const height = lpe.canvasHeight;
|
||||||
|
clearLayers();
|
||||||
|
|
||||||
|
// debugger;
|
||||||
|
//
|
||||||
currFile.canvasSize = [width, height];
|
currFile.canvasSize = [width, height];
|
||||||
|
|
||||||
// If this is the first pixel I'm creating since the app has started
|
if( lpe.layers && lpe.layers.length ) {
|
||||||
if (EditorState.firstPixel()) {
|
currFile.currentLayer = new Layer(width, height, `pixel-canvas`,"","layer-li-template");
|
||||||
// Creating the first layer
|
|
||||||
currFile.currentLayer = new Layer(width, height, 'pixel-canvas', "");
|
|
||||||
currFile.currentLayer.canvas.style.zIndex = 2;
|
currFile.currentLayer.canvas.style.zIndex = 2;
|
||||||
}
|
currFile.sublayers.push(currFile.currentLayer);
|
||||||
else {
|
|
||||||
// Deleting all the extra layers and canvases, leaving only one
|
|
||||||
let nLayers = currFile.layers.length;
|
|
||||||
for (let i=2; i < currFile.layers.length - nAppLayers; i++) {
|
|
||||||
let currentEntry = currFile.layers[i].menuEntry;
|
|
||||||
let associatedLayer;
|
|
||||||
|
|
||||||
if (currentEntry != null) {
|
let selectedIdx = lpe.selectedLayer ?? 0;
|
||||||
// Getting the associated layer
|
|
||||||
associatedLayer = LayerList.getLayerByID(currentEntry.id);
|
|
||||||
|
|
||||||
// Deleting its canvas
|
lpe.layers.forEach((layerData, i) => {
|
||||||
associatedLayer.canvas.remove();
|
//console.log('lpe.layers[i] === ', i);
|
||||||
|
let layerImage = layerData.src;
|
||||||
|
if (layerData != null) {
|
||||||
|
// Setting id
|
||||||
|
let createdLayer = LayerList.addLayer(layerData.id, false, layerData.name);
|
||||||
|
if(i===selectedIdx)createdLayer.selectLayer();
|
||||||
|
// Setting name
|
||||||
|
createdLayer.menuEntry.getElementsByTagName("p")[0].innerHTML = layerData.name;
|
||||||
|
|
||||||
// Adding the id to the unused ones
|
// Adding the image (I can do that because they're sorted by increasing z-index)
|
||||||
Layer.unusedIDs.push(currentEntry.id);
|
let img = new Image();
|
||||||
// Removing the entry from the menu
|
img.onload = function() {
|
||||||
currentEntry.remove();
|
createdLayer.context.drawImage(img, 0, 0);
|
||||||
|
createdLayer.updateLayerPreview();
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = layerImage;
|
||||||
|
|
||||||
|
// Setting visibility and lock options
|
||||||
|
if (!layerData.isVisible) {
|
||||||
|
createdLayer.hide();
|
||||||
|
}
|
||||||
|
if (layerData.isLocked) {
|
||||||
|
createdLayer.lock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
// Removing the old layers from the list
|
} else {
|
||||||
for (let i=2; i<nLayers - nAppLayers; i++) {
|
currFile.currentLayer = new Layer(width, height, `pixel-canvas`,"");
|
||||||
currFile.layers.splice(2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setting up the current layer
|
|
||||||
currFile.layers[1] = new Layer(width, height, currFile.layers[1].canvas, currFile.layers[1].menuEntry);
|
|
||||||
currFile.currentLayer = currFile.layers[1];
|
|
||||||
currFile.currentLayer.canvas.style.zIndex = 2;
|
currFile.currentLayer.canvas.style.zIndex = 2;
|
||||||
|
currFile.sublayers.push(currFile.currentLayer);
|
||||||
|
|
||||||
|
const defaultLayerId = "layer0";
|
||||||
|
const defaultLayerName = "Layer 0";
|
||||||
|
|
||||||
|
let createdLayer = LayerList.addLayer(defaultLayerId, false, defaultLayerName);
|
||||||
|
createdLayer.selectLayer();
|
||||||
|
// Setting name
|
||||||
|
createdLayer.menuEntry.getElementsByTagName("p")[0].innerHTML = defaultLayerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding the checkerboard behind it
|
// Adding the checkerboard behind it
|
||||||
currFile.checkerBoard = new Checkerboard(width, height, null);
|
currFile.checkerBoard = new Checkerboard(width, height, null);
|
||||||
// Pixel grid
|
// Pixel grid
|
||||||
console.log("CREATED GRID");
|
////console.log("CREATED GRID");
|
||||||
currFile.pixelGrid = new PixelGrid(width, height, "pixel-grid");
|
currFile.pixelGrid = new PixelGrid(width, height, "pixel-grid");
|
||||||
|
|
||||||
// Creating the vfx layer on top of everything
|
// Creating the vfx layer on top of everything
|
||||||
@ -111,14 +144,10 @@ const Startup = (() => {
|
|||||||
// Tmp layer to draw previews on
|
// Tmp layer to draw previews on
|
||||||
currFile.TMPLayer = new Layer(width, height, 'tmp-canvas');
|
currFile.TMPLayer = new Layer(width, height, 'tmp-canvas');
|
||||||
|
|
||||||
if (EditorState.firstPixel()) {
|
currFile.sublayers.push(currFile.checkerBoard);
|
||||||
// Adding the first layer and the checkerboard to the list of layers
|
currFile.sublayers.push(currFile.TMPLayer);
|
||||||
currFile.layers.push(currFile.checkerBoard);
|
currFile.sublayers.push(currFile.pixelGrid);
|
||||||
currFile.layers.push(currFile.currentLayer);
|
currFile.sublayers.push(currFile.VFXLayer);
|
||||||
currFile.layers.push(currFile.TMPLayer);
|
|
||||||
currFile.layers.push(currFile.pixelGrid);
|
|
||||||
currFile.layers.push(currFile.VFXLayer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initPalette() {
|
function initPalette() {
|
||||||
@ -168,42 +197,6 @@ const Startup = (() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFromLPE(fileContent) {
|
|
||||||
// I add every layer the file had in it
|
|
||||||
for (let i=0; i<fileContent['nLayers']; i++) {
|
|
||||||
let layerData = fileContent['layer' + i];
|
|
||||||
let layerImage = fileContent['layer' + i + 'ImageData'];
|
|
||||||
|
|
||||||
if (layerData != null) {
|
|
||||||
// Setting id
|
|
||||||
let createdLayer = LayerList.addLayer(layerData.id, false);
|
|
||||||
// Setting name
|
|
||||||
createdLayer.menuEntry.getElementsByTagName("p")[0].innerHTML = layerData.name;
|
|
||||||
|
|
||||||
// Adding the image (I can do that because they're sorted by increasing z-index)
|
|
||||||
let img = new Image();
|
|
||||||
img.onload = function() {
|
|
||||||
createdLayer.context.drawImage(img, 0, 0);
|
|
||||||
createdLayer.updateLayerPreview();
|
|
||||||
|
|
||||||
if (i == (fileContent['nLayers'] - 1)) {
|
|
||||||
ColorModule.createPaletteFromLayers();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
img.src = layerImage;
|
|
||||||
|
|
||||||
// Setting visibility and lock options
|
|
||||||
if (!layerData.isVisible) {
|
|
||||||
createdLayer.hide();
|
|
||||||
}
|
|
||||||
if (layerData.isLocked) {
|
|
||||||
createdLayer.lock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetInput() {
|
function resetInput() {
|
||||||
//reset new form
|
//reset new form
|
||||||
Util.setValue('size-width', 64);
|
Util.setValue('size-width', 64);
|
||||||
|
@ -62,12 +62,10 @@ class Tool {
|
|||||||
else {
|
else {
|
||||||
this.toolTutorial.style.top = this.mainButton.getBoundingClientRect().top - 48 + "px";
|
this.toolTutorial.style.top = this.mainButton.getBoundingClientRect().top - 48 + "px";
|
||||||
}
|
}
|
||||||
this.toolTutorial.style.display = "inline-block";
|
|
||||||
this.toolTutorial.className = "fade-in";
|
this.toolTutorial.className = "fade-in";
|
||||||
}
|
}
|
||||||
hideTutorial() {
|
hideTutorial() {
|
||||||
this.toolTutorial.className = "fade-out";
|
this.toolTutorial.className = "fade-out";
|
||||||
setTimeout(function(){this.toolTutorial.style.display = "none"}.bind(this), 200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resetTutorial() {
|
resetTutorial() {
|
||||||
@ -160,9 +158,10 @@ class Tool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDrag(mousePos, mouseTarget) {
|
onDrag(mousePos, mouseTarget) {
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnd(mousePos, mouseTarget) {
|
onEnd(mousePos, mouseTarget) {
|
||||||
this.endMousePos = mousePos;
|
this.endMousePos = mousePos;
|
||||||
|
FileManager.localStorageSave();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -109,7 +109,7 @@ const ToolManager = (() => {
|
|||||||
currTool.onRightDrag(mousePos, mouseEvent.target);
|
currTool.onRightDrag(mousePos, mouseEvent.target);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log("wtf");
|
////console.log("wtf");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const TopMenuModule = (() => {
|
const TopMenuModule = (() => {
|
||||||
|
|
||||||
const mainMenuItems = document.getElementById('main-menu').children;
|
const mainMenuItems = document.getElementById('main-menu')?.children ?? [];
|
||||||
let infoList = document.getElementById('editor-info');
|
let infoList = document.getElementById('editor-info') ?? document.createElement("div");
|
||||||
let infoElements = {};
|
let infoElements = {};
|
||||||
|
|
||||||
initMenu();
|
initMenu();
|
||||||
@ -113,7 +113,12 @@ const TopMenuModule = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateField(fieldId, value) {
|
function updateField(fieldId, value) {
|
||||||
document.getElementById(fieldId).value = value;
|
const elm = document.getElementById(fieldId);
|
||||||
|
if(elm) {
|
||||||
|
elm.value = value;
|
||||||
|
} else {
|
||||||
|
//console.warn('elm === ', elm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addInfoElement(fieldId, field) {
|
function addInfoElement(fieldId, field) {
|
||||||
|
@ -83,7 +83,7 @@ class Util {
|
|||||||
return document.getElementById(elementOrElementId);
|
return document.getElementById(elementOrElementId);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log("Type not supported: " + typeof(elementOrElementId));
|
////console.log("Type not supported: " + typeof(elementOrElementId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
211
js/canvas_util.js
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
function drawTinyNumber(ctx,str,x,y) {
|
||||||
|
const CHARS = [
|
||||||
|
`0111110111011111`,
|
||||||
|
`0110111001101111`,
|
||||||
|
`1111001111001111`,
|
||||||
|
`1111011100111111`,
|
||||||
|
`1001100111110011`,
|
||||||
|
`1111110000111111`,
|
||||||
|
`1100111110111111`,
|
||||||
|
`1111001101100110`,
|
||||||
|
`1110101111010111`,
|
||||||
|
`1111101111110011`,
|
||||||
|
]
|
||||||
|
.map(n=>n.split("").map(Number))
|
||||||
|
;
|
||||||
|
str.split("").reduce((xo,n)=>{
|
||||||
|
let bitArr = CHARS[n];
|
||||||
|
let cw = bitArr.length / 4;
|
||||||
|
bitArr.forEach((bit,i)=>{
|
||||||
|
const _x = x + xo + (i%cw);
|
||||||
|
const _y = y + Math.floor(i/cw);
|
||||||
|
if(bit)ctx.fillRect(_x,_y,1,1);
|
||||||
|
});
|
||||||
|
xo+=cw+1;
|
||||||
|
return xo;
|
||||||
|
},0);
|
||||||
|
}
|
||||||
|
function drawTinyText( ctx, str, x = 0, y = 0, font = "Verdana", w = 16, h = 16, xo = 0, yo = 0 ) {
|
||||||
|
for(let i = 0; i < 4;i++){
|
||||||
|
drawTinyTextOne( ctx, str, x, y, font, w, h, xo+0, yo+i );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function drawTinyTextOne( ctx, str, x = 0, y = 0, font = "Verdana", w = 16, h = 16, xo = 0, yo = 0 ) {
|
||||||
|
const CHARS = generateCharsFromFont(font, w, h, 8, 8, undefined, undefined, xo, yo)
|
||||||
|
.map(n=>n.split("").map(Number))
|
||||||
|
;
|
||||||
|
////console.log('CHARS === ',CHARS);
|
||||||
|
str.split("").reduce((_xo,n)=>{
|
||||||
|
const code = n.charCodeAt(0) - 33;
|
||||||
|
// ////console.log('n,code === ',n,code);
|
||||||
|
let charWidth = CHARS[code].length / w;
|
||||||
|
CHARS[code].forEach((bit,i)=>{
|
||||||
|
const _x = x + _xo + (i%charWidth);
|
||||||
|
const _y = y + Math.floor(i/charWidth);
|
||||||
|
|
||||||
|
// ////console.log('bit === ',bit);
|
||||||
|
if(bit)ctx.fillRect(_x,_y,1,1);
|
||||||
|
});
|
||||||
|
_xo+=charWidth+1;
|
||||||
|
return _xo;
|
||||||
|
},0);
|
||||||
|
}
|
||||||
|
function generateCharsFromFont(font, charW = 7, charH = 7, sampleScale = 8, scale = 8, previewDiv, debugDiv, xo = 0, yo = 0) {
|
||||||
|
return [...Array(94)].map((_,i)=>{
|
||||||
|
const char = String.fromCharCode(i+33);
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
if(debugDiv)debugDiv.appendChild(canvas);
|
||||||
|
const sz = sampleScale;
|
||||||
|
const w = charW * sz;
|
||||||
|
const h = charH * sz;
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.font = `${h}px ` + font;
|
||||||
|
ctx.shadowColor="black";
|
||||||
|
ctx.shadowBlur=sz*2;
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.textBaseline = "bottom";
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.fillText(char,w/2,h);
|
||||||
|
ctx.strokeStyle = "black";
|
||||||
|
ctx.strokeText(char,w/2,h);
|
||||||
|
const imageData = ctx.getImageData(0,0,w,h);
|
||||||
|
// ////console.log('imageData === ',imageData);
|
||||||
|
let ret = '';
|
||||||
|
ctx.fillStyle = "red";
|
||||||
|
const previewCanvas = document.createElement('canvas');
|
||||||
|
previewCanvas.width = charW;
|
||||||
|
previewCanvas.height = charH;
|
||||||
|
const ctx2 = previewCanvas.getContext('2d');
|
||||||
|
if(previewDiv)previewDiv.appendChild(previewCanvas);
|
||||||
|
|
||||||
|
for(let y = scale/2; y < h;y+=scale) {
|
||||||
|
for(let x = scale/2; x < w;x+=scale) {
|
||||||
|
const _x = (x-(scale/2))/scale;
|
||||||
|
const _y = (y-(scale/2))/scale;
|
||||||
|
const _imageData = ctx.getImageData(x+xo,y+yo,1,1);
|
||||||
|
let specResult = _imageData.data[3] > 128;
|
||||||
|
ctx2.fillStyle = "black";
|
||||||
|
if(specResult) {
|
||||||
|
ctx2.fillRect(_x,_y,1,1);
|
||||||
|
ret += "1";
|
||||||
|
} else {
|
||||||
|
ret += "0";
|
||||||
|
}
|
||||||
|
ctx.fillStyle = specResult ? "#00ff00" : "#ff0000";
|
||||||
|
ctx.fillRect(x,y,1,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function pixelButtonMeta(x, y, img, options) {
|
||||||
|
return Object.entries(options).reduce((r,n,i)=>{
|
||||||
|
const [k,v] = n;
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function pixelButton(x,y,xo,yo,img,colors=["#112","#334","#556","#778","#99A","#BBC"]) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const w = img.width+4;
|
||||||
|
const h = img.height+5;
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.fillStyle = colors[0];
|
||||||
|
ctx.fillRect(x+0+xo,y+yo,img.width+4,img.height+5);
|
||||||
|
ctx.fillStyle = colors[1];
|
||||||
|
ctx.fillRect(x+0+xo,y+yo,img.width+4,img.height+4);
|
||||||
|
ctx.fillStyle = colors[3];
|
||||||
|
ctx.fillRect(x+1+xo,y+yo,img.width+2,img.height+4);
|
||||||
|
ctx.fillStyle = colors[3];
|
||||||
|
ctx.fillRect(x+0+xo,y+yo,img.width+4,img.height+2);
|
||||||
|
ctx.fillStyle = colors[2];
|
||||||
|
ctx.fillRect(x+1+xo,y+yo,img.width+2,img.height+2);
|
||||||
|
ctx.drawImage(img,x+2,y+2);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
function scaleImageData(imageData, scale) {
|
||||||
|
if (scale === 1) return imageData;
|
||||||
|
var scaledImageData = document.createElement("canvas").getContext("2d").createImageData(imageData.width * scale, imageData.height * scale);
|
||||||
|
for (var row = 0; row < imageData.height; row++) {
|
||||||
|
for (var col = 0; col < imageData.width; col++) {
|
||||||
|
var sourcePixel = [
|
||||||
|
imageData.data[(row * imageData.width + col) * 4 + 0],
|
||||||
|
imageData.data[(row * imageData.width + col) * 4 + 1],
|
||||||
|
imageData.data[(row * imageData.width + col) * 4 + 2],
|
||||||
|
imageData.data[(row * imageData.width + col) * 4 + 3]
|
||||||
|
];
|
||||||
|
for (var y = 0; y < scale; y++) {
|
||||||
|
var destRow = row * scale + y;
|
||||||
|
for (var x = 0; x < scale; x++) {
|
||||||
|
var destCol = col * scale + x;
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
scaledImageData.data[(destRow * scaledImageData.width + destCol) * 4 + i] =
|
||||||
|
sourcePixel[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scaledImageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageChopper(img,tileHeight,tileWidth) {
|
||||||
|
const c = document.createElement('canvas');
|
||||||
|
const w = c.width = img.width;
|
||||||
|
const h = c.height = img.height;
|
||||||
|
const ctx = c.getContext('2d');
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const arr = [];
|
||||||
|
for (let y = 0; y < h; y += tileHeight) {
|
||||||
|
for (let x = 0; x < w; x += tileWidth) {
|
||||||
|
const imageData = ctx.getImageData(x, y, tileWidth, tileHeight);
|
||||||
|
const tileCanvas = document.createElement('canvas');
|
||||||
|
tileCanvas.width = tileWidth;
|
||||||
|
tileCanvas.height = tileHeight;
|
||||||
|
const tileCtx = tileCanvas.getContext('2d');
|
||||||
|
tileCtx.putImageData(imageData,0,0);
|
||||||
|
arr.push(tileCanvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
function imageDataToCanvas(imageData, x = 0, y = 0) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = imageData.width;
|
||||||
|
canvas.height = imageData.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.putImageData(imageData, x, y);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tilesToCanvas(arr,columns,tilesData) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const rows = Math.floor(arr.length / columns);
|
||||||
|
if(rows !== (arr.length / columns)){
|
||||||
|
debugger;
|
||||||
|
//console.error("wtf this should never happen...");
|
||||||
|
}
|
||||||
|
const w = tilesData[0].width * columns;
|
||||||
|
const h = tilesData[0].height * rows;
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
/* first draw the tiles... */
|
||||||
|
arr.forEach((tileIdx,i) => {
|
||||||
|
if(tileIdx >= 0) {
|
||||||
|
const c = tilesData[tileIdx];
|
||||||
|
const x = i%columns;
|
||||||
|
const y = Math.floor(i/columns);
|
||||||
|
document.body.appendChild(c);
|
||||||
|
ctx.drawImage(c, x * c.width, y * c.width);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* then draw tile fringe? */ // TODO
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
}
|
385
js/color_utils.js
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
// UTILITY
|
||||||
|
|
||||||
|
let firstColor = "#000000", secondColor = "#000000";
|
||||||
|
let log = document.getElementById("log");
|
||||||
|
|
||||||
|
// CONSTS
|
||||||
|
|
||||||
|
// Degrees to radiants
|
||||||
|
let degreesToRad = Math.PI / 180;
|
||||||
|
// I'm pretty sure that precision is necessary
|
||||||
|
let referenceWhite = {x: 95.05, y: 100, z: 108.89999999999999};
|
||||||
|
|
||||||
|
// COLOUR SIMILARITY
|
||||||
|
|
||||||
|
// Min distance under which 2 colours are considered similar
|
||||||
|
let distanceThreshold = 10;
|
||||||
|
|
||||||
|
// Threshold used to consider a colour "dark"
|
||||||
|
let darkColoursThreshold = 50;
|
||||||
|
// Threshold used to tell if 2 dark colours are similar
|
||||||
|
let darkColoursSimilarityThreshold = 40;
|
||||||
|
|
||||||
|
// Threshold used to consider a colour "light"
|
||||||
|
let lightColoursThreshold = 190;
|
||||||
|
// Threshold used to tell if 2 light colours are similar
|
||||||
|
let lightColoursSimilarityThreshold = 30;
|
||||||
|
|
||||||
|
|
||||||
|
// document.getElementById("color1").addEventListener("change", updateColor);
|
||||||
|
// document.getElementById("color2").addEventListener("change", updateColor);
|
||||||
|
|
||||||
|
function updateColor(e) {
|
||||||
|
////console.log(e);
|
||||||
|
|
||||||
|
switch (e.target.id) {
|
||||||
|
case "color1":
|
||||||
|
firstColor = e.target.value;
|
||||||
|
break;
|
||||||
|
case "color2":
|
||||||
|
secondColor = e.target.value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWarnings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWarnings() {
|
||||||
|
let toSet = "";
|
||||||
|
////console.log("colors: " + firstColor + ", " + secondColor);
|
||||||
|
toSet += similarColours(firstColor, secondColor) ? 'Colours are similar!' + '\n' : "";
|
||||||
|
|
||||||
|
log.innerHTML = toSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************SECTION: COLOUR SIMILARITY*********************************/
|
||||||
|
|
||||||
|
function similarColours(rgb1, rgb2) {
|
||||||
|
let ret = differenceCiede2000(rgb1, rgb2);
|
||||||
|
const lightInRange = lightColoursCheck(rgb1, rgb2);
|
||||||
|
const darkInRange = darkColoursCheck(rgb1, rgb2);
|
||||||
|
|
||||||
|
// if((ret < distanceThreshold && lightColoursCheck(rgb1, rgb2)) || darkColoursCheck(rgb1, rgb2)){
|
||||||
|
// return ret;
|
||||||
|
// }
|
||||||
|
// return 100;
|
||||||
|
if((ret < distanceThreshold && lightInRange) || darkInRange) {
|
||||||
|
// ////console.log('GOOD ret === ',ret);
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
// ////console.log('BAD ret === ',ret);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lightColoursCheck(c1, c2) {
|
||||||
|
let rDelta = Math.abs(c1.r - c2.r);
|
||||||
|
let gDelta = Math.abs(c1.g - c2.g);
|
||||||
|
let bDelta = Math.abs(c1.b - c2.b);
|
||||||
|
|
||||||
|
// Checking only if the colours are dark enough
|
||||||
|
if (c1.r > lightColoursThreshold && c1.g > lightColoursThreshold && c1.b > lightColoursThreshold &&
|
||||||
|
c2.r > lightColoursThreshold && c2.g > lightColoursThreshold && c2.b > lightColoursThreshold) {
|
||||||
|
return rDelta < lightColoursSimilarityThreshold && gDelta < lightColoursSimilarityThreshold &&
|
||||||
|
bDelta < lightColoursSimilarityThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function darkColoursCheck(c1, c2) {
|
||||||
|
let rDelta = Math.abs(c1.r - c2.r);
|
||||||
|
let gDelta = Math.abs(c1.g - c2.g);
|
||||||
|
let bDelta = Math.abs(c1.b - c2.b);
|
||||||
|
|
||||||
|
// Checking only if the colours are dark enough
|
||||||
|
if (c1.r < darkColoursThreshold && c1.g < darkColoursThreshold && c1.b < darkColoursThreshold &&
|
||||||
|
c2.r < darkColoursThreshold && c2.g < darkColoursThreshold && c2.b < darkColoursThreshold) {
|
||||||
|
return rDelta < darkColoursSimilarityThreshold && gDelta < darkColoursSimilarityThreshold &&
|
||||||
|
bDelta < darkColoursSimilarityThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance based on CIEDE2000 (https://en.wikipedia.org/wiki/Color_difference#CIEDE2000)
|
||||||
|
function differenceCiede2000(c1, c2) {
|
||||||
|
var kL = 1, kC = 1, kH = 0.9;
|
||||||
|
var LabStd = RGBtoCIELAB(c1);
|
||||||
|
var LabSmp = RGBtoCIELAB(c2);
|
||||||
|
|
||||||
|
var lStd = LabStd.l;
|
||||||
|
var aStd = LabStd.a;
|
||||||
|
var bStd = LabStd.b;
|
||||||
|
var cStd = Math.sqrt(aStd * aStd + bStd * bStd);
|
||||||
|
|
||||||
|
var lSmp = LabSmp.l;
|
||||||
|
var aSmp = LabSmp.a;
|
||||||
|
var bSmp = LabSmp.b;
|
||||||
|
var cSmp = Math.sqrt(aSmp * aSmp + bSmp * bSmp);
|
||||||
|
|
||||||
|
var cAvg = (cStd + cSmp) / 2;
|
||||||
|
|
||||||
|
var G = 0.5 * (1 - Math.sqrt(Math.pow(cAvg, 7) / (Math.pow(cAvg, 7) + Math.pow(25, 7))));
|
||||||
|
|
||||||
|
var apStd = aStd * (1 + G);
|
||||||
|
var apSmp = aSmp * (1 + G);
|
||||||
|
|
||||||
|
var cpStd = Math.sqrt(apStd * apStd + bStd * bStd);
|
||||||
|
var cpSmp = Math.sqrt(apSmp * apSmp + bSmp * bSmp);
|
||||||
|
|
||||||
|
var hpStd = Math.abs(apStd) + Math.abs(bStd) === 0 ? 0 : Math.atan2(bStd, apStd);
|
||||||
|
hpStd += (hpStd < 0) * 2 * Math.PI;
|
||||||
|
|
||||||
|
var hpSmp = Math.abs(apSmp) + Math.abs(bSmp) === 0 ? 0 : Math.atan2(bSmp, apSmp);
|
||||||
|
hpSmp += (hpSmp < 0) * 2 * Math.PI;
|
||||||
|
|
||||||
|
var dL = lSmp - lStd;
|
||||||
|
var dC = cpSmp - cpStd;
|
||||||
|
|
||||||
|
var dhp = cpStd * cpSmp === 0 ? 0 : hpSmp - hpStd;
|
||||||
|
dhp -= (dhp > Math.PI) * 2 * Math.PI;
|
||||||
|
dhp += (dhp < -Math.PI) * 2 * Math.PI;
|
||||||
|
|
||||||
|
var dH = 2 * Math.sqrt(cpStd * cpSmp) * Math.sin(dhp / 2);
|
||||||
|
|
||||||
|
var Lp = (lStd + lSmp) / 2;
|
||||||
|
var Cp = (cpStd + cpSmp) / 2;
|
||||||
|
|
||||||
|
var hp;
|
||||||
|
if (cpStd * cpSmp === 0) {
|
||||||
|
hp = hpStd + hpSmp;
|
||||||
|
} else {
|
||||||
|
hp = (hpStd + hpSmp) / 2;
|
||||||
|
hp -= (Math.abs(hpStd - hpSmp) > Math.PI) * Math.PI;
|
||||||
|
hp += (hp < 0) * 2 * Math.PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Lpm50 = Math.pow(Lp - 50, 2);
|
||||||
|
var T = 1 -
|
||||||
|
0.17 * Math.cos(hp - Math.PI / 6) +
|
||||||
|
0.24 * Math.cos(2 * hp) +
|
||||||
|
0.32 * Math.cos(3 * hp + Math.PI / 30) -
|
||||||
|
0.20 * Math.cos(4 * hp - 63 * Math.PI / 180);
|
||||||
|
|
||||||
|
var Sl = 1 + (0.015 * Lpm50) / Math.sqrt(20 + Lpm50);
|
||||||
|
var Sc = 1 + 0.045 * Cp;
|
||||||
|
var Sh = 1 + 0.015 * Cp * T;
|
||||||
|
|
||||||
|
var deltaTheta = 30 * Math.PI / 180 * Math.exp(-1 * Math.pow((180 / Math.PI * hp - 275)/25, 2));
|
||||||
|
var Rc = 2 * Math.sqrt(
|
||||||
|
Math.pow(Cp, 7) / (Math.pow(Cp, 7) + Math.pow(25, 7))
|
||||||
|
);
|
||||||
|
|
||||||
|
var Rt = -1 * Math.sin(2 * deltaTheta) * Rc;
|
||||||
|
|
||||||
|
return Math.sqrt(
|
||||||
|
Math.pow(dL / (kL * Sl), 2) +
|
||||||
|
Math.pow(dC / (kC * Sc), 2) +
|
||||||
|
Math.pow(dH / (kH * Sh), 2) +
|
||||||
|
Rt * dC / (kC * Sc) * dH / (kH * Sh)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************SECTION: COLOUR CONVERSIONS****************************** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an HSL color value to RGB. Conversion formula
|
||||||
|
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||||
|
* Assumes h, s, and l are contained in the set [0, 1] and
|
||||||
|
* returns r, g, and b in the set [0, 255].
|
||||||
|
*
|
||||||
|
* @param {number} h The hue
|
||||||
|
* @param {number} s The saturation
|
||||||
|
* @param {number} l The lightness
|
||||||
|
* @return {Array} The RGB representation
|
||||||
|
*/
|
||||||
|
function hslToRgb(h, s, l){
|
||||||
|
var r, g, b;
|
||||||
|
|
||||||
|
h /= 360;
|
||||||
|
s /= 100;
|
||||||
|
l /= 100;
|
||||||
|
|
||||||
|
if(s == 0){
|
||||||
|
r = g = b = l; // achromatic
|
||||||
|
}else{
|
||||||
|
var hue2rgb = function hue2rgb(p, q, t){
|
||||||
|
if(t < 0) t += 1;
|
||||||
|
if(t > 1) t -= 1;
|
||||||
|
if(t < 1/6) return p + (q - p) * 6 * t;
|
||||||
|
if(t < 1/2) return q;
|
||||||
|
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
var p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1/3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1/3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function hsvToRgb(h, s, v) {
|
||||||
|
var r, g, b;
|
||||||
|
|
||||||
|
h /= 360;
|
||||||
|
s /= 100;
|
||||||
|
v /= 100;
|
||||||
|
var i = Math.floor(h * 6);
|
||||||
|
var f = h * 6 - i;
|
||||||
|
var p = v * (1 - s);
|
||||||
|
var q = v * (1 - f * s);
|
||||||
|
var t = v * (1 - (1 - f) * s);
|
||||||
|
|
||||||
|
switch (i % 6) {
|
||||||
|
case 0: r = v, g = t, b = p; break;
|
||||||
|
case 1: r = q, g = v, b = p; break;
|
||||||
|
case 2: r = p, g = v, b = t; break;
|
||||||
|
case 3: r = p, g = q, b = v; break;
|
||||||
|
case 4: r = t, g = p, b = v; break;
|
||||||
|
case 5: r = v, g = p, b = q; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ r * 255, g * 255, b * 255 ];
|
||||||
|
}
|
||||||
|
|
||||||
|
function hslToHex(h, s, l) {
|
||||||
|
h /= 360;
|
||||||
|
s /= 100;
|
||||||
|
l /= 100;
|
||||||
|
let r, g, b;
|
||||||
|
if (s === 0) {
|
||||||
|
r = g = b = l; // achromatic
|
||||||
|
} else {
|
||||||
|
const hue2rgb = (p, q, t) => {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1 / 2) return q;
|
||||||
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
const p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1 / 3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1 / 3);
|
||||||
|
}
|
||||||
|
const toHex = x => {
|
||||||
|
const hex = Math.round(x * 255).toString(16);
|
||||||
|
return hex.length === 1 ? '0' + hex : hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
return `${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHsl(col) {
|
||||||
|
let r = col.r;
|
||||||
|
let g = col.g;
|
||||||
|
let b = col.b;
|
||||||
|
|
||||||
|
r /= 255, g /= 255, b /= 255;
|
||||||
|
|
||||||
|
let max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||||
|
let myH, myS, myL = (max + min) / 2;
|
||||||
|
|
||||||
|
if (max == min) {
|
||||||
|
myH = myS = 0; // achromatic
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let d = max - min;
|
||||||
|
myS = myL > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
|
||||||
|
switch (max) {
|
||||||
|
case r: myH = (g - b) / d + (g < b ? 6 : 0); break;
|
||||||
|
case g: myH = (b - r) / d + 2; break;
|
||||||
|
case b: myH = (r - g) / d + 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
myH /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {h: myH, s: myS, l: myL };
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHsv(col) {
|
||||||
|
let r = col.r;
|
||||||
|
let g = col.g;
|
||||||
|
let b = col.b;
|
||||||
|
|
||||||
|
r /= 255, g /= 255, b /= 255;
|
||||||
|
|
||||||
|
let max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||||
|
let myH, myS, myV = max;
|
||||||
|
|
||||||
|
let d = max - min;
|
||||||
|
myS = max == 0 ? 0 : d / max;
|
||||||
|
|
||||||
|
if (max == min) {
|
||||||
|
myH = 0; // achromatic
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (max) {
|
||||||
|
case r: myH = (g - b) / d + (g < b ? 6 : 0); break;
|
||||||
|
case g: myH = (b - r) / d + 2; break;
|
||||||
|
case b: myH = (r - g) / d + 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
myH /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {h: myH, s: myS, v: myV};
|
||||||
|
}
|
||||||
|
|
||||||
|
function RGBtoCIELAB(rgbColour) {
|
||||||
|
// Convert to XYZ first via matrix transformation
|
||||||
|
let x = 0.412453 * rgbColour.r + 0.357580 * rgbColour.g + 0.180423 * rgbColour.b;
|
||||||
|
let y = 0.212671 * rgbColour.r + 0.715160 * rgbColour.g + 0.072169 * rgbColour.b;
|
||||||
|
let z = 0.019334 * rgbColour.r + 0.119193 * rgbColour.g + 0.950227 * rgbColour.b;
|
||||||
|
|
||||||
|
let xFunc = CIELABconvF(x / referenceWhite.x);
|
||||||
|
let yFunc = CIELABconvF(y / referenceWhite.y);
|
||||||
|
let zFunc = CIELABconvF(z / referenceWhite.z);
|
||||||
|
|
||||||
|
let myL = 116 * yFunc - 16;
|
||||||
|
let myA = 500 * (xFunc - yFunc);
|
||||||
|
let myB = 200 * (yFunc - zFunc);
|
||||||
|
|
||||||
|
return {l: myL, a: myA, b: myB};
|
||||||
|
|
||||||
|
}
|
||||||
|
function CIELABconvF(value) {
|
||||||
|
if (value > Math.pow(6/29, 3)) {
|
||||||
|
return Math.cbrt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1/3 * Math.pow(6/29, 2) * value + 4/29;
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorToRGB(color) {
|
||||||
|
if(window.colorCache && window.colorCache[color]){
|
||||||
|
return window.colorCache[color];
|
||||||
|
}
|
||||||
|
if (!window.cachedCtx) {
|
||||||
|
window.cachedCtx = document.createElement("canvas").getContext("2d");
|
||||||
|
window.colorCache = {};
|
||||||
|
}
|
||||||
|
let ctx = window.cachedCtx;
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
return hexToRgb(ctx.fillStyle);
|
||||||
|
|
||||||
|
function hexToRgb(hex) {
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return {
|
||||||
|
r: parseInt(result[1], 16),
|
||||||
|
g: parseInt(result[2], 16),
|
||||||
|
b: parseInt(result[3], 16)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
338
js/color_utils3.js
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
// Min distance under which 2 colours are considered similar
|
||||||
|
let distanceThreshold = 10;
|
||||||
|
|
||||||
|
// Threshold used to consider a colour "dark"
|
||||||
|
let darkColoursThreshold = 50;
|
||||||
|
// Threshold used to tell if 2 dark colours are similar
|
||||||
|
let darkColoursSimilarityThreshold = 40;
|
||||||
|
|
||||||
|
// Threshold used to consider a colour "light"
|
||||||
|
let lightColoursThreshold = 190;
|
||||||
|
// Threshold used to tell if 2 light colours are similar
|
||||||
|
let lightColoursSimilarityThreshold = 30;
|
||||||
|
|
||||||
|
let referenceWhite = {
|
||||||
|
x: 95.05,
|
||||||
|
y: 100,
|
||||||
|
z: 108.89999999999999
|
||||||
|
};
|
||||||
|
const example = {
|
||||||
|
"red": [
|
||||||
|
"#bf6f4a",
|
||||||
|
"#e07438",
|
||||||
|
"#c64524",
|
||||||
|
"#ff5000"
|
||||||
|
],
|
||||||
|
"green": [
|
||||||
|
"#99e65f",
|
||||||
|
"#5ac54f",
|
||||||
|
"#33984b"
|
||||||
|
],
|
||||||
|
"blue": [
|
||||||
|
"#0069aa",
|
||||||
|
"#0098dc",
|
||||||
|
"#00cdf9"
|
||||||
|
],
|
||||||
|
"cyan": [
|
||||||
|
"#0069aa",
|
||||||
|
"#0098dc",
|
||||||
|
"#00cdf9",
|
||||||
|
"#0cf1ff"
|
||||||
|
],
|
||||||
|
"yellow": [
|
||||||
|
"#ffa214",
|
||||||
|
"#ffc825",
|
||||||
|
"#ffeb57"
|
||||||
|
],
|
||||||
|
"magenta": [
|
||||||
|
"#db3ffd"
|
||||||
|
],
|
||||||
|
"light": [
|
||||||
|
"#ffffff",
|
||||||
|
"#f9e6cf",
|
||||||
|
"#fdd2ed"
|
||||||
|
],
|
||||||
|
"dark": [
|
||||||
|
"#131313",
|
||||||
|
"#1b1b1b",
|
||||||
|
"#272727",
|
||||||
|
"#3d3d3d",
|
||||||
|
"#5d5d5d"
|
||||||
|
],
|
||||||
|
"brown": [
|
||||||
|
"#e69c69",
|
||||||
|
"#f6ca9f",
|
||||||
|
"#f9e6cf",
|
||||||
|
"#edab50",
|
||||||
|
"#e07438",
|
||||||
|
"#ed7614",
|
||||||
|
"#ffa214",
|
||||||
|
"#ffc825",
|
||||||
|
"#ffeb57"
|
||||||
|
],
|
||||||
|
"neon": [
|
||||||
|
"#ff0040",
|
||||||
|
"#ff5000",
|
||||||
|
"#ed7614",
|
||||||
|
"#ffa214",
|
||||||
|
"#ffc825",
|
||||||
|
"#0098dc",
|
||||||
|
"#00cdf9",
|
||||||
|
"#0cf1ff",
|
||||||
|
"#7a09fa",
|
||||||
|
"#3003d9"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const COLOR_META = {
|
||||||
|
red: { color: "#ff0000", flux:{ h:25, v:40, s:40} },
|
||||||
|
green: { color: "#00ff00", flux:{ h:35} },
|
||||||
|
blue: { color: "#0077dd", flux:{ h:25, v:30, s:30} },
|
||||||
|
cyan: { color: "#00ffff", flux:{ h:25, v:40, s:40} },
|
||||||
|
yellow: { color: "#ffff00", flux:{ h:25, v:40, s:40} },
|
||||||
|
magenta: { color: "#ff00ff", flux:{ h:15, v:40, s:40} },
|
||||||
|
light: { color: "#ffffff", flux:{ v:10, s:30} },
|
||||||
|
dark: { color: "#000000", flux:{ v:30, v:40, s:20} },
|
||||||
|
brown: { color: "#ffaa00", flux:{ h:20} },
|
||||||
|
neon: { color: "#00ffff", flux:{ s:20, v:20} },
|
||||||
|
};
|
||||||
|
Object.keys(COLOR_META).forEach(metaName=>{
|
||||||
|
COLOR_META[metaName].colorMeta = colorMeta(COLOR_META[metaName].color);
|
||||||
|
});
|
||||||
|
function paletteMeta(colorArr) {
|
||||||
|
const colorMetaArr = colorArr.map(colorMeta);
|
||||||
|
//////console.log('colorMetaArr === ',colorMetaArr);
|
||||||
|
|
||||||
|
const ret = {};
|
||||||
|
Object.keys(COLOR_META).forEach(metaName=>{
|
||||||
|
const {color,colorMeta,flux} = COLOR_META[metaName];
|
||||||
|
|
||||||
|
const fluxKeys = Object.keys(flux);
|
||||||
|
|
||||||
|
ret[metaName] = colorArr.filter((c,i)=>{
|
||||||
|
const colorMeta2 = colorMetaArr[i];
|
||||||
|
return fluxKeys.filter(k=>{
|
||||||
|
return (colorMeta[k] + flux[k]) > colorMeta2[k]
|
||||||
|
&&
|
||||||
|
(colorMeta[k] - flux[k]) < colorMeta2[k]
|
||||||
|
;
|
||||||
|
}).length === fluxKeys.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
//////console.log(JSON.stringify(ret,null,4));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
function colorMeta(colorStr) {
|
||||||
|
const rgb = colorToRGB(colorStr);
|
||||||
|
const hsv = rgb2hsv(rgb.r, rgb.g, rgb.b);
|
||||||
|
const lab = rgb2lab(rgb.r, rgb.g, rgb.b);
|
||||||
|
const cie = {c:lab.l,i:lab.a,e:lab.b};
|
||||||
|
return {
|
||||||
|
...rgb,
|
||||||
|
...hsv,
|
||||||
|
...cie
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function rgb2hex(r, g, b) {
|
||||||
|
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
||||||
|
}
|
||||||
|
function rgb2hsv(r, g, b) {
|
||||||
|
let rabs, gabs, babs, rr, gg, bb, h, s, v, diff, diffc, percentRoundFn;
|
||||||
|
rabs = r / 255;
|
||||||
|
gabs = g / 255;
|
||||||
|
babs = b / 255;
|
||||||
|
v = Math.max(rabs, gabs, babs),
|
||||||
|
diff = v - Math.min(rabs, gabs, babs);
|
||||||
|
diffc = c => (v - c) / 6 / diff + 1 / 2;
|
||||||
|
percentRoundFn = num => Math.round(num * 100) / 100;
|
||||||
|
if (diff == 0) {
|
||||||
|
h = s = 0;
|
||||||
|
} else {
|
||||||
|
s = diff / v;
|
||||||
|
rr = diffc(rabs);
|
||||||
|
gg = diffc(gabs);
|
||||||
|
bb = diffc(babs);
|
||||||
|
|
||||||
|
if (rabs === v) {
|
||||||
|
h = bb - gg;
|
||||||
|
} else if (gabs === v) {
|
||||||
|
h = (1 / 3) + rr - bb;
|
||||||
|
} else if (babs === v) {
|
||||||
|
h = (2 / 3) + gg - rr;
|
||||||
|
}
|
||||||
|
if (h < 0) {
|
||||||
|
h += 1;
|
||||||
|
}else if (h > 1) {
|
||||||
|
h -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
h: Math.round(h * 360),
|
||||||
|
s: percentRoundFn(s * 100),
|
||||||
|
v: percentRoundFn(v * 100)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function similarColors(rgb1, rgb2) {
|
||||||
|
let ret = differenceCiede2000(rgb1, rgb2)
|
||||||
|
//////console.log(ret);
|
||||||
|
return (ret < distanceThreshold && lightColoursCheck(rgb1, rgb2)) || darkColoursCheck(rgb1, rgb2);
|
||||||
|
}
|
||||||
|
function lightColoursCheck(rgb1, rgb2) {
|
||||||
|
let rDelta = Math.abs(rgb1.r - rgb2.r);
|
||||||
|
let gDelta = Math.abs(rgb1.g - rgb2.g);
|
||||||
|
let bDelta = Math.abs(rgb1.b - rgb2.b);
|
||||||
|
|
||||||
|
// Checking only if the colours are dark enough
|
||||||
|
if (rgb1.r > lightColoursThreshold && rgb1.g > lightColoursThreshold && rgb1.b > lightColoursThreshold &&
|
||||||
|
rgb2.r > lightColoursThreshold && rgb2.g > lightColoursThreshold && rgb2.b > lightColoursThreshold) {
|
||||||
|
return rDelta < lightColoursSimilarityThreshold && gDelta < lightColoursSimilarityThreshold &&
|
||||||
|
bDelta < lightColoursSimilarityThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function darkColoursCheck(rgb1, rgb2) {
|
||||||
|
let rDelta = Math.abs(rgb1.r - rgb2.r);
|
||||||
|
let gDelta = Math.abs(rgb1.g - rgb2.g);
|
||||||
|
let bDelta = Math.abs(rgb1.b - rgb2.b);
|
||||||
|
|
||||||
|
// Checking only if the colours are dark enough
|
||||||
|
if (rgb1.r < darkColoursThreshold && rgb1.g < darkColoursThreshold && rgb1.b < darkColoursThreshold &&
|
||||||
|
rgb2.r < darkColoursThreshold && rgb2.g < darkColoursThreshold && rgb2.b < darkColoursThreshold) {
|
||||||
|
return rDelta < darkColoursSimilarityThreshold && gDelta < darkColoursSimilarityThreshold &&
|
||||||
|
bDelta < darkColoursSimilarityThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Distance based on CIEDE2000 (https://en.wikipedia.org/wiki/Color_difference#CIEDE2000)
|
||||||
|
function differenceCiede2000(rgb1, rgb2) {
|
||||||
|
var kL = 1,
|
||||||
|
kC = 1,
|
||||||
|
kH = 0.9;
|
||||||
|
var LabStd = rgb2lab(rgb1);
|
||||||
|
var LabSmp = rgb2lab(rgb2);
|
||||||
|
|
||||||
|
var lStd = LabStd.l;
|
||||||
|
var aStd = LabStd.a;
|
||||||
|
var bStd = LabStd.b;
|
||||||
|
var cStd = Math.sqrt(aStd * aStd + bStd * bStd);
|
||||||
|
|
||||||
|
var lSmp = LabSmp.l;
|
||||||
|
var aSmp = LabSmp.a;
|
||||||
|
var bSmp = LabSmp.b;
|
||||||
|
var cSmp = Math.sqrt(aSmp * aSmp + bSmp * bSmp);
|
||||||
|
|
||||||
|
var cAvg = (cStd + cSmp) / 2;
|
||||||
|
|
||||||
|
var G = 0.5 * (1 - Math.sqrt(Math.pow(cAvg, 7) / (Math.pow(cAvg, 7) + Math.pow(25, 7))));
|
||||||
|
|
||||||
|
var apStd = aStd * (1 + G);
|
||||||
|
var apSmp = aSmp * (1 + G);
|
||||||
|
|
||||||
|
var cpStd = Math.sqrt(apStd * apStd + bStd * bStd);
|
||||||
|
var cpSmp = Math.sqrt(apSmp * apSmp + bSmp * bSmp);
|
||||||
|
|
||||||
|
var hpStd = Math.abs(apStd) + Math.abs(bStd) === 0 ? 0 : Math.atan2(bStd, apStd);
|
||||||
|
hpStd += (hpStd < 0) * 2 * Math.PI;
|
||||||
|
|
||||||
|
var hpSmp = Math.abs(apSmp) + Math.abs(bSmp) === 0 ? 0 : Math.atan2(bSmp, apSmp);
|
||||||
|
hpSmp += (hpSmp < 0) * 2 * Math.PI;
|
||||||
|
|
||||||
|
var dL = lSmp - lStd;
|
||||||
|
var dC = cpSmp - cpStd;
|
||||||
|
|
||||||
|
var dhp = cpStd * cpSmp === 0 ? 0 : hpSmp - hpStd;
|
||||||
|
dhp -= (dhp > Math.PI) * 2 * Math.PI;
|
||||||
|
dhp += (dhp < -Math.PI) * 2 * Math.PI;
|
||||||
|
|
||||||
|
var dH = 2 * Math.sqrt(cpStd * cpSmp) * Math.sin(dhp / 2);
|
||||||
|
|
||||||
|
var Lp = (lStd + lSmp) / 2;
|
||||||
|
var Cp = (cpStd + cpSmp) / 2;
|
||||||
|
|
||||||
|
var hp;
|
||||||
|
if (cpStd * cpSmp === 0) {
|
||||||
|
hp = hpStd + hpSmp;
|
||||||
|
} else {
|
||||||
|
hp = (hpStd + hpSmp) / 2;
|
||||||
|
hp -= (Math.abs(hpStd - hpSmp) > Math.PI) * Math.PI;
|
||||||
|
hp += (hp < 0) * 2 * Math.PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Lpm50 = Math.pow(Lp - 50, 2);
|
||||||
|
var T = 1 -
|
||||||
|
0.17 * Math.cos(hp - Math.PI / 6) +
|
||||||
|
0.24 * Math.cos(2 * hp) +
|
||||||
|
0.32 * Math.cos(3 * hp + Math.PI / 30) -
|
||||||
|
0.20 * Math.cos(4 * hp - 63 * Math.PI / 180);
|
||||||
|
|
||||||
|
var Sl = 1 + (0.015 * Lpm50) / Math.sqrt(20 + Lpm50);
|
||||||
|
var Sc = 1 + 0.045 * Cp;
|
||||||
|
var Sh = 1 + 0.015 * Cp * T;
|
||||||
|
|
||||||
|
var deltaTheta = 30 * Math.PI / 180 * Math.exp(-1 * Math.pow((180 / Math.PI * hp - 275) / 25, 2));
|
||||||
|
var Rc = 2 * Math.sqrt(
|
||||||
|
Math.pow(Cp, 7) / (Math.pow(Cp, 7) + Math.pow(25, 7))
|
||||||
|
);
|
||||||
|
|
||||||
|
var Rt = -1 * Math.sin(2 * deltaTheta) * Rc;
|
||||||
|
|
||||||
|
return Math.sqrt(
|
||||||
|
Math.pow(dL / (kL * Sl), 2) +
|
||||||
|
Math.pow(dC / (kC * Sc), 2) +
|
||||||
|
Math.pow(dH / (kH * Sh), 2) +
|
||||||
|
Rt * dC / (kC * Sc) * dH / (kH * Sh)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function rgb2lab(r, g, b) {
|
||||||
|
// Convert to XYZ first via matrix transformation
|
||||||
|
let x = 0.412453 * r + 0.357580 * g + 0.180423 * b;
|
||||||
|
let y = 0.212671 * r + 0.715160 * g + 0.072169 * b;
|
||||||
|
let z = 0.019334 * r + 0.119193 * g + 0.950227 * b;
|
||||||
|
|
||||||
|
let xFunc = CIELABconvF(x / referenceWhite.x);
|
||||||
|
let yFunc = CIELABconvF(y / referenceWhite.y);
|
||||||
|
let zFunc = CIELABconvF(z / referenceWhite.z);
|
||||||
|
|
||||||
|
let myL = 116 * yFunc - 16;
|
||||||
|
let myA = 500 * (xFunc - yFunc);
|
||||||
|
let myB = 200 * (yFunc - zFunc);
|
||||||
|
|
||||||
|
return {
|
||||||
|
l: myL,
|
||||||
|
a: myA,
|
||||||
|
b: myB
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function CIELABconvF(value) {
|
||||||
|
if (value > Math.pow(6 / 29, 3)) {
|
||||||
|
return Math.cbrt(value);
|
||||||
|
}
|
||||||
|
return 1 / 3 * Math.pow(6 / 29, 2) * value + 4 / 29;
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorToRGB(color) {
|
||||||
|
if(window.colorCache && window.colorCache[color]){
|
||||||
|
return window.colorCache[color];
|
||||||
|
}
|
||||||
|
if (!window.cachedCtx) {
|
||||||
|
window.cachedCtx = document.createElement("canvas").getContext("2d");
|
||||||
|
window.colorCache = {};
|
||||||
|
}
|
||||||
|
let ctx = window.cachedCtx;
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
return hexToRgb(ctx.fillStyle);
|
||||||
|
|
||||||
|
function hexToRgb(hex) {
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return {
|
||||||
|
r: parseInt(result[1], 16),
|
||||||
|
g: parseInt(result[2], 16),
|
||||||
|
b: parseInt(result[3], 16)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,5 @@ const MAX_Z_INDEX = 5000;
|
|||||||
|
|
||||||
// Index of the first layer the user can use in the layers array
|
// Index of the first layer the user can use in the layers array
|
||||||
const firstUserLayerIndex = 2;
|
const firstUserLayerIndex = 2;
|
||||||
// Number of layers that are only used by the editor
|
|
||||||
const nAppLayers = 3;
|
|
||||||
|
|
||||||
const MIN_ZOOM_LEVEL = 0.5;
|
const MIN_ZOOM_LEVEL = 0.5;
|
@ -18,7 +18,7 @@ class Layer {
|
|||||||
// TODO: this is simply terrible. menuEntry can either be an HTML element, a string or undefined.
|
// TODO: this is simply terrible. menuEntry can either be an HTML element, a string or undefined.
|
||||||
// If it's an HTML element it is added, if it's a string nothing happens, if it's undefined the
|
// If it's an HTML element it is added, if it's a string nothing happens, if it's undefined the
|
||||||
// first menuEntry is used (the one that appears on load)
|
// first menuEntry is used (the one that appears on load)
|
||||||
constructor(width, height, canvas, menuEntry) {
|
constructor(width, height, canvas, menuEntry, id) {
|
||||||
// REFACTOR: the canvas should actually be a Canvas instance
|
// REFACTOR: the canvas should actually be a Canvas instance
|
||||||
this.canvas = Util.getElement(canvas);
|
this.canvas = Util.getElement(canvas);
|
||||||
this.canvas.width = width;
|
this.canvas.width = width;
|
||||||
@ -37,18 +37,24 @@ class Layer {
|
|||||||
else if (menuEntry !== undefined)
|
else if (menuEntry !== undefined)
|
||||||
this.menuEntry = menuEntry;
|
this.menuEntry = menuEntry;
|
||||||
|
|
||||||
let id = Layer.unusedIDs.pop();
|
let hadId = false;
|
||||||
|
if(typeof id !== "undefined"){
|
||||||
|
hadId = true;
|
||||||
|
} else {
|
||||||
|
id = Layer.unusedIDs.pop();
|
||||||
|
}
|
||||||
|
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
id = Layer.currentID;
|
id = Layer.currentID;
|
||||||
Layer.currentID++;
|
Layer.currentID++;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.id = "layer" + id;
|
this.id = hadId ? id : "layer" + id;
|
||||||
|
|
||||||
// Binding the events
|
// Binding the events
|
||||||
if (this.menuEntry !== undefined) {
|
if (this.menuEntry !== undefined) {
|
||||||
this.name = this.menuEntry.getElementsByTagName("p")[0].innerHTML;
|
this.name = this.menuEntry.getElementsByTagName("p")[0].innerHTML;
|
||||||
|
|
||||||
this.menuEntry.id = "layer" + id;
|
this.menuEntry.id = "layer" + id;
|
||||||
|
|
||||||
this.menuEntry.onmouseover = () => this.hover();
|
this.menuEntry.onmouseover = () => this.hover();
|
||||||
@ -69,16 +75,58 @@ class Layer {
|
|||||||
this.menuEntry.getElementsByTagName("canvas")[0].getContext('2d').imageSmoothingEnabled = false;
|
this.menuEntry.getElementsByTagName("canvas")[0].getContext('2d').imageSmoothingEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(hadId){
|
||||||
|
this.menuEntry.classList.remove("layers-menu-entry");
|
||||||
|
} else {
|
||||||
|
if(this.menuEntry)this.menuEntry.classList.add("layers-menu-entry");
|
||||||
|
}
|
||||||
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCanvas() {
|
hasCanvas() {
|
||||||
return this.menuEntry != null;
|
return this.menuEntry != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
tryDelete() {
|
delete(layerIndex) {
|
||||||
|
//console.log('layerIndex === ',layerIndex);
|
||||||
|
let toDelete = currFile.layers[layerIndex];
|
||||||
|
let previousSibling;
|
||||||
|
if(toDelete){
|
||||||
|
//console.log('toDelete === ',toDelete);
|
||||||
|
previousSibling = toDelete.menuEntry.previousElementSibling;
|
||||||
|
//console.log('previousSibling === ',previousSibling);
|
||||||
|
// Adding the ids to the unused ones
|
||||||
|
// Deleting canvas and entry
|
||||||
|
toDelete.canvas.remove();
|
||||||
|
toDelete.menuEntry.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
Layer.unusedIDs.push(this.id);
|
||||||
|
|
||||||
|
if(this.isSelected) {
|
||||||
|
// Selecting the nearest layer
|
||||||
|
const nearestLayer = (currFile.layers[layerIndex + 1] ?? currFile.layers[layerIndex - 1]);
|
||||||
|
if(nearestLayer){
|
||||||
|
nearestLayer.selectLayer();
|
||||||
|
//console.log('changing to nearest layer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Removing the layer from the list
|
||||||
|
currFile.layers.splice(layerIndex, 1);
|
||||||
|
|
||||||
|
if(toDelete){
|
||||||
|
new HistoryState().DeleteLayer(toDelete, previousSibling, layerIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tryDelete() { //TODO: quote yoda
|
||||||
if (Input.getLastTarget() != this.menuEntry && Input.getLastTarget().parentElement != this.menuEntry)
|
if (Input.getLastTarget() != this.menuEntry && Input.getLastTarget().parentElement != this.menuEntry)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LayerList.deleteLayer();
|
LayerList.deleteLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +169,7 @@ class Layer {
|
|||||||
|
|
||||||
hover() {
|
hover() {
|
||||||
// Hides all the layers but the current one
|
// Hides all the layers but the current one
|
||||||
for (let i=1; i<currFile.layers.length - nAppLayers; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
if (currFile.layers[i] !== this) {
|
if (currFile.layers[i] !== this) {
|
||||||
currFile.layers[i].canvas.style.opacity = 0.3;
|
currFile.layers[i].canvas.style.opacity = 0.3;
|
||||||
}
|
}
|
||||||
@ -130,7 +178,7 @@ class Layer {
|
|||||||
|
|
||||||
unhover() {
|
unhover() {
|
||||||
// Shows all the layers again
|
// Shows all the layers again
|
||||||
for (let i=1; i<currFile.layers.length - nAppLayers; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
if (currFile.layers[i] !== this) {
|
if (currFile.layers[i] !== this) {
|
||||||
currFile.layers[i].canvas.style.opacity = 1;
|
currFile.layers[i].canvas.style.opacity = 1;
|
||||||
}
|
}
|
||||||
@ -196,6 +244,8 @@ class Layer {
|
|||||||
this.isSelected = true;
|
this.isSelected = true;
|
||||||
this.menuEntry.classList.add("selected-layer");
|
this.menuEntry.classList.add("selected-layer");
|
||||||
currFile.currentLayer = this;
|
currFile.currentLayer = this;
|
||||||
|
|
||||||
|
FileManager.localStorageSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLock() {
|
toggleLock() {
|
||||||
|
@ -78,58 +78,73 @@ window.onload = function () {
|
|||||||
// Apply checkboxes
|
// Apply checkboxes
|
||||||
|
|
||||||
|
|
||||||
//check if there are any url parameters
|
let args = window.location.pathname.split('/');
|
||||||
if (window.location.pathname.replace('/pixel-editor/','').length <= 1) {
|
let paletteSlug = args[2];
|
||||||
//show splash screen
|
let dimensions = args[3];
|
||||||
Dialogue.showDialogue('splash', false);
|
// let prefillWidth = args[4] ?? 9; // TODO
|
||||||
}
|
// let prefill = args[5] ?? "110101111110100110111100110110101111";
|
||||||
//url parameters were specified
|
// let customColors = args[6] ?? ""; // ex: "#ffffff,#000000"
|
||||||
else {
|
// console.log('prefill === ',prefill);
|
||||||
let args = window.location.pathname.split('/');
|
if(paletteSlug && dimensions) {
|
||||||
let paletteSlug = args[2];
|
|
||||||
let dimentions = args[3];
|
|
||||||
|
|
||||||
//fetch palette via lospec palette API
|
//fetch palette via lospec palette API
|
||||||
fetch('https://lospec.com/palette-list/'+paletteSlug+'.json')
|
fetch('https://lospec.com/palette-list/'+paletteSlug+'.json')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
//palette loaded successfully
|
//palette loaded successfully
|
||||||
palettes[paletteSlug] = data;
|
palettes[paletteSlug] = data;
|
||||||
palettes[paletteSlug].specified = true;
|
palettes[paletteSlug].specified = true;
|
||||||
|
//refresh list of palettes
|
||||||
|
document.getElementById('palette-menu-splash').refresh();
|
||||||
|
|
||||||
//refresh list of palettes
|
//if the dimensions were specified
|
||||||
document.getElementById('palette-menu-splash').refresh();
|
if (dimensions && dimensions.length >= 3 && dimensions.includes('x')) {
|
||||||
|
let width = dimensions.split('x')[0];
|
||||||
|
let height = dimensions.split('x')[1];
|
||||||
|
const layers = [];
|
||||||
|
let selectedLayer;
|
||||||
|
Startup.newPixel({
|
||||||
|
canvasWidth: width,
|
||||||
|
canvasHeight: height,
|
||||||
|
selectedLayer,
|
||||||
|
colors: data.colors.map(n=>"#"+n),
|
||||||
|
layers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//dimensions were not specified -- show splash screen with palette preselected
|
||||||
|
else {
|
||||||
|
//show splash
|
||||||
|
Dialogue.showDialogue('new-pixel', false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//error fetching url (either palette doesn't exist, or lospec is down)
|
||||||
|
.catch((error) => {
|
||||||
|
//console.warn('failed to load palette "'+paletteSlug+'"', error);
|
||||||
|
//proceed to splash screen
|
||||||
|
Dialogue.showDialogue('splash', false);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if(FileManager.localStorageCheck()) {
|
||||||
|
//load cached document
|
||||||
|
const lpe = FileManager.localStorageLoad();
|
||||||
|
|
||||||
//if the dimentions were specified
|
Startup.newPixel(lpe);
|
||||||
if (dimentions && dimentions.length >= 3 && dimentions.includes('x')) {
|
}
|
||||||
let width = dimentions.split('x')[0];
|
//check if there are any url parameters
|
||||||
let height = dimentions.split('x')[1];
|
else if (window.location.pathname.replace('/pixel-editor/','').length <= 1) {
|
||||||
|
//show splash screen
|
||||||
//create new document
|
Dialogue.showDialogue('splash', false);
|
||||||
Startup.newPixel(width, height);
|
}
|
||||||
}
|
}
|
||||||
//dimentions were not specified -- show splash screen with palette preselected
|
}
|
||||||
else {
|
|
||||||
//show splash
|
|
||||||
Dialogue.showDialogue('new-pixel', false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
//error fetching url (either palette doesn't exist, or lospec is down)
|
|
||||||
.catch((error) => {
|
|
||||||
console.warn('failed to load palette "'+paletteSlug+'"', error);
|
|
||||||
//proceed to splash screen
|
|
||||||
Dialogue.showDialogue('splash', false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//prevent user from leaving page with unsaved data
|
//prevent user from leaving page with unsaved data
|
||||||
window.onbeforeunload = function() {
|
// window.onbeforeunload = function() {
|
||||||
if (EditorState.documentCreated)
|
// if (EditorState.documentCreated)
|
||||||
return 'You will lose your pixel if it\'s not saved!';
|
// return 'You will lose your pixel if it\'s not saved!';
|
||||||
|
|
||||||
else return;
|
// else return;
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Compatibility functions
|
// Compatibility functions
|
||||||
function closeCompatibilityWarning() {
|
function closeCompatibilityWarning() {
|
||||||
|
@ -12,7 +12,7 @@ class BrushTool extends ResizableTool {
|
|||||||
this.addTutorialKey("Left drag", " to draw a stroke");
|
this.addTutorialKey("Left drag", " to draw a stroke");
|
||||||
this.addTutorialKey("Right drag", " to resize the brush");
|
this.addTutorialKey("Right drag", " to resize the brush");
|
||||||
this.addTutorialKey("+ or -", " to resize the brush");
|
this.addTutorialKey("+ or -", " to resize the brush");
|
||||||
this.addTutorialImg("brush-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/brush-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
onStart(mousePos, cursorTarget) {
|
onStart(mousePos, cursorTarget) {
|
||||||
|
@ -25,7 +25,7 @@ class EllipseTool extends ResizableTool {
|
|||||||
this.addTutorialKey("Left drag", " to draw an ellipse");
|
this.addTutorialKey("Left drag", " to draw an ellipse");
|
||||||
this.addTutorialKey("Right drag", " to resize the brush");
|
this.addTutorialKey("Right drag", " to resize the brush");
|
||||||
this.addTutorialKey("+ or -", " to resize the brush");
|
this.addTutorialKey("+ or -", " to resize the brush");
|
||||||
this.addTutorialImg("ellipse-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/ellipse-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
changeFillType() {
|
changeFillType() {
|
||||||
|
@ -12,7 +12,7 @@ class EraserTool extends ResizableTool {
|
|||||||
this.addTutorialKey("Left drag", " to erase an area");
|
this.addTutorialKey("Left drag", " to erase an area");
|
||||||
this.addTutorialKey("Right drag", " to resize the eraser");
|
this.addTutorialKey("Right drag", " to resize the eraser");
|
||||||
this.addTutorialKey("+ or -", " to resize the eraser");
|
this.addTutorialKey("+ or -", " to resize the eraser");
|
||||||
this.addTutorialImg("eraser-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/eraser-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
onStart(mousePos) {
|
onStart(mousePos) {
|
||||||
|
@ -14,7 +14,7 @@ class EyeDropperTool extends Tool {
|
|||||||
this.addTutorialKey("Aòt + left drag", " to preview the picked colour");
|
this.addTutorialKey("Aòt + left drag", " to preview the picked colour");
|
||||||
this.addTutorialKey("Left click", " to select a colour");
|
this.addTutorialKey("Left click", " to select a colour");
|
||||||
this.addTutorialKey("Alt + click", " to select a colour");
|
this.addTutorialKey("Alt + click", " to select a colour");
|
||||||
this.addTutorialImg("eyedropper-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/eyedropper-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
onStart(mousePos, target) {
|
onStart(mousePos, target) {
|
||||||
|
@ -8,7 +8,7 @@ class FillTool extends DrawingTool {
|
|||||||
this.addTutorialTitle("Fill tool");
|
this.addTutorialTitle("Fill tool");
|
||||||
this.addTutorialKey("F", " to select the fill tool");
|
this.addTutorialKey("F", " to select the fill tool");
|
||||||
this.addTutorialKey("Left click", " to fill a contiguous area");
|
this.addTutorialKey("Left click", " to fill a contiguous area");
|
||||||
this.addTutorialImg("fill-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/fill-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
onStart(mousePos, target) {
|
onStart(mousePos, target) {
|
||||||
@ -25,7 +25,7 @@ class FillTool extends DrawingTool {
|
|||||||
static fill(cursorLocation, context) {
|
static fill(cursorLocation, context) {
|
||||||
//changes a pixels color
|
//changes a pixels color
|
||||||
function colorPixel(tempImage, pixelPos, fillColor) {
|
function colorPixel(tempImage, pixelPos, fillColor) {
|
||||||
//console.log('colorPixel:',pixelPos);
|
//////console.log('colorPixel:',pixelPos);
|
||||||
tempImage.data[pixelPos] = fillColor.r;
|
tempImage.data[pixelPos] = fillColor.r;
|
||||||
tempImage.data[pixelPos + 1] = fillColor.g;
|
tempImage.data[pixelPos + 1] = fillColor.g;
|
||||||
tempImage.data[pixelPos + 2] = fillColor.b;
|
tempImage.data[pixelPos + 2] = fillColor.b;
|
||||||
@ -34,13 +34,13 @@ class FillTool extends DrawingTool {
|
|||||||
|
|
||||||
//change x y to color value passed from the function and use that as the original color
|
//change x y to color value passed from the function and use that as the original color
|
||||||
function matchStartColor(tempImage, pixelPos, color) {
|
function matchStartColor(tempImage, pixelPos, color) {
|
||||||
//console.log('matchPixel:',x,y)
|
//////console.log('matchPixel:',x,y)
|
||||||
|
|
||||||
let r = tempImage.data[pixelPos];
|
let r = tempImage.data[pixelPos];
|
||||||
let g = tempImage.data[pixelPos + 1];
|
let g = tempImage.data[pixelPos + 1];
|
||||||
let b = tempImage.data[pixelPos + 2];
|
let b = tempImage.data[pixelPos + 2];
|
||||||
let a = tempImage.data[pixelPos + 3];
|
let a = tempImage.data[pixelPos + 3];
|
||||||
//console.log(r == color[0] && g == color[1] && b == color[2]);
|
//////console.log(r == color[0] && g == color[1] && b == color[2]);
|
||||||
return (r == color[0] && g == color[1] && b == color[2] && a == color[3]);
|
return (r == color[0] && g == color[1] && b == color[2] && a == color[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ class FillTool extends DrawingTool {
|
|||||||
|
|
||||||
//this is an array that holds all of the pixels at the top of the cluster
|
//this is an array that holds all of the pixels at the top of the cluster
|
||||||
let topmostPixelsArray = [[Math.floor(cursorLocation[0]/currFile.zoom), Math.floor(cursorLocation[1]/currFile.zoom)]];
|
let topmostPixelsArray = [[Math.floor(cursorLocation[0]/currFile.zoom), Math.floor(cursorLocation[1]/currFile.zoom)]];
|
||||||
//console.log('topmostPixelsArray:',topmostPixelsArray)
|
//////console.log('topmostPixelsArray:',topmostPixelsArray)
|
||||||
|
|
||||||
//the offset of the pixel in the temp image data to start with
|
//the offset of the pixel in the temp image data to start with
|
||||||
let startingPosition = (topmostPixelsArray[0][1] * currFile.canvasSize[0] + topmostPixelsArray[0][0]) * 4;
|
let startingPosition = (topmostPixelsArray[0][1] * currFile.canvasSize[0] + topmostPixelsArray[0][0]) * 4;
|
||||||
|
@ -15,7 +15,7 @@ class LassoSelectionTool extends SelectionTool {
|
|||||||
this.addTutorialKey("CTRL+C", " to copy a selection")
|
this.addTutorialKey("CTRL+C", " to copy a selection")
|
||||||
this.addTutorialKey("CTRL+V", " to paste a selection")
|
this.addTutorialKey("CTRL+V", " to paste a selection")
|
||||||
this.addTutorialKey("CTRL+X", " to cut a selection")
|
this.addTutorialKey("CTRL+X", " to cut a selection")
|
||||||
this.addTutorialImg("lassoselect-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/lassoselect-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
onStart(mousePos, mouseTarget) {
|
onStart(mousePos, mouseTarget) {
|
||||||
|
@ -12,7 +12,7 @@ class LineTool extends ResizableTool {
|
|||||||
this.addTutorialKey("Left drag", " to draw a line");
|
this.addTutorialKey("Left drag", " to draw a line");
|
||||||
this.addTutorialKey("Right drag", " to resize the brush");
|
this.addTutorialKey("Right drag", " to resize the brush");
|
||||||
this.addTutorialKey("+ or -", " to resize the brush");
|
this.addTutorialKey("+ or -", " to resize the brush");
|
||||||
this.addTutorialImg("line-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/line-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
onStart(mousePos) {
|
onStart(mousePos) {
|
||||||
|
@ -13,7 +13,7 @@ class MagicWandTool extends SelectionTool {
|
|||||||
this.addTutorialKey("CTRL+C", " to copy a selection");
|
this.addTutorialKey("CTRL+C", " to copy a selection");
|
||||||
this.addTutorialKey("CTRL+V", " to paste a selection");
|
this.addTutorialKey("CTRL+V", " to paste a selection");
|
||||||
this.addTutorialKey("CTRL+X", " to cut a selection");
|
this.addTutorialKey("CTRL+X", " to cut a selection");
|
||||||
this.addTutorialImg("magicwand-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/magicwand-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnd(mousePos, mouseTarget) {
|
onEnd(mousePos, mouseTarget) {
|
||||||
@ -22,7 +22,7 @@ class MagicWandTool extends SelectionTool {
|
|||||||
!Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom]))
|
!Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom]))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
////console.log('this.moveTool === ',this.moveTool);
|
||||||
this.switchFunc(this.moveTool);
|
this.switchFunc(this.moveTool);
|
||||||
this.moveTool.setSelectionData(this.getSelection(), this);
|
this.moveTool.setSelectionData(this.getSelection(), this);
|
||||||
}
|
}
|
||||||
@ -52,8 +52,10 @@ class MagicWandTool extends SelectionTool {
|
|||||||
this.outlineData = new ImageData(currFile.canvasSize[0], currFile.canvasSize[1]);
|
this.outlineData = new ImageData(currFile.canvasSize[0], currFile.canvasSize[1]);
|
||||||
this.previewData = selectedData;
|
this.previewData = selectedData;
|
||||||
this.drawSelectedArea();
|
this.drawSelectedArea();
|
||||||
this.boundingBoxCenter = [this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2,
|
this.boundingBoxCenter = [
|
||||||
this.boundingBox.minY + (this.boundingBox.maxY - this.boundingBox.minY) / 2];
|
this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2,
|
||||||
|
this.boundingBox.minY + (this.boundingBox.maxY - this.boundingBox.minY) / 2
|
||||||
|
];
|
||||||
|
|
||||||
// Cut the selection
|
// Cut the selection
|
||||||
this.cutSelection();
|
this.cutSelection();
|
||||||
@ -61,7 +63,7 @@ class MagicWandTool extends SelectionTool {
|
|||||||
currFile.TMPLayer.context.putImageData(this.previewData, 0, 0);
|
currFile.TMPLayer.context.putImageData(this.previewData, 0, 0);
|
||||||
|
|
||||||
// Draw the bounding box
|
// Draw the bounding box
|
||||||
this.drawBoundingBox();
|
this.drawBoundingBox(1, 1);
|
||||||
|
|
||||||
return selectedData;
|
return selectedData;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ class PanTool extends Tool {
|
|||||||
this.addTutorialKey("P", " to select the lasso selection tool");
|
this.addTutorialKey("P", " to select the lasso selection tool");
|
||||||
this.addTutorialKey("Left drag", " to move the viewport");
|
this.addTutorialKey("Left drag", " to move the viewport");
|
||||||
this.addTutorialKey("Space + drag", " to move the viewport");
|
this.addTutorialKey("Space + drag", " to move the viewport");
|
||||||
this.addTutorialImg("pan-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/pan-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
onStart(mousePos, target) {
|
onStart(mousePos, target) {
|
||||||
|
@ -23,7 +23,7 @@ class RectangleTool extends ResizableTool {
|
|||||||
this.addTutorialKey("Left drag", " to draw a rectangle");
|
this.addTutorialKey("Left drag", " to draw a rectangle");
|
||||||
this.addTutorialKey("Right drag", " to resize the brush");
|
this.addTutorialKey("Right drag", " to resize the brush");
|
||||||
this.addTutorialKey("+ or -", " to resize the brush");
|
this.addTutorialKey("+ or -", " to resize the brush");
|
||||||
this.addTutorialImg("rectangle-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/rectangle-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
changeFillType() {
|
changeFillType() {
|
||||||
|
@ -14,7 +14,7 @@ class RectangularSelectionTool extends SelectionTool {
|
|||||||
this.addTutorialKey("CTRL+C", " to copy a selection");
|
this.addTutorialKey("CTRL+C", " to copy a selection");
|
||||||
this.addTutorialKey("CTRL+V", " to paste a selection");
|
this.addTutorialKey("CTRL+V", " to paste a selection");
|
||||||
this.addTutorialKey("CTRL+X", " to cut a selection");
|
this.addTutorialKey("CTRL+X", " to cut a selection");
|
||||||
this.addTutorialImg("rectselect-tutorial.gif");
|
this.addTutorialImg("/images/ToolTutorials/rectselect-tutorial.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
onStart(mousePos, mouseTarget) {
|
onStart(mousePos, mouseTarget) {
|
||||||
|
@ -44,8 +44,10 @@ class SelectionTool extends Tool {
|
|||||||
this.currSelection = {};
|
this.currSelection = {};
|
||||||
this.moveOffset = [0, 0];
|
this.moveOffset = [0, 0];
|
||||||
|
|
||||||
this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
|
this.updateBoundingBox(
|
||||||
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1));
|
Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
|
||||||
|
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrag(mousePos) {
|
onDrag(mousePos) {
|
||||||
@ -66,8 +68,10 @@ class SelectionTool extends Tool {
|
|||||||
|
|
||||||
|
|
||||||
if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) {
|
if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) {
|
||||||
this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
|
this.updateBoundingBox(
|
||||||
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1));
|
Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
|
||||||
|
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,8 +85,10 @@ class SelectionTool extends Tool {
|
|||||||
let mouseY = mousePos[1] / currFile.zoom;
|
let mouseY = mousePos[1] / currFile.zoom;
|
||||||
|
|
||||||
if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) {
|
if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) {
|
||||||
this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
|
this.updateBoundingBox(
|
||||||
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1));
|
Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
|
||||||
|
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.boundingBoxCenter = [this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2,
|
this.boundingBoxCenter = [this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2,
|
||||||
@ -211,6 +217,7 @@ class SelectionTool extends Tool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
////console.log('this.currSelection === ',this.currSelection);
|
||||||
|
|
||||||
// Save the selection outline
|
// Save the selection outline
|
||||||
this.outlineData = currFile.VFXLayer.context.getImageData(this.boundingBox.minX,
|
this.outlineData = currFile.VFXLayer.context.getImageData(this.boundingBox.minX,
|
||||||
@ -246,16 +253,25 @@ class SelectionTool extends Tool {
|
|||||||
this.boundingBox.minY + this.moveOffset[1]);
|
this.boundingBox.minY + this.moveOffset[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawBoundingBox() {
|
drawBoundingBox(xo = 0, yo = 0) {
|
||||||
currFile.VFXLayer.context.fillStyle = "red";
|
currFile.VFXLayer.context.fillStyle = "red";
|
||||||
currFile.VFXLayer.context.fillRect(this.boundingBox.minX + this.moveOffset[0],
|
|
||||||
this.boundingBox.minY + this.moveOffset[1], 1, 1);
|
currFile.VFXLayer.context.fillRect(
|
||||||
currFile.VFXLayer.context.fillRect(this.boundingBox.minX+ this.moveOffset[0],
|
this.boundingBox.minX + this.moveOffset[0] - xo,
|
||||||
this.boundingBox.maxY + this.moveOffset[1], 1, 1);
|
this.boundingBox.minY + this.moveOffset[1] - yo,
|
||||||
currFile.VFXLayer.context.fillRect(this.boundingBox.maxX+ this.moveOffset[0],
|
1, 1);
|
||||||
this.boundingBox.minY + this.moveOffset[1], 1, 1);
|
currFile.VFXLayer.context.fillRect(
|
||||||
currFile.VFXLayer.context.fillRect(this.boundingBox.maxX+ this.moveOffset[0],
|
this.boundingBox.minX + this.moveOffset[0] - xo,
|
||||||
this.boundingBox.maxY + this.moveOffset[1], 1, 1);
|
this.boundingBox.maxY + this.moveOffset[1] + yo,
|
||||||
|
1, 1);
|
||||||
|
currFile.VFXLayer.context.fillRect(
|
||||||
|
this.boundingBox.maxX + this.moveOffset[0] + xo,
|
||||||
|
this.boundingBox.minY + this.moveOffset[1] - yo,
|
||||||
|
1, 1);
|
||||||
|
currFile.VFXLayer.context.fillRect(
|
||||||
|
this.boundingBox.maxX + this.moveOffset[0] + xo,
|
||||||
|
this.boundingBox.maxY + this.moveOffset[1] + yo,
|
||||||
|
1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
isBorderOfBox(pixel) {
|
isBorderOfBox(pixel) {
|
||||||
|
@ -75,8 +75,11 @@ class ZoomTool extends Tool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i=1; i<currFile.layers.length; i++) {
|
for (let i=0; i<currFile.layers.length; i++) {
|
||||||
currFile.layers[i].copyData(currFile.layers[0]);
|
currFile.layers[i].copyData(currFile.layers[0]);
|
||||||
}
|
}
|
||||||
|
for (let i=0; i<currFile.sublayers.length; i++) {
|
||||||
|
currFile.sublayers[i].copyData(currFile.layers[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
73
poc_pages/pixelate_font.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
<img src="/images/test_8x8.png" id="test_image" style="display:none;">
|
||||||
|
<script src="/js/canvas_util.js"></script>
|
||||||
|
<script src="/js/color_utils.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.40.2/codemirror.min.js"></script>
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.40.2/codemirror.css" rel="stylesheet" />
|
||||||
|
<div id="target" style="position:fixed;top:0;left:0;right:50%;bottom:0;">
|
||||||
|
<textarea id="editorTextArea">
|
||||||
|
const word = Math.random() > 0.5 ? "Hello" : "Goodbye cruel";
|
||||||
|
alert(`${word} world`);
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
<div id="preview_div" style="position:fixed;top:0;left:50%;right:0;bottom:90%;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="debug_div" style="position:fixed;top:10%;left:50%;right:0;bottom:0;overflow:scroll;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const charW = 8;
|
||||||
|
const charH = 8;
|
||||||
|
const charArr = generateCharsFromFont("monospace", charW, charH, 8, 8, preview_div, debug_div);
|
||||||
|
////console.log('charArr === ',charArr);
|
||||||
|
|
||||||
|
const editorTextArea = document.getElementById('editorTextArea');
|
||||||
|
const cm = new CodeMirror.fromTextArea(editorTextArea, {
|
||||||
|
lineNumbers: true
|
||||||
|
});
|
||||||
|
cm.setSize("100%", "100%");
|
||||||
|
|
||||||
|
CodeMirror.on(cm, "cursorActivity", function() {
|
||||||
|
const isSomethingSelected = cm.somethingSelected();
|
||||||
|
////console.log(isSomethingSelected);
|
||||||
|
const cursor = cm.getCursor();
|
||||||
|
////console.log(cursor);
|
||||||
|
if (isSomethingSelected) {
|
||||||
|
const text = cm.getSelection();
|
||||||
|
const cursor = cm.getCursor();
|
||||||
|
const selectionData = {text,cursor};
|
||||||
|
////console.log("selectionData:",selectionData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let count = 0;
|
||||||
|
cm.setValue(`\
|
||||||
|
const CHAR = [
|
||||||
|
${charArr.map((n,i)=>` \`${linebreakify(n,charW)}\`, //${String.fromCharCode(33+i)} `).join("\n")}
|
||||||
|
];
|
||||||
|
`);
|
||||||
|
// setInterval(function(){
|
||||||
|
// count++;
|
||||||
|
// cm.replaceRange(count%2 ? "Hi" : "Hello",{line:0,ch:36},{line:0,ch:(count%2 ? 41 : 38)})
|
||||||
|
// },1000);
|
||||||
|
function linebreakify(str,cols = 4) {
|
||||||
|
const arr = str.split("");
|
||||||
|
const arr2 = [];
|
||||||
|
for(let i = 0; i < arr.length;i++){
|
||||||
|
if((i%cols)===0)arr2.push("");
|
||||||
|
arr2[arr2.length-1] += arr[i];
|
||||||
|
}
|
||||||
|
return "\\\n"+arr2.join("\n");
|
||||||
|
}
|
||||||
|
</script>
|
269
poc_pages/rotation_POC_latest.html
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
<img src="/images/test_8x8.png" id="test_image" style="display:none;">
|
||||||
|
<script src="/js/canvas_util.js"></script>
|
||||||
|
<script src="/js/color_utils.js"></script>
|
||||||
|
<style>
|
||||||
|
#content_wrapper{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content_wrapper">
|
||||||
|
<div id="orig_colors_wrapper"></div>
|
||||||
|
<div id="color_compare_wrapper"></div>
|
||||||
|
<div id="preview_wrapper"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.ROTATION_CACHE = {};
|
||||||
|
window.SCORE_CACHE = {};
|
||||||
|
window.onload = () => {
|
||||||
|
const p0 = 30;
|
||||||
|
const p1 = 40;
|
||||||
|
const p2 = 50;
|
||||||
|
const p3 = 60;
|
||||||
|
const p4 = 70;
|
||||||
|
const offset = 50;
|
||||||
|
const to = -240;
|
||||||
|
const testArr = [
|
||||||
|
[Math.PI * 0.000, test_image, `calc(${p2}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
|
||||||
|
[Math.PI * 0.825, test_image, `calc(${p1}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
|
||||||
|
[Math.PI * 0.750, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
|
||||||
|
[Math.PI * 0.625, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p1}% - ${offset + to}px)`],
|
||||||
|
[Math.PI * 0.500, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p2}% - ${offset + to}px)`],
|
||||||
|
[Math.PI * 0.375, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p3}% - ${offset + to}px)`],
|
||||||
|
[Math.PI * 0.250, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
|
||||||
|
[Math.PI * 0.125, test_image, `calc(${p1}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
|
||||||
|
[Math.PI *-0.125, test_image, `calc(${p3}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
|
||||||
|
[Math.PI *-0.250, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
|
||||||
|
[Math.PI *-0.375, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p3}% - ${offset + to}px)`],
|
||||||
|
[Math.PI *-0.500, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p2}% - ${offset + to}px)`],
|
||||||
|
[Math.PI *-0.625, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p1}% - ${offset + to}px)`],
|
||||||
|
[Math.PI *-0.750, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
|
||||||
|
[Math.PI *-0.825, test_image, `calc(${p3}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
|
||||||
|
[Math.PI *-1.000, test_image, `calc(${p2}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
|
||||||
|
];
|
||||||
|
const canvasArr = testArr.map(n=>canvasRotateTest(...n));
|
||||||
|
canvasArr.forEach(canvas=>{
|
||||||
|
preview_wrapper.appendChild(canvas);
|
||||||
|
});
|
||||||
|
|
||||||
|
insertOrigColors();
|
||||||
|
}
|
||||||
|
function insertOrigColors(){
|
||||||
|
////console.log('window.ORIG_IMAGE_META.colorCounts === ',window.ORIG_IMAGE_META.colorCounts);
|
||||||
|
Object.keys(window.ORIG_IMAGE_META.colorCounts).forEach(colorStr=>{
|
||||||
|
////console.log('colorStr === ',colorStr);
|
||||||
|
const div = document.createElement('div');
|
||||||
|
const size = 42;
|
||||||
|
div.innerHTML = `\
|
||||||
|
<div style="float:left;margin:1px;">
|
||||||
|
<div style="float:left;width:${size}px;height:${size}px;background-color:${colorStr}"></div>
|
||||||
|
</div>`;
|
||||||
|
div.onclick = () => {
|
||||||
|
//////console.log('scores === ',scores);
|
||||||
|
}
|
||||||
|
orig_colors_wrapper.appendChild(div);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function canvasRotateTest(radians = 0, testImage, left, top) {
|
||||||
|
if(radians === 0)window.ORIG_IMAGE_META = imageMeta(testImage);
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
// const w = 1000;
|
||||||
|
// const h = 1000;
|
||||||
|
// const p = 100;
|
||||||
|
const rotationCacheKey = `${testImage.src}_${radians}`;
|
||||||
|
const rotationCacheKey0 = `${testImage.src}_${0}`;
|
||||||
|
//////console.log('rotationCacheKey, rotationCacheKey0 === ',rotationCacheKey, rotationCacheKey0);
|
||||||
|
const p = 1;
|
||||||
|
const x = p;
|
||||||
|
const y = p;
|
||||||
|
const w = (testImage.width + (p*2)) ?? 100;
|
||||||
|
const h = (testImage.height + (p*2)) ?? 100;
|
||||||
|
const sw = testImage.width;
|
||||||
|
const sh = testImage.height;
|
||||||
|
const sw2 = sw/2;
|
||||||
|
const sh2 = sh/2;
|
||||||
|
const sr = [x,y,sw,sh];
|
||||||
|
const center = [sr[0] + sw2, sr[1] + sh2];
|
||||||
|
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(center[0],center[1]);
|
||||||
|
ctx.fillStyle = "#fff";
|
||||||
|
ctx.rotate(radians);
|
||||||
|
ctx.translate(-center[0],-center[1]);
|
||||||
|
ctx.lineWidth = 10;
|
||||||
|
ctx.strokeStyle = "#000";
|
||||||
|
if(testImage) {
|
||||||
|
ctx.drawImage(testImage, sr[0], sr[1]);
|
||||||
|
} else {
|
||||||
|
ctx.strokeRect(...sr);
|
||||||
|
}
|
||||||
|
canvas.style.imageRendering = "pixelated";
|
||||||
|
canvas.style.transform = `scale(8)`;
|
||||||
|
canvas.style.position = "fixed";
|
||||||
|
|
||||||
|
canvas.style.top = top;
|
||||||
|
canvas.style.left = left;
|
||||||
|
|
||||||
|
if(radians==0)return canvas;
|
||||||
|
const imageData = ctx.getImageData(0,0,w,h);
|
||||||
|
const colorCounts = countImageDataColors(imageData);
|
||||||
|
const uniqueColors = Object.keys(colorCounts);
|
||||||
|
const uniqueFullColors = Object.keys(colorCounts).filter(n=>n.includes(",255)"));
|
||||||
|
//////console.log('uniqueFullColors === ',uniqueFullColors);
|
||||||
|
|
||||||
|
const bestMatches = {};
|
||||||
|
uniqueFullColors.forEach(colorStr=>{
|
||||||
|
const rgb = colorToRGB(colorStr);
|
||||||
|
const orig = window.ROTATION_CACHE[rotationCacheKey0] ?? {uniqueFullColors:[...uniqueFullColors]};
|
||||||
|
let minScore = Infinity;
|
||||||
|
let maxScore = -Infinity;
|
||||||
|
let minScoreChampion;
|
||||||
|
let maxScoreChampion;
|
||||||
|
const scores = orig.uniqueFullColors.map(origColor=>{
|
||||||
|
const origRgb = colorToRGB(origColor);
|
||||||
|
const scoreKey = `${rgb.r},${rgb.g},${rgb.b}_${origRgb.r},${origRgb.g},${origRgb.b}`;
|
||||||
|
// //////console.log('rgb, origRgb === ',rgb, origRgb);
|
||||||
|
const score = window.SCORE_CACHE[scoreKey] ?? similarColours(rgb, origRgb);//TODO: find a fire wizard to optimize this
|
||||||
|
window.SCORE_CACHE[scoreKey] = score;
|
||||||
|
const betterMinScore = minScore > score;
|
||||||
|
const betterMaxScore = maxScore < score;
|
||||||
|
if(betterMinScore) {
|
||||||
|
minScore = score;
|
||||||
|
minScoreChampion = origRgb;
|
||||||
|
}
|
||||||
|
if(betterMaxScore) {
|
||||||
|
maxScore = score;
|
||||||
|
maxScoreChampion = origRgb;
|
||||||
|
}
|
||||||
|
return score;
|
||||||
|
});
|
||||||
|
bestMatches[colorStr] = {
|
||||||
|
min:minScoreChampion,
|
||||||
|
max:maxScoreChampion,
|
||||||
|
scores
|
||||||
|
};
|
||||||
|
});
|
||||||
|
for(let i = 0; i < imageData.data.length;i += 4) {// <-- NOTE the 4 here
|
||||||
|
const r = imageData.data[i];
|
||||||
|
const g = imageData.data[i+1];
|
||||||
|
const b = imageData.data[i+2];
|
||||||
|
const a = imageData.data[i+3];//TODO: perf diffs between overwriting 'a' vs ignoring it
|
||||||
|
const rgbStr = `rgba(${r},${g},${b},${a})`;
|
||||||
|
const meta = bestMatches[rgbStr];
|
||||||
|
if(meta) {
|
||||||
|
// const bestRgb = meta.max;
|
||||||
|
const bestRgb = meta.min;
|
||||||
|
if(a === 255){
|
||||||
|
imageData.data[i] = bestRgb.r;
|
||||||
|
imageData.data[i+1] = bestRgb.g;
|
||||||
|
imageData.data[i+2] = bestRgb.b;
|
||||||
|
} else { // probably not needed since a<255 colors are not currently added to bestMatches
|
||||||
|
//////console.log('alpha < 255');
|
||||||
|
imageData.data[i] = 0;
|
||||||
|
imageData.data[i+1] = 0;
|
||||||
|
imageData.data[i+2] = 0;
|
||||||
|
imageData.data[i+3] = 0;
|
||||||
|
}
|
||||||
|
} else if (a < 255) {
|
||||||
|
imageData.data[i] = 0;
|
||||||
|
imageData.data[i+1] = 0;
|
||||||
|
imageData.data[i+2] = 0;
|
||||||
|
imageData.data[i+3] = 0;
|
||||||
|
}
|
||||||
|
// //////console.log('meta === ',meta);
|
||||||
|
}
|
||||||
|
ctx.putImageData(imageData,0,0);
|
||||||
|
//////console.log('bestMatches === ',bestMatches);
|
||||||
|
|
||||||
|
//////console.log('uniqueColors.length === ', uniqueColors.length);
|
||||||
|
//////console.log('uniqueFullColors.length === ', uniqueFullColors.length);
|
||||||
|
|
||||||
|
window.ROTATION_CACHE[(!window.ROTATION_CACHE[rotationCacheKey0]) ? rotationCacheKey0 : rotationCacheKey] = {
|
||||||
|
canvas,
|
||||||
|
ctx,
|
||||||
|
imageData,
|
||||||
|
uniqueColors,
|
||||||
|
uniqueFullColors,
|
||||||
|
bestMatches
|
||||||
|
};
|
||||||
|
const color_compare_row = document.createElement('div');
|
||||||
|
color_compare_row.style.display = "flex";
|
||||||
|
color_compare_row.style.borderBottom = "2px solid black";
|
||||||
|
color_compare_row.style.marginBottom = "2x";
|
||||||
|
color_compare_row.setAttribute("class","color-compare-row");
|
||||||
|
Object.keys(bestMatches).forEach(colorStr=>{
|
||||||
|
const div = document.createElement('div');
|
||||||
|
const {min,max,scores} = bestMatches[colorStr];
|
||||||
|
const minMatchColor = `rgba(${min.r},${min.g},${min.b},255)`;
|
||||||
|
const maxMatchColor = `rgba(${max.r},${max.g},${max.b},255)`;
|
||||||
|
div.innerHTML = `\
|
||||||
|
<div style="display:flex;margin:1px;">
|
||||||
|
<div style="width:10px;height:20px;background-color:${colorStr}"></div>
|
||||||
|
<div style="width:10px;height:20px;background-color:${minMatchColor}"></div>
|
||||||
|
</div>`;
|
||||||
|
div.onclick = () => {
|
||||||
|
////console.log('scores === ',scores);
|
||||||
|
}
|
||||||
|
color_compare_row.appendChild(div);
|
||||||
|
});
|
||||||
|
color_compare_wrapper.appendChild(color_compare_row);
|
||||||
|
color_compare_wrapper.setAttribute("class","color-compare-wrapper");
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
function countImageDataColors(imageData) {
|
||||||
|
const w = imageData.width;
|
||||||
|
const h = imageData.height;
|
||||||
|
const colorCounts = {};
|
||||||
|
const startTime = window.performance.now();
|
||||||
|
for (let x = 0; x < w; x++) {
|
||||||
|
for (let y = 0; y < h; y++) {
|
||||||
|
const i = (x + y * w) * 4;
|
||||||
|
const r = imageData.data[i];
|
||||||
|
const g = imageData.data[i + 1];
|
||||||
|
const b = imageData.data[i + 2];
|
||||||
|
const a = imageData.data[i + 3];
|
||||||
|
const key = `rgba(${r},${g},${b},${a})`;
|
||||||
|
if (!colorCounts[key]) colorCounts[key] = 0;
|
||||||
|
colorCounts[key]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const endTime = window.performance.now();
|
||||||
|
//////console.log('Count ImageData Colors duration (ms) === ',endTime - startTime);
|
||||||
|
return colorCounts;
|
||||||
|
}
|
||||||
|
function imageMeta(img) {
|
||||||
|
const canvas = imageToCanvas(img);
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const imageData = ctx.getImageData(0,0,img.width,img.height);
|
||||||
|
return {
|
||||||
|
img,
|
||||||
|
colorCounts: countImageDataColors(imageData),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function imageToCanvas(img) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
</script>
|
268
poc_pages/rotation_POC_old.html
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
<!-- <img src="/images/test_8x8.png" id="test_image" style="display:none;"> -->
|
||||||
|
<img src="/images/sked_tree_32x32.png" id="test_image" style="display:none;">
|
||||||
|
<script src="/js/canvas_util.js"></script>
|
||||||
|
<script src="/js/color_utils.js"></script>
|
||||||
|
<style>
|
||||||
|
#content_wrapper{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content_wrapper">
|
||||||
|
<div id="orig_colors_wrapper"></div>
|
||||||
|
<div id="color_compare_wrapper"></div>
|
||||||
|
<div id="preview_wrapper"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.ROTATION_CACHE = {};
|
||||||
|
window.SCORE_CACHE = {};
|
||||||
|
window.onload = () => {
|
||||||
|
const p0 = 30;
|
||||||
|
const p1 = 40;
|
||||||
|
const p2 = 50;
|
||||||
|
const p3 = 60;
|
||||||
|
const p4 = 70;
|
||||||
|
const offset = 50;
|
||||||
|
const to = -40;
|
||||||
|
const testArr = [
|
||||||
|
[Math.PI * 0.000, test_image, `calc(${p2}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
|
||||||
|
[Math.PI * 0.825, test_image, `calc(${p1}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI * 0.750, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI * 0.625, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p1}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI * 0.500, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p2}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI * 0.375, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p3}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI * 0.250, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI * 0.125, test_image, `calc(${p1}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI *-0.125, test_image, `calc(${p3}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI *-0.250, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI *-0.375, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p3}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI *-0.500, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p2}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI *-0.625, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p1}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI *-0.750, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI *-0.825, test_image, `calc(${p3}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
|
||||||
|
// [Math.PI *-1.000, test_image, `calc(${p2}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
|
||||||
|
];
|
||||||
|
const canvasArr = testArr.map(n=>canvasRotateTest(...n));
|
||||||
|
canvasArr.forEach(canvas=>{
|
||||||
|
preview_wrapper.appendChild(canvas);
|
||||||
|
});
|
||||||
|
|
||||||
|
insertOrigColors();
|
||||||
|
}
|
||||||
|
function insertOrigColors(){
|
||||||
|
Object.keys(window.ORIG_IMAGE_META.colorCounts).forEach(colorStr=>{
|
||||||
|
const div = document.createElement('div');
|
||||||
|
const size = 42;
|
||||||
|
div.innerHTML = `\
|
||||||
|
<div style="float:left;margin:1px;">
|
||||||
|
<div style="float:left;width:${size}px;height:${size}px;background-color:${colorStr}"></div>
|
||||||
|
</div>`;
|
||||||
|
div.onclick = () => {
|
||||||
|
//////console.log('scores === ',scores);
|
||||||
|
}
|
||||||
|
orig_colors_wrapper.appendChild(div);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function canvasRotateTest(radians = 0, testImage, left, top) {
|
||||||
|
if(radians === 0)window.ORIG_IMAGE_META = imageMeta(testImage);
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
// const w = 1000;
|
||||||
|
// const h = 1000;
|
||||||
|
// const p = 100;
|
||||||
|
const rotationCacheKey = `${testImage.src}_${radians}`;
|
||||||
|
const rotationCacheKey0 = `${testImage.src}_${0}`;
|
||||||
|
//////console.log('rotationCacheKey, rotationCacheKey0 === ',rotationCacheKey, rotationCacheKey0);
|
||||||
|
const p = 1;
|
||||||
|
const x = p;
|
||||||
|
const y = p;
|
||||||
|
const w = (testImage.width + (p*2)) ?? 100;
|
||||||
|
const h = (testImage.height + (p*2)) ?? 100;
|
||||||
|
const sw = testImage.width;
|
||||||
|
const sh = testImage.height;
|
||||||
|
const sw2 = sw/2;
|
||||||
|
const sh2 = sh/2;
|
||||||
|
const sr = [x,y,sw,sh];
|
||||||
|
const center = [sr[0] + sw2, sr[1] + sh2];
|
||||||
|
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(center[0],center[1]);
|
||||||
|
ctx.fillStyle = "#fff";
|
||||||
|
ctx.rotate(radians);
|
||||||
|
ctx.translate(-center[0],-center[1]);
|
||||||
|
ctx.lineWidth = 10;
|
||||||
|
ctx.strokeStyle = "#000";
|
||||||
|
if(testImage) {
|
||||||
|
ctx.drawImage(testImage, sr[0], sr[1]);
|
||||||
|
} else {
|
||||||
|
ctx.strokeRect(...sr);
|
||||||
|
}
|
||||||
|
canvas.style.imageRendering = "pixelated";
|
||||||
|
canvas.style.transform = `scale(4)`;
|
||||||
|
canvas.style.position = "fixed";
|
||||||
|
|
||||||
|
canvas.style.top = top;
|
||||||
|
canvas.style.left = left;
|
||||||
|
|
||||||
|
if(radians==0)return canvas;
|
||||||
|
const imageData = ctx.getImageData(0,0,w,h);
|
||||||
|
const colorCounts = countImageDataColors(imageData);
|
||||||
|
const uniqueColors = Object.keys(colorCounts);
|
||||||
|
const uniqueFullColors = Object.keys(colorCounts).filter(n=>n.includes(",255)"));
|
||||||
|
//////console.log('uniqueFullColors === ',uniqueFullColors);
|
||||||
|
|
||||||
|
const bestMatches = {};
|
||||||
|
uniqueFullColors.forEach(colorStr=>{
|
||||||
|
const rgb = colorToRGB(colorStr);
|
||||||
|
const orig = window.ROTATION_CACHE[rotationCacheKey0] ?? {uniqueFullColors:[...uniqueFullColors]};
|
||||||
|
let minScore = Infinity;
|
||||||
|
let maxScore = -Infinity;
|
||||||
|
let minScoreChampion;
|
||||||
|
let maxScoreChampion;
|
||||||
|
const scores = orig.uniqueFullColors.map(origColor=>{
|
||||||
|
const origRgb = colorToRGB(origColor);
|
||||||
|
const scoreKey = `${rgb.r},${rgb.g},${rgb.b}_${origRgb.r},${origRgb.g},${origRgb.b}`;
|
||||||
|
// //////console.log('rgb, origRgb === ',rgb, origRgb);
|
||||||
|
const score = window.SCORE_CACHE[scoreKey] ?? similarColours(rgb, origRgb);//TODO: find a fire wizard to optimize this
|
||||||
|
window.SCORE_CACHE[scoreKey] = score;
|
||||||
|
const betterMinScore = minScore > score;
|
||||||
|
const betterMaxScore = maxScore < score;
|
||||||
|
if(betterMinScore) {
|
||||||
|
minScore = score;
|
||||||
|
minScoreChampion = origRgb;
|
||||||
|
}
|
||||||
|
if(betterMaxScore) {
|
||||||
|
maxScore = score;
|
||||||
|
maxScoreChampion = origRgb;
|
||||||
|
}
|
||||||
|
return score;
|
||||||
|
});
|
||||||
|
bestMatches[colorStr] = {
|
||||||
|
min:minScoreChampion,
|
||||||
|
max:maxScoreChampion,
|
||||||
|
scores
|
||||||
|
};
|
||||||
|
});
|
||||||
|
for(let i = 0; i < imageData.data.length;i += 4) {// <-- NOTE the 4 here
|
||||||
|
const r = imageData.data[i];
|
||||||
|
const g = imageData.data[i+1];
|
||||||
|
const b = imageData.data[i+2];
|
||||||
|
const a = imageData.data[i+3];//TODO: perf diffs between overwriting 'a' vs ignoring it
|
||||||
|
const rgbStr = `rgba(${r},${g},${b},${a})`;
|
||||||
|
const meta = bestMatches[rgbStr];
|
||||||
|
if(meta) {
|
||||||
|
// const bestRgb = meta.max;
|
||||||
|
const bestRgb = meta.min;
|
||||||
|
if(a === 255){
|
||||||
|
imageData.data[i] = bestRgb.r;
|
||||||
|
imageData.data[i+1] = bestRgb.g;
|
||||||
|
imageData.data[i+2] = bestRgb.b;
|
||||||
|
} else { // probably not needed since a<255 colors are not currently added to bestMatches
|
||||||
|
//////console.log('alpha < 255');
|
||||||
|
imageData.data[i] = 0;
|
||||||
|
imageData.data[i+1] = 0;
|
||||||
|
imageData.data[i+2] = 0;
|
||||||
|
imageData.data[i+3] = 0;
|
||||||
|
}
|
||||||
|
} else if (a < 255) {
|
||||||
|
imageData.data[i] = 0;
|
||||||
|
imageData.data[i+1] = 0;
|
||||||
|
imageData.data[i+2] = 0;
|
||||||
|
imageData.data[i+3] = 0;
|
||||||
|
}
|
||||||
|
// //////console.log('meta === ',meta);
|
||||||
|
}
|
||||||
|
ctx.putImageData(imageData,0,0);
|
||||||
|
//////console.log('bestMatches === ',bestMatches);
|
||||||
|
|
||||||
|
//////console.log('uniqueColors.length === ', uniqueColors.length);
|
||||||
|
//////console.log('uniqueFullColors.length === ', uniqueFullColors.length);
|
||||||
|
|
||||||
|
window.ROTATION_CACHE[(!window.ROTATION_CACHE[rotationCacheKey0]) ? rotationCacheKey0 : rotationCacheKey] = {
|
||||||
|
canvas,
|
||||||
|
ctx,
|
||||||
|
imageData,
|
||||||
|
uniqueColors,
|
||||||
|
uniqueFullColors,
|
||||||
|
bestMatches
|
||||||
|
};
|
||||||
|
const color_compare_row = document.createElement('div');
|
||||||
|
color_compare_row.style.display = "flex";
|
||||||
|
color_compare_row.style.borderBottom = "2px solid black";
|
||||||
|
color_compare_row.style.marginBottom = "2x";
|
||||||
|
color_compare_row.setAttribute("class","color-compare-row");
|
||||||
|
Object.keys(bestMatches).forEach(colorStr=>{
|
||||||
|
const div = document.createElement('div');
|
||||||
|
const {min,max,scores} = bestMatches[colorStr];
|
||||||
|
const minMatchColor = `rgba(${min.r},${min.g},${min.b},255)`;
|
||||||
|
const maxMatchColor = `rgba(${max.r},${max.g},${max.b},255)`;
|
||||||
|
div.innerHTML = `\
|
||||||
|
<div style="display:flex;margin:1px;">
|
||||||
|
<div style="width:10px;height:20px;background-color:${colorStr}"></div>
|
||||||
|
<div style="width:10px;height:20px;background-color:${minMatchColor}"></div>
|
||||||
|
</div>`;
|
||||||
|
div.onclick = () => {
|
||||||
|
////console.log('scores === ',scores);
|
||||||
|
}
|
||||||
|
color_compare_row.appendChild(div);
|
||||||
|
});
|
||||||
|
color_compare_wrapper.appendChild(color_compare_row);
|
||||||
|
color_compare_wrapper.setAttribute("class","color-compare-wrapper");
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
function countImageDataColors(imageData) {
|
||||||
|
const w = imageData.width;
|
||||||
|
const h = imageData.height;
|
||||||
|
const colorCounts = {};
|
||||||
|
const startTime = window.performance.now();
|
||||||
|
for (let x = 0; x < w; x++) {
|
||||||
|
for (let y = 0; y < h; y++) {
|
||||||
|
const i = (x + y * w) * 4;
|
||||||
|
const r = imageData.data[i];
|
||||||
|
const g = imageData.data[i + 1];
|
||||||
|
const b = imageData.data[i + 2];
|
||||||
|
const a = imageData.data[i + 3];
|
||||||
|
const key = `rgba(${r},${g},${b},${a})`;
|
||||||
|
if (!colorCounts[key]) colorCounts[key] = 0;
|
||||||
|
colorCounts[key]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const endTime = window.performance.now();
|
||||||
|
////console.log('Count ImageData Colors duration (ms) === ',endTime - startTime);
|
||||||
|
return colorCounts;
|
||||||
|
}
|
||||||
|
function imageMeta(img) {
|
||||||
|
const canvas = imageToCanvas(img);
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const imageData = ctx.getImageData(0,0,img.width,img.height);
|
||||||
|
return {
|
||||||
|
img,
|
||||||
|
colorCounts: countImageDataColors(imageData),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function imageToCanvas(img) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
</script>
|
45
poc_pages/tiny_text.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="chaotic_good"></canvas>
|
||||||
|
<canvas id="neutral_good"></canvas>
|
||||||
|
<canvas id="lawful_good"></canvas>
|
||||||
|
<canvas id="chaotic_neutral"></canvas>
|
||||||
|
<canvas id="true_neutral"></canvas>
|
||||||
|
<canvas id="lawful_neutral"></canvas>
|
||||||
|
<canvas id="chaotic_evil"></canvas>
|
||||||
|
<canvas id="neutral_evil"></canvas>
|
||||||
|
<canvas id="lawful_evil"></canvas>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<script src="/js/canvas_util.js"></script>
|
||||||
|
<script src="/js/color_utils.js"></script>
|
||||||
|
<script>
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const w = 256;
|
||||||
|
const h = 256;
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.fillStyle = 'black';
|
||||||
|
ctx.fillRect(0,0,w,h);
|
||||||
|
ctx.fillStyle = 'red';
|
||||||
|
ctx.fillRect(1,1,w-2,h-2);
|
||||||
|
canvas.style.imageRendering = "pixelated";
|
||||||
|
canvas.style.transform = `scale(4)`;
|
||||||
|
canvas.style.position = "fixed";
|
||||||
|
canvas.style.top = "calc(50% - "+(h/2)+"px)";
|
||||||
|
canvas.style.left = "calc(50% - "+(w/2)+"px)";
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
|
||||||
|
ctx.fillStyle = 'black';
|
||||||
|
drawTinyText(ctx, "HELLO", 7, 7, "monospace");
|
||||||
|
|
||||||
|
</script>
|
130
poc_pages/wang_tiles_16.html
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Basic three.js template</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; }
|
||||||
|
canvas {
|
||||||
|
/* position: fixed; */
|
||||||
|
/* top: 450px;
|
||||||
|
left: 450px; */
|
||||||
|
/* transform: scale(4); */
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- import the three.js library -->
|
||||||
|
<img src="/images/wang_tilesets_32x32.png" style="display:none;" id="tiles_image">
|
||||||
|
<img src="/images/icons_14x14.png" data-tile-width="14" data-tile-height="14" style="display:none;" id="icons_img1">
|
||||||
|
<script src="/js/canvas_util.js"></script>
|
||||||
|
<!-- <script src="https://cdn.jsdelivr.net/npm/three@0.122.0/build/three.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/three@0.122.0/examples/js/controls/OrbitControls.js"></script> -->
|
||||||
|
<script>
|
||||||
|
// var scene = new THREE.Scene();
|
||||||
|
// var camera = new THREE.PerspectiveCamera(
|
||||||
|
// 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
|
||||||
|
// var renderer = new THREE.WebGLRenderer();
|
||||||
|
// renderer.setSize( window.innerWidth, window.innerHeight );
|
||||||
|
// document.body.appendChild( renderer.domElement );
|
||||||
|
// camera.position.z = 5;
|
||||||
|
|
||||||
|
// const controls = new THREE.OrbitControls( camera, renderer.domElement );
|
||||||
|
|
||||||
|
//var geometry = new THREE.BoxGeometry( 1, 1, 1 );
|
||||||
|
//var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
|
||||||
|
//var cube = new THREE.Mesh( geometry, material );
|
||||||
|
//scene.add( cube );
|
||||||
|
|
||||||
|
const data = `
|
||||||
|
10000000
|
||||||
|
00100000
|
||||||
|
01110010
|
||||||
|
00100010
|
||||||
|
00000010
|
||||||
|
00111000
|
||||||
|
00000010
|
||||||
|
00000000
|
||||||
|
`;
|
||||||
|
const ASSET_CACHE = {};
|
||||||
|
const TOOL_ARR = [
|
||||||
|
// {name:"Pencil",hotkey:"B",art:{img: icons_img1,idx:0}},
|
||||||
|
{name:"Eraser",hotkey:"E",art:{img: icons_img1,idx:1}},
|
||||||
|
{name:"Rectangle",hotkey:"R",art:{img: icons_img1,idx:2}},
|
||||||
|
{name:"Ellipse",hotkey:"S",art:{img: icons_img1,idx:2}},
|
||||||
|
{name:"Line",hotkey:"S",art:{img: icons_img1,idx:2}},
|
||||||
|
{name:"Line",hotkey:"S",art:{img: icons_img1,idx:2}},
|
||||||
|
];
|
||||||
|
window.onload = () => {
|
||||||
|
insertTools(TOOL_ARR);
|
||||||
|
const columns = 8;
|
||||||
|
const tilesetsArr = imageChopper(tiles_image,32,32);
|
||||||
|
const tilesetsCanvasArrArr = tilesetsArr.map((canvas)=>{
|
||||||
|
return imageChopper(canvas,8,8);
|
||||||
|
});
|
||||||
|
////console.log('tilesetsCanvasArrArr === ', tilesetsCanvasArrArr);
|
||||||
|
const data = `
|
||||||
|
10000000
|
||||||
|
00100000
|
||||||
|
01110010
|
||||||
|
00100010
|
||||||
|
00000010
|
||||||
|
00111000
|
||||||
|
00000010
|
||||||
|
00000000
|
||||||
|
`
|
||||||
|
.replaceAll("\n","")
|
||||||
|
.replaceAll(" ","")
|
||||||
|
.replaceAll("\t","")
|
||||||
|
;
|
||||||
|
////console.log('data === ',data);
|
||||||
|
let i = 0;
|
||||||
|
const width = columns;
|
||||||
|
const height = 8;
|
||||||
|
const tileIndexArr = [];
|
||||||
|
for(let i = 0; i < data.length;i++) {
|
||||||
|
// debugger;
|
||||||
|
const x = i%width;
|
||||||
|
const y = Math.floor(i/width);
|
||||||
|
const v = data[i];
|
||||||
|
let binNum = -1;
|
||||||
|
if(v === "1") {
|
||||||
|
// if(i === 10)debugger;
|
||||||
|
const n = y > 0 ? data[i-width] : "0";
|
||||||
|
const e = x < (width-1) ? data[i+1] : "0";
|
||||||
|
const s = data[i+width];
|
||||||
|
const w = i > 0 ? data[i-1] : "0";
|
||||||
|
const binStr = `${w}${s}${e}${n}`;
|
||||||
|
binNum = parseInt(binStr,2);
|
||||||
|
////console.log(v,`data[${i}]`,w,s,e,n,"= "+binNum);
|
||||||
|
}
|
||||||
|
tileIndexArr.push(binNum);
|
||||||
|
}
|
||||||
|
////console.log('tileIndexArr === ',tileIndexArr);
|
||||||
|
const retCanvas = tilesToCanvas(tileIndexArr,columns,tilesetsCanvasArrArr[1]);
|
||||||
|
////console.log('retCanvas === ',retCanvas);
|
||||||
|
document.body.appendChild(retCanvas);
|
||||||
|
}
|
||||||
|
function insertTools(arr){
|
||||||
|
arr.forEach((tool,i)=>{
|
||||||
|
if(!ASSET_CACHE[tool.art.img.src]){
|
||||||
|
ASSET_CACHE[tool.art.img.src] = imageChopper(
|
||||||
|
tool.art.img,
|
||||||
|
Number(tool.art.img.getAttribute("data-tile-width")),
|
||||||
|
Number(tool.art.img.getAttribute("data-tile-height"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tool.art.canvas = ASSET_CACHE[tool.art.img.src][tool.art.idx];
|
||||||
|
// const canvas = document.createElement('canvas');
|
||||||
|
// const w = 64;
|
||||||
|
// const h = 64;
|
||||||
|
// canvas.width = w;
|
||||||
|
// canvas.height = h;
|
||||||
|
// const ctx = canvas.getContext('2d');
|
||||||
|
const canvas = pixelButton(0,0,0,0,tool.art.canvas);
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
});
|
||||||
|
////console.log('ASSET_CACHE === ',ASSET_CACHE);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
20
server.js
@ -33,13 +33,25 @@ app.use('/', express.static(FULLBUILDPATH, {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//ROUTE - match / or any route with just numbers letters and dashes, and return index.htm (all other routes should have been handled already)
|
//ROUTE - match / or any route with just numbers letters and dashes, and return index.htm (all other routes should have been handled already)
|
||||||
app.get('/', (req, res, next) => {
|
app.get('/', (req, res, next) => {
|
||||||
console.log('root')
|
//console.log('root')
|
||||||
res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) {
|
res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) {
|
||||||
console.log('sent file');
|
//console.log('sent file');
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.get('/pixel-editor', (req, res, next) => {
|
||||||
|
//console.log('root')
|
||||||
|
res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) {
|
||||||
|
//console.log('sent file');
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.get('/pixel-editor/?:palette/?:resolution/?:patternWidth/?:patternBinStr', (req, res, next) => {
|
||||||
|
//console.log('root')
|
||||||
|
res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) {
|
||||||
|
//console.log('sent file');
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|