feat: add inpaint region functionality and related tools

- Enhanced cursor behavior for new tools: semantic select, mask lasso, and mask rectangle.
- Updated mask edit state to include mask asset ID and kind.
- Implemented inpaint region commands for adding, applying, and removing inpaint regions.
- Introduced new operations for lasso and semantic selection tools.
- Created UI components for candidate review and inpaint region management.
- Added tests for inpaint region commands to ensure functionality.
- Updated various components to support new inpaint features and improve user experience.
This commit is contained in:
syntaxbullet
2026-07-11 16:41:22 +02:00
parent f4e13b80e7
commit ff762b8f17
78 changed files with 1632 additions and 301 deletions

View File

@@ -3,7 +3,7 @@ import type { Asset } from "@core/asset";
import type { AppStore } from "@editor/store";
import { analyzeMask, runMaskOperation, type MaskAnalysis, type MaskRasterOperation } from "@operations/masks/rasterActions";
export function MaskStatus({ asset }: { asset: Asset }) {
export function MaskStatus({ asset, purpose = "visibility" }: { asset: Asset; purpose?: "visibility" | "inpaint" }) {
const [analysis, setAnalysis] = useState<MaskAnalysis>();
useEffect(() => {
@@ -24,8 +24,7 @@ export function MaskStatus({ asset }: { asset: Asset }) {
return (
<span className="inline-flex min-w-0 items-center gap-1.5 rounded-md bg-white/[0.04] px-1.5 py-1 text-[0.68rem] text-sky-100/60">
<img src={analysis.thumbnail} alt="" className="h-7 w-10 rounded-md bg-black/30 object-cover ring-1 ring-white/10" />
<span className="whitespace-nowrap">Reveal {formatPercent(analysis.coverage)}</span>
<span className="whitespace-nowrap text-sky-100/45">Inpaint {formatPercent(analysis.hiddenCoverage)}</span>
{purpose === "inpaint" ? <span className="whitespace-nowrap">Replace {formatPercent(analysis.coverage)}</span> : <><span className="whitespace-nowrap">Reveal {formatPercent(analysis.coverage)}</span><span className="whitespace-nowrap text-sky-100/45">Hidden {formatPercent(analysis.hiddenCoverage)}</span></>}
</span>
);
}

View File

@@ -36,7 +36,7 @@ describe("layer thumbnail read model", () => {
test("indexes nested group previews once for row lookup", () => {
const child = { ...base("child-group", "Child"), type: "group" as const, children: [raster("nested", "asset-b")] };
const group = { ...base("root", "Root"), type: "group" as const, children: [child] };
const document = { id: "doc", version: 1, name: "Doc", assets: [...assets.values()], artboards: [{ id: "artboard", name: "Artboard", bounds: { x: 0, y: 0, w: 100, h: 100 }, backgroundColor: "#000000", visible: true, locked: false, layers: [group] }] };
const document = { id: "doc", version: 1, name: "Doc", assets: [...assets.values()], inpaintRegions: [], artboards: [{ id: "artboard", name: "Artboard", bounds: { x: 0, y: 0, w: 100, h: 100 }, backgroundColor: "#000000", visible: true, locked: false, layers: [group] }] };
const index = createLayerThumbnailIndex(document, assets);
expect(index.get("nested")?.kind).toBe("raster");
expect(index.get("child-group")).toMatchObject({ kind: "group", previews: [{ source: "blob:b" }] });