JS打造一款你自己的专用字体:使用p5.js与JavaScript实现

news2024/11/26 11:27:09
前言

在最近的生成艺术项目中遇到一个小问题:如何在作品中优雅地添加文本元素,同时避免使用网络字体,要么侵权要么花钱~~给项目增加不必要的负担,我决定不走寻常路,自己动手,丰衣足食,用JS打造一款自己的专用字体!

问题分析

首先,虽然网上字体库很多而且也比较方便,但除了某些字体版权限制之外其显示效果往往受限于用户的浏览器和设置,导致在不同环境下出现视觉差异,这对于追求视觉一致性的艺术作品来说是不可接受的。其次,直接嵌入字体文件虽然能解决一致性问题,但会增加页面加载时间,影响用户体验。更关键的是,我渴望在这个项目中实现每一个视觉元素的自主创作,让作品真正打上“原创”的标签。

解决方案:手写字母表

于是,我萌生了一个大胆的想法:使用p5.js这个强大的图形库结合JavaScript的灵活编程能力,自行绘制一套路径式字母表。这种方法的好处在于,我可以完全控制每个字母的形状、线条粗细、颜色等属性,实现高度的自定义和个性化。

实现步骤
1. 实现原理

在深入探索字体设计的奥秘时,首先系统地查阅了字体结构中那些关键点的术语与位置,它们如同构建字体的基石,为我后续的创作提供了坚实的理论基础。然而,在实际动中生成手绘制字母时,并没有遵循传统路径,而是选择了一条更为直观且富有创意的道路——以中心点(mid_x, mid_y)观察字母间的间距调整(kerning)时,意识到如果从字母底部左侧这一自然起点出发进行定义,将能更有效地解决当前存在的一些细微不一致问题。于是,在Letter类的设计中,重新布局了包括x高度、顶端高度在内的多个关键位置参数,这些参数不仅与字体大小紧密相关,更以中心点为基准,构建了一个灵活而精确的坐标体系。

为了进一步提升字母的精细度和美观度,还引入了一系列与高度成比例的小距离调整值。这些调整值,如this.adj_1this.adj_4等,通过乘以字体全高(h_full)的不同比例系数得出,为字母的每一个细节点提供了微调的可能性。例如,this.adj_1是字体全高的5%,而this.adj_4则是其20%,这样的设计使得在保持整体协调的同时,能够轻松地对字母的局部进行精细雕琢。如下是设定的一些与高度相关的小距离,用来微调各个点的位置。具体如下:

this.adj_1 = this.h_full * 0.05;
this.adj_15 = this.h_full * 0.075;
this.adj_2 = this.h_full * 0.1;
this.adj_25 = this.h_full * 0.125;
this.adj_3 = this.h_full * 0.15;
this.adj_35 = this.h_full * 0.175;
this.adj_4 = this.h_full * 0.2;
2. 定义字母

在纸上随手写下每个字母,捕捉其自然的流动与形态。这些草图是我对字母最直观的反映。再利用p5.js,根据草图勾勒出字母的初步路径。这些路径由上述关键点组成,它们标记了字母轮廓的转折点。路径的初步定义往往不够完美,需要反复调整。通过移动关键点的位置、修改路径的曲率,甚至添加新的点来细化字母的形状,以确保字母既符合审美又易于识别。字母的形状达到满意后,我会进行最终的测试,确保它在不同尺寸和背景下都能保持良好的显示效果,

create_a(){   
  this.paths = [
    [ // stem
      {x: this.x_left+this.adj_2, y: this.y_x + this.adj_4},
      {x: this.x_left+this.adj_3, y: this.y_x + this.adj_1},
      {x: this.x_mid+this.adj_2, y: this.y_x},
      {x: this.x_right, y: this.y_x+this.adj_2},
      {x: this.x_right, y: this.y_base-this.adj_4},
      {x: this.x_right+this.adj_1, y: this.y_base},
    ],
    [ // round
      {x: this.x_right-this.adj_1, y: this.y_mid-this.adj_15},
      {x: this.x_mid-this.adj_1, y: this.y_mid-this.adj_1},
      {x: this.x_left, y: this.y_mid+this.adj_35},          
      {x: this.x_mid, y: this.y_base},
      {x: this.x_mid+this.adj_2, y: this.y_base-this.adj_1},
      {x: this.x_right+this.adj_2, y: this.y_base - this.adj_4},
    ]
  ];
}

3. 路径弯曲处理

