手册改为曲面

This commit is contained in:
strawberry 2025-07-13 23:38:07 +08:00
parent f7863ef77c
commit 4940314d0f

View File

@ -22136,20 +22136,64 @@ let time=0
textureCanvas.width = 325;
textureCanvas.height = 400;
const textureCtx = textureCanvas.getContext('2d');
// 创建弯曲路径函数(复用)
const createCurvedPath = (ctx, offsetY = 0) => {
ctx.beginPath();
ctx.moveTo(0, 20 + offsetY);
// 上边缘曲线
const cp1x = 325 * 0.5;
const cp1y = offsetY;
ctx.quadraticCurveTo(cp1x, cp1y, 325, 20 + offsetY);
// 右侧直线
ctx.lineTo(325, 400 + offsetY);
// 下边缘曲线
const cp2x = 325 * 0.5;
const cp2y = 380 + offsetY;
ctx.quadraticCurveTo(cp2x, cp2y, 0, 400 + offsetY);
// 左侧直线
ctx.closePath();
};
// 创建弯曲书页的蒙版
const createPageCurveMask = () => {
const curveCanvas = document.createElement('canvas');
curveCanvas.width = 325;
curveCanvas.height = 400;
const curveCtx = curveCanvas.getContext('2d');
curveCtx.clearRect(0, 0, 325, 400);
createCurvedPath(curveCtx);
curveCtx.fillStyle = 'white';
curveCtx.fill();
return curveCanvas;
};
// 创建书页内容 (400px高居中放置)
const createPageContent = () => {
const contentCanvas = document.createElement('canvas');
contentCanvas.width = 325;
contentCanvas.height = 400;
const contentCtx = contentCanvas.getContext('2d');
// 生成羊皮纸基础色(更精细的暖色调范围)
const baseHue = 45; // 35-60黄色到橙色范围
const baseSaturation = 40; // 25-50%饱和度
const baseLightness = 85; // 80-95%亮度
// 1. 创建高度自然的基底纹理
// 创建高度自然的基底纹理
const createOrganicBase = () => {
// 基础底色(轻微噪点纹理)
textureCtx.fillStyle = `hsl(${baseHue}, ${baseSaturation}%, ${baseLightness}%)`;
textureCtx.fillRect(0, 0, 325, 400);
contentCtx.fillStyle = `hsl(${baseHue}, ${baseSaturation}%, ${baseLightness}%)`;
contentCtx.fillRect(0, 0, 325, 400);
// 生成有机噪点纹理(模拟羊皮纸纤维)
const noiseData = textureCtx.createImageData(325, 400);
const noiseData = contentCtx.createImageData(325, 400);
for (let i = 0; i < noiseData.data.length; i += 4) {
// Perlin噪声生成更自然的纹理
const x = (i / 4) % 325;
@ -22180,52 +22224,8 @@ let time=0
).b;
noiseData.data[i + 3] = 255;
}
textureCtx.putImageData(noiseData, 0, 0);
contentCtx.putImageData(noiseData, 0, 0);
// 添加手工染色效果(不规则色斑)
/*for (let i = 0; i < 8 + Math.random() * 5; i++) {
const centerX = Math.random() * 325;
const centerY = Math.random() * 400;
const radius = 30 + Math.random() * 70;
const hueVar = (Math.random() - 0.5) * 15;
const satVar = (Math.random() - 0.5) * 20;
const lightVar = (Math.random() - 0.5) * 12;
// 创建有机形状的渐变
const gradient = textureCtx.createRadialGradient(
centerX, centerY, 0,
centerX + (Math.random() - 0.5) * 20,
centerY + (Math.random() - 0.5) * 20,
radius * (0.8 + Math.random() * 0.4)
);
gradient.addColorStop(0, `hsla(
${baseHue + hueVar},
${baseSaturation + satVar}%,
${baseLightness + lightVar}%,
${0.2 + Math.random() * 0.3}
)`);
gradient.addColorStop(1, 'hsla(0, 0%, 0%, 0)');
textureCtx.fillStyle = gradient;
textureCtx.beginPath();
// 有机形状绘制(非正圆)
const points = 12 + Math.floor(Math.random() * 8);
textureCtx.moveTo(
centerX + radius * Math.cos(0),
centerY + radius * Math.sin(0)
);
for (let j = 1; j <= points; j++) {
const angle = (j / points) * Math.PI * 2;
const dist = radius * (0.7 + Math.random() * 0.6);
textureCtx.lineTo(
centerX + dist * Math.cos(angle),
centerY + dist * Math.sin(angle)
);
}
textureCtx.closePath();
textureCtx.fill();
}*/
};
createOrganicBase();
// 更自然的边缘破损效果
@ -22278,57 +22278,66 @@ let time=0
}
// 绘制破损形状
textureCtx.beginPath();
textureCtx.moveTo(x, y);
points.forEach(p => textureCtx.lineTo(p.x, p.y));
textureCtx.closePath();
contentCtx.beginPath();
contentCtx.moveTo(x, y);
points.forEach(p => contentCtx.lineTo(p.x, p.y));
contentCtx.closePath();
// 破损内部阴影
textureCtx.fillStyle = `rgba(140, 110, 80, ${0.08 + Math.random()*0.07})`;
textureCtx.fill();
contentCtx.fillStyle = `rgba(140, 110, 80, ${0.08 + Math.random()*0.07})`;
contentCtx.fill();
// 破损边缘线
textureCtx.lineWidth = 0.3 + Math.random() * 0.4;
textureCtx.strokeStyle = `rgba(100, 80, 60, ${0.4 + Math.random()*0.2})`;
textureCtx.stroke();
contentCtx.lineWidth = 0.3 + Math.random() * 0.4;
contentCtx.strokeStyle = `rgba(100, 80, 60, ${0.4 + Math.random()*0.2})`;
contentCtx.stroke();
// 添加一些细节线条
for (let j = 0; j < 2; j++) {
textureCtx.beginPath();
contentCtx.beginPath();
const detailX = x + (points[0].x - x) * 0.3 * (j + 1);
const detailY = y + (points[0].y - y) * 0.3 * (j + 1);
textureCtx.moveTo(detailX, detailY);
contentCtx.moveTo(detailX, detailY);
for (let k = 1; k < points.length; k++) {
const p = points[k];
const toX = x + (p.x - x) * 0.3 * (j + 1);
const toY = y + (p.y - y) * 0.3 * (j + 1);
textureCtx.lineTo(toX, toY);
contentCtx.lineTo(toX, toY);
}
textureCtx.lineWidth = 0.1 + Math.random() * 0.2;
textureCtx.strokeStyle = `rgba(120, 90, 70, ${0.2 + Math.random()*0.1})`;
textureCtx.stroke();
contentCtx.lineWidth = 0.1 + Math.random() * 0.2;
contentCtx.strokeStyle = `rgba(120, 90, 70, ${0.2 + Math.random()*0.1})`;
contentCtx.stroke();
}
}
};
addEdgeTears();
// 2. 完美四角阴影系统
/**
* 根据方向调整边缘阴影强度
* 根据方向调整边缘阴影强度适配弯曲蒙版
* @param {string} dir 方向参数('left''right')
*/
const addMasterfulEdgeShadows = (dir) => {
const shadowHue = baseHue - 8;
const shadowSaturation = baseSaturation + 15;
const shadowLightness = baseLightness * 0.55;
const shadowSize = 35 + Math.random() * 15;
const shadowSize = 35;
const shadowCanvas = document.createElement('canvas');
shadowCanvas.width = 325;
shadowCanvas.height = 400;
const shadowCtx = shadowCanvas.getContext('2d');
// 创建临时canvas用于检测弯曲区域
const tempCanvas = document.createElement('canvas');
tempCanvas.width = 325;
tempCanvas.height = 400;
const tempCtx = tempCanvas.getContext('2d');
createCurvedPath(tempCtx);
tempCtx.fillStyle = 'white';
tempCtx.fill();
const maskData = tempCtx.getImageData(0, 0, 325, 400).data;
// 计算每个像素的阴影强度
const gradientData = shadowCtx.createImageData(325, 400);
for (let i = 0; i < gradientData.data.length; i += 4) {
@ -22336,10 +22345,10 @@ let time=0
const y = Math.floor((i / 4) / 325);
// 基础距离计算
const leftDist = x / shadowSize;
const rightDist = (325 - x) / shadowSize;
const topDist = y / shadowSize;
const bottomDist = (400 - y) / shadowSize;
const leftDist = x / shadowSize * (dir === "left" ? 1 : 0.7);
const rightDist = (325 - x) / shadowSize * (dir === "left" ? 0.7 : 1);
const topDist = (y - Math.abs(x - 162.5) / 162.5 * 20) / shadowSize;
const bottomDist = (400 - y - (1 - Math.abs(x - 162.5) / 162.5) * 20) / shadowSize;
// 根据方向调整左右阴影强度
let leftShadowIntensity = 0.7;
@ -22370,12 +22379,14 @@ let time=0
shadowAlpha = Math.max(shadowAlpha, rightShadowIntensity * (1 - rightDist));
}
// 顶部和底部阴影(保持不变)
// 顶部和底部阴影(根据弯曲路径调整)
if (topDist < 1) {
shadowAlpha = Math.max(shadowAlpha, 0.7 * (1 - topDist));
shadowAlpha = Math.max(shadowAlpha, (1 - topDist) * 0.7);
}
if (bottomDist < 1) {
shadowAlpha = Math.max(shadowAlpha, 0.7 * (1 - bottomDist));
shadowAlpha = Math.max(shadowAlpha, (1 - bottomDist) * 0.7);
}
// 角部阴影(取相邻两边强度的中间值)
@ -22410,14 +22421,39 @@ let time=0
shadowCtx.putImageData(gradientData, 0, 0);
const createHighlight = () => {
const highlightCanvas = document.createElement('canvas');
highlightCanvas.width = 325;
highlightCanvas.height = 400;
const highlightCtx = highlightCanvas.getContext('2d');
highlightCtx.clearRect(0, 0, 325, 400);
// 根据方向设置高光位置
const gradient = dir === 'right' ?
highlightCtx.createLinearGradient(10, 0, 50, 0) :
highlightCtx.createLinearGradient(275, 0, 315, 0);
gradient.addColorStop(0, 'rgba(255,255,255,0)');
gradient.addColorStop(0.5, 'rgba(255,255,255,0.5)');
gradient.addColorStop(1, 'rgba(255,255,255,0)');
highlightCtx.fillStyle = gradient;
highlightCtx.fillRect(dir === 'right' ? 10 : 275, 0, 40, 400);
return highlightCanvas;
};
// 应用阴影
textureCtx.globalCompositeOperation = 'multiply';
textureCtx.drawImage(shadowCanvas, 0, 0);
textureCtx.globalCompositeOperation = 'source-over';
contentCtx.globalCompositeOperation = 'multiply';
contentCtx.drawImage(shadowCanvas, 0, 0);
contentCtx.globalCompositeOperation = 'source-over'
//应用高光
contentCtx.drawImage(createHighlight(), 0, 0)
}
addMasterfulEdgeShadows(dir);
// 3. 超真实细节系统
// 超真实细节系统
const addHyperRealisticDetails = () => {
// 3.1 纤维纹理(模拟羊皮纸纤维)
for (let i = 0; i < 2000; i++) {
@ -22427,24 +22463,24 @@ let time=0
const angle = Math.random() * Math.PI * 2;
const lightVar = (Math.random() > 0.5 ? 5 : -5) * (0.5 + Math.random());
textureCtx.beginPath();
textureCtx.moveTo(x, y);
textureCtx.lineTo(
contentCtx.beginPath();
contentCtx.moveTo(x, y);
contentCtx.lineTo(
x + Math.cos(angle) * length,
y + Math.sin(angle) * length
);
textureCtx.strokeStyle = `hsla(
contentCtx.strokeStyle = `hsla(
${baseHue + (Math.random() - 0.5) * 8},
${baseSaturation * (0.7 + Math.random() * 0.6)}%,
${baseLightness + lightVar}%,
${0.03 + Math.random() * 0.04}
)`;
textureCtx.lineWidth = 0.2 + Math.random() * 0.3;
textureCtx.stroke();
contentCtx.lineWidth = 0.2 + Math.random() * 0.3;
contentCtx.stroke();
}
// 3.2 自然划痕系统
// 自然划痕系统
for (let i = 0; i < 10 + Math.random() * 10; i++) {
const scratchLength = 15 + Math.random() * 60;
const startX = Math.random() * 325;
@ -22453,8 +22489,8 @@ let time=0
const curveIntensity = 5 + Math.random() * 10;
// 主划痕路径
textureCtx.beginPath();
textureCtx.moveTo(startX, startY);
contentCtx.beginPath();
contentCtx.moveTo(startX, startY);
// 贝塞尔曲线控制点
const cp1x = startX + Math.cos(angle) * scratchLength * 0.3 + (Math.random() - 0.5) * curveIntensity;
@ -22464,30 +22500,30 @@ let time=0
const endX = startX + Math.cos(angle) * scratchLength;
const endY = startY + Math.sin(angle) * scratchLength;
textureCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY);
contentCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY);
// 划痕样式(宽度变化)
const lineWidth = 0.1 + Math.random() * 0.4;
const scratchDarkness = 0.6 + Math.random() * 0.3;
textureCtx.strokeStyle = `hsla(
contentCtx.strokeStyle = `hsla(
${baseHue - 5 + Math.random() * 10},
${baseSaturation + 5 + Math.random() * 15}%,
${baseLightness * scratchDarkness}%,
${0.4 + Math.random() * 0.3}
)`;
textureCtx.lineWidth = lineWidth;
textureCtx.lineCap = 'round';
textureCtx.stroke();
contentCtx.lineWidth = lineWidth;
contentCtx.lineCap = 'round';
contentCtx.stroke();
// 划痕高光(模拟凹陷)
if (Math.random() > 0.3) {
textureCtx.beginPath();
textureCtx.moveTo(
contentCtx.beginPath();
contentCtx.moveTo(
startX + (Math.random() - 0.5) * lineWidth * 2,
startY + (Math.random() - 0.5) * lineWidth * 2
);
textureCtx.bezierCurveTo(
contentCtx.bezierCurveTo(
cp1x + (Math.random() - 0.5) * lineWidth * 2,
cp1y + (Math.random() - 0.5) * lineWidth * 2,
cp2x + (Math.random() - 0.5) * lineWidth * 2,
@ -22496,18 +22532,18 @@ let time=0
endY + (Math.random() - 0.5) * lineWidth * 2
);
textureCtx.strokeStyle = `hsla(
contentCtx.strokeStyle = `hsla(
${baseHue + 5 + Math.random() * 5},
${baseSaturation * 0.7}%,
${baseLightness * 1.1}%,
${0.2 + Math.random() * 0.1}
)`;
textureCtx.lineWidth = lineWidth * 0.7;
textureCtx.stroke();
contentCtx.lineWidth = lineWidth * 0.7;
contentCtx.stroke();
}
}
// 3.3 专业级污渍系统
// 专业级污渍系统
for (let i = 0; i < 3 + Math.random() * 5; i++) {
const stainType = Math.floor(Math.random() * 3); // 0: 液体污渍, 1: 指纹, 2: 老化斑点
@ -22525,20 +22561,20 @@ let time=0
const angle = (j / 15) * Math.PI * 2 + rotation;
const width = size * (0.03 + Math.random() * 0.05);
textureCtx.beginPath();
textureCtx.arc(
contentCtx.beginPath();
contentCtx.arc(
centerX + Math.cos(angle) * radius * 0.5,
centerY + Math.sin(angle) * radius * 0.5,
width, 0, Math.PI * 2
);
textureCtx.fillStyle = `hsla(
contentCtx.fillStyle = `hsla(
${baseHue - 5 + Math.random() * 10},
${baseSaturation + 5}%,
${baseLightness * 0.8}%,
${0.1 + Math.random() * 0.05}
)`;
textureCtx.fill();
contentCtx.fill();
}
} else {
// 老化斑点
@ -22547,8 +22583,8 @@ let time=0
const size = 5 + Math.random() * 15;
const points = 8 + Math.floor(Math.random() * 6);
textureCtx.beginPath();
textureCtx.moveTo(
contentCtx.beginPath();
contentCtx.moveTo(
centerX + size * Math.cos(0),
centerY + size * Math.sin(0)
);
@ -22556,14 +22592,14 @@ let time=0
for (let j = 1; j <= points; j++) {
const angle = (j / points) * Math.PI * 2;
const radius = size * (0.7 + Math.random() * 0.6);
textureCtx.lineTo(
contentCtx.lineTo(
centerX + radius * Math.cos(angle),
centerY + radius * Math.sin(angle)
);
}
textureCtx.closePath();
contentCtx.closePath();
const gradient = textureCtx.createRadialGradient(
const gradient = contentCtx.createRadialGradient(
centerX, centerY, 0,
centerX, centerY, size * 1.2
);
@ -22575,13 +22611,80 @@ let time=0
)`);
gradient.addColorStop(1, 'hsla(0, 0%, 0%, 0)');
textureCtx.fillStyle = gradient;
textureCtx.fill();
contentCtx.fillStyle = gradient;
contentCtx.fill();
}
}
};
addHyperRealisticDetails();
return contentCanvas;
};
// 创建弯曲阴影(新增)
const createCurvedShadow = () => {
const shadowCanvas = document.createElement('canvas');
shadowCanvas.width = 325;
shadowCanvas.height = 400;
const shadowCtx = shadowCanvas.getContext('2d');
shadowCtx.clearRect(0, 0, 325, 400);
// 绘制顶部阴影(跟随曲线)
const topShadow = shadowCtx.createLinearGradient(0, 0, 0, 50);
topShadow.addColorStop(0, 'rgba(0,0,0,0.25)');
topShadow.addColorStop(1, 'rgba(0,0,0,0)');
shadowCtx.save();
createCurvedPath(shadowCtx);
shadowCtx.clip();
shadowCtx.fillStyle = topShadow;
shadowCtx.fillRect(0, 0, 325, 50);
shadowCtx.restore();
// 绘制底部阴影(跟随曲线)
const bottomShadow = shadowCtx.createLinearGradient(0, 350, 0, 400);
bottomShadow.addColorStop(0, 'rgba(0,0,0,0)');
bottomShadow.addColorStop(1, 'rgba(0,0,0,0.25)');
shadowCtx.save();
createCurvedPath(shadowCtx);
shadowCtx.clip();
shadowCtx.fillStyle = bottomShadow;
shadowCtx.fillRect(0, 350, 325, 50);
shadowCtx.restore();
return shadowCanvas;
};
// 应用弯曲变形
const applyPageCurve = (content, mask, shadow) => {
const resultCanvas = document.createElement('canvas');
resultCanvas.width = 325;
resultCanvas.height = 400;
const resultCtx = resultCanvas.getContext('2d');
resultCtx.clearRect(0, 0, 325, 400);
// 先绘制阴影(在内容下方)
resultCtx.drawImage(shadow, 0, 0);
// 绘制内容并应用蒙版
resultCtx.drawImage(content, 0, 10);
resultCtx.globalCompositeOperation = 'destination-in';
resultCtx.drawImage(mask, 0, 0);
resultCtx.globalCompositeOperation = 'source-over';
return resultCanvas;
};
// 主流程
const mask = createPageCurveMask();
const content = createPageContent();
const shadow = createCurvedShadow();
const curvedPage = applyPageCurve(content, mask, shadow);
// 最终绘制
textureCtx.drawImage(curvedPage, 0, 0);
return textureCanvas;
}
@ -22707,26 +22810,26 @@ let time=0
dy = 416 - this.height
ctx.moveTo(0, 416)
ctx.lineTo(dx, this.height)
ctx.lineTo(dx, 0)
ctx.lineTo(0, dy)
ctx.lineTo(dx, 20)
ctx.lineTo(0, dy + 20)
ctx.closePath()
ctx.fillStyle = "#e0d6c2"
ctx.fillStyle = `rgb(${248*0.7}, ${244*0.7}, ${230*0.7})`
ctx.fill()
ctx.beginPath()
ctx.moveTo(676, 416)
ctx.lineTo(676 - dx, this.height)
ctx.lineTo(676 - dx, 0)
ctx.lineTo(676, dy)
ctx.lineTo(676 - dx, 20)
ctx.lineTo(676, dy + 20)
ctx.closePath()
ctx.fill()
ctx.strokeStyle = `rgb(${248*0.7}, ${244*0.7}, ${230*0.7})`;
ctx.strokeStyle = `rgb(${248*0.5}, ${244*0.5}, ${230*0.5})`;
ctx.beginPath()
ctx.moveTo(2, 100)
ctx.lineTo(2, 130)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(5, 20)
ctx.lineTo(5, 30)
ctx.moveTo(5, 320)
ctx.lineTo(5, 290)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(6, 240)
@ -22745,7 +22848,7 @@ let time=0
ctx.lineTo(670, 160)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(671, 20)
ctx.moveTo(671, 50)
ctx.lineTo(671, 130)
ctx.stroke()
ctx.beginPath()
@ -22758,50 +22861,15 @@ let time=0
ctx.beginPath()
ctx.moveTo(0, 416)
ctx.lineTo(dx, this.height)
ctx.lineTo(338, this.height)
ctx.lineTo(this.width, 416)
ctx.quadraticCurveTo(this.width / 2 + dx, this.height - 20, 338, this.height)
ctx.quadraticCurveTo(this.width * 3 / 2 + dx, this.height - 20, 676 - dx, this.height)
ctx.lineTo(676, 416)
ctx.quadraticCurveTo(676 - this.width / 2, 396, 676 - this.width, 416)
ctx.quadraticCurveTo(338, 406, this.width, 416)
ctx.quadraticCurveTo(this.width / 2, 396, 0, 416)
ctx.closePath()
ctx.fillStyle = '#706b61'
ctx.fill()
ctx.beginPath()
ctx.moveTo(676, 416)
ctx.lineTo(676 - dx, this.height)
ctx.lineTo(338, this.height)
ctx.lineTo(338 + dx, 416)
ctx.closePath()
ctx.fill()
ctx.beginPath()
ctx.moveTo(20, 402)
ctx.lineTo(50, 402)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(180, 405)
ctx.lineTo(220, 405)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(40, 409)
ctx.lineTo(80, 409)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(268, 412)
ctx.lineTo(290, 412)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(430, 403)
ctx.lineTo(470, 403)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(510, 407)
ctx.lineTo(530, 407)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(350, 414)
ctx.lineTo(370, 414)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(602, 411)
ctx.lineTo(610, 411)
ctx.stroke()
ctx.restore(); //恢复变换前的坐标,否则将连续转置