Merge pull request #87 from pxlvxl/master

Various changes and features (revision 2)
This commit is contained in:
Nicola 2022-10-25 12:30:15 +02:00 committed by GitHub
commit 5d55425e83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 3302 additions and 610 deletions

4
.gitignore vendored
View File

@ -4,4 +4,6 @@ routes
build
node_modules
.idea
.history
.history
*.css
*.map

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 {
@ -85,9 +85,9 @@
#canvas-view {
bottom: 0px;
left: 48px;
right: 48px;
top: 48px;
left: var(--top-nav-height);
right: var(--top-nav-height);
top: var(--top-nav-height);
cursor: default;
position: fixed;
display: block;
@ -97,9 +97,9 @@
box-shadow: inset 0px 0px 4px 0px rgba(0, 0, 0, 0.4);
position: fixed;
bottom: 0px;
left: 48px;
right: 48px;
top: 48px;
left: var(--top-nav-height);
right: var(--top-nav-height);
top: var(--top-nav-height);
display: block;
pointer-events: none;
}

View File

@ -1,36 +1,51 @@
#colors-menu {
right: 200px;
width: 48px;
width:var(--layers-width);
display: flex;
justify-content: flex-start;
flex-direction: column;
flex-direction: row;
list-style-type: none;
top: 48px;
bottom: 0;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
height: var(--palette-height);
padding: 0;
margin: 0;
background-color: $basecolor;
box-sizing: border-box;
position: fixed;
align-items: flex-start;
z-index: 1120;
overflow-y: scroll;
resize: vertical;
li {
width: 48px;
flex-basis: 48px;
&:not(.noshrink) {
flex-grow: 1;
}
&.noshrink {
flex-grow: 0;
flex-shrink: 0;
}
height: 48px;
}
svg {
margin-top: 4px;
}
&::-webkit-scrollbar {
background: #232125;
width: 1em;
}
&::-webkit-scrollbar-track {
margin-top: -0.125em;
width: 1em;
}
&::-webkit-scrollbar-thumb {
background: #332f35;
border-radius: 0.25em;
border: solid 0.125em #232125; //same color as scrollbar back to fake padding
}
&::-webkit-scrollbar-corner {
background: #232125;
}
scrollbar-color: #332f35 #232125;
scroll-behavior: smooth;
}
//added when the color is a duplicate of another
#duplicate-color-warning {
display: inline-block;
@ -118,14 +133,14 @@
content: "";
display: block;
position: absolute;
top: -3px;
left: -3px;
top: 0px;
left: 0px;
border: solid 3px #fff;
width: 100%;
height: 100%;
border-radius: 4px;
box-shadow: 0px 0px 0px 3px rgba(0, 0, 0, 0.15);
box-shadow: 0px 0px 0px 3px rgba(0, 0, 0, 0.15) inset;
z-index: 10;
box-sizing: border-box;
} //inner outline
&.selected button::after {
content: "";
@ -149,7 +164,9 @@
background: $basehover;
}
.jscolor-wrap {
z-index: 10000 !important;
}
.jscolor-picker-bottom {
display: none;
@ -173,7 +190,6 @@
}
}
.delete-color-button {
background: none;
padding: 0px;
@ -193,4 +209,31 @@
fill: lighten($basecolor, 10%) !important;
}
}
}
#colors-menu-settings {
width: 98%;
padding: 0px 0px 0px 0px;
position:relative;
top:0px;
background: $basecolor;
button {
position:relative;
left:0px;
margin-left:0px;
padding: 2px;
height:30px;
width:49%;
color: $basetext;
background: $basecolor;
border:none;
&:hover {
color: $basehovertext;
background-color: $basehover;
cursor:pointer;
}
}
}

24
css/_containers.scss Normal file
View File

@ -0,0 +1,24 @@
#right-container{
display: flex;
flex-direction: column;
position: fixed;
top: var(--top-nav-height);
right: var(--top-nav-height);
bottom: 0;
}
#bottom-container{
display: flex;
flex-direction: column;
position: fixed;
left: 0;
right: 0;
bottom: 0;
}
#left-container{
display: flex;
flex-direction: column;
position: fixed;
top: var(--top-nav-height);
bottom: 0;
left: var(--top-nav-height);
}

View File

@ -43,4 +43,15 @@ svg {
#data-holders, .preload {
display: none;
}
}
#right-nav {
position: fixed;
top: var(--top-nav-height);
right: 0;
bottom: 0;
width: var(--layers-width);
z-index: 1100;
display: flex;
flex-direction: column;
}

View File

@ -1,4 +1,3 @@
#layer-properties-menu {
visibility: hidden;
margin: 0;
@ -8,7 +7,7 @@
width: 120px;
text-align: center;
margin-right: 200px;
margin-right: var(--layers-width);
/*border:1px solid $basetext;*/
list-style: none;
position: relative;
@ -72,19 +71,16 @@
}
scrollbar-color: #332f35 #232125;
scroll-behavior: smooth;
width:200px;
top: 48px;
bottom: 0;
right:0;
width:var(--layers-width);
padding: 0;
margin: 0;
background-color: $basecolor;
box-sizing: border-box;
position: fixed;
z-index: 1120;
list-style-type: none;
overflow-y:scroll;
overflow-x:hidden;
flex: 1;
#add-layer-button {
path {
fill: $baseicon;
@ -112,6 +108,33 @@
color: $basehovertext;
background-color: $basehover;
}
#add-reference-button {
path {
fill: $baseicon;
}
svg {
position: relative;
margin-right: 10px;
}
position:relative;
justify-content: center;
display:none;
align-items:center;
margin-top:2px;
font-size: 1.2em;
color: $basetext;
height: 100%;
width: 100%;
padding: 17px;
background: none;
border: none;
cursor: pointer;
}
#add-reference-button:hover {
color: $basehovertext;
background-color: $basehover;
}
}
.selected-layer {

View File

@ -1,6 +1,6 @@
#main-menu {
height: 48px;
height: var(--top-nav-height);
left: 0;
right: 0;
list-style-type: none;
@ -35,7 +35,7 @@
li ul {
display: none;
position: absolute;
top: 48px;
top: var(--top-nav-height);
list-style-type: none;
padding: 0;
margin: 0;

View File

@ -6,6 +6,18 @@
font-style: italic;
}
#new-pixel-inventory {
display: none;
}
#new-pixel-map {
display: none;
}
#new-voxel-world {
display: none;
}
.dimentions-x {
margin: -2px 7px;
path {
@ -13,12 +25,10 @@
}
}
#no-palette-button {
display: none;
}
#editor-mode-info {
font-style: italic;
}

View File

@ -3,11 +3,12 @@
#splash {
width:100% !important;
height:100%!important;
position:fixed;
position:static;
margin-top:-20px;
background-color: #232125 !important;
opacity: 1 !important;
z-index:1300;
#splash-input {
width:74%;

View File

@ -1,9 +1,9 @@
#tools-menu {
left: 0;
width: 48px;
width: var(--top-nav-height);
list-style-type: none;
top: 48px;
top: var(--top-nav-height);
bottom: 0;
padding: 0;
margin: 0;
@ -24,7 +24,7 @@
width: 100%;
padding: 0;
cursor: pointer;
height: 48px;
height: var(--top-nav-height);
z-index:0;
}
@ -73,7 +73,7 @@
#tools-menu .size-buttons {
position:absolute;
display: none;
height:48px;
height:var(--top-nav-height);
left:8px;
z-index:-1;
background-color: $basecolor !important;
@ -128,6 +128,8 @@
.fade-out {
animation: fadeOut .1s forwards;
display: none !important;
/* TODO: Search Google for: "How to fade out and then set display none" */
}
.is-paused {
@ -138,8 +140,8 @@
#tool-tutorial {
display:inline-block;
position:absolute;
margin-left:48px;
margin-top:48px;
margin-left:var(--top-nav-height);
margin-top:var(--top-nav-height);
background-color: $basehover;
color:$basetext;
font-size:14px;

View File

@ -13,4 +13,12 @@ $baseselected: lighten($basecolor, 15%); //color(selectedTool, background),
$baseselectediconhover: lighten($basecolor, 70%); //color(subbutton, foreground, hover)
$baseselectedhover: lighten($basecolor, 25%); //color(subbutton, background, hover)
$indent: darken($basecolor, 5%); //color(indent)
$indenttext: lighten($basecolor, 50%); //color(indent, foreground)
$indenttext: lighten($basecolor, 50%); //color(indent, foreground)
:root{
--layers-width: 206px;
--palette-height: 38%;
--top-nav-height: 48px;
--drag-bar-size: var(--top-nav-height);
}

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)
);

BIN
images/Logs/palettegrid.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
images/icons_14x14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
images/le_button_18x19.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

BIN
images/lospec_mock1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
images/lospec_mock2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

BIN
images/sked_tree_32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

BIN
images/sked_x1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
images/sked_x10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
images/test_8x8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 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

