canvas scaling?
Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
parent
6d9155261e
commit
ea68e25cf0
11 changed files with 578 additions and 98 deletions
|
@ -132,3 +132,72 @@
|
|||
flex: 1;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
/* Resizing buttons */
|
||||
.expand-button {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition-duration: 300ms;
|
||||
|
||||
border: 2px solid #293d3d30;
|
||||
}
|
||||
|
||||
.expand-button::after {
|
||||
content: "";
|
||||
|
||||
background-color: #293d3d77;
|
||||
|
||||
mask-image: url("/res/icons/chevron-up.svg");
|
||||
mask-size: contain;
|
||||
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.expand-button:hover::after {
|
||||
background-color: #466;
|
||||
}
|
||||
|
||||
.expand-button.right::after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.expand-button.bottom::after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.expand-button.left::after {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.expand-button.left {
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
}
|
||||
.expand-button.top {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
}
|
||||
.expand-button.right {
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
.expand-button.bottom {
|
||||
border-bottom-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
}
|
||||
|
||||
.expand-button:hover {
|
||||
backdrop-filter: brightness(60%);
|
||||
}
|
||||
|
|
18
js/index.js
18
js/index.js
|
@ -343,8 +343,8 @@ function newImage(evt) {
|
|||
clearPaintedMask();
|
||||
uil.layers.forEach(({layer}) => {
|
||||
commands.runCommand("eraseImage", "Clear Canvas", {
|
||||
x: 0,
|
||||
y: 0,
|
||||
x: -layer.origin.x,
|
||||
y: -layer.origin.y,
|
||||
w: layer.canvas.width,
|
||||
h: layer.canvas.height,
|
||||
ctx: layer.ctx,
|
||||
|
@ -353,7 +353,7 @@ function newImage(evt) {
|
|||
}
|
||||
|
||||
function clearPaintedMask() {
|
||||
maskPaintCtx.clearRect(0, 0, maskPaintCanvas.width, maskPaintCanvas.height);
|
||||
maskPaintLayer.clear();
|
||||
}
|
||||
|
||||
function march(bb, options = {}) {
|
||||
|
@ -558,8 +558,16 @@ function drawBackground() {
|
|||
// Checkerboard
|
||||
let darkTileColor = "#333";
|
||||
let lightTileColor = "#555";
|
||||
for (var x = 0; x < bgLayer.canvas.width; x += 64) {
|
||||
for (var y = 0; y < bgLayer.canvas.height; y += 64) {
|
||||
for (
|
||||
var x = -bgLayer.origin.x - 64;
|
||||
x < bgLayer.canvas.width - bgLayer.origin.x;
|
||||
x += 64
|
||||
) {
|
||||
for (
|
||||
var y = -bgLayer.origin.y - 64;
|
||||
y < bgLayer.canvas.height - bgLayer.origin.y;
|
||||
y += 64
|
||||
) {
|
||||
bgLayer.ctx.fillStyle =
|
||||
(x + y) % 128 === 0 ? lightTileColor : darkTileColor;
|
||||
bgLayer.ctx.fillRect(x, y, 64, 64);
|
||||
|
|
|
@ -17,6 +17,19 @@ mouse.listen.world.onmousemove.on((evn) => {
|
|||
canvasYInfo.textContent = evn.y;
|
||||
snapXInfo.textContent = evn.x + snap(evn.x);
|
||||
snapYInfo.textContent = evn.y + snap(evn.y);
|
||||
|
||||
if (debug) {
|
||||
debugLayer.clear();
|
||||
debugCtx.fillStyle = "#F0F";
|
||||
debugCtx.beginPath();
|
||||
debugCtx.arc(viewport.cx, viewport.cy, 5, 0, Math.PI * 2);
|
||||
debugCtx.fill();
|
||||
|
||||
debugCtx.fillStyle = "#0FF";
|
||||
debugCtx.beginPath();
|
||||
debugCtx.arc(evn.x, evn.y, 5, 0, Math.PI * 2);
|
||||
debugCtx.fill();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -53,44 +53,99 @@ uiCanvas.width = uiCanvas.clientWidth;
|
|||
uiCanvas.height = uiCanvas.clientHeight;
|
||||
const uiCtx = uiCanvas.getContext("2d", {desynchronized: true});
|
||||
|
||||
/**
|
||||
* Here we setup canvas dynamic scaling
|
||||
*/
|
||||
(() => {
|
||||
let expandSize = localStorage.getItem("expand-size") || 1024;
|
||||
expandSize = parseInt(expandSize, 10);
|
||||
|
||||
const askSize = () => {
|
||||
const by = prompt("How much do you want to expand by?", expandSize);
|
||||
|
||||
if (!by) return null;
|
||||
else {
|
||||
const len = parseInt(by, 10);
|
||||
localStorage.setItem("expand-size", len);
|
||||
expandSize = len;
|
||||
return len;
|
||||
}
|
||||
};
|
||||
|
||||
const leftButton = makeElement("button", -64, 0);
|
||||
leftButton.classList.add("expand-button", "left");
|
||||
leftButton.style.width = "64px";
|
||||
leftButton.style.height = `${imageCollection.size.h}px`;
|
||||
leftButton.addEventListener("click", () => {
|
||||
let size = null;
|
||||
if ((size = askSize())) {
|
||||
imageCollection.expand(size, 0, 0, 0);
|
||||
drawBackground();
|
||||
const newLeft = -imageCollection.inputOffset.x - imageCollection.origin.x;
|
||||
leftButton.style.left = newLeft - 64 + "px";
|
||||
topButton.style.left = newLeft + "px";
|
||||
bottomButton.style.left = newLeft + "px";
|
||||
topButton.style.width = imageCollection.size.w + "px";
|
||||
bottomButton.style.width = imageCollection.size.w + "px";
|
||||
}
|
||||
});
|
||||
|
||||
const rightButton = makeElement("button", imageCollection.size.w, 0);
|
||||
rightButton.classList.add("expand-button", "right");
|
||||
rightButton.style.width = "64px";
|
||||
rightButton.style.height = `${imageCollection.size.h}px`;
|
||||
rightButton.addEventListener("click", () => {
|
||||
let size = null;
|
||||
if ((size = askSize())) {
|
||||
imageCollection.expand(0, 0, size, 0);
|
||||
drawBackground();
|
||||
rightButton.style.left =
|
||||
parseInt(rightButton.style.left, 10) + size + "px";
|
||||
topButton.style.width = imageCollection.size.w + "px";
|
||||
bottomButton.style.width = imageCollection.size.w + "px";
|
||||
}
|
||||
});
|
||||
|
||||
const topButton = makeElement("button", 0, -64);
|
||||
topButton.classList.add("expand-button", "top");
|
||||
topButton.style.height = "64px";
|
||||
topButton.style.width = `${imageCollection.size.w}px`;
|
||||
topButton.addEventListener("click", () => {
|
||||
let size = null;
|
||||
if ((size = askSize())) {
|
||||
imageCollection.expand(0, size, 0, 0);
|
||||
drawBackground();
|
||||
const newTop = -imageCollection.inputOffset.y - imageCollection.origin.y;
|
||||
topButton.style.top = newTop - 64 + "px";
|
||||
leftButton.style.top = newTop + "px";
|
||||
rightButton.style.top = newTop + "px";
|
||||
leftButton.style.height = imageCollection.size.h + "px";
|
||||
rightButton.style.height = imageCollection.size.h + "px";
|
||||
}
|
||||
});
|
||||
|
||||
const bottomButton = makeElement("button", 0, imageCollection.size.h);
|
||||
bottomButton.classList.add("expand-button", "bottom");
|
||||
bottomButton.style.height = "64px";
|
||||
bottomButton.style.width = `${imageCollection.size.w}px`;
|
||||
bottomButton.addEventListener("click", () => {
|
||||
let size = null;
|
||||
if ((size = askSize())) {
|
||||
imageCollection.expand(0, 0, 0, size);
|
||||
drawBackground();
|
||||
bottomButton.style.top =
|
||||
parseInt(bottomButton.style.top, 10) + size + "px";
|
||||
leftButton.style.height = imageCollection.size.h + "px";
|
||||
rightButton.style.height = imageCollection.size.h + "px";
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
debugLayer.hide(); // Hidden by default
|
||||
|
||||
layers.registerCollection("mask", {name: "Mask Layers", requiresActive: true});
|
||||
|
||||
// Where CSS and javascript magic happens to make the canvas viewport work
|
||||
/**
|
||||
* Ended up using a CSS transforms approach due to more flexibility on transformations
|
||||
* and capability to automagically translate input coordinates to layer space.
|
||||
*/
|
||||
mouse.registerContext(
|
||||
"world",
|
||||
(evn, ctx) => {
|
||||
// Fix because in chrome layerX and layerY simply doesnt work
|
||||
ctx.coords.prev.x = ctx.coords.pos.x;
|
||||
ctx.coords.prev.y = ctx.coords.pos.y;
|
||||
|
||||
// Get element bounding rect
|
||||
const bb = imageCollection.element.getBoundingClientRect();
|
||||
|
||||
// Get element width/height (css, cause I don't trust client sizes in chrome anymore)
|
||||
const w = imageCollection.size.w;
|
||||
const h = imageCollection.size.h;
|
||||
|
||||
// Get cursor position
|
||||
const x = evn.clientX;
|
||||
const y = evn.clientY;
|
||||
|
||||
// Map to layer space
|
||||
const layerX = ((x - bb.left) / bb.width) * w;
|
||||
const layerY = ((y - bb.top) / bb.height) * h;
|
||||
|
||||
//
|
||||
ctx.coords.pos.x = Math.round(layerX);
|
||||
ctx.coords.pos.y = Math.round(layerY);
|
||||
},
|
||||
{target: imageCollection.inputElement}
|
||||
);
|
||||
|
||||
/**
|
||||
* The global viewport object (may be modularized in the future). All
|
||||
* coordinates given are of the center of the viewport
|
||||
|
@ -158,6 +213,31 @@ let worldInit = null;
|
|||
|
||||
viewport.transform(imageCollection.element);
|
||||
|
||||
/**
|
||||
* Ended up using a CSS transforms approach due to more flexibility on transformations
|
||||
* and capability to automagically translate input coordinates to layer space.
|
||||
*/
|
||||
mouse.registerContext(
|
||||
"world",
|
||||
(evn, ctx) => {
|
||||
// Fix because in chrome layerX and layerY simply doesnt work
|
||||
ctx.coords.prev.x = ctx.coords.pos.x;
|
||||
ctx.coords.prev.y = ctx.coords.pos.y;
|
||||
|
||||
// Get cursor position
|
||||
const x = evn.clientX;
|
||||
const y = evn.clientY;
|
||||
|
||||
// Map to layer space
|
||||
const layerCoords = viewport.viewToCanvas(x, y);
|
||||
|
||||
// Set coords
|
||||
ctx.coords.pos.x = Math.round(layerCoords.x);
|
||||
ctx.coords.pos.y = Math.round(layerCoords.y);
|
||||
},
|
||||
{target: imageCollection.inputElement}
|
||||
);
|
||||
|
||||
mouse.listen.window.onwheel.on((evn) => {
|
||||
if (evn.evn.ctrlKey) {
|
||||
evn.evn.preventDefault();
|
||||
|
@ -176,14 +256,6 @@ mouse.listen.window.onwheel.on((evn) => {
|
|||
viewport.transform(imageCollection.element);
|
||||
|
||||
toolbar.currentTool.redraw();
|
||||
|
||||
if (debug) {
|
||||
debugCtx.clearRect(0, 0, debugCanvas.width, debugCanvas.height);
|
||||
debugCtx.fillStyle = "#F0F";
|
||||
debugCtx.beginPath();
|
||||
debugCtx.arc(viewport.cx, viewport.cy, 5, 0, Math.PI * 2);
|
||||
debugCtx.fill();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
309
js/lib/layers.js
309
js/lib/layers.js
|
@ -3,6 +3,150 @@
|
|||
*
|
||||
* It manages canvases and their locations and sizes according to current viewport views
|
||||
*/
|
||||
/**
|
||||
* Here is where the old magic is created.
|
||||
*
|
||||
* This is probably not recommended, but it works and
|
||||
* is probably the most reliable way to not break everything.
|
||||
*/
|
||||
(() => {
|
||||
const original = {
|
||||
drawImage: CanvasRenderingContext2D.prototype.drawImage,
|
||||
getImageData: CanvasRenderingContext2D.prototype.getImageData,
|
||||
putImageData: CanvasRenderingContext2D.prototype.putImageData,
|
||||
|
||||
// Drawing methods
|
||||
moveTo: CanvasRenderingContext2D.prototype.moveTo,
|
||||
lineTo: CanvasRenderingContext2D.prototype.lineTo,
|
||||
|
||||
arc: CanvasRenderingContext2D.prototype.arc,
|
||||
fillRect: CanvasRenderingContext2D.prototype.fillRect,
|
||||
clearRect: CanvasRenderingContext2D.prototype.clearRect,
|
||||
};
|
||||
|
||||
// Backing up original functions to <key>Root
|
||||
Object.keys(original).forEach((key) => {
|
||||
CanvasRenderingContext2D.prototype[key + "Root"] = function (...args) {
|
||||
return original[key].call(this, ...args);
|
||||
};
|
||||
});
|
||||
|
||||
// Modifying drawImage
|
||||
Reflect.defineProperty(CanvasRenderingContext2D.prototype, "drawImage", {
|
||||
value: function (...args) {
|
||||
switch (args.length) {
|
||||
case 3:
|
||||
case 5:
|
||||
if (this.origin !== undefined) {
|
||||
args[1] += this.origin.x;
|
||||
args[2] += this.origin.y;
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
// Check for origin on source
|
||||
const sctx = args[0].getContext && args[0].getContext("2d");
|
||||
if (sctx && sctx.origin !== undefined) {
|
||||
args[1] += sctx.origin.x;
|
||||
args[2] += sctx.origin.y;
|
||||
}
|
||||
|
||||
// Check for origin on destination
|
||||
if (this.origin !== undefined) {
|
||||
args[5] += this.origin.x;
|
||||
args[6] += this.origin.y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Pass arguments through
|
||||
return original.drawImage.call(this, ...args);
|
||||
},
|
||||
});
|
||||
|
||||
// Modifying getImageData method
|
||||
Reflect.defineProperty(CanvasRenderingContext2D.prototype, "getImageData", {
|
||||
value: function (...args) {
|
||||
if (this.origin) {
|
||||
args[0] += this.origin.x;
|
||||
args[1] += this.origin.y;
|
||||
}
|
||||
// Pass arguments through
|
||||
return original.getImageData.call(this, ...args);
|
||||
},
|
||||
});
|
||||
|
||||
// Modifying putImageData method
|
||||
Reflect.defineProperty(CanvasRenderingContext2D.prototype, "putImageData", {
|
||||
value: function (...args) {
|
||||
if (this.origin) {
|
||||
args[0] += this.origin.x;
|
||||
args[1] += this.origin.y;
|
||||
}
|
||||
// Pass arguments through
|
||||
return original.putImageData.call(this, ...args);
|
||||
},
|
||||
});
|
||||
|
||||
// Modifying moveTo method
|
||||
Reflect.defineProperty(CanvasRenderingContext2D.prototype, "moveTo", {
|
||||
value: function (...args) {
|
||||
if (this.origin) {
|
||||
args[0] += this.origin.x;
|
||||
args[1] += this.origin.y;
|
||||
}
|
||||
// Pass arguments through
|
||||
return original.moveTo.call(this, ...args);
|
||||
},
|
||||
});
|
||||
|
||||
// Modifying lineTo method
|
||||
Reflect.defineProperty(CanvasRenderingContext2D.prototype, "lineTo", {
|
||||
value: function (...args) {
|
||||
if (this.origin) {
|
||||
args[0] += this.origin.x;
|
||||
args[1] += this.origin.y;
|
||||
}
|
||||
// Pass arguments through
|
||||
return original.lineTo.call(this, ...args);
|
||||
},
|
||||
});
|
||||
|
||||
// Modifying arc
|
||||
Reflect.defineProperty(CanvasRenderingContext2D.prototype, "arc", {
|
||||
value: function (...args) {
|
||||
if (this.origin) {
|
||||
args[0] += this.origin.x;
|
||||
args[1] += this.origin.y;
|
||||
}
|
||||
// Pass arguments through
|
||||
return original.arc.call(this, ...args);
|
||||
},
|
||||
});
|
||||
|
||||
// Modifying fillRect
|
||||
Reflect.defineProperty(CanvasRenderingContext2D.prototype, "fillRect", {
|
||||
value: function (...args) {
|
||||
if (this.origin) {
|
||||
args[0] += this.origin.x;
|
||||
args[1] += this.origin.y;
|
||||
}
|
||||
// Pass arguments through
|
||||
return original.fillRect.call(this, ...args);
|
||||
},
|
||||
});
|
||||
// Modifying clearRect
|
||||
Reflect.defineProperty(CanvasRenderingContext2D.prototype, "clearRect", {
|
||||
value: function (...args) {
|
||||
if (this.origin) {
|
||||
args[0] += this.origin.x;
|
||||
args[1] += this.origin.y;
|
||||
}
|
||||
// Pass arguments through
|
||||
return original.clearRect.call(this, ...args);
|
||||
},
|
||||
});
|
||||
})();
|
||||
// End of black magic
|
||||
|
||||
const layers = {
|
||||
_collections: [],
|
||||
collections: makeWriteOnce({}, "layers.collections"),
|
||||
|
@ -29,7 +173,7 @@ const layers = {
|
|||
},
|
||||
|
||||
// Input multiplier (Size of the input element div)
|
||||
inputSizeMultiplier: 3,
|
||||
inputSizeMultiplier: 9,
|
||||
|
||||
// Target
|
||||
targetElement: document.getElementById("layer-render"),
|
||||
|
@ -81,11 +225,21 @@ const layers = {
|
|||
return this._inputOffset;
|
||||
},
|
||||
|
||||
_origin: {x: 0, y: 0},
|
||||
get origin() {
|
||||
return {...this._origin};
|
||||
},
|
||||
|
||||
_resizeInputDiv() {
|
||||
// Set offset
|
||||
const oldOffset = {...this._inputOffset};
|
||||
this._inputOffset = {
|
||||
x: -Math.floor(options.inputSizeMultiplier / 2) * size.w,
|
||||
y: -Math.floor(options.inputSizeMultiplier / 2) * size.h,
|
||||
x:
|
||||
-Math.floor(options.inputSizeMultiplier / 2) * size.w -
|
||||
this._origin.x,
|
||||
y:
|
||||
-Math.floor(options.inputSizeMultiplier / 2) * size.h -
|
||||
this._origin.y,
|
||||
};
|
||||
|
||||
// Resize the input element
|
||||
|
@ -97,6 +251,40 @@ const layers = {
|
|||
this.inputElement.style.height = `${
|
||||
size.h * options.inputSizeMultiplier
|
||||
}px`;
|
||||
|
||||
// Move elements inside to new offset
|
||||
for (const child of this.inputElement.children) {
|
||||
if (child.style.position === "absolute") {
|
||||
child.style.left = `${
|
||||
parseInt(child.style.left, 10) +
|
||||
oldOffset.x -
|
||||
this._inputOffset.x
|
||||
}px`;
|
||||
child.style.top = `${
|
||||
parseInt(child.style.top, 10) +
|
||||
oldOffset.y -
|
||||
this._inputOffset.y
|
||||
}px`;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
expand(left, top, right, bottom) {
|
||||
this._layers.forEach((layer) => {
|
||||
if (layer.full) layer._expand(left, top, right, bottom);
|
||||
});
|
||||
|
||||
this._origin.x += left;
|
||||
this._origin.y += top;
|
||||
|
||||
this.size.w += left + right;
|
||||
this.size.h += top + bottom;
|
||||
|
||||
this._resizeInputDiv();
|
||||
|
||||
for (const layer of this._layers) {
|
||||
layer.moveTo(layer.x, layer.y);
|
||||
}
|
||||
},
|
||||
|
||||
size,
|
||||
|
@ -115,7 +303,7 @@ const layers = {
|
|||
* @param {object} options.ctxOptions
|
||||
* @returns
|
||||
*/
|
||||
registerLayer: (key = null, options = {}) => {
|
||||
registerLayer(key = null, options = {}) {
|
||||
// Make ID
|
||||
const id = guid();
|
||||
|
||||
|
@ -124,7 +312,12 @@ const layers = {
|
|||
name: key || `Temporary ${id}`,
|
||||
|
||||
// Bounding box for layer
|
||||
bb: {x: 0, y: 0, w: collection.size.w, h: collection.size.h},
|
||||
bb: {
|
||||
x: -collection.origin.x,
|
||||
y: -collection.origin.y,
|
||||
w: collection.size.w,
|
||||
h: collection.size.h,
|
||||
},
|
||||
|
||||
// Resolution for layer
|
||||
resolution: null,
|
||||
|
@ -139,11 +332,25 @@ const layers = {
|
|||
ctxOptions: {},
|
||||
});
|
||||
|
||||
// Calculate resolution
|
||||
// Check if the layer is full
|
||||
let full = false;
|
||||
if (
|
||||
options.bb.x === -collection.origin.x &&
|
||||
options.bb.y === -collection.origin.y &&
|
||||
options.bb.w === collection.size.w &&
|
||||
options.bb.h === collection.size.h
|
||||
)
|
||||
full = true;
|
||||
|
||||
if (!options.resolution)
|
||||
// Calculate resolution
|
||||
options.resolution = {
|
||||
w: (collection.resolution.w / collection.size.w) * options.bb.w,
|
||||
h: (collection.resolution.h / collection.size.h) * options.bb.h,
|
||||
w: Math.round(
|
||||
(collection.resolution.w / collection.size.w) * options.bb.w
|
||||
),
|
||||
h: Math.round(
|
||||
(collection.resolution.h / collection.size.h) * options.bb.h
|
||||
),
|
||||
};
|
||||
|
||||
// This layer's canvas
|
||||
|
@ -166,7 +373,21 @@ const layers = {
|
|||
options.after.canvas.after(canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Here we set the context origin for using the black magic.
|
||||
*/
|
||||
const ctx = canvas.getContext("2d", options.ctxOptions);
|
||||
if (full) {
|
||||
// Modify context to add origin information
|
||||
ctx.origin = {
|
||||
get x() {
|
||||
return collection.origin.x;
|
||||
},
|
||||
get y() {
|
||||
return collection.origin.y;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Path used for logging purposes
|
||||
const _layerlogpath = key
|
||||
|
@ -177,9 +398,12 @@ const layers = {
|
|||
_logpath: _layerlogpath,
|
||||
_collection: collection,
|
||||
|
||||
bb: new BoundingBox(options.bb),
|
||||
resolution: new Size(options.resolution),
|
||||
id,
|
||||
key,
|
||||
name: options.name,
|
||||
full,
|
||||
|
||||
state: new Proxy(
|
||||
{visible: true},
|
||||
|
@ -195,10 +419,71 @@ const layers = {
|
|||
}
|
||||
),
|
||||
|
||||
get x() {
|
||||
return this.bb.x;
|
||||
},
|
||||
|
||||
get y() {
|
||||
return this.bb.y;
|
||||
},
|
||||
|
||||
get width() {
|
||||
return this.bb.w;
|
||||
},
|
||||
|
||||
get height() {
|
||||
return this.bb.h;
|
||||
},
|
||||
|
||||
get w() {
|
||||
return this.bb.w;
|
||||
},
|
||||
|
||||
get h() {
|
||||
return this.bb.h;
|
||||
},
|
||||
|
||||
get origin() {
|
||||
return this._collection.origin;
|
||||
},
|
||||
|
||||
/** Our canvas */
|
||||
canvas,
|
||||
ctx,
|
||||
|
||||
_expand(left, top, right, bottom) {
|
||||
const tmpCanvas = document.createElement("canvas");
|
||||
tmpCanvas.width = this.w;
|
||||
tmpCanvas.height = this.h;
|
||||
tmpCanvas.getContext("2d").drawImage(this.canvas, 0, 0);
|
||||
|
||||
this.resize(this.w + left + right, this.h + top + bottom);
|
||||
this.clear();
|
||||
this.ctx.drawImageRoot(tmpCanvas, left, top);
|
||||
|
||||
this.moveTo(this.x - left, this.y - top);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the layer contents
|
||||
*/
|
||||
clear() {
|
||||
this.ctx.clearRectRoot(
|
||||
0,
|
||||
0,
|
||||
this.canvas.width,
|
||||
this.canvas.height
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Recalculates DOM positioning
|
||||
*/
|
||||
syncDOM() {
|
||||
this.moveTo(this.x, this.y);
|
||||
this.resize(this.w, this.h);
|
||||
},
|
||||
|
||||
/**
|
||||
* Moves this layer to another level (after given layer)
|
||||
*
|
||||
|
@ -224,8 +509,10 @@ const layers = {
|
|||
* @param {number} y Y coordinate of the top left of the canvas
|
||||
*/
|
||||
moveTo(x, y) {
|
||||
canvas.style.left = `${x}px`;
|
||||
canvas.style.top = `${y}px`;
|
||||
this.bb.x = x;
|
||||
this.bb.y = y;
|
||||
this.canvas.style.left = `${x}px`;
|
||||
this.canvas.style.top = `${y}px`;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -241,6 +528,8 @@ const layers = {
|
|||
canvas.height = Math.round(
|
||||
options.resolution.h * (h / options.bb.h)
|
||||
);
|
||||
this.bb.w = w;
|
||||
this.bb.h = h;
|
||||
canvas.style.width = `${w}px`;
|
||||
canvas.style.height = `${h}px`;
|
||||
},
|
||||
|
|
|
@ -10,6 +10,19 @@
|
|||
* @property {number} y - y coordinate
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a size
|
||||
*/
|
||||
class Size {
|
||||
w = 0;
|
||||
h = 0;
|
||||
|
||||
constructor({w, h} = {w: 0, h: 0}) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a simple bouding box
|
||||
*/
|
||||
|
@ -288,14 +301,19 @@ function cropCanvas(sourceCanvas, options = {}) {
|
|||
|
||||
const w = sourceCanvas.width;
|
||||
const h = sourceCanvas.height;
|
||||
var imageData = sourceCanvas.getContext("2d").getImageData(0, 0, w, h);
|
||||
const srcCtx = sourceCanvas.getContext("2d");
|
||||
const offset = {
|
||||
x: (srcCtx.origin && -srcCtx.origin.x) || 0,
|
||||
y: (srcCtx.origin && -srcCtx.origin.y) || 0,
|
||||
};
|
||||
var imageData = srcCtx.getImageData(offset.x, offset.y, w, h);
|
||||
/** @type {BoundingBox} */
|
||||
const bb = new BoundingBox();
|
||||
|
||||
let minx = w;
|
||||
let maxx = -1;
|
||||
let miny = h;
|
||||
let maxy = -1;
|
||||
let minx = Infinity;
|
||||
let maxx = -Infinity;
|
||||
let miny = Infinity;
|
||||
let maxy = -Infinity;
|
||||
|
||||
for (let y = 0; y < h; y++) {
|
||||
for (let x = 0; x < w; x++) {
|
||||
|
@ -303,10 +321,10 @@ function cropCanvas(sourceCanvas, options = {}) {
|
|||
const index = (y * w + x) * 4; // OHHH OK this is setting the imagedata.data uint8clampeddataarray index for the specified x/y coords
|
||||
//this part i get, this is checking that 4th RGBA byte for opacity
|
||||
if (imageData.data[index + 3] > 0) {
|
||||
minx = Math.min(minx, x);
|
||||
maxx = Math.max(maxx, x);
|
||||
miny = Math.min(miny, y);
|
||||
maxy = Math.max(maxy, y);
|
||||
minx = Math.min(minx, x + offset.x);
|
||||
maxx = Math.max(maxx, x + offset.x);
|
||||
miny = Math.min(miny, y + offset.y);
|
||||
maxy = Math.max(maxy, y + offset.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,13 +298,23 @@ const uil = {
|
|||
canvas.width = bb.w;
|
||||
canvas.height = bb.h;
|
||||
if (options.includeBg)
|
||||
ctx.drawImage(bgLayer.canvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
|
||||
ctx.drawImage(
|
||||
bgLayer.canvas,
|
||||
bb.x + bgLayer.origin.x,
|
||||
bb.y + bgLayer.origin.y,
|
||||
bb.w,
|
||||
bb.h,
|
||||
0,
|
||||
0,
|
||||
bb.w,
|
||||
bb.h
|
||||
);
|
||||
this.layers.forEach((layer) => {
|
||||
if (!layer.hidden)
|
||||
ctx.drawImage(
|
||||
layer.layer.canvas,
|
||||
bb.x,
|
||||
bb.y,
|
||||
bb.x + layer.layer.origin.x,
|
||||
bb.y + layer.layer.origin.y,
|
||||
bb.w,
|
||||
bb.h,
|
||||
0,
|
||||
|
|
|
@ -41,7 +41,7 @@ const colorBrushTool = () =>
|
|||
"Color Brush",
|
||||
(state, opt) => {
|
||||
// Draw new cursor immediately
|
||||
uiCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||
state.movecb({
|
||||
...mouse.coords.world.pos,
|
||||
evn: {
|
||||
|
@ -64,11 +64,11 @@ const colorBrushTool = () =>
|
|||
after: imgLayer,
|
||||
ctxOptions: {willReadFrequently: true},
|
||||
});
|
||||
state.drawLayer.canvas.style.filter = "opacity(70%)";
|
||||
state.eraseLayer = imageCollection.registerLayer(null, {
|
||||
after: imgLayer,
|
||||
ctxOptions: {willReadFrequently: true},
|
||||
});
|
||||
state.eraseLayer.canvas.style.display = "none";
|
||||
state.eraseLayer.hide();
|
||||
state.eraseBackup = imageCollection.registerLayer(null, {
|
||||
after: imgLayer,
|
||||
|
@ -209,7 +209,7 @@ const colorBrushTool = () =>
|
|||
state.brushSize -
|
||||
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
||||
);
|
||||
uiCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||
state.movecb(evn);
|
||||
}
|
||||
};
|
||||
|
@ -231,12 +231,8 @@ const colorBrushTool = () =>
|
|||
state.keyupcb = (evn) => {
|
||||
switch (evn.code) {
|
||||
case "ShiftLeft":
|
||||
if (!keyboard.isPressed("ShiftRight")) {
|
||||
state.disableDropper();
|
||||
}
|
||||
break;
|
||||
case "ShiftRight":
|
||||
if (!keyboard.isPressed("ShiftLeft")) {
|
||||
if (!keyboard.isPressed(evn.code)) {
|
||||
state.disableDropper();
|
||||
}
|
||||
break;
|
||||
|
@ -286,8 +282,9 @@ const colorBrushTool = () =>
|
|||
const canvas = state.drawLayer.canvas;
|
||||
const ctx = state.drawLayer.ctx;
|
||||
|
||||
const cropped = cropCanvas(canvas, {border: 10});
|
||||
const cropped = cropCanvas(canvas, {border: 10, origin: uil.origin});
|
||||
const bb = cropped.bb;
|
||||
|
||||
commands.runCommand("drawImage", "Color Brush Draw", {
|
||||
image: cropped.canvas,
|
||||
...bb,
|
||||
|
@ -302,10 +299,9 @@ const colorBrushTool = () =>
|
|||
if (state.affectMask) _mask_brush_erase_callback(evn, state);
|
||||
|
||||
// Make a backup of the current image to apply erase later
|
||||
const bkpcanvas = state.eraseBackup.canvas;
|
||||
const bkpctx = state.eraseBackup.ctx;
|
||||
bkpctx.clearRect(0, 0, bkpcanvas.width, bkpcanvas.height);
|
||||
bkpctx.drawImage(uil.canvas, 0, 0);
|
||||
state.eraseBackup.clear();
|
||||
bkpctx.drawImageRoot(uil.canvas, 0, 0);
|
||||
|
||||
uil.ctx.globalCompositeOperation = "destination-out";
|
||||
_color_brush_erase_callback(evn, state, uil.ctx);
|
||||
|
@ -335,8 +331,8 @@ const colorBrushTool = () =>
|
|||
const bb = cropped.bb;
|
||||
|
||||
uil.ctx.filter = null;
|
||||
uil.ctx.clearRect(0, 0, uil.canvas.width, uil.canvas.height);
|
||||
uil.ctx.drawImage(bkpcanvas, 0, 0);
|
||||
uil.layer.clear();
|
||||
uil.ctx.drawImageRoot(bkpcanvas, 0, 0);
|
||||
|
||||
commands.runCommand("eraseImage", "Color Brush Erase", {
|
||||
mask: cropped.canvas,
|
||||
|
|
|
@ -318,7 +318,7 @@ const _generate = async (endpoint, request, bb, options = {}) => {
|
|||
ctx.drawImage(keepUnmaskCanvas, 0, 0);
|
||||
}
|
||||
|
||||
layer.ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
|
||||
layer.clear();
|
||||
layer.ctx.drawImage(
|
||||
canvas,
|
||||
0,
|
||||
|
@ -765,8 +765,8 @@ const dream_generate_callback = async (bb, resolution, state) => {
|
|||
bbCtx.globalCompositeOperation = "destination-in";
|
||||
bbCtx.drawImage(
|
||||
maskPaintCanvas,
|
||||
bb.x,
|
||||
bb.y,
|
||||
bb.x + maskPaintLayer.origin.x,
|
||||
bb.y + maskPaintLayer.origin.y,
|
||||
bb.w,
|
||||
bb.h,
|
||||
0,
|
||||
|
@ -793,8 +793,8 @@ const dream_generate_callback = async (bb, resolution, state) => {
|
|||
bbCtx.globalCompositeOperation = "destination-out"; // ???
|
||||
bbCtx.drawImage(
|
||||
maskPaintCanvas,
|
||||
bb.x,
|
||||
bb.y,
|
||||
bb.x + maskPaintLayer.origin.x,
|
||||
bb.y + maskPaintLayer.origin.y,
|
||||
bb.w,
|
||||
bb.h,
|
||||
0,
|
||||
|
@ -915,7 +915,17 @@ const dream_img2img_callback = (bb, resolution, state) => {
|
|||
bbCtx.fillStyle = state.invertMask ? "#FFFF" : "#000F";
|
||||
bbCtx.fillRect(0, 0, bb.w, bb.h);
|
||||
bbCtx.globalCompositeOperation = "destination-out";
|
||||
bbCtx.drawImage(maskPaintCanvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
|
||||
bbCtx.drawImage(
|
||||
maskPaintCanvas,
|
||||
bb.x + maskPaintLayer.origin.x,
|
||||
bb.y + maskPaintLayer.origin.y,
|
||||
bb.w,
|
||||
bb.h,
|
||||
0,
|
||||
0,
|
||||
bb.w,
|
||||
bb.h
|
||||
);
|
||||
|
||||
bbCtx.globalCompositeOperation = "destination-atop";
|
||||
bbCtx.fillStyle = state.invertMask ? "#000F" : "#FFFF";
|
||||
|
|
|
@ -240,12 +240,7 @@ const maskBrushTool = () =>
|
|||
clearMaskButton.textContent = "Clear";
|
||||
clearMaskButton.title = "Clears Painted Mask";
|
||||
clearMaskButton.onclick = () => {
|
||||
maskPaintCtx.clearRect(
|
||||
0,
|
||||
0,
|
||||
maskPaintCanvas.width,
|
||||
maskPaintCanvas.height
|
||||
);
|
||||
maskPaintLayer.clear();
|
||||
};
|
||||
|
||||
const previewMaskButton = document.createElement("button");
|
||||
|
|
|
@ -6,7 +6,7 @@ const stampTool = () =>
|
|||
state.loaded = true;
|
||||
|
||||
// Draw new cursor immediately
|
||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||
ovLayer.clear();
|
||||
state.movecb({...mouse.coords.world.pos});
|
||||
|
||||
// Start Listeners
|
||||
|
@ -47,7 +47,7 @@ const stampTool = () =>
|
|||
child.classList.remove("active");
|
||||
});
|
||||
|
||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||
ovLayer.clear();
|
||||
},
|
||||
{
|
||||
init: (state) => {
|
||||
|
@ -88,7 +88,7 @@ const stampTool = () =>
|
|||
state.selected = null;
|
||||
}
|
||||
|
||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||
ovLayer.clear();
|
||||
if (state.loaded) state.movecb(state.lastMouseMove);
|
||||
};
|
||||
|
||||
|
@ -300,7 +300,7 @@ const stampTool = () =>
|
|||
|
||||
state.lastMouseMove = evn;
|
||||
|
||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||
ovLayer.clear();
|
||||
|
||||
// Draw selected image
|
||||
if (state.selected) {
|
||||
|
|
Loading…
Reference in a new issue