Started project opening

Fixed a bug in the flatten visible undo, created an array to store the colours in the current palette, implemented project saving.
This commit is contained in:
unsettledgames 2020-06-27 13:29:28 +02:00
parent 0b5bb3ab1a
commit 2868363bb7
10 changed files with 140 additions and 89 deletions

View File

@ -12,11 +12,11 @@ The next version is mostly focused on adding missing essential features and port
Suggestions / Planned features: Suggestions / Planned features:
- Save project while keeping layer data
- Line tool - Line tool
- Resize canvas - Resize canvas
- Snap brush preview to pixel grid - Snap brush preview to pixel grid
- Move selection with arrows - Move selection with arrows
- Load palette from LPE file
- Custom color picker - Custom color picker
- custom code without dependencies - custom code without dependencies
@ -36,7 +36,7 @@ Suggestions / Planned features:
- Another currentLayer.canvas - Another currentLayer.canvas
- Must be rescaled each zoom - Must be rescaled each zoom
- Possibly add collaborate function using together.js - Possibly add collaborate function
- Bug fix - Bug fix
- Alt + scroll broken - Alt + scroll broken

View File

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

View File

@ -28,10 +28,11 @@ function colorChanged(e) {
var newColor = hexToRgb(e.target.value); var newColor = hexToRgb(e.target.value);
var oldColor = e.target.oldColor; var oldColor = e.target.oldColor;
currentPalette.splice(currentPalette.indexOf("#" + newColor), 1);
newColor.a = 255; newColor.a = 255;
//save undo state //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)); new HistoryStateEditColor(e.target.value.toLowerCase(), rgbToHex(oldColor));
//get the currently selected color //get the currently selected color
@ -82,7 +83,7 @@ function colorChanged(e) {
//set new old color to changed color //set new old color to changed color
e.target.oldColor = newColor; e.target.oldColor = newColor;
currentPalette.push('#' + newColorHex);
//if this is the current color, update the drawing color //if this is the current color, update the drawing color
if (e.target.colorElement.parentElement.classList.contains('selected')) { if (e.target.colorElement.parentElement.classList.contains('selected')) {

View File

@ -34,12 +34,6 @@ function createColorPalette(selectedPalette, fillBackground) {
} }
//fill bg with lightest color
if (fillBackground) {
currentLayer.context.fillStyle = lightestColor;
currentLayer.context.fillRect(0, 0, canvasSize[0], canvasSize[1]);
}
//set as current color //set as current color
currentLayer.context.fillStyle = darkestColor; currentLayer.context.fillStyle = darkestColor;
} }

View File

@ -33,6 +33,29 @@ for (var i = 1; i < mainMenuItems.length; i++) {
//File Menu //File Menu
case 'New': case 'New':
showDialogue('new-pixel'); 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; break;
case 'Open': case 'Open':
//if a document exists //if a document exists
@ -48,9 +71,8 @@ for (var i = 1; i < mainMenuItems.length; i++) {
break; break;
case 'Save as...': case 'Export':
if (documentCreated) { if (documentCreated) {
//create name //create name
var selectedPalette = getText('palette-button'); var selectedPalette = getText('palette-button');
if (selectedPalette != 'Choose a palette...'){ if (selectedPalette != 'Choose a palette...'){
@ -66,7 +88,7 @@ for (var i = 1; i < mainMenuItems.length; i++) {
// Creating a tmp canvas to flatten everything // Creating a tmp canvas to flatten everything
var exportCanvas = document.createElement("canvas"); var exportCanvas = document.createElement("canvas");
var emptyCanvas = document.createElement("canvas"); var emptyCanvas = document.createElement("canvas");
var layersCopy = layers.slice();; var layersCopy = layers.slice();
exportCanvas.width = canvasSize[0]; exportCanvas.width = canvasSize[0];
exportCanvas.height = canvasSize[1]; exportCanvas.height = canvasSize[1];
@ -75,7 +97,7 @@ for (var i = 1; i < mainMenuItems.length; i++) {
emptyCanvas.height = canvasSize[1]; emptyCanvas.height = canvasSize[1];
// Sorting the layers by z index // Sorting the layers by z index
layersCopy.sort((a, b) => (a.canvas.zIndex > b.canvas.zIndex) ? 1 : -1); layersCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1);
// Merging every layer on the export canvas // Merging every layer on the export canvas
for (let i=0; i<layersCopy.length; i++) { for (let i=0; i<layersCopy.length; i++) {
@ -100,7 +122,7 @@ for (var i = 1; i < mainMenuItems.length; i++) {
exportCanvas.remove(); exportCanvas.remove();
//track google event //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; break;
@ -149,7 +171,6 @@ for (var i = 1; i < mainMenuItems.length; i++) {
break; break;
//Help Menu //Help Menu
case 'Settings': case 'Settings':
//fill form with current settings values //fill form with current settings values
setValue('setting-numberOfHistoryStates', settings.numberOfHistoryStates); setValue('setting-numberOfHistoryStates', settings.numberOfHistoryStates);
@ -178,3 +199,27 @@ function closeMenu () {
deselect(mainMenuItems[i]); deselect(mainMenuItems[i]);
} }
} }
function getProjectData() {
// use a dictionary
let dictionary = [];
// store canvas size
dictionary.push({key: "canvasWidth", value: currentLayer.canvasSize[0]});
dictionary.push({key: "canvasHeight", value: currentLayer.canvasSize[1]});
// store palette
for (let i=0; i<currentPalette.length; i++) {
dictionary.push({key: "color" + i, value: currentPalette[i]});
}
// store layers
for (let i=0; i<layers.length; i++) {
// Only saving the layers the user has access to (no vfx, tmp or checkerboard layers)
if (layers[i].menuEntry != null) {
dictionary.push({key: "layer" + i, value: layers[i]});
dictionary.push({key: "layer" + i + "ImageData",
value: layers[i].canvas.toDataURL()
});
}
}
return JSON.stringify(dictionary);
}

View File

@ -25,20 +25,15 @@ function HistoryStateFlattenVisible(flattened) {
saveHistoryState(this); saveHistoryState(this);
} }
function HistoryStateFlattenTwoVisibles(belowImageData, beforeAbove, layerIndex, aboveLayer, belowLayer) { function HistoryStateFlattenTwoVisibles(belowImageData, afterAbove, layerIndex, aboveLayer, belowLayer) {
this.aboveLayer = aboveLayer; this.aboveLayer = aboveLayer;
this.belowLayer = belowLayer; this.belowLayer = belowLayer;
this.belowImageData = belowImageData; this.belowImageData = belowImageData;
this.undo = function() { this.undo = function() {
// SCEMOOOO DEVI METTERCI PURE I PIXELSSSS console.log(afterAbove.menuEntry);
canvasView.append(aboveLayer.canvas); canvasView.append(aboveLayer.canvas);
if (beforeAbove != null) { layerList.insertBefore(aboveLayer.menuEntry, afterAbove);
layerList.insertBefore(aboveLayer.menuEntry, beforeAbove.menuEntry);
}
else {
layerList.prepend(aboveLayer.menuEntry);
}
belowLayer.context.clearRect(0, 0, belowLayer.canvasSize[0], belowLayer.canvasSize[1]); belowLayer.context.clearRect(0, 0, belowLayer.canvasSize[0], belowLayer.canvasSize[1]);
belowLayer.context.putImageData(this.belowImageData, 0, 0); belowLayer.context.putImageData(this.belowImageData, 0, 0);

View File

@ -55,6 +55,7 @@ class Layer {
this.id = "layer" + id; this.id = "layer" + id;
if (menuEntry != null) { if (menuEntry != null) {
this.name = menuEntry.getElementsByTagName("p")[0].innerHTML;
menuEntry.id = "layer" + id; menuEntry.id = "layer" + id;
menuEntry.onclick = () => this.selectLayer(); menuEntry.onclick = () => this.selectLayer();
menuEntry.getElementsByTagName("button")[0].onclick = () => this.toggleLock(); menuEntry.getElementsByTagName("button")[0].onclick = () => this.toggleLock();
@ -195,7 +196,10 @@ class Layer {
isRenamingLayer = false; isRenamingLayer = false;
if (oldLayerName != null) { if (oldLayerName != null) {
new HistoryStateRenameLayer(oldLayerName, this.menuEntry.getElementsByTagName("p")[0].innerHTML, currentLayer); let name = this.menuEntry.getElementsByTagName("p")[0].innerHTML;
this.name = name;
new HistoryStateRenameLayer(oldLayerName, name, currentLayer);
oldLayerName = null; oldLayerName = null;
} }
} }
@ -333,16 +337,17 @@ function flatten(onlyVisible) {
} }
// Sorting them by z-index // Sorting them by z-index
visibleLayers.sort((a, b) => (a.canvas.zIndex > b.canvas.zIndex) ? -1 : 1); 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) // Selecting the last visible layer (the only one that won't get deleted)
visibleLayers[visibleLayers.length - 1].selectLayer(); visibleLayers[visibleLayers.length - 1].selectLayer();
// Merging all the layer but the last one // Merging all the layer but the last one
for (let i=0; i<visibleLayers.length - 1; i++) { for (let i=0; i<visibleLayers.length - 1; i++) {
nToFlatten++; nToFlatten++;
console.log(visibleLayers[i].menuEntry.nextElementSibling);
new HistoryStateFlattenTwoVisibles( new HistoryStateFlattenTwoVisibles(
visibleLayers[i + 1].context.getImageData(0, 0, visibleLayers[i].canvasSize[0], visibleLayers[i].canvasSize[1]), visibleLayers[i + 1].context.getImageData(0, 0, visibleLayers[i].canvasSize[0], visibleLayers[i].canvasSize[1]),
visibleLayers[i].menuEntry.previousElementSibling, visibleLayers[i].menuEntry.nextElementSibling,
layers.indexOf(visibleLayers[i]), layers.indexOf(visibleLayers[i]),
visibleLayers[i], visibleLayers[i + 1] visibleLayers[i], visibleLayers[i + 1]
); );
@ -572,7 +577,6 @@ function addLayer(id, saveHistory = true) {
layerList.insertBefore(toAppend, layerList.childNodes[0]); layerList.insertBefore(toAppend, layerList.childNodes[0]);
if (id != null && typeof(id) == "string") { if (id != null && typeof(id) == "string") {
console.log("imposto");
newLayer.setID(id); newLayer.setID(id);
} }
// Basically "if I'm not adding a layer because redo() is telling meto do so", then I can save the history // Basically "if I'm not adding a layer because redo() is telling meto do so", then I can save the history

View File

@ -3,59 +3,65 @@ document.getElementById('open-image-browse-holder').addEventListener('change', f
//make sure file is allowed filetype //make sure file is allowed filetype
var fileContentType = this.files[0].type; var fileContentType = this.files[0].type;
if (fileContentType == 'image/png' || fileContentType == 'image/gif') { console.log("File: " + fileContentType);
//load file if (fileContentType == 'image/png' || fileContentType == 'image/gif' || fileContentType == '.lpe') {
var fileReader = new FileReader(); if (fileContentType == '.lpe') {
fileReader.onload = function(e) { console.log("OK");
var img = new Image(); }
img.onload = function() { 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 //create a new pixel with the images dimentions
newPixel(this.width, this.height, []); newPixel(this.width, this.height, []);
//draw the image onto the canvas //draw the image onto the canvas
currentLayer.context.drawImage(img, 0, 0); currentLayer.context.drawImage(img, 0, 0);
var colorPalette = {}; var colorPalette = {};
var imagePixelData = currentLayer.context.getImageData(0,0,this.width, this.height).data; var imagePixelData = currentLayer.context.getImageData(0,0,this.width, this.height).data;
var imagePixelDataLength = imagePixelData.length; var imagePixelDataLength = imagePixelData.length;
console.log(imagePixelData); console.log(imagePixelData);
for (var i = 0; i < imagePixelDataLength; i += 4) { for (var i = 0; i < imagePixelDataLength; i += 4) {
var color = imagePixelData[i]+','+imagePixelData[i + 1]+','+imagePixelData[i + 2]; var color = imagePixelData[i]+','+imagePixelData[i + 1]+','+imagePixelData[i + 2];
if (!colorPalette[color]) { if (!colorPalette[color]) {
colorPalette[color] = {r:imagePixelData[i],g:imagePixelData[i + 1],b:imagePixelData[i + 2]}; colorPalette[color] = {r:imagePixelData[i],g:imagePixelData[i + 1],b:imagePixelData[i + 2]};
//don't allow more than 256 colors to be added //don't allow more than 256 colors to be added
if (Object.keys(colorPalette).length >= settings.maxColorsOnImportedImage) { if (Object.keys(colorPalette).length >= settings.maxColorsOnImportedImage) {
alert('The image loaded seems to have more than '+settings.maxColorsOnImportedImage+' colors.'); alert('The image loaded seems to have more than '+settings.maxColorsOnImportedImage+' colors.');
break; break;
}
} }
} }
}
//create array out of colors object //create array out of colors object
var colorPaletteArray = []; var colorPaletteArray = [];
for (var color in colorPalette) { for (var color in colorPalette) {
if( colorPalette.hasOwnProperty(color) ) { if( colorPalette.hasOwnProperty(color) ) {
colorPaletteArray.push('#'+rgbToHex(colorPalette[color])); colorPaletteArray.push('#'+rgbToHex(colorPalette[color]));
}
} }
} console.log('COLOR PALETTE ARRAY', colorPaletteArray);
console.log('COLOR PALETTE ARRAY', colorPaletteArray);
//create palette form colors array //create palette form colors array
createColorPalette(colorPaletteArray, false); createColorPalette(colorPaletteArray, false);
//track google event //track google event
ga('send', 'event', 'Pixel Editor Load', colorPalette.length, this.width+'/'+this.height); /*global ga*/ 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,6 +1,7 @@
let firstPixel = true; let firstPixel = true;
function newPixel (width, height, palette) { function newPixel (width, height, palette) {
currentPalette = [];
if (firstPixel) { if (firstPixel) {
layerList = document.getElementById("layers-menu"); layerList = document.getElementById("layers-menu");
layerListEntry = layerList.firstElementChild; layerListEntry = layerList.firstElementChild;
@ -115,7 +116,7 @@ function newPixel (width, height, palette) {
closeDialogue(); closeDialogue();
currentTool.updateCursor(); currentTool.updateCursor();
document.getElementById('save-as-button').classList.remove('disabled'); document.getElementById('export-button').classList.remove('disabled');
documentCreated = true; documentCreated = true;
firstPixel = false; firstPixel = false;

View File

@ -43,8 +43,9 @@
<button>File</button> <button>File</button>
<ul> <ul>
<li><button>New</button></li> <li><button>New</button></li>
<li><button>Save project</button></li>
<li><button>Open</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> <li><a href="/pixel-editor">Exit</a></li>
</ul> </ul>
</li> </li>
@ -149,8 +150,7 @@
</li> </li>
</ul> </ul>
<p>Layer 0 <p>Layer 0<div class = "gradient"></div></p>
<div class = "gradient"></div></p>
</li> </li>
<li> <li>
@ -158,24 +158,24 @@
{{svg "plus.svg" width="20" height="20"}} Add layer {{svg "plus.svg" width="20" height="20"}} Add layer
</button> </button>
</li> </li>
</ul>
<ul id = "layer-properties-menu"> <ul id = "layer-properties-menu">
<li> <li>
<button onclick = "deleteLayer()">Delete</button> <button onclick = "deleteLayer()">Delete</button>
</li> </li>
<li> <li>
<button onclick = "renameLayer()">Rename</button> <button onclick = "renameLayer()">Rename</button>
</li> </li>
<li> <li>
<button onclick = "merge()">Merge below</button> <button onclick = "merge()">Merge below</button>
</li> </li>
<li> <li>
<button onclick = "flatten(true)">Flatten visible</button> <button onclick = "flatten(true)">Flatten visible</button>
</li> </li>
<li> <li>
<button onclick = "flatten(false)">Flatten all</button> <button onclick = "flatten(false)">Flatten all</button>
</li> </li>
</ul>
</ul> </ul>
<div id="eyedropper-preview"></div> <div id="eyedropper-preview"></div>
@ -192,7 +192,8 @@
<div id="data-holders"> <div id="data-holders">
<a id="save-image-link-holder" href="#">dl</a> <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"/> <input id="load-palette-browse-holder" type="file" accept="image/png, image/gif"/>
<canvas id="load-palette-canvas-holder"></canvas> <canvas id="load-palette-canvas-holder"></canvas>
</div> </div>