使用 Chaikin 曲线算法来平滑路径

Chaikin 平滑曲线算法是一种递归方法,用于对多边形(或路径)进行平滑处理,使得其形状更加自然和流畅。这种方法特别适用于计算机图形学和字体设计等领域,因为它能够生成视觉上更加吸引人的曲线。

这个图可以直观的看出来它是如何工作的

接下来我将详细解释如何使用 Chaikin 算法来平滑一条简单的路径。假设我们有一个由点集 P = {p0, p1, ..., pn} 组成的路径,其中 n 是点的总数。

Chaikin 平滑算法步骤
  1. 初始化:首先,我们有一个由 n+1 个点(包括起始点和终止点)组成的路径 P

  2. 递归步骤:在每一轮递归中,我们创建一个新的点集 P',它包含以下点:

    • 开始点P' 的第一个点是 P 的第一个点 p0
    • 中间点:对于 P 中的每一对相邻点 (pi, pi+1)(其中 i 从 0 到 n-2),我们在距离 pi 25% 和距离 pi+1 75% 的位置插入两个新点。这两个新点的位置可以用向量计算得出,例如,第一个新点 qi 可以用 (3/4) * pi + (1/4) * (pi+1) 计算,第二个新点 qi+1 用 (1/4) * pi + (3/4) * (pi+1) 计算。
    • 结束点P' 的最后一个点是 P 的最后一个点 pn
  3. 递归终止:重复上述步骤,直到路径 P' 足够平滑或达到预设的递归深度。

我们按照以下步骤创建一条新路径:

  • 复制第一个点(末端保持原位)

  • 对于最后一点之前的其余点:

    • 在距上一点 25% 的位置添加一个点

    • 在距离下一个点 25% 的位置添加一个点

  • 复制最后一点。

一轮之后,我们得到了这个。新路径用红色标记。

然后我们对得到的路径应用相同的步骤。得到下面第 2 轮和第 3 轮之后的结果。

            

最终结果如下

   经过一轮处理后,路径变得更加平滑。对于小尺寸字体,3 到 4 轮的 Chaikin 算法迭代就足够了。如果字体尺寸较大,可以增加迭代次数,确保边缘不过于尖锐。

代码优化

通过将路径定义与中点、字母顶点等关键位置关联起来,可以更清晰地理解如何绘制字母路径,例如,与其说“b 的主干从中点上方 14.1 像素处开始,到中点下方 7.4 像素处结束”,不如简单地理解为“b 的主干从字母顶点延伸到字母底部”,这样更容易理解。

然而,最终生成的代码非常冗长,不够简洁,就像上面的 create_a 函数那样,看上去就乱

为了解决这个问题,编写了一个函数用来遍历每个字母并生成新的代码将所有内容转换成简单的数值格式,如下:

let string = "";
    for(let l of this.letters){
      string += "create_" + l.letter + "(){\n";
      string += "  this.ip = [\n";
      for(let path of l.ip){
        string += "  [";
        for(let p of path){
          string += "{x: " + nf(p.x, 0, 1) + ", y: " + nf(p.y, 0, 1)  + "}";
          if (path.indexOf(p) != path.length-1) string += ", ";
        }
        string += "]";
        if (l.ip.indexOf(path) != l.ip.length-1) string += ",\n";
        else string += "\n";
      }
      string += "  ]\n";
      string += "}\n";
    }
    console.log(string)

这样一来,生成字母啊的函数大大减少了:

create_a(){
  this.ip = [
    [{x: -2.8, y: -3.4}, {x: -1.7, y: -6.8}, {x: 2.3, y: -8.0}, {x: 5.4, y: -5.7}, {x: 5.4, y: 2.9}, {x: 6.6, y: 7.4}],
    [{x: 4.3, y: -1.7}, {x: -0.9, y: -1.1}, {x: -5.1, y: 3.9}, {x: -2.1, y: 7.4}, {x: 2.3, y: 6.3}, {x: 5.4, y: 2.9}]
  ]
}
重塑字母路径宽度

这是目前运行之后生成的所有字母

看起来挺自然,不过我感觉线条粗细过于细长且单调,毕竟手写出来的字体,不可能有这么规范,为了更加贴近真正的手写字母实现不同宽度的路径,我使用了另一种算法函数将路径变成了二维形状,还是最初这张图,稍微讲解一下是如何实现路径宽度变化的

