|
|
|
const imageCollection = layers.registerCollection( |
|
"image", |
|
{ |
|
w: parseInt( |
|
(localStorage && |
|
localStorage.getItem("openoutpaint/settings.canvas-width")) || |
|
2048 |
|
), |
|
h: parseInt( |
|
(localStorage && |
|
localStorage.getItem("openoutpaint/settings.canvas-height")) || |
|
2048 |
|
), |
|
}, |
|
{ |
|
name: "Image Layers", |
|
} |
|
); |
|
|
|
const bgLayer = imageCollection.registerLayer("bg", { |
|
name: "Background", |
|
category: "background", |
|
}); |
|
|
|
bgLayer.canvas.classList.add("pixelated"); |
|
|
|
const imgLayer = imageCollection.registerLayer("image", { |
|
name: "Image", |
|
category: "image", |
|
ctxOptions: {desynchronized: true}, |
|
}); |
|
const maskPaintLayer = imageCollection.registerLayer("mask", { |
|
name: "Mask Paint", |
|
category: "mask", |
|
ctxOptions: {desynchronized: true}, |
|
}); |
|
const ovLayer = imageCollection.registerLayer("overlay", { |
|
name: "Overlay", |
|
category: "display", |
|
}); |
|
const debugLayer = imageCollection.registerLayer("debug", { |
|
name: "Debug Layer", |
|
category: "display", |
|
}); |
|
|
|
const imgCanvas = imgLayer.canvas; |
|
const imgCtx = imgLayer.ctx; |
|
|
|
const maskPaintCanvas = maskPaintLayer.canvas; |
|
const maskPaintCtx = maskPaintLayer.ctx; |
|
|
|
maskPaintCanvas.classList.add("mask-canvas"); |
|
|
|
const ovCanvas = ovLayer.canvas; |
|
const ovCtx = ovLayer.ctx; |
|
|
|
const debugCanvas = debugLayer.canvas; |
|
const debugCtx = debugLayer.ctx; |
|
|
|
|
|
|
|
const uiCanvas = document.getElementById("layer-overlay"); |
|
uiCanvas.width = uiCanvas.clientWidth; |
|
uiCanvas.height = uiCanvas.clientHeight; |
|
const uiCtx = uiCanvas.getContext("2d", {desynchronized: true}); |
|
|
|
|
|
|
|
|
|
(() => { |
|
let expandSize = localStorage.getItem("openoutpaint/expand-size") || 1024; |
|
expandSize = parseInt(expandSize, 10); |
|
|
|
const askSize = (e) => { |
|
if (e.ctrlKey) return expandSize; |
|
const by = prompt("How much do you want to expand by?", expandSize); |
|
|
|
if (!by) return null; |
|
else { |
|
const len = parseInt(by, 10); |
|
localStorage.setItem("openoutpaint/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", (e) => { |
|
let size = null; |
|
if ((size = askSize(e))) { |
|
imageCollection.expand(size, 0, 0, 0); |
|
bgLayer.canvas.style.backgroundPosition = `${-snap( |
|
imageCollection.origin.x, |
|
0, |
|
config.gridSize * 2 |
|
)}px ${-snap(imageCollection.origin.y, 0, config.gridSize * 2)}px`; |
|
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", (e) => { |
|
let size = null; |
|
if ((size = askSize(e))) { |
|
imageCollection.expand(0, 0, size, 0); |
|
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", (e) => { |
|
let size = null; |
|
if ((size = askSize(e))) { |
|
imageCollection.expand(0, size, 0, 0); |
|
bgLayer.canvas.style.backgroundPosition = `${-snap( |
|
imageCollection.origin.x, |
|
0, |
|
config.gridSize * 2 |
|
)}px ${-snap(imageCollection.origin.y, 0, config.gridSize * 2)}px`; |
|
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", (e) => { |
|
let size = null; |
|
if ((size = askSize(e))) { |
|
imageCollection.expand(0, 0, 0, size); |
|
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(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Viewport { |
|
cx = 0; |
|
cy = 0; |
|
|
|
zoom = 1; |
|
|
|
|
|
|
|
|
|
get w() { |
|
return window.innerWidth * this.zoom; |
|
} |
|
|
|
|
|
|
|
|
|
get h() { |
|
return window.innerHeight * this.zoom; |
|
} |
|
|
|
constructor(x, y) { |
|
this.x = x; |
|
this.y = y; |
|
} |
|
|
|
get v2c() { |
|
const m = new DOMMatrix(); |
|
|
|
m.translateSelf(-this.w / 2, -this.h / 2); |
|
m.translateSelf(this.cx, this.cy); |
|
m.scaleSelf(this.zoom); |
|
|
|
return m; |
|
} |
|
|
|
get c2v() { |
|
return this.v2c.invertSelf(); |
|
} |
|
|
|
viewToCanvas(x, y) { |
|
if (x.x !== undefined) return this.v2c.transformPoint(x); |
|
return this.v2c.transformPoint({x, y}); |
|
} |
|
|
|
canvasToView(x, y) { |
|
if (x.x !== undefined) return this.c2v.transformPoint(x); |
|
return this.c2v.transformPoint({x, y}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
transform(el) { |
|
el.style.transformOrigin = "0px 0px"; |
|
el.style.transform = this.c2v; |
|
} |
|
} |
|
|
|
const viewport = new Viewport(0, 0); |
|
|
|
viewport.cx = imageCollection.size.w / 2; |
|
viewport.cy = imageCollection.size.h / 2; |
|
|
|
let worldInit = null; |
|
|
|
viewport.transform(imageCollection.element); |
|
|
|
|
|
|
|
|
|
|
|
mouse.registerContext( |
|
"world", |
|
(evn, ctx) => { |
|
|
|
ctx.coords.prev.x = ctx.coords.pos.x; |
|
ctx.coords.prev.y = ctx.coords.pos.y; |
|
|
|
|
|
const x = evn.clientX; |
|
const y = evn.clientY; |
|
|
|
|
|
const layerCoords = viewport.viewToCanvas(x, y); |
|
|
|
|
|
ctx.coords.pos.x = Math.round(layerCoords.x); |
|
ctx.coords.pos.y = Math.round(layerCoords.y); |
|
}, |
|
{ |
|
target: imageCollection.inputElement, |
|
validate: (evn) => { |
|
if ((!global.hasActiveInput && !evn.ctrlKey) || evn.type === "mousemove") |
|
return true; |
|
return false; |
|
}, |
|
} |
|
); |
|
|
|
mouse.registerContext( |
|
"camera", |
|
(evn, ctx) => { |
|
ctx.coords.prev.x = ctx.coords.pos.x; |
|
ctx.coords.prev.y = ctx.coords.pos.y; |
|
|
|
|
|
ctx.coords.pos.x = evn.x; |
|
ctx.coords.pos.y = evn.y; |
|
}, |
|
{ |
|
validate: (evn) => { |
|
return !!evn.ctrlKey; |
|
}, |
|
} |
|
); |
|
|
|
|
|
(() => { |
|
mouse.listen.window.onany.on((evn) => { |
|
const activeInput = DOM.hasActiveInput(); |
|
if (global.hasActiveInput !== activeInput) { |
|
global.hasActiveInput = activeInput; |
|
toolbar.currentTool && |
|
toolbar.currentTool.state.redraw && |
|
toolbar.currentTool.state.redraw(); |
|
} |
|
}); |
|
})(); |
|
|
|
mouse.listen.camera.onwheel.on((evn) => { |
|
evn.evn.preventDefault(); |
|
|
|
|
|
const wcursor = viewport.viewToCanvas(evn.x, evn.y); |
|
|
|
|
|
const wcx = viewport.cx; |
|
const wcy = viewport.cy; |
|
|
|
|
|
viewport.zoom *= 1 + evn.delta * 0.0002; |
|
|
|
|
|
const nwcursor = viewport.viewToCanvas(evn.x, evn.y); |
|
|
|
|
|
viewport.cx = wcx; |
|
viewport.cy = wcy; |
|
|
|
|
|
viewport.cx += wcursor.x - nwcursor.x; |
|
viewport.cy += wcursor.y - nwcursor.y; |
|
|
|
viewport.transform(imageCollection.element); |
|
|
|
toolbar._current_tool.redrawui && toolbar._current_tool.redrawui(); |
|
}); |
|
|
|
const cameraPaintStart = (evn) => { |
|
worldInit = {x: viewport.cx, y: viewport.cy}; |
|
}; |
|
|
|
const cameraPaint = (evn) => { |
|
if (worldInit) { |
|
viewport.cx = worldInit.x + (evn.ix - evn.x) * viewport.zoom; |
|
viewport.cy = worldInit.y + (evn.iy - evn.y) * viewport.zoom; |
|
|
|
|
|
viewport.cx = Math.max( |
|
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 |
|
); |
|
|
|
|
|
} |
|
|
|
viewport.transform(imageCollection.element); |
|
toolbar._current_tool.state.redrawui && |
|
toolbar._current_tool.state.redrawui(); |
|
|
|
if (global.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(); |
|
} |
|
}; |
|
|
|
const cameraPaintEnd = (evn) => { |
|
worldInit = null; |
|
}; |
|
|
|
mouse.listen.camera.btn.middle.onpaintstart.on(cameraPaintStart); |
|
mouse.listen.camera.btn.left.onpaintstart.on(cameraPaintStart); |
|
|
|
mouse.listen.camera.btn.middle.onpaint.on(cameraPaint); |
|
mouse.listen.camera.btn.left.onpaint.on(cameraPaint); |
|
|
|
mouse.listen.window.btn.middle.onpaintend.on(cameraPaintEnd); |
|
mouse.listen.window.btn.left.onpaintend.on(cameraPaintEnd); |
|
|
|
window.addEventListener("resize", () => { |
|
viewport.transform(imageCollection.element); |
|
uiCanvas.width = uiCanvas.clientWidth; |
|
uiCanvas.height = uiCanvas.clientHeight; |
|
}); |
|
|