From 02806c7efa1d60fb62afb71c6ef7a6712b0c0395 Mon Sep 17 00:00:00 2001
From: NSSure <19178714+NSSure@users.noreply.github.com>
Date: Fri, 21 Oct 2022 03:52:55 -0400
Subject: [PATCH 1/5] Added file option to import image to current layer
---
css/_import-image.scss | 13 +++++++
css/pixel-editor.scss | 3 +-
js/FileManager.js | 65 +++++++++++++++++++++++++++++++++++
js/TopMenuModule.js | 3 ++
js/Util.js | 4 +++
package-lock.json | 1 +
views/holders.hbs | 1 +
views/index.hbs | 1 +
views/main-menu.hbs | 1 +
views/popups/import-image.hbs | 20 +++++++++++
10 files changed, 111 insertions(+), 1 deletion(-)
create mode 100644 css/_import-image.scss
create mode 100644 views/popups/import-image.hbs
diff --git a/css/_import-image.scss b/css/_import-image.scss
new file mode 100644
index 0000000..1e88c36
--- /dev/null
+++ b/css/_import-image.scss
@@ -0,0 +1,13 @@
+#import-image {
+ .import-image-file {
+ button {
+ margin: 0;
+ }
+ }
+
+ .import-image-checkbox {
+ height: 50px;
+ display: flex;
+ align-items: center;
+ }
+}
\ No newline at end of file
diff --git a/css/pixel-editor.scss b/css/pixel-editor.scss
index 4f24dae..b45d998 100644
--- a/css/pixel-editor.scss
+++ b/css/pixel-editor.scss
@@ -17,4 +17,5 @@
@import 'palette-editor';
@import 'splash-page';
@import "export";
-@import "save-project";
\ No newline at end of file
+@import "save-project";
+@import "import-image";
\ No newline at end of file
diff --git a/js/FileManager.js b/js/FileManager.js
index fe5e7d8..28556f1 100644
--- a/js/FileManager.js
+++ b/js/FileManager.js
@@ -3,9 +3,11 @@ const FileManager = (() => {
// Binding the browse holder change event to file loading
const browseHolder = document.getElementById('open-image-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', browsePaletteHolder, loadPalette);
+ Events.on('change', importImageHolder, loadImage);
Events.on('click', 'export-confirm', exportProject);
function openSaveProjectWindow() {
@@ -270,11 +272,74 @@ const FileManager = (() => {
browsePaletteHolder.value = null;
}
+ /**
+ * Displays the import image window to allow for configurations
+ * to be made be the image is imported.
+ */
+ function openImportImageWindow() {
+ Events.on("click", "select-image", () => document.getElementById('import-image-browse-holder')?.click());
+ Events.on("click", "import-image-confirm", importImage);
+ 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 = function(e) {
+ var img = new Image();
+ img.onload = () => {
+ let shouldResizeCanvas = document.getElementById('import-image-match-size').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);
+ }
+
+ currFile.currentLayer.context.drawImage(img, 0, 0)
+
+ 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;
+ }
+ }
+ }
+
return {
saveProject,
exportProject,
openPixelExportWindow,
openSaveProjectWindow,
+ openImportImageWindow,
open
}
})();
\ No newline at end of file
diff --git a/js/TopMenuModule.js b/js/TopMenuModule.js
index 3b70d64..95221ab 100644
--- a/js/TopMenuModule.js
+++ b/js/TopMenuModule.js
@@ -43,6 +43,9 @@ const TopMenuModule = (() => {
case 'Open':
Events.on('click', currSubmenuButton, FileManager.open);
break;
+ case 'Import':
+ Events.on('click', currSubmenuButton, FileManager.openImportImageWindow);
+ break;
case 'Export':
Events.on('click', currSubmenuButton, FileManager.openPixelExportWindow);
break;
diff --git a/js/Util.js b/js/Util.js
index a6ba566..7f0b2a0 100644
--- a/js/Util.js
+++ b/js/Util.js
@@ -130,4 +130,8 @@ class Util {
static cursorInCanvas(canvasSize, mousePos) {
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();
+ }
}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 0c67187..21ca0ca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,7 @@
"": {
"name": "pixel-editor",
"version": "1.0.0",
+ "license": "GPL-3.0-only",
"dependencies": {
"concurrently": "^6.0.2",
"express": "^4.16.4",
diff --git a/views/holders.hbs b/views/holders.hbs
index 1c83b49..a144583 100644
--- a/views/holders.hbs
+++ b/views/holders.hbs
@@ -3,5 +3,6 @@
dl
+
\ No newline at end of file
diff --git a/views/index.hbs b/views/index.hbs
index f2768bd..94de922 100644
--- a/views/index.hbs
+++ b/views/index.hbs
@@ -38,6 +38,7 @@
{{> settings}}
{{> pixel-export}}
{{> save-project}}
+ {{> import-image}}
diff --git a/views/main-menu.hbs b/views/main-menu.hbs
index f40d9b1..ff4243c 100644
--- a/views/main-menu.hbs
+++ b/views/main-menu.hbs
@@ -6,6 +6,7 @@
+
Exit
diff --git a/views/popups/import-image.hbs b/views/popups/import-image.hbs
new file mode 100644
index 0000000..c848956
--- /dev/null
+++ b/views/popups/import-image.hbs
@@ -0,0 +1,20 @@
+
+
+
+
Import Image
+
+
Imports image into the current selected layer.
+
+
+
+
+
+
+
+ Match canvas to image size
+
+
+
+
\ No newline at end of file
From eb139ef8e76de765970224b5f2d20cde2975597b Mon Sep 17 00:00:00 2001
From: NSSure <19178714+NSSure@users.noreply.github.com>
Date: Fri, 21 Oct 2022 04:18:10 -0400
Subject: [PATCH 2/5] Reset import image window on open
---
js/FileManager.js | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/js/FileManager.js b/js/FileManager.js
index 28556f1..905c6d9 100644
--- a/js/FileManager.js
+++ b/js/FileManager.js
@@ -277,8 +277,14 @@ const FileManager = (() => {
* 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-name').innerText = "";
+
Events.on("click", "select-image", () => document.getElementById('import-image-browse-holder')?.click());
Events.on("click", "import-image-confirm", importImage);
+
Dialogue.showDialogue('import-image', false);
}
From b8d1f11f93b7d506305ce35b2520968375b4d67f Mon Sep 17 00:00:00 2001
From: NSSure <19178714+NSSure@users.noreply.github.com>
Date: Sat, 22 Oct 2022 05:46:48 -0400
Subject: [PATCH 3/5] Added ability to select import position
---
css/_import-image.scss | 9 ++++-
js/ColorModule.js | 67 ++++++++++++++++++++++++++++++----
js/FileManager.js | 47 ++++++++++++++++++++++--
js/Util.js | 69 +++++++++++++++++++++++++++++++++++
views/popups/import-image.hbs | 28 +++++++++++++-
5 files changed, 204 insertions(+), 16 deletions(-)
diff --git a/css/_import-image.scss b/css/_import-image.scss
index 1e88c36..e34ed29 100644
--- a/css/_import-image.scss
+++ b/css/_import-image.scss
@@ -1,12 +1,17 @@
#import-image {
+ ul {
+ list-style-type: none;
+ margin: 15px 0;
+ padding: 0;
+ }
+
.import-image-file {
button {
margin: 0;
}
}
- .import-image-checkbox {
- height: 50px;
+ .import-image-location-pivot {
display: flex;
align-items: center;
}
diff --git a/js/ColorModule.js b/js/ColorModule.js
index 9b90237..0fac69e 100644
--- a/js/ColorModule.js
+++ b/js/ColorModule.js
@@ -197,6 +197,7 @@ const ColorModule = (() => {
//add # at beginning if not present
if (newColor.charAt(0) != '#')
newColor = '#' + newColor;
+
currentPalette.push(newColor);
//create list item
const listItem = document.createElement('li');
@@ -388,18 +389,69 @@ const ColorModule = (() => {
*
*/
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]);
+ }
+
+ return;
+
+ // Compare the current layer colors to the current palette and add any layer colors that aren't in the palette.
+ let mergedPalette = [...currentPalette];
+
+ for (let i = 0; i < layersPaletteArray.length; i++) {
+ let isNewPaletteColor = true;
+
+ for (let j = 0; j < currentPalette.length; j++) {
+ // If the layer color matches an existing palette color skip it so it isn't added again.
+ if (layersPaletteArray[i] === mergedPalette[j]) {
+ isNewPaletteColor = false;
+ break;
+ }
+ }
+
+ if (isNewPaletteColor) {
+ mergedPalette.push(layersPaletteArray[i]);
+ }
+ }
+
+ // Recreate the palette using the merged colors array.
+ createColorPalette(mergedPalette);
+ }
+
+ /**
+ * Iterates each layer and grab each unique color.
+ * @returns Array of colors used within the current layers.
+ */
+ function getLayerColors() {
let colors = {};
let nColors = 0;
//create array out of colors object
let colorPaletteArray = [];
- for (let i=0; i {
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;
nColors++;
+
//don't allow more than 256 colors to be added
if (nColors >= Settings.getCurrSettings().maxColorsOnImportedImage) {
alert('The image loaded seems to have more than '+Settings.getCurrSettings().maxColorsOnImportedImage+' colors.');
@@ -418,10 +471,7 @@ const ColorModule = (() => {
}
}
- //create palette from colors array
- createColorPalette(colorPaletteArray);
-
- console.log("Done 2");
+ return colorPaletteArray;
}
function updateCurrentColor(color, refLayer) {
@@ -446,6 +496,7 @@ const ColorModule = (() => {
resetPalette,
createColorPalette,
createPaletteFromLayers,
+ updatePaletteFromLayers,
updateCurrentColor,
}
})();
\ No newline at end of file
diff --git a/js/FileManager.js b/js/FileManager.js
index 905c6d9..84cf9d6 100644
--- a/js/FileManager.js
+++ b/js/FileManager.js
@@ -272,6 +272,10 @@ const FileManager = (() => {
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.
@@ -279,11 +283,25 @@ const FileManager = (() => {
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 = "";
- Events.on("click", "select-image", () => document.getElementById('import-image-browse-holder')?.click());
- Events.on("click", "import-image-confirm", importImage);
+ // 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);
+ }
Dialogue.showDialogue('import-image', false);
}
@@ -301,17 +319,25 @@ const FileManager = (() => {
var fileReader = new FileReader();
// Once the image has been loaded draw the image to the current layer at the top right.
- fileReader.onload = function(e) {
+ 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);
}
- currFile.currentLayer.context.drawImage(img, 0, 0)
+ // 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();
};
@@ -340,6 +366,19 @@ const FileManager = (() => {
}
}
+ /**
+ * 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 {
saveProject,
exportProject,
diff --git a/js/Util.js b/js/Util.js
index 7f0b2a0..9056d99 100644
--- a/js/Util.js
+++ b/js/Util.js
@@ -134,4 +134,73 @@ class Util {
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;
+ }
}
\ No newline at end of file
diff --git a/views/popups/import-image.hbs b/views/popups/import-image.hbs
index c848956..c85cba2 100644
--- a/views/popups/import-image.hbs
+++ b/views/popups/import-image.hbs
@@ -10,8 +10,32 @@
-
-
Match canvas to image size
+
+
+
Canvas Location
+
+
If matching to image size canvas location is ignored.
+
+
+
+