commit
dd52529b6d
13 changed files with 701 additions and 123 deletions
|
@ -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 {
|
||||||
|
background-color: #293d3d77;
|
||||||
|
}
|
||||||
|
|
19
js/index.js
19
js/index.js
|
@ -343,17 +343,14 @@ 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,
|
...layer.bb,
|
||||||
y: 0,
|
|
||||||
w: layer.canvas.width,
|
|
||||||
h: layer.canvas.height,
|
|
||||||
ctx: layer.ctx,
|
ctx: layer.ctx,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearPaintedMask() {
|
function clearPaintedMask() {
|
||||||
maskPaintCtx.clearRect(0, 0, maskPaintCanvas.width, maskPaintCanvas.height);
|
maskPaintLayer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function march(bb, options = {}) {
|
function march(bb, options = {}) {
|
||||||
|
@ -558,8 +555,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);
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -53,44 +53,97 @@ 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});
|
|
||||||
|
|
||||||
// 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 +211,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 +254,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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -197,8 +267,14 @@ mouse.listen.window.btn.middle.onpaint.on((evn) => {
|
||||||
viewport.cy = worldInit.y + (evn.iy - evn.y) / viewport.zoom;
|
viewport.cy = worldInit.y + (evn.iy - evn.y) / viewport.zoom;
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
viewport.cx = Math.max(Math.min(viewport.cx, imageCollection.size.w), 0);
|
viewport.cx = Math.max(
|
||||||
viewport.cy = Math.max(Math.min(viewport.cy, imageCollection.size.h), 0);
|
Math.min(viewport.cx, imageCollection.size.w - imageCollection.origin.x),
|
||||||
|
-imageCollection.origin.x
|
||||||
|
);
|
||||||
|
viewport.cy = Math.max(
|
||||||
|
Math.min(viewport.cy, imageCollection.size.h - imageCollection.origin.y),
|
||||||
|
-imageCollection.origin.y
|
||||||
|
);
|
||||||
|
|
||||||
// Draw Viewport location
|
// Draw Viewport location
|
||||||
}
|
}
|
||||||
|
|
47
js/lib/layers.d.js
Normal file
47
js/lib/layers.d.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* A layer
|
||||||
|
*
|
||||||
|
* @typedef {object} Layer
|
||||||
|
* @property {string} id The id of the layer
|
||||||
|
* @property {string} key A identifier for the layer
|
||||||
|
* @property {string} name The display name of the layer
|
||||||
|
* @property {BoundingBox} bb The current bounding box of the layer, in layer coordinates
|
||||||
|
* @property {Size} resolution The resolution of the layer (canvas)
|
||||||
|
* @property {boolean} full If the layer is a full layer (occupies the full collection)
|
||||||
|
* @property {number} x The x coordinate of the layer
|
||||||
|
* @property {number} y The y coordinate of the layer
|
||||||
|
* @property {number} width The width of the layer
|
||||||
|
* @property {number} w The width of the layer
|
||||||
|
* @property {number} height The height of the layer
|
||||||
|
* @property {number} h The height of the layer
|
||||||
|
* @property {Point} origin The location of the origin ((0, 0) point) of the layer (If canvas goes from -64, -32 to 128, 512, it's (64, 32))
|
||||||
|
* @property {HTMLCanvasElement} canvas The canvas element of the layers
|
||||||
|
* @property {CanvasRenderingContext2D} ctx The context of the canvas of the layer
|
||||||
|
* @property {function} clear Clears the layer contents
|
||||||
|
* @property {function} moveAfter Moves this layer to another level (after given layer)
|
||||||
|
* @property {function} moveBefore Moves this layer to another level (before given layer)
|
||||||
|
* @property {function} moveTo Moves this layer to another location
|
||||||
|
* @property {function} resize Resizes the layer in place
|
||||||
|
* @property {function} hide Hides the layer
|
||||||
|
* @property {function} unhide Unhides the layer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A layer collection
|
||||||
|
*
|
||||||
|
* @typedef {object} LayerCollection
|
||||||
|
* @property {string} id The id of the collection
|
||||||
|
* @property {string} key A identifier for the collection
|
||||||
|
* @property {string} name The display name of the collection
|
||||||
|
* @property {HTMLDivElement} element The base element of the collection
|
||||||
|
* @property {HTMLDivElement} inputElement The element used for input handling for the collection
|
||||||
|
* @property {Point} inputOffset The offset for calculating layer coordinates from input element input information
|
||||||
|
* @property {Point} origin The location of the origin ((0, 0) point) of the collection (If canvas goes from -64, -32 to 128, 512, it's (64, 32))
|
||||||
|
* @property {BoundingBox} bb The current bounding box of the collection, in layer coordinates
|
||||||
|
* @property {{[key: string]: Layer}} layers An object for quick access to named layers of the collection
|
||||||
|
* @property {Size} size The size of the collection (CSS)
|
||||||
|
* @property {Size} resolution The resolution of the collection (canvas)
|
||||||
|
* @property {function} expand Expands the collection and its full layers by the specified amounts
|
||||||
|
* @property {function} registerLayer Registers a new layer
|
||||||
|
* @property {function} deleteLayer Deletes a layer from the collection
|
||||||
|
*/
|
374
js/lib/layers.js
374
js/lib/layers.js
|
@ -3,6 +3,162 @@
|
||||||
*
|
*
|
||||||
* 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);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add basic get bounding box support (canvas coordinates)
|
||||||
|
Reflect.defineProperty(CanvasRenderingContext2D.prototype, "bb", {
|
||||||
|
get: function () {
|
||||||
|
return new BoundingBox({
|
||||||
|
x: -this.origin.x,
|
||||||
|
y: -this.origin.y,
|
||||||
|
w: this.canvas.width,
|
||||||
|
h: this.canvas.height,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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"),
|
||||||
|
@ -17,6 +173,18 @@ const layers = {
|
||||||
|
|
||||||
// Registers a new collection
|
// Registers a new collection
|
||||||
// Layer collections are a group of layers (canvases) that are rendered in tandem. (same width, height, position, transform, etc)
|
// Layer collections are a group of layers (canvases) that are rendered in tandem. (same width, height, position, transform, etc)
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} key A key used to identify the collection
|
||||||
|
* @param {Size} size The initial size of the collection in pixels (CSS size)
|
||||||
|
* @param {object} options Extra options for the collection
|
||||||
|
* @param {string} [options.name=key] The display name of the collection
|
||||||
|
* @param {{key: string, options: object}} [options.initLayer] The configuration for the initial layer to be created
|
||||||
|
* @param {number} [options.inputSizeMultiplier=9] Size of the input area element, in pixels
|
||||||
|
* @param {HTMLElement} [options.targetElement] Element the collection will be inserted into
|
||||||
|
* @param {Size} [options.resolution=size] The resolution of the collection (canvas size). Not sure it works.
|
||||||
|
* @returns {LayerCollection} The newly created layer collection
|
||||||
|
*/
|
||||||
registerCollection: (key, size, options = {}) => {
|
registerCollection: (key, size, options = {}) => {
|
||||||
defaultOpt(options, {
|
defaultOpt(options, {
|
||||||
// Display name for the collection
|
// Display name for the collection
|
||||||
|
@ -29,7 +197,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"),
|
||||||
|
@ -64,6 +232,7 @@ const layers = {
|
||||||
|
|
||||||
options.targetElement.appendChild(element);
|
options.targetElement.appendChild(element);
|
||||||
|
|
||||||
|
/** @type {LayerCollection} */
|
||||||
const collection = makeWriteOnce(
|
const collection = makeWriteOnce(
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
|
@ -73,6 +242,7 @@ const layers = {
|
||||||
_layers: [],
|
_layers: [],
|
||||||
layers: {},
|
layers: {},
|
||||||
|
|
||||||
|
key,
|
||||||
name: options.name,
|
name: options.name,
|
||||||
element,
|
element,
|
||||||
inputElement: inputel,
|
inputElement: inputel,
|
||||||
|
@ -81,11 +251,30 @@ const layers = {
|
||||||
return this._inputOffset;
|
return this._inputOffset;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_origin: {x: 0, y: 0},
|
||||||
|
get origin() {
|
||||||
|
return {...this._origin};
|
||||||
|
},
|
||||||
|
|
||||||
|
get bb() {
|
||||||
|
return new BoundingBox({
|
||||||
|
x: -this.origin.x,
|
||||||
|
y: -this.origin.y,
|
||||||
|
w: this.size.w,
|
||||||
|
h: this.size.h,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
_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 +286,48 @@ 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`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands the collection and its full layers by the specified amounts
|
||||||
|
*
|
||||||
|
* @param {number} left Pixels to expand left
|
||||||
|
* @param {number} top Pixels to expand top
|
||||||
|
* @param {number} right Pixels to expand right
|
||||||
|
* @param {number} bottom Pixels to expand bottom
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
@ -113,9 +344,9 @@ const layers = {
|
||||||
* @param {?string} options.group
|
* @param {?string} options.group
|
||||||
* @param {object} options.after
|
* @param {object} options.after
|
||||||
* @param {object} options.ctxOptions
|
* @param {object} options.ctxOptions
|
||||||
* @returns
|
* @returns {Layer} The newly created layer
|
||||||
*/
|
*/
|
||||||
registerLayer: (key = null, options = {}) => {
|
registerLayer(key = null, options = {}) {
|
||||||
// Make ID
|
// Make ID
|
||||||
const id = guid();
|
const id = guid();
|
||||||
|
|
||||||
|
@ -124,7 +355,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 +375,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 +416,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 +441,16 @@ const layers = {
|
||||||
_logpath: _layerlogpath,
|
_logpath: _layerlogpath,
|
||||||
_collection: collection,
|
_collection: collection,
|
||||||
|
|
||||||
|
_bb: new BoundingBox(options.bb),
|
||||||
|
get bb() {
|
||||||
|
return new BoundingBox(this._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 +466,81 @@ 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,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called by the collection when the layer must be expanded.
|
||||||
|
*
|
||||||
|
* Should NOT be called directly
|
||||||
|
*
|
||||||
|
* @param {number} left Pixels to expand left
|
||||||
|
* @param {number} top Pixels to expand top
|
||||||
|
* @param {number} right Pixels to expand right
|
||||||
|
* @param {number} bottom Pixels to expand bottom
|
||||||
|
*/
|
||||||
|
_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 +566,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 +585,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`;
|
||||||
},
|
},
|
||||||
|
@ -282,7 +628,11 @@ const layers = {
|
||||||
return layer;
|
return layer;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Deletes a layer
|
/**
|
||||||
|
* Deletes a layer from the collection
|
||||||
|
*
|
||||||
|
* @param {Layer} layer Layer to delete
|
||||||
|
*/
|
||||||
deleteLayer: (layer) => {
|
deleteLayer: (layer) => {
|
||||||
const lobj = collection._layers.splice(
|
const lobj = collection._layers.splice(
|
||||||
collection._layers.findIndex(
|
collection._layers.findIndex(
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -231,7 +244,12 @@ function makeWriteOnce(obj, name = "write-once object", exceptions = []) {
|
||||||
* @returns an offset, in which [i + offset = (a location snapped to the grid)]
|
* @returns an offset, in which [i + offset = (a location snapped to the grid)]
|
||||||
*/
|
*/
|
||||||
function snap(i, offset = 0, gridSize = 64) {
|
function snap(i, offset = 0, gridSize = 64) {
|
||||||
const modulus = (i - offset) % gridSize;
|
let diff = i - offset;
|
||||||
|
if (diff < 0) {
|
||||||
|
diff += gridSize * Math.ceil(Math.abs(diff / gridSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
const modulus = diff % gridSize;
|
||||||
var snapOffset = modulus;
|
var snapOffset = modulus;
|
||||||
|
|
||||||
if (modulus > gridSize / 2) snapOffset = modulus - gridSize;
|
if (modulus > gridSize / 2) snapOffset = modulus - gridSize;
|
||||||
|
@ -288,14 +306,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.getImageDataRoot(0, 0, 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 +326,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,7 +339,8 @@ function cropCanvas(sourceCanvas, options = {}) {
|
||||||
bb.w = maxx - minx + 1 + 2 * options.border;
|
bb.w = maxx - minx + 1 + 2 * options.border;
|
||||||
bb.h = maxy - miny + 1 + 2 * options.border;
|
bb.h = maxy - miny + 1 + 2 * options.border;
|
||||||
|
|
||||||
if (maxx < 0) throw new NoContentError("Canvas has no content to crop");
|
if (!Number.isFinite(maxx))
|
||||||
|
throw new NoContentError("Canvas has no content to crop");
|
||||||
|
|
||||||
var cutCanvas = document.createElement("canvas");
|
var cutCanvas = document.createElement("canvas");
|
||||||
cutCanvas.width = bb.w;
|
cutCanvas.width = bb.w;
|
||||||
|
@ -339,12 +363,7 @@ function cropCanvas(sourceCanvas, options = {}) {
|
||||||
function downloadCanvas(options = {}) {
|
function downloadCanvas(options = {}) {
|
||||||
defaultOpt(options, {
|
defaultOpt(options, {
|
||||||
cropToContent: true,
|
cropToContent: true,
|
||||||
canvas: uil.getVisible({
|
canvas: uil.getVisible(imageCollection.bb),
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
w: imageCollection.size.w,
|
|
||||||
h: imageCollection.size.h,
|
|
||||||
}),
|
|
||||||
filename:
|
filename:
|
||||||
new Date()
|
new Date()
|
||||||
.toISOString()
|
.toISOString()
|
||||||
|
@ -360,6 +379,7 @@ function downloadCanvas(options = {}) {
|
||||||
var croppedCanvas = options.cropToContent
|
var croppedCanvas = options.cropToContent
|
||||||
? cropCanvas(options.canvas).canvas
|
? cropCanvas(options.canvas).canvas
|
||||||
: options.canvas;
|
: options.canvas;
|
||||||
|
|
||||||
if (croppedCanvas != null) {
|
if (croppedCanvas != null) {
|
||||||
croppedCanvas.toBlob((blob) => {
|
croppedCanvas.toBlob((blob) => {
|
||||||
link.href = URL.createObjectURL(blob);
|
link.href = URL.createObjectURL(blob);
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -288,6 +284,7 @@ const colorBrushTool = () =>
|
||||||
|
|
||||||
const cropped = cropCanvas(canvas, {border: 10});
|
const cropped = cropCanvas(canvas, {border: 10});
|
||||||
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,
|
||||||
|
|
|
@ -39,12 +39,13 @@ const _monitorProgress = (bb, oncheck = null) => {
|
||||||
|
|
||||||
oncheck && oncheck(data);
|
oncheck && oncheck(data);
|
||||||
|
|
||||||
|
layer.clear();
|
||||||
|
|
||||||
// Draw Progress Bar
|
// Draw Progress Bar
|
||||||
layer.ctx.fillStyle = "#5F5";
|
layer.ctx.fillStyle = "#5F5";
|
||||||
layer.ctx.fillRect(1, 1, bb.w * data.progress, 10);
|
layer.ctx.fillRect(1, 1, bb.w * data.progress, 10);
|
||||||
|
|
||||||
// Draw Progress Text
|
// Draw Progress Text
|
||||||
layer.ctx.clearRect(0, 11, expanded.w, 40);
|
|
||||||
layer.ctx.fillStyle = "#FFF";
|
layer.ctx.fillStyle = "#FFF";
|
||||||
|
|
||||||
layer.ctx.fillRect(0, 15, 60, 25);
|
layer.ctx.fillRect(0, 15, 60, 25);
|
||||||
|
@ -295,8 +296,7 @@ const _generate = async (endpoint, request, bb, options = {}) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const redraw = (url = images[at]) => {
|
const redraw = (url = images[at]) => {
|
||||||
if (url === null)
|
if (url === null) layer.clear();
|
||||||
layer.ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
|
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
|
@ -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,
|
||||||
|
@ -770,8 +770,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,
|
||||||
|
@ -798,8 +798,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,
|
||||||
|
@ -920,7 +920,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";
|
||||||
|
|
|
@ -4,10 +4,8 @@ const interrogateTool = () =>
|
||||||
"Interrogate",
|
"Interrogate",
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovLayer.clear();
|
||||||
state.mousemovecb({
|
state.redraw();
|
||||||
...mouse.coords.world.pos,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start Listeners
|
// Start Listeners
|
||||||
mouse.listen.world.onmousemove.on(state.mousemovecb);
|
mouse.listen.world.onmousemove.on(state.mousemovecb);
|
||||||
|
@ -37,8 +35,7 @@ const interrogateTool = () =>
|
||||||
state.invertMask = false;
|
state.invertMask = false;
|
||||||
state.overMaskPx = 0;
|
state.overMaskPx = 0;
|
||||||
|
|
||||||
state.erasePrevReticle = () =>
|
state.erasePrevReticle = () => ovLayer.clear();
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
|
||||||
|
|
||||||
state.mousemovecb = (evn) => {
|
state.mousemovecb = (evn) => {
|
||||||
state.erasePrevReticle();
|
state.erasePrevReticle();
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -4,7 +4,7 @@ const selectTransformTool = () =>
|
||||||
"Select Image",
|
"Select Image",
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// 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);
|
||||||
|
|
||||||
// Canvas left mouse handlers
|
// Canvas left mouse handlers
|
||||||
|
@ -46,7 +46,7 @@ const selectTransformTool = () =>
|
||||||
state.reset();
|
state.reset();
|
||||||
|
|
||||||
// Resets cursor
|
// Resets cursor
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovLayer.clear();
|
||||||
|
|
||||||
// Clears overlay
|
// Clears overlay
|
||||||
imageCollection.inputElement.style.cursor = "auto";
|
imageCollection.inputElement.style.cursor = "auto";
|
||||||
|
@ -80,7 +80,7 @@ const selectTransformTool = () =>
|
||||||
state.lastMouseMove = null;
|
state.lastMouseMove = null;
|
||||||
|
|
||||||
state.redraw = () => {
|
state.redraw = () => {
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovLayer.clear();
|
||||||
state.movecb(state.lastMouseMove);
|
state.movecb(state.lastMouseMove);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ const selectTransformTool = () =>
|
||||||
|
|
||||||
// Mouse move handler. As always, also renders cursor
|
// Mouse move handler. As always, also renders cursor
|
||||||
state.movecb = (evn) => {
|
state.movecb = (evn) => {
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovLayer.clear();
|
||||||
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
imageCollection.inputElement.style.cursor = "auto";
|
imageCollection.inputElement.style.cursor = "auto";
|
||||||
state.lastMouseTarget = evn.target;
|
state.lastMouseTarget = evn.target;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue