canvas scaling?

Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
Victor Seiji Hariki 2022-12-16 22:57:28 -03:00
parent 6d9155261e
commit ea68e25cf0
11 changed files with 578 additions and 98 deletions

View file

@ -132,3 +132,72 @@
flex: 1; flex: 1;
height: 25px; 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%);
}

View file

@ -343,8 +343,8 @@ function newImage(evt) {
clearPaintedMask(); clearPaintedMask();
uil.layers.forEach(({layer}) => { uil.layers.forEach(({layer}) => {
commands.runCommand("eraseImage", "Clear Canvas", { commands.runCommand("eraseImage", "Clear Canvas", {
x: 0, x: -layer.origin.x,
y: 0, y: -layer.origin.y,
w: layer.canvas.width, w: layer.canvas.width,
h: layer.canvas.height, h: layer.canvas.height,
ctx: layer.ctx, ctx: layer.ctx,
@ -353,7 +353,7 @@ function newImage(evt) {
} }
function clearPaintedMask() { function clearPaintedMask() {
maskPaintCtx.clearRect(0, 0, maskPaintCanvas.width, maskPaintCanvas.height); maskPaintLayer.clear();
} }
function march(bb, options = {}) { function march(bb, options = {}) {
@ -558,8 +558,16 @@ function drawBackground() {
// Checkerboard // Checkerboard
let darkTileColor = "#333"; let darkTileColor = "#333";
let lightTileColor = "#555"; let lightTileColor = "#555";
for (var x = 0; x < bgLayer.canvas.width; x += 64) { for (
for (var y = 0; y < bgLayer.canvas.height; y += 64) { 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 = bgLayer.ctx.fillStyle =
(x + y) % 128 === 0 ? lightTileColor : darkTileColor; (x + y) % 128 === 0 ? lightTileColor : darkTileColor;
bgLayer.ctx.fillRect(x, y, 64, 64); bgLayer.ctx.fillRect(x, y, 64, 64);

View file

@ -17,6 +17,19 @@ mouse.listen.world.onmousemove.on((evn) => {
canvasYInfo.textContent = evn.y; canvasYInfo.textContent = evn.y;
snapXInfo.textContent = evn.x + snap(evn.x); snapXInfo.textContent = evn.x + snap(evn.x);
snapYInfo.textContent = evn.y + snap(evn.y); 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();
}
}); });
/** /**

View file

@ -53,44 +53,99 @@ uiCanvas.width = uiCanvas.clientWidth;
uiCanvas.height = uiCanvas.clientHeight; uiCanvas.height = uiCanvas.clientHeight;
const uiCtx = uiCanvas.getContext("2d", {desynchronized: true}); 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 debugLayer.hide(); // Hidden by default
layers.registerCollection("mask", {name: "Mask Layers", requiresActive: true}); layers.registerCollection("mask", {name: "Mask Layers", requiresActive: true});
// Where CSS and javascript magic happens to make the canvas viewport work // 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 * The global viewport object (may be modularized in the future). All
* coordinates given are of the center of the viewport * coordinates given are of the center of the viewport
@ -158,6 +213,31 @@ let worldInit = null;
viewport.transform(imageCollection.element); 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) => { mouse.listen.window.onwheel.on((evn) => {
if (evn.evn.ctrlKey) { if (evn.evn.ctrlKey) {
evn.evn.preventDefault(); evn.evn.preventDefault();
@ -176,14 +256,6 @@ mouse.listen.window.onwheel.on((evn) => {
viewport.transform(imageCollection.element); viewport.transform(imageCollection.element);
toolbar.currentTool.redraw(); 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();
}
} }
}); });

View file

@ -3,6 +3,150 @@
* *
* It manages canvases and their locations and sizes according to current viewport views * 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 = { const layers = {
_collections: [], _collections: [],
collections: makeWriteOnce({}, "layers.collections"), collections: makeWriteOnce({}, "layers.collections"),
@ -29,7 +173,7 @@ const layers = {
}, },
// Input multiplier (Size of the input element div) // Input multiplier (Size of the input element div)
inputSizeMultiplier: 3, inputSizeMultiplier: 9,
// Target // Target
targetElement: document.getElementById("layer-render"), targetElement: document.getElementById("layer-render"),
@ -81,11 +225,21 @@ const layers = {
return this._inputOffset; return this._inputOffset;
}, },
_origin: {x: 0, y: 0},
get origin() {
return {...this._origin};
},
_resizeInputDiv() { _resizeInputDiv() {
// Set offset // Set offset
const oldOffset = {...this._inputOffset};
this._inputOffset = { this._inputOffset = {
x: -Math.floor(options.inputSizeMultiplier / 2) * size.w, x:
y: -Math.floor(options.inputSizeMultiplier / 2) * size.h, -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 // Resize the input element
@ -97,6 +251,40 @@ const layers = {
this.inputElement.style.height = `${ this.inputElement.style.height = `${
size.h * options.inputSizeMultiplier size.h * options.inputSizeMultiplier
}px`; }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, size,
@ -115,7 +303,7 @@ const layers = {
* @param {object} options.ctxOptions * @param {object} options.ctxOptions
* @returns * @returns
*/ */
registerLayer: (key = null, options = {}) => { registerLayer(key = null, options = {}) {
// Make ID // Make ID
const id = guid(); const id = guid();
@ -124,7 +312,12 @@ const layers = {
name: key || `Temporary ${id}`, name: key || `Temporary ${id}`,
// Bounding box for layer // 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 for layer
resolution: null, resolution: null,
@ -139,11 +332,25 @@ const layers = {
ctxOptions: {}, 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) if (!options.resolution)
// Calculate resolution
options.resolution = { options.resolution = {
w: (collection.resolution.w / collection.size.w) * options.bb.w, w: Math.round(
h: (collection.resolution.h / collection.size.h) * options.bb.h, (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 // This layer's canvas
@ -166,7 +373,21 @@ const layers = {
options.after.canvas.after(canvas); options.after.canvas.after(canvas);
} }
/**
* Here we set the context origin for using the black magic.
*/
const ctx = canvas.getContext("2d", options.ctxOptions); 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 // Path used for logging purposes
const _layerlogpath = key const _layerlogpath = key
@ -177,9 +398,12 @@ const layers = {
_logpath: _layerlogpath, _logpath: _layerlogpath,
_collection: collection, _collection: collection,
bb: new BoundingBox(options.bb),
resolution: new Size(options.resolution),
id, id,
key, key,
name: options.name, name: options.name,
full,
state: new Proxy( state: new Proxy(
{visible: true}, {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 */ /** Our canvas */
canvas, canvas,
ctx, 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) * 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 * @param {number} y Y coordinate of the top left of the canvas
*/ */
moveTo(x, y) { moveTo(x, y) {
canvas.style.left = `${x}px`; this.bb.x = x;
canvas.style.top = `${y}px`; 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( canvas.height = Math.round(
options.resolution.h * (h / options.bb.h) options.resolution.h * (h / options.bb.h)
); );
this.bb.w = w;
this.bb.h = h;
canvas.style.width = `${w}px`; canvas.style.width = `${w}px`;
canvas.style.height = `${h}px`; canvas.style.height = `${h}px`;
}, },

View file

@ -10,6 +10,19 @@
* @property {number} y - y coordinate * @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 * Represents a simple bouding box
*/ */
@ -288,14 +301,19 @@ function cropCanvas(sourceCanvas, options = {}) {
const w = sourceCanvas.width; const w = sourceCanvas.width;
const h = sourceCanvas.height; 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} */ /** @type {BoundingBox} */
const bb = new BoundingBox(); const bb = new BoundingBox();
let minx = w; let minx = Infinity;
let maxx = -1; let maxx = -Infinity;
let miny = h; let miny = Infinity;
let maxy = -1; let maxy = -Infinity;
for (let y = 0; y < h; y++) { for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) { 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 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 //this part i get, this is checking that 4th RGBA byte for opacity
if (imageData.data[index + 3] > 0) { if (imageData.data[index + 3] > 0) {
minx = Math.min(minx, x); minx = Math.min(minx, x + offset.x);
maxx = Math.max(maxx, x); maxx = Math.max(maxx, x + offset.x);
miny = Math.min(miny, y); miny = Math.min(miny, y + offset.y);
maxy = Math.max(maxy, y); maxy = Math.max(maxy, y + offset.y);
} }
} }
} }

View file

@ -298,13 +298,23 @@ const uil = {
canvas.width = bb.w; canvas.width = bb.w;
canvas.height = bb.h; canvas.height = bb.h;
if (options.includeBg) 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) => { this.layers.forEach((layer) => {
if (!layer.hidden) if (!layer.hidden)
ctx.drawImage( ctx.drawImage(
layer.layer.canvas, layer.layer.canvas,
bb.x, bb.x + layer.layer.origin.x,
bb.y, bb.y + layer.layer.origin.y,
bb.w, bb.w,
bb.h, bb.h,
0, 0,

View file

@ -41,7 +41,7 @@ const colorBrushTool = () =>
"Color Brush", "Color Brush",
(state, opt) => { (state, opt) => {
// Draw new cursor immediately // Draw new cursor immediately
uiCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
state.movecb({ state.movecb({
...mouse.coords.world.pos, ...mouse.coords.world.pos,
evn: { evn: {
@ -64,11 +64,11 @@ const colorBrushTool = () =>
after: imgLayer, after: imgLayer,
ctxOptions: {willReadFrequently: true}, ctxOptions: {willReadFrequently: true},
}); });
state.drawLayer.canvas.style.filter = "opacity(70%)";
state.eraseLayer = imageCollection.registerLayer(null, { state.eraseLayer = imageCollection.registerLayer(null, {
after: imgLayer, after: imgLayer,
ctxOptions: {willReadFrequently: true}, ctxOptions: {willReadFrequently: true},
}); });
state.eraseLayer.canvas.style.display = "none";
state.eraseLayer.hide(); state.eraseLayer.hide();
state.eraseBackup = imageCollection.registerLayer(null, { state.eraseBackup = imageCollection.registerLayer(null, {
after: imgLayer, after: imgLayer,
@ -209,7 +209,7 @@ const colorBrushTool = () =>
state.brushSize - state.brushSize -
Math.floor(state.config.brushScrollSpeed * evn.delta) 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); state.movecb(evn);
} }
}; };
@ -231,12 +231,8 @@ const colorBrushTool = () =>
state.keyupcb = (evn) => { state.keyupcb = (evn) => {
switch (evn.code) { switch (evn.code) {
case "ShiftLeft": case "ShiftLeft":
if (!keyboard.isPressed("ShiftRight")) {
state.disableDropper();
}
break;
case "ShiftRight": case "ShiftRight":
if (!keyboard.isPressed("ShiftLeft")) { if (!keyboard.isPressed(evn.code)) {
state.disableDropper(); state.disableDropper();
} }
break; break;
@ -286,8 +282,9 @@ const colorBrushTool = () =>
const canvas = state.drawLayer.canvas; const canvas = state.drawLayer.canvas;
const ctx = state.drawLayer.ctx; const ctx = state.drawLayer.ctx;
const cropped = cropCanvas(canvas, {border: 10}); const cropped = cropCanvas(canvas, {border: 10, origin: uil.origin});
const bb = cropped.bb; const bb = cropped.bb;
commands.runCommand("drawImage", "Color Brush Draw", { commands.runCommand("drawImage", "Color Brush Draw", {
image: cropped.canvas, image: cropped.canvas,
...bb, ...bb,
@ -302,10 +299,9 @@ const colorBrushTool = () =>
if (state.affectMask) _mask_brush_erase_callback(evn, state); if (state.affectMask) _mask_brush_erase_callback(evn, state);
// Make a backup of the current image to apply erase later // Make a backup of the current image to apply erase later
const bkpcanvas = state.eraseBackup.canvas;
const bkpctx = state.eraseBackup.ctx; const bkpctx = state.eraseBackup.ctx;
bkpctx.clearRect(0, 0, bkpcanvas.width, bkpcanvas.height); state.eraseBackup.clear();
bkpctx.drawImage(uil.canvas, 0, 0); bkpctx.drawImageRoot(uil.canvas, 0, 0);
uil.ctx.globalCompositeOperation = "destination-out"; uil.ctx.globalCompositeOperation = "destination-out";
_color_brush_erase_callback(evn, state, uil.ctx); _color_brush_erase_callback(evn, state, uil.ctx);
@ -335,8 +331,8 @@ const colorBrushTool = () =>
const bb = cropped.bb; const bb = cropped.bb;
uil.ctx.filter = null; uil.ctx.filter = null;
uil.ctx.clearRect(0, 0, uil.canvas.width, uil.canvas.height); uil.layer.clear();
uil.ctx.drawImage(bkpcanvas, 0, 0); uil.ctx.drawImageRoot(bkpcanvas, 0, 0);
commands.runCommand("eraseImage", "Color Brush Erase", { commands.runCommand("eraseImage", "Color Brush Erase", {
mask: cropped.canvas, mask: cropped.canvas,

View file

@ -318,7 +318,7 @@ const _generate = async (endpoint, request, bb, options = {}) => {
ctx.drawImage(keepUnmaskCanvas, 0, 0); ctx.drawImage(keepUnmaskCanvas, 0, 0);
} }
layer.ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height); layer.clear();
layer.ctx.drawImage( layer.ctx.drawImage(
canvas, canvas,
0, 0,
@ -765,8 +765,8 @@ const dream_generate_callback = async (bb, resolution, state) => {
bbCtx.globalCompositeOperation = "destination-in"; bbCtx.globalCompositeOperation = "destination-in";
bbCtx.drawImage( bbCtx.drawImage(
maskPaintCanvas, maskPaintCanvas,
bb.x, bb.x + maskPaintLayer.origin.x,
bb.y, bb.y + maskPaintLayer.origin.y,
bb.w, bb.w,
bb.h, bb.h,
0, 0,
@ -793,8 +793,8 @@ const dream_generate_callback = async (bb, resolution, state) => {
bbCtx.globalCompositeOperation = "destination-out"; // ??? bbCtx.globalCompositeOperation = "destination-out"; // ???
bbCtx.drawImage( bbCtx.drawImage(
maskPaintCanvas, maskPaintCanvas,
bb.x, bb.x + maskPaintLayer.origin.x,
bb.y, bb.y + maskPaintLayer.origin.y,
bb.w, bb.w,
bb.h, bb.h,
0, 0,
@ -915,7 +915,17 @@ const dream_img2img_callback = (bb, resolution, state) => {
bbCtx.fillStyle = state.invertMask ? "#FFFF" : "#000F"; bbCtx.fillStyle = state.invertMask ? "#FFFF" : "#000F";
bbCtx.fillRect(0, 0, bb.w, bb.h); bbCtx.fillRect(0, 0, bb.w, bb.h);
bbCtx.globalCompositeOperation = "destination-out"; 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.globalCompositeOperation = "destination-atop";
bbCtx.fillStyle = state.invertMask ? "#000F" : "#FFFF"; bbCtx.fillStyle = state.invertMask ? "#000F" : "#FFFF";

View file

@ -240,12 +240,7 @@ const maskBrushTool = () =>
clearMaskButton.textContent = "Clear"; clearMaskButton.textContent = "Clear";
clearMaskButton.title = "Clears Painted Mask"; clearMaskButton.title = "Clears Painted Mask";
clearMaskButton.onclick = () => { clearMaskButton.onclick = () => {
maskPaintCtx.clearRect( maskPaintLayer.clear();
0,
0,
maskPaintCanvas.width,
maskPaintCanvas.height
);
}; };
const previewMaskButton = document.createElement("button"); const previewMaskButton = document.createElement("button");

View file

@ -6,7 +6,7 @@ const stampTool = () =>
state.loaded = true; state.loaded = true;
// Draw new cursor immediately // Draw new cursor immediately
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); ovLayer.clear();
state.movecb({...mouse.coords.world.pos}); state.movecb({...mouse.coords.world.pos});
// Start Listeners // Start Listeners
@ -47,7 +47,7 @@ const stampTool = () =>
child.classList.remove("active"); child.classList.remove("active");
}); });
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); ovLayer.clear();
}, },
{ {
init: (state) => { init: (state) => {
@ -88,7 +88,7 @@ const stampTool = () =>
state.selected = null; state.selected = null;
} }
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); ovLayer.clear();
if (state.loaded) state.movecb(state.lastMouseMove); if (state.loaded) state.movecb(state.lastMouseMove);
}; };
@ -300,7 +300,7 @@ const stampTool = () =>
state.lastMouseMove = evn; state.lastMouseMove = evn;
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); ovLayer.clear();
// Draw selected image // Draw selected image
if (state.selected) { if (state.selected) {