feat: enhance layer mask handling and opacity management

- Introduced `getLayerMask` utility to streamline layer mask retrieval.
- Updated layer rendering logic to incorporate layer masks and opacity adjustments.
- Added functionality to prevent dropping a group into its descendants.
- Enhanced image texture rendering to support opacity parameters across various rendering functions.
- Implemented group layer bounds application for better scaling and positioning.
- Added tests to ensure correct behavior when handling layer masks and group layers.
- Created new types for asset generation provenance to track generated assets more effectively.
This commit is contained in:
syntaxbullet
2026-07-05 16:06:23 +02:00
parent a83024c35c
commit 5eaf37ba28
33 changed files with 635 additions and 88 deletions

56
core/asset-provenance.ts Normal file
View File

@@ -0,0 +1,56 @@
import type { Rect, Size } from "./geometry";
import type { AssetId, LayerId } from "./id";
export type GeneratedAssetMode = "text-to-image" | "image-to-image" | "inpaint" | "outpaint";
export type GeneratedAssetAcceptance = "layer" | "variant-layer" | "replacement";
export type AssetGenerationProvenance = {
kind: "generated";
candidateId: string;
mode: GeneratedAssetMode;
acceptance: GeneratedAssetAcceptance;
prompt: string;
negativePrompt: string;
seed: number;
outputSize: Size;
settings: {
architecture: string;
model: string;
textEncoder: string;
vae: string;
strength: number;
steps: number;
cfg: number;
sampler: string;
scheduler: string;
width: number;
height: number;
};
inpaint?: {
targetLayerId: LayerId;
maskLayerId: LayerId;
sourceAssetId: AssetId;
maskAssetId: AssetId;
crop: {
assetBounds: Rect;
documentBounds: Rect;
padding: number;
maskedAreaOnly: boolean;
};
mask: {
polarity: "hidden" | "revealed";
activeBounds: Rect;
};
backend: {
growMaskBy: number;
maskedContent: string;
maskBlur: number;
maskFeather: number;
maskExpand: number;
cropPadding: number;
};
};
};
export type AssetProvenance = AssetGenerationProvenance;

View File

@@ -1,5 +1,6 @@
import type { Size } from "./geometry";
import type { AssetId } from "./id";
import type { AssetProvenance } from "./asset-provenance";
export type Asset = {
id: AssetId;
@@ -7,4 +8,5 @@ export type Asset = {
mimeType: string;
source: string;
intrinsicSize: Size;
provenance?: AssetProvenance;
};

View File

@@ -1,5 +1,6 @@
import type { Transform } from "./geometry";
import type { LayerId } from "./id";
import type { LayerMask } from "./layer-mask";
export type LayerClippingMask = {
maskLayerId: LayerId;
@@ -12,5 +13,7 @@ export type BaseLayer = {
locked: boolean;
opacity: number;
transform: Transform;
layerMask?: LayerMask;
/** @deprecated Use layerMask. Kept readable for older documents. */
clippingMask?: LayerClippingMask;
};

View File

@@ -1,5 +1,6 @@
export type { Artboard } from "./artboard";
export type { Asset } from "./asset";
export type { AssetGenerationProvenance, AssetProvenance, GeneratedAssetAcceptance, GeneratedAssetMode } from "./asset-provenance";
export type { BaseLayer, LayerClippingMask } from "./base-layer";
export type { ImageDocument } from "./document";
export type {
@@ -15,5 +16,7 @@ export type {
export type { ArtboardId, AssetId, DocumentId, LayerId } from "./id";
export type { ImageLayer } from "./image-layer";
export type { Layer } from "./layer";
export type { LayerMask } from "./layer-mask";
export { getLayerMask, hasLayerMask } from "./layer-mask-utils";
export type { LayerGroup } from "./layer-group";
export type { RasterLayer } from "./raster-layer";

18
core/layer-mask-utils.ts Normal file
View File

@@ -0,0 +1,18 @@
import type { Layer } from "./layer";
import type { LayerMask } from "./layer-mask";
export function getLayerMask(layer: Layer | undefined): LayerMask | undefined {
if (layer.layerMask) return layer.layerMask;
if (!layer.clippingMask) return undefined;
return {
kind: "raster",
maskLayerId: layer.clippingMask.maskLayerId,
enabled: true,
inverted: false,
};
}
export function hasLayerMask(layer: Layer): boolean {
return Boolean(getLayerMask(layer));
}

8
core/layer-mask.ts Normal file
View File

@@ -0,0 +1,8 @@
import type { LayerId } from "./id";
export type LayerMask = {
kind: "raster";
maskLayerId: LayerId;
enabled: boolean;
inverted: boolean;
};