first commit
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
_ext
|
||||
routes
|
||||
build
|
||||
node_modules
|
36
README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Lospec Procedural Pixel Art Generator
|
||||
|
||||
This is a browser based tool used to create a template which can generate randomized pixel art.
|
||||
|
||||
The tool can be viewed online here: https://lospec.com/procedural-pixel-art-generator/
|
||||
|
||||
## What to Contribute
|
||||
|
||||
Any changes that fix bugs or add features are welcome.
|
||||
|
||||
Suggestions / Planned features:
|
||||
|
||||
- Mobile support
|
||||
- Variable canvas size
|
||||
- More Layers
|
||||
- More Colors
|
||||
- Color variations (choose a color randomly)
|
||||
|
||||
## How to Contribute
|
||||
|
||||
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**
|
||||
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
|
||||
6. Add, Commit and Push your changes to your fork.
|
||||
7. On this page, click **New Pull Request** above the file list.
|
||||
8. Change the **head repository** dropdown to your fork.
|
||||
9. Add a title and description explaining your changes.
|
||||
10. Click create pull request.
|
||||
|
||||
If you have any trouble, see this page: https://help.github.com/en/articles/creating-a-pull-request-from-a-fork
|
||||
|
||||
## License
|
||||
|
||||
This software may not be resold, redistributed, rehosted or otherwise conveyed to a third party.
|
160
_ext/sass/_colors.scss
Normal file
@ -0,0 +1,160 @@
|
||||
$base-color: #332f35;
|
||||
$shop: #b63831;
|
||||
$red: #e3474a;
|
||||
|
||||
$palettes: (
|
||||
base: (
|
||||
background: (
|
||||
default: $base-color,
|
||||
hover: lighten($base-color, 5%),
|
||||
lighthover: lighten($base-color, 4%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($base-color, 20%),
|
||||
text: lighten($base-color, 50%),
|
||||
bold: lighten($base-color, 60%),
|
||||
weak: lighten($base-color, 30%),
|
||||
link: lighten($base-color, 100%),
|
||||
h1: lighten($base-color, 100%),
|
||||
h2: lighten($base-color, 70%),
|
||||
h3: lighten($base-color, 60%),
|
||||
surveyQuestion: lighten($base-color, 60%),
|
||||
hover: lighten($base-color, 40%),
|
||||
separator: lighten($base-color, 5%),
|
||||
disabled: lighten($base-color, 10%),
|
||||
)
|
||||
),
|
||||
user-menu: (
|
||||
background: (
|
||||
default: darken($base-color, 3%),
|
||||
hover: darken($base-color, 1.5%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($base-color, 50%),
|
||||
hover: lighten($base-color, 100%),
|
||||
)
|
||||
),
|
||||
menu: (
|
||||
background: (
|
||||
default: lighten($base-color, 5%),
|
||||
hover: lighten($base-color, 15%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($base-color, 50%),
|
||||
hover: lighten($base-color, 100%),
|
||||
)
|
||||
),
|
||||
button: (
|
||||
background: (
|
||||
default: lighten($base-color, 10%),
|
||||
hover: lighten($base-color, 15%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($base-color, 80%),
|
||||
text: lighten($base-color, 100%),
|
||||
)
|
||||
),
|
||||
selectedTool: (
|
||||
background: (
|
||||
default: lighten($base-color, 10%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($base-color, 50%),
|
||||
)
|
||||
),
|
||||
subbutton: (
|
||||
background: (
|
||||
hover: lighten($base-color, 15%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($base-color, 30%),
|
||||
hover: lighten($base-color, 50%),
|
||||
)
|
||||
),
|
||||
indent: (
|
||||
background: (
|
||||
default: darken($base-color, 4%),
|
||||
separator: darken($base-color, 8%),
|
||||
hover: lighten($base-color, 5%),
|
||||
),
|
||||
foreground: (
|
||||
default: #fff,
|
||||
symbol: lighten($base-color, 5%),
|
||||
symbol-hover: lighten($base-color, 20%),
|
||||
weak: lighten($base-color, 20%),
|
||||
form: lighten($base-color, 50%),
|
||||
)
|
||||
),
|
||||
indent-dark: (
|
||||
background: (
|
||||
default: darken($base-color, 6%),
|
||||
separator: darken($base-color, 11%),
|
||||
button: lighten($base-color, 2.5%),
|
||||
button-hover: lighten($base-color, 5%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($base-color, 30%),
|
||||
link: lighten($base-color, 40%),
|
||||
hover: lighten($base-color, 50%),
|
||||
button: lighten($base-color, 50%),
|
||||
button-hover: lighten($base-color, 70%),
|
||||
)
|
||||
),
|
||||
footer: (
|
||||
background: (
|
||||
default: darken($base-color, 9%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($base-color, 20%),
|
||||
hover: lighten($base-color, 35%),
|
||||
symbol: lighten($base-color, 7.5%),
|
||||
),
|
||||
),
|
||||
warning-banner: (
|
||||
background: (
|
||||
default: lighten($base-color, 10%),
|
||||
button: lighten($base-color, 20%),
|
||||
button-hover: lighten($base-color, 25%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($base-color, 50%),
|
||||
button: lighten($base-color, 40%),
|
||||
button-hover: lighten($base-color, 45%),
|
||||
),
|
||||
),
|
||||
image-label: (
|
||||
background: (
|
||||
default: lighten($base-color, 15%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($base-color, 80%),
|
||||
),
|
||||
triangle: (
|
||||
default: lighten($base-color, 6%),
|
||||
),
|
||||
),
|
||||
shop: (
|
||||
background: (
|
||||
default: $shop,
|
||||
hover: lighten($shop, 5%),
|
||||
),
|
||||
foreground: (
|
||||
default: lighten($shop, 65%),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@function color($element: 'base', $location: 'background', $hover: 'default') {
|
||||
@return map-get(map-get(map-get($palettes, $element), $location), $hover);
|
||||
}
|
||||
|
||||
$twitter: #00b6f1;
|
||||
$patreon: #F96854;
|
||||
$facebook: #3b5998;
|
||||
$reddit: #ff5700;
|
||||
$youtube: #b31217;
|
||||
$pintrest: #cb2027;
|
||||
$tumblr: #2c4762;
|
||||
$deviantart: #4a5d4e;
|
||||
$instagram: #c2368a;
|
||||
$pixeljoint: #73d731;
|
67
_ext/sass/_zindex.scss
Normal file
@ -0,0 +1,67 @@
|
||||
// this function is used whenever you need to define a z-index
|
||||
@function -z($key, $mod: 0) {
|
||||
|
||||
$index: -index($layers, $key) * 10;
|
||||
|
||||
@if $mod {
|
||||
@return $index + $mod;
|
||||
}
|
||||
|
||||
@return $index;
|
||||
}
|
||||
|
||||
// dependency for the other function
|
||||
@function -index($list, $value) {
|
||||
@for $i from 1 through length($list) {
|
||||
@if nth($list, $i) == $value {
|
||||
@return $i;
|
||||
}
|
||||
}
|
||||
|
||||
@return null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
define your layers from bottom to top:
|
||||
|
||||
$layers: (
|
||||
background,
|
||||
content,
|
||||
dropdown,
|
||||
subnav,
|
||||
mobile-nav,
|
||||
header,
|
||||
overlay,
|
||||
modal,
|
||||
modal-background,
|
||||
modal-content
|
||||
);
|
||||
|
||||
|
||||
|
||||
//////////use:
|
||||
|
||||
.test {
|
||||
z-index: -z(content);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//////////go one layer above content
|
||||
|
||||
.test-2 {
|
||||
z-index: -z(content, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////go one layer below content
|
||||
|
||||
.test-2 {
|
||||
z-index: -z(content, -1)
|
||||
}
|
||||
|
||||
*/
|
172
_ext/scripts/libraries/cookies.js
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Cookies.js - 1.2.3
|
||||
* https://github.com/ScottHamper/Cookies
|
||||
*
|
||||
* This is free and unencumbered software released into the public domain.
|
||||
*/
|
||||
(function (global, undefined) {
|
||||
'use strict';
|
||||
|
||||
var factory = function (window) {
|
||||
if (typeof window.document !== 'object') {
|
||||
throw new Error('Cookies.js requires a `window` with a `document` object');
|
||||
}
|
||||
|
||||
var Cookies = function (key, value, options) {
|
||||
return arguments.length === 1 ?
|
||||
Cookies.get(key) : Cookies.set(key, value, options);
|
||||
};
|
||||
|
||||
// Allows for setter injection in unit tests
|
||||
Cookies._document = window.document;
|
||||
|
||||
// Used to ensure cookie keys do not collide with
|
||||
// built-in `Object` properties
|
||||
Cookies._cacheKeyPrefix = 'cookey.'; // Hurr hurr, :)
|
||||
|
||||
Cookies._maxExpireDate = new Date('Fri, 31 Dec 9999 23:59:59 UTC');
|
||||
|
||||
Cookies.defaults = {
|
||||
path: '/',
|
||||
secure: false
|
||||
};
|
||||
|
||||
Cookies.get = function (key) {
|
||||
if (Cookies._cachedDocumentCookie !== Cookies._document.cookie) {
|
||||
Cookies._renewCache();
|
||||
}
|
||||
|
||||
var value = Cookies._cache[Cookies._cacheKeyPrefix + key];
|
||||
|
||||
return value === undefined ? undefined : decodeURIComponent(value);
|
||||
};
|
||||
|
||||
Cookies.set = function (key, value, options) {
|
||||
options = Cookies._getExtendedOptions(options);
|
||||
options.expires = Cookies._getExpiresDate(value === undefined ? -1 : options.expires);
|
||||
|
||||
Cookies._document.cookie = Cookies._generateCookieString(key, value, options);
|
||||
|
||||
return Cookies;
|
||||
};
|
||||
|
||||
Cookies.expire = function (key, options) {
|
||||
return Cookies.set(key, undefined, options);
|
||||
};
|
||||
|
||||
Cookies._getExtendedOptions = function (options) {
|
||||
return {
|
||||
path: options && options.path || Cookies.defaults.path,
|
||||
domain: options && options.domain || Cookies.defaults.domain,
|
||||
expires: options && options.expires || Cookies.defaults.expires,
|
||||
secure: options && options.secure !== undefined ? options.secure : Cookies.defaults.secure
|
||||
};
|
||||
};
|
||||
|
||||
Cookies._isValidDate = function (date) {
|
||||
return Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime());
|
||||
};
|
||||
|
||||
Cookies._getExpiresDate = function (expires, now) {
|
||||
now = now || new Date();
|
||||
|
||||
if (typeof expires === 'number') {
|
||||
expires = expires === Infinity ?
|
||||
Cookies._maxExpireDate : new Date(now.getTime() + expires * 1000);
|
||||
} else if (typeof expires === 'string') {
|
||||
expires = new Date(expires);
|
||||
}
|
||||
|
||||
if (expires && !Cookies._isValidDate(expires)) {
|
||||
throw new Error('`expires` parameter cannot be converted to a valid Date instance');
|
||||
}
|
||||
|
||||
return expires;
|
||||
};
|
||||
|
||||
Cookies._generateCookieString = function (key, value, options) {
|
||||
key = key.replace(/[^#$&+\^`|]/g, encodeURIComponent);
|
||||
key = key.replace(/\(/g, '%28').replace(/\)/g, '%29');
|
||||
value = (value + '').replace(/[^!#$&-+\--:<-\[\]-~]/g, encodeURIComponent);
|
||||
options = options || {};
|
||||
|
||||
var cookieString = key + '=' + value;
|
||||
cookieString += options.path ? ';path=' + options.path : '';
|
||||
cookieString += options.domain ? ';domain=' + options.domain : '';
|
||||
cookieString += options.expires ? ';expires=' + options.expires.toUTCString() : '';
|
||||
cookieString += options.secure ? ';secure' : '';
|
||||
|
||||
return cookieString;
|
||||
};
|
||||
|
||||
Cookies._getCacheFromString = function (documentCookie) {
|
||||
var cookieCache = {};
|
||||
var cookiesArray = documentCookie ? documentCookie.split('; ') : [];
|
||||
|
||||
for (var i = 0; i < cookiesArray.length; i++) {
|
||||
var cookieKvp = Cookies._getKeyValuePairFromCookieString(cookiesArray[i]);
|
||||
|
||||
if (cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] === undefined) {
|
||||
cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] = cookieKvp.value;
|
||||
}
|
||||
}
|
||||
|
||||
return cookieCache;
|
||||
};
|
||||
|
||||
Cookies._getKeyValuePairFromCookieString = function (cookieString) {
|
||||
// "=" is a valid character in a cookie value according to RFC6265, so cannot `split('=')`
|
||||
var separatorIndex = cookieString.indexOf('=');
|
||||
|
||||
// IE omits the "=" when the cookie value is an empty string
|
||||
separatorIndex = separatorIndex < 0 ? cookieString.length : separatorIndex;
|
||||
|
||||
var key = cookieString.substr(0, separatorIndex);
|
||||
var decodedKey;
|
||||
try {
|
||||
decodedKey = decodeURIComponent(key);
|
||||
} catch (e) {
|
||||
if (console && typeof console.error === 'function') {
|
||||
console.error('Could not decode cookie with key "' + key + '"', e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
key: decodedKey,
|
||||
value: cookieString.substr(separatorIndex + 1) // Defer decoding value until accessed
|
||||
};
|
||||
};
|
||||
|
||||
Cookies._renewCache = function () {
|
||||
Cookies._cache = Cookies._getCacheFromString(Cookies._document.cookie);
|
||||
Cookies._cachedDocumentCookie = Cookies._document.cookie;
|
||||
};
|
||||
|
||||
Cookies._areEnabled = function () {
|
||||
var testKey = 'cookies.js';
|
||||
var areEnabled = Cookies.set(testKey, 1).get(testKey) === '1';
|
||||
Cookies.expire(testKey);
|
||||
return areEnabled;
|
||||
};
|
||||
|
||||
Cookies.enabled = Cookies._areEnabled();
|
||||
|
||||
return Cookies;
|
||||
};
|
||||
var cookiesExport = (global && typeof global.document === 'object') ? factory(global) : factory;
|
||||
|
||||
// AMD support
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(function () { return cookiesExport; });
|
||||
// CommonJS/Node.js support
|
||||
} else if (typeof exports === 'object') {
|
||||
// Support Node.js specific `module.exports` (which can be a function)
|
||||
if (typeof module === 'object' && typeof module.exports === 'object') {
|
||||
exports = module.exports = cookiesExport;
|
||||
}
|
||||
// But always support CommonJS module 1.1.1 spec (`exports` cannot be a function)
|
||||
exports.Cookies = cookiesExport;
|
||||
} else {
|
||||
global.Cookies = cookiesExport;
|
||||
}
|
||||
})(typeof window === 'undefined' ? this : window);
|
10
_ext/scripts/utilities/getSetText.js
Normal file
@ -0,0 +1,10 @@
|
||||
//get text of specified element
|
||||
function getText(elementId) {
|
||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||
return element.textContent;
|
||||
}
|
||||
|
||||
function setText(elementId, text) {
|
||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||
element.textContent = text;
|
||||
}
|
11
_ext/scripts/utilities/getSetValue.js
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
function getValue(elementId) {
|
||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||
return element.value;
|
||||
}
|
||||
|
||||
function setValue(elementId, value) {
|
||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||
element.value = value;
|
||||
}
|
21
_ext/scripts/utilities/hexToRgb.js
Normal file
@ -0,0 +1,21 @@
|
||||
//put in a hex color code (f464b2 or #f464b2) string
|
||||
//and get an rgb color object {r:0,g:0,b:0}
|
||||
//divisor is an optional argument, which makes it so you can get values other than 0-255
|
||||
|
||||
function hexToRgb(hex, divisor) {
|
||||
//if divisor isn't set, set it to one (so it has no effect)
|
||||
divisor = divisor || 1;
|
||||
|
||||
//split given hex code into array of 3 values
|
||||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex.trim());
|
||||
|
||||
//console.log('hex: '+hex)
|
||||
//console.log([parseInt(result[1], 16)/divisor, parseInt(result[2], 16)/divisor, parseInt(result[3], 16)/divisor])
|
||||
//console.log(result)
|
||||
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16)/divisor,
|
||||
g: parseInt(result[2], 16)/divisor,
|
||||
b: parseInt(result[3], 16)/divisor
|
||||
} : null;
|
||||
}
|
32
_ext/scripts/utilities/hslToRgb.js
Normal file
@ -0,0 +1,32 @@
|
||||
function hslToRgb(h, s, l){
|
||||
h /= 255;
|
||||
s /= 255;
|
||||
l /= 255;
|
||||
|
||||
var r, g, b;
|
||||
|
||||
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 {
|
||||
r:Math.round(r * 255),
|
||||
g:Math.round(g * 255),
|
||||
b:Math.round(b * 255)
|
||||
};
|
||||
}
|
22
_ext/scripts/utilities/on.js
Normal file
@ -0,0 +1,22 @@
|
||||
//add event listener for any element which calls a function
|
||||
//element can be provided as a direct reference or with just a string of the name
|
||||
|
||||
function on(event, elementId, functionCallback) {
|
||||
|
||||
|
||||
|
||||
//if element provided is string, get the actual element
|
||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||
|
||||
//console.log('added '+event+' event listener on '+element)
|
||||
|
||||
element.addEventListener(event,
|
||||
function (e) {
|
||||
// e = event
|
||||
//this = element clicked
|
||||
functionCallback(e, this);
|
||||
//if you need to access the event or this variable, you need to add them
|
||||
//when you define the callback, but you cant use the word this, eg:
|
||||
//on('click', menuButton, function (e, button) {});
|
||||
});
|
||||
}
|
14
_ext/scripts/utilities/onChildren.js
Normal file
@ -0,0 +1,14 @@
|
||||
//add event listener to each of specified element's children
|
||||
|
||||
function onChildren(event, parentElement, functionCallback) {
|
||||
console.log('onChildren()');
|
||||
|
||||
var parentElement = (typeof parentElement == 'string' ? document.getElementById(parentElement) : parentElement);
|
||||
|
||||
var children = parentElement.children;
|
||||
|
||||
//loop through children and add onClick listener
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
on(event, children[i],functionCallback);
|
||||
}
|
||||
}
|
9
_ext/scripts/utilities/onClick.js
Normal file
@ -0,0 +1,9 @@
|
||||
//DEPRECATED - USE on('click')
|
||||
|
||||
|
||||
//add click event listener for any element which calls a function
|
||||
//element can be provided as a direct reference or with just a string of the name
|
||||
function onClick(elementId, functionCallback) {
|
||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||
element.addEventListener('click',functionCallback);
|
||||
}
|
13
_ext/scripts/utilities/onClickChildren.js
Normal file
@ -0,0 +1,13 @@
|
||||
//add click listener to each of specified element's children
|
||||
|
||||
function onClickChildren(parentElement, functionCallback) {
|
||||
|
||||
var parentElement = (typeof parentElement == 'string' ? document.getElementById(parentElement) : parentElement);
|
||||
|
||||
var children = parentElement.children;
|
||||
|
||||
//loop through children and add onClick listener
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
onClick(children[i],functionCallback);
|
||||
}
|
||||
}
|
23
_ext/scripts/utilities/rgbToHex.js
Normal file
@ -0,0 +1,23 @@
|
||||
//convert rgb values to a hex string for html
|
||||
function rgbToHex (argument0,g,b) {
|
||||
var r;
|
||||
|
||||
//if the first argument is an object
|
||||
if (typeof argument0 === 'object'){
|
||||
r = argument0.r;
|
||||
g = argument0.g;
|
||||
b = argument0.b;
|
||||
}
|
||||
else
|
||||
r = argument0;
|
||||
|
||||
//console.log('converting rgb('+r+','+g+','+b+') to hex');
|
||||
|
||||
//convert a decimal number to 2-digit hex
|
||||
function componentToHex (c) {
|
||||
var hex = c.toString(16);
|
||||
return hex.length == 1 ? "0" + hex : hex;
|
||||
}
|
||||
|
||||
return componentToHex(r) + componentToHex(g) + componentToHex(b);
|
||||
}
|
36
_ext/scripts/utilities/rgbToHsl.js
Normal file
@ -0,0 +1,36 @@
|
||||
//put in red green blue values and get out hue saturation luminosity values
|
||||
|
||||
function rgbToHsl(argument0, g, b){
|
||||
var r;
|
||||
|
||||
//if the first argument is an object
|
||||
if (typeof argument0 === 'object'){
|
||||
r = argument0.r;
|
||||
g = argument0.g;
|
||||
b = argument0.b;
|
||||
}
|
||||
else
|
||||
r = argument0;
|
||||
|
||||
|
||||
|
||||
r /= 255, g /= 255, b /= 255;
|
||||
|
||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
var hue, saturation, luminosity = (max + min) / 2;
|
||||
|
||||
if(max == min){
|
||||
hue = saturation = 0; // achromatic
|
||||
}else{
|
||||
var d = max - min;
|
||||
saturation = luminosity > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch(max){
|
||||
case r: hue = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: hue = (b - r) / d + 2; break;
|
||||
case b: hue = (r - g) / d + 4; break;
|
||||
}
|
||||
hue /= 6;
|
||||
}
|
||||
|
||||
return {h:hue, s:saturation, l:luminosity};
|
||||
}
|
20
_ext/scripts/utilities/select.js
Normal file
@ -0,0 +1,20 @@
|
||||
//add class .selected to specified element
|
||||
function select(elementId) {
|
||||
//console.log(arguments.callee.caller.name, 'selected ', elementId);
|
||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||
element.classList.add('selected');
|
||||
}
|
||||
|
||||
//remove .selected class from specified element
|
||||
function deselect(elementId) {
|
||||
//console.log('deselected ', elementId);
|
||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||
element.classList.remove('selected');
|
||||
}
|
||||
|
||||
//toggle the status of the .selected class on the specified element
|
||||
function toggle(elementId) {
|
||||
//console.log('toggled ', elementId);
|
||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||
element.classList.toggle('selected');
|
||||
}
|
88
build.js
Normal file
@ -0,0 +1,88 @@
|
||||
var fs = require('fs-extra');
|
||||
var hbs = require('handlebars');
|
||||
var glob = require("glob");
|
||||
var sass = require("sass");
|
||||
var path = require("path");
|
||||
var gulp = require('gulp');
|
||||
var include = require('gulp-include');
|
||||
|
||||
const BUILDDIR = './build';
|
||||
|
||||
console.log(process.env.HOST?true:false)
|
||||
|
||||
console.log('Building Procedural Pixel Art Generator');
|
||||
|
||||
hbs.registerHelper('svg', require('handlebars-helper-svg'));
|
||||
require('hbs-register-helpers')(hbs,'./_ext/modules/hbs/helpers/**/*.js');
|
||||
require('hbs-register-partials')(hbs,'./_ext/modules/hbs/_*.hbs');
|
||||
|
||||
//empty the build folder, or create it
|
||||
fs.emptyDirSync(BUILDDIR);
|
||||
|
||||
|
||||
//copy images
|
||||
fs.copySync('./images','./build/procedural-pixel-art-generator');
|
||||
|
||||
//render js
|
||||
gulp.src('./js/*.js')
|
||||
.pipe(include({includePaths: [
|
||||
'_ext/scripts',
|
||||
'js'
|
||||
]}))
|
||||
.on('error', console.log)
|
||||
.pipe(gulp.dest("build/procedural-pixel-art-generator"));
|
||||
|
||||
|
||||
//render css
|
||||
var sassFiles = glob.sync('css/*.scss');
|
||||
|
||||
sassFiles.forEach((s) => {
|
||||
|
||||
var f = sass.renderSync({file: s, outFile: 'test.css', includePaths: ['css', '_ext/sass', '_ext/modules/css']});
|
||||
|
||||
console.log('compiling:',path.basename(f.stats.entry))
|
||||
fs.writeFileSync('build/procedural-pixel-art-generator/'+path.basename(f.stats.entry,'scss')+'css', f.css);
|
||||
});
|
||||
|
||||
|
||||
|
||||
//compile content
|
||||
var contentTemplate = hbs.compile(fs.readFileSync('./views/procedural-pixel-art-generator.hbs',{encoding: 'utf8'}));
|
||||
var content = contentTemplate();
|
||||
|
||||
//compile page
|
||||
var pageTemplate = hbs.compile(fs.readFileSync('./_ext/modules/hbs/layout-contribute.hbs',{encoding: 'utf8'}));
|
||||
var page = pageTemplate({
|
||||
projectSlug: 'procedural-pixel-art-generator',
|
||||
title: 'Lospec Procedural Pixel Art Generator',
|
||||
body: content,
|
||||
css: ['https://lospec.com/stylesheets/style.css','procedural-pixel-art-generator/procedural-pixel-art-generator.css'],
|
||||
js: ['procedural-pixel-art-generator/procedural-pixel-art-generator.js'],
|
||||
});
|
||||
|
||||
//save output
|
||||
fs.writeFileSync('./build/index.htm',page);
|
||||
|
||||
|
||||
//server
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
|
||||
//ROUTE - index.htm
|
||||
app.get('/', (req, res) => res.sendFile(path.join(__dirname+'/build/index.htm')));
|
||||
|
||||
//ROUTE - other files
|
||||
app.use(express.static(path.join(__dirname, 'build')));
|
||||
|
||||
//start server
|
||||
app.listen(3000, () => {
|
||||
console.log('\nTemp server started at http://localhost:3000!');
|
||||
console.log('press ctrl+c to stop ');
|
||||
|
||||
var opn = require('opn');
|
||||
|
||||
// opens the url in the default browser
|
||||
opn('http://localhost:3000');
|
||||
|
||||
});
|
10
css/pixel-editor-splash-page.scss
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
.pixel-editor-screenshot {
|
||||
box-shadow: 0px 10px 40px 0px rgba(0,0,0,0.45);
|
||||
margin: auto;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
border: solid 5px white;
|
||||
}
|
||||
|
||||
|
719
css/pixel-editor.scss
Normal file
@ -0,0 +1,719 @@
|
||||
@import 'colors';
|
||||
@import 'zindex';
|
||||
body {
|
||||
background: color(indent-dark);
|
||||
font-family: 'Roboto', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
-moz-user-select: none;
|
||||
/* Firefox */
|
||||
-ms-user-select: none;
|
||||
/* Internet Explorer */
|
||||
-khtml-user-select: none;
|
||||
/* KHTML browsers (e.g. Konqueror) */
|
||||
-webkit-user-select: none;
|
||||
/* Chrome, Safari, and Opera */
|
||||
-webkit-touch-callout: none;
|
||||
/* Disable Android and iOS callouts*/
|
||||
}
|
||||
|
||||
//don't let svg handle click events, just send to parents
|
||||
svg {
|
||||
pointer-events: none;
|
||||
path {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
//remove blue outline in chrome
|
||||
*:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
#pixel-canvas {
|
||||
cursor: url('/pixel-art-where-to-start/pencil-tool-cursor.png');
|
||||
|
||||
border: solid 1px #fff;
|
||||
image-rendering:optimizeSpeed; /* Legal fallback */
|
||||
image-rendering:-moz-crisp-edges; /* Firefox */
|
||||
image-rendering:-o-crisp-edges; /* Opera */
|
||||
image-rendering:-webkit-optimize-contrast; /* Safari */
|
||||
image-rendering:optimize-contrast; /* CSS3 Proposed */
|
||||
image-rendering:crisp-edges; /* CSS4 Proposed */
|
||||
image-rendering:pixelated; /* CSS4 Proposed */
|
||||
-ms-interpolation-mode:nearest-neighbor; /* IE8+ */
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
display: none;
|
||||
position: fixed;
|
||||
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.64);
|
||||
}
|
||||
|
||||
#eyedropper-preview {
|
||||
position: absolute;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
border-radius: 30px;
|
||||
border: solid 10px red;
|
||||
z-index: 1200;
|
||||
display: none;
|
||||
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.25), inset 0 0 6px 0 rgba(0, 0, 0, 0.2);
|
||||
pointer-events: none;
|
||||
&.dark {
|
||||
box-shadow: 0 0 8px 0 rgba(255, 255, 255, 0.5), inset 0 0 6px 0 rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
#brush-preview {
|
||||
position: absolute;
|
||||
border: solid 1px #fff;
|
||||
z-index: 1200;
|
||||
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.5), inset 0 0 2px 0 rgba(0, 0, 0, 0.5);
|
||||
pointer-events: none;
|
||||
left: -500px;
|
||||
&.dark {
|
||||
border: solid 1px #000;
|
||||
box-shadow: 0 0 3px 0 rgba(255, 255, 255, 0.8), inset 0 0 3px 0 rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
#canvas-view {
|
||||
background: color(indent-dark);
|
||||
bottom: 0px;
|
||||
left: 64px;
|
||||
right: 48px;
|
||||
top: 48px;
|
||||
cursor: none;
|
||||
position: fixed;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#canvas-view-shadow {
|
||||
box-shadow: inset 0px 0px 4px 0px rgba(0, 0, 0, 0.4);
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
left: 64px;
|
||||
right: 48px;
|
||||
top: 48px;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#main-menu {
|
||||
height: 48px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: color(base);
|
||||
position: fixed;
|
||||
z-index: 1110;
|
||||
overflow: visible;
|
||||
&>li {
|
||||
float: left;
|
||||
height: 100%;
|
||||
}
|
||||
li button {
|
||||
color: color(menu, foreground);
|
||||
height: 100%;
|
||||
padding: 17px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
li.selected {
|
||||
background-color: color(menu);
|
||||
&>button {
|
||||
color: color(menu, foreground, hover);
|
||||
}
|
||||
ul {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
li ul {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: color(menu);
|
||||
box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.5);
|
||||
padding-bottom: 2px;
|
||||
li {
|
||||
width: 100%;
|
||||
button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 8px 32px 8px 16px;
|
||||
&:hover {
|
||||
background-color: color(menu, background, hover);
|
||||
color: color(menu, foreground, hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled {
|
||||
color: #6f6e70 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*app title*/
|
||||
|
||||
.logo {
|
||||
color: #6f6873;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
padding: 17px 10px 0;
|
||||
cursor: default;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#data-holders {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#tools-menu,
|
||||
#colors-menu {
|
||||
list-style-type: none;
|
||||
top: 48px;
|
||||
bottom: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: color(base);
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
z-index: 1120;
|
||||
}
|
||||
|
||||
#tools-menu {
|
||||
left: 0;
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
#colors-menu {
|
||||
right: 0;
|
||||
width: 48px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
li {
|
||||
width: 48px;
|
||||
flex-basis: 48px;
|
||||
|
||||
&:not(.noshrink) {
|
||||
flex-grow: 1;
|
||||
}
|
||||
&.noshrink {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//added when the color is a duplicate of another
|
||||
#duplicate-color-warning {
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
margin-left: 5px;
|
||||
opacity: 0.75;
|
||||
cursor: help;
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.shake {
|
||||
animation: shake 0.82s cubic-bezier(.36, .07, .19, .97) both;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%,
|
||||
90% {
|
||||
transform: translate3d(-1px, 0, 0);
|
||||
}
|
||||
20%,
|
||||
80% {
|
||||
transform: translate3d(1px, 0, 0);
|
||||
}
|
||||
30%,
|
||||
50%,
|
||||
70% {
|
||||
transform: translate3d(-2px, 0, 0);
|
||||
}
|
||||
40%,
|
||||
60% {
|
||||
transform: translate3d(2px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
//floating button to open jscolor picker
|
||||
.color-edit-button {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 0px;
|
||||
background: color(base);
|
||||
padding: 6px 10px 3px 6px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
cursor: pointer;
|
||||
transition: left 0.25s;
|
||||
z-index: -1;
|
||||
box-shadow: 0px 15px 15px 0px rgba(0, 0, 0, 0.2);
|
||||
path {
|
||||
fill: color(base, foreground);
|
||||
}
|
||||
&:hover {
|
||||
background: color(base, background, hover);
|
||||
path {
|
||||
fill: color(base, foreground, hover);
|
||||
}
|
||||
}
|
||||
//class added when jscolor is opened
|
||||
&.hidden {
|
||||
left: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
#colors-menu li:hover .color-edit-button {
|
||||
display: block;
|
||||
left: -32px;
|
||||
}
|
||||
|
||||
#colors-menu li.selected:hover .color-edit-button {
|
||||
display: block;
|
||||
left: -35px;
|
||||
}
|
||||
|
||||
#tools-menu li,
|
||||
#colors-menu li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#colors-menu li button {
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.color-value {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#add-color-button {
|
||||
background: color(base);
|
||||
|
||||
path {
|
||||
fill: #6f6873;
|
||||
}
|
||||
}
|
||||
|
||||
#tools-menu li button:first-child {
|
||||
text-align: center;
|
||||
border: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
#tools-menu li button path {
|
||||
fill: color(base, foreground);
|
||||
}
|
||||
|
||||
#tools-menu li:hover button:first-child path {
|
||||
fill: color(base, foreground, hover);
|
||||
}
|
||||
|
||||
#colors-menu li {
|
||||
button {
|
||||
border: none;
|
||||
width: 100%;
|
||||
cursor: url('/pixel-editor/eyedropper.png'), auto;
|
||||
}
|
||||
//white outline
|
||||
&.selected button::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: -3px;
|
||||
border: solid 3px #fff;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 0px 0px 3px rgba(0, 0, 0, 0.15);
|
||||
z-index: 10;
|
||||
}
|
||||
//inner outline
|
||||
&.selected button::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: solid 1px rgba(0, 0, 0, 0.15);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
#colors-menu li.noshrink button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#tools-menu li.selected {
|
||||
background: color(selectedTool, background) !important;
|
||||
}
|
||||
|
||||
#tools-menu li.selected button:first-child path {
|
||||
fill: color(selectedTool, foreground);
|
||||
}
|
||||
|
||||
#tools-menu li.selected.expanded {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
#tools-menu li:hover,
|
||||
#main-menu li button:hover,
|
||||
#add-color-button:hover,
|
||||
#main-menu li.open {
|
||||
background: color(base, background, hover);
|
||||
}
|
||||
|
||||
.tools-menu-sub-button {
|
||||
text-align: center;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
width: 50%;
|
||||
height: 22px;
|
||||
display: none;
|
||||
line-height: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
path {
|
||||
fill: color(subbutton, foreground) !important;
|
||||
}
|
||||
&:hover {
|
||||
background: color(subbutton, background, hover) !important;
|
||||
path {
|
||||
fill: color(subbutton, foreground, hover) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#tools-menu li button#pencil-bigger-button,
|
||||
#tools-menu li button#zoom-in-button {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#tools-menu li button#pencil-smaller-button,
|
||||
#tools-menu li button#zoom-out-button {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#tools-menu li.selected button#pencil-bigger-button,
|
||||
#tools-menu li.selected button#pencil-smaller-button,
|
||||
#tools-menu li.selected button#zoom-in-button,
|
||||
#tools-menu li.selected button#zoom-out-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#pop-up-container {
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(35, 32, 36, 0.75);
|
||||
display: none;
|
||||
color: color(base, foreground, text);
|
||||
cursor: default;
|
||||
&>div {
|
||||
background: color(base);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.5);
|
||||
width: 400px;
|
||||
padding: 20px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: none;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 10px;
|
||||
color: color(base, foreground, h1);
|
||||
text-transform: uppercase;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
h2 {
|
||||
margin: 25px 0 10px;
|
||||
color: color(base, foreground, h2);
|
||||
text-transform: uppercase;
|
||||
font-size: 1em;
|
||||
}
|
||||
a {
|
||||
color: color(base, foreground, link);
|
||||
border-bottom: dotted 1px transparent;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
border-bottom: dotted 1px color(base, foreground, text);
|
||||
}
|
||||
}
|
||||
|
||||
.close-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: color(base, foreground);
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
border-radius: 0 3px 0 0;
|
||||
path {
|
||||
fill: color(base, foreground);
|
||||
}
|
||||
&:hover {
|
||||
background: color(base, background, hover);
|
||||
path {
|
||||
fill: color(base, foreground, hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.default {
|
||||
float: right;
|
||||
background: color(button);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: color(button, foreground);
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
margin: 20px 0 0 10px;
|
||||
&:hover {
|
||||
background: color(button, background, hover);
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
background: color(indent);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: color(indent, foreground);
|
||||
padding: 10px 20px;
|
||||
margin: 0;
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dropdown-button {
|
||||
background: color(button) url('/pixel-editor/dropdown-arrow.png') right center no-repeat;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: color(button, foreground);
|
||||
padding: 5px 20px 5px 5px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
width: 200px;
|
||||
text-align: left;
|
||||
&:hover {
|
||||
background: color(button, background, hover) url('/pixel-editor/dropdown-arrow-hover.png') right center no-repeat;
|
||||
}
|
||||
&.selected {
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
background: color(button);
|
||||
border: none;
|
||||
color: color(button, foreground);
|
||||
padding: 0;
|
||||
margin: -1px 0 0 0;
|
||||
width: 200px;
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
border-radius: 0 0 4px 4px;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
&.selected {
|
||||
display: block;
|
||||
}
|
||||
|
||||
button {
|
||||
background: color(button);
|
||||
border: none;
|
||||
color: color(button, foreground);
|
||||
padding: 5px 20px 5px 5px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
&:hover {
|
||||
background: color(button, background, hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.keyboard-key {
|
||||
background: lighten($base-color, 20%);
|
||||
box-shadow: 0 3px 0 2px lighten($base-color, 12%);
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
margin: 6px;
|
||||
display: inline-block;
|
||||
color: #c0bfc1;
|
||||
}
|
||||
|
||||
#settings-container {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
label {
|
||||
flex: 1;
|
||||
}
|
||||
input {
|
||||
width: 90px !important;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.preload {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#new-pixel-warning {
|
||||
display: none;
|
||||
text-align: center;
|
||||
margin: 20px 0 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.dimentions-x {
|
||||
margin: -2px 7px;
|
||||
path {
|
||||
fill: color(base, foreground)
|
||||
}
|
||||
}
|
||||
|
||||
.jscolor-picker-bottom {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: -4px;
|
||||
right: -4px;
|
||||
bottom: -7px;
|
||||
color: color(base, foreground, text);
|
||||
span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
input {
|
||||
width: 64px;
|
||||
background: color(indent);
|
||||
color: color(indent, foreground);
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 3px 12px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.delete-color-button {
|
||||
background: none;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
path {
|
||||
fill: color(base, foreground);
|
||||
}
|
||||
&:hover path {
|
||||
fill: color(base, foreground, hover);
|
||||
}
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
& path {
|
||||
fill: color(base, foreground, disabled) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#no-palette-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#cookies-disabled-warning {
|
||||
display: none;
|
||||
color: color(base, foreground, weak);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#compatibility-warning {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
visibility: hidden;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(35, 32, 36, 0.92);
|
||||
color: color(base, foreground, text);
|
||||
div {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
div {
|
||||
width: 400px;
|
||||
background-color: color(base);
|
||||
padding: 20px;
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
||||
margin: -120px 0 0 -220px;
|
||||
}
|
||||
}
|
||||
a {
|
||||
color: color(base, foreground, link);
|
||||
border-bottom: dotted 1px transparent;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
border-bottom: dotted 1px color(base, foreground, text);
|
||||
}
|
||||
}
|
||||
button {
|
||||
background: color(button);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: color(button, foreground);
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
&:hover {
|
||||
background: color(button, background, hover);
|
||||
}
|
||||
}
|
||||
}
|
BIN
images/dropdown-arrow-hover.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
images/dropdown-arrow.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
images/eyedropper.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
images/fill.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
images/pan-held.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
images/pan.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
images/pencil-tool-cursor.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
images/pencil.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
images/pixel-editor-screenshot.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
images/zoom-in.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
49
js/_addColor.js
Normal file
@ -0,0 +1,49 @@
|
||||
//adds the given color to the palette
|
||||
//input hex color string
|
||||
//returns list item element
|
||||
function addColor (newColor) {
|
||||
|
||||
//add # at beginning if not present
|
||||
if (newColor.charAt(0) != '#')
|
||||
newColor = '#' + newColor;
|
||||
|
||||
//create list item
|
||||
var listItem = document.createElement("li");
|
||||
|
||||
//create button
|
||||
var button = document.createElement("button");
|
||||
button.classList.add("color-button");
|
||||
button.style.backgroundColor = newColor;
|
||||
button.addEventListener("mouseup", clickedColor);
|
||||
listItem.appendChild(button);
|
||||
|
||||
/*
|
||||
//create input to hold color value
|
||||
var colorValue = document.createElement("input");
|
||||
colorValue.classList.add("color-value");
|
||||
listItem.appendChild(colorValue);
|
||||
*/
|
||||
|
||||
//insert new listItem element at the end of the colors menu (right before add button)
|
||||
colorsMenu.insertBefore(listItem, colorsMenu.children[colorsMenu.children.length-1]);
|
||||
|
||||
//add jscolor functionality
|
||||
initColor(button);
|
||||
|
||||
//add edit button
|
||||
var editButtonTemplate = document.getElementsByClassName('color-edit-button')[0];
|
||||
newEditButton = editButtonTemplate.cloneNode(true);
|
||||
listItem.appendChild(newEditButton);
|
||||
|
||||
//when you click the edit button
|
||||
on('click', newEditButton, function (event, button) {
|
||||
|
||||
//hide edit button
|
||||
button.parentElement.lastChild.classList.add('hidden');
|
||||
|
||||
//show jscolor picker
|
||||
button.parentElement.firstChild.jscolor.show();
|
||||
});
|
||||
|
||||
return listItem;
|
||||
}
|
58
js/_addColorButton.js
Normal file
@ -0,0 +1,58 @@
|
||||
//add color button
|
||||
on('click', 'add-color-button', function(){
|
||||
if (!documentCreated) return;
|
||||
|
||||
var colorCheckingStyle = `
|
||||
color: white;
|
||||
background: #3c4cc2;
|
||||
`;
|
||||
|
||||
var colorIsUnique = true
|
||||
do {
|
||||
//console.log('%cchecking for unique colors', colorCheckingStyle)
|
||||
//generate random color
|
||||
var hue = Math.floor(Math.random()*255);
|
||||
var sat = 130+Math.floor(Math.random()*100);
|
||||
var lit = 70+Math.floor(Math.random()*100);
|
||||
var newColorRgb = hslToRgb(hue,sat,lit)
|
||||
var newColor = rgbToHex(newColorRgb.r,newColorRgb.g,newColorRgb.b)
|
||||
|
||||
var newColorHex = newColor;
|
||||
|
||||
//check if color has been used before
|
||||
colors = document.getElementsByClassName('color-button');
|
||||
colorCheckingLoop: for (var i = 0; i < colors.length; i++) {
|
||||
//console.log('%c'+newColorHex +' '+ colors[i].jscolor.toString(), colorCheckingStyle)
|
||||
|
||||
//if generated color matches this color
|
||||
if (newColorHex == colors[i].jscolor.toString()) {
|
||||
//console.log('%ccolor already exists', colorCheckingStyle)
|
||||
|
||||
//start loop again
|
||||
colorIsUnique = false;
|
||||
|
||||
//exit
|
||||
break colorCheckingLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (colorIsUnique == false);
|
||||
|
||||
//remove current color selection
|
||||
document.querySelector("#colors-menu li.selected").classList.remove("selected");
|
||||
|
||||
//add new color and make it selected
|
||||
var addedColor = addColor(newColor);
|
||||
addedColor.classList.add('selected');
|
||||
context.fillStyle = '#' + newColor;
|
||||
|
||||
//add history state
|
||||
//saveHistoryState({type: 'addcolor', colorValue: addedColor.firstElementChild.jscolor.toString()});
|
||||
new HistoryStateAddColor(addedColor.firstElementChild.jscolor.toString());
|
||||
|
||||
//show color picker
|
||||
addedColor.firstElementChild.jscolor.show();
|
||||
|
||||
//hide edit button
|
||||
addedColor.lastChild.classList.add('hidden');
|
||||
}, false);
|
18
js/_changeTool.js
Normal file
@ -0,0 +1,18 @@
|
||||
function changeTool (selectedTool) {
|
||||
|
||||
//set tool and temp tje tje tpp;
|
||||
currentTool = selectedTool;
|
||||
currentToolTemp = selectedTool;
|
||||
|
||||
var tools = document.getElementById("tools-menu").children;
|
||||
|
||||
for (var i = 0; i < tools.length; i++) {
|
||||
tools[i].classList.remove("selected");
|
||||
}
|
||||
|
||||
//give the button of the selected tool the .selected class
|
||||
document.getElementById(selectedTool+"-button").parentNode.classList.add("selected");
|
||||
|
||||
//change cursor
|
||||
updateCursor();
|
||||
}
|
34
js/_changeZoom.js
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
function changeZoom (direction, cursorLocation) {
|
||||
|
||||
var oldWidth = canvasSize[0] * zoom;
|
||||
var oldHeight = canvasSize[1] * zoom;
|
||||
var newWidth, newHeight;
|
||||
|
||||
//change zoom level
|
||||
//if you want to zoom out, and the zoom isnt already at the smallest level
|
||||
if (direction == 'out' && zoom > 1) {
|
||||
zoom -= Math.ceil(zoom / 10);
|
||||
newWidth = canvasSize[0] * zoom;
|
||||
newHeight = canvasSize[1] * zoom;
|
||||
|
||||
//adjust canvas position
|
||||
setCanvasOffset(canvas.offsetLeft + (oldWidth - newWidth) *cursorLocation[0]/oldWidth, 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){
|
||||
zoom += Math.ceil(zoom/10);
|
||||
newWidth = canvasSize[0] * zoom;
|
||||
newHeight = canvasSize[1] * zoom;
|
||||
|
||||
//adjust canvas position
|
||||
setCanvasOffset(canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight))
|
||||
}
|
||||
|
||||
//resize canvas
|
||||
canvas.style.width = (canvas.width*zoom)+'px';
|
||||
canvas.style.height = (canvas.height*zoom)+'px';
|
||||
|
||||
// adjust brush size
|
||||
updateCursor();
|
||||
}
|
20
js/_checkCompatibility.js
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
/////=include libraries/bowser.js
|
||||
|
||||
function closeCompatibilityWarning () {
|
||||
document.getElementById('compatibility-warning').style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
console.log('checking compatibility')
|
||||
|
||||
//check browser/version
|
||||
if ( (bowser.msie && bowser.version < 11) ||
|
||||
(bowser.firefox && bowser.version < 28) ||
|
||||
(bowser.chrome && bowser.version < 29) ||
|
||||
(bowser.msedge && bowser.version < 12) ||
|
||||
(bowser.safari && bowser.version < 9) ||
|
||||
(bowser.opera && bowser.version < 17) )
|
||||
//show warning
|
||||
document.getElementById('compatibility-warning').style.visibility = 'visible';
|
||||
|
||||
else alert(bowser.name+' '+bowser.version+' is fine!')
|
29
js/_clickedColor.js
Normal file
@ -0,0 +1,29 @@
|
||||
//color in palette has been clicked
|
||||
function clickedColor (e){
|
||||
|
||||
//left clicked color
|
||||
if (e.which == 1) {
|
||||
|
||||
//remove current color selection
|
||||
var selectedColor = document.querySelector("#colors-menu li.selected")
|
||||
if (selectedColor) selectedColor.classList.remove("selected");
|
||||
|
||||
//set current color
|
||||
context.fillStyle = this.style.backgroundColor;
|
||||
|
||||
//make color selected
|
||||
e.target.parentElement.classList.add('selected');
|
||||
|
||||
//right clicked color
|
||||
} else if (e.which == 3) {
|
||||
console.log('right clicked color button')
|
||||
|
||||
//hide edit color button (to prevent it from showing)
|
||||
e.target.parentElement.lastChild.classList.add('hidden');
|
||||
|
||||
//show color picker
|
||||
e.target.jscolor.show();
|
||||
|
||||
}
|
||||
}
|
||||
|
102
js/_colorChanged.js
Normal file
@ -0,0 +1,102 @@
|
||||
|
||||
|
||||
document.getElementById('jscolor-hex-input').addEventListener('change', colorChanged, false);
|
||||
|
||||
on('input', 'jscolor-hex-input', function (e) {
|
||||
|
||||
//get hex value
|
||||
var newColorHex = e.target.value.toLowerCase();
|
||||
|
||||
//if the color is not (yet) a valid hex color, exit this function and do nothing
|
||||
if (/^[0-9a-f]{6}$/i.test(newColorHex) == false)
|
||||
return;
|
||||
|
||||
//get currently editing color
|
||||
var currentlyEditedColor = document.getElementsByClassName('jscolor-active')[0];
|
||||
|
||||
//update the actual color picker to the inputted color
|
||||
currentlyEditedColor.firstChild.jscolor.fromString(newColorHex);
|
||||
|
||||
colorChanged(e);
|
||||
})
|
||||
|
||||
|
||||
//changes all of one color to another after being changed from color picker
|
||||
function colorChanged(e) {
|
||||
console.log('colorChanged()');
|
||||
|
||||
|
||||
|
||||
//get colors
|
||||
var newColor = hexToRgb(e.target.value);
|
||||
var oldColor = e.target.oldColor;
|
||||
|
||||
console.log('newColor',newColor)
|
||||
console.log('oldColor',oldColor)
|
||||
|
||||
//save undo state
|
||||
//saveHistoryState({type: 'colorchange', newColor: e.target.value, oldColor: rgbToHex(oldColor), canvas: context.getImageData(0, 0, canvasSize[0], canvasSize[1])});
|
||||
new HistoryStateEditColor(e.target.value.toLowerCase(), rgbToHex(oldColor));
|
||||
|
||||
//get the currently selected color
|
||||
var currentlyEditedColor = document.getElementsByClassName('jscolor-active')[0];
|
||||
var duplicateColorWarning = document.getElementById("duplicate-color-warning");
|
||||
|
||||
//check if selected color already matches another color
|
||||
colors = document.getElementsByClassName('color-button');
|
||||
var colorCheckingStyle = "background: #bc60c4; color: white"
|
||||
var newColorHex = e.target.value.toLowerCase();
|
||||
|
||||
//if the color is not a valid hex color, exit this function and do nothing
|
||||
if (/^[0-9a-f]{6}$/i.test(newColorHex) == false)
|
||||
return;
|
||||
|
||||
//loop through all colors in palette
|
||||
for (var i = 0; i < colors.length; i++) {
|
||||
console.log('%c'+newColorHex +' '+ colors[i].jscolor.toString(), colorCheckingStyle)
|
||||
|
||||
//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);
|
||||
|
||||
//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)
|
||||
|
||||
//show the duplicate color warning
|
||||
duplicateColorWarning.style.visibility = 'visible';
|
||||
|
||||
//shake warning icon
|
||||
duplicateColorWarning.classList.remove('shake');
|
||||
void duplicateColorWarning.offsetWidth;
|
||||
duplicateColorWarning.classList.add('shake');
|
||||
|
||||
//exit function without updating color
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if the color being edited has a duplicate color warning, remove it
|
||||
duplicateColorWarning.style.visibility = 'hidden';
|
||||
|
||||
currentlyEditedColor.firstChild.jscolor.fromString(newColorHex)
|
||||
|
||||
replaceAllOfColor(oldColor, newColor);
|
||||
|
||||
//set new old color to changed color
|
||||
e.target.oldColor = newColor;
|
||||
|
||||
console.log(e.target.colorElement);
|
||||
|
||||
//if this is the current color, update the drawing color
|
||||
if (e.target.colorElement.parentElement.classList.contains('selected')) {
|
||||
console.log('this color is the current color');
|
||||
context.fillStyle = '#'+ rgbToHex(newColor.r,newColor.g,newColor.b);
|
||||
}
|
||||
/* this is wrong and bad
|
||||
if (settings.switchToChangedColor) {
|
||||
|
||||
}*/
|
||||
}
|
||||
|
22
js/_createButton.js
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
on('click', 'create-button', function (){
|
||||
var width = getValue('size-width');
|
||||
var height = getValue('size-height');
|
||||
newPixel(width,height,'asdfg');
|
||||
document.getElementById('new-pixel-warning').style.display = 'block';
|
||||
|
||||
//get selected palette name
|
||||
var selectedPalette = getText('palette-button');
|
||||
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);
|
||||
setText('palette-button', 'Choose a palette...');
|
||||
setText('preset-button', 'Choose a preset...');
|
||||
});
|
45
js/_createColorPalette.js
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
function createColorPalette(selectedPalette, fillBackground) {
|
||||
//remove current palette
|
||||
colors = document.getElementsByClassName('color-button');
|
||||
while (colors.length > 0) {
|
||||
colors[0].parentElement.remove();
|
||||
}
|
||||
|
||||
var lightestColor = '#000000';
|
||||
var darkestColor = '#ffffff';
|
||||
|
||||
for (var i = 0; i < selectedPalette.length; i++) {
|
||||
var newColor = selectedPalette[i];
|
||||
var newColorElement = addColor(newColor);
|
||||
|
||||
var newColorHex = hexToRgb(newColor);
|
||||
|
||||
var lightestColorHex = hexToRgb(lightestColor);
|
||||
if (newColorHex.r + newColorHex.g + newColorHex.b > lightestColorHex.r + lightestColorHex.g + lightestColorHex.b)
|
||||
lightestColor = newColor;
|
||||
|
||||
var darkestColorHex = hexToRgb(darkestColor);
|
||||
if (newColorHex.r + newColorHex.g + newColorHex.b < darkestColorHex.r + darkestColorHex.g + darkestColorHex.b) {
|
||||
|
||||
//remove current color selection
|
||||
var selectedColor = document.querySelector("#colors-menu li.selected")
|
||||
if (selectedColor) selectedColor.classList.remove("selected");
|
||||
|
||||
//set as current color
|
||||
newColorElement.classList.add('selected');
|
||||
|
||||
darkestColor = newColor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//fill bg with lightest color
|
||||
if (fillBackground) {
|
||||
context.fillStyle = lightestColor;
|
||||
context.fillRect(0, 0, canvasSize[0], canvasSize[1]);
|
||||
}
|
||||
|
||||
//set as current color
|
||||
context.fillStyle = darkestColor;
|
||||
}
|
84
js/_deleteColor.js
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
|
||||
//called when the delete button is pressed on color picker
|
||||
//input color button or hex string
|
||||
function deleteColor (color) {
|
||||
const logStyle = 'background: #913939; color: white; padding: 5px;';
|
||||
|
||||
//console.log('%c'+'deleting color', logStyle);
|
||||
|
||||
//if color is a string, then find the corresponding button
|
||||
if (typeof color === 'string') {
|
||||
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())
|
||||
|
||||
if (color == colors[i].jscolor.toString()) {
|
||||
console.log('match')
|
||||
//set color to the color button
|
||||
color = colors[i];
|
||||
console.log('found color', color);
|
||||
|
||||
//exit loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//if the color wasn't found
|
||||
if (typeof color === 'string') {
|
||||
console.log('color not found');
|
||||
//exit function
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//hide color picker
|
||||
color.jscolor.hide();
|
||||
|
||||
|
||||
//find lightest color in palette
|
||||
var colors = document.getElementsByClassName('color-button');
|
||||
var lightestColor = [0,null];
|
||||
for (var i = 0; i < colors.length; i++) {
|
||||
|
||||
//get colors lightness
|
||||
var lightness = rgbToHsl(colors[i].jscolor.toRgb()).l;
|
||||
//console.log('%c'+lightness, logStyle)
|
||||
|
||||
//if not the color we're deleting
|
||||
if (colors[i] != color) {
|
||||
|
||||
//if lighter than the current lightest, set as the new lightest
|
||||
if (lightness > lightestColor[0]) {
|
||||
lightestColor[0] = lightness;
|
||||
lightestColor[1] = colors[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//console.log('%c'+'replacing with lightest color: '+lightestColor[1].jscolor.toString(), logStyle)
|
||||
|
||||
//replace deleted color with lightest color
|
||||
replaceAllOfColor(color.jscolor.toString(),lightestColor[1].jscolor.toString());
|
||||
|
||||
|
||||
//if the color you are deleting is the currently selected color
|
||||
if (color.parentElement.classList.contains('selected')) {
|
||||
//console.log('%c'+'deleted color is currently selected', logStyle);
|
||||
|
||||
//set current color TO LIGHTEST COLOR
|
||||
lightestColor[1].parentElement.classList.add('selected');
|
||||
context.fillStyle = '#'+lightestColor[1].jscolor.toString();
|
||||
}
|
||||
|
||||
//delete the element
|
||||
colorsMenu.removeChild(color.parentElement);
|
||||
|
||||
|
||||
|
||||
}
|
36
js/_dialogue.js
Normal file
@ -0,0 +1,36 @@
|
||||
function showDialogue (dialogueName, trackEvent) {
|
||||
if (typeof trackEvent === 'undefined') trackEvent = true;
|
||||
|
||||
dialogueOpen = true;
|
||||
popUpContainer.style.display = 'block';
|
||||
|
||||
document.getElementById(dialogueName).style.display = 'block';
|
||||
|
||||
//track google event
|
||||
if (trackEvent)
|
||||
ga('send', 'event', 'Palette Editor Dialogue', dialogueName); /*global ga*/
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
popUpContainer.addEventListener("click", function (e) {
|
||||
if (e.target == popUpContainer)
|
||||
closeDialogue();
|
||||
});
|
||||
|
||||
//add click handlers for all cancel buttons
|
||||
var cancelButtons = popUpContainer.getElementsByClassName('close-button');
|
||||
for (var i = 0; i < cancelButtons.length; i++) {
|
||||
cancelButtons[i].addEventListener('click', function () {
|
||||
closeDialogue();
|
||||
});
|
||||
}
|
21
js/_drawLine.js
Normal file
@ -0,0 +1,21 @@
|
||||
//draw a line between two points on canvas
|
||||
function line(x0,y0,x1,y1) {
|
||||
|
||||
var dx = Math.abs(x1-x0);
|
||||
var dy = Math.abs(y1-y0);
|
||||
var sx = (x0 < x1 ? 1 : -1);
|
||||
var sy = (y0 < y1 ? 1 : -1);
|
||||
var err = dx-dy;
|
||||
var breaker = 0;
|
||||
|
||||
while (true) {
|
||||
//set pixel
|
||||
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;}
|
||||
}
|
||||
}
|
140
js/_fileMenu.js
Normal file
@ -0,0 +1,140 @@
|
||||
var mainMenuItems = document.getElementById("main-menu").children;
|
||||
|
||||
//for each button in main menu (starting at 1 to avoid logo)
|
||||
for (var i = 1; i < mainMenuItems.length; i++) {
|
||||
|
||||
//get the button that's in the list item
|
||||
var menuItem = mainMenuItems[i];
|
||||
var menuButton = menuItem.children[0];
|
||||
|
||||
console.log(mainMenuItems)
|
||||
|
||||
//when you click a main menu items button
|
||||
on('click', menuButton, function (e, button) {
|
||||
console.log('parent ', button.parentElement)
|
||||
select(button.parentElement);
|
||||
});
|
||||
|
||||
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++) {
|
||||
|
||||
var subMenuItem = subMenuItems[j];
|
||||
var subMenuButton = subMenuItem.children[0];
|
||||
|
||||
subMenuButton.addEventListener("click", function () {
|
||||
|
||||
switch(this.textContent) {
|
||||
|
||||
//File Menu
|
||||
case 'New':
|
||||
showDialogue('new-pixel');
|
||||
break;
|
||||
case 'Open':
|
||||
|
||||
|
||||
//if a document exists
|
||||
if (documentCreated) {
|
||||
//check if the user wants to overwrite
|
||||
if (confirm('Opening a pixel will discard your current one. Are you sure you want to do that?'))
|
||||
//open file selection dialog
|
||||
document.getElementById("open-image-browse-holder").click();
|
||||
}
|
||||
else
|
||||
//open file selection dialog
|
||||
document.getElementById("open-image-browse-holder").click();
|
||||
|
||||
break;
|
||||
|
||||
case 'Save as...':
|
||||
if (documentCreated) {
|
||||
|
||||
//create name
|
||||
var selectedPalette = getText('palette-button');
|
||||
if (selectedPalette != 'Choose a palette...'){
|
||||
var paletteAbbreviation = palettes[selectedPalette].abbreviation;
|
||||
var fileName = 'pixel-'+paletteAbbreviation+'-'+canvasSize[0]+'x'+canvasSize[1]+'.png';
|
||||
} else {
|
||||
var fileName = 'pixel-'+canvasSize[0]+'x'+canvasSize[1]+'.png';
|
||||
selectedPalette = 'none';
|
||||
}
|
||||
|
||||
//set download link
|
||||
var linkHolder = document.getElementById("save-image-link-holder");
|
||||
linkHolder.href = canvas.toDataURL();
|
||||
linkHolder.download = fileName;
|
||||
|
||||
linkHolder.click();
|
||||
|
||||
//track google event
|
||||
ga('send', 'event', 'Pixel Editor Save', selectedPalette, canvasSize[0]+'/'+canvasSize[1]); /*global ga*/
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'Exit':
|
||||
//if a document exists, make sure they want to delete it
|
||||
if (documentCreated && confirm('Exiting will discard your current pixel. Are you sure you want to do that?')) {
|
||||
|
||||
//skip onbeforeunload prompt
|
||||
window.onbeforeunload = null;
|
||||
|
||||
//go back to main site
|
||||
window.location.href = "https://lospec.com/pixel-editor";
|
||||
}
|
||||
else {
|
||||
|
||||
//skip onbeforeunload prompt
|
||||
window.onbeforeunload = null;
|
||||
|
||||
//go back to main site
|
||||
window.location.href = "https://lospec.com/pixel-editor";
|
||||
}
|
||||
break;
|
||||
//Edit Menu
|
||||
case 'Undo':
|
||||
undo();
|
||||
break;
|
||||
case 'Redo':
|
||||
redo();
|
||||
break;
|
||||
|
||||
//Palette Menu
|
||||
case 'Add color':
|
||||
addColor('#eeeeee');
|
||||
break;
|
||||
//Help Menu
|
||||
case 'Settings':
|
||||
|
||||
//fill form with current settings values
|
||||
setValue('setting-numberOfHistoryStates', settings.numberOfHistoryStates);
|
||||
|
||||
showDialogue('settings');
|
||||
break;
|
||||
//Help Menu
|
||||
case 'Help':
|
||||
showDialogue('help');
|
||||
break;
|
||||
case 'About':
|
||||
showDialogue('about');
|
||||
break;
|
||||
case 'Changelog':
|
||||
showDialogue('changelog');
|
||||
break;
|
||||
}
|
||||
|
||||
closeMenu();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function closeMenu () {
|
||||
//remove .selected class from all menu buttons
|
||||
for (var i = 0; i < mainMenuItems.length; i++) {
|
||||
deselect(mainMenuItems[i]);
|
||||
}
|
||||
}
|
107
js/_fill.js
Normal file
@ -0,0 +1,107 @@
|
||||
function fill(cursorLocation) {
|
||||
|
||||
//changes a pixels color
|
||||
function colorPixel(tempImage, pixelPos, fillColor) {
|
||||
//console.log('colorPixel:',pixelPos);
|
||||
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
|
||||
function matchStartColor(tempImage, pixelPos, color) {
|
||||
//console.log('matchPixel:',x,y)
|
||||
|
||||
var r = tempImage.data[pixelPos];
|
||||
var g = tempImage.data[pixelPos + 1];
|
||||
var b = tempImage.data[pixelPos + 2];
|
||||
//console.log(r == color[0] && g == color[1] && b == color[2]);
|
||||
return (r == color[0] && g == color[1] && b == color[2]);
|
||||
}
|
||||
|
||||
//save history state
|
||||
new HistoryStateEditCanvas();
|
||||
//saveHistoryState({type: 'canvas', canvas: context.getImageData(0, 0, canvasSize[0], canvasSize[1])});
|
||||
//console.log('filling at '+ Math.floor(cursorLocation[0]/zoom) + ','+ Math.floor(cursorLocation[1]/zoom));
|
||||
|
||||
//temporary image holds the data while we change it
|
||||
var tempImage = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
|
||||
//this is an array that holds all of the pixels at the top of the cluster
|
||||
var topmostPixelsArray = [[Math.floor(cursorLocation[0]/zoom), Math.floor(cursorLocation[1]/zoom)]];
|
||||
//console.log('topmostPixelsArray:',topmostPixelsArray)
|
||||
|
||||
//the offset of the pixel in the temp image data to start with
|
||||
var startingPosition = (topmostPixelsArray[0][1] * canvasSize[0] + topmostPixelsArray[0][0]) * 4;
|
||||
|
||||
//the color of the cluster that is being filled
|
||||
var clusterColor = [tempImage.data[startingPosition],tempImage.data[startingPosition+1],tempImage.data[startingPosition+2]];
|
||||
|
||||
//the new color to fill with
|
||||
var fillColor = hexToRgb(context.fillStyle);
|
||||
|
||||
//if you try to fill with the same color that's already there, exit the function
|
||||
if (clusterColor[0] == fillColor.r &&
|
||||
clusterColor[1] == fillColor.g &&
|
||||
clusterColor[2] == fillColor.b )
|
||||
return;
|
||||
|
||||
//loop until there are no more values left in this array
|
||||
while (topmostPixelsArray.length) {
|
||||
|
||||
var reachLeft, reachRight;
|
||||
|
||||
//move the most recent pixel from the array and set it as our current working pixels
|
||||
var currentPixel = topmostPixelsArray.pop();
|
||||
|
||||
//set the values of this pixel to x/y variables just for readability
|
||||
var x = currentPixel[0];
|
||||
var y = currentPixel[1];
|
||||
|
||||
//this variable holds the index of where the starting values for the current pixel are in the data array
|
||||
//we multiply the number of rows down (y) times the width of each row, then add x. at the end we multiply by 4 because
|
||||
//each pixel has 4 values, rgba
|
||||
var pixelPos = (y * canvasSize[0] + x) * 4;
|
||||
|
||||
|
||||
//move up in the image until you reach the top or the pixel you hit was not the right color
|
||||
while (y-- >= 0 && matchStartColor(tempImage, pixelPos, clusterColor)) {
|
||||
pixelPos -= canvasSize[0] * 4;
|
||||
}
|
||||
pixelPos += canvasSize[0] * 4;
|
||||
++y;
|
||||
reachLeft = false;
|
||||
reachRight = false;
|
||||
while (y++ < canvasSize[1] - 1 && matchStartColor(tempImage, pixelPos, clusterColor)) {
|
||||
colorPixel(tempImage, pixelPos, fillColor);
|
||||
|
||||
if (x > 0) {
|
||||
if (matchStartColor(tempImage, pixelPos - 4, clusterColor)) {
|
||||
if (!reachLeft) {
|
||||
topmostPixelsArray.push([x - 1, y]);
|
||||
reachLeft = true;
|
||||
}
|
||||
}
|
||||
else if (reachLeft) {
|
||||
reachLeft = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (x < canvasSize[0] - 1) {
|
||||
if (matchStartColor(tempImage, pixelPos + 4, clusterColor)) {
|
||||
if (!reachRight) {
|
||||
topmostPixelsArray.push([x + 1, y]);
|
||||
reachRight = true;
|
||||
}
|
||||
}
|
||||
else if (reachRight) {
|
||||
reachRight = false;
|
||||
}
|
||||
}
|
||||
|
||||
pixelPos += canvasSize[0] * 4;
|
||||
}
|
||||
}
|
||||
context.putImageData(tempImage, 0, 0);
|
||||
//console.log('done filling')
|
||||
}
|
18
js/_getCursorPosition.js
Normal file
@ -0,0 +1,18 @@
|
||||
//get cursor position relative to canvas
|
||||
function getCursorPosition(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 -= canvas.offsetLeft;
|
||||
y -= canvas.offsetTop;
|
||||
|
||||
return [x,y];
|
||||
}
|
198
js/_history.js
Normal file
@ -0,0 +1,198 @@
|
||||
var undoStates = [];
|
||||
var redoStates = [];
|
||||
|
||||
const undoLogStyle = 'background: #87ff1c; color: black; padding: 5px;';
|
||||
|
||||
//prototype for undoing canvas changes
|
||||
function HistoryStateEditCanvas () {
|
||||
this.canvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
|
||||
this.undo = function () {
|
||||
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
context.putImageData(this.canvas, 0, 0);
|
||||
|
||||
this.canvas = currentCanvas;
|
||||
redoStates.push(this);
|
||||
}
|
||||
|
||||
this.redo = function () {
|
||||
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
context.putImageData(this.canvas, 0, 0);
|
||||
|
||||
this.canvas = currentCanvas;
|
||||
undoStates.push(this);
|
||||
}
|
||||
|
||||
//add self to undo array
|
||||
saveHistoryState(this);
|
||||
}
|
||||
|
||||
//prototype for undoing added colors
|
||||
function HistoryStateAddColor (colorValue) {
|
||||
this.colorValue = colorValue;
|
||||
|
||||
this.undo = function () {
|
||||
redoStates.push(this);
|
||||
deleteColor(this.colorValue);
|
||||
}
|
||||
|
||||
this.redo = function () {
|
||||
addColor(this.colorValue);
|
||||
undoStates.push(this);
|
||||
}
|
||||
|
||||
//add self to undo array
|
||||
saveHistoryState(this);
|
||||
}
|
||||
|
||||
//prototype for undoing deleted colors
|
||||
function HistoryStateDeleteColor (colorValue) {
|
||||
this.colorValue = colorValue;
|
||||
this.canvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
|
||||
this.undo = function () {
|
||||
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
context.putImageData(this.canvas, 0, 0);
|
||||
|
||||
addColor(this.colorValue);
|
||||
|
||||
this.canvas = currentCanvas;
|
||||
redoStates.push(this);
|
||||
}
|
||||
|
||||
this.redo = function () {
|
||||
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
context.putImageData(this.canvas, 0, 0);
|
||||
|
||||
deleteColor(this.colorValue);
|
||||
|
||||
this.canvas = currentCanvas;
|
||||
undoStates.push(this);
|
||||
}
|
||||
|
||||
//add self to undo array
|
||||
saveHistoryState(this);
|
||||
}
|
||||
|
||||
//prototype for undoing colors edits
|
||||
function HistoryStateEditColor (newColorValue, oldColorValue) {
|
||||
this.newColorValue = newColorValue;
|
||||
this.oldColorValue = oldColorValue;
|
||||
this.canvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
|
||||
this.undo = function () {
|
||||
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
context.putImageData(this.canvas, 0, 0);
|
||||
|
||||
//find new color in palette and change it back to old color
|
||||
var colors = document.getElementsByClassName('color-button');
|
||||
for (var i = 0; i < colors.length; i++) {
|
||||
console.log(newColorValue, '==', colors[i].jscolor.toString())
|
||||
if (newColorValue == colors[i].jscolor.toString()) {
|
||||
colors[i].jscolor.fromString(oldColorValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.canvas = currentCanvas;
|
||||
redoStates.push(this);
|
||||
}
|
||||
|
||||
this.redo = function () {
|
||||
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
context.putImageData(this.canvas, 0, 0);
|
||||
|
||||
//find old color in palette and change it back to new color
|
||||
var colors = document.getElementsByClassName('color-button');
|
||||
for (var i = 0; i < colors.length; i++) {
|
||||
console.log(oldColorValue, '==', colors[i].jscolor.toString())
|
||||
if (oldColorValue == colors[i].jscolor.toString()) {
|
||||
colors[i].jscolor.fromString(newColorValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.canvas = currentCanvas;
|
||||
undoStates.push(this);
|
||||
}
|
||||
|
||||
//add self to undo array
|
||||
saveHistoryState(this);
|
||||
}
|
||||
|
||||
|
||||
//rename to add undo state
|
||||
function saveHistoryState (state) {
|
||||
console.log('%csaving history state', undoLogStyle)
|
||||
console.log(state)
|
||||
|
||||
//get current canvas data and save to undoStates array
|
||||
undoStates.push(state);
|
||||
|
||||
//limit the number of states to settings.numberOfHistoryStates
|
||||
if (undoStates.length > settings.numberOfHistoryStates) {
|
||||
undoStates = undoStates.splice(-settings.numberOfHistoryStates, settings.numberOfHistoryStates);
|
||||
}
|
||||
|
||||
//there is now definitely at least 1 undo state, so the button shouldnt be disabled
|
||||
document.getElementById('undo-button').classList.remove('disabled');
|
||||
|
||||
//there should be no redoStates after an undoState is saved
|
||||
redoStates = [];
|
||||
|
||||
console.log(undoStates)
|
||||
console.log(redoStates)
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
//get state
|
||||
var undoState = undoStates[undoStates.length-1];
|
||||
console.log(undoState);
|
||||
|
||||
//restore the state
|
||||
undoState.undo();
|
||||
|
||||
//remove from the undo list
|
||||
undoStates.splice(undoStates.length-1,1);
|
||||
|
||||
//if theres none left to undo, disable the option
|
||||
if (undoStates.length == 0)
|
||||
document.getElementById('undo-button').classList.add('disabled');
|
||||
}
|
||||
|
||||
console.log(undoStates)
|
||||
console.log(redoStates)
|
||||
}
|
||||
|
||||
function redo () {
|
||||
console.log('%credo', undoLogStyle);
|
||||
|
||||
if (redoStates.length > 0) {
|
||||
|
||||
//enable undo button
|
||||
document.getElementById('undo-button').classList.remove('disabled');
|
||||
|
||||
//get state
|
||||
var redoState = redoStates[redoStates.length-1];
|
||||
console.log(redoState);
|
||||
|
||||
//restore the state
|
||||
redoState.redo();
|
||||
|
||||
//remove from redo array
|
||||
redoStates.splice(redoStates.length-1,1);
|
||||
|
||||
//if theres none left to redo, disable the option
|
||||
if (redoStates.length == 0)
|
||||
document.getElementById('redo-button').classList.add('disabled');
|
||||
}
|
||||
console.log(undoStates)
|
||||
console.log(redoStates)
|
||||
}
|
66
js/_hotkeyListener.js
Normal file
@ -0,0 +1,66 @@
|
||||
var spacePressed = false;
|
||||
|
||||
function KeyPress(e) {
|
||||
var keyboardEvent = window.event? event : e;
|
||||
|
||||
//if the user is typing in an input field, ignore these hotkeys
|
||||
if (document.activeElement.tagName == 'INPUT') return;
|
||||
|
||||
//if no document has been created yet,
|
||||
//orthere is a dialog box open
|
||||
//ignore hotkeys
|
||||
if (!documentCreated || dialogueOpen) return;
|
||||
|
||||
//
|
||||
switch (keyboardEvent.keyCode) {
|
||||
//pencil tool - 1, b
|
||||
case 49: case 66:
|
||||
changeTool('pencil');
|
||||
break;
|
||||
//fill tool - 2, f
|
||||
case 50: case 70:
|
||||
changeTool('fill');
|
||||
break;
|
||||
//eyedropper - 3, e
|
||||
case 51: case 69:
|
||||
changeTool('eyedropper');
|
||||
break;
|
||||
//pan - 4, p, m
|
||||
case 52: case 80: case 77:
|
||||
changeTool('pan');
|
||||
break;
|
||||
//zoom - 5
|
||||
case 53:
|
||||
changeTool('zoom');
|
||||
break;
|
||||
//Z
|
||||
case 90:
|
||||
console.log('PRESSED Z ', keyboardEvent.ctrlKey)
|
||||
//CTRL+ALT+Z redo
|
||||
if (keyboardEvent.altKey && keyboardEvent.ctrlKey)
|
||||
redo();
|
||||
//CTRL+Z undo
|
||||
else if (keyboardEvent.ctrlKey)
|
||||
undo();
|
||||
//Z switch to zoom tool
|
||||
else
|
||||
changeTool('zoom');
|
||||
break;
|
||||
//redo - ctrl y
|
||||
case 89:
|
||||
if (keyboardEvent.ctrlKey)
|
||||
redo();
|
||||
break;
|
||||
case 32:
|
||||
spacePressed=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
document.onkeydown = KeyPress;
|
||||
|
||||
window.addEventListener("keyup", function (e) {
|
||||
|
||||
if (e.keyCode == 32) spacePressed = false;
|
||||
|
||||
});
|
23
js/_initColor.js
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
//format a color button
|
||||
function initColor (colorElement) {
|
||||
//console.log('initColor()');
|
||||
//console.log(document.getElementById('jscolor-hex-input'))
|
||||
|
||||
|
||||
//add jscolor picker for this color
|
||||
colorElement.jscolor = new jscolor(colorElement.parentElement, {
|
||||
valueElement: null, //if you dont set this to null, it turns the button (colorElement) into text, we set it when you open the picker
|
||||
styleElement: colorElement,
|
||||
width:151,
|
||||
position: 'left',
|
||||
padding:0,
|
||||
borderWidth:14,
|
||||
borderColor: '#332f35',
|
||||
backgroundColor: '#332f35',
|
||||
insetColor: 'transparent',
|
||||
value: colorElement.style.backgroundColor,
|
||||
deleteButton: true,
|
||||
});
|
||||
|
||||
}
|
1964
js/_jscolor.js
Normal file
61
js/_loadImage.js
Normal file
@ -0,0 +1,61 @@
|
||||
document.getElementById('open-image-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') {
|
||||
|
||||
//load file
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(e) {
|
||||
var img = new Image();
|
||||
img.onload = function() {
|
||||
|
||||
//create a new pixel with the images dimentions
|
||||
newPixel(this.width, this.height, []);
|
||||
|
||||
//draw the image onto the canvas
|
||||
context.drawImage(img, 0, 0);
|
||||
|
||||
var colorPalette = {};
|
||||
var imagePixelData = context.getImageData(0,0,this.width, this.height).data;
|
||||
|
||||
var imagePixelDataLength = imagePixelData.length;
|
||||
|
||||
console.log(imagePixelData)
|
||||
for (var i = 0; i < imagePixelDataLength; i += 4) {
|
||||
var color = imagePixelData[i]+','+imagePixelData[i + 1]+','+imagePixelData[i + 2];
|
||||
if (!colorPalette[color]) {
|
||||
colorPalette[color] = {r:imagePixelData[i],g:imagePixelData[i + 1],b:imagePixelData[i + 2]};
|
||||
|
||||
//don't allow more than 256 colors to be added
|
||||
if (Object.keys(colorPalette).length >= settings.maxColorsOnImportedImage) {
|
||||
alert('The image loaded seems to have more than '+settings.maxColorsOnImportedImage+' colors.')
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//create array out of colors object
|
||||
var colorPaletteArray = [];
|
||||
for (var color in colorPalette) {
|
||||
if( colorPalette.hasOwnProperty(color) ) {
|
||||
colorPaletteArray.push('#'+rgbToHex(colorPalette[color]));
|
||||
}
|
||||
}
|
||||
console.log('COLOR PALETTE ARRAY', colorPaletteArray)
|
||||
|
||||
//create palette form colors array
|
||||
createColorPalette(colorPaletteArray, false);
|
||||
|
||||
//track google event
|
||||
ga('send', 'event', 'Pixel Editor Load', colorPalette.length, this.width+'/'+this.height); /*global ga*/
|
||||
|
||||
};
|
||||
img.src = e.target.result;
|
||||
};
|
||||
fileReader.readAsDataURL(this.files[0]);
|
||||
}
|
||||
else alert('Only PNG and GIF files are allowed at this time.');
|
||||
}
|
||||
});
|
50
js/_loadPalette.js
Normal file
@ -0,0 +1,50 @@
|
||||
//this is called when a user picks a file after selecting "load palette" from the new pixel dialogue
|
||||
|
||||
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') {
|
||||
|
||||
//load file
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(e) {
|
||||
var img = new Image();
|
||||
img.onload = function() {
|
||||
|
||||
//draw image onto the temporary canvas
|
||||
var loadPaletteCanvas = document.getElementById("load-palette-canvas-holder");
|
||||
var loadPaletteContext = loadPaletteCanvas.getContext("2d");
|
||||
|
||||
loadPaletteCanvas.width = img.width;
|
||||
loadPaletteCanvas.height = img.height;
|
||||
|
||||
loadPaletteContext.drawImage(img, 0, 0);
|
||||
|
||||
//create array to hold found colors
|
||||
var colorPalette = [];
|
||||
var imagePixelData = loadPaletteContext.getImageData(0,0,this.width, this.height).data;
|
||||
|
||||
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]);
|
||||
if (colorPalette.indexOf(color) == -1) {
|
||||
colorPalette.push(color);
|
||||
}
|
||||
}
|
||||
|
||||
//add to palettes so that it can be loaded when they click okay
|
||||
palettes['Loaded palette'] = {};
|
||||
palettes['Loaded palette'].colors = colorPalette;
|
||||
setText('palette-button', 'Loaded palette');
|
||||
};
|
||||
img.src = e.target.result;
|
||||
};
|
||||
fileReader.readAsDataURL(this.files[0]);
|
||||
}
|
||||
else alert('Only PNG and GIF files are supported at this time.');
|
||||
}
|
||||
});
|
210
js/_mouseEvents.js
Normal file
@ -0,0 +1,210 @@
|
||||
|
||||
//mousedown - start drawing
|
||||
window.addEventListener("mousedown", function (mouseEvent) {
|
||||
|
||||
//if no document has been created yet, or this is a dialog open
|
||||
if (!documentCreated || dialogueOpen) return;
|
||||
//prevent right mouse clicks and such, which will open unwanted menus
|
||||
//mouseEvent.preventDefault();
|
||||
|
||||
lastPos = getCursorPosition(mouseEvent);
|
||||
|
||||
dragging = true;
|
||||
//left or right click
|
||||
if (mouseEvent.which == 1) {
|
||||
|
||||
if (spacePressed)
|
||||
currentTool = 'pan';
|
||||
else if (mouseEvent.altKey)
|
||||
currentTool = 'eyedropper';
|
||||
else if (mouseEvent.target == canvas && currentTool == 'pencil')
|
||||
new HistoryStateEditCanvas();
|
||||
//saveHistoryState({type: 'canvas', canvas: context.getImageData(0, 0, canvasSize[0], canvasSize[1])});
|
||||
|
||||
updateCursor();
|
||||
|
||||
draw(mouseEvent);
|
||||
}
|
||||
else if (currentTool == 'pencil' && mouseEvent.which == 3) {
|
||||
currentTool = 'resize-brush';
|
||||
prevBrushSize=brushSize;
|
||||
}
|
||||
|
||||
if (currentTool == 'eyedropper' && mouseEvent.target == canvas)
|
||||
eyedropperPreview.style.display = 'block';
|
||||
|
||||
return false;
|
||||
}, false);
|
||||
|
||||
|
||||
|
||||
//mouseup - end drawing
|
||||
window.addEventListener("mouseup", function (mouseEvent) {
|
||||
|
||||
closeMenu();
|
||||
|
||||
if (!documentCreated || dialogueOpen) return;
|
||||
|
||||
if (currentTool == 'eyedropper' && mouseEvent.target == canvas) {
|
||||
var cursorLocation = getCursorPosition(mouseEvent);
|
||||
var selectedColor = context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1);
|
||||
var newColor = rgbToHex(selectedColor.data[0],selectedColor.data[1],selectedColor.data[2]);
|
||||
|
||||
console.log(newColor);
|
||||
|
||||
var colors = document.getElementsByClassName('color-button');
|
||||
for (var i = 0; i < colors.length; i++) {
|
||||
console.log(colors[i].jscolor.toString());
|
||||
|
||||
//if picked color matches this color
|
||||
if (newColor == colors[i].jscolor.toString()) {
|
||||
console.log('color found');
|
||||
|
||||
//remove current color selection
|
||||
var selectedColor = document.querySelector("#colors-menu li.selected")
|
||||
if (selectedColor) selectedColor.classList.remove("selected");
|
||||
|
||||
//set current color
|
||||
context.fillStyle = '#'+newColor;
|
||||
|
||||
//make color selected
|
||||
colors[i].parentElement.classList.add('selected');
|
||||
|
||||
//hide eyedropper
|
||||
eyedropperPreview.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if (currentTool == 'fill' && mouseEvent.target == canvas) {
|
||||
console.log('filling')
|
||||
//if you clicked on anything but the canvas, do nothing
|
||||
if (!mouseEvent.target == canvas) return;
|
||||
|
||||
//get cursor postion
|
||||
var cursorLocation = getCursorPosition(mouseEvent);
|
||||
|
||||
//offset to match cursor point
|
||||
cursorLocation[0] += 2;
|
||||
cursorLocation[1] += 12;
|
||||
|
||||
//fill starting at the location
|
||||
fill(cursorLocation);
|
||||
}
|
||||
else if (currentTool == 'zoom' && mouseEvent.target == canvas) {
|
||||
if (mouseEvent.which == 1) changeZoom('in', getCursorPosition(mouseEvent));
|
||||
else if (mouseEvent.which == 3) changeZoom('out', getCursorPosition(mouseEvent))
|
||||
}
|
||||
|
||||
dragging = false;
|
||||
currentTool = currentToolTemp;
|
||||
|
||||
updateCursor();
|
||||
|
||||
|
||||
}, false);
|
||||
|
||||
|
||||
//mouse is moving on canvas
|
||||
window.addEventListener("mousemove", draw, false);
|
||||
function draw (mouseEvent) {
|
||||
var cursorLocation = getCursorPosition(mouseEvent);
|
||||
|
||||
//if a document hasnt yet been created, exit this function
|
||||
if (!documentCreated || dialogueOpen) return;
|
||||
|
||||
|
||||
eyedropperPreview.style.display = 'none';
|
||||
|
||||
if (currentTool == 'pencil') {
|
||||
|
||||
//move the brush preview
|
||||
brushPreview.style.left = cursorLocation[0] + canvas.offsetLeft - brushSize * zoom / 2 + 'px';
|
||||
brushPreview.style.top = cursorLocation[1] + canvas.offsetTop - brushSize * zoom / 2 + 'px';
|
||||
|
||||
//hide brush preview outside of canvas / canvas view
|
||||
if (mouseEvent.target == canvas || mouseEvent.target == canvasView)
|
||||
brushPreview.style.visibility = 'visible';
|
||||
else
|
||||
brushPreview.style.visibility = 'hidden';
|
||||
|
||||
//draw line to current pixel
|
||||
if (dragging) {
|
||||
if (mouseEvent.target == canvas || mouseEvent.target == canvasView) {
|
||||
line(Math.floor(lastPos[0]/zoom),Math.floor(lastPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom));
|
||||
lastPos = cursorLocation;
|
||||
}
|
||||
}
|
||||
|
||||
//get lightness value of color
|
||||
var selectedColor = context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1).data;
|
||||
var colorLightness = Math.max(selectedColor[0],selectedColor[1],selectedColor[2])
|
||||
|
||||
//for the darkest 50% of colors, change the brush preview to dark mode
|
||||
if (colorLightness>127) brushPreview.classList.remove('dark');
|
||||
else brushPreview.classList.add('dark');
|
||||
}
|
||||
else if (currentTool == 'pan' && dragging) {
|
||||
|
||||
|
||||
setCanvasOffset(canvas.offsetLeft + (cursorLocation[0] - lastPos[0]), canvas.offsetTop + (cursorLocation[1] - lastPos[1]))
|
||||
/*
|
||||
if (
|
||||
//right
|
||||
canvas.offsetLeft + (cursorLocation[0] - lastPos[0]) < window.innerWidth - canvasSize[0]*zoom*0.25 - 48 &&
|
||||
//left
|
||||
canvas.offsetLeft + (cursorLocation[0] - lastPos[0]) > -canvasSize[0]*zoom*0.75 + 64)
|
||||
canvas.style.left = canvas.offsetLeft + (cursorLocation[0] - lastPos[0]) +'px';
|
||||
|
||||
if (
|
||||
//bottom
|
||||
canvas.offsetTop + (cursorLocation[1] - lastPos[1]) < window.innerHeight-canvasSize[1]*zoom*0.25 &&
|
||||
//top
|
||||
canvas.offsetTop + (cursorLocation[1] - lastPos[1]) > -canvasSize[0]*zoom*0.75 + 48)
|
||||
canvas.style.top = canvas.offsetTop + (cursorLocation[1] - lastPos[1]) +'px';
|
||||
*/
|
||||
}
|
||||
else if (currentTool == 'eyedropper' && dragging && mouseEvent.target == canvas) {
|
||||
var selectedColor = context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1).data;
|
||||
eyedropperPreview.style.borderColor = '#'+rgbToHex(selectedColor[0],selectedColor[1],selectedColor[2]);
|
||||
eyedropperPreview.style.display = 'block';
|
||||
|
||||
eyedropperPreview.style.left = cursorLocation[0] + canvas.offsetLeft - 30 + 'px';
|
||||
eyedropperPreview.style.top = cursorLocation[1] + canvas.offsetTop - 30 + 'px';
|
||||
|
||||
var colorLightness = Math.max(selectedColor[0],selectedColor[1],selectedColor[2]);
|
||||
|
||||
//for the darkest 50% of colors, change the eyedropper preview to dark mode
|
||||
if (colorLightness>127) eyedropperPreview.classList.remove('dark');
|
||||
else eyedropperPreview.classList.add('dark');
|
||||
}
|
||||
else if (currentTool == 'resize-brush' && dragging) {
|
||||
|
||||
//get new brush size based on x distance from original clicking location
|
||||
var distanceFromClick = cursorLocation[0] - lastPos[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 brushSizeChange = Math.round(distanceFromClick/10);
|
||||
var newBrushSize = prevBrushSize + brushSizeChange;
|
||||
|
||||
//set the brush to the new size as long as its bigger than 1
|
||||
brushSize = Math.max(1,newBrushSize);
|
||||
|
||||
//fix offset so the cursor stays centered
|
||||
brushPreview.style.left = lastPos[0] + canvas.offsetLeft - brushSize * zoom / 2 + 'px';
|
||||
brushPreview.style.top = lastPos[1] + canvas.offsetTop - brushSize * zoom / 2 + 'px';
|
||||
|
||||
updateCursor();
|
||||
}
|
||||
}
|
||||
|
||||
//mousewheel scrroll
|
||||
canvasView.addEventListener("wheel", function(mouseEvent){
|
||||
|
||||
if (currentTool == 'zoom' || mouseEvent.altKey) {
|
||||
if (mouseEvent.deltaY < 0) changeZoom('in', getCursorPosition(mouseEvent));
|
||||
else if (mouseEvent.deltaY > 0) changeZoom('out', getCursorPosition(mouseEvent))
|
||||
}
|
||||
|
||||
});
|
79
js/_newPixel.js
Normal file
@ -0,0 +1,79 @@
|
||||
|
||||
function newPixel (width, height, palette) {
|
||||
|
||||
canvasSize = [width,height];
|
||||
|
||||
var maxHorizontalZoom = Math.floor(window.innerWidth/canvasSize[0]*0.75);
|
||||
var maxVerticalZoom = Math.floor(window.innerHeight/canvasSize[1]*0.75);
|
||||
|
||||
zoom = Math.min(maxHorizontalZoom,maxVerticalZoom);
|
||||
if (zoom < 1) zoom = 1;
|
||||
|
||||
//resize canvas
|
||||
canvas.width = canvasSize[0];
|
||||
canvas.height = canvasSize[1];
|
||||
canvas.style.width = (canvas.width*zoom)+'px';
|
||||
canvas.style.height = (canvas.height*zoom)+'px';
|
||||
|
||||
//unhide canvas
|
||||
canvas.style.display = 'block';
|
||||
|
||||
//center canvas in window
|
||||
canvas.style.left = 64+canvasView.clientWidth/2-(canvasSize[0]*zoom/2)+'px';
|
||||
canvas.style.top = 48+canvasView.clientHeight/2-(canvasSize[1]*zoom/2)+'px';
|
||||
|
||||
//remove current palette
|
||||
colors = document.getElementsByClassName('color-button');
|
||||
while (colors.length > 0) {
|
||||
colors[0].parentElement.remove();
|
||||
}
|
||||
|
||||
//add colors from selected palette
|
||||
var selectedPalette = getText('palette-button');
|
||||
if (selectedPalette != 'Choose a palette...') {
|
||||
|
||||
//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
|
||||
createColorPalette(palettes[selectedPalette].colors,true);
|
||||
}
|
||||
else {
|
||||
//this wasn't a specified palette, so reset the url
|
||||
history.pushState(null, null, '/pixel-editor/app');
|
||||
|
||||
//generate default colors
|
||||
var fg = hslToRgb(Math.floor(Math.random()*255), 230,70);
|
||||
var bg = hslToRgb(Math.floor(Math.random()*255), 230,170);
|
||||
|
||||
//convert colors to hex
|
||||
var defaultForegroundColor = rgbToHex(fg.r,fg.g,fg.b);
|
||||
var defaultBackgroundColor = rgbToHex(bg.r,bg.g,bg.b);
|
||||
|
||||
//add colors to paletee
|
||||
addColor(defaultForegroundColor).classList.add('selected');
|
||||
addColor(defaultBackgroundColor);
|
||||
|
||||
//fill background of canvas with bg color
|
||||
context.fillStyle = '#'+defaultBackgroundColor;
|
||||
context.fillRect(0, 0, canvasSize[0], canvasSize[1]);
|
||||
|
||||
console.log('#'+defaultBackgroundColor)
|
||||
|
||||
//set current drawing color as foreground color
|
||||
context.fillStyle = '#'+defaultForegroundColor;
|
||||
selectedPalette = 'none';
|
||||
}
|
||||
|
||||
//reset undo and redo states
|
||||
undoStates = [];
|
||||
redoStates = [];
|
||||
|
||||
closeDialogue();
|
||||
updateCursor();
|
||||
|
||||
document.getElementById('save-as-button').classList.remove('disabled');
|
||||
documentCreated = true;
|
||||
|
||||
}
|
12
js/_onLoad.js
Normal file
@ -0,0 +1,12 @@
|
||||
//when the page is donw loading, you can get ready to start
|
||||
window.onload = function(){
|
||||
updateCursor();
|
||||
|
||||
//if the user specified dimentions
|
||||
if (specifiedDimentions)
|
||||
//create a new pixel
|
||||
newPixel(getValue('size-width'),getValue('size-height'),'');
|
||||
else
|
||||
//otherwise show the new pixel dialog
|
||||
showDialogue('new-pixel', false);
|
||||
};
|
7
js/_onbeforeunload.js
Normal file
@ -0,0 +1,7 @@
|
||||
//prevent user from leaving page with unsaved data
|
||||
window.onbeforeunload = function() {
|
||||
if (documentCreated)
|
||||
return 'You will lose your pixel if it\'s not saved!';
|
||||
|
||||
else return;
|
||||
}
|
62
js/_palettes.js
Normal file
@ -0,0 +1,62 @@
|
||||
//populate palettes list in new pixel menu
|
||||
Object.keys(palettes).forEach(function(paletteName,index) {
|
||||
|
||||
var palettesMenu = document.getElementById("palette-menu");
|
||||
|
||||
//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);
|
||||
//Show empty palette option
|
||||
document.getElementById('no-palette-button').style.display = 'block';
|
||||
}
|
||||
|
||||
on('click', button, function() {
|
||||
|
||||
//hide the dropdown menu
|
||||
deselect('palette-menu');
|
||||
deselect('palette-button');
|
||||
|
||||
//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);
|
||||
});
|
||||
});
|
||||
|
||||
//select no palette
|
||||
on('click', 'no-palette-button', function () {
|
||||
document.getElementById('no-palette-button').style.display = 'none';
|
||||
setText('palette-button', 'Choose a palette...');
|
||||
});
|
||||
|
||||
//select load palette
|
||||
on('click', 'load-palette-button', function () {
|
||||
document.getElementById("load-palette-browse-holder").click();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
on('click', 'palette-button', function (e){
|
||||
toggle('palette-button');
|
||||
toggle('palette-menu');
|
||||
|
||||
deselect('preset-button');
|
||||
deselect('preset-menu');
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
on('click', 'new-pixel', function (){
|
||||
deselect('preset-button');
|
||||
deselect('preset-menu');
|
||||
deselect('palette-button');
|
||||
deselect('palette-menu');
|
||||
});
|
64
js/_presets.js
Normal file
@ -0,0 +1,64 @@
|
||||
//prests
|
||||
var presets = {
|
||||
'Gameboy Color': {
|
||||
width: 240,
|
||||
height: 203,
|
||||
palette: 'Gameboy Color'
|
||||
},
|
||||
'PICO-8': {
|
||||
width: 128,
|
||||
height: 128,
|
||||
palette: 'PICO-8',
|
||||
},
|
||||
'Commodore 64': {
|
||||
width: 40,
|
||||
height: 80,
|
||||
palette: 'Commodore 64'
|
||||
}
|
||||
};
|
||||
|
||||
//populate preset list in new pixel menu
|
||||
Object.keys(presets).forEach(function(presetName,index) {
|
||||
|
||||
var presetsMenu = document.getElementById("preset-menu");
|
||||
|
||||
//create button
|
||||
var button = document.createElement("button");
|
||||
button.appendChild(document.createTextNode(presetName));
|
||||
|
||||
//insert new element
|
||||
presetsMenu.appendChild(button);
|
||||
|
||||
//add click event listener
|
||||
on('click', button, function() {
|
||||
|
||||
//change dimentions on new pixel form
|
||||
setValue('size-width', presets[presetName].width);
|
||||
setValue('size-height', presets[presetName].height);
|
||||
|
||||
//set the text of the dropdown to the newly selected preset
|
||||
setText('palette-button', presets[presetName].palette);
|
||||
|
||||
//hide the dropdown menu
|
||||
deselect('preset-menu');
|
||||
deselect('preset-button');
|
||||
|
||||
//set the text of the dropdown to the newly selected preset
|
||||
setText('preset-button', presetName);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
on('click', 'preset-button', function (e){
|
||||
//open or close the preset menu
|
||||
toggle('preset-button');
|
||||
toggle('preset-menu');
|
||||
|
||||
//close the palette menu
|
||||
deselect('palette-button');
|
||||
deselect('palette-menu');
|
||||
|
||||
//stop the click from propogating to the parent element
|
||||
e.stopPropagation();
|
||||
});
|
25
js/_replaceAllOfColor.js
Normal file
@ -0,0 +1,25 @@
|
||||
//replaces all of a single color on the canvas with a different color
|
||||
//input two rgb color objects {r:0,g:0,b:0}
|
||||
function replaceAllOfColor (oldColor, newColor) {
|
||||
|
||||
//convert strings to objects if nessesary
|
||||
if (typeof oldColor === 'string') oldColor = hexToRgb(oldColor);
|
||||
if (typeof newColor === 'string') newColor = hexToRgb(newColor);
|
||||
|
||||
//create temporary image from canvas to search through
|
||||
var tempImage = context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
|
||||
//loop through all pixels
|
||||
for (var i=0;i<tempImage.data.length;i+=4) {
|
||||
//check if pixel matches old color
|
||||
if(tempImage.data[i]==oldColor.r && tempImage.data[i+1]==oldColor.g && tempImage.data[i+2]==oldColor.b){
|
||||
//change to new color
|
||||
tempImage.data[i]=newColor.r;
|
||||
tempImage.data[i+1]=newColor.g;
|
||||
tempImage.data[i+2]=newColor.b;
|
||||
}
|
||||
}
|
||||
|
||||
//put temp image back onto canvas
|
||||
context.putImageData(tempImage,0,0);
|
||||
}
|
24
js/_setCanvasOffset.js
Normal file
@ -0,0 +1,24 @@
|
||||
function setCanvasOffset (offsetLeft, offsetTop) {
|
||||
|
||||
//horizontal offset
|
||||
var minXOffset = -canvasSize[0]*zoom+ 164;
|
||||
var maxXOffset = window.innerWidth - 148
|
||||
|
||||
if (offsetLeft < minXOffset)
|
||||
canvas.style.left = minXOffset +'px';
|
||||
else if (offsetLeft > maxXOffset)
|
||||
canvas.style.left = maxXOffset +'px';
|
||||
else
|
||||
canvas.style.left = offsetLeft +'px';
|
||||
|
||||
//vertical offset
|
||||
var minYOffset = -canvasSize[1]*zoom + 164;
|
||||
var maxYOffset = window.innerHeight-100;
|
||||
|
||||
if (offsetTop < minYOffset)
|
||||
canvas.style.top = minYOffset +'px';
|
||||
else if (offsetTop > maxYOffset)
|
||||
canvas.style.top = maxYOffset +'px';
|
||||
else
|
||||
canvas.style.top = offsetTop +'px';
|
||||
}
|
45
js/_settings.js
Normal file
@ -0,0 +1,45 @@
|
||||
var settings;
|
||||
|
||||
if (!Cookies.enabled) {
|
||||
document.getElementById('cookies-disabled-warning').style.display = 'block';
|
||||
}
|
||||
|
||||
//try to load settings from cookie
|
||||
var settingsFromCookie = Cookies.get('pixelEditorSettings');
|
||||
if(!settingsFromCookie) {
|
||||
console.log('settings cookie not found')
|
||||
settings = {
|
||||
switchToChangedColor: true,
|
||||
enableDynamicCursorOutline: true, //unused - performance
|
||||
enableBrushPreview: true, //unused - performance
|
||||
enableEyedropperPreview: true, //unused - performance
|
||||
numberOfHistoryStates: 20,
|
||||
maxColorsOnImportedImage: 128
|
||||
};
|
||||
}
|
||||
else{
|
||||
console.log('settings cookie found');
|
||||
console.log(settingsFromCookie);
|
||||
var settings = JSON.parse(settingsFromCookie);
|
||||
}
|
||||
console.log(settings);
|
||||
|
||||
//on clicking the save button in the settings dialog
|
||||
on('click', 'save-settings', function (){
|
||||
|
||||
//check if values are valid
|
||||
if (isNaN(getValue('setting-numberOfHistoryStates'))) {
|
||||
alert('Invalid value for numberOfHistoryStates')
|
||||
return;
|
||||
}
|
||||
|
||||
//save new settings to settings object
|
||||
settings.numberOfHistoryStates = getValue('setting-numberOfHistoryStates');
|
||||
|
||||
//save settings object to cookie
|
||||
var cookieValue = JSON.stringify(settings);
|
||||
Cookies.set('pixelEditorSettings', cookieValue, { expires: Infinity });
|
||||
|
||||
//close window
|
||||
closeDialogue();
|
||||
});
|
47
js/_toolButtons.js
Normal file
@ -0,0 +1,47 @@
|
||||
//pencil
|
||||
on('click',"pencil-button", function(){
|
||||
changeTool('pencil');
|
||||
}, false);
|
||||
|
||||
//pencil bigger
|
||||
on('click',"pencil-bigger-button", function(){
|
||||
brushSize++;
|
||||
updateCursor();
|
||||
}, false);
|
||||
|
||||
//pencil smaller
|
||||
on('click',"pencil-smaller-button", function(e){
|
||||
if(brushSize > 1) brushSize--;
|
||||
updateCursor();
|
||||
}, false);
|
||||
|
||||
//fill
|
||||
on('click',"fill-button", function(){
|
||||
changeTool('fill');
|
||||
}, false);
|
||||
|
||||
//pan
|
||||
on('click',"pan-button", function(){
|
||||
changeTool('pan');
|
||||
}, false);
|
||||
|
||||
//eyedropper
|
||||
on('click',"eyedropper-button", function(){
|
||||
changeTool('eyedropper');
|
||||
}, false);
|
||||
|
||||
//zoom tool button
|
||||
on('click',"zoom-button", function(){
|
||||
changeTool('zoom');
|
||||
}, false);
|
||||
|
||||
//zoom in button
|
||||
on('click',"zoom-in-button", function(){
|
||||
//changeZoom('in',[window.innerWidth/2-canvas.offsetLeft,window.innerHeight/2-canvas.offsetTop]);
|
||||
changeZoom('in',[canvasSize[0]*zoom/2,canvasSize[1]*zoom/2]);
|
||||
}, false);
|
||||
|
||||
//zoom out button
|
||||
on('click',"zoom-out-button", function(){
|
||||
changeZoom('out',[canvasSize[0]*zoom/2,canvasSize[1]*zoom/2]);
|
||||
}, false);
|
32
js/_updateCursor.js
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
//set the correct cursor for the current tool
|
||||
function updateCursor () {
|
||||
if (currentTool == 'pencil' || currentTool == 'resize-brush') {
|
||||
canvasView.style.cursor = 'crosshair';
|
||||
brushPreview.style.display = 'block';
|
||||
brushPreview.style.width = brushSize * zoom + 'px';
|
||||
brushPreview.style.height = brushSize * zoom + 'px';
|
||||
} else
|
||||
brushPreview.style.display = 'none';
|
||||
|
||||
if (currentTool == 'eyedropper') {
|
||||
canvasView.style.cursor = "url('/pixel-editor/eyedropper.png'), auto";
|
||||
} else
|
||||
eyedropperPreview.style.display = 'none';
|
||||
|
||||
if (currentTool == 'pan')
|
||||
if (dragging)
|
||||
canvasView.style.cursor = "url('/pixel-editor/pan-held.png'), auto";
|
||||
else
|
||||
canvasView.style.cursor = "url('/pixel-editor/pan.png'), auto";
|
||||
|
||||
if (currentTool == 'fill')
|
||||
canvasView.style.cursor = "url('/pixel-editor/fill.png'), auto";
|
||||
|
||||
if (currentTool == 'zoom')
|
||||
canvasView.style.cursor = "url('/pixel-editor/zoom-in.png'), auto";
|
||||
|
||||
if (currentTool == 'resize-brush')
|
||||
canvasView.style.cursor = 'default';
|
||||
|
||||
}
|
25
js/_variables.js
Normal file
@ -0,0 +1,25 @@
|
||||
//init variables
|
||||
var canvasSize,zoom;
|
||||
var dragging = false;
|
||||
var lastPos = [0,0];
|
||||
var canvasPosition;
|
||||
var currentTool = 'pencil';
|
||||
var currentToolTemp = 'pencil';
|
||||
var brushSize = 1;
|
||||
var prevBrushSize = 1;
|
||||
var menuOpen = false;
|
||||
var dialogueOpen = false;
|
||||
var documentCreated = false;
|
||||
|
||||
//common elements
|
||||
var brushPreview = document.getElementById("brush-preview");
|
||||
var eyedropperPreview = document.getElementById("eyedropper-preview");
|
||||
var canvasView = document.getElementById("canvas-view");
|
||||
var colors = document.getElementsByClassName("color-button");
|
||||
var colorsMenu = document.getElementById("colors-menu");
|
||||
var popUpContainer = document.getElementById("pop-up-container");
|
||||
|
||||
//html canvas
|
||||
var canvas = document.getElementById("pixel-canvas");
|
||||
var context = canvas.getContext("2d");
|
||||
|
67
js/pixel-editor.js
Normal file
@ -0,0 +1,67 @@
|
||||
|
||||
|
||||
/**utilities**/
|
||||
//=include utilities/on.js
|
||||
//=include utilities/onChildren.js
|
||||
//=include utilities/onClick.js
|
||||
//=include utilities/onClickChildren.js
|
||||
//=include utilities/select.js
|
||||
//=include utilities/getSetText.js
|
||||
//=include utilities/getSetValue.js
|
||||
//=include utilities/hexToRgb.js
|
||||
//=include utilities/rgbToHex.js
|
||||
//=include utilities/rgbToHsl.js
|
||||
//=include utilities/hslToRgb.js
|
||||
//=include libraries/cookies.js
|
||||
|
||||
|
||||
|
||||
/**init**/
|
||||
//=include _variables.js
|
||||
//=include _settings.js
|
||||
|
||||
/**dropdown formatting**/
|
||||
//=include _presets.js
|
||||
//=include _palettes.js
|
||||
|
||||
/**functions**/
|
||||
//=include _newPixel.js
|
||||
//=include _createColorPalette.js
|
||||
//=include _setCanvasOffset.js
|
||||
//=include _changeZoom.js
|
||||
//=include _addColor.js
|
||||
//=include _colorChanged.js
|
||||
//=include _initColor.js
|
||||
//=include _changeTool.js
|
||||
//=include _dialogue.js
|
||||
//=include _updateCursor.js
|
||||
//=include _drawLine.js
|
||||
//=include _getCursorPosition.js
|
||||
//=include _fill.js
|
||||
//=include _history.js
|
||||
//=include _deleteColor.js
|
||||
//=include _replaceAllOfColor.js
|
||||
|
||||
|
||||
/**load file**/
|
||||
//=include _loadImage.js
|
||||
//=include _loadPalette.js
|
||||
|
||||
/**event listeners**/
|
||||
//=include _hotkeyListener.js
|
||||
//=include _mouseEvents.js
|
||||
|
||||
/**buttons**/
|
||||
//=include _toolButtons.js
|
||||
//=include _addColorButton.js
|
||||
//=include _clickedColor.js
|
||||
//=include _fileMenu.js
|
||||
//=include _createButton.js
|
||||
|
||||
|
||||
/**onload**/
|
||||
//=include _onLoad.js
|
||||
//=include _onbeforeunload.js
|
||||
|
||||
/**libraries**/
|
||||
//=include _jscolor.js
|
4122
package-lock.json
generated
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "pixel-editor",
|
||||
"version": "1.0.0",
|
||||
"description": "Online pixel art creation tool",
|
||||
"main": "build.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Lospec",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.16.4",
|
||||
"fs-extra": "^7.0.1",
|
||||
"glob": "^7.1.3",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-include": "^2.3.1",
|
||||
"handlebars-helper-svg": "^2.0.1",
|
||||
"hbs": "^4.0.3",
|
||||
"opn": "^6.0.0",
|
||||
"sass": "^1.17.3",
|
||||
"hbs-register-helpers": "git+https://skeddles@bitbucket.org/skeddles/hbs-register-helpers.git",
|
||||
"hbs-register-partials": "git+https://skeddles@bitbucket.org/skeddles/hbs-register-partials.git"
|
||||
}
|
||||
}
|
31
views/pixel-editor-splash-page.hbs
Normal file
@ -0,0 +1,31 @@
|
||||
<h1>{{title}}</h1>
|
||||
|
||||
<p>
|
||||
The Lospec Pixel editor is a free pixel art program that you can use right here
|
||||
in your web browser. Our goal was to create an easy to use, intuitive and
|
||||
unobtrusive pixel art application that you can use anywhere. Whether you're
|
||||
creating assets for a game or just want to make 8 bit art, this tool is an easy
|
||||
way to pixel fast.
|
||||
</p>
|
||||
|
||||
<img class="pixel-editor-screenshot" src="/pixel-editor/pixel-editor-screenshot.png" alt="Lospec Pixel Editor Screenshot" />
|
||||
|
||||
<p>
|
||||
This application does not have all the features of more advanced desktop editor,
|
||||
but we will add more over time. Desperate for a feature? Talk about it with us
|
||||
in the <a target="_blank" href="http://reddit.com/r/lospec">Lospec subreddit</a>.
|
||||
It currently features a pencil, fill, pan, eyedropper
|
||||
and zoom tool. You can also easily adjust any colors in your palette. You can
|
||||
use any palette in our <a href="/palette-list">Palette List</a> by clicking the pencil next to the palette
|
||||
title.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This app currently only works on desktops, and requires a modern browser such as
|
||||
the latest versions of <a target="_blank" href="https://www.mozilla.org/en-US/firefox/new/">Firefox or Chrome</a>.
|
||||
</p>
|
||||
|
||||
<a class="link-button" href="/pixel-editor/app">Enter app now {{svg "angle-right.svg" width="32" height="32"}}</a>
|
||||
|
||||
|
||||
|
234
views/pixel-editor.hbs
Normal file
@ -0,0 +1,234 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{title}}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,700,900" rel="stylesheet">
|
||||
<link rel='stylesheet' href='/pixel-editor/pixel-editor.css' />
|
||||
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
|
||||
{{{google-analytics}}}
|
||||
{{{favicons}}}
|
||||
|
||||
</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>
|
||||
<p>We detected that you may have an out of date or unsupported web browser. This tool, like many others on this site and across the web uses features only available in new web browsers. We reccommend updating your current browser or downloading <a href="https://www.mozilla.org/en-US/firefox/new/" target="_blank">Firefox</a> or <a href="https://www.google.com/chrome/browser/desktop/index.html" target="_blank">Chrome</a>. </p>
|
||||
<button onclick="closeCompatibilityWarning()">Continue</button>
|
||||
</div></div>
|
||||
</div>
|
||||
<script src="/javascripts/checkCompatibilityPixelEditor.js"></script>
|
||||
|
||||
<div class="preload">
|
||||
<img src="/pixel-editor/dropdown-arrow.png" />
|
||||
<img src="/pixel-editor/dropdown-arrow-hover.png" />
|
||||
<img src="/pixel-editor/eyedropper.png" />
|
||||
<img src="/pixel-editor/fill.png" />
|
||||
<img src="/pixel-editor/pan.png" />
|
||||
<img src="/pixel-editor/pan-held.png" />
|
||||
<img src="/pixel-editor/pencil.png" />
|
||||
<img src="/pixel-editor/zoom-in.png" />
|
||||
</div>
|
||||
<ul id="main-menu">
|
||||
<li class="logo">Lospec Pixel Editor</li>
|
||||
<li>
|
||||
<button>File</button>
|
||||
<ul>
|
||||
<li><button>New</button></li>
|
||||
<li><button>Open</button></li>
|
||||
<li><button id="save-as-button" class="disabled">Save as...</button></li>
|
||||
<li><button>Exit</button></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<button>Edit</button>
|
||||
<ul>
|
||||
<li><button id="undo-button" class="disabled">Undo</button></li>
|
||||
<li><button id="redo-button" class="disabled">Redo</button></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<button>Help</button>
|
||||
<ul>
|
||||
<li><button>Settings</button></li>
|
||||
<li><button>Help</button></li>
|
||||
<li><button>About</button></li>
|
||||
<li><button>Changelog</button></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul id="tools-menu">
|
||||
<li class="selected expanded">
|
||||
<button title="Pencil Tool (B)" id="pencil-button">{{svg "pencil.svg" width="32" height="32"}}</button>
|
||||
<button title="Increase Brush Size" id="pencil-bigger-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
|
||||
<button title="Decrease Brush Size" id="pencil-smaller-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
|
||||
</li>
|
||||
<li><button title="Fill Tool (F)" id="fill-button">{{svg "fill.svg" width="32" height="32"}}</button></li>
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<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>
|
||||
<div id="eyedropper-preview"></div>
|
||||
<div id="brush-preview"></div>
|
||||
|
||||
<div id="canvas-view">
|
||||
<canvas id="pixel-canvas"></canvas>
|
||||
</div>
|
||||
<div id="canvas-view-shadow"></div>
|
||||
|
||||
<div id="data-holders">
|
||||
<a id="save-image-link-holder" href="#">dl</a>
|
||||
<input id="open-image-browse-holder" type="file" accept="image/png, image/gif"/>
|
||||
<input id="load-palette-browse-holder" type="file" accept="image/png, image/gif"/>
|
||||
<canvas id="load-palette-canvas-holder"></canvas>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="jscolor-picker-bottom">
|
||||
<span>#</span><input type="text" id="jscolor-hex-input"/>
|
||||
<div id="duplicate-color-warning" title="Color is a duplicate of another in palette">{{svg "warning.svg" width="14" height="12" }}</div>
|
||||
<button class="delete-color-button">{{svg "trash.svg" width="20" height="20" }}</button>
|
||||
</div>
|
||||
|
||||
<div class="color-edit-button">
|
||||
{{svg "adjust.svg" width="20" height="20" }}
|
||||
</div>
|
||||
|
||||
<div id="pop-up-container">
|
||||
<div id="new-pixel">
|
||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||
<h1>New Pixel</h1>
|
||||
|
||||
<h2>Preset</h2>
|
||||
<button id="preset-button" class="dropdown-button">Choose a preset...</button>
|
||||
<div id="preset-menu" class="dropdown-menu"></div>
|
||||
|
||||
<h2>Size</h2>
|
||||
<input id="size-width" value="{{#if width}}{{width}}{{else}}64{{/if}}" autocomplete="off" />{{svg "x.svg" width="16" height="16" class="dimentions-x"}}<input id="size-height" value="{{#if height}}{{height}}{{else}}64{{/if}}" autocomplete="off" />
|
||||
<h2>Palette</h2>
|
||||
<button id="palette-button" class="dropdown-button">Choose a palette...</button>
|
||||
<div id="palette-menu" class="dropdown-menu"><button id="no-palette-button">Empty Palette</button><button id="load-palette-button">Load palette...</button></div>
|
||||
|
||||
<div id="new-pixel-warning">Creating a new pixel will discard your current one.</div>
|
||||
<div>
|
||||
<button id="create-button" class="default">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="help">
|
||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||
<h1>Help</h1>
|
||||
<h2>Palette</h2>
|
||||
<ul>
|
||||
<li>Left Click - Choose Color</li>
|
||||
<li>Right Click - Edit Color</li>
|
||||
</ul>
|
||||
<h2>Hotkeys</h2>
|
||||
<ul>
|
||||
<li>Pencil: <span class="keyboard-key">B</span> or <span class="keyboard-key">1</span></li>
|
||||
<li>Fill: <span class="keyboard-key">F</span> or <span class="keyboard-key">2</span></li>
|
||||
<li>Eyedropper: <span class="keyboard-key">E</span> or <span class="keyboard-key">3</span></li>
|
||||
<li>Pan: <span class="keyboard-key">P</span> or <span class="keyboard-key">M</span> or <span class="keyboard-key">4</span></li>
|
||||
<li>Zoom: <span class="keyboard-key">Z</span> or <span class="keyboard-key">5</span></li>
|
||||
<li>Undo: Ctrl + <span class="keyboard-key">Z</span></li>
|
||||
<li>Redo: Ctrl + <span class="keyboard-key">Y</span> or Ctrl + Alt + <span class="keyboard-key">Z</span></li>
|
||||
</ul>
|
||||
<h2>Mouse Shortcuts</h2>
|
||||
<ul>
|
||||
<li>Alt + Click - Eyedropper</li>
|
||||
<li>Space + Click - Pan</li>
|
||||
<li>Alt + Scroll Wheel - Zoom</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="about">
|
||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||
<h1>About Lospec Pixel Editor</h1>
|
||||
<div>version 1.0.0</div>
|
||||
<p>This is a web-based tool for creating and editing pixel art.</p>
|
||||
<p>The goal of this tool is to be an accessible and intuitive tool that's simple enough for a first time pixel artist while still being usable enough for a veteran. </p>
|
||||
<p>In the future I hope to add enough features to become a full fledged pixel art editor, with everything an artist could need.</p>
|
||||
<h1>About Lospec</h1>
|
||||
<p>Lospec is a website created to host tools for pixel artists. To see more of our tools, visit our <a href="/">homepage</a>. To hear about any updates or new tools, follow us on <a href="http://twitter.com/lospecofficial">Twitter</a>.</p>
|
||||
</div>
|
||||
<div id="changelog">
|
||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||
<h1>Changelog</h1>
|
||||
<h2>Version 1.0.0</h2>
|
||||
<ul>
|
||||
<li>Initial Release</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="credits">
|
||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||
<h1>Credits</h1>
|
||||
<h2>Icons</h2>
|
||||
<ul>
|
||||
<li><div>Icons made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div></li>
|
||||
<li><div>Font Awesome by Dave Gandy - <a href="http://fontawesome.io">http://fontawesome.io</a></div></li>
|
||||
<li><div>Icons made by <a href="http://www.flaticon.com/authors/those-icons" title="Those Icons">Those Icons</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="settings">
|
||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||
<h1>Settings</h1>
|
||||
|
||||
<div id="settings-container">
|
||||
<label for="setting-numberOfHistoryStates">Number of History States</label> <input id="setting-numberOfHistoryStates" value="20" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<p id="cookies-disabled-warning">Your browsers cookies are disabled, settings will be lost upon closing this page.</p>
|
||||
|
||||
<div>
|
||||
<button id="save-settings" class="default">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
palettes = { {{#palettes}}
|
||||
'{{name}}': {
|
||||
colors: '{{colors}}'.split(',')
|
||||
},
|
||||
{{/palettes}}
|
||||
{{#specifiedPalette}}
|
||||
'{{name}}': {
|
||||
colors: '{{colors}}'.split(','),
|
||||
specified: true
|
||||
}
|
||||
{{/specifiedPalette}}
|
||||
}
|
||||
{{#if width}}
|
||||
var specifiedDimentions = true;
|
||||
{{else}}
|
||||
var specifiedDimentions = false;
|
||||
{{/if}}
|
||||
|
||||
{{#specifiedPalette}}
|
||||
var keepUrl = true;
|
||||
{{/specifiedPalette}}
|
||||
</script>
|
||||
<script src="/pixel-editor/pixel-editor.js"></script>
|
||||
</body>
|
||||
</html>
|