Merge pull request #2 from npalomba/master

Added eraser tool
This commit is contained in:
Lospec 2019-04-04 13:45:18 -04:00 committed by GitHub
commit 3a62ba155e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1166 additions and 938 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ _ext
routes routes
build build
node_modules node_modules
.idea

View File

@ -21,9 +21,9 @@ Suggestions / Planned features:
- Selections - Selections
- New selection tool - New selection tool
- New canvas layer above the drawing layer - New currentLayer.canvas layer above the drawing layer
- Move when click and drag - Move when click and drag
- Merge with canvas when click outside - Merge with currentLayer.canvas when click outside
- Copy/paste - Copy/paste
- Add as selection - Add as selection
@ -35,7 +35,7 @@ Suggestions / Planned features:
- Palette option remove unused colors - Palette option remove unused colors
- Pixel Grid - Pixel Grid
- Another canvas - Another currentLayer.canvas
- Must be rescaled each zoom - Must be rescaled each zoom
- Possibly add collaborate function using together.js - Possibly add collaborate function using together.js

View File

@ -35,7 +35,7 @@ svg {
outline: 0 !important; outline: 0 !important;
} }
#pixel-canvas { .drawingCanvas {
cursor: url('/pixel-art-where-to-start/pencil-tool-cursor.png'); cursor: url('/pixel-art-where-to-start/pencil-tool-cursor.png');
border: solid 1px #fff; border: solid 1px #fff;
@ -49,11 +49,20 @@ svg {
-ms-interpolation-mode:nearest-neighbor; /* IE8+ */ -ms-interpolation-mode:nearest-neighbor; /* IE8+ */
width: 400px; width: 400px;
height: 400px; height: 400px;
display: none;
position: fixed; position: fixed;
display:none;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.64); box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.64);
} }
#checkerboard {
z-index:1;
}
#pixel-canvas {
z-index:2;
background:transparent;
}
#eyedropper-preview { #eyedropper-preview {
position: absolute; position: absolute;
width: 45px; width: 45px;
@ -83,7 +92,6 @@ svg {
} }
#canvas-view { #canvas-view {
background: color(indent-dark);
bottom: 0px; bottom: 0px;
left: 64px; left: 64px;
right: 48px; right: 48px;
@ -414,19 +422,23 @@ svg {
} }
#tools-menu li button#pencil-bigger-button, #tools-menu li button#pencil-bigger-button,
#tools-menu li button#zoom-in-button { #tools-menu li button#zoom-in-button,
#tools-menu li button#eraser-bigger-button{
left: 0; left: 0;
} }
#tools-menu li button#pencil-smaller-button, #tools-menu li button#pencil-smaller-button,
#tools-menu li button#zoom-out-button { #tools-menu li button#zoom-out-button,
#tools-menu li button#eraser-smaller-button{
right: 0; right: 0;
} }
#tools-menu li.selected button#pencil-bigger-button, #tools-menu li.selected button#pencil-bigger-button,
#tools-menu li.selected button#pencil-smaller-button, #tools-menu li.selected button#pencil-smaller-button,
#tools-menu li.selected button#zoom-in-button, #tools-menu li.selected button#zoom-in-button,
#tools-menu li.selected button#zoom-out-button { #tools-menu li.selected button#zoom-out-button,
#tools-menu li.selected button#eraser-bigger-button,
#tools-menu li.selected button#eraser-smaller-button{
display: block; display: block;
} }

