I am trying to create a app with Konva and react-konva where the user can freely draw anything he wants on the stage, but mainly to use the mouse as a pen or a erasor.
The user can only use two colors: red and green. Although, the user have to see the background. So the colors I am using are actually rgba(255,0,0,125)
and rgba(0,255,0,125)
, so they can see the image in the Konva.Stage while they draw on it.
The free drawing region:
const Region = forwardRef(({ lines }, ref) => {
const [stroke, setStroke] = useState(30);
const line1Ref = useRef(null);
const line2Ref = useRef(null);
console.log(line1Ref?.current);
return (
<Layer ref={ref}>
{lines.map((line, i) => (
<>
<Line
ref={line1Ref}
points={line.points}
stroke="black"
strokeWidth={stroke}
lineCap="round"
lineJoin="round"
tension={0.5}
globalCompositeOperation="destination-out"
/>
<Line
ref={line2Ref}
points={line.points}
stroke={line.color}
strokeWidth={stroke}
tension={0.5}
lineCap="round"
lineJoin="round"
/>
</>
))}
</Layer>
);
});
The stage is drawn as follows
const Konva = (props) => {
const regionRef = useRef(null);
const [drawing, setDrawing] = useState(false);
const [lines, setLines] = useState([]);
const [finish, setFinish] = useState(false);
const [image, setImage] = useState(null);
const [color, setColor] = useState('rgba(255,0,0,0.49)');
const stageRef = useRef(null);
useEffect(() => {
if (props.url) {
const { url } = props;
const img = new Image();
img.onload = () => {
setImage(img);
setFinish(true);
};
img.src = url;
}
}, [matchingFrame]);
const handleMouseDown = (e) => {
setDrawing(true);
const pos = e.target.getStage().getPointerPosition();
setLines((prevLines) => {
return [...prevLines, { color, points: [pos.x, pos.y] }];
});
};
const handleMouseUp = () => {
setDrawing(false);
};
const handleMouseMove = (e) => {
if (!drawing) {
return;
}
const stage = e.target.getStage();
const point = stage.getPointerPosition();
const lastLine = lines[lines.length - 1];
// add point
lastLine.points = lastLine.points.concat([point.x, point.y]);
// replace last
lines.splice(lines.length - 1, 1, lastLine);
setLines(lines.concat());
};
if(!image) return null;
return (<Stage
ref={stageRef}
width={image.width}
height={image.height}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseUp}
>
<Layer ref={imgRef}>
<KonvaImage image={image} />
</Layer>
<Region ref={regionRef} lines={lines} color={color} />
</Stage>)
}
After the drawing I convert the data to an encoded array mask. Althought, the joint points between the colors have different rgba besides the ones I desired due to the anti-aliasing that make the pen looks curved. If I ignore this mix of colors, there will be pixels with no color when they should have, like in the image
Is there any way to avoid this colors interpolation? The idea I have now is to set this pixels into a nearest neighbor approach where I would fill them with the nearest color, red or green.
The code developed is based on the demos developed by Anton Lavrenov. Image labeling and Free drawing