diff --git a/_ext/svg/fullrect.svg b/_ext/svg/fullrect.svg new file mode 100644 index 0000000..05958ee --- /dev/null +++ b/_ext/svg/fullrect.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_ext/svg/rectangle.svg b/_ext/svg/rectangle.svg new file mode 100644 index 0000000..7ebcba9 --- /dev/null +++ b/_ext/svg/rectangle.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_ext/svg/rectselect.svg b/_ext/svg/rectselect.svg new file mode 100644 index 0000000..edb2491 --- /dev/null +++ b/_ext/svg/rectselect.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/css/pixel-editor.scss b/css/pixel-editor.scss index 010a418..d954313 100644 --- a/css/pixel-editor.scss +++ b/css/pixel-editor.scss @@ -52,6 +52,7 @@ svg { position: fixed; display:none; box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.64); + background-color:transparent; } #checkerboard { @@ -63,6 +64,16 @@ svg { background:transparent; } +#tmp-canvas { + z-index:3; + background:transparent; +} + +#vfx-canvas { + z-index:-5000; + background:transparent; +} + #eyedropper-preview { position: absolute; width: 45px; @@ -429,13 +440,15 @@ svg { #tools-menu li button#pencil-bigger-button, #tools-menu li button#zoom-in-button, -#tools-menu li button#eraser-bigger-button{ +#tools-menu li button#eraser-bigger-button, +#tools-menu li button#rectangle-bigger-button{ left: 0; } #tools-menu li button#pencil-smaller-button, #tools-menu li button#zoom-out-button, -#tools-menu li button#eraser-smaller-button{ +#tools-menu li button#eraser-smaller-button, +#tools-menu li button#rectangle-smaller-button{ right: 0; } @@ -444,7 +457,9 @@ svg { #tools-menu li.selected button#zoom-in-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{ +#tools-menu li.selected button#eraser-smaller-button, +#tools-menu li.selected button#rectangle-bigger-button, +#tools-menu li.selected button#rectangle-smaller-button{ display: block; } diff --git a/images/layout.png b/images/layout.png new file mode 100644 index 0000000..3fb3b2c Binary files /dev/null and b/images/layout.png differ diff --git a/images/rectangle.png b/images/rectangle.png new file mode 100644 index 0000000..124d40c Binary files /dev/null and b/images/rectangle.png differ diff --git a/images/rectselect.png b/images/rectselect.png new file mode 100644 index 0000000..3ed7407 Binary files /dev/null and b/images/rectselect.png differ diff --git a/js/_changeTool.js b/js/_changeTool.js index e2af449..25ebcf3 100644 --- a/js/_changeTool.js +++ b/js/_changeTool.js @@ -1,5 +1,8 @@ function changeTool (selectedTool) { - + // Ending any selection in progress + if (currentTool.includes("select") && !selectedTool.includes("select") && !selectionCanceled) { + endSelection(); + } //set tool and temp tje tje tpp; currentTool = selectedTool; currentToolTemp = selectedTool; diff --git a/js/_clickedColor.js b/js/_clickedColor.js index 835a821..7c4f5b8 100644 --- a/js/_clickedColor.js +++ b/js/_clickedColor.js @@ -10,6 +10,7 @@ function clickedColor (e){ //set current color currentLayer.context.fillStyle = this.style.backgroundColor; + currentGlobalColor = this.style.backgroundColor; //make color selected e.target.parentElement.classList.add('selected'); diff --git a/js/_colorChanged.js b/js/_colorChanged.js index 93f052c..5fcc23f 100644 --- a/js/_colorChanged.js +++ b/js/_colorChanged.js @@ -28,9 +28,6 @@ function colorChanged(e) { 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)); @@ -50,15 +47,14 @@ function colorChanged(e) { //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); + //console.log('%ccolor already exists'+(colors[i].parentElement.classList.contains('jscolor-active')?' (but is the current color)':''), colorCheckingStyle); //if the color isnt the one that has the picker currently open if (!colors[i].parentElement.classList.contains('jscolor-active')) { - console.log('%cColor is duplicate', colorCheckingStyle) + //console.log('%cColor is duplicate', colorCheckingStyle) //show the duplicate color warning duplicateColorWarning.style.visibility = 'visible'; @@ -83,13 +79,12 @@ function colorChanged(e) { //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); + //console.log('this color is the current color'); + context.fillStyle = currentColor; } /* this is wrong and bad if (settings.switchToChangedColor) { diff --git a/js/_consts.js b/js/_consts.js new file mode 100644 index 0000000..de4fe38 --- /dev/null +++ b/js/_consts.js @@ -0,0 +1,2 @@ +const MIN_Z_INDEX = -5000; +const MAX_Z_INDEX = 5000; \ No newline at end of file diff --git a/js/_drawLine.js b/js/_drawLine.js index 7008a7b..455cffe 100644 --- a/js/_drawLine.js +++ b/js/_drawLine.js @@ -1,5 +1,5 @@ //draw a line between two points on canvas -function line(x0,y0,x1,y1) { +function line(x0,y0,x1,y1, brushSize) { var dx = Math.abs(x1-x0); var dy = Math.abs(y1-y0); @@ -10,7 +10,7 @@ function line(x0,y0,x1,y1) { while (true) { //set pixel // If the current tool is the brush - if (currentTool == 'pencil') { + if (currentTool == 'pencil' || currentTool == 'rectangle') { // I fill the rect currentLayer.context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize); } else if (currentTool == 'eraser') { @@ -18,6 +18,34 @@ function line(x0,y0,x1,y1) { 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 ((x0==x1) && (y0==y1)) break; + var e2 = 2*err; + if (e2 >-dy) {err -=dy; x0+=sx;} + if (e2 < dx) {err +=dx; y0+=sy;} + } +} + +//draw a line between two points on canvas +function lineOnLayer(x0,y0,x1,y1, brushSize, context) { + + 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; + + while (true) { + //set pixel + // If the current tool is the brush + if (currentTool == 'pencil' || currentTool == 'rectangle') { + // I fill the rect + 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 + 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 ((x0==x1) && (y0==y1)) break; var e2 = 2*err; diff --git a/js/_getCursorPosition.js b/js/_getCursorPosition.js index b8d0715..5855409 100644 --- a/js/_getCursorPosition.js +++ b/js/_getCursorPosition.js @@ -2,6 +2,7 @@ function getCursorPosition(e) { var x; var y; + if (e.pageX != undefined && e.pageY != undefined) { x = e.pageX; y = e.pageY; @@ -14,5 +15,29 @@ function getCursorPosition(e) { x -= canvas.offsetLeft; y -= canvas.offsetTop; + return [x,y]; +} + +// TODO: apply the function below to every getCursorPosition call + +// TODO: FIX THIS BELOW + +//get cursor position relative to canvas +function getCursorPositionRelative(e, layer) { + 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 -= layer.canvas.offsetLeft; + y -= layer.canvas.offsetTop; + return [x,y]; } \ No newline at end of file diff --git a/js/_hotkeyListener.js b/js/_hotkeyListener.js index 5ef277f..13021d4 100644 --- a/js/_hotkeyListener.js +++ b/js/_hotkeyListener.js @@ -1,65 +1,86 @@ 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: + 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; + + // + if (e.key === "Escape") { + if (!selectionCanceled) { + endSelection(); 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; - // eraser -6, r - case 54: case 82: - console.log("Pressed r"); - changeTool('eraser'); - 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 + } + } + else { + 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, + case 52: case 80: + changeTool('pan'); + break; + //zoom - 5 + case 53: changeTool('zoom'); - break; - //redo - ctrl y - case 89: - if (keyboardEvent.ctrlKey) - redo(); - break; - case 32: - spacePressed=true; - break; - } + break; + // eraser -6, r + case 54: case 82: + console.log("Pressed r"); + changeTool('eraser'); + break; + // Rectangular selection + case 77: case 109: + changeTool('rectselect'); + break; + //Z + case 90: + console.log('PRESSED Z ', keyboardEvent.ctrlKey) + //CTRL+ALT+Z redo + if (keyboardEvent.altKey && keyboardEvent.ctrlKey) + redo(); + if (!selectionCanceled) { + endSelection(); + changeTool('pencil'); + } + //CTRL+Z undo + else if (keyboardEvent.ctrlKey) { + undo(); + if (!selectionCanceled) { + endSelection(); + changeTool('pencil'); + } + } + //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; diff --git a/js/_canvas.js b/js/_layer.js similarity index 72% rename from js/_canvas.js rename to js/_layer.js index dafb44a..614b25d 100644 --- a/js/_canvas.js +++ b/js/_layer.js @@ -1,48 +1,61 @@ -/** 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; - } +/** TODO LIST FOR LAYERS + - move the tmp layer so that it's always right below the active layer + - when the move tool is selected (to move a selection), the tmp layer must be put right above the + active layer to show a preview + - mouse events will always have at least a canvas target, so evey time there's an event, we'll have to check + the actual element type instead of the current layer and then apply the tool on the currentLayer, not on + the first one in order of z-index +*/ + + +/** Handler class for a single canvas (a single layer) + * + * @param width Canvas width + * @param height Canvas height + * @param canvas HTML canvas element + */ +function Layer(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'; + + this.context.imageSmoothingEnabled = false; + this.context.mozImageSmoothingEnabled = false; + }, + // 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; + } } \ No newline at end of file diff --git a/js/_mouseEvents.js b/js/_mouseEvents.js index 2ecc472..a590d36 100644 --- a/js/_mouseEvents.js +++ b/js/_mouseEvents.js @@ -1,6 +1,12 @@ +var currentMouseEvent; +// TODO: replace every position calculation with lastMousePos +var lastMousePos; //mousedown - start drawing window.addEventListener("mousedown", function (mouseEvent) { + // Saving the event in case something else needs it + currentMouseEvent = mouseEvent; + canDraw = true; //if no document has been created yet, or this is a dialog open if (!documentCreated || dialogueOpen) return; @@ -10,31 +16,43 @@ window.addEventListener("mousedown", function (mouseEvent) { lastPos = getCursorPosition(mouseEvent); dragging = true; - //left or right click + //left or right click ? if (mouseEvent.which == 1) { - if (spacePressed) currentTool = 'pan'; else if (mouseEvent.altKey) currentTool = 'eyedropper'; - else if (mouseEvent.target == currentLayer.canvas && (currentTool == 'pencil' || currentTool == 'eraser')) + else if (mouseEvent.target.className == 'drawingCanvas' && + (currentTool == 'pencil' || currentTool == 'eraser' || currentTool == 'rectangle')) new HistoryStateEditCanvas(); + else if (currentTool == 'moveselection') { + if (!cursorInSelectedArea()) { + changeTool('pencil'); + canDraw = false; + } + } //saveHistoryState({type: 'canvas', canvas: context.getImageData(0, 0, canvasSize[0], canvasSize[1])}); updateCursor(); - draw(mouseEvent); + if (canDraw) { + draw(mouseEvent); + } } else if (currentTool == 'pencil' && mouseEvent.which == 3) { currentTool = 'resize-brush'; - prevBrushSize = brushSize; + prevBrushSize = pencilSize; } else if (currentTool == 'eraser' && mouseEvent.which == 3) { currentTool = 'resize-eraser'; prevEraserSize = eraserSize; } - - if (currentTool == 'eyedropper' && mouseEvent.target == currentLayer.canvas) + else if (currentTool == 'rectangle' && mouseEvent.which == 3) { + currentTool = 'resize-rectangle'; + prevRectangleSize = rectangleSize; + } + + if (currentTool == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas') eyedropperPreview.style.display = 'block'; return false; @@ -44,47 +62,45 @@ window.addEventListener("mousedown", function (mouseEvent) { //mouseup - end drawing window.addEventListener("mouseup", function (mouseEvent) { - + // Saving the event in case something else needs it + currentMouseEvent = mouseEvent; + closeMenu(); if (!documentCreated || dialogueOpen) return; - if (currentTool == 'eyedropper' && mouseEvent.target == currentLayer.canvas) { + if (currentTool == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas') { 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 newColor = rgbToHex(selectedColor.data[0],selectedColor.data[1],selectedColor.data[2]); + + currentGlobalColor = "#" + 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'; - } - } - - + 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 == currentLayer.canvas) { + else if (currentTool == 'fill' && mouseEvent.target.className == 'drawingCanvas') { console.log('filling') - //if you clicked on anything but the canvas, do nothing - if (!mouseEvent.target == currentLayer.canvas) return; //get cursor postion var cursorLocation = getCursorPosition(mouseEvent); @@ -96,7 +112,7 @@ window.addEventListener("mouseup", function (mouseEvent) { //fill starting at the location fill(cursorLocation); } - else if (currentTool == 'zoom' && mouseEvent.target == canvasView) { + else if (currentTool == 'zoom' && mouseEvent.target.className == 'drawingCanvas') { let mode; if (mouseEvent.which == 1){ mode = "in"; @@ -111,6 +127,12 @@ window.addEventListener("mouseup", function (mouseEvent) { layers[i].copyData(layers[0]); } } + else if (currentTool == 'rectselect' && isRectSelecting) { + endRectSelection(mouseEvent); + } + else if (currentTool == 'rectangle') { + endRectDrawing(mouseEvent); + } dragging = false; currentTool = currentToolTemp; @@ -121,10 +143,16 @@ window.addEventListener("mouseup", function (mouseEvent) { }, false); +// OPTIMIZABLE: redundant || mouseEvent.target.className in currentTool ifs + //mouse is moving on canvas window.addEventListener("mousemove", draw, false); function draw (mouseEvent) { - var cursorLocation = getCursorPosition(mouseEvent); + lastMousePos = getCursorPosition(mouseEvent); + // Saving the event in case something else needs it + currentMouseEvent = mouseEvent; + + var cursorLocation = lastMousePos; //if a document hasnt yet been created, exit this function if (!documentCreated || dialogueOpen) return; @@ -133,21 +161,20 @@ function draw (mouseEvent) { eyedropperPreview.style.display = 'none'; if (currentTool == 'pencil') { - //move the brush preview - brushPreview.style.left = cursorLocation[0] + currentLayer.canvas.offsetLeft - brushSize * zoom / 2 + 'px'; - brushPreview.style.top = cursorLocation[1] + currentLayer.canvas.offsetTop - brushSize * zoom / 2 + 'px'; + brushPreview.style.left = cursorLocation[0] + currentLayer.canvas.offsetLeft - pencilSize * zoom / 2 + 'px'; + brushPreview.style.top = cursorLocation[1] + currentLayer.canvas.offsetTop - pencilSize * zoom / 2 + 'px'; //hide brush preview outside of canvas / canvas view - if (mouseEvent.target == currentLayer.canvas || mouseEvent.target == canvasView) + if (mouseEvent.target.className == 'drawingCanvas'|| mouseEvent.target.className == 'drawingCanvas') brushPreview.style.visibility = 'visible'; else brushPreview.style.visibility = 'hidden'; //draw line to current pixel if (dragging) { - 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)); + if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') { + line(Math.floor(lastPos[0]/zoom),Math.floor(lastPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom), pencilSize); lastPos = cursorLocation; } } @@ -168,19 +195,38 @@ function draw (mouseEvent) { 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) + if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') brushPreview.style.visibility = 'visible'; else brushPreview.style.visibility = 'hidden'; //draw line to current pixel if (dragging) { - 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)); + if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') { + line(Math.floor(lastPos[0]/zoom),Math.floor(lastPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom), eraserSize); lastPos = cursorLocation; } } } + else if (currentTool == 'rectangle') + { + //move the brush preview + brushPreview.style.left = cursorLocation[0] + currentLayer.canvas.offsetLeft - rectangleSize * zoom / 2 + 'px'; + brushPreview.style.top = cursorLocation[1] + currentLayer.canvas.offsetTop - rectangleSize * zoom / 2 + 'px'; + + //hide brush preview outside of canvas / canvas view + if (mouseEvent.target.className == 'drawingCanvas'|| mouseEvent.target.className == 'drawingCanvas') + brushPreview.style.visibility = 'visible'; + else + brushPreview.style.visibility = 'hidden'; + + if (!isDrawingRect && dragging) { + startRectDrawing(mouseEvent); + } + else if (dragging){ + updateRectDrawing(mouseEvent); + } + } 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])); @@ -189,8 +235,9 @@ function draw (mouseEvent) { layers[i].copyData(layers[0]); } } - else if (currentTool == 'eyedropper' && dragging && mouseEvent.target == currentLayer.canvas) { + else if (currentTool == 'eyedropper' && dragging && mouseEvent.target.className == 'drawingCanvas') { 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'; @@ -212,11 +259,11 @@ function draw (mouseEvent) { var newBrushSize = prevBrushSize + brushSizeChange; //set the brush to the new size as long as its bigger than 1 - brushSize = Math.max(1,newBrushSize); + pencilSize = Math.max(1,newBrushSize); //fix offset so the cursor stays centered - brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - brushSize * zoom / 2 + 'px'; - brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - brushSize * zoom / 2 + 'px'; + brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - pencilSize * zoom / 2 + 'px'; + brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - pencilSize * zoom / 2 + 'px'; updateCursor(); } @@ -237,6 +284,45 @@ function draw (mouseEvent) { updateCursor(); } + else if (currentTool == 'resize-rectangle' && 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 rectangleSizeChange = Math.round(distanceFromClick/10); + var newRectangleSize = prevRectangleSize + rectangleSizeChange; + + //set the brush to the new size as long as its bigger than 1 + rectangleSize = Math.max(1,newRectangleSize); + + //fix offset so the cursor stays centered + brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - rectangleSize * zoom / 2 + 'px'; + brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - rectangleSize * zoom / 2 + 'px'; + + updateCursor(); + } + else if (currentTool == 'rectselect') { + if (dragging && !isRectSelecting && mouseEvent.target.className == 'drawingCanvas') { + isRectSelecting = true; + console.log("cominciata selezione su " + mouseEvent.target.className); + startRectSelection(mouseEvent); + } + else if (dragging && isRectSelecting) { + updateRectSelection(mouseEvent); + } + else if (isRectSelecting) { + endRectSelection(); + } + } + else if (currentTool == 'moveselection') { + // Updating the cursor (move if inside rect, cross if not) + updateCursor(); + + // If I'm dragging, I move the preview + if (dragging && cursorInSelectedArea()) { + updateMovePreview(mouseEvent); + } + } } //mousewheel scrroll diff --git a/js/_move.js b/js/_move.js new file mode 100644 index 0000000..4b182ca --- /dev/null +++ b/js/_move.js @@ -0,0 +1,72 @@ +var imageDataToMove; +var originalDataPosition; +var canMoveSelection = false; +var lastMovePos; +var selectionCanceled = true; +var firstTimeMove = true; + +// TODO: move with arrows +function updateMovePreview(mouseEvent) { + // ISSUE + selectionCanceled = false; + + if (firstTimeMove) { + cutSelection(mouseEvent); + } + + firstTimeMove = false; + + lastMousePos = getCursorPosition(mouseEvent); + // clear the entire tmp layer + TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height); + // put the image data with offset + TMPLayer.context.putImageData( + imageDataToMove, + Math.round(lastMousePos[0] / zoom - imageDataToMove.width / 2), + Math.round(lastMousePos[1] / zoom - imageDataToMove.height / 2)); + + lastMovePos = lastMousePos; + moveSelection(lastMousePos[0] / zoom, lastMousePos[1] / zoom, imageDataToMove.width, imageDataToMove.height) +} + +function endSelection() { + TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height); + VFXLayer.context.clearRect(0, 0, VFXLayer.canvas.width, VFXLayer.canvas.height); + + if (imageDataToMove !== undefined) { + let underlyingImageData = currentLayer.context.getImageData(startX, startY, endX - startX, endY - startY); + + for (let i=0; i 0) { @@ -55,6 +66,7 @@ function newPixel (width, height, palette) { //set current drawing color as foreground color currentLayer.context.fillStyle = '#'+defaultForegroundColor; + currentGlobalColor = '#' + defaultForegroundColor; selectedPalette = 'none'; } diff --git a/js/_pixelEditorUtility.js b/js/_pixelEditorUtility.js new file mode 100644 index 0000000..67874f1 --- /dev/null +++ b/js/_pixelEditorUtility.js @@ -0,0 +1,7 @@ +function isPixelEmpty(pixel) { + if ((pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0) || pixel[3] == 0) { + return true; + } + + return false; +} \ No newline at end of file diff --git a/js/_rectSelect.js b/js/_rectSelect.js new file mode 100644 index 0000000..74c02f0 --- /dev/null +++ b/js/_rectSelect.js @@ -0,0 +1,151 @@ +var isRectSelecting = false; +let startX; +let startY; +let endX; +let endY; + +function startRectSelection(mouseEvent) { + // Saving the canvas + new HistoryStateEditCanvas(); + // Putting the vfx layer on top of everything + VFXCanvas.style.zIndex = MAX_Z_INDEX; + + // Saving the start coords of the rect + let cursorPos = getCursorPosition(mouseEvent); + startX = Math.round(cursorPos[0] / zoom) - 0.5; + startY = Math.round(cursorPos[1] / zoom) - 0.5; + + // Avoiding external selections + if (startX < 0) { + startX = 0; + } + else if (startX > currentLayer.canvas.width) { + startX = currentLayer.canvas.width; + } + + if (startY < 0) { + startY = 0; + } + else if (startY > currentLayer.canvas.height) { + startY = currentLayer.canvas.height; + } + + // Drawing the rect + drawRect(startX, startY); + selectionCanceled = false; +} + +function updateRectSelection(mouseEvent) { + let pos = getCursorPosition(mouseEvent); + + // Drawing the rect + drawRect(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5); +} + +function endRectSelection(mouseEvent) { + // Getting the end position + let currentPos = getCursorPosition(mouseEvent); + endX = Math.round(currentPos[0] / zoom) + 0.5; + endY = Math.round(currentPos[1] / zoom) + 0.5; + + // Inverting end and start (start must always be the top left corner) + if (endX < startX) { + let tmp = endX; + endX = startX; + startX = tmp; + } + // Same for the y + if (endY < startY) { + let tmp = endY; + endY = startY; + startY = tmp; + } + + // Selecting the move tool + currentTool = 'moveselection'; + currentToolTemp = currentTool; + + // Resetting this + isRectSelecting = false; + + // Updating the cursor + updateCursor(); +} + +function cutSelection(mouseEvent) { + console.log("Coordinate: start x, y: " + startX + ", " + startY + " end x, y: " + endX + ", " + endY); + // Getting the selected pixels + imageDataToMove = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1); + + currentLayer.context.clearRect(startX - 0.5, startY - 0.5, endX - startX + 1, endY - startY + 1); + // Moving those pixels from the current layer to the tmp layer + TMPLayer.context.putImageData(imageDataToMove, startX + 1, startY); + + //originalDataPosition = [currentPos[0], currentPos[1]]; +} + +function drawRect(x, y) { + // Getting the vfx context + let vfxContext = VFXCanvas.getContext("2d"); + + // Clearing the vfx canvas + vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height); + vfxContext.lineWidth = 1; + vfxContext.strokeStyle = "black"; + vfxContext.setLineDash([4]); + + // Drawing the rect + vfxContext.beginPath(); + vfxContext.rect(startX, startY, x - startX, y - startY); + + vfxContext.stroke(); + + // TODO: make the rect blink from black to white in case of dark backgrounds +} + +function applyChanges() { + VFXCanvas.style.zIndex = MIN_Z_INDEX; +} + +// Checks whether the pointer is inside the selected area or not +function cursorInSelectedArea() { + let cursorPos = getCursorPosition(currentMouseEvent); + let x = cursorPos[0] / zoom; + let y = cursorPos[1] / zoom; + + let leftX = Math.min(startX, endX); + let rightX = Math.max(startX, endX); + let topY = Math.max(startY, endY); + let bottomY = Math.min(startY, endY); + + if (leftX <= x && x <= rightX) { + if (bottomY <= y && y <= topY) { + return true; + } + + return false; + } + + return false; +} + +function moveSelection(x, y, width, height) { + // Getting the vfx context + let vfxContext = VFXCanvas.getContext("2d"); + + // Clearing the vfx canvas + vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height); + vfxContext.lineWidth = 1; + vfxContext.setLineDash([4]); + + startX = Math.round(Math.round(x) - Math.round(width / 2)) + 0.5; + startY = Math.round(Math.round(y) - Math.round(height / 2)) + 0.5; + endX = startX + Math.round(width); + endY = startY + Math.round(height); + + // Drawing the rect + vfxContext.beginPath(); + vfxContext.rect(startX, startY, width, height); + + vfxContext.stroke(); +} \ No newline at end of file diff --git a/js/_rectangle.js b/js/_rectangle.js new file mode 100644 index 0000000..3249ce4 --- /dev/null +++ b/js/_rectangle.js @@ -0,0 +1,121 @@ +var rectangleSize = 1; +var prevRectangleSie = rectangleSize; +var emptySVG = document.getElementById("empty-button-svg"); +var fullSVG = document.getElementById("full-button-svg"); + +var drawMode = 'empty'; +var isDrawingRect = false; + +let startRectX; +let startRectY; +let endRectX; +let endRectY; + + +function startRectDrawing(mouseEvent) { + // Putting the vfx layer on top of everything + VFXCanvas.style.zIndex = MAX_Z_INDEX; + // Updating flag + isDrawingRect = true; + + // Saving the start coords of the rect + let cursorPos = getCursorPosition(mouseEvent); + startRectX = Math.round(cursorPos[0] / zoom) - 0.5; + startRectY = Math.round(cursorPos[1] / zoom) - 0.5; + + drawRectangle(startRectX, startRectY); +} + +function updateRectDrawing(mouseEvent) { + let pos = getCursorPosition(mouseEvent); + + // Drawing the rect + drawRectangle(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5); +} + +function endRectDrawing(mouseEvent) { + // Getting the end position + let currentPos = getCursorPosition(mouseEvent); + let vfxContext = VFXCanvas.getContext("2d"); + + endRectX = Math.round(currentPos[0] / zoom) + 0.5; + endRectY = Math.round(currentPos[1] / zoom) + 0.5; + + // Inverting end and start (start must always be the top left corner) + if (endRectX < startRectX) { + let tmp = endRectX; + endRectX = startRectX; + startRectX = tmp; + } + // Same for the y + if (endRectY < startRectY) { + let tmp = endRectY; + endRectY = startRectY; + startRectY = tmp; + } + + let hexColor = hexToRgb(currentLayer.context.fillStyle); + + // Resetting this + isDrawingRect = false; + // Drawing the rect + startRectY -= 0.5; + endRectY -= 0.5; + endRectX -= 0.5; + startRectX -= 0.5; + + currentLayer.context.lineWidth = rectangleSize; + currentLayer.context.fillStyle = currentGlobalColor; + + line(startRectX, startRectY, endRectX, startRectY, rectangleSize); + line(endRectX, startRectY, endRectX, endRectY, rectangleSize); + line(endRectX, endRectY, startRectX, endRectY, rectangleSize); + line(startRectX, endRectY, startRectX, startRectY, rectangleSize); + + if (drawMode == 'fill') { + currentLayer.context.fillRect(startRectX, startRectY, endRectX - startRectX, endRectY - startRectY); + } + + // Clearing the vfx canvas + vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height); +} + +function drawRectangle(x, y) { + // Getting the vfx context + let vfxContext = VFXCanvas.getContext("2d"); + + // Clearing the vfx canvas + vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height); + + // Drawing the rect + vfxContext.lineWidth = rectangleSize; + vfxContext.strokeStyle = currentGlobalColor; + + // Drawing the rect + vfxContext.beginPath(); + if ((rectangleSize % 2 ) == 0) { + vfxContext.rect(startRectX - 0.5, startRectY - 0.5, x - startRectX, y - startRectY); + } + else { + vfxContext.rect(startRectX, startRectY, x - startRectX, y - startRectY); + } + + vfxContext.setLineDash([]); + + vfxContext.stroke(); +} + +function setRectToolSvg() { + if (drawMode == 'empty') { + emptySVG.setAttribute("display", "visible"); + fullSVG.setAttribute("display", "none"); + } + else { + emptySVG.setAttribute("display", "none"); + fullSVG.setAttribute("display", "visible"); + } +} + +function applyChanges() { + VFXCanvas.style.zIndex = MIN_Z_INDEX; +} \ No newline at end of file diff --git a/js/_toolButtons.js b/js/_toolButtons.js index b96c953..6c846d1 100644 --- a/js/_toolButtons.js +++ b/js/_toolButtons.js @@ -32,6 +32,36 @@ on('click',"eraser-smaller-button", function(e){ updateCursor(); }, false); +// rectangle +on('click',"rectangle-button", function(){ + // If the user clicks twice on the button, they change the draw mode + if (currentTool == 'rectangle') { + if (drawMode == 'empty') { + drawMode = 'fill'; + setRectToolSvg(); + } + else { + drawMode = 'empty'; + setRectToolSvg(); + } + } + else { + changeTool('rectangle'); + } +}, false); + +// rectangle bigger +on('click',"rectangle-bigger-button", function(){ + rectangleSize++; + updateCursor(); +}, false); + +// rectangle smaller +on('click',"rectangle-smaller-button", function(e){ + if(rectangleSize > 1) rectangleSize--; + updateCursor(); +}, false); + //fill on('click',"fill-button", function(){ changeTool('fill'); @@ -69,4 +99,9 @@ on('click',"zoom-out-button", function(){ for (let i=1; i + @@ -10,7 +16,6 @@ {{{google-analytics}}} {{{favicons}}} - @@ -34,7 +39,10 @@ + + +