feat: 实现4层晕染绘制系统,替换原有渐变绘制逻辑
This commit is contained in:
@@ -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})`);
|
||||
|
||||
Reference in New Issue
Block a user