有了画布,就可以涂鸦。画布是代码,涂的也就是代码,确切地说,是 MetaPost 代码,然而更确切地说,是 ConTeXt 里的 MetaPost 代码。ConTeXt 里的 MetaPost,就是 MetaFun。
原点
最简单的鸦,是原点:
\environment card-env
\startuseMPgraphic{绘图代码}
draw (0, 0) withpen pencircle scaled 4pt withcolor darkgreen;
\stopuseMPgraphic
\starttext
\startstandardmakeup[align=middle]
\strut\canvas{画布}\strut
\stopstandardmakeup
\stoptext
排版结果为
上述示例代码里的
draw (0, 0) withpen pencircle scaled 4pt withcolor darkgreen;
可解读为:画(draw)一个点,它的坐标为 (0, 0)
,用 4pt 粗的圆头笔(pencircle)来画,颜色为暗绿色(darkgeen)。这是非常典型的英文句式,只是省略了主语「我」。
上述 MetaPost 语句,可拆为两句:
pickup pencircle scaled 4pt;
draw (0, 0) withcolor darkgreen;
亦即,抓起一支画笔,画一个点。
更多的点
抓起的画笔可以一直用下去,直到抓起另一支画笔。
例如,
pickup pencircle scaled 4pt;
draw (0, 0) withcolor darkgreen;
draw (5mm, 0) withcolor darkgreen;
draw (0, 5mm) withcolor darkgreen;
draw (-5mm, 0) withcolor darkgreen;
draw (0, -5mm) withcolor darkgreen;
鸦就变为
在 MetaPost 语言里,点坐标就是序对(pair),而且可以给点坐标取名字:
pair O, a, b, c, d;
O := (0, 0);
a := (5mm, 0); b := (0, 5mm); c := (-5mm, 0); d := (0, -5mm);
pickup pencircle scaled 4pt;
draw O withcolor darkgreen;
draw a withcolor darkgreen;
draw b withcolor darkgreen;
draw c withcolor darkgreen;
draw d withcolor darkgreen;
在 MetaPost 语言里,有名字的东西,就是变量,例如笔是变量,颜色是变量,点是变量。:=
表示给变量赋值——赋以意义。
迭代
上一节最后的代码有些繁冗,可使用 MetaPost 的迭代语法予以简化:
pair O, a, b, c, d;
O := (0, 0);
a := (5mm, 0); b := (0, 5mm); c := (-5mm, 0); d := (0, -5mm);
pickup pencircle scaled 4pt;
for i = O, a, b, c, d:
draw i withcolor darkgreen;
endfor;
上述代码里,5mm
被重复使用多次,倘若需要将其修改为 4mm
,不得不对代码做较多改动,因此应当为 5mm
也取个名字:
numeric u; u := 5mm;
pair O, a, b, c, d;
O := (0, 0);
a := (u, 0); b := (0, u); c := (-u, 0); d := (0, -u);
pickup pencircle scaled 4pt;
for i = O, a, b, c, d:
draw i withcolor darkgreen;
endfor;
路径
欧几里得说,两点之间,线段最短。MetaPost 也遵守这样的几何学。下面的代码,除了可以画 5 个点之外,还可以画穿过它们的 2 条线段:
numeric u; u := 5mm;
pair O, a, b, c, d;
O := (0, 0);
a := (u, 0); b := (0, u); c := (-u, 0); d := (0, -u);
pickup pencircle scaled 4pt;
for i = O, a, b, c, d:
draw i withcolor darkgreen;
endfor;
path ac, bd;
ac := a -- c; bd := b -- d;
pickup pencircle scaled 2pt;
for i = ac, bd:
draw i withcolor darkgreen;
endfor;
结果为
组合与变换
表面上,我现在的涂鸦作品是一个十字图形,但是在代码里,它还是一团散碎——一些点,一些线条……难以像鸦那样在画布里飞翔。MetaPost 语言深深意识到了这个问题的严重性,为了搞好涂鸦工作,就像提供 pair
和 path
这样的变量类型之外,也提供了 picture
类型。
现在,试着将我的作品合成为 picture
:
numeric u;
u := 5mm;
pair O, a, b, c, d;
O := (0, 0);
a := (u, 0); b := (0, u); c := (-u, 0); d := (0, -u);
pickup pencircle scaled 4pt;
for i = O, a, b, c, d:
draw i withcolor darkgreen;
endfor;
path ac, bd;
ac := a -- c; bd := b -- d;
pickup pencircle scaled 1pt;
for i = ac, bd:
draw i withcolor darkgreen;
endfor;
picture darkgreencrow;
darkgreencrow := currentpicture;
上述代码的重点在于最后两行。我创建了一只名字叫暗绿色的乌鸦(darkgreen crow),然后将当前涂鸦的结果 currentpicture
赋予了它。
这只乌鸦能飞吗?
能!
请看,
draw darkgreencrow shifted (2.75u, 0);
draw darkgreencrow shifted (-2.75u, 0);
draw darkgreencrow shifted (0, 2.75u);
draw darkgreencrow shifted (0, -2.75u);
结果是一个阵势:
也可以让它飞起来的样子别那么严肃:
draw darkgreencrow rotated 15 shifted (2.75u, 0);
draw darkgreencrow rotated 30 shifted (-2.75u, 0);
draw darkgreencrow rotated 45 shifted (0, 2.75u);
draw darkgreencrow rotated 60 shifted (0, -2.75u);
注意,乌鸦从起飞位置,先旋转(rotated)再移动(shifted),与先移动再旋转,是两回事,后者是下面这样:
draw darkgreencrow shifted (2.75u, 0) rotated 15;
draw darkgreencrow shifted (-2.75u, 0) rotated 30;
draw darkgreencrow shifted (0, 2.75u) rotated 45;
draw darkgreencrow shifted (0, -2.75u) rotated 60;
之所以如此,是因为旋转需要一个中心,这个中心是原点。移动前绕原点旋转,跟移动后再绕原点旋转,自然是有着很大不同。
image 宏
那个作为 currentpicture
的鸦,能不能也像后来这 4 只呢,表现为 picture
。大家都是鸦,理应有一致性的本质。答案是,能,但是需要用 MetaPost 的 image
宏。下面重新画一次原始鸦:
picture darkgreencrow;
numeric u;
u := 5mm;
pair O, a, b, c, d;
O := (0, 0);
a := (u, 0); b := (0, u); c := (-u, 0); d := (0, -u);
darkgreencrow := image (
pickup pencircle scaled 4pt;
for i = O, a, b, c, d:
draw i withcolor darkgreen;
endfor;
);
path ac, bd;
ac := a -- c; bd := b -- d;
addto darkgreencrow also image (
pickup pencircle scaled 1pt;
for i = ac, bd:
draw i withcolor darkgreen;
endfor;
);
draw darkgreencrow;
上述代码的最后一句,真正地画出了原始鸦。可通过在实践里意会的办法理解 image(...)
以及addto ... also ...
语法。
有了上述重新定义的 darkgreencrow
之后,可以再用 rotated
和 shifted
让它飞:
draw darkgreencrow rotated 15 shifted (2.75u, 0);
draw darkgreencrow rotated 30 shifted (-2.75u, 0);
draw darkgreencrow rotated 45 shifted (0, 2.75u);
draw darkgreencrow rotated 60 shifted (0, -2.75u);
上述 4 句 draw darkgreencrow ...
,有很多重复的代码,可再用 for
简化一番呢?
试试看,
numeric n; n := 0;
for i = (2.75u, 0), (-2.75u, 0), (0, 2.75u), (0, -2.75u):
n := n + 15;
draw darkgreencrow rotated n shifted i;
endfor;
可行!
能否将
draw darkgreencrow;
也合并到上述 for
语句里呢?没有什么不可以的,
numeric n; n := 0;
for i = (0, 0), (2.75u, 0), (-2.75u, 0), (0, 2.75u), (0, -2.75u):
draw darkgreencrow rotated n shifted i;
n := n + 15;
endfor;
数组和另一种 for
MetaPost 允许将它支持的任何类型的数据存放在数组里……是不是很像 Lua 表?
可将旋转角度值存放在数组里,
numeric angles[];
for i = 0 upto 4:
angles[i] := 15 * i;
endfor;
上述代码里有另一种 for
……意会吧!
也可以将点存到数组里,
pair points[];
points[0] := (0, 0);
points[1] := (2.75u, 0);
points[2] := (-2.75u, 0);
points[3] := (0, 2.75u);
points[4] := (0, -2.75u);
有了上述数组,与上一节最后的代码等价的代码如下:
for i = 0 upto 4:
draw darkgreencrow rotated angles[i] shifted points[i];
endfor;
扰动
ConTeXt 里的 MetaPost(确切地说,是 MetaFun)有 randomized
宏,它可以让涂鸦的结果不那么精确。不精确的结果,似乎能有机会跻身艺术殿堂。
下面,重新给出一份完整的涂鸦作品,亦即一份 ConTeXt 源文件 foo.tex,其内容如下:
\environment card-env
\startuniqueMPgraphic{绘图代码}
picture darkgreencrow;
numeric u;
u := 5mm;
pair a, b, c, d;
a := (u, 0); b := (0, u); c := (-u, 0); d := (0, -u);
path ac, bd;
ac := a -- c; bd := b -- d;
darkgreencrow := image (
pickup pencircle scaled 4pt;
draw ac withcolor darkgreen;
draw bd withcolor darkgreen;
);
draw darkgreencrow;
\stopuniqueMPgraphic
\starttext
\startstandardmakeup[align=middle]
\strut\canvas{画布}\strut
\stopstandardmakeup
\stoptext
对应的排版结果为
现在,将上述代码里负责涂鸦的代码的最后一句
draw darkgreencrow;
修改为
draw drakgreencrow randomized .5u;
其中,randomized .25u
的作用是,让 darkgreencrow
这个 picture
里的内容在 [-0.25 * 5mm, 0.25 * 5mm] 这个区间内随机晃动,结果如下图所示,
在类似于如上图所示的随机扰动的图形之后,倘若执行
$ context --purgeall
清除 ConTeXt 的 TeX 编译器为 foo.tex 生成的中间文件,然后再次执行
$ context foo
可以得到新的随机图形。之所以如此,是因为 ConTeXt 的 TeX 编译器在处理含有随机扰动图形的源文件时,会在扩展名为 .tuc 的文件里存放一个随机数的种子。倘若删除 .tuc 文件,ConTeXt 的 TeX 编译器会再次生成新的 .tuc 文件,里面会包含新的随机数种子。基于不同的随机数种子,可以生成不同的图形。
如果让这只随机的原始鸦的涂画语句换成以下代码,可能会让画面出现些许艺术气息:
numeric angles[];
for i = 0 upto 4:
angles[i] := 15 * i;
endfor;
pair points[];
points[0] := (0, 0);
points[1] := (2.75u, 0);
points[2] := (-2.75u, 0);
points[3] := (0, 2.75u);
points[4] := (0, -2.75u);
for i = 0 upto 4:
draw darkgreencrow randomized .25u rotated angles[i] shifted points[i];
endfor;
这五只鸦凌空飞舞的画作如下:
倘若再大胆一些,可以创造群鸦齐飞的盛况:
对应的绘图代码如下:
for i = 0 upto 50:
draw darkgreencrow randomized .5u
rotated (0 randomized 60)
shifted ((0, 0) randomized (OverlayWidth, OverlayHeight));
endfor;
其中,(0 randomized 60)
可为 rotated
生成一个位于 [-30, 30] 这个区间的随机角度值。为什么要用括号将随机值括起来呢?因为不将 randomized
放在括号内,TeX 编译器会认为 randomized
修饰的是 draw
要绘制的对象。
上述代码里的
((0, 0) randomized (OverlayWidth, OverlayHeight))
可为 shifted
生成一个随机的点,这个点的横坐标的取值区间是 [-0.5 * OverlayWidth, 0.5 * OverlayWidth],纵坐标的取值区间是 [-0.5 * OverlayHeight, 0.5 * OverlayHeight]。
OverlayWidth 和 OverlayHeight 是什么?它们是画布的长度和宽度值啊,还记得画布的定义么?
\defineoverlay
[foo]
[\uniqueMPgraphic{绘图代码}]
\defineframed
[canvas]
[width=\textwidth,
height=\textheight,
framecolor=darkred,
rulethickness=.25em,
location=lohi,
empty=yes,
background=foo]
这个叫 foo 的 overlay,它的宽度和长度在 ConTeXt 里对应的值分别是 \overlaywidth
和 \overlayheight
。当 foo 作为框框的背景时,框框的长度和宽度便是 foo 的宽度和长度。
我原本也不知道 randomized
后面跟随一个 numeric
类型的值或者跟随一个 pair
类型的值,它产生的扰动范围是多大,但是根据上述示例的取值,再结合群鸦齐飞的结果,便推断出来了。
五颜六色的鸦
随机扰动,也能修改颜色值。在 MetaPost 语言里,一个颜色,就是三元组。例如,
color foo;
foo := (0, .625, 0);
foo
实际上就是 darkgreen
,只不过后者是 ConTeXt 已定义的颜色值而已。
white
也是已定义的颜色值,它的值是 (1, 1, 1)
。倘若 white
被 randomized
扰动,就可以生成一种不可预知的颜色:
white randomized (1, 1, 1);
当 randomized
后面跟随三元组的时候,它不会生成负数,而是生成一个随机的三元组,其中每个元素的值是 [0, 1] 区间内的小数。当 randomized
前面是颜色值的时候,它将随机三元组的值分别与颜色分量相乘。这是我的猜测……我没在 MetaFun 手册里看到 Hans 对此的解释。
现在试试用随机颜色涂鸦:
for i = 0 upto 50:
draw darkgreencrow randomized .5u
rotated (0 randomized 60)
shifted ((0, 0) randomized (OverlayWidth, OverlayHeight))
withcolor (white randomized (1, 1, 1));
endfor;
结果为
结语
也许画画不好的人,才会喜欢 MetaPost 吧。MetaFun,出郭相扶将。
网友评论