fix(gui): draw single clicks and avoid blending mask with itself
This commit is contained in:
parent
df7bba47dd
commit
5e23f84e5a
|
@ -48,19 +48,6 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
const { source, mask } = props;
|
const { source, mask } = props;
|
||||||
const { params } = mustExist(useContext(ConfigContext));
|
const { params } = mustExist(useContext(ConfigContext));
|
||||||
|
|
||||||
function saveMask(): void {
|
|
||||||
if (doesExist(bufferRef.current)) {
|
|
||||||
if (maskState.current === MASK_STATE.clean) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferRef.current.toBlob((blob) => {
|
|
||||||
maskState.current = MASK_STATE.clean;
|
|
||||||
props.onSave(mustExist(blob));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawBuffer() {
|
function drawBuffer() {
|
||||||
if (doesExist(brushRef.current) && doesExist(bufferRef.current) && doesExist(canvasRef.current)) {
|
if (doesExist(brushRef.current) && doesExist(bufferRef.current) && doesExist(canvasRef.current)) {
|
||||||
const { ctx } = getClearContext(canvasRef);
|
const { ctx } = getClearContext(canvasRef);
|
||||||
|
@ -73,10 +60,38 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawBrush(point: Point): void {
|
||||||
|
const { ctx } = getClearContext(brushRef);
|
||||||
|
ctx.fillStyle = grayToRGB(brush.color, brush.strength);
|
||||||
|
|
||||||
|
drawCircle(ctx, {
|
||||||
|
x: point.x,
|
||||||
|
y: point.y,
|
||||||
|
}, brush.size);
|
||||||
|
|
||||||
|
drawBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawClicks(): void {
|
||||||
|
if (clicks.length > 0) {
|
||||||
|
const { ctx } = getContext(bufferRef);
|
||||||
|
ctx.fillStyle = grayToRGB(brush.color, brush.strength);
|
||||||
|
|
||||||
|
for (const click of clicks) {
|
||||||
|
drawCircle(ctx, click, brush.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
clicks.length = 0;
|
||||||
|
|
||||||
|
drawBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function drawSource(file: Blob): void {
|
function drawSource(file: Blob): void {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
const { ctx } = getContext(bufferRef);
|
const { ctx } = getClearContext(bufferRef);
|
||||||
|
ctx.globalAlpha = 1.0;
|
||||||
ctx.drawImage(image, 0, 0);
|
ctx.drawImage(image, 0, 0);
|
||||||
URL.revokeObjectURL(src);
|
URL.revokeObjectURL(src);
|
||||||
|
|
||||||
|
@ -90,12 +105,25 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
function finishPainting() {
|
function finishPainting() {
|
||||||
if (doesExist(brushRef.current)) {
|
if (doesExist(brushRef.current)) {
|
||||||
getClearContext(brushRef);
|
getClearContext(brushRef);
|
||||||
drawBuffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawClicks();
|
||||||
|
|
||||||
if (maskState.current === MASK_STATE.painting) {
|
if (maskState.current === MASK_STATE.painting) {
|
||||||
maskState.current = MASK_STATE.dirty;
|
maskState.current = MASK_STATE.dirty;
|
||||||
save();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveMask(): void {
|
||||||
|
if (doesExist(bufferRef.current)) {
|
||||||
|
if (maskState.current === MASK_STATE.clean) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferRef.current.toBlob((blob) => {
|
||||||
|
maskState.current = MASK_STATE.clean;
|
||||||
|
props.onSave(mustExist(blob));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,21 +146,6 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
const setBrush = useStore(state, (s) => s.setBrush);
|
const setBrush = useStore(state, (s) => s.setBrush);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// including clicks.length prevents the initial render from saving a blank canvas
|
|
||||||
if (doesExist(bufferRef.current) && maskState.current === MASK_STATE.painting && clicks.length > 0) {
|
|
||||||
const { ctx } = getContext(bufferRef);
|
|
||||||
ctx.fillStyle = grayToRGB(brush.color, brush.strength);
|
|
||||||
|
|
||||||
for (const click of clicks) {
|
|
||||||
drawCircle(ctx, click, brush.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
clicks.length = 0;
|
|
||||||
drawBuffer();
|
|
||||||
}
|
|
||||||
}, [clicks.length]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (maskState.current === MASK_STATE.dirty) {
|
if (maskState.current === MASK_STATE.dirty) {
|
||||||
save();
|
save();
|
||||||
|
@ -155,6 +168,9 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
}
|
}
|
||||||
}, [source]);
|
}, [source]);
|
||||||
|
|
||||||
|
// last resort to draw lost clicks
|
||||||
|
drawClicks();
|
||||||
|
|
||||||
const styles: React.CSSProperties = {
|
const styles: React.CSSProperties = {
|
||||||
border: '1px solid black',
|
border: '1px solid black',
|
||||||
maxHeight: params.height.default,
|
maxHeight: params.height.default,
|
||||||
|
@ -191,16 +207,13 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
const canvas = mustExist(canvasRef.current);
|
const canvas = mustExist(canvasRef.current);
|
||||||
const bounds = canvas.getBoundingClientRect();
|
const bounds = canvas.getBoundingClientRect();
|
||||||
|
|
||||||
const { ctx } = getContext(bufferRef);
|
setClicks([...clicks, {
|
||||||
ctx.fillStyle = grayToRGB(brush.color, brush.strength);
|
|
||||||
|
|
||||||
drawCircle(ctx, {
|
|
||||||
x: event.clientX - bounds.left,
|
x: event.clientX - bounds.left,
|
||||||
y: event.clientY - bounds.top,
|
y: event.clientY - bounds.top,
|
||||||
}, brush.size);
|
}]);
|
||||||
|
|
||||||
|
drawClicks();
|
||||||
maskState.current = MASK_STATE.dirty;
|
maskState.current = MASK_STATE.dirty;
|
||||||
save();
|
|
||||||
}}
|
}}
|
||||||
onMouseDown={() => {
|
onMouseDown={() => {
|
||||||
maskState.current = MASK_STATE.painting;
|
maskState.current = MASK_STATE.painting;
|
||||||
|
@ -218,15 +231,10 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
y: event.clientY - bounds.top,
|
y: event.clientY - bounds.top,
|
||||||
}]);
|
}]);
|
||||||
} else {
|
} else {
|
||||||
const { ctx } = getClearContext(brushRef);
|
drawBrush({
|
||||||
ctx.fillStyle = grayToRGB(brush.color, brush.strength);
|
|
||||||
|
|
||||||
drawCircle(ctx, {
|
|
||||||
x: event.clientX - bounds.left,
|
x: event.clientX - bounds.left,
|
||||||
y: event.clientY - bounds.top,
|
y: event.clientY - bounds.top,
|
||||||
}, brush.size);
|
});
|
||||||
|
|
||||||
drawBuffer();
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -272,7 +280,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
floodCanvas(bufferRef, floodBelow);
|
floodCanvas(bufferRef, floodBelow);
|
||||||
drawBuffer();
|
drawBuffer();
|
||||||
save();
|
maskState.current = MASK_STATE.dirty;
|
||||||
}}>
|
}}>
|
||||||
Gray to black
|
Gray to black
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -282,7 +290,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
floodCanvas(bufferRef, floodBlack);
|
floodCanvas(bufferRef, floodBlack);
|
||||||
drawBuffer();
|
drawBuffer();
|
||||||
save();
|
maskState.current = MASK_STATE.dirty;
|
||||||
}}>
|
}}>
|
||||||
Fill with black
|
Fill with black
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -292,7 +300,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
floodCanvas(bufferRef, floodWhite);
|
floodCanvas(bufferRef, floodWhite);
|
||||||
drawBuffer();
|
drawBuffer();
|
||||||
save();
|
maskState.current = MASK_STATE.dirty;
|
||||||
}}>
|
}}>
|
||||||
Fill with white
|
Fill with white
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -302,7 +310,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
floodCanvas(bufferRef, floodAbove);
|
floodCanvas(bufferRef, floodAbove);
|
||||||
drawBuffer();
|
drawBuffer();
|
||||||
save();
|
maskState.current = MASK_STATE.dirty;
|
||||||
}}>
|
}}>
|
||||||
Gray to white
|
Gray to white
|
||||||
</Button>
|
</Button>
|
||||||
|
|
Loading…
Reference in New Issue