diff --git a/js/ToolManager.js b/js/ToolManager.js index 2aff531..b3a9ace 100644 --- a/js/ToolManager.js +++ b/js/ToolManager.js @@ -3,8 +3,9 @@ const ToolManager = (() => { eraserTool = new EraserTool("eraser", {type: 'html'}, switchTool); rectangleTool = new RectangleTool("rectangle", {type: 'html'}, switchTool); lineTool = new LineTool("line", {type: 'html'}, switchTool); - fillTool = new FillTool("fill", {type: 'cursor', pic: 'fill.png'}, switchTool); - eyedropperTool = new EyedropperTool("eyedropper", {type: 'cursor', pic: 'none'}, switchTool); + fillTool = new FillTool("fill", {type: 'cursor', style: 'crosshair'}, switchTool); + eyedropperTool = new EyedropperTool("eyedropper", {type: 'cursor', style: 'crosshair'}, switchTool); + panTool = new PanTool("pan", {type: 'custom'}, switchTool); currTool = brushTool; currTool.onSelect(); diff --git a/js/_resizeSprite.js b/js/_resizeSprite.js index 402fd9a..aa2e00d 100644 --- a/js/_resizeSprite.js +++ b/js/_resizeSprite.js @@ -295,4 +295,102 @@ function toggleRatio(event) { */ function changedAlgorithm(event) { currentAlgo = event.target.value; -} \ No newline at end of file +} + +/** Resizes an imageData depending on the algorithm and on the new width and height + * + * @param {*} image The imageData to scale + * @param {*} width The new width of the imageData + * @param {*} height The new height of the imageData + * @param {*} algorithm Scaling algorithm chosen by the user in the dialogue + */ + function resizeImageData (image, width, height, algorithm) { + algorithm = algorithm || 'bilinear-interpolation' + + let resize; + switch (algorithm) { + case 'nearest-neighbor': resize = nearestNeighbor; break + case 'bilinear-interpolation': resize = bilinearInterpolation; break + default: return image; + } + + const result = new ImageData(width, height) + + resize(image, result) + + return result +} + + +/** Nearest neighbor algorithm to scale a sprite + * + * @param {*} src The source imageData + * @param {*} dst The destination imageData + */ + function nearestNeighbor (src, dst) { + let pos = 0 + + // Just applying the nearest neighbor algorithm + for (let y = 0; y < dst.height; y++) { + for (let x = 0; x < dst.width; x++) { + const srcX = Math.floor(x * src.width / dst.width) + const srcY = Math.floor(y * src.height / dst.height) + + let srcPos = ((srcY * src.width) + srcX) * 4 + + dst.data[pos++] = src.data[srcPos++] // R + dst.data[pos++] = src.data[srcPos++] // G + dst.data[pos++] = src.data[srcPos++] // B + dst.data[pos++] = src.data[srcPos++] // A + } + } +} + +/** Bilinear interpolation used to scale a sprite + * + * @param {*} src The source imageData + * @param {*} dst The destination imageData + */ +function bilinearInterpolation (src, dst) { + // Applying the bilinear interpolation algorithm + + function interpolate (k, kMin, kMax, vMin, vMax) { + return Math.round((k - kMin) * vMax + (kMax - k) * vMin) + } + + function interpolateHorizontal (offset, x, y, xMin, xMax) { + const vMin = src.data[((y * src.width + xMin) * 4) + offset] + if (xMin === xMax) return vMin + + const vMax = src.data[((y * src.width + xMax) * 4) + offset] + return interpolate(x, xMin, xMax, vMin, vMax) + } + + function interpolateVertical (offset, x, xMin, xMax, y, yMin, yMax) { + const vMin = interpolateHorizontal(offset, x, yMin, xMin, xMax) + if (yMin === yMax) return vMin + + const vMax = interpolateHorizontal(offset, x, yMax, xMin, xMax) + return interpolate(y, yMin, yMax, vMin, vMax) + } + + let pos = 0 + + for (let y = 0; y < dst.height; y++) { + for (let x = 0; x < dst.width; x++) { + const srcX = x * src.width / dst.width + const srcY = y * src.height / dst.height + + const xMin = Math.floor(srcX) + const yMin = Math.floor(srcY) + + const xMax = Math.min(Math.ceil(srcX), src.width - 1) + const yMax = Math.min(Math.ceil(srcY), src.height - 1) + + dst.data[pos++] = interpolateVertical(0, srcX, xMin, xMax, srcY, yMin, yMax) // R + dst.data[pos++] = interpolateVertical(1, srcX, xMin, xMax, srcY, yMin, yMax) // G + dst.data[pos++] = interpolateVertical(2, srcX, xMin, xMax, srcY, yMin, yMax) // B + dst.data[pos++] = interpolateVertical(3, srcX, xMin, xMax, srcY, yMin, yMax) // A + } + } +} diff --git a/js/_toolButtons.js b/js/_toolButtons.js index fc49004..dc51683 100644 --- a/js/_toolButtons.js +++ b/js/_toolButtons.js @@ -27,11 +27,6 @@ Events.on('click',"ellipse-smaller-button", function(e){ tool.ellipse.brushSize--; }, false); -//pan -Events.on('click',"pan-button", function(){ - tool.pan.switchTo(); -}, false); - //rectangular selection button Events.on('click', "rectselect-button", function(){ tool.rectselect.switchTo(); diff --git a/js/_tools.js b/js/_tools.js index 5d66c2e..ee5c687 100644 --- a/js/_tools.js +++ b/js/_tools.js @@ -104,7 +104,7 @@ class Tool { } decreaseSize() { - if (this.currSize > 0) { + if (this.currSize > 1) { this.currSize--; this.updateCursor(); } diff --git a/js/tools/PanTool.js b/js/tools/PanTool.js index e69de29..c4fb842 100644 --- a/js/tools/PanTool.js +++ b/js/tools/PanTool.js @@ -0,0 +1,39 @@ +class PanTool extends Tool { + + constructor(name, options, switchFunction) { + super(name, options); + + Events.on('click', this.mainButton, switchFunction, this); + } + + onStart(mousePos) { + super.onStart(mousePos); + canvasView.style.cursor = "url(\'/pixel-editor/pan-held.png\'), auto"; + } + + onDrag(mousePos) { + super.onDrag(mousePos); + + // Setting first layer position + layers[0].setCanvasOffset(layers[0].canvas.offsetLeft + (mousePos[0] - this.startMousePos[0]), layers[0].canvas.offsetTop + (mousePos[1] - this.startMousePos[1])); + // Copying that position to the other layers + for (let i=1; i