Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{
"name": "Node.js",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/javascript-node:0-18",

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000],

// Use 'postCreateCommand' to run commands after the container is created.
//write post create command npm install & npm run hot
"postCreateCommand": "npm install"

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This is a browser based software for creating pixel art

The tool can be viewed online here: https://lospec.com/pixel-editor

## How to contribute
## Before contributing

Before starting to work, please open an issue for discussion so that we can organize the work without creating too many conflicts. If your contribution is going to fix a bug, please
make a fork and use the bug-fixes branch. If you want to work on a new feature, please use the new-feature branch instead.
Expand Down Expand Up @@ -44,13 +44,20 @@ Suggestions / Planned features:
## How to Contribute

### Requirements

You must have node.js and git installed.
No requirements if you want to use Github's Codespaces. If you prefer to setup your environment on desktop, you'll need to have node.js and git installed.

You also need `npm` in version 7 (because of 2nd version of lockfile which was introduced there) which comes with Node.js 15 or newer. To simplify installation of proper versions you can make use of [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) and run `nvm install` – it will activate proper Node.js version in your current command prompt session.

### Contribution Workflow

#### Github Codespaces
1. Click **Fork** above. It will automatically create a copy of this repository and add it to your account.
2. At the top of this page, select the branch you want to work on.
3. Click on "Code". Select the "Codespaces" submenu and click on "Create codespace on **branch name**".
4. Run `npm install`. Then run `npm run hot`: it will open a popup containing the editor, so make sure to disable your adblock if you're using one.

#### Desktop environment