点成线,线成面,要想实现一个二维路径,肯定不能只有单一的点组合,为了创建形状路径,我们沿着这条路径行进,在每个点处:

  • 找到从该点到下一个点的角度(对于最后一个点,找到到前一个点的角度并将其翻转 180°)

  • 使用 Perlin 噪声,选择该点的路径宽度。

这里我在路径的每个点都画了一条线,以展示这些角度和宽度。请注意每条线的长度略有不同,并且它们的角度是跟随路径曲线不断变化的

这样就可以绘制路径的内外边缘,形成一个具有宽度变化的路径

很容易看出如何在内部路径周围绘制一条路径来创建宽度变化的路径。在线条的末端,绘制了一个围绕 180° 的小点环,以创建一个完整的圆形结尾。

不过这个算法也并不完美,当笔触宽度较宽时,这些尴尬由于颜色相同也压根看不出什么问题,所以这点小BUG貌似并不重要。

最最最最后,为了让结果看起来更加自然,我使用 Perlin 噪声稍微抖动所有点,这为字母增加了另一层自然感,让他们更加随机多样

整个字母表的样子如下:

经过这些步骤,最终生成了一套完整的手写字母,尽管整个过程非常耗时,但最终的效果其实和手写效果基本一致,字母线条的厚度自然变化,使得视觉效果更为生动,当然你也可以根据字母的位置来改变线宽。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2092559.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++11 新特性基础

前言 C11是继,C98/03之后的最大的一次更新,这次更新虽然花了很长的时间,但是真的推出了很多的干货!本期内容开始我们将介绍C11常用的操作! 目录 前言 一、C11介绍 二、统一的列表初始化 1、{}初始化 2、std::in…

【机器学习】循环神经网络(RNN)介绍

🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 ​💫个人格言: "如无必要,勿增实体" 文章目录 循环神经网络(RNN)介绍什么是RNN?RNN的基本原理递归神经网络单元前向传播反向传…

Ubuntu下安装和配置MQTT服务器Mosquitto

MQTT(Message Queuing Telemetry Transport)是一种轻量级的通信协议,设计用于物联网设备之间的低带宽、不稳定网络环境下的高效通信。MQTT允许设备通过发布(publish)和订阅(subscribe)模式进行消…

清华2024内地录取3500人,其中900多人是走这个政策进来的... ...

2024年, 清华大学共录取本科新生3800余人,其中内地学生3500余人,覆盖全国31个省份1000 多所生源中学;港澳台学生60余人,国际学生约230人。 大李露个脸 清华大学2024年新生数据 普通批提前批共录取1549人,占比44% 强基计…

火龙果检测-目标检测数据集(包括VOC格式、YOLO格式)

火龙果检测-目标检测数据集(包括VOC格式、YOLO格式) 数据集: 链接:https://pan.baidu.com/s/1NdRBsHnYCK9xZd7bzQoN5w?pwd779l 提取码:779l 数据集信息介绍: 共有 1106 张图像和一一对应的标注文件 标注…

macos OneNote 2016 for Mac 官方pkg下载地址 - macos 10.15 Catalion 可用Onenote版本官方下载地址

macos 10.15 Catalion 版本的系统已经无法正常从应用商店下载到可用的Onenote 应用,原因是版本不受支持, 而且onenote官方链接的应用商店地址https://apps.apple.com/us/app/microsoft-onenote/id784801555?mt12在中国地区也无法访问, 所以中国地区用户如果想使用onenote应用…

【云原生系列之SkyWalking的部署】

1、分布式链路追踪 1.1概念 在较大的web集群和微服务环境中,客户端的一次请求需要经过不同的模块,多个不同中间件,多个不同机器一起相互协作才能处理完成客户端的请求,而在这一系列的请求过程之中,处理流程可能是串行执行,也可能…

华为云征文|基于Flexus云服务器X实例之安装长亭雷池waf教程

🔴大家好,我是雄雄,欢迎关注微信公众号:雄雄的小课堂 先看这里 写在前面何为长亭雷池waf安装社区版雷池雷池环境要求查看华为云Flexus云服务器X实例的配置一条命令安装雷池waf检查查看是否安装成功 雷池使用登录雷池配置站点 写在…

51单片机.之ADC数字模拟转换

1、数字转模拟电路&#xff0c;输出波形&#xff0c;示波器采集来显示波形 单片机通过i2c给&#xff0c;模数转换器&#xff0c;写入数字信号&#xff0c;定时器1s扫描按键的切换 1、key.c 切换波形 #include <reg52.h>sbit KEY_IN_1 P2^4; sbit KEY_IN_2 P2^5; …

