mirror of
https://github.com/lospec/pixel-editor.git
synced 2023-08-10 21:12:51 +03:00
commit
dd7d7decdb
15
README.md
15
README.md
@ -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
20
_ext/svg/invisible.svg
Normal 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 |
16
_ext/svg/lockedpadlock.svg
Normal file
16
_ext/svg/lockedpadlock.svg
Normal 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 |
15
_ext/svg/unlockedpadlock.svg
Normal file
15
_ext/svg/unlockedpadlock.svg
Normal 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
16
_ext/svg/visible.svg
Normal 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 |
@ -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;
|
||||
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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++) {
|
||||
|
@ -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')) {
|
||||
|
@ -20,6 +20,10 @@ function copySelection() {
|
||||
}
|
||||
|
||||
function pasteSelection() {
|
||||
// Can't paste if the layer is locked
|
||||
if (currentLayer.isLocked) {
|
||||
return;
|
||||
}
|
||||
endSelection();
|
||||
|
||||
isPasting = true;
|
||||
|
@ -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...');
|
||||
});
|
||||
|
@ -1,10 +1,12 @@
|
||||
|
||||
function createColorPalette(selectedPalette, fillBackground) {
|
||||
function createColorPalette(selectedPalette, fillBackground, deletePreviousPalette = true) {
|
||||
//remove current palette
|
||||
if (deletePreviousPalette) {
|
||||
colors = document.getElementsByClassName('color-button');
|
||||
while (colors.length > 0) {
|
||||
colors[0].parentElement.remove();
|
||||
}
|
||||
}
|
||||
|
||||
var lightestColor = '#000000';
|
||||
var darkestColor = '#ffffff';
|
||||
@ -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);
|
||||
}
|
@ -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 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 >-dy) {
|
||||
err -=dy;
|
||||
x0+=sx;
|
||||
}
|
||||
|
||||
if (e2 < dx) {
|
||||
err +=dx;
|
||||
y0+=sy;
|
||||
}
|
||||
|
||||
console.log(x0 + ", " + x1);
|
||||
}
|
||||
}
|
96
js/_editorMode.js
Normal file
96
js/_editorMode.js
Normal 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);
|
||||
});
|
||||
|
||||
});
|
@ -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);
|
||||
}
|
@ -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;
|
||||
|
278
js/_history.js
278
js/_history.js
@ -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');
|
||||
|
@ -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();
|
||||
|
555
js/_layer.js
555
js/_layer.js
@ -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) {
|
||||
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;
|
||||
}
|
@ -1,52 +1,32 @@
|
||||
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);
|
||||
|
||||
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, []);
|
||||
newPixel(this.width, this.height, 'Advanced');
|
||||
|
||||
//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);
|
||||
createPaletteFromLayers();
|
||||
|
||||
//track google event
|
||||
ga('send', 'event', 'Pixel Editor Load', colorPalette.length, this.width+'/'+this.height); /*global ga*/
|
||||
@ -56,6 +36,7 @@ document.getElementById('open-image-browse-holder').addEventListener('change', f
|
||||
};
|
||||
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.');
|
||||
}
|
||||
});
|
@ -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);
|
||||
@ -113,6 +117,7 @@ window.addEventListener("mouseup", function (mouseEvent) {
|
||||
|
||||
//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) {
|
||||
|
@ -84,6 +84,7 @@ function endSelection() {
|
||||
isPasting = false;
|
||||
isCutting = false;
|
||||
lastMovePos = undefined;
|
||||
currentLayer.updateLayerPreview();
|
||||
|
||||
new HistoryStateEditCanvas();
|
||||
}
|
126
js/_newPixel.js
126
js/_newPixel.js
@ -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;
|
||||
|
||||
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,7 +85,7 @@ function newPixel (width, height, palette) {
|
||||
//fill the palette with specified palette
|
||||
createColorPalette(palettes[selectedPalette].colors,true);
|
||||
}
|
||||
else {
|
||||
else if (fileContent == null) {
|
||||
//this wasn't a specified palette, so reset the url
|
||||
history.pushState(null, null, '/pixel-editor/app');
|
||||
|
||||
@ -51,25 +97,19 @@ function newPixel (width, height, palette) {
|
||||
var defaultForegroundColor = rgbToHex(fg.r,fg.g,fg.b);
|
||||
var defaultBackgroundColor = rgbToHex(bg.r,bg.g,bg.b);
|
||||
|
||||
//add colors to paletee
|
||||
//add colors to palette
|
||||
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)
|
||||
*/
|
||||
|
||||
//set current drawing color as foreground color
|
||||
currentLayer.context.fillStyle = '#'+defaultForegroundColor;
|
||||
currentGlobalColor = '#' + defaultForegroundColor;
|
||||
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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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');
|
||||
|
@ -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];
|
||||
}
|
@ -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');
|
||||
|
@ -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() {
|
||||
|
@ -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') {
|
||||
|
@ -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();
|
||||
|
@ -4,6 +4,7 @@ var dragging = false;
|
||||
var lastPos = [0,0];
|
||||
var dialogueOpen = false;
|
||||
var documentCreated = false;
|
||||
var pixelEditorMode;
|
||||
|
||||
// Checkerboard management
|
||||
// Checkerboard color 1
|
||||
|
@ -19,6 +19,7 @@
|
||||
//=include _settings.js
|
||||
|
||||
/**dropdown formatting**/
|
||||
//=include _editorMode.js
|
||||
//=include _presets.js
|
||||
//=include _palettes.js
|
||||
|
||||
|
@ -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}}': {
|
||||
|
Loading…
Reference in New Issue
Block a user