Merge branch 'master' into hot-reload

This commit is contained in:
Julian Webb 2021-04-26 11:04:47 -07:00 committed by GitHub
commit ea357dab04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 6903 additions and 473 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
_ext
/_ext/*
!/_ext/svg/
routes
build
node_modules

View File

@ -12,16 +12,14 @@ The next version is mostly focused on adding missing essential features and port
Suggestions / Planned features:
- Documentation
- Possibility to hide and resize menus (layers, palette)
- Line tool
- Tiled mode
- Load palette from LPE file
- Move colours in (advanced) palette editor
- Symmetry options
- Custom color picker
- custom code without dependencies
- more features such as sliders / color modes
- Mobile
- Touch equivalent for mouse clicks
- Hide or scale ui
@ -32,15 +30,13 @@ Suggestions / Planned features:
- Possibly add collaborate function
- Polish:
- ctrl + a to select everything / selection -> all, same for deselection
- Show colors which would need to be added to palette
- ctrl a to select everything / selection -> all, same for deselection
- Warning windows for wrong inputs
- Palette option remove unused colors
- Move selection with arrows
- Update pivot buttons when resizing canvas
- Update borders by dragging the canvas' edges with the mouse when resizing canvas
- Move the canvases so they're centered after resizing the canvas (maybe a .center() method in layer class)
- Trim canvas
- Scale selection
## How to Contribute
@ -48,9 +44,9 @@ Requirements: you must have node.js and git installed.
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**
3. Open the folder in command prompt and run **`npm install`**
4. Make any changes you would like to suggest.
5. In command prompt run **node build.js** which will compile it to the */build* folder, where you can make sure it works
5. In command prompt run **`npm run build`** which will compile it to the `/build` folder, where you can make sure it works the easiest way to do so is to run **`npm run serve`**. You can also do both at once by running `npm test`.
6. Add, Commit and Push your changes to your fork.
7. On the github page for your fork, click **New Pull Request** above the file list.
8. Change the **head repository** dropdown to your fork.

41
_ext/svg/line.svg Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512.001 512.001" style="enable-background:new 0 0 512.001 512.001;" xml:space="preserve">
<g>
<g>
<path d="M506.143,5.859c-7.811-7.811-20.475-7.811-28.285,0l-472,472c-7.811,7.811-7.811,20.474,0,28.284
c3.905,3.906,9.024,5.858,14.142,5.858s10.237-1.953,14.143-5.858l472-472C513.954,26.333,513.954,13.67,506.143,5.859z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 799 B

View File

@ -15,7 +15,7 @@ console.log('Building Pixel Editor');
function copy_images(){
gulp.src('./images/')
gulp.src('./images/*.png')
.pipe(gulp.dest(path.join(BUILDDIR, SLUG)));
}

View File

@ -125,7 +125,7 @@ body {
z-index: 1120;
list-style-type: none;
overflow-y:scroll;
overflow-x:hidden; // TODO: make the scroll bar a bit fancier
overflow-x:hidden;
#add-layer-button {
path {
fill: $baseicon;
@ -178,7 +178,7 @@ body {
.layers-menu-entry {
cursor: pointer;
margin-top: 2px;
margin-bottom: 2px;
font-size: 1em;
color: $basetext;
background-color: lighten($basecolor, 4%);
@ -301,7 +301,7 @@ svg {
}
#pixel-canvas {
z-index: 2;
z-index: 3;
background: transparent;
}
@ -311,7 +311,7 @@ svg {
}
#tmp-canvas {
z-index: 4;
z-index: 5;
background: transparent;
}
@ -687,14 +687,16 @@ svg {
#tools-menu li button#pencil-bigger-button,
#tools-menu li button#zoom-in-button,
#tools-menu li button#eraser-bigger-button,
#tools-menu li button#rectangle-bigger-button {
#tools-menu li button#rectangle-bigger-button,
#tools-menu li button#line-bigger-button {
left: 0;
}
#tools-menu li button#pencil-smaller-button,
#tools-menu li button#zoom-out-button,
#tools-menu li button#eraser-smaller-button,
#tools-menu li button#rectangle-smaller-button {
#tools-menu li button#rectangle-smaller-button,
#tools-menu li button#line-smaller-button {
right: 0;
}
@ -705,7 +707,9 @@ svg {
#tools-menu li.selected button#eraser-bigger-button,
#tools-menu li.selected button#eraser-smaller-button,
#tools-menu li.selected button#rectangle-bigger-button,
#tools-menu li.selected button#rectangle-smaller-button {
#tools-menu li.selected button#rectangle-smaller-button,
#tools-menu li.selected button#line-bigger-button,
#tools-menu li.selected button#line-smaller-button {
display: block;
}
@ -775,8 +779,32 @@ svg {
}
}
div.update {
input {
background: $indent;
border: none;
border-radius: 4px;
color: $indenttext;
padding: 10px 20px;
margin: 0;
width: 60px;
text-align: center;
}
}
/*
input {
background: $indent;
border: none;
border-radius: 4px;
color: $indenttext;
padding: 10px 20px;
margin: 0;
width: 60px;
text-align: center;
}
*/
button.default {
float: right;
background: $basehover;
border: none;
border-radius: 4px;
@ -789,17 +817,6 @@ svg {
}
}
input {
background: $indent;
border: none;
border-radius: 4px;
color: $indenttext;
padding: 10px 20px;
margin: 0;
width: 60px;
text-align: center;
}
.dropdown-button {
background: $basehover url('/pixel-editor/dropdown-arrow.png') right center no-repeat;
border: none;
@ -1038,9 +1055,9 @@ svg {
input[type=number] {
position:relative;
margin-left:10px;
height:15px;
width:40px;
padding:8px;
height:15px !important;
width:40px !important;
padding:8px !important;
}
input[type=number]::-webkit-outer-spin-button,
@ -1212,4 +1229,544 @@ svg {
background: $baseselected;
}
}
}
/***********************COLOUR PICKER*****************************/
#colour-picker {
background-color:$basecolor;
width:250px;
height:350px;
position:absolute;
display:inline-block;
input[type=text] {
background-color:$basetext;
color:$basecolor;
box-shadow:none;
border:none;
}
input[type=range] {
width: 100%;
margin: 2.2px 0;
background-color: transparent;
-webkit-appearance: none;
}
input[type=range]::-webkit-slider-runnable-track {
background: #484d4d;
border: 0;
width: 100%;
height: 25.6px;
cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
margin-top: -2.2px;
width: 18px;
height: 30px;
background: $basetext;
border: 0;
cursor: pointer;
-webkit-appearance: none;
}
input[type=range]::-moz-range-track {
background: #484d4d;
border: 0;
width: 100%;
height: 25.6px;
cursor: pointer;
}
input[type=range]::-moz-range-thumb {
width: 18px;
height: 30px;
background: $basetextweak;
border: 0;
cursor: pointer;
}
/*TODO: Use one of the selectors from https://stackoverflow.com/a/20541859/7077589 and figure out
how to remove the vertical space around the range input in IE*/
@supports (-ms-ime-align:auto) {
/* Pre-Chromium Edge only styles, selector taken from hhttps://stackoverflow.com/a/32202953/7077589 */
input[type=range].slider {
margin: 0;
/*Edge starts the margin from the thumb, not the track as other browsers do*/
}
}
}
#cp-modes {
margin: 0 0 0 0;
font-size:0;
height:40px;
float:left;
display:flex;
font-family: 'Roboto', sans-serif;
background-color:$basetextweak;
width:100%;
button {
font-size:14px;
left:0;
right:0;
margin:0 0 0 0;
border: none;
border-radius: 0;
height:100%;
width:37x;
background-color:$basehover;
color:$basetext;
cursor:pointer;
}
button:hover {
background-color:$baseicon;
color:$basetext;
}
button.cp-selected-mode {
background-color:$baseicon;
color:$basetext;
}
input {
width:50px;
right:0;
position:absolute;
}
div {
background-color:yellow;
width:100%;
height:100%;
z-index:2;
position:relative;
}
}
#sliders-container {
padding:10px;
}
.cp-slider-entry {
width:100%;
height:30px;
display:flex;
align-items:center;
margin-top:2px;
position:relative;
label {
width: 20px;
font-size:15px;
font-style: bold;
}
input[type=text] {
text-align:center;
width: 30px;
overflow:visible;
margin-left:4px;
}
}
.colour-picker-slider {
width:90%;
}
#cp-minipicker {
width:100%;
height:100px;
position:relative;
margin: 0 0 0 0;
z-index: inherit 2000;
input {
width:100%;
margin: none;
padding: none;
}
.cp-colours-previews {
width:100%;
position:relative;
}
.cp-colour-preview {
width:100%;
position:relative;
background-color:blue;
color:$basecolor;
float:left;
height:30px;
justify-content: center;
display:flex;
align-items: center;
font-size:12px;
}
#cp-colour-picking-modes {
width:100%;
position:relative;
}
button {
font-size:14px;
left:0;
right:0;
margin:0 0 0 0;
border: none;
border-radius: 0;
height:30px;
width:16.66666%;
float:left;
overflow:hidden;
background-color:$basehover;
color:$basetext;
cursor:pointer;
}
button:hover {
background-color:$baseicon;
color:$basetext;
}
button.cp-selected-mode {
background-color:$baseicon;
color:$basetext;
}
}
#cp-canvas-container {
width:100%;
height:100%;
position:relative;
}
#cp-spectrum {
width:100%;
height:100px;
position:absolute;
background-color: transparent;
}
.cp-picker-icon{
width:16px;
height:16px;
border-radius:100%;
position:absolute;
background-color:white;
z-index:2;
border:2px solid black;
}
/***************PALETTE BLOCK****************/
div#palette-block {
z-index:1000;
position:relative;
resize: horizontal;
margin: 0 0 0 0;
width:600px;
height:400px;
}
div#palette-container {
display:inline-block;
background-color: #232125;
position:absolute;
scrollbar-color: #332f35 #232125;
scroll-behavior: smooth;
left:300px;
width:300px;
height:320px;
overflow-y: scroll;
&::-webkit-scrollbar {
background: #232125;
width: 0.5em;
}
&::-webkit-scrollbar-track {
margin-top: -0.125em;
width: 0.5em;
}
&::-webkit-scrollbar-thumb {
background: #332f35;
border-radius: 0.25em;
border: solid 0.125em #232125; //same color as scrollbar back to fake padding
}
&::-webkit-scrollbar-corner {
background: #232125;
}
}
ul#palette-list {
list-style:none;
margin: 0 0 0 0;
padding: 0 0 0 0;
position:relative;
display:inline-block;
li {
float:left;
width:50px;
height:50px;
border:none;
min-width:20px;
min-height:20px;
max-width:75px;
max-height:75px;
}
}
div#pb-options {
position:relative;
left:280px;
height:30px;
width:300px;
top:300px;
button {
border-radius:none;
position:relative;
float:left;
width:50%;
height:100%;
text-align:center;
cursor: pointer;
font-size:16px;
background-color:$baseicon;
border:none;
}
button:hover {
color: $basehovertext;
background-color: $basehover;
}
}
/********FEATURES LOG*************/
#splash {
width:100% !important;
height:100% !important;
background-color: #232125 !important;
opacity: 1 !important;
overflow-y: scroll;
#splash-input {
width:74%;
height:100vh !important;
color:$baselink;
#splash-menu {
position:relative;
height:100%;
left:0;
top:0;
}
#editor-logo {
font-weight:bold;
text-transform:uppercase;
font-size:20px;
height:35vh;
width:100%;
position:relative;
background-image:url('https://cdn.discordapp.com/attachments/506277390050131978/795660870221955082/final.png');
background-size:cover;
background-position:center;
}
#black {
width:100%;
height:100%;
position:relative;
background-color:rgba(0,0,0,0.2);
}
#sp-coverdata {
padding:20px;
p, a {
font-size:15px;
position:absolute;
text-transform:none;
right:20px;
}
p {
top:0px;
}
a {
font-size:17px;
bottom:20px;
text-decoration:underline;
}
}
}
#sp-quickstart-container {
left:250px;
height:65vh;
width:650px;
display:inline;
float:right;
}
#sp-quickstart-title {
padding:20px;
font-size:27px;
text-transform: uppercase;
font-weight: bold;
}
.sp-template {
display:table;
vertical-align: middle;
text-transform: uppercase;
position:relative;
width:100px;
height:100px;
border-radius:5%;
margin-right:30px;
margin-top:30px;
background-color:$basecolor;
float:left;
font-size: 18px;
text-align:center;
font-weight: bold;
&:hover {
cursor:pointer;
background-color:$baseselected;
}
p {
span {
display:block;
font-size:14px !important;
margin: 0 0 0 0;
padding: 0 0 0 0;
}
display:table-cell;
width:100%;
height:100%;
vertical-align: middle;
}
}
#sp-newpixel {
display: inline-block;
width:220px;
height:64vh;
padding:20px;
position:relative;
background-color:$basecolor;
.sp-np-entry {
width:100%;
text-align:center;
}
input {
border:none;
background-color: #232125;
color:$basetext;
font-size:14px;
width:40px;
padding:7px;
text-align:center;
}
#create-button {
font-size:18px;
width:150px;
margin-top:40px;
font-weight: bold;
}
#sp-mode-palette {
text-align: center;
position: relative;
float:bottom;
font-size:16px;
font-weight: bold;
div.button-menu {
border:2px solid $basetextweak;
border-radius:5px;
position:relative;
display:inline-block;
padding: 0 0 0 0;
text-align:left;
div {
border:none;
padding:none;
margin:none;
background-color:transparent;
width:100px;
float:left;
text-align: center;
height:25px;
cursor:pointer;
z-index:1;
p {
z-index:0;
-ms-transform: translateY(-60%);
transform: translateY(-60%);
}
}
.bm-right {
border-left:3px solid $basetextweak;
}
.sp-interface-selected {
background-color: $basetextweak;
}
}
}
}
#splash-news {
position:relative;
right:20px;
height:95%;
top:2.5%;
background-color: #232125 !important;
}
#latest-update {
font-size:15px;
width:350px !important;
height:90%;
overflow-y: scroll;
line-height: 1.5;
position:absolute;
top:40px;
right:0px;
&::-webkit-scrollbar {
background: #232125;
width: 0.5em;
}
&::-webkit-scrollbar-track {
margin-top: -0.125em;
width: 0.5em;
}
&::-webkit-scrollbar-thumb {
background: #332f35;
border-radius: 0.25em;
border: solid 0.125em #232125; //same color as scrollbar back to fake padding
}
&::-webkit-scrollbar-corner {
background: #232125;
}
}
}

1
debug.log Normal file
View File

@ -0,0 +1 @@
[0114/110029.536:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)

BIN
images/sked.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -1,8 +1,10 @@
let currentPalette = [];
//adds the given color to the palette
//input hex color string
//returns list item element
/** Adds the given color to the palette
*
* @param {*} newColor the colour to add
* @return the list item containing the added colour
*/
function addColor (newColor) {
//add # at beginning if not present
if (newColor.charAt(0) != '#')
@ -17,6 +19,7 @@ function addColor (newColor) {
button.style.backgroundColor = newColor;
button.addEventListener('mouseup', clickedColor);
listItem.appendChild(button);
listItem.classList.add("draggable-colour")
//insert new listItem element at the end of the colors menu (right before add button)
colorsMenu.insertBefore(listItem, colorsMenu.children[colorsMenu.children.length-1]);
@ -35,11 +38,19 @@ function addColor (newColor) {
//hide edit button
button.parentElement.lastChild.classList.add('hidden');
//show jscolor picker
button.parentElement.firstChild.jscolor.show();
//show jscolor picker, if basic mode is enabled
if (pixelEditorMode == 'Basic')
button.parentElement.firstChild.jscolor.show();
else
showDialogue("palette-block", false);
});
console.log(currentPalette);
return listItem;
}
new Sortable(document.getElementById("colors-menu"), {
animation:100,
filter: ".noshrink",
draggable: ".draggable-colour",
onEnd: makeIsDraggingFalse
});

View File

