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.

    + + +
    + + + + + + + + + + +