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)
This commit is contained in:
pxlvxl 2022-02-23 11:16:23 -05:00
parent 40299853ca
commit a1757553cf
122 changed files with 4976 additions and 466 deletions

View File

@ -24,12 +24,12 @@
height: 400px;
position: fixed;
display: none;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.64);
background-color: transparent;
}
#checkerboard {
z-index: 1;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.64);
}
#pixel-canvas {

View File

@ -0,0 +1,48 @@
.pixel-editor-screenshot {
-webkit-box-shadow: 0px 10px 40px 0px rgba(0, 0, 0, 0.45);
box-shadow: 0px 10px 40px 0px rgba(0, 0, 0, 0.45);
margin: auto;
display: block;
max-width: 100%;
border: solid 5px white;
}
hr {
margin: 4em 0 2em;
}
.contributors {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.contributors a {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
font-size: 0.7em;
text-align: center;
padding: 1em;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
color: #b3adb6;
}
.contributors a img {
width: 60px;
height: 60px;
}
.contributors a:hover {
background: #262327;
color: white;
}
/*# sourceMappingURL=pixel-editor-splash-page.css.map */

View File

@ -0,0 +1,9 @@
{
"version": 3,
"mappings": "AAEA,AAAA,wBAAwB,CAAC;EACvB,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,mBAAgB;EAC9C,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,KAAK;EACd,SAAS,EAAE,IAAI;EACf,MAAM,EAAE,eAAe;CACxB;;AAED,AAAA,EAAE,CAAC;EACF,MAAM,EAAE,SAAS;CACjB;;AAED,AAAA,aAAa,CAAC;EACb,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI;CAqBf;;AAvBD,AAIC,aAJY,CAIZ,CAAC,CAAC;EACD,OAAO,EAAE,IAAI;EACb,cAAc,EAAE,MAAM;EACtB,SAAS,EAAE,KAAK;EAChB,UAAU,EAAE,MAAM;EAClB,OAAO,EAAE,GAAG;EACZ,WAAW,EAAE,MAAM;EACnB,KAAK,EAAE,OAAwB;CAW/B;;AAtBF,AAYE,aAZW,CAIZ,CAAC,CAQA,GAAG,CAAC;EACH,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;CACZ;;AAfH,AAiBE,aAjBW,CAIZ,CAAC,AAaC,MAAM,CAAC;EACP,UAAU,EAAE,OAAsB;EAClC,KAAK,EAAE,KAAK;CACZ",
"sources": [
"pixel-editor-splash-page.scss"
],
"names": [],
"file": "pixel-editor-splash-page.css"
}

2025
css/pixel-editor.css Normal file

File diff suppressed because it is too large Load Diff

29
css/pixel-editor.css.map Normal file

File diff suppressed because one or more lines are too long

30
file_copier.js Normal file
View File

@ -0,0 +1,30 @@
const fs = require('fs');
const fileArr = walk("./",n => !n.includes("node_modules")
&& !n.includes(".git")
&& !n.includes(".vscode")
&& !n.includes("build")
);
const destPath = `C:/Users/jaman/OneDrive/Documents/GitHub/pixel-editor/`;
console.log('fileArr === ',fileArr);
fileArr.forEach(pathStr=>{
const fileStr = fs.readFileSync(pathStr, "utf8");
const destFilePathStr = destPath + pathStr;
console.log('destFilePathStr, fileStr.length === ',destFilePathStr, fileStr.length);
fs.writeFileSync(destFilePathStr, fileStr);
})
function walk(dir, condition = () => true ) {
// console.log('dir === ',dir);
let results = [];
for (let file of fs.readdirSync(dir)) {
file = dir + '/' + file;
const stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
results = results.concat(walk(file, condition));
} else {
if(condition(file))results.push(file);
}
}
return results;
}

52
hbs_parser.js Normal file
View 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)
);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 KiB

After

Width:  |  Height:  |  Size: 984 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 972 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 KiB

After

Width:  |  Height:  |  Size: 764 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 580 KiB

After

Width:  |  Height:  |  Size: 998 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 KiB

After

Width:  |  Height:  |  Size: 681 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 676 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 MiB

After

Width:  |  Height:  |  Size: 19 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 757 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
images/icons_14x14.png Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
images/le_button_18x19.png Normal file

Binary file not shown.

BIN
images/lospec_mock1.png Normal file

Binary file not shown.

BIN
images/lospec_mock2.png Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 167 KiB

BIN
images/sked_tree_32x32.png Normal file

Binary file not shown.

BIN
images/sked_x1.png Normal file

Binary file not shown.

BIN
images/sked_x10.png Normal file

Binary file not shown.

BIN
images/test_8x8.png Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -30,7 +30,7 @@ class Color {
this.hsv = Color.rgbToHsv(this.rgb);
break;
default:
console.error("Unsupported color mode " + fmt);
//console.error("Unsupported color mode " + fmt);
break;
}
}

View File