BIN
images/eraser.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -52,6 +52,7 @@ on('click', 'add-color-button', function(){
//show color picker //show color picker
addedColor.firstElementChild.jscolor.show(); addedColor.firstElementChild.jscolor.show();
console.log("showing picker");
//hide edit button //hide edit button
addedColor.lastChild.classList.add('hidden'); addedColor.lastChild.classList.add('hidden');

48
js/_canvas.js Normal file
View File

@ -0,0 +1,48 @@
/** Handler class for a single canvas (a single layer)
*
* @param width Canvas width
* @param height Canvas height
* @param canvas HTML canvas element
*/
function Canvas(width, height, canvas) {
this.canvasSize = [width, height],
this.canvas = canvas,
this.context = this.canvas.getContext("2d"),
// Initializes the canvas
this.initialize = function() {
var maxHorizontalZoom = Math.floor(window.innerWidth/this.canvasSize[0]*0.75);
var maxVerticalZoom = Math.floor(window.innerHeight/this.canvasSize[1]*0.75);
zoom = Math.min(maxHorizontalZoom,maxVerticalZoom);
if (zoom < 1) zoom = 1;
//resize canvas
this.canvas.width = this.canvasSize[0];
this.canvas.height = this.canvasSize[1];
this.canvas.style.width = (this.canvas.width*zoom)+'px';
this.canvas.style.height = (this.canvas.height*zoom)+'px';
//unhide canvas
this.canvas.style.display = 'block';
//center canvas in window
this.canvas.style.left = 64+canvasView.clientWidth/2-(this.canvasSize[0]*zoom/2)+'px';
this.canvas.style.top = 48+canvasView.clientHeight/2-(this.canvasSize[1]*zoom/2)+'px';
},
// Resizes canvas
this.resize = function() {
let newWidth = (this.canvas.width * zoom) + 'px';
let newHeight = (this.canvas.height *zoom)+ 'px';
this.canvas.style.width = newWidth;
this.canvas.style.height = newHeight;
},
// Copies the otherCanvas' position and size
this.copyData = function(otherCanvas) {
this.canvas.style.width = otherCanvas.canvas.style.width;
this.canvas.style.height = otherCanvas.canvas.style.height;
this.canvas.style.left = otherCanvas.canvas.style.left;
this.canvas.style.top = otherCanvas.canvas.style.top;
}
}

View File

@ -1,6 +1,5 @@
function changeZoom (direction, cursorLocation) { function changeZoom (layer, direction, cursorLocation) {
var oldWidth = canvasSize[0] * zoom; var oldWidth = canvasSize[0] * zoom;
var oldHeight = canvasSize[1] * zoom; var oldHeight = canvasSize[1] * zoom;
var newWidth, newHeight; var newWidth, newHeight;
@ -13,7 +12,7 @@ function changeZoom (direction, cursorLocation) {
newHeight = canvasSize[1] * zoom; newHeight = canvasSize[1] * zoom;
//adjust canvas position //adjust canvas position
setCanvasOffset(canvas.offsetLeft + (oldWidth - newWidth) *cursorLocation[0]/oldWidth, canvas.offsetTop + (oldHeight - newHeight) *cursorLocation[1]/oldWidth) setCanvasOffset(layer.canvas, layer.canvas.offsetLeft + (oldWidth - newWidth) *cursorLocation[0]/oldWidth, layer.canvas.offsetTop + (oldHeight - newHeight) *cursorLocation[1]/oldWidth)
} }
//if you want to zoom in //if you want to zoom in
else if (direction == 'in' && zoom + Math.ceil(zoom/10) < window.innerHeight/4){ else if (direction == 'in' && zoom + Math.ceil(zoom/10) < window.innerHeight/4){
@ -22,12 +21,11 @@ function changeZoom (direction, cursorLocation) {
newHeight = canvasSize[1] * zoom; newHeight = canvasSize[1] * zoom;
//adjust canvas position //adjust canvas position
setCanvasOffset(canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight)) setCanvasOffset(layer.canvas, layer.canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), layer.canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight))
} }
//resize canvas //resize canvas
canvas.style.width = (canvas.width*zoom)+'px'; layer.resize();
canvas.style.height = (canvas.height*zoom)+'px';
// adjust brush size // adjust brush size
updateCursor(); updateCursor();

59
js/_checkerboard.js Normal file
View File

@ -0,0 +1,59 @@
// This script contains all the functions used to manage the checkboard
// Setting current colour (each square has a different colour
var currentColor = firstCheckerBoardColor;
// Saving number of squares filled until now
var nSquaresFilled = 0;
function fillCheckerboard() {
// Getting checkerboard context
var context = checkerBoard.context;
// Cycling through the canvas (using it as a matrix)
for (var i=0; i<canvasSize[0] / checkerBoardSquareSize; i++) {
nSquaresFilled = 0;
for (var j=0; j<canvasSize[1] / checkerBoardSquareSize; j++) {
var rectX;
var rectY;
// Managing the not perfect squares (the ones at the sides if the canvas' sizes are not powers of checkerBoardSquareSize
if (i * checkerBoardSquareSize < canvasSize[0]) {
rectX = i * checkerBoardSquareSize;
}
else {
rectX = canvasSize[0];
}
if (j * checkerBoardSquareSize < canvasSize[1]) {
rectY = j * checkerBoardSquareSize;
}
else {
rectY = canvasSize[1];
}
// Selecting the colour
context.fillStyle = currentColor;
context.fillRect(rectX, rectY, checkerBoardSquareSize, checkerBoardSquareSize);
// Changing colour
changeCheckerboardColor();
nSquaresFilled++;
}
// If the number of filled squares was even, I change colour for the next column
if ((nSquaresFilled % 2) == 0) {
changeCheckerboardColor();
}
}
}
// Simply switches the checkerboard colour
function changeCheckerboardColor(isVertical) {
if (currentColor == firstCheckerBoardColor) {
currentColor = secondCheckerBoardColor;
} else if (currentColor == secondCheckerBoardColor) {
currentColor = firstCheckerBoardColor;
}
}

View File

@ -9,7 +9,7 @@ function clickedColor (e){
if (selectedColor) selectedColor.classList.remove("selected"); if (selectedColor) selectedColor.classList.remove("selected");
//set current color //set current color
context.fillStyle = this.style.backgroundColor; currentLayer.context.fillStyle = this.style.backgroundColor;
//make color selected //make color selected
e.target.parentElement.classList.add('selected'); e.target.parentElement.classList.add('selected');

View File

@ -24,9 +24,6 @@ on('input', 'jscolor-hex-input', function (e) {
//changes all of one color to another after being changed from color picker //changes all of one color to another after being changed from color picker
function colorChanged(e) { function colorChanged(e) {
console.log('colorChanged()'); console.log('colorChanged()');
//get colors //get colors
var newColor = hexToRgb(e.target.value); var newColor = hexToRgb(e.target.value);
var oldColor = e.target.oldColor; var oldColor = e.target.oldColor;

View File

@ -36,10 +36,10 @@ function createColorPalette(selectedPalette, fillBackground) {
//fill bg with lightest color //fill bg with lightest color
if (fillBackground) { if (fillBackground) {
context.fillStyle = lightestColor; currentLayer.context.fillStyle = lightestColor;
context.fillRect(0, 0, canvasSize[0], canvasSize[1]); currentLayer.context.fillRect(0, 0, canvasSize[0], canvasSize[1]);
} }
//set as current color //set as current color
context.fillStyle = darkestColor; currentLayer.context.fillStyle = darkestColor;
} }

View File

@ -73,7 +73,7 @@ function deleteColor (color) {
//set current color TO LIGHTEST COLOR //set current color TO LIGHTEST COLOR
lightestColor[1].parentElement.classList.add('selected'); lightestColor[1].parentElement.classList.add('selected');
context.fillStyle = '#'+lightestColor[1].jscolor.toString(); currentLayer.context.fillStyle = '#'+lightestColor[1].jscolor.toString();
} }
//delete the element //delete the element

View File

@ -6,11 +6,17 @@ function line(x0,y0,x1,y1) {
var sx = (x0 < x1 ? 1 : -1); var sx = (x0 < x1 ? 1 : -1);
var sy = (y0 < y1 ? 1 : -1); var sy = (y0 < y1 ? 1 : -1);
var err = dx-dy; var err = dx-dy;
var breaker = 0;
while (true) { while (true) {
//set pixel //set pixel
context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize); // If the current tool is the brush
if (currentTool == 'pencil') {
// I fill the rect
currentLayer.context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize);
} else if (currentTool == 'eraser') {
// In case I'm using the eraser I must clear the rect
currentLayer.context.clearRect(x0-Math.floor(eraserSize/2), y0-Math.floor(eraserSize/2), eraserSize, eraserSize);
}
//if we've reached the end goal, exit the loop //if we've reached the end goal, exit the loop
if ((x0==x1) && (y0==y1)) break; if ((x0==x1) && (y0==y1)) break;

View File

@ -6,6 +6,12 @@ function fill(cursorLocation) {
tempImage.data[pixelPos] = fillColor.r; tempImage.data[pixelPos] = fillColor.r;
tempImage.data[pixelPos + 1] = fillColor.g; tempImage.data[pixelPos + 1] = fillColor.g;
tempImage.data[pixelPos + 2] = fillColor.b; tempImage.data[pixelPos + 2] = fillColor.b;
tempImage.data[pixelPos + 3] = 255;
/*
tempImage.data[pixelPos] = fillColor.r;
tempImage.data[pixelPos + 1] = fillColor.g;
tempImage.data[pixelPos + 2] = fillColor.b;
*/
} }
//change x y to color value passed from the function and use that as the original color //change x y to color value passed from the function and use that as the original color
@ -25,7 +31,7 @@ function fill(cursorLocation) {
//console.log('filling at '+ Math.floor(cursorLocation[0]/zoom) + ','+ Math.floor(cursorLocation[1]/zoom)); //console.log('filling at '+ Math.floor(cursorLocation[0]/zoom) + ','+ Math.floor(cursorLocation[1]/zoom));
//temporary image holds the data while we change it //temporary image holds the data while we change it
var tempImage = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); var tempImage = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
//this is an array that holds all of the pixels at the top of the cluster //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)]]; var topmostPixelsArray = [[Math.floor(cursorLocation[0]/zoom), Math.floor(cursorLocation[1]/zoom)]];
@ -38,7 +44,7 @@ function fill(cursorLocation) {
var clusterColor = [tempImage.data[startingPosition],tempImage.data[startingPosition+1],tempImage.data[startingPosition+2]]; var clusterColor = [tempImage.data[startingPosition],tempImage.data[startingPosition+1],tempImage.data[startingPosition+2]];
//the new color to fill with //the new color to fill with
var fillColor = hexToRgb(context.fillStyle); var fillColor = hexToRgb(currentLayer.context.fillStyle);
//if you try to fill with the same color that's already there, exit the function //if you try to fill with the same color that's already there, exit the function
if (clusterColor[0] == fillColor.r && if (clusterColor[0] == fillColor.r &&
@ -48,7 +54,6 @@ function fill(cursorLocation) {
//loop until there are no more values left in this array //loop until there are no more values left in this array
while (topmostPixelsArray.length) { while (topmostPixelsArray.length) {
var reachLeft, reachRight; var reachLeft, reachRight;
//move the most recent pixel from the array and set it as our current working pixels //move the most recent pixel from the array and set it as our current working pixels
@ -63,7 +68,6 @@ function fill(cursorLocation) {
//each pixel has 4 values, rgba //each pixel has 4 values, rgba
var pixelPos = (y * canvasSize[0] + x) * 4; 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 //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)) { while (y-- >= 0 && matchStartColor(tempImage, pixelPos, clusterColor)) {
pixelPos -= canvasSize[0] * 4; pixelPos -= canvasSize[0] * 4;
@ -74,7 +78,6 @@ function fill(cursorLocation) {
reachRight = false; reachRight = false;
while (y++ < canvasSize[1] - 1 && matchStartColor(tempImage, pixelPos, clusterColor)) { while (y++ < canvasSize[1] - 1 && matchStartColor(tempImage, pixelPos, clusterColor)) {
colorPixel(tempImage, pixelPos, fillColor); colorPixel(tempImage, pixelPos, fillColor);
if (x > 0) { if (x > 0) {
if (matchStartColor(tempImage, pixelPos - 4, clusterColor)) { if (matchStartColor(tempImage, pixelPos - 4, clusterColor)) {
if (!reachLeft) { if (!reachLeft) {
@ -102,6 +105,6 @@ function fill(cursorLocation) {
pixelPos += canvasSize[0] * 4; pixelPos += canvasSize[0] * 4;
} }
} }
context.putImageData(tempImage, 0, 0); currentLayer.context.putImageData(tempImage, 0, 0);
//console.log('done filling') //console.log('done filling')
} }

View File

@ -5,19 +5,19 @@ const undoLogStyle = 'background: #87ff1c; color: black; padding: 5px;';
//prototype for undoing canvas changes //prototype for undoing canvas changes
function HistoryStateEditCanvas () { function HistoryStateEditCanvas () {
this.canvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); this.canvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
this.undo = function () { this.undo = function () {
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); var currentCanvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
context.putImageData(this.canvas, 0, 0); currentLayer.context.putImageData(this.canvas, 0, 0);
this.canvas = currentCanvas; this.canvas = currentCanvas;
redoStates.push(this); redoStates.push(this);
} }
this.redo = function () { this.redo = function () {
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); var currentCanvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
context.putImageData(this.canvas, 0, 0); currentLayer.context.putImageData(this.canvas, 0, 0);
this.canvas = currentCanvas; this.canvas = currentCanvas;
undoStates.push(this); undoStates.push(this);
@ -48,11 +48,11 @@ function HistoryStateAddColor (colorValue) {
//prototype for undoing deleted colors //prototype for undoing deleted colors
function HistoryStateDeleteColor (colorValue) { function HistoryStateDeleteColor (colorValue) {
this.colorValue = colorValue; this.colorValue = colorValue;
this.canvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); this.canvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
this.undo = function () { this.undo = function () {
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); var currentCanvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
context.putImageData(this.canvas, 0, 0); currentLayer.context.putImageData(this.canvas, 0, 0);
addColor(this.colorValue); addColor(this.colorValue);
@ -61,8 +61,8 @@ function HistoryStateDeleteColor (colorValue) {
} }
this.redo = function () { this.redo = function () {
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); var currentCanvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
context.putImageData(this.canvas, 0, 0); currentLayer.context.putImageData(this.canvas, 0, 0);
deleteColor(this.colorValue); deleteColor(this.colorValue);
@ -78,11 +78,11 @@ function HistoryStateDeleteColor (colorValue) {
function HistoryStateEditColor (newColorValue, oldColorValue) { function HistoryStateEditColor (newColorValue, oldColorValue) {
this.newColorValue = newColorValue; this.newColorValue = newColorValue;
this.oldColorValue = oldColorValue; this.oldColorValue = oldColorValue;
this.canvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); this.canvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
this.undo = function () { this.undo = function () {
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); var currentCanvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
context.putImageData(this.canvas, 0, 0); currentLayer.context.putImageData(this.canvas, 0, 0);
//find new color in palette and change it back to old color //find new color in palette and change it back to old color
var colors = document.getElementsByClassName('color-button'); var colors = document.getElementsByClassName('color-button');
@ -99,8 +99,8 @@ function HistoryStateEditColor (newColorValue, oldColorValue) {
} }
this.redo = function () { this.redo = function () {
var currentCanvas = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); var currentCanvas = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
context.putImageData(this.canvas, 0, 0); currentLayer.context.putImageData(this.canvas, 0, 0);
//find old color in palette and change it back to new color //find old color in palette and change it back to new color
var colors = document.getElementsByClassName('color-button'); var colors = document.getElementsByClassName('color-button');

View File

@ -33,6 +33,11 @@ function KeyPress(e) {
case 53: case 53:
changeTool('zoom'); changeTool('zoom');
break; break;
// eraser -6, r
case 54: case 82:
console.log("Pressed r");
changeTool('eraser');
break;
//Z //Z
case 90: case 90:
console.log('PRESSED Z ', keyboardEvent.ctrlKey) console.log('PRESSED Z ', keyboardEvent.ctrlKey)

View File

@ -547,7 +547,7 @@ var jsc = {
//console.log(e.target,'=====================================') //console.log(e.target,'=====================================')
//if they clicked on the delete button [lospec] //if they clicked on the delete button [lospec]
if (e.target.className == 'delete-color-button') { if (e.target.className == 'delete-color-button') {
//saveHistoryState({type: 'deletecolor', colorValue: jsc.picker.owner.toString(), canvas: context.getImageData(0, 0, canvasSize[0], canvasSize[1])}); //saveHistoryState({type: 'deletecolor', colorValue: jsc.picker.owner.toString(), canvas: canvas.context.getImageData(0, 0, canvasSize[0], canvasSize[1])});
new HistoryStateDeleteColor(jsc.picker.owner.toString()); new HistoryStateDeleteColor(jsc.picker.owner.toString());
deleteColor(jsc.picker.owner.styleElement); deleteColor(jsc.picker.owner.styleElement);

View File

@ -15,10 +15,10 @@ document.getElementById('open-image-browse-holder').addEventListener('change', f
newPixel(this.width, this.height, []); newPixel(this.width, this.height, []);
//draw the image onto the canvas //draw the image onto the canvas
context.drawImage(img, 0, 0); currentLayer.context.drawImage(img, 0, 0);
var colorPalette = {}; var colorPalette = {};
var imagePixelData = context.getImageData(0,0,this.width, this.height).data; var imagePixelData = currentLayer.context.getImageData(0,0,this.width, this.height).data;
var imagePixelDataLength = imagePixelData.length; var imagePixelDataLength = imagePixelData.length;

View File

@ -17,7 +17,7 @@ window.addEventListener("mousedown", function (mouseEvent) {
currentTool = 'pan'; currentTool = 'pan';
else if (mouseEvent.altKey) else if (mouseEvent.altKey)
currentTool = 'eyedropper'; currentTool = 'eyedropper';
else if (mouseEvent.target == canvas && currentTool == 'pencil') else if (mouseEvent.target == currentLayer.canvas && (currentTool == 'pencil' || currentTool == 'eraser'))
new HistoryStateEditCanvas(); new HistoryStateEditCanvas();
//saveHistoryState({type: 'canvas', canvas: context.getImageData(0, 0, canvasSize[0], canvasSize[1])}); //saveHistoryState({type: 'canvas', canvas: context.getImageData(0, 0, canvasSize[0], canvasSize[1])});
@ -29,8 +29,12 @@ window.addEventListener("mousedown", function (mouseEvent) {
currentTool = 'resize-brush'; currentTool = 'resize-brush';
prevBrushSize = brushSize; prevBrushSize = brushSize;
} }
else if (currentTool == 'eraser' && mouseEvent.which == 3) {
currentTool = 'resize-eraser';
prevEraserSize = eraserSize;
}
if (currentTool == 'eyedropper' && mouseEvent.target == canvas) if (currentTool == 'eyedropper' && mouseEvent.target == currentLayer.canvas)
eyedropperPreview.style.display = 'block'; eyedropperPreview.style.display = 'block';
return false; return false;
@ -45,7 +49,7 @@ window.addEventListener("mouseup", function (mouseEvent) {
if (!documentCreated || dialogueOpen) return; if (!documentCreated || dialogueOpen) return;
if (currentTool == 'eyedropper' && mouseEvent.target == canvas) { if (currentTool == 'eyedropper' && mouseEvent.target == currentLayer.canvas) {
var cursorLocation = getCursorPosition(mouseEvent); var cursorLocation = getCursorPosition(mouseEvent);
var selectedColor = context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1); 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]); var newColor = rgbToHex(selectedColor.data[0],selectedColor.data[1],selectedColor.data[2]);
@ -77,10 +81,10 @@ window.addEventListener("mouseup", function (mouseEvent) {
} }
else if (currentTool == 'fill' && mouseEvent.target == canvas) { else if (currentTool == 'fill' && mouseEvent.target == currentLayer.canvas) {
console.log('filling') console.log('filling')
//if you clicked on anything but the canvas, do nothing //if you clicked on anything but the canvas, do nothing
if (!mouseEvent.target == canvas) return; if (!mouseEvent.target == currentLayer.canvas) return;
//get cursor postion //get cursor postion
var cursorLocation = getCursorPosition(mouseEvent); var cursorLocation = getCursorPosition(mouseEvent);
@ -92,9 +96,20 @@ window.addEventListener("mouseup", function (mouseEvent) {
//fill starting at the location //fill starting at the location
fill(cursorLocation); fill(cursorLocation);
} }
else if (currentTool == 'zoom' && mouseEvent.target == canvas) { else if (currentTool == 'zoom' && mouseEvent.target == canvasView) {
if (mouseEvent.which == 1) changeZoom('in', getCursorPosition(mouseEvent)); let mode;
else if (mouseEvent.which == 3) changeZoom('out', getCursorPosition(mouseEvent)) if (mouseEvent.which == 1){
mode = "in";
}
else if (mouseEvent.which == 3){
mode = "out";
}
changeZoom(layers[0], mode, getCursorPosition(mouseEvent));
for (let i=1; i<layers.length; i++) {
layers[i].copyData(layers[0]);
}
} }
dragging = false; dragging = false;
@ -120,18 +135,18 @@ function draw (mouseEvent) {
if (currentTool == 'pencil') { if (currentTool == 'pencil') {
//move the brush preview //move the brush preview
brushPreview.style.left = cursorLocation[0] + canvas.offsetLeft - brushSize * zoom / 2 + 'px'; brushPreview.style.left = cursorLocation[0] + currentLayer.canvas.offsetLeft - brushSize * zoom / 2 + 'px';
brushPreview.style.top = cursorLocation[1] + canvas.offsetTop - brushSize * zoom / 2 + 'px'; brushPreview.style.top = cursorLocation[1] + currentLayer.canvas.offsetTop - brushSize * zoom / 2 + 'px';
//hide brush preview outside of canvas / canvas view //hide brush preview outside of canvas / canvas view
if (mouseEvent.target == canvas || mouseEvent.target == canvasView) if (mouseEvent.target == currentLayer.canvas || mouseEvent.target == canvasView)
brushPreview.style.visibility = 'visible'; brushPreview.style.visibility = 'visible';
else else
brushPreview.style.visibility = 'hidden'; brushPreview.style.visibility = 'hidden';
//draw line to current pixel //draw line to current pixel
if (dragging) { if (dragging) {
if (mouseEvent.target == canvas || mouseEvent.target == canvasView) { if (mouseEvent.target == currentLayer.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)); line(Math.floor(lastPos[0]/zoom),Math.floor(lastPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom));
lastPos = cursorLocation; lastPos = cursorLocation;
} }
@ -145,33 +160,42 @@ function draw (mouseEvent) {
if (colorLightness>127) brushPreview.classList.remove('dark'); if (colorLightness>127) brushPreview.classList.remove('dark');
else brushPreview.classList.add('dark'); else brushPreview.classList.add('dark');
} }
else if (currentTool == 'pan' && dragging) { // Decided to write a different implementation in case of differences between the brush and the eraser tool
else if (currentTool == 'eraser') {
// Uses the same preview as the brush
//move the brush preview
brushPreview.style.left = cursorLocation[0] + canvas.offsetLeft - eraserSize * zoom / 2 + 'px';
brushPreview.style.top = cursorLocation[1] + canvas.offsetTop - eraserSize * zoom / 2 + 'px';
//hide brush preview outside of canvas / canvas view
if (mouseEvent.target == currentLayer.canvas || mouseEvent.target == canvasView)
brushPreview.style.visibility = 'visible';
else
brushPreview.style.visibility = 'hidden';
setCanvasOffset(canvas.offsetLeft + (cursorLocation[0] - lastPos[0]), canvas.offsetTop + (cursorLocation[1] - lastPos[1])) //draw line to current pixel
/* if (dragging) {
if ( if (mouseEvent.target == currentLayer.canvas || mouseEvent.target == canvasView) {
//right line(Math.floor(lastPos[0]/zoom),Math.floor(lastPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom));
canvas.offsetLeft + (cursorLocation[0] - lastPos[0]) < window.innerWidth - canvasSize[0]*zoom*0.25 - 48 && lastPos = cursorLocation;
//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) { }
}
else if (currentTool == 'pan' && dragging) {
// Setting first layer position
setCanvasOffset(layers[0].canvas, layers[0].canvas.offsetLeft + (cursorLocation[0] - lastPos[0]), layers[0].canvas.offsetTop + (cursorLocation[1] - lastPos[1]));
// Copying that position to the other layers
for (let i=1; i<layers.length; i++) {
layers[i].copyData(layers[0]);
}
}
else if (currentTool == 'eyedropper' && dragging && mouseEvent.target == currentLayer.canvas) {
var selectedColor = context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1).data; 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.borderColor = '#'+rgbToHex(selectedColor[0],selectedColor[1],selectedColor[2]);
eyedropperPreview.style.display = 'block'; eyedropperPreview.style.display = 'block';
eyedropperPreview.style.left = cursorLocation[0] + canvas.offsetLeft - 30 + 'px'; eyedropperPreview.style.left = cursorLocation[0] + currentLayer.canvas.offsetLeft - 30 + 'px';
eyedropperPreview.style.top = cursorLocation[1] + canvas.offsetTop - 30 + 'px'; eyedropperPreview.style.top = cursorLocation[1] + currentLayer.canvas.offsetTop - 30 + 'px';
var colorLightness = Math.max(selectedColor[0],selectedColor[1],selectedColor[2]); var colorLightness = Math.max(selectedColor[0],selectedColor[1],selectedColor[2]);
@ -180,7 +204,6 @@ function draw (mouseEvent) {
else eyedropperPreview.classList.add('dark'); else eyedropperPreview.classList.add('dark');
} }
else if (currentTool == 'resize-brush' && dragging) { else if (currentTool == 'resize-brush' && dragging) {
//get new brush size based on x distance from original clicking location //get new brush size based on x distance from original clicking location
var distanceFromClick = cursorLocation[0] - lastPos[0]; var distanceFromClick = cursorLocation[0] - lastPos[0];
//var roundingAmount = 20 - Math.round(distanceFromClick/10); //var roundingAmount = 20 - Math.round(distanceFromClick/10);
@ -192,8 +215,25 @@ function draw (mouseEvent) {
brushSize = Math.max(1,newBrushSize); brushSize = Math.max(1,newBrushSize);
//fix offset so the cursor stays centered //fix offset so the cursor stays centered
brushPreview.style.left = lastPos[0] + canvas.offsetLeft - brushSize * zoom / 2 + 'px'; brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - brushSize * zoom / 2 + 'px';
brushPreview.style.top = lastPos[1] + canvas.offsetTop - brushSize * zoom / 2 + 'px'; brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - brushSize * zoom / 2 + 'px';
updateCursor();
}
else if (currentTool == 'resize-eraser' && 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 eraserSizeChange = Math.round(distanceFromClick/10);
var newEraserSizeChange = prevEraserSize + eraserSizeChange;
//set the brush to the new size as long as its bigger than 1
eraserSize = Math.max(1,newEraserSizeChange);
//fix offset so the cursor stays centered
brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - eraserSize * zoom / 2 + 'px';
brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - eraserSize * zoom / 2 + 'px';
updateCursor(); updateCursor();
} }
@ -203,8 +243,21 @@ function draw (mouseEvent) {
canvasView.addEventListener("wheel", function(mouseEvent){ canvasView.addEventListener("wheel", function(mouseEvent){
if (currentTool == 'zoom' || mouseEvent.altKey) { if (currentTool == 'zoom' || mouseEvent.altKey) {
if (mouseEvent.deltaY < 0) changeZoom('in', getCursorPosition(mouseEvent)); let mode;
else if (mouseEvent.deltaY > 0) changeZoom('out', getCursorPosition(mouseEvent)) if (mouseEvent.deltaY < 0){
mode = 'in';
}
else if (mouseEvent.deltaY > 0) {
mode = 'out';
}
// Changing zoom and position of the first layer
changeZoom(layers[0], mode, getCursorPosition(mouseEvent))
for (let i=1; i<layers.length; i++) {
// Copying first layer's data into the other layers
layers[i].copyData(layers[0]);
}
} }
}); });

View File

@ -1,26 +1,15 @@
function newPixel (width, height, palette) { function newPixel (width, height, palette) {
currentLayer = new Canvas(width, height, canvas);
currentLayer.initialize();
canvasSize = [width,height]; checkerBoard = new Canvas(width, height, checkerBoardCanvas);
checkerBoard.initialize();
var maxHorizontalZoom = Math.floor(window.innerWidth/canvasSize[0]*0.75); canvasSize = currentLayer.canvasSize;
var maxVerticalZoom = Math.floor(window.innerHeight/canvasSize[1]*0.75);
zoom = Math.min(maxHorizontalZoom,maxVerticalZoom); layers.push(currentLayer);
if (zoom < 1) zoom = 1; layers.push(checkerBoard);
//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 //remove current palette
colors = document.getElementsByClassName('color-button'); colors = document.getElementsByClassName('color-button');
@ -56,13 +45,16 @@ function newPixel (width, height, palette) {
addColor(defaultBackgroundColor); addColor(defaultBackgroundColor);
//fill background of canvas with bg color //fill background of canvas with bg color
context.fillStyle = '#'+defaultBackgroundColor; fillCheckerboard();
context.fillRect(0, 0, canvasSize[0], canvasSize[1]); /*
currentLayer.context.fillStyle = '#'+defaultBackgroundColor;
currentLayer.context.fillRect(0, 0, canvasSize[0], canvasSize[1]);
console.log('#'+defaultBackgroundColor) console.log('#'+defaultBackgroundColor)
*/
//set current drawing color as foreground color //set current drawing color as foreground color
context.fillStyle = '#'+defaultForegroundColor; currentLayer.context.fillStyle = '#'+defaultForegroundColor;
selectedPalette = 'none'; selectedPalette = 'none';
} }

View File

@ -7,7 +7,7 @@ function replaceAllOfColor (oldColor, newColor) {
if (typeof newColor === 'string') newColor = hexToRgb(newColor); if (typeof newColor === 'string') newColor = hexToRgb(newColor);
//create temporary image from canvas to search through //create temporary image from canvas to search through
var tempImage = context.getImageData(0, 0, canvasSize[0], canvasSize[1]); var tempImage = currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
//loop through all pixels //loop through all pixels
for (var i=0;i<tempImage.data.length;i+=4) { for (var i=0;i<tempImage.data.length;i+=4) {
@ -21,5 +21,5 @@ function replaceAllOfColor (oldColor, newColor) {
} }
//put temp image back onto canvas //put temp image back onto canvas
context.putImageData(tempImage,0,0); currentLayer.context.putImageData(tempImage,0,0);
} }

View File

@ -1,8 +1,7 @@
function setCanvasOffset (offsetLeft, offsetTop) { function setCanvasOffset (canvas, offsetLeft, offsetTop) {
//horizontal offset //horizontal offset
var minXOffset = -canvasSize[0]*zoom+ 164; var minXOffset = -canvasSize[0]*zoom+ 164;
var maxXOffset = window.innerWidth - 148 var maxXOffset = window.innerWidth - 148;
if (offsetLeft < minXOffset) if (offsetLeft < minXOffset)
canvas.style.left = minXOffset +'px'; canvas.style.left = minXOffset +'px';

View File

@ -10,11 +10,28 @@ on('click',"pencil-bigger-button", function(){
}, false); }, false);
//pencil smaller //pencil smaller
on('click',"pencil-smaller-button", function(e){ on('click',"pencil-smaller-button", function(){
if(brushSize > 1) brushSize--; if(brushSize > 1) brushSize--;
updateCursor(); updateCursor();
}, false); }, false);
//eraser
on('click',"eraser-button", function(){
changeTool('eraser');
}, false);
//eraser bigger
on('click',"eraser-bigger-button", function(){
eraserSize++;
updateCursor();
}, false);
//eraser smaller
on('click',"eraser-smaller-button", function(e){
if(eraserSize > 1) eraserSize--;
updateCursor();
}, false);
//fill //fill
on('click',"fill-button", function(){ on('click',"fill-button", function(){
changeTool('fill'); changeTool('fill');
@ -38,10 +55,18 @@ on('click',"zoom-button", function(){
//zoom in button //zoom in button
on('click',"zoom-in-button", function(){ on('click',"zoom-in-button", function(){
//changeZoom('in',[window.innerWidth/2-canvas.offsetLeft,window.innerHeight/2-canvas.offsetTop]); //changeZoom('in',[window.innerWidth/2-canvas.offsetLeft,window.innerHeight/2-canvas.offsetTop]);
changeZoom('in',[canvasSize[0]*zoom/2,canvasSize[1]*zoom/2]); changeZoom(layers[0],'in', [canvasSize[0] * zoom / 2, canvasSize[1] * zoom / 2]);
for (let i=1; i<layers.length; i++) {
layers[i].copyData(layers[0]);
}
}, false); }, false);
//zoom out button //zoom out button
on('click',"zoom-out-button", function(){ on('click',"zoom-out-button", function(){
changeZoom('out',[canvasSize[0]*zoom/2,canvasSize[1]*zoom/2]); changeZoom(layers[0],'out',[canvasSize[0]*zoom/2,canvasSize[1]*zoom/2]);
for (let i=1; i<layers.length; i++) {
layers[i].copyData(layers[0]);
}
}, false); }, false);

View File

@ -6,6 +6,11 @@ function updateCursor () {
brushPreview.style.display = 'block'; brushPreview.style.display = 'block';
brushPreview.style.width = brushSize * zoom + 'px'; brushPreview.style.width = brushSize * zoom + 'px';
brushPreview.style.height = brushSize * zoom + 'px'; brushPreview.style.height = brushSize * zoom + 'px';
} else if (currentTool == 'eraser' || currentTool == 'resize-eraser') {
canvasView.style.cursor = 'crosshair';
brushPreview.style.display = 'block';
brushPreview.style.width = eraserSize * zoom + 'px';
brushPreview.style.height = eraserSize * zoom + 'px';
} else } else
brushPreview.style.display = 'none'; brushPreview.style.display = 'none';
@ -26,7 +31,6 @@ function updateCursor () {
if (currentTool == 'zoom') if (currentTool == 'zoom')
canvasView.style.cursor = "url('/pixel-editor/zoom-in.png'), auto"; canvasView.style.cursor = "url('/pixel-editor/zoom-in.png'), auto";
if (currentTool == 'resize-brush') if (currentTool == 'resize-brush' || currentTool == 'resize-eraser')
canvasView.style.cursor = 'default'; canvasView.style.cursor = 'default';
} }

View File

@ -2,15 +2,25 @@
var canvasSize,zoom; var canvasSize,zoom;
var dragging = false; var dragging = false;
var lastPos = [0,0]; var lastPos = [0,0];
var canvasPosition;
var currentTool = 'pencil'; var currentTool = 'pencil';
var currentToolTemp = 'pencil'; var currentToolTemp = 'pencil';
var brushSize = 1; var brushSize = 1;
var eraserSize = 1;
var prevBrushSize = 1; var prevBrushSize = 1;
var menuOpen = false; var prevEraserSize = 1;
var dialogueOpen = false; var dialogueOpen = false;
var documentCreated = false; var documentCreated = false;
// Checkerboard management
// Checkerboard color 1
var firstCheckerBoardColor = 'rgba(179, 173, 182, 1)';
// Checkerboard color 2
var secondCheckerBoardColor = 'rgba(204, 200, 206, 1)';
// Square size for the checkerboard
var checkerBoardSquareSize = 16;
// Checkerboard canvas
var checkerBoardCanvas = document.getElementById("checkerboard");
//common elements //common elements
var brushPreview = document.getElementById("brush-preview"); var brushPreview = document.getElementById("brush-preview");
var eyedropperPreview = document.getElementById("eyedropper-preview"); var eyedropperPreview = document.getElementById("eyedropper-preview");
@ -19,7 +29,12 @@ var colors = document.getElementsByClassName("color-button");
var colorsMenu = document.getElementById("colors-menu"); var colorsMenu = document.getElementById("colors-menu");
var popUpContainer = document.getElementById("pop-up-container"); var popUpContainer = document.getElementById("pop-up-container");
//html canvas // main canvas
var canvas = document.getElementById("pixel-canvas"); var canvas = document.getElementById("pixel-canvas");
var context = canvas.getContext("2d"); var context = canvas.getContext("2d");
// Layers
var layers = [];
// Currently selected layer
var currentLayer;

View File

@ -41,6 +41,8 @@
//=include _history.js //=include _history.js
//=include _deleteColor.js //=include _deleteColor.js
//=include _replaceAllOfColor.js //=include _replaceAllOfColor.js
//=include _checkerboard.js
//=include _canvas.js
/**load file**/ /**load file**/

1543
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,9 +16,9 @@
"gulp-include": "^2.3.1", "gulp-include": "^2.3.1",
"handlebars-helper-svg": "git+https://bitbucket.org/skeddles/npm-handlebars-helper-svg-lospec-open-source.git", "handlebars-helper-svg": "git+https://bitbucket.org/skeddles/npm-handlebars-helper-svg-lospec-open-source.git",
"hbs": "^4.0.3", "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-helpers": "git+https://skeddles@bitbucket.org/skeddles/hbs-register-helpers.git",
"hbs-register-partials": "git+https://skeddles@bitbucket.org/skeddles/hbs-register-partials.git" "hbs-register-partials": "git+https://skeddles@bitbucket.org/skeddles/hbs-register-partials.git",
"opn": "^6.0.0",
"sass": "^1.17.3"
} }
} }

View File

@ -33,6 +33,7 @@
<img src="/pixel-editor/pan-held.png" /> <img src="/pixel-editor/pan-held.png" />
<img src="/pixel-editor/pencil.png" /> <img src="/pixel-editor/pencil.png" />
<img src="/pixel-editor/zoom-in.png" /> <img src="/pixel-editor/zoom-in.png" />
<img src = "/pixel-editor/eraser.png"/>
</div> </div>
<ul id="main-menu"> <ul id="main-menu">
<li class="logo">Lospec Pixel Editor</li> <li class="logo">Lospec Pixel Editor</li>
@ -77,6 +78,11 @@
<button title="Zoom In" id="zoom-in-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button> <button title="Zoom In" id="zoom-in-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
<button title="Zoom Out" id="zoom-out-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button> <button title="Zoom Out" id="zoom-out-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
</li> </li>
<li class = "expanded">
<button title="Eraser tool (R)" id="eraser-button">{{svg "eraser.svg" width="32" height="32"}}</button>
<button title="Increase Eraser Size" id="eraser-bigger-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
<button title="Decrease Eraser Size" id="eraser-smaller-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
</li>
</ul> </ul>
<ul id="colors-menu"> <ul id="colors-menu">
@ -92,7 +98,8 @@
<div id="brush-preview"></div> <div id="brush-preview"></div>
<div id="canvas-view"> <div id="canvas-view">
<canvas id="pixel-canvas"></canvas> <canvas id="pixel-canvas" class = "drawingCanvas"></canvas>
<canvas id="checkerboard" class = "drawingCanvas"></canvas>
</div> </div>
<div id="canvas-view-shadow"></div> <div id="canvas-view-shadow"></div>
@ -103,8 +110,6 @@
<canvas id="load-palette-canvas-holder"></canvas> <canvas id="load-palette-canvas-holder"></canvas>
</div> </div>
<div class="jscolor-picker-bottom"> <div class="jscolor-picker-bottom">
<span>#</span><input type="text" id="jscolor-hex-input"/> <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> <div id="duplicate-color-warning" title="Color is a duplicate of another in palette">{{svg "warning.svg" width="14" height="12" }}</div>