diff --git a/README.md b/README.md index 614b7d5..bf2c1c9 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,10 @@ The next version is mostly focused on adding missing essential features and port Suggestions / Planned features: - Line tool -- Resize canvas -- Snap brush preview to pixel grid -- Move selection with arrows +- Tiled mode - Load palette from LPE file -- Move colours in palette editor -- Duplicate layer -- Hide non-hovered layers +- Move colours in (advanced) palette editor +- Symmetry options - Custom color picker - custom code without dependencies @@ -31,17 +28,19 @@ Suggestions / Planned features: - Maybe rearrange UI on portrait - Stack colors when too many - Fix popups - -- Show colors which would need to be added to palette - -- Palette option remove unused colors -- Pixel Grid - - Another currentLayer.canvas - - Must be rescaled each zoom - + - Possibly add collaborate function -- Bug fix - - Alt + scroll broken + +- Polish: + - ctrl + a to select everything / selection -> all, same for deselection + - Show colors which would need to be added to palette + - Warning windows for wrong inputs + - Palette option remove unused colors + - Move selection with arrows + - Update pivot buttons when resizing canvas + - Update borders by dragging the canvas' edges with the mouse when resizing canvas + - Move the canvases so they're centered after resizing the canvas (maybe a .center() method in layer class) + - Trim canvas ## How to Contribute diff --git a/_ext/scripts/utilities/getSetValue.js b/_ext/scripts/utilities/getSetValue.js index eed10fd..f69e489 100644 --- a/_ext/scripts/utilities/getSetValue.js +++ b/_ext/scripts/utilities/getSetValue.js @@ -2,6 +2,7 @@ function getValue(elementId) { var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId); + console.log("setting: " + elementId + ": " + element.value); return element.value; } diff --git a/_ext/svg/arrows/bottom.svg b/_ext/svg/arrows/bottom.svg new file mode 100644 index 0000000..af4022a --- /dev/null +++ b/_ext/svg/arrows/bottom.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/_ext/svg/arrows/bottomleft.svg b/_ext/svg/arrows/bottomleft.svg new file mode 100644 index 0000000..343692d --- /dev/null +++ b/_ext/svg/arrows/bottomleft.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/_ext/svg/arrows/bottomright.svg b/_ext/svg/arrows/bottomright.svg new file mode 100644 index 0000000..8cca929 --- /dev/null +++ b/_ext/svg/arrows/bottomright.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/_ext/svg/arrows/left.svg b/_ext/svg/arrows/left.svg new file mode 100644 index 0000000..f7ff47a --- /dev/null +++ b/_ext/svg/arrows/left.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/_ext/svg/arrows/middle.svg b/_ext/svg/arrows/middle.svg new file mode 100644 index 0000000..aa46824 --- /dev/null +++ b/_ext/svg/arrows/middle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_ext/svg/arrows/right.svg b/_ext/svg/arrows/right.svg new file mode 100644 index 0000000..3ecd449 --- /dev/null +++ b/_ext/svg/arrows/right.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/_ext/svg/arrows/top.svg b/_ext/svg/arrows/top.svg new file mode 100644 index 0000000..1d44872 --- /dev/null +++ b/_ext/svg/arrows/top.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/_ext/svg/arrows/topleft.svg b/_ext/svg/arrows/topleft.svg new file mode 100644 index 0000000..9ee7f10 --- /dev/null +++ b/_ext/svg/arrows/topleft.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/_ext/svg/arrows/topright.svg b/_ext/svg/arrows/topright.svg new file mode 100644 index 0000000..a845a01 --- /dev/null +++ b/_ext/svg/arrows/topright.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/css/pixel-editor.scss b/css/pixel-editor.scss index d0f12e4..98d3b9e 100644 --- a/css/pixel-editor.scss +++ b/css/pixel-editor.scss @@ -305,11 +305,17 @@ svg { background: transparent; } -#tmp-canvas { - z-index: 3; +#pixel-grid { + z-index: 5000; background: transparent; } +#tmp-canvas { + z-index: 4; + background: transparent; +} + + #vfx-canvas { z-index: -5000; background: transparent; @@ -855,9 +861,11 @@ svg { color: #c0bfc1; } -#settings-container { +.settings-entry { display: flex; align-items: baseline; + margin-top:10px; + label { flex: 1; } @@ -865,6 +873,7 @@ svg { width: 90px !important; display: block; box-sizing: border-box; + float:right; } } @@ -947,6 +956,211 @@ svg { display: none; } +#resize-canvas, #resize-sprite { + display:flex; + position:relative; + flex-wrap:wrap; +} + +#pivot-menu { + position: relative; + display:inline-flex; + flex-wrap:wrap; + vertical-align:middle; + text-align:center; + width:130px; + float:left; + + button { + margin-right:10px; + margin-bottom:10px; + position:relative; + width:32px; + height:32px; + background:$basehover; + border:none; + + path { + fill:$basehovericon; + } + transition: background 100ms ease-in-out, + transform 100ms ease; + -webkit-appearance: none; + -moz-appearance: none; + } + + button:hover, + button:focus, + button.rc-selected-pivot { + cursor:pointer; + background-color: $baseicon; + path { + fill:$basehovericonhover; + } + border: 2px solid color(base, foreground); + } + button:active { + transform: scale(0.95); + } +} + +#borders-menu, #rc-size-menu, #rs-size-menu, #rs-percentage-menu { + display:flex; + position:relative; + flex-wrap: wrap; + width:250px; + font-size:15px; + left:10px; + text-align:center; + + button { + background:$basehover; + border:none; + + color: $basehovericon; + transition: background 100ms ease-in-out, + transform 100ms ease; + -webkit-appearance: none; + -moz-appearance: none; + } + + button:hover, + button:focus { + cursor:pointer; + background-color: $baseicon; + color:$basehovericonhover; + border: 2px solid color(base, foreground); + } + button:active { + transform: scale(0.95); + } + + input[type=number] { + position:relative; + margin-left:10px; + height:15px; + width:40px; + padding:8px; + } + + input[type=number]::-webkit-outer-spin-button, + input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + /* Firefox */ + input[type=number] { + -moz-appearance: textfield; + } + + span { + padding-right:10px; + float:left; + position:relative; + vertical-align:middle; + height:40px; + width:100px; + display: inline-flex; + align-items: center; + } +} + +#rs-percentage-menu { + width: 400px; + span { + width:150px; + } +} + +#rs-size-menu { + width: 400px; + span { + width:150px; + } +} + +#rs-percentage-menu, #rs-size-menu { + justify-content: center; + + div { + float: none; + } +} + +#borders-menu { + width:400px; + justify-content: center; + + div { + float: none; + width: 330px; + padding-left:50px; + + span { + padding-right:20px; + } + } +} + +#rs-ratio-div { + width:400px; + justify-content: center; + padding-left:20px; + + span { + width:400px; + justify-content: center; + } + + select { + height:30px; + background-color: $basehover; + color: $basehovericon; + border:none; + position:relative; + left:10px; + + option { + background-color: $basehover; + color:$basehovericon; + padding:5px; + } + + option:checked, option:hover { + box-shadow: 0 0 10px 100px $basehovericon inset; + color: $basehovericonhover; + } + } +} + +#rs-keep-ratio { + background: color(button); + border: none; + font-size:14px; + color: color(button, foreground); + padding: 10px 20px; + margin: 0 auto; + position:relative; + display: block; +} + +#resize-canvas-confirm, #resize-sprite-confirm { + background: color(button); + border: none; + font-size:18px; + border-radius: 4px; + color: color(button, foreground); + padding: 10px 20px; + cursor: pointer; + margin: 0 auto; + position:relative; + top:10px; + display: block; + &:hover { + background: color(button, background, hover); + } +} + #compatibility-warning { display: flex; justify-content: center; diff --git a/js/_addColor.js b/js/_addColor.js index d8e5b62..ac95af0 100644 --- a/js/_addColor.js +++ b/js/_addColor.js @@ -4,7 +4,6 @@ let currentPalette = []; //input hex color string //returns list item element function addColor (newColor) { - //add # at beginning if not present if (newColor.charAt(0) != '#') newColor = '#' + newColor; @@ -19,13 +18,6 @@ function addColor (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]); diff --git a/js/_addColorButton.js b/js/_addColorButton.js index 16f7c56..9da3169 100644 --- a/js/_addColorButton.js +++ b/js/_addColorButton.js @@ -44,7 +44,7 @@ on('click', 'add-color-button', function(){ //add new color and make it selected var addedColor = addColor(newColor); addedColor.classList.add('selected'); - context.fillStyle = '#' + newColor; + currentLayer.context.fillStyle = '#' + newColor; //add history state //saveHistoryState({type: 'addcolor', colorValue: addedColor.firstElementChild.jscolor.toString()}); diff --git a/js/_changeZoom.js b/js/_changeZoom.js index d05a5b1..4dc9eff 100644 --- a/js/_changeZoom.js +++ b/js/_changeZoom.js @@ -11,7 +11,7 @@ function changeZoom (layer, direction, cursorLocation) { newHeight = canvasSize[1] * zoom; //adjust canvas position - setCanvasOffset(layer.canvas, layer.canvas.offsetLeft + (oldWidth - newWidth) *cursorLocation[0]/oldWidth, layer.canvas.offsetTop + (oldHeight - newHeight) *cursorLocation[1]/oldWidth) + layer.setCanvasOffset(layer.canvas.offsetLeft + (oldWidth - newWidth) *cursorLocation[0]/oldWidth, layer.canvas.offsetTop + (oldHeight - newHeight) *cursorLocation[1]/oldWidth); } //if you want to zoom in else if (direction == 'in' && zoom + Math.ceil(zoom/10) < window.innerHeight/4){ @@ -20,7 +20,7 @@ function changeZoom (layer, direction, cursorLocation) { newHeight = canvasSize[1] * zoom; //adjust canvas position - setCanvasOffset(layer.canvas, layer.canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), layer.canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight)) + layer.setCanvasOffset(layer.canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), layer.canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight)); } //resize canvas diff --git a/js/_checkerboard.js b/js/_checkerboard.js index adfedd0..28c28b6 100644 --- a/js/_checkerboard.js +++ b/js/_checkerboard.js @@ -1,10 +1,19 @@ // This script contains all the functions used to manage the checkboard +// 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'); // 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; diff --git a/js/_clickedColor.js b/js/_clickedColor.js index f09ef07..d326818 100644 --- a/js/_clickedColor.js +++ b/js/_clickedColor.js @@ -8,7 +8,7 @@ function clickedColor (e){ if (selectedColor) selectedColor.classList.remove('selected'); //set current color - for (let i=1; i this.index + 1) { + layers[this.index + 1].selectLayer(); + } + else { + layers[this.index - 1].selectLayer(); + } + this.added.canvas.remove(); this.added.menuEntry.remove(); + layers.splice(index, 1); }; diff --git a/js/_layer.js b/js/_layer.js index 9ae86c3..3500c43 100644 --- a/js/_layer.js +++ b/js/_layer.js @@ -57,6 +57,8 @@ class Layer { if (menuEntry != null) { this.name = menuEntry.getElementsByTagName("p")[0].innerHTML; menuEntry.id = "layer" + id; + menuEntry.onmouseover = () => this.hover(); + menuEntry.onmouseout = () => this.unhover(); menuEntry.onclick = () => this.selectLayer(); menuEntry.getElementsByTagName("button")[0].onclick = () => this.toggleLock(); menuEntry.getElementsByTagName("button")[1].onclick = () => this.toggleVisibility(); @@ -76,12 +78,13 @@ class Layer { // Initializes the canvas initialize() { + /* var maxHorizontalZoom = Math.floor(window.innerWidth/this.canvasSize[0]*0.75); var maxVerticalZoom = Math.floor(window.innerHeight/this.canvasSize[1]*0.75); zoom = Math.min(maxHorizontalZoom,maxVerticalZoom); if (zoom < 1) zoom = 1; - + */ //resize canvas this.canvas.width = this.canvasSize[0]; this.canvas.height = this.canvasSize[1]; @@ -99,6 +102,24 @@ class Layer { this.context.mozImageSmoothingEnabled = false; } + hover() { + // Hide all the layers but the current one + for (let i=1; i maxXOffset) + this.canvas.style.left = maxXOffset +'px'; + else + this.canvas.style.left = offsetLeft +'px'; + + //vertical offset + var minYOffset = -this.canvasSize[1] * zoom + 164; + var maxYOffset = window.innerHeight - 100; + + if (offsetTop < minYOffset) + this.canvas.style.top = minYOffset +'px'; + else if (offsetTop > maxYOffset) + this.canvas.style.top = maxYOffset +'px'; + else + this.canvas.style.top = offsetTop +'px'; + } + + // Copies the otherLayer's position and size + copyData(otherLayer) { + this.canvas.style.width = otherLayer.canvas.style.width; + this.canvas.style.height = otherLayer.canvas.style.height; + + this.canvas.style.left = otherLayer.canvas.style.left; + this.canvas.style.top = otherLayer.canvas.style.top; } openOptionsMenu(event) { @@ -219,9 +265,10 @@ class Layer { layer.menuEntry.classList.add("selected-layer"); currentLayer = layer; } - +/* canvas = currentLayer.canvas; context = currentLayer.context; +*/ } toggleLock() { @@ -433,6 +480,55 @@ function deleteLayer(saveHistory = true) { currentLayer.closeOptionsMenu(); } +function duplicateLayer(event, saveHistory = true) { + let layerIndex = layers.indexOf(currentLayer); + let toDuplicate = currentLayer; + let menuEntries = layerList.children; + + // Increasing z-indexes of the layers above + for (let i=getMenuEntryIndex(menuEntries, toDuplicate.menuEntry) - 1; i>=0; i--) { + getLayerByID(menuEntries[i].id).canvas.style.zIndex++; + } + maxZIndex++; + + // Creating a new canvas + let newCanvas = document.createElement("canvas"); + // Setting up the new canvas + canvasView.append(newCanvas); + newCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex) + 1; + newCanvas.classList.add("drawingCanvas"); + + if (!layerListEntry) return console.warn('skipping adding layer because no document'); + + // Clone the default layer + let toAppend = currentLayer.menuEntry.cloneNode(true); + // Setting the default name for the layer + toAppend.getElementsByTagName('p')[0].innerHTML += " copy"; + // Removing the selected class + toAppend.classList.remove("selected-layer"); + // Adding the layer to the list + layerCount++; + + // Creating a layer object + let newLayer = new Layer(currentLayer.canvasSize[0], currentLayer.canvasSize[1], newCanvas, toAppend); + newLayer.context.fillStyle = currentLayer.context.fillStyle; + newLayer.copyData(currentLayer); + + layers.splice(layerIndex, 0, newLayer); + + // Insert it before the Add layer button + layerList.insertBefore(toAppend, currentLayer.menuEntry); + + // Copy the layer content + newLayer.context.putImageData(currentLayer.context.getImageData( + 0, 0, currentLayer.canvasSize[0], currentLayer.canvasSize[1]), 0, 0); + newLayer.updateLayerPreview(); + // Basically "if I'm not adding a layer because redo() is telling meto do so", then I can save the history + if (saveHistory) { + new HistoryStateDuplicateLayer(newLayer, currentLayer); + } +} + function renameLayer(event) { let layerIndex = layers.indexOf(currentLayer); let toRename = currentLayer; @@ -521,6 +617,16 @@ function moveLayers(toDropLayer, staticLayer, saveHistory = true) { } } +function getMenuEntryIndex(list, entry) { + for (let i=0; i127) brushPreview.classList.remove('dark'); + else brushPreview.classList.add('dark'); + + currentLayer.updateLayerPreview(); + } + // Decided to write a different implementation in case of differences between the brush and the eraser tool + else if (currentTool.name == 'eraser') { + //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'; + + //draw line to current pixel + if (dragging) { + if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') { + line(Math.floor(lastMouseClickPos[0]/zoom),Math.floor(lastMouseClickPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom), currentTool.brushSize); + lastMouseClickPos = cursorLocation; + } + } + + currentLayer.updateLayerPreview(); + } + else if (currentTool.name == 'rectangle') + { + //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); } } - - //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'); - - currentLayer.updateLayerPreview(); - } - // Decided to write a different implementation in case of differences between the brush and the eraser tool - else if (currentTool.name == 'eraser') { - // Uses the same preview as the brush - //move the brush preview - brushPreview.style.left = cursorLocation[0] + canvas.offsetLeft - currentTool.brushSize * zoom / 2 + 'px'; - brushPreview.style.top = cursorLocation[1] + canvas.offsetTop - currentTool.brushSize * 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'; - - //draw line to current pixel - if (dragging) { - 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), currentTool.brushSize); - lastPos = cursorLocation; - } - } - - currentLayer.updateLayerPreview(); - } - else if (currentTool.name == 'rectangle') - { - //move the brush preview - brushPreview.style.left = cursorLocation[0] + currentLayer.canvas.offsetLeft - currentTool.brushSize * zoom / 2 + 'px'; - brushPreview.style.top = cursorLocation[1] + currentLayer.canvas.offsetTop - currentTool.brushSize * 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 (currentTool.name == 'pan' && dragging) { + // Setting first layer position + layers[0].setCanvasOffset(layers[0].canvas.offsetLeft + (cursorLocation[0] - lastMouseClickPos[0]), layers[0].canvas.offsetTop + (cursorLocation[1] - lastMouseClickPos[1])); + // Copying that position to the other layers + for (let i=1; i127) eyedropperPreview.classList.remove('dark'); + else eyedropperPreview.classList.add('dark'); + } + else if (currentTool.name == 'resizebrush' && dragging) { + //get new brush size based on x distance from original clicking location + var distanceFromClick = cursorLocation[0] - lastMouseClickPos[0]; + //var roundingAmount = 20 - Math.round(distanceFromClick/10); + //this doesnt work in reverse... because... it's not basing it off of the brush size which it should be + var brushSizeChange = Math.round(distanceFromClick/10); + var newBrushSize = tool.pencil.previousBrushSize + brushSizeChange; + + //set the brush to the new size as long as its bigger than 1 + tool.pencil.brushSize = Math.max(1,newBrushSize); + + //fix offset so the cursor stays centered + tool.pencil.moveBrushPreview(lastMouseClickPos); + currentTool.updateCursor(); + } + else if (currentTool.name == 'resizeeraser' && dragging) { + //get new brush size based on x distance from original clicking location + var distanceFromClick = cursorLocation[0] - lastMouseClickPos[0]; + //var roundingAmount = 20 - Math.round(distanceFromClick/10); + //this doesnt work in reverse... because... it's not basing it off of the brush size which it should be + var eraserSizeChange = Math.round(distanceFromClick/10); + var newEraserSizeChange = tool.eraser.previousBrushSize + eraserSizeChange; + + //set the brush to the new size as long as its bigger than 1 + tool.eraser.brushSize = Math.max(1,newEraserSizeChange); + + //fix offset so the cursor stays centered + tool.eraser.moveBrushPreview(lastMouseClickPos); + currentTool.updateCursor(); + } + else if (currentTool.name == 'resizerectangle' && dragging) { + //get new brush size based on x distance from original clicking location + var distanceFromClick = cursorLocation[0] - lastMouseClickPos[0]; + //var roundingAmount = 20 - Math.round(distanceFromClick/10); + //this doesnt work in reverse... because... it's not basing it off of the brush size which it should be + var rectangleSizeChange = Math.round(distanceFromClick/10); + var newRectangleSize = tool.rectangle.previousBrushSize + rectangleSizeChange; + + //set the brush to the new size as long as its bigger than 1 + tool.rectangle.brushSize = Math.max(1,newRectangleSize); + + //fix offset so the cursor stays centered + tool.rectangle.moveBrushPreview(lastMouseClickPos); + currentTool.updateCursor(); + } + else if (currentTool.name == 'rectselect') { + if (dragging && !isRectSelecting && mouseEvent.target.className == 'drawingCanvas') { + isRectSelecting = true; + startRectSelection(mouseEvent); + } + else if (dragging && isRectSelecting) { + updateRectSelection(mouseEvent); + } + else if (isRectSelecting) { + endRectSelection(); + } + } + else if (currentTool.name == 'moveselection') { + // Updating the cursor (move if inside rect, cross if not) + currentTool.updateCursor(); + + // If I'm dragging, I move the preview + if (dragging && cursorInSelectedArea()) { + updateMovePreview(getCursorPosition(mouseEvent)); + } } } - else if (currentTool.name == '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; i127) eyedropperPreview.classList.remove('dark'); - else eyedropperPreview.classList.add('dark'); - } - else if (currentTool.name == 'resizebrush' && 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 = tool.pencil.previousBrushSize + brushSizeChange; - - //set the brush to the new size as long as its bigger than 1 - tool.pencil.brushSize = Math.max(1,newBrushSize); - - //fix offset so the cursor stays centered - brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - tool.pencil.brushSize * zoom / 2 + 'px'; - brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - tool.pencil.brushSize * zoom / 2 + 'px'; - - currentTool.updateCursor(); - } - else if (currentTool.name == 'resizeeraser' && 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 = tool.eraser.previousBrushSize + eraserSizeChange; - - //set the brush to the new size as long as its bigger than 1 - tool.eraser.brushSize = Math.max(1,newEraserSizeChange); - - //fix offset so the cursor stays centered - brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - tool.eraser.brushSize * zoom / 2 + 'px'; - brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - tool.eraser.brushSize * zoom / 2 + 'px'; - - currentTool.updateCursor(); - } - else if (currentTool.name == 'resizerectangle' && 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 = tool.rectangle.previousBrushSize + rectangleSizeChange; - - //set the brush to the new size as long as its bigger than 1 - tool.rectangle.brushSize = Math.max(1,newRectangleSize); - - //fix offset so the cursor stays centered - brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - tool.rectangle.brushSize * zoom / 2 + 'px'; - brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - tool.rectangle.brushSize * zoom / 2 + 'px'; - - currentTool.updateCursor(); - } - else if (currentTool.name == 'rectselect') { - if (dragging && !isRectSelecting && mouseEvent.target.className == 'drawingCanvas') { - isRectSelecting = true; - startRectSelection(mouseEvent); - } - else if (dragging && isRectSelecting) { - updateRectSelection(mouseEvent); - } - else if (isRectSelecting) { - endRectSelection(); - } - } - else if (currentTool.name == 'moveselection') { - // Updating the cursor (move if inside rect, cross if not) - currentTool.updateCursor(); - - // If I'm dragging, I move the preview - if (dragging && cursorInSelectedArea()) { - updateMovePreview(getCursorPosition(mouseEvent)); - } - } } -//mousewheel scrroll +//mousewheel scroll canvasView.addEventListener("wheel", function(mouseEvent){ - - if (currentTool.name == 'zoom' || mouseEvent.altKey) { - let mode; - 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 0) { + mode = 'out'; } + // Changing zoom and position of the first layer + changeZoom(layers[0], mode, getCursorPosition(mouseEvent)); + + for (let i=1; i= 0; i-=4) { + if (!isPixelEmpty( + [imageData.data[i - 3], imageData.data[i - 2], + -imageData.data[i - 1], imageData.data[i]])) { + pixelPosition = getPixelPosition(i); + + // max x + if (pixelPosition[0] > maxX) { + maxX = pixelPosition[0]; + } + // min x + if (pixelPosition[0] < minX) { + minX = pixelPosition[0]; + } + // max y + if (pixelPosition[1] > maxY) { + maxY = pixelPosition[1]; + } + // min y + if (pixelPosition[1] < minY) { + minY = pixelPosition[1]; + } + } + } + } + + tmp = minY; + minY = maxY; + maxY = tmp; + + minY = layers[0].canvasSize[1] - minY; + maxY = layers[0].canvasSize[1] - maxY; + + borders.right = (maxX - layers[0].canvasSize[0]) + 1; + borders.left = -minX; + borders.top = maxY - layers[0].canvasSize[1] + 1; + borders.bottom = -minY; + + // Saving the data + for (let i=0; i 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'; -} diff --git a/js/_settings.js b/js/_settings.js index 198d3f3..33e3d08 100644 --- a/js/_settings.js +++ b/js/_settings.js @@ -14,7 +14,8 @@ if(!settingsFromCookie) { enableBrushPreview: true, //unused - performance enableEyedropperPreview: true, //unused - performance numberOfHistoryStates: 20, - maxColorsOnImportedImage: 128 + maxColorsOnImportedImage: 128, + pixelGridColour: '#0000FF' }; } else{ @@ -35,6 +36,9 @@ on('click', 'save-settings', function (){ //save new settings to settings object settings.numberOfHistoryStates = getValue('setting-numberOfHistoryStates'); + settings.pixelGridColour = getValue('setting-pixelGridColour'); + // Filling pixel grid again if colour changed + fillPixelGrid(); //save settings object to cookie var cookieValue = JSON.stringify(settings); diff --git a/js/_tools.js b/js/_tools.js index a521336..3c10d70 100644 --- a/js/_tools.js +++ b/js/_tools.js @@ -64,6 +64,46 @@ class Tool { //change cursor this.updateCursor(); } + + updateCursor () { + //switch to that tools cursor + canvasView.style.cursor = this.cursor || 'default'; + + //if the tool uses a brush preview, make it visible and update the size + if (this.brushPreview) { + //console.log('brush size',this.currentBrushSize) + brushPreview.style.display = 'block'; + brushPreview.style.width = this.currentBrushSize * zoom + 'px'; + brushPreview.style.height = this.currentBrushSize * zoom + 'px'; + } + + //show / hide eyedropper color preview + if (this.eyedropperPreview) eyedropperPreview.style.display = 'block'; + else eyedropperPreview.style.display = 'none'; + + //moveSelection + if (currentTool.name == 'moveselection') { + if (cursorInSelectedArea()) { + canMoveSelection = true; + canvasView.style.cursor = 'move'; + brushPreview.style.display = 'none'; + } + else { + canvasView.style.cursor = 'crosshair'; + } + } + } + + moveBrushPreview(cursorLocation) { + let toSub = 0; + // Prevents the brush to be put in the middle of pixels + if (this.currentBrushSize % 2 == 0) { + toSub = 0.5; + } + + brushPreview.style.left = (Math.ceil(cursorLocation[0] / zoom) * zoom + currentLayer.canvas.offsetLeft - this.currentBrushSize * zoom / 2 - zoom / 2 - toSub * zoom) + 'px'; + brushPreview.style.top = (Math.ceil(cursorLocation[1] / zoom) * zoom + currentLayer.canvas.offsetTop - this.currentBrushSize * zoom / 2 - zoom / 2 - toSub * zoom) + 'px'; + } } diff --git a/js/_updateCursor.js b/js/_updateCursor.js deleted file mode 100644 index bbd207f..0000000 --- a/js/_updateCursor.js +++ /dev/null @@ -1,35 +0,0 @@ - - -//set the correct cursor for the current tool -Tool.prototype.updateCursor = function () { - //console.log('updateCursor()', currentTool) - - //switch to that tools cursor - canvasView.style.cursor = this.cursor || 'default'; - - //if the tool uses a brush preview, make it visible and update the size - if (this.brushPreview) { - //console.log('brush size',this.currentBrushSize) - brushPreview.style.display = 'block'; - brushPreview.style.width = this.currentBrushSize * zoom + 'px'; - brushPreview.style.height = this.currentBrushSize * zoom + 'px'; - } - - //show / hide eyedropper color preview - if (this.eyedropperPreview) eyedropperPreview.style.display = 'block'; - else eyedropperPreview.style.display = 'none'; - - //moveSelection - if (currentTool.name == 'moveselection') { - if (cursorInSelectedArea()) { - canMoveSelection = true; - canvasView.style.cursor = 'move'; - brushPreview.style.display = 'none'; - } - else { - canvasView.style.cursor = 'crosshair'; - } - } -} - -/*global Tool, dragging, canvasView, brushPreview, canMoveSelection, cursorInSelectedArea, eyedropperPreview, zoom, currentTool */ diff --git a/js/_variables.js b/js/_variables.js index 3286b78..b84b1a3 100644 --- a/js/_variables.js +++ b/js/_variables.js @@ -1,21 +1,12 @@ //init variables -var canvasSize,zoom; +var canvasSize; +var zoom = 7; var dragging = false; -var lastPos = [0,0]; +var lastMouseClickPos = [0,0]; var dialogueOpen = false; var documentCreated = false; var pixelEditorMode; -// 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 var brushPreview = document.getElementById("brush-preview"); var eyedropperPreview = document.getElementById("eyedropper-preview"); @@ -26,7 +17,6 @@ var popUpContainer = document.getElementById("pop-up-container"); // main canvas var canvas = document.getElementById('pixel-canvas'); -var context = canvas.getContext('2d'); var currentGlobalColor; // Layers @@ -43,3 +33,13 @@ var VFXCanvas = document.getElementById('vfx-canvas'); var TMPLayer; // TMP canvas var TMPCanvas = document.getElementById('tmp-canvas'); + +// Pixel grid layer +var pixelGrid; +// Pixel grid canvas +var pixelGridCanvas; + +// Index of the first layer the user can use in the layers array +var firstUserLayerIndex = 2; +// Number of layers that are only used by the editor +var nAppLayers = 3; \ No newline at end of file diff --git a/js/pixel-editor.js b/js/pixel-editor.js index 3b05c3e..52b1f4b 100644 --- a/js/pixel-editor.js +++ b/js/pixel-editor.js @@ -42,8 +42,11 @@ //=include _deleteColor.js //=include _replaceAllOfColor.js //=include _checkerboard.js +//=include _pixelGrid.js //=include _layer.js //=include _copyPaste.js +//=include _resizeCanvas.js +//=include _resizeSprite.js /**load file**/ //=include _loadImage.js diff --git a/js/tools/_select.js b/js/tools/_select.js index 984a015..89052d5 100644 --- a/js/tools/_select.js +++ b/js/tools/_select.js @@ -2,6 +2,7 @@ new Tool('rectselect', { cursor: 'crosshair', + brushPreview: true, }); diff --git a/views/pixel-editor.hbs b/views/pixel-editor.hbs index 507f648..2c72b99 100644 --- a/views/pixel-editor.hbs +++ b/views/pixel-editor.hbs @@ -52,14 +52,24 @@
    • +
    • +
    • +
  • +
  • + +
      +
    • +
    +
    • +
    • @@ -81,13 +91,13 @@
      • +
      • -
      • @@ -171,10 +181,13 @@
        • - +
        • - + +
        • +
        • +
        • @@ -187,6 +200,7 @@
        +
        @@ -196,6 +210,7 @@ +
        @@ -217,7 +232,8 @@ {{svg "adjust.svg" width="20" height="20" }} -
        +
        +

        New Pixel

        @@ -245,6 +261,116 @@
        + + +
        + +

        Scale sprite

        + +

        New size

        + +
        + + Width: + + + + Height: + +
        +
        + +

        Resize percentages

        + +
        + + Width % + + + + Height % + +
        +
        + + Keep current ratio + + + Scaling algorithm: + + +
        + +
        +
        +
        + + +
        + +

        Resize canvas

        + + + + + + + + + + + + + + + +

        Size

        +
        + + Width: + + + + Height: + +
        +
        + + +

        Borders offsets

        +
        + + Left: + + + + Right: + + + + Top: + + + + Bottom: + +
        + +
        +

        Help

        @@ -306,7 +432,15 @@

        Settings

        - +

        History

        +
        + +
        + +

        Pixel grid

        +
        + +

        Your browsers cookies are disabled, settings will be lost upon closing this page.