first commit

This commit is contained in:
Sam Keddy 2019-03-26 23:20:54 +00:00
commit 7126e83f5f
67 changed files with 9722 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
_ext
routes
build
node_modules

36
README.md Normal file
View 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
View 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
View 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)
}
*/

View 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);

View 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;
}

View 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;
}

View 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;
}

View 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)
};
}

View 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) {});
});
}

View 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);
}
}

View 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);
}

View 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);
}
}

View 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);
}

View 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};
}

View 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
View 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');
});

View 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
View 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);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
images/dropdown-arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
images/eyedropper.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
images/fill.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
images/pan-held.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
images/pan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

BIN
images/pencil.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
images/zoom-in.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

49
js/_addColor.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

61
js/_loadImage.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View 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"
}
}

View 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&nbsp;&nbsp;{{svg "angle-right.svg" width="32" height="32"}}</a>

234
views/pixel-editor.hbs Normal file
View 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>