diff --git a/css/_import-image.scss b/css/_import-image.scss new file mode 100644 index 0000000..e34ed29 --- /dev/null +++ b/css/_import-image.scss @@ -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; + } +} \ 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/ColorModule.js b/js/ColorModule.js index 9b90237..8ee599b 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,45 @@ 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]); + } + } + + /** + * 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 +447,7 @@ const ColorModule = (() => { } } - //create palette from colors array - createColorPalette(colorPaletteArray); - - console.log("Done 2"); + return colorPaletteArray; } function updateCurrentColor(color, refLayer) { @@ -446,6 +472,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 fe5e7d8..a974b92 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,121 @@ 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. + */ + 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 { 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..9056d99 100644 --- a/js/Util.js +++ b/js/Util.js @@ -130,4 +130,77 @@ 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(); + } + + 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/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..c85cba2 --- /dev/null +++ b/views/popups/import-image.hbs @@ -0,0 +1,44 @@ +
    + + +

    Import Image

    + +

    Imports image into the current selected layer.

    + +
    + + +
    + + + +

    Canvas Location

    + +

    If matching to image size canvas location is ignored.

    + + +
    + + + + + + + + + + + +
    + + +
    \ No newline at end of file