@ -1,4 +1,4 @@
//add color button
// add-color-button management
on('click', 'add-color-button', function(){
if (!documentCreated) return;

185
js/_algorithms.js Normal file
View File

@ -0,0 +1,185 @@
// CONSTS
// Degrees to radiants
let degreesToRad = Math.PI / 180;
// I'm pretty sure that precision is necessary
let referenceWhite = {x: 95.05, y: 100, z: 108.89999999999999};
/**********************SECTION: COLOUR CONVERSIONS****************************** */
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* @param {number} h The hue
* @param {number} s The saturation
* @param {number} l The lightness
* @return {Array} The RGB representation
*/
function cpHslToRgb(h, s, l){
var r, g, b;
h /= 360;
s /= 100;
l /= 100;
if(s == 0){
r = g = b = l; // achromatic
}else{
var hue2rgb = function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function hsvToRgb(h, s, v) {
var r, g, b;
h /= 360;
s /= 100;
v /= 100;
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return [ r * 255, g * 255, b * 255 ];
}
function hslToHex(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
const toHex = x => {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function rgbToHsl(col) {
let r = col.r;
let g = col.g;
let b = col.b;
r /= 255, g /= 255, b /= 255;
let max = Math.max(r, g, b), min = Math.min(r, g, b);
let myH, myS, myL = (max + min) / 2;
if (max == min) {
myH = myS = 0; // achromatic
}
else {
let d = max - min;
myS = myL > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: myH = (g - b) / d + (g < b ? 6 : 0); break;
case g: myH = (b - r) / d + 2; break;
case b: myH = (r - g) / d + 4; break;
}
myH /= 6;
}
return {h: myH, s: myS, l: myL };
}
function rgbToHsv(col) {
let r = col.r;
let g = col.g;
let b = col.b;
r /= 255, g /= 255, b /= 255;
let max = Math.max(r, g, b), min = Math.min(r, g, b);
let myH, myS, myV = max;
let d = max - min;
myS = max == 0 ? 0 : d / max;
if (max == min) {
myH = 0; // achromatic
}
else {
switch (max) {
case r: myH = (g - b) / d + (g < b ? 6 : 0); break;
case g: myH = (b - r) / d + 2; break;
case b: myH = (r - g) / d + 4; break;
}
myH /= 6;
}
return {h: myH, s: myS, v: myV};
}
function RGBtoCIELAB(rgbColour) {
// Convert to XYZ first via matrix transformation
let x = 0.412453 * rgbColour.r + 0.357580 * rgbColour.g + 0.180423 * rgbColour.b;
let y = 0.212671 * rgbColour.r + 0.715160 * rgbColour.g + 0.072169 * rgbColour.b;
let z = 0.019334 * rgbColour.r + 0.119193 * rgbColour.g + 0.950227 * rgbColour.b;
let xFunc = CIELABconvF(x / referenceWhite.x);
let yFunc = CIELABconvF(y / referenceWhite.y);
let zFunc = CIELABconvF(z / referenceWhite.z);
let myL = 116 * yFunc - 16;
let myA = 500 * (xFunc - yFunc);
let myB = 200 * (yFunc - zFunc);
return {l: myL, a: myA, b: myB};
}
function CIELABconvF(value) {
if (value > Math.pow(6/29, 3)) {
return Math.cbrt(value);
}
return 1/3 * Math.pow(6/29, 2) * value + 4/29;
}

View File

@ -1,4 +1,9 @@
function changeZoom (layer, direction, cursorLocation) {
/** Changes the zoom level of the canvas
* @param {*} direction 'in' or 'out'
* @param {*} cursorLocation The position of the cursor when the user zoomed
*/
function changeZoom (direction, cursorLocation) {
// Computing current width and height
var oldWidth = canvasSize[0] * zoom;
var oldHeight = canvasSize[1] * zoom;
var newWidth, newHeight;
@ -11,7 +16,9 @@ function changeZoom (layer, direction, cursorLocation) {
newHeight = canvasSize[1] * zoom;
//adjust canvas position
layer.setCanvasOffset(layer.canvas.offsetLeft + (oldWidth - newWidth) *cursorLocation[0]/oldWidth, layer.canvas.offsetTop + (oldHeight - newHeight) *cursorLocation[1]/oldWidth);
layers[0].setCanvasOffset(
layers[0].canvas.offsetLeft + (oldWidth - newWidth) * cursorLocation[0]/oldWidth,
layers[0].canvas.offsetTop + (oldHeight - newHeight) * cursorLocation[1]/oldWidth);
}
//if you want to zoom in
else if (direction == 'in' && zoom + Math.ceil(zoom/10) < window.innerHeight/4){
@ -20,11 +27,13 @@ function changeZoom (layer, direction, cursorLocation) {
newHeight = canvasSize[1] * zoom;
//adjust canvas position
layer.setCanvasOffset(layer.canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), layer.canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight));
layers[0].setCanvasOffset(
layers[0].canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth),
layers[0].canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight));
}
//resize canvas
layer.resize();
layers[0].resize();
// adjust brush size
currentTool.updateCursor();

View File

@ -13,7 +13,9 @@ var currentColor = firstCheckerBoardColor;
// Saving number of squares filled until now
var nSquaresFilled = 0;
/** Fills the checkerboard canvas with squares with alternating colours
*
*/
function fillCheckerboard() {
// Getting checkerboard context
var context = checkerBoard.context;

View File

@ -17,7 +17,7 @@ function clickedColor (e){
e.target.parentElement.classList.add('selected');
} else if (e.which == 3) { //right clicked color
console.log('right clicked color button');
//console.log('right clicked color button');
//hide edit color button (to prevent it from showing)
e.target.parentElement.lastChild.classList.add('hidden');

View File

@ -23,7 +23,7 @@ on('input', 'jscolor-hex-input', function (e) {
//changes all of one color to another after being changed from color picker
function colorChanged(e) {
console.log('colorChanged() to ' + e.target.value);
//console.log('colorChanged() to ' + e.target.value);
//get colors
var newColor = hexToRgb(e.target.value);
var oldColor = e.target.oldColor;
@ -41,7 +41,7 @@ function colorChanged(e) {
//check if selected color already matches another color
colors = document.getElementsByClassName('color-button');
console.log(colors);
//console.log(colors);
var colorCheckingStyle = 'background: #bc60c4; color: white';
var newColorHex = e.target.value.toLowerCase();
@ -54,11 +54,11 @@ function colorChanged(e) {
//if generated color matches this color
if (newColorHex == colors[i].jscolor.toString()) {
console.log('%ccolor already exists'+(colors[i].parentElement.classList.contains('jscolor-active')?' (but is the current color)':''), colorCheckingStyle);
//console.log('%ccolor already exists'+(colors[i].parentElement.classList.contains('jscolor-active')?' (but is the current color)':''), colorCheckingStyle);
//if the color isnt the one that has the picker currently open
if (!colors[i].parentElement.classList.contains('jscolor-active')) {
console.log('%cColor is duplicate', colorCheckingStyle);
//console.log('%cColor is duplicate', colorCheckingStyle);
//show the duplicate color warning
duplicateColorWarning.style.visibility = 'visible';

806
js/_colorPicker.js Normal file
View File

@ -0,0 +1,806 @@
let sliders = document.getElementsByClassName("cp-slider-entry");
let colourPreview = document.getElementById("cp-colour-preview");
let colourValue = document.getElementById("cp-hex");
let currentPickerMode = "rgb";
let currentPickingMode = "mono";
let styleElement = document.createElement("style");
let miniPickerCanvas = document.getElementById("cp-spectrum");
let miniPickerSlider = document.getElementById("cp-minipicker-slider");
let activePickerIcon = document.getElementById("cp-active-icon");
let pickerIcons = [activePickerIcon];
let hexContainers = [document.getElementById("cp-colours-previews").children[0],null,null,null];
let startPickerIconPos = [[0,0],[0,0],[0,0],[0,0]];
let currPickerIconPos = [[0,0], [0,0],[0,0],[0,0]];
let styles = ["",""];
let draggingCursor = false;
cpInit();
function cpInit() {
// Appending the palette styles
document.getElementsByTagName("head")[0].appendChild(styleElement);
// Saving first icon position
startPickerIconPos[0] = [miniPickerCanvas.getBoundingClientRect().left, miniPickerCanvas.getBoundingClientRect().top];
// Set the correct size of the canvas
miniPickerCanvas.height = miniPickerCanvas.getBoundingClientRect().height;
miniPickerCanvas.width = miniPickerCanvas.getBoundingClientRect().width;
// Update picker position
updatePickerByHex(colourValue.value);
// Startup updating
updateAllSliders();
// Fill minislider
updateMiniSlider(colourValue.value);
// Fill minipicker
updatePickerByHex(colourValue.value);
updateMiniPickerSpectrum();
}
function hexUpdated() {
updatePickerByHex(colourValue.value);
updateSlidersByHex(colourValue.value);
}
// Applies the styles saved in the style array to the style element in the head of the document
function updateStyles() {
styleElement.innerHTML = styles[0] + styles[1];
}
/** Updates the background gradients of the sliders given their value
* Updates the hex colour and its preview
* Updates the minipicker according to the computed hex colour
*
*/
function updateSliderValue (sliderIndex, updateMini = true) {
let toUpdate;
let slider;
let input;
let hexColour;
let sliderValues;
toUpdate = sliders[sliderIndex - 1];
slider = toUpdate.getElementsByTagName("input")[0];
input = toUpdate.getElementsByTagName("input")[1];
// Update label value
input.value = slider.value;
// Update preview colour
// get slider values
sliderValues = getSlidersValues();
// Generate preview colour
switch (currentPickerMode) {
case 'rgb':
hexColour = rgbToHex(sliderValues[0], sliderValues[1], sliderValues[2]);
break;
case 'hsv':
let tmpRgb = hsvToRgb(sliderValues[0], sliderValues[1], sliderValues[2]);
hexColour = rgbToHex(parseInt(tmpRgb[0]), parseInt(tmpRgb[1]), parseInt(tmpRgb[2]));
break;
case 'hsl':
hexColour = hslToHex(sliderValues[0], sliderValues[1], sliderValues[2]);
break;
default:
console.log("wtf select a decent picker mode");
return;
}
// Update preview colour div
colourPreview.style.backgroundColor = '#' + hexColour;
colourValue.value = '#' + hexColour;
// Update sliders background
// there's no other way than creating a custom css file, appending it to the head and
// specify the sliders' backgrounds here
styles[0] = '';
for (let i=0; i<sliders.length; i++) {
styles[0] += getSliderCSS(i + 1, sliderValues);
}
updateStyles();
if (updateMini) {
updatePickerByHex(colourValue.value);
updateMiniPickerSpectrum();
}
}
// Calculates the css gradient for a slider
function getSliderCSS(index, sliderValues) {
let ret = 'input[type=range]#';
let sliderId;
let gradientMin;
let gradientMax;
let hueGradient;
let rgbColour;
switch (index) {
case 1:
sliderId = 'first-slider';
switch (currentPickerMode) {
case 'rgb':
gradientMin = 'rgba(0,' + sliderValues[1] + ',' + sliderValues[2] + ',1)';
gradientMax = 'rgba(255,' + sliderValues[1] + ',' + sliderValues[2] + ',1)';
break;
case 'hsv':
hueGradient = getHueGradientHSV(sliderValues);
break;
case 'hsl':
// Hue gradient
hueGradient = getHueGradientHSL(sliderValues);
break;
}
break;
case 2:
sliderId = 'second-slider';
switch (currentPickerMode) {
case 'rgb':
gradientMin = 'rgba(' + sliderValues[0] + ',0,' + sliderValues[2] + ',1)';
gradientMax = 'rgba(' + sliderValues[0] + ',255,' + sliderValues[2] + ',1)';
break;
case 'hsv':
rgbColour = hsvToRgb(sliderValues[0], 0, sliderValues[2]);
gradientMin = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
rgbColour = hsvToRgb(sliderValues[0], 100, sliderValues[2]);
gradientMax = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
break;
case 'hsl':
rgbColour = cpHslToRgb(sliderValues[0], 0, sliderValues[2]);
gradientMin = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
rgbColour = cpHslToRgb(sliderValues[0], 100, sliderValues[2]);
gradientMax = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
break;
}
break;
case 3:
sliderId = 'third-slider';
switch (currentPickerMode) {
case 'rgb':
gradientMin = 'rgba(' + sliderValues[0] + ',' + sliderValues[1] + ',0,1)';
gradientMax = 'rgba(' + sliderValues[0] + ',' + sliderValues[1] + ',255,1)';
break;
case 'hsv':
rgbColour = hsvToRgb(sliderValues[0], sliderValues[1], 0);
gradientMin = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
rgbColour = hsvToRgb(sliderValues[0], sliderValues[1], 100);
gradientMax = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
break;
case 'hsl':
gradientMin = 'rgba(0,0,0,1)';
gradientMax = 'rgba(255,255,255,1)';
break;
}
break;
default:
return '';
}
ret += sliderId;
ret += '::-webkit-slider-runnable-track {';
switch (currentPickerMode) {
case 'rgb':
ret += 'background: linear-gradient(90deg, rgba(2,0,36,1) 0%, ' +
gradientMin + ' 0%, ' + gradientMax + '100%)';
break;
case 'hsv':
case 'hsl':
ret += 'background: ';
if (index == 1) {
ret += hueGradient;
}
else {
ret += 'linear-gradient(90deg, rgba(2,0,36,1) 0%, ' + gradientMin + ' 0%, ';
// For hsl I also have to add a middle point
if (currentPickerMode == 'hsl' && index == 3) {
let rgb = cpHslToRgb(sliderValues[0], sliderValues[1], 50);
ret += 'rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',1) 50%,';
}
ret += gradientMax + '100%);';
}
break;
}
ret += '}'
ret += ret.replace('::-webkit-slider-runnable-track', '::-moz-range-track');
return ret;
}
// Computes the hue gradient used for hsl
function getHueGradientHSL(sliderValues) {
return 'linear-gradient(90deg, rgba(2,0,36,1) 0%, \
hsl(0,' + sliderValues[1] + '%,' + sliderValues[2]+ '%) 0%, \
hsl(60,' + sliderValues[1] + '%,' + sliderValues[2]+ '%) 16.6666%, \
hsl(120,' + sliderValues[1] + '%,' + sliderValues[2]+ '%) 33.3333333333%, \
hsl(180,'+ sliderValues[1] + '%,' + sliderValues[2]+ '%) 50%, \
hsl(240,' + sliderValues[1] + '%,' + sliderValues[2]+ '%) 66.66666%, \
hsl(300,'+ sliderValues[1] + '%,' + sliderValues[2]+ '%) 83.333333%, \
hsl(360,'+ sliderValues[1] + '%,' + sliderValues[2]+ '%) 100%);';
}
// Computes the hue gradient used for hsv
function getHueGradientHSV(sliderValues) {
let col = hsvToRgb(0, sliderValues[1], sliderValues[2]);
let ret = 'linear-gradient(90deg, rgba(2,0,36,1) 0%, ';
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 0%,'
col = hsvToRgb(60, sliderValues[1], sliderValues[2]);
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 16.6666%,';
col = hsvToRgb(120, sliderValues[1], sliderValues[2]);
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 33.3333333333%,';
col = hsvToRgb(180, sliderValues[1], sliderValues[2]);
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 50%,';
col = hsvToRgb(240, sliderValues[1], sliderValues[2]);
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 66.66666%,';
col = hsvToRgb(300, sliderValues[1], sliderValues[2]);
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 83.333333%,';
col = hsvToRgb(360, sliderValues[1], sliderValues[2]);
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 100%);';
return ret;
}
// Fired when the values in the labels are changed
function inputChanged(target, index) {
let sliderIndex = index - 1;
sliders[sliderIndex].getElementsByTagName("input")[0].value = target.value;
updateSliderValue(index);
}
// Updates the colour model used to pick colours
function changePickerMode(target, newMode) {
let maxRange;
let colArray;
let rgbTmp;
let hexColour = colourValue.value.replace('#', '');
currentPickerMode = newMode;
document.getElementsByClassName("cp-selected-mode")[0].classList.remove("cp-selected-mode");
target.classList.add("cp-selected-mode");
switch (newMode)
{
case 'rgb':
maxRange = [255,255,255];
sliders[0].getElementsByTagName("label")[0].innerHTML = 'R';
sliders[1].getElementsByTagName("label")[0].innerHTML = 'G';
sliders[2].getElementsByTagName("label")[0].innerHTML = 'B';
break;
case 'hsv':
maxRange = [360, 100, 100];
sliders[0].getElementsByTagName("label")[0].innerHTML = 'H';
sliders[1].getElementsByTagName("label")[0].innerHTML = 'S';
sliders[2].getElementsByTagName("label")[0].innerHTML = 'V';
break;
case 'hsl':
maxRange = [360, 100, 100];
sliders[0].getElementsByTagName("label")[0].innerHTML = 'H';
sliders[1].getElementsByTagName("label")[0].innerHTML = 'S';
sliders[2].getElementsByTagName("label")[0].innerHTML = 'L';
break;
default:
console.log("wtf select a decent picker mode");
break;
}
for (let i=0; i<sliders.length; i++) {
let slider = sliders[i].getElementsByTagName("input")[0];
slider.setAttribute("max", maxRange[i]);
}
// Putting the current colour in the new slider
switch(currentPickerMode) {
case 'rgb':
colArray = hexToRgb(hexColour);
colArray = [colArray.r, colArray.g, colArray.b];
break;
case 'hsv':
rgbTmp = hexToRgb(hexColour);
colArray = rgbToHsv(rgbTmp);
colArray.h *= 360;
colArray.s *= 100;
colArray.v *= 100;
colArray = [colArray.h, colArray.s, colArray.v];
break;
case 'hsl':
rgbTmp = hexToRgb(hexColour);
colArray = rgbToHsl(rgbTmp);
colArray.h *= 360;
colArray.s *= 100;
colArray.l *= 100;
colArray = [colArray.h, colArray.s, colArray.l];
break;
default:
break;
}
for (let i=0; i<3; i++) {
sliders[i].getElementsByTagName("input")[0].value = colArray[i];
}
updateAllSliders();
}
// Returns an array containing the values of the sliders
function getSlidersValues() {
return [parseInt(sliders[0].getElementsByTagName("input")[0].value),
parseInt(sliders[1].getElementsByTagName("input")[0].value),
parseInt(sliders[2].getElementsByTagName("input")[0].value)];
}
// Updates every slider
function updateAllSliders(updateMini=true) {
for (let i=1; i<=3; i++) {
updateSliderValue(i, updateMini);
}
}
/******************SECTION: MINIPICKER******************/
// Moves the picker icon according to the mouse position on the canvas
function movePickerIcon(event) {
event.preventDefault();
if (event.which == 1 || draggingCursor) {
let cursorPos = getCursorPosMinipicker(event);
let canvasRect = miniPickerCanvas.getBoundingClientRect();
let left = (cursorPos[0] - startPickerIconPos[0][0] - 8);
let top = (cursorPos[1] - startPickerIconPos[0][1] - 8);
if (left > -8 && top > -8 && left < canvasRect.width-8 && top < canvasRect.height-8){
activePickerIcon.style["left"] = "" + left + "px";
activePickerIcon.style["top"]= "" + top + "px";
currPickerIconPos[0] = [left, top];
}
updateMiniPickerColour();
updateOtherIcons();
}
}
// Updates the main sliders given a hex value computed with the minipicker
function updateSlidersByHex(hex, updateMini = true) {
let colour;
let mySliders = [sliders[0].getElementsByTagName("input")[0],
sliders[1].getElementsByTagName("input")[0],
sliders[2].getElementsByTagName("input")[0]];
switch (currentPickerMode) {
case 'rgb':
colour = hexToRgb(hex);
mySliders[0].value = colour.r;
mySliders[1].value = colour.g;
mySliders[2].value = colour.b;
break;
case 'hsv':
colour = rgbToHsv(hexToRgb(hex));
mySliders[0].value = colour.h * 360;
mySliders[1].value = colour.s * 100;
mySliders[2].value = colour.v * 100;
break;
case 'hsl':
colour = rgbToHsl(hexToRgb(hex));
mySliders[0].value = colour.h * 360;
mySliders[1].value = colour.s * 100;
mySliders[2].value = colour.l * 100;
break;
default:
break;
}
updateAllSliders(false);
}
// Gets the position of the picker cursor relative to the canvas
function getCursorPosMinipicker(e) {
var x;
var y;
if (e.pageX != undefined && e.pageY != undefined) {
x = e.pageX;
y = e.pageY;
}
else {
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
x -= miniPickerCanvas.offsetLeft;
y -= miniPickerCanvas.offsetTop;
return [Math.round(x), Math.round(y)];
}
// Updates the minipicker given a hex computed by the main sliders
// Moves the cursor
function updatePickerByHex(hex) {
let hsv = rgbToHsv(hexToRgb(hex));
let xPos = miniPickerCanvas.width * hsv.h - 8;
let yPos = miniPickerCanvas.height * hsv.s + 8;
miniPickerSlider.value = hsv.v * 100;
currPickerIconPos[0][0] = xPos;
currPickerIconPos[0][1] = miniPickerCanvas.height - yPos;
if (currPickerIconPos[0][1] >= 92)
{
currPickerIconPos[0][1] = 91.999;
}
activePickerIcon.style.left = '' + xPos + 'px';
activePickerIcon.style.top = '' + (miniPickerCanvas.height - yPos) + 'px';
activePickerIcon.style.backgroundColor = '#' + getMiniPickerColour();
colourPreview.style.backgroundColor = hex;
updateOtherIcons();
updateMiniSlider(hex);
}
// Fired when the value of the minislider changes: updates the spectrum gradient and the hex colour
function miniSliderInput(event) {
let newHex;
let newHsv = rgbToHsv(hexToRgb(getMiniPickerColour()));
let rgb;
// Adding slider value to value
newHsv.v = parseInt(event.target.value);
// Updating hex
rgb = hsvToRgb(newHsv.h * 360, newHsv.s * 100, newHsv.v);
newHex = rgbToHex(Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2]));
colourValue.value = newHex;
updateMiniPickerSpectrum();
updateMiniPickerColour();
}
// Updates the hex colour after having changed the minislider (MERGE)
function updateMiniPickerColour() {
let hex = getMiniPickerColour();
activePickerIcon.style.backgroundColor = '#' + hex;
// Update hex and sliders based on hex
colourValue.value = '#' + hex;
colourPreview.style.backgroundColor = '#' + hex;
updateSlidersByHex(hex);
updateMiniSlider(hex);
updateOtherIcons();
}
// Returns the current colour of the minipicker
function getMiniPickerColour() {
let hex;
let pickedColour;
pickedColour = miniPickerCanvas.getContext('2d').getImageData(currPickerIconPos[0][0] + 8,
currPickerIconPos[0][1] + 8, 1, 1).data;
hex = rgbToHex(pickedColour[0], pickedColour[1], pickedColour[2]);
return hex;
}
// Update the background gradient of the slider in the minipicker
function updateMiniSlider(hex) {
let rgb = hexToRgb(hex);
styles[1] = "input[type=range]#cp-minipicker-slider::-webkit-slider-runnable-track { background: rgb(2,0,36);";
styles[1] += "background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(0,0,0,1) 0%, " +
"rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + ",1) 100%);}";
updateMiniPickerSpectrum();
updateStyles();
}
// Updates the gradient of the spectrum canvas in the minipicker
function updateMiniPickerSpectrum() {
let ctx = miniPickerCanvas.getContext('2d');
let hsv = rgbToHsv(hexToRgb(colourValue.value));
let tmp;
let white = {h:hsv.h * 360, s:0, v: parseInt(miniPickerSlider.value)};
white = hsvToRgb(white.h, white.s, white.v);
ctx.clearRect(0, 0, miniPickerCanvas.width, miniPickerCanvas.height);
// Drawing hues
var hGrad = ctx.createLinearGradient(0, 0, miniPickerCanvas.width, 0);
for (let i=0; i<7; i++) {
tmp = hsvToRgb(60 * i, 100, hsv.v * 100);
hGrad.addColorStop(i / 6, '#' + rgbToHex(Math.round(tmp[0]), Math.round(tmp[1]), Math.round(tmp[2])));
}
ctx.fillStyle = hGrad;
ctx.fillRect(0, 0, miniPickerCanvas.width, miniPickerCanvas.height);
// Drawing sat / lum
var vGrad = ctx.createLinearGradient(0, 0, 0, miniPickerCanvas.height);
vGrad.addColorStop(0, 'rgba(' + white[0] +',' + white[1] + ',' + white[2] + ',0)');
/*
vGrad.addColorStop(0.1, 'rgba(255,255,255,0)');
vGrad.addColorStop(0.9, 'rgba(255,255,255,1)');
*/
vGrad.addColorStop(1, 'rgba(' + white[0] +',' + white[1] + ',' + white[2] + ',1)');
ctx.fillStyle = vGrad;
ctx.fillRect(0, 0, miniPickerCanvas.width, miniPickerCanvas.height);
}
function toggleDraggingCursor() {
draggingCursor = !draggingCursor;
}
function changePickingMode(event, newMode) {
let nIcons = pickerIcons.length;
let canvasContainer = document.getElementById("cp-canvas-container");
// Number of hex containers to add
let nHexContainers;
// Remove selected class from previous mode
document.getElementById("cp-colour-picking-modes").getElementsByClassName("cp-selected-mode")[0].classList.remove("cp-selected-mode");
// Updating mode
currentPickingMode = newMode;
// Adding selected class to new mode
event.target.classList.add("cp-selected-mode");
for (let i=1; i<nIcons; i++) {
// Deleting extra icons
pickerIcons.pop();
canvasContainer.removeChild(canvasContainer.children[2]);
// Deleting extra hex containers
hexContainers[0].parentElement.removeChild(hexContainers[0].parentElement.children[1]);
hexContainers[i] = null;
}
// Resetting first hex container size
hexContainers[0].style.width = '100%';
switch (currentPickingMode)
{
case 'analog':
createIcon();
createIcon();
nHexContainers = 2;
break;
case 'cmpt':
// Easiest one, add 180 to the H value and move the icon
createIcon();
nHexContainers = 1;
break;
case 'tri':
createIcon();
createIcon();
nHexContainers = 2;
break
case 'scmpt':
createIcon();
createIcon();
nHexContainers = 2;
break;
case 'tetra':
for (let i=0; i<3; i++) {
createIcon();
}
nHexContainers = 3;
break;
default:
console.log("How did you select the " + currentPickingMode + ", hackerman?");
break;
}
// Editing the size of the first container
hexContainers[0].style.width = '' + 100 / (nHexContainers + 1) + '%';
// Adding hex preview containers
for (let i=0; i<nHexContainers; i++) {
let newContainer = document.createElement("div");
newContainer.classList.add("cp-colour-preview");
newContainer.style.width = "" + (100 / (nHexContainers + 1)) + "%";
hexContainers[0].parentElement.appendChild(newContainer);
hexContainers[i + 1] = newContainer;
}
function createIcon() {
let newIcon = document.createElement("div");
newIcon.classList.add("cp-picker-icon");
pickerIcons.push(newIcon);
canvasContainer.appendChild(newIcon);
}
updateOtherIcons();
}
function updateOtherIcons() {
let currentColorHex = colourValue.value;
let currentColourHsv = rgbToHsv(hexToRgb(currentColorHex));
let newColourHsv = {h:currentColourHsv.h, s:currentColourHsv.s, v:currentColourHsv.v};
let newColourHexes = ['', '', ''];
let tmpRgb;
// Salvo tutti i
switch (currentPickingMode)
{
case 'mono':
break;
case 'analog':
// First colour
newColourHsv.h = (((currentColourHsv.h*360 + 40) % 360) / 360);
currPickerIconPos[1][0] = miniPickerCanvas.width * newColourHsv.h - 8;
currPickerIconPos[1][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
newColourHexes[0] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
// Second colour
newColourHsv.h = (((currentColourHsv.h*360 - 40) % 360) / 360);
if (newColourHsv.h < 0) {
newColourHsv.h += 1;
}
currPickerIconPos[2][0] = miniPickerCanvas.width * newColourHsv.h - 8;
currPickerIconPos[2][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
newColourHexes[1] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
break;
case 'cmpt':
newColourHsv.h = (((currentColourHsv.h*360 + 180) % 360) / 360);
currPickerIconPos[1][0] = miniPickerCanvas.width * newColourHsv.h - 8;
currPickerIconPos[1][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
newColourHexes[0] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
break;
case 'tri':
for (let i=1; i< 3; i++) {
newColourHsv.h = (((currentColourHsv.h*360 + 120*i) % 360) / 360);
currPickerIconPos[i][0] = miniPickerCanvas.width * newColourHsv.h - 8;
currPickerIconPos[i][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
newColourHexes[i - 1] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
}
break
case 'scmpt':
// First colour
newColourHsv.h = (((currentColourHsv.h*360 + 210) % 360) / 360);
currPickerIconPos[1][0] = miniPickerCanvas.width * newColourHsv.h - 8;
currPickerIconPos[1][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
newColourHexes[0] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
// Second colour
newColourHsv.h = (((currentColourHsv.h*360 + 150) % 360) / 360);
currPickerIconPos[2][0] = miniPickerCanvas.width * newColourHsv.h - 8;
currPickerIconPos[2][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
newColourHexes[1] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
break;
case 'tetra':
for (let i=1; i< 4; i++) {
newColourHsv.h = (((currentColourHsv.h*360 + 90*i) % 360) / 360);
currPickerIconPos[i][0] = miniPickerCanvas.width * newColourHsv.h - 8;
currPickerIconPos[i][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
newColourHexes[i - 1] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
}
break;
default:
console.log("How did you select the " + currentPickingMode + ", hackerman?");
break;
}
hexContainers[0].style.color = getHexPreviewColour(colourValue.value);
for (let i=1; i<pickerIcons.length; i++) {
pickerIcons[i].style.left = '' + currPickerIconPos[i][0] + 'px';
pickerIcons[i].style.top = '' + currPickerIconPos[i][1] + 'px';
pickerIcons[i].style.backgroundColor = '#' + newColourHexes[i - 1];
}
if (currentPickingMode != "analog") {
hexContainers[0].style.backgroundColor = colourValue.value;
hexContainers[0].innerHTML = colourValue.value;
for (let i=0; i<pickerIcons.length - 1; i++) {
hexContainers[i + 1].style.backgroundColor = '#' + newColourHexes[i];
hexContainers[i + 1].innerHTML = '#' + newColourHexes[i];
hexContainers[i + 1].style.color = getHexPreviewColour(newColourHexes[i]);
}
}
// If I'm using analogous mode, I place the current colour in the middle
else {
hexContainers[1].style.backgroundColor = colourValue.value;
hexContainers[1].innerHTML = colourValue.value;
hexContainers[2].style.backgroundColor = '#' + newColourHexes[0];
hexContainers[2].innerHTML = '#' + newColourHexes[0];
hexContainers[0].style.backgroundColor = '#' + newColourHexes[1];
hexContainers[0].innerHTML = '#' + newColourHexes[1];
for (let i=1; i<3; i++) {
hexContainers[i].style.color = getHexPreviewColour(newColourHexes[i - 1]);
}
}
}
function getSelectedColours() {
let ret = [];
for (let i=0; i<hexContainers.length; i++) {
if (hexContainers[i] != null) {
ret.push(hexContainers[i].innerHTML);
}
}
return ret;
}
function getHexPreviewColour(hex) {
//if brightness is over threshold, make the text dark
if (colorBrightness(hex) > 110) {
return '#332f35'
}
else {
return '#c2bbc7';
}
//take in a color and return its brightness
function colorBrightness (color) {
var r = parseInt(color.slice(1, 3), 16);
var g = parseInt(color.slice(3, 5), 16);
var b = parseInt(color.slice(5, 7), 16);
return Math.round(((parseInt(r) * 299) + (parseInt(g) * 587) + (parseInt(b) * 114)) / 1000);
}
}

View File

@ -1,11 +1,17 @@
// Data saved when copying or cutting
let clipboardData;
// Tells if the user is pasting something or not
let isPasting = false;
// Coordinates of the copied (or cut) selection
let copiedStartX;
let copiedStartY;
let copiedEndX;
let copiedEndY;
/** Copies the current selection to the clipboard
*
*/
function copySelection() {
copiedEndX = endX;
copiedEndY = endY;
@ -16,13 +22,19 @@ function copySelection() {
clipboardData = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1);
}
/** Pastes the clipboard data onto the current layer
*
*/
function pasteSelection() {
// Can't paste if the layer is locked
if (currentLayer.isLocked) {
return;
}
// Cancel the current selection
endSelection();
// I'm pasting
isPasting = true;
// Putting the image data on the tmp layer
TMPLayer.context.putImageData(clipboardData, copiedStartX, copiedStartY);
@ -43,9 +55,11 @@ function pasteSelection() {
//drawRect(copiedStartX, copiedEndX, copiedStartY, copiedEndY);
}
/** Cuts the current selection and copies it to the clipboard
*
*/
function cutSelectionTool() {
console.log("Taglio");
// Saving the coordinates
copiedEndX = endX;
copiedEndY = endY;
@ -53,13 +67,19 @@ function cutSelectionTool() {
copiedStartY = startY;
// Getting the selected pixels
// If I'm already moving a selection
if (imageDataToMove !== undefined) {
// I just save that selection in the clipboard
clipboardData = imageDataToMove;
// And clear the underlying space
TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height);
// The image has been cleared, so I don't have anything to move anymore
imageDataToMove = undefined;
}
else {
clipboardData = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1);
// Otherwise, I copy the current selection into the clipboard
copySelection();
// And clear the selection
currentLayer.context.clearRect(startX - 0.5, startY - 0.5, endX - startX + 1, endY - startY + 1);
}
}

View File

@ -1,7 +1,51 @@
function create(isSplash) {
var splashPostfix = '';
// If I'm creating from the splash menu, I append '-splash' so I get the corresponding values
if (isSplash) {
splashPostfix = '-splash';
}
var width = getValue('size-width' + splashPostfix);
var height = getValue('size-height' + splashPostfix);
// If I'm creating from the splash screen, I use the splashMode variable
var mode = isSplash ? splashMode : getValue('editor-mode');
newPixel(width, height, mode);
// If I'm not creating from the splash page, then this is not the first project I've created
if (!isSplash)
document.getElementById('new-pixel-warning').style.display = 'block';
//get selected palette name
var selectedPalette = getText('palette-button' + splashPostfix);
if (selectedPalette == 'Choose a palette...')
selectedPalette = 'none';
//track google event
ga('send', 'event', 'Pixel Editor New', selectedPalette, width+'/'+height); /*global ga*/
//reset new form
setValue('size-width', 64);
setValue('size-height', 64);
setValue("editor-mode", 'Advanced')
setText('editor-mode-button', 'Choose a mode...');
setText('palette-button', 'Choose a palette...');
setText('preset-button', 'Choose a preset...');
}
/** Triggered when the "Create" button in the new pixel dialogue is pressed
*
*/
on('click', 'create-button', function (){
// Getting the values of the form
var width = getValue('size-width');
var height = getValue('size-height');
var mode = getValue("editor-mode");
// Creating a new pixel with those properties
newPixel(width, height, mode);
document.getElementById('new-pixel-warning').style.display = 'block';
@ -23,3 +67,37 @@ on('click', 'create-button', function (){
setText('palette-button', 'Choose a palette...');
setText('preset-button', 'Choose a preset...');
});
/** Triggered when the "Create" button in the new pixel dialogue is pressed
*
*/
on('click', 'create-button-splash', function (){
// Getting the values of the form
var width = getValue('size-width-splash');
var height = getValue('size-height-splash');
var mode = pixelEditorMode;
if (mode == 'Advanced')
mode = "Basic";
else
mode = "Advanced";
// Creating a new pixel with those properties
newPixel(width, height, mode);
//track google event
ga('send', 'event', 'Pixel Editor New', selectedPalette, width+'/'+height); /*global ga*/
document.getElementById('new-pixel-warning').style.display = 'block';
// Resetting the new pixel values
selectedPalette = 'none';
//reset new pixel form
setValue('size-width-splash', 64);
setValue('size-height-splash', 64);
setValue("editor-mode", 'Advanced')
setText('editor-mode-button', 'Choose a mode...');
setText('palette-button', 'Choose a palette...');
setText('preset-button', 'Choose a preset...');
});

View File

@ -1,5 +1,11 @@
function createColorPalette(paletteColors, fillBackground, deletePreviousPalette = true) {
/** Creates the colour palette
*
* @param {*} paletteColors The colours of the palette
* @param {*} deletePreviousPalette Tells if the app should delete the previous palette or not
* (used when opening a file, for example)
*/
function createColorPalette(paletteColors, deletePreviousPalette = true) {
//remove current palette
if (deletePreviousPalette) {
colors = document.getElementsByClassName('color-button');
@ -11,6 +17,7 @@ function createColorPalette(paletteColors, fillBackground, deletePreviousPalette
var lightestColor = '#000000';
var darkestColor = '#ffffff';
// Adding all the colours in the array
for (var i = 0; i < paletteColors.length; i++) {
var newColor = paletteColors[i];
var newColorElement = addColor(newColor);
@ -42,6 +49,9 @@ function createColorPalette(paletteColors, fillBackground, deletePreviousPalette
currentLayer.context.fillStyle = darkestColor;
}
/** Creates the palette with the colours used in all the layers
*
*/
function createPaletteFromLayers() {
let colors = {};
@ -68,8 +78,6 @@ function createPaletteFromLayers() {
}
}
console.log(colors);
//create array out of colors object
let colorPaletteArray = [];
for (let color in colors) {
@ -77,8 +85,7 @@ function createPaletteFromLayers() {
colorPaletteArray.push('#'+rgbToHex(colors[color]));
}
}
console.log('COLOR PALETTE ARRAY', colorPaletteArray);
//create palette form colors array
createColorPalette(colorPaletteArray, false);
//create palette from colors array
createColorPalette(colorPaletteArray, true);
}

View File

@ -9,19 +9,19 @@ function deleteColor (color) {
//if color is a string, then find the corresponding button
if (typeof color === 'string') {
console.log('trying to find ',color);
//console.log('trying to find ',color);
//get all colors in palette
colors = document.getElementsByClassName('color-button');
//loop through colors
for (var i = 0; i < colors.length; i++) {
console.log(color,'=',colors[i].jscolor.toString());
//console.log(color,'=',colors[i].jscolor.toString());
if (color == colors[i].jscolor.toString()) {
console.log('match');
//console.log('match');
//set color to the color button
color = colors[i];
console.log('found color', color);
//console.log('found color', color);
//exit loop
break;
@ -30,7 +30,7 @@ function deleteColor (color) {
//if the color wasn't found
if (typeof color === 'string') {
console.log('color not found');
//console.log('color not found');
//exit function
return;
}

View File

@ -1,27 +1,55 @@
let currentOpenDialogue = "";
/** Shows the dialogue window called dialogueName, which is a child of pop-up-container in pixel-editor.hbs
*
* @param {*} dialogueName The name of the window to show
* @param {*} trackEvent Should I track the GA event?
*/
function showDialogue (dialogueName, trackEvent) {
if (typeof trackEvent === 'undefined') trackEvent = true;
// Updating currently open dialogue
currentOpenDialogue = dialogueName;
// The pop up window is open
dialogueOpen = true;
// Showing the pop up container
popUpContainer.style.display = 'block';
// Showing the window
document.getElementById(dialogueName).style.display = 'block';
// If I'm opening the palette window, I initialize the colour picker
if (dialogueName == 'palette-block' && documentCreated) {
cpInit();
pbInit();
}
//track google event
if (trackEvent)
ga('send', 'event', 'Palette Editor Dialogue', dialogueName); /*global ga*/
}
/** Closes the current dialogue by hiding the window and the pop-up-container
*
*/
function closeDialogue () {
popUpContainer.style.display = 'none';
var popups = popUpContainer.children;
for (var i = 0; i < popups.length; i++) {
popups[i].style.display = 'none';
}
dialogueOpen = false;
if (currentOpenDialogue == "palette-block") {
pbAddToSimplePalette();
}
}
/** Closes a dialogue window if the user clicks everywhere but in the current window
*
*/
popUpContainer.addEventListener('click', function (e) {
if (e.target == popUpContainer)
closeDialogue();
@ -31,6 +59,6 @@ popUpContainer.addEventListener('click', function (e) {
var cancelButtons = popUpContainer.getElementsByClassName('close-button');
for (var i = 0; i < cancelButtons.length; i++) {
cancelButtons[i].addEventListener('click', function () {
closeDialogue();
closeDialogue();
});
}

View File

@ -1,4 +1,4 @@
//draw a line between two points on canvas
//draws a line between two points on canvas
function line(x0,y0,x1,y1, brushSize) {
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);

View File

@ -3,27 +3,48 @@ let modes = {
description: 'Basic mode is perfect if you want to create simple sprites or try out palettes.'
},
'Advanced' : {
description: 'Choose advanced mode to gain access to features such as layers.'
description: 'Choose advanced mode to gain access to more advanced features such as layers.'
}
}
let infoBox = document.getElementById('editor-mode-info');
let currentSplashButton = document.getElementById("sp-mode-palette").children[0].children[1];
function switchMode(currentMode, mustConfirm = true) {
function splashMode(mouseEvent, mode) {
console.log("UE");
if (currentSplashButton == undefined) {
currentSplashButton = mouseEvent.target.parentElement;
}
if (mode !== pixelEditorMode) {
console.log("Mode target: " + mouseEvent.target);
// Remove selected class to old button
currentSplashButton.classList.remove("sp-interface-selected");
// Add selected class to new button
mouseEvent.target.parentElement.classList.add("sp-interface-selected");
// Setting the new mode
pixelEditorMode = mode;
}
// Setting the new selected button
currentSplashButton = mouseEvent.target.parentElement;
}
function switchMode(mustConfirm = true) {
//switch to advanced mode
if (currentMode == 'Basic') {
if (pixelEditorMode == 'Basic') {
// Switch to advanced ez pez lemon squez
document.getElementById('switch-mode-button').innerHTML = 'Switch to basic mode';
// Show the layer menus
layerList.style.display = "inline-block";
document.getElementById('layer-button').style.display = 'inline-block';
// Move the palette menu
document.getElementById('colors-menu').style.right = '200px';
// Hide the palette menu
document.getElementById('colors-menu').style.right = '200px'
pixelEditorMode = 'Advanced';
}
//switch to basic mode
else {
//if there is a current layer (a document is active)
@ -47,15 +68,17 @@ function switchMode(currentMode, mustConfirm = true) {
// Hide the layer menus
layerList.style.display = 'none';
document.getElementById('layer-button').style.display = 'none';
// Move the palette menu
document.getElementById('colors-menu').style.right = '0px';
// Show the palette menu
document.getElementById('colors-menu').style.display = 'flex';
// Move the palette menu
document.getElementById('colors-menu').style.right = '0px';
pixelEditorMode = 'Basic';
}
}
on('click', 'switch-mode-button', function (e) {
switchMode(pixelEditorMode);
switchMode();
});
// Makes the menu open

1
js/_featuresLog.js Normal file
View File

@ -0,0 +1 @@
showDialogue("splash", false);

View File

@ -18,8 +18,6 @@ for (var i = 1; i < mainMenuItems.length; i++) {
var subMenu = menuItem.children[1];
var subMenuItems = subMenu.children;
//when you click an item within a menu button
for (var j = 0; j < subMenuItems.length; j++) {

View File

@ -7,11 +7,6 @@ function fill(cursorLocation) {
tempImage.data[pixelPos + 1] = fillColor.g;
tempImage.data[pixelPos + 2] = fillColor.b;
tempImage.data[pixelPos + 3] = 255;
/*
tempImage.data[pixelPos] = fillColor.r;
tempImage.data[pixelPos + 1] = fillColor.g;
tempImage.data[pixelPos + 2] = fillColor.b;
*/
}
//change x y to color value passed from the function and use that as the original color

View File

@ -1,4 +1,4 @@
//get cursor position relative to canvas
//gets cursor position relative to canvas
function getCursorPosition(e) {
var x;
var y;

View File

@ -1,3 +1,17 @@
/** How the history works
* - undoStates stores the states that can be undone
* - redoStates stores the states that can be redone
* - undo() undoes an action and adds it to the redoStates
* - redo() redoes an action and adds it to the undoStates
* - Each HistoryState must implement an undo() and redo() function
* Those functions actually implement the undo and redo mechanism for that action,
* so you'll need to save the data you need as attributes in the constructor. For example,
* for the HistoryStateAddColour, the added colour is saved so that it can be removed in
* undo() or added back in redo().
* - Each HistoryState must call saveHistoryState(this) so that it gets added to the stack
*
*/
var undoStates = [];
var redoStates = [];
@ -473,8 +487,6 @@ function saveHistoryState (state) {
}
function undo () {
//console.log('%cundo', undoLogStyle);
//if there are any states saved to undo
if (undoStates.length > 0) {
document.getElementById('redo-button').classList.remove('disabled');
@ -496,8 +508,6 @@ function undo () {
}
function redo () {
//console.log('%credo', undoLogStyle);
if (redoStates.length > 0) {
//enable undo button

View File

@ -1,5 +1,9 @@
var spacePressed = false;
/** Just listens to hotkeys and calls the linked functions
*
* @param {*} e
*/
function KeyPress(e) {
var keyboardEvent = window.event? event : e;
@ -46,6 +50,9 @@ function KeyPress(e) {
case 52: case 80:
tool.pan.switchTo();
break;
case 76:
tool.line.switchTo();
break;
//zoom - 5
case 53:
tool.zoom.switchTo();

View File

@ -1,5 +1,7 @@
// NEXTPULL: to remove when the new palette system is added
//format a color button
//formats a color button
function initColor (colorElement) {
//console.log('initColor()');
//console.log(document.getElementById('jscolor-hex-input'))

View File

@ -1,3 +1,5 @@
// NEXTPULL: to remove when the new palette system is added
/**
* jscolor - JavaScript Color Picker
*

View File

@ -1,31 +1,29 @@
/** TODO LIST FOR LAYERS
GENERAL REQUIREMENTS:
- Saving the state of an artwork to a .lospec file so that people can work on it later keeping
the layers they created? That'd be cool, even for the app users, that could just double click on a lospec
file for it to be opened right in the pixel editor
OPTIONAL:
1 - Fix issues
*/
// HTML element that contains the layer entries
let layerList;
// A single layer entry (used as a prototype to create the new ones)
let layerListEntry;
// NEXTPULL: remove the drag n drop system and use Sortable.js instead
let layerDragSource = null;
// Number of layers at the beginning
let layerCount = 1;
// Current max z index (so that I know which z-index to assign to new layers)
let maxZIndex = 3;
// When a layer is deleted, its id is added to this array and can be reused
let unusedIDs = [];
// Id for the next added layer
let currentID = layerCount;
let idToDelete;
// Layer menu
let layerOptions = document.getElementById("layer-properties-menu");
// Is the user currently renaming a layer?
let isRenamingLayer = false;
// I need to save this, trust me
let oldLayerName = null;
let dragStartLayer;
// Binding the add layer button to the function
on('click',"add-layer-button", addLayer, false);
/** Handler class for a single canvas (a single layer)
@ -54,6 +52,7 @@ class Layer {
this.id = "layer" + id;
// Binding the events
if (menuEntry != null) {
this.name = menuEntry.getElementsByTagName("p")[0].innerHTML;
menuEntry.id = "layer" + id;
@ -78,20 +77,13 @@ class Layer {
// Initializes the canvas
initialize() {
/*
var maxHorizontalZoom = Math.floor(window.innerWidth/this.canvasSize[0]*0.75);
var maxVerticalZoom = Math.floor(window.innerHeight/this.canvasSize[1]*0.75);
zoom = Math.min(maxHorizontalZoom,maxVerticalZoom);
if (zoom < 1) zoom = 1;
*/
//resize canvas
this.canvas.width = this.canvasSize[0];
this.canvas.height = this.canvasSize[1];
this.canvas.style.width = (this.canvas.width*zoom)+'px';
this.canvas.style.height = (this.canvas.height*zoom)+'px';
//unhide canvas
//show canvas
this.canvas.style.display = 'block';
//center canvas in window
@ -103,7 +95,7 @@ class Layer {
}
hover() {
// Hide all the layers but the current one
// Hides all the layers but the current one
for (let i=1; i<layers.length - nAppLayers; i++) {
if (layers[i] !== this) {
layers[i].canvas.style.opacity = 0.3;
@ -112,7 +104,7 @@ class Layer {
}
unhover() {
// Show all the layers again
// Shows all the layers again
for (let i=1; i<layers.length - nAppLayers; i++) {
if (layers[i] !== this) {
layers[i].canvas.style.opacity = 1;
@ -127,52 +119,6 @@ class Layer {
}
}
layerDragStart(element) {
layerDragSource = this;
element.dataTransfer.effectAllowed = 'move';
element.dataTransfer.setData('text/html', this.id);
this.classList.add('dragElem');
}
layerDragOver(element) {
if (element.preventDefault) {
element.preventDefault(); // Necessary. Allows us to drop.
}
this.classList.add('layerdragover');
element.dataTransfer.dropEffect = 'move';
return false;
}
layerDragLeave(element) {
this.classList.remove('layerdragover');
}
layerDragDrop(element) {
if (element.stopPropagation) {
element.stopPropagation(); // Stops some browsers from redirecting.
}
// Don't do anything if dropping the same column we're dragging.
if (layerDragSource != this) {
let toDropID = element.dataTransfer.getData('text/html');
let thisID = this.id;
moveLayers(toDropID, thisID);
}
this.classList.remove('layerdragover');
dragging = false;
return false;
}
layerDragEnd(element) {
this.classList.remove('layerdragover');
}
// Resizes canvas
resize() {
let newWidth = (this.canvas.width * zoom) + 'px';
@ -217,20 +163,20 @@ class Layer {
openOptionsMenu(event) {
if (event.which == 3) {
let selectedId;
let target = event.target;
let offsets = getElementAbsolutePosition(this);
while (target != null && target.classList != null && !target.classList.contains("layers-menu-entry")) {
target = target.parentElement;
}
idToDelete = target.id;
selectedId = target.id;
layerOptions.style.visibility = "visible";
layerOptions.style.top = "0";
layerOptions.style.marginTop = "" + (event.clientY - 25) + "px";
getLayerByID(idToDelete).selectLayer();
getLayerByID(selectedId).selectLayer();
}
}
@ -265,10 +211,6 @@ class Layer {
layer.menuEntry.classList.add("selected-layer");
currentLayer = layer;
}
/*
canvas = currentLayer.canvas;
context = currentLayer.context;
*/
}
toggleLock() {
@ -442,7 +384,6 @@ function merge(saveHistory = true) {
// Updating the layer preview
currentLayer.updateLayerPreview();
}
}
function deleteLayer(saveHistory = true) {
@ -456,7 +397,7 @@ function deleteLayer(saveHistory = true) {
unusedIDs.push(toDelete.id);
// Selecting the next layer
if (layerIndex != (layers.length - 3)) {
if (layerIndex != (layers.length - 4)) {
layers[layerIndex + 1].selectLayer();
}
// or the previous one if the next one doesn't exist
@ -489,13 +430,13 @@ function duplicateLayer(event, saveHistory = true) {
for (let i=getMenuEntryIndex(menuEntries, toDuplicate.menuEntry) - 1; i>=0; i--) {
getLayerByID(menuEntries[i].id).canvas.style.zIndex++;
}
maxZIndex++;
maxZIndex+=2;
// Creating a new canvas
let newCanvas = document.createElement("canvas");
// Setting up the new canvas
canvasView.append(newCanvas);
newCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex) + 1;
newCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex) + 2;
newCanvas.classList.add("drawingCanvas");
if (!layerListEntry) return console.warn('skipping adding layer because no document');
@ -545,78 +486,6 @@ function renameLayer(event) {
isRenamingLayer = true;
}
// Swaps two layer entries in the layer menu
function moveLayers(toDropLayer, staticLayer, saveHistory = true) {
let toDrop = getLayerByID(toDropLayer);
let staticc = getLayerByID(staticLayer);
let layerCopy = layers.slice();
let beforeToDrop = toDrop.menuEntry.nextElementSibling;
let nMoved = 0;
layerCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1);
let toDropIndex = layerCopy.indexOf(toDrop);
let staticIndex = layerCopy.indexOf(staticc);
layerList.insertBefore(toDrop.menuEntry, staticc.menuEntry);
if (toDropIndex < staticIndex) {
let tmp = toDrop.canvas.style.zIndex;
let tmp2;
for (let i=toDropIndex+1; i<=staticIndex; i++) {
tmp2 = layerCopy[i].canvas.style.zIndex;
if (saveHistory) {
new HistoryStateMoveTwoLayers(layerCopy[i], tmp2, tmp);
}
layerCopy[i].canvas.style.zIndex = tmp;
tmp = tmp2;
nMoved++;
}
if (saveHistory) {
new HistoryStateMoveTwoLayers(toDrop, toDrop.canvas.style.zIndex, tmp);
}
toDrop.canvas.style.zIndex = tmp;
if (saveHistory) {
new HistoryStateMoveLayer(beforeToDrop, toDrop, staticc, nMoved);
}
}
else {
// BUG QUI
let tmp = toDrop.canvas.style.zIndex;
let tmp2;
for (let i=toDropIndex-1; i>staticIndex; i--) {
tmp2 = layerCopy[i].canvas.style.zIndex;
if (saveHistory) {
new HistoryStateMoveTwoLayers(layerCopy[i], tmp2, tmp);
}
layerCopy[i].canvas.style.zIndex = tmp;
tmp = tmp2;
nMoved++;
}
if (saveHistory) {
new HistoryStateMoveTwoLayers(toDrop, toDrop.canvas.style.zIndex, tmp);
}
toDrop.canvas.style.zIndex = tmp;
if (saveHistory) {
new HistoryStateMoveLayer(beforeToDrop, toDrop, staticc, nMoved);
}
}
}
function getMenuEntryIndex(list, entry) {
for (let i=0; i<list.length; i++) {
if (list[i] === entry) {
@ -660,7 +529,7 @@ function addLayer(id, saveHistory = true) {
let newCanvas = document.createElement("canvas");
// Setting up the new canvas
canvasView.append(newCanvas);
maxZIndex++;
maxZIndex+=2;
newCanvas.style.zIndex = maxZIndex;
newCanvas.classList.add("drawingCanvas");
@ -696,4 +565,49 @@ function addLayer(id, saveHistory = true) {
return newLayer;
}
layerList = document.getElementById("layers-menu");
/** Saves the layer that is being moved when the dragging starts
*
* @param {*} event
*/
function layerDragStart(event) {
dragStartLayer = getLayerByID(layerList.children[event.oldIndex].id);
}
/** Sets the z indexes of the layers when the user drops the layer in the menu
*
* @param {*} event
*/
function layerDragDrop(event) {
let oldIndex = event.oldDraggableIndex;
let newIndex = event.newDraggableIndex;
let movedZIndex = dragStartLayer.canvas.style.zIndex;
if (oldIndex > newIndex)
{
for (let i=newIndex; i<oldIndex; i++) {
getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i + 1].id).canvas.style.zIndex;
}
}
else
{
for (let i=newIndex; i>oldIndex; i--) {
getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i - 1].id).canvas.style.zIndex;
}
}
getLayerByID(layerList.children[oldIndex].id).canvas.style.zIndex = movedZIndex;
dragging = false;
}
layerList = document.getElementById("layers-menu");
// Making the layers list sortable
new Sortable(document.getElementById("layers-menu"), {
animation: 100,
filter: ".layer-button",
draggable: ".layers-menu-entry",
onStart: layerDragStart,
onEnd: layerDragDrop
});

45
js/_line.js Normal file
View File

@ -0,0 +1,45 @@
function diagLine(lastMouseClickPos, zoom, cursorLocation) {
let x0 = Math.floor(lastMouseClickPos[0]/zoom);
let y0 = Math.floor(lastMouseClickPos[1]/zoom);
let x1 = Math.floor(cursorLocation[0]/zoom);
let y1 = Math.floor(cursorLocation[1]/zoom);
let dx = Math.abs(x1-x0);
let dy = Math.abs(y1-y0);
let sx = (x0 < x1 ? 1 : -1);
let sy = (y0 < y1 ? 1 : -1);
let err = dx-dy;
const brushSize = tool.line.brushSize;
const canvas = document.getElementById('tmp-canvas');
const context = canvas.getContext('2d');
context.fillStyle=currentGlobalColor;
context.clearRect(0, 0, canvas.width, canvas.height);
canvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex, 10) + 1;
//console.log(canvas.style.zIndex, currentLayer.canvas.style.zIndex);
while (true) {
if (currentTool.name !== 'line') return;
context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize);
//if we've reached the end goal, exit the loop
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2 >-dy) {
err -=dy;
x0+=sx;
}
if (e2 < dx) {
err +=dx;
y0+=sy;
}
}
}

View File

@ -1,18 +1,27 @@
/** Loads a file (.png or .lpe)
*
*/
document.getElementById('open-image-browse-holder').addEventListener('change', function () {
let fileName = document.getElementById("open-image-browse-holder").value;
// Getting the extension
let extension = fileName.substring(fileName.lastIndexOf('.')+1, fileName.length) || fileName;
// I didn't write this check and I have no idea what it does
if (this.files && this.files[0]) {
// Btw, checking if the extension is supported
if (extension == 'png' || extension == 'gif' || extension == 'lpe') {
// If it's a Lospec Pixel Editor tm file, I load the project
if (extension == 'lpe') {
let file = this.files[0];
let reader = new FileReader();
// Getting all the data
reader.readAsText(file, "UTF-8");
// Converting the data to a json object and creating a new pixel (see _newPixel.js for more)
reader.onload = function (e) {
let dictionary = JSON.parse(e.target.result);
newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], dictionary['editorMode'], dictionary);
let mode = dictionary['editorMode'] == 'Advanced' ? 'Basic' : 'Advanced';
newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], mode, dictionary);
}
}
else {

View File

@ -1,8 +1,8 @@
//this is called when a user picks a file after selecting "load palette" from the new pixel dialogue
// TODO: load palette from .lpe file
document.getElementById('load-palette-browse-holder').addEventListener('change', function () {
if (this.files && this.files[0]) {
//make sure file is allowed filetype
var fileContentType = this.files[0].type;
if (fileContentType == 'image/png' || fileContentType == 'image/gif') {
@ -26,8 +26,6 @@ document.getElementById('load-palette-browse-holder').addEventListener('change',
var colorPalette = [];
var imagePixelData = loadPaletteContext.getImageData(0,0,this.width, this.height).data;
console.log(imagePixelData);
//loop through pixels looking for colors to add to palette
for (var i = 0; i < imagePixelData.length; i += 4) {
var color = '#'+rgbToHex(imagePixelData[i],imagePixelData[i + 1],imagePixelData[i + 2]);
@ -40,6 +38,8 @@ document.getElementById('load-palette-browse-holder').addEventListener('change',
palettes['Loaded palette'] = {};
palettes['Loaded palette'].colors = colorPalette;
setText('palette-button', 'Loaded palette');
setText('palette-button-splash', 'Loaded palette');
toggle('palette-menu-splash');
};
img.src = e.target.result;
};

View File

@ -22,7 +22,7 @@ window.addEventListener("mousedown", function (mouseEvent) {
else if (mouseEvent.altKey)
currentTool = tool.eyedropper;
else if (mouseEvent.target.className == 'drawingCanvas' &&
(currentTool.name == 'pencil' || currentTool.name == 'eraser' || currentTool.name == 'rectangle'))
(currentTool.name == 'pencil' || currentTool.name == 'eraser' || currentTool.name == 'rectangle' || currentTool.name === 'line'))
new HistoryStateEditCanvas();
else if (currentTool.name == 'moveselection') {
if (!cursorInSelectedArea() &&
@ -50,6 +50,10 @@ window.addEventListener("mousedown", function (mouseEvent) {
currentTool = tool.resizerectangle;
tool.rectangle.previousBrushSize = tool.rectangle.brushSize;
}
else if (currentTool.name == 'line' && mouseEvent.which == 3) {
currentTool = tool.resizeline;
tool.line.previousBrushSize = tool.line.brushSize;
}
if (currentTool.name == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas')
eyedropperPreview.style.display = 'block';
@ -70,6 +74,15 @@ window.addEventListener("mouseup", function (mouseEvent) {
currentLayer.closeOptionsMenu();
}
// If the user finished placing down a line, clear the tmp canvas and copy the data to the current layer
if (currentTool.name === "line") {
const tmpCanvas = document.getElementById('tmp-canvas');
currentLayer.context.drawImage(tmpCanvas, 0, 0);
const tmpContext = tmpCanvas.getContext('2d');
tmpContext.clearRect(0, 0, tmpCanvas.width, tmpCanvas.height);
}
if (!documentCreated || dialogueOpen || !currentLayer.isVisible || currentLayer.isLocked) return;
if (currentTool.name == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas') {
@ -134,7 +147,7 @@ window.addEventListener("mouseup", function (mouseEvent) {
mode = 'out';
}
changeZoom(layers[0], mode, getCursorPosition(mouseEvent));
changeZoom(mode, getCursorPosition(mouseEvent));
for (let i=1; i<layers.length; i++) {
layers[i].copyData(layers[0]);
@ -325,6 +338,21 @@ function draw (mouseEvent) {
tool.rectangle.moveBrushPreview(lastMouseClickPos);
currentTool.updateCursor();
}
else if (currentTool.name == 'resizeline' && dragging) {
//get new brush size based on x distance from original clicking location
var distanceFromClick = cursorLocation[0] - lastMouseClickPos[0];
//var roundingAmount = 20 - Math.round(distanceFromClick/10);
//this doesnt work in reverse... because... it's not basing it off of the brush size which it should be
var lineSizeChange = Math.round(distanceFromClick/10);
var newLineSize = tool.line.previousBrushSize + lineSizeChange;
//set the brush to the new size as long as its bigger than 1
tool.line.brushSize = Math.max(1, newLineSize);
//fix offset so the cursor stays centered
tool.line.moveBrushPreview(lastMouseClickPos);
currentTool.updateCursor();
}
else if (currentTool.name == 'rectselect') {
if (dragging && !isRectSelecting && mouseEvent.target.className == 'drawingCanvas') {
isRectSelecting = true;
@ -346,6 +374,19 @@ function draw (mouseEvent) {
updateMovePreview(getCursorPosition(mouseEvent));
}
}
else if (currentTool.name === "line") {
if (mouseEvent.target.className == 'drawingCanvas'|| mouseEvent.target.className == 'drawingCanvas') {
brushPreview.style.visibility = 'visible';
} else {
brushPreview.style.visibility = 'hidden';
}
if (dragging) {
if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') {
diagLine(lastMouseClickPos, zoom, cursorLocation);
}
}
currentLayer.updateLayerPreview();
}
}
}
@ -360,7 +401,7 @@ canvasView.addEventListener("wheel", function(mouseEvent){
}
// Changing zoom and position of the first layer
changeZoom(layers[0], mode, getCursorPosition(mouseEvent));
changeZoom(mode, getCursorPosition(mouseEvent));
for (let i=1; i<layers.length; i++) {
// Copying first layer's data into the other layers

View File

@ -6,9 +6,15 @@ var selectionCanceled = true;
var firstTimeMove = true;
// TODO: move with arrows
/** Updates the move preview so that is placed in the right position
*
* @param {*} mousePosition The position of the cursor
*/
function updateMovePreview(mousePosition) {
// I haven't canceled the selection
selectionCanceled = false;
// If it's the first time that I move the selection, I cut it from its original position
if (firstTimeMove) {
cutSelection(mousePosition);
}
@ -18,26 +24,34 @@ function updateMovePreview(mousePosition) {
lastMousePos = mousePosition;
// clear the entire tmp layer
TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height);
// put the image data with offset
// put the image data on the tmp layer with offset
TMPLayer.context.putImageData(
imageDataToMove,
Math.round(lastMousePos[0] / zoom) - imageDataToMove.width / 2,
Math.round(lastMousePos[1] / zoom) - imageDataToMove.height / 2);
lastMovePos = lastMousePos;
// Moving the the rectangular ants
moveSelection(lastMousePos[0] / zoom, lastMousePos[1] / zoom, imageDataToMove.width, imageDataToMove.height);
}
/** Ends a selection, meaning that it makes the changes definitive and creates the history states
*
*/
function endSelection() {
// Clearing the tmp (move preview) and vfx (ants) layers
TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height);
VFXLayer.context.clearRect(0, 0, VFXLayer.canvas.width, VFXLayer.canvas.height);
// Preparing an empty imageData with the size of the canvas
let cleanImageData = new ImageData(endX - startX, endY - startY);
// If I was moving something
if (imageDataToMove !== undefined) {
console.log("definito");
// Saving the current clipboard before editing it in order to merge it with the current layer
cleanImageData.data.set(imageDataToMove.data);
// I have to save the underlying data, so that the transparent pixels in the clipboard
// don't override the coloured pixels in the canvas
let underlyingImageData = currentLayer.context.getImageData(startX, startY, endX - startX, endY - startY);
for (let i=0; i<underlyingImageData.data.length; i+=4) {
@ -51,6 +65,7 @@ function endSelection() {
underlyingImageData.data[i+2], underlyingImageData.data[i+3]
];
// If the pixel of the clipboard is empty, but the one below it isn't, I use the pixel below
if (isPixelEmpty(currentMovePixel)) {
if (!isPixelEmpty(underlyingImageData)) {
imageDataToMove.data[i] = currentUnderlyingPixel[0];
@ -61,13 +76,16 @@ function endSelection() {
}
}
// If I moved the selection before confirming it
if (lastMovePos !== undefined) {
// I put it in the new position
currentLayer.context.putImageData(
imageDataToMove,
Math.round(lastMovePos[0] / zoom) - imageDataToMove.width / 2,
Math.round(lastMovePos[1] / zoom) - imageDataToMove.height / 2);
}
else {
// I put it in the same position
currentLayer.context.putImageData(
imageDataToMove,
copiedStartX,
@ -77,6 +95,7 @@ function endSelection() {
imageDataToMove.data.set(cleanImageData.data);
}
// Resetting all the flags
selectionCanceled = true;
isRectSelecting = false;
firstTimeMove = true;
@ -86,5 +105,6 @@ function endSelection() {
lastMovePos = undefined;
currentLayer.updateLayerPreview();
// Saving the history
new HistoryStateEditCanvas();
}

View File

@ -1,16 +1,32 @@
let firstPixel = true;
/** Creates a new, empty file
*
* @param {*} width Start width of the canvas
* @param {*} height Start height of the canvas
* @param {*} editorMode The editor mode chosen by the user (advanced or basic)
* @param {*} fileContent If fileContent != null, then the newPixel is being called from the open menu
*/
function newPixel (width, height, editorMode, fileContent = null) {
// Saving the editor mode
pixelEditorMode = editorMode;
// The palette is empty, at the beginning
currentPalette = [];
// If this is the first pixel I'm creating since the app has started
if (firstPixel) {
// I configure the layers elements
layerListEntry = layerList.firstElementChild;
// Creating the first layer
currentLayer = new Layer(width, height, canvas, layerListEntry);
currentLayer.canvas.style.zIndex = 2;
}
else {
// If it's not the first Pixel, I have to reset the app
// Deleting all the extra layers and canvases, leaving only one
let nLayers = layers.length;
for (let i=2; i < layers.length - nAppLayers; i++) {
let currentEntry = layers[i].menuEntry;
@ -38,10 +54,9 @@ function newPixel (width, height, editorMode, fileContent = null) {
// Setting up the current layer
layers[1] = new Layer(width, height, layers[1].canvas, layers[1].menuEntry);
currentLayer = layers[1];
currentLayer.canvas.style.zIndex = 2;
// Updating canvas size
// Updating canvas size to the new size
for (let i=0; i<nLayers; i++) {
layers[i].canvasSize = [width, height];
}
@ -58,7 +73,7 @@ function newPixel (width, height, editorMode, fileContent = null) {
// Pixel grid
pixelGrid = new Layer(width, height, pixelGridCanvas);
// Setting the general canvasSize
canvasSize = currentLayer.canvasSize;
if (firstPixel) {
@ -80,16 +95,23 @@ function newPixel (width, height, editorMode, fileContent = null) {
}
//add colors from selected palette
var selectedPalette = getText('palette-button');
var selectedPalette;
if (!firstPixel)
var selectedPalette = getText('palette-button');
else
var selectedPalette = getText('palette-button-splash');
// If the user selected a palette and isn't opening a file, I load the selected palette
if (selectedPalette != 'Choose a palette...' && fileContent == null) {
//if this palette isnt the one specified in the url, then reset the url
if (!palettes[selectedPalette].specified)
history.pushState(null, null, '/pixel-editor/app');
//fill the palette with specified palette
//fill the palette with specified colours
createColorPalette(palettes[selectedPalette].colors,true);
}
// Otherwise, I just generate 2 semirandom colours
else if (fileContent == null) {
//this wasn't a specified palette, so reset the url
history.pushState(null, null, '/pixel-editor/app');
@ -120,15 +142,21 @@ function newPixel (width, height, editorMode, fileContent = null) {
undoStates = [];
redoStates = [];
// Closing the "New Pixel dialogue"
closeDialogue();
// Updating the cursor of the current tool
currentTool.updateCursor();
// The user is now able to export the Pixel
document.getElementById('export-button').classList.remove('disabled');
documentCreated = true;
// This is not the first Pixel anymore
firstPixel = false;
// Now, if I opened an LPE file
if (fileContent != null) {
// I add every layer the file had in it
for (let i=0; i<fileContent['nLayers']; i++) {
let layerData = fileContent['layer' + i];
let layerImage = fileContent['layer' + i + 'ImageData'];
@ -166,10 +194,23 @@ function newPixel (width, height, editorMode, fileContent = null) {
deleteLayer(false);
}
// Applying the correct editor mode
if (pixelEditorMode == 'Basic') {
switchMode('Advanced', false);
switchMode(false);
}
else {
switchMode('Basic', false);
switchMode(false);
}
}
function newFromTemplate(preset, x, y) {
if (preset != '') {
setText('palette-button-splash', presets[preset].palette);
setText('palette-button', presets[preset].palette);
x = presets[preset].width;
y = presets[preset].height;
}
newPixel(x, y, pixelEditorMode == 'Advanced' ? 'Basic' : 'Advanced');
}

View File

@ -8,5 +8,5 @@ window.onload = function(){
newPixel(getValue('size-width'),getValue('size-height'), getValue('editor-mode'));
else
//otherwise show the new pixel dialog
showDialogue('new-pixel', false);
};
showDialogue('splash', false);
};

335
js/_paletteBlock.js Normal file
View File

@ -0,0 +1,335 @@
/** INIT is called when it shouldn't **/
let coloursList = document.getElementById("palette-list");
let rampMenu = document.getElementById("pb-ramp-options");
let pbRampDialogue = document.getElementById("pb-ramp-dialogue");
let currentSquareSize = coloursList.children[0].clientWidth;
let blockData = {blockWidth: 300, blockHeight: 320, squareSize: 40};
let isRampSelecting = false;
let ramps = [];
let currentSelection = {startIndex:0, endIndex:0, startCoords:[], endCoords: [], name: "", colour: "", label: null};
// Making the palette list sortable
new Sortable(document.getElementById("palette-list"), {
animation: 100,
onEnd: updateRampSelection
});
// Listening for the palette block resize
new ResizeObserver(updateSizeData).observe(coloursList.parentElement);
// Initializes the palette block
function pbInit() {
let simplePalette = document.getElementById("colors-menu");
let childCount = coloursList.childElementCount;
currentSquareSize = coloursList.children[0].clientWidth;
coloursList = document.getElementById("palette-list");
// Remove all the colours
for (let i=0; i<childCount; i++) {
coloursList.children[0].remove();
}
// Add all the colours from the simplepalette
for (let i=0; i<simplePalette.childElementCount-1; i++) {
addSingleColour(cssToHex(simplePalette.children[i].children[0].style.backgroundColor));
}
}
/** Listens for the mouse wheel, used to change the size of the squares in the palette list
*
*/
coloursList.parentElement.addEventListener("wheel", function (mouseEvent) {
// Only resize when pressing alt, used to distinguish between scrolling through the palette and
// resizing it
if (mouseEvent.altKey) {
resizeSquares(mouseEvent);
}
});
/** Tells whether a colour is in the palette or not
*
* @param {*} colour The colour to add
*/
function hasColour(colour) {
for (let i=0; i<coloursList.childElementCount; i++) {
let currentCol = coloursList.children[i].style.backgroundColor;
let currentHex = cssToHex(currentCol);
if (currentHex == colour) {
return true;
}
}
return false;
}
/** Adds a single colour to the palette
*
* @param {*} colour The colour to add
*/
function addSingleColour(colour) {
if (!hasColour(colour)) {
let li = document.createElement("li");
li.style.width = currentSquareSize + "px";
li.style.height = currentSquareSize + "px";
li.style.backgroundColor = colour;
li.addEventListener("mousedown", startRampSelection);
li.addEventListener("mouseup", endRampSelection);
li.addEventListener("mousemove", updateRampSelection);
li.addEventListener("onclick", endRampSelection);
coloursList.appendChild(li);
}
}
/** Adds all the colours currently selected in the colour picker
*
*/
function pbAddColours() {
let colours = getSelectedColours();
for (let i=0; i<colours.length; i++) {
addSingleColour(colours[i]);
}
}
/** Removes all the currently selected colours from the palette
*
*/
function pbRemoveColours() {
let startIndex = currentSelection.startIndex;
let endIndex = currentSelection.endIndex;
if (startIndex > endIndex) {
let tmp = startIndex;
startIndex = endIndex;
endIndex = tmp;
}
for (let i=startIndex; i<=endIndex; i++) {
coloursList.removeChild(coloursList.children[startIndex]);
}
clearBorders();
// TODO: make it so that ramps update correctly (change start and end indexes if necessary)
}
/** Starts selecting a ramp. Saves the data needed to draw the outline.
*
* @param {*} mouseEvent
*/
function startRampSelection(mouseEvent) {
if (mouseEvent.which == 3) {
let index = getElementIndex(mouseEvent.target);
isRampSelecting = true;
currentSelection.startIndex = index;
currentSelection.endIndex = index;
currentSelection.startCoords = getColourCoordinates(index);
currentSelection.endCoords = getColourCoordinates(index);
}
}
/** Updates the outline for the current selection.
*
* @param {*} mouseEvent
*/
function updateRampSelection(mouseEvent) {
if (mouseEvent != null && mouseEvent.which == 3) {
currentSelection.endIndex = getElementIndex(mouseEvent.target);
}
if (mouseEvent == null || mouseEvent.which == 3) {
let startCoords = getColourCoordinates(currentSelection.startIndex);
let endCoords = getColourCoordinates(currentSelection.endIndex);
let startIndex = currentSelection.startIndex;
let endIndex = currentSelection.endIndex;
if (currentSelection.startIndex > endIndex) {
let tmp = startIndex;
startIndex = endIndex;
endIndex = tmp;
tmp = startCoords;
startCoords = endCoords;
endCoords = tmp;
}
clearBorders();
for (let i=startIndex; i<=endIndex; i++) {
let currentSquare = coloursList.children[i];
let currentCoords = getColourCoordinates(i);
let borderStyle = "3px solid white";
let bordersToSet = [];
// Deciding which borders to use to make the outline
if (i == 0 || i == startIndex) {
bordersToSet.push("border-left");
}
if (currentCoords[1] == startCoords[1] || ((currentCoords[1] == startCoords[1] + 1)) && currentCoords[0] < startCoords[0]) {
bordersToSet.push("border-top");
}
if (currentCoords[1] == endCoords[1] || ((currentCoords[1] == endCoords[1] - 1)) && currentCoords[0] > endCoords[0]) {
bordersToSet.push("border-bottom");
}
if ((i == coloursList.childElementCount - 1) || (currentCoords[0] == Math.floor(blockData.blockWidth / blockData.squareSize) - 1)
|| i == endIndex) {
bordersToSet.push("border-right");
}
if (bordersToSet != []) {
currentSquare.style["box-sizing"] = "border-box";
for (let i=0; i<bordersToSet.length; i++) {
currentSquare.style[bordersToSet[i]] = borderStyle;
}
}
}
}
}
/** Removes all the borders from all the squares. The borders are cleared only for the
* current selection, so every border that is not white is kept.
*
*/
function clearBorders() {
for (let i=0; i<coloursList.childElementCount; i++) {
coloursList.children[i].style["border-top"] = "none";
coloursList.children[i].style["border-left"] = "none";
coloursList.children[i].style["border-right"] = "none";
coloursList.children[i].style["border-bottom"] = "none";
}
}
/** Ends the current selection, opens the ramp menu
*
* @param {*} mouseEvent
*/
function endRampSelection(mouseEvent) {
let col;
if (currentSelection.startCoords.length == 0) {
currentSelection.endIndex = getElementIndex(mouseEvent.target);
currentSelection.startIndex = currentSelection.endIndex;
currentSelection.startCoords = getColourCoordinates(currentSelection.startIndex);
}
// I'm not selecting a ramp anymore
isRampSelecting = false;
// Setting the end coordinates
currentSelection.endCoords = getColourCoordinates(getElementIndex(mouseEvent.target));
// Setting the colour in the colour picker
col = cssToHex(coloursList.children[currentSelection.startIndex].style.backgroundColor);
updatePickerByHex(col);
updateSlidersByHex(col);
updateMiniPickerColour();
updateRampSelection();
currentSelection.startCoords = [];
}
function closeAllSubmenus() {
let menus = document.getElementsByClassName("pb-submenu");
for (let i=0; i<menus.length; i++) {
menus[i].style.display = "none";
}
}
/** Updates the current data about the size of the palette list (height, width and square size).
* It also updates the outline after doing so.
*
*/
function updateSizeData() {
blockData.blockHeight = coloursList.parentElement.clientHeight;
blockData.blockWidth = coloursList.parentElement.clientWidth;
blockData.squareSize = coloursList.children[0].clientWidth;
updateRampSelection();
}
/** Gets the colour coordinates relative to the colour list seen as a matrix. Coordinates
* start from the top left angle.
*
* @param {*} index The index of the colour in the list seen as a linear array
*/
function getColourCoordinates(index) {
let yIndex = Math.floor(index / Math.floor(blockData.blockWidth / blockData.squareSize));
let xIndex = Math.floor(index % Math.floor(blockData.blockWidth / blockData.squareSize));
return [xIndex, yIndex];
}
/** Returns the index of the element in the colour list
*
* @param {*} element The element of which we need to get the index
*/
function getElementIndex(element) {
for (let i=0; i<coloursList.childElementCount; i++) {
if (element == coloursList.children[i]) {
return i;
}
}
}
/** Resizes the squares depending on the scroll amount (only resizes if the user is
* also holding alt)
*
* @param {*} mouseEvent
*/
function resizeSquares(mouseEvent) {
let amount = mouseEvent.deltaY > 0 ? -5 : 5;
currentSquareSize += amount;
for (let i=0; i<coloursList.childElementCount; i++) {
let currLi = coloursList.children[i];
currLi.style["box-sizing"] = "content-box";
currLi.style.width = currLi.clientWidth + amount + "px";
currLi.style.height = currLi.clientHeight + amount + "px";
}
updateSizeData();
}
/** Converts a CSS colour eg rgb(x,y,z) to a hex string
*
* @param {*} rgb
*/
function cssToHex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
function pbAddToSimplePalette() {
let simplePalette = document.getElementById("colors-menu");
let childCount = simplePalette.childElementCount;
// Removing all the colours
for (let i=0; i<childCount-1; i++) {
simplePalette.removeChild(simplePalette.children[0]);
}
// Adding the new ones
for (let i=0; i<coloursList.childElementCount; i++) {
let col = coloursList.children[i].style.backgroundColor;
if (col.includes("rgb")) {
addColor(cssToHex(col));
}
else {
addColor(col);
}
}
}

View File

@ -2,56 +2,83 @@
Object.keys(palettes).forEach(function(paletteName,index) {
var palettesMenu = document.getElementById('palette-menu');
var splashPalettes = document.getElementById('palette-menu-splash');
//create button
var button = document.createElement('button');
button.appendChild(document.createTextNode(paletteName));
//insert new element
palettesMenu.appendChild(button);
//if the palette was specified by the user, change the dropdown to it
if (palettes[paletteName].specified == true) {
setText('palette-button', paletteName);
setText('palette-button-splash', paletteName)
//Show empty palette option
document.getElementById('no-palette-button').style.display = 'block';
}
on('click', button, function() {
var buttonEvent = function() {
//hide the dropdown menu
deselect('palette-menu');
deselect('palette-button');
deselect('palette-menu-splash');
deselect('palette-button-splash');
//show empty palette option
document.getElementById('no-palette-button').style.display = 'block';
//set the text of the dropdown to the newly selected preset
setText('palette-button', paletteName);
});
setText('palette-button-splash', paletteName);
};
on('click', button, buttonEvent);
//insert new element
palettesMenu.appendChild(button);
// Making a copy for the splash page too
var copyButton = button.cloneNode(true);
// Attaching the same event
on('click', copyButton, buttonEvent);
// Appending it to the splash palette menu
splashPalettes.appendChild(copyButton);
});
//select no palette
on('click', 'no-palette-button', function () {
var noPaletteButtonClickEvent = function () {
document.getElementById('no-palette-button').style.display = 'none';
setText('palette-button', 'Choose a palette...');
});
}
//select load palette
on('click', 'load-palette-button', function () {
var loadPaletteButtonEvent = function () {
document.getElementById('load-palette-browse-holder').click();
});
}
on('click', 'palette-button', function (e){
var clickPaletteButtonEvent = function (e){
toggle('palette-button');
toggle('palette-menu');
deselect('preset-button');
deselect('preset-menu');
// Splash version
toggle('palette-button-splash');
toggle('palette-menu-splash');
e.stopPropagation();
});
}
//select no palette
on('click', 'no-palette-button', noPaletteButtonClickEvent);
//select load palette
on('click', 'load-palette-button', loadPaletteButtonEvent);
//select load palette
on('click', 'load-palette-button-splash', loadPaletteButtonEvent);
// Palette menu click
on('click', 'palette-button', clickPaletteButtonEvent);
on('click', 'palette-button-splash', clickPaletteButtonEvent);
on('click', 'new-pixel', function (){
deselect('editor-mode-menu');
@ -59,4 +86,8 @@ on('click', 'new-pixel', function (){
deselect('preset-menu');
deselect('palette-button');
deselect('palette-menu');
});
// Splash version
deselect('palette-button-splash');
deselect('palette-menu-splash');
});

View File

@ -1,3 +1,10 @@
/***********MISCELLANEOUS UTILITY FUNCTIONS**************/
/** Merges topLayer onto belowLayer
*
* @param {*} belowLayer The layer on the bottom of the layer stack
* @param {*} topLayer The layer on the top of the layer stack
*/
function mergeLayers(belowLayer, topLayer) {
// Copying the above content on the layerBelow
let belowImageData = belowLayer.getImageData(0, 0, canvas.width, canvas.height);
@ -24,10 +31,19 @@ function mergeLayers(belowLayer, topLayer) {
}
}
// Putting the top data into the belowdata
belowLayer.putImageData(toMergeImageData, 0, 0);
}
/** Used to programmatically create an input event
*
* @param {*} keyCode KeyCode of the key to press
* @param {*} ctrl Is ctrl pressed?
* @param {*} alt Is alt pressed?
* @param {*} shift Is shift pressed?
*/
function simulateInput(keyCode, ctrl, alt, shift) {
// I just copy pasted this from stack overflow lol please have mercy
let keyboardEvent = document.createEvent("KeyboardEvent");
let initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";
@ -46,38 +62,62 @@ function simulateInput(keyCode, ctrl, alt, shift) {
document.dispatchEvent(keyboardEvent);
}
/** Tells if a pixel is empty (has alpha = 0)
*
* @param {*} pixel
*/
function isPixelEmpty(pixel) {
if (pixel == null || pixel === undefined) {
return false;
}
if ((pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0) || pixel[3] == 0) {
// If the alpha channel is 0, the current pixel is empty
if (pixel[3] == 0) {
return true;
}
return false;
}
/** Tells if element is a child of an element with class className
*
* @param {*} element
* @param {*} className
*/
function isChildOfByClass(element, className) {
// Getting the element with class className
while (element != null && element.classList != null && !element.classList.contains(className)) {
element = element.parentElement;
}
// If that element exists and its class is the correct one
if (element != null && element.classList != null && element.classList.contains(className)) {
// Then element is a chld of an element with class className
return true;
}
return false;
}
/** Gets the eyedropped colour (the colour of the pixel pointed by the cursor when the user is using the eyedropper).
* It takes the colour of the canvas with the biggest z-index, basically the one the user can see, since it doesn't
* make much sense to sample a colour which is hidden behind a different layer
*
* @param {*} cursorLocation The position of the cursor
*/
function getEyedropperColor(cursorLocation) {
// Making sure max will take some kind of value
let max = -1;
// Using tmpColour to sample the sprite
let tmpColour;
// Returned colour
let selectedColor;
for (let i=1; i<layers.length; i++) {
// Getting the colour of the pixel in the cursorLocation
tmpColour = layers[i].context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1).data;
// If it's not empty, I check if it's on the top of the previous colour
if (layers[i].canvas.style.zIndex > max || isPixelEmpty(selectedColor) || selectedColor === undefined) {
max = layers[i].canvas.style.zIndex;
@ -87,6 +127,7 @@ function getEyedropperColor(cursorLocation) {
}
}
// If the final colour was empty, I return black
if (isPixelEmpty(tmpColour) && selectedColor === undefined) {
selectedColor = [0, 0, 0];
}
@ -94,7 +135,12 @@ function getEyedropperColor(cursorLocation) {
return selectedColor;
}
/** Gets the absolute position of the element (position on the screen)
*
* @param {*} element The element of which we have to get the position
*/
function getElementAbsolutePosition(element) {
// Probably copy pasted this from stack overflow too, if not I don't recall how it works
let curleft = curtop = 0;
if (element.offsetParent) {
@ -107,9 +153,15 @@ function getElementAbsolutePosition(element) {
return [curleft,curtop];
}
/** Nearest neighbor algorithm to scale a sprite
*
* @param {*} src The source imageData
* @param {*} dst The destination imageData
*/
function nearestNeighbor (src, dst) {
let pos = 0
// Just applying the nearest neighbor algorithm
for (let y = 0; y < dst.height; y++) {
for (let x = 0; x < dst.width; x++) {
const srcX = Math.floor(x * src.width / dst.width)
@ -125,7 +177,14 @@ function nearestNeighbor (src, dst) {
}
}
/** Bilinear interpolation used to scale a sprite
*
* @param {*} src The source imageData
* @param {*} dst The destination imageData
*/
function bilinearInterpolation (src, dst) {
// Applying the bilinear interpolation algorithm
function interpolate (k, kMin, kMax, vMin, vMax) {
return Math.round((k - kMin) * vMax + (kMax - k) * vMin)
}
@ -167,14 +226,21 @@ function bilinearInterpolation (src, dst) {
}
}
/** Resizes an imageData depending on the algorithm and on the new width and height
*
* @param {*} image The imageData to scale
* @param {*} width The new width of the imageData
* @param {*} height The new height of the imageData
* @param {*} algorithm Scaling algorithm chosen by the user in the dialogue
*/
function resizeImageData (image, width, height, algorithm) {
algorithm = algorithm || 'bilinear-interpolation'
let resize
let resize;
switch (algorithm) {
case 'nearest-neighbor': resize = nearestNeighbor; break
case 'bilinear-interpolation': resize = bilinearInterpolation; break
default: throw new Error(`Unknown algorithm: ${algorithm}`)
default: return image;
}
const result = new ImageData(width, height)
@ -184,10 +250,21 @@ function resizeImageData (image, width, height, algorithm) {
return result
}
/** Gets the position in (x, y) format of the pixel with index "index"
*
* @param {*} index The index of the pixel of which we need the (x, y) position
*/
function getPixelPosition(index) {
let linearIndex = index / 4;
let x = linearIndex % layers[0].canvasSize[0];
let y = Math.floor(linearIndex / layers[0].canvasSize[0]);
return [Math.ceil(x), Math.ceil(y)];
}
/** Sets isDragging to false, used when the user interacts with sortable lists
*
*/
function makeIsDraggingFalse(event) {
dragging = false;
}

View File

@ -1,27 +1,44 @@
// Start colour of the pixel grid (can be changed in the preferences)
let pixelGridColor = "#0000FF";
// Distance between one line and another in HTML pixels
let lineDistance = 12;
// The grid is not visible at the beginning
let pixelGridVisible = false;
// Saving the canvas containing the pixel grid
pixelGridCanvas = document.getElementById("pixel-grid");
/** Shows or hides the pixel grid depening on its current visibility
* (triggered by the show pixel grid button in the top menu)
*
*/
function togglePixelGrid(event) {
// Getting the button because I have to change its text
let button = document.getElementById("toggle-pixelgrid-button");
// Toggling the state
pixelGridVisible = !pixelGridVisible;
// If it was visible, I hide it
if (pixelGridVisible) {
button.innerHTML = "Hide pixel grid";
pixelGridCanvas.style.display = "inline-block";
}
// Otherwise, I show it
else {
button.innerHTML = "Show pixel grid";
pixelGridCanvas.style.display = "none";
}
}
/** Fills the pixelGridCanvas with the pixelgrid
*
*/
function fillPixelGrid() {
let context = pixelGridCanvas.getContext("2d");
let originalSize = layers[0].canvasSize;
// The pixelGridCanvas is lineDistance times bigger so that the lines don't take 1 canvas pixel
// (which would cover the whole canvas with the pixelGridColour), but they take 1/lineDistance canvas pixels
pixelGridCanvas.width = originalSize[0] * lineDistance;
pixelGridCanvas.height = originalSize[1] * lineDistance;

View File

@ -1,4 +1,4 @@
//prests
//presets
var presets = {
'Gameboy Color': {
width: 240,

View File

@ -4,6 +4,10 @@ let startY;
let endX;
let endY;
/** Starts the selection: saves the canvas, sets the start coordinates
*
* @param {*} mouseEvent
*/
function startRectSelection(mouseEvent) {
// Saving the canvas
new HistoryStateEditCanvas();
@ -35,6 +39,10 @@ function startRectSelection(mouseEvent) {
selectionCanceled = false;
}
/** Updates the selection
*
* @param {*} mouseEvent
*/
function updateRectSelection(mouseEvent) {
let pos = getCursorPosition(mouseEvent);
@ -42,6 +50,10 @@ function updateRectSelection(mouseEvent) {
drawRect(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5);
}
/** Ends the selection: sets the end coordiantes
*
* @param {*} mouseEvent
*/
function endRectSelection(mouseEvent) {
// Getting the end position
let currentPos = getCursorPosition(mouseEvent);
@ -72,6 +84,10 @@ function endRectSelection(mouseEvent) {
currentTool.updateCursor();
}
/** Cuts the selection from its canvas and puts it in the tmp layer so it can be moved
*
* @param {*} mousePosition
*/
function cutSelection(mousePosition) {
// Getting the selected pixels
imageDataToMove = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1);
@ -79,10 +95,13 @@ function cutSelection(mousePosition) {
currentLayer.context.clearRect(startX - 0.5, startY - 0.5, endX - startX + 1, endY - startY + 1);
// Moving those pixels from the current layer to the tmp layer
TMPLayer.context.putImageData(imageDataToMove, startX + 1, startY);
//originalDataPosition = [currentPos[0], currentPos[1]];
}
/** Draws a dashed rectangle representing the selection
*
* @param {*} x Current end x coordinate of the selection
* @param {*} y Current end y coordinate of the selection
*/
function drawRect(x, y) {
// Getting the vfx context
let vfxContext = VFXCanvas.getContext('2d');
@ -106,10 +125,13 @@ function applyChanges() {
// Checks whether the pointer is inside the selected area or not
function cursorInSelectedArea() {
// Getting the cursor position
let cursorPos = getCursorPosition(currentMouseEvent);
// Getting the coordinates relatively to the canvas
let x = cursorPos[0] / zoom;
let y = cursorPos[1] / zoom;
// This is to avoid rightX or topY being less than leftX or bottomY
let leftX = Math.min(startX, endX);
let rightX = Math.max(startX, endX);
let topY = Math.max(startY, endY);
@ -126,6 +148,13 @@ function cursorInSelectedArea() {
return false;
}
/** Moves the rect ants to the specified position
*
* @param {*} x X coordinate of the rect ants
* @param {*} y Y coordinat of the rect ants
* @param {*} width Width of the selection
* @param {*} height Height of the selectin
*/
function moveSelection(x, y, width, height) {
// Getting the vfx context
let vfxContext = VFXCanvas.getContext('2d');
@ -135,6 +164,7 @@ function moveSelection(x, y, width, height) {
vfxContext.lineWidth = 1;
vfxContext.setLineDash([4]);
// Fixing the coordinates
startX = Math.round(Math.round(x) - 0.5 - Math.round(width / 2)) + 0.5;
startY = Math.round(Math.round(y) - 0.5 - Math.round(height / 2)) + 0.5;
endX = startX + Math.round(width);

View File

@ -1,19 +1,26 @@
// Saving the empty rect svg
var emptySVG = document.getElementById("empty-button-svg");
// and the full rect svg so that I can change them when the user changes rect modes
var fullSVG = document.getElementById("full-button-svg");
// The start mode is empty rectangle
var drawMode = 'empty';
// I'm not drawing a rectangle at the beginning
var isDrawingRect = false;
// Rect coordinates
let startRectX;
let startRectY;
let endRectX;
let endRectY;
/** Starts drawing the rect, saves the start coordinates
*
* @param {*} mouseEvent
*/
function startRectDrawing(mouseEvent) {
// Putting the vfx layer on top of everything
VFXCanvas.style.zIndex = MAX_Z_INDEX;
VFXCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex, 10) + 1;;
// Updating flag
isDrawingRect = true;
@ -25,13 +32,22 @@ function startRectDrawing(mouseEvent) {
drawRectangle(startRectX, startRectY);
}
/** Updates the rectangle preview depending on the position of the mouse
*
* @param {*} mouseEvent The mouseEvent from which we'll get the mouse position
*/
function updateRectDrawing(mouseEvent) {
let pos = getCursorPosition(mouseEvent);
// Drawing the rect
// Drawing the rect at the right position
drawRectangle(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5);
}
/** Finishes drawing the rect, decides the end coordinates and moves the preview rectangle to the
* current layer
*
* @param {*} mouseEvent event from which we'll get the mouse position
*/
function endRectDrawing(mouseEvent) {
// Getting the end position
let currentPos = getCursorPosition(mouseEvent);
@ -61,14 +77,17 @@ function endRectDrawing(mouseEvent) {
endRectX -= 0.5;
startRectX -= 0.5;
// Setting the correct linewidth and colour
currentLayer.context.lineWidth = tool.rectangle.brushSize;
currentLayer.context.fillStyle = currentGlobalColor;
// Drawing the rect using 4 lines
line(startRectX, startRectY, endRectX, startRectY, tool.rectangle.brushSize);
line(endRectX, startRectY, endRectX, endRectY, tool.rectangle.brushSize);
line(endRectX, endRectY, startRectX, endRectY, tool.rectangle.brushSize);
line(startRectX, endRectY, startRectX, startRectY, tool.rectangle.brushSize);
// If I have to fill it, I do so
if (drawMode == 'fill') {
currentLayer.context.fillRect(startRectX, startRectY, endRectX - startRectX, endRectY - startRectY);
}
@ -77,6 +96,12 @@ function endRectDrawing(mouseEvent) {
vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height);
}
/** Draws a rectangle with end coordinates given by x and y on the VFX layer (draws
* the preview for the rectangle tool)
*
* @param {*} x The current end x of the rectangle
* @param {*} y The current end y of the rectangle
*/
function drawRectangle(x, y) {
// Getting the vfx context
let vfxContext = VFXCanvas.getContext("2d");
@ -98,10 +123,12 @@ function drawRectangle(x, y) {
}
vfxContext.setLineDash([]);
vfxContext.stroke();
}
/** Sets the correct tool icon depending on its mode
*
*/
function setRectToolSvg() {
if (drawMode == 'empty') {
emptySVG.setAttribute('display', 'visible');
@ -114,5 +141,5 @@ function setRectToolSvg() {
}
function applyChanges() {
VFXCanvas.style.zIndex = MIN_Z_INDEX;
//VFXCanvas.style.zIndex = MIN_Z_INDEX;
}

View File

@ -1,16 +1,31 @@
let resizeCanvasContainer = document.getElementById("resize-canvas");
let rcPivot = "middle";
let currentPivotObject;
let borders = {left: 0, right: 0, top: 0, bottom: 0};
/* This scripts contains all the code used to handle the canvas resizing */
// Resize canvas pop up window
let resizeCanvasContainer = document.getElementById("resize-canvas");
// Start pivot
let rcPivot = "middle";
// Selected pivot button
let currentPivotObject;
// Border offsets
let rcBorders = {left: 0, right: 0, top: 0, bottom: 0};
/** Opens the canvas resize window
*
*/
function openResizeCanvasWindow() {
// Initializes the inputs
initResizeCanvasInputs();
showDialogue('resize-canvas');
}
/** Initializes the canvas resizing input
*
*/
function initResizeCanvasInputs() {
// Getting the pivot buttons
let buttons = document.getElementsByClassName("pivot-button");
// Adding the event handlers for them
for (let i=0; i<buttons.length; i++) {
buttons[i].addEventListener("click", changePivot);
if (buttons[i].getAttribute("value").includes("middle")) {
@ -33,13 +48,21 @@ function initResizeCanvasInputs() {
console.log("Pivot selezionato: " + currentPivotObject);
}
/** Fired when a border offset is changed: it updates the width and height
*
* @param {*} event
*/
function rcChangedBorder(event) {
rcUpdateBorders();
document.getElementById("rc-width").value = parseInt(layers[0].canvasSize[0]) + borders.left + borders.right;
document.getElementById("rc-height").value = parseInt(layers[0].canvasSize[1]) + borders.top + borders.bottom;
document.getElementById("rc-width").value = parseInt(layers[0].canvasSize[0]) + rcBorders.left + rcBorders.right;
document.getElementById("rc-height").value = parseInt(layers[0].canvasSize[1]) + rcBorders.top + rcBorders.bottom;
}
/** Fired when width or height are changed: updates the border offsets
*
* @param {*} event
*/
function rcChangedSize(event) {
let widthOffset = Math.abs(document.getElementById("rc-width").value) - layers[0].canvasSize[0];
let heightOffset = Math.abs(document.getElementById("rc-height").value) - layers[0].canvasSize[1];
@ -54,12 +77,19 @@ function rcChangedSize(event) {
document.getElementById("rc-border-top").value = top;
document.getElementById("rc-border-bottom").value = bottom;
borders.left = left;
borders.right = right;
borders.top = top;
borders.bottom = bottom;
rcBorders.left = left;
rcBorders.right = right;
rcBorders.top = top;
rcBorders.bottom = bottom;
}
/** Resizes the canvas
*
* @param {*} event The event that triggered the canvas resizing
* @param {*} size The new size of the picture
* @param {*} customData Used when ctrl+z ing
* @param {*} saveHistory Should I save the history? You shouldn't if you're undoing
*/
function resizeCanvas(event, size, customData, saveHistory = true) {
let imageDatas = [];
let leftOffset = 0;
@ -87,8 +117,8 @@ function resizeCanvas(event, size, customData, saveHistory = true) {
if (saveHistory) {
// Saving history
new HistoryStateResizeCanvas(
{x: parseInt(layers[0].canvasSize[0]) + borders.left + borders.right,
y: parseInt(layers[0].canvasSize[1]) + borders.top + borders.bottom},
{x: parseInt(layers[0].canvasSize[0]) + rcBorders.left + rcBorders.right,
y: parseInt(layers[0].canvasSize[1]) + rcBorders.top + rcBorders.bottom},
{x: layers[0].canvasSize[0],
y: layers[0].canvasSize[1]},
@ -100,8 +130,8 @@ function resizeCanvas(event, size, customData, saveHistory = true) {
// Resize the canvases
for (let i=0; i<layers.length; i++) {
layers[i].canvasSize[0] = parseInt(layers[i].canvasSize[0]) + borders.left + borders.right;
layers[i].canvasSize[1] = parseInt(layers[i].canvasSize[1]) + borders.top + borders.bottom;
layers[i].canvasSize[0] = parseInt(layers[i].canvasSize[0]) + rcBorders.left + rcBorders.right;
layers[i].canvasSize[1] = parseInt(layers[i].canvasSize[1]) + rcBorders.top + rcBorders.bottom;
layers[i].canvas.width = layers[i].canvasSize[0];
layers[i].canvas.height = layers[i].canvasSize[1];
@ -121,42 +151,43 @@ function resizeCanvas(event, size, customData, saveHistory = true) {
topOffset = 0;
break;
case 'top':
leftOffset = (borders.left + borders.right) / 2;
leftOffset = (rcBorders.left + rcBorders.right) / 2;
topOffset = 0;
break;
case 'topright':
leftOffset = borders.left + borders.right;
leftOffset = rcBorders.left + rcBorders.right;
topOffset = 0;
break;
case 'left':
leftOffset = 0;
topOffset = (borders.top + borders.bottom) / 2;
topOffset = (rcBorders.top + rcBorders.bottom) / 2;
break;
case 'middle':
leftOffset = (borders.left + borders.right) / 2;
topOffset = (borders.top + borders.bottom) / 2;
leftOffset = (rcBorders.left + rcBorders.right) / 2;
topOffset = (rcBorders.top + rcBorders.bottom) / 2;
break;
case 'right':
leftOffset = borders.left + borders.right;
topOffset = (borders.top + borders.bottom) / 2;
leftOffset = rcBorders.left + rcBorders.right;
topOffset = (rcBorders.top + rcBorders.bottom) / 2;
break;
case 'bottomleft':
leftOffset = 0;
topOffset = borders.top + borders.bottom;
topOffset = rcBorders.top + rcBorders.bottom;
break;
case 'bottom':
leftOffset = (borders.left + borders.right) / 2;
topOffset = borders.top + borders.bottom;
leftOffset = (rcBorders.left + rcBorders.right) / 2;
topOffset = rcBorders.top + rcBorders.bottom;
break;
case 'bottomright':
leftOffset = borders.left + borders.right;
topOffset = borders.top + borders.bottom;
leftOffset = rcBorders.left + rcBorders.right;
topOffset = rcBorders.top + rcBorders.bottom;
break;
default:
console.log('Pivot does not exist, please report an issue at https://github.com/lospec/pixel-editor');
break;
}
// Putting all the data for each layer with the right offsets (decided by the pivot)
for (let i=0; i<layers.length; i++) {
if (layers[i].menuEntry != null) {
if (customData == undefined) {
@ -176,6 +207,11 @@ function resizeCanvas(event, size, customData, saveHistory = true) {
closeDialogue();
}
/** Trims the canvas so tat the sprite is perfectly contained in it
*
* @param {*} event
* @param {*} saveHistory Should I save the history? You shouldn't if you're undoing
*/
function trimCanvas(event, saveHistory) {
let minY = Infinity;
let minX = Infinity;
@ -189,6 +225,7 @@ function trimCanvas(event, saveHistory) {
rcPivot = "topleft";
console.log("debug");
// Computing the min and max coordinates in which there's a non empty pixel
for (let i=1; i<layers.length - nAppLayers; i++) {
let imageData = layers[i].context.getImageData(0, 0, layers[0].canvasSize[0], layers[0].canvasSize[1]);
let pixelPosition;
@ -226,10 +263,11 @@ function trimCanvas(event, saveHistory) {
minY = layers[0].canvasSize[1] - minY;
maxY = layers[0].canvasSize[1] - maxY;
borders.right = (maxX - layers[0].canvasSize[0]) + 1;
borders.left = -minX;
borders.top = maxY - layers[0].canvasSize[1] + 1;
borders.bottom = -minY;
// Setting the borders coherently with the values I've just computed
rcBorders.right = (maxX - layers[0].canvasSize[0]) + 1;
rcBorders.left = -minX;
rcBorders.top = maxY - layers[0].canvasSize[1] + 1;
rcBorders.bottom = -minY;
// Saving the data
for (let i=0; i<layers.length; i++) {
@ -241,11 +279,12 @@ function trimCanvas(event, saveHistory) {
console.log(imageDatas);
//console.log("sx: " + borders.left + "dx: " + borders.right + "top: " + borders.top + "btm: " + borders.bottom);
document.getElementById("rc-border-left").value = borders.left;
document.getElementById("rc-border-right").value = borders.right;
document.getElementById("rc-border-top").value = borders.top;
document.getElementById("rc-border-bottom").value = borders.bottom;
document.getElementById("rc-border-left").value = rcBorders.left;
document.getElementById("rc-border-right").value = rcBorders.right;
document.getElementById("rc-border-top").value = rcBorders.top;
document.getElementById("rc-border-bottom").value = rcBorders.bottom;
// Resizing the canvas with the decided border offsets
resizeCanvas(null, null, imageDatas.slice(), historySave);
// Resetting the previous pivot
rcPivot = prevPivot;
@ -253,16 +292,16 @@ function trimCanvas(event, saveHistory) {
function rcUpdateBorders() {
// Getting input
borders.left = document.getElementById("rc-border-left").value;
borders.right = document.getElementById("rc-border-right").value;
borders.top = document.getElementById("rc-border-top").value;
borders.bottom = document.getElementById("rc-border-bottom").value;
rcBorders.left = document.getElementById("rc-border-left").value;
rcBorders.right = document.getElementById("rc-border-right").value;
rcBorders.top = document.getElementById("rc-border-top").value;
rcBorders.bottom = document.getElementById("rc-border-bottom").value;
// Validating input
borders.left == "" ? borders.left = 0 : borders.left = Math.round(parseInt(borders.left));
borders.right == "" ? borders.right = 0 : borders.right = Math.round(parseInt(borders.right));
borders.top == "" ? borders.top = 0 : borders.top = Math.round(parseInt(borders.top));
borders.bottom == "" ? borders.bottom = 0 : borders.bottom = Math.round(parseInt(borders.bottom));
rcBorders.left == "" ? rcBorders.left = 0 : rcBorders.left = Math.round(parseInt(rcBorders.left));
rcBorders.right == "" ? rcBorders.right = 0 : rcBorders.right = Math.round(parseInt(rcBorders.right));
rcBorders.top == "" ? rcBorders.top = 0 : rcBorders.top = Math.round(parseInt(rcBorders.top));
rcBorders.bottom == "" ? rcBorders.bottom = 0 : rcBorders.bottom = Math.round(parseInt(rcBorders.bottom));
}
function changePivot(event) {

View File

@ -1,15 +1,26 @@
/* This scripts contains all the code used to handle the sprite scaling */
// Should I keep the sprite ratio?
let keepRatio = true;
// Used to store the current ratio
let currentRatio;
// The currenty selected resizing algorithm (nearest-neighbor or bilinear-interpolation)
let currentAlgo = 'nearest-neighbor';
// Current resize data
let data = {width: 0, height: 0, widthPercentage: 100, heightPercentage: 100};
// Start resize data
let startData = {width: 0, height:0, widthPercentage: 100, heightPercentage: 100};
/** Opens the sprite resizing window
*
*/
function openResizeSpriteWindow() {
// Inits the sprie resize inputs
initResizeSpriteInputs();
// Computing the current ratio
currentRatio = layers[0].canvasSize[0] / layers[0].canvasSize[1];
// Initializing the input fields
data.width = layers[0].canvasSize[0];
data.height = layers[1].canvasSize[1];
@ -18,9 +29,13 @@ function openResizeSpriteWindow() {
startData.heightPercentage = 100;
startData.widthPercentage = 100;
// Opening the pop up now that it's ready
showDialogue('resize-sprite');
}
/** Initalizes the input values and binds the elements to their events
*
*/
function initResizeSpriteInputs() {
document.getElementById("rs-width").value = layers[0].canvasSize[0];
document.getElementById("rs-height").value = layers[0].canvasSize[1];
@ -40,11 +55,21 @@ function initResizeSpriteInputs() {
document.getElementById("resize-algorithm-combobox").addEventListener("change", changedAlgorithm);
}
/** Resizes (scales) the sprite
*
* @param {*} event
* @param {*} ratio Keeps infos about the x ratio and y ratio
*/
function resizeSprite(event, ratio) {
// Old data
let oldWidth, oldHeight;
// New data
let newWidth, newHeight;
// Current imageDatas
let rsImageDatas = [];
// Index that will be used a few lines below
let layerIndex = 0;
// Copy of the imageDatas that will be stored in the history
let imageDatasCopy = [];
oldWidth = layers[0].canvasSize[0];
@ -70,6 +95,7 @@ function resizeSprite(event, ratio) {
break;
}
// Computing newWidth and newHeight
if (ratio == null) {
newWidth = data.width;
newHeight = data.height;
@ -88,8 +114,11 @@ function resizeSprite(event, ratio) {
}
}
// ratio is null when the user is undoing
if (ratio == null) {
// Copying the image data
imageDatasCopy = rsImageDatas.slice();
// Saving the history
new HistoryStateResizeSprite(newWidth / oldWidth, newHeight / oldHeight, currentAlgo, imageDatasCopy);
}
@ -124,6 +153,12 @@ function resizeSprite(event, ratio) {
closeDialogue();
}
/* Trust me, the math for the functions below works. If you want to optimize them feel free to have a look, though */
/** Fired when the input field for width is changed. Updates th othe input fields consequently
*
* @param {*} event
*/
function changedWidth(event) {
let oldValue = data.width;
let ratio;
@ -150,6 +185,10 @@ function changedWidth(event) {
document.getElementById("rs-width-percentage").value = newWidthPerc;
}
/**Fired when the input field for width is changed. Updates the other input fields consequently
*
* @param {*} event
*/
function changedHeight(event) {
let oldValue = 100;
let ratio;
@ -176,6 +215,10 @@ function changedHeight(event) {
data.heightPercentage = newHeightPerc;
}
/**Fired when the input field for width percentage is changed. Updates the other input fields consequently
*
* @param {*} event
*/
function changedWidthPercentage(event) {
let oldValue = 100;
let ratio;
@ -204,6 +247,10 @@ function changedWidthPercentage(event) {
data.width = newWidth;
}
/**Fired when the input field for height percentage is changed. Updates the other input fields consequently
*
* @param {*} event
*/
function changedHeightPercentage(event) {
let oldValue = data.heightPercentage;
let ratio;
@ -230,10 +277,18 @@ function changedHeightPercentage(event) {
data.height = newHeight;
}
/** Toggles the keepRatio value (fired by the checkbox in the pop up window)
*
* @param {*} event
*/
function toggleRatio(event) {
keepRatio = !keepRatio;
}
/** Changes the scaling algorithm (fired by the combobox in the pop up window)
*
* @param {*} event
*/
function changedAlgorithm(event) {
currentAlgo = event.target.value;
}

View File

@ -26,8 +26,9 @@ else{
console.log(settings);
//on clicking the save button in the settings dialog
on('click', 'save-settings', function (){
on('click', 'save-settings', saveSettings);
function saveSettings() {
//check if values are valid
if (isNaN(getValue('setting-numberOfHistoryStates'))) {
alert('Invalid value for numberOfHistoryStates');
@ -46,4 +47,4 @@ on('click', 'save-settings', function (){
//close window
closeDialogue();
});
}

View File

@ -75,33 +75,24 @@ on('click',"eyedropper-button", function(){
tool.eyedropper.switchTo();
}, false);
//zoom tool button
on('click',"zoom-button", function(){
tool.zoom.switchTo();
}, false);
//zoom in button
on('click','zoom-in-button', function(){
//changeZoom('in',[window.innerWidth/2-canvas.offsetLeft,window.innerHeight/2-canvas.offsetTop]);
changeZoom(layers[0],'in', [canvasSize[0] * zoom / 2, canvasSize[1] * zoom / 2]);
for (let i=1; i<layers.length; i++) {
layers[i].copyData(layers[0]);
}
}, false);
//zoom out button
on('click','zoom-out-button', function(){
changeZoom(layers[0],'out',[canvasSize[0]*zoom/2,canvasSize[1]*zoom/2]);
for (let i=1; i<layers.length; i++) {
layers[i].copyData(layers[0]);
}
}, false);
//rectangular selection button
on('click', "rectselect-button", function(){
tool.rectselect.switchTo();
}, false);
//line
on('click',"line-button", function(){
tool.line.switchTo();
}, false);
on('click',"line-bigger-button", function(){
tool.line.brushSize++;
}, false);
on('click',"line-smaller-button", function(){
if(tool.line.brushSize > 1)
tool.line.brushSize--;
}, false);
/*global on */

View File

@ -5,7 +5,7 @@ var dragging = false;
var lastMouseClickPos = [0,0];
var dialogueOpen = false;
var documentCreated = false;
var pixelEditorMode;
var pixelEditorMode = "Advanced";
//common elements
var brushPreview = document.getElementById("brush-preview");

View File

@ -12,6 +12,8 @@
//=include utilities/hslToRgb.js
//=include libraries/cookies.js
//=include _pixelEditorUtility.js
//=include sortable.js
//=include _algorithms.js
/**init**/
//=include _consts.js
@ -34,10 +36,12 @@
//=include _colorChanged.js
//=include _initColor.js
//=include _dialogue.js
//=include _featuresLog.js
//=include _updateCursor.js
//=include _drawLine.js
//=include _getCursorPosition.js
//=include _fill.js
//=include _line.js
//=include _history.js
//=include _deleteColor.js
//=include _replaceAllOfColor.js
@ -47,6 +51,8 @@
//=include _copyPaste.js
//=include _resizeCanvas.js
//=include _resizeSprite.js
//=include _colorPicker.js
//=include _paletteBlock.js
/**load file**/
//=include _loadImage.js

3709
js/sortable.js Normal file

File diff suppressed because it is too large Load Diff

64
js/tools/_all.js Normal file
View File

@ -0,0 +1,64 @@
new Tool('eraser', {
cursor: 'crosshair',
brushPreview: true,
});
new Tool('resizeeraser', {
cursor: 'default',
});
new Tool('eyedropper', {
imageCursor: 'eyedropper',
});
new Tool('fill', {
imageCursor: 'fill',
});
new Tool('line', {
cursor: 'crosshair',
brushPreview: true,
});
new Tool('resizeline', {
cursor: 'default',
});
new Tool('pan', {
cursor: function () {
if (dragging) return 'url(\'/pixel-editor/pan-held.png\'), auto';
else return 'url(\'/pixel-editor/pan.png\'), auto';
},
});
new Tool('pencil', {
cursor: 'crosshair',
brushPreview: true,
});
new Tool('resizebrush', {
cursor: 'default',
});
new Tool('rectangle', {
cursor: 'crosshair',
brushPreview: true,
});
new Tool('resizerectangle', {
cursor: 'default',
});
new Tool('rectselect', {
cursor: 'crosshair',
brushPreview: true,
});
new Tool('moveselection', {
cursor: 'crosshair',
});
new Tool('zoom', {
imageCursor: 'zoom-in',
});
//set a default tool
var currentTool = tool.pencil;
var currentToolTemp = tool.pencil;

View File

@ -1,13 +0,0 @@
new Tool('eraser', {
cursor: 'crosshair',
brushPreview: true,
});
new Tool('resizeeraser', {
cursor: 'default',
});
/*global Tool, tool*/

View File

@ -1,7 +0,0 @@
new Tool('eyedropper', {
imageCursor: 'eyedropper',
});
/*global Tool, tool*/

View File

@ -1,7 +0,0 @@
new Tool('fill', {
imageCursor: 'fill',
});
/*global Tool, tool*/

View File

@ -1,10 +0,0 @@
new Tool('pan', {
cursor: function () {
if (dragging) return 'url(\'/pixel-editor/pan-held.png\'), auto';
else return 'url(\'/pixel-editor/pan.png\'), auto';
},
});
/*global Tool, tool*/

View File

@ -1,17 +0,0 @@
new Tool('pencil', {
cursor: 'crosshair',
brushPreview: true,
});
new Tool('resizebrush', {
cursor: 'default',
});
//set as default tool
var currentTool = tool.pencil;
var currentToolTemp = tool.pencil;
/*global Tool, tool*/

View File

@ -1,14 +0,0 @@
new Tool('rectangle', {
cursor: 'crosshair',
brushPreview: true,
});
new Tool('resizerectangle', {
cursor: 'default',
});
/*global Tool, tool*/

View File

@ -1,14 +0,0 @@
new Tool('rectselect', {
cursor: 'crosshair',
brushPreview: true,
});
new Tool('moveselection', {
cursor: 'crosshair',
});
/*global Tool, tool*/

View File

@ -1,6 +0,0 @@
new Tool('zoom', {
imageCursor: 'zoom-in',
});
/*global Tool, tool*/

147
package-lock.json generated
View File

@ -11556,7 +11556,14 @@
"resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz",
"integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==",
"requires": {
"ini": "1.3.7"
"ini": "1.3.7
},
"dependencies": {
"ini": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz",
"integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="
}
}
},
"global-modules": {
@ -12265,6 +12272,11 @@
"resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz",
"integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw=="
},
"is-yarn-global": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz",
"integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw=="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@ -12996,6 +13008,139 @@
}
}
},
"nodemon": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz",
"integrity": "sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==",
"requires": {
"chokidar": "^3.2.2",
"debug": "^3.2.6",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.0.4",
"pstree.remy": "^1.1.7",
"semver": "^5.7.1",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.3",
"update-notifier": "^4.1.0"
},
"dependencies": {
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
}
},
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"requires": {
"fill-range": "^7.0.1"
}
},
"chokidar": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
"requires": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"fsevents": "~2.3.1",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.5.0"
}
},
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"requires": {
"ms": "^2.1.1"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"requires": {
"to-regex-range": "^5.0.1"
}
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"optional": true
},
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"requires": {
"is-glob": "^4.0.1"
}
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"requires": {
"binary-extensions": "^2.0.0"
}
},
"is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
},
"readdirp": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
"requires": {
"picomatch": "^2.2.1"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"requires": {
"is-number": "^7.0.0"
}
}
}
},
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",

View File

@ -10,13 +10,14 @@ const PORT = process.argv[3] || 3000;
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) {
if(err){
console.log('error sending file',err);
console.log('error sending file', err);
} else {
console.log("Server: Successfully served index.html");
setTimeout(()=>{
console.log('closing server');
res.app.server.close();
process.exit();
},1000*10);
},1000*10);
}
});
});

View File

@ -13,7 +13,6 @@
</head>
<body oncontextmenu="return false;">
<div id="compatibility-warning">
<div><div>
<p><strong>Warning: a modern, desktop, web browser is required to use this tool.</strong></p>
@ -91,6 +90,7 @@
<button>Editor</button>
<ul>
<li><button id="switch-mode-button">Switch to basic mode</button></li>
<li><button onclick="showDialogue('splash', false)">Return to splash page</button></li>
<li><button>Settings</button></li>
</ul>
</li>
@ -130,29 +130,23 @@
<li><button title="Eyedropper Tool (E)" id="eyedropper-button">{{svg "eyedropper.svg" width="32" height="32"}}</button></li>
<li><button title="Pan Tool (P)" id="pan-button">{{svg "pan.svg" width="32" height="32"}}</button></li>
<li class="expanded">
<button title="Zoom Tool (Z)" id="zoom-button">{{svg "zoom.svg" width="32" height="32"}}</button>
<button title="Zoom In" id="zoom-in-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
<button title="Zoom Out" id="zoom-out-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
<button title="Line Tool (L)" id="line-button">{{svg "line.svg" width="32" height="32"}}</button>
<button title="Increase Line Size" id="line-bigger-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
<button title="Decrease Line Size" id="line-smaller-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
</li>
<li><button title = "Rectangular Selection Tool (M)" id = "rectselect-button">{{svg "rectselect.svg" width = "32" height = "32"}}</button><li>
</ul>
<!-- PALETTE -->
<ul id="colors-menu">
{{!
<li class="noshrink"><button id="current-color" class="jscolor {valueElement: 'current-color-value', styleElement: 'current-color-preview', onFineChange:'setColor(this)', width:151, position: 'left', padding:0,
borderWidth:14, borderColor: '#332f35',backgroundColor: '#332f35', insetColor:'transparent'}"><div id="current-color-preview"></div></button><input id="current-color-value" class="color-value" value="#000000" autocomplete="off" /></li>
}}
<li class="noshrink"><button title="Add Current Color To Palette" id="add-color-button">{{svg "./plus.svg" width="30" height="30"}}</button></li>
</ul>
<!-- LAYER MENU -->
<ul id = "layers-menu">
<li class = "layers-menu-entry selected-layer" draggable = "true">
<li class = "layers-menu-entry selected-layer">
<canvas class = "preview-canvas"></canvas>
<ul class="layer-buttons">
<li class = "layer-button">
@ -232,9 +226,9 @@
{{svg "adjust.svg" width="20" height="20" }}
</div>
<div id="pop-up-container" id = "new-pixel-container">
<div id="pop-up-container">
<!-- NEW PIXEL -->
<div id="new-pixel">
<div id="new-pixel" class="update">
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
<h1>New Pixel</h1>
@ -262,8 +256,78 @@
</div>
</div>
<!-- Splash page -->
<div id = "splash">
<div class="splash-news">
<div id="latest-update">
<h1>Latest updates</h1>
</div>
</div>
<div id="splash-input">
<div class="splash-menu">
<div id="editor-logo">
<div id="black">
<div id="sp-coverdata">
<img src="https://lospec.com/brand/lospec_logo_3x.png"/> pixel editor
<p>Version 1.4.0</p>
<a href="https://cdn.discordapp.com/attachments/506277390050131978/795660870221955082/final.png">Art by Unsettled</a>
</div>
</div>
</div>
<div id="sp-newpixel">
<h1>New Custom Pixel</h1>
<!-- Editor mode-->
<h2>Editor mode</h2>
<div class="sp-np-entry" id="sp-mode-palette">
<div class="button-menu">
<div class="bm-left" onclick="splashMode(event,'Basic')"><p>Basic</p></div>
<div class="sp-interface-selected bm-right" onclick="splashMode(event,'Advanced')"><p>Advanced</p></div>
</div>
</div>
<h2>Size</h2>
<div class="sp-np-entry">
<input id="size-width-splash" value="{{#if width}}{{width}}{{else}}64{{/if}}" autocomplete="off" />{{svg "x.svg" width="16" height="16" class="dimentions-x"}}<input id="size-height-splash" value="{{#if height}}{{height}}{{else}}64{{/if}}" autocomplete="off" />
</div>
<h2>Palette</h2>
<button id="palette-button-splash" class="dropdown-button">Choose a palette...</button>
<div id="palette-menu-splash" class="dropdown-menu"><button id="load-palette-button-splash">Load palette...</button></div>
<div id="new-pixel-warning">Creating a new pixel will discard your current one.</div>
<div class="sp-np-entry">
<button id="create-button-splash" class="default">Create</button>
</div>
</div>
<div id = "sp-quickstart-container">
<div id="sp-quickstart-title">
Quickstart
</div>
<div id="sp-quickstart">
<div class="sp-template" onclick="document.getElementById('open-image-browse-holder').click()"><p>Load</p></div>
<div class="sp-template" onclick="newFromTemplate('Gameboy Color')"><p><span>New</span> Gameboy</p></div>
<div class="sp-template" onclick="newFromTemplate('Commodore 64')"><p><span>New</span> C64</p></div>
<div class="sp-template" onclick="newFromTemplate('PICO-8')"><p><span>New</span> Pico8</p></div>
<div class="sp-template" onclick="newFromTemplate('',16,16)"><p><span>New</span> 16x16</p></div>
<div class="sp-template" onclick="newFromTemplate('',32,32)"><p><span>New</span> 32x32</p></div>
<div class="sp-template" onclick="newFromTemplate('',64,64)"><p><span>New</span> 64x64</p></div>
<div class="sp-template" onclick="newFromTemplate('',128,128)"><p><span>New</span> 128x128</p></div>
<div class="sp-template" onclick="newFromTemplate('',256,256)"><p><span>New</span> 256x256</p></div>
<div class="sp-template" onclick="newFromTemplate('',512,512)"><p><span>New</span> 512x512</p></div>
</div>
</div>
</div>
</div>
</div>
<!--SPRITE RESIZE-->
<div id = "resize-sprite">
<div class="update" id = "resize-sprite">
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
<h1>Scale sprite</h1>
<!-- SIZE-->
@ -313,7 +377,7 @@
</div>
<!--CANVAS RESIZE-->
<div id = "resize-canvas">
<div class="update" id = "resize-canvas">
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
<h1>Resize canvas</h1>
@ -371,6 +435,82 @@
<button id = "resize-canvas-confirm">Resize canvas</button>
</span>
</div>
<!-- PALETTE -->
<div id = "palette-block">
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
<h1>Edit palette</h1>
<div id = "colour-picker">
<div id = "cp-modes">
<button id="cp-rgb" class="cp-selected-mode" onclick="changePickerMode(this, 'rgb')">RGB</button>
<button id="cp-hsv" onclick="changePickerMode(this, 'hsv')">HSV</button>
<button id="cp-hsl" onclick="changePickerMode(this, 'hsl')">HSL</button>
<div id="cp-colour-preview" class="cp-colour-preview"></div>
<input id="cp-hex" type="text" value="#123456" onchange="hexUpdated()"/>
</div>
<div id = "sliders-container">
<div class = "cp-slider-entry">
<label for = "first-slider">R</label>
<input type="range" min="0" max="255" class="colour-picker-slider" id="first-slider" onmousemove="updateSliderValue(1)" onclick="updateSliderValue(1)"/>
<input type = "text" value = "128" onchange="inputChanged(this,1)"/>
</div>
<div class = "cp-slider-entry">
<label for = "second-slider">G</label>
<input type="range" min="0" max ="255" class="colour-picker-slider" id="second-slider" onmousemove="updateSliderValue(2)" onclick="updateSliderValue(2)"/>
<input type = "text" value = "128" onchange="inputChanged(this,2)"/>
</div>
<div class = "cp-slider-entry">
<label for = "third-slider">B</label>
<input type="range" min = "0" max = "255" class = "colour-picker-slider" id = "third-slider" onmousemove="updateSliderValue(3)" onclick="updateSliderValue(3)"/>
<input type = "text" value = "128" onchange="inputChanged(this,3)"/>
</div>
</div>
<div id = "cp-minipicker">
<input type = "range" min = "0" max = "100" id = "cp-minipicker-slider" onmousemove="miniSliderInput(event)" onclick="miniSliderInput(event)"/>
<div id="cp-canvas-container" onmousemove="movePickerIcon(event)">
<canvas id = "cp-spectrum"></canvas>
<div id="cp-active-icon" class="cp-picker-icon"></div>
</div>
<div id = "cp-colours-previews">
<div class = "cp-colour-preview">
#123456
</div>
</div>
<div id = "cp-colour-picking-modes">
<button class="cp-selected-mode" onclick="changePickingMode(event,'mono')">Mono</button>
<button onclick="changePickingMode(event,'analog')">Nlgs</button>
<button onclick="changePickingMode(event,'cmpt')">Cmpt</button>
<button onclick="changePickingMode(event,'tri')">Tri</button>
<button onclick="changePickingMode(event,'scmpt')">Scm</button>
<button onclick="changePickingMode(event,'tetra')">Tetra</button>
</div>
</div>
</div>
<div id = "palette-container">
<ul id = "palette-list">
<li style = "background-color:rgb(255,0,0);width:40px;height:40px;" onmousedown="startRampSelection(event)"
onmousemove="updateRampSelection(event)" onmouseup="endRampSelection(event)"></li>
<li style = "background-color:rgb(0,255,0);width:40px;height:40px;"onmousedown="startRampSelection(event)"
onmousemove="updateRampSelection(event)" onmouseup="endRampSelection(event)"></li>
</ul>
</div>
<div id="pb-options">
<button title="Add colours to palette" onclick="pbAddColours()">Add colours</button>
<button title="Remove colours from palette" onclick="pbRemoveColours()">Remove colours</button>
</div>
</div>
<div id="help">
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
<h1>Help</h1>