Merge pull request #102 from NSSure/master
Added file option to import image to current layer
This commit is contained in:
commit
9eba2f6fd7
|
@ -0,0 +1,18 @@
|
||||||
|
#import-image {
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 15px 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-image-file {
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-image-location-pivot {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,4 +17,5 @@
|
||||||
@import 'palette-editor';
|
@import 'palette-editor';
|
||||||
@import 'splash-page';
|
@import 'splash-page';
|
||||||
@import "export";
|
@import "export";
|
||||||
@import "save-project";
|
@import "save-project";
|
||||||
|
@import "import-image";
|
|
@ -197,6 +197,7 @@ const ColorModule = (() => {
|
||||||
//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);
|
currentPalette.push(newColor);
|
||||||
//create list item
|
//create list item
|
||||||
const listItem = document.createElement('li');
|
const listItem = document.createElement('li');
|
||||||
|
@ -388,18 +389,45 @@ const ColorModule = (() => {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function createPaletteFromLayers() {
|
function createPaletteFromLayers() {
|
||||||
|
//create array out of colors object
|
||||||
|
let colorPaletteArray = getLayerColors();
|
||||||
|
|
||||||
|
//create palette from colors array
|
||||||
|
createColorPalette(colorPaletteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan the layers for any colors that are not currently in the palette. If any colors
|
||||||
|
* are found they should be added as new colors for the palette.
|
||||||
|
*/
|
||||||
|
function updatePaletteFromLayers() {
|
||||||
|
let layersPaletteArray = getLayerColors();
|
||||||
|
|
||||||
|
for (let i = 0; i < layersPaletteArray.length; i++) {
|
||||||
|
if (currentPalette.indexOf(layersPaletteArray[i]) !== -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
addColor(layersPaletteArray[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates each layer and grab each unique color.
|
||||||
|
* @returns Array of colors used within the current layers.
|
||||||
|
*/
|
||||||
|
function getLayerColors() {
|
||||||
let colors = {};
|
let colors = {};
|
||||||
let nColors = 0;
|
let nColors = 0;
|
||||||
//create array out of colors object
|
//create array out of colors object
|
||||||
let colorPaletteArray = [];
|
let colorPaletteArray = [];
|
||||||
|
|
||||||
for (let i=0; i<currFile.layers.length; i++) {
|
for (let i = 0; i < currFile.layers.length; i++) {
|
||||||
if (currFile.layers[i].hasCanvas()) {
|
if (currFile.layers[i].hasCanvas()) {
|
||||||
let imageData = currFile.layers[i].context.getImageData(
|
let imageData = currFile.layers[i].context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]).data;
|
||||||
0, 0, currFile.canvasSize[0], currFile.canvasSize[1]).data;
|
|
||||||
let dataLength = imageData.length;
|
let dataLength = imageData.length;
|
||||||
|
|
||||||
for (let j=0; j<dataLength; j += 4) {
|
for (let j=0; j < dataLength; j += 4) {
|
||||||
if (!Util.isPixelEmpty(imageData[j])) {
|
if (!Util.isPixelEmpty(imageData[j])) {
|
||||||
let color = imageData[j]+','+imageData[j + 1]+','+imageData[j + 2];
|
let color = imageData[j]+','+imageData[j + 1]+','+imageData[j + 2];
|
||||||
|
|
||||||
|
@ -407,6 +435,7 @@ const ColorModule = (() => {
|
||||||
colorPaletteArray.push('#' + new Color("rgb", imageData[j], imageData[j + 1], imageData[j + 2]).hex);
|
colorPaletteArray.push('#' + new Color("rgb", imageData[j], imageData[j + 1], imageData[j + 2]).hex);
|
||||||
colors[color] = new Color("rgb", imageData[j], imageData[j + 1], imageData[j + 2]).rgb;
|
colors[color] = new Color("rgb", imageData[j], imageData[j + 1], imageData[j + 2]).rgb;
|
||||||
nColors++;
|
nColors++;
|
||||||
|
|
||||||
//don't allow more than 256 colors to be added
|
//don't allow more than 256 colors to be added
|
||||||
if (nColors >= Settings.getCurrSettings().maxColorsOnImportedImage) {
|
if (nColors >= Settings.getCurrSettings().maxColorsOnImportedImage) {
|
||||||
alert('The image loaded seems to have more than '+Settings.getCurrSettings().maxColorsOnImportedImage+' colors.');
|
alert('The image loaded seems to have more than '+Settings.getCurrSettings().maxColorsOnImportedImage+' colors.');
|
||||||
|
@ -418,10 +447,7 @@ const ColorModule = (() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//create palette from colors array
|
return colorPaletteArray;
|
||||||
createColorPalette(colorPaletteArray);
|
|
||||||
|
|
||||||
console.log("Done 2");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCurrentColor(color, refLayer) {
|
function updateCurrentColor(color, refLayer) {
|
||||||
|
@ -446,6 +472,7 @@ const ColorModule = (() => {
|
||||||
resetPalette,
|
resetPalette,
|
||||||
createColorPalette,
|
createColorPalette,
|
||||||
createPaletteFromLayers,
|
createPaletteFromLayers,
|
||||||
|
updatePaletteFromLayers,
|
||||||
updateCurrentColor,
|
updateCurrentColor,
|
||||||
}
|
}
|
||||||
})();
|
})();
|
|
@ -3,9 +3,11 @@ const FileManager = (() => {
|
||||||
// Binding the browse holder change event to file loading
|
// Binding the browse holder change event to file loading
|
||||||
const browseHolder = document.getElementById('open-image-browse-holder');
|
const browseHolder = document.getElementById('open-image-browse-holder');
|
||||||
const browsePaletteHolder = document.getElementById('load-palette-browse-holder');
|
const browsePaletteHolder = document.getElementById('load-palette-browse-holder');
|
||||||
|
const importImageHolder = document.getElementById('import-image-browse-holder');
|
||||||
|
|
||||||
Events.on('change', browseHolder, loadFile);
|
Events.on('change', browseHolder, loadFile);
|
||||||
Events.on('change', browsePaletteHolder, loadPalette);
|
Events.on('change', browsePaletteHolder, loadPalette);
|
||||||
|
Events.on('change', importImageHolder, loadImage);
|
||||||
Events.on('click', 'export-confirm', exportProject);
|
Events.on('click', 'export-confirm', exportProject);
|
||||||
|
|
||||||
function openSaveProjectWindow() {
|
function openSaveProjectWindow() {
|
||||||
|
@ -270,11 +272,121 @@ const FileManager = (() => {
|
||||||
browsePaletteHolder.value = null;
|
browsePaletteHolder.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentImportPivotElement = undefined;
|
||||||
|
currentImportPivotPosition = 'middle';
|
||||||
|
isImportWindowInitialized = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the import image window to allow for configurations
|
||||||
|
* to be made be the image is imported.
|
||||||
|
*/
|
||||||
|
function openImportImageWindow() {
|
||||||
|
// Reset window values.
|
||||||
|
importImageHolder.value = null;
|
||||||
|
|
||||||
|
document.getElementById('import-image-match-size').checked = false;
|
||||||
|
document.getElementById('import-image-update-palette').checked = false;
|
||||||
|
document.getElementById('import-image-name').innerText = "";
|
||||||
|
|
||||||
|
// Workaround to prevent events from firing twice for the import window.
|
||||||
|
if (!this.isImportWindowInitialized) {
|
||||||
|
// Getting the pivot buttons and setting the default pivot selection.
|
||||||
|
let pivotButtons = document.getElementsByClassName("pivot-button");
|
||||||
|
this.currentImportPivotElement = document.querySelector('.import-image-location-pivot .rc-selected-pivot');
|
||||||
|
|
||||||
|
// Add event handlers for each pivot.
|
||||||
|
for (let i=0; i < pivotButtons.length; i++) {
|
||||||
|
Events.on("click", pivotButtons[i], onImportPivotChanged.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
Events.on("click", "select-image", () => document.getElementById('import-image-browse-holder')?.click());
|
||||||
|
Events.on("click", "import-image-confirm", importImage);
|
||||||
|
|
||||||
|
this.isImportWindowInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialogue.showDialogue('import-image', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the image and draws it to the current canvas layer. Called when
|
||||||
|
* the import image window is finalized.
|
||||||
|
*/
|
||||||
|
function importImage() {
|
||||||
|
if (!importImageHolder.files || importImageHolder.files.length === 0) {
|
||||||
|
alert('Please select a file before attempting to import.')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileReader = new FileReader();
|
||||||
|
|
||||||
|
// Once the image has been loaded draw the image to the current layer at the top right.
|
||||||
|
fileReader.onload = (e) => {
|
||||||
|
var img = new Image();
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
let shouldResizeCanvas = document.getElementById('import-image-match-size').checked;
|
||||||
|
let shouldImportColors = document.getElementById('import-image-update-palette').checked;
|
||||||
|
|
||||||
|
// Resize the canvas to the image size if the flag was set to true.
|
||||||
|
if (shouldResizeCanvas) {
|
||||||
|
currFile.resizeCanvas(null, { x: img.width, y: img.height }, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate pivot offset and draw the imported image. Ensure the pivot position accounts for the imported images dimensions.
|
||||||
|
let offset = Util.getPivotPosition(this.currentImportPivotPosition, currFile.canvasSize[0], currFile.canvasSize[1], img.width, img.height);
|
||||||
|
currFile.currentLayer.context.drawImage(img, offset.x, offset.y);
|
||||||
|
|
||||||
|
if (shouldImportColors) {
|
||||||
|
ColorModule.updatePaletteFromLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialogue.closeDialogue();
|
||||||
|
};
|
||||||
|
img.src = e.target.result;
|
||||||
|
};
|
||||||
|
|
||||||
|
fileReader.readAsDataURL(importImageHolder.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the import image holder file input fires an onchange event.
|
||||||
|
*/
|
||||||
|
function loadImage() {
|
||||||
|
if (importImageHolder.files && importImageHolder.files[0]) {
|
||||||
|
let fileName = document.getElementById("import-image-browse-holder").value;
|
||||||
|
let extension = Util.getFileExtension(fileName);
|
||||||
|
|
||||||
|
// Display the file name in the window.
|
||||||
|
document.getElementById('import-image-name').innerText = importImageHolder.files[0].name;
|
||||||
|
|
||||||
|
// Checking if the extension is supported
|
||||||
|
if (extension !== 'png') {
|
||||||
|
alert('Only PNG files are currently allowed to be imported at this time.')
|
||||||
|
importImageHolder.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the selected pivot for the import image is changed.
|
||||||
|
* @param {*} event The event for the selected pivot.
|
||||||
|
*/
|
||||||
|
function onImportPivotChanged(event) {
|
||||||
|
this.currentImportPivotPosition = event.target.getAttribute("value");
|
||||||
|
|
||||||
|
// Setting the selected class
|
||||||
|
this.currentImportPivotElement.classList.remove("rc-selected-pivot");
|
||||||
|
this.currentImportPivotElement = event.target;
|
||||||
|
this.currentImportPivotElement.classList.add("rc-selected-pivot");
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
saveProject,
|
saveProject,
|
||||||
exportProject,
|
exportProject,
|
||||||
openPixelExportWindow,
|
openPixelExportWindow,
|
||||||
openSaveProjectWindow,
|
openSaveProjectWindow,
|
||||||
|
openImportImageWindow,
|
||||||
open
|
open
|
||||||
}
|
}
|
||||||
})();
|
})();
|
|
@ -43,6 +43,9 @@ const TopMenuModule = (() => {
|
||||||
case 'Open':
|
case 'Open':
|
||||||
Events.on('click', currSubmenuButton, FileManager.open);
|
Events.on('click', currSubmenuButton, FileManager.open);
|
||||||
break;
|
break;
|
||||||
|
case 'Import':
|
||||||
|
Events.on('click', currSubmenuButton, FileManager.openImportImageWindow);
|
||||||
|
break;
|
||||||
case 'Export':
|
case 'Export':
|
||||||
Events.on('click', currSubmenuButton, FileManager.openPixelExportWindow);
|
Events.on('click', currSubmenuButton, FileManager.openPixelExportWindow);
|
||||||
break;
|
break;
|
||||||
|
|
73
js/Util.js
73
js/Util.js
|
@ -130,4 +130,77 @@ class Util {
|
||||||
static cursorInCanvas(canvasSize, mousePos) {
|
static cursorInCanvas(canvasSize, mousePos) {
|
||||||
return mousePos[0] >= 0 && mousePos[1] >= 0 && canvasSize[0] > mousePos[0] && canvasSize[1] > mousePos[1];
|
return mousePos[0] >= 0 && mousePos[1] >= 0 && canvasSize[0] > mousePos[0] && canvasSize[1] > mousePos[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getFileExtension(fileName) {
|
||||||
|
return (fileName.substring(fileName.lastIndexOf('.')+1, fileName.length) || fileName).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCanvasBorders() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the x and y offset for drawing images at a specific point 'topleft', 'middle', etc.
|
||||||
|
*
|
||||||
|
* @param {*} pivot A string representing the position of the pivot 'topleft', 'middle', etc.
|
||||||
|
* @param {*} width Width of the bounds often represents the canvas width.
|
||||||
|
* @param {*} height Height of the bounds often represents the canvas height.
|
||||||
|
* @param {*} imageWidth Substracts the offset from calculated x position of the pivot. Defaults to 0.
|
||||||
|
* @param {*} imageHeight Subtracts the offset from the calculated y position of the pivot. Defaults to 0.
|
||||||
|
*
|
||||||
|
* @returns Object containing the x and y offset for the pivot.
|
||||||
|
*/
|
||||||
|
static getPivotPosition(pivot, width, height, imageWidth = 0, imageHeight = 0) {
|
||||||
|
let position = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
let centerX = width / 2;
|
||||||
|
let centerY = height / 2;
|
||||||
|
|
||||||
|
switch (pivot)
|
||||||
|
{
|
||||||
|
case 'topleft':
|
||||||
|
position.x = 0;
|
||||||
|
position.y = 0;
|
||||||
|
break;
|
||||||
|
case 'top':
|
||||||
|
position.x = centerX - (imageWidth / 2);
|
||||||
|
position.y = 0;
|
||||||
|
break;
|
||||||
|
case 'topright':
|
||||||
|
position.x = width - imageWidth;
|
||||||
|
position.y = 0;
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
position.x = 0;
|
||||||
|
position.y = centerY - (imageHeight / 2);
|
||||||
|
break;
|
||||||
|
case 'middle':
|
||||||
|
position.x = centerX - (imageWidth / 2);
|
||||||
|
position.y = centerY - (imageHeight / 2);
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
position.x = width - imageWidth;
|
||||||
|
position.y = centerY - (imageHeight / 2);
|
||||||
|
break;
|
||||||
|
case 'bottomleft':
|
||||||
|
position.x = 0;
|
||||||
|
position.y = height - imageHeight;
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
position.x = centerX - (imageWidth / 2);
|
||||||
|
position.y = height - imageHeight;
|
||||||
|
break;
|
||||||
|
case 'bottomright':
|
||||||
|
position.x = width - imageWidth
|
||||||
|
position.y = height - imageHeight;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "pixel-editor",
|
"name": "pixel-editor",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"license": "GPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"concurrently": "^6.0.2",
|
"concurrently": "^6.0.2",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
|
|
|
@ -3,5 +3,6 @@
|
||||||
<a id="save-project-link-holder" href="#">dl</a>
|
<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="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" />
|
||||||
|
<input id="import-image-browse-holder" type="file" accept="image/png" />
|
||||||
<canvas id="load-palette-canvas-holder"></canvas>
|
<canvas id="load-palette-canvas-holder"></canvas>
|
||||||
</div>
|
</div>
|
|
@ -38,6 +38,7 @@
|
||||||
{{> settings}}
|
{{> settings}}
|
||||||
{{> pixel-export}}
|
{{> pixel-export}}
|
||||||
{{> save-project}}
|
{{> save-project}}
|
||||||
|
{{> import-image}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="pixel-editor.js"></script>
|
<script src="pixel-editor.js"></script>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<li><button>New</button></li>
|
<li><button>New</button></li>
|
||||||
<li><button>Save project</button></li>
|
<li><button>Save project</button></li>
|
||||||
<li><button>Open</button></li>
|
<li><button>Open</button></li>
|
||||||
|
<li><button>Import</button></li>
|
||||||
<li><button id="export-button" class="disabled">Export</button></li>
|
<li><button id="export-button" class="disabled">Export</button></li>
|
||||||
<li><a href="https://lospec.com/pixel-editor">Exit</a></li>
|
<li><a href="https://lospec.com/pixel-editor">Exit</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
<div id="import-image" class="import-image">
|
||||||
|
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||||
|
|
||||||
|
<h1>Import Image</h1>
|
||||||
|
|
||||||
|
<p>Imports image into the current selected layer.</p>
|
||||||
|
|
||||||
|
<div class="import-image-file">
|
||||||
|
<button class="default" id="select-image">Select image</button>
|
||||||
|
<span id="import-image-name"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul role="group">
|
||||||
|
<li>
|
||||||
|
<span><input type="checkbox" id="import-image-match-size"/> Match canvas to image size</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span><input type="checkbox" id="import-image-update-palette"/> Append palette with image colors</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Canvas Location</h2>
|
||||||
|
|
||||||
|
<p>If matching to image size canvas location is ignored.</p>
|
||||||
|
|
||||||
|
<!--PIVOTS-->
|
||||||
|
<div class="import-image-location-pivot">
|
||||||
|
<span id = "pivot-menu">
|
||||||
|
<button class="pivot-button" value="topleft">{{svg "arrows/topleft.svg" width="20" height="20"}}</button>
|
||||||
|
<button class="pivot-button" value="top">{{svg "arrows/top.svg" width="20" height="20"}}</button>
|
||||||
|
<button class="pivot-button" value="topright">{{svg "arrows/topright.svg" width="20" height="20"}}</button>
|
||||||
|
<button class="pivot-button" value="left">{{svg "arrows/left.svg" width="20" height="20"}}</button>
|
||||||
|
<button class="pivot-button rc-selected-pivot" value="middle">{{svg "arrows/middle.svg" width="20" height="20"}}</button>
|
||||||
|
<button class="pivot-button" value="right">{{svg "arrows/right.svg" width="20" height="20"}}</button>
|
||||||
|
<button class="pivot-button" value="bottomleft">{{svg "arrows/bottomleft.svg" width="20" height="20"}}</button>
|
||||||
|
<button class="pivot-button" value="bottom">{{svg "arrows/bottom.svg" width="20" height="20"}}</button>
|
||||||
|
<button class="pivot-button" value="bottomright">{{svg "arrows/bottomright.svg" width="20" height="20"}}</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="popup-actions">
|
||||||
|
<button class="default" id="import-image-confirm">Import</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Reference in New Issue