@ -57,7 +57,7 @@ const ColorModule = (() => {
if (newColorHex == colors[i].jscolor.toString()) {
//if the color isnt the one that has the picker currently open
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
duplicateColorWarning.style.visibility = 'visible';
@ -251,7 +251,7 @@ const ColorModule = (() => {
//loop through colors
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()) {
//set color to the color button
@ -346,7 +346,7 @@ const ColorModule = (() => {
* @param {*} paletteColors The colours of the palette
*/
function createColorPalette(paletteColors) {
console.log("creating palette");
////console.log("creating palette");
//remove current palette
while (colorsMenu.childElementCount > 1)
colorsMenu.children[0].remove();
@ -358,7 +358,7 @@ const ColorModule = (() => {
for (var i = 0; i < paletteColors.length; i++) {
var newColor = new Color("hex", paletteColors[i]);
var newColorElement = ColorModule.addColor(newColor.hex);
////console.log('newColor.hex === ',newColor.hex);
var newColRgb = newColor.rgb;
var lightestColorRgb = lightestColor.rgb;
@ -382,6 +382,8 @@ const ColorModule = (() => {
//set as current color
updateCurrentColor(darkestColor.hex);
////console.log('getCurrentPalette() === ',getCurrentPalette());
}
/** Creates the palette with the colours used in all the layers
@ -421,7 +423,7 @@ const ColorModule = (() => {
//create palette from colors array
createColorPalette(colorPaletteArray);
console.log("Done 2");
////console.log("Done 2");
}
function updateCurrentColor(color, refLayer) {

View File

@ -696,7 +696,7 @@ const ColorPicker = (() => {
}
break;
default:
console.log("How did you select the " + currentPickingMode + ", hackerman?");
////console.log("How did you select the " + currentPickingMode + ", hackerman?");
break;
}

View File

@ -5,7 +5,7 @@ const Dialogue = (() => {
let currentOpenDialogue = "";
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');
Events.onCustom("esc-pressed", closeDialogue);
@ -31,6 +31,9 @@ const Dialogue = (() => {
* @param {*} trackEvent Should I track the GA event?
*/
function showDialogue (dialogueName, trackEvent) {
if (typeof trackEvent === 'undefined') trackEvent = true;
// Updating currently open dialogue
@ -83,4 +86,4 @@ const Dialogue = (() => {
}
})();
console.log("Dialog: " + Dialogue);
////console.log("Dialog: " + Dialogue);

View File

@ -9,8 +9,10 @@ const EditorState = (() => {
return pixelEditorMode;
}
function switchMode(newMode) {
if (!firstFile && newMode == "Basic" && !confirm('Switching to basic mode will flatten all the visible layers. Are you sure you want to continue?')) {
function switchMode(newMode, skipConfirm = false) {
////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;
}
//switch to advanced mode

View File

@ -100,8 +100,9 @@ const Events = (() => {
*/
function on(event, elementId, functionCallback, ...args) {
//if element provided is string, get the actual element
if(!elementId)return;
const element = Util.getElement(elementId);
if(!element)return;
element.addEventListener(event,
function (e) {
functionCallback(...args, e);

View File

@ -2,11 +2,12 @@ class File {
// Canvas, canvas state
canvasSize = [];
zoom = 7;
canvasView = document.getElementById("canvas-view");
canvasView = document.getElementById("canvas-view") ?? document.createElement("canvas");
inited = false;
// Layers
layers = [];
sublayers = [];
currentLayer = undefined;
VFXLayer = undefined;
TMPLayer = undefined;
@ -129,11 +130,9 @@ class File {
// Save all imageDatas
for (let i=0; i<currFile.layers.length; i++) {
if (currFile.layers[i].hasCanvas()) {
imageDatas.push(currFile.layers[i].context.getImageData(
0, 0, currFile.canvasSize[0], currFile.canvasSize[1])
);
}
imageDatas.push(currFile.layers[i].context.getImageData(
0, 0, currFile.canvasSize[0], currFile.canvasSize[1])
);
}
// 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]) +
this.rcBorders.top + this.rcBorders.bottom;
console.trace();
console.log(currFile.canvasSize);
////console.trace();
////console.log(currFile.canvasSize);
// Resize the canvases
for (let i=0; i<currFile.layers.length; i++) {
currFile.layers[i].canvas.width = currFile.canvasSize[0];
currFile.layers[i].canvas.height = currFile.canvasSize[1];
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
currFile.checkerBoard.fillCheckerboard();
@ -213,16 +216,14 @@ class File {
// Putting all the data for each layer with the right offsets (decided by the pivot)
for (let i=0; i<currFile.layers.length; i++) {
if (currFile.layers[i].hasCanvas()) {
if (customData == undefined) {
currFile.layers[i].context.putImageData(imageDatas[copiedDataIndex], leftOffset, topOffset);
}
else {
currFile.layers[i].context.putImageData(customData[copiedDataIndex], 0, 0);
}
currFile.layers[i].updateLayerPreview();
copiedDataIndex++;
if (customData == undefined) {
currFile.layers[i].context.putImageData(imageDatas[copiedDataIndex], leftOffset, topOffset);
}
else {
currFile.layers[i].context.putImageData(customData[copiedDataIndex], 0, 0);
}
currFile.layers[i].updateLayerPreview();
copiedDataIndex++;
}
Dialogue.closeDialogue();
@ -246,7 +247,7 @@ class File {
this.rcPivot = "topleft";
// 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 pixelPosition;
@ -291,12 +292,10 @@ class File {
// Saving the data
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-right").value = this.rcBorders.right;
@ -430,11 +429,9 @@ class File {
// Get all the image datas
for (let i=0; i<currFile.layers.length; i++) {
if (currFile.layers[i].hasCanvas()) {
rsImageDatas.push(currFile.layers[i].context.getImageData(
0, 0, currFile.canvasSize[0], currFile.canvasSize[1])
);
}
rsImageDatas.push(currFile.layers[i].context.getImageData(
0, 0, currFile.canvasSize[0], currFile.canvasSize[1])
);
}
// event is null when the user is undoing
@ -450,13 +447,11 @@ class File {
// Put the image datas on the new canvases
for (let i=0; i<currFile.layers.length; i++) {
if (currFile.layers[i].hasCanvas()) {
currFile.layers[i].context.putImageData(
this.resizeImageData(rsImageDatas[layerIndex], newWidth, newHeight, this.currentAlgo), 0, 0
);
currFile.layers[i].updateLayerPreview();
layerIndex++;
}
currFile.layers[i].context.putImageData(
this.resizeImageData(rsImageDatas[layerIndex], newWidth, newHeight, this.currentAlgo), 0, 0
);
currFile.layers[i].updateLayerPreview();
layerIndex++;
}
// Updating start values when I finish scaling the sprite

View File

@ -1,3 +1,4 @@
// localStorage.setItem("lpe-cache",`{}`)
const FileManager = (() => {
// Binding the browse holder change event to file loading
@ -6,6 +7,7 @@ const FileManager = (() => {
Events.on('change', browseHolder, loadFile);
Events.on('change', browsePaletteHolder, loadPalette);
Events.on("click", "save-project-confirm", saveProject);
function openSaveProjectWindow() {
//create name
@ -20,7 +22,6 @@ const FileManager = (() => {
}
Util.setValue('lpe-file-name', fileName);
Events.on("click", "save-project-confirm", saveProject);
Dialogue.showDialogue('save-project', false);
}
@ -42,6 +43,7 @@ const FileManager = (() => {
function saveProject() {
// Get name
// debugger;
let fileName = Util.getValue("lpe-file-name") + ".lpe";
let selectedPalette = Util.getText('palette-button');
//set download link
@ -55,6 +57,9 @@ const FileManager = (() => {
if (typeof ga !== 'undefined')
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() {
@ -117,7 +122,39 @@ const FileManager = (() => {
//open file selection dialog
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() {
let fileName = document.getElementById("open-image-browse-holder").value;
// Getting the extension
@ -139,7 +176,6 @@ const FileManager = (() => {
browseHolder.value = null;
}
function openFile() {
//load file
var fileReader = new FileReader();
@ -165,59 +201,88 @@ const FileManager = (() => {
};
fileReader.readAsDataURL(browseHolder.files[0]);
}
function openProject() {
let file = browseHolder.files[0];
let reader = new FileReader();
function openProject(lpeData) {
// Getting all the data
reader.readAsText(file, "UTF-8");
// Converting the data to a json object and creating a new pixel (see _newPixel.js for more)
reader.onload = function (e) {
let dictionary = JSON.parse(e.target.result);
Startup.newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], dictionary);
for (let i=0; dictionary['color' + i] != null; i++) {
ColorModule.addColor(dictionary['color'+i]);
if(lpeData){
_parseLPE(lpeData);
} else {
let file = uri ?? browseHolder.files[0];
let reader = new FileReader();
reader.readAsText(file, "UTF-8");
// Converting the data to a json object and creating a new pixel (see _newPixel.js for more)
reader.onload = function (e) {
let dictionary = JSON.parse(e.target.result);
_parseLPE(dictionary);
}
// Removing the default colours
ColorModule.deleteColor(ColorModule.getCurrentPalette()[0]);
ColorModule.deleteColor(ColorModule.getCurrentPalette()[0]);
}
function _parseLPE(dictionary) {
Startup.newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], dictionary, true);
}
}
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() {
// use a dictionary
let dictionary = {};
// sorting layers by increasing z-index
let layersCopy = currFile.layers.slice();
layersCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1);
// save canvas size
let layersCopy = currFile.layers.filter(n=>!!n.menuEntry).slice();
// layersCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1);
dictionary['canvasWidth'] = currFile.canvasSize[0];
dictionary['canvasHeight'] = currFile.canvasSize[1];
// save editor mode
dictionary['editorMode'] = EditorState.getCurrentMode();
// save palette
for (let i=0; i<ColorModule.getCurrentPalette().length; i++) {
dictionary["color" + i] = ColorModule.getCurrentPalette()[i];
}
// save number of layers
dictionary["nLayers"] = layersCopy.length;
// save layers
for (let i=0; i<layersCopy.length; i++) {
// 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();
}
}
dictionary.colors = [
...ColorModule.getCurrentPalette()
];
dictionary.layers = layersCopy
.map((n,i)=>{
//console.log('n.name === ',n.name);
if(n.isSelected)dictionary.selectedLayer = i;
return {
...n,
src: n.canvas.toDataURL(),
};
});
return JSON.stringify(dictionary);
}
function loadPalette() {
if (browsePaletteHolder.files && browsePaletteHolder.files[0]) {
//make sure file is allowed filetype
@ -268,9 +333,45 @@ const FileManager = (() => {
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 {
loadFromLPE,
getProjectData,
localStorageReset,
localStorageCheck,
localStorageSave,
localStorageLoad,
upgradeLPE,
saveProject,
openProject,
exportProject,
openPixelExportWindow,
openSaveProjectWindow,

332
js/HBS_META_DATA.js Normal file
View File

@ -0,0 +1,332 @@
const HBS_META_DATA = {
"./views/colors-menu.hbs": {
"fileStr": "<ul id=\"colors-menu\">\n\t<li class=\"noshrink\"><button title=\"Add Current Color To Palette\" id=\"add-color-button\">{{svg \"./plus.svg\" width=\"30\" height=\"30\"}}</button></li>\n</ul>\n\n<div class=\"jscolor-picker-bottom\">\n\t<span>#</span><input type=\"text\" id=\"jscolor-hex-input\" />\n\t<div id=\"duplicate-color-warning\" title=\"Color is a duplicate of another in palette\">{{svg \"warning.svg\" width=\"14\"\n\t\theight=\"12\" }}</div>\n\t<button class=\"delete-color-button\">{{svg \"trash.svg\" width=\"20\" height=\"20\" }}</button>\n</div>\n\n<div class=\"color-edit-button\">\n\t{{svg \"adjust.svg\" width=\"20\" height=\"20\" }}\n</div>",
"filePath": "./views/colors-menu.hbs",
"dblCurlsArr": [
"svg \"./plus.svg\" width=\"30\" height=\"30\"",
"svg \"warning.svg\" width=\"14\"\n\t\theight=\"12\" ",
"svg \"trash.svg\" width=\"20\" height=\"20\" ",
"svg \"adjust.svg\" width=\"20\" height=\"20\" "
],
"partialArr": []
},
"./views/foobar.hbs": {
"fileStr": "<!DOCTYPE html>\r\n<html>\r\n\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <title>{{title}}</title>\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <link href=\"https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,700,900\" rel=\"stylesheet\">\r\n <link rel=\"stylesheet\" href=\"/pixel-editor.css\" />\r\n <meta name=\"ROBOTS\" content=\"NOINDEX, NOFOLLOW\">\r\n</head>\r\n\r\n<body oncontextmenu=\"return false;\">\r\n\t{{> canvases}}\r\n \r\n <div id=\"pop-up-container\">\r\n </div>\r\n\r\n <script src=\"/pixel-editor.js\"></script>\r\n {{#reload}}<script src=\"/reload/reload.js\"></script>{{/reload}}\r\n</body>\r\n</html>",
"filePath": "./views/foobar.hbs",
"dblCurlsArr": [
"title",
"> canvases",
"#reload",
"/reload"
],
"partialArr": [
"canvases"
]
},
"./views/index.hbs": {
"fileStr": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"UTF-8\">\n <title>{{title}}</title>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <link href=\"https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,700,900\" rel=\"stylesheet\">\n <link rel=\"stylesheet\" href=\"/pixel-editor.css\" />\n <meta name=\"ROBOTS\" content=\"NOINDEX, NOFOLLOW\">\n {{{google-analytics}}}\n {{{favicons}}}\n</head>\n\n<body oncontextmenu=\"return false;\">\n\t{{> compatibility-warning}}\n\t{{> preload}}\n\n\t{{> main-menu}}\n\t{{> tools-menu}}\n\t{{> colors-menu}}\n\t{{> layers-menu}}\n\n\t{{> tool-previews}}\n\t{{> canvases}}\n\t{{> holders}}\n \n <div id=\"pop-up-container\">\n\t\t{{> new-pixel}}\n\t\t{{> splash-page}}\n\t\t{{> sprite-resize}}\n\t\t{{> canvas-resize}}\n\t\t{{> palette}}\n\t\t{{> help}}\n\t\t{{> about}}\n\t\t{{> changelog}}\n\t\t{{> credits}}\n\t\t{{> settings}}\n\t\t{{> pixel-export}}\n\t\t{{> save-project}}\n </div>\n\n <script src=\"/pixel-editor.js\"></script>\n {{#reload}}<script src=\"/reload/reload.js\"></script>{{/reload}}\n</body>\n</html>",
"filePath": "./views/index.hbs",
"dblCurlsArr": [
"title",
"{google-analytics",
"{favicons",
"> compatibility-warning",
"> preload",
"> main-menu",
"> tools-menu",
"> colors-menu",
"> layers-menu",
"> tool-previews",
"> canvases",
"> holders",
"> new-pixel",
"> splash-page",
"> sprite-resize",
"> canvas-resize",
"> palette",
"> help",
"> about",
"> changelog",
"> credits",
"> settings",
"> pixel-export",
"> save-project",
"#reload",
"/reload"
],
"partialArr": [
"compatibility-warning",
"preload",
"main-menu",
"tools-menu",
"colors-menu",
"layers-menu",
"tool-previews",
"canvases",
"holders",
"new-pixel",
"splash-page",
"sprite-resize",
"canvas-resize",
"palette",
"help",
"about",
"changelog",
"credits",
"settings",
"pixel-export",
"save-project"
]
},
"./views/layers-menu.hbs": {
"fileStr": "<!-- LAYER MENU -->\n<ul id=\"layers-menu\">\n\t<li class = \"layers-menu-entry selected-layer\">\n\t\t<canvas class = \"preview-canvas\"></canvas>\n\t\t<ul class=\"layer-buttons\">\n\t\t\t<li class = \"layer-button\">\n\t\t\t\t<button title=\"Lock layer\" class=\"lock-layer-button\">\n\t\t\t\t\t{{svg \"unlockedpadlock.svg\" width=\"15\" height=\"15\" class = \"default-icon\"}}\n\t\t\t\t\t{{svg \"lockedpadlock.svg\" width=\"15\" height=\"15\" class = \"edited-icon\" display = \"none\"}}\n\t\t\t\t</button>\n\t\t\t</li>\n\t\t\t<li class = \"layer-button\">\n\t\t\t\t<button title=\"Show / hide layer\" class=\"hide-layer-button\">\n\t\t\t\t\t{{svg \"visible.svg\" width=\"15\" height=\"15\" class = \"default-icon\"}}\n\t\t\t\t\t{{svg \"invisible.svg\" width=\"15\" height=\"15\" class = \"edited-icon\" display = \"none\"}}\n\t\t\t\t</button>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<p>Layer 0<div class = \"gradient\"></div></p>\n\t</li>\n\n\t<li>\n\t\t<button id=\"add-layer-button\">\n\t\t\t{{svg \"plus.svg\" width=\"20\" height=\"20\"}} Add layer\n\t\t</button>\n\t</li>\n</ul>\n\n<ul id=\"layer-properties-menu\">\n\t<li>\n\t\t<button onclick = \"LayerList.renameLayer()\">Rename</button>\n\t</li>\n\t<li>\n\t\t<button onclick = \"LayerList.duplicateLayer()\">Duplicate</button>\n\t</li>\n\t<li>\n\t\t<button onclick = \"LayerList.deleteLayer()\">Delete</button>\n\t</li>\n\t<li>\n\t\t<button onclick = \"LayerList.merge()\">Merge below</button>\n\t</li>\n\t<li> \n\t\t<button onclick = \"LayerList.flatten(true)\">Flatten visible</button>\n\t</li>\n\t<li>\n\t\t<button onclick = \"LayerList.flatten(false)\">Flatten all</button>\n\t</li>\n</ul>",
"filePath": "./views/layers-menu.hbs",
"dblCurlsArr": [
"svg \"unlockedpadlock.svg\" width=\"15\" height=\"15\" class = \"default-icon\"",
"svg \"lockedpadlock.svg\" width=\"15\" height=\"15\" class = \"edited-icon\" display = \"none\"",
"svg \"visible.svg\" width=\"15\" height=\"15\" class = \"default-icon\"",
"svg \"invisible.svg\" width=\"15\" height=\"15\" class = \"edited-icon\" display = \"none\"",
"svg \"plus.svg\" width=\"20\" height=\"20\""
],
"partialArr": []
},
"./views/layout-contribute.hbs": {
"fileStr": "<!doctype html>\n\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n {{#title}}<title>{{this}}</title>{{/title}}\n {{#css}}<link rel=\"stylesheet\" href=\"{{this}}\">{{/css}}\n</head>\n\n<body>\n <section class=\"wrapper\">\n {{{body}}}\n </section>\n {{#js}}<script src=\"{{this}}\"></script>{{/js}}\n</body>\n</html>",
"filePath": "./views/layout-contribute.hbs",
"dblCurlsArr": [
"#title",
"this",
"/title",
"#css",
"this",
"/css",
"{body",
"#js",
"this",
"/js"
],
"partialArr": []
},
"./views/main-menu.hbs": {
"fileStr": "<ul id=\"main-menu\" class=\"editor-top-menu\">\n\t<li class=\"logo\">Lospec Pixel Editor</li>\n\t<li>\n\t\t<button>File</button>\n\t\t<ul>\n\t\t\t<li><button>New</button></li>\n\t\t\t<li><button>Save project</button></li>\n\t\t\t<li><button>Open</button></li>\n\t\t\t<li><button id=\"export-button\" class=\"disabled\">Export</button></li>\n\t\t\t<li><a href=\"https://lospec.com/pixel-editor\">Exit</a></li>\n\t\t</ul>\n\t</li>\n\t<li>\n\t\t<button>Edit</button>\n\t\t<ul>\n\t\t\t<li><button id=\"resize-canvas-button\" onclick = \"currFile.openResizeCanvasWindow()\">Resize canvas</button></li>\n\t\t\t<li><button id=\"resize-sprite-button\" onclick = \"currFile.openResizeSpriteWindow()\">Scale sprite</button></li>\n\t\t\t<li><button onclick = \"currFile.trimCanvas()\">Trim canvas</button></li>\n\t\t\t<li><button id=\"undo-button\" class=\"disabled\">Undo</button></li>\n\t\t\t<li><button id=\"redo-button\" class=\"disabled\">Redo</button></li>\n\t\t</ul>\n\t</li>\n\t<li>\n\t\t<button>View</button>\n\t\t<ul>\n\t\t\t<li><button id=\"toggle-pixelgrid-button\" onclick=\"currFile.pixelGrid.togglePixelGrid()\">Show pixel grid</button></li>\n\t\t</ul>\n\t</li>\n\t<li>\n\t\t<button id=\"layer-button\">Layer</button>\n\t\t<ul>\n\t\t\t<li><button onclick = \"LayerList.addLayer()\">New layer</button></li>\n\t\t\t<li><button onclick = \"LayerList.duplicateLayer()\">Duplicate</button></li>\n\t\t\t<li><button onclick = \"LayerList.renameLayer()\">Rename</button></li>\n\t\t\t<li><button onclick = \"LayerList.deleteLayer()\">Delete</button></li>\n\t\t\t<li><button onclick = \"LayerList.merge()\">Merge below</button></li>\n\t\t\t<li><button onclick = \"LayerList.flatten(false)\">Flatten all</button></li>\n\t\t\t<li><button onclick = \"LayerList.flatten(true)\">Flatten visible</button></li>\n\t\t\t\n\t\t</ul>\n\t</li>\n\t<li>\n\t\t<button>Selection</button>\n\t\t<ul>\n\t\t\t<li><button id=\"copy-button\">Copy</button></li>\n\t\t\t<li><button id=\"cut-button\">Cut</button></li>\n\t\t\t<li><button id=\"paste-button\">Paste</button></li>\n\t\t\t<li><button id=\"cancelSelection-button\">Cancel</button></li>\n\t\t</ul>\n\t</li>\n\t<li>\n\t\t<button>Editor</button>\n\t\t<ul>\n\t\t\t<li><button id=\"switch-mode-button\">Switch to basic mode</button></li>\n\t\t\t<li><button onclick=\"Dialogue.showDialogue('splash', false)\">Splash page</button></li>\n\t\t\t<li><button>Settings</button></li>\n\t\t</ul>\n\t</li>\n\n\t<li>\n\t\t<button>Help</button>\n\t\t<ul>\n\t\t\t<li><button>Help</button></li>\n\t\t\t<li><button>About</button></li>\n\t\t\t<li><button>Changelog</button></li>\n\t\t</ul>\n\t</li>\n\n\t<li id=\"editor-info\">\n\t\t<ul>\n\t\t\t<li><label>Tool size: <input type=\"number\"/></label></li>\n\t\t\t<li>{{> checkbox}}</li>\n\t\t</ul>\n\t</li>\n</ul>",
"filePath": "./views/main-menu.hbs",
"dblCurlsArr": [
"> checkbox"
],
"partialArr": [
"checkbox"
]
},
"./views/tools-menu.hbs": {
"fileStr": "<ul id=\"tools-menu\">\n\t<li class=\"selected expanded\">\n\t\t<button id=\"brush-button\">{{svg \"pencil.svg\" width=\"24\" height=\"24\"}}</button>\n\t\t<ul class=\"size-buttons\">\n\t\t\t<button title=\"Increase Brush Size\" id=\"brush-bigger-button\" class=\"tools-menu-sub-button\">{{svg \"plus.svg\" width=\"12\" height=\"12\"}}</button>\n\t\t\t<button title=\"Decrease Brush Size\" id=\"brush-smaller-button\" class=\"tools-menu-sub-button\">{{svg \"minus.svg\" width=\"12\" height=\"12\"}}</button>\n\t\t</ul>\n\t</li>\n\n\t<li class = \"expanded\">\n\t\t<button id=\"eraser-button\">{{svg \"eraser.svg\" width=\"24\" height=\"24\"}}</button>\n\t\t<ul class=\"size-buttons\">\n\t\t\t<button title=\"Increase Eraser Size\" id=\"eraser-bigger-button\" class=\"tools-menu-sub-button\">{{svg \"plus.svg\" width=\"12\" height=\"12\"}}</button>\n\t\t\t<button title=\"Decrease Eraser Size\" id=\"eraser-smaller-button\" class=\"tools-menu-sub-button\">{{svg \"minus.svg\" width=\"12\" height=\"12\"}}</button>\n\t\t</ul>\n\t</li>\n\n\t<li class=\"expanded\">\n\t\t<button id=\"rectangle-button\">{{svg \"rectangle.svg\" width=\"24\" height=\"24\" id=\"rectangle-empty-button-svg\"}}\n\t\t{{svg \"fullrect.svg\" width=\"24\" height=\"24\" id=\"rectangle-full-button-svg\" display = \"none\"}}</button>\n\t\t<ul class=\"size-buttons\">\n\t\t\t<button title=\"Increase Rectangle Size\" id=\"rectangle-bigger-button\" class=\"tools-menu-sub-button\">{{svg \"plus.svg\" width=\"12\" height=\"12\"}}</button>\n\t\t\t<button title=\"Decrease Rectangle Size\" id=\"rectangle-smaller-button\" class=\"tools-menu-sub-button\">{{svg \"minus.svg\" width=\"12\" height=\"12\"}}</button>\n\t\t</ul>\n\t</li>\n\n\t<li class=\"expanded\">\n\t\t<button id=\"ellipse-button\">\n\t\t\t{{svg \"ellipse.svg\" width=\"24\" height=\"24\" id=\"ellipse-empty-button-svg\"}}\n\t\t\t{{svg \"filledellipse.svg\" width=\"24\" height=\"24\" id=\"ellipse-full-button-svg\" display = \"none\"}}\n\t\t</button>\n\t\t<ul class=\"size-buttons\">\n\t\t\t<button title=\"Increase Ellipse Size\" id=\"ellipse-bigger-button\" class=\"tools-menu-sub-button\">{{svg \"plus.svg\" width=\"12\" height=\"12\"}}</button>\n\t\t\t<button title=\"Decrease Ellipse Size\" id=\"ellipse-smaller-button\" class=\"tools-menu-sub-button\">{{svg \"minus.svg\" width=\"12\" height=\"12\"}}</button>\n\t\t</ul>\n\t</li>\n\n\t<li class=\"expanded\">\n\t\t<button id=\"line-button\">{{svg \"line.svg\" width=\"24\" height=\"24\"}}</button>\n\t\t<ul class=\"size-buttons\">\n\t\t\t<button title=\"Increase Line Size\" id=\"line-bigger-button\" class=\"tools-menu-sub-button\">{{svg \"plus.svg\" width=\"12\" height=\"12\"}}</button>\n\t\t\t<button title=\"Decrease Line Size\" id=\"line-smaller-button\" class=\"tools-menu-sub-button\">{{svg \"minus.svg\" width=\"12\" height=\"12\"}}</button>\n\t\t</ul>\n\t</li>\n\n\t<li><button id=\"fill-button\">{{svg \"fill.svg\" width=\"24\" height=\"24\"}}</button></li>\n\n\t<li><button id=\"rectselect-button\">{{svg \"rectselect.svg\" width = \"24\" height = \"24\"}}</button><li>\n\n\t<li><button id=\"lassoselect-button\">{{svg \"lasso.svg\" width = \"26\" height = \"26\"}}</button></li>\n\n\t<li><button id=\"magicwand-button\">{{svg \"magicwand.svg\" width = \"26\" height = \"26\"}}</button></li>\n\n\t<li><button id=\"eyedropper-button\">{{svg \"eyedropper.svg\" width=\"24\" height=\"24\"}}</button></li>\n\n\t<li><button id=\"pan-button\">{{svg \"pan.svg\" width=\"24\" height=\"24\"}}</button></li>\n</ul>\n\n<div id=\"tool-tutorial\" class=\"fade-in\">\n\t<h3>Brush tool</h3>\n\t<ul>\n\t\t<li><span class=\"keyboard-key\">B</span> to select the tool</li>\n\t\t<li><span class=\"keyboard-key\">Left drag</span> to use the tool</li>\n\t\t<li><span class=\"keyboard-key\">Right drag</span> to change tool size</li>\n\t\t<li><span class=\"keyboard-key\">+</span> or <span class=\"keyboard-key\">-</span> to change tool size</li>\n\t</ul>\n\t<img src=\"brush-tutorial.gif\"/>\n</div>\"",
"filePath": "./views/tools-menu.hbs",
"dblCurlsArr": [
"svg \"pencil.svg\" width=\"24\" height=\"24\"",
"svg \"plus.svg\" width=\"12\" height=\"12\"",
"svg \"minus.svg\" width=\"12\" height=\"12\"",
"svg \"eraser.svg\" width=\"24\" height=\"24\"",
"svg \"plus.svg\" width=\"12\" height=\"12\"",
"svg \"minus.svg\" width=\"12\" height=\"12\"",
"svg \"rectangle.svg\" width=\"24\" height=\"24\" id=\"rectangle-empty-button-svg\"",
"svg \"fullrect.svg\" width=\"24\" height=\"24\" id=\"rectangle-full-button-svg\" display = \"none\"",
"svg \"plus.svg\" width=\"12\" height=\"12\"",
"svg \"minus.svg\" width=\"12\" height=\"12\"",
"svg \"ellipse.svg\" width=\"24\" height=\"24\" id=\"ellipse-empty-button-svg\"",
"svg \"filledellipse.svg\" width=\"24\" height=\"24\" id=\"ellipse-full-button-svg\" display = \"none\"",
"svg \"plus.svg\" width=\"12\" height=\"12\"",
"svg \"minus.svg\" width=\"12\" height=\"12\"",
"svg \"line.svg\" width=\"24\" height=\"24\"",
"svg \"plus.svg\" width=\"12\" height=\"12\"",
"svg \"minus.svg\" width=\"12\" height=\"12\"",
"svg \"fill.svg\" width=\"24\" height=\"24\"",
"svg \"rectselect.svg\" width = \"24\" height = \"24\"",
"svg \"lasso.svg\" width = \"26\" height = \"26\"",
"svg \"magicwand.svg\" width = \"26\" height = \"26\"",
"svg \"eyedropper.svg\" width=\"24\" height=\"24\"",
"svg \"pan.svg\" width=\"24\" height=\"24\""
],
"partialArr": []
},
"./views/components/checkbox.hbs": {
"fileStr": "<div class=\"checkbox-holder\">\n <div class=\"checkbox checked\">\n <label>Snap to grid</label>\n <input type=\"hidden\"/>\n <div class=\"box\">{{svg \"check\"}}</div>\n </div>\n</div>",
"filePath": "./views/components/checkbox.hbs",
"dblCurlsArr": [
"svg \"check\""
],
"partialArr": []
},
"./views/popups/about.hbs": {
"fileStr": "<div id=\"about\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\t<h1>About Lospec Pixel Editor</h1>\n\t<div>version 1.1.0</div>\n\t<p>This is a web-based tool for creating and editing pixel art.</p>\n\t<p>The goal of this tool is to be an accessible and intuitive tool that's simple enough for a first time pixel artist while still being usable enough for a veteran. </p>\n\t<p>In the future I hope to add enough features to become a full fledged pixel art editor, with everything an artist could need.</p>\n\t<h1>About Lospec</h1>\n\t<p>Lospec is a website created to host tools for pixel artists. To see more of our tools, visit our <a href=\"/\">homepage</a>. To hear about any updates or new tools, follow us on <a href=\"http://twitter.com/lospecofficial\">Twitter</a>.</p>\n</div>",
"filePath": "./views/popups/about.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\""
],
"partialArr": []
},
"./views/popups/canvas-resize.hbs": {
"fileStr": "<!--CANVAS RESIZE-->\n<div class=\"update\" id=\"resize-canvas\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\t<h1>Resize canvas</h1>\n\n\t<!--PIVOTS-->\n\t<span id=\"pivot-menu\">\n\t\t<button class=\"pivot-button\" value=\"topleft\">{{svg \"arrows/topleft.svg\" width=\"20\" height=\"20\"}}</button>\n\t\t<button class=\"pivot-button\" value=\"top\">{{svg \"arrows/top.svg\" width=\"20\" height=\"20\"}}</button>\n\t\t<button class=\"pivot-button\" value=\"topright\">{{svg \"arrows/topright.svg\" width=\"20\" height=\"20\"}}</button>\n\t\t<button class=\"pivot-button\" value=\"left\">{{svg \"arrows/left.svg\" width=\"20\" height=\"20\"}}</button>\n\t\t<button class=\"pivot-button rc-selected-pivot\" value=\"middle\">{{svg \"arrows/middle.svg\" width=\"20\" height=\"20\"}}</button>\n\t\t<button class=\"pivot-button\" value=\"right\">{{svg \"arrows/right.svg\" width=\"20\" height=\"20\"}}</button>\n\t\t<button class=\"pivot-button\" value=\"bottomleft\">{{svg \"arrows/bottomleft.svg\" width=\"20\" height=\"20\"}}</button>\n\t\t<button class=\"pivot-button\" value=\"bottom\">{{svg \"arrows/bottom.svg\" width=\"20\" height=\"20\"}}</button>\n\t\t<button class=\"pivot-button\" value=\"bottomright\">{{svg \"arrows/bottomright.svg\" width=\"20\" height=\"20\"}}</button>\n\t</span>\n\t<!-- SIZE-->\n\t<span id=\"rc-size-menu\">\n\t\t<h2>Size</h2>\n\t\t<div>\n\t\t\t<span>\n\t\t\t\tWidth: <input id=\"rc-width\" type=\"number\" default=\"0\" step=\"1\" \n\t\t\t\tvalue=\"{{#if border}}{{border}}{{else}}0{{/if}}\" autocomplete=\"off\"/>\n\t\t\t</span>\n\t\t\t\n\t\t\t<span>\n\t\t\t\tHeight: <input id=\"rc-height\" default=\"0\" step=\"1\" type=\"number\" \n\t\t\t\tvalue=\"{{#if border}}{{border}}{{else}}0{{/if}}\" autocomplete=\"off\"/>\n\t\t\t</span>\n\t\t</div>\n\t</span> \n\t<!--BORDERS-->\n\t<span id=\"borders-menu\">\n\t\t<h2>Borders offsets</h2>\n\t\t<div>\n\t\t\t<span>\n\t\t\t\tLeft: <input id=\"rc-border-left\" type=\"number\" default=\"0\" step=\"1\" \n\t\t\t\tvalue=\"{{#if border}}{{border}}{{else}}0{{/if}}\" autocomplete=\"off\"/>\n\t\t\t</span>\n\t\t\t\n\t\t\t<span>\n\t\t\t\tRight: <input id=\"rc-border-right\" type=\"number\" default=\"0\" step=\"1\" \n\t\t\t\tvalue=\"{{#if border}}{{border}}{{else}}0{{/if}}\" autocomplete=\"off\"/>\n\t\t\t</span>\n\t\t\t\n\t\t\t<span>\n\t\t\t\tTop: <input id=\"rc-border-top\" type=\"number\" default=\"0\" step=\"1\" \n\t\t\t\tvalue=\"{{#if border}}{{border}}{{else}}0{{/if}}\" autocomplete=\"off\"/>\n\t\t\t</span>\n\t\t\t\n\t\t\t<span>\n\t\t\t\tBottom: <input id=\"rc-border-bottom\" default=\"0\" step=\"1\" type=\"number\" \n\t\t\t\tvalue=\"{{#if border}}{{border}}{{else}}0{{/if}}\" autocomplete=\"off\"/>\n\t\t\t</span>\n\t\t</div>\n\t\t<button id=\"resize-canvas-confirm\">Resize canvas</button>\n\t</span> \n</div>",
"filePath": "./views/popups/canvas-resize.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\"",
"svg \"arrows/topleft.svg\" width=\"20\" height=\"20\"",
"svg \"arrows/top.svg\" width=\"20\" height=\"20\"",
"svg \"arrows/topright.svg\" width=\"20\" height=\"20\"",
"svg \"arrows/left.svg\" width=\"20\" height=\"20\"",
"svg \"arrows/middle.svg\" width=\"20\" height=\"20\"",
"svg \"arrows/right.svg\" width=\"20\" height=\"20\"",
"svg \"arrows/bottomleft.svg\" width=\"20\" height=\"20\"",
"svg \"arrows/bottom.svg\" width=\"20\" height=\"20\"",
"svg \"arrows/bottomright.svg\" width=\"20\" height=\"20\"",
"#if border",
"border",
"else",
"/if",
"#if border",
"border",
"else",
"/if",
"#if border",
"border",
"else",
"/if",
"#if border",
"border",
"else",
"/if",
"#if border",
"border",
"else",
"/if",
"#if border",
"border",
"else",
"/if"
],
"partialArr": []
},
"./views/popups/changelog.hbs": {
"fileStr": "<div id=\"changelog\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\n\t<h1>Changelog</h1>\n\t{{#each changelog}}\n\t\t<h2>Version {{@key}}</h2>\n\t\t<ul>{{#each this}}\n\t\t\t<li>{{change}} <span class=\"weak\">- {{author}}</span></li>\n\t\t{{/each}}</ul>\n\t{{/each}}\n</div>",
"filePath": "./views/popups/changelog.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\"",
"#each changelog",
"@key",
"#each this",
"change",
"author",
"/each",
"/each"
],
"partialArr": []
},
"./views/popups/credits.hbs": {
"fileStr": "<div id=\"credits\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\t<h1>Credits</h1>\n\t<h2>Icons</h2>\n\t<ul>\n\t\t<li><div>Icons made by <a href=\"http://www.freepik.com\" title=\"Freepik\">Freepik</a> from <a href=\"http://www.flaticon.com\" title=\"Flaticon\">www.flaticon.com</a> is licensed by <a href=\"http://creativecommons.org/licenses/by/3.0/\" title=\"Creative Commons BY 3.0\" target=\"_blank\">CC 3.0 BY</a></div></li>\n\t\t<li><div>Font Awesome by Dave Gandy - <a href=\"http://fontawesome.io\">http://fontawesome.io</a></div></li>\n\t\t<li><div>Icons made by <a href=\"http://www.flaticon.com/authors/those-icons\" title=\"Those Icons\">Those Icons</a> from <a href=\"http://www.flaticon.com\" title=\"Flaticon\">www.flaticon.com</a> is licensed by <a href=\"http://creativecommons.org/licenses/by/3.0/\" title=\"Creative Commons BY 3.0\" target=\"_blank\">CC 3.0 BY</a></div></li>\n\t</ul>\n</div>",
"filePath": "./views/popups/credits.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\""
],
"partialArr": []
},
"./views/popups/help.hbs": {
"fileStr": "<div id=\"help\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\t<h1>Help</h1>\n\t<h2>Palette</h2>\n\t<ul>\n\t\t<li>Left Click - Choose Color</li>\n\t\t<li>Right Click - Edit Color</li>\n\t</ul>\n\t<h2>Hotkeys</h2>\n\t<ul>\n\t\t<li><strong>Pencil:</strong> <span class=\"keyboard-key\">B</span> or <span class=\"keyboard-key\">1</span></li>\n\t\t<li><strong>Eraser:</strong> <span class=\"keyboard-key\">R</span></li>\n\t\t<li><strong>Rectangle:</strong> <span class=\"keyboard-key\">U</span></li>\n\t\t<li><strong>Line:</strong> <span class=\"keyboard-key\">L</span></li>\n\t\t<li><strong>Fill:</strong> <span class=\"keyboard-key\">F</span> or <span class=\"keyboard-key\">2</span></li>\n\t\t<li><strong>Eyedropper:</strong> <span class=\"keyboard-key\">E</span> or <span class=\"keyboard-key\">3</span></li>\n\t\t<li><strong>Pan:</strong> <span class=\"keyboard-key\">P</span> or <span class=\"keyboard-key\">M</span> or <span class=\"keyboard-key\">4</span></li>\n\t\t<li><strong>Zoom:</strong> <span class=\"keyboard-key\">Z</span> or <span class=\"keyboard-key\">5</span></li>\n\t\t<li><strong>Undo:</strong> Ctrl + <span class=\"keyboard-key\">Z</span></li>\n\t\t<li><strong>Redo:</strong> Ctrl + <span class=\"keyboard-key\">Y</span> or Ctrl + Alt + <span class=\"keyboard-key\">Z</span></li>\n\t\t<li><strong>Rectangular selection:</strong> <span class=\"keyboard-key\">M</span></li>\n\t</ul>\n\t<h2>Mouse Shortcuts</h2>\n\t<ul>\n\t\t<li><strong>Eyedropper: </strong>Alt + Click</li>\n\t\t<li><strong>Pan: </strong>Space + Click</li>\n\t\t<li><strong>Zoom: </strong>Alt + Scroll Wheel</li>\n\t</ul>\n\t<h2>Layers</h2>\n\t<ul>\n\t\t<li>{{svg \"visible.svg\" width=\"15\" height=\"15\" class = \"default-icon\"}}: show / hide layer</li>\n\t\t<li>{{svg \"lockedpadlock.svg\" width=\"15\" height=\"15\" class = \"default-icon\"}}: lock / unlock layer, when a layer is locked it's not possible to draw on it</li>\n\t\t<li>Right click on a layer to open the <strong>menu</strong>:\n\t\t\t<ul>\n\t\t\t\t<li><strong>Rename:</strong> change the name of the layer</li>\n\t\t\t\t<li><strong>Duplicate:</strong> duplicate the layer</li>\n\t\t\t\t<li><strong>Delete:</strong> delete the layer (doesn't work if there's only one layer)</li>\n\t\t\t\t<li><strong>Merge below:</strong> merges the selected the layer with the one below it</li>\n\t\t\t\t<li><strong>Flatten visible:</strong> merges all the visible layers</li>\n\t\t\t\t<li></strong>Flatten all:</strong> merges all the layers</li>\n\t\t\t</ul>\n\t\t</li>\n\t</ul>\n</div>",
"filePath": "./views/popups/help.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\"",
"svg \"visible.svg\" width=\"15\" height=\"15\" class = \"default-icon\"",
"svg \"lockedpadlock.svg\" width=\"15\" height=\"15\" class = \"default-icon\""
],
"partialArr": []
},
"./views/popups/new-pixel.hbs": {
"fileStr": "<!-- NEW PIXEL -->\n<div id=\"new-pixel\" class=\"update\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\t<h1>New Pixel</h1>\n\n\t<!-- Preset-->\n\t<h2>Preset</h2>\n\t<button id=\"preset-button\" class=\"dropdown-button\">Choose a preset...</button>\n\t<div id=\"preset-menu\" class=\"dropdown-menu\"></div>\n\n\t<h2>Size</h2>\n\t<input id=\"size-width\" value=\"{{#if width}}{{width}}{{else}}64{{/if}}\" autocomplete=\"off\" />{{svg \"x.svg\" width=\"16\" height=\"16\" class=\"dimentions-x\"}}<input id=\"size-height\" value=\"{{#if height}}{{height}}{{else}}64{{/if}}\" autocomplete=\"off\" />\n\t<h2>Palette</h2>\n\t<button id=\"palette-button\" class=\"dropdown-button\">Choose a palette...</button>\n\t<div id=\"palette-menu\" class=\"dropdown-menu\"><button id=\"no-palette-button\">Empty Palette</button><button id=\"load-palette-button\">Load palette...</button></div>\n\n\t<div id=\"new-pixel-warning\">Creating a new pixel will discard your current one.</div>\n\t<div>\n\t\t<button id=\"create-button\" class=\"default\">Create</button>\n\t</div>\n</div>",
"filePath": "./views/popups/new-pixel.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\"",
"#if width",
"width",
"else",
"/if",
"svg \"x.svg\" width=\"16\" height=\"16\" class=\"dimentions-x\"",
"#if height",
"height",
"else",
"/if"
],
"partialArr": []
},
"./views/popups/palette.hbs": {
"fileStr": "<!-- PALETTE -->\n<div id=\"palette-block\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\t\n\t<h1>Edit palette</h1>\n\t\n\t<div id=\"colour-picker\">\n\t\t<div id=\"cp-modes\">\n\t\t\t<button id=\"cp-rgb\" class=\"cp-selected-mode\">RGB</button>\n\t\t\t<button id=\"cp-hsv\">HSV</button>\n\t\t\t<button id=\"cp-hsl\">HSL</button>\n\t\t\t\n\t\t\t<div id=\"cp-colour-preview\" class=\"cp-colour-preview\"></div>\n\t\t\t<input id=\"cp-hex\" type=\"text\" value=\"#123456\"/>\n\t\t</div>\n\n\t\t<div id=\"sliders-container\">\n\t\t\t<div class = \"cp-slider-entry\">\n\t\t\t\t<label for = \"first-slider\">R</label>\n\t\t\t\t<input type=\"range\" min=\"0\" max=\"255\" class=\"colour-picker-slider\" id=\"first-slider\"/>\n\t\t\t\t<input type = \"text\" value = \"128\" id=\"cp-sliderText1\"/>\n\t\t\t</div>\n\n\t\t\t<div class = \"cp-slider-entry\">\n\t\t\t\t<label for = \"second-slider\">G</label>\n\t\t\t\t<input type=\"range\" min=\"0\" max =\"255\" class=\"colour-picker-slider\" id=\"second-slider\"/>\n\t\t\t\t<input type = \"text\" value = \"128\" id=\"cp-sliderText2\"/>\n\t\t\t</div>\n\n\t\t\t<div class = \"cp-slider-entry\">\n\t\t\t\t<label for = \"third-slider\">B</label>\n\t\t\t\t<input type=\"range\" min = \"0\" max = \"255\" class = \"colour-picker-slider\" id=\"third-slider\"/>\n\t\t\t\t<input type = \"text\" value = \"128\" id=\"cp-sliderText3\"/>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div id=\"cp-minipicker\">\n\t\t\t<input type = \"range\" min = \"0\" max = \"100\" id=\"cp-minipicker-slider\"/>\n\t\t\t<div id=\"cp-canvas-container\">\n\t\t\t\t<canvas id=\"cp-spectrum\"></canvas>\n\t\t\t\t<div id=\"cp-active-icon\" class=\"cp-picker-icon\"></div>\n\t\t\t</div>\n\n\t\t\t<div id=\"cp-colours-previews\">\n\t\t\t\t<div class = \"cp-colour-preview\">\n\t\t\t\t\t#123456\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div id=\"cp-colour-picking-modes\">\n\t\t\t\t<button id=\"cp-mono\" class=\"cp-selected-mode\">Mono</button>\n\t\t\t\t<button id=\"cp-analog\">Nlgs</button>\n\t\t\t\t<button id=\"cp-cmpt\">Cmpt</button>\n\t\t\t\t<button id=\"cp-tri\">Tri</button>\n\t\t\t\t<button id=\"cp-scmpt\">Scm</button>\n\t\t\t\t<button id=\"cp-tetra\">Tetra</button>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\n\t<div id=\"palette-container\">\n\t\t<ul id=\"palette-list\">\n\t\t\t<li style = \"background-color:rgb(255,0,0);width:40px;height:40px;\" onmousedown=\"PaletteBlock.startRampSelection(event)\"\n\t\t\tonmousemove=\"PaletteBlock.updateRampSelection(event)\" onmouseup=\"PaletteBlock.endRampSelection(event)\"></li>\n\t\t\t<li style = \"background-color:rgb(0,255,0);width:40px;height:40px;\"onmousedown=\"PaletteBlock.startRampSelection(event)\"\n\t\t\tonmousemove=\"PaletteBlock.updateRampSelection(event)\" onmouseup=\"PaletteBlock.endRampSelection(event)\"></li>\n\t\t</ul>\n\t</div>\n\n\t<div id=\"pb-options\">\n\t\t<button title=\"Add colours to palette\" id=\"pb-addcolours\">Add colours</button>\n\t\t<button title=\"Remove colours from palette\" id=\"pb-removecolours\">Remove colours</button>\n\t</div>\n</div>",
"filePath": "./views/popups/palette.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\""
],
"partialArr": []
},
"./views/popups/pixel-export.hbs": {
"fileStr": "<div id=\"export\" class=\"pixel-export\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\t\n\t<h1>Export File</h1>\n\n\t<div class=\"export-configuration\">\n\t\t<h2>File Name</h2>\n\t\t<input id=\"export-file-name\" autocomplete=\"off\"/>\n\t</div>\n\n\t<div class=\"popup-actions\">\n\t\t<button class=\"default\" id=\"export-confirm\">Export</button>\n\t</div>\n</div>",
"filePath": "./views/popups/pixel-export.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\""
],
"partialArr": []
},
"./views/popups/save-project.hbs": {
"fileStr": "<div id=\"save-project\" class=\"save-project\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\t\n\t<h1>Save Project</h1>\n\n\t<div class=\"save-project-configuration\">\n\t\t<h2>File Name</h2>\n\t\t<input id=\"lpe-file-name\" autocomplete=\"off\" />\n\t</div>\n\n\t<div class=\"popup-actions\">\n\t\t<button class=\"default\" id=\"save-project-confirm\">Save</button>\n\t</div>\n</div>",
"filePath": "./views/popups/save-project.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\""
],
"partialArr": []
},
"./views/popups/settings.hbs": {
"fileStr": "<div id=\"settings\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\t<h1>Settings</h1>\n\n\t<div id=\"settings-container\">\n\t\t<h2>History</h2>\n\t\t<div class = \"settings-entry\">\n\t\t\t<label for=\"setting-numberOfHistoryStates\">Number of History States</label> <input id=\"setting-numberOfHistoryStates\" value=\"200\" autocomplete=\"off\" />\n\t\t</div>\n\n\t\t<h2>Pixel grid</h2>\n\t\t<div class = \"settings-entry\">\n\t\t\t<label for=\"setting-pixelGridColour\">Colour of the pixel grid</label><input id=\"setting-pixelGridColour\" value = \"#0000FF\" autocomplete=\"off\"/>\n\t\t</div>\n\t</div>\n\n\t<p id=\"cookies-disabled-warning\">Your browsers cookies are disabled, settings will be lost upon closing this page.</p>\n\n\t<div>\n\t\t<button id=\"save-settings\" class=\"default\">Save</button>\n\t</div>\n</div>",
"filePath": "./views/popups/settings.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\""
],
"partialArr": []
},
"./views/popups/splash-page.hbs": {
"fileStr": "<!-- Splash page -->\n<div id=\"splash\">\n\t<div id=\"splash-news\">\n\t\t\t<div id=\"latest-update\">\n\t\t\t<h1>Latest updates</h1>\n\n\t\t\t{{> latestLog}}\n\t\t</div>\n\t</div>\n\n\t<div id=\"splash-input\">\n\t\t<div id=\"editor-logo\">\n\t\t\t<div id=\"black\">\n\t\t\t\t<div id=\"sp-coverdata\">\n\t\t\t\t\t<img src=\"https://cdn.lospec.com/static/brand/lospec_logo_3x.png\"/> pixel editor\n\t\t\t\t\t<p>Version 1.4.0</p>\n\t\t\t\t\t<a href=\"https://cdn.discordapp.com/attachments/506277390050131978/795660870221955082/final.png\">Art by Unsettled</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div class=\"splash-menu\">\n\t\t\t<div id=\"sp-newpixel\">\n\t\t\t\t<h1>New Custom Pixel</h1>\n\n\t\t\t\t<h2>Size</h2>\n\t\t\t\t<div class=\"sp-np-entry\">\n\t\t\t\t\t<input id=\"size-width-splash\" value=\"{{#if width}}{{width}}{{else}}64{{/if}}\" autocomplete=\"off\" />{{svg \"x.svg\" width=\"16\" height=\"16\" class=\"dimentions-x\"}}<input id=\"size-height-splash\" value=\"{{#if height}}{{height}}{{else}}64{{/if}}\" autocomplete=\"off\" />\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t<h2>Palette</h2>\n\t\t\t\t<button id=\"palette-button-splash\" class=\"dropdown-button\">Choose a palette...</button>\n\t\t\t\t<div id=\"palette-menu-splash\" class=\"dropdown-menu\"><button id=\"load-palette-button-splash\">Load palette...</button></div>\n\n\t\t\t\t<div id=\"new-pixel-warning\">Creating a new pixel will discard your current one.</div>\n\t\t\t\t<div class=\"sp-np-entry\">\n\t\t\t\t\t<button id=\"create-button-splash\" class=\"default\">Create</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\n\t\t\t<div id=\"sp-quickstart-container\">\n\t\t\t\t<div id=\"sp-quickstart-title\">\n\t\t\t\t\tQuickstart\n\t\t\t\t</div>\n\n\t\t\t\t<div id=\"sp-quickstart\">\n\t\t\t\t\t<div class=\"sp-template\" onclick=\"document.getElementById('open-image-browse-holder').click()\"><p>Load</p></div>\n\t\t\t\t\t<div class=\"sp-template\" onclick=\"Startup.newFromTemplate('Gameboy Color')\"><p><span>New</span> Gameboy</p></div>\n\t\t\t\t\t<div class=\"sp-template\" onclick=\"Startup.newFromTemplate('Commodore 64')\"><p><span>New</span> C64</p></div>\n\t\t\t\t\t<div class=\"sp-template\" onclick=\"Startup.newFromTemplate('PICO-8')\"><p><span>New</span> Pico8</p></div>\n\t\t\t\t\t<div class=\"sp-template\" onclick=\"Startup.newFromTemplate('',16,16)\"><p><span>New</span> 16x16</p></div>\n\t\t\t\t\t<div class=\"sp-template\" onclick=\"Startup.newFromTemplate('',32,32)\"><p><span>New</span> 32x32</p></div>\n\t\t\t\t\t<div class=\"sp-template\" onclick=\"Startup.newFromTemplate('',64,64)\"><p><span>New</span> 64x64</p></div>\n\t\t\t\t\t<div class=\"sp-template\" onclick=\"Startup.newFromTemplate('',128,128)\"><p><span>New</span> 128x128</p></div>\n\t\t\t\t\t<div class=\"sp-template\" onclick=\"Startup.newFromTemplate('',256,256)\"><p><span>New</span> 256x256</p></div>\n\t\t\t\t\t<div class=\"sp-template\" onclick=\"Startup.newFromTemplate('',512,512)\"><p><span>New</span> 512x512</p></div>\n\t\t\t\t</div>\n\n\t\t\t\t<div class=\"mode-switcher\">\n\t\t\t\t\t<span class=\"basic\">You are using Basic Mode.</span> \n\t\t\t\t\t<span class=\"advanced\">You are using Advanced Mode.</span> \n\t\t\t\t\t<a id=\"switch-editor-mode-splash\" href=\"#\">\n\t\t\t\t\t\t<span class=\"basic\">Switch to Advanced Mode.</span> \n\t\t\t\t\t\t<span class=\"advanced\">Switch to using Basic Mode.</span> \n\t\t\t\t\t\t»\n\t\t\t\t\t\t</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</div>",
"filePath": "./views/popups/splash-page.hbs",
"dblCurlsArr": [
"> latestLog",
"#if width",
"width",
"else",
"/if",
"svg \"x.svg\" width=\"16\" height=\"16\" class=\"dimentions-x\"",
"#if height",
"height",
"else",
"/if"
],
"partialArr": [
"latestLog"
]
},
"./views/popups/sprite-resize.hbs": {
"fileStr": "<!--SPRITE RESIZE-->\n<div class=\"update\" id=\"resize-sprite\">\n\t<button class=\"close-button\">{{svg \"x.svg\" width=\"20\" height=\"20\"}}</button>\n\t<h1>Scale sprite</h1>\n\t<!-- SIZE-->\n\t<h2>New size</h2>\n\t<span id=\"rs-size-menu\">\n\t\t<div>\n\t\t\t<span>\n\t\t\t\tWidth: <input id=\"rs-width\" type=\"number\" default=\"0\" step=\"1\" \n\t\t\t\tvalue=\"{{#if border}}{{border}}{{else}}0{{/if}}\" autocomplete=\"off\"/>\n\t\t\t</span>\n\t\t\t\n\t\t\t<span>\n\t\t\t\tHeight: <input id=\"rs-height\" default=\"0\" step=\"1\" type=\"number\" \n\t\t\t\tvalue=\"{{#if border}}{{border}}{{else}}0{{/if}}\" autocomplete=\"off\"/>\n\t\t\t</span>\n\t\t</div>\n\t</span> \n\t<!--BORDERS-->\n\t<h2>Resize percentages</h2>\n\t<span id=\"rs-percentage-menu\">\n\t\t<div>\n\t\t\t<span>\n\t\t\t\tWidth <input id=\"rs-width-percentage\" type=\"number\" default=\"0\" step=\"1\" \n\t\t\t\tvalue=\"{{#if border}}{{border}}{{else}}0{{/if}}\" autocomplete=\"off\"/> %\n\t\t\t</span>\n\t\t\t\n\t\t\t<span>\n\t\t\t\tHeight <input id=\"rs-height-percentage\" type=\"number\" default=\"0\" step=\"1\" \n\t\t\t\tvalue=\"{{#if border}}{{border}}{{else}}0{{/if}}\" autocomplete=\"off\"/> %\n\t\t\t</span>\n\t\t</div>\n\t\t<div id=\"rs-ratio-div\">\n\t\t\t<span>\n\t\t\t\tKeep current ratio <input type = \"checkbox\" id=\"rs-keep-ratio\"/>\n\t\t\t</span>\n\t\t\t<span>\n\t\t\t\tScaling algorithm:\n\t\t\t\t<select name = \"resize-algorithm\" id=\"resize-algorithm-combobox\">\n\t\t\t\t\t<option value = \"nearest-neighbor\">Nearest neighbour</option>\n\t\t\t\t\t<option value = \"bilinear-interpolation\">Bilinear</option>\n\t\t\t\t</select>\n\t\t\t</span>\n\t\t\t</br>\n\t\t\t<button id=\"resize-sprite-confirm\">Scale sprite</button>\n\t\t</div>\n\t</span> \n</div>",
"filePath": "./views/popups/sprite-resize.hbs",
"dblCurlsArr": [
"svg \"x.svg\" width=\"20\" height=\"20\"",
"#if border",
"border",
"else",
"/if",
"#if border",
"border",
"else",
"/if",
"#if border",
"border",
"else",
"/if",
"#if border",
"border",
"else",
"/if"
],
"partialArr": []
}
}

View File

@ -38,12 +38,12 @@ const History = (() => {
}
function undo () {
console.log("undoing");
////console.log("undoing");
undoOrRedo('undo');
}
function redo () {
console.log("redoing");
////console.log("redoing");
undoOrRedo('redo');
}
@ -186,13 +186,13 @@ class HistoryState {
this.nFlattened = nFlattened;
this.undo = function() {
for (let i=0; i<this.nFlattened - nAppLayers; i++) {
for (let i=0; i<this.nFlattened; i++) {
undo();
}
};
this.redo = function() {
for (let i=0; i<this.nFlattened - nAppLayers; i++) {
for (let i=0; i<this.nFlattened; i++) {
redo();
}
};
@ -241,7 +241,9 @@ class HistoryState {
this.undo = function() {
addedLayer.selectLayer();
LayerList.deleteLayer(false);
if (currFile.layers.length != 4) {//TODO: repent and rebirth lol
LayerList.deleteLayer(false);
}
};
this.redo = function() {
@ -268,7 +270,9 @@ class HistoryState {
this.redo = function() {
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.undo = function() {
if (currFile.layers.length - nAppLayers > this.index + 1) {
if (currFile.layers.length > this.index + 1) {
currFile.layers[this.index + 1].selectLayer();
}
else {
@ -412,7 +416,7 @@ class HistoryState {
//find new color in palette and change it back to old color
let colors = document.getElementsByClassName('color-button');
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()) {
colors[i].jscolor.fromString(oldColorValue);
break;
@ -429,7 +433,7 @@ class HistoryState {
//find old color in palette and change it back to new color
let colors = document.getElementsByClassName('color-button');
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()) {
colors[i].jscolor.fromString(newColorValue);
break;

View File

@ -163,7 +163,7 @@ const Input = (() => {
spacePressed = true;
break;
case 46:
console.log("Pressed del");
////console.log("Pressed del");
Events.emit("del");
break;
}

View File

@ -1,46 +1,36 @@
const LayerList = (() => {
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 dragStartLayer;
// Binding the right click menu
Events.on("mousedown", layerList, openOptionsMenu);
// Binding the add layer button to the right function
Events.on('click',"add-layer-button", addLayer, false);
// Listening to the switch mode event so I can change the layout
Events.onCustom("switchedToAdvanced", showMenu);
Events.onCustom("switchedToBasic", hideMenu);
Events.onCustom("switchedToAdvanced", showLayerList);
Events.onCustom("switchedToBasic", hideLayerList);
// Making the layers list sortable
new Sortable(layerList, {
animation: 100,
filter: ".layer-button",
draggable: ".layers-menu-entry",
onStart: layerDragStart,
onEnd: layerDragDrop
onEnd: layerDragEnd
});
function showMenu() {
function showLayerList() {
layerList.style.display = "inline-block";
document.getElementById('layer-button').style.display = 'inline-block';
}
function hideMenu() {
function hideLayerList() {
if (EditorState.documentCreated()) {
// Selecting the current layer
currFile.currentLayer.selectLayer();
// Flatten the layers
flatten(true);
}
layerList.style.display = "none";
document.getElementById('layer-button').style.display = 'none';
}
function addLayer(id, saveHistory = true, layerName) {
function addLayer(id, saveHistory = true) {
// layers.length - 3
let index = currFile.layers.length - 3;
let index = currFile.layers.length;
// Creating a new canvas
let newCanvas = document.createElement("canvas");
// Setting up the new canvas
@ -49,12 +39,16 @@ const LayerList = (() => {
newCanvas.style.zIndex = Layer.maxZIndex;
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
let toAppend = layerListEntry.cloneNode(true);
toAppend.style.display = "flex";
//console.log('toAppend === ',toAppend);
// 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
toAppend.classList.remove("selected-layer");
// Adding the layer to the list
@ -65,10 +59,11 @@ const LayerList = (() => {
newLayer.context.fillStyle = currFile.currentLayer.context.fillStyle;
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
layerList.insertBefore(toAppend, layerList.childNodes[0]);
layerList.insertBefore(toAppend, document.getElementById("add-layer-li"));
if (id != null && typeof(id) == "string") {
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
if (saveHistory) {
new HistoryState().AddLayer(newLayer, index);
FileManager.localStorageSave();
}
return newLayer;
}
/** Merges topLayer onto belowLayer
*
* @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]
];
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+2], belowImageData.data[i+3]
];
@ -115,34 +110,54 @@ const LayerList = (() => {
// Putting the top data into the belowdata
belowLayer.putImageData(toMergeImageData, 0, 0);
}
/** Sets the z indexes of the layers when the user drops the layer in the menu
*
* @param {*} event
*/
function layerDragDrop(event) {
let oldIndex = event.oldDraggableIndex;
let newIndex = event.newDraggableIndex;
function layerDragEnd(event) {
// let oldIndex = event.oldDraggableIndex;
// let newIndex = event.newDraggableIndex;
let movedZIndex = dragStartLayer.canvas.style.zIndex;
// let movedZIndex = dragStartLayer.canvas.style.zIndex;
if (oldIndex > newIndex)
{
for (let i=newIndex; i<oldIndex; i++) {
getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i + 1].id).canvas.style.zIndex;
}
}
else
{
for (let i=newIndex; i>oldIndex; i--) {
getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i - 1].id).canvas.style.zIndex;
}
}
// if (oldIndex > newIndex)
// {
// for (let i=newIndex; i<oldIndex; i++) {
// getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i + 1].id).canvas.style.zIndex;
// }
// }
// else
// {
// 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[oldIndex].id).canvas.style.zIndex = movedZIndex;
// getLayerByID(layerList.children[oldIndex].id).canvas.style.zIndex = movedZIndex;
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
*
* @param {*} event
@ -150,21 +165,27 @@ const LayerList = (() => {
function layerDragStart(event) {
dragStartLayer = getLayerByID(layerList.children[event.oldIndex].id);
}
// Finds a layer given its 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++) {
if (currFile.layers[i].hasCanvas()) {
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) {
for (let i=0; i<currFile.layers.length; i++) {
if (currFile.layers[i].hasCanvas()) {
@ -176,7 +197,6 @@ const LayerList = (() => {
return null;
}
function startRenamingLayer(event) {
let p = currFile.currentLayer.menuEntry.getElementsByTagName("p")[0];
@ -189,7 +209,6 @@ const LayerList = (() => {
renamingLayer = true;
}
function duplicateLayer(event, saveHistory = true) {
function getMenuEntryIndex(list, entry) {
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.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
let toAppend = currFile.currentLayer.menuEntry.cloneNode(true);
@ -248,41 +267,52 @@ const LayerList = (() => {
new HistoryState().DuplicateLayer(newLayer, currFile.currentLayer);
}
}
function clearLayers() {
//console.log('currFile.layers === ',currFile.layers);
function deleteLayer(saveHistory = true) {
// Cannot delete all the layers
if (currFile.layers.length != 4) {
let layerIndex = currFile.layers.indexOf(currFile.currentLayer);
let toDelete = currFile.layers[layerIndex];
let previousSibling = toDelete.menuEntry.previousElementSibling;
// 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);
}
currFile.layers.forEach(()=>deleteLayer());
//console.log('currFile.layers.length === ',currFile.layers.length);
for(let i = 0; i < currFile.layers.length;i++){
const layer = currFile.layers[i];
//console.log('i === ', i);
//console.log('layer === ',layer);
}
}
function deleteLayer(saveHistory = true) {
//console.log('deleting layer: ', currFile.currentLayer.name, currFile.currentLayer);
//console.trace();
deleteLayerDirectly(currFile.currentLayer, saveHistory);
// Closing the menu
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) {
// Saving the layer that should be merged
let toMerge = currFile.currentLayer;
@ -312,7 +342,6 @@ const LayerList = (() => {
currFile.currentLayer.updateLayerPreview();
}
}
function flatten(onlyVisible) {
if (!onlyVisible) {
// Selecting the first layer
@ -366,7 +395,6 @@ const LayerList = (() => {
currFile.currentLayer.updateLayerPreview();
}
}
function openOptionsMenu(event) {
if (event.which == 3) {
let selectedId;
@ -385,21 +413,17 @@ const LayerList = (() => {
getLayerByID(selectedId).selectLayer(false);
}
}
function closeOptionsMenu(event) {
Layer.layerOptions.style.visibility = "hidden";
currFile.currentLayer.rename();
renamingLayer = false;
}
function getLayerListEntries() {
return layerList;
}
function isRenamingLayer() {
return renamingLayer;
}
return {
addLayer,
mergeLayers,
@ -407,7 +431,9 @@ const LayerList = (() => {
getLayerByName,
renameLayer: startRenamingLayer,
duplicateLayer,
clearLayers,
deleteLayer,
deleteLayerDirectly,
merge,
flatten,
closeOptionsMenu,

View File

@ -6,7 +6,7 @@ const PresetModule = (() => {
};
function instrumentPresetMenu() {
console.info("Initializing presets..");
//console.info("Initializing presets..");
// Add a button for all the presets available
const presetsMenu = document.getElementById('preset-menu');
Object.keys(presets).forEach((presetName,) => {
@ -17,7 +17,7 @@ const PresetModule = (() => {
presetsMenu.appendChild(button);
button.addEventListener('click', () => {
console.log("Preset: " + presetName);
////console.log("Preset: " + presetName);
//change dimentions on new pixel form
Util.setValue('size-width', presets[presetName].width);
Util.setValue('size-height', presets[presetName].height);

View File

@ -14,7 +14,7 @@ const Settings = (() => {
settingsFromCookie = Cookies.get('pixelEditorSettings');
if(!settingsFromCookie) {
console.log('settings cookie not found');
////console.log('settings cookie not found');
settings = {
switchToChangedColor: true,
@ -27,8 +27,8 @@ const Settings = (() => {
};
}
else{
console.log('settings cookie found');
console.log(settingsFromCookie);
////console.log('settings cookie found');
////console.log(settingsFromCookie);
settings = JSON.parse(settingsFromCookie);
}

View File

@ -1,6 +1,8 @@
const Startup = (() => {
let splashPostfix = '';
let cacheIntervalIdx;
Events.on('click', 'create-button', create, false);
Events.on('click', 'create-button-splash', create, true);
@ -15,7 +17,10 @@ const Startup = (() => {
var height = Util.getValue('size-height' + splashPostfix);
var selectedPalette = Util.getText('palette-button' + splashPostfix);
newPixel(width, height);
newPixel({
canvasWidth: width,
canvasHeight: height,
});
resetInput();
//track google event
@ -25,15 +30,16 @@ const Startup = (() => {
/** 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 {*} 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
ColorModule.resetPalette();
initLayers(width, height);
initLayers(fileContent);
initPalette();
// Closing the "New Pixel dialogue"
@ -46,64 +52,91 @@ const Startup = (() => {
// Now, if I opened an LPE file
if (fileContent != null) {
loadFromLPE(fileContent);
// Deleting the default layer
LayerList.deleteLayer(false);
// Selecting the new one
currFile.layers[1].selectLayer();
FileManager.loadFromLPE(fileContent);
}
EditorState.switchMode(EditorState.getCurrentMode());
////console.log('ColorModule.getCurrentPalette() === ',ColorModule.getCurrentPalette());
EditorState.switchMode(EditorState.getCurrentMode(), skipModeConfirm);
// This is not the first Pixel anymore
EditorState.created();
}
function initLayers(width, height) {
// Setting the general canvasSize
////console.log('ColorModule.getCurrentPalette() === ',ColorModule.getCurrentPalette());
////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];
// If this is the first pixel I'm creating since the app has started
if (EditorState.firstPixel()) {
// Creating the first layer
currFile.currentLayer = new Layer(width, height, 'pixel-canvas', "");
if( lpe.layers && lpe.layers.length ) {
currFile.currentLayer = new Layer(width, height, `pixel-canvas`,"","layer-li-template");
currFile.currentLayer.canvas.style.zIndex = 2;
}
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;
currFile.sublayers.push(currFile.currentLayer);
if (currentEntry != null) {
// Getting the associated layer
associatedLayer = LayerList.getLayerByID(currentEntry.id);
let selectedIdx = lpe.selectedLayer ?? 0;
// Deleting its canvas
associatedLayer.canvas.remove();
// Adding the id to the unused ones
Layer.unusedIDs.push(currentEntry.id);
// Removing the entry from the menu
currentEntry.remove();
lpe.layers.forEach((layerData, i) => {
//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 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();
}
}
}
});
// Removing the old layers from the list
for (let i=2; i<nLayers - nAppLayers; i++) {
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];
} else {
currFile.currentLayer = new Layer(width, height, `pixel-canvas`,"");
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
currFile.checkerBoard = new Checkerboard(width, height, null);
// Pixel grid
console.log("CREATED GRID");
////console.log("CREATED GRID");
currFile.pixelGrid = new PixelGrid(width, height, "pixel-grid");
// Creating the vfx layer on top of everything
@ -111,14 +144,10 @@ const Startup = (() => {
// Tmp layer to draw previews on
currFile.TMPLayer = new Layer(width, height, 'tmp-canvas');
if (EditorState.firstPixel()) {
// Adding the first layer and the checkerboard to the list of layers
currFile.layers.push(currFile.checkerBoard);
currFile.layers.push(currFile.currentLayer);
currFile.layers.push(currFile.TMPLayer);
currFile.layers.push(currFile.pixelGrid);
currFile.layers.push(currFile.VFXLayer);
}
currFile.sublayers.push(currFile.checkerBoard);
currFile.sublayers.push(currFile.TMPLayer);
currFile.sublayers.push(currFile.pixelGrid);
currFile.sublayers.push(currFile.VFXLayer);
}
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() {
//reset new form
Util.setValue('size-width', 64);

View File

@ -62,12 +62,10 @@ class Tool {
else {
this.toolTutorial.style.top = this.mainButton.getBoundingClientRect().top - 48 + "px";
}
this.toolTutorial.style.display = "inline-block";
this.toolTutorial.className = "fade-in";
}
hideTutorial() {
this.toolTutorial.className = "fade-out";
setTimeout(function(){this.toolTutorial.style.display = "none"}.bind(this), 200);
}
resetTutorial() {
@ -158,11 +156,12 @@ class Tool {
onStart(mousePos, mouseTarget) {
this.startMousePos = mousePos;
}
onDrag(mousePos, mouseTarget) {
}
}
onEnd(mousePos, mouseTarget) {
this.endMousePos = mousePos;
this.endMousePos = mousePos;
FileManager.localStorageSave();
}
}

View File

@ -109,7 +109,7 @@ const ToolManager = (() => {
currTool.onRightDrag(mousePos, mouseEvent.target);
break;
default:
console.log("wtf");
////console.log("wtf");
break;
}
}

View File

@ -1,7 +1,7 @@
const TopMenuModule = (() => {
const mainMenuItems = document.getElementById('main-menu').children;
let infoList = document.getElementById('editor-info');
const mainMenuItems = document.getElementById('main-menu')?.children ?? [];
let infoList = document.getElementById('editor-info') ?? document.createElement("div");
let infoElements = {};
initMenu();
@ -113,7 +113,12 @@ const TopMenuModule = (() => {
}
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) {

View File

@ -83,7 +83,7 @@ class Util {
return document.getElementById(elementOrElementId);
}
else {
console.log("Type not supported: " + typeof(elementOrElementId));
////console.log("Type not supported: " + typeof(elementOrElementId));
}
}

211
js/canvas_util.js Normal file
View 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
View 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
View 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)
};
}
}

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,5 @@ const MAX_Z_INDEX = 5000;
// Index of the first layer the user can use in the layers array
const firstUserLayerIndex = 2;
// Number of layers that are only used by the editor
const nAppLayers = 3;
const MIN_ZOOM_LEVEL = 0.5;

View File

@ -18,7 +18,7 @@ class Checkerboard extends Layer {
initialize() {
super.initialize();
console.log("Square size: " + this.checkerBoardSquareSize);
////console.log("Square size: " + this.checkerBoardSquareSize);
this.currentColor = this.firstCheckerBoardColor;
this.fillCheckerboard();
}

View File

@ -18,7 +18,7 @@ class Layer {
// 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
// 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
this.canvas = Util.getElement(canvas);
this.canvas.width = width;
@ -37,18 +37,24 @@ class Layer {
else if (menuEntry !== undefined)
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) {
id = Layer.currentID;
Layer.currentID++;
}
this.id = "layer" + id;
this.id = hadId ? id : "layer" + id;
// Binding the events
if (this.menuEntry !== undefined) {
this.name = this.menuEntry.getElementsByTagName("p")[0].innerHTML;
this.menuEntry.id = "layer" + id;
this.menuEntry.onmouseover = () => this.hover();
@ -69,16 +75,58 @@ class Layer {
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();
}
hasCanvas() {
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)
return;
LayerList.deleteLayer();
}
@ -121,7 +169,7 @@ class Layer {
hover() {
// 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) {
currFile.layers[i].canvas.style.opacity = 0.3;
}
@ -130,7 +178,7 @@ class Layer {
unhover() {
// 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) {
currFile.layers[i].canvas.style.opacity = 1;
}
@ -187,6 +235,8 @@ class Layer {
}
selectLayer(hideOptions = true) {
//console.log('called selectLayer');
////console.trace();
if (hideOptions)
LayerList.closeOptionsMenu();
// Deselecting the old layer
@ -196,6 +246,8 @@ class Layer {
this.isSelected = true;
this.menuEntry.classList.add("selected-layer");
currFile.currentLayer = this;
FileManager.localStorageSave();
}
toggleLock() {

View File

@ -127,7 +127,7 @@
decodedKey = decodeURIComponent(key);
} catch (e) {
if (console && typeof console.error === 'function') {
console.error('Could not decode cookie with key "' + key + '"', e);
//console.error('Could not decode cookie with key "' + key + '"', e);
}
}

View File

@ -35,7 +35,7 @@ if (!window.jscolor) { window.jscolor = (function () {
init : function () {
//console.log('init()')
//////console.log('init()')
if (jsc.jscolor.lookupClass) {
jsc.jscolor.installByClassName(jsc.jscolor.lookupClass);
}
@ -533,7 +533,7 @@ if (!window.jscolor) { window.jscolor = (function () {
onDocumentMouseDown : function (e) {
//console.log(e)
//////console.log(e)
if (!e) { e = window.event; }
var target = e.target || e.srcElement;
@ -547,7 +547,7 @@ if (!window.jscolor) { window.jscolor = (function () {
} else {
// Mouse is outside the picker controls -> hide the color picker!
if (jsc.picker && jsc.picker.owner) {
//console.log(e.target,'=====================================')
//////console.log(e.target,'=====================================')
//if they clicked on the delete button [lospec]
if (e.target.className == 'delete-color-button') {
new HistoryState().DeleteColor(jsc.picker.owner.toString());
@ -555,13 +555,13 @@ if (!window.jscolor) { window.jscolor = (function () {
ColorModule.deleteColor(jsc.picker.owner.styleElement);
}
else if (e.target.className == 'jscolor-picker-bottom') {
//console.log('clicked color picker bottom')
//////console.log('clicked color picker bottom')
}
else if (e.target.parentElement.classList.contains('jscolor-picker-bottom')) {
//console.log('clicked element in color picker bottom')
//////console.log('clicked element in color picker bottom')
}
else {
//console.log('clicked outside of color picker')
//////console.log('clicked outside of color picker')
//unhide hidden edit button [lospec]
var hiddenButton = document.querySelector('.color-edit-button.hidden');
if (hiddenButton) hiddenButton.classList.remove('hidden');
@ -1068,7 +1068,7 @@ if (!window.jscolor) { window.jscolor = (function () {
this.hide = function () {
///console.log(this.styleElement)
///////console.log(this.styleElement)
if (isPickerOwner()) {
//set the color to old color, in case the color is a duplicate that hasn't been resolved yet [lospec]
var hexInput = document.getElementById('jscolor-hex-input');
@ -1164,7 +1164,7 @@ if (!window.jscolor) { window.jscolor = (function () {
if (this.hash) { value = '#' + value; }
if (jsc.isElementType(this.valueElement, 'input')) {
//console.log('SETTING VALUE')
//////console.log('SETTING VALUE')
//this sets the value element's value
this.valueElement.value = value;
} else {
@ -1439,7 +1439,7 @@ function detachPicker () {
function drawPicker () {
//console.log('drawPicker ()')
//////console.log('drawPicker ()')
// At this point, when drawing the picker, we know what the parent elements are
// and we can do all related DOM operations, such as registering events on them
// or checking their positioning
@ -1811,7 +1811,7 @@ function isPickerOwner () {
function blurValue () {
//console.log('blurValue()')
//////console.log('blurValue()')
THIS.importColor();
}
@ -1842,7 +1842,7 @@ this.valueElement = jsc.fetchElement(this.valueElement);
// Find the style element
this.styleElement = jsc.fetchElement(this.styleElement);
//console.log('VALUE ELEMENT: ', this.valueElement)
//////console.log('VALUE ELEMENT: ', this.valueElement)
var THIS = this;
var container =
@ -1894,7 +1894,7 @@ do {
if (jsc.isElementType(this.valueElement, 'input')) {
var updateField = function () {
//console.log('updateField()')
//////console.log('updateField()')
THIS.fromString(THIS.valueElement.value, jsc.leaveValue);
jsc.dispatchFineChange(THIS);
};

View File

@ -77,59 +77,95 @@ window.onload = function () {
ToolManager.currentTool().updateCursor();
// Apply checkboxes
//check if there are any url parameters
if (window.location.pathname.replace('/pixel-editor/','').length <= 1) {
//show splash screen
Dialogue.showDialogue('splash', false);
}
//url parameters were specified
else {
let args = window.location.pathname.split('/');
let paletteSlug = args[2];
let dimentions = args[3];
////console.log('window.location.pathname === ',window.location.pathname);
//fetch palette via lospec palette API
fetch('https://lospec.com/palette-list/'+paletteSlug+'.json')
.then(response => response.json())
.then(data => {
//palette loaded successfully
palettes[paletteSlug] = data;
palettes[paletteSlug].specified = true;
////console.log('window.location === ',window.location);
let args = window.location.pathname.split('/');
let paletteSlug = args[2];
let dimensions = args[3];
// let prefillWidth = args[4] ?? 9; // TODO
// let prefill = args[5] ?? "110101111110100110111100110110101111";
// let customColors = args[6] ?? ""; // ex: "#ffffff,#000000"
// console.log('prefill === ',prefill);
if(paletteSlug && dimensions) {
//refresh list of palettes
document.getElementById('palette-menu-splash').refresh();
//if the dimentions were specified
if (dimentions && dimentions.length >= 3 && dimentions.includes('x')) {
let width = dimentions.split('x')[0];
let height = dimentions.split('x')[1];
//create new document
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);
});
}
};
//fetch palette via lospec palette API
fetch('https://lospec.com/palette-list/'+paletteSlug+'.json')
.then(response => response.json())
.then(data => {
//palette loaded successfully
palettes[paletteSlug] = data;
palettes[paletteSlug].specified = true;
////console.log('palettes[paletteSlug] === ',palettes[paletteSlug]);
//refresh list of palettes
document.getElementById('palette-menu-splash').refresh();
//if the dimensions were specified
if (dimensions && dimensions.length >= 3 && dimensions.includes('x')) {
let width = dimensions.split('x')[0];
let height = dimensions.split('x')[1];
const layers = [];
let selectedLayer;
// if(prefill && prefillWidth){ // TODO
// layers.push({
// id: "layer0",
// name: "Layer 0",
// prefillWidth,
// prefill
// });
// selectedLayer = 0;
// }
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);
}
prefill.split("").forEach((n,i) => {
let idx = 0;
const num = Number(n);
////console.log('num === ',num);
if(n && isNaN(num)) {
////console.log('n === ',n);
}
// prefillWidth
})
})
//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();
Startup.newPixel(lpe);
}
//check if there are any url parameters
else if (window.location.pathname.replace('/pixel-editor/','').length <= 1) {
//show splash screen
Dialogue.showDialogue('splash', false);
}
}
}
//prevent user from leaving page with unsaved data
window.onbeforeunload = function() {
if (EditorState.documentCreated)
return 'You will lose your pixel if it\'s not saved!';
// window.onbeforeunload = function() {
// if (EditorState.documentCreated)
// return 'You will lose your pixel if it\'s not saved!';
else return;
};
// else return;
// };
// Compatibility functions
function closeCompatibilityWarning() {

View File

@ -12,7 +12,7 @@ class BrushTool extends ResizableTool {
this.addTutorialKey("Left drag", " to draw a stroke");
this.addTutorialKey("Right drag", " 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) {

View File

@ -25,7 +25,7 @@ class EllipseTool extends ResizableTool {
this.addTutorialKey("Left drag", " to draw an ellipse");
this.addTutorialKey("Right drag", " to resize the brush");
this.addTutorialKey("+ or -", " to resize the brush");
this.addTutorialImg("ellipse-tutorial.gif");
this.addTutorialImg("/images/ToolTutorials/ellipse-tutorial.gif");
}
changeFillType() {

View File

@ -12,7 +12,7 @@ class EraserTool extends ResizableTool {
this.addTutorialKey("Left drag", " to erase an area");
this.addTutorialKey("Right drag", " 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) {

View File

@ -14,7 +14,7 @@ class EyeDropperTool extends Tool {
this.addTutorialKey("Aòt + left drag", " to preview the picked colour");
this.addTutorialKey("Left 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) {

View File

@ -8,7 +8,7 @@ class FillTool extends DrawingTool {
this.addTutorialTitle("Fill tool");
this.addTutorialKey("F", " to select the fill tool");
this.addTutorialKey("Left click", " to fill a contiguous area");
this.addTutorialImg("fill-tutorial.gif");
this.addTutorialImg("/images/ToolTutorials/fill-tutorial.gif");
}
onStart(mousePos, target) {
@ -25,7 +25,7 @@ class FillTool extends DrawingTool {
static fill(cursorLocation, context) {
//changes a pixels color
function colorPixel(tempImage, pixelPos, fillColor) {
//console.log('colorPixel:',pixelPos);
//////console.log('colorPixel:',pixelPos);
tempImage.data[pixelPos] = fillColor.r;
tempImage.data[pixelPos + 1] = fillColor.g;
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
function matchStartColor(tempImage, pixelPos, color) {
//console.log('matchPixel:',x,y)
//////console.log('matchPixel:',x,y)
let r = tempImage.data[pixelPos];
let g = tempImage.data[pixelPos + 1];
let b = tempImage.data[pixelPos + 2];
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]);
}
@ -52,7 +52,7 @@ class FillTool extends DrawingTool {
//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)]];
//console.log('topmostPixelsArray:',topmostPixelsArray)
//////console.log('topmostPixelsArray:',topmostPixelsArray)
//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;

View File

@ -15,7 +15,7 @@ class LassoSelectionTool extends SelectionTool {
this.addTutorialKey("CTRL+C", " to copy a selection")
this.addTutorialKey("CTRL+V", " to paste 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) {

View File

@ -12,7 +12,7 @@ class LineTool extends ResizableTool {
this.addTutorialKey("Left drag", " to draw a line");
this.addTutorialKey("Right drag", " 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) {

View File

@ -13,7 +13,7 @@ class MagicWandTool extends SelectionTool {
this.addTutorialKey("CTRL+C", " to copy a selection");
this.addTutorialKey("CTRL+V", " to paste 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) {
@ -22,7 +22,7 @@ class MagicWandTool extends SelectionTool {
!Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom]))
return;
////console.log('this.moveTool === ',this.moveTool);
this.switchFunc(this.moveTool);
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.previewData = selectedData;
this.drawSelectedArea();
this.boundingBoxCenter = [this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2,
this.boundingBox.minY + (this.boundingBox.maxY - this.boundingBox.minY) / 2];
this.boundingBoxCenter = [
this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2,
this.boundingBox.minY + (this.boundingBox.maxY - this.boundingBox.minY) / 2
];
// Cut the selection
this.cutSelection();
@ -61,7 +63,7 @@ class MagicWandTool extends SelectionTool {
currFile.TMPLayer.context.putImageData(this.previewData, 0, 0);
// Draw the bounding box
this.drawBoundingBox();
this.drawBoundingBox(1, 1);
return selectedData;
}

View File

@ -10,7 +10,7 @@ class PanTool extends Tool {
this.addTutorialKey("P", " to select the lasso selection tool");
this.addTutorialKey("Left 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) {

View File

@ -23,7 +23,7 @@ class RectangleTool extends ResizableTool {
this.addTutorialKey("Left drag", " to draw a rectangle");
this.addTutorialKey("Right drag", " to resize the brush");
this.addTutorialKey("+ or -", " to resize the brush");
this.addTutorialImg("rectangle-tutorial.gif");
this.addTutorialImg("/images/ToolTutorials/rectangle-tutorial.gif");
}
changeFillType() {

Some files were not shown because too many files have changed in this diff Show More