Skip to content

Commit e51f510

Browse files
Merge branch 'master' into pxlvxl-master
2 parents 9e250c0 + 9eba2f6 commit e51f510

17 files changed

+993
-19
lines changed

Dockerfile

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM node:16-alpine3.15
2+
3+
RUN apk add git
4+
5+
# Docker has --chown flag for COPY, but it does not expand ENV so we fallback to:
6+
# COPY src src
7+
# RUN sudo chown -R $USER:$USER $HOME
8+
9+
WORKDIR /home/node/app
10+
COPY ./ /home/node/app
11+
RUN chown -R node:node /home/node/app
12+
13+
USER node
14+
RUN npm install
15+
16+
CMD ["npm", "run", "hot"]

LICENCE

+674
Large diffs are not rendered by default.

README.md

-4
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,3 @@ If you have any trouble, see this page: https://help.github.com/en/articles/crea
6969
Some feature might be hidden by default. Functions to enable/disable them are available inside global `featureToggles` and operate on a `window.localStorage`.
7070

7171
For example use `featureToggles.enableEllipseTool()` to make ellipse tool button visible. Then `featureToggles.disableEllipseTool()` to hide it.
72-
73-
## License
74-
75-
This software may not be resold, redistributed, rehosted or otherwise conveyed to a third party.

build.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function copy_logs() {
3434
}
3535

