feat: 实现4层晕染绘制系统,替换原有渐变绘制逻辑

This commit is contained in:
张翔
2026-05-03 19:40:04 +08:00
parent 119ce83b56
commit 9b1939dd02
+90 -46
View File
@@ -289,6 +289,94 @@ function drawInkDot(
ctx.restore();
}
function drawInkBlob(
ctx: CanvasRenderingContext2D,
cx: number,
cy: number,
baseR: number,
seed1: number,
seed2: number,
wobble: number,
detail: number,
rgb: string,
opacity: number,
): void {
ctx.beginPath();
for (let i = 0; i <= detail; i++) {
const angle = (i / detail) * Math.PI * 2;
const noise = Math.sin(angle * 3 + seed1) * 0.3 + Math.cos(angle * 5 + seed2) * 0.2;
const r = Math.max(0.5, baseR * (1 + noise * wobble));
const x = cx + Math.cos(angle) * r;
const y = cy + Math.sin(angle) * r;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.fillStyle = `rgba(${rgb}, ${opacity})`;
ctx.fill();
}
function drawInkLayers(
ctx: CanvasRenderingContext2D,
p: Particle,
config: ResponsiveConfig,
): void {
const tone = TONES[p.toneIndex];
if (!tone) {
return;
}
const isAccent = ACCENT_TONES.includes(p.toneIndex);
const deepRgb = isAccent ? tone.rgb : (p.toneIndex <= 1 ? tone.rgb : TONES[0].rgb);
const lightRgb = isAccent ? TONES[1].rgb : (p.toneIndex >= 2 ? tone.rgb : TONES[2].rgb);
const r = Math.max(0.5, p.radius) * config.inkRadiusScale;
const op = p.opacity;
const detail = config.wobbleDetail;
const layers: Array<{ radiusMul: number; opacityMul: number; rgb: string; wobble: number; useRect: boolean }> = [
{ radiusMul: 5, opacityMul: 0.04, rgb: lightRgb, wobble: 0.25, useRect: true },
{ radiusMul: 3, opacityMul: 0.12, rgb: lightRgb, wobble: 0.2, useRect: true },
{ radiusMul: 1.5, opacityMul: 0.4, rgb: deepRgb, wobble: 0.12, useRect: false },
{ radiusMul: 1, opacityMul: 0.9, rgb: deepRgb, wobble: 0.06, useRect: false },
];
const layerCount = p.inkLayerCount;
const startIdx = layers.length - layerCount;
for (let i = startIdx; i < layers.length; i++) {
const layer = layers[i];
if (!layer) {
continue;
}
const layerR = r * layer.radiusMul;
const layerOp = op * layer.opacityMul;
if (layer.useRect) {
const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, layerR);
grad.addColorStop(0, `rgba(${layer.rgb}, ${layerOp})`);
grad.addColorStop(0.5, `rgba(${layer.rgb}, ${layerOp * 0.3})`);
grad.addColorStop(1, 'transparent');
ctx.fillStyle = grad;
ctx.fillRect(p.x - layerR, p.y - layerR, layerR * 2, layerR * 2);
} else {
const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, layerR);
grad.addColorStop(0, `rgba(${layer.rgb}, ${layerOp})`);
grad.addColorStop(0.6, `rgba(${layer.rgb}, ${layerOp * 0.5})`);
grad.addColorStop(1, 'transparent');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(p.x, p.y, layerR, 0, Math.PI * 2);
ctx.fill();
drawInkBlob(ctx, p.x, p.y, layerR * 0.7, p.seed1, p.seed2, layer.wobble, detail, layer.rgb, layerOp * 0.6);
}
}
}
function drawParticle(
ctx: CanvasRenderingContext2D,
p: Particle,
@@ -309,53 +397,9 @@ function drawParticle(
if (p.phase === 'spreading') {
drawTrail(ctx, p, rgb);
if (config.gradientLayers >= 3) {
const grad3 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 5 * gs);
grad3.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.06})`);
grad3.addColorStop(0.5, `rgba(${rgb}, ${p.opacity * 0.02})`);
grad3.addColorStop(1, 'transparent');
ctx.fillStyle = grad3;
ctx.fillRect(p.x - r * 5 * gs, p.y - r * 5 * gs, r * 10 * gs, r * 10 * gs);
}
const grad2R = r * 2.8 * gs;
const grad2 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, grad2R);
grad2.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.2})`);
grad2.addColorStop(0.6, `rgba(${rgb}, ${p.opacity * 0.06})`);
grad2.addColorStop(1, 'transparent');
ctx.fillStyle = grad2;
ctx.fillRect(p.x - grad2R, p.y - grad2R, grad2R * 2, grad2R * 2);
const grad1 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r);
grad1.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.9})`);
grad1.addColorStop(0.7, `rgba(${rgb}, ${p.opacity * 0.4})`);
grad1.addColorStop(1, 'transparent');
ctx.fillStyle = grad1;
ctx.beginPath();
ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
ctx.fill();
drawInkDot(ctx, p.x, p.y, r * 0.6, p.rotation, p.scaleX, p.scaleY, rgb, p.opacity * 0.7);
drawInkLayers(ctx, p, config);
} else if (p.phase === 'settling') {
const grad2R = r * 2.2 * gs;
const grad2 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, grad2R);
grad2.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.15})`);
grad2.addColorStop(0.5, `rgba(${rgb}, ${p.opacity * 0.05})`);
grad2.addColorStop(1, 'transparent');
ctx.fillStyle = grad2;
ctx.fillRect(p.x - grad2R, p.y - grad2R, grad2R * 2, grad2R * 2);
const grad1 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r);
grad1.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.85})`);
grad1.addColorStop(0.6, `rgba(${rgb}, ${p.opacity * 0.3})`);
grad1.addColorStop(1, 'transparent');
ctx.fillStyle = grad1;
ctx.beginPath();
ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
ctx.fill();
drawInkDot(ctx, p.x, p.y, r * 0.5, p.rotation, p.scaleX, p.scaleY, rgb, p.opacity * 0.5);
drawInkLayers(ctx, p, config);
} else if (p.phase === 'fading') {
const grad1 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 1.5);
grad1.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.6})`);