This commit is contained in:
th committed 2026-04-20 00:07:08 +02:00
1 parent 6b6157d786
commit 3ef5fb5a2d
19 files changed
+1244 -94

No files matched your search

+24
View File
@@ -149,18 +149,38 @@ const ENGINE_SOURCE = `(function(){
ctx.translate(p.x,p.y);
ctx.rotate(p.rotation*Math.PI/180);
ctx.scale(p.scaleX,p.scaleY);
var sOp=p.shadowOpacity||0;
var sBl=p.shadowBlur||0;
var sDi=p.shadowDistance||0;
if(sOp>0 && (sBl>0 || sDi>0)){
var sAn=(p.shadowAngle==null?90:p.shadowAngle)*Math.PI/180;
var sHex=(p.shadowColor||'#000000').replace('#','');
if(sHex.length===3){sHex=sHex[0]+sHex[0]+sHex[1]+sHex[1]+sHex[2]+sHex[2];}
var sNum=parseInt(sHex.slice(0,6),16);
if(isNaN(sNum))sNum=0;
ctx.shadowColor='rgba('+((sNum>>16)&255)+','+((sNum>>8)&255)+','+(sNum&255)+','+sOp+')';
ctx.shadowBlur=sBl;
ctx.shadowOffsetX=Math.cos(sAn)*sDi;
ctx.shadowOffsetY=Math.sin(sAn)*sDi;
}
if(asset.kind==='rect'){
var rOffX=layer.offsetX!=null?layer.offsetX:asset.width/2;
var rOffY=layer.offsetY!=null?layer.offsetY:asset.height/2;
var rBlur=(p.blur!=null?p.blur:(asset.blur||0));
ctx.fillStyle=p.fill||asset.fill;
if(rBlur>0) ctx.filter='blur('+rBlur+'px)';
ctx.fillRect(-rOffX,-rOffY,asset.width,asset.height);
ctx.filter='none';
}else if(asset.kind==='circle'){
var cOffX=layer.offsetX!=null?layer.offsetX:0;
var cOffY=layer.offsetY!=null?layer.offsetY:0;
var cBlur=(p.blur!=null?p.blur:(asset.blur||0));
ctx.fillStyle=p.fill||asset.fill;
if(cBlur>0) ctx.filter='blur('+cBlur+'px)';
ctx.beginPath();
ctx.arc(-cOffX,-cOffY,asset.radius,0,Math.PI*2);
ctx.fill();
ctx.filter='none';
}else if(asset.kind==='text'){
var tOffX=layer.offsetX!=null?layer.offsetX:0;
var tOffY=layer.offsetY!=null?layer.offsetY:0;
@@ -176,6 +196,10 @@ const ENGINE_SOURCE = `(function(){
ctx.drawImage(img,-iOffX,-iOffY,asset.width,asset.height);
}
}
ctx.shadowColor='transparent';
ctx.shadowBlur=0;
ctx.shadowOffsetX=0;
ctx.shadowOffsetY=0;
ctx.restore();
}
function draw(time){
+13
View File
@@ -49,6 +49,19 @@ function lerpProps(a: AnimatableProps, b: AnimatableProps, t: number): Animatabl
if (a.fill && b.fill) out.fill = lerpColor(a.fill, b.fill, t)
else if (b.fill) out.fill = b.fill
else if (a.fill) out.fill = a.fill
if (a.blur !== undefined && b.blur !== undefined) out.blur = lerp(a.blur, b.blur, t)
else if (b.blur !== undefined) out.blur = b.blur
else if (a.blur !== undefined) out.blur = a.blur
for (const key of ['shadowBlur', 'shadowOpacity', 'shadowAngle', 'shadowDistance'] as const) {
const av = a[key]
const bv = b[key]
if (av !== undefined && bv !== undefined) out[key] = lerp(av, bv, t)
else if (bv !== undefined) out[key] = bv
else if (av !== undefined) out[key] = av
}
if (a.shadowColor && b.shadowColor) out.shadowColor = lerpColor(a.shadowColor, b.shadowColor, t)
else if (b.shadowColor) out.shadowColor = b.shadowColor
else if (a.shadowColor) out.shadowColor = a.shadowColor
return out
}
+71 -3
View File
@@ -1,4 +1,4 @@
import type { Asset, Layer, Scene } from '@/types/scene'
import type { AnimatableProps, Asset, Layer, Scene } from '@/types/scene'
import { sampleLayer } from './interpolate'
export async function loadSceneImages(scene: Scene): Promise<Record<string, HTMLImageElement>> {
@@ -24,6 +24,62 @@ export async function loadSceneImages(scene: Scene): Promise<Record<string, HTML
return result
}
interface ShadowState {
sx: number
sy: number
blur: number
color: string
}
function hexToRgba(hex: string, alpha: number): string {
const h = hex.trim().replace('#', '')
const full = h.length === 3 ? h.split('').map((c) => c + c).join('') : h
if (full.length < 6) return `rgba(0, 0, 0, ${alpha})`
const n = parseInt(full.slice(0, 6), 16)
if (Number.isNaN(n)) return `rgba(0, 0, 0, ${alpha})`
const r = (n >> 16) & 255
const g = (n >> 8) & 255
const b = n & 255
return `rgba(${r}, ${g}, ${b}, ${alpha})`
}
function computeShadow(p: AnimatableProps): ShadowState | null {
const opacity = p.shadowOpacity ?? 0
if (opacity <= 0) return null
const blur = p.shadowBlur ?? 0
const distance = p.shadowDistance ?? 0
if (blur <= 0 && distance <= 0) return null
const angle = p.shadowAngle ?? 90
const rad = (angle * Math.PI) / 180
return {
sx: Math.cos(rad) * distance,
sy: Math.sin(rad) * distance,
blur,
color: hexToRgba(p.shadowColor ?? '#000000', opacity),
}
}
function applyShadow(ctx: CanvasRenderingContext2D, s: ShadowState | null) {
if (!s) {
ctx.shadowColor = 'transparent'
ctx.shadowBlur = 0
ctx.shadowOffsetX = 0
ctx.shadowOffsetY = 0
return
}
ctx.shadowColor = s.color
ctx.shadowBlur = s.blur
ctx.shadowOffsetX = s.sx
ctx.shadowOffsetY = s.sy
}
function clearShadow(ctx: CanvasRenderingContext2D) {
ctx.shadowColor = 'transparent'
ctx.shadowBlur = 0
ctx.shadowOffsetX = 0
ctx.shadowOffsetY = 0
}
function drawLayers(
ctx: CanvasRenderingContext2D,
layers: Layer[],
@@ -44,18 +100,29 @@ function drawLayers(
ctx.rotate((p.rotation * Math.PI) / 180)
ctx.scale(p.scaleX, p.scaleY)
const shadow = computeShadow(p)
applyShadow(ctx, shadow)
if (asset.kind === 'rect') {
const offX = layer.offsetX ?? asset.width / 2
const offY = layer.offsetY ?? asset.height / 2
ctx.fillStyle = p.fill ?? asset.fill
const fill = p.fill ?? asset.fill
const blur = p.blur ?? asset.blur ?? 0
ctx.fillStyle = fill
if (blur > 0) ctx.filter = `blur(${blur}px)`
ctx.fillRect(-offX, -offY, asset.width, asset.height)
ctx.filter = 'none'
} else if (asset.kind === 'circle') {
const offX = layer.offsetX ?? 0
const offY = layer.offsetY ?? 0
ctx.fillStyle = p.fill ?? asset.fill
const fill = p.fill ?? asset.fill
const blur = p.blur ?? asset.blur ?? 0
ctx.fillStyle = fill
if (blur > 0) ctx.filter = `blur(${blur}px)`
ctx.beginPath()
ctx.arc(-offX, -offY, asset.radius, 0, Math.PI * 2)
ctx.fill()
ctx.filter = 'none'
} else if (asset.kind === 'text') {
const offX = layer.offsetX ?? 0
const offY = layer.offsetY ?? 0
@@ -72,6 +139,7 @@ function drawLayers(
}
}
clearShadow(ctx)
ctx.restore()
}
}