1409 lines
40 KiB
JavaScript
1409 lines
40 KiB
JavaScript
"use strict"
|
|
var selectedExport;
|
|
var maskStaysOnTop = true;
|
|
|
|
if (location.pathname.includes("/page/")) {
|
|
//don't init if not in the right path
|
|
//this is to stop code running from comfyUI directly
|
|
$(initPaint);
|
|
}
|
|
|
|
var hotkeys = {};
|
|
|
|
var initialPalette = [
|
|
"#000000","#ffffff",
|
|
"#202020","#404040",
|
|
"#606060","#808080",
|
|
"#a0a0a0","#b8b8b8",
|
|
"#d0d0d0","#e0e0e0",
|
|
|
|
"#ff0000","#ff8000",
|
|
"#ffff00","#008000",
|
|
"#00ff00","#00ff80",
|
|
"#0080ff","#50C0ff",
|
|
"#0000ff","#8080ff",
|
|
"#a00060","#ff00ff",
|
|
"#ffa0a0","#a06040"];
|
|
|
|
var tip = {
|
|
lastX : 0,
|
|
lastY : 0,
|
|
x : 0.0,
|
|
y : 0.0,
|
|
color : "black",
|
|
size : 5,
|
|
drawOperation : feltTip,
|
|
}
|
|
|
|
|
|
var tool=feltTip;
|
|
|
|
var brushSizeControl = brushDiameterControl("tip_diameter");
|
|
|
|
var picStack = [];
|
|
|
|
var activePic;
|
|
var targetLayer = null;
|
|
|
|
function setActivePic(newValue) {
|
|
activePic=newValue;
|
|
updateLayerList()
|
|
if (!activePic) return;
|
|
activePic.bringToFront();
|
|
|
|
}
|
|
|
|
var dragging = false;
|
|
var dragStartX;
|
|
var dragStartY;
|
|
|
|
var draggingElement = null;
|
|
|
|
|
|
function setExportPic(pic) {
|
|
setActivePic(pic);
|
|
|
|
selectedExport=pic;
|
|
|
|
$(".sidebutton").removeClass("output")
|
|
const button=pic.element.querySelector(".sidebutton");
|
|
button.classList.add("output");
|
|
|
|
pic.updateVisualRepresentation(true);
|
|
transmitMask(pic.mask.canvas);
|
|
}
|
|
|
|
function closePic(pic) {
|
|
if (selectedExport===pic) selectedExport=null;
|
|
pic.element.parentElement.removeChild(pic.element);
|
|
if (pic === activePic) {
|
|
const newActive = document.querySelector("#workspace .pic");
|
|
setActivePic(newActive?.pic);
|
|
}
|
|
}
|
|
|
|
var lastUsedMaskColor = "#402040";
|
|
|
|
class Layer {
|
|
canvas = document.createElement("canvas");
|
|
ctx = this.canvas.getContext("2d",{willReadFrequently:true});
|
|
mask = false;
|
|
visible = true;
|
|
compositeOperation = "source-over";
|
|
opacity = 1;
|
|
_maskColor = lastUsedMaskColor;
|
|
constructor (pic,title, {width,height}, mask = false) {
|
|
this.parentPic = pic;
|
|
this.canvas.width=width;
|
|
this.canvas.height=height;
|
|
this.mask = mask;
|
|
this.title= title;
|
|
if (mask) {
|
|
this.compositeOperation="source-over";
|
|
}
|
|
}
|
|
get maskColor() {
|
|
return this._maskColor;
|
|
}
|
|
set maskColor(newValue) {
|
|
this._maskColor=newValue;
|
|
if (this.mask) {
|
|
this.ctx.save();
|
|
this.ctx.globalCompositeOperation="source-atop";
|
|
this.ctx.fillStyle=newValue;
|
|
this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);
|
|
this.ctx.restore();
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateMirrorGridButtonImage() {
|
|
const columns = $(".grid_mirrors #repeat_x").val();
|
|
const rows = $(".grid_mirrors #repeat_y").val();
|
|
const image=gridImage(rows,columns,48);
|
|
$("#mirror_grid").css('background-image',`url(${image.toDataURL()})`);
|
|
}
|
|
|
|
function updateMirrorRotationButtonImage() {
|
|
const fins = $(".rotational_mirrors input").val();
|
|
|
|
const image=spiralImage(fins,36);
|
|
$("#mirror_rotational").css('background-image',`url(${image.toDataURL()})`);
|
|
}
|
|
|
|
var mirrorFunction = a=>a;
|
|
|
|
function updateMirrors() {
|
|
let result = a=>a;
|
|
if($("#mirror_rotational.mirror.button").hasClass("down")) {
|
|
const ways = $(".rotational_mirrors input").val();
|
|
const rotationFunction = rotationalSymmetry(ways);
|
|
result = composeFunction(result,rotationFunction);
|
|
}
|
|
|
|
if($("#mirror_grid.mirror.button").hasClass("down")) {
|
|
const columns = $(".grid_mirrors #repeat_x").val();
|
|
const rows = $(".grid_mirrors #repeat_y").val();
|
|
const gridFunction = cells(columns,rows);
|
|
result = composeFunction(result,gridFunction);
|
|
}
|
|
if($("#mirror_x.mirror.button").hasClass("down")) result = composeFunction(result,mirrorX);
|
|
if($("#mirror_y.mirror.button").hasClass("down")) result = composeFunction(result,mirrorY);
|
|
if($("#mirror_tlbr.mirror.button").hasClass("down")) result = composeFunction(result,mirrorDiagonalXY);
|
|
if($("#mirror_trbl.mirror.button").hasClass("down")) result = composeFunction(result,mirrorDiagonalY1MinusX);
|
|
|
|
mirrorFunction=result;
|
|
}
|
|
|
|
function createDrawArea(canvas = blankCanvas()) {
|
|
const undoDepth = 10;
|
|
const element = document.createElement("div")
|
|
const ctx=canvas.getContext("2d");
|
|
const eventOverlay = document.createElement("div");
|
|
const sidebar = document.createElement("div");
|
|
const closeButton = document.createElement("div");
|
|
var updatingStroke=false;
|
|
sidebar.className = "sidebutton";
|
|
closeButton.className = "closebutton";
|
|
|
|
element.className="pic";
|
|
eventOverlay.className="eventoverlay fillparent";
|
|
|
|
element.appendChild(sidebar);
|
|
element.appendChild(canvas);
|
|
element.appendChild(eventOverlay);
|
|
element.appendChild(closeButton);
|
|
canvas.ctx=ctx;
|
|
|
|
const activeOperationCanvas = document.createElement("canvas");
|
|
activeOperationCanvas.width = canvas.width;
|
|
activeOperationCanvas.height = canvas.height;
|
|
activeOperationCanvas.ctx = activeOperationCanvas.getContext("2d",{willReadFrequently:true});
|
|
|
|
|
|
const image = canvas.ctx.getAllImageData();
|
|
const undoStack = [];
|
|
const redoStack = [];
|
|
const layers = [];
|
|
var mask;
|
|
|
|
eventOverlay.addEventListener("mousedown", handleMouseDown);
|
|
eventOverlay.addEventListener("mouseup", handleMouseUp);
|
|
eventOverlay.addEventListener("mousemove", handleMouseMove);
|
|
eventOverlay.addEventListener("contextmenu",function(e){e.preventDefault(); return false;});
|
|
|
|
|
|
const pic = {element,eventOverlay,image,
|
|
get layers() {return layers},
|
|
get canvas() {return canvas},
|
|
get mask() {return mask},
|
|
set mask(m) {mask=m},
|
|
scale:1,
|
|
scalefactor:0,
|
|
offsetX:0,
|
|
offsetY:0,
|
|
isDrawing:false,
|
|
activeLayer:null,
|
|
strokeCoordinates :[],
|
|
strokeModifier: a=>a,
|
|
setTransform( ) {
|
|
const {element,scale,offsetX,offsetY} = this;
|
|
element.style.setProperty("--scalefactor",scale)
|
|
element.style.setProperty("--translateX",offsetX+"px")
|
|
element.style.setProperty("--translateY",offsetY+"px")
|
|
},
|
|
bringToFront() {
|
|
const oldPos = picStack.indexOf(this);
|
|
if (oldPos >= 0) {
|
|
picStack.splice(oldPos,1);
|
|
}
|
|
picStack.unshift(this);
|
|
let z = 100;
|
|
for (const {element} of picStack) {
|
|
element.style.zIndex=z;
|
|
element.setAttribute("data-z",z);
|
|
z-=1;
|
|
}
|
|
},
|
|
|
|
composite(suppressMask=false) {
|
|
canvas.ctx.save();
|
|
canvas.ctx.clearRect(0,0,canvas.width,canvas.height)
|
|
for (const layer of this.layers) {
|
|
if (suppressMask && this.mask==layer) continue;
|
|
if (!layer.visible) continue;
|
|
canvas.ctx.globalAlpha = layer.opacity;
|
|
canvas.ctx.globalCompositeOperation=layer.compositeOperation;
|
|
if (this.isDrawing && layer===this.activeLayer) {
|
|
canvas.ctx.drawImage(activeOperationCanvas,0,0);
|
|
} else {
|
|
canvas.ctx.drawImage(layer.canvas,0,0);
|
|
|
|
}
|
|
}
|
|
canvas.ctx.restore();
|
|
},
|
|
|
|
commit() {
|
|
let data = activeOperationCanvas.ctx.getAllImageData()
|
|
const undoRecord = {
|
|
type : "layer",
|
|
layer: this.activeLayer,
|
|
data: this.activeLayer.ctx.getAllImageData()
|
|
}
|
|
undoStack.push(undoRecord);
|
|
if (undoStack.length > undoDepth) undoStack.shift();
|
|
redoStack.length=0;
|
|
|
|
this.activeLayer.ctx.putImageData(data,0,0);
|
|
this.isDrawing = false;
|
|
if (selectedExport===this) {
|
|
if (this.activeLayer===this.mask) {
|
|
transmitMask(this.mask.canvas);
|
|
} else {
|
|
this.composite(true);
|
|
transmitCanvas(canvas);
|
|
}
|
|
|
|
}
|
|
if (activePic===this) {
|
|
updateLayerList(); //inefficient to remake all controls on edit, fix this
|
|
}
|
|
this.updateVisualRepresentation(false);
|
|
|
|
},
|
|
clearLayer() {
|
|
activeOperationCanvas.ctx.clearRect(0,0,activeOperationCanvas.width,activeOperationCanvas.height);
|
|
this.commit();
|
|
},
|
|
fillLayer(color = "#fff") {
|
|
activeOperationCanvas.ctx.fillStyle=color;
|
|
activeOperationCanvas.ctx.fillRect(0,0,activeOperationCanvas.width,activeOperationCanvas.height);
|
|
this.commit();
|
|
},
|
|
updateVisualRepresentation(transmit=true) {
|
|
if (transmit && this===selectedExport) {
|
|
this.composite(true); //suppress mask for transmitted canvas
|
|
transmitCanvas(canvas);
|
|
}
|
|
this.composite(false);
|
|
},
|
|
|
|
startDraw(x,y) {
|
|
if (!this.activeLayer.visible) return; //don't draw on hidden layers.
|
|
this.strokeModifier=mirrorFunction;
|
|
this.isDrawing=true;
|
|
this.strokeCoordinates=[{x:x+0.001,y}];
|
|
tip.x=x;
|
|
tip.y=y;
|
|
this.draw(x,y);
|
|
},
|
|
|
|
stopDraw(x,y) {
|
|
const self=this;
|
|
if (updatingStroke) {
|
|
setTimeout(_=>self.stopDraw(x,y),1)
|
|
} else {
|
|
this.isDrawing=false;
|
|
this.commit();
|
|
}
|
|
},
|
|
|
|
draw(x,y) {
|
|
this.strokeCoordinates.push({x,y});
|
|
//This is done in a timeout to allow multiple draw movements
|
|
//to accumulate rather than slowing things down
|
|
if (!updatingStroke) {
|
|
updatingStroke=true;
|
|
setTimeout(_=>{
|
|
activeOperationCanvas.ctx.clearRect(0,0,activeOperationCanvas.width,activeOperationCanvas.height);
|
|
activeOperationCanvas.ctx.drawImage(this.activeLayer.canvas,0,0);
|
|
|
|
const unitRange=this.strokeCoordinates.map(({x,y})=>({x:x/canvas.width,y:y/canvas.height}));
|
|
|
|
const strokes = this.strokeModifier([unitRange])
|
|
for (const unitStroke of strokes) {
|
|
const stroke=unitStroke.map(({x,y})=>({x:x*canvas.width,y:y*canvas.height}));
|
|
tip.drawOperation(activeOperationCanvas.ctx,tip,stroke)
|
|
}
|
|
if (this.activeLayer.mask) {
|
|
const ctx=activeOperationCanvas.ctx;
|
|
ctx.save();
|
|
ctx.globalCompositeOperation="source-atop";
|
|
ctx.fillStyle = this.activeLayer.maskColor;
|
|
ctx.fillRect(0,0,activeOperationCanvas.width,activeOperationCanvas.height);
|
|
ctx.restore();
|
|
}
|
|
this.composite();
|
|
updatingStroke=false;
|
|
},1)
|
|
}
|
|
},
|
|
updateLayerList(newList) {
|
|
if (maskStaysOnTop && this.mask) {
|
|
const maskIndex = newList.indexOf(this.mask);
|
|
console.log({maskIndex})
|
|
if (maskIndex !== newList.length - 1) {
|
|
newList.splice(maskIndex, 1);
|
|
newList.push(this.mask);
|
|
}
|
|
}
|
|
const undoRecord = {
|
|
type: 'layerList',
|
|
previousList: [...layers]
|
|
};
|
|
undoStack.push(undoRecord);
|
|
if (undoStack.length > undoDepth) undoStack.shift();
|
|
layers.length = 0; // Clear the existing array
|
|
layers.push(...newList); // Fill with new values
|
|
},
|
|
/*
|
|
removeLayer(layer=this.activeLayer) {
|
|
const i = layers.indexOf(layer);
|
|
if (i !==-1) {
|
|
layers.splice(i,1);
|
|
if (layer === this.activeLayer) {
|
|
this.activeLayer = layers[Math.min(layers.length-1,i)];
|
|
}
|
|
}
|
|
this.updateVisualRepresentation(true);
|
|
},
|
|
addEmptyLayer() {
|
|
let result = new Layer("new layer", canvas)
|
|
layers.push(result);
|
|
return result;
|
|
},
|
|
insertLayer(layer, below = null) {
|
|
const indexExisting = layers.indexOf(layer);
|
|
if (indexExisting !== -1) {
|
|
layers.splice(indexExisting, 1);
|
|
}
|
|
|
|
const indexBelow = layers.indexOf(below);
|
|
if (indexBelow !== -1) {
|
|
layers.splice(indexBelow, 0, layer);
|
|
} else {
|
|
layers.push(layer);
|
|
}
|
|
},
|
|
*/
|
|
addEmptyLayer() {
|
|
const newLayer = new Layer(this,"new layer", canvas);
|
|
const newList = [...layers, newLayer];
|
|
this.updateLayerList(newList);
|
|
return newLayer;
|
|
},
|
|
addDuplicateLayer(layer) {
|
|
if (!layer) {
|
|
return this.addEmptyLayer();
|
|
} else {
|
|
// Duplicate the provided layer
|
|
const newLayer = new Layer(this, layer.title, {width: layer.canvas.width, height: layer.canvas.height}, layer.mask);
|
|
newLayer.ctx.drawImage(layer.canvas, 0, 0); // Copy the content of the original layer
|
|
newLayer.visible = layer.visible;
|
|
newLayer.compositeOperation = layer.compositeOperation;
|
|
newLayer.opacity = layer.opacity;
|
|
const newList = [...this.layers, newLayer];
|
|
this.updateLayerList(newList);
|
|
return newLayer;
|
|
}
|
|
|
|
},
|
|
removeLayer(layer = this.activeLayer) {
|
|
const newList = layers.filter(l => l !== layer);
|
|
this.updateLayerList(newList);
|
|
},
|
|
insertLayer(layer, below = null) {
|
|
const newList = [...layers];
|
|
const indexExisting = newList.indexOf(layer);
|
|
if (indexExisting !== -1) {
|
|
newList.splice(indexExisting, 1);
|
|
}
|
|
const indexBelow = newList.indexOf(below);
|
|
if (indexBelow !== -1) {
|
|
newList.splice(indexBelow, 0, layer);
|
|
} else {
|
|
newList.push(layer);
|
|
}
|
|
this.updateLayerList(newList);
|
|
},
|
|
undo() {
|
|
const lastRecord = undoStack.pop();
|
|
if (lastRecord) {
|
|
if (lastRecord.type === 'layerList') {
|
|
redoStack.push({type: 'layerList', previousList: [...layers]});
|
|
layers.length = 0;
|
|
layers.push(...lastRecord.previousList);
|
|
} else if (lastRecord.type === 'layer') {
|
|
redoStack.push({
|
|
type: 'layer',
|
|
layer: lastRecord.layer,
|
|
data: lastRecord.layer.ctx.getAllImageData()
|
|
});
|
|
lastRecord.layer.ctx.putImageData(lastRecord.data, 0, 0);
|
|
}
|
|
//... (handle other types of undoRecords)
|
|
}
|
|
this.updateVisualRepresentation(true);
|
|
updateLayerList();
|
|
},
|
|
|
|
redo() {
|
|
const lastRecord = redoStack.pop();
|
|
if (lastRecord) {
|
|
if (lastRecord.type === 'layerList') {
|
|
undoStack.push({type: 'layerList', previousList: [...layers]});
|
|
layers.length = 0;
|
|
layers.push(...lastRecord.previousList);
|
|
} else if (lastRecord.type === 'layer') {
|
|
undoStack.push({
|
|
type: 'layer',
|
|
layer: lastRecord.layer,
|
|
data: lastRecord.layer.ctx.getAllImageData()
|
|
});
|
|
lastRecord.layer.ctx.putImageData(lastRecord.data, 0, 0);
|
|
}
|
|
//... (handle other types of redoRecords)
|
|
}
|
|
this.updateVisualRepresentation(true);
|
|
updateLayerList();
|
|
},
|
|
}
|
|
|
|
layers.push( new Layer(pic,"base", canvas));
|
|
|
|
mask = new Layer(pic,"mask", canvas,true);
|
|
mask.opacity = 0.6;
|
|
layers.push(mask);
|
|
pic.activeLayer= layers[0];
|
|
|
|
layers[0].ctx.putImageData(image,0,0);
|
|
activeOperationCanvas.ctx.putImageData(image,0,0);
|
|
|
|
|
|
eventOverlay.pic = pic;
|
|
sidebar.addEventListener("mousedown",_=> setExportPic(pic))
|
|
closeButton.addEventListener("click",_=> closePic(pic))
|
|
|
|
pic.setTransform();
|
|
setActivePic(pic)
|
|
return pic;
|
|
}
|
|
|
|
|
|
|
|
function initPaint(){
|
|
var palette=$("#palette");
|
|
for (let i of initialPalette) {
|
|
const e=`<div class="paletteentry" style="background-color:${i}"> </div>`;
|
|
palette.append($(e).data("colour",i));
|
|
}
|
|
palette.append($(`<div class="paletteentry erase">Erase</div>`).data("colour","#0000"))
|
|
poulateLayerControl();
|
|
$(".background")[0].addEventListener("wheel",handleMouseWheel);
|
|
|
|
$(".paletteentry").on("mousedown", function(e) {
|
|
if (![feltTip,pixelTip].includes(tool)) {
|
|
setTool(feltTip);
|
|
}
|
|
let eraser=false;
|
|
let c= $(e.currentTarget).data("colour");
|
|
if (c==="#0000") {
|
|
c="#ffffff";
|
|
eraser = true;
|
|
}
|
|
if (e.which == 1) {
|
|
if (e.ctrlKey && !eraser) {
|
|
c=$("#foreground").val();
|
|
$(e.currentTarget).data("colour",c).css("background-color",c)
|
|
} else {
|
|
$("#foreground").val(c).data("eraser",eraser);
|
|
}
|
|
}
|
|
if (e.which == 2 && !eraser) {
|
|
c=$("#foreground").val();
|
|
$(e.currentTarget).data("colour",c).css("background-color",c)
|
|
}
|
|
if (e.which == 3 ) {
|
|
if (e.ctrlKey && !eraser) {
|
|
c=$("#background").val();
|
|
$(e.currentTarget).data("colour",c).css("background-color",c)
|
|
} else {
|
|
$("#background").val(c).data("eraser",eraser);
|
|
}
|
|
}
|
|
}).on("contextmenu",
|
|
function(){return false;}
|
|
);
|
|
|
|
$("#pen")[0].tool=feltTip;
|
|
$("#pixels")[0].tool=pixelTip;
|
|
$("#eraser")[0].tool=eraserTip;
|
|
$("#fine_eraser")[0].tool=pixelClear;
|
|
$("#eyedropper")[0].tool=eyeDropper;
|
|
|
|
$(".tool.button").on("click",function(e) {setTool(e.currentTarget.tool);});
|
|
|
|
$("#clear").on("click",function(e) {activePic?.clearLayer()});
|
|
$("#undo").on("click",function(e) {activePic?.undo()});
|
|
$("#redo").on("click",function(e) {activePic?.redo()});
|
|
|
|
brushSizeControl.addEventListener("changed", e=> {
|
|
tip.size=brushSizeControl.diameter;
|
|
|
|
for (let p of $(".pic")) {
|
|
updateBrushCursor(p);
|
|
}
|
|
|
|
});
|
|
|
|
addEventListener("keydown", handleKeyDown);
|
|
|
|
$(".panel").append(brushSizeControl).append(`
|
|
<div class="subpanel simple_mirrors">
|
|
<div id="mirror_x" class="mirror button"></div>
|
|
<div id="mirror_y" class="mirror button"></div>
|
|
<div id="mirror_tlbr" class="mirror button"></div>
|
|
<div id="mirror_trbl" class="mirror button"></div>
|
|
</div>
|
|
<div class="subpanel grid_mirrors">
|
|
<span style="display:inline-block;">
|
|
<input id="repeat_x" type="number" min="1" max="10" value="3" step="1" required />
|
|
<input id="repeat_y" type="number" min="1" max="10" value="3" step="1" required />
|
|
</span>
|
|
<div id="mirror_grid" class="mirror button"></div>
|
|
</div>
|
|
<div class="subpanel rotational_mirrors">
|
|
<div id="mirror_rotational" class="mirror button"></div>
|
|
<input id="repeat_x" type="number" min="2" max="180" value="2" step="1" required />
|
|
</div>
|
|
`);
|
|
|
|
$('.grid_mirrors input').on("change",_=>{updateMirrorGridButtonImage(); updateMirrors()})
|
|
$('.rotational_mirrors input').on("change",_=>{updateMirrorRotationButtonImage(); updateMirrors()})
|
|
|
|
$('.mirror.button').on("mousedown", e=>{
|
|
e.currentTarget.classList.toggle('down');
|
|
updateMirrors();
|
|
})
|
|
updateMirrorGridButtonImage();
|
|
updateMirrorRotationButtonImage();
|
|
|
|
$("#newImageBtn").on("click", function() {
|
|
console.log("click #newImageBtn")
|
|
$("#newImageModal").addClass("modal-active");
|
|
});
|
|
|
|
$(".close-button").on("click", function() {
|
|
$(".modal-active").removeClass("modal-active");
|
|
});
|
|
|
|
$(".cancel-button").on("click", function() {
|
|
$(".modal-active").removeClass("modal-active");
|
|
});
|
|
|
|
$(".create-button").on("click", function() {
|
|
const width = $("#imageWidth").val();
|
|
const height = $("#imageHeight").val();
|
|
$(".modal-active").removeClass("modal-active");
|
|
let area=createDrawArea(blankCanvas(width, height))
|
|
$("#workspace").append(area.element);
|
|
});
|
|
|
|
$(window).on("click", function(event) {
|
|
if ($(event.target).hasClass("modal")) {
|
|
$(".modal-active").removeClass("modal-active");
|
|
}
|
|
});
|
|
|
|
$("#newImageForm").on("submit", function(event) {
|
|
event.preventDefault();
|
|
});
|
|
|
|
$(window).on("keydown", function(event) {
|
|
if (event.key === "Escape" && $(".modal-active").length) {
|
|
$(".modal-active").removeClass("modal-active");
|
|
}
|
|
});
|
|
|
|
|
|
|
|
window.test1=createDrawArea();
|
|
window.test2=createDrawArea();
|
|
|
|
$("#workspace").append(test1.element);
|
|
$("#workspace").append(test2.element);
|
|
|
|
|
|
brushSizeControl.diameter=tip.size;
|
|
|
|
setExportPic(test2);
|
|
setTool(feltTip)
|
|
}
|
|
|
|
function addNewImage(image) {
|
|
console.log( `addNewImage(${image.width},${image.height})`)
|
|
const target=blankCanvas(image.width,image.height)
|
|
var ctx = target.getContext('2d');
|
|
|
|
ctx.drawImage(image, 0, 0);
|
|
|
|
let result =createDrawArea(target);
|
|
$("#workspace").append(result.element);
|
|
//transmitCanvas(target);
|
|
return(result)
|
|
}
|
|
|
|
function addNewLayer(image) {
|
|
if (!selectedExport) {
|
|
setExportPic(addNewImage(image));
|
|
return;
|
|
}
|
|
let pic = selectedExport;
|
|
if (pic.canvas.width === image.width && pic.canvas.height==image.height) {
|
|
const layer = new Layer(pic,"generation",pic.canvas);
|
|
layer.ctx.drawImage(image,0,0);
|
|
console.log("new layer", layer)
|
|
console.log(layer.canvas.width, layer.canvas.height)
|
|
pic.insertLayer(layer)
|
|
pic.activeLayer=layer;
|
|
updateLayerList();
|
|
pic.updateVisualRepresentation(true);
|
|
}
|
|
else {
|
|
addNewImage(image);
|
|
}
|
|
}
|
|
|
|
function handleKeyDown(e) {
|
|
if (!e.key) return;
|
|
if (e.key === "Dead" ) return;
|
|
if (e.key === "Unidentified" ) return;
|
|
|
|
|
|
//checking for hotkeys
|
|
let keyID = "";
|
|
if (e.ctrlKey) keyID+="CTRL_"
|
|
if (e.altKey) keyID+="ALT_"
|
|
if (e.shiftKey) keyID+="SHIFT_"
|
|
let key = e.key.toUpperCase();
|
|
if (key === " ") key = 'SPACE';
|
|
|
|
const hotkeyCode= keyID+key;
|
|
console.log({hotkeyCode})
|
|
if (hotkeys.hasOwnProperty(hotkeyCode)) {
|
|
hotkeys[hotkeyCode]();
|
|
}
|
|
}
|
|
|
|
function handleMouseDown(e) {
|
|
//console.log("mousedown on pic",e);
|
|
let pic = e.currentTarget.pic;
|
|
setActivePic(pic);
|
|
|
|
const maskLayer = activePic.activeLayer.mask
|
|
|
|
|
|
switch (e.button) {
|
|
case 0:
|
|
tip.drawOperation = $("#foreground").data("eraser")?eraserTip:tool;
|
|
tip.colour = maskLayer?"#000":$("#foreground").val();
|
|
pic.startDraw(e.offsetX,e.offsetY);
|
|
createCaptureOverlay(e.currentTarget)
|
|
break;
|
|
case 1:
|
|
e.preventDefault();
|
|
dragStartX=pic.offsetX;
|
|
dragStartY=pic.offsetY;
|
|
mouseDownX=e.clientX;
|
|
mouseDownY=e.clientY;
|
|
$(e.currentTarget.pic.element).css("transition" , "none");
|
|
dragging = true;
|
|
createCaptureOverlay(e.currentTarget)
|
|
|
|
break;
|
|
case 2:
|
|
tip.colour = $("#background").val();
|
|
tip.drawOperation = (maskLayer || $("#background").data("eraser"))?eraserTip:tool;
|
|
pic.startDraw(e.offsetX,e.offsetY);
|
|
createCaptureOverlay(e.currentTarget)
|
|
|
|
e.preventDefault();
|
|
break;
|
|
}
|
|
}
|
|
function handleMouseLeave(e) {
|
|
const bounds=e.currentTarget.getBoundingClientRect();
|
|
let pic = e.currentTarget.pic;
|
|
if (dragging) {
|
|
|
|
console.log("mouseleave on pic",e);
|
|
let cx = e.pageX-bounds.left;
|
|
let cy = e.pageY-bounds.top;
|
|
console.log({cx,cy})
|
|
|
|
if (e.buttons!==4) {
|
|
stopDragging();
|
|
return;
|
|
}
|
|
var dx = e.clientX-mouseDownX;
|
|
var dy = e.clientY-mouseDownY;
|
|
console.log({dx,dy})
|
|
pic.offsetX=dragStartX+dx;
|
|
pic.offsetY=dragStartY+dy;
|
|
pic.setTransform();
|
|
}
|
|
|
|
}
|
|
|
|
function handleMouseUp(e) {
|
|
let pic = e.currentTarget.pic;
|
|
activePic = pic;
|
|
|
|
if (dragging) {
|
|
if (e.buttons!==4) {
|
|
stopDragging();
|
|
removeCaptureOverlay();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (pic.isDrawing && e.buttons === 0) {
|
|
removeCaptureOverlay();
|
|
pic.stopDraw(e.offsetX,e.offsetY)
|
|
}
|
|
|
|
}
|
|
function handleMouseMove(e) {
|
|
let pic = e.currentTarget.pic;
|
|
if (pic!==activePic) return;
|
|
if (dragging) {
|
|
if (e.buttons!==4) {
|
|
stopDragging();
|
|
removeCaptureOverlay();
|
|
return;
|
|
}
|
|
var dx = e.clientX-mouseDownX;
|
|
var dy = e.clientY-mouseDownY;
|
|
pic.offsetX=dragStartX+dx;
|
|
pic.offsetY=dragStartY+dy;
|
|
//console.log({dx,dy});
|
|
pic.setTransform();
|
|
}
|
|
|
|
if (pic.isDrawing && e.buttons === 0) {
|
|
removeCaptureOverlay();
|
|
pic.stopDraw(e.offsetX,e.offsetY)
|
|
}
|
|
if (pic.isDrawing) pic.draw(e.offsetX,e.offsetY);
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleMouseWheel(e) {
|
|
let direction = -Math.sign(e.deltaY);
|
|
const scaleAround= {x:e.pageX,y:e.pageY};
|
|
|
|
setScale(activePic.scalefactor+direction,scaleAround);
|
|
activePic.setTransform();
|
|
}
|
|
|
|
function scalefactorToSize(n) {
|
|
return (Math.pow(2,(n/2)));
|
|
}
|
|
|
|
function setScale(newfactor, around) {
|
|
const pic = activePic
|
|
const bounds = pic.element.getBoundingClientRect();
|
|
const parentBounds = pic.element.offsetParent.getBoundingClientRect();
|
|
const center = {x : bounds.x+bounds.width/2, y:bounds.y+bounds.width/2};
|
|
if (!insideBounds(around,bounds)) around = center;
|
|
const px = (around.x - bounds.left) /bounds.width;
|
|
const py = (around.y - bounds.top) / bounds.height;
|
|
|
|
const newScale = scalefactorToSize(newfactor);
|
|
const expectedWidth = pic.element.clientWidth*newScale
|
|
const expectedHeight = pic.element.clientHeight*newScale
|
|
|
|
|
|
|
|
const candidateX = around.x - expectedWidth *px - parentBounds.left;
|
|
const candidateY = around.y - expectedHeight *py - parentBounds.top;
|
|
|
|
pic.offsetX = candidateX
|
|
pic.offsetY = candidateY
|
|
|
|
pic.scale=newScale;
|
|
pic.scalefactor=newfactor;
|
|
pic.setTransform();
|
|
updateBrushCursor(pic.element);
|
|
}
|
|
|
|
function setTool(newValue) {
|
|
tool=newValue;
|
|
for (const pic of picStack) {
|
|
updateBrushCursor(pic.element)
|
|
}
|
|
|
|
$(".tool.button").removeClass("down");
|
|
|
|
for (let e of $(".tool.button")) {
|
|
e.classList.toggle("down",(e.tool === tool))
|
|
};
|
|
brushSizeControl.diameter=tip.size;
|
|
}
|
|
|
|
function pixelTip(ctx,toolInfo,strokePath) {
|
|
ctx.fillStyle=toolInfo.colour;
|
|
for (let {x,y} of strokePath) {
|
|
x=Math.floor(x-0.25);
|
|
y=Math.floor(y-0.25);
|
|
ctx.fillRect(x,y,1,1);
|
|
};
|
|
}
|
|
|
|
function eyeDropper(ctx,toolInfo,strokePath) {
|
|
const last = strokePath.at(-1);
|
|
let {x,y} = last;
|
|
x=Math.floor(x-0.25);
|
|
y=Math.floor(y-0.25);
|
|
let canvas = ctx.canvas;
|
|
if (x>=0 && y>=0 && x<canvas.width && y<canvas.height) {
|
|
const sample = activePic.canvas.ctx.getImageData(x,y,1,1).data;
|
|
const toHex = (byte) => byte.toString(16).padStart(2, '0');
|
|
const color= "#" + toHex(sample[0]) + toHex(sample[1]) + toHex(sample[2]);
|
|
$("#foreground").val(color)
|
|
}
|
|
}
|
|
|
|
function feltTip(ctx,toolInfo,strokePath) {
|
|
ctx.lineWidth=toolInfo.size;
|
|
ctx.strokeStyle=toolInfo.colour;
|
|
ctx.lineCap="round";
|
|
ctx.lineJoin="round";
|
|
ctx.beginPath();
|
|
for (let {x,y} of strokePath) {
|
|
ctx.lineTo(x,y);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
feltTip.cursorFunction = circleBrush;
|
|
|
|
|
|
function eraserTip(ctx,toolInfo,strokePath) {
|
|
ctx.save();
|
|
ctx.lineWidth=toolInfo.size;
|
|
ctx.strokeStyle="white";
|
|
ctx.lineCap="round";
|
|
ctx.lineJoin="round";
|
|
ctx.globalCompositeOperation="destination-out";
|
|
|
|
ctx.beginPath();
|
|
for (let {x,y} of strokePath) {
|
|
ctx.lineTo(x,y);
|
|
}
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
}
|
|
eraserTip.cursorFunction=circleBrush;
|
|
|
|
|
|
function pixelClear(ctx,toolInfo,strokePath) {
|
|
let x=Math.floor(toolInfo.x-0.25);
|
|
let y=Math.floor(toolInfo.y-0.25);
|
|
ctx.clearRect(x,y,1,1);
|
|
for (let {x,y} of strokePath) {
|
|
x=Math.floor(x-0.25);
|
|
y=Math.floor(y-0.25);
|
|
ctx.clearRect(x,y,1,1);
|
|
};
|
|
}
|
|
|
|
|
|
var mouseDownX=0;
|
|
var mouseDownY=0;
|
|
|
|
function stopDragging() {
|
|
dragging=false;
|
|
//console.log("stop dragging");
|
|
$(activePic.element).css("transition" , "");
|
|
}
|
|
|
|
function brushDiameterControl(id="diameter") {
|
|
const element = document.createElement("canvas");
|
|
element.className="diameter_control"
|
|
element.id=id;
|
|
const ctx = element.getContext("2d");
|
|
const max_diameter = 48;
|
|
element.width=192;
|
|
element.height=max_diameter;
|
|
//radius divider removes one radius for the half circle at the end of the control
|
|
const radiusDivider = (element.width/(max_diameter/2))-1;
|
|
|
|
let diameter = 15;
|
|
function redraw() {
|
|
let radius = diameter/2;
|
|
let max_radius = max_diameter/2;
|
|
ctx.clearRect(0,0,element.width,element.height);
|
|
ctx.beginPath();
|
|
ctx.arc(element.width-max_radius,max_radius,max_radius,-Math.PI/2,Math.PI/2);
|
|
ctx.lineTo(0,max_radius);
|
|
ctx.fillStyle="#8888";
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(radius*radiusDivider,max_radius,radius,0,Math.PI*2);
|
|
ctx.fillStyle="#000";
|
|
ctx.fill();
|
|
ctx.fillText(diameter,8,10);
|
|
}
|
|
|
|
Object.defineProperty(element, 'radius', {
|
|
get() { return this.diameter/2; },
|
|
set(value) {this.diameter=value*2}
|
|
});
|
|
|
|
Object.defineProperty(element, 'diameter', {
|
|
get() { return diameter; },
|
|
set(value) {
|
|
value=Math.round(value);
|
|
if (value<1) value=1;
|
|
if (value>max_diameter) value=max_diameter;
|
|
if (value !== diameter) {
|
|
diameter=value;
|
|
element.dispatchEvent(new Event("changed"));
|
|
}
|
|
redraw();
|
|
}
|
|
});
|
|
|
|
let dragging = false;
|
|
function handleMouseDown(e) {
|
|
if (e.button !==0 ) return
|
|
let {x,y} = containerToCanvas(element,e.clientX,e.clientY)
|
|
e.stopPropagation();
|
|
element.radius=x/radiusDivider;
|
|
dragging=(y>0) && (y<element.height);
|
|
}
|
|
function handleMouseMove(e) {
|
|
if (!dragging) return;
|
|
if (e.buttons === 0 )
|
|
{
|
|
dragging=false;
|
|
return
|
|
}
|
|
|
|
let {x,y} = containerToCanvas(element,e.clientX,e.clientY)
|
|
|
|
element.radius=x/radiusDivider;
|
|
|
|
}
|
|
function handleMouseUp(e) {
|
|
if (e.button !==0 ) return
|
|
dragging=false;
|
|
}
|
|
|
|
element.addEventListener("mousedown", handleMouseDown,{capture:true});
|
|
element.addEventListener("mousemove", handleMouseMove);
|
|
element.addEventListener("mouseup", handleMouseUp);
|
|
|
|
|
|
redraw();
|
|
return element
|
|
}
|
|
|
|
|
|
function poulateLayerControl() {
|
|
const layer_control = document.querySelector("#layer_control");
|
|
const content = $(`
|
|
<div class="layer-attributes">
|
|
<div class="imageLayer">
|
|
<select name="composite-node" class="composite-mode">
|
|
<option value="source-over">Standard Color</option>
|
|
<option value="lighter">Lighter</option>
|
|
<option value="multiply">Multiply</option>
|
|
<option value="screen">Screen</option>
|
|
<option value="overlay">Overlay</option>
|
|
<option value="lighten">Lighten</option>
|
|
<option value="darken">Darken</option>
|
|
<option value="color-dodge">Color Dodge</option>
|
|
<option value="color-burn">Color Burn</option>
|
|
<option value="hard-light">Hard Light</option>
|
|
<option value="soft-light">Soft Light</option>
|
|
<option value="difference">Difference</option>
|
|
<option value="exclusion">Exclusion</option>
|
|
<option value="hue">Hue</option>
|
|
<option value="saturation">Saturation</option>
|
|
<option value="color">Color</option>
|
|
<option value="luminosity">Luminosity</option>
|
|
</select>
|
|
</div>
|
|
<div class="maskLayer">
|
|
<input type="color" class="maskColor" value="#402040" />
|
|
</div>
|
|
<div>
|
|
<label for="opacity">Opacity</label>
|
|
<input type="range" class="opacity" name="opacity" min="0" max="100" step="1" value="100" />
|
|
</div>
|
|
</div>
|
|
<div class="layer_list layer_dropzone">
|
|
|
|
</div>
|
|
<div class="layer_actions">
|
|
<div class="add_layer" title="New empty layer"></div>
|
|
<div class="duplicate_layer" title="duplicate current layer. Ctrl duplicate target layer"></div>
|
|
<div class="remove_layer" title="delete selected layer"></div>
|
|
</div>
|
|
`)
|
|
|
|
$(layer_control).append(content);
|
|
|
|
$('select.composite-mode').on("change", e=>{
|
|
const mode = e.currentTarget.value;
|
|
activePic.activeLayer.compositeOperation=mode;
|
|
activePic.updateVisualRepresentation(true);
|
|
})
|
|
$("input.maskColor").on("change", e=>{
|
|
lastUsedMaskColor = e.currentTarget.value;
|
|
activePic.activeLayer.maskColor=lastUsedMaskColor;
|
|
activePic.updateVisualRepresentation(false);
|
|
updateLayerList();
|
|
});
|
|
|
|
$("input.opacity").on("input", e=> {
|
|
activePic.activeLayer.opacity = e.currentTarget.value/100;
|
|
activePic.updateVisualRepresentation(true);
|
|
//shouldn't need this
|
|
updateLayerList();
|
|
});
|
|
|
|
$(".add_layer").on("click",_=>{
|
|
activePic.activeLayer = activePic.addEmptyLayer();
|
|
updateLayerList();
|
|
});
|
|
|
|
$(".duplicate_layer").on("click",e=>{
|
|
const layer = e.ctrlKey?targetLayer:activePic.activeLayer;
|
|
activePic.activeLayer = activePic.addDuplicateLayer(layer);
|
|
activePic.updateVisualRepresentation();
|
|
updateLayerList();
|
|
});
|
|
|
|
$(".remove_layer").on("click",_=>{
|
|
activePic?.removeLayer(activePic.activeLayer)
|
|
activePic?.updateVisualRepresentation(true)
|
|
updateLayerList();
|
|
});
|
|
|
|
let dropzone =$(".layer_list.layer_dropzone");
|
|
dropzone.on("dragover",e=>{
|
|
dropzone.children().removeClass("insert_after");
|
|
dropzone.removeClass("insert_top")
|
|
const insertPoint=findInsertPoint(dropzone[0],e);
|
|
if (insertPoint !== draggingElement) {
|
|
if (insertPoint) {
|
|
insertPoint.classList.add("insert_after");
|
|
} else {
|
|
dropzone.addClass("insert_top")
|
|
}
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
dropzone.on("dragleave", e=>{
|
|
dropzone.removeClass("insert_top")
|
|
dropzone.children().removeClass("insert_after");
|
|
});
|
|
dropzone.on("drop", e=>{
|
|
dropzone.removeClass("insert_top")
|
|
const insertPoint=findInsertPoint(dropzone[0],e);
|
|
if (insertPoint !== draggingElement) {
|
|
if (draggingElement) {
|
|
const layer = draggingElement.layer;
|
|
const below = insertPoint?.layer;
|
|
activePic.insertLayer(layer,below);
|
|
updateLayerList();
|
|
activePic.updateVisualRepresentation(true);
|
|
} else {
|
|
//handle file drop maybe?
|
|
}
|
|
|
|
}
|
|
})
|
|
}
|
|
|
|
function findInsertPoint(dropzone, event) {
|
|
const dropzoneRect = dropzone.getBoundingClientRect();
|
|
const children = Array.from(dropzone.children);
|
|
|
|
// Normalize the event clientY coordinate to the dropzone's coordinate space
|
|
const eventClientY = event.clientY - dropzoneRect.top;
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
const child = children[i];
|
|
const childRect = child.getBoundingClientRect();
|
|
const childTop = childRect.top - dropzoneRect.top;
|
|
const childBottom = childTop + childRect.height;
|
|
|
|
if (eventClientY >= childTop && eventClientY <= childBottom) {
|
|
if (child === draggingElement) {
|
|
return draggingElement;
|
|
}
|
|
if (eventClientY < childTop + childRect.height / 2) {
|
|
// Event is in the top half of the child element
|
|
return i === 0 ? null : children[i - 1];
|
|
} else {
|
|
// Event is in the bottom half of the child element
|
|
return child;
|
|
}
|
|
}
|
|
}
|
|
|
|
return children.length > 0 ? children[children.length - 1] : null;
|
|
}
|
|
|
|
function updateLayerTitle(newTitle, layer) {
|
|
layer.title = newTitle;
|
|
updateLayerList();
|
|
activePic.updateVisualRepresentation(true);
|
|
}
|
|
|
|
function updateLayerList() {
|
|
function makeLayerWidget(layer) {
|
|
const pic=activePic;
|
|
const result = $(`<div class="layer_widget ${layer===pic.activeLayer?'active':''} ${targetLayer==layer?'target':''}" draggable="true">
|
|
<div class= "visibilitybox ${layer.visible?'showing':''}"> </div>
|
|
<canvas class="thumbnail" width="32" height="32"> </canvas>
|
|
<div class="layer_name"> ${layer.title} </div>
|
|
${layer.mask?`<div class="checkbox ${pic.mask===layer?'checked':''}"></div>`:''}
|
|
</div>
|
|
`)[0];
|
|
|
|
window.dummyGlobal=result;
|
|
const canvas = result.querySelector(".thumbnail");
|
|
const ctx=canvas.getContext("2d");
|
|
ctx.drawImage(layer.canvas,0,0,canvas.width,canvas.height);
|
|
result.layer=layer;
|
|
|
|
// Renaming handler
|
|
const layerNameDiv = result.querySelector('.layer_name');
|
|
layerNameDiv.ondblclick = function() {
|
|
const input = document.createElement('input');
|
|
input.type = 'text';
|
|
input.value = layer.title;
|
|
input.className = 'layer_name_input';
|
|
input.onblur = () => updateLayerTitle(input.value, layer);
|
|
input.onkeydown = function(e) {
|
|
if (e.key === 'Backspace') {
|
|
e.stopPropagation();
|
|
}
|
|
if (e.key === 'Enter') {
|
|
updateLayerTitle(input.value, layer);
|
|
e.preventDefault();
|
|
} else if (e.key === 'Escape') {
|
|
layerNameDiv.textContent = layer.title;
|
|
}
|
|
};
|
|
layerNameDiv.textContent = '';
|
|
layerNameDiv.appendChild(input);
|
|
input.focus();
|
|
};
|
|
|
|
|
|
|
|
result.querySelector(".visibilitybox").onmousedown = e => {
|
|
e.stopPropagation();
|
|
layer.visible=!layer.visible;
|
|
pic.updateVisualRepresentation(true);
|
|
updateLayerList();
|
|
}
|
|
result.addEventListener("contextmenu", e=>e.preventDefault())
|
|
|
|
result.addEventListener('mousedown', (e) => {
|
|
// Check for non-mask layer, middle mouse button click or left click with Ctrl key
|
|
if (!layer.mask && (e.button === 2) ) {
|
|
targetLayer = (targetLayer === layer) ? null : layer;
|
|
e.preventDefault();
|
|
updateLayerList();
|
|
}
|
|
});
|
|
|
|
if (layer.mask) {
|
|
result.querySelector(".checkbox").onmousedown = e => {
|
|
e.stopPropagation();
|
|
pic.mask= (pic.mask===layer) ? null : layer;
|
|
updateLayerList();
|
|
}
|
|
}
|
|
result.onmousedown = e=> {
|
|
if (e.ctrlKey && e.button==0 && pic.activeLayer?.visible) {
|
|
pic.removeLayer(layer);
|
|
pic.updateVisualRepresentation(true)
|
|
updateLayerList();
|
|
} else
|
|
if (e.button==0) {
|
|
if (pic.activeLayer !== layer) {
|
|
pic.activeLayer=layer;
|
|
updateLayerList()
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
result.ondrag = e=>{
|
|
draggingElement=e.currentTarget;
|
|
}
|
|
result.ondragend = e=> {
|
|
if (draggingElement === e.currentTarget ) draggingElement=null;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
|
|
const layer_control=document.querySelector("#layer_control")
|
|
const layer_list =layer_control.querySelector(".layer_list")
|
|
while(layer_list.firstChild) layer_list.removeChild(layer_list.firstChild)
|
|
|
|
if (!activePic) return;
|
|
|
|
const newControls = activePic.layers.map(makeLayerWidget);
|
|
|
|
newControls.reverse().forEach(element=>layer_list.appendChild(element))
|
|
|
|
$(".layer-attributes").toggleClass("mask", activePic.activeLayer.mask)
|
|
|
|
lastUsedMaskColor=activePic.activeLayer.maskColor;
|
|
$("input.maskColor").val(lastUsedMaskColor)
|
|
$("input.opacity").val((activePic.activeLayer.opacity*100)|0)
|
|
$('select.composite-mode').val(activePic.activeLayer.compositeOperation);
|
|
|
|
|
|
}
|
|
|
|
|
|
function circleBrush(scale) {
|
|
let newCursor = circleImage(tip.size * scale);
|
|
let offset = (newCursor.width/2)|0;
|
|
let value = `url(${newCursor.toDataURL()}) ${offset} ${offset}, auto `;
|
|
if ((newCursor.width < 2) || (newCursor.width>120)) value="crosshair";
|
|
return value;
|
|
}
|
|
|
|
function updateBrushCursor(picElement) {
|
|
const p=picElement;
|
|
if (!p) return;
|
|
let scale = parseFloat(p.style.getPropertyValue("--scalefactor"));
|
|
let value ="crosshair";
|
|
if (tool.cursorFunction) value = tool.cursorFunction(scale);
|
|
p.style.setProperty("cursor",value);
|
|
}
|
|
|
|
|
|
function cloneEvent(event, modifications = {}) {
|
|
let eventProperties = {
|
|
"bubbles": event.bubbles,
|
|
"cancelBubble": event.cancelBubble,
|
|
"cancelable": event.cancelable,
|
|
"composed": event.composed,
|
|
"currentTarget": event.currentTarget,
|
|
"defaultPrevented": event.defaultPrevented,
|
|
"eventPhase": event.eventPhase,
|
|
"isTrusted": event.isTrusted,
|
|
"target": event.target,
|
|
"timeStamp": event.timeStamp,
|
|
"type": event.type,
|
|
};
|
|
|
|
// Checking if the event is a MouseEvent, add mouse event properties
|
|
if(event instanceof MouseEvent) {
|
|
eventProperties = {
|
|
...eventProperties,
|
|
"altKey": event.altKey,
|
|
"button": event.button,
|
|
"buttons": event.buttons,
|
|
"clientX": event.clientX,
|
|
"clientY": event.clientY,
|
|
"ctrlKey": event.ctrlKey,
|
|
"metaKey": event.metaKey,
|
|
"movementX": event.movementX,
|
|
"movementY": event.movementY,
|
|
"offsetX": event.offsetX,
|
|
"offsetY": event.offsetY,
|
|
"pageX": event.pageX,
|
|
"pageY": event.pageY,
|
|
"relatedTarget": event.relatedTarget,
|
|
"screenX": event.screenX,
|
|
"screenY": event.screenY,
|
|
"shiftKey": event.shiftKey,
|
|
};
|
|
}
|
|
|
|
return { ...modifications, ...eventProperties };
|
|
}
|
|
|
|
function forwardEvent(mouseEvent, recipient) {
|
|
var rect = recipient.getBoundingClientRect();
|
|
|
|
const options = cloneEvent(mouseEvent,{
|
|
clientX:mouseEvent.clientX - rect.left,
|
|
clientY:mouseEvent.clientY - rect.top,
|
|
});
|
|
|
|
var newEvent = new MouseEvent(mouseEvent.type, options )
|
|
|
|
recipient.dispatchEvent(newEvent);
|
|
}
|
|
|
|
function createCaptureOverlay(element) {
|
|
var overlay = document.createElement('div');
|
|
overlay.id = 'global_overlay';
|
|
overlay.className = 'fillparent';
|
|
|
|
document.body.appendChild(overlay);
|
|
|
|
var forwardMouseEvent = function(e) {
|
|
if (e.buttons==0) removeCaptureOverlay();
|
|
forwardEvent(e, element);
|
|
};
|
|
|
|
['mousedown', 'mouseup', 'mousemove', 'click', 'dblclick'].forEach(function(eventName) {
|
|
overlay.addEventListener(eventName, forwardMouseEvent);
|
|
});
|
|
overlay.addEventListener("contextmenu",function(e){e.preventDefault(); return false;});
|
|
}
|
|
|
|
function removeCaptureOverlay() {
|
|
$('#global_overlay').remove();
|
|
}
|
|
|
|
|
|
|
|
function fillOrClear(from) {
|
|
if (from.data("eraser")){
|
|
activePic?.clearLayer()
|
|
} else {
|
|
activePic?.fillLayer(from.val());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
hotkeys["CTRL_Z"] = _=>{activePic?.undo()}
|
|
hotkeys["CTRL_SHIFT_Z"] = _=>{activePic?.redo()}
|
|
hotkeys["BACKSPACE"] = _=>{activePic?.clearLayer()}
|
|
|
|
hotkeys["ALT_BACKSPACE"] = _=>{fillOrClear($("#foreground"))}
|
|
hotkeys["CTRL_BACKSPACE"] = _=>{fillOrClear($("#background"))}
|
|
|
|
hotkeys["P"] = _=>{setTool(eyeDropper);}
|
|
hotkeys["B"] = _=>{setTool(feltTip);}
|
|
hotkeys["Z"] = _=>{setTool(pixelTip);}
|
|
hotkeys["E"] = _=>{setTool(eraserTip);}
|
|
|
|
hotkeys["]"] = _=>{brushSizeControl.diameter+=1}
|
|
hotkeys["["] = _=>{brushSizeControl.diameter-=1;}
|
|
|