diff --git a/project/plugins.js b/project/plugins.js index 0d1e136..5473467 100644 --- a/project/plugins.js +++ b/project/plugins.js @@ -22136,451 +22136,554 @@ 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 baseHue = 45; // 35-60黄色到橙色范围 - const baseSaturation = 40; // 25-50%饱和度 - const baseLightness = 85; // 80-95%亮度 + // 上边缘曲线 + const cp1x = 325 * 0.5; + const cp1y = offsetY; + ctx.quadraticCurveTo(cp1x, cp1y, 325, 20 + offsetY); - // 1. 创建高度自然的基底纹理 - const createOrganicBase = () => { - // 基础底色(轻微噪点纹理) - textureCtx.fillStyle = `hsl(${baseHue}, ${baseSaturation}%, ${baseLightness}%)`; - textureCtx.fillRect(0, 0, 325, 400); + // 右侧直线 + ctx.lineTo(325, 400 + offsetY); - // 生成有机噪点纹理(模拟羊皮纸纤维) - const noiseData = textureCtx.createImageData(325, 400); - for (let i = 0; i < noiseData.data.length; i += 4) { - // Perlin噪声生成更自然的纹理 - const x = (i / 4) % 325; - const y = Math.floor((i / 4) / 325); - const noiseVal = this.perlinNoise(x / 50, y / 50) * 0.5 + - this.perlinNoise(x / 20, y / 20) * 0.3 + - this.perlinNoise(x / 5, y / 5) * 0.2; + // 下边缘曲线 + const cp2x = 325 * 0.5; + const cp2y = 380 + offsetY; + ctx.quadraticCurveTo(cp2x, cp2y, 0, 400 + offsetY); - // 基于噪声调整颜色 - const hueVar = noiseVal * 10; - const satVar = noiseVal * 15; - const lightVar = noiseVal * 8; + // 左侧直线 + ctx.closePath(); + }; - noiseData.data[i] = this.HSLtoRGB( - (baseHue + hueVar) % 360, - Math.max(10, Math.min(70, baseSaturation + satVar)), - Math.max(70, Math.min(98, baseLightness + lightVar)) - ).r; - noiseData.data[i + 1] = this.HSLtoRGB( - (baseHue + hueVar) % 360, - Math.max(10, Math.min(70, baseSaturation + satVar)), - Math.max(70, Math.min(98, baseLightness + lightVar)) - ).g; - noiseData.data[i + 2] = this.HSLtoRGB( - (baseHue + hueVar) % 360, - Math.max(10, Math.min(70, baseSaturation + satVar)), - Math.max(70, Math.min(98, baseLightness + lightVar)) - ).b; - noiseData.data[i + 3] = 255; + // 创建弯曲书页的蒙版 + 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%亮度 + + // 创建高度自然的基底纹理 + const createOrganicBase = () => { + // 基础底色(轻微噪点纹理) + contentCtx.fillStyle = `hsl(${baseHue}, ${baseSaturation}%, ${baseLightness}%)`; + contentCtx.fillRect(0, 0, 325, 400); + + // 生成有机噪点纹理(模拟羊皮纸纤维) + const noiseData = contentCtx.createImageData(325, 400); + for (let i = 0; i < noiseData.data.length; i += 4) { + // Perlin噪声生成更自然的纹理 + const x = (i / 4) % 325; + const y = Math.floor((i / 4) / 325); + const noiseVal = this.perlinNoise(x / 50, y / 50) * 0.5 + + this.perlinNoise(x / 20, y / 20) * 0.3 + + this.perlinNoise(x / 5, y / 5) * 0.2; + + // 基于噪声调整颜色 + const hueVar = noiseVal * 10; + const satVar = noiseVal * 15; + const lightVar = noiseVal * 8; + + noiseData.data[i] = this.HSLtoRGB( + (baseHue + hueVar) % 360, + Math.max(10, Math.min(70, baseSaturation + satVar)), + Math.max(70, Math.min(98, baseLightness + lightVar)) + ).r; + noiseData.data[i + 1] = this.HSLtoRGB( + (baseHue + hueVar) % 360, + Math.max(10, Math.min(70, baseSaturation + satVar)), + Math.max(70, Math.min(98, baseLightness + lightVar)) + ).g; + noiseData.data[i + 2] = this.HSLtoRGB( + (baseHue + hueVar) % 360, + Math.max(10, Math.min(70, baseSaturation + satVar)), + Math.max(70, Math.min(98, baseLightness + lightVar)) + ).b; + noiseData.data[i + 3] = 255; + } + contentCtx.putImageData(noiseData, 0, 0); + + }; + createOrganicBase(); + // 更自然的边缘破损效果 + const addEdgeTears = () => { + const tearCount = Math.floor(Math.random() * 4) + 2; + + for (let i = 0; i < tearCount; i++) { + const size = 5 + Math.random() * 12; + const roughness = 2 + Math.random() * 3; + + if (dir === "left") { + // 左侧边缘破损 + if (Math.random() > 0.3) { + const y = Math.random() * 350 + 25; + createOrganicTear(0, y, size, Math.PI / 2, roughness); + } else { + const x = Math.random() * 50; + const y = Math.random() > 0.5 ? 0 : 400; + createOrganicTear(x, y, size * 0.7, Math.random() > 0.5 ? 0 : Math.PI, roughness); + } + } else { + // 右侧边缘破损 + if (Math.random() > 0.3) { + const y = Math.random() * 350 + 25; + createOrganicTear(325, y, size, -Math.PI / 2, roughness); + } else { + const x = 275 + Math.random() * 50; + const y = Math.random() > 0.5 ? 0 : 400; + createOrganicTear(x, y, size * 0.7, Math.random() > 0.5 ? 0 : Math.PI, roughness); + } + } + } + + function createOrganicTear(x, y, size, angle, roughness) { + const points = []; + const steps = 10 + Math.floor(Math.random() * 10); + + // 生成有机形状的点 + for (let i = 0; i <= steps; i++) { + const t = i / steps; + const r = size * (0.8 + Math.random() * 0.4); + const a = angle + Math.PI + t * Math.PI; + const noiseX = (Math.random() * 2 - 1) * roughness; + const noiseY = (Math.random() * 2 - 1) * roughness; + + points.push({ + x: x + r * Math.cos(a) * (0.5 + t * 0.5) + noiseX, + y: y + r * Math.sin(a) * (0.5 + t * 0.5) + noiseY + }); + } + + // 绘制破损形状 + contentCtx.beginPath(); + contentCtx.moveTo(x, y); + points.forEach(p => contentCtx.lineTo(p.x, p.y)); + contentCtx.closePath(); + + // 破损内部阴影 + contentCtx.fillStyle = `rgba(140, 110, 80, ${0.08 + Math.random()*0.07})`; + contentCtx.fill(); + + // 破损边缘线 + 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++) { + contentCtx.beginPath(); + const detailX = x + (points[0].x - x) * 0.3 * (j + 1); + const detailY = y + (points[0].y - y) * 0.3 * (j + 1); + 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); + contentCtx.lineTo(toX, toY); + } + + contentCtx.lineWidth = 0.1 + Math.random() * 0.2; + contentCtx.strokeStyle = `rgba(120, 90, 70, ${0.2 + Math.random()*0.1})`; + contentCtx.stroke(); + } + } + }; + addEdgeTears(); + /** + * 根据方向调整边缘阴影强度,适配弯曲蒙版 + * @param {string} dir 方向参数('left'或'right') + */ + const addMasterfulEdgeShadows = (dir) => { + const shadowHue = baseHue - 8; + const shadowSaturation = baseSaturation + 15; + const shadowLightness = baseLightness * 0.55; + 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) { + const x = (i / 4) % 325; + const y = Math.floor((i / 4) / 325); + + // 基础距离计算 + 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; + let rightShadowIntensity = 0.7; + + if (dir === "left") { + rightShadowIntensity *= 0.6; // 削弱右侧阴影 + } else if (dir === "right") { + leftShadowIntensity *= 0.6; // 削弱左侧阴影 + } + + // 计算角部距离 + const tlDist = Math.sqrt(x * x + y * y) / (shadowSize * 1.4); + const trDist = Math.sqrt((325 - x) * (325 - x) + y * y) / (shadowSize * 1.4); + const blDist = Math.sqrt(x * x + (400 - y) * (400 - y)) / (shadowSize * 1.4); + const brDist = Math.sqrt((325 - x) * (325 - x) + (400 - y) * (400 - y)) / (shadowSize * 1.4); + + // 综合阴影强度 + let shadowAlpha = 0; + + // 左侧阴影计算 + if (leftDist < 1) { + shadowAlpha = Math.max(shadowAlpha, leftShadowIntensity * (1 - leftDist)); + } + + // 右侧阴影计算 + if (rightDist < 1) { + shadowAlpha = Math.max(shadowAlpha, rightShadowIntensity * (1 - rightDist)); + } + + // 顶部和底部阴影(根据弯曲路径调整) + if (topDist < 1) { + + shadowAlpha = Math.max(shadowAlpha, (1 - topDist) * 0.7); + } + if (bottomDist < 1) { + + shadowAlpha = Math.max(shadowAlpha, (1 - bottomDist) * 0.7); + } + + // 角部阴影(取相邻两边强度的中间值) + if (tlDist < 1) { + const cornerAlpha = (leftShadowIntensity + 0.7) / 2 * (1 - tlDist); + shadowAlpha = Math.max(shadowAlpha, cornerAlpha); + } + if (trDist < 1) { + const cornerAlpha = (rightShadowIntensity + 0.7) / 2 * (1 - trDist); + shadowAlpha = Math.max(shadowAlpha, cornerAlpha); + } + if (blDist < 1) { + const cornerAlpha = (leftShadowIntensity + 0.7) / 2 * (1 - blDist); + shadowAlpha = Math.max(shadowAlpha, cornerAlpha); + } + if (brDist < 1) { + const cornerAlpha = (rightShadowIntensity + 0.7) / 2 * (1 - brDist); + shadowAlpha = Math.max(shadowAlpha, cornerAlpha); + } + + // 限制最大透明度 + shadowAlpha = Math.min(0.6, shadowAlpha); + + // 转换为RGB + const rgb = this.HSLtoRGB(shadowHue, shadowSaturation, shadowLightness); + + gradientData.data[i] = rgb.r; + gradientData.data[i + 1] = rgb.g; + gradientData.data[i + 2] = rgb.b; + gradientData.data[i + 3] = shadowAlpha * 255; + } + + 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; + }; + + // 应用阴影 + contentCtx.globalCompositeOperation = 'multiply'; + contentCtx.drawImage(shadowCanvas, 0, 0); + contentCtx.globalCompositeOperation = 'source-over' + //应用高光 + contentCtx.drawImage(createHighlight(), 0, 0) } - textureCtx.putImageData(noiseData, 0, 0); + addMasterfulEdgeShadows(dir); - // 添加手工染色效果(不规则色斑) - /*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 addHyperRealisticDetails = () => { + // 3.1 纤维纹理(模拟羊皮纸纤维) + for (let i = 0; i < 2000; i++) { + const x = Math.random() * 325; + const y = Math.random() * 400; + const length = 1 + Math.random() * 4; + const angle = Math.random() * Math.PI * 2; + const lightVar = (Math.random() > 0.5 ? 5 : -5) * (0.5 + Math.random()); - // 创建有机形状的渐变 - 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) + contentCtx.beginPath(); + contentCtx.moveTo(x, y); + contentCtx.lineTo( + x + Math.cos(angle) * length, + y + Math.sin(angle) * length ); - } - textureCtx.closePath(); - textureCtx.fill(); - }*/ - }; - createOrganicBase(); - // 更自然的边缘破损效果 - const addEdgeTears = () => { - const tearCount = Math.floor(Math.random() * 4) + 2; - for (let i = 0; i < tearCount; i++) { - const size = 5 + Math.random() * 12; - const roughness = 2 + Math.random() * 3; - - if (dir === "left") { - // 左侧边缘破损 - if (Math.random() > 0.3) { - const y = Math.random() * 350 + 25; - createOrganicTear(0, y, size, Math.PI / 2, roughness); - } else { - const x = Math.random() * 50; - const y = Math.random() > 0.5 ? 0 : 400; - createOrganicTear(x, y, size * 0.7, Math.random() > 0.5 ? 0 : Math.PI, roughness); - } - } else { - // 右侧边缘破损 - if (Math.random() > 0.3) { - const y = Math.random() * 350 + 25; - createOrganicTear(325, y, size, -Math.PI / 2, roughness); - } else { - const x = 275 + Math.random() * 50; - const y = Math.random() > 0.5 ? 0 : 400; - createOrganicTear(x, y, size * 0.7, Math.random() > 0.5 ? 0 : Math.PI, roughness); - } - } - } - - function createOrganicTear(x, y, size, angle, roughness) { - const points = []; - const steps = 10 + Math.floor(Math.random() * 10); - - // 生成有机形状的点 - for (let i = 0; i <= steps; i++) { - const t = i / steps; - const r = size * (0.8 + Math.random() * 0.4); - const a = angle + Math.PI + t * Math.PI; - const noiseX = (Math.random() * 2 - 1) * roughness; - const noiseY = (Math.random() * 2 - 1) * roughness; - - points.push({ - x: x + r * Math.cos(a) * (0.5 + t * 0.5) + noiseX, - y: y + r * Math.sin(a) * (0.5 + t * 0.5) + noiseY - }); - } - - // 绘制破损形状 - textureCtx.beginPath(); - textureCtx.moveTo(x, y); - points.forEach(p => textureCtx.lineTo(p.x, p.y)); - textureCtx.closePath(); - - // 破损内部阴影 - textureCtx.fillStyle = `rgba(140, 110, 80, ${0.08 + Math.random()*0.07})`; - textureCtx.fill(); - - // 破损边缘线 - textureCtx.lineWidth = 0.3 + Math.random() * 0.4; - textureCtx.strokeStyle = `rgba(100, 80, 60, ${0.4 + Math.random()*0.2})`; - textureCtx.stroke(); - - // 添加一些细节线条 - for (let j = 0; j < 2; j++) { - textureCtx.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); - - 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); - } - - textureCtx.lineWidth = 0.1 + Math.random() * 0.2; - textureCtx.strokeStyle = `rgba(120, 90, 70, ${0.2 + Math.random()*0.1})`; - textureCtx.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 shadowCanvas = document.createElement('canvas'); - shadowCanvas.width = 325; - shadowCanvas.height = 400; - const shadowCtx = shadowCanvas.getContext('2d'); - - // 计算每个像素的阴影强度 - const gradientData = shadowCtx.createImageData(325, 400); - for (let i = 0; i < gradientData.data.length; i += 4) { - const x = (i / 4) % 325; - 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; - - // 根据方向调整左右阴影强度 - let leftShadowIntensity = 0.7; - let rightShadowIntensity = 0.7; - - if (dir === "left") { - rightShadowIntensity *= 0.6; // 削弱右侧阴影 - } else if (dir === "right") { - leftShadowIntensity *= 0.6; // 削弱左侧阴影 - } - - // 计算角部距离 - const tlDist = Math.sqrt(x * x + y * y) / (shadowSize * 1.4); - const trDist = Math.sqrt((325 - x) * (325 - x) + y * y) / (shadowSize * 1.4); - const blDist = Math.sqrt(x * x + (400 - y) * (400 - y)) / (shadowSize * 1.4); - const brDist = Math.sqrt((325 - x) * (325 - x) + (400 - y) * (400 - y)) / (shadowSize * 1.4); - - // 综合阴影强度 - let shadowAlpha = 0; - - // 左侧阴影计算 - if (leftDist < 1) { - shadowAlpha = Math.max(shadowAlpha, leftShadowIntensity * (1 - leftDist)); - } - - // 右侧阴影计算 - if (rightDist < 1) { - shadowAlpha = Math.max(shadowAlpha, rightShadowIntensity * (1 - rightDist)); - } - - // 顶部和底部阴影(保持不变) - if (topDist < 1) { - shadowAlpha = Math.max(shadowAlpha, 0.7 * (1 - topDist)); - } - if (bottomDist < 1) { - shadowAlpha = Math.max(shadowAlpha, 0.7 * (1 - bottomDist)); - } - - // 角部阴影(取相邻两边强度的中间值) - if (tlDist < 1) { - const cornerAlpha = (leftShadowIntensity + 0.7) / 2 * (1 - tlDist); - shadowAlpha = Math.max(shadowAlpha, cornerAlpha); - } - if (trDist < 1) { - const cornerAlpha = (rightShadowIntensity + 0.7) / 2 * (1 - trDist); - shadowAlpha = Math.max(shadowAlpha, cornerAlpha); - } - if (blDist < 1) { - const cornerAlpha = (leftShadowIntensity + 0.7) / 2 * (1 - blDist); - shadowAlpha = Math.max(shadowAlpha, cornerAlpha); - } - if (brDist < 1) { - const cornerAlpha = (rightShadowIntensity + 0.7) / 2 * (1 - brDist); - shadowAlpha = Math.max(shadowAlpha, cornerAlpha); - } - - // 限制最大透明度 - shadowAlpha = Math.min(0.6, shadowAlpha); - - // 转换为RGB - const rgb = this.HSLtoRGB(shadowHue, shadowSaturation, shadowLightness); - - gradientData.data[i] = rgb.r; - gradientData.data[i + 1] = rgb.g; - gradientData.data[i + 2] = rgb.b; - gradientData.data[i + 3] = shadowAlpha * 255; - } - - shadowCtx.putImageData(gradientData, 0, 0); - - // 应用阴影 - textureCtx.globalCompositeOperation = 'multiply'; - textureCtx.drawImage(shadowCanvas, 0, 0); - textureCtx.globalCompositeOperation = 'source-over'; - } - addMasterfulEdgeShadows(dir); - - // 3. 超真实细节系统 - const addHyperRealisticDetails = () => { - // 3.1 纤维纹理(模拟羊皮纸纤维) - for (let i = 0; i < 2000; i++) { - const x = Math.random() * 325; - const y = Math.random() * 400; - const length = 1 + Math.random() * 4; - 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( - 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; - const startY = Math.random() * 400; - const angle = Math.random() * Math.PI * 2; - const curveIntensity = 5 + Math.random() * 10; + // 自然划痕系统 + for (let i = 0; i < 10 + Math.random() * 10; i++) { + const scratchLength = 15 + Math.random() * 60; + const startX = Math.random() * 325; + const startY = Math.random() * 400; + const angle = Math.random() * Math.PI * 2; + 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; - const cp1y = startY + Math.sin(angle) * scratchLength * 0.3 + (Math.random() - 0.5) * curveIntensity; - const cp2x = startX + Math.cos(angle) * scratchLength * 0.7 + (Math.random() - 0.5) * curveIntensity; - const cp2y = startY + Math.sin(angle) * scratchLength * 0.7 + (Math.random() - 0.5) * curveIntensity; - const endX = startX + Math.cos(angle) * scratchLength; - const endY = startY + Math.sin(angle) * scratchLength; + // 贝塞尔曲线控制点 + const cp1x = startX + Math.cos(angle) * scratchLength * 0.3 + (Math.random() - 0.5) * curveIntensity; + const cp1y = startY + Math.sin(angle) * scratchLength * 0.3 + (Math.random() - 0.5) * curveIntensity; + const cp2x = startX + Math.cos(angle) * scratchLength * 0.7 + (Math.random() - 0.5) * curveIntensity; + const cp2y = startY + Math.sin(angle) * scratchLength * 0.7 + (Math.random() - 0.5) * curveIntensity; + 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; + // 划痕样式(宽度变化) + 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( - startX + (Math.random() - 0.5) * lineWidth * 2, - startY + (Math.random() - 0.5) * lineWidth * 2 - ); - textureCtx.bezierCurveTo( - cp1x + (Math.random() - 0.5) * lineWidth * 2, - cp1y + (Math.random() - 0.5) * lineWidth * 2, - cp2x + (Math.random() - 0.5) * lineWidth * 2, - cp2y + (Math.random() - 0.5) * lineWidth * 2, - endX + (Math.random() - 0.5) * lineWidth * 2, - endY + (Math.random() - 0.5) * lineWidth * 2 - ); + // 划痕高光(模拟凹陷) + if (Math.random() > 0.3) { + contentCtx.beginPath(); + contentCtx.moveTo( + startX + (Math.random() - 0.5) * lineWidth * 2, + startY + (Math.random() - 0.5) * lineWidth * 2 + ); + contentCtx.bezierCurveTo( + cp1x + (Math.random() - 0.5) * lineWidth * 2, + cp1y + (Math.random() - 0.5) * lineWidth * 2, + cp2x + (Math.random() - 0.5) * lineWidth * 2, + cp2y + (Math.random() - 0.5) * lineWidth * 2, + endX + (Math.random() - 0.5) * lineWidth * 2, + 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: 老化斑点 + // 专业级污渍系统 + for (let i = 0; i < 3 + Math.random() * 5; i++) { + const stainType = Math.floor(Math.random() * 3); // 0: 液体污渍, 1: 指纹, 2: 老化斑点 - if (stainType === 1) { - // 指纹污渍 - const centerX = Math.random() * 325; - const centerY = Math.random() * 400; - const size = 10 + Math.random() * 15; - const rotation = Math.random() * Math.PI * 2; + if (stainType === 1) { + // 指纹污渍 + const centerX = Math.random() * 325; + const centerY = Math.random() * 400; + const size = 10 + Math.random() * 15; + const rotation = Math.random() * Math.PI * 2; - // 指纹纹路 - for (let j = 0; j < 15 + Math.random() * 10; j++) { - const radius = size * (0.3 + Math.random() * 0.7); - const angle = (j / 15) * Math.PI * 2 + rotation; - const width = size * (0.03 + Math.random() * 0.05); + // 指纹纹路 + for (let j = 0; j < 15 + Math.random() * 10; j++) { + const radius = size * (0.3 + Math.random() * 0.7); + const angle = (j / 15) * Math.PI * 2 + rotation; + const width = size * (0.03 + Math.random() * 0.05); - textureCtx.beginPath(); - textureCtx.arc( - centerX + Math.cos(angle) * radius * 0.5, - centerY + Math.sin(angle) * radius * 0.5, - width, 0, Math.PI * 2 - ); + 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(); - } - } else { - // 老化斑点 - const centerX = Math.random() * 325; - const centerY = Math.random() * 400; - const size = 5 + Math.random() * 15; - const points = 8 + Math.floor(Math.random() * 6); + contentCtx.fill(); + } + } else { + // 老化斑点 + const centerX = Math.random() * 325; + const centerY = Math.random() * 400; + const size = 5 + Math.random() * 15; + const points = 8 + Math.floor(Math.random() * 6); - textureCtx.beginPath(); - textureCtx.moveTo( - centerX + size * Math.cos(0), - centerY + size * Math.sin(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( - centerX + radius * Math.cos(angle), - centerY + radius * Math.sin(angle) + contentCtx.beginPath(); + contentCtx.moveTo( + centerX + size * Math.cos(0), + centerY + size * Math.sin(0) ); - } - textureCtx.closePath(); - const gradient = textureCtx.createRadialGradient( - centerX, centerY, 0, - centerX, centerY, size * 1.2 - ); - gradient.addColorStop(0, `hsla( + for (let j = 1; j <= points; j++) { + const angle = (j / points) * Math.PI * 2; + const radius = size * (0.7 + Math.random() * 0.6); + contentCtx.lineTo( + centerX + radius * Math.cos(angle), + centerY + radius * Math.sin(angle) + ); + } + contentCtx.closePath(); + + const gradient = contentCtx.createRadialGradient( + centerX, centerY, 0, + centerX, centerY, size * 1.2 + ); + gradient.addColorStop(0, `hsla( ${baseHue - 15 + Math.random() * 20}, ${baseSaturation + 20}%, ${baseLightness * 0.5}%, ${0.2 + Math.random() * 0.1} )`); - gradient.addColorStop(1, 'hsla(0, 0%, 0%, 0)'); + gradient.addColorStop(1, 'hsla(0, 0%, 0%, 0)'); - textureCtx.fillStyle = gradient; - textureCtx.fill(); + contentCtx.fillStyle = gradient; + contentCtx.fill(); + } } - } + }; + addHyperRealisticDetails(); + + return contentCanvas; }; - addHyperRealisticDetails(); + + // 创建弯曲阴影(新增) + 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(); //恢复变换前的坐标,否则将连续转置