Merge pull request #13 from unsettledgames/master

Implemented layers
This commit is contained in:
Lospec 2020-09-08 21:40:42 -04:00 committed by GitHub
commit dd7d7decdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1739 additions and 278 deletions

View File

@ -12,6 +12,15 @@ The next version is mostly focused on adding missing essential features and port
Suggestions / Planned features:
- Line tool
- Resize canvas
- Snap brush preview to pixel grid
- Move selection with arrows
- Load palette from LPE file
- Move colours in palette editor
- Duplicate layer
- Hide non-hovered layers
- Custom color picker
- custom code without dependencies
- more features such as sliders / color modes
@ -23,16 +32,14 @@ Suggestions / Planned features:
- Stack colors when too many
- Fix popups
- Copy/paste
- Add as selection
- Show colors which would need to be added to palette
- Show colors which would need to be added to palette
- Palette option remove unused colors
- Pixel Grid
- Another currentLayer.canvas
- Must be rescaled each zoom
- Possibly add collaborate function using together.js
- Possibly add collaborate function
- Bug fix
- Alt + scroll broken

20
_ext/svg/invisible.svg Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 469.44 469.44" style="enable-background:new 0 0 469.44 469.44;" xml:space="preserve">
<g>
<g>
<g>
<path d="M231.147,160.373l67.2,67.2l0.32-3.52c0-35.307-28.693-64-64-64L231.147,160.373z"/>
<path d="M234.667,117.387c58.88,0,106.667,47.787,106.667,106.667c0,13.76-2.773,26.88-7.573,38.933l62.4,62.4
c32.213-26.88,57.6-61.653,73.28-101.333c-37.013-93.653-128-160-234.773-160c-29.867,0-58.453,5.333-85.013,14.933l46.08,45.973
C207.787,120.267,220.907,117.387,234.667,117.387z"/>
<path d="M21.333,59.253l48.64,48.64l9.707,9.707C44.48,145.12,16.64,181.707,0,224.053c36.907,93.653,128,160,234.667,160
c33.067,0,64.64-6.4,93.547-18.027l9.067,9.067l62.187,62.293l27.2-27.093L48.533,32.053L21.333,59.253z M139.307,177.12
l32.96,32.96c-0.96,4.587-1.6,9.173-1.6,13.973c0,35.307,28.693,64,64,64c4.8,0,9.387-0.64,13.867-1.6l32.96,32.96
c-14.187,7.04-29.973,11.307-46.827,11.307C175.787,330.72,128,282.933,128,224.053C128,207.2,132.267,191.413,139.307,177.12z"
/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="486.733px" height="486.733px" viewBox="0 0 486.733 486.733" style="enable-background:new 0 0 486.733 486.733;"
xml:space="preserve">
<g>
<path d="M403.88,196.563h-9.484v-44.388c0-82.099-65.151-150.681-146.582-152.145c-2.225-0.04-6.671-0.04-8.895,0
C157.486,1.494,92.336,70.076,92.336,152.175v44.388h-9.485c-14.616,0-26.538,15.082-26.538,33.709v222.632
c0,18.606,11.922,33.829,26.539,33.829h321.028c14.616,0,26.539-15.223,26.539-33.829V230.272
C430.419,211.646,418.497,196.563,403.88,196.563z M273.442,341.362v67.271c0,7.703-6.449,14.222-14.158,14.222H227.45
c-7.71,0-14.159-6.519-14.159-14.222v-67.271c-7.477-7.36-11.83-17.537-11.83-28.795c0-21.334,16.491-39.666,37.459-40.513
c2.222-0.09,6.673-0.09,8.895,0c20.968,0.847,37.459,19.179,37.459,40.513C285.272,323.825,280.919,334.002,273.442,341.362z
M331.886,196.563h-84.072h-8.895h-84.072v-44.388c0-48.905,39.744-89.342,88.519-89.342c48.775,0,88.521,40.437,88.521,89.342
V196.563z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="486.866px" height="486.866px" viewBox="0 0 486.866 486.866" style="enable-background:new 0 0 486.866 486.866;"
xml:space="preserve">
<g>
<path d="M393.904,214.852h-8.891v-72.198c0-76.962-61.075-141.253-137.411-142.625c-2.084-0.038-6.254-0.038-8.338,0
C162.927,1.4,101.853,65.691,101.853,142.653v1.603c0,16.182,13.118,29.3,29.3,29.3c16.182,0,29.299-13.118,29.299-29.3v-1.603
c0-45.845,37.257-83.752,82.98-83.752s82.981,37.907,82.981,83.752v72.198H92.963c-13.702,0-24.878,14.139-24.878,31.602v208.701
c0,17.44,11.176,31.712,24.878,31.712h300.941c13.703,0,24.878-14.271,24.878-31.712V246.452
C418.783,228.989,407.607,214.852,393.904,214.852z M271.627,350.591v63.062c0,7.222-6.046,13.332-13.273,13.332h-29.841
c-7.228,0-13.273-6.11-13.273-13.332v-63.062c-7.009-6.9-11.09-16.44-11.09-26.993c0-19.999,15.459-37.185,35.115-37.977
c2.083-0.085,6.255-0.085,8.337,0c19.656,0.792,35.115,17.978,35.115,37.977C282.717,334.149,278.637,343.69,271.627,350.591z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

16
_ext/svg/visible.svg Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 469.333 469.333" style="enable-background:new 0 0 469.333 469.333;" xml:space="preserve">
<g>
<g>
<g>
<path d="M234.667,170.667c-35.307,0-64,28.693-64,64s28.693,64,64,64s64-28.693,64-64S269.973,170.667,234.667,170.667z"/>
<path d="M234.667,74.667C128,74.667,36.907,141.013,0,234.667c36.907,93.653,128,160,234.667,160
c106.773,0,197.76-66.347,234.667-160C432.427,141.013,341.44,74.667,234.667,74.667z M234.667,341.333
c-58.88,0-106.667-47.787-106.667-106.667S175.787,128,234.667,128s106.667,47.787,106.667,106.667
S293.547,341.333,234.667,341.333z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 875 B

View File

@ -22,6 +22,214 @@ body {
/* Disable Android and iOS callouts*/
}
#layer-properties-menu {
visibility:hidden;
margin: 0;
padding: 0;
top: 0;
right: 0;
width:120px;
text-align:center;
margin-right:200px;
/*border:1px solid color(menu, foreground);*/
list-style:none;
position:relative;
z-index:1200;
list-style-type: none;
background-color: color(base);
position: fixed;
overflow: visible;
li button {
color: color(menu, foreground);
height: 100%;
padding: 10px;
background: none;
border: none;
cursor: pointer;
width:100%;
}
li button:hover {
background-color:color(menu, background);
}
}
.preview-canvas {
image-rendering:optimizeSpeed; /* Legal fallback */
image-rendering:-moz-crisp-edges; /* Firefox */
image-rendering:-o-crisp-edges; /* Opera */
image-rendering:-webkit-optimize-contrast; /* Safari */
image-rendering:optimize-contrast; /* CSS3 Proposed */
image-rendering:crisp-edges; /* CSS4 Proposed */
image-rendering:pixelated; /* CSS4 Proposed */
-ms-interpolation-mode:nearest-neighbor; /* IE8+ */
}
#layers-menu {
&::-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;
width:200px;
top: 48px;
bottom: 0;
right:0;
padding: 0;
margin: 0;
background-color: color(base);
box-sizing: border-box;
position: fixed;
z-index: 1120;
list-style-type: none;
overflow-y:scroll;
overflow-x:hidden;
// TODO: make the scroll bar a bit fancier
#add-layer-button {
path {
fill:color(menu, foreground);
}
svg {
position:relative;
margin-right:10px;
}
position:relative;
justify-content: center;
display:flex;
align-items:center;
margin-top:2px;
font-size: 1.2em;
color: color(menu, foreground);
height: 100%;
width: 100%;
padding: 17px;
background: none;
border: none;
cursor: pointer;
background-color: color(menu);
transition: color 0.2s, background-color 0.2s;
}
#add-layer-button:hover {
color: color(base, foreground, bold);
background-color: color(base, foreground, default);
}
}
.selected-layer {
ul.layer-buttons li{
visibility:visible;
}
color: color(base, foreground, bold);
background-color: color(base, foreground, default);
}
.layerdragover {
margin-top:5px;
border-top: 3px solid color(base, foreground, bold);
}
.layers-menu-entry {
cursor:pointer;
margin-top:2px;
font-size: 1em;
color: color(base, foreground, text);
background-color: color(menu, background);
display:inline-block;
height:50px;
width:100%;
display:flex;
align-items:center;
ul.layer-buttons {
top:0;
left:0;
margin:0;
padding:0;
box-sizing:border-box;
position:relative;
height:100%;
list-style:none;
path {
fill: color(base, foreground);
}
li:hover {
background: color(base, background, hover);
path {
fill: color(base, foreground, hover);
}
}
.layer-button {
visibility:hidden;
height:50%;
}
}
.lock-layer-button, .hide-layer-button {
color: color(menu, foreground);
background: none;
border: none;
cursor: pointer;
background-color: color(menu);
transition: color 0.2s, background-color 0.2s;
height:100%;
position:relative;
}
canvas {
display:inline-block;
height:50px;
width:50px;
background-color:lightgrey;
left:4px;
}
p {
right:0;
display:inline-block;
padding-left:10px;
height:18px;
overflow:hidden;
position:relative;
}
transition: color 0.1s, background-color 0.1s;
-moz-transition: color 0.1s, background-color 0.1s;
-webkit-transition: color 0.1s, background-color 0.1s;
}
.layers-menu-entry:hover, .selected-layer {
ul.layer-buttons li{
visibility:visible !important;
}
color: color(base, foreground, bold);
background-color: color(base, foreground, default);
}
//don't let svg handle click events, just send to parents
svg {
pointer-events: none;
@ -36,8 +244,8 @@ svg {
}
.weak {
font-size: 0.8em;
color: color(base,foreground,weak);
font-size: 0.8em;
color: color(base,foreground,weak);
}
.drawingCanvas {
@ -183,9 +391,9 @@ svg {
}
}
a {
display: block;
text-decoration: none;
box-sizing: border-box;
display: block;
text-decoration: none;
box-sizing: border-box;
}
}
}
@ -229,7 +437,7 @@ svg {
}
#colors-menu {
right: 0;
right: 200px;
width: 48px;
display: flex;
justify-content: flex-start;
@ -703,6 +911,14 @@ svg {
font-style: italic;
}
#editor-mode-info {
font-style:italic;
}
#editor-mode {
display:none;
}
#compatibility-warning {
display: flex;
justify-content: center;

