Merge branch 'next-update' into new-feature

This commit is contained in:
Nicola
2022-02-05 17:40:03 +01:00
committed by GitHub
65 changed files with 1745 additions and 579 deletions

View File

@@ -1,4 +1,4 @@
const featureToggles = (function featureTogglesModule() {
/*const featureToggles = (function featureTogglesModule() {
const ellipseToolLocalStorageKey = 'feature_ellipseTool';
@@ -30,4 +30,4 @@ const featureToggles = (function featureTogglesModule() {
}
})();
*/

View File

@@ -3,6 +3,7 @@ class File {
canvasSize = [];
zoom = 7;
canvasView = document.getElementById("canvas-view");
inited = false;
// Layers
layers = [];
@@ -36,11 +37,13 @@ class File {
// Start resize data
startData = {width: 0, height:0, widthPercentage: 100, heightPercentage: 100};
// Sprite scaling attributes
openResizeCanvasWindow() {
if (!this.inited) {
this.initResizeCanvasInputs();
this.inited = true;
}
// Initializes the inputs
this.initResizeCanvasInputs();
Dialogue.showDialogue('resize-canvas');
}
@@ -153,6 +156,9 @@ class File {
currFile.canvasSize[1] = parseInt(currFile.canvasSize[1]) +
this.rcBorders.top + this.rcBorders.bottom;
console.trace();
console.log(currFile.canvasSize);
// Resize the canvases
for (let i=0; i<currFile.layers.length; i++) {
currFile.layers[i].canvas.width = currFile.canvasSize[0];

View File

@@ -116,15 +116,22 @@ const Input = (() => {
case 77: case 109:
Events.emit("tool-shortcut", "rectselect");
break;
// TODO: [ELLIPSE] Decide on a shortcut to use. "s" was chosen without any in-team consultation.
// Lasso tool, q
case 81: case 113:
Events.emit("tool-shortcut", "lassoselect");
break;
// ellipse tool, s
case 83:
//Events.emit("tool-shortcut", "ellipse");
Events.emit("tool-shortcut", "ellipse");
break;
// rectangle tool, u
case 85:
Events.emit("tool-shortcut", "rectangle");
break;
// magic wand tool
case 87: case 119:
Events.emit("tool-shortcut", "magicwand");
break;
// Paste tool
case 86: case 118:
if (keyboardEvent.ctrlKey) {

73
js/InputComponents.js Normal file
View File

@@ -0,0 +1,73 @@
const InputComponents = (() => {
setInputEvents();
function setInputEvents() {
// Make the checkboxes toggleable
let checkboxes = document.getElementsByClassName("checkbox");
for (let i=0; i<checkboxes.length; i++) {
Events.on("click", checkboxes[i], toggleBox);
}
}
function toggleBox(event) {
if (event.target.classList.contains("checked"))
event.target.classList.remove("checked");
else
event.target.classList.add("checked");
}
function createNumber(id, label) {
let element = document.createElement("label");
let inputEl = document.createElement("input");
inputEl.id = id;
inputEl.type = "number";
element.innerHTML = label;
element.appendChild(inputEl);
return element;
}
function createCheckbox(id, label) {
let element = document.createElement("div");
let inner = document.createElement("div");
let hiddenInput = document.createElement("input");
let box = document.createElement("div");
let labelEl = document.createElement("label");
labelEl.innerHTML = label;
box.innerHTML = '\
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\
width="405.272px" height="405.272px" viewBox="0 0 405.272 405.272" style="enable-background:new 0 0 405.272 405.272;"\
xml:space="preserve">\
<g>\
<path d="M393.401,124.425L179.603,338.208c-15.832,15.835-41.514,15.835-57.361,0L11.878,227.836\
c-15.838-15.835-15.838-41.52,0-57.358c15.841-15.841,41.521-15.841,57.355-0.006l81.698,81.699L336.037,67.064\
c15.841-15.841,41.523-15.829,57.358,0C409.23,82.902,409.23,108.578,393.401,124.425z"/>\
</g>\
</svg>';
element.className = "checkbox-holder";
inner.className = "checkbox";
hiddenInput.type = "hidden";
box.className = "box";
box.id = id;
inner.appendChild(labelEl);
inner.appendChild(hiddenInput);
inner.appendChild(box);
element.appendChild(inner);
}
function updated() {
setInputEvents();
}
return {
updated,
createCheckbox,
createNumber
}
})();

View File

@@ -6,6 +6,7 @@ var tool = {};
class Tool {
name = "AbstractTool";
isSelected = false;
switchFunction = undefined;
// Cursor and brush size
cursorType = {};
@@ -20,17 +21,70 @@ class Tool {
// HTML elements
mainButton = undefined;
biggerButton = undefined;
smallerButton = undefined;
brushPreview = document.getElementById("brush-preview");
// Tool tutorial
toolTutorial = document.getElementById("tool-tutorial");
tutorialTimer = undefined;
tutorialString = "";
constructor (name, options) {
this.name = name;
this.cursorType = options;
this.mainButton = document.getElementById(name + "-button");
this.biggerButton = document.getElementById(name + "-bigger-button");
this.smallerButton = document.getElementById(name + "-smaller-button");
if (this.mainButton != undefined) {
// Timer to show the tutorial
Events.on("mouseenter", this.mainButton, function(){
this.setTutorial();
this.tutorialTimer = setTimeout(this.showTutorial.bind(this), 750)
}.bind(this));
// Clear the callback if the user cancels the hovering
Events.on("mouseleave", this.mainButton, function() {
if (this.tutorialTimer != undefined)
clearTimeout(this.tutorialTimer);
this.tutorialTimer = undefined;
this.hideTutorial();
}.bind(this))
this.hideTutorial();
}
}
showTutorial() {
let tutorialRect = this.toolTutorial.getBoundingClientRect();
if ((this.mainButton.getBoundingClientRect().top - 48 + (tutorialRect.bottom - tutorialRect.top)) > window.innerHeight) {
this.toolTutorial.style.top = window.innerHeight - 48 - (tutorialRect.bottom - tutorialRect.top) + "px";
}
else {
this.toolTutorial.style.top = this.mainButton.getBoundingClientRect().top - 48 + "px";
}
this.toolTutorial.className = "fade-in";
}
hideTutorial() {
this.toolTutorial.className = "fade-out";
}
resetTutorial() {
this.tutorialString = "";
}
setTutorial() {
this.toolTutorial.innerHTML = this.tutorialString;
}
addTutorialKey(key, text) {
this.tutorialString += '<li><span class="keyboard-key">' + key + '</span> ' + text + '</li>';
}
addTutorialText(key, text) {
this.tutorialString += '<li>' + key + ': ' + text + '</li>';
}
addTutorialImg(imgPath) {
this.tutorialString += '</ul><img src="' + imgPath + '"/>';
}
addTutorialTitle(text) {
this.tutorialString += "<h3>" + text + "</h3><ul>";
}
onSelect() {
@@ -38,6 +92,7 @@ class Tool {
this.mainButton.parentElement.classList.add("selected");
this.isSelected = true;
// Update the cursor
switch (this.cursorType.type) {
case 'html':
currFile.canvasView.style.cursor = 'none';
@@ -49,6 +104,9 @@ class Tool {
default:
break;
}
// Reset the topbar
TopMenuModule.resetInfos();
}
updateCursor() {
@@ -95,32 +153,14 @@ class Tool {
currFile.canvasView.style.cursor = 'default';
}
onStart(mousePos) {
onStart(mousePos, mouseTarget) {
this.startMousePos = mousePos;
}
onDrag(mousePos) {
onDrag(mousePos, mouseTarget) {
}
onEnd(mousePos) {
onEnd(mousePos, mouseTarget) {
this.endMousePos = mousePos;
}
increaseSize() {
if (this.currSize < 128) {
this.currSize++;
this.updateCursor();
}
}
decreaseSize() {
if (this.currSize > 1) {
this.currSize--;
this.updateCursor();
}
}
get size() {
return this.currSize;
}
}

View File

@@ -6,9 +6,10 @@ const ToolManager = (() => {
tools["eraser"] = new EraserTool("eraser", {type: 'html'}, switchTool);
tools["rectangle"] = new RectangleTool("rectangle", {type: 'html'}, switchTool);
tools["line"] = new LineTool("line", {type: 'html'}, switchTool);
tools["ellipse"] = new EllipseTool("ellipse", {type: 'cursor', style: 'crosshair'}, switchTool);
tools["fill"] = new FillTool("fill", {type: 'cursor', style: 'crosshair'}, switchTool);
tools["eyedropper"] = new EyedropperTool("eyedropper", {type: 'cursor', style: 'crosshair'}, switchTool);
tools["eyedropper"] = new EyeDropperTool("eyedropper", {type: 'cursor', style: 'crosshair'}, switchTool);
tools["pan"] = new PanTool("pan", {type: 'custom'}, switchTool);
tools["zoom"] = new ZoomTool("zoom", {type:'custom'});
@@ -16,6 +17,10 @@ const ToolManager = (() => {
{type:'cursor', style:'crosshair'}, switchTool, tools["brush"]);
tools["rectselect"] = new RectangularSelectionTool("rectselect",
{type: 'cursor', style:'crosshair'}, switchTool, tools["moveselection"]);
tools["lassoselect"] = new LassoSelectionTool("lassoselect",
{type: 'cursor', style:'crosshair'}, switchTool, tools["moveselection"]);
tools["magicwand"] = new MagicWandTool("magicwand",
{type: 'cursor', style:'crosshair'}, switchTool, tools["moveselection"]);
currTool = tools["brush"];
currTool.onSelect();
@@ -26,6 +31,9 @@ const ToolManager = (() => {
Events.on("mousedown", window, onMouseDown);
Events.on("wheel", window, onMouseWheel);
// Assign a selection tool to the move tool
tools["moveselection"].selectionTool = tools["lassoselect"];
// Bind tool shortcuts
Events.onCustom("tool-shortcut", onShortcut);
@@ -36,7 +44,6 @@ const ToolManager = (() => {
}
function onMouseWheel(mouseEvent) {
console.log("MOUSE WHEEL");
if (!EditorState.documentCreated || Dialogue.isOpen())
return;
@@ -109,7 +116,7 @@ const ToolManager = (() => {
}
function onMouseUp(mouseEvent) {
if (!EditorState.documentCreated())
if (!EditorState.documentCreated || Dialogue.isOpen())
return;
let mousePos = Input.getCursorPosition(mouseEvent);
@@ -123,11 +130,11 @@ const ToolManager = (() => {
tools["eyedropper"].onEnd(mousePos, mouseEvent.target);
}
else if (!currFile.currentLayer.isLocked || !((Object.getPrototypeOf(currTool) instanceof DrawingTool))) {
currTool.onEnd(mousePos);
currTool.onEnd(mousePos, mouseEvent.target);
}
break;
case 2:
tools["pan"].onEnd(mousePos);
tools["pan"].onEnd(mousePos, mouseEvent.target);
break;
case 3:
currTool.onRightEnd(mousePos, mouseEvent.target);
@@ -142,10 +149,13 @@ const ToolManager = (() => {
return currTool;
}
function switchTool(newTool) {
function switchTool(newTool, event) {
currTool.onDeselect();
currTool = newTool;
currTool.onSelect();
if (event != undefined)
event.stopPropagation();
}
return {

View File

@@ -1,12 +1,15 @@
const TopMenuModule = (() => {
const mainMenuItems = document.getElementById('main-menu').children;
let infoList = document.getElementById('editor-info');
let infoElements = {};
initMenu();
function initMenu() {
//for each button in main menu (starting at 1 to avoid logo)
for (let i = 1; i < mainMenuItems.length; i++) {
// for each button in main menu (starting at 1 to avoid logo), ending at length-1 to avoid
// editor info
for (let i = 1; i < mainMenuItems.length-1; i++) {
//get the button that's in the list item
const menuItem = mainMenuItems[i];
@@ -18,6 +21,7 @@ const TopMenuModule = (() => {
closeMenu();
// Select the item
Util.select(e.target.parentElement);
e.stopPropagation();
});
const subMenu = menuItem.children[1];
@@ -53,18 +57,29 @@ const TopMenuModule = (() => {
e.preventDefault();
}
break;
// REFACTOR: move the binding to the Selection IIFE or something like that once it's done
case 'Paste':
Events.on('click', currSubmenuButton, function(){Events.emit("ctrl+v");});
Events.on('click', currSubmenuButton, function(e){
Events.emit("ctrl+v");
e.stopPropagation();
});
break;
case 'Copy':
Events.on('click', currSubmenuButton, function(){Events.emit("ctrl+c");});
Events.on('click', currSubmenuButton, function(e){
Events.emit("ctrl+c");
e.stopPropagation();
});
break;
case 'Cut':
Events.on('click', currSubmenuButton, function(){Events.emit("ctrl+x");});
Events.on('click', currSubmenuButton, function(e){
Events.emit("ctrl+x");
e.stopPropagation();
});
break;
case 'Cancel':
Events.on('click', currSubmenuButton, function(){Events.emit("esc-pressed")});
Events.on('click', currSubmenuButton, function(e){
Events.emit("esc-pressed");
e.stopPropagation();
});
break;
//Help Menu
case 'Settings':
@@ -93,7 +108,27 @@ const TopMenuModule = (() => {
}
}
function resetInfos() {
infoList.innerHTML = "<ul></ul>";
}
function updateField(fieldId, value) {
document.getElementById(fieldId).value = value;
}
function addInfoElement(fieldId, field) {
let liEl = document.createElement("li");
infoElements[fieldId] = field;
liEl.appendChild(field);
infoList.firstChild.appendChild(liEl);
}
return {
closeMenu
closeMenu,
updateField,
addInfoElement,
resetInfos
}
})();

View File

@@ -1,6 +1,37 @@
// Acts as a public static class
class Util {
/** Pastes the source image data on the destination image data, keeping the pixels where the
* source image data is transparent
*
* @param {*} source
* @param {*} destination
*/
static pasteData(underlyingImageData, pasteData, finalContext) {
for (let i=0; i<underlyingImageData.data.length; i+=4) {
let currentMovePixel = [
pasteData.data[i], pasteData.data[i+1], pasteData.data[i+2], pasteData.data[i+3]
];
let currentUnderlyingPixel = [
underlyingImageData.data[i], underlyingImageData.data[i+1],
underlyingImageData.data[i+2], underlyingImageData.data[i+3]
];
// If the pixel of the clipboard is empty, but the one below it isn't, I use the pixel below
if (Util.isPixelEmpty(currentMovePixel)) {
if (!Util.isPixelEmpty(currentUnderlyingPixel)) {
pasteData.data[i] = currentUnderlyingPixel[0];
pasteData.data[i+1] = currentUnderlyingPixel[1];
pasteData.data[i+2] = currentUnderlyingPixel[2];
pasteData.data[i+3] = currentUnderlyingPixel[3];
}
}
}
finalContext.putImageData(pasteData, 0, 0);
}
/** Tells if a pixel is empty (has alpha = 0)
*
* @param {*} pixel
@@ -86,4 +117,17 @@ class Util {
static toggle(elementId) {
Util.getElement(elementId).classList.toggle('selected');
}
static getPixelColor(data, x, y, dataWidth) {
let pos = (y * dataWidth + x) * 4;
return [data[pos], data[pos+1], data[pos+2], data[pos + 3]];
}
static isPixelTransparent(data, x, y, dataWidth) {
return this.getPixelColor(data, x, y, dataWidth)[3] == 255;
}
static cursorInCanvas(canvasSize, mousePos) {
return mousePos[0] >= 0 && mousePos[1] >= 0 && canvasSize[0] > mousePos[0] && canvasSize[1] > mousePos[1];
}
}

View File

@@ -283,7 +283,7 @@ class Layer {
previewWidth, previewHeight);
}
drawLine(x0,y0,x1,y1, brushSize) {
drawLine(x0,y0,x1,y1, brushSize, clear=false) {
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1 ? 1 : -1);
@@ -293,12 +293,13 @@ class Layer {
while (true) {
//set pixel
// If the current tool is the brush
if (ToolManager.currentTool().name == 'brush' || ToolManager.currentTool().name == 'rectangle' || ToolManager.currentTool().name == 'ellipse') {
// REFACTOR: this is terrible
if (!clear) {
// I fill the rect
currFile.currentLayer.context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize);
} else if (ToolManager.currentTool().name == 'eraser') {
this.context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize);
} else {
// In case I'm using the eraser I must clear the rect
currFile.currentLayer.context.clearRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize);
this.context.clearRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize);
}
//if we've reached the end goal, exit the loop

View File

@@ -82,8 +82,6 @@ class PixelGrid extends Layer {
this.context.strokeStyle = Settings.getCurrSettings().pixelGridColour;
console.log("Line ditance: " + this.lineDistance)
// OPTIMIZABLE, could probably be a bit more elegant
// Draw horizontal lines
for (let i=0; i<this.canvas.width / Math.round(this.lineDistance); i++) {

View File

@@ -9,11 +9,16 @@
/** UTILITY AND INPUT **/
//=include Util.js
//=include Events.js
//=include InputComponents.js
//=include Dialogue.js
//=include History.js
//=include Settings.js
//=include EditorState.js
/** MENUS **/
//=include FileManager.js
//=include TopMenuModule.js
/** COLOR-RELATED **/
//=include Color.js
//=include ColorPicker.js
@@ -39,12 +44,15 @@
//=include tools/EraserTool.js
//=include tools/LineTool.js
//=include tools/RectangleTool.js
//=include tools/EllipseTool.js
//=include tools/FillTool.js
//=include tools/EyedropperTool.js
//=include tools/EyeDropperTool.js
//=include tools/PanTool.js
//=include tools/ZoomTool.js
//=include tools/RectangularSelectionTool.js
//=include tools/MoveSelectionTool.js
//=include tools/RectangularSelectionTool.js
//=include tools/LassoSelectionTool.js
//=include tools/MagicWandTool.js
/** MODULES AND MENUS **/
//=include SplashPage.js
@@ -55,8 +63,6 @@
/** STARTUP AND FILE MANAGEMENT **/
//=include Startup.js
//=include FileManager.js
//=include TopMenuModule.js
/** HTML INPUT EVENTS **/
//=include Input.js
@@ -69,9 +75,10 @@ PresetModule.instrumentPresetMenu();
//when the page is done loading, you can get ready to start
window.onload = function () {
featureToggles.onLoad();
// First cursor update
ToolManager.currentTool().updateCursor();
// Apply checkboxes
//check if there are any url parameters
if (window.location.pathname.replace('/pixel-editor/','').length <= 1) {

View File

@@ -5,11 +5,34 @@ class BrushTool extends ResizableTool {
Events.on('click', this.mainButton, switchFunction, this);
Events.on('click', this.biggerButton, this.increaseSize.bind(this));
Events.on('click', this.smallerButton, this.decreaseSize.bind(this));
this.resetTutorial();
this.addTutorialTitle("Pencil tool");
this.addTutorialKey("B", " to select the brush");
this.addTutorialKey("Left drag", " to draw a stroke");
this.addTutorialKey("Right drag", " to resize the brush");
this.addTutorialKey("+ or -", " to resize the brush");
this.addTutorialImg("brush-tutorial.gif");
}
onStart(mousePos) {
onStart(mousePos, cursorTarget) {
super.onStart(mousePos);
if (cursorTarget === undefined)
return;
new HistoryState().EditCanvas();
//draw line to current pixel
if (cursorTarget.className == 'drawingCanvas' || cursorTarget.className == 'drawingCanvas') {
currFile.currentLayer.drawLine(
Math.floor(mousePos[0]/currFile.zoom),
Math.floor(mousePos[1]/currFile.zoom),
Math.floor(mousePos[0]/currFile.zoom),
Math.floor(mousePos[1]/currFile.zoom),
this.currSize
);
}
currFile.currentLayer.updateLayerPreview();
}
onDrag(mousePos, cursorTarget) {
@@ -23,7 +46,7 @@ class BrushTool extends ResizableTool {
Math.floor(this.prevMousePos[0]/currFile.zoom),
Math.floor(this.prevMousePos[1]/currFile.zoom),
Math.floor(this.currMousePos[0]/currFile.zoom),
Math.floor(this.currMousePos[1]/currFile.zoom),
Math.floor(this.currMousePos[1]/currFile.zoom),
this.currSize
);
@@ -84,7 +107,7 @@ class BrushTool extends ResizableTool {
this.mirrorDraw(mirrorPrevX, mirrorPrevY, mirrorCurrentX, mirrorCurrentY, true, true);
}
}
currFile.currentLayer.updateLayerPreview();
}

188
js/tools/EllipseTool.js Normal file
View File

@@ -0,0 +1,188 @@
class EllipseTool extends ResizableTool {
// Saving the empty rect svg
emptyEllipseSVG = document.getElementById("ellipse-empty-button-svg");
// and the full rect svg so that I can change them when the user changes rect modes
fullEllipseSVG = document.getElementById("ellipse-full-button-svg");
// Current fill mode
currFillMode = 'empty';
filledPixels = {};
switchFunction = null;
constructor(name, options, switchFunction) {
super(name, options);
this.switchFunction = switchFunction;
Events.on('click', this.mainButton, this.changeFillType.bind(this));
Events.on('click', this.biggerButton, this.increaseSize.bind(this));
Events.on('click', this.smallerButton, this.decreaseSize.bind(this));
this.resetTutorial();
this.addTutorialTitle("Ellipse tool");
this.addTutorialKey("S", " to select the ellipse");
this.addTutorialKey("S while selected", " to change fill mode (empty or fill)");
this.addTutorialKey("Left drag", " to draw an ellipse");
this.addTutorialKey("Right drag", " to resize the brush");
this.addTutorialKey("+ or -", " to resize the brush");
this.addTutorialImg("ellipse-tutorial.gif");
}
changeFillType() {
if (this.isSelected)
if (this.currFillMode == 'empty') {
this.currFillMode = 'fill';
this.emptyEllipseSVG.setAttribute('display', 'none');
this.fullEllipseSVG.setAttribute('display', 'visible');
}
else {
this.currFillMode = 'empty'
this.emptyEllipseSVG.setAttribute('display', 'visible');
this.fullEllipseSVG.setAttribute('display', 'none');
}
else
this.switchFunction(this);
}
onStart(mousePos, mouseTarget) {
super.onStart(mousePos);
if (mouseTarget.className != "drawingCanvas")
return;
// Putting the tmp layer on top of everything
currFile.TMPLayer.canvas.style.zIndex = parseInt(currFile.currentLayer.canvas.style.zIndex, 10) + 1;
this.startMousePos[0] = Math.floor(mousePos[0] / currFile.zoom) + 0.5;
this.startMousePos[1] = Math.floor(mousePos[1] / currFile.zoom) + 0.5;
new HistoryState().EditCanvas();
}
onDrag(mousePos) {
// Drawing the rect at the right position
this.drawEllipse(Math.floor(mousePos[0] / currFile.zoom) + 0.5, Math.floor(mousePos[1] / currFile.zoom) + 0.5,
currFile.TMPLayer.context);
}
/** Finishes drawing the rect, decides the end coordinates and moves the preview rectangle to the
* current layer
*
* @param {*} mousePos The position of the mouse when the user stopped dragging
*/
onEnd(mousePos) {
super.onEnd(mousePos);
if (this.startMousePos == undefined)
return;
let tmpContext = currFile.TMPLayer.context;
this.endMousePos[0] = Math.floor(mousePos[0] / currFile.zoom) + 0.5;
this.endMousePos[1] = Math.floor(mousePos[1] / currFile.zoom) + 0.5;
// If I have to fill it, I do so
if (this.currFillMode == 'fill') {
// Use the fill tool on the tmp canvas
FillTool.fill([this.startMousePos[0] * currFile.zoom, this.startMousePos[1] * currFile.zoom],
currFile.TMPLayer.context);
}
Util.pasteData(currFile.currentLayer.context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]),
currFile.TMPLayer.context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]),
currFile.currentLayer.context);
// Update the layer preview
currFile.currentLayer.updateLayerPreview();
// Clearing the tmp canvas
tmpContext.clearRect(0, 0, currFile.TMPLayer.canvas.width, currFile.TMPLayer.canvas.height);
this.startMousePos = undefined;
}
onSelect() {
super.onSelect();
}
onDeselect() {
super.onDeselect();
}
/** Draws an ellipse with end coordinates given by x and y on the tmp layer (draws
* the preview for the ellipse tool)
*
* @param {*} x The current end x of the ellipse
* @param {*} y The current end y of the ellipse
*/
drawEllipse(x, y, context) {
// Width and height of the ellipse
let width = undefined;
let height = undefined;
// Clearing the tmp canvas
currFile.TMPLayer.context.clearRect(0, 0, currFile.TMPLayer.canvas.width, currFile.TMPLayer.canvas.height);
// Compute width and height
width = Math.abs(x - this.startMousePos[0]);
height = Math.abs(y - this.startMousePos[1]);
// Drawing the ellipse
this.previewEllipse(context, this.startMousePos[0], this.startMousePos[1], width, height);
}
previewEllipse(context, xc, yc, a, b) {
let x, y1, y2;
let toFill = {};
let removed = {};
x = xc - a;
while (x < (xc + a)) {
let root = Math.sqrt((1 - (((x - xc)*(x - xc)) / (a*a))) * b*b);
let flooredX = Math.floor(x);
let flooredY1, flooredY2;
y1 = root + yc;
y2 = -root + yc;
flooredY1 = Math.floor(y1);
flooredY2 = Math.floor(y2);
toFill[[flooredX, flooredY1]] = true;
toFill[[flooredX, flooredY2]] = true;
x += 0.005;
}
for (const coord in toFill) {
let arrayCoord = JSON.parse("[" + coord + "]");
if (arrayCoord[0]-xc < 0 || arrayCoord[1]-yc < 0) {
continue;
}
if (!(
// Top and left
(toFill[[arrayCoord[0], arrayCoord[1] - 1]] && toFill[[arrayCoord[0] - 1, arrayCoord[1]]] &&
!removed[[arrayCoord[0], arrayCoord[1] - 1]] && !removed[arrayCoord[0] - 1, arrayCoord[1]]) ||
// Top and right
(toFill[[arrayCoord[0], arrayCoord[1] - 1]] && toFill[[arrayCoord[0] + 1, arrayCoord[1]]] &&
!removed[[arrayCoord[0], arrayCoord[1] - 1]] && !removed[arrayCoord[0] + 1, arrayCoord[1]]) ||
// Bottom and left
(toFill[[arrayCoord[0], arrayCoord[1] + 1]] && toFill[[arrayCoord[0] - 1, arrayCoord[1]]] &&
!removed[[arrayCoord[0], arrayCoord[1] + 1]] && !removed[arrayCoord[0] - 1, arrayCoord[1]]) ||
// Bottom and right
(toFill[[arrayCoord[0], arrayCoord[1] + 1]] && toFill[[arrayCoord[0] + 1, arrayCoord[1]]] &&
!removed[[arrayCoord[0], arrayCoord[1] + 1]] && !removed[arrayCoord[0] + 1, arrayCoord[1]])) ||
removed[arrayCoord]) {
context.fillRect(arrayCoord[0], arrayCoord[1], this.currSize, this.currSize);
context.fillRect(xc - Math.abs(xc - arrayCoord[0]), arrayCoord[1], this.currSize, this.currSize);
context.fillRect(arrayCoord[0], yc - Math.abs(yc - arrayCoord[1]), this.currSize, this.currSize);
context.fillRect(xc - Math.abs(xc - arrayCoord[0]), yc - Math.abs(yc - arrayCoord[1]), this.currSize, this.currSize);
}
removed[arrayCoord] = true;
}
}
}

View File

@@ -5,6 +5,14 @@ class EraserTool extends ResizableTool {
Events.on('click', this.mainButton, switchFunction, this);
Events.on('click', this.biggerButton, this.increaseSize.bind(this));
Events.on('click', this.smallerButton, this.decreaseSize.bind(this));
this.resetTutorial();
this.addTutorialTitle("Eraser tool");
this.addTutorialKey("E", " to select the eraser");
this.addTutorialKey("Left drag", " to erase an area");
this.addTutorialKey("Right drag", " to resize the eraser");
this.addTutorialKey("+ or -", " to resize the eraser");
this.addTutorialImg("eraser-tutorial.gif");
}
onStart(mousePos) {
@@ -23,7 +31,7 @@ class EraserTool extends ResizableTool {
Math.floor(this.prevMousePos[1]/currFile.zoom),
Math.floor(this.currMousePos[0]/currFile.zoom),
Math.floor(this.currMousePos[1]/currFile.zoom),
this.currSize
this.currSize, true
);
}

View File

@@ -1,4 +1,4 @@
class EyedropperTool extends Tool {
class EyeDropperTool extends Tool {
eyedropperPreview = document.getElementById("eyedropper-preview");
selectedColor = {r:0, g:0, b:0};
@@ -6,6 +6,15 @@ class EyedropperTool extends Tool {
super(name, options);
Events.on('click', this.mainButton, switchFunction, this);
this.resetTutorial();
this.addTutorialTitle("Eyedropper tool");
this.addTutorialKey("E", " to select the lasso selection tool");
this.addTutorialKey("Left drag", " to preview the picked colour");
this.addTutorialKey("Aòt + left drag", " to preview the picked colour");
this.addTutorialKey("Left click", " to select a colour");
this.addTutorialKey("Alt + click", " to select a colour");
this.addTutorialImg("eyedropper-tutorial.gif");
}
onStart(mousePos, target) {

View File

@@ -3,6 +3,12 @@ class FillTool extends DrawingTool {
super(name, options);
Events.on('click', this.mainButton, switchFunction, this);
this.resetTutorial();
this.addTutorialTitle("Fill tool");
this.addTutorialKey("F", " to select the fill tool");
this.addTutorialKey("Left click", " to fill a contiguous area");
this.addTutorialImg("fill-tutorial.gif");
}
onStart(mousePos, target) {
@@ -13,8 +19,9 @@ class FillTool extends DrawingTool {
if (target.className != 'drawingCanvas')
return;
this.fill(mousePos);
new HistoryState().EditCanvas();
FillTool.fill(mousePos);
let midX = (currFile.canvasSize[0] / 2);
let midY = (currFile.canvasSize[1] / 2);
@@ -28,7 +35,7 @@ class FillTool extends DrawingTool {
mirrorY = Math.floor(midY - Math.abs(midY - y0));
}
let symmetryPos = [mousePos[0], mirrorY * currFile.zoom];
this.fill(symmetryPos);
FillTool.fill(symmetryPos);
}
if (currFile.vSymmetricLayer.isEnabled) {
@@ -38,21 +45,19 @@ class FillTool extends DrawingTool {
mirrorX = Math.floor(midX - Math.abs(midX - x0));
}
let symmetryPos = [mirrorX * currFile.zoom, mousePos[1]];
this.fill(symmetryPos);
FillTool.fill(symmetryPos);
}
if (currFile.hSymmetricLayer.isEnabled && currFile.vSymmetricLayer.isEnabled) {
let symmetryPos = [mirrorX * currFile.zoom, mirrorY * currFile.zoom];
this.fill(symmetryPos);
FillTool.fill(symmetryPos);
}
currFile.currentLayer.updateLayerPreview();
new HistoryState().EditCanvas();
}
fill(cursorLocation) {
static fill(cursorLocation, context) {
//changes a pixels color
function colorPixel(tempImage, pixelPos, fillColor) {
//console.log('colorPixel:',pixelPos);
@@ -74,8 +79,11 @@ class FillTool extends DrawingTool {
return (r == color[0] && g == color[1] && b == color[2] && a == color[3]);
}
if (context == undefined)
context = currFile.currentLayer.context;
//temporary image holds the data while we change it
let tempImage = currFile.currentLayer.context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
let tempImage = context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
//this is an array that holds all of the pixels at the top of the cluster
let topmostPixelsArray = [[Math.floor(cursorLocation[0]/currFile.zoom), Math.floor(cursorLocation[1]/currFile.zoom)]];
@@ -88,7 +96,7 @@ class FillTool extends DrawingTool {
let clusterColor = [tempImage.data[startingPosition],tempImage.data[startingPosition+1],tempImage.data[startingPosition+2], tempImage.data[startingPosition+3]];
//the color to fill with
let fillColor = Color.hexToRgb(currFile.currentLayer.context.fillStyle);
let fillColor = Color.hexToRgb(context.fillStyle);
//if you try to fill with the same color that's already there, exit the function
if (clusterColor[0] == fillColor.r &&
@@ -153,7 +161,7 @@ class FillTool extends DrawingTool {
pixelPos += currFile.canvasSize[0] * 4;
}
}
currFile.currentLayer.context.putImageData(tempImage, 0, 0);
context.putImageData(tempImage, 0, 0);
}
onDrag(mousePos, cursorTarget) {

View File

@@ -0,0 +1,95 @@
class LassoSelectionTool extends SelectionTool {
currentPixels = [];
constructor (name, options, switchFunc, moveTool) {
super(name, options, switchFunc, moveTool);
Events.on('click', this.mainButton, switchFunc, this);
this.resetTutorial();
this.addTutorialTitle("Lasso selection tool");
this.addTutorialKey("Q", " to select the lasso selection tool");
this.addTutorialKey("Left drag", " to select an area");
this.addTutorialKey("Left drag", " to move a selection");
this.addTutorialKey("Esc", " to cancel a selection")
this.addTutorialKey("Click", " outside the selection to cancel it")
this.addTutorialKey("CTRL+C", " to copy a selection")
this.addTutorialKey("CTRL+V", " to paste a selection")
this.addTutorialKey("CTRL+X", " to cut a selection")
this.addTutorialImg("lassoselect-tutorial.gif");
}
onStart(mousePos, mouseTarget) {
super.onStart(mousePos, mouseTarget);
if (Util.isChildOfByClass(mouseTarget, "editor-top-menu") ||
!Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom]))
return;
this.currentPixels = [];
this.drawSelection();
this.currentPixels.push([mousePos[0] / currFile.zoom, mousePos[1] / currFile.zoom]);
}
onDrag(mousePos, mouseTarget) {
super.onDrag(mousePos, mouseTarget);
if (!Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom]))
return;
if (this.currentPixels[this.currentPixels.length - 1] != mousePos)
this.currentPixels.push([mousePos[0] / currFile.zoom, mousePos[1] / currFile.zoom]);
this.drawSelection();
}
onEnd(mousePos, mouseTarget) {
super.onEnd(mousePos, mouseTarget);
new HistoryState().EditCanvas();
if (Util.isChildOfByClass(mouseTarget, "editor-top-menu"))
return;
this.currentPixels.push([this.startMousePos[0] / currFile.zoom, this.startMousePos[1] / currFile.zoom]);
// Include extreme borders
this.boundingBox.maxX++;
this.boundingBox.maxY++;
// Switch to the move tool so that the user can move the selection
this.switchFunc(this.moveTool);
this.moveTool.setSelectionData(this.getSelection(), this);
}
onSelect() {
super.onSelect();
}
onDeselect() {
super.onDeselect();
}
drawSelection() {
if (this.currentPixels.length <= 1)
return;
let point = [];
let prevPoint = [];
currFile.VFXLayer.context.clearRect(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
currFile.VFXLayer.context.fillStyle = 'rgba(0,0,0,1)';
for (var index = 0; index < this.currentPixels.length; index ++) {
point = this.currentPixels[index];
if (index == 0)
currFile.VFXLayer.context.moveTo(point[0], point[1]);
else {
prevPoint = this.currentPixels[index- 1];
currFile.VFXLayer.drawLine(Math.floor(prevPoint[0]), Math.floor(prevPoint[1]),
Math.floor(point[0]), Math.floor(point[1]), 1);
}
}
currFile.VFXLayer.drawLine(Math.floor(point[0]), Math.floor(point[1]),
Math.floor(this.currentPixels[0][0]), Math.floor(this.currentPixels[0][1]), 1);
}
}

View File

@@ -5,6 +5,14 @@ class LineTool extends ResizableTool {
Events.on('click', this.mainButton, switchFunction, this);
Events.on('click', this.biggerButton, this.increaseSize.bind(this));
Events.on('click', this.smallerButton, this.decreaseSize.bind(this));
this.resetTutorial();
this.addTutorialTitle("Line tool");
this.addTutorialKey("L", " to select the line");
this.addTutorialKey("Left drag", " to draw a line");
this.addTutorialKey("Right drag", " to resize the brush");
this.addTutorialKey("+ or -", " to resize the brush");
this.addTutorialImg("line-tutorial.gif");
}
onStart(mousePos) {

116
js/tools/MagicWandTool.js Normal file
View File

@@ -0,0 +1,116 @@
class MagicWandTool extends SelectionTool {
constructor (name, options, switchFunc, moveTool) {
super(name, options, switchFunc, moveTool);
Events.on('click', this.mainButton, switchFunc, this);
this.resetTutorial();
this.addTutorialTitle("Magic wand tool");
this.addTutorialKey("W", " to select the magic wand tool");
this.addTutorialKey("Left click", " to select a contiguous area");
this.addTutorialKey("Esc", " to cancel a selection");
this.addTutorialKey("Click", " outside the selection to cancel it");
this.addTutorialKey("CTRL+C", " to copy a selection");
this.addTutorialKey("CTRL+V", " to paste a selection");
this.addTutorialKey("CTRL+X", " to cut a selection");
this.addTutorialImg("magicwand-tutorial.gif");
}
onEnd(mousePos, mouseTarget) {
super.onStart(mousePos, mouseTarget);
if (Util.isChildOfByClass(mouseTarget, "editor-top-menu") ||
!Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom]))
return;
this.switchFunc(this.moveTool);
this.moveTool.setSelectionData(this.getSelection(), this);
}
getSelection() {
let coords = [Math.floor(this.endMousePos[0]), Math.floor(this.endMousePos[1])];
let data = currFile.currentLayer.context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]).data;
let index = (coords[1] * currFile.canvasSize[0] + coords[0]) * 4;
let color = [data[index], data[index+1], data[index+2], data[index+3]];
let selectedData = new ImageData(currFile.canvasSize[0], currFile.canvasSize[1]);
this.visit([Math.floor(this.endMousePos[0]), Math.floor(this.endMousePos[1])],
this.currSelection, data, color);
for (const pixel in this.currSelection) {
let coords = [parseInt(pixel.split(",")[0]), parseInt(pixel.split(",")[1])];
let index = (currFile.canvasSize[0] * coords[1] + coords[0]) * 4;
selectedData[index] = color[0];
selectedData[index+1] = color[1];
selectedData[index+2] = color[2];
selectedData[index+3] = color[3];
this.updateBoundingBox(coords[0], coords[1]);
}
this.outlineData = new ImageData(currFile.canvasSize[0], currFile.canvasSize[1]);
this.previewData = selectedData;
this.drawSelectedArea();
this.boundingBoxCenter = [this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2,
this.boundingBox.minY + (this.boundingBox.maxY - this.boundingBox.minY) / 2];
// Cut the selection
this.cutSelection();
// Put it on the TMP layer
currFile.TMPLayer.context.putImageData(this.previewData, 0, 0);
// Draw the bounding box
this.drawBoundingBox();
return selectedData;
}
visit(pixel, selected, data, color) {
let toVisit = [pixel];
let visited = [];
while (toVisit.length > 0) {
pixel = toVisit.pop();
visited[pixel] = true;
let col = Util.getPixelColor(data, pixel[0], pixel[1], currFile.canvasSize[0]);
if (col[0] == color[0] && col[1] == color[1] && col[2] == color[2] && col[3] == color[3])
selected[pixel] = true;
else
continue;
let top, bottom, left, right;
if (pixel[1] > 0)
top = [pixel[0], pixel[1] - 1];
else
top = undefined;
if (pixel[0] > 0)
left = [pixel[0] - 1, pixel[1]];
else
left = undefined;
if (pixel[1] < currFile.canvasSize[1])
bottom = [pixel[0], pixel[1] + 1];
else
bottom = undefined;
if (pixel[0] < currFile.canvasSize[0])
right = [pixel[0] + 1, pixel[1]];
else
right = undefined;
if (right != undefined && visited[right] == undefined)
toVisit.push(right);
if (left != undefined && visited[left] == undefined)
toVisit.push(left);
if (top != undefined && visited[top] == undefined)
toVisit.push(top);
if (bottom != undefined && visited[bottom] == undefined)
toVisit.push(bottom);
}
return selected;
}
}

View File

@@ -14,17 +14,20 @@ class MoveSelectionTool extends DrawingTool {
Events.onCustom("esc-pressed", this.endSelection.bind(this));
Events.onCustom("ctrl+c", this.copySelection.bind(this));
Events.onCustom("ctrl+x", this.cutSelection.bind(this));
Events.onCustom("ctrl+c", this.copySelection.bind(this), true);
Events.onCustom("ctrl+x", this.cutSelection.bind(this), true);
Events.onCustom("ctrl+v", this.pasteSelection.bind(this));
}
copySelection() {
copySelection(event) {
this.lastCopiedSelection = this.currSelection;
this.cutting = false;
if (event)
this.switchFunc(this.selectionTool);
}
cutSelection() {
cutSelection(event) {
if (currFile.currentLayer.isLocked)
return;
this.cutting = true;
@@ -32,8 +35,12 @@ class MoveSelectionTool extends DrawingTool {
this.endSelection();
this.currSelection = this.lastCopiedSelection;
// Cut the data
currFile.currentLayer.context.clearRect(this.currSelection.left-0.5, this.currSelection.top-0.5,
this.currSelection.width, this.currSelection.height);
this.selectionTool.cutSelection();
if (event)
this.switchFunc(this.selectionTool);
new HistoryState().EditCanvas();
}
pasteSelection() {
@@ -41,6 +48,10 @@ class MoveSelectionTool extends DrawingTool {
return;
if (this.lastCopiedSelection === undefined)
return;
if (!(this.currMousePos[0]/currFile.zoom >= 0 && this.currMousePos[1]/currFile.zoom >= 0 &&
this.currMousePos[0]/currFile.zoom < currFile.canvasSize[0] && this.currMousePos[1]/currFile.zoom < currFile.canvasSize[1]))
this.currMousePos = [currFile.canvasSize[0]*currFile.zoom / 2, currFile.canvasSize[1]*currFile.zoom /2];
// Finish the current selection and start a new one with the same data
if (!this.cutting) {
this.endSelection();
@@ -49,6 +60,7 @@ class MoveSelectionTool extends DrawingTool {
this.switchFunc(this);
this.currSelection = this.lastCopiedSelection;
this.selectionTool.drawSelectedArea();
// Putting the vfx layer on top of everything
currFile.VFXLayer.canvas.style.zIndex = MAX_Z_INDEX;
@@ -59,30 +71,35 @@ class MoveSelectionTool extends DrawingTool {
onStart(mousePos, mouseTarget) {
super.onStart(mousePos, mouseTarget);
if (!this.cursorInSelectedArea(mousePos) &&
!Util.isChildOfByClass(mouseTarget, "editor-top-menu")) {
this.endSelection();
}
}
onDrag(mousePos) {
super.onDrag(mousePos);
this.currSelection = this.selectionTool.moveAnts(mousePos[0]/currFile.zoom,
mousePos[1]/currFile.zoom, this.currSelection.width, this.currSelection.height);
this.selectionTool.moveOffset =
[Math.floor(mousePos[0] / currFile.zoom - currFile.canvasSize[0] / 2 - (this.selectionTool.boundingBoxCenter[0] - currFile.canvasSize[0]/2)),
Math.floor(mousePos[1] / currFile.zoom - currFile.canvasSize[1] / 2- (this.selectionTool.boundingBoxCenter[1] - currFile.canvasSize[1]/2))];
// clear the entire tmp layer
currFile.TMPLayer.context.clearRect(0, 0, currFile.TMPLayer.canvas.width, currFile.TMPLayer.canvas.height);
currFile.TMPLayer.context.clearRect(0, 0, currFile.TMPLayer.canvas.width, currFile.TMPLayer.canvas.height);
// put the image data on the tmp layer with offset
currFile.TMPLayer.context.putImageData(
this.currSelection.data,
Math.round(mousePos[0] / currFile.zoom) - this.currSelection.width / 2,
Math.round(mousePos[1] / currFile.zoom) - this.currSelection.height / 2);
currFile.TMPLayer.context.putImageData(this.currSelection,
this.selectionTool.moveOffset[0], this.selectionTool.moveOffset[1]);
// Draw the selection area and outline
this.selectionTool.drawOutline();
this.selectionTool.drawSelectedArea();
this.selectionTool.drawBoundingBox();
}
onEnd(mousePos) {
super.onEnd(mousePos);
onEnd(mousePos, mouseTarget) {
super.onEnd(mousePos, mouseTarget);
if (!this.selectionTool.cursorInSelectedArea(mousePos) &&
!Util.isChildOfByClass(mouseTarget, "editor-top-menu")) {
this.endSelection();
// Switch to selection tool
this.switchFunc(this.selectionTool);
}
}
onSelect() {
@@ -91,6 +108,7 @@ class MoveSelectionTool extends DrawingTool {
onDeselect() {
super.onDeselect();
this.endSelection();
}
setSelectionData(data, tool) {
@@ -102,7 +120,7 @@ class MoveSelectionTool extends DrawingTool {
onHover(mousePos) {
super.onHover(mousePos);
if (this.cursorInSelectedArea(mousePos)) {
if (this.selectionTool.cursorInSelectedArea(mousePos)) {
currFile.canvasView.style.cursor = 'move';
}
else {
@@ -110,65 +128,13 @@ class MoveSelectionTool extends DrawingTool {
}
}
cursorInSelectedArea(cursorPos) {
// Getting the coordinates relatively to the canvas
let x = cursorPos[0] / currFile.zoom;
let y = cursorPos[1] / currFile.zoom;
if (this.currSelection.left <= x && x <= this.currSelection.right) {
if (y <= this.currSelection.bottom && y >= this.currSelection.top) {
return true;
}
return false;
}
return false;
}
endSelection() {
endSelection(event) {
if (this.currSelection == undefined)
return;
// Clearing the tmp (move preview) and vfx (ants) layers
currFile.TMPLayer.context.clearRect(0, 0, currFile.TMPLayer.canvas.width, currFile.TMPLayer.canvas.height);
currFile.VFXLayer.context.clearRect(0, 0, currFile.VFXLayer.canvas.width, currFile.VFXLayer.canvas.height);
// I have to save the underlying data, so that the transparent pixels in the clipboard
// don't override the coloured pixels in the canvas
let underlyingImageData = currFile.currentLayer.context.getImageData(
this.currSelection.left, this.currSelection.top,
this.currSelection.width+1, this.currSelection.height+1
);
let pasteData = this.currSelection.data.data.slice();
for (let i=0; i<underlyingImageData.data.length; i+=4) {
let currentMovePixel = [
pasteData[i], pasteData[i+1], pasteData[i+2], pasteData[i+3]
];
let currentUnderlyingPixel = [
underlyingImageData.data[i], underlyingImageData.data[i+1],
underlyingImageData.data[i+2], underlyingImageData.data[i+3]
];
// If the pixel of the clipboard is empty, but the one below it isn't, I use the pixel below
if (Util.isPixelEmpty(currentMovePixel)) {
if (!Util.isPixelEmpty(currentUnderlyingPixel)) {
pasteData[i] = currentUnderlyingPixel[0];
pasteData[i+1] = currentUnderlyingPixel[1];
pasteData[i+2] = currentUnderlyingPixel[2];
pasteData[i+3] = currentUnderlyingPixel[3];
}
}
}
currFile.currentLayer.context.putImageData(new ImageData(pasteData, this.currSelection.width+1),
this.currSelection.left, this.currSelection.top
);
this.currSelection = undefined;
currFile.currentLayer.updateLayerPreview();
currFile.VFXLayer.canvas.style.zIndex = MIN_Z_INDEX;
// Switch to brush
this.switchFunc(this.endTool);
this.selectionTool.pasteSelection();
if (event)
this.switchFunc(this.selectionTool);
}
}

View File

@@ -4,13 +4,20 @@ class PanTool extends Tool {
super(name, options);
Events.on('click', this.mainButton, switchFunction, this);
this.resetTutorial();
this.addTutorialTitle("Pan tool");
this.addTutorialKey("P", " to select the lasso selection tool");
this.addTutorialKey("Left drag", " to move the viewport");
this.addTutorialKey("Space + drag", " to move the viewport");
this.addTutorialImg("pan-tutorial.gif");
}
onStart(mousePos, target) {
super.onStart(mousePos);
if (target.className != 'drawingCanvas')
return;
currFile.canvasView.style.cursor = "url(\'/pixel-editor/pan-held.png\'), auto";
currFile.canvasView.style.cursor = "url(\'pan-held.png\'), auto";
}
onDrag(mousePos, target) {
@@ -31,12 +38,12 @@ class PanTool extends Tool {
if (target.className != 'drawingCanvas')
return;
currFile.canvasView.style.cursor = "url(\'/pixel-editor/pan.png\'), auto";
currFile.canvasView.style.cursor = "url(\'pan.png\'), auto";
}
onSelect() {
super.onSelect();
currFile.canvasView.style.cursor = "url(\'/pixel-editor/pan.png\'), auto";
currFile.canvasView.style.cursor = "url(\'pan.png\'), auto";
}
onDeselect() {

View File

@@ -1,5 +1,3 @@
// TODO: FIX SELECTION
class RectangleTool extends ResizableTool {
// Saving the empty rect svg
emptyRectangleSVG = document.getElementById("rectangle-empty-button-svg");
@@ -22,6 +20,15 @@ class RectangleTool extends ResizableTool {
Events.on('click', this.mainButton, this.changeFillType.bind(this));
Events.on('click', this.biggerButton, this.increaseSize.bind(this));
Events.on('click', this.smallerButton, this.decreaseSize.bind(this));
this.resetTutorial();
this.addTutorialTitle("Rectangle tool");
this.addTutorialKey("U", " to select the rectangle");
this.addTutorialKey("U while selected", " to change fill mode (empty or fill)");
this.addTutorialKey("Left drag", " to draw a rectangle");
this.addTutorialKey("Right drag", " to resize the brush");
this.addTutorialKey("+ or -", " to resize the brush");
this.addTutorialImg("rectangle-tutorial.gif");
}
changeFillType() {

View File

@@ -1,25 +1,28 @@
class RectangularSelectionTool extends SelectionTool {
switchFunc = undefined;
moveTool = undefined;
currSelection = {};
constructor (name, options, switchFunc, moveTool) {
super(name, options, switchFunc);
this.switchFunc = switchFunc;
this.moveTool = moveTool;
super(name, options, switchFunc, moveTool);
Events.on('click', this.mainButton, switchFunc, this);
this.resetTutorial();
this.addTutorialTitle("Rectangular selection tool");
this.addTutorialKey("M", " to select the rectangular selection tool");
this.addTutorialKey("Left drag", " to select a rectangular area");
this.addTutorialKey("Left drag", " to move a selection");
this.addTutorialKey("Esc", " to cancel a selection");
this.addTutorialKey("Click", " outside the selection to cancel it");
this.addTutorialKey("CTRL+C", " to copy a selection");
this.addTutorialKey("CTRL+V", " to paste a selection");
this.addTutorialKey("CTRL+X", " to cut a selection");
this.addTutorialImg("rectselect-tutorial.gif");
}
onStart(mousePos) {
super.onStart(mousePos);
onStart(mousePos, mouseTarget) {
super.onStart(mousePos, mouseTarget);
// Putting the vfx layer on top of everything
currFile.VFXLayer.canvas.style.zIndex = MAX_Z_INDEX;
// Saving the start coords of the rect
this.startMousePos[0] = Math.round(this.startMousePos[0] / currFile.zoom) - 0.5;
this.startMousePos[1] = Math.round(this.startMousePos[1] / currFile.zoom) - 0.5;
if (Util.isChildOfByClass(mouseTarget, "editor-top-menu") ||
!Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom]))
return;
// Avoiding external selections
if (this.startMousePos[0] < 0) {
@@ -40,20 +43,28 @@ class RectangularSelectionTool extends SelectionTool {
this.drawSelection(this.startMousePos[0], this.startMousePos[1]);
}
onDrag(mousePos) {
super.onDrag(mousePos);
onDrag(mousePos, mouseTarget) {
super.onDrag(mousePos, mouseTarget);
if (Util.isChildOfByClass(mouseTarget, "editor-top-menu") ||
!Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom]))
return;
// Drawing the rect
this.drawSelection(Math.round(mousePos[0] / currFile.zoom) + 0.5, Math.round(mousePos[1] / currFile.zoom) + 0.5);
this.endMousePos = [Math.floor(mousePos[0] / currFile.zoom), Math.floor(mousePos[1] / currFile.zoom)];
this.drawSelection(Math.floor(mousePos[0] / currFile.zoom), Math.floor(mousePos[1] / currFile.zoom));
}
onEnd(mousePos) {
super.onEnd(mousePos);
onEnd(mousePos, mouseTarget) {
super.onEnd(mousePos, mouseTarget);
if (Util.isChildOfByClass(mouseTarget, "editor-top-menu"))
return;
new HistoryState().EditCanvas();
// Getting the end position
this.endMousePos[0] = Math.round(this.endMousePos[0] / currFile.zoom) + 0.5;
this.endMousePos[1] = Math.round(this.endMousePos[1] / currFile.zoom) + 0.5;
this.endMousePos = [Math.floor(mousePos[0] / currFile.zoom), Math.floor(mousePos[1] / currFile.zoom)];
// Inverting end and start (start must always be the top left corner)
if (this.endMousePos[0] < this.startMousePos[0]) {
@@ -68,33 +79,23 @@ class RectangularSelectionTool extends SelectionTool {
this.startMousePos[1] = tmp;
}
if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) {
this.boundingBox.minX = this.startMousePos[0] - 1;
this.boundingBox.maxX = this.endMousePos[0] + 1;
this.boundingBox.minY = this.startMousePos[1] - 1;
this.boundingBox.maxY = this.endMousePos[1] + 1;
}
// Switch to the move tool so that the user can move the selection
this.switchFunc(this.moveTool);
// Preparing data for the move tool
let dataWidth = this.endMousePos[0] - this.startMousePos[0];
let dataHeight = this.endMousePos[1] - this.startMousePos[1];
// Obtain the selected pixels
this.moveTool.setSelectionData(this.getSelection(), this);
}
this.currSelection = {
left: this.startMousePos[0], right: this.endMousePos[0],
top: this.startMousePos[1], bottom: this.endMousePos[1],
width: dataWidth,
height: dataHeight,
data: currFile.currentLayer.context.getImageData(
this.startMousePos[0], this.startMousePos[1],
dataWidth + 1, dataHeight + 1)
};
// Moving the selection to the TMP layer. It will be moved back to the original
// layer if the user will cancel or end the selection
currFile.currentLayer.context.clearRect(this.startMousePos[0] - 0.5, this.startMousePos[1] - 0.5,
dataWidth + 1, dataHeight + 1);
// Moving those pixels from the current layer to the tmp layer
currFile.TMPLayer.context.putImageData(this.currSelection.data, this.startMousePos[0], this.startMousePos[1]);
this.moveTool.setSelectionData(this.currSelection, this);
console.log("data set");
cutSelection() {
super.cutSelection();
currFile.currentLayer.context.clearRect(this.currSelection.left-0.5, this.currSelection.top-0.5,
this.currSelection.width, this.currSelection.height);
}
onSelect() {
@@ -105,53 +106,16 @@ class RectangularSelectionTool extends SelectionTool {
super.onDeselect();
}
drawSelection(x, y) {
drawSelection() {
// Getting the vfx context
let vfxContext = currFile.VFXLayer.context;
// Clearing the vfx canvas
vfxContext.clearRect(0, 0, currFile.VFXLayer.canvas.width, currFile.VFXLayer.canvas.height);
vfxContext.lineWidth = 1;
vfxContext.strokeStyle = 'black';
vfxContext.setLineDash([4]);
// Drawing the rect
vfxContext.beginPath();
vfxContext.rect(this.startMousePos[0], this.startMousePos[1], x - this.startMousePos[0], y - this.startMousePos[1]);
vfxContext.stroke();
}
/** Moves the rect ants to the specified position
*
* @param {*} x X coordinate of the rect ants
* @param {*} y Y coordinat of the rect ants
* @param {*} width Width of the selection
* @param {*} height Height of the selectione
*
* @return The data regarding the current position and size of the selection
*/
moveAnts(x, y, width, height) {
// Getting the vfx context
let vfxContext = currFile.VFXLayer.context;
let ret = this.currSelection;
// Clearing the vfx canvas
vfxContext.clearRect(0, 0, currFile.VFXLayer.canvas.width, currFile.VFXLayer.canvas.height);
vfxContext.lineWidth = 1;
vfxContext.setLineDash([4]);
// Fixing the coordinates
this.currSelection.left = Math.round(Math.round(x) - 0.5 - Math.round(width / 2)) + 0.5;
this.currSelection.top = Math.round(Math.round(y) - 0.5 - Math.round(height / 2)) + 0.5;
this.currSelection.right = this.currSelection.left + Math.round(width);
this.currSelection.bottom = this.currSelection.top + Math.round(height);
// Drawing the rect
vfxContext.beginPath();
vfxContext.rect(this.currSelection.left, this.currSelection.top, width, height);
vfxContext.stroke();
return ret;
currFile.VFXLayer.drawLine(this.startMousePos[0], this.startMousePos[1], this.endMousePos[0], this.startMousePos[1], 1);
currFile.VFXLayer.drawLine(this.endMousePos[0], this.startMousePos[1], this.endMousePos[0], this.endMousePos[1], 1);
currFile.VFXLayer.drawLine(this.endMousePos[0], this.endMousePos[1], this.startMousePos[0], this.endMousePos[1], 1);
currFile.VFXLayer.drawLine(this.startMousePos[0], this.endMousePos[1], this.startMousePos[0], this.startMousePos[1], 1);
}
}

View File

@@ -2,9 +2,32 @@ class ResizableTool extends DrawingTool {
startResizePos = undefined;
currSize = 1;
prevSize = 1;
toolSizeInput = undefined;
biggerButton = undefined;
smallerButton = undefined;
constructor(name, options, switchFunc) {
super(name, options, switchFunc);
this.biggerButton = document.getElementById(name + "-bigger-button");
this.smallerButton = document.getElementById(name + "-smaller-button");
}
onSelect(mousePos) {
super.onSelect(mousePos);
if (this.toolSizeInput == undefined) {
this.toolSizeInput = InputComponents.createNumber(this.name + "-input", "Tool size");
Events.on("change", this.toolSizeInput.getElementsByTagName("input")[0], this.updateSize.bind(this));
}
TopMenuModule.addInfoElement(this.name + "-input", this.toolSizeInput);
TopMenuModule.updateField(this.name + "-input", this.currSize);
}
updateSize(event) {
let value = event.target.value;
this.currSize = value;
}
onRightStart(mousePos, mouseEvent) {
@@ -24,9 +47,30 @@ class ResizableTool extends DrawingTool {
//fix offset so the cursor stays centered
this.updateCursor();
this.onHover(this.startResizePos, mouseEvent);
TopMenuModule.updateField(this.name + "-input", this.currSize);
}
onRightEnd(mousePos, mouseEvent) {
}
increaseSize() {
if (this.currSize < 128) {
this.currSize++;
this.updateCursor();
TopMenuModule.updateField(this.name + "-input", this.currSize);
}
}
decreaseSize() {
if (this.currSize > 1) {
this.currSize--;
this.updateCursor();
TopMenuModule.updateField(this.name + "-input", this.currSize);
}
}
get size() {
return this.currSize;
}
}

View File

@@ -1,5 +1,276 @@
class SelectionTool extends Tool {
constructor(name, options, switchFunc) {
super(name, options, switchFunc);
switchFunc = undefined;
moveTool = undefined;
boundingBox = {minX: 9999999, maxX: -1, minY: 9999999, maxY: -1};
currSelection = {};
outlineData = undefined;
previewData = undefined;
selectedPixel = undefined;
moveOffset = [0, 0];
boundingBoxCenter = [0,0];
reconstruct = {left:false, right:false, top:false, bottom:false};
constructor(name, options, switchFunc, moveTool) {
super(name, options);
this.moveTool = moveTool;
this.switchFunc = switchFunc;
}
onStart(mousePos, mouseTarget) {
super.onStart(mousePos);
if (mouseTarget == undefined || Util.isChildOfByClass(mouseTarget, "editor-top-menu") ||
!Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom]))
return;
// Putting the vfx layer on top of everything
currFile.VFXLayer.canvas.style.zIndex = MAX_Z_INDEX;
currFile.VFXLayer.context.fillStyle = "rgba(0,0,0,1)";
this.startMousePos = [Math.floor(mousePos[0] / currFile.zoom),
Math.floor(mousePos[1] / currFile.zoom)];
this.endMousePos = [this.startMousePos[0], this.startMousePos[1]];
let mouseX = mousePos[0] / currFile.zoom;
let mouseY = mousePos[1] / currFile.zoom;
this.boundingBox = {minX: 9999999, maxX: -1, minY: 9999999, maxY: -1};
this.reconstruct = {left:false, right:false, top:false, bottom:false};
this.currSelection = {};
this.moveOffset = [0, 0];
this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1));
}
onDrag(mousePos) {
super.onDrag(mousePos);
let mouseX = mousePos[0] / currFile.zoom;
let mouseY = mousePos[1] / currFile.zoom;
if (mouseX > currFile.canvasSize[0])
this.reconstruct.right = true;
else if (mouseX < 0)
this.reconstruct.left = true;
if (mouseY > currFile.canvasSize[1])
this.reconstruct.bottom = true;
else if (mouseY < 0)
this.reconstruct.top = true;
if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) {
this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1));
}
}
onEnd(mousePos, mouseTarget) {
super.onEnd(mousePos);
if (mouseTarget == undefined || Util.isChildOfByClass(mouseTarget, "editor-top-menu"))
return;
let mouseX = mousePos[0] / currFile.zoom;
let mouseY = mousePos[1] / currFile.zoom;
if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) {
this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1),
Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1));
}
this.boundingBoxCenter = [this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2,
this.boundingBox.minY + (this.boundingBox.maxY - this.boundingBox.minY) / 2];
}
cutSelection() {
let currLayerData = currFile.currentLayer.context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]).data;
// Save the selected pixels so that they can be moved and pasted back in the right place
for (const key in this.currSelection) {
let x = parseInt(key.split(",")[0]);
let y = parseInt(key.split(",")[1]);
let index = (y * currFile.canvasSize[0] + x) * 4;
for (let i=0; i<4; i++) {
// Save the pixel
this.previewData.data[index + i] = currLayerData[index + i];
// Delete the data below
currLayerData[index + i] = 0;
}
}
currFile.currentLayer.context.putImageData(new ImageData(currLayerData, currFile.canvasSize[0]), 0, 0);
}
pasteSelection(){
if (this.currSelection == undefined)
return;
// I have to save the underlying data, so that the transparent pixels in the clipboard
// don't override the coloured pixels in the canvas
let underlyingImageData = currFile.currentLayer.context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
let pasteData = currFile.TMPLayer.context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
// Clearing the tmp (move preview) and vfx (ants) layers
currFile.TMPLayer.context.clearRect(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
currFile.VFXLayer.context.clearRect(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
Util.pasteData(underlyingImageData, pasteData, currFile.currentLayer.context);
currFile.currentLayer.updateLayerPreview();
currFile.VFXLayer.canvas.style.zIndex = MIN_Z_INDEX;
}
cursorInSelectedArea(mousePos) {
let floored = [Math.floor(mousePos[0] / currFile.zoom) - this.moveOffset[0],
Math.floor(mousePos[1] / currFile.zoom) - this.moveOffset[1]];
if (this.currSelection[floored] != undefined)
return true;
return false;
}
visit(pixel, visited, data) {
let toVisit = [pixel];
let selected = [];
let currVisited = {};
currFile.TMPLayer.context.clearRect(0, 0, 512, 512);
while (toVisit.length > 0) {
pixel = toVisit.pop();
selected.push(pixel);
visited[pixel] = true;
currVisited[pixel] = true;
let col = Util.getPixelColor(data, pixel[0], pixel[1], currFile.canvasSize[0]);
if (col[3] == 255)
continue;
if (this.isBorderOfBox(pixel))
return [];
let top, bottom, left, right;
if (pixel[1] > 0)
top = [pixel[0], pixel[1] - 1];
else
top = undefined;
if (pixel[0] > 0)
left = [pixel[0] - 1, pixel[1]];
else
left = undefined;
if (pixel[1] < currFile.canvasSize[1])
bottom = [pixel[0], pixel[1] + 1];
else
bottom = undefined;
if (pixel[0] < currFile.canvasSize[0])
right = [pixel[0] + 1, pixel[1]];
else
right = undefined;
if (right != undefined && currVisited[right] == undefined)
toVisit.push(right);
if (left != undefined && currVisited[left] == undefined)
toVisit.push(left);
if (top != undefined && currVisited[top] == undefined)
toVisit.push(top);
if (bottom != undefined && currVisited[bottom] == undefined)
toVisit.push(bottom);
}
return selected;
}
getSelection() {
let selected = [];
let visited = {};
let data = currFile.VFXLayer.context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]).data;
// BFS: a pixel that causes the algorithm to visit a pixel of the bounding box is outside the
// selection. Otherwise, since the algorithm stops visiting when it reaches the outline,
// the pixel is inside the selection (and so are all the ones that have been visited)
for (let x=this.boundingBox.minX-1; x<=this.boundingBox.maxX+1; x++) {
for (let y=this.boundingBox.minY-1; y<=this.boundingBox.maxY+1; y++) {
if (visited[[x, y]] == undefined) {
let insidePixels = this.visit([x,y], visited, data);
for (let i=0; i<insidePixels.length; i++) {
selected.push(insidePixels[i]);
this.currSelection[insidePixels[i]] = true;
}
}
}
}
// Save the selection outline
this.outlineData = currFile.VFXLayer.context.getImageData(this.boundingBox.minX,
this.boundingBox.minY, this.boundingBox.maxX - this.boundingBox.minX,
this.boundingBox.maxY - this.boundingBox.minY);
// Create the image data containing the selected pixels
this.previewData = new ImageData(currFile.canvasSize[0], currFile.canvasSize[1]);
// Cut the selection
this.cutSelection();
// Put it on the TMP layer
currFile.TMPLayer.context.putImageData(this.previewData, 0, 0);
// Draw the selected area and the bounding box
this.drawSelectedArea();
this.drawBoundingBox();
return this.previewData;
}
drawSelectedArea() {
for (const key in this.currSelection) {
let x = parseInt(key.split(",")[0]);
let y = parseInt(key.split(",")[1]);
currFile.VFXLayer.context.fillStyle = "rgba(10, 0, 40, 0.3)";
currFile.VFXLayer.context.fillRect(x + this.moveOffset[0], y + this.moveOffset[1], 1, 1);
}
}
drawOutline() {
currFile.VFXLayer.context.clearRect(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
currFile.VFXLayer.context.putImageData(this.outlineData, this.boundingBox.minX + this.moveOffset[0],
this.boundingBox.minY + this.moveOffset[1]);
}
drawBoundingBox() {
currFile.VFXLayer.context.fillStyle = "red";
currFile.VFXLayer.context.fillRect(this.boundingBox.minX + this.moveOffset[0],
this.boundingBox.minY + this.moveOffset[1], 1, 1);
currFile.VFXLayer.context.fillRect(this.boundingBox.minX+ this.moveOffset[0],
this.boundingBox.maxY + this.moveOffset[1], 1, 1);
currFile.VFXLayer.context.fillRect(this.boundingBox.maxX+ this.moveOffset[0],
this.boundingBox.minY + this.moveOffset[1], 1, 1);
currFile.VFXLayer.context.fillRect(this.boundingBox.maxX+ this.moveOffset[0],
this.boundingBox.maxY + this.moveOffset[1], 1, 1);
}
isBorderOfBox(pixel) {
return pixel[0] == this.boundingBox.minX || pixel[0] == this.boundingBox.maxX ||
pixel[1] == this.boundingBox.minY || pixel[1] == this.boundingBox.maxY;
}
updateBoundingBox(mouseX, mouseY) {
if (mouseX > this.boundingBox.maxX)
this.boundingBox.maxX = Math.floor(mouseX);
if (mouseX < this.boundingBox.minX)
this.boundingBox.minX = Math.floor(mouseX);
if (mouseY < this.boundingBox.minY)
this.boundingBox.minY = Math.floor(mouseY);
if (mouseY > this.boundingBox.maxY)
this.boundingBox.maxY = Math.floor(mouseY);
}
}

View File

@@ -1,144 +0,0 @@
// Saving the empty rect svg
var emptyEllipseSVG = document.getElementById("ellipse-empty-button-svg");
// and the full rect svg so that I can change them when the user changes rect modes
var fullEllipseSVG = document.getElementById("ellipse-full-button-svg");
// The start mode is empty ellipse
var ellipseDrawMode = 'empty';
// I'm not drawing a ellipse at the beginning
var isDrawingEllipse = false;
// Ellipse coordinates
let startEllipseX;
let startEllipseY;
let endEllipseX;
let endEllipseY;
// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle
/** Starts drawing the ellipse, saves the start coordinates
*
* @param {*} mouseEvent
*/
function startEllipseDrawing(mouseEvent) {
// Putting the vfx layer on top of everything
VFXLayer.canvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex, 10) + 1;;
// Updating flag
isDrawingEllipse = true;
// Saving the start coords of the ellipse
let cursorPos = Input.getCursorPosition(mouseEvent);
startEllipseX = Math.floor(cursorPos[0] / zoom) + 0.5;
startEllipseY = Math.floor(cursorPos[1] / zoom) + 0.5;
drawEllipse(startEllipseX, startEllipseY);
}
// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle
/** Updates the ellipse preview depending on the position of the mouse
*
* @param {*} mouseEvent The mouseEvent from which we'll get the mouse position
*/
function updateEllipseDrawing(mouseEvent) {
let pos = Input.getCursorPosition(mouseEvent);
// Drawing the ellipse at the right position
drawEllipse(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5);
}
// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle
/** Finishes drawing the ellipse, decides the end coordinates and moves the preview ellipse to the
* current layer
*
* @param {*} mouseEvent event from which we'll get the mouse position
*/
function endEllipseDrawing(mouseEvent) {
// Getting the end position
let currentPos = Input.getCursorPosition(mouseEvent);
let vfxContext = VFXLayer.context;
endEllipseX = Math.round(currentPos[0] / zoom) + 0.5;
endEllipseY = Math.round(currentPos[1] / zoom) + 0.5;
// Inverting end and start (start must always be the top left corner)
if (endEllipseX < startEllipseX) {
let tmp = endEllipseX;
endEllipseX = startEllipseX;
startEllipseX = tmp;
}
// Same for the y
if (endEllipseY < startEllipseY) {
let tmp = endEllipseY;
endEllipseY = startEllipseY;
startEllipseY = tmp;
}
// Resetting this
isDrawingEllipse = false;
// Drawing the ellipse
startEllipseY -= 0.5;
endEllipseY -= 0.5;
endEllipseX -= 0.5;
startEllipseX -= 0.5;
// Setting the correct linewidth
currentLayer.context.lineWidth = tool.ellipse.brushSize;
// Drawing the ellipse using 4 lines
currentLayer.drawLine(startEllipseX, startEllipseY, endEllipseX, startEllipseY, tool.ellipse.brushSize);
currentLayer.drawLine(endEllipseX, startEllipseY, endEllipseX, endEllipseY, tool.ellipse.brushSize);
currentLayer.drawLine(endEllipseX, endEllipseY, startEllipseX, endEllipseY, tool.ellipse.brushSize);
currentLayer.drawLine(startEllipseX, endEllipseY, startEllipseX, startEllipseY, tool.ellipse.brushSize);
// If I have to fill it, I do so
if (ellipseDrawMode == 'fill') {
currentLayer.context.fillRect(startEllipseX, startEllipseY, endEllipseX - startEllipseX, endEllipseY - startEllipseY);
}
// Clearing the vfx canvas
vfxContext.clearRect(0, 0, VFXLayer.canvas.width, VFXLayer.canvas.height);
}
// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle
/** Draws a ellipse with end coordinates given by x and y on the VFX layer (draws
* the preview for the ellipse tool)
*
* @param {*} x The current end x of the ellipse
* @param {*} y The current end y of the ellipse
*/
function drawEllipse(x, y) {
// Getting the vfx context
let vfxContext = VFXLayer.context;
// Clearing the vfx canvas
vfxContext.clearRect(0, 0, VFXLayer.canvas.width, VFXLayer.canvas.height);
// Drawing the ellipse
vfxContext.lineWidth = tool.ellipse.brushSize;
// Drawing the ellipse
vfxContext.beginPath();
if ((tool.ellipse.brushSize % 2 ) == 0) {
vfxContext.rect(startEllipseX - 0.5, startEllipseY - 0.5, x - startEllipseX, y - startEllipseY);
}
else {
vfxContext.rect(startEllipseX, startEllipseY, x - startEllipseX, y - startEllipseY);
}
vfxContext.setLineDash([]);
vfxContext.stroke();
}
/** Sets the correct tool icon depending on its mode
*
*/
function setEllipseToolSvg() {
console.log("set eilipse svg");
if (ellipseDrawMode == 'empty') {
emptyEllipseSVG.setAttribute('display', 'visible');
fullEllipseSVG.setAttribute('display', 'none');
}
else {
emptyEllipseSVG.setAttribute('display', 'none');
fullEllipseSVG.setAttribute('display', 'visible');
}
}