@ -7,13 +7,22 @@ const ColorModule = (() => {
// Reference to the HTML palette
const coloursList = document.getElementById("palette-list");
// Reference to the colours menu
const colorsMenu = document.getElementById("colors-menu");
let colorsMenu = document.getElementById("colors-menu");
// Square size
const minSquareSize = 38;
let squareSize = colorsMenu.children[0].getBoundingClientRect().width;
// Binding events to callbacks
document.getElementById('jscolor-hex-input').addEventListener('change',colorChanged, false);
document.getElementById('jscolor-hex-input').addEventListener('input', colorChanged, false);
document.getElementById('add-color-button').addEventListener('click', addColorButtonEvent, false);
document.getElementById('add-color-button').addEventListener('click', addColorButtonEvent);
Events.on("wheel", "colors-menu", resizeSquares);
Events.on("click", document.getElementById("cm-add"), addColorButtonEvent);
Events.on("click", document.getElementById("cm-remove"), deleteColor, undefined);
Events.on("click", document.getElementById("cm-zoomin"), resizeSquares, {altKey:true, deltaY: -1.0});
Events.on("click", document.getElementById("cm-zoomout"), resizeSquares, {altKey:true, deltaY: 1.0});
// Making the colours in the HTML menu sortable
new Sortable(document.getElementById("colors-menu"), {
animation:100,
@ -22,6 +31,17 @@ const ColorModule = (() => {
onEnd: function() {Events.simulateMouseEvent(window, "mouseup");}
});
function resizeSquares(event) {
if (!event.altKey) return;
squareSize = Math.max(minSquareSize, (squareSize - 3 * Math.sign(event.deltaY)));
for (let i=0; i< colorsMenu.children.length; i++) {
colorsMenu.children[i].style.width = squareSize + 'px';
colorsMenu.children[i].style.height = squareSize + 'px';
}
}
/** Changes all of one color to another after being changed from the color picker
*
* @param {*} colorHexElement The element that has been changed
@ -57,7 +77,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';
@ -94,25 +114,42 @@ const ColorModule = (() => {
*
* @param {*} e The event that triggered the callback
*/
function clickedColor (e){
function clickedColor (e) {
//left clicked color
if (e.which == 1) {
// remove current color selection
const currentSelectedColorButton = document.querySelector('#colors-menu li.selected .color-button');
const selectedColor = currentSelectedColorButton.style.backgroundColor;
const clickedColor = e.target.style.backgroundColor;
document.querySelector('#colors-menu li.selected')?.classList.remove('selected');
//set current color
updateCurrentColor(Color.cssToHex(e.target.style.backgroundColor));
updateCurrentColor(Color.cssToHex(clickedColor));
//make color selected
e.target.parentElement.classList.add('selected');
if(selectedColor === clickedColor) {
if (EditorState.getCurrentMode() == "Basic") {
e.target.parentElement.lastChild.classList.add('hidden');
e.target.jscolor.show();
}
else {
Dialogue.showDialogue("palette-block");
}
}
}
//right clicked color
else if (e.which == 3) {
//hide edit color button (to prevent it from showing)
e.target.parentElement.lastChild.classList.add('hidden');
//show color picker
e.target.jscolor.show();
if (EditorState.getCurrentMode() == "Basic") {
//hide edit color button (to prevent it from showing)
e.target.parentElement.lastChild.classList.add('hidden');
//show color picker
e.target.jscolor.show();
}
else {
Dialogue.showDialogue("palette-block");
}
}
}
@ -129,15 +166,12 @@ const ColorModule = (() => {
//add new color and make it selected
let addedColor = addColor(newColor);
addedColor.classList.add('selected');
addedColor.style.width = squareSize + "px";
addedColor.style.height = squareSize + "px";
updateCurrentColor(newColor);
//add history state
new HistoryState().AddColor(addedColor.firstElementChild.jscolor.toString());
//show color picker
addedColor.firstElementChild.jscolor.show();
//hide edit button
addedColor.lastChild.classList.add('hidden');
}
/** Adds the colors that have been added through the advanced-mode color picker to the
@ -232,6 +266,8 @@ const ColorModule = (() => {
Dialogue.showDialogue("palette-block", false);
});
if (!document.querySelector('#colors-menu li.selected'))
colorsMenu.children[0].classList.add('selected');
return listItem;
}
@ -241,6 +277,9 @@ const ColorModule = (() => {
* that should be removed.
*/
function deleteColor (color) {
if (!color)
color = getSelectedColor();
const logStyle = 'background: #913939; color: white; padding: 5px;';
//if color is a string, then find the corresponding button
@ -252,7 +291,6 @@ const ColorModule = (() => {
//loop through colors
for (var i = 0; i < colors.length; i++) {
//console.log(color,'=',colors[i].jscolor.toString());
if (color == colors[i].jscolor.toString()) {
//set color to the color button
@ -335,7 +373,11 @@ const ColorModule = (() => {
}
function getCurrentPalette() {
return currentPalette;
let ret = [...currentPalette];
if(ret.length === 0) {
ret = [...document.querySelectorAll(".color-button")].map(n=>n.style.backgroundColor);
}
return ret;
}
function resetPalette() {
@ -347,7 +389,6 @@ const ColorModule = (() => {
* @param {*} paletteColors The colours of the palette
*/
function createColorPalette(paletteColors) {
console.log("creating palette");
//remove current palette
while (colorsMenu.childElementCount > 1)
colorsMenu.children[0].remove();
@ -359,7 +400,6 @@ const ColorModule = (() => {
for (var i = 0; i < paletteColors.length; i++) {
var newColor = new Color("hex", paletteColors[i]);
var newColorElement = ColorModule.addColor(newColor.hex);
var newColRgb = newColor.rgb;
var lightestColorRgb = lightestColor.rgb;
@ -383,6 +423,7 @@ const ColorModule = (() => {
//set as current color
updateCurrentColor(darkestColor.hex);
}
/** Creates the palette with the colours used in all the layers
@ -457,10 +498,20 @@ const ColorModule = (() => {
if (refLayer)
color = refLayer.context.fillStyle;
for (let i=0; i<currFile.layers.length - 1; i++) {
for (let i=0; i<currFile.layers.length; i++) {
currFile.layers[i].context.fillStyle = color;
currFile.layers[i].context.strokeStyle = color;
}
for (let i=0; i<currFile.sublayers.length; i++) {
currFile.sublayers[i].context.fillStyle = color;
currFile.sublayers[i].context.strokeStyle = color;
}
}
function getSelectedColor() {
const currentSelectedColorButton = document.querySelector('#colors-menu li.selected .color-button');
return currentSelectedColorButton.jscolor.toString();
}
return {
@ -474,5 +525,6 @@ const ColorModule = (() => {
createPaletteFromLayers,
updatePaletteFromLayers,
updateCurrentColor,
getSelectedColor,
}
})();

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,15 +9,15 @@ 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
if (newMode == 'Advanced') {
Events.emit("switchedToAdvanced");
// Hide the palette menu
document.getElementById('colors-menu').style.right = '200px'
pixelEditorMode = 'Advanced';
document.getElementById("switch-mode-button").innerHTML = 'Switch to basic 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("div");
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
@ -9,6 +10,7 @@ const FileManager = (() => {
Events.on('change', browsePaletteHolder, loadPalette);
Events.on('change', importImageHolder, loadImage);
Events.on('click', 'export-confirm', exportProject);
Events.on("click", "save-project-confirm", saveProject);
function openSaveProjectWindow() {
//create name
@ -23,7 +25,6 @@ const FileManager = (() => {
}
Util.setValue('lpe-file-name', fileName);
Events.on("click", "save-project-confirm", saveProject);
Dialogue.showDialogue('save-project', false);
}
@ -45,6 +46,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
@ -58,6 +60,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() {
@ -71,6 +76,9 @@ const FileManager = (() => {
let emptyCanvas = document.createElement("canvas");
let layersCopy = currFile.layers.slice();
exportCanvas.getContext("2d").willReadFrequently = true;
emptyCanvas.getContext("2d").willReadFrequently = true;
exportCanvas.width = currFile.canvasSize[0];
exportCanvas.height = currFile.canvasSize[1];
@ -120,7 +128,62 @@ 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('BEFORE JSON.stringify(lpe.colors,null,4) === ',JSON.stringify(lpe.colors,null,4));
console.log([...ColorModule.getCurrentPalette()]);
if(lpe.colors.length < 1)lpe.colors = [...ColorModule.getCurrentPalette()];
if(lpe.colors.length < 1)lpe.colors.push("#000000");
console.log('AFTER JSON.stringify(lpe.colors,null,4) === ',JSON.stringify(lpe.colors,null,4));
if(!lpe.canvasWidth)lpe.canvasWidth = 16;
if(!lpe.canvasHeight)lpe.canvasHeight = 16;
console.log('LPE saved === ',lpe);
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 defaultLPE(w,h,colors) {
return {
"canvasWidth":w,
"canvasHeight":h,
"editorMode":"Advanced",
colors,
"selectedLayer":0,
"layers":[
{"canvas":{},"context":{"mozImageSmoothingEnabled":false},"isSelected":true,"isVisible":true,"isLocked":false,"oldLayerName":null,"menuEntry":{},"id":"layer0","name":"Layer 0","src":emptyCanvasSrc(w,h)}
]
};
function emptyCanvasSrc(w,h) {
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
return canvas.toDataURL();
}
}
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
@ -132,6 +195,7 @@ const FileManager = (() => {
// If it's a Lospec Pixel Editor tm file, I load the project
if (extension == 'lpe') {
openProject();
// openFile();
}
else {
openFile();
@ -142,7 +206,6 @@ const FileManager = (() => {
browseHolder.value = null;
}
function openFile() {
//load file
var fileReader = new FileReader();
@ -152,7 +215,11 @@ const FileManager = (() => {
img.onload = function() {
//create a new pixel with the images dimentions
Startup.newPixel(this.width, this.height);
console.log('this === ',this);
Startup.newPixel({
canvasWidth: this.width,
canvasHeight: this.height
});
EditorState.switchMode('Advanced');
//draw the image onto the canvas
@ -168,59 +235,63 @@ const FileManager = (() => {
};
fileReader.readAsDataURL(browseHolder.files[0]);
}
function openProject() {
let file = browseHolder.files[0];
let reader = new FileReader();
function openProject(lpeData) {
console.log('lpeData === ',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 = browseHolder.files[0];
let reader = new FileReader();
// Converting the data to a json object and creating a new pixel (see _newPixel.js for more)
reader.onload = function (e) {
console.log('this === ',this);
console.log('e === ',e);
let dictionary = JSON.parse(e.target.result);
console.log('FileManager.js => openProject => loaded lpe dictionary === ',dictionary);
_parseLPE(dictionary);
}
// Removing the default colours
ColorModule.deleteColor(ColorModule.getCurrentPalette()[0]);
ColorModule.deleteColor(ColorModule.getCurrentPalette()[0]);
reader.readAsText(file, "UTF-8");
}
function _parseLPE(dictionary) {
Startup.newPixel(dictionary);
}
}
function loadFromLPE(dictionary) {
ColorModule.resetPalette();
//console.log('dictionary === ',dictionary);
EditorState.switchMode(dictionary.editorMode ?? 'Advanced');
if(dictionary.colors)ColorModule.createColorPalette(dictionary.colors);
// Startup.newPixel(dictionary);
}
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();
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
@ -380,13 +451,79 @@ const FileManager = (() => {
this.currentImportPivotElement = event.target;
this.currentImportPivotElement.classList.add("rc-selected-pivot");
}
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++;
}
console.log('Object.keys(dictionary) === ',Object.keys(dictionary));
dictionary.layers = Object.keys(dictionary).reduce((r,k,i)=>{
if(k.slice(0,5) === "layer" && dictionary[k]){
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;
},[]);
console.log('dictionary.layers === ',dictionary.layers);
}
console.log('dictionary === ',dictionary);
return dictionary;
}
function toggleCache(elm){
console.log('elm === ',elm);
FileManager.cacheEnabled = !FileManager.cacheEnabled;
localStorage.setItem("lpe-cache-enabled",FileManager.cacheEnabled ? "1" : "0");
elm.textContent = cacheBtnText(FileManager.cacheEnabled);
}
function cacheBtnText(cacheEnabled) {
return `${cacheEnabled ? "Disable" : "Enable"} auto-cache`;
}
return {
const cacheEnabled = !!Number(localStorage.getItem("lpe-cache-enabled"));
document.getElementById("auto-cache-button").textContent = cacheBtnText(cacheEnabled);
const ret = {
cacheEnabled,
loadFromLPE,
toggleCache,
getProjectData,
localStorageReset,
localStorageCheck,
localStorageSave,
localStorageLoad,
upgradeLPE,
defaultLPE,
saveProject,
openProject,
exportProject,
openPixelExportWindow,
openSaveProjectWindow,
openImportImageWindow,
open
}
Object.keys(ret).forEach(k=>{
if(typeof ret[k] === "function"){
const orig = ret[k];
ret[k] = function() {
DEBUG_ARR.push(`called FileManager -> ${k}`);
return orig.call(this,...arguments);
}
}
})
return ret;
})();

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,60 +1,60 @@
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.on('click',"add-layer-button", addLayerClick, false);
Events.on('click',"add-reference-button", addReferenceClick, false);
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) {
// layers.length - 3
let index = currFile.layers.length - 3;
function addReferenceClick(id, saveHistory = true, layerName) {
addLayer(...arguments);
currFile.layers[currFile.layers.length-1].selectLayer();
}
function addLayerClick(id, saveHistory = true, layerName) {
addLayer(...arguments);
currFile.layers[currFile.layers.length-1].selectLayer();
}
function addLayer(id, saveHistory = true, layerName) {
let index = currFile.layers.length;
// Creating a new canvas
let newCanvas = document.createElement("canvas");
// Setting up the new canvas
currFile.canvasView.append(newCanvas);
Layer.maxZIndex+=2;
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
@ -63,12 +63,14 @@ const LayerList = (() => {
// Creating a layer object
let newLayer = new Layer(currFile.canvasSize[0], currFile.canvasSize[1], newCanvas, toAppend);
newLayer.context.fillStyle = currFile.currentLayer.context.fillStyle;
newLayer.context.willReadFrequently = true;
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 +78,16 @@ 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);
if(FileManager.cacheEnabled)FileManager.localStorageSave();
}
currFile.layers.forEach((layer, i) => {
const _i = currFile.layers.length - i;
layer.canvas.style.zIndex = (_i+1) * 10;
})
return newLayer;
}
/** Merges topLayer onto belowLayer
*
* @param {*} belowLayer The layer on the bottom of the layer stack
@ -97,7 +104,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 +122,38 @@ 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;
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;
}
}
getLayerByID(layerList.children[oldIndex].id).canvas.style.zIndex = movedZIndex;
function layerDragEnd(event) {
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;
});
currFile.layers.forEach((layer, i) => {
const _i = currFile.layers.length - i;
layer.canvas.style.zIndex = (_i+1) * 10;
});
if(FileManager.cacheEnabled)FileManager.localStorageSave();
}
/** Saves the layer that is being moved when the dragging starts
*
* @param {*} event
@ -150,21 +161,17 @@ const LayerList = (() => {
function layerDragStart(event) {
dragStartLayer = getLayerByID(layerList.children[event.oldIndex].id);
}
// Finds a layer given its id
function getLayerByID(id) {
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];
}
}
}
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 +183,6 @@ const LayerList = (() => {
return null;
}
function startRenamingLayer(event) {
let p = currFile.currentLayer.menuEntry.getElementsByTagName("p")[0];
@ -189,36 +195,17 @@ const LayerList = (() => {
renamingLayer = true;
}
function duplicateLayer(event, saveHistory = true) {
function getMenuEntryIndex(list, entry) {
for (let i=0; i<list.length; i++) {
if (list[i] === entry) {
return i;
}
}
return -1;
}
let layerIndex = currFile.layers.indexOf(currFile.currentLayer);
let toDuplicate = currFile.currentLayer;
let menuEntries = layerList.children;
// Increasing z-indexes of the layers above
for (let i=getMenuEntryIndex(menuEntries, toDuplicate.menuEntry) - 1; i>=0; i--) {
LayerList.getLayerByID(menuEntries[i].id).canvas.style.zIndex++;
}
Layer.maxZIndex+=2;
// Creating a new canvas
let newCanvas = document.createElement("canvas");
// Setting up the new canvas
currFile.canvasView.append(newCanvas);
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);
@ -243,46 +230,59 @@ const LayerList = (() => {
newLayer.context.putImageData(currFile.currentLayer.context.getImageData(
0, 0, currFile.canvasSize[0], currFile.canvasSize[1]), 0, 0);
newLayer.updateLayerPreview();
LayerList.refreshZ();
// 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().DuplicateLayer(newLayer, currFile.currentLayer);
}
}
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);
}
function clearLayers() {
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 +312,6 @@ const LayerList = (() => {
currFile.currentLayer.updateLayerPreview();
}
}
function flatten(onlyVisible) {
if (!onlyVisible) {
// Selecting the first layer
@ -366,7 +365,6 @@ const LayerList = (() => {
currFile.currentLayer.updateLayerPreview();
}
}
function openOptionsMenu(event) {
if (event.which == 3) {
let selectedId;
@ -385,29 +383,47 @@ 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;
}
function refreshZ() {
try{
let selectedZIndex = 0;
let maxZ = 0;
currFile.layers.forEach((layer, i) => {
const _i = currFile.layers.length - i;
let z = (_i+1) * 10;
if(maxZ < z)maxZ = z;
layer.canvas.style.zIndex = z;
if(layer.isSelected){
selectedZIndex = z;
}
});
currFile.checkerBoard.canvas.style.zIndex = 1;
currFile.pixelGrid.canvas.style.zIndex = 2;
currFile.TMPLayer.canvas.style.zIndex = selectedZIndex + 1;
currFile.VFXLayer.canvas.style.zIndex = maxZ + 10;
}catch(e){}
}
return {
refreshZ,
addLayer,
mergeLayers,
getLayerByID,
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,9 @@
let DEBUG_ARR = [];
const Startup = (() => {
let splashPostfix = '';
let cacheIntervalIdx;
Events.on('click', 'create-button', create, false);
Events.on('click', 'create-button-splash', create, true);
@ -15,7 +18,7 @@ const Startup = (() => {
var height = Util.getValue('size-height' + splashPostfix);
var selectedPalette = Util.getText('palette-button' + splashPostfix);
newPixel(width, height);
newPixel(FileManager.defaultLPE(width,height));
resetInput();
//track google event
@ -25,15 +28,18 @@ 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 {*} lpe If lpe != 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 (lpe = null, skipModeConfirm = false) {
DEBUG_ARR.push('called Startup -> newPixel');
console.trace();
// The palette is empty, at the beginning
ColorModule.resetPalette();
initLayers(width, height);
lpe = FileManager.upgradeLPE(lpe);
initLayers(lpe);
initPalette();
// Closing the "New Pixel dialogue"
@ -44,66 +50,95 @@ const Startup = (() => {
// The user is now able to export the Pixel
document.getElementById('export-button').classList.remove('disabled');
// 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();
if (lpe != null) {
FileManager.loadFromLPE(lpe);
}
EditorState.switchMode(EditorState.getCurrentMode());
////console.log('ColorModule.getCurrentPalette() === ',ColorModule.getCurrentPalette());
EditorState.switchMode(EditorState.getCurrentMode(), skipModeConfirm);
// This is not the first Pixel anymore
EditorState.created();
////console.log('ColorModule.getCurrentPalette() === ',ColorModule.getCurrentPalette());
////console.trace();
}
function initLayers(width, height) {
// Setting the general canvasSize
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', "");
currFile.currentLayer.canvas.style.zIndex = 2;
function clearLayers() {
DEBUG_ARR.push('called Startup -> clearLayers');
console.dir(currFile.layers);
for(let i = currFile.layers.length-1; i >= 0;i--) {
currFile.layers[i].delete(i);
}
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;
console.dir(currFile.layers);
for(let i = currFile.sublayers.length-1; i >= 0;i--) {
currFile.sublayers[i].delete(i);
}
}
function initLayers(lpe) {
DEBUG_ARR.push('called Startup -> initLayers');
//console.group('called initLayers');
//console.log('currFile.layers === ',currFile.layers);
if (currentEntry != null) {
// Getting the associated layer
associatedLayer = LayerList.getLayerByID(currentEntry.id);
const width = lpe.canvasWidth = Number(lpe.canvasWidth);
const height = lpe.canvasHeight = Number(lpe.canvasHeight);
clearLayers();
// Deleting its canvas
associatedLayer.canvas.remove();
// debugger;
//
currFile.canvasSize = [width, height];
console.log('lpe === ',lpe);
if( lpe.layers && lpe.layers.length ) {
currFile.currentLayer = new Layer(width, height, `pixel-canvas`,"","layer-li-template");
currFile.sublayers.push(currFile.currentLayer);
// Adding the id to the unused ones
Layer.unusedIDs.push(currentEntry.id);
// Removing the entry from the menu
currentEntry.remove();
let selectedIdx = lpe.selectedLayer ?? 0;
lpe.layers.forEach((layerData, i) => {
//console.log('lpe.layers[i] === ', i);
const _i = lpe.layers.length - 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];
currFile.currentLayer.canvas.style.zIndex = 2;
} else {
currFile.currentLayer = new Layer(width, height, `pixel-canvas`,"");
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,17 +146,16 @@ 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);
LayerList.refreshZ();
}
function initPalette() {
DEBUG_ARR.push('called Startup -> initPalette');
// Get selected palette
let selectedPalette = Util.getText('palette-button' + splashPostfix);
@ -168,43 +202,8 @@ 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() {
DEBUG_ARR.push('called Startup -> resetInput');
//reset new form
Util.setValue('size-width', 64);
Util.setValue('size-height', 64);
@ -214,6 +213,7 @@ const Startup = (() => {
}
function newFromTemplate(preset, x, y) {
DEBUG_ARR.push('called Startup -> newFromTemplate');
if (preset != '') {
const presetProperties = PresetModule.propertiesOf(preset);
Util.setText('palette-button-splash', presetProperties.palette);
@ -222,10 +222,12 @@ const Startup = (() => {
x = presetProperties.width;
y = presetProperties.height;
}
newPixel(x, y);
newPixel(FileManager.defaultLPE(x,y));
}
function splashEditorMode(mode) {
DEBUG_ARR.push('called Startup -> splashEditorMode');
editorMode = mode;
}

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;
if(FileManager.cacheEnabled)FileManager.localStorageSave();
}
}

View File

@ -47,6 +47,9 @@ const ToolManager = (() => {
if (!EditorState.documentCreated || Dialogue.isOpen())
return;
const isHoveringMenuElement = !!mouseEvent.path.find(n=>n.id && n.id.includes("-menu"));
if(isHoveringMenuElement)return;
let mousePos = Input.getCursorPosition(mouseEvent);
tools["zoom"].onMouseWheel(mousePos, mouseEvent.deltaY < 0 ? 'in' : 'out');
}
@ -109,7 +112,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();
@ -116,7 +116,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

@ -8,7 +8,6 @@
*/
class Layer {
static layerCount = 1;
static maxZIndex = 3;
static unusedIDs = [];
static currentID = 1;
@ -18,7 +17,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 +36,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 +74,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 +168,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 +177,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 +234,8 @@ class Layer {
}
selectLayer(hideOptions = true) {
//console.log('called selectLayer');
////console.trace();
if (hideOptions)
LayerList.closeOptionsMenu();
// Deselecting the old layer
@ -196,6 +245,12 @@ class Layer {
this.isSelected = true;
this.menuEntry.classList.add("selected-layer");
currFile.currentLayer = this;
if(currFile.VFXLayer) { // only refresh z after init
LayerList.refreshZ();
}
if(FileManager.cacheEnabled)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

@ -5,7 +5,9 @@
//=include data/consts.js
//=include data/palettes.js
// str.split(`//=include `).slice(1).map(n=>{
// return `<script src="${jsPath}/${n.split('\n')[0]}"></script>`;
// });
/** UTILITY AND INPUT **/
//=include Util.js
//=include Events.js
@ -77,59 +79,83 @@ 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();
console.log('paletteSlug === ',paletteSlug);
console.log('dimensions === ',dimensions);
//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 = 0;
// if(prefill && prefillWidth){ // TODO
// layers.push({
// id: "layer0",
// name: "Layer 0",
// prefillWidth,
// prefill
// });
// selectedLayer = 0;
// }
let _lpe = FileManager.defaultLPE(width, height, (data.colors ?? []).map(n=>"#"+n));
console.log('_lpe === ',_lpe);
Startup.newPixel(_lpe);
}
//dimensions were not specified -- show splash screen with palette preselected
else {
//show splash
Dialogue.showDialogue('new-pixel', false);
}
})
//error fetching url (either palette doesn't exist, or lospec is down)
.catch((error) => {
//console.warn('failed to load palette "'+paletteSlug+'"', error);
//proceed to splash screen
Dialogue.showDialogue('splash', false);
});
} else {
if(FileManager.cacheEnabled && 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

@ -45,11 +45,11 @@ class EllipseTool extends ResizableTool {
}
onStart(mousePos, mouseTarget) {
super.onStart(mousePos);
if (mouseTarget.className != "drawingCanvas")
if (mouseTarget.className != "drawingCanvas") {
return;
}
super.onStart(mousePos);
// Putting the tmp layer on top of everything
currFile.TMPLayer.canvas.style.zIndex = parseInt(currFile.currentLayer.canvas.style.zIndex, 10) + 1;
@ -65,7 +65,7 @@ class EllipseTool extends ResizableTool {
currFile.TMPLayer.context);
}
/** Finishes drawing the rect, decides the end coordinates and moves the preview rectangle to the
/** Finishes drawing the ellipse, decides the end coordinates and moves the preview ellipse to the
* current layer
*
* @param {*} mousePos The position of the mouse when the user stopped dragging

View File

@ -86,7 +86,9 @@ class EyeDropperTool extends Tool {
// Returned colour
let selectedColor;
for (let i=1; i<currFile.layers.length-3; i++) {
for (let i=0; i<currFile.layers.length; i++) {
if(!currFile.layers[i].isVisible)continue;
// Getting the colour of the pixel in the cursorLocation
tmpColour = currFile.layers[i].context.getImageData(Math.floor(cursorLocation[0]/currFile.zoom),Math.floor(cursorLocation[1]/currFile.zoom),1,1).data;

View File

@ -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

@ -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

@ -31,6 +31,9 @@ class PanTool extends Tool {
for (let i=1; i<currFile.layers.length; i++) {
currFile.layers[i].copyData(currFile.layers[0]);
}
for (let i=0; i<currFile.sublayers.length; i++) {
currFile.sublayers[i].copyData(currFile.layers[0]);
}
}
onEnd(mousePos, target) {

View File

@ -44,8 +44,10 @@ class SelectionTool extends Tool {
this.currSelection = {};
this.moveOffset = [0, 0];
this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1));
this.updateBoundingBox(
Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1)
);
}
onDrag(mousePos) {
@ -66,8 +68,10 @@ class SelectionTool extends Tool {
if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) {
this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1));
this.updateBoundingBox(
Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1)
);
}
}
@ -81,8 +85,10 @@ class SelectionTool extends Tool {
let mouseY = mousePos[1] / currFile.zoom;
if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) {
this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1));
this.updateBoundingBox(
Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1)
);
}
this.boundingBoxCenter = [this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2,
@ -211,6 +217,7 @@ class SelectionTool extends Tool {
}
}
}
////console.log('this.currSelection === ',this.currSelection);
// Save the selection outline
this.outlineData = currFile.VFXLayer.context.getImageData(this.boundingBox.minX,
@ -246,16 +253,25 @@ class SelectionTool extends Tool {
this.boundingBox.minY + this.moveOffset[1]);
}
drawBoundingBox() {
drawBoundingBox(xo = 0, yo = 0) {
currFile.VFXLayer.context.fillStyle = "red";
currFile.VFXLayer.context.fillRect(this.boundingBox.minX + this.moveOffset[0],
this.boundingBox.minY + this.moveOffset[1], 1, 1);
currFile.VFXLayer.context.fillRect(this.boundingBox.minX+ this.moveOffset[0],
this.boundingBox.maxY + this.moveOffset[1], 1, 1);
currFile.VFXLayer.context.fillRect(this.boundingBox.maxX+ this.moveOffset[0],
this.boundingBox.minY + this.moveOffset[1], 1, 1);
currFile.VFXLayer.context.fillRect(this.boundingBox.maxX+ this.moveOffset[0],
this.boundingBox.maxY + this.moveOffset[1], 1, 1);
currFile.VFXLayer.context.fillRect(
this.boundingBox.minX + this.moveOffset[0] - xo,
this.boundingBox.minY + this.moveOffset[1] - yo,
1, 1);
currFile.VFXLayer.context.fillRect(
this.boundingBox.minX + this.moveOffset[0] - xo,
this.boundingBox.maxY + this.moveOffset[1] + yo,
1, 1);
currFile.VFXLayer.context.fillRect(
this.boundingBox.maxX + this.moveOffset[0] + xo,
this.boundingBox.minY + this.moveOffset[1] - yo,
1, 1);
currFile.VFXLayer.context.fillRect(
this.boundingBox.maxX + this.moveOffset[0] + xo,
this.boundingBox.maxY + this.moveOffset[1] + yo,
1, 1);
}
isBorderOfBox(pixel) {

View File

@ -75,8 +75,11 @@ class ZoomTool extends Tool {
}
}
for (let i=1; i<currFile.layers.length; i++) {
for (let i=0; i<currFile.layers.length; i++) {
currFile.layers[i].copyData(currFile.layers[0]);
}
for (let i=0; i<currFile.sublayers.length; i++) {
currFile.sublayers[i].copyData(currFile.layers[0]);
}
}
}

53
old_lpe1.json Normal file
View File

@ -0,0 +1,53 @@
{
"canvasWidth": 128,
"canvasHeight": 128,
"editorMode": "Advanced",
"color0": "#798040",
"color1": "#33ff69",
"color2": "#2f748a",
"nLayers": 7,
"layer4": {
"canvas": {},
"context": {
"mozImageSmoothingEnabled": false
},
"isSelected": false,
"isVisible": true,
"isLocked": false,
"oldLayerName": null,
"menuEntry": {},
"id": "layer1",
"name": "Layer 0"
},
"layer4ImageData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAABCRJREFUeF7tmrFx3DAQRXXlOFYDjtyLS3BkOXIJ7sWRG1DscuShZjADcwgSBD4ofO5TouCIvd3 / H3ZB8h5P / IVW4BG6eop / AoDgEAAAAARXIHj5dAAACK5A8PLpAAAQXIHg5dMBACC4AsHLpwMAQHAFgpdPBwCA4AoEL58OAADBFQhePh0AAIIrELx8OgAABFcgePl0AAAIrkDw8ukAABBcgeDl0wEAILgCwcunAwBAcAWCl08HAIDgCgQvnw4AAPEU + Pby + W2v6t / fP21 + /Pr4dbsNc7uCjnDeMz8Z/ + XH380wP1 / +3E6 vqQp6fvv6vjNH7bSS + UfGJxoA4Gh7dX4 + CoBe4wGg09ja5SMAWJufz / dSqy / lSweodbLxOjUAufm1bX4vdQBoNLZm2WL + MvvT / 5 o1e9ck8xXGMwJ63ahYrwYgdZOzbZ4OUGHWiEuUACyxlMbTAUY4voqpAmCU + Uu6nAEGgtALwIiWvy4XAAYBkB / 8 Wg6Bac3RI97e9AGgV8HC + h4ArjKfETDI / CVsS / vPnxmM3vkcAgea3wLAekxcBUCrDDOPjileBp3pAG7mzz46rABwNH / 28 TENAItQpdfApXcEs7f + fGTMOgamB6B0W + hk / sxjYBoAtnb / 3 jMBAGg9kv6 / bloAjh4IAcBNANia70fmL6UDwE0BqDFfAYDydwI1VnAI3FBpff9fa34PAFcbz23gzvbIAdi7DdwKcXYEfJTxAHDwAqj1d4C1AHy08QCwA0Da9Wdafwp3BMAsxgNAYfbnLV8JwGzGA8DO4S991AtAz2 / 9 a07wimu4C6g4DJ4ROp0dljUjfgB6JpeaawFAAICb6XnJANAIQG56 / r7g6BBYsyuvvAYADtTOjc4vLb0iBgANvlO8DGop5WoA0g7u + d4ZuwAAFOgrmQUALdv1RmsA4EZmtpTSA8D6 + 2 YYCbYjoMU8xRoAUKhoHAMAjM1TpA4AChWNYwCAsXmK1AFAoaJxDAAwNk + ROgAoVDSOAQDG5ilSBwCFiuYxVBDwJNAUBAAwNU6VNgColDSNAwCmxinT7oVghvm / 6 MHbwA4qeiAAgA7hWapTgA6g09IyEgBY2qZLGgB0WlpGAgBL23RJA4BOS8tIAGBpmy5pANBpaRkJACxt0yUNADotLSMBgKVtuqQBQKelZSQAsLRNlzQA6LS0jAQAlrbpkgYAnZaWkQDA0jZd0gCg09IyEgBY2qZLGgB0WlpGAgBL23RJA4BOS8tIAGBpmy5pANBpaRkJACxt0yUNADotLSMBgKVtuqQBQKelZSQAsLRNlzQA6LS0jAQAlrbpkgYAnZaWkQDA0jZd0gCg09IyEgBY2qZLGgB0WlpGAgBL23RJA4BOS8tIAGBpmy5pANBpaRkJACxt0yUNADotLSMBgKVtuqQBQKelZaR / PIgEn3reoTUAAAAASUVORK5CYII = ",
"layer5": {
"canvas": {},
"context": {
"mozImageSmoothingEnabled": false
},
"isSelected": false,
"isVisible": true,
"isLocked": false,
"oldLayerName": null,
"menuEntry": {},
"id": "layer6",
"name": "Layer 1"
},
"layer5ImageData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAA7ZJREFUeF7tnFFyo0AQQ + Mz7an2RDnVnilbqYqrHMo2aNBgLL380gMj6XUDdpLLBz / VDlyq1SP + AwDKIQAAACh3oFw + EwAAyh0ol88EAIByB8rlMwEAoNyBcvlMAAAod6BcPhMAAModKJfPBACAcgfK5TMBAKDcgXL5TAAAKHegXD4TAADKHSiXzwQAgHIHyuUzAQCg3IFy + UwAACh3oFw + EwAAyh0ol88EAIByB8rlMwEAoNyBcvlMAAAod6BcPhMAAModKJfPBPgB4M / X3y + FhX + XzwjvIkQowS1r1eCv6wFgj + snWTsa / vf2AeAkIY5uY0 / 4 ADDq + knW7Q0fAE4S5Mg2HOEDwIjzJ1jjCh8AThCmugVn + G4ARvfmeBCteA0cNfgZZHvNd + xp7x6 + 9 QGAOkp + 6 lXzHYEvt6ru4Z7UeABmGL / 1 FjDr2s4Po6IBmBnAo + 6 beU0mgDiuZ4WxDH / Wddbkcgt44tCMUK6Gzzj3Wtj3jgPAA9fOEtBIqMoaALjjVkv4Wx9E14CKewgEgLXIfx + PAqApfCbAAvS28AEAACy / lBJzC2ACaPf + azUAjPn28lWOV8CoL4PaJgAAlD8DAEAxAK7wuQUccCef8cURAJz8I + DZXxUDwAkB2BKK40F1y3WUwcZroOLWTa0ahCN816d / t5IBQABADf321A4A9lz / kUwAeAKAy3BH + DO6n7eARfiuwJdMOQCYtbeYCSBM8kNLHeHP6v6oCXBoqsLFHADM6n4AEIIcKXWEP7P7AWAkVWGNA4CZ3Q8AQphqqSP82d0PAGqqQr0DgNndDwBCoEqpI / wjuh8AlFSFWgcAR3Q / AAihbi11hH9U9wPA1lSFOgcAR3U / AAjBbil1hH9k9wPAllSFGgcAR3Y / AAjhrpU6wj + 6 + wFgLVXhuAOAo7sfAISA10oBYM2h8ON7AXhF9zMBjFACgNHMdzzVHgBe1f1MACNpAGA08x1PNQrAK7ufCWAkDQCMZr7jqQDgHVMz7nkEgFePf24BANDx7 + KNOcedij8MiYtUEwQAml9x1QAQF6kmCAA0v + KqASAuUk0QAGh + xVUDQFykmiAA0PyKqwaAuEg1QQCg + RVXDQBxkWqCAEDzK64aAOIi1QQBgOZXXDUAxEWqCQIAza + 4 agCIi1QTBACaX3HVABAXqSYIADS / 4 qoBIC5STRAAaH7FVQNAXKSaIADQ / IqrBoC4SDVBAKD5FVcNAHGRaoIAQPMrrhoA4iLVBAGA5ldcNQDERaoJAgDNr7jq / yPDGJDPZGMlAAAAAElFTkSuQmCC ",
"layer6": {
"canvas": {},
"context": {
"mozImageSmoothingEnabled": false
},
"isSelected": true,
"isVisible": true,
"isLocked": false,
"oldLayerName": null,
"menuEntry": {},
"id": "layer7",
"name": "Layer 2"
},
"layer6ImageData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAA9VJREFUeF7tnFuO1TAQRGdWxOYQi0BsjhWBrsQVmSgPt92VVNxnfse4y1UnZQshPj / 4 Ke3AZ + nTc / gPACgOAQAAQHEHih + fBgCA4g4UPz4NAADFHSh + fBoAAIo7UPz4NAAAFHeg + PFpAAAo7kDx49MAAFDcgeLHpwEAoLgDxY9PAwBAcQeKH58GAIDiDhQ / fqkG + Pb9559o3r9 //Zjao6kPtw67B4DlHjPCUAaA0fC3mmMGIAAgeiccrH8iECUAUHz9R9w8CQQASGyA9VZPAGF6AK7++p/2VgAAYQNEGiEKala7AMCFALxG7QUHAIIgoqYKJOxuuQYhqpUGaEgramrDlqlLliFGtQJAQxRRUxu2TF/yDrJHawYEU78BekxNT1i4IQAcmDt7+EcPyghz0zYAALRhAAAbPo08ztpsz1nFFSC6AraMdWwUANgBYCSsM1NH9s757v/vcqa1Zd6UV8BISK2mjsxoCaZlTavWo70AYOVO1NQ7QYhq3QIBABau9Bp6FwS9epcgAEACAO8trgYBAJIfgRmGXglBht7pGmAkgAxDX0yOaGh5/L3XZOgFgH9uZpi5Dk8NQoZmABACoG4DANjoy96vLsPMvfru1XR2HWRopgHEDaBsAQBIaoAMI8++VkULZOimAQ7+oeZZqNHfZ0MAAA9qAMVfFgEAAAw3+PAG0RpUr++p2YwvKXKuHo1b+2foBoAL3wDLEEchyAj/pac8AFlGRhog4z2QpRsAbv4vYHqaICt8GuCm+u9pC9WfoQFubgBVsK37AgAAtLLyjHXROzXzPn2GQ19V0gA0wBO53ddMA8TypAFogBgx7qtpgFhCNAANECPGfTUNEEuIBqABYsS4r6YBYgnRADRAjBj31TRALCEagAaIEeO+mgaIJUQD0AAxYtxX0wCxhGgAGiBGDKvncmC6BpgrHv1pAEDvsfUEALCORy8OAPQeW08AAOt49OIAQO+x9QQAsI5HLw4A9B5bTwAA63j04gBA77H1BACwjkcvDgD0HltPAADrePTiAEDvsfUEALCORy8OAPQeW08AAOt49OIAQO+x9QQAsI5HLw4A9B5bTwAA63j04gBA77H1BACwjkcvDgD0HltPAADrePTiAEDvsfUEALCORy8OAPQeW08AAOt49OIAQO+x9QQAsI5HLw4A9B5bTwAA63j04gBA77H1BACwjkcvDgD0HltPAADrePTiAEDvsfUEALCORy8OAPQeW08AAOt49OIAQO+x9QQAsI5HLw4A9B5bTwAA63j04gBA77H1BACwjkcvDgD0HltPAADrePTi/gKPB0SQMNaI5gAAAABJRU5ErkJggg=="
}

35
package-lock.json generated
View File

@ -21,7 +21,7 @@
"nodemon": "^2.0.7",
"open": "^8.0.6",
"open-cli": "^6.0.1",
"sass": "^1.17.3"
"sass": "^1.49.7"
},
"devDependencies": {
"cross-env": "7.0.3",
@ -6069,18 +6069,19 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sass": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.44.0.tgz",
"integrity": "sha512-0hLREbHFXGQqls/K8X+koeP+ogFRPF4ZqetVB19b7Cst9Er8cOR0rc6RU7MaI4W1JmUShd1BPgPoeqmmgMMYFw==",
"version": "1.49.7",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.7.tgz",
"integrity": "sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0"
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=8.9.0"
"node": ">=12.0.0"
}
},
"node_modules/sass/node_modules/anymatch": {
@ -6523,6 +6524,14 @@
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-resolve": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
@ -12614,12 +12623,13 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sass": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.44.0.tgz",
"integrity": "sha512-0hLREbHFXGQqls/K8X+koeP+ogFRPF4ZqetVB19b7Cst9Er8cOR0rc6RU7MaI4W1JmUShd1BPgPoeqmmgMMYFw==",
"version": "1.49.7",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.7.tgz",
"integrity": "sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==",
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0"
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"dependencies": {
"anymatch": {
@ -12959,6 +12969,11 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
},
"source-map-resolve": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",

View File

@ -28,7 +28,7 @@
"nodemon": "^2.0.7",
"open": "^8.0.6",
"open-cli": "^6.0.1",
"sass": "^1.17.3"
"sass": "^1.49.7"
},
"devDependencies": {
"cross-env": "7.0.3",

View File

@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<img src="/images/test_8x8.png" id="test_image" style="display:none;">
<script src="/js/canvas_util.js"></script>
<script src="/js/color_utils.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.40.2/codemirror.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.40.2/codemirror.css" rel="stylesheet" />
<div id="target" style="position:fixed;top:0;left:0;right:50%;bottom:0;">
<textarea id="editorTextArea">
const word = Math.random() > 0.5 ? "Hello" : "Goodbye cruel";
alert(`${word} world`);
</textarea>
</div>
<div id="preview_div" style="position:fixed;top:0;left:50%;right:0;bottom:90%;">
</div>
<div id="debug_div" style="position:fixed;top:10%;left:50%;right:0;bottom:0;overflow:scroll;">
</div>
</head>
<body>
</body>
</html>
<script>
const charW = 8;
const charH = 8;
const charArr = generateCharsFromFont("monospace", charW, charH, 8, 8, preview_div, debug_div);
////console.log('charArr === ',charArr);
const editorTextArea = document.getElementById('editorTextArea');
const cm = new CodeMirror.fromTextArea(editorTextArea, {
lineNumbers: true
});
cm.setSize("100%", "100%");
CodeMirror.on(cm, "cursorActivity", function() {
const isSomethingSelected = cm.somethingSelected();
////console.log(isSomethingSelected);
const cursor = cm.getCursor();
////console.log(cursor);
if (isSomethingSelected) {
const text = cm.getSelection();
const cursor = cm.getCursor();
const selectionData = {text,cursor};
////console.log("selectionData:",selectionData);
}
});
let count = 0;
cm.setValue(`\
const CHAR = [
${charArr.map((n,i)=>` \`${linebreakify(n,charW)}\`, //${String.fromCharCode(33+i)} `).join("\n")}
];
`);
// setInterval(function(){
// count++;
// cm.replaceRange(count%2 ? "Hi" : "Hello",{line:0,ch:36},{line:0,ch:(count%2 ? 41 : 38)})
// },1000);
function linebreakify(str,cols = 4) {
const arr = str.split("");
const arr2 = [];
for(let i = 0; i < arr.length;i++){
if((i%cols)===0)arr2.push("");
arr2[arr2.length-1] += arr[i];
}
return "\\\n"+arr2.join("\n");
}
</script>

View File

@ -0,0 +1,269 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<img src="/images/test_8x8.png" id="test_image" style="display:none;">
<script src="/js/canvas_util.js"></script>
<script src="/js/color_utils.js"></script>
<style>
#content_wrapper{
display: flex;
flex-direction: column;
}
</style>
</head>
<body>
<div id="content_wrapper">
<div id="orig_colors_wrapper"></div>
<div id="color_compare_wrapper"></div>
<div id="preview_wrapper"></div>
</div>
</body>
</html>
<script>
window.ROTATION_CACHE = {};
window.SCORE_CACHE = {};
window.onload = () => {
const p0 = 30;
const p1 = 40;
const p2 = 50;
const p3 = 60;
const p4 = 70;
const offset = 50;
const to = -240;
const testArr = [
[Math.PI * 0.000, test_image, `calc(${p2}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
[Math.PI * 0.825, test_image, `calc(${p1}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
[Math.PI * 0.750, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
[Math.PI * 0.625, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p1}% - ${offset + to}px)`],
[Math.PI * 0.500, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p2}% - ${offset + to}px)`],
[Math.PI * 0.375, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p3}% - ${offset + to}px)`],
[Math.PI * 0.250, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
[Math.PI * 0.125, test_image, `calc(${p1}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
[Math.PI *-0.125, test_image, `calc(${p3}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
[Math.PI *-0.250, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
[Math.PI *-0.375, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p3}% - ${offset + to}px)`],
[Math.PI *-0.500, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p2}% - ${offset + to}px)`],
[Math.PI *-0.625, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p1}% - ${offset + to}px)`],
[Math.PI *-0.750, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
[Math.PI *-0.825, test_image, `calc(${p3}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
[Math.PI *-1.000, test_image, `calc(${p2}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
];
const canvasArr = testArr.map(n=>canvasRotateTest(...n));
canvasArr.forEach(canvas=>{
preview_wrapper.appendChild(canvas);
});
insertOrigColors();
}
function insertOrigColors(){
////console.log('window.ORIG_IMAGE_META.colorCounts === ',window.ORIG_IMAGE_META.colorCounts);
Object.keys(window.ORIG_IMAGE_META.colorCounts).forEach(colorStr=>{
////console.log('colorStr === ',colorStr);
const div = document.createElement('div');
const size = 42;
div.innerHTML = `\
<div style="float:left;margin:1px;">
<div style="float:left;width:${size}px;height:${size}px;background-color:${colorStr}"></div>
</div>`;
div.onclick = () => {
//////console.log('scores === ',scores);
}
orig_colors_wrapper.appendChild(div);
});
}
function canvasRotateTest(radians = 0, testImage, left, top) {
if(radians === 0)window.ORIG_IMAGE_META = imageMeta(testImage);
const canvas = document.createElement('canvas');
// const w = 1000;
// const h = 1000;
// const p = 100;
const rotationCacheKey = `${testImage.src}_${radians}`;
const rotationCacheKey0 = `${testImage.src}_${0}`;
//////console.log('rotationCacheKey, rotationCacheKey0 === ',rotationCacheKey, rotationCacheKey0);
const p = 1;
const x = p;
const y = p;
const w = (testImage.width + (p*2)) ?? 100;
const h = (testImage.height + (p*2)) ?? 100;
const sw = testImage.width;
const sh = testImage.height;
const sw2 = sw/2;
const sh2 = sh/2;
const sr = [x,y,sw,sh];
const center = [sr[0] + sw2, sr[1] + sh2];
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
ctx.save();
ctx.translate(center[0],center[1]);
ctx.fillStyle = "#fff";
ctx.rotate(radians);
ctx.translate(-center[0],-center[1]);
ctx.lineWidth = 10;
ctx.strokeStyle = "#000";
if(testImage) {
ctx.drawImage(testImage, sr[0], sr[1]);
} else {
ctx.strokeRect(...sr);
}
canvas.style.imageRendering = "pixelated";
canvas.style.transform = `scale(8)`;
canvas.style.position = "fixed";
canvas.style.top = top;
canvas.style.left = left;
if(radians==0)return canvas;
const imageData = ctx.getImageData(0,0,w,h);
const colorCounts = countImageDataColors(imageData);
const uniqueColors = Object.keys(colorCounts);
const uniqueFullColors = Object.keys(colorCounts).filter(n=>n.includes(",255)"));
//////console.log('uniqueFullColors === ',uniqueFullColors);
const bestMatches = {};
uniqueFullColors.forEach(colorStr=>{
const rgb = colorToRGB(colorStr);
const orig = window.ROTATION_CACHE[rotationCacheKey0] ?? {uniqueFullColors:[...uniqueFullColors]};
let minScore = Infinity;
let maxScore = -Infinity;
let minScoreChampion;
let maxScoreChampion;
const scores = orig.uniqueFullColors.map(origColor=>{
const origRgb = colorToRGB(origColor);
const scoreKey = `${rgb.r},${rgb.g},${rgb.b}_${origRgb.r},${origRgb.g},${origRgb.b}`;
// //////console.log('rgb, origRgb === ',rgb, origRgb);
const score = window.SCORE_CACHE[scoreKey] ?? similarColours(rgb, origRgb);//TODO: find a fire wizard to optimize this
window.SCORE_CACHE[scoreKey] = score;
const betterMinScore = minScore > score;
const betterMaxScore = maxScore < score;
if(betterMinScore) {
minScore = score;
minScoreChampion = origRgb;
}
if(betterMaxScore) {
maxScore = score;
maxScoreChampion = origRgb;
}
return score;
});
bestMatches[colorStr] = {
min:minScoreChampion,
max:maxScoreChampion,
scores
};
});
for(let i = 0; i < imageData.data.length;i += 4) {// <-- NOTE the 4 here
const r = imageData.data[i];
const g = imageData.data[i+1];
const b = imageData.data[i+2];
const a = imageData.data[i+3];//TODO: perf diffs between overwriting 'a' vs ignoring it
const rgbStr = `rgba(${r},${g},${b},${a})`;
const meta = bestMatches[rgbStr];
if(meta) {
// const bestRgb = meta.max;
const bestRgb = meta.min;
if(a === 255){
imageData.data[i] = bestRgb.r;
imageData.data[i+1] = bestRgb.g;
imageData.data[i+2] = bestRgb.b;
} else { // probably not needed since a<255 colors are not currently added to bestMatches
//////console.log('alpha < 255');
imageData.data[i] = 0;
imageData.data[i+1] = 0;
imageData.data[i+2] = 0;
imageData.data[i+3] = 0;
}
} else if (a < 255) {
imageData.data[i] = 0;
imageData.data[i+1] = 0;
imageData.data[i+2] = 0;
imageData.data[i+3] = 0;
}
// //////console.log('meta === ',meta);
}
ctx.putImageData(imageData,0,0);
//////console.log('bestMatches === ',bestMatches);
//////console.log('uniqueColors.length === ', uniqueColors.length);
//////console.log('uniqueFullColors.length === ', uniqueFullColors.length);
window.ROTATION_CACHE[(!window.ROTATION_CACHE[rotationCacheKey0]) ? rotationCacheKey0 : rotationCacheKey] = {
canvas,
ctx,
imageData,
uniqueColors,
uniqueFullColors,
bestMatches
};
const color_compare_row = document.createElement('div');
color_compare_row.style.display = "flex";
color_compare_row.style.borderBottom = "2px solid black";
color_compare_row.style.marginBottom = "2x";
color_compare_row.setAttribute("class","color-compare-row");
Object.keys(bestMatches).forEach(colorStr=>{
const div = document.createElement('div');
const {min,max,scores} = bestMatches[colorStr];
const minMatchColor = `rgba(${min.r},${min.g},${min.b},255)`;
const maxMatchColor = `rgba(${max.r},${max.g},${max.b},255)`;
div.innerHTML = `\
<div style="display:flex;margin:1px;">
<div style="width:10px;height:20px;background-color:${colorStr}"></div>
<div style="width:10px;height:20px;background-color:${minMatchColor}"></div>
</div>`;
div.onclick = () => {
////console.log('scores === ',scores);
}
color_compare_row.appendChild(div);
});
color_compare_wrapper.appendChild(color_compare_row);
color_compare_wrapper.setAttribute("class","color-compare-wrapper");
return canvas;
}
function countImageDataColors(imageData) {
const w = imageData.width;
const h = imageData.height;
const colorCounts = {};
const startTime = window.performance.now();
for (let x = 0; x < w; x++) {
for (let y = 0; y < h; y++) {
const i = (x + y * w) * 4;
const r = imageData.data[i];
const g = imageData.data[i + 1];
const b = imageData.data[i + 2];
const a = imageData.data[i + 3];
const key = `rgba(${r},${g},${b},${a})`;
if (!colorCounts[key]) colorCounts[key] = 0;
colorCounts[key]++;
}
}
const endTime = window.performance.now();
//////console.log('Count ImageData Colors duration (ms) === ',endTime - startTime);
return colorCounts;
}
function imageMeta(img) {
const canvas = imageToCanvas(img);
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0,0,img.width,img.height);
return {
img,
colorCounts: countImageDataColors(imageData),
};
}
function imageToCanvas(img) {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
return canvas;
}
</script>

View File

@ -0,0 +1,268 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- <img src="/images/test_8x8.png" id="test_image" style="display:none;"> -->
<img src="/images/sked_tree_32x32.png" id="test_image" style="display:none;">
<script src="/js/canvas_util.js"></script>
<script src="/js/color_utils.js"></script>
<style>
#content_wrapper{
display: flex;
flex-direction: column;
}
</style>
</head>
<body>
<div id="content_wrapper">
<div id="orig_colors_wrapper"></div>
<div id="color_compare_wrapper"></div>
<div id="preview_wrapper"></div>
</div>
</body>
</html>
<script>
window.ROTATION_CACHE = {};
window.SCORE_CACHE = {};
window.onload = () => {
const p0 = 30;
const p1 = 40;
const p2 = 50;
const p3 = 60;
const p4 = 70;
const offset = 50;
const to = -40;
const testArr = [
[Math.PI * 0.000, test_image, `calc(${p2}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
[Math.PI * 0.825, test_image, `calc(${p1}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
// [Math.PI * 0.750, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
// [Math.PI * 0.625, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p1}% - ${offset + to}px)`],
// [Math.PI * 0.500, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p2}% - ${offset + to}px)`],
// [Math.PI * 0.375, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p3}% - ${offset + to}px)`],
// [Math.PI * 0.250, test_image, `calc(${p0}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
// [Math.PI * 0.125, test_image, `calc(${p1}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
// [Math.PI *-0.125, test_image, `calc(${p3}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
// [Math.PI *-0.250, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p4}% - ${offset + to}px)`],
// [Math.PI *-0.375, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p3}% - ${offset + to}px)`],
// [Math.PI *-0.500, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p2}% - ${offset + to}px)`],
// [Math.PI *-0.625, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p1}% - ${offset + to}px)`],
// [Math.PI *-0.750, test_image, `calc(${p4}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
// [Math.PI *-0.825, test_image, `calc(${p3}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
// [Math.PI *-1.000, test_image, `calc(${p2}% - ${offset}px)`, `calc(${p0}% - ${offset + to}px)`],
];
const canvasArr = testArr.map(n=>canvasRotateTest(...n));
canvasArr.forEach(canvas=>{
preview_wrapper.appendChild(canvas);
});
insertOrigColors();
}
function insertOrigColors(){
Object.keys(window.ORIG_IMAGE_META.colorCounts).forEach(colorStr=>{
const div = document.createElement('div');
const size = 42;
div.innerHTML = `\
<div style="float:left;margin:1px;">
<div style="float:left;width:${size}px;height:${size}px;background-color:${colorStr}"></div>
</div>`;
div.onclick = () => {
//////console.log('scores === ',scores);
}
orig_colors_wrapper.appendChild(div);
});
}
function canvasRotateTest(radians = 0, testImage, left, top) {
if(radians === 0)window.ORIG_IMAGE_META = imageMeta(testImage);
const canvas = document.createElement('canvas');
// const w = 1000;
// const h = 1000;
// const p = 100;
const rotationCacheKey = `${testImage.src}_${radians}`;
const rotationCacheKey0 = `${testImage.src}_${0}`;
//////console.log('rotationCacheKey, rotationCacheKey0 === ',rotationCacheKey, rotationCacheKey0);
const p = 1;
const x = p;
const y = p;
const w = (testImage.width + (p*2)) ?? 100;
const h = (testImage.height + (p*2)) ?? 100;
const sw = testImage.width;
const sh = testImage.height;
const sw2 = sw/2;
const sh2 = sh/2;
const sr = [x,y,sw,sh];
const center = [sr[0] + sw2, sr[1] + sh2];
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
ctx.save();
ctx.translate(center[0],center[1]);
ctx.fillStyle = "#fff";
ctx.rotate(radians);
ctx.translate(-center[0],-center[1]);
ctx.lineWidth = 10;
ctx.strokeStyle = "#000";
if(testImage) {
ctx.drawImage(testImage, sr[0], sr[1]);
} else {
ctx.strokeRect(...sr);
}
canvas.style.imageRendering = "pixelated";
canvas.style.transform = `scale(4)`;
canvas.style.position = "fixed";
canvas.style.top = top;
canvas.style.left = left;
if(radians==0)return canvas;
const imageData = ctx.getImageData(0,0,w,h);
const colorCounts = countImageDataColors(imageData);
const uniqueColors = Object.keys(colorCounts);
const uniqueFullColors = Object.keys(colorCounts).filter(n=>n.includes(",255)"));
//////console.log('uniqueFullColors === ',uniqueFullColors);
const bestMatches = {};
uniqueFullColors.forEach(colorStr=>{
const rgb = colorToRGB(colorStr);
const orig = window.ROTATION_CACHE[rotationCacheKey0] ?? {uniqueFullColors:[...uniqueFullColors]};
let minScore = Infinity;
let maxScore = -Infinity;
let minScoreChampion;
let maxScoreChampion;
const scores = orig.uniqueFullColors.map(origColor=>{
const origRgb = colorToRGB(origColor);
const scoreKey = `${rgb.r},${rgb.g},${rgb.b}_${origRgb.r},${origRgb.g},${origRgb.b}`;
// //////console.log('rgb, origRgb === ',rgb, origRgb);
const score = window.SCORE_CACHE[scoreKey] ?? similarColours(rgb, origRgb);//TODO: find a fire wizard to optimize this
window.SCORE_CACHE[scoreKey] = score;
const betterMinScore = minScore > score;
const betterMaxScore = maxScore < score;
if(betterMinScore) {
minScore = score;
minScoreChampion = origRgb;
}
if(betterMaxScore) {
maxScore = score;
maxScoreChampion = origRgb;
}
return score;
});
bestMatches[colorStr] = {
min:minScoreChampion,
max:maxScoreChampion,
scores
};
});
for(let i = 0; i < imageData.data.length;i += 4) {// <-- NOTE the 4 here
const r = imageData.data[i];
const g = imageData.data[i+1];
const b = imageData.data[i+2];
const a = imageData.data[i+3];//TODO: perf diffs between overwriting 'a' vs ignoring it
const rgbStr = `rgba(${r},${g},${b},${a})`;
const meta = bestMatches[rgbStr];
if(meta) {
// const bestRgb = meta.max;
const bestRgb = meta.min;
if(a === 255){
imageData.data[i] = bestRgb.r;
imageData.data[i+1] = bestRgb.g;
imageData.data[i+2] = bestRgb.b;
} else { // probably not needed since a<255 colors are not currently added to bestMatches
//////console.log('alpha < 255');
imageData.data[i] = 0;
imageData.data[i+1] = 0;
imageData.data[i+2] = 0;
imageData.data[i+3] = 0;
}
} else if (a < 255) {
imageData.data[i] = 0;
imageData.data[i+1] = 0;
imageData.data[i+2] = 0;
imageData.data[i+3] = 0;
}
// //////console.log('meta === ',meta);
}
ctx.putImageData(imageData,0,0);
//////console.log('bestMatches === ',bestMatches);
//////console.log('uniqueColors.length === ', uniqueColors.length);
//////console.log('uniqueFullColors.length === ', uniqueFullColors.length);
window.ROTATION_CACHE[(!window.ROTATION_CACHE[rotationCacheKey0]) ? rotationCacheKey0 : rotationCacheKey] = {
canvas,
ctx,
imageData,
uniqueColors,
uniqueFullColors,
bestMatches
};
const color_compare_row = document.createElement('div');
color_compare_row.style.display = "flex";
color_compare_row.style.borderBottom = "2px solid black";
color_compare_row.style.marginBottom = "2x";
color_compare_row.setAttribute("class","color-compare-row");
Object.keys(bestMatches).forEach(colorStr=>{
const div = document.createElement('div');
const {min,max,scores} = bestMatches[colorStr];
const minMatchColor = `rgba(${min.r},${min.g},${min.b},255)`;
const maxMatchColor = `rgba(${max.r},${max.g},${max.b},255)`;
div.innerHTML = `\
<div style="display:flex;margin:1px;">
<div style="width:10px;height:20px;background-color:${colorStr}"></div>
<div style="width:10px;height:20px;background-color:${minMatchColor}"></div>
</div>`;
div.onclick = () => {
////console.log('scores === ',scores);
}
color_compare_row.appendChild(div);
});
color_compare_wrapper.appendChild(color_compare_row);
color_compare_wrapper.setAttribute("class","color-compare-wrapper");
return canvas;
}
function countImageDataColors(imageData) {
const w = imageData.width;
const h = imageData.height;
const colorCounts = {};
const startTime = window.performance.now();
for (let x = 0; x < w; x++) {
for (let y = 0; y < h; y++) {
const i = (x + y * w) * 4;
const r = imageData.data[i];
const g = imageData.data[i + 1];
const b = imageData.data[i + 2];
const a = imageData.data[i + 3];
const key = `rgba(${r},${g},${b},${a})`;
if (!colorCounts[key]) colorCounts[key] = 0;
colorCounts[key]++;
}
}
const endTime = window.performance.now();
////console.log('Count ImageData Colors duration (ms) === ',endTime - startTime);
return colorCounts;
}
function imageMeta(img) {
const canvas = imageToCanvas(img);
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0,0,img.width,img.height);
return {
img,
colorCounts: countImageDataColors(imageData),
};
}
function imageToCanvas(img) {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
return canvas;
}
</script>

45
poc_pages/tiny_text.html Normal file
View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="chaotic_good"></canvas>
<canvas id="neutral_good"></canvas>
<canvas id="lawful_good"></canvas>
<canvas id="chaotic_neutral"></canvas>
<canvas id="true_neutral"></canvas>
<canvas id="lawful_neutral"></canvas>
<canvas id="chaotic_evil"></canvas>
<canvas id="neutral_evil"></canvas>
<canvas id="lawful_evil"></canvas>
</body>
</html>
<script src="/js/canvas_util.js"></script>
<script src="/js/color_utils.js"></script>
<script>
const canvas = document.createElement('canvas');
const w = 256;
const h = 256;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
ctx.fillRect(0,0,w,h);
ctx.fillStyle = 'red';
ctx.fillRect(1,1,w-2,h-2);
canvas.style.imageRendering = "pixelated";
canvas.style.transform = `scale(4)`;
canvas.style.position = "fixed";
canvas.style.top = "calc(50% - "+(h/2)+"px)";
canvas.style.left = "calc(50% - "+(w/2)+"px)";
document.body.appendChild(canvas);
ctx.fillStyle = 'black';
drawTinyText(ctx, "HELLO", 7, 7, "monospace");
</script>

View File

@ -0,0 +1,120 @@
<html>
<head>
<title>Basic three.js template</title>
<style>
body { margin: 0; }
canvas {
/* position: fixed; */
/* top: 450px;
left: 450px; */
/* transform: scale(4); */
image-rendering: pixelated;
}
</style>
</head>
<body>
<!-- import the three.js library -->
<img src="/images/wang_tilesets_32x32.png" style="display:none;" id="tiles_image">
<img src="/images/icons_14x14.png" data-tile-width="14" data-tile-height="14" style="display:none;" id="icons_img1">
<script src="/js/canvas_util.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/three@0.122.0/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.122.0/examples/js/controls/OrbitControls.js"></script> -->
<script>
// var scene = new THREE.Scene();
// var camera = new THREE.PerspectiveCamera(
// 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
// var renderer = new THREE.WebGLRenderer();
// renderer.setSize( window.innerWidth, window.innerHeight );
// document.body.appendChild( renderer.domElement );
// camera.position.z = 5;
// const controls = new THREE.OrbitControls( camera, renderer.domElement );
//var geometry = new THREE.BoxGeometry( 1, 1, 1 );
//var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
//var cube = new THREE.Mesh( geometry, material );
//scene.add( cube );
const data = `
10000000
00100000
01110010
00100010
00000010
00111000
00000010
00000000
`
.replaceAll("\n","")
.replaceAll(" ","")
.replaceAll("\t","")
;
const ASSET_CACHE = {};
const TOOL_ARR = [
// {name:"Pencil",hotkey:"B",art:{img: icons_img1,idx:0}},
{name:"Eraser",hotkey:"E",art:{img: icons_img1,idx:1}},
{name:"Rectangle",hotkey:"R",art:{img: icons_img1,idx:2}},
{name:"Ellipse",hotkey:"S",art:{img: icons_img1,idx:2}},
{name:"Line",hotkey:"S",art:{img: icons_img1,idx:2}},
{name:"Line",hotkey:"S",art:{img: icons_img1,idx:2}},
];
window.onload = () => {
const columns = 8;
const tilesetsArr = imageChopper(tiles_image,32,32);
const tilesetsCanvasArrArr = tilesetsArr.map((canvas)=>{
return imageChopper(canvas,8,8);
});
////console.log('tilesetsCanvasArrArr === ', tilesetsCanvasArrArr);
////console.log('data === ',data);
let i = 0;
const width = columns;
const height = 8;
const tileIndexArr = [];
for(let i = 0; i < data.length;i++) {
// debugger;
const x = i%width;
const y = Math.floor(i/width);
const v = data[i];
let binNum = -1;
if(v === "1") {
// if(i === 10)debugger;
const n = y > 0 ? data[i-width] : "0";
const e = x < (width-1) ? data[i+1] : "0";
const s = data[i+width];
const w = i > 0 ? data[i-1] : "0";
const binStr = `${w}${s}${e}${n}`;
binNum = parseInt(binStr,2);
////console.log(v,`data[${i}]`,w,s,e,n,"= "+binNum);
}
tileIndexArr.push(binNum);
}
////console.log('tileIndexArr === ',tileIndexArr);
const retCanvas = tilesToCanvas(tileIndexArr,columns,tilesetsCanvasArrArr[1]);
////console.log('retCanvas === ',retCanvas);
document.body.appendChild(retCanvas);
}
function insertTools(arr){
arr.forEach((tool,i)=>{
if(!ASSET_CACHE[tool.art.img.src]){
ASSET_CACHE[tool.art.img.src] = imageChopper(
tool.art.img,
Number(tool.art.img.getAttribute("data-tile-width")),
Number(tool.art.img.getAttribute("data-tile-height"))
);
}
tool.art.canvas = ASSET_CACHE[tool.art.img.src][tool.art.idx];
// const canvas = document.createElement('canvas');
// const w = 64;
// const h = 64;
// canvas.width = w;
// canvas.height = h;
// const ctx = canvas.getContext('2d');
const canvas = pixelButton(0,0,0,0,tool.art.canvas);
document.body.appendChild(canvas);
});
////console.log('ASSET_CACHE === ',ASSET_CACHE);
}
</script>
</body>
</html>

View File

@ -9,7 +9,7 @@ const FULLBUILDPATH = path.join(__dirname, BUILDDIR)
//LOGGING
app.use((req, res, next)=> {
//console.log('REQUEST', req.method+' '+req.originalUrl, res.statusCode);
//////console.log('REQUEST', req.method+' '+req.originalUrl, res.statusCode);
next();
});
@ -29,17 +29,29 @@ app.use((req, res, next) => {
app.use('/', express.static(FULLBUILDPATH, {
//custom function required for logging static files
setHeaders: (res, filePath, fileStats) => {
console.info('GET', '/'+path.relative(FULLBUILDPATH, filePath), res.statusCode);
//console.info('GET', '/'+path.relative(FULLBUILDPATH, filePath), res.statusCode);
}
}));
//ROUTE - match / or any route with just numbers letters and dashes, and return index.htm (all other routes should have been handled already)
app.get('/', (req, res, next) => {
console.log('root')
////console.log('root')
res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) {
console.log('sent file');
////console.log('sent file');
return next();
});
});
app.get('/pixel-editor', (req, res, next) => {
////console.log('root')
res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) {
////console.log('sent file');
return next();
});
});
app.get('/pixel-editor/?:palette/?:resolution/?:patternWidth/?:patternBinStr', (req, res, next) => {
////console.log('root')
res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) {
////console.log('sent file');
return next();
});
});
@ -52,13 +64,13 @@ if (process.env.RELOAD === "yes") {
reload(app).then(() => {
//start server
app.server = app.listen(PORT, () => {
console.log(`Web server listening on port ${PORT} (with reload module)`);
////console.log(`Web server listening on port ${PORT} (with reload module)`);
})
});
} else {
app.listen(PORT, () => {
console.log(`Web server listening on port ${PORT}`);
////console.log(`Web server listening on port ${PORT}`);
})
}
@ -70,5 +82,5 @@ app.use(function(req, res, next) {
//LOGGING
app.use((req, res, next)=> {
console.log(req.method+' '+req.originalUrl, res.statusCode);
////console.log(req.method+' '+req.originalUrl, res.statusCode);
});

View File

@ -1,7 +1,7 @@
<!-- CANVASES -->
<div id="canvas-view">
<canvas id="vfx-canvas" class = "drawingCanvas"></canvas>
<canvas id = "tmp-canvas" class = "drawingCanvas"></canvas>
<canvas id="tmp-canvas" class = "drawingCanvas"></canvas>
<canvas id="pixel-canvas" class = "drawingCanvas"></canvas>
<canvas id="checkerboard" class = "drawingCanvas"></canvas>
<canvas id="pixel-grid" class = "drawingCanvas"></canvas>

View File

@ -1,3 +1,18 @@
<span id="colors-menu-settings">
<button id="cm-add">
Add color
</button>
<button id="cm-remove">
Remove color
</button>
<button id="cm-zoomin">
Zoom in
</button>
<button id="cm-zoomout">
Zoom out
</button>
</span>
<ul id="colors-menu">
<li class="noshrink"><button title="Add Current Color To Palette" id="add-color-button">{{svg "./plus.svg" width="30" height="30"}}</button></li>
</ul>

View File

@ -18,8 +18,11 @@
{{> main-menu}}
{{> tools-menu}}
{{> colors-menu}}
{{> layers-menu}}
<div id="right-nav">
{{> colors-menu}}
{{> layers-menu}}
</div>
{{> tool-previews}}
{{> canvases}}

View File

@ -1,52 +1,20 @@
Heyo! New pixel editor update, with some very requested changes. After the code refactoring, adding features
is way easier: I introduced some more selection tools, the ellipse tool and an info bar in the top menu :)
<h2>Lasso tool</h2>
Finally! With the lasso tool you're not forced to select rectangular areas anymore. Have fun selecting, cutting,
copying and pasting any kind of selection with pixel-perfect precision.
</br><img src="lassoselect-tutorial.gif"/>
<h2>Magic wand</h2>
In addition to the lasso tool, we added a new selection tool: the magic wand. You can use it to select
contiguous areas of the same colour! If you need to exactly select the pixels of a certain colour, you're
probably going to find the magic wand useful.
</br><img src="magicwand-tutorial.gif"/>
<h2>Ellipse tool</h2>
I added a cute friend for the rectangle tool: with the ellipse tool you'll be able to draw circles and
ellipses of all sizes. The tool works similarly to the rectangle tool: select it to draw empty ellipses,
click on the ellipse button again to draw filled ellipses.
</br><img src="ellipse-tutorial.gif"/>
<h2>Tool tutorials</h2>
I know what you're thinking, "wow those gifs are so cute, that guy must have put a lot of love in them".
Well, I'm glad you like them, and I have good news for you: there are more! Move the cursor on a tool
button: after a little a small tutorial explaining how to use the tool will appear. Hope it's useful for
everyone who's new to the editor!
</br><img src="tool-tutorials.gif"/>
<h2>Top bar info</h2>
Depending on the tool you're using, you'll notice that the top right part of the editor will slightly change.
When using a resizable tool (eraser, brush, rectangle, ellipse, line), it's now possible to select a precise
size by typing it in the input field that appears when you select it. More features that make use of the
top bar are planned.
<h2>Bug fixes and minor details</h2>
Hi! Been a while after the last update, the lack of contributors slowed down the support a bit. You may have seen
it from the Lospec homepage, but this editor has been liberated and released under the GPL-3.0 license! This
probably incentivized more contributors to help with development. I want to thank them for their work.
<ul>
<li>Squares in the splash page are now...well...actual squares</li>
<li>Using the mouse when a dialogue popup is open will no longer edit the canvas</li>
<li>For coders: the selection system has been uniformed</li>
<li>Tool buttons have been shrinked to make room for more</li>
<li><a href="https://github.com/NSSure">NSSure</a> added the ability to import images in the editor. Very
useful if you need to have a reference layer!</li>
<li><a href="https://github.com/blueprismo">Blueprismo</a> added a Dockerfile to the repository, making the
editor even more accessible.</li>
<li><a href="https://github.com/hacknorris-aka-penguin">hacknorris-aka-penguin</a> and
<a href="https://github.com/matthewd673">matthewd673</a> fixed a few bugs we had.</li>
</ul>
In addition, we had a pretty big contribution by <a href="https://github.com/jbrundage">Jaman</a>. He did some
needed refactoring that will help us with development. In addition, he kindly added a new palette layout so that
big palettes aren't too stretched out, like it happened with the previous vertical layout.
</br>
<img src="palettegrid.gif"/>
<h2>End of log</h2>
You've reached the end of this log, congrats. Special thanks to Jaman on Discord, who's helping us and
who found a quite nasty bug in the selectiont tools. Hope to see you soon in a new log!</br>
- <a href="https://github.com/unsettledgames">Unsettled</a>
</br></br>
P.S.: we're always looking for contributors! Join the <a href="https://discord.com/invite/QjsgTQM">Lospec discord</a> to get in touch
or have a look at the <a href="https://github.com/lospec/pixel-edior">editor repository</a>!
</br>
Lastly, I would also like to thank all the people who submitted feature suggestions and bug reports on Github, they
are very welcome and we will try to address as many as possible of them.

View File

@ -1,6 +1,6 @@
<!-- LAYER MENU -->
<ul id = "layers-menu">
<li class = "layers-menu-entry selected-layer">
<ul id="layers-menu">
<li id="default-layer-list-item" class="layers-menu-entry selected-layer" style="display:none;">
<canvas class = "preview-canvas"></canvas>
<ul class="layer-buttons">
<li class = "layer-button">
@ -17,17 +17,20 @@
</li>
</ul>
<p>Layer 0<div class = "gradient"></div></p>
<p>Background<div class = "gradient"></div></p>
</li>
<li>
<button id = "add-layer-button">
<li id="add-layer-li">
<button id="add-layer-button">
{{svg "plus.svg" width="20" height="20"}} Add layer
</button>
<button id="add-reference-button">
{{svg "plus.svg" width="20" height="20"}} Add reference
</button>
</li>
</ul>
<ul id = "layer-properties-menu">
<ul id="layer-properties-menu">
<li>
<button onclick = "LayerList.renameLayer()">Rename</button>
</li>

View File

@ -0,0 +1,52 @@
Heyo! New pixel editor update, with some very requested changes. After the code refactoring, adding features
is way easier: I introduced some more selection tools, the ellipse tool and an info bar in the top menu :)
<h2>Lasso tool</h2>
Finally! With the lasso tool you're not forced to select rectangular areas anymore. Have fun selecting, cutting,
copying and pasting any kind of selection with pixel-perfect precision.
</br><img src="/lassoselect-tutorial.gif"/>
<h2>Magic wand</h2>
In addition to the lasso tool, we added a new selection tool: the magic wand. You can use it to select
contiguous areas of the same colour! If you need to exactly select the pixels of a certain colour, you're
probably going to find the magic wand useful.
</br><img src="/magicwand-tutorial.gif"/>
<h2>Ellipse tool</h2>
I added a cute friend for the rectangle tool: with the ellipse tool you'll be able to draw circles and
ellipses of all sizes. The tool works similarly to the rectangle tool: select it to draw empty ellipses,
click on the ellipse button again to draw filled ellipses.
</br><img src="/ellipse-tutorial.gif"/>
<h2>Tool tutorials</h2>
I know what you're thinking, "wow those gifs are so cute, that guy must have put a lot of love in them".
Well, I'm glad you like them, and I have good news for you: there are more! Move the cursor on a tool
button: after a little a small tutorial explaining how to use the tool will appear. Hope it's useful for
everyone who's new to the editor!
</br><img src="/tool-tutorials.gif"/>
<h2>Top bar info</h2>
Depending on the tool you're using, you'll notice that the top right part of the editor will slightly change.
When using a resizable tool (eraser, brush, rectangle, ellipse, line), it's now possible to select a precise
size by typing it in the input field that appears when you select it. More features that make use of the
top bar are planned.
<h2>Bug fixes and minor details</h2>
<ul>
<li>Squares in the splash page are now...well...actual squares</li>
<li>Using the mouse when a dialogue popup is open will no longer edit the canvas</li>
<li>For coders: the selection system has been uniformed</li>
<li>Tool buttons have been shrinked to make room for more</li>
</ul>
<h2>End of log</h2>
You've reached the end of this log, congrats. Special thanks to Jaman on Discord, who's helping us and
who found a quite nasty bug in the selectiont tools. Hope to see you soon in a new log!</br>
- <a href="https://github.com/unsettledgames">Unsettled</a>
</br></br>
P.S.: we're always looking for contributors! Join the <a href="https://discord.com/invite/QjsgTQM">Lospec discord</a> to get in touch
or have a look at the <a href="https://github.com/lospec/pixel-edior">editor repository</a>!

View File

@ -28,7 +28,7 @@
</ul>
</li>
<li>
<button id = "layer-button">Layer</button>
<button id="layer-button">Layer</button>
<ul>
<li><button onclick = "LayerList.addLayer()">New layer</button></li>
<li><button onclick = "LayerList.duplicateLayer()">Duplicate</button></li>
@ -54,6 +54,7 @@
<ul>
<li><button id="switch-mode-button">Switch to basic mode</button></li>
<li><button onclick="Dialogue.showDialogue('splash', false)">Splash page</button></li>
<li><button id="auto-cache-button" onclick="FileManager.toggleCache(this)">Enable auto-cache</button></li>
<li><button>Settings</button></li>
</ul>
</li>

View File

@ -1,10 +1,10 @@
<!--CANVAS RESIZE-->
<div class="update" id = "resize-canvas">
<div class="update" id="resize-canvas">
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
<h1>Resize canvas</h1>
<!--PIVOTS-->
<span id = "pivot-menu">
<span id="pivot-menu">
<button class="pivot-button" value="topleft">{{svg "arrows/topleft.svg" width="20" height="20"}}</button>
<button class="pivot-button" value="top">{{svg "arrows/top.svg" width="20" height="20"}}</button>
<button class="pivot-button" value="topright">{{svg "arrows/topright.svg" width="20" height="20"}}</button>
@ -16,7 +16,7 @@
<button class="pivot-button" value="bottomright">{{svg "arrows/bottomright.svg" width="20" height="20"}}</button>
</span>
<!-- SIZE-->
<span id = "rc-size-menu">
<span id="rc-size-menu">
<h2>Size</h2>
<div>
<span>
@ -31,7 +31,7 @@
</div>
</span>
<!--BORDERS-->
<span id = "borders-menu">
<span id="borders-menu">
<h2>Borders offsets</h2>
<div>
<span>
@ -54,6 +54,6 @@
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/>
</span>
</div>
<button id = "resize-canvas-confirm">Resize canvas</button>
<button id="resize-canvas-confirm">Resize canvas</button>
</span>
</div>

View File

@ -1,11 +1,11 @@
<!-- PALETTE -->
<div id = "palette-block">
<div id="palette-block">
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
<h1>Edit palette</h1>
<div id = "colour-picker">
<div id = "cp-modes">
<div id="colour-picker">
<div id="cp-modes">
<button id="cp-rgb" class="cp-selected-mode">RGB</button>
<button id="cp-hsv">HSV</button>
<button id="cp-hsl">HSL</button>
@ -14,7 +14,7 @@
<input id="cp-hex" type="text" value="#123456"/>
</div>
<div id = "sliders-container">
<div id="sliders-container">
<div class = "cp-slider-entry">
<label for = "first-slider">R</label>
<input type="range" min="0" max="255" class="colour-picker-slider" id="first-slider"/>
@ -29,25 +29,25 @@
<div class = "cp-slider-entry">
<label for = "third-slider">B</label>
<input type="range" min = "0" max = "255" class = "colour-picker-slider" id = "third-slider"/>
<input type="range" min = "0" max = "255" class = "colour-picker-slider" id="third-slider"/>
<input type = "text" value = "128" id="cp-sliderText3"/>
</div>
</div>
<div id = "cp-minipicker">
<input type = "range" min = "0" max = "100" id = "cp-minipicker-slider"/>
<div id="cp-minipicker">
<input type = "range" min = "0" max = "100" id="cp-minipicker-slider"/>
<div id="cp-canvas-container">
<canvas id = "cp-spectrum"></canvas>
<canvas id="cp-spectrum"></canvas>
<div id="cp-active-icon" class="cp-picker-icon"></div>
</div>
<div id = "cp-colours-previews">
<div id="cp-colours-previews">
<div class = "cp-colour-preview">
#123456
</div>
</div>
<div id = "cp-colour-picking-modes">
<div id="cp-colour-picking-modes">
<button id="cp-mono" class="cp-selected-mode">Mono</button>
<button id="cp-analog">Nlgs</button>
<button id="cp-cmpt">Cmpt</button>
@ -58,8 +58,8 @@
</div>
</div>
<div id = "palette-container">
<ul id = "palette-list">
<div id="palette-container">
<ul id="palette-list">
<li style = "background-color:rgb(255,0,0);width:40px;height:40px;" onmousedown="PaletteBlock.startRampSelection(event)"
onmousemove="PaletteBlock.updateRampSelection(event)" onmouseup="PaletteBlock.endRampSelection(event)"></li>
<li style = "background-color:rgb(0,255,0);width:40px;height:40px;"onmousedown="PaletteBlock.startRampSelection(event)"

View File

@ -9,6 +9,6 @@
</div>
<div class="popup-actions">
<button class="default" id = "export-confirm">Export</button>
<button class="default" id="export-confirm">Export</button>
</div>
</div>

View File

@ -9,6 +9,6 @@
</div>
<div class="popup-actions">
<button class="default" id = "save-project-confirm">Save</button>
<button class="default" id="save-project-confirm">Save</button>
</div>
</div>

View File

@ -1,5 +1,5 @@
<!-- Splash page -->
<div id = "splash">
<div id="splash">
<div id="splash-news">
<div id="latest-update">
<h1>Latest updates</h1>
@ -38,7 +38,7 @@
</div>
</div>
<div id = "sp-quickstart-container">
<div id="sp-quickstart-container">
<div id="sp-quickstart-title">
Quickstart
</div>

View File

@ -1,10 +1,10 @@
<!--SPRITE RESIZE-->
<div class="update" id = "resize-sprite">
<div class="update" id="resize-sprite">
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
<h1>Scale sprite</h1>
<!-- SIZE-->
<h2>New size</h2>
<span id = "rs-size-menu">
<span id="rs-size-menu">
<div>
<span>
Width: <input id="rs-width" type="number" default="0" step="1"
@ -19,7 +19,7 @@
</span>
<!--BORDERS-->
<h2>Resize percentages</h2>
<span id = "rs-percentage-menu">
<span id="rs-percentage-menu">
<div>
<span>
Width <input id="rs-width-percentage" type="number" default="0" step="1"
@ -31,19 +31,19 @@
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/> %
</span>
</div>
<div id = "rs-ratio-div">
<div id="rs-ratio-div">
<span>
Keep current ratio <input type = "checkbox" id = "rs-keep-ratio"/>
Keep current ratio <input type = "checkbox" id="rs-keep-ratio"/>
</span>
<span>
Scaling algorithm:
<select name = "resize-algorithm" id = "resize-algorithm-combobox">
<select name = "resize-algorithm" id="resize-algorithm-combobox">
<option value = "nearest-neighbor">Nearest neighbour</option>
<option value = "bilinear-interpolation">Bilinear</option>
</select>
</span>
</br>
<button id = "resize-sprite-confirm">Scale sprite</button>
<button id="resize-sprite-confirm">Scale sprite</button>
</div>
</span>
</div>

View File

@ -16,8 +16,8 @@
</li>
<li class="expanded">
<button id="rectangle-button">{{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"}}</button>
<button id="rectangle-button">{{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"}}</button>
<ul class="size-buttons">
<button title="Increase Rectangle Size" id="rectangle-bigger-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
<button title="Decrease Rectangle Size" id="rectangle-smaller-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
@ -26,8 +26,8 @@
<li class="expanded">
<button id="ellipse-button">
{{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 "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"}}
</button>
<ul class="size-buttons">
<button title="Increase Ellipse Size" id="ellipse-bigger-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
@ -45,11 +45,11 @@
<li><button id="fill-button">{{svg "fill.svg" width="24" height="24"}}</button></li>
<li><button id = "rectselect-button">{{svg "rectselect.svg" width = "24" height = "24"}}</button><li>
<li><button id="rectselect-button">{{svg "rectselect.svg" width = "24" height = "24"}}</button><li>
<li><button id = "lassoselect-button">{{svg "lasso.svg" width = "26" height = "26"}}</button></li>
<li><button id="lassoselect-button">{{svg "lasso.svg" width = "26" height = "26"}}</button></li>
<li><button id = "magicwand-button">{{svg "magicwand.svg" width = "26" height = "26"}}</button></li>
<li><button id="magicwand-button">{{svg "magicwand.svg" width = "26" height = "26"}}</button></li>
<li><button id="eyedropper-button">{{svg "eyedropper.svg" width="24" height="24"}}</button></li>