248 lines
7.2 KiB
JavaScript
248 lines
7.2 KiB
JavaScript
|
const _color_brush_draw_callback = (evn, state) => {
|
||
|
const ctx = state.drawLayer.ctx;
|
||
|
|
||
|
ctx.strokeStyle = state.color;
|
||
|
|
||
|
ctx.filter = "blur(" + state.brushBlur + "px)";
|
||
|
ctx.lineWidth = state.brushSize;
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(
|
||
|
evn.px === undefined ? evn.x : evn.px,
|
||
|
evn.py === undefined ? evn.y : evn.py
|
||
|
);
|
||
|
ctx.lineTo(evn.x, evn.y);
|
||
|
ctx.lineJoin = ctx.lineCap = "round";
|
||
|
ctx.stroke();
|
||
|
};
|
||
|
|
||
|
const _color_brush_erase_callback = (evn, state, ctx) => {
|
||
|
ctx.strokeStyle = "black";
|
||
|
|
||
|
ctx.lineWidth = state.brushSize;
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(
|
||
|
evn.px === undefined ? evn.x : evn.px,
|
||
|
evn.py === undefined ? evn.y : evn.py
|
||
|
);
|
||
|
ctx.lineTo(evn.x, evn.y);
|
||
|
ctx.lineJoin = ctx.lineCap = "round";
|
||
|
ctx.stroke();
|
||
|
};
|
||
|
|
||
|
const colorBrushTool = () =>
|
||
|
toolbar.registerTool(
|
||
|
"res/icons/brush.svg",
|
||
|
"Color Brush",
|
||
|
(state, opt) => {
|
||
|
// Draw new cursor immediately
|
||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||
|
state.movecb({...mouse.coords.world.pos});
|
||
|
state.drawLayer = imageCollection.registerLayer(null, {
|
||
|
after: imgLayer,
|
||
|
});
|
||
|
state.eraseLayer = imageCollection.registerLayer(null, {
|
||
|
after: imgLayer,
|
||
|
});
|
||
|
state.eraseLayer.canvas.style.display = "none";
|
||
|
state.eraseBackup = imageCollection.registerLayer(null, {
|
||
|
after: imgLayer,
|
||
|
});
|
||
|
state.eraseBackup.canvas.style.display = "none";
|
||
|
|
||
|
// Start Listeners
|
||
|
mouse.listen.world.onmousemove.on(state.movecb);
|
||
|
mouse.listen.world.onwheel.on(state.wheelcb);
|
||
|
|
||
|
mouse.listen.world.btn.left.onpaintstart.on(state.drawstartcb);
|
||
|
mouse.listen.world.btn.left.onpaint.on(state.drawcb);
|
||
|
mouse.listen.world.btn.left.onpaintend.on(state.drawendcb);
|
||
|
|
||
|
mouse.listen.world.btn.right.onpaintstart.on(state.erasestartcb);
|
||
|
mouse.listen.world.btn.right.onpaint.on(state.erasecb);
|
||
|
mouse.listen.world.btn.right.onpaintend.on(state.eraseendcb);
|
||
|
|
||
|
// Display Color
|
||
|
setMask("none");
|
||
|
},
|
||
|
(state, opt) => {
|
||
|
// Clear Listeners
|
||
|
mouse.listen.world.onmousemove.clear(state.movecb);
|
||
|
mouse.listen.world.onwheel.clear(state.wheelcb);
|
||
|
|
||
|
mouse.listen.world.btn.left.onpaintstart.clear(state.drawstartcb);
|
||
|
mouse.listen.world.btn.left.onpaint.clear(state.drawcb);
|
||
|
mouse.listen.world.btn.left.onpaintend.clear(state.drawendcb);
|
||
|
|
||
|
mouse.listen.world.btn.right.onpaintstart.clear(state.erasestartcb);
|
||
|
mouse.listen.world.btn.right.onpaint.clear(state.erasecb);
|
||
|
mouse.listen.world.btn.right.onpaintend.clear(state.eraseendcb);
|
||
|
|
||
|
// Delete layer
|
||
|
imageCollection.deleteLayer(state.drawLayer);
|
||
|
imageCollection.deleteLayer(state.eraseBackup);
|
||
|
imageCollection.deleteLayer(state.eraseLayer);
|
||
|
},
|
||
|
{
|
||
|
init: (state) => {
|
||
|
state.config = {
|
||
|
brushScrollSpeed: 1 / 5,
|
||
|
minBrushSize: 2,
|
||
|
maxBrushSize: 500,
|
||
|
minBlur: 0,
|
||
|
maxBlur: 30,
|
||
|
};
|
||
|
|
||
|
state.color = "#FFFFFF";
|
||
|
state.brushSize = 32;
|
||
|
state.brushBlur = 0;
|
||
|
state.affectMask = true;
|
||
|
state.setBrushSize = (size) => {
|
||
|
state.brushSize = size;
|
||
|
state.ctxmenu.brushSizeRange.value = size;
|
||
|
state.ctxmenu.brushSizeText.value = size;
|
||
|
};
|
||
|
|
||
|
state.movecb = (evn) => {
|
||
|
// draw big translucent white blob cursor
|
||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||
|
ovCtx.beginPath();
|
||
|
ovCtx.arc(evn.x, evn.y, state.brushSize / 2, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line???
|
||
|
ovCtx.fillStyle = state.color + "50";
|
||
|
ovCtx.fill();
|
||
|
};
|
||
|
|
||
|
state.wheelcb = (evn) => {
|
||
|
if (!evn.evn.ctrlKey) {
|
||
|
state.brushSize = state.setBrushSize(
|
||
|
state.brushSize -
|
||
|
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
||
|
);
|
||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||
|
state.movecb(evn);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
state.drawstartcb = (evn) => {
|
||
|
if (state.affectMask) _mask_brush_draw_callback(evn, state);
|
||
|
_color_brush_draw_callback(evn, state);
|
||
|
};
|
||
|
|
||
|
state.drawcb = (evn) => {
|
||
|
if (state.affectMask) _mask_brush_draw_callback(evn, state);
|
||
|
_color_brush_draw_callback(evn, state);
|
||
|
};
|
||
|
|
||
|
state.drawendcb = (evn) => {
|
||
|
const canvas = state.drawLayer.canvas;
|
||
|
const ctx = state.drawLayer.ctx;
|
||
|
|
||
|
const cropped = cropCanvas(canvas, {border: 10});
|
||
|
const bb = cropped.bb;
|
||
|
commands.runCommand("drawImage", "Color Brush Draw", {
|
||
|
image: cropped.canvas,
|
||
|
...bb,
|
||
|
});
|
||
|
|
||
|
ctx.clearRect(bb.x, bb.y, bb.w, bb.h);
|
||
|
};
|
||
|
|
||
|
state.erasestartcb = (evn) => {
|
||
|
if (state.affectMask) _mask_brush_erase_callback(evn, state);
|
||
|
|
||
|
// Make a backup of the current image to apply erase later
|
||
|
const bkpcanvas = state.eraseBackup.canvas;
|
||
|
const bkpctx = state.eraseBackup.ctx;
|
||
|
bkpctx.clearRect(0, 0, bkpcanvas.width, bkpcanvas.height);
|
||
|
bkpctx.drawImage(imgCanvas, 0, 0);
|
||
|
|
||
|
imgCtx.globalCompositeOperation = "destination-out";
|
||
|
_color_brush_erase_callback(evn, state, imgCtx);
|
||
|
imgCtx.globalCompositeOperation = "source-over";
|
||
|
_color_brush_erase_callback(evn, state, state.eraseLayer.ctx);
|
||
|
};
|
||
|
|
||
|
state.erasecb = (evn) => {
|
||
|
if (state.affectMask) _mask_brush_erase_callback(evn, state);
|
||
|
imgCtx.globalCompositeOperation = "destination-out";
|
||
|
_color_brush_erase_callback(evn, state, imgCtx);
|
||
|
imgCtx.globalCompositeOperation = "source-over";
|
||
|
_color_brush_erase_callback(evn, state, state.eraseLayer.ctx);
|
||
|
};
|
||
|
|
||
|
state.eraseendcb = (evn) => {
|
||
|
const canvas = state.eraseLayer.canvas;
|
||
|
const ctx = state.eraseLayer.ctx;
|
||
|
|
||
|
const bkpcanvas = state.eraseBackup.canvas;
|
||
|
|
||
|
const cropped = cropCanvas(canvas, {border: 10});
|
||
|
const bb = cropped.bb;
|
||
|
|
||
|
imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
|
||
|
imgCtx.drawImage(bkpcanvas, 0, 0);
|
||
|
|
||
|
commands.runCommand("eraseImage", "Color Brush Erase", {
|
||
|
mask: cropped.canvas,
|
||
|
...bb,
|
||
|
});
|
||
|
|
||
|
ctx.clearRect(bb.x, bb.y, bb.w, bb.h);
|
||
|
};
|
||
|
},
|
||
|
populateContextMenu: (menu, state) => {
|
||
|
if (!state.ctxmenu) {
|
||
|
state.ctxmenu = {};
|
||
|
|
||
|
// Affects Mask Checkbox
|
||
|
const affectMaskCheckbox = _toolbar_input.checkbox(
|
||
|
state,
|
||
|
"affectMask",
|
||
|
"Affect Mask"
|
||
|
).label;
|
||
|
|
||
|
state.ctxmenu.affectMaskCheckbox = affectMaskCheckbox;
|
||
|
|
||
|
// Brush size slider
|
||
|
const brushSizeSlider = _toolbar_input.slider(
|
||
|
state,
|
||
|
"brushSize",
|
||
|
"Brush Size",
|
||
|
state.config.minBrushSize,
|
||
|
state.config.maxBrushSize,
|
||
|
1
|
||
|
);
|
||
|
state.ctxmenu.brushSizeSlider = brushSizeSlider.slider;
|
||
|
state.setBrushSize = brushSizeSlider.setValue;
|
||
|
|
||
|
// Brush size slider
|
||
|
const brushBlurSlider = _toolbar_input.slider(
|
||
|
state,
|
||
|
"brushBlur",
|
||
|
"Brush Blur",
|
||
|
state.config.minBlur,
|
||
|
state.config.maxBlur,
|
||
|
1
|
||
|
);
|
||
|
state.ctxmenu.brushBlurSlider = brushBlurSlider.slider;
|
||
|
|
||
|
// Brush color
|
||
|
const brushColorPicker = document.createElement("input");
|
||
|
brushColorPicker.type = "color";
|
||
|
brushColorPicker.style.width = "100%";
|
||
|
brushColorPicker.value = state.color;
|
||
|
brushColorPicker.addEventListener("input", (evn) => {
|
||
|
state.color = evn.target.value;
|
||
|
});
|
||
|
|
||
|
state.ctxmenu.brushColorPicker = brushColorPicker;
|
||
|
}
|
||
|
|
||
|
menu.appendChild(state.ctxmenu.affectMaskCheckbox);
|
||
|
menu.appendChild(state.ctxmenu.brushSizeSlider);
|
||
|
menu.appendChild(state.ctxmenu.brushBlurSlider);
|
||
|
menu.appendChild(state.ctxmenu.brushColorPicker);
|
||
|
},
|
||
|
shortcut: "C",
|
||
|
}
|
||
|
);
|