五、Selenium操作指南(一)

文章目录 一、基本用法&#xff08;一&#xff09;初始化浏览器对象&#xff08;二&#xff09;访问页面&#xff08;三&#xff09;设置浏览器大小&#xff08;四&#xff09;刷新页面&#xff08;五&#xff09;前进后退 二、获取页面基础属性三、定位页面元素&#xff08;一…

【QNX+Android虚拟化方案】112 - 获取 88Q5152 Switch Port1、Port2 端口的主从模式 / 传输速率 / 链路状态

【QNX+Android虚拟化方案】112 - 获取 88Q5152 Switch Port1、Port2 端口的主从模式 / 传输速率 / 链路状态 1. 读取 P1、P2 端口 主从模式 / 传输速率2. 读取 P1、P2 端口 Link Status3. 读取 P1、P2 端口 Duplex 全双工/半双工模式4. 读取 P1、P2 链路信号SQI质量5. 完整代码…

基于单片机的肺活量检测仪设计

本设计主要对其中的一种测量方法和原理进行介绍与运用&#xff0c;设计了一款基于STC12C5A60S2单片机的肺活量检测仪&#xff0c;包括供电模块、气流检测模块、按键模块、显示模块、语音输出模块和蓝牙模块&#xff0c;实现对肺活量的数值检测&#xff0c;并对数据进行语音播报…

linux固定ip

背景 VMware&#xff0c;centos7 查询 网关 linux指执行 ip addr 命令 拿到自动分配的ip : 192.168.150.102 [rootlocalhost ~]# cd /etc/sysconfig/network-scripts/ 执行: cd /etc/sysconfig/network-scripts/ 进入到network-scripts文件中 执行: vi ifcfg-ens33 编辑ifc…

PPT到PDF转换器:一个功能强大的Python GUI应用(unzip,convert,replace,merge)

在当今的数字时代,文档格式转换已成为一项常见需求。特别是将PowerPoint演示文稿转换为PDF格式,这不仅可以确保文档的一致性,还能方便分享和打印。今天,我们将深入探讨一个使用Python开发的强大GUI应用程序,它不仅可以将PPT转换为PDF,还具备文本替换、PDF处理和文件合并等多项功…

TDesign 微信小程序组件库配置

文章目录 1.安装 npm 包2. 构建 npm3. 构建完成后即可使用 npm 包。4.修改 app.json5.修改 tsconfig.json6.使用组件 1.安装 npm 包 在小程序 package.json 所在的目录中执行命令安装 npm 包&#xff1a; npm install结果报错 PS C:\WeChatProjects\miniprogram-1> npm i…

七、库存管理——调拨、预留业务

1、库存管理业务总览 2、转储过账和库存转移 3、转储过账 3.1 库存状态到库存状态&#xff08;类型较多&#xff09; 3.1.1 从质检库存转移到非限制使用库存 转储模式&#xff1a;事务类型A08&#xff08;转移过账&#xff09;凭证类型R10&#xff08;其他&#xff09;移动类…

Having trouble using OpenAI API

题意&#xff1a;"使用OpenAI API遇到困难" 问题背景&#xff1a; I am having trouble with this code. I want to implement AI using OpenAI API in my React.js project but I cannot seem to get what the issue is. I ask it a question in the search bar in…

java 教程-我的第一个JAVA程序

Java视频教程 我的第一个JAVA程序 以下我们通过一个简单的实例来展示Java编程&#xff0c;本实例输出"编程字典&#xff0c;Java教程&#xff01;"&#xff0c;这也是所有语言入门的第一个实例程序&#xff1a; packagecodingdict.com; publicclassHelloWorld{ publi…

REGTR: End-to-end Point Cloud Correspondences with Transformers 论文解读

目录 一、导言 二、先导知识 1、3DRegNet 2、Kabsch-Umeyama算法 3、InfoNCE损失函数 三、相关工作 1、基于对应关系的配准 2、全局配准工作 3、过滤问题 4、Transformer 四、REGTR网络 1、降采样和特征提取 2、Transformer 交叉编码器 Transformer为什么要用FF…

MySQL高阶练习题2-没有广告的剧集

目录 题目 准备数据 分析数据 实现代码 总结 题目 找出所有没有广告出现过的剧集。 返回结果 无顺序要求 。 准备数据 create database db; use db;Create table If Not Exists Playback(session_id int,customer_id int,start_time int,end_time int); Create table I…