2022-11-26 19:35:16 -06:00
|
|
|
const setMask = (state) => {
|
2022-11-29 14:55:25 -06:00
|
|
|
const canvas = imageCollection.layers.mask.canvas;
|
2022-11-26 19:35:16 -06:00
|
|
|
switch (state) {
|
|
|
|
case "clear":
|
|
|
|
canvas.classList.remove("hold");
|
|
|
|
canvas.classList.add("display", "clear");
|
|
|
|
break;
|
|
|
|
case "hold":
|
|
|
|
canvas.classList.remove("clear");
|
|
|
|
canvas.classList.add("display", "hold");
|
|
|
|
break;
|
|
|
|
case "neutral":
|
|
|
|
canvas.classList.remove("clear", "hold");
|
|
|
|
canvas.classList.add("display");
|
|
|
|
break;
|
|
|
|
case "none":
|
|
|
|
canvas.classList.remove("display", "hold", "clear");
|
|
|
|
break;
|
|
|
|
default:
|
2022-12-06 09:32:37 -06:00
|
|
|
console.debug(`[maskbrush.setMask] Invalid mask type: ${state}`);
|
2022-11-26 19:35:16 -06:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-12-04 16:35:44 -06:00
|
|
|
const _mask_brush_draw_callback = (evn, state, opacity = 100) => {
|
2022-11-29 14:55:25 -06:00
|
|
|
maskPaintCtx.globalCompositeOperation = "source-over";
|
|
|
|
maskPaintCtx.strokeStyle = "black";
|
2022-11-22 16:24:55 -06:00
|
|
|
|
2022-12-04 16:35:44 -06:00
|
|
|
maskPaintCtx.filter =
|
|
|
|
"blur(" + state.brushBlur + "px) opacity(" + opacity + "%)";
|
2022-11-29 14:55:25 -06:00
|
|
|
maskPaintCtx.lineWidth = state.brushSize;
|
|
|
|
maskPaintCtx.beginPath();
|
|
|
|
maskPaintCtx.moveTo(
|
|
|
|
evn.px === undefined ? evn.x : evn.px,
|
|
|
|
evn.py === undefined ? evn.y : evn.py
|
|
|
|
);
|
|
|
|
maskPaintCtx.lineTo(evn.x, evn.y);
|
|
|
|
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
|
|
|
|
maskPaintCtx.stroke();
|
2022-12-04 16:35:44 -06:00
|
|
|
maskPaintCtx.filter = null;
|
2022-11-22 16:24:55 -06:00
|
|
|
};
|
|
|
|
|
2022-12-04 16:35:44 -06:00
|
|
|
const _mask_brush_erase_callback = (evn, state, opacity = 100) => {
|
2022-11-29 14:55:25 -06:00
|
|
|
maskPaintCtx.globalCompositeOperation = "destination-out";
|
|
|
|
maskPaintCtx.strokeStyle = "black";
|
2022-11-22 16:24:55 -06:00
|
|
|
|
2022-12-04 16:35:44 -06:00
|
|
|
maskPaintCtx.filter = "blur(" + state.brushBlur + "px)";
|
|
|
|
maskPaintCtx.filter =
|
|
|
|
"blur(" + state.brushBlur + "px) opacity(" + opacity + "%)";
|
2022-11-29 14:55:25 -06:00
|
|
|
maskPaintCtx.lineWidth = state.brushSize;
|
|
|
|
maskPaintCtx.beginPath();
|
|
|
|
maskPaintCtx.moveTo(
|
|
|
|
evn.px === undefined ? evn.x : evn.px,
|
|
|
|
evn.py === undefined ? evn.y : evn.py
|
|
|
|
);
|
|
|
|
maskPaintCtx.lineTo(evn.x, evn.y);
|
|
|
|
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
|
|
|
|
maskPaintCtx.stroke();
|
2022-12-04 16:35:44 -06:00
|
|
|
maskPaintCtx.filter = null;
|
2022-11-22 16:24:55 -06:00
|
|
|
};
|
2022-11-24 09:30:13 -06:00
|
|
|
|
|
|
|
const maskBrushTool = () =>
|
|
|
|
toolbar.registerTool(
|
2022-12-07 10:31:15 -06:00
|
|
|
"/res/icons/paintbrush.svg",
|
2022-11-24 09:30:13 -06:00
|
|
|
"Mask Brush",
|
|
|
|
(state, opt) => {
|
|
|
|
// Draw new cursor immediately
|
2022-12-04 13:22:35 -06:00
|
|
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
|
|
|
state.redraw();
|
2022-11-24 09:30:13 -06:00
|
|
|
|
|
|
|
// Start Listeners
|
2022-11-29 14:55:25 -06:00
|
|
|
mouse.listen.world.onmousemove.on(state.movecb);
|
|
|
|
mouse.listen.world.onwheel.on(state.wheelcb);
|
|
|
|
mouse.listen.world.btn.left.onpaintstart.on(state.drawcb);
|
|
|
|
mouse.listen.world.btn.left.onpaint.on(state.drawcb);
|
|
|
|
mouse.listen.world.btn.right.onpaintstart.on(state.erasecb);
|
|
|
|
mouse.listen.world.btn.right.onpaint.on(state.erasecb);
|
2022-11-26 19:35:16 -06:00
|
|
|
|
|
|
|
// Display Mask
|
|
|
|
setMask("neutral");
|
2022-11-24 09:30:13 -06:00
|
|
|
},
|
|
|
|
(state, opt) => {
|
|
|
|
// Clear Listeners
|
2022-11-29 14:55:25 -06:00
|
|
|
mouse.listen.world.onmousemove.clear(state.movecb);
|
|
|
|
mouse.listen.world.onwheel.clear(state.wheelcb);
|
|
|
|
mouse.listen.world.btn.left.onpaintstart.clear(state.drawcb);
|
|
|
|
mouse.listen.world.btn.left.onpaint.clear(state.drawcb);
|
|
|
|
mouse.listen.world.btn.right.onpaintstart.clear(state.erasecb);
|
|
|
|
mouse.listen.world.btn.right.onpaint.clear(state.erasecb);
|
2022-11-26 19:35:16 -06:00
|
|
|
|
|
|
|
// Hide Mask
|
|
|
|
setMask("none");
|
2022-11-26 20:08:37 -06:00
|
|
|
state.ctxmenu.previewMaskButton.classList.remove("active");
|
2022-11-26 20:00:41 -06:00
|
|
|
maskPaintCanvas.classList.remove("opaque");
|
2022-11-26 20:08:37 -06:00
|
|
|
state.preview = false;
|
2022-12-04 13:22:35 -06:00
|
|
|
|
|
|
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
2022-11-24 09:30:13 -06:00
|
|
|
},
|
|
|
|
{
|
|
|
|
init: (state) => {
|
|
|
|
state.config = {
|
|
|
|
brushScrollSpeed: 1 / 4,
|
|
|
|
minBrushSize: 10,
|
|
|
|
maxBrushSize: 500,
|
2022-12-04 16:35:44 -06:00
|
|
|
minBlur: 0,
|
|
|
|
maxBlur: 30,
|
2022-11-24 09:30:13 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
state.brushSize = 64;
|
2022-12-04 16:35:44 -06:00
|
|
|
state.brushBlur = 0;
|
|
|
|
state.brushOpacity = 1;
|
2022-11-24 09:30:13 -06:00
|
|
|
state.setBrushSize = (size) => {
|
|
|
|
state.brushSize = size;
|
|
|
|
state.ctxmenu.brushSizeRange.value = size;
|
|
|
|
state.ctxmenu.brushSizeText.value = size;
|
|
|
|
};
|
|
|
|
|
2022-11-26 20:08:37 -06:00
|
|
|
state.preview = false;
|
|
|
|
|
2022-12-01 15:05:14 -06:00
|
|
|
state.clearPrevCursor = () =>
|
2022-12-04 13:22:35 -06:00
|
|
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
|
|
|
|
|
|
|
state.redraw = () => {
|
|
|
|
state.movecb({
|
|
|
|
...mouse.coords.world.pos,
|
|
|
|
evn: {
|
|
|
|
clientX: mouse.coords.window.pos.x,
|
|
|
|
clientY: mouse.coords.window.pos.y,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
2022-11-26 20:08:37 -06:00
|
|
|
|
2022-12-01 15:05:14 -06:00
|
|
|
state.movecb = (evn) => {
|
2022-12-04 13:22:35 -06:00
|
|
|
const vcp = {x: evn.evn.clientX, y: evn.evn.clientY};
|
|
|
|
const scp = state.brushSize * viewport.zoom;
|
|
|
|
|
|
|
|
state.clearPrevCursor();
|
|
|
|
state;
|
|
|
|
clearPrevCursor = () =>
|
|
|
|
uiCtx.clearRect(
|
|
|
|
vcp.x - scp / 2 - 10,
|
|
|
|
vcp.y - scp / 2 - 10,
|
|
|
|
vcp.x + scp / 2 + 10,
|
|
|
|
vcp.y + scp / 2 + 10
|
2022-12-01 15:05:14 -06:00
|
|
|
);
|
2022-12-04 13:22:35 -06:00
|
|
|
|
|
|
|
uiCtx.beginPath();
|
|
|
|
uiCtx.arc(vcp.x, vcp.y, scp / 2, 0, 2 * Math.PI, true);
|
|
|
|
uiCtx.strokeStyle = "black";
|
|
|
|
uiCtx.stroke();
|
|
|
|
|
|
|
|
uiCtx.beginPath();
|
|
|
|
uiCtx.arc(vcp.x, vcp.y, scp / 2, 0, 2 * Math.PI, true);
|
|
|
|
uiCtx.fillStyle = "#FFFFFF50";
|
|
|
|
uiCtx.fill();
|
2022-11-24 09:30:13 -06:00
|
|
|
};
|
|
|
|
|
2022-12-06 19:06:30 -06:00
|
|
|
state.redraw = () => {
|
|
|
|
state.movecb({
|
|
|
|
...mouse.coords.world.pos,
|
|
|
|
evn: {
|
|
|
|
clientX: mouse.coords.window.pos.x,
|
|
|
|
clientY: mouse.coords.window.pos.y,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-11-24 09:30:13 -06:00
|
|
|
state.wheelcb = (evn) => {
|
2022-11-29 14:55:25 -06:00
|
|
|
if (!evn.evn.ctrlKey) {
|
2022-11-24 09:30:13 -06:00
|
|
|
state.brushSize = state.setBrushSize(
|
|
|
|
state.brushSize -
|
|
|
|
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
|
|
|
);
|
2022-12-06 19:06:30 -06:00
|
|
|
state.redraw();
|
2022-11-24 09:30:13 -06:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-12-04 16:35:44 -06:00
|
|
|
state.drawcb = (evn) =>
|
|
|
|
_mask_brush_draw_callback(evn, state, state.brushOpacity * 100);
|
|
|
|
state.erasecb = (evn) =>
|
|
|
|
_mask_brush_erase_callback(evn, state, state.brushOpacity * 100);
|
2022-11-24 09:30:13 -06:00
|
|
|
},
|
|
|
|
populateContextMenu: (menu, state) => {
|
|
|
|
if (!state.ctxmenu) {
|
|
|
|
state.ctxmenu = {};
|
2022-12-04 16:35:44 -06:00
|
|
|
|
|
|
|
// Brush size slider
|
2022-11-24 09:30:13 -06:00
|
|
|
const brushSizeSlider = _toolbar_input.slider(
|
|
|
|
state,
|
|
|
|
"brushSize",
|
|
|
|
"Brush Size",
|
2022-12-03 04:50:23 -06:00
|
|
|
{
|
|
|
|
min: state.config.minBrushSize,
|
|
|
|
max: state.config.maxBrushSize,
|
|
|
|
step: 5,
|
|
|
|
textStep: 1,
|
|
|
|
cb: (v) => {
|
|
|
|
if (!state.cursorLayer) return;
|
2022-12-04 13:22:35 -06:00
|
|
|
|
|
|
|
state.redraw();
|
2022-12-03 04:50:23 -06:00
|
|
|
},
|
2022-12-01 15:05:14 -06:00
|
|
|
}
|
2022-11-24 09:30:13 -06:00
|
|
|
);
|
|
|
|
state.ctxmenu.brushSizeSlider = brushSizeSlider.slider;
|
|
|
|
state.setBrushSize = brushSizeSlider.setValue;
|
2022-11-26 20:00:41 -06:00
|
|
|
|
2022-12-04 16:35:44 -06:00
|
|
|
// Brush opacity slider
|
|
|
|
const brushOpacitySlider = _toolbar_input.slider(
|
|
|
|
state,
|
|
|
|
"brushOpacity",
|
|
|
|
"Brush Opacity",
|
|
|
|
{
|
|
|
|
min: 0,
|
|
|
|
max: 1,
|
|
|
|
step: 0.05,
|
|
|
|
textStep: 0.001,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
state.ctxmenu.brushOpacitySlider = brushOpacitySlider.slider;
|
|
|
|
|
|
|
|
// Brush blur slider
|
|
|
|
const brushBlurSlider = _toolbar_input.slider(
|
|
|
|
state,
|
|
|
|
"brushBlur",
|
|
|
|
"Brush Blur",
|
|
|
|
{
|
|
|
|
min: state.config.minBlur,
|
|
|
|
max: state.config.maxBlur,
|
|
|
|
step: 1,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
state.ctxmenu.brushBlurSlider = brushBlurSlider.slider;
|
|
|
|
|
2022-11-26 20:00:41 -06:00
|
|
|
// Some mask-related action buttons
|
|
|
|
const actionArray = document.createElement("div");
|
|
|
|
actionArray.classList.add("button-array");
|
|
|
|
|
|
|
|
const clearMaskButton = document.createElement("button");
|
|
|
|
clearMaskButton.classList.add("button", "tool");
|
|
|
|
clearMaskButton.textContent = "Clear";
|
|
|
|
clearMaskButton.title = "Clears Painted Mask";
|
|
|
|
clearMaskButton.onclick = () => {
|
|
|
|
maskPaintCtx.clearRect(
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
maskPaintCanvas.width,
|
|
|
|
maskPaintCanvas.height
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const previewMaskButton = document.createElement("button");
|
|
|
|
previewMaskButton.classList.add("button", "tool");
|
|
|
|
previewMaskButton.textContent = "Preview";
|
|
|
|
previewMaskButton.title = "Displays Mask with Full Opacity";
|
|
|
|
previewMaskButton.onclick = () => {
|
|
|
|
if (previewMaskButton.classList.contains("active")) {
|
|
|
|
maskPaintCanvas.classList.remove("opaque");
|
2022-11-26 20:08:37 -06:00
|
|
|
state.preview = false;
|
2022-12-04 13:22:35 -06:00
|
|
|
|
|
|
|
state.redraw();
|
2022-11-26 20:00:41 -06:00
|
|
|
} else {
|
|
|
|
maskPaintCanvas.classList.add("opaque");
|
2022-11-26 20:08:37 -06:00
|
|
|
state.preview = true;
|
2022-12-04 13:22:35 -06:00
|
|
|
state.redraw();
|
2022-11-26 20:00:41 -06:00
|
|
|
}
|
|
|
|
previewMaskButton.classList.toggle("active");
|
|
|
|
};
|
|
|
|
|
2022-11-26 20:08:37 -06:00
|
|
|
state.ctxmenu.previewMaskButton = previewMaskButton;
|
|
|
|
|
2022-11-26 20:00:41 -06:00
|
|
|
actionArray.appendChild(clearMaskButton);
|
|
|
|
actionArray.appendChild(previewMaskButton);
|
|
|
|
|
|
|
|
state.ctxmenu.actionArray = actionArray;
|
2022-11-24 09:30:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
menu.appendChild(state.ctxmenu.brushSizeSlider);
|
2022-12-04 16:35:44 -06:00
|
|
|
menu.appendChild(state.ctxmenu.brushOpacitySlider);
|
|
|
|
menu.appendChild(state.ctxmenu.brushBlurSlider);
|
2022-11-26 20:00:41 -06:00
|
|
|
menu.appendChild(state.ctxmenu.actionArray);
|
2022-11-24 09:30:13 -06:00
|
|
|
},
|
|
|
|
shortcut: "M",
|
|
|
|
}
|
|
|
|
);
|