View File

@ -1,3 +1,5 @@
let currentPalette = [];
//adds the given color to the palette
//input hex color string
//returns list item element
@ -6,7 +8,7 @@ function addColor (newColor) {
//add # at beginning if not present
if (newColor.charAt(0) != '#')
newColor = '#' + newColor;
currentPalette.push(newColor);
//create list item
var listItem = document.createElement('li');
@ -45,5 +47,7 @@ function addColor (newColor) {
button.parentElement.firstChild.jscolor.show();
});
console.log(currentPalette);
return listItem;
}

View File

@ -8,6 +8,7 @@ var nSquaresFilled = 0;
function fillCheckerboard() {
// Getting checkerboard context
var context = checkerBoard.context;
context.clearRect(0, 0, canvasSize[0], canvasSize[1]);
// Cycling through the canvas (using it as a matrix)
for (var i=0; i<canvasSize[0] / checkerBoardSquareSize; i++) {

View File

@ -28,10 +28,11 @@ function colorChanged(e) {
var newColor = hexToRgb(e.target.value);
var oldColor = e.target.oldColor;
currentPalette.splice(currentPalette.indexOf("#" + newColor), 1);
newColor.a = 255;
//save undo state
//saveHistoryState({type: 'colorchange', newColor: e.target.value, oldColor: rgbToHex(oldColor), canvas: context.getImageData(0, 0, canvasSize[0], canvasSize[1])});
new HistoryStateEditColor(e.target.value.toLowerCase(), rgbToHex(oldColor));
//get the currently selected color
@ -82,7 +83,7 @@ function colorChanged(e) {
//set new old color to changed color
e.target.oldColor = newColor;
currentPalette.push('#' + newColorHex);
//if this is the current color, update the drawing color
if (e.target.colorElement.parentElement.classList.contains('selected')) {

View File

@ -20,6 +20,10 @@ function copySelection() {
}
function pasteSelection() {
// Can't paste if the layer is locked
if (currentLayer.isLocked) {
return;
}
endSelection();
isPasting = true;

View File

@ -1,7 +1,8 @@
on('click', 'create-button', function (){
var width = getValue('size-width');
var height = getValue('size-height');
newPixel(width,height,'asdfg');
var mode = getValue("editor-mode");
newPixel(width, height, mode);
document.getElementById('new-pixel-warning').style.display = 'block';
//get selected palette name
@ -16,6 +17,9 @@ on('click', 'create-button', function (){
//reset new form
setValue('size-width', 64);
setValue('size-height', 64);
setValue("editor-mode", 'Advanced')
setText('editor-mode-button', 'Choose a mode...');
setText('palette-button', 'Choose a palette...');
setText('preset-button', 'Choose a preset...');
});

View File

@ -1,9 +1,11 @@
function createColorPalette(selectedPalette, fillBackground) {
function createColorPalette(selectedPalette, fillBackground, deletePreviousPalette = true) {
//remove current palette
colors = document.getElementsByClassName('color-button');
while (colors.length > 0) {
colors[0].parentElement.remove();
if (deletePreviousPalette) {
colors = document.getElementsByClassName('color-button');
while (colors.length > 0) {
colors[0].parentElement.remove();
}
}
var lightestColor = '#000000';
@ -31,15 +33,49 @@ function createColorPalette(selectedPalette, fillBackground) {
darkestColor = newColor;
}
}
//fill bg with lightest color
if (fillBackground) {
currentLayer.context.fillStyle = lightestColor;
currentLayer.context.fillRect(0, 0, canvasSize[0], canvasSize[1]);
}
//set as current color
currentLayer.context.fillStyle = darkestColor;
}
function createPaletteFromLayers() {
let colors = {};
for (let i=0; i<layers.length; i++) {
if (layers[i].menuEntry != null) {
let imageData = layers[i].context.getImageData(0, 0, layers[i].canvasSize[0], layers[i].canvasSize[1]).data;
let dataLength = imageData.length;
for (let j=0; j<dataLength; j += 4) {
if (!isPixelEmpty(imageData[j])) {
let color = imageData[j]+','+imageData[j + 1]+','+imageData[j + 2];
if (!colors[color]) {
colors[color] = {r:imageData[j],g:imageData[j + 1],b:imageData[j + 2]};
//don't allow more than 256 colors to be added
if (Object.keys(colors).length >= settings.maxColorsOnImportedImage) {
alert('The image loaded seems to have more than '+settings.maxColorsOnImportedImage+' colors.');
break;
}
}
}
}
}
}
console.log(colors);
//create array out of colors object
let colorPaletteArray = [];
for (let color in colors) {
if (colors.hasOwnProperty(color)) {
colorPaletteArray.push('#'+rgbToHex(colors[color]));
}
}
console.log('COLOR PALETTE ARRAY', colorPaletteArray);
//create palette form colors array
createColorPalette(colorPaletteArray, false);
}

View File

@ -1,6 +1,5 @@
//draw a line between two points on canvas
function line(x0,y0,x1,y1, brushSize) {
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1 ? 1 : -1);
@ -21,35 +20,17 @@ function line(x0,y0,x1,y1, brushSize) {
//if we've reached the end goal, exit the loop
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2 >-dy) {err -=dy; x0+=sx;}
if (e2 < dx) {err +=dx; y0+=sy;}
}
}
//draw a line between two points on canvas
function lineOnLayer(x0,y0,x1,y1, brushSize, context) {
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1 ? 1 : -1);
var sy = (y0 < y1 ? 1 : -1);
var err = dx-dy;
while (true) {
//set pixel
// If the current tool is the brush
if (currentTool.name == 'pencil' || currentTool.name == 'rectangle') {
// I fill the rect
context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize);
} else if (currentTool.name == 'eraser') {
// In case I'm using the eraser I must clear the rect
context.clearRect(x0-Math.floor(tool.eraser.brushSize/2), y0-Math.floor(tool.eraser.brushSize/2), tool.eraser.brushSize, tool.eraser.brushSize);
if (e2 >-dy) {
err -=dy;
x0+=sx;
}
//if we've reached the end goal, exit the loop
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2 >-dy) {err -=dy; x0+=sx;}
if (e2 < dx) {err +=dx; y0+=sy;}
if (e2 < dx) {
err +=dx;
y0+=sy;
}
console.log(x0 + ", " + x1);
}
}

96
js/_editorMode.js Normal file
View File

@ -0,0 +1,96 @@
let modes = {
'Basic' : {
description: 'Basic mode is perfect if you want to create simple sprites or try out palettes.'
},
'Advanced' : {
description: 'Choose advanced mode to gain access to features such as layers.'
}
}
let infoBox = document.getElementById('editor-mode-info');
function switchMode(currentMode, mustConfirm = true) {
if (currentMode == 'Basic') {
// Switch to advanced ez pez lemon squez
document.getElementById('switch-mode-button').innerHTML = 'Switch to basic mode';
// Show the layer menus
layerList.style.display = "inline-block";
document.getElementById('layer-button').style.display = 'inline-block';
// Move the palette menu
document.getElementById('colors-menu').style.right = '200px';
pixelEditorMode = 'Advanced';
}
else {
// Switch to basic
if (mustConfirm) {
if (!confirm('Switching to basic mode will flatten all the visible layers. Are you sure you want to continue?')) {
return;
}
}
document.getElementById('switch-mode-button').innerHTML = 'Switch to advanced mode';
// Selecting the current layer
currentLayer.selectLayer();
// Flatten the layers
flatten(true);
// Hide the layer menus
layerList.style.display = 'none';
document.getElementById('layer-button').style.display = 'none';
// Move the palette menu
document.getElementById('colors-menu').style.right = '0px';
pixelEditorMode = 'Basic';
}
}
on('click', 'switch-mode-button', function (e) {
switchMode(pixelEditorMode);
});
// Makes the menu open
on('click', 'editor-mode-button', function (e){
//open or close the preset menu
toggle('editor-mode-button');
toggle('editor-mode-menu');
//close the palette menu
deselect('palette-button');
deselect('palette-menu');
//close the preset menu
deselect('preset-button');
deselect('preset-menu');
//stop the click from propogating to the parent element
e.stopPropagation();
});
//populate preset list in new pixel menu
Object.keys(modes).forEach(function(modeName,index) {
var editorModeMenu = document.getElementById('editor-mode-menu');
//create button
var button = document.createElement('button');
button.appendChild(document.createTextNode(modeName));
//insert new element
editorModeMenu.appendChild(button);
//add click event listener
on('click', button, function() {
//change mode on new pixel
setValue('editor-mode', modeName);
// Change description
infoBox.innerHTML = modes[modeName].description;
//hide the dropdown menu
deselect('editor-mode-menu');
deselect('editor-mode-button');
//set the text of the dropdown to the newly selected mode
setText('editor-mode-button', modeName);
});
});

View File

@ -33,10 +33,31 @@ for (var i = 1; i < mainMenuItems.length; i++) {
//File Menu
case 'New':
showDialogue('new-pixel');
break;
case 'Save project':
//create name
var selectedPalette = getText('palette-button');
if (selectedPalette != 'Choose a palette...'){
var paletteAbbreviation = palettes[selectedPalette].abbreviation;
var fileName = 'pixel-'+paletteAbbreviation+'-'+canvasSize[0]+'x'+canvasSize[1]+'.lpe';
} else {
var fileName = 'pixel-'+canvasSize[0]+'x'+canvasSize[1]+'.lpe';
selectedPalette = 'none';
}
//set download link
var linkHolder = document.getElementById('save-project-link-holder');
// create file content
var content = getProjectData();
linkHolder.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(content);
linkHolder.download = fileName;
linkHolder.click();
ga('send', 'event', 'Pixel Editor Save', selectedPalette, canvasSize[0]+'/'+canvasSize[1]); /*global ga*/
break;
case 'Open':
//if a document exists
if (documentCreated) {
//check if the user wants to overwrite
@ -50,9 +71,8 @@ for (var i = 1; i < mainMenuItems.length; i++) {
break;
case 'Save as...':
case 'Export':
if (documentCreated) {
//create name
var selectedPalette = getText('palette-button');
if (selectedPalette != 'Choose a palette...'){
@ -65,13 +85,44 @@ for (var i = 1; i < mainMenuItems.length; i++) {
//set download link
var linkHolder = document.getElementById('save-image-link-holder');
linkHolder.href = canvas.toDataURL();
// Creating a tmp canvas to flatten everything
var exportCanvas = document.createElement("canvas");
var emptyCanvas = document.createElement("canvas");
var layersCopy = layers.slice();
exportCanvas.width = canvasSize[0];
exportCanvas.height = canvasSize[1];
emptyCanvas.width = canvasSize[0];
emptyCanvas.height = canvasSize[1];
// Sorting the layers by z index
layersCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1);
// Merging every layer on the export canvas
for (let i=0; i<layersCopy.length; i++) {
if (layersCopy[i].menuEntry != null && layersCopy[i].isVisible) {
mergeLayers(exportCanvas.getContext('2d'), layersCopy[i].context);
}
// I'm not going to find out why the layer ordering screws up if you don't copy
// a blank canvas when layers[i] is not set as visible, but if you have time to
// spend, feel free to investigate (comment the else, create 3 layers: hide the
// middle one and export, the other 2 will be swapped in their order)
else {
mergeLayers(exportCanvas.getContext('2d'), emptyCanvas.getContext('2d'));
}
}
linkHolder.href = exportCanvas.toDataURL();
linkHolder.download = fileName;
linkHolder.click();
emptyCanvas.remove();
exportCanvas.remove();
//track google event
ga('send', 'event', 'Pixel Editor Save', selectedPalette, canvasSize[0]+'/'+canvasSize[1]); /*global ga*/
ga('send', 'event', 'Pixel Editor Export', selectedPalette, canvasSize[0]+'/'+canvasSize[1]); /*global ga*/
}
break;
@ -120,7 +171,6 @@ for (var i = 1; i < mainMenuItems.length; i++) {
break;
//Help Menu
case 'Settings':
//fill form with current settings values
setValue('setting-numberOfHistoryStates', settings.numberOfHistoryStates);
@ -149,3 +199,34 @@ function closeMenu () {
deselect(mainMenuItems[i]);
}
}
function getProjectData() {
// use a dictionary
let dictionary = {};
// sorting layers by increasing z-index
let layersCopy = layers.slice();
layersCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1);
// save canvas size
dictionary['canvasWidth'] = currentLayer.canvasSize[0];
dictionary['canvasHeight'] = currentLayer.canvasSize[1];
// save editor mode
dictionary['editorMode'] = pixelEditorMode;
// save palette
for (let i=0; i<currentPalette.length; i++) {
dictionary["color" + i] = currentPalette[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();
}
}
return JSON.stringify(dictionary);
}

View File

@ -18,10 +18,6 @@ function getCursorPosition(e) {
return [x,y];
}
// TODO: apply the function below to every getCursorPosition call
// TODO: FIX THIS BELOW
//get cursor position relative to canvas
function getCursorPositionRelative(e, layer) {
var x;

View File

@ -3,26 +3,259 @@ var redoStates = [];
const undoLogStyle = 'background: #87ff1c; color: black; padding: 5px;';
//prototype for undoing canvas changes
function HistoryStateEditCanvas () {
this.canvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
function HistoryStateFlattenVisible(flattened) {
this.nFlattened = flattened;
this.undo = function () {
var currentCanvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
currentLayer.context.putImageData(this.canvas, 0, 0);
this.undo = function() {
for (let i=0; i<this.nFlattened; i++) {
undo();
}
this.canvas = currentCanvas;
redoStates.push(this);
};
this.redo = function () {
var currentCanvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
currentLayer.context.putImageData(this.canvas, 0, 0);
this.redo = function() {
for (let i=0; i<this.nFlattened; i++) {
redo();
}
this.canvas = currentCanvas;
undoStates.push(this);
};
saveHistoryState(this);
}
function HistoryStateFlattenTwoVisibles(belowImageData, afterAbove, layerIndex, aboveLayer, belowLayer) {
this.aboveLayer = aboveLayer;
this.belowLayer = belowLayer;
this.belowImageData = belowImageData;
this.undo = function() {
console.log(afterAbove.menuEntry);
canvasView.append(aboveLayer.canvas);
layerList.insertBefore(aboveLayer.menuEntry, afterAbove);
belowLayer.context.clearRect(0, 0, belowLayer.canvasSize[0], belowLayer.canvasSize[1]);
belowLayer.context.putImageData(this.belowImageData, 0, 0);
belowLayer.updateLayerPreview();
layers.splice(layerIndex, 0, aboveLayer);
redoStates.push(this);
};
this.redo = function() {
mergeLayers(belowLayer.context, aboveLayer.context);
// Deleting the above layer
aboveLayer.canvas.remove();
aboveLayer.menuEntry.remove();
layers.splice(layers.indexOf(aboveLayer), 1);
undoStates.push(this);
};
saveHistoryState(this);
}
function HistoryStateFlattenAll(nFlattened) {
this.nFlattened = nFlattened;
this.undo = function() {
for (let i=0; i<this.nFlattened - 2; i++) {
undo();
}
redoStates.push(this);
};
this.redo = function() {
for (let i=0; i<this.nFlattened - 2; i++) {
redo();
}
undoStates.push(this);
};
saveHistoryState(this);
}
function HistoryStateMergeLayer(aboveIndex, aboveLayer, belowData, belowLayer) {
this.aboveIndex = aboveIndex;
this.belowData = belowData;
this.aboveLayer = aboveLayer;
this.belowLayer = belowLayer;
this.undo = function() {
layerList.insertBefore(this.aboveLayer.menuEntry, this.belowLayer.menuEntry);
canvasView.append(this.aboveLayer.canvas);
belowLayer.context.clearRect(0, 0, this.belowLayer.canvasSize[0], this.belowLayer.canvasSize[1]);
belowLayer.context.putImageData(this.belowData, 0, 0);
belowLayer.updateLayerPreview();
layers.splice(this.aboveIndex, 0, this.aboveLayer);
redoStates.push(this);
};
this.redo = function() {
aboveLayer.selectLayer();
merge(false);
undoStates.push(this);
};
saveHistoryState(this);
}
function HistoryStateRenameLayer(oldName, newName, layer) {
this.edited = layer;
this.oldName = oldName;
this.newName = newName;
this.undo = function() {
layer.menuEntry.getElementsByTagName("p")[0].innerHTML = oldName;
redoStates.push(this);
};
this.redo = function() {
layer.menuEntry.getElementsByTagName("p")[0].innerHTML = newName;
undoStates.push(this);
};
saveHistoryState(this);
}
function HistoryStateDeleteLayer(layerData, before, index) {
this.deleted = layerData;
this.before = before;
this.index = index;
this.undo = function() {
canvasView.append(this.deleted.canvas);
if (this.before != null) {
layerList.insertBefore(this.deleted.menuEntry, this.before);
}
else {
layerList.prepend(this.deleted.menuEntry);
}
layers.splice(this.index, 0, this.deleted);
redoStates.push(this);
};
this.redo = function() {
this.deleted.selectLayer();
deleteLayer(false);
undoStates.push(this);
};
saveHistoryState(this);
}
function HistoryStateMoveTwoLayers(layer, oldIndex, newIndex) {
this.layer = layer;
this.oldIndex = oldIndex;
this.newIndex = newIndex;
this.undo = function() {
layer.canvas.style.zIndex = oldIndex;
redoStates.push(this);
};
this.redo = function() {
layer.canvas.style.zIndex = newIndex;
undoStates.push(this);
};
saveHistoryState(this);
}
function HistoryStateMoveLayer(afterToDrop, toDrop, static, nMoved) {
this.beforeToDrop = afterToDrop;
this.toDrop = toDrop;
this.undo = function() {
toDrop.menuEntry.remove();
if (afterToDrop != null) {
layerList.insertBefore(toDrop.menuEntry, afterToDrop)
}
else {
layerList.append(toDrop.menuEntry);
}
for (let i=0; i<nMoved; i++) {
undo();
}
redoStates.push(this);
};
this.redo = function() {
moveLayers(toDrop.menuEntry.id, static.menuEntry.id, true);
undoStates.push(this);
};
saveHistoryState(this);
}
function HistoryStateAddLayer(layerData, index) {
this.added = layerData;
this.index = index;
this.undo = function() {
redoStates.push(this);
this.added.canvas.remove();
this.added.menuEntry.remove();
layers.splice(index, 1);
};
this.redo = function() {
undoStates.push(this);
canvasView.append(this.added.canvas);
layerList.prepend(this.added.menuEntry);
layers.splice(this.index, 0, this.added);
};
saveHistoryState(this);
}
//prototype for undoing canvas changes
function HistoryStateEditCanvas () {
this.canvasState = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
this.layerID = currentLayer.id;
this.undo = function () {
var stateLayer = getLayerByID(this.layerID);
var currentCanvas = stateLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
stateLayer.context.putImageData(this.canvasState, 0, 0);
this.canvasState = currentCanvas;
redoStates.push(this);
stateLayer.updateLayerPreview();
};
this.redo = function () {
console.log("YEET");
var stateLayer = getLayerByID(this.layerID);
var currentCanvas = stateLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
stateLayer.context.putImageData(this.canvasState, 0, 0);
this.canvasState = currentCanvas;
undoStates.push(this);
stateLayer.updateLayerPreview();
};
//add self to undo array
saveHistoryState(this);
}
@ -123,9 +356,6 @@ function HistoryStateEditColor (newColorValue, oldColorValue) {
//rename to add undo state
function saveHistoryState (state) {
//console.log('%csaving history state', undoLogStyle);
//console.log(state);
//get current canvas data and save to undoStates array
undoStates.push(state);
@ -139,9 +369,6 @@ function saveHistoryState (state) {
//there should be no redoStates after an undoState is saved
redoStates = [];
//console.log(undoStates);
//console.log(redoStates);
}
function undo () {
@ -149,26 +376,22 @@ function undo () {
//if there are any states saved to undo
if (undoStates.length > 0) {
document.getElementById('redo-button').classList.remove('disabled');
//get state
var undoState = undoStates[undoStates.length-1];
//console.log(undoState);
//restore the state
undoState.undo();
//remove from the undo list
undoStates.splice(undoStates.length-1,1);
//restore the state
undoState.undo();
//if theres none left to undo, disable the option
if (undoStates.length == 0)
document.getElementById('undo-button').classList.add('disabled');
}
//console.log(undoStates);
//console.log(redoStates);
}
function redo () {
@ -181,14 +404,13 @@ function redo () {
//get state
var redoState = redoStates[redoStates.length-1];
//console.log(redoState);
//remove from redo array (do this before restoring the state, else the flatten state will break)
redoStates.splice(redoStates.length-1,1);
//restore the state
redoState.redo();
//remove from redo array
redoStates.splice(redoStates.length-1,1);
//if theres none left to redo, disable the option
if (redoStates.length == 0)
document.getElementById('redo-button').classList.add('disabled');

View File

@ -1,27 +1,15 @@
var spacePressed = false;
/**
Copy / paste / cut logic:
- The user selects an area
- Pressing ctrl+c copies the selection
- Pressing ctrl+v ends the current selection and copies the clipboard in the tmp layer:
the editor enters move mode and lets the user move the copied selection around.
Pressing ctrl+v while moving a copy has the same effect of pressing ctrl+v after a ctrl+c
- The behaviour of ctrl+v is the same and doesn't depend on how the selected area was obtained
(with ctrl+c or with ctrl+v)
- Selecting a different tool while moving the copied or cut selection has the same effect of selecting
a different tool while moving a standard selection
- You can paste at any other time
BUGS:
-
*/
function KeyPress(e) {
var keyboardEvent = window.event? event : e;
//if the user is typing in an input field, ignore these hotkeys
if (document.activeElement.tagName == 'INPUT') return;
//if the user is typing in an input field or renaming a layer, ignore these hotkeys, unless it's an enter key
if (document.activeElement.tagName == 'INPUT' || isRenamingLayer) {
if (e.keyCode == 13) {
currentLayer.closeOptionsMenu();
}
return;
}
//if no document has been created yet,
//orthere is a dialog box open
@ -42,7 +30,6 @@ function KeyPress(e) {
break;
// copy tool c
case 67: case 99:
console.log("Copying");
if (keyboardEvent.ctrlKey && !dragging && currentTool.name == 'moveselection') {
copySelection();
}
@ -65,7 +52,6 @@ function KeyPress(e) {
break;
// eraser -6, r
case 54: case 82:
console.log("Pressed r");
tool.eraser.switchTo()
break;
// Rectangular selection
@ -74,13 +60,11 @@ function KeyPress(e) {
break;
// Paste tool
case 86: case 118:
console.log("Pasting");
if (keyboardEvent.ctrlKey && !dragging) {
pasteSelection();
}
break;
case 88: case 120:
console.log("Cutting");
if (keyboardEvent.ctrlKey && !dragging && currentTool.name == 'moveselection') {
cutSelectionTool();
tool.pencil.switchTo();
@ -88,7 +72,6 @@ function KeyPress(e) {
break;
//Z
case 90:
console.log('PRESSED Z ', keyboardEvent.ctrlKey)
//CTRL+ALT+Z redo
if (keyboardEvent.altKey && keyboardEvent.ctrlKey)
redo();

View File

@ -1,12 +1,32 @@
/** TODO LIST FOR LAYERS
- move the tmp layer so that it's always right below the active layer
- when the move tool is selected (to move a selection), the tmp layer must be put right above the
active layer to show a preview
- mouse events will always have at least a canvas target, so evey time there's an event, we'll have to check
the actual element type instead of the current layer and then apply the tool on the currentLayer, not on
the first one in order of z-index
GENERAL REQUIREMENTS:
- Saving the state of an artwork to a .lospec file so that people can work on it later keeping
the layers they created? That'd be cool, even for the app users, that could just double click on a lospec
file for it to be opened right in the pixel editor
OPTIONAL:
1 - Fix issues
*/
let layerList;
let layerListEntry;
let layerDragSource = null;
let layerCount = 1;
let maxZIndex = 3;
let unusedIDs = [];
let currentID = layerCount;
let idToDelete;
let layerOptions = document.getElementById("layer-properties-menu");
let isRenamingLayer = false;
let oldLayerName = null;
on('click',"add-layer-button", addLayer, false);
/** Handler class for a single canvas (a single layer)
*
@ -14,12 +34,48 @@
* @param height Canvas height
* @param canvas HTML canvas element
*/
function Layer(width, height, canvas) {
this.canvasSize = [width, height];
this.canvas = canvas;
this.context = this.canvas.getContext('2d');
class Layer {
constructor(width, height, canvas, menuEntry) {
this.canvasSize = [width, height];
this.canvas = canvas;
this.context = this.canvas.getContext('2d');
this.isSelected = false;
this.isVisible = true;
this.isLocked = false;
this.menuEntry = menuEntry;
let id = unusedIDs.pop();
console.log("id creato: " + id);
if (id == null) {
id = currentID;
currentID++;
}
this.id = "layer" + id;
if (menuEntry != null) {
this.name = menuEntry.getElementsByTagName("p")[0].innerHTML;
menuEntry.id = "layer" + id;
menuEntry.onclick = () => this.selectLayer();
menuEntry.getElementsByTagName("button")[0].onclick = () => this.toggleLock();
menuEntry.getElementsByTagName("button")[1].onclick = () => this.toggleVisibility();
menuEntry.addEventListener("mouseup", this.openOptionsMenu, false);
menuEntry.addEventListener("dragstart", this.layerDragStart, false);
menuEntry.addEventListener("drop", this.layerDragDrop, false);
menuEntry.addEventListener("dragover", this.layerDragOver, false);
menuEntry.addEventListener("dragleave", this.layerDragLeave, false);
menuEntry.addEventListener("dragend", this.layerDragEnd, false);
menuEntry.getElementsByTagName("canvas")[0].getContext('2d').imageSmoothingEnabled = false;
}
this.initialize();
}
// Initializes the canvas
this.initialize = function() {
initialize() {
var maxHorizontalZoom = Math.floor(window.innerWidth/this.canvasSize[0]*0.75);
var maxVerticalZoom = Math.floor(window.innerHeight/this.canvasSize[1]*0.75);
@ -41,21 +97,494 @@ function Layer(width, height, canvas) {
this.context.imageSmoothingEnabled = false;
this.context.mozImageSmoothingEnabled = false;
};
}
setID(id) {
this.id = id;
if (this.menuEntry != null) {
this.menuEntry.id = id;
}
}
layerDragStart(element) {
layerDragSource = this;
element.dataTransfer.effectAllowed = 'move';
element.dataTransfer.setData('text/html', this.id);
this.classList.add('dragElem');
}
layerDragOver(element) {
if (element.preventDefault) {
element.preventDefault(); // Necessary. Allows us to drop.
}
this.classList.add('layerdragover');
element.dataTransfer.dropEffect = 'move';
return false;
}
layerDragLeave(element) {
this.classList.remove('layerdragover');
}
layerDragDrop(element) {
if (element.stopPropagation) {
element.stopPropagation(); // Stops some browsers from redirecting.
}
// Don't do anything if dropping the same column we're dragging.
if (layerDragSource != this) {
let toDropID = element.dataTransfer.getData('text/html');
let thisID = this.id;
moveLayers(toDropID, thisID);
}
this.classList.remove('layerdragover');
dragging = false;
return false;
}
layerDragEnd(element) {
this.classList.remove('layerdragover');
}
// Resizes canvas
this.resize = function() {
resize() {
let newWidth = (this.canvas.width * zoom) + 'px';
let newHeight = (this.canvas.height *zoom)+ 'px';
this.canvas.style.width = newWidth;
this.canvas.style.height = newHeight;
};
}
// Copies the otherCanvas' position and size
this.copyData = function(otherCanvas) {
copyData(otherCanvas) {
this.canvas.style.width = otherCanvas.canvas.style.width;
this.canvas.style.height = otherCanvas.canvas.style.height;
this.canvas.style.left = otherCanvas.canvas.style.left;
this.canvas.style.top = otherCanvas.canvas.style.top;
};
}
openOptionsMenu(event) {
if (event.which == 3) {
let target = event.target;
let offsets = getElementAbsolutePosition(this);
while (target != null && target.classList != null && !target.classList.contains("layers-menu-entry")) {
target = target.parentElement;
}
idToDelete = target.id;
layerOptions.style.visibility = "visible";
layerOptions.style.top = "0";
layerOptions.style.marginTop = "" + (event.clientY - 25) + "px";
getLayerByID(idToDelete).selectLayer();
}
}
closeOptionsMenu(event) {
layerOptions.style.visibility = "hidden";
currentLayer.menuEntry.getElementsByTagName("p")[0].setAttribute("contenteditable", false);
isRenamingLayer = false;
if (oldLayerName != null) {
let name = this.menuEntry.getElementsByTagName("p")[0].innerHTML;
this.name = name;
new HistoryStateRenameLayer(oldLayerName, name, currentLayer);
oldLayerName = null;
}
}
selectLayer(layer) {
if (layer == null) {
// Deselecting the old layer
currentLayer.deselectLayer();
// Selecting the current layer
this.isSelected = true;
this.menuEntry.classList.add("selected-layer");
currentLayer = getLayerByName(this.menuEntry.getElementsByTagName("p")[0].innerHTML);
}
else {
currentLayer.deselectLayer();
layer.isSelected = true;
layer.menuEntry.classList.add("selected-layer");
currentLayer = layer;
}
canvas = currentLayer.canvas;
context = currentLayer.context;
}
toggleLock() {
if (this.isLocked) {
this.unlock();
}
else {
this.lock();
}
}
toggleVisibility() {
if (this.isVisible) {
this.hide();
}
else {
this.show();
}
}
deselectLayer() {
this.isSelected = false;
this.menuEntry.classList.remove("selected-layer");
}
lock() {
this.isLocked = true;
this.menuEntry.getElementsByClassName("layer-button")[0].style.visibility = "visible";
this.menuEntry.getElementsByClassName("default-icon")[0].style.display = "none";
this.menuEntry.getElementsByClassName("edited-icon")[0].style.display = "inline-block";
}
unlock() {
this.isLocked = false;
this.menuEntry.getElementsByClassName("layer-button")[0].style.visibility = "hidden";
this.menuEntry.getElementsByClassName("default-icon")[0].style.display = "inline-block";
this.menuEntry.getElementsByClassName("edited-icon")[0].style.display = "none";
}
show() {
this.isVisible = true;
this.canvas.style.visibility = "visible";
this.menuEntry.getElementsByClassName("layer-button")[1].style.visibility = "hidden";
// Changing icon
this.menuEntry.getElementsByClassName("default-icon")[1].style.display = "inline-block";
this.menuEntry.getElementsByClassName("edited-icon")[1].style.display = "none";
}
hide() {
this.isVisible = false;
this.canvas.style.visibility = "hidden";
this.menuEntry.getElementsByClassName("layer-button")[1].style.visibility = "visible";
// Changing icon
this.menuEntry.getElementsByClassName("default-icon")[1].style.display = "none";
this.menuEntry.getElementsByClassName("edited-icon")[1].style.display = "inline-block";
}
updateLayerPreview() {
// Getting the canvas
let destination = this.menuEntry.getElementsByTagName("canvas")[0];
let widthRatio = this.canvasSize[0] / this.canvasSize[1];
let heightRatio = this.canvasSize[1] / this.canvasSize[0];
// Computing width and height for the preview image
let previewWidth = destination.width;
let previewHeight = destination.height;
// If the sprite is rectangular, I apply the ratio to the preview as well
if (widthRatio < 1) {
previewWidth = destination.width * widthRatio;
}
else if (widthRatio > 1) {
previewHeight = destination.height * heightRatio;
}
// La appiccico sulla preview
destination.getContext('2d').clearRect(0, 0, destination.width, destination.height);
destination.getContext('2d').drawImage(this.canvas,
// This is necessary to center the preview in the canvas
(destination.width - previewWidth) / 2, (destination.height - previewHeight) / 2,
previewWidth, previewHeight);
}
}
function flatten(onlyVisible) {
if (!onlyVisible) {
// Selecting the first layer
let firstLayer = layerList.firstElementChild;
let nToFlatten = layerList.childElementCount - 1;
getLayerByID(firstLayer.id).selectLayer();
for (let i = 0; i < nToFlatten; i++) {
merge();
}
new HistoryStateFlattenAll(nToFlatten);
}
else {
// Getting all the visible layers
let visibleLayers = [];
let nToFlatten = 0;
for (let i=0; i<layers.length; i++) {
console.log(layers[i].name);
if (layers[i].menuEntry != null && layers[i].isVisible) {
visibleLayers.push(layers[i]);
}
}
console.log("da piallare: " + visibleLayers.length);
// Sorting them by z-index
visibleLayers.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? -1 : 1);
// Selecting the last visible layer (the only one that won't get deleted)
visibleLayers[visibleLayers.length - 1].selectLayer();
// Merging all the layer but the last one
for (let i=0; i<visibleLayers.length - 1; i++) {
nToFlatten++;
console.log(visibleLayers[i].menuEntry.nextElementSibling);
new HistoryStateFlattenTwoVisibles(
visibleLayers[i + 1].context.getImageData(0, 0, visibleLayers[i].canvasSize[0], visibleLayers[i].canvasSize[1]),
visibleLayers[i].menuEntry.nextElementSibling,
layers.indexOf(visibleLayers[i]),
visibleLayers[i], visibleLayers[i + 1]
);
mergeLayers(visibleLayers[i + 1].context, visibleLayers[i].context);
// Deleting the above layer
visibleLayers[i].canvas.remove();
visibleLayers[i].menuEntry.remove();
layers.splice(layers.indexOf(visibleLayers[i]), 1);
}
new HistoryStateFlattenVisible(nToFlatten);
// Updating the layer preview
currentLayer.updateLayerPreview();
}
}
function merge(saveHistory = true) {
// Saving the layer that should be merged
let toMerge = currentLayer;
let toMergeIndex = layers.indexOf(toMerge);
// Getting layer below
let layerBelow = getLayerByID(currentLayer.menuEntry.nextElementSibling.id);
// If I have something to merge with
if (layerBelow != null) {
// Selecting that layer
layerBelow.selectLayer();
if (saveHistory) {
new HistoryStateMergeLayer(toMergeIndex, toMerge,
layerBelow.context.getImageData(0, 0, layerBelow.canvasSize[0], layerBelow.canvasSize[1]),
layerBelow);
}
mergeLayers(currentLayer.context, toMerge.context);
// Deleting the above layer
toMerge.canvas.remove();
toMerge.menuEntry.remove();
layers.splice(toMergeIndex, 1);
// Updating the layer preview
currentLayer.updateLayerPreview();
}
}
function deleteLayer(saveHistory = true) {
// Cannot delete all the layers
if (layers.length != 4) {
let layerIndex = layers.indexOf(currentLayer);
let toDelete = layers[layerIndex];
let previousSibling = toDelete.menuEntry.previousElementSibling;
// Adding the ids to the unused ones
console.log("id cancellato: " + toDelete.id);
unusedIDs.push(toDelete.id);
// Selecting the next layer
if (layerIndex != (layers.length - 3)) {
layers[layerIndex + 1].selectLayer();
}
// or the previous one if the next one doesn't exist
else {
layers[layerIndex - 1].selectLayer();
}
// Deleting canvas and entry
toDelete.canvas.remove();
toDelete.menuEntry.remove();
// Removing the layer from the list
layers.splice(layerIndex, 1);
if (saveHistory) {
new HistoryStateDeleteLayer(toDelete, previousSibling, layerIndex);
}
}
// Closing the menu
currentLayer.closeOptionsMenu();
}
function renameLayer(event) {
let layerIndex = layers.indexOf(currentLayer);
let toRename = currentLayer;
let p = currentLayer.menuEntry.getElementsByTagName("p")[0];
oldLayerName = p.innerHTML;
p.setAttribute("contenteditable", true);
p.classList.add("layer-name-editable");
p.focus();
simulateInput(65, true, false, false);
isRenamingLayer = true;
}
// Swaps two layer entries in the layer menu
function moveLayers(toDropLayer, staticLayer, saveHistory = true) {
let toDrop = getLayerByID(toDropLayer);
let static = getLayerByID(staticLayer);
let layerCopy = layers.slice();
let beforeToDrop = toDrop.menuEntry.nextElementSibling;
let nMoved = 0;
layerCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1);
let toDropIndex = layerCopy.indexOf(toDrop);
let staticIndex = layerCopy.indexOf(static);
layerList.insertBefore(toDrop.menuEntry, static.menuEntry);
if (toDropIndex < staticIndex) {
let tmp = toDrop.canvas.style.zIndex;
let tmp2;
for (let i=toDropIndex+1; i<=staticIndex; i++) {
tmp2 = layerCopy[i].canvas.style.zIndex;
if (saveHistory) {
new HistoryStateMoveTwoLayers(layerCopy[i], tmp2, tmp);
}
layerCopy[i].canvas.style.zIndex = tmp;
tmp = tmp2;
nMoved++;
}
if (saveHistory) {
new HistoryStateMoveTwoLayers(toDrop, toDrop.canvas.style.zIndex, tmp);
}
toDrop.canvas.style.zIndex = tmp;
if (saveHistory) {
new HistoryStateMoveLayer(beforeToDrop, toDrop, static, nMoved);
}
}
else {
// BUG QUI
let tmp = toDrop.canvas.style.zIndex;
let tmp2;
for (let i=toDropIndex-1; i>staticIndex; i--) {
tmp2 = layerCopy[i].canvas.style.zIndex;
if (saveHistory) {
new HistoryStateMoveTwoLayers(layerCopy[i], tmp2, tmp);
}
layerCopy[i].canvas.style.zIndex = tmp;
tmp = tmp2;
nMoved++;
}
if (saveHistory) {
new HistoryStateMoveTwoLayers(toDrop, toDrop.canvas.style.zIndex, tmp);
}
toDrop.canvas.style.zIndex = tmp;
if (saveHistory) {
new HistoryStateMoveLayer(beforeToDrop, toDrop, static, nMoved);
}
}
}
// Finds a layer given its name
function getLayerByName(name) {
for (let i=0; i<layers.length; i++) {
if (layers[i].menuEntry != null) {
if (layers[i].menuEntry.getElementsByTagName("p")[0].innerHTML == name) {
return layers[i];
}
}
}
return null;
}
// Finds a layer given its id
function getLayerByID(id) {
for (let i=0; i<layers.length; i++) {
if (layers[i].menuEntry != null) {
if (layers[i].menuEntry.id == id) {
return layers[i];
}
}
}
return null;
}
function addLayer(id, saveHistory = true) {
// layers.length - 3
let index = layers.length - 3;
// Creating a new canvas
let newCanvas = document.createElement("canvas");
// Setting up the new canvas
canvasView.append(newCanvas);
maxZIndex++;
newCanvas.style.zIndex = maxZIndex;
newCanvas.classList.add("drawingCanvas");
console.log("Tela creata: " + newCanvas);
// Clone the default layer
let toAppend = layerListEntry.cloneNode(true);
// Setting the default name for the layer
toAppend.getElementsByTagName('p')[0].innerHTML = "Layer " + layerCount;
// Removing the selected class
toAppend.classList.remove("selected-layer");
// Adding the layer to the list
layerCount++;
// Creating a layer object
let newLayer = new Layer(currentLayer.canvasSize[0], currentLayer.canvasSize[1], newCanvas, toAppend);
newLayer.context.fillStyle = currentLayer.context.fillStyle;
newLayer.copyData(currentLayer);
layers.splice(index, 0, newLayer);
// Insert it before the Add layer button
layerList.insertBefore(toAppend, layerList.childNodes[0]);
if (id != null && typeof(id) == "string") {
newLayer.setID(id);
}
// Basically "if I'm not adding a layer because redo() is telling meto do so", then I can save the history
if (saveHistory) {
new HistoryStateAddLayer(newLayer, index);
}
return newLayer;
}

View File

@ -1,61 +1,42 @@
document.getElementById('open-image-browse-holder').addEventListener('change', function () {
let fileName = document.getElementById("open-image-browse-holder").value;
let extension = fileName.substring(fileName.lastIndexOf('.')+1, fileName.length) || fileName;
if (this.files && this.files[0]) {
if (extension == 'png' || extension == 'gif' || extension == 'lpe') {
if (extension == 'lpe') {
let file = this.files[0];
let reader = new FileReader();
//make sure file is allowed filetype
var fileContentType = this.files[0].type;
if (fileContentType == 'image/png' || fileContentType == 'image/gif') {
reader.readAsText(file, "UTF-8");
reader.onload = function (e) {
let dictionary = JSON.parse(e.target.result);
//load file
var fileReader = new FileReader();
fileReader.onload = function(e) {
var img = new Image();
img.onload = function() {
newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], dictionary['editorMode'], dictionary);
}
}
else {
//load file
var fileReader = new FileReader();
fileReader.onload = function(e) {
var img = new Image();
img.onload = function() {
//create a new pixel with the images dimentions
newPixel(this.width, this.height, 'Advanced');
//create a new pixel with the images dimentions
newPixel(this.width, this.height, []);
//draw the image onto the canvas
currentLayer.context.drawImage(img, 0, 0);
createPaletteFromLayers();
//draw the image onto the canvas
currentLayer.context.drawImage(img, 0, 0);
var colorPalette = {};
var imagePixelData = currentLayer.context.getImageData(0,0,this.width, this.height).data;
var imagePixelDataLength = imagePixelData.length;
console.log(imagePixelData);
for (var i = 0; i < imagePixelDataLength; i += 4) {
var color = imagePixelData[i]+','+imagePixelData[i + 1]+','+imagePixelData[i + 2];
if (!colorPalette[color]) {
colorPalette[color] = {r:imagePixelData[i],g:imagePixelData[i + 1],b:imagePixelData[i + 2]};
//don't allow more than 256 colors to be added
if (Object.keys(colorPalette).length >= settings.maxColorsOnImportedImage) {
alert('The image loaded seems to have more than '+settings.maxColorsOnImportedImage+' colors.');
break;
}
}
}
//create array out of colors object
var colorPaletteArray = [];
for (var color in colorPalette) {
if( colorPalette.hasOwnProperty(color) ) {
colorPaletteArray.push('#'+rgbToHex(colorPalette[color]));
}
}
console.log('COLOR PALETTE ARRAY', colorPaletteArray);
//create palette form colors array
createColorPalette(colorPaletteArray, false);
//track google event
ga('send', 'event', 'Pixel Editor Load', colorPalette.length, this.width+'/'+this.height); /*global ga*/
//track google event
ga('send', 'event', 'Pixel Editor Load', colorPalette.length, this.width+'/'+this.height); /*global ga*/
};
img.src = e.target.result;
};
img.src = e.target.result;
};
fileReader.readAsDataURL(this.files[0]);
fileReader.readAsDataURL(this.files[0]);
}
}
else alert('Only PNG and GIF files are allowed at this time.');
else alert('Only .lpe project files, PNG and GIF files are allowed at this time.');
}
});

View File

@ -1,5 +1,4 @@
var currentMouseEvent;
// TODO: replace every position calculation with lastMousePos
var lastMousePos;
//mousedown - start drawing
@ -8,8 +7,8 @@ window.addEventListener("mousedown", function (mouseEvent) {
currentMouseEvent = mouseEvent;
canDraw = true;
//if no document has been created yet, or this is a dialog open
if (!documentCreated || dialogueOpen) return;
//if no document has been created yet, or this is a dialog open, or the currentLayer is locked
if (!documentCreated || dialogueOpen || currentLayer.isLocked || !currentLayer.isVisible) return;
//prevent right mouse clicks and such, which will open unwanted menus
//mouseEvent.preventDefault();
@ -32,11 +31,10 @@ window.addEventListener("mousedown", function (mouseEvent) {
canDraw = false;
}
}
//saveHistoryState({type: 'canvas', canvas: context.getImageData(0, 0, canvasSize[0], canvasSize[1])});
currentTool.updateCursor();
if (canDraw) {
if (!currentLayer.isLocked && !currentLayer.isVisible && canDraw) {
draw(mouseEvent);
}
}
@ -45,7 +43,6 @@ window.addEventListener("mousedown", function (mouseEvent) {
tool.pencil.previousBrushSize = tool.pencil.brushSize;
}
else if (currentTool.name == 'eraser' && mouseEvent.which == 3) {
console.log('resize eraser')
currentTool = tool.resizeeraser;
tool.eraser.previousBrushSize = tool.eraser.brushSize;
}
@ -69,29 +66,37 @@ window.addEventListener("mouseup", function (mouseEvent) {
closeMenu();
if (!documentCreated || dialogueOpen) return;
if (currentLayer != null && !isChildOfByClass(mouseEvent.target, "layers-menu-entry")) {
currentLayer.closeOptionsMenu();
}
if (!documentCreated || dialogueOpen || !currentLayer.isVisible || currentLayer.isLocked) return;
if (currentTool.name == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas') {
var cursorLocation = getCursorPosition(mouseEvent);
var selectedColor = context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1);
var newColor = rgbToHex(selectedColor.data[0],selectedColor.data[1],selectedColor.data[2]);
var selectedColor = getEyedropperColor(cursorLocation);
var newColor = rgbToHex(selectedColor[0],selectedColor[1],selectedColor[2]);
currentGlobalColor = "#" + newColor;
for (let i=1; i<layers.length - 1; i++) {
layers[i].context.fillStyle = currentGlobalColor;
}
var colors = document.getElementsByClassName('color-button');
for (var i = 0; i < colors.length; i++) {
console.log(colors[i].jscolor.toString());
//if picked color matches this color
if (newColor == colors[i].jscolor.toString()) {
console.log('color found');
//remove current color selection
var selectedColor = document.querySelector("#colors-menu li.selected")
if (selectedColor) selectedColor.classList.remove("selected");
//set current color
context.fillStyle = '#'+newColor;
for (let i=2; i<layers.length; i++) {
layers[i].context.fillStyle = '#' + newColor;
}
//make color selected
colors[i].parentElement.classList.add('selected');
@ -102,7 +107,6 @@ window.addEventListener("mouseup", function (mouseEvent) {
}
}
else if (currentTool.name == 'fill' && mouseEvent.target.className == 'drawingCanvas') {
console.log('filling')
//get cursor postion
var cursorLocation = getCursorPosition(mouseEvent);
@ -111,8 +115,9 @@ window.addEventListener("mouseup", function (mouseEvent) {
cursorLocation[0] += 2;
cursorLocation[1] += 12;
//fill starting at the location
//fill starting at the location
fill(cursorLocation);
currentLayer.updateLayerPreview();
}
else if (currentTool.name == 'zoom' && mouseEvent.target.className == 'drawingCanvas') {
let mode;
@ -120,21 +125,6 @@ window.addEventListener("mouseup", function (mouseEvent) {
mode = "in";
}
}
else if (currentTool == 'fill' && mouseEvent.target.className == 'drawingCanvas') {
console.log('filling');
//if you clicked on anything but the canvas, do nothing
if (!mouseEvent.target == currentLayer.canvas) return;
//get cursor postion
var cursorLocation = getCursorPosition(mouseEvent);
//offset to match cursor point
cursorLocation[0] += 2;
cursorLocation[1] += 12;
//fill starting at the location
fill(cursorLocation);
}
else if (currentTool == 'zoom' && mouseEvent.target.className == 'drawingCanvas') {
let mode;
if (mouseEvent.which == 1){
@ -153,8 +143,9 @@ window.addEventListener("mouseup", function (mouseEvent) {
else if (currentTool.name == 'rectselect' && isRectSelecting) {
endRectSelection(mouseEvent);
}
else if (currentTool.name == 'rectangle') {
else if (currentTool.name == 'rectangle' && isDrawingRect) {
endRectDrawing(mouseEvent);
currentLayer.updateLayerPreview();
}
dragging = false;
@ -165,6 +156,7 @@ window.addEventListener("mouseup", function (mouseEvent) {
}, false);
// TODO: Make it snap to the pixel grid
function setPreviewPosition(preview, cursor, size){
preview.style.left = (
currentLayer.canvas.offsetLeft
@ -190,8 +182,8 @@ function draw (mouseEvent) {
var cursorLocation = lastMousePos;
//if a document hasnt yet been created, exit this function
if (!documentCreated || dialogueOpen) return;
//if a document hasnt yet been created or the current layer is locked, exit this function
if (!documentCreated || dialogueOpen || !currentLayer.isVisible || currentLayer.isLocked) return;
eyedropperPreview.style.display = 'none';
@ -222,6 +214,8 @@ function draw (mouseEvent) {
//for the darkest 50% of colors, change the brush preview to dark mode
if (colorLightness>127) brushPreview.classList.remove('dark');
else brushPreview.classList.add('dark');
currentLayer.updateLayerPreview();
}
// Decided to write a different implementation in case of differences between the brush and the eraser tool
else if (currentTool.name == 'eraser') {
@ -243,6 +237,8 @@ function draw (mouseEvent) {
lastPos = cursorLocation;
}
}
currentLayer.updateLayerPreview();
}
else if (currentTool.name == 'rectangle')
{
@ -272,7 +268,7 @@ function draw (mouseEvent) {
}
}
else if (currentTool.name == 'eyedropper' && dragging && mouseEvent.target.className == 'drawingCanvas') {
var selectedColor = context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1).data;
let selectedColor = getEyedropperColor(cursorLocation);
eyedropperPreview.style.borderColor = '#'+rgbToHex(selectedColor[0],selectedColor[1],selectedColor[2]);
eyedropperPreview.style.display = 'block';
@ -340,7 +336,6 @@ function draw (mouseEvent) {
else if (currentTool.name == 'rectselect') {
if (dragging && !isRectSelecting && mouseEvent.target.className == 'drawingCanvas') {
isRectSelecting = true;
console.log("cominciata selezione su " + mouseEvent.target.className);
startRectSelection(mouseEvent);
}
else if (dragging && isRectSelecting) {

View File

@ -84,6 +84,7 @@ function endSelection() {
isPasting = false;
isCutting = false;
lastMovePos = undefined;
currentLayer.updateLayerPreview();
new HistoryStateEditCanvas();
}

View File

@ -1,26 +1,72 @@
function newPixel (width, height, palette) {
// Setting the current layer
currentLayer = new Layer(width, height, canvas);
currentLayer.initialize();
let firstPixel = true;
function newPixel (width, height, editorMode, fileContent = null) {
pixelEditorMode = editorMode;
currentPalette = [];
if (firstPixel) {
layerList = document.getElementById("layers-menu");
layerListEntry = layerList.firstElementChild;
// Setting up the current layer
currentLayer = new Layer(width, height, canvas, layerListEntry);
canvas.style.zIndex = 2;
}
else {
let nLayers = layers.length;
for (let i=2; i < layers.length - 2; i++) {
let currentEntry = layers[i].menuEntry;
let associatedLayer;
if (currentEntry != null) {
// Getting the associated layer
associatedLayer = getLayerByID(currentEntry.id);
// Deleting its canvas
associatedLayer.canvas.remove();
// Adding the id to the unused ones
unusedIDs.push(currentEntry.id);
// Removing the entry from the menu
currentEntry.remove();
}
}
// Removing the old layers from the list
for (let i=2; i<nLayers - 2; i++) {
layers.splice(2, 1);
}
// Setting up the current layer
layers[1] = new Layer(width, height, layers[1].canvas, layers[1].menuEntry);
currentLayer = layers[1];
canvas = currentLayer.canvas;
context = currentLayer.context;
canvas.style.zIndex = 2;
}
// Adding the checkerboard behind it
checkerBoard = new Layer(width, height, checkerBoardCanvas);
checkerBoard.initialize();
// Creating the vfx layer on top of everything
VFXLayer = new Layer(width, height, VFXCanvas);
VFXLayer.initialize();
// Tmp layer to draw previews on
TMPLayer = new Layer(width, height, TMPCanvas);
TMPLayer.initialize();
canvasSize = currentLayer.canvasSize;
// Adding the first layer and the checkerboard to the list of layers
layers.push(checkerBoard);
layers.push(currentLayer);
layers.push(VFXLayer);
layers.push(TMPLayer);
if (firstPixel) {
// Cloning the entry so that when I change something on the first layer, those changes aren't
// propagated to the other ones
layerListEntry = layerListEntry.cloneNode(true);
// Adding the first layer and the checkerboard to the list of layers
layers.push(checkerBoard);
layers.push(currentLayer);
layers.push(VFXLayer);
layers.push(TMPLayer);
}
//remove current palette
colors = document.getElementsByClassName('color-button');
@ -30,7 +76,7 @@ function newPixel (width, height, palette) {
//add colors from selected palette
var selectedPalette = getText('palette-button');
if (selectedPalette != 'Choose a palette...') {
if (selectedPalette != 'Choose a palette...' && fileContent == null) {
//if this palette isnt the one specified in the url, then reset the url
if (!palettes[selectedPalette].specified)
@ -39,30 +85,21 @@ function newPixel (width, height, palette) {
//fill the palette with specified palette
createColorPalette(palettes[selectedPalette].colors,true);
}
else {
//this wasn't a specified palette, so reset the url
history.pushState(null, null, '/pixel-editor/app');
else if (fileContent == null) {
//this wasn't a specified palette, so reset the url
history.pushState(null, null, '/pixel-editor/app');
//generate default colors
var fg = hslToRgb(Math.floor(Math.random()*255), 230,70);
var bg = hslToRgb(Math.floor(Math.random()*255), 230,170);
//generate default colors
var fg = hslToRgb(Math.floor(Math.random()*255), 230,70);
var bg = hslToRgb(Math.floor(Math.random()*255), 230,170);
//convert colors to hex
var defaultForegroundColor = rgbToHex(fg.r,fg.g,fg.b);
var defaultBackgroundColor = rgbToHex(bg.r,bg.g,bg.b);
//convert colors to hex
var defaultForegroundColor = rgbToHex(fg.r,fg.g,fg.b);
var defaultBackgroundColor = rgbToHex(bg.r,bg.g,bg.b);
//add colors to paletee
addColor(defaultForegroundColor).classList.add('selected');
addColor(defaultBackgroundColor);
//fill background of canvas with bg color
fillCheckerboard();
/*
currentLayer.context.fillStyle = '#'+defaultBackgroundColor;
currentLayer.context.fillRect(0, 0, canvasSize[0], canvasSize[1]);
console.log('#'+defaultBackgroundColor)
*/
//add colors to palette
addColor(defaultForegroundColor).classList.add('selected');
addColor(defaultBackgroundColor);
//set current drawing color as foreground color
currentLayer.context.fillStyle = '#'+defaultForegroundColor;
@ -70,6 +107,9 @@ function newPixel (width, height, palette) {
selectedPalette = 'none';
}
//fill background of canvas with bg color
fillCheckerboard();
//reset undo and redo states
undoStates = [];
redoStates = [];
@ -77,7 +117,53 @@ function newPixel (width, height, palette) {
closeDialogue();
currentTool.updateCursor();
document.getElementById('save-as-button').classList.remove('disabled');
document.getElementById('export-button').classList.remove('disabled');
documentCreated = true;
firstPixel = false;
if (fileContent != null) {
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 = 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)) {
createPaletteFromLayers();
}
};
img.src = layerImage;
// Setting visibility and lock options
if (!layerData.isVisible) {
createdLayer.hide();
}
if (layerData.isLocked) {
createdLayer.lock();
}
}
}
// Deleting the default layer
deleteLayer(false);
}
if (pixelEditorMode == 'Basic') {
switchMode('Advanced', false);
}
else {
switchMode('Basic', false);
}
}

View File

@ -5,7 +5,7 @@ window.onload = function(){
//if the user specified dimentions
if (specifiedDimentions)
//create a new pixel
newPixel(getValue('size-width'),getValue('size-height'),'');
newPixel(getValue('size-width'),getValue('size-height'), getValue('editor-mode'));
else
//otherwise show the new pixel dialog
showDialogue('new-pixel', false);

View File

@ -44,7 +44,6 @@ on('click', 'load-palette-button', function () {
on('click', 'palette-button', function (e){
toggle('palette-button');
toggle('palette-menu');
@ -55,6 +54,7 @@ on('click', 'palette-button', function (e){
});
on('click', 'new-pixel', function (){
deselect('editor-mode-menu');
deselect('preset-button');
deselect('preset-menu');
deselect('palette-button');

View File

@ -1,7 +1,108 @@
function mergeLayers(belowLayer, topLayer) {
// Copying the above content on the layerBelow
let belowImageData = belowLayer.getImageData(0, 0, canvas.width, canvas.height);
let toMergeImageData = topLayer.getImageData(0, 0, canvas.width, canvas.height);
for (let i=0; i<belowImageData.data.length; i+=4) {
let currentMovePixel = [
toMergeImageData.data[i], toMergeImageData.data[i+1],
toMergeImageData.data[i+2], toMergeImageData.data[i+3]
];
let currentUnderlyingPixel = [
belowImageData.data[i], belowImageData.data[i+1],
belowImageData.data[i+2], belowImageData.data[i+3]
];
if (isPixelEmpty(currentMovePixel)) {
if (!isPixelEmpty(belowImageData)) {
toMergeImageData.data[i] = currentUnderlyingPixel[0];
toMergeImageData.data[i+1] = currentUnderlyingPixel[1];
toMergeImageData.data[i+2] = currentUnderlyingPixel[2];
toMergeImageData.data[i+3] = currentUnderlyingPixel[3];
}
}
}
belowLayer.putImageData(toMergeImageData, 0, 0);
}
function simulateInput(keyCode, ctrl, alt, shift) {
let keyboardEvent = document.createEvent("KeyboardEvent");
let initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";
keyboardEvent[initMethod](
"keydown", // event type: keydown, keyup, keypress
true, // bubbles
true, // cancelable
window, // view: should be window
ctrl, // ctrlKey
alt, // altKey
shift, // shiftKey
false, // metaKey
keyCode, // keyCode: unsigned long - the virtual key code, else 0
keyCode // charCode: unsigned long - the Unicode character associated with the depressed key, else 0
);
document.dispatchEvent(keyboardEvent);
}
function isPixelEmpty(pixel) {
if (pixel == null || pixel === undefined) {
return false;
}
if ((pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0) || pixel[3] == 0) {
return true;
}
return false;
}
function isChildOfByClass(element, className) {
while (element != null && element.classList != null && !element.classList.contains(className)) {
element = element.parentElement;
}
if (element != null && element.classList != null && element.classList.contains(className)) {
return true;
}
return false;
}
function getEyedropperColor(cursorLocation) {
let max = -1;
let tmpColour;
let selectedColor;
for (let i=1; i<layers.length; i++) {
tmpColour = layers[i].context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1).data;
if (layers[i].canvas.style.zIndex > max || isPixelEmpty(selectedColor) || selectedColor === undefined) {
max = layers[i].canvas.style.zIndex;
if (!isPixelEmpty(tmpColour)) {
selectedColor = tmpColour;
}
}
}
if (isPixelEmpty(tmpColour) && selectedColor === undefined) {
selectedColor = [0, 0, 0];
}
return selectedColor;
}
function getElementAbsolutePosition(element) {
let curleft = curtop = 0;
if (element.offsetParent) {
do {
curleft += element.offsetLeft;
curtop += element.offsetTop;
} while (element = element.offsetParent);
}
return [curleft,curtop];
}

View File

@ -49,7 +49,6 @@ Object.keys(presets).forEach(function(presetName,index) {
});
on('click', 'preset-button', function (e){
//open or close the preset menu
toggle('preset-button');

View File

@ -98,8 +98,6 @@ function drawRect(x, y) {
vfxContext.rect(startX, startY, x - startX, y - startY);
vfxContext.stroke();
// TODO: make the rect blink from black to white in case of dark backgrounds
}
function applyChanges() {

View File

@ -16,6 +16,7 @@ on('click',"pencil-smaller-button", function(){
//eraser
on('click',"eraser-button", function(){
console.log("selecting eraser");
tool.eraser.switchTo();
}, false);
@ -31,7 +32,7 @@ on('click',"eraser-smaller-button", function(e){
}, false);
// rectangle
on('click','rectangle-button', function(){
on('click','rectangle-button', function(e){
// If the user clicks twice on the button, they change the draw mode
if (currentTool.name == 'rectangle') {
if (drawMode == 'empty') {

View File

@ -40,9 +40,6 @@ class Tool {
//switch to this tool (replaced global changeTool())
switchTo () {
console.log('changing tool to',this.name)
// Ending any selection in progress
if (currentTool.name.includes("select") && !this.name.includes("select") && !selectionCanceled) {
endSelection();

View File

@ -4,6 +4,7 @@ var dragging = false;
var lastPos = [0,0];
var dialogueOpen = false;
var documentCreated = false;
var pixelEditorMode;
// Checkerboard management
// Checkerboard color 1

View File

@ -19,6 +19,7 @@
//=include _settings.js
/**dropdown formatting**/
//=include _editorMode.js
//=include _presets.js
//=include _palettes.js

View File

@ -1,10 +1,4 @@
<!DOCTYPE html>
<!--todo: credit RECTANGULAR SELECTION ICON BY https://www.flaticon.com/authors/pixel-perfect or
design an original icon (this is just a placeholder)
Must also credit this guy for the rectangle tool
Icons made by <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon"> www.flaticon.com</a>
-->
<html>
<head>
@ -49,8 +43,9 @@
<button>File</button>
<ul>
<li><button>New</button></li>
<li><button>Save project</button></li>
<li><button>Open</button></li>
<li><button id="save-as-button" class="disabled">Save as...</button></li>
<li><button id="export-button" class="disabled">Export</button></li>
<li><a href="/pixel-editor">Exit</a></li>
</ul>
</li>
@ -61,6 +56,18 @@
<li><button id="redo-button" class="disabled">Redo</button></li>
</ul>
</li>
<li>
<button id = "layer-button">Layer</button>
<ul>
<li><button onclick = "addLayer()">New layer</button></li>
<li><button onclick = "renameLayer()">Rename</button></li>
<li><button onclick = "deleteLayer()">Delete</button></li>
<li><button onclick = "merge()">Merge below</button></li>
<li><button onclick = "flatten(false)">Flatten all</button></li>
<li><button onclick = "flatten(true)">Flatten visible</button></li>
</ul>
</li>
<li>
<button>Selection</button>
<ul>
@ -69,6 +76,14 @@
<li><button id="paste-button">Paste</button></li>
<li><button id="cancelSelection-button">Cancel</button></li>
</ul>
</li>
<li>
<button>Editor</button>
<ul>
<li><button id="switch-mode-button">Switch to basic mode</button></li>
</ul>
</li>
<li>
<button>Help</button>
<ul>
@ -96,8 +111,8 @@
<li class="expanded">
<button title="Rectangle Tool (U)" id="rectangle-button">{{svg "rectangle.svg" width="32" height="32" id = "empty-button-svg"}}
{{svg "fullrect.svg" width="32" height="32" id = "full-button-svg" display = "none"}}</button>
<button title="Increase Brush Size" id="rectangle-bigger-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
<button title="Decrease Brush Size" id="rectangle-smaller-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
<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>
</li>
<li><button title="Fill Tool (F)" id="fill-button">{{svg "fill.svg" width="32" height="32"}}</button></li>
@ -114,6 +129,7 @@
<li><button title = "Rectangular Selection Tool (M)" id = "rectselect-button">{{svg "rectselect.svg" width = "32" height = "32"}}</button><li>
</ul>
<!-- PALETTE -->
<ul id="colors-menu">
{{!
@ -123,9 +139,58 @@
<li class="noshrink"><button title="Add Current Color To Palette" id="add-color-button">{{svg "./plus.svg" width="30" height="30"}}</button></li>
</ul>
<!-- LAYER MENU -->
<ul id = "layers-menu">
<li class = "layers-menu-entry selected-layer" draggable = "true">
<canvas class = "preview-canvas"></canvas>
<ul class="layer-buttons">
<li class = "layer-button">
<button title="Lock layer" class="lock-layer-button">
{{svg "unlockedpadlock.svg" width="15" height="15" class = "default-icon"}}
{{svg "lockedpadlock.svg" width="15" height="15" class = "edited-icon" display = "none"}}
</button>
</li>
<li class = "layer-button">
<button title="Show / hide layer" class="hide-layer-button">
{{svg "visible.svg" width="15" height="15" class = "default-icon"}}
{{svg "invisible.svg" width="15" height="15" class = "edited-icon" display = "none"}}
</button>
</li>
</ul>
<p>Layer 0<div class = "gradient"></div></p>
</li>
<li>
<button id = "add-layer-button">
{{svg "plus.svg" width="20" height="20"}} Add layer
</button>
</li>
</ul>
<ul id = "layer-properties-menu">
<li>
<button onclick = "deleteLayer()">Delete</button>
</li>
<li>
<button onclick = "renameLayer()">Rename</button>
</li>
<li>
<button onclick = "merge()">Merge below</button>
</li>
<li>
<button onclick = "flatten(true)">Flatten visible</button>
</li>
<li>
<button onclick = "flatten(false)">Flatten all</button>
</li>
</ul>
<div id="eyedropper-preview"></div>
<div id="brush-preview"></div>
<!-- CANVASES -->
<div id="canvas-view">
<canvas id="vfx-canvas" class = "drawingCanvas"></canvas>
<canvas id = "tmp-canvas" class = "drawingCanvas"></canvas>
@ -136,7 +201,8 @@
<div id="data-holders">
<a id="save-image-link-holder" href="#">dl</a>
<input id="open-image-browse-holder" type="file" accept="image/png, image/gif"/>
<a id="save-project-link-holder" href="#">dl</a>
<input id="open-image-browse-holder" type="file" accept="image/png, image/gif, .lpe"/>
<input id="load-palette-browse-holder" type="file" accept="image/png, image/gif"/>
<canvas id="load-palette-canvas-holder"></canvas>
</div>
@ -156,6 +222,14 @@
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
<h1>New Pixel</h1>
<!-- Editor mode-->
<h2>Editor mode</h2>
<button id = "editor-mode-button" class = "dropdown-button">Choose a mode...</button>
<div id = "editor-mode-menu" class = "dropdown-menu"></div>
<input id="editor-mode" value="{{#if mode}}{{mode}}{{else}}'Advanced'{{/if}}" autocomplete="off" />
<p id = "editor-mode-info"></p>
<!-- Preset-->
<h2>Preset</h2>
<button id="preset-button" class="dropdown-button">Choose a preset...</button>
<div id="preset-menu" class="dropdown-menu"></div>
@ -243,8 +317,6 @@
</div>
</div>
</div>
<script>
palettes = { {{#palettes}}
'{{title}}': {