1. Click **Fork** above. It will automatically create a copy of this repository and add it to your account.
2. Clone the repository to your computer.
3. Open the folder in command prompt and run **`npm install`**
Expand Down
19 changes: 11 additions & 8 deletions js/ColorModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,15 @@ const ColorModule = (() => {
*/
function addColorButtonEvent() {
//generate random color
const newColor = new Color("hsv", Math.floor(Math.random()*360), Math.floor(Math.random()*100), Math.floor(Math.random()*100)).hex;

//remove current color selection
document.querySelector('#colors-menu li.selected')?.classList.remove('selected');
const newColor = new Color("hsv", Math.floor(Math.random()*360), Math.floor(Math.random()*100), Math.floor(Math.random()*100)).hex;

//add new color and make it selected
let addedColor = addColor(newColor);
addedColor.classList.add('selected');

//remove previous color selection
document.querySelector('#colors-menu li.selected')?.classList.remove('selected');

addedColor.style.width = squareSize + "px";
addedColor.style.height = squareSize + "px";
updateCurrentColor(newColor);
Expand Down Expand Up @@ -388,10 +389,12 @@ const ColorModule = (() => {
*
* @param {*} paletteColors The colours of the palette
*/
function createColorPalette(paletteColors) {
function createColorPalette(paletteColors, clearCurrent=true) {
//remove current palette
while (colorsMenu.childElementCount > 1)
colorsMenu.children[0].remove();
if (clearCurrent) {
while (colorsMenu.childElementCount > 1)
colorsMenu.children[0].remove();
}

var lightestColor = new Color("hex", '#000000');
var darkestColor = new Color("hex", '#ffffff');
Expand Down Expand Up @@ -527,4 +530,4 @@ const ColorModule = (() => {
updateCurrentColor,
getSelectedColor,
}
})();
})();
173 changes: 130 additions & 43 deletions js/FileManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,57 +292,143 @@ const FileManager = (() => {

return JSON.stringify(dictionary);
}

let fromMenu = false;

function openImportPaletteWindow() {
fromMenu = true;
document.getElementById('load-palette-browse-holder').click();
}

function loadPalette() {
if (browsePaletteHolder.files && browsePaletteHolder.files[0]) {
//make sure file is allowed filetype
var fileContentType = browsePaletteHolder.files[0].type;
if (fileContentType == 'image/png' || fileContentType == 'image/gif') {

//load file
var fileReader = new FileReader();
fileReader.onload = function(e) {
var img = new Image();
img.onload = function() {

//draw image onto the temporary canvas
var loadPaletteCanvas = document.getElementById('load-palette-canvas-holder');
var loadPaletteContext = loadPaletteCanvas.getContext('2d');

loadPaletteCanvas.width = img.width;
loadPaletteCanvas.height = img.height;

loadPaletteContext.drawImage(img, 0, 0);

//create array to hold found colors
var colorPalette = [];
var imagePixelData = loadPaletteContext.getImageData(0,0,this.width, this.height).data;

//loop through pixels looking for colors to add to palette
for (var i = 0; i < imagePixelData.length; i += 4) {
const newColor = {r:imagePixelData[i],g:imagePixelData[i + 1],b:imagePixelData[i + 2]};
var color = '#' + Color.rgbToHex(newColor);
if (colorPalette.indexOf(color) == -1) {
colorPalette.push(color);
}
}

//add to palettes so that it can be loaded when they click okay
palettes['Loaded palette'] = {};
palettes['Loaded palette'].colors = colorPalette;
Util.setText('palette-button', 'Loaded palette');
Util.setText('palette-button-splash', 'Loaded palette');
Util.toggle('palette-menu-splash');
};
img.src = e.target.result;
};
let file = browsePaletteHolder.files[0];
var fileContentType =
file.type
|| file.name.split('.').slice(-1)[0];

var fileReader = new FileReader();

// dispatch on file type
switch (fileContentType) {
case 'image/png':
case 'image/gif':
fileReader.onload = loadPaletteFromImage;
fileReader.readAsDataURL(browsePaletteHolder.files[0]);
break;
case 'gpl':
fileReader.onload = loadPaletteFromGimp;
fileReader.readAsText(browsePaletteHolder.files[0]);
break;
case 'hex':
fileReader.onload = loadPaletteFromHex;
fileReader.readAsText(browsePaletteHolder.files[0]);
break;
default:
alert('Only PNG, GIF, .hex and .gpl files are supported at this time.');
}
else alert('Only PNG and GIF files are supported at this time.');
}

browsePaletteHolder.value = null;
}

function addPalette(colors) {
if (fromMenu) {
ColorModule.createColorPalette(colors, clearCurrent=false);
} else {
// From splash screen
// add to palettes so that it can be loaded when they click okay
palettes['Loaded palette'] = {};
palettes['Loaded palette'].colors = colors;
Util.setText('palette-button', 'Loaded palette');
Util.setText('palette-button-splash', 'Loaded palette');
Util.toggle('palette-menu-splash');
}
}

function loadPaletteFromImage(e) {
var img = new Image();
img.onload = function() {
//draw image onto the temporary canvas
var loadPaletteCanvas = document.getElementById('load-palette-canvas-holder');
var loadPaletteContext = loadPaletteCanvas.getContext('2d');

loadPaletteCanvas.width = img.width;
loadPaletteCanvas.height = img.height;

loadPaletteContext.drawImage(img, 0, 0);

//create array to hold found colors
var colorPalette = [];
var imagePixelData = loadPaletteContext.getImageData(0,0,this.width, this.height).data;

//loop through pixels looking for colors to add to palette
for (var i = 0; i < imagePixelData.length; i += 4) {
const newColor = {r:imagePixelData[i],g:imagePixelData[i + 1],b:imagePixelData[i + 2]};
var color = '#' + Color.rgbToHex(newColor);
if (colorPalette.indexOf(color) == -1) {
colorPalette.push(color);
}
}

addPalette(colorPalette);
};
img.src = e.target.result;
}

function loadPaletteFromGimp(e) {
let content = e.target.result;
let colorPalette = content.split(/\r?\n/)
// Skip header line
.slice(1)
.map((line) => line.trim())
.filter((line) => line != "")
// discard comment lines
.filter((line) => !line.startsWith('#'))
// discard meta data lines
.filter((line) => !line.includes(':'))
.map((line) => {
let components = line.split(/\s+/);

if (components.length < 3) {
alert(`Invalid color specification ${line}.`);
return "#000000"
}

let [r, g, b, ...rest] = components;
let color = {
r: parseInt(r),
g: parseInt(g),
b: parseInt(b),
};

if (isNaN(color.r) || isNaN(color.g) || isNaN(color.b)) {
alert(`Invalid color specification ${line}.`);
return "#000000"
}

return '#' + Color.rgbToHex(color);
});
addPalette(colorPalette);
}

function loadPaletteFromHex(e) {
let content = e.target.result;
let colorPalette = content.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line != "")
// discard comment lines
.filter((line) => !line.startsWith('#'))
.map((line) => {
if (line.match(/[0-9A-Fa-f]{6}/)) {
return '#' + line;
}
alert(`Invalid hex color ${line}.`);
return '#000000';
});
addPalette(colorPalette);
}

currentImportPivotElement = undefined;
currentImportPivotPosition = 'middle';
isImportWindowInitialized = false;
Expand Down Expand Up @@ -513,6 +599,7 @@ const FileManager = (() => {
openPixelExportWindow,
openSaveProjectWindow,
openImportImageWindow,
openImportPaletteWindow,
open
}

Expand All @@ -526,4 +613,4 @@ const FileManager = (() => {
}
})
return ret;
})();
})();
10 changes: 9 additions & 1 deletion js/PaletteBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,15 @@ const PaletteBlock = (() => {
startIndex = endIndex;
endIndex = tmp;
}

// If there is only 1 color in palette and user wants to remove it, do nothing
if(coloursList.childElementCount == 1) {
return;
}

// If user wants to remove all colors of palette, remove all colors except last one
if(coloursList.childElementCount == endIndex-startIndex+1) {
endIndex--;
}
for (let i=startIndex; i<=endIndex; i++) {
coloursList.removeChild(coloursList.children[startIndex]);
}
Expand Down
7 changes: 4 additions & 3 deletions js/ToolManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ const ToolManager = (() => {
if (!EditorState.documentCreated || Dialogue.isOpen())
return;

const isHoveringMenuElement = !!mouseEvent.path.find(n=>n.id && n.id.includes("-menu"));
if(isHoveringMenuElement)return;
// Hovering a menu element
const path = mouseEvent.composedPath && mouseEvent.composedPath();
if (path && !!path.find(n=>n.id && n.id.includes("-menu"))) return;

let mousePos = Input.getCursorPosition(mouseEvent);
tools["zoom"].onMouseWheel(mousePos, mouseEvent.deltaY < 0 ? 'in' : 'out');
Expand Down Expand Up @@ -164,4 +165,4 @@ const ToolManager = (() => {
return {
currentTool
}
})();
})();
5 changes: 4 additions & 1 deletion js/TopMenuModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const TopMenuModule = (() => {
case 'Import':
Events.on('click', currSubmenuButton, FileManager.openImportImageWindow);
break;
case 'Load palette':
Events.on('click', currSubmenuButton, FileManager.openImportPaletteWindow);
break;
case 'Export':
Events.on('click', currSubmenuButton, FileManager.openPixelExportWindow);
break;
Expand Down Expand Up @@ -139,4 +142,4 @@ const TopMenuModule = (() => {
addInfoElement,
resetInfos
}
})();
})();
Loading