From c46d28891fee62ca2d3132da876af6411a41d9da Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 15 Apr 2026 15:45:50 +0200 Subject: feat: text / custom masks, fix preview pan, refactor selection --- .../renderer/compositor/frame-descriptor.ts | 36 ++++++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) (limited to 'apps/web/src/services/renderer/compositor') diff --git a/apps/web/src/services/renderer/compositor/frame-descriptor.ts b/apps/web/src/services/renderer/compositor/frame-descriptor.ts index 8b8201d5..f93eae27 100644 --- a/apps/web/src/services/renderer/compositor/frame-descriptor.ts +++ b/apps/web/src/services/renderer/compositor/frame-descriptor.ts @@ -401,6 +401,11 @@ function buildMaskArtifacts({ } const definition = masksRegistry.get(mask.type); + + if (definition.isActive?.(mask.params) === false) { + return { mask: null, strokeLayer: null }; + } + const elementMaskCanvas = createOffscreenCanvas({ width: Math.round(transform.width), height: Math.round(transform.height), @@ -416,7 +421,12 @@ function buildMaskArtifacts({ let strokePath: Path2D | null = null; let feather = mask.params.feather; - if (mask.params.feather > 0 && definition.renderer.renderMask) { + const canRenderMaskDirectly = Boolean(definition.renderer.renderMask); + const shouldRenderMaskDirectly = + canRenderMaskDirectly && + (!definition.renderer.buildPath || + (mask.params.feather > 0 && definition.renderer.renderMaskHandlesFeather)); + if (shouldRenderMaskDirectly && definition.renderer.renderMask) { definition.renderer.renderMask({ resolvedParams: mask.params, ctx: elementMaskCtx, @@ -424,13 +434,18 @@ function buildMaskArtifacts({ height: Math.round(transform.height), feather: mask.params.feather, }); - feather = 0; + if (definition.renderer.renderMaskHandlesFeather) { + feather = 0; + } strokePath = definition.renderer.buildStrokePath?.({ resolvedParams: mask.params, width: transform.width, height: transform.height, }) ?? null; } else { + if (!definition.renderer.buildPath) { + return { mask: null, strokeLayer: null }; + } const path2d = definition.renderer.buildPath({ resolvedParams: mask.params, width: transform.width, @@ -472,7 +487,7 @@ function buildMaskArtifacts({ }); let strokeLayer: FrameItemDescriptor | null = null; - if (mask.params.strokeWidth > 0 && strokePath) { + if (mask.params.strokeWidth > 0 && (strokePath || definition.renderer.renderStroke)) { const strokeCanvas = createOffscreenCanvas({ width: Math.round(transform.width), height: Math.round(transform.height), @@ -482,9 +497,18 @@ function buildMaskArtifacts({ | OffscreenCanvasRenderingContext2D | null; if (strokeCtx) { - strokeCtx.strokeStyle = mask.params.strokeColor; - strokeCtx.lineWidth = mask.params.strokeWidth; - strokeCtx.stroke(strokePath); + if (definition.renderer.renderStroke) { + definition.renderer.renderStroke({ + resolvedParams: mask.params, + ctx: strokeCtx, + width: transform.width, + height: transform.height, + }); + } else if (strokePath) { + strokeCtx.strokeStyle = mask.params.strokeColor; + strokeCtx.lineWidth = mask.params.strokeWidth; + strokeCtx.stroke(strokePath); + } const fullStrokeCanvas = createOffscreenCanvas({ width: renderer.width, -- cgit v1.3.1