本文要说的:
乱哄哄,你方唱罢我登场,反认他乡是故乡;甚荒唐,到头来都是为他人作嫁衣裳。
“满纸荒唐言”出自小说《红楼梦》的开篇。正所谓,“满纸荒唐言,一把辛酸泪。都云作者痴,谁解其中味?”雪芹先生于悼红轩中,披阅十载,增删五次,纂成目录,分出章回,又题曰《金陵十二钗》,并题了该诗。
吾近来读之,甚喜。自以为文章合本意,故以此为引。
古人有云,“声不能传于异地,留于异时,于是乎文字生。文字者,所以为意与声之迹。”
当然,本文志在体味“脱帽露顶王公前,挥毫落纸如云烟”的洒脱与超然,而非细究文字的“传于异地,留于异时”的实用性。
就艺术的眼光看来,文字即无言之诗,无形之舞,无图之画,无声之乐。
这不禁让我想起了2008年北京奥运会开幕式的场景,国人向全世界展示中华文字艺术的魅力,好不壮观。
src="//player.bilibili.com/player.html?aid=16342941&cid=26662221&page=1" border="0" scrolling="no" allowfullscreen="true">以下,是我用 Processing 制作的一个标题。代码在线,点击这里。
于程序而言,其显示步骤如下:
step 1:Create the font;
至于创建字体,吾有三计——
计之一:利用创建字体工具手动创建字体。
在 Processing 工作窗口的“工具”(Tools)菜单中选择“创建字体…”(Create Font)命令,在弹出的窗口中选择字体和字号,点击“确定”(OK)完成创建。生成的 vlw 格式字体被默认放置于 data 文件夹中(若没有,PDE 会自动创建 data 文件夹)中。(“vlw”是 Processing 特有的字体格式)
计之二:使用用户系统中的字体。
如何查看用户系统中有字体呢?调用 PFont.list() 函数:
String[] list=PFont.list();
printArray(list); // 在控制台输出字体名称
换而言之,我们可以在自己的电脑系统里安装好所需的字体,而后在 Processing 直接加载调用。
而美中不足的是,这种方式会导致程序的加载速度变缓,毕竟我们增大了加载难度。同时这也并非所有字体都可以使用,有些可能只适用于一个操作系统。
计之三:使用网上下载的字体。
此计可谓是结合了前二计的综合法。一为将下载好字体放置于 data 文件夹中(计一),一为在用户系统上安装好下载的字体(计二)。
step 2:Declare an object of type PFont;
声明变量:
// PFont + 变量名
PFont font; // 变量名为 font
step 3:Load the font;
这里理应有两种做法:
法之一:当需要将.vlw
格式的字体加载到PFont
对象时,我们调用 loadFont() 方法。
举个栗子:
PFont font;
// 字体必须位于“data”文件夹中才能成功加载
font = loadFont("LetterGothicStd-32.vlw");
法之二:利用 createFont() 方法,将“data”文件夹中的.ttf
或.otf
文件或计算机上其他位置安装好的字体,动态地转换为 Processing 使用的格式。
举个栗子:
PFont myFont;
void setup() {
size(200, 200);
// 取消以下两行注释,以查看可用字体
// String[] fontList = PFont.list();
// printArray(fontList);
myFont = createFont("Georgia", 32);
}
step 4:Specify the font;
此处应有 textFont()
。
textFont()
:设置将使用text()
绘制的当前字体。其可输入一个或两个参数,字体变量和字体大小。若输入不包含字体大小,则字体将显示为字体的原始加载大小。值得注意的是,如果我们指定的字体大小与加载的字体大小不同,则可能会显示像素化或质量差的文本。
举个栗子:
textFont(f,36);
step 5:Specify a color;
上色工序,你可能需要:
fill(rgb)
fill(rgb, alpha)
fill(gray)
fill(gray, alpha)
fill(v1, v2, v3)
fill(v1, v2, v3, alpha)
step 6:Display text。
显示文本咯!详细请查阅text()
。
举个栗子:
text("word", 10, 90);
厚地高天,堪叹古今情不尽;痴男怨女,可怜风月债难偿。
欲取文台幻境,且需步步为营。
在下文中,愚笨的笔者举了一个小小的栗子,以期读者能够领悟如何由浅入深、循序渐进地增长自身功力,成为绝世大侠。
小栗子的结构:
【明】袁宏道言:一林过雨芦花白,半壁疏云栗子黄。
无名氏言:毛刺随身雾里狂,针球栗子郁山乡。
京津传言:堆盘栗子炒深黄,客到长谈索酒尝。寒火三更灯半灺,门前高喊“灌香糖”。
代码在线,点击这里。
【元】王哲曰:栗子甘甜美芋头。
……
关于创意字体设计,这里有两个可谓实用的库(NextText 已在 Processing 3 中弃用了,此处且不再提及):
Ⅰ:Geomerative,By Ricard Marxer
Introduction:
Geomerative 扩展了 2D 几何操作,以促进生成几何。其包括了 TrueType 字体和 SVG 解释器。
Ⅱ:Fontastic,By Andreas Koller
Introduction:
Fontastic 用于创建 TTF 和 WOFF 格式的字体文件。
所以,你该用谁呢? 以笔者的理解,前者如雕刻刀,后者若凿石斧。Geomerative 追求创意,Fontastic 旨在简易。
当然,我也收集到了一些使用 Geomerative 创建的字体:
可叹的是,这里并不谈论《石头记》里面那些薄命女子,而是以87版《红楼梦》十二钗的美艳剧照为本,编写出一个以文字绘制肖像的创意小程序——“Twelve Beauties of Jinling”。
src="//player.bilibili.com/player.html?aid=35479486&cid=62200318&page=1" border="0" scrolling="no" allowfullscreen="true">该程序我已在 OpenProcessing 上传,获取之,然而其运行效果尚不理想,但倘若读者利用 Processing 运行,它确实没有问题。这可能是网站的 Bug,目前我也没找到较好的解决办法。
因此,我在 CSDN 上补发了一份完整的源代码+资源,点击这里。
以下,为其完整的源代码:
/**
* Twelve_Beauties_of_Jinling
*
* By Hewes 18/11/6
* https://blog.csdn.net/Hewes
* Controls:
* MousePress - 切换美人图
*
**/
//String[] imgNames = {"黛玉.jpg", "宝钗.jpg", "元春.jpg",
// "探春.jpg", "湘云.jpg", "妙玉.jpg", "熙凤.jpg", "惜春.jpg",
// "迎春.jpg", "巧姐.jpg", "李纨.jpg", "可卿.jpg"};
String[] imgNames = {"黛玉:欠泪的泪已尽.jpg", "宝钗:富贵的金银散尽.jpg",
"元春:欲知命短问前生.jpg", "探春:分离聚合皆前定.jpg", "湘云:为官的家业凋零.jpg",
"妙玉:无情的分明报应.jpg", "熙凤:痴迷的枉送了性命.jpg", "惜春:看破的遁入空门.jpg",
"迎春:欠命的命已还.jpg", "巧姐:有恩的死里逃生.jpg", "李纨:老来富贵也真侥幸.jpg",
"可卿:冤冤相报岂非轻.jpg"};
PImage img, bg;
int imgIndex = 0, index=0;
String[] lines;
PFont font1, font2;
char[] chars;
String displayText;
float cx, cy, r, degree;
float minWeight = 2, maxWeight = 6, currWeight;
float spacing = maxWeight; // 字符间隔
float goldenRatio = (sqrt(5) + 1 ) / 2;
int iter = 0;
void setup() {
size(900, 900);
frameRate(30);
font1 = createFont("楷体", 10);
font2 = createFont("隶书", 80);
bg=loadImage("bg.jpg");
bg.resize(width, height);
textAlign(CENTER, CENTER);
lines=loadStrings("十二钗判词.txt"); // 读取并创建其各行的 String 数组
nextImage(); // 下一张肖像
}
void draw() {
pushMatrix();
translate(width/2-120, height/2);
if (cx>= 0 && 1.1*cx<= img.width && cy >= 0 && 1.1*cy<= img.height ) {
for (int i = 30; i > 0; --i) {
index = index % displayText.length(); // 遍历文本
degree = (iter * goldenRatio) * 360; // 字符的角度
r = sqrt(iter++) * spacing;
// 计算字符的位置
calcCharPos(img.width/2, img.height/2, r, (degree % 360));
color pix = img.get((int)cx, (int)cy);
currWeight = map(brightness(pix), 255, 0, minWeight, maxWeight);
textSize(currWeight*2);
fill(pix);
text(chars[index], 1.3*(cx-img.width/2), 1.3*(cy-img.height/2));
index++;
}
}
popMatrix();
}
// 计算文字的位置
void calcCharPos(float x, float y, float radius, float angle) {
cx = x + cos(radians(angle))*(radius/2);
cy = y + sin(radians(angle))*(radius/2);
}
// 下一张肖像
void nextImage() {
background(bg);
textFont(font1);
img = loadImage(imgNames[imgIndex]);
img.resize(520, 520);
img.loadPixels(); // 将当前显示窗口的快照加载到 pixels[] 数组中
displayText=loadText(lines[imgIndex]); // 文本更新加载
// 显示美人名
textFont(font2);
fill(0, 80);
text(chars[0], 750, 350);
text(chars[1], 750, 550);
textFont(font1);
// 循环图片索引
imgIndex += 1;
if (imgIndex >=imgNames.length) {
imgIndex = 0;
}
index = 0;
iter = 0;
cx = 0;
cy = 0;
}
// 加载字符,需输入其索引
String loadText(String inText) {
String allText=join(lines, " ");
chars=new char[inText.length()];
int i=0;
int charPos = 0;
if (imgIndex==0) {
charPos=1;
}
char currChar; // 当前字符
// 遍历片段内字符,保存到 displayText 中
while (charPos < inText.length()) {
currChar = inText.charAt(charPos);
charPos++;
chars[i]=currChar;
i++;
}
//println(new String(chars));
return new String(chars); // 返回将显示的判词
}
void mousePressed() {
nextImage(); // 下一张肖像
}
【欢迎关注】
Hewes 的知乎专栏:https://zhuanlan.zhihu.com/c_123529691
Hewes 的 CSDN 博客:https://blog.csdn.net/Hewes