纯前端传统金石碑帖拓印微引擎:基于微表面高度接触检测与松烟墨色干湿渐变算法

发布时间:2026/10/23 8:19:15

纯前端传统金石碑帖拓印微引擎:基于微表面高度接触检测与松烟墨色干湿渐变算法 深秋时节若得半日闲暇我总喜欢翻看桌边的汉魏碑刻与砖瓦拓本。那些历经千年风雨侵蚀的石刻文字被古人以澄心堂纸、松烟黑墨与手作拓包覆于其上细细扑打拓印出来。字迹如骨凹槽留白似雪凸起处墨色深沉如铁且在边缘处伴随着飞白与水墨微微晕染的温润颗粒。在数字化插画与现代手账设计中纯矢量或扁平黑白填充往往失之于呆板锐利缺少金石拓片那种沉浑古雅的厚度与呼吸感。今天我想和大家分享一个纯前端实现的“金石碑帖拓印微引擎”。我们不需要依赖任何臃肿的 WebGL 3D 渲染引擎仅在标准的 2D Canvas 与离屏像素缓冲区内利用微表面高度场接触检测Heightmap Contact Sampling与松烟墨色物理扩散算法重现“覆纸、打拓、受墨、留白”的传统拓印体验。传统金石拓印的物理机理拆解要用算法模拟出逼真的拓片质感必须先理解传统打拓的物理过程凹凸高度场Heightmap Engraved Depths石碑表面并非光滑平面阴刻的文字或花纹是低于基准面的凹槽而石面本身由于岁月风化具有细微的砂石起伏。当湿润的宣纸覆于石面并用鬃刷轻轻打入凹陷后纸面就形成了与石刻完全契合的微三维起伏。拓包触碰与受墨面积Contact Compression拓工手持由丝绸与棉花包裹而成的拓包墨扑蘸取微湿的松烟墨。当拓包拍打或擦拭纸面时拓包柔软的弧形表面只会接触到突起的石面平整处而深陷在文字凹线内的纸张则不会受到垂直压力。拓包施加的下压力越大触及的边缘斜坡面积也越宽。松烟墨的干湿与纤维渗透Soot Ink Dynamics墨汁在宣纸纤维中受表面张力与毛细效应影响并非均质色块。墨色在初触纸时形成浓墨焦墨多次反复扑拓则墨层层层叠加而如果墨汁水分较少在砂砾凸起处会形成苍茫的“飞白”凹陷边界则会有自然的颗粒状墨尘沉积。核心架构离屏高度场检测与交互拓包实现在前端工程中我们将用户输入的黑白线稿或灰度图像作为高度场输入白色或明亮区域代表平整凸起石面黑色阴线代表深刻凹槽拓包则是携带压力与含墨量参数的动力学画刷。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title纯前端传统金石碑帖拓印微引擎/title style :root { --bg-desk: #211e1b; --panel-bg: #2d2925; --paper-cream: #eee5d8; --accent-wood: #a67c52; } * { box-sizing: border-box; margin: 0; padding: 0; } body { background-color: var(--bg-desk); color: var(--paper-cream); font-family: -apple-system, BlinkMacSystemFont, PingFang SC, Kaiti SC, serif; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 30px 20px; user-select: none; } h1 { font-size: 20px; font-weight: 400; letter-spacing: 0.15em; margin-bottom: 8px; } .sub-tip { font-size: 13px; color: #9c9186; margin-bottom: 24px; } .workspace { position: relative; background-color: var(--paper-cream); box-shadow: 0 15px 40px rgba(0, 0, 0, 0.5); border-radius: 4px; cursor: crosshair; } canvas { display: block; border-radius: 4px; } .tools { margin-top: 24px; display: flex; gap: 20px; align-items: center; background: var(--panel-bg); padding: 12px 24px; border-radius: 100px; border: 1px solid rgba(255, 255, 255, 0.08); font-size: 13px; } .control-group { display: flex; align-items: center; gap: 8px; } button { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.15); color: var(--paper-cream); padding: 6px 16px; border-radius: 20px; cursor: pointer; font-size: 12px; transition: all 0.2s ease; } button:hover { background: var(--accent-wood); color: #fff; } input[typerange] { accent-color: var(--accent-wood); width: 90px; } /style /head body h1金石金石 • 传统碑帖拓印工坊/h1 div classsub-tip用鼠标或手指按住纸面如手持墨拓包般轻轻擦拓凹槽字迹自会留白显现/div div classworkspace canvas idrubbingCanvas width600 height420/canvas /div div classtools div classcontrol-group span拓包尺寸:/span input typerange idsizeRange min15 max50 value30 /div div classcontrol-group span下压墨重:/span input typerange idpressureRange min0.1 max0.8 step0.05 value0.35 /div div classcontrol-group span纸面宣透:/span input typerange iddryRange min0.2 max0.9 step0.05 value0.55 /div button idresetPaperBtn重换净纸/button /div script const canvas document.getElementById(rubbingCanvas); const ctx canvas.getContext(2d, { willReadFrequently: true }); // 离屏高度场 Canvas存储石刻的高度三维信息 const heightCanvas document.createElement(canvas); heightCanvas.width canvas.width; heightCanvas.height canvas.height; const heightCtx heightCanvas.getContext(2d); let heightData null; let isRubbing false; // 1. 初始化模拟石碑底板刻痕生成带砂砾噪点与汉魏风骨的深刻阴文 function initStoneHeightmap() { heightCtx.fillStyle #ffffff; // 高平面基准平整石面 heightCtx.fillRect(0, 0, heightCanvas.width, heightCanvas.height); // 叠加石面自然风化砂砾颗粒噪点 const imgData heightCtx.getImageData(0, 0, heightCanvas.width, heightCanvas.height); const data imgData.data; for (let i 0; i data.length; i 4) { const noise (Math.random() - 0.5) * 20; const val Math.min(255, Math.max(0, 255 noise)); data[i] val; data[i 1] val; data[i 2] val; } heightCtx.putImageData(imgData, 0, 0); // 镌刻阴文书法字符深色表示深凹槽拓包拍不到 heightCtx.fillStyle #050505; heightCtx.font bold 64px Kaiti SC, Songti SC, serif; heightCtx.textAlign center; heightCtx.textBaseline middle; const lines [静水深流, 秋水长天, 金石永固]; lines.forEach((line, idx) { heightCtx.fillText(line, heightCanvas.width / 2, 120 idx * 90); }); // 刻画古雅的汉砖回纹边框 heightCtx.strokeStyle #050505; heightCtx.lineWidth 6; heightCtx.strokeRect(30, 30, heightCanvas.width - 60, heightCanvas.height - 60); // 缓存高度场原始像素数据以供微表面碰撞检测 heightData heightCtx.getImageData(0, 0, heightCanvas.width, heightCanvas.height).data; } // 2. 铺上一张洁净的微黄生宣纸带细微纤维纹理 function resetPaper() { ctx.fillStyle #f4ece1; ctx.fillRect(0, 0, canvas.width, canvas.height); // 绘制生宣纸的细微木浆纤维底色 ctx.fillStyle rgba(180, 160, 140, 0.04); for (let i 0; i 15000; i) { const x Math.random() * canvas.width; const y Math.random() * canvas.height; ctx.fillRect(x, y, 1.5, 1.5); } } // 3. 核心算法微表面拓包擦拓接触动力学 function applyRubbingDab(centerX, centerY, radius, pressure, dryness) { const startX Math.max(0, Math.floor(centerX - radius)); const endX Math.min(canvas.width, Math.ceil(centerX radius)); const startY Math.max(0, Math.floor(centerY - radius)); const endY Math.min(canvas.height, Math.ceil(centerY radius)); const w endX - startX; const h endY - startY; if (w 0 || h 0) return; const paperImgData ctx.getImageData(startX, startY, w, h); const paperPixels paperImgData.data; // 遍历拓包覆盖圆域 for (let y 0; y h; y) { const curY startY y; for (let x 0; x w; x) { const curX startX x; const dx curX - centerX; const dy curY - centerY; const distSq dx * dx dy * dy; if (distSq radius * radius) continue; // 拓包中心向边缘的球形接触衰减穹顶受力曲线 const domePressure Math.cos((Math.sqrt(distSq) / radius) * (Math.PI / 2)); // 从石板高度场中读取当前像素的微高程0~255越接近 255 说明越平整突出 const heightIndex (curY * canvas.width curX) * 4; const surfaceHeight heightData[heightIndex]; // 0~255 // 关键阈值检测只有表面高度高于凹陷门槛的凸起石面才会被拓包接触到 // 凹痕深处文字笔画低于 140拓包完全悬空留白 // 边缘斜坡依据下压力产生部分接触 const contactThreshold 140; if (surfaceHeight contactThreshold) { // 归一化接触率 const heightFactor (surfaceHeight - contactThreshold) / (255 - contactThreshold); // 加入松烟墨的干涩飞白随机因子 const speckleNoise (Math.random() * 0.4 0.6); // 综合受墨强度 const inkAlpha pressure * domePressure * heightFactor * speckleNoise * (1 - dryness * 0.3); if (inkAlpha 0.01) { const pixelIndex (y * w x) * 4; // 松烟墨深黑底色带一点点青灰矿物色温 const targetR 26; const targetG 25; const targetB 24; // Alpha 混合渗透 paperPixels[pixelIndex] Math.round(paperPixels[pixelIndex] * (1 - inkAlpha) targetR * inkAlpha); paperPixels[pixelIndex 1] Math.round(paperPixels[pixelIndex 1] * (1 - inkAlpha) targetG * inkAlpha); paperPixels[pixelIndex 2] Math.round(paperPixels[pixelIndex 2] * (1 - inkAlpha) targetB * inkAlpha); } } } } ctx.putImageData(paperImgData, startX, startY); } // 4. 鼠标与触控事件监听 function handleMove(e) { if (!isRubbing) return; const rect canvas.getBoundingClientRect(); const x (e.clientX || e.touches[0].clientX) - rect.left; const y (e.clientY || e.touches[0].clientY) - rect.top; const radius parseFloat(document.getElementById(sizeRange).value); const pressure parseFloat(document.getElementById(pressureRange).value); const dryness parseFloat(document.getElementById(dryRange).value); applyRubbingDab(x, y, radius, pressure, dryness); } canvas.addEventListener(mousedown, (e) { isRubbing true; handleMove(e); }); window.addEventListener(mousemove, handleMove); window.addEventListener(mouseup, () { isRubbing false; }); canvas.addEventListener(touchstart, (e) { isRubbing true; handleMove(e); }, { passive: false }); window.addEventListener(touchmove, handleMove, { passive: false }); window.addEventListener(touchend, () { isRubbing false; }); document.getElementById(resetPaperBtn).addEventListener(click, () { resetPaper(); }); // 初始化 initStoneHeightmap(); resetPaper(); /script /body /html算法精髓如何用像素级碰撞实现“石黑字白”的神韵在实现这个拓印微引擎时我反复推敲了如何避免一般画笔“一刷子抹黑全部”的问题。传统的绘图软件依靠笔刷蒙版但那种蒙版是静态的、与纸下物体无关的。而在真实拓印中纸下的石板骨骼决定了墨色去往何方双层像素场的正交分离我们维护了一个不可见的高度场离屏画布heightCanvas它的作用就像埋藏在宣纸底下的花岗岩石碑。文字的黑色笔画本质上是“海拔极低的地沟”。拓包在宣纸表面摩擦时只根据heightData的高低差来决定墨水能否渗透着色。拓包穹顶曲面的力学模拟棉布拓包在下压时中心受力最大边缘逐渐翘起。我们在算法中引入了domePressure Math.cos(...)的余弦半波函数让拓包画出的轨迹自然呈现从中央到外缘的渐进过渡彻底摆脱了机械硬边缘的锯齿生硬感。松烟微粒随机沉降为了表现中国传统松烟墨轻盈细腻、偶有飞白的微观特性代码在接触率计算中注入了带有高斯倾向的微小散斑噪点Speckle Noise。多次在同一个地方擦拓墨层会由淡淡的微灰层层加重为沉郁苍茫的焦墨宛如手工打拓数遍后的温润包浆。结语与秋日随想做完这个金石拓印算法后我自己在画布上拿着虚拟拓包反反复复扑打了大半个小时。当原本空无一物的宣纸在鼠标的轻拭下一点点浮现出“静水深流”四个苍劲字迹的留白轮廓时那种发自内心的宁静感让人恍若隔世。在快节奏的工程交付中我们习惯了追求极速、高效与无损的矢量图形但在某些时刻正是那些手工带来的不均质、细微的凹凸阻力与墨色的渐次沉淀才构成了数字艺术中最动人的温度。希望这个小巧的拓印引擎也能成为你秋日手账创作中一抹古朴沉静的墨香。
延伸阅读

更多相关文章

2026/10/23 8:19:15

TCP滑动窗口与快速重传:从原理到实战排查全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/23 8:19:15

二维最大子矩阵和:枚举上下界与Kadane算法的完整实战解析

二维最大子矩阵和这个经典问题,我在两组场景里都遇到过:一次是刷算法题,一次是业务中要在热力数据网格上找最大热点区域。大多数人的第一反应都是“枚举四条边界 二维前缀和加速求和”,小矩阵上还能跑,数据一旦到几百…

2026/10/23 10:30:07

YOLO26的轻量化设计:nano版本如何在边缘设备上做到实时推理?

一句话结论:YOLO26n 的“实时”不是靠堆 TOPS 堆出来的,而是把 NMS 从推理图里连根拔掉、在 CPU 上做了减法换来的。根据 EdgeFirst Perception Index 2026 年 7 月的独立全流程基准,Jetson Orin Nano 上 YOLO26n 通过 TensorRT 实现 251 FPS 实际吞吐量;根据 Ultralytics …

2026/10/23 10:30:07

Agent ——State(一图流)

核心概念概念一句话定义State当前任务的结构化状态Observation一次操作得到的反馈State Update将新信息合并进当前状态Goal系统要完成的任务Status任务当前执行阶段或状态Checkpoint保存某一时刻的 State 以便恢复三个实际应用论文研究 Agent: 记录已找到的论文、去…

2026/10/23 10:30:07

装饰器模式与开闭原则

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/23 10:30:07

【无标题】第三章

一、存储系统概述 & 半导体存储器(第一部分) 3.1 存储系统概述 存储系统的层次结构 计算机存储采用分层结构,速度越快,容量越小,价格越高;从CPU向外依次:寄存器 → Cache → 主存 → 辅存。…

2026/10/22 11:29:28

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/23 0:02:26

Verilog实现FPGA密码锁:矩阵键盘与状态机设计实战

说实话,这几年接触了不少想入门FPGA的朋友,如果你问哪个项目能一口气把Verilog的核心基本功串起来,我通常会推荐电子密码锁。这个项目看着不起眼,却把矩阵键盘扫描、按键消抖、状态机设计、动态显示刷新、外设控制这些数字逻辑里的…

2026/10/23 0:02:26

数据服务监控运维实战:从指标体系到告警治理与故障排查

周末凌晨两点,手机震了。第一反应是“数据服务挂了”。打开电脑看了下,果然,某张核心报表的数据产出时间已经超过SLA两小时,下游三个业务方连着发来问询。这种场景,做过大数据运维的人都不会陌生。数据服务的监控与运维…

2026/10/23 0:02:26

FreeRTOS任务通知实战详解:内核原理、API与避坑指南

裸机编程切到RTOS之后,任务之间的通信和同步永远是躲不开的坎。前面聊过队列、信号量、互斥锁、事件组,这次把FreeRTOS任务通知(Task Notification)单独拎出来讲透。原因很简单:任务通知是FreeRTOS里最容易用错、也最容…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