3636
function render_js(){
37-
gulp.src('./js/*.js')
37+
gulp.src('./js/pixel-editor.js')
3838
.pipe(include({includePaths: [
3939
'js',
4040
'!js/_*.js',

css/_import-image.scss

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#import-image {
2+
ul {
3+
list-style-type: none;
4+
margin: 15px 0;
5+
padding: 0;
6+
}
7+
8+
.import-image-file {
9+
button {
10+
margin: 0;
11+
}
12+
}
13+
14+
.import-image-location-pivot {
15+
display: flex;
16+
align-items: center;
17+
}
18+
}

css/pixel-editor.scss

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@
1717
@import 'palette-editor';
1818
@import 'splash-page';
1919
@import "export";
20-
@import "save-project";
20+
@import "save-project";
21+
@import "import-image";

js/ColorModule.js

+34-4
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ const ColorModule = (() => {
236236
//add # at beginning if not present
237237
if (newColor.charAt(0) != '#')
238238
newColor = '#' + newColor;
239+
239240
currentPalette.push(newColor);
240241
//create list item
241242
const listItem = document.createElement('li');
@@ -433,25 +434,53 @@ const ColorModule = (() => {
433434
*
434435
*/
435436
function createPaletteFromLayers() {
437+
//create array out of colors object
438+
let colorPaletteArray = getLayerColors();
439+
440+
//create palette from colors array
441+
createColorPalette(colorPaletteArray);
442+
}
443+
444+
/**
445+
* Scan the layers for any colors that are not currently in the palette. If any colors
446+
* are found they should be added as new colors for the palette.
447+
*/
448+
function updatePaletteFromLayers() {
449+
let layersPaletteArray = getLayerColors();
450+
451+
for (let i = 0; i < layersPaletteArray.length; i++) {
452+
if (currentPalette.indexOf(layersPaletteArray[i]) !== -1) {
453+
continue;
454+
}
455+
456+
addColor(layersPaletteArray[i]);
457+
}
458+
}
459+
460+
/**
461+
* Iterates each layer and grab each unique color.
462+
* @returns Array of colors used within the current layers.
463+
*/
464+
function getLayerColors() {
436465
let colors = {};
437466
let nColors = 0;
438467
//create array out of colors object
439468
let colorPaletteArray = [];
440469

441-
for (let i=0; i<currFile.layers.length; i++) {
470+
for (let i = 0; i < currFile.layers.length; i++) {
442471
if (currFile.layers[i].hasCanvas()) {
443-
let imageData = currFile.layers[i].context.getImageData(
444-
0, 0, currFile.canvasSize[0], currFile.canvasSize[1]).data;
472+
let imageData = currFile.layers[i].context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]).data;
445473
let dataLength = imageData.length;
446474

447-
for (let j=0; j<dataLength; j += 4) {
475+
for (let j=0; j < dataLength; j += 4) {
448476
if (!Util.isPixelEmpty(imageData[j])) {
449477
let color = imageData[j]+','+imageData[j + 1]+','+imageData[j + 2];
450478

451479
if (!colors[color]) {
452480
colorPaletteArray.push('#' + new Color("rgb", imageData[j], imageData[j + 1], imageData[j + 2]).hex);
453481
colors[color] = new Color("rgb", imageData[j], imageData[j + 1], imageData[j + 2]).rgb;
454482
nColors++;
483+
455484
//don't allow more than 256 colors to be added
456485
if (nColors >= Settings.getCurrSettings().maxColorsOnImportedImage) {
457486
alert('The image loaded seems to have more than '+Settings.getCurrSettings().maxColorsOnImportedImage+' colors.');
@@ -495,6 +524,7 @@ const ColorModule = (() => {
495524
resetPalette,
496525
createColorPalette,
497526
createPaletteFromLayers,
527+
updatePaletteFromLayers,
498528
updateCurrentColor,
499529
getSelectedColor,
500530
}

js/FileManager.js

+118-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ const FileManager = (() => {
44
// Binding the browse holder change event to file loading
55
const browseHolder = document.getElementById('open-image-browse-holder');
66
const browsePaletteHolder = document.getElementById('load-palette-browse-holder');
7+
const importImageHolder = document.getElementById('import-image-browse-holder');
78

89
Events.on('change', browseHolder, loadFile);
910
Events.on('change', browsePaletteHolder, loadPalette);
11+
Events.on('change', importImageHolder, loadImage);
12+
Events.on('click', 'export-confirm', exportProject);
1013
Events.on("click", "save-project-confirm", saveProject);
1114

1215
function openSaveProjectWindow() {
@@ -27,17 +30,17 @@ const FileManager = (() => {
2730

2831
function openPixelExportWindow() {
2932
let selectedPalette = Util.getText('palette-button');
33+
3034

3135
if (selectedPalette != 'Choose a palette...'){
3236
var paletteAbbreviation = palettes[selectedPalette].name;
33-
var fileName = 'pixel-'+paletteAbbreviation+'-'+canvasSize[0]+'x'+canvasSize[1]+'.png';
37+
var fileName = 'pixel-'+paletteAbbreviation+'-'+currFile.canvasSize[0]+'x'+currFile.canvasSize[1]+'.png';
3438
} else {
3539
var fileName = 'pixel-'+currFile.canvasSize[0]+'x'+currFile.canvasSize[1]+'.png';
3640
selectedPalette = 'none';
3741
}
3842

3943
Util.setValue('export-file-name', fileName);
40-
Events.on("click", "export-confirm", exportProject);
4144
Dialogue.showDialogue('export', false);
4245
}
4346

@@ -339,6 +342,116 @@ const FileManager = (() => {
339342

340343
browsePaletteHolder.value = null;
341344
}
345+
346+
currentImportPivotElement = undefined;
347+
currentImportPivotPosition = 'middle';
348+
isImportWindowInitialized = false;
349+
350+
/**
351+
* Displays the import image window to allow for configurations
352+
* to be made be the image is imported.
353+
*/
354+
function openImportImageWindow() {
355+
// Reset window values.
356+
importImageHolder.value = null;
357+
358+
document.getElementById('import-image-match-size').checked = false;
359+
document.getElementById('import-image-update-palette').checked = false;
360+
document.getElementById('import-image-name').innerText = "";
361+
362+
// Workaround to prevent events from firing twice for the import window.
363+
if (!this.isImportWindowInitialized) {
364+
// Getting the pivot buttons and setting the default pivot selection.
365+
let pivotButtons = document.getElementsByClassName("pivot-button");
366+
this.currentImportPivotElement = document.querySelector('.import-image-location-pivot .rc-selected-pivot');
367+
368+
// Add event handlers for each pivot.
369+
for (let i=0; i < pivotButtons.length; i++) {
370+
Events.on("click", pivotButtons[i], onImportPivotChanged.bind(this));
371+
}
372+
373+
Events.on("click", "select-image", () => document.getElementById('import-image-browse-holder')?.click());
374+
Events.on("click", "import-image-confirm", importImage);
375+
376+
this.isImportWindowInitialized = true;
377+
}
378+
379+
Dialogue.showDialogue('import-image', false);
380+
}
381+
382+
/**
383+
* Loads the image and draws it to the current canvas layer. Called when
384+
* the import image window is finalized.
385+
*/
386+
function importImage() {
387+
if (!importImageHolder.files || importImageHolder.files.length === 0) {
388+
alert('Please select a file before attempting to import.')
389+
return;
390+
}
391+
392+
var fileReader = new FileReader();
393+
394+
// Once the image has been loaded draw the image to the current layer at the top right.
395+
fileReader.onload = (e) => {
396+
var img = new Image();
397+
398+
img.onload = () => {
399+
let shouldResizeCanvas = document.getElementById('import-image-match-size').checked;
400+
let shouldImportColors = document.getElementById('import-image-update-palette').checked;
401+
402+
// Resize the canvas to the image size if the flag was set to true.
403+
if (shouldResizeCanvas) {
404+
currFile.resizeCanvas(null, { x: img.width, y: img.height }, null, false);
405+
}
406+
407+
// Calculate pivot offset and draw the imported image. Ensure the pivot position accounts for the imported images dimensions.
408+
let offset = Util.getPivotPosition(this.currentImportPivotPosition, currFile.canvasSize[0], currFile.canvasSize[1], img.width, img.height);
409+
currFile.currentLayer.context.drawImage(img, offset.x, offset.y);
410+
411+
if (shouldImportColors) {
412+
ColorModule.updatePaletteFromLayers();
413+
}
414+
415+
Dialogue.closeDialogue();
416+
};
417+
img.src = e.target.result;
418+
};
419+
420+
fileReader.readAsDataURL(importImageHolder.files[0]);
421+
}
422+
423+
/**
424+
* Called when the import image holder file input fires an onchange event.
425+
*/
426+
function loadImage() {
427+
if (importImageHolder.files && importImageHolder.files[0]) {
428+
let fileName = document.getElementById("import-image-browse-holder").value;
429+
let extension = Util.getFileExtension(fileName);
430+
431+
// Display the file name in the window.
432+
document.getElementById('import-image-name').innerText = importImageHolder.files[0].name;
433+
434+
// Checking if the extension is supported
435+
if (extension !== 'png') {
436+
alert('Only PNG files are currently allowed to be imported at this time.')
437+
importImageHolder.value = null;
438+
}
439+
}
440+
}
441+
442+
/**
443+
* Called when the selected pivot for the import image is changed.
444+
* @param {*} event The event for the selected pivot.
445+
*/
446+
function onImportPivotChanged(event) {
447+
this.currentImportPivotPosition = event.target.getAttribute("value");
448+
449+
// Setting the selected class
450+
this.currentImportPivotElement.classList.remove("rc-selected-pivot");
451+
this.currentImportPivotElement = event.target;
452+
this.currentImportPivotElement.classList.add("rc-selected-pivot");
453+
}
454+
342455
function upgradeLPE(dictionary) {
343456
console.log('dictionary === ',dictionary);
344457
if(dictionary.color0 && !dictionary.colors) {
@@ -399,8 +512,10 @@ const FileManager = (() => {
399512
exportProject,
400513
openPixelExportWindow,
401514
openSaveProjectWindow,
515+
openImportImageWindow,
402516
open
403-
};
517+
}
518+
404519
Object.keys(ret).forEach(k=>{
405520
if(typeof ret[k] === "function"){
406521
const orig = ret[k];

js/TopMenuModule.js

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const TopMenuModule = (() => {
4343
case 'Open':
4444
Events.on('click', currSubmenuButton, FileManager.open);
4545
break;
46+
case 'Import':
47+
Events.on('click', currSubmenuButton, FileManager.openImportImageWindow);
48+
break;
4649
case 'Export':
4750
Events.on('click', currSubmenuButton, FileManager.openPixelExportWindow);
4851
break;

js/Util.js

+73
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,77 @@ class Util {
130130
static cursorInCanvas(canvasSize, mousePos) {
131131
return mousePos[0] >= 0 && mousePos[1] >= 0 && canvasSize[0] > mousePos[0] && canvasSize[1] > mousePos[1];
132132
}
133+
134+
static getFileExtension(fileName) {
135+
return (fileName.substring(fileName.lastIndexOf('.')+1, fileName.length) || fileName).toLowerCase();
136+
}
137+
138+
static getCanvasBorders() {
139+
140+
}
141+
142+
/**
143+
* Determines the x and y offset for drawing images at a specific point 'topleft', 'middle', etc.
144+
*
145+
* @param {*} pivot A string representing the position of the pivot 'topleft', 'middle', etc.
146+
* @param {*} width Width of the bounds often represents the canvas width.
147+
* @param {*} height Height of the bounds often represents the canvas height.
148+
* @param {*} imageWidth Substracts the offset from calculated x position of the pivot. Defaults to 0.
149+
* @param {*} imageHeight Subtracts the offset from the calculated y position of the pivot. Defaults to 0.
150+
*
151+
* @returns Object containing the x and y offset for the pivot.
152+
*/
153+
static getPivotPosition(pivot, width, height, imageWidth = 0, imageHeight = 0) {
154+
let position = {
155+
x: 0,
156+
y: 0
157+
};
158+
159+
let centerX = width / 2;
160+
let centerY = height / 2;
161+
162+
switch (pivot)
163+
{
164+
case 'topleft':
165+
position.x = 0;
166+
position.y = 0;
167+
break;
168+
case 'top':
169+
position.x = centerX - (imageWidth / 2);
170+
position.y = 0;
171+
break;
172+
case 'topright':
173+
position.x = width - imageWidth;
174+
position.y = 0;
175+
break;
176+
case 'left':
177+
position.x = 0;
178+
position.y = centerY - (imageHeight / 2);
179+
break;
180+
case 'middle':
181+
position.x = centerX - (imageWidth / 2);
182+
position.y = centerY - (imageHeight / 2);
183+
break;
184+
case 'right':
185+
position.x = width - imageWidth;
186+
position.y = centerY - (imageHeight / 2);
187+
break;
188+
case 'bottomleft':
189+
position.x = 0;
190+
position.y = height - imageHeight;
191+
break;
192+
case 'bottom':
193+
position.x = centerX - (imageWidth / 2);
194+
position.y = height - imageHeight;
195+
break;
196+
case 'bottomright':
197+
position.x = width - imageWidth
198+
position.y = height - imageHeight;
199+
break;
200+
default:
201+
break;
202+
}
203+
204+
return position;
205+
}
133206
}

package-lock.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)