Big restructure to support layer transformations
This commit is contained in:
+156
-109
@@ -52,6 +52,7 @@ function setActivePic(newValue) {
|
||||
if (!activePic) return;
|
||||
activePic.bringToFront();
|
||||
|
||||
if (typeof tool.drawUI === "function") tool.drawUI();
|
||||
}
|
||||
|
||||
var dragging = false;
|
||||
@@ -88,6 +89,9 @@ function closePic(pic) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
var lastUsedMaskColor = "#402040";
|
||||
|
||||
class Layer {
|
||||
@@ -104,6 +108,9 @@ class Layer {
|
||||
this.canvas.height=height;
|
||||
this.mask = mask;
|
||||
this.title= title;
|
||||
this.transform = [1,0 ,0,1, 0,0];
|
||||
this.rotationCenter= {x:width/2,y:height/2};
|
||||
|
||||
if (mask) {
|
||||
this.compositeOperation="source-over";
|
||||
}
|
||||
@@ -111,6 +118,12 @@ class Layer {
|
||||
get maskColor() {
|
||||
return this._maskColor;
|
||||
}
|
||||
get width() {
|
||||
return this.canvas.width;
|
||||
}
|
||||
get height() {
|
||||
return this.canvas.height;
|
||||
}
|
||||
set maskColor(newValue) {
|
||||
this._maskColor=newValue;
|
||||
if (this.mask) {
|
||||
@@ -121,6 +134,58 @@ class Layer {
|
||||
this.ctx.restore();
|
||||
}
|
||||
}
|
||||
draw(ctx) {
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation=this.compositeOperation;
|
||||
ctx.globalAlpha=this.opacity;
|
||||
ctx.setTransform(...this.transform);
|
||||
ctx.drawImage(this.canvas);
|
||||
ctx.restore();
|
||||
}
|
||||
convertFromPicCoords(x, y) {
|
||||
return reverseTransform({x,y},this.transform)
|
||||
}
|
||||
// Converts coordinates from this layer's coordinates to the picture's canvas
|
||||
convertToPicCoords(x, y) {
|
||||
return applyTransform({x,y},this.transform);
|
||||
}
|
||||
|
||||
rotate(angle) {
|
||||
const radians = angle * Math.PI / 180;
|
||||
const cos = Math.cos(radians);
|
||||
const sin = Math.sin(radians);
|
||||
|
||||
// Convert relative coordinates to absolute
|
||||
const centerX = this.rotationCenter.x;
|
||||
const centerY = this.rotationCenter.y;
|
||||
|
||||
// Create a translation matrix to move the rotation point to (0,0)
|
||||
const translateToOrigin = [1, 0, 0, 1, -centerX, -centerY];
|
||||
|
||||
// Create a rotation matrix
|
||||
const rotation = [cos, sin, -sin, cos, 0, 0];
|
||||
|
||||
// Create a translation matrix to move back from (0,0)
|
||||
const translateBack = [1, 0, 0, 1, centerX, centerY];
|
||||
|
||||
// Concatenate the transforms: translate back -> rotate -> translate to origin
|
||||
let combined = concatenateTransforms(translateBack, rotation);
|
||||
combined = concatenateTransforms(combined, translateToOrigin);
|
||||
|
||||
// Apply to the current transform
|
||||
this.transform = concatenateTransforms(this.transform, combined);
|
||||
}
|
||||
|
||||
translate(dx, dy) {
|
||||
const newTransform = [1, 0, 0, 1, dx, dy];
|
||||
this.transform = concatenateTransforms(this.transform, newTransform);
|
||||
}
|
||||
|
||||
scale(sx, sy = sx) {
|
||||
const newTransform = [sx, 0, 0, sy, 0, 0];
|
||||
this.transform = concatenateTransforms(this.transform, newTransform);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function updateMirrorGridButtonImage() {
|
||||
@@ -161,6 +226,17 @@ function updateMirrors() {
|
||||
mirrorFunction=result;
|
||||
}
|
||||
|
||||
function getDrawAreaAtPoint(uiX, uiY) {
|
||||
for (let pic of picStack) {
|
||||
const {x,y} = convertCoordsFromUIToPic(uiX, uiY, pic);
|
||||
|
||||
if (x >= 0 && x <= pic.width && y >= 0 && y <= pic.height) {
|
||||
return pic;
|
||||
}
|
||||
}
|
||||
return null; // No drawArea found at the point
|
||||
}
|
||||
|
||||
function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
const undoDepth = 10;
|
||||
const element = document.createElement("div")
|
||||
@@ -214,6 +290,8 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
get canvas() {return canvas},
|
||||
get mask() {return mask},
|
||||
set mask(m) {mask=m},
|
||||
get width() {return canvas.width},
|
||||
get height() {return canvas.height},
|
||||
scale:1,
|
||||
scalefactor:0,
|
||||
offsetX:0,
|
||||
@@ -222,7 +300,7 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
activeLayer:null,
|
||||
strokeCoordinates :[],
|
||||
strokeModifier: a=>a,
|
||||
setTransform( ) {
|
||||
setCSSTransform( ) {
|
||||
const {element,scale,offsetX,offsetY} = this;
|
||||
element.style.setProperty("--scalefactor",scale)
|
||||
element.style.setProperty("--translateX",offsetX+"px")
|
||||
@@ -231,7 +309,7 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
setPosition(x,y) {
|
||||
this.offsetX=x;
|
||||
this.offsetY=y;
|
||||
this.setTransform();
|
||||
this.setCSSTransform();
|
||||
},
|
||||
bringToFront() {
|
||||
const oldPos = picStack.indexOf(this);
|
||||
@@ -258,6 +336,7 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
if (this.isDrawing && layer===this.activeLayer) {
|
||||
canvas.ctx.drawImage(activeOperationCanvas,0,0);
|
||||
} else {
|
||||
canvas.ctx.setTransform(...layer.transform);
|
||||
canvas.ctx.drawImage(layer.canvas,0,0);
|
||||
|
||||
}
|
||||
@@ -270,6 +349,7 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
const undoRecord = {
|
||||
type : "layer",
|
||||
layer: this.activeLayer,
|
||||
transform: [...this.activeLayer.transform],
|
||||
data: this.activeLayer.ctx.getAllImageData()
|
||||
}
|
||||
undoStack.push(undoRecord);
|
||||
@@ -277,6 +357,8 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
redoStack.length=0;
|
||||
|
||||
this.activeLayer.ctx.putImageData(data,0,0);
|
||||
this.activeLayer.transform= [1,0 ,0,1, 0,0];
|
||||
|
||||
this.isDrawing = false;
|
||||
if (selectedExport===this) {
|
||||
if (this.activeLayer===this.mask) {
|
||||
@@ -337,9 +419,12 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
if (!updatingStroke) {
|
||||
updatingStroke=true;
|
||||
setTimeout(_=>{
|
||||
activeOperationCanvas.ctx.clearRect(0,0,activeOperationCanvas.width,activeOperationCanvas.height);
|
||||
activeOperationCanvas.ctx.drawImage(this.activeLayer.canvas,0,0);
|
||||
|
||||
const ctx = activeOperationCanvas.ctx;
|
||||
ctx.clearRect(0,0,activeOperationCanvas.width,activeOperationCanvas.height);
|
||||
ctx.save()
|
||||
ctx.setTransform(...this.activeLayer.transform);
|
||||
ctx.drawImage(this.activeLayer.canvas,0,0);
|
||||
ctx.restore();
|
||||
const unitRange=this.strokeCoordinates.map(({x,y})=>({x:x/canvas.width,y:y/canvas.height}));
|
||||
|
||||
const strokes = this.strokeModifier([unitRange])
|
||||
@@ -377,63 +462,35 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
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() {
|
||||
|
||||
addEmptyLayer(above = this.activeLayer) {
|
||||
const newLayer = new Layer(this,"new layer", canvas);
|
||||
const newList = [...layers, newLayer];
|
||||
this.updateLayerList(newList);
|
||||
this.insertLayerAbove(newLayer, above);
|
||||
this.activeLayer = newLayer;
|
||||
return newLayer;
|
||||
},
|
||||
addDuplicateLayer(layer) {
|
||||
addDuplicateLayer(layer,above=layer) {
|
||||
if (!layer) {
|
||||
return this.addEmptyLayer();
|
||||
} else {
|
||||
if (layers.indexOf(above) < 0) above=this.activeLayer;
|
||||
// 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.transform=[...layer.transform];
|
||||
newLayer.visible = layer.visible;
|
||||
newLayer.compositeOperation = layer.compositeOperation;
|
||||
newLayer.opacity = layer.opacity;
|
||||
const newList = [...this.layers, newLayer];
|
||||
this.updateLayerList(newList);
|
||||
this.insertLayerAbove(newLayer, above);
|
||||
this.activeLayer = newLayer;
|
||||
return newLayer;
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
removeLayer(layer = this.activeLayer) {
|
||||
const newList = layers.filter(l => l !== layer);
|
||||
this.updateLayerList(newList);
|
||||
},
|
||||
insertLayer(layer, below = null) {
|
||||
insertLayerBelow(layer, below = null) {
|
||||
const newList = [...layers];
|
||||
const indexExisting = newList.indexOf(layer);
|
||||
if (indexExisting !== -1) {
|
||||
@@ -446,7 +503,21 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
newList.push(layer);
|
||||
}
|
||||
this.updateLayerList(newList);
|
||||
},
|
||||
},
|
||||
insertLayerAbove(layer, above = null) {
|
||||
const newList = [...this.layers];
|
||||
const indexExisting = newList.indexOf(layer);
|
||||
if (indexExisting !== -1) {
|
||||
newList.splice(indexExisting, 1);
|
||||
}
|
||||
const indexAbove = newList.indexOf(above);
|
||||
if (indexAbove !== -1) {
|
||||
newList.splice(indexAbove + 1, 0, layer);
|
||||
} else {
|
||||
newList.unshift(layer); // Inserts at the top if 'above' layer is not found
|
||||
}
|
||||
this.updateLayerList(newList);
|
||||
},
|
||||
undo() {
|
||||
const lastRecord = undoStack.pop();
|
||||
if (lastRecord) {
|
||||
@@ -458,9 +529,14 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
redoStack.push({
|
||||
type: 'layer',
|
||||
layer: lastRecord.layer,
|
||||
transform : [...lastRecord.layer.transform],
|
||||
data: lastRecord.layer.ctx.getAllImageData()
|
||||
});
|
||||
lastRecord.layer.ctx.putImageData(lastRecord.data, 0, 0);
|
||||
const ctx = lastRecord.layer.ctx;
|
||||
ctx.save();
|
||||
ctx.setTransform(...lastRecord.transform);
|
||||
ctx.putImageData(lastRecord.data, 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
//... (handle other types of undoRecords)
|
||||
}
|
||||
@@ -479,6 +555,7 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
undoStack.push({
|
||||
type: 'layer',
|
||||
layer: lastRecord.layer,
|
||||
transform: [...lastRecord.layer.transform],
|
||||
data: lastRecord.layer.ctx.getAllImageData()
|
||||
});
|
||||
lastRecord.layer.ctx.putImageData(lastRecord.data, 0, 0);
|
||||
@@ -531,7 +608,7 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
})
|
||||
});
|
||||
|
||||
pic.setTransform();
|
||||
pic.setCSSTransform();
|
||||
setActivePic(pic)
|
||||
return pic;
|
||||
|
||||
@@ -556,8 +633,11 @@ function createDrawArea(canvas = blankCanvas(),initialTitle="Image") {
|
||||
|
||||
|
||||
function initPaint(){
|
||||
$("#background").val("#ffffff").data("eraser",true);
|
||||
|
||||
// Call this function at initialization
|
||||
window.uiCanvas = initUICanvas();
|
||||
|
||||
$("#background").val("#ffffff").data("eraser",true);
|
||||
var palette=$("#palette");
|
||||
for (let i of initialPalette) {
|
||||
const e=`<div class="paletteentry" style="background-color:${i}"> </div>`;
|
||||
@@ -606,6 +686,7 @@ function initPaint(){
|
||||
$("#eraser")[0].tool=eraserTip;
|
||||
$("#fine_eraser")[0].tool=pixelClear;
|
||||
$("#eyedropper")[0].tool=eyeDropper;
|
||||
$("#transform")[0].tool=transformTool;
|
||||
|
||||
$(".tool.button").on("click",function(e) {setTool(e.currentTarget.tool);});
|
||||
|
||||
@@ -736,7 +817,7 @@ function addNewLayer(image) {
|
||||
layer.ctx.drawImage(image,0,0);
|
||||
console.log("new layer", layer)
|
||||
console.log(layer.canvas.width, layer.canvas.height)
|
||||
pic.insertLayer(layer)
|
||||
pic.insertLayerBelow(layer)
|
||||
pic.activeLayer=layer;
|
||||
updateLayerList();
|
||||
pic.updateVisualRepresentation(true);
|
||||
@@ -826,7 +907,7 @@ function handleMouseLeave(e) {
|
||||
console.log({dx,dy})
|
||||
pic.offsetX=dragStartX+dx;
|
||||
pic.offsetY=dragStartY+dy;
|
||||
pic.setTransform();
|
||||
pic.setCSSTransform();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -862,7 +943,7 @@ function handleMouseMove(e) {
|
||||
var dy = e.clientY-mouseDownY;
|
||||
pic.offsetX=dragStartX+dx;
|
||||
pic.offsetY=dragStartY+dy;
|
||||
pic.setTransform();
|
||||
pic.setCSSTransform();
|
||||
}
|
||||
|
||||
if (pic.isDrawing && e.buttons === 0) {
|
||||
@@ -880,7 +961,7 @@ function handleMouseWheel(e) {
|
||||
const scaleAround= {x:e.pageX,y:e.pageY};
|
||||
|
||||
setScale(activePic.scalefactor+direction,scaleAround);
|
||||
activePic.setTransform();
|
||||
activePic.setCSSTransform();
|
||||
}
|
||||
|
||||
function scalefactorToSize(n) {
|
||||
@@ -910,12 +991,20 @@ function setScale(newfactor, around) {
|
||||
|
||||
pic.scale=newScale;
|
||||
pic.scalefactor=newfactor;
|
||||
pic.setTransform();
|
||||
pic.setCSSTransform();
|
||||
updateBrushCursor(pic.element);
|
||||
}
|
||||
|
||||
function setTool(newValue) {
|
||||
tool=newValue;
|
||||
if (newValue !== tool)
|
||||
{
|
||||
tool=newValue;
|
||||
const ctx=uiCanvas.getContext("2d");
|
||||
ctx.clearRect(0,0,1e5,1e5);
|
||||
if (typeof tool?.init === "function") tool.init();
|
||||
}
|
||||
|
||||
|
||||
for (const pic of picStack) {
|
||||
updateBrushCursor(pic.element)
|
||||
}
|
||||
@@ -926,6 +1015,8 @@ function setTool(newValue) {
|
||||
e.classList.toggle("down",(e.tool === tool))
|
||||
};
|
||||
brushSizeControl.diameter=tip.size;
|
||||
uiCanvas.style.pointerEvents=tool?.eventHandlers?"":"none";
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1124,7 +1215,7 @@ function poulateLayerControl() {
|
||||
if (draggingElement) {
|
||||
const layer = draggingElement.layer;
|
||||
const below = insertPoint?.layer;
|
||||
activePic.insertLayer(layer,below);
|
||||
activePic.insertLayerBelow(layer,below);
|
||||
updateLayerList();
|
||||
activePic.updateVisualRepresentation(true);
|
||||
} else {
|
||||
@@ -1185,7 +1276,10 @@ function updateLayerList() {
|
||||
window.dummyGlobal=result;
|
||||
const canvas = result.querySelector(".thumbnail");
|
||||
const ctx=canvas.getContext("2d");
|
||||
ctx.save()
|
||||
ctx.setTransform(...layer.transform);
|
||||
ctx.drawImage(layer.canvas,0,0,canvas.width,canvas.height);
|
||||
ctx.restore();
|
||||
result.layer=layer;
|
||||
|
||||
// Renaming handler
|
||||
@@ -1291,63 +1385,11 @@ function updateBrushCursor(picElement) {
|
||||
let value ="crosshair";
|
||||
if (tool.cursorFunction) value = tool.cursorFunction(scale);
|
||||
p.style.setProperty("cursor",value);
|
||||
//set matching cursor for user interface overlay;
|
||||
uiCanvas.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');
|
||||
@@ -1406,3 +1448,8 @@ hotkeys["E"] = _=>{setTool(eraserTip);}
|
||||
hotkeys["]"] = _=>{brushSizeControl.diameter+=1}
|
||||
hotkeys["["] = _=>{brushSizeControl.diameter-=1;}
|
||||
|
||||
|
||||
hotkeys["Q"] = _=>{
|
||||
|
||||
drawPicFramesOnUICanvas(uiCanvas, picStack);
|
||||
}
|
||||
|
||||
+6
-5
@@ -24,11 +24,12 @@
|
||||
<option value="ignore">Ignore</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="pen" title="brush 🇧" class="tool button"></div>
|
||||
<div id="eraser" title="eraser 🇪" class="tool button" style="background-image:url(images/eraser.png);"></div>
|
||||
<div id="pixels" title="pixels 🇿" class="tool button" style="background-image:url(images/pixels.png);"></div>
|
||||
<div id="fine_eraser" title="clear pixels" class="tool button" style="background-image:url(images/fine_eraser.png);"></div>
|
||||
<div id="eyedropper" title="sample color 🇵" class="tool button" ></div>
|
||||
<div id="pen" title="Brush 🇧" class="tool button"></div>
|
||||
<div id="eraser" title="Eraser 🇪" class="tool button" style="background-image:url(images/eraser.png);"></div>
|
||||
<div id="pixels" title="Pixels 🇿" class="tool button" style="background-image:url(images/pixels.png);"></div>
|
||||
<div id="fine_eraser" title="Clear pixels" class="tool button" style="background-image:url(images/fine_eraser.png);"></div>
|
||||
<div id="eyedropper" title="Sample color 🇵" class="tool button" ></div>
|
||||
<div id="transform" title="Tansform Layer 🇹" class="tool button" ></div>
|
||||
<div class="subpanel actions">
|
||||
<div id="clear" class="action button" ></div>
|
||||
<div id="undo" class="action button" ></div>
|
||||
|
||||
@@ -1,3 +1,130 @@
|
||||
'use strict';
|
||||
|
||||
function convertCoordsFromPicToUI(x,y, pic) {
|
||||
const picRect = pic.element.getBoundingClientRect();
|
||||
const workspaceRect = document.getElementById('workspace').getBoundingClientRect();
|
||||
|
||||
// Scale the point
|
||||
const scaledX = x * pic.scale;
|
||||
const scaledY = y * pic.scale;
|
||||
|
||||
// Translate the point based on the pic's position in the workspace
|
||||
const uiX = picRect.left - workspaceRect.left + scaledX;
|
||||
const uiY = picRect.top - workspaceRect.top + scaledY;
|
||||
|
||||
return { x: uiX, y: uiY };
|
||||
}
|
||||
|
||||
function convertCoordsFromUIToPic(x,y, pic) {
|
||||
const picRect = pic.element.getBoundingClientRect();
|
||||
const workspaceRect = document.getElementById('workspace').getBoundingClientRect();
|
||||
|
||||
// Translate the point to the pic's coordinate system
|
||||
const picX = x - (picRect.left - workspaceRect.left);
|
||||
const picY = y - (picRect.top - workspaceRect.top);
|
||||
|
||||
// Scale the point based on the pic's scale
|
||||
const scaledX = picX / pic.scale;
|
||||
const scaledY = picY / pic.scale;
|
||||
|
||||
return { x: scaledX, y: scaledY };
|
||||
}
|
||||
|
||||
|
||||
function initUICanvas() {
|
||||
const uiCanvas = document.createElement('canvas');
|
||||
uiCanvas.id = 'uiCanvas';
|
||||
uiCanvas.style.position = 'absolute';
|
||||
uiCanvas.style.left = '0';
|
||||
uiCanvas.style.top = '0';
|
||||
uiCanvas.style.zIndex = '101'; // Ensure it's above other elements
|
||||
|
||||
const workspace = document.getElementById('workspace');
|
||||
workspace.appendChild(uiCanvas);
|
||||
|
||||
const resizeCanvas = () => {
|
||||
uiCanvas.width = workspace.clientWidth;
|
||||
uiCanvas.height = workspace.clientHeight;
|
||||
};
|
||||
|
||||
// Initial resize
|
||||
resizeCanvas();
|
||||
|
||||
// Resize canvas when window resizes
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
uiCanvas.addEventListener('mousedown', (event) => handleMouseEvent(event, 'mousedown'));
|
||||
uiCanvas.addEventListener('mouseup', (event) => handleMouseEvent(event, 'mouseup'));
|
||||
uiCanvas.addEventListener('mousemove', (event) => handleMouseEvent(event, 'mousemove'));
|
||||
uiCanvas.addEventListener('contextmenu', (event) => event.preventDefault());
|
||||
|
||||
function handleMouseEvent(event, eventType) {
|
||||
if (tool.eventHandlers) {
|
||||
if ((event.buttons&4)==4) {
|
||||
passEventToElementBelow(event);
|
||||
}else {
|
||||
if (tool.eventHandlers[eventType]) {
|
||||
// Call the tool's event handler
|
||||
tool.eventHandlers[eventType](event);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Pass the event to the element below
|
||||
passEventToElementBelow(event);
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
}
|
||||
|
||||
function passEventToElementBelow(event) {
|
||||
uiCanvas.style.pointerEvents = 'none';
|
||||
|
||||
// Find the element below the cursor
|
||||
let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
|
||||
|
||||
uiCanvas.style.pointerEvents = '';
|
||||
|
||||
// Dispatch the event to the element below
|
||||
if (elemBelow) {
|
||||
forwardEvent(event,elemBelow);
|
||||
}
|
||||
}
|
||||
|
||||
return uiCanvas;
|
||||
}
|
||||
|
||||
function drawPicFramesOnUICanvas(uiCanvas, picList) {
|
||||
const ctx = uiCanvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, uiCanvas.width, uiCanvas.height); // Clear the canvas
|
||||
|
||||
picList.forEach(pic => {
|
||||
// Convert pic's corners to UI Canvas coordinates
|
||||
const topLeft = convertCoordsFromPicToUI(0, 0, pic);
|
||||
const topRight = convertCoordsFromPicToUI(pic.width, 0, pic);
|
||||
const bottomLeft = convertCoordsFromPicToUI(0, pic.height, pic);
|
||||
const bottomRight = convertCoordsFromPicToUI(pic.width, pic.height, pic);
|
||||
|
||||
// Draw rectangle
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(topLeft.x,topLeft.y);
|
||||
ctx.lineTo(topRight.x,topRight.y);
|
||||
ctx.lineTo(bottomRight.x,bottomRight.y);
|
||||
ctx.lineTo(bottomLeft.x,bottomLeft.y);
|
||||
ctx.lineTo(topLeft.x,topLeft.y);
|
||||
ctx.strokeStyle = 'red';
|
||||
ctx.stroke();
|
||||
|
||||
// Draw cross lines
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(topLeft.x, topLeft.y);
|
||||
ctx.lineTo(bottomRight.x, bottomRight.y);
|
||||
ctx.moveTo(topRight.x, topRight.y);
|
||||
ctx.lineTo(bottomLeft.x, bottomLeft.y);
|
||||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
const pixelTip = {
|
||||
@@ -24,6 +151,22 @@ const eyeDropper={
|
||||
const color= "#" + toHex(sample[0]) + toHex(sample[1]) + toHex(sample[2]);
|
||||
$("#foreground").val(color)
|
||||
}
|
||||
},
|
||||
eventHandlers: {
|
||||
mousedown(e) {
|
||||
const {offsetX,offsetY} = e;
|
||||
const pic = getDrawAreaAtPoint(offsetX,offsetY);
|
||||
|
||||
if (!pic) return;
|
||||
const {x,y} = convertCoordsFromUIToPic(offsetX,offsetY,pic);
|
||||
if (x>=0 && y>=0 && x<pic.width && y<pic.height) {
|
||||
const sample = pic.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)
|
||||
}
|
||||
console.log({x,y})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,3 +221,252 @@ const pixelClear = {
|
||||
}
|
||||
|
||||
|
||||
const transformTool = (_=> { //closure
|
||||
let rotateMode = false;
|
||||
let doubleClickGap=300;
|
||||
let mouseDownTime =0;
|
||||
let mouseDownTransform = [1,0, 0,1, 0,0];
|
||||
let mouseDownAngle = 0;
|
||||
let layer;
|
||||
let dragHandler;
|
||||
let mouseDownPosition
|
||||
let preserveAspect = false;
|
||||
let scaleHandlers= [
|
||||
dragTopLeft,dragTopRight,dragBottomRight,dragBottomLeft,dragTop,dragRight,dragBottom,dragLeft
|
||||
];
|
||||
|
||||
|
||||
const tool = {
|
||||
init() {
|
||||
this.drawUI();
|
||||
},
|
||||
drawOperation() {
|
||||
console.log("transform tool should not draw, this is a bug")
|
||||
},
|
||||
drawUI() {
|
||||
//drawPicFramesOnUICanvas(uiCanvas,[activePic]);
|
||||
const ctx=uiCanvas.getContext("2d");
|
||||
ctx.clearRect(0,0,1e5,1e5);
|
||||
let handles=controlPoints(0,0,activePic.activeLayer.width,activePic.activeLayer.height);
|
||||
ctx.fillStyle="black";
|
||||
ctx.strokeStyle="white";
|
||||
|
||||
|
||||
if (rotateMode) {
|
||||
let {x,y} = activePic.activeLayer.rotationCenter;
|
||||
const picPoints = activePic.activeLayer.convertToPicCoords(x,y)
|
||||
const uiPos = convertCoordsFromPicToUI(picPoints.x,picPoints.y,activePic);
|
||||
ctx.strokeStyle="black";
|
||||
ctx.strokeRect(uiPos.x-5,uiPos.y-5,10,10);
|
||||
ctx.strokeStyle="white";
|
||||
ctx.strokeRect(uiPos.x-4,uiPos.y-4,8,8);
|
||||
for (let{x,y} of handles) {
|
||||
const picPoints = activePic.activeLayer.convertToPicCoords(x,y)
|
||||
const uiPos = convertCoordsFromPicToUI(picPoints.x,picPoints.y,activePic);
|
||||
ctx.beginPath();
|
||||
ctx.arc(uiPos.x,uiPos.y,8,0,Math.PI*2);
|
||||
ctx.fill();
|
||||
ctx.beginPath();
|
||||
ctx.arc(uiPos.x,uiPos.y,7,0,Math.PI*2);
|
||||
ctx.stroke();
|
||||
}
|
||||
} else {
|
||||
for (let{x,y} of handles) {
|
||||
const picPoints = activePic.activeLayer.convertToPicCoords(x,y)
|
||||
const uiPos = convertCoordsFromPicToUI(picPoints.x,picPoints.y,activePic);
|
||||
ctx.fillRect(uiPos.x-5,uiPos.y-5,10,10);
|
||||
ctx.strokeRect(uiPos.x-4,uiPos.y-4,8,8);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
tool.eventHandlers ={
|
||||
mousedown(e) {
|
||||
if (e.button == 0) {
|
||||
let now = Date.now();
|
||||
let clickSpacing = now-mouseDownTime;
|
||||
mouseDownTime=now;
|
||||
if (clickSpacing < doubleClickGap) {
|
||||
doubleClickHandler(e);
|
||||
return;
|
||||
}
|
||||
|
||||
const mousePos = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
layer = activePic?.activeLayer;
|
||||
if (!layer) return;
|
||||
let handles=controlPoints(0,0,layer.width,layer.height);
|
||||
dragHandler=null;
|
||||
for (let i=0;i<handles.length;i++) {
|
||||
const {x,y} = handles[i];
|
||||
const picPoints = layer.convertToPicCoords(x,y)
|
||||
//const uiPos = convertCoordsFromPicToUI(picPoints.x,picPoints.y,activePic);
|
||||
//if (max( abs(uiPos.x-e.offsetX), abs(uiPos.y-e.offsetY) ) <8 ) {
|
||||
if (max( abs(picPoints.x-mousePos.x), abs(picPoints.y-mousePos.y) ) <8 ) {
|
||||
dragHandler=rotateMode?rotateHandler:scaleHandlers[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dragHandler) {
|
||||
if ( mousePos.x >0 &&
|
||||
mousePos.x<=layer.width &&
|
||||
mousePos.y >0 &&
|
||||
mousePos.y<=layer.height
|
||||
) {
|
||||
dragHandler=translateHandler;
|
||||
}
|
||||
if (rotateMode) {
|
||||
let {x,y} = layer.convertToPicCoords(layer.rotationCenter.x,layer.rotationCenter.y);
|
||||
console.log({x,y,mousePos})
|
||||
if (max( abs(x-mousePos.x), abs(y-mousePos.y) ) <8 ) {
|
||||
dragHandler=translateCenterHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dragHandler) {
|
||||
let center = layer.convertToPicCoords(layer.rotationCenter.x,layer.rotationCenter.y)
|
||||
mouseDownAngle= v2Angle(v2Sub(mousePos,center));
|
||||
mouseDownTransform=[...layer.transform];
|
||||
mouseDownPosition = {x:e.offsetX,y:e.offsetY}
|
||||
console.log({mouseDownAngle})
|
||||
}
|
||||
}
|
||||
},
|
||||
mouseup(e) {
|
||||
|
||||
},
|
||||
mousemove(e) {
|
||||
|
||||
if ((e.buttons && 1) !== 1) dragStop();
|
||||
if (dragHandler) dragHandler(e);
|
||||
tool.drawUI();
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
function controlPoints (left, top, right, bottom) {
|
||||
const midX = (left+right)/2;
|
||||
const midY = (top+bottom)/2;
|
||||
return [
|
||||
{x:left, y:top},
|
||||
{x:right, y:top},
|
||||
{x:right, y:bottom},
|
||||
{x:left, y:bottom},
|
||||
{x:midX, y:top},
|
||||
{x:right, y:midY},
|
||||
{x:midX, y:bottom},
|
||||
{x:left, y:midY}
|
||||
]
|
||||
}
|
||||
|
||||
function dragStop() {
|
||||
dragHandler=null;
|
||||
}
|
||||
|
||||
function doubleClickHandler(e) {
|
||||
rotateMode=!rotateMode;
|
||||
tool.drawUI();
|
||||
}
|
||||
|
||||
function scaleHandler(handle,anchor,position,lockX=false,lockY=false) {
|
||||
const newTransform=scaleTransformByHandle(handle,anchor,position,mouseDownTransform,lockX,lockY);
|
||||
|
||||
layer.transform = newTransform;
|
||||
|
||||
activePic.updateVisualRepresentation();
|
||||
|
||||
}
|
||||
|
||||
function translateHandler(e) {
|
||||
let dx = e.offsetX -mouseDownPosition.x
|
||||
let dy = e.offsetY -mouseDownPosition.y;
|
||||
|
||||
let newTransform = [...mouseDownTransform];
|
||||
newTransform[4]+=dx;
|
||||
newTransform[5]+=dy;
|
||||
|
||||
layer.transform=newTransform;
|
||||
activePic.updateVisualRepresentation();
|
||||
}
|
||||
|
||||
function translateCenterHandler(e) {
|
||||
const mousePos = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
layer.rotationCenter= layer.convertFromPicCoords(mousePos.x,mousePos.y);
|
||||
activePic.updateVisualRepresentation();
|
||||
|
||||
}
|
||||
|
||||
function dragTopLeft(e) {
|
||||
const mouseCurrentPosition = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
scaleHandler({x:0,y:0},{x:layer.width,y:layer.height},mouseCurrentPosition);
|
||||
}
|
||||
|
||||
|
||||
function dragTopRight(e) {
|
||||
const mouseCurrentPosition = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
scaleHandler({x:layer.width,y:0},{x:0,y:layer.height},mouseCurrentPosition);
|
||||
|
||||
}
|
||||
function dragBottomRight(e) {
|
||||
const mouseCurrentPosition = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
scaleHandler({x:layer.width,y:layer.height},{x:0,y:0},mouseCurrentPosition);
|
||||
}
|
||||
function dragBottomLeft(e) {
|
||||
const mouseCurrentPosition = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
scaleHandler({x:0,y:layer.height},{x:layer.width,y:0},mouseCurrentPosition);
|
||||
}
|
||||
function dragTop(e) {
|
||||
const mouseCurrentPosition = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
scaleHandler({x:layer.width/2,y:0},{x:layer.width,y:layer.height},mouseCurrentPosition,true);
|
||||
}
|
||||
function dragRight(e) {
|
||||
const mouseCurrentPosition = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
scaleHandler({x:layer.width,y:layer.height/2},{x:0,y:0},mouseCurrentPosition,false,true);
|
||||
}
|
||||
function dragLeft(e) {
|
||||
const mouseCurrentPosition = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
scaleHandler({x:0,y:layer.height/2},{x:layer.width,y:0},mouseCurrentPosition,false,true);
|
||||
|
||||
}
|
||||
function dragBottom(e) {
|
||||
const mouseCurrentPosition = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
scaleHandler({x:layer.width/2,y:layer.height},{x:layer.width,y:0},mouseCurrentPosition,true);
|
||||
}
|
||||
|
||||
function rotateHandler(e) {
|
||||
let center = layer.convertToPicCoords(layer.rotationCenter.x,layer.rotationCenter.y)
|
||||
let mousePos = convertCoordsFromUIToPic(e.offsetX,e.offsetY,activePic);
|
||||
|
||||
let angle= v2Angle(v2Sub(mousePos,center));
|
||||
let angleDelta = angle-mouseDownAngle;
|
||||
layer.transform=[...mouseDownTransform];
|
||||
layer.rotate(angleDelta*RadiansToDegrees);
|
||||
activePic.updateVisualRepresentation();
|
||||
}
|
||||
|
||||
function rotateTopRight(e) {
|
||||
|
||||
}
|
||||
function rotateBottomLeft(e) {
|
||||
|
||||
}
|
||||
function rotateBottomRight(e) {
|
||||
|
||||
}
|
||||
function rotateTop(e) {
|
||||
|
||||
}
|
||||
function rotateBottom(e) {
|
||||
|
||||
}
|
||||
function rotateLeft(e) {
|
||||
|
||||
}
|
||||
function rotateRight(e) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
return tool;
|
||||
})(); //end closure for transformTool
|
||||
|
||||
@@ -94,6 +94,12 @@ svg.tool.button {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath d='M34 10A1 1 90 0139 15L34 19 33 20 34 21 33 22 27 16 28 15 29 16 30 15ZM27.4 17.4 12.1 31.4 12.1 33.3 9.3 35.7 9.5 36.9 13.2 33.7 13.2 31.7 27.5 18.9 30.2 21.5 16.1 34.4 14.1 34.6 10.5 37.7 12 37.9 14.6 35.5 16.4 35.4 31.5 21.7'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
#transform.tool.button {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-2 -2 32 32'%3E%3Cpath d='m14 2 4 4h-3v7h7v-3l4 4-4 3v-2h-7v7h3l-4 4-4-4h3v-7h-7v3l-4-4 4-4v3h7v-7h-3z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
|
||||
|
||||
#clear.action.button {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath d='M19 24 12 17 17 12 24 19 31 12 36 17 29 24 36 31 31 36 24 29 17 36 12 31'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,191 @@
|
||||
// these are support functions for bspaint,
|
||||
// Nothing in here should rely on global state
|
||||
'use strict';
|
||||
|
||||
const abs=Math.abs;
|
||||
const min=Math.min;
|
||||
const max=Math.max;
|
||||
const median = (a, b, c) => max(min(a, b), min(max(a, b), c));
|
||||
|
||||
const RadiansToDegrees = 180/Math.PI;
|
||||
|
||||
function v2Distance(a,b) {
|
||||
return Math.hypot(a.x-b.x,a.y-b.y);
|
||||
}
|
||||
|
||||
function v2Length({x,y}) {
|
||||
return Math.hypot(x,y)
|
||||
}
|
||||
|
||||
function v2Sub(a,b) {
|
||||
return {x:a.x-b.x,y:a.y-b.y};
|
||||
}
|
||||
function v2Add(a,b) {
|
||||
return {x:a.x+b.x,y:a.y+b.y};
|
||||
}
|
||||
|
||||
function v2Scale(a,scale) {
|
||||
return {x:a.x*scale,y:a.y*scale}
|
||||
}
|
||||
|
||||
function v2Normalise(a,length=1) {
|
||||
const scale = length/v2Length(a);
|
||||
return v2Scale(a,scale);
|
||||
}
|
||||
|
||||
function v2Dot(a,b) {
|
||||
return (a.x*b.x + a.y*b.y);
|
||||
}
|
||||
|
||||
function v2PurpCW({x, y}) {
|
||||
return {x: y, y: -x};
|
||||
}
|
||||
|
||||
function v2PurpCCW({x, y}) {
|
||||
return {x: -y, y: x};
|
||||
}
|
||||
function v2Angle({x,y}) {
|
||||
return Math.atan2(y,x);
|
||||
}
|
||||
|
||||
var v2Purp=v2PurpCCW;
|
||||
|
||||
|
||||
function applyTransform({x,y},transform) {
|
||||
const [a, b, c, d, e, f] = transform;
|
||||
|
||||
// Apply the layer's transformation matrix
|
||||
const newX = a * x + c * y + e;
|
||||
const newY = b * x + d * y + f;
|
||||
|
||||
return { x: newX, y: newY };
|
||||
}
|
||||
|
||||
function reverseTransform({x,y},transform) {
|
||||
const [a, b, c, d, e, f] = transform;
|
||||
|
||||
// Apply the inverse of the layer's transformation matrix
|
||||
const det = a * d - b * c;
|
||||
|
||||
if (det === 0) {
|
||||
console.error("Transformation matrix is not invertible");
|
||||
return {x,y}
|
||||
}
|
||||
|
||||
const newX = (d * (x - e) - c * (y - f)) / det;
|
||||
const newY = (-b * (x - e) + a * (y - f)) / det;
|
||||
|
||||
return { x: newX, y: newY };
|
||||
}
|
||||
|
||||
function concatenateTransforms([a1, b1, c1, d1, e1, f1], [a2, b2, c2, d2, e2, f2]) {
|
||||
return [
|
||||
a1 * a2 + c1 * b2,
|
||||
b1 * a2 + d1 * b2,
|
||||
a1 * c2 + c1 * d2,
|
||||
b1 * c2 + d1 * d2,
|
||||
a1 * e2 + c1 * f2 + e1,
|
||||
b1 * e2 + d1 * f2 + f1
|
||||
];
|
||||
}
|
||||
|
||||
function scaleTranformWithSkewFudge(A, B, newTransformedA, originalTransform, skewCompensation=0, lockX=false,lockY=false) {
|
||||
|
||||
const transformedA = applyTransform(A, originalTransform);
|
||||
const transformedB = applyTransform(B, originalTransform);
|
||||
// Calculate relative differences
|
||||
const originalRelDiff = {
|
||||
x: transformedB.x - transformedA.x,
|
||||
y: transformedB.y - transformedA.y
|
||||
};
|
||||
const newRelDiff = {
|
||||
x: transformedB.x - newTransformedA.x,
|
||||
y: transformedB.y - newTransformedA.y
|
||||
};
|
||||
|
||||
const angle = Math.atan2(originalTransform[1], originalTransform[0]);
|
||||
// Calculate inverse rotation matrix
|
||||
const cosTheta = Math.cos(-angle);
|
||||
const sinTheta = Math.sin(-angle);
|
||||
const inverseRotation = [cosTheta, sinTheta, -sinTheta, cosTheta, 0, 0];
|
||||
|
||||
// Apply inverse rotation to the relative differences
|
||||
const rotatedOriginalRelDiff = applyTransform(originalRelDiff, inverseRotation);
|
||||
const rotatedNewRelDiff = applyTransform(newRelDiff, inverseRotation);
|
||||
|
||||
// Calculate scale factors
|
||||
let scale = {
|
||||
x: rotatedNewRelDiff.x / rotatedOriginalRelDiff.x,
|
||||
y: rotatedNewRelDiff.y / rotatedOriginalRelDiff.y
|
||||
}
|
||||
|
||||
const divergence =v2Dot( v2Normalise(v2Sub(transformedB,transformedA)), v2Purp(v2Normalise(v2Sub(transformedA,newTransformedA))));
|
||||
const lengthAMovement = v2Distance(transformedA,newTransformedA);
|
||||
|
||||
|
||||
const scaleTransform= [scale.x + skewCompensation* divergence*lengthAMovement, 0, 0, scale.y, 0, 0];
|
||||
|
||||
if (lockX) scaleTransform[0]=1;
|
||||
if (lockY) scaleTransform[3]=1;
|
||||
|
||||
const newTransform = concatenateTransforms(originalTransform,scaleTransform);
|
||||
const newTransformedB = applyTransform(B,newTransform);
|
||||
|
||||
// Calculate translation to keep B in place
|
||||
const translateX = transformedB.x - newTransformedB.x;
|
||||
const translateY = transformedB.y - newTransformedB.y;
|
||||
|
||||
const translation = [1, 0, 0, 1, translateX, translateY];
|
||||
|
||||
const translated = concatenateTransforms(translation,originalTransform);
|
||||
const result = concatenateTransforms(translated,scaleTransform);
|
||||
return result;
|
||||
}
|
||||
|
||||
function scaleTransformWithDynamicFix(a,b,desiredA,transform,lockX = false,lockY=false) {
|
||||
function errorVector(skewCandidate) {
|
||||
const newTransform = scaleTranformWithSkewFudge(a, b, desiredA, transform,skewCandidate,false,false);
|
||||
const actualA = applyTransform(a,newTransform);
|
||||
return v2Sub(actualA,desiredA);
|
||||
}
|
||||
const candidate1 = 0;
|
||||
const candidate2 = 110;
|
||||
var skewAdjustment=0;
|
||||
|
||||
let errorVector1 = errorVector(candidate1);
|
||||
if (errorVector1.x===0 && errorVector1.y===0) {
|
||||
//All good to go
|
||||
skewAdjustment=0;
|
||||
} else {
|
||||
let errorVector2 = errorVector(candidate2);
|
||||
|
||||
// Calculate the slopes for x and y components of the error vector
|
||||
let slopeX = (errorVector2.x - errorVector1.x) / (candidate2 - candidate1);
|
||||
let slopeY = (errorVector2.y - errorVector1.y) / (candidate2 - candidate1);
|
||||
|
||||
// Calculate where the line crosses zero for both components
|
||||
let zeroCrossingX = candidate1 - errorVector1.x / slopeX;
|
||||
let zeroCrossingY = candidate1 - errorVector1.y / slopeY;
|
||||
|
||||
if (slopeY==0) {
|
||||
skewAdjustment=slopeX;
|
||||
}
|
||||
else if (slopeX == 0) {
|
||||
skewAdjustment=slopeY;
|
||||
} else {
|
||||
//not strictly necessary to average here but we're in full-on dodgy math country now.
|
||||
skewAdjustment = (zeroCrossingX + zeroCrossingY) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
return scaleTranformWithSkewFudge(a,b,desiredA,transform,skewAdjustment,lockX,lockY)
|
||||
}
|
||||
|
||||
|
||||
function scaleTransformByHandle(a, stationaryPoint, desiredTransformedA, originalTransform, lockX = false, lockY=false) {
|
||||
return scaleTransformWithDynamicFix(a,stationaryPoint,desiredTransformedA,originalTransform,lockX,lockY)
|
||||
}
|
||||
|
||||
|
||||
function cells(x=1,y=1) {
|
||||
let width = 1/x;
|
||||
@@ -172,3 +358,58 @@ function circleBrush(scale) {
|
||||
if ((newCursor.width < 2) || (newCursor.width>120)) value="crosshair";
|
||||
return 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user