This commit is contained in:
th committed 2026-04-20 01:05:27 +02:00
1 parent 43933f93d1
commit 03b1147a93
10 files changed
+804 -80

No files matched your search

+101 -14
View File
@@ -1,6 +1,5 @@
import type { AnimatableProps, Asset, Layer, Scene } from '@/types/scene'
import { sampleLayer } from './interpolate'
import { expandedTrackOrder } from './sceneOrder'
export async function loadSceneImages(scene: Scene): Promise<Record<string, HTMLImageElement>> {
const result: Record<string, HTMLImageElement> = {}
@@ -145,6 +144,80 @@ function drawLayers(
}
}
function drawMaskShape(
ctx: CanvasRenderingContext2D,
layer: Layer,
assetMap: Map<string, Asset>,
time: number,
) {
const asset = assetMap.get(layer.assetId)
if (!asset) return
if (asset.kind !== 'rect' && asset.kind !== 'circle' && asset.kind !== 'text') return
const p = sampleLayer(layer, time)
ctx.save()
ctx.translate(p.x, p.y)
ctx.rotate((p.rotation * Math.PI) / 180)
ctx.scale(p.scaleX, p.scaleY)
ctx.fillStyle = '#000000'
if (asset.kind === 'rect') {
const offX = layer.offsetX ?? asset.width / 2
const offY = layer.offsetY ?? asset.height / 2
ctx.fillRect(-offX, -offY, asset.width, asset.height)
} else if (asset.kind === 'circle') {
const offX = layer.offsetX ?? 0
const offY = layer.offsetY ?? 0
ctx.beginPath()
ctx.arc(-offX, -offY, asset.radius, 0, Math.PI * 2)
ctx.fill()
} else {
const offX = layer.offsetX ?? 0
const offY = layer.offsetY ?? 0
ctx.font = `${asset.fontSize}px sans-serif`
ctx.textBaseline = 'top'
ctx.fillText(asset.text, -offX, -offY)
}
ctx.restore()
}
function drawMaskedGroup(
ctx: CanvasRenderingContext2D,
children: Layer[],
assetMap: Map<string, Asset>,
images: Record<string, HTMLImageElement>,
time: number,
) {
const masks = children.filter((l) => l.isMask)
const content = children.filter((l) => !l.isMask)
if (masks.length === 0) {
drawLayers(ctx, children, assetMap, images, time)
return
}
const w = ctx.canvas.width
const h = ctx.canvas.height
const off = document.createElement('canvas')
off.width = w
off.height = h
const offCtx = off.getContext('2d')
if (!offCtx) {
drawLayers(ctx, content, assetMap, images, time)
return
}
const m = ctx.getTransform()
offCtx.setTransform(m.a, m.b, m.c, m.d, m.e, m.f)
drawLayers(offCtx, content, assetMap, images, time)
for (const mask of masks) {
offCtx.save()
offCtx.globalCompositeOperation = mask.maskMode === 'negative' ? 'destination-out' : 'destination-in'
drawMaskShape(offCtx, mask, assetMap, time)
offCtx.restore()
}
ctx.save()
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.drawImage(off, 0, 0)
ctx.restore()
}
export function drawSceneFrame(
ctx: CanvasRenderingContext2D,
scene: Scene,
@@ -174,27 +247,41 @@ export function drawSceneFrame(
}
const clipById = new Map<string, NonNullable<typeof scene.clips>[number]>()
for (const c of scene.clips ?? []) clipById.set(c.id, c)
const groupById = new Map<string, NonNullable<typeof scene.groups>[number]>()
for (const g of scene.groups ?? []) groupById.set(g.id, g)
const order = expandedTrackOrder(scene).length
? expandedTrackOrder(scene)
const rawOrder = scene.trackOrder?.length
? scene.trackOrder
: scene.layers.map((l) => l.id)
for (let i = order.length - 1; i >= 0; i--) {
const id = order[i]
for (let i = rawOrder.length - 1; i >= 0; i--) {
const id = rawOrder[i]
const layer = layerById.get(id)
if (layer) {
drawLayers(ctx, [layer], assetMap, images, time)
continue
}
const instances = instancesByTrack.get(id) ?? []
for (const inst of instances) {
const clip = clipById.get(inst.clipId)
if (!clip) continue
const trimStart = inst.trimStart ?? 0
const trimEnd = inst.trimEnd ?? 0
const visible = Math.max(0, clip.duration - trimStart - trimEnd)
const offsetIntoBlock = time - inst.startTime
if (offsetIntoBlock < 0 || offsetIntoBlock > visible) continue
drawLayers(ctx, clip.layers, assetMap, images, trimStart + offsetIntoBlock)
if (instances.length) {
for (const inst of instances) {
const clip = clipById.get(inst.clipId)
if (!clip) continue
const trimStart = inst.trimStart ?? 0
const trimEnd = inst.trimEnd ?? 0
const visible = Math.max(0, clip.duration - trimStart - trimEnd)
const offsetIntoBlock = time - inst.startTime
if (offsetIntoBlock < 0 || offsetIntoBlock > visible) continue
drawLayers(ctx, clip.layers, assetMap, images, trimStart + offsetIntoBlock)
}
continue
}
const group = groupById.get(id)
if (group) {
const childLayers: Layer[] = []
for (let k = group.childIds.length - 1; k >= 0; k--) {
const cl = layerById.get(group.childIds[k])
if (cl) childLayers.push(cl)
}
drawMaskedGroup(ctx, childLayers, assetMap, images, time)
}
}