feat(tools): add eraser tool

This commit is contained in:
syntaxbullet
2026-07-03 17:39:00 +02:00
parent 9fc0c18114
commit c13a6f5917
5 changed files with 14 additions and 5 deletions

View File

@@ -9,15 +9,16 @@ import type { AppStore } from "@editor/store";
export type BrushSession = {
layerId: string;
previousPoint: Vec2D;
mode: "brush" | "eraser";
};
export function beginBrushSession(document: ImageDocument, editor: EditorState, point: Vec2D): BrushSession | undefined {
if (editor.tools.activeTool !== "brush") return undefined;
if (editor.tools.activeTool !== "brush" && editor.tools.activeTool !== "eraser") return undefined;
const layerId = editor.selection.layerIds[0];
if (!layerId) return undefined;
const layer = findRasterLayer(document.artboards.flatMap((artboard) => artboard.layers), layerId);
if (!layer || layer.locked || !layer.visible) return undefined;
return { layerId, previousPoint: point };
return { layerId, previousPoint: point, mode: editor.tools.activeTool };
}
export async function updateBrushSession(options: {
@@ -42,6 +43,7 @@ export async function updateBrushSession(options: {
to: documentPointToAssetPoint(options.point, layer, asset.intrinsicSize.w, asset.intrinsicSize.h),
color: options.color ?? "#111827",
size: options.size ?? 8,
mode: options.session.mode,
});
options.store.dispatch(commandIds.documentUpdateAssetSource, { assetId: asset.id, source });
@@ -63,6 +65,7 @@ async function drawStroke(options: {
to: Vec2D;
color: string;
size: number;
mode: "brush" | "eraser";
}) {
const canvas = document.createElement("canvas");
canvas.width = Math.max(1, Math.round(options.width));
@@ -72,6 +75,7 @@ async function drawStroke(options: {
const image = await loadImage(options.source);
context.drawImage(image, 0, 0, canvas.width, canvas.height);
context.globalCompositeOperation = options.mode === "eraser" ? "destination-out" : "source-over";
context.strokeStyle = options.color;
context.lineWidth = options.size;
context.lineCap = "round";
@@ -80,6 +84,7 @@ async function drawStroke(options: {
context.moveTo(options.from.x, options.from.y);
context.lineTo(options.to.x, options.to.y);
context.stroke();
context.globalCompositeOperation = "source-over";
return canvas.toDataURL("image/png");
}

View File

@@ -5,6 +5,6 @@ import type { CanvasInputState } from "./useCanvasInput";
export function canvasCursorClass(interactionMode: InteractionMode, input: CanvasInputState) {
if (input.isPanning) return "cursor-grabbing";
if (isPanInteractionMode(interactionMode)) return "cursor-grab";
if (interactionMode.type === "tool" && (interactionMode.tool === "crop" || interactionMode.tool === "brush")) return "cursor-crosshair";
if (interactionMode.type === "tool" && (interactionMode.tool === "crop" || interactionMode.tool === "brush" || interactionMode.tool === "eraser")) return "cursor-crosshair";
return "cursor-default";
}