首页 » 开发 » Metapost

学习MetaPost:在线编程(使用这个工具要注意,可能需要在绘图时引入其他的package,例如画box就需要)。参考大量的MetaPost实例

MetaPost基础

颜色 - 变形(平移、旋转、伸缩) - -

注释行以%打头。

度量单位。在MetaPost中,长度单位有很多,默认单位(unit)是1/72英寸,与PostScript的单位一致。1 inch = 2.54 cm,1/72 inch ≈ 0.03528 cm。bp表示big points,pt表示printer's points(1/72.27 inch),in表示inches英寸,cmmm表示厘米和毫米。这些单位,其实代表缩放因子(factor),因此2cm其实代表2*cm。我们通常使用u来定义长度单位,例如:

u = 1cm;
draw (0,0)--(1u,1u);

在MetaPost绘制的图形中,线条是主要构成部分。线条粗细有多种方式控制(注意默认是0.5point,4pt即8倍):

metapost draw line
draw (0,1cm)--(2cm,1cm);
draw (0,0)--(2cm,0) withpen pencircle scaled 2bp;
pickup pencircle scaled 4pt;
draw (0,-1cm)--(2cm,-1cm);

可以通过for循环控制绘制:

metapost for loop
u = 1cm;
pickup pencircle scaled 4pt;
for i=0 upto 2:
  for j=0 upto 2: draw (i*u,j*u); endfor
endfor

绘制曲线(curves)。这里是一个半径为1cm的半圆。注意,如果只有两个点,用draw a..b;绘制出来也是直线,而非曲线。

metapost draw line
pair a,b,c;
a = (0,0);
b = (1cm,1cm);
c = (0,2cm);
draw a..b..c;
pickup pencircle scaled 4pt;
draw a; draw b; draw c;

绘制曲线,可以控制方向:

metapost draw line
for i=0 upto 9:
  draw (0,0){dir 45}..{dir -10i}(6cm,0);
endfor

for i=0 upto 9:
  draw (7cm,1cm){dir 45}..{dir 10i}(13cm,1cm);
endfor

直线

metapost draw line
pair a, b;
a = (0,0);
b = (2cm,0);
draw a--b;

箭头

metapost drawarrow
pair a, b;
a = (0,0);
b = (2cm,0);
drawarrow a--b;

双向箭头与反向箭头

metapost drawarrow
pair a, b;
a = (0,0);
b = (2cm,0);
drawdblarrow a--b;

pair a, b;
a = (0,-1cm);
b = (2cm,-1cm);
drawarrow reverse(a--b);

正方形

metapost draw square
pair a, b, c, d;
a = (0,0);
b = (2cm,0);
c = (2cm,2cm);
d = (0,2cm);
draw a--b--c--d--cycle;

三角形。在顶点用圆点和颜色修饰。另注意a作为原点,在左上角,因此b、c的y轴取值都是负数。

metapost draw triangle
pair a, b, c;
a = (0,0);
b = (2cm,-2cm);
c = (0,-2cm);
draw a--b--c--cycle;
draw a withpen pencircle scaled 4bp withcolor red;
draw b withpen pencircle scaled 4bp;
draw c withpen pencircle scaled 4bp withcolor blue;

使用fill填充三角形。

metapost draw triangle
pair a, b, c;
a = (0,0);
b = (2cm,-2cm);
c = (0,-2cm);
draw a--b--c--cycle;
fill a--b--c--cycle withcolor .8white;

带连线的三角形。

metapost draw triangle
pair a, b, c;
a = (0,0);
b = (2cm,-2cm);
c = (0,-2cm);
draw a--b--c--cycle;
draw 1/2[b,c]--a;
draw 1/2[a,c]--b;
draw 1/2[a,b]--c;
draw 1/3a + 1/3b + 1/3c withpen pencircle scaled 4bp;

两个对称的三角形。注意在这个图形中原点a在左下角而非左上角。

metapost draw triangle
pair a,b,c,d;
a = (0,0);
b = (2cm,0);
c = (2cm,2cm);
d = (0,2cm);
fill a--c--b--d--cycle withcolor .8white;

为点加标签

metapost add label to dot
pair a;
a = (0,0);
draw a withpen pencircle scaled 4bp;
dotlabel.urt(btex $A$ etex, a);

为三角形加标签

metapost draw triangle with label
pair a, b, c;
a = (0,0);
b = (2cm,-2cm);
c = (0,-2cm);
draw a--b--c--cycle;
dotlabel.lft(btex $A$ etex, a);
dotlabel.urt(btex $B$ etex, b);
dotlabel.llft(btex $C$ etex, c);

在八个位置加标签:上top、下bot、左lft、右rt;右上urt、左上ulft、右下lrt、左下llft。

metapost label
pair a,b,c,d;
a = (0,0); b = (2cm,0); c = (2cm,2cm); d = (0,2cm);
draw a--b--c--d--cycle;
dotlabel.top(btex $top$ etex, 1/2[d,c]);
dotlabel.bot(btex $bot$ etex, 1/2[a,b]);
dotlabel.lft(btex $lft$ etex, 1/2[a,d]);
dotlabel.rt(btex $rt$ etex, 1/2[b,c]);
dotlabel.ulft(btex $ulft$ etex, d);
dotlabel.urt(btex $urt$ etex, c);
dotlabel.llft(btex $llft$ etex, a);
dotlabel.lrt(btex $lrt$ etex, b);

画圆

metapost full circle
draw (0,0) withpen pencircle scaled 4bp;
draw fullcircle scaled 2cm;

画圆,带有偏移

metapost full circle
draw (0,0) withpen pencircle scaled 4bp;
draw fullcircle scaled 2cm;

在三角形顶点画空心圆。注意,A是标准的做法,B和C都只完成了一半的操作

metapost full circle
pair a,b,c;
a = (0,0); b = (2cm,0); c = (0,2cm);
draw a--b--c--cycle;
fill fullcircle scaled 4bp shifted a withcolor white;
draw fullcircle scaled 4bp shifted a;
fill fullcircle scaled 4bp shifted b withcolor white;
draw fullcircle scaled 4bp shifted c;
label.lft(btex $A$ etex, a);
label.rt(btex $B$ etex, b);
label.lft(btex $C$ etex, c);

画圆,并伴随一个坐标系

metapost full circle
pair a,b,c,d;
a = (0,1cm); b = (1cm,0); c = (0,-1cm); d = (-1cm,0);
draw a..b..c..d..cycle;
dotlabel.urt(btex $(0,0)$ etex, (0,0));

pair a,b,c,d;
a = (-2cm,0); b = (2cm,0); c = (0, -2cm); d = (0,2cm);
drawarrow a--b;
drawarrow c--d;

画盒子,带边框与不带边框

metapost draw box
boxit.a(btex $Berlinix$ etex);
a.c = (0,0);
drawboxed(a);

boxit.b(btex $Berlinix$ etex);
b.c = (3cm,0);
drawunboxed(b);

画盒子,带箭头指示。注意:A指向B是正确的,B指向A的箭头挤到框里去了,需要用cutbefore, cutafter切掉。

metapost draw box
boxit.a(btex A etex);
boxit.b(btex B etex);
a.c = (-1cm,0); b.c = (1cm,0);
drawboxed(a,b);
drawarrow a.c{dir 45}..b.c{dir -45} cutbefore bpath.a cutafter bpath.b;
drawarrow b.c{dir -135}..a.c{dir 135};

画一堆盒子。注意:盒子的对齐方式,大写字母都是相同高度,小写字母高度不同。

metapost draw box
boxjoin(a.e = b.w);
boxit.a(btex A etex);
boxit.b(btex B etex);
boxit.c(btex C etex);
boxit.d(btex D etex);
drawboxed(a,b,c,d);

boxjoin(e.e = f.w);
boxit.e(btex a etex);
boxit.f(btex b etex);
e.c = (3cm,0);
drawboxed(e,f);

boxjoin(g.se = h.sw; g.ne = h.nw);
boxit.g(btex a etex);
boxit.h(btex b etex);
g.c = (5cm,0);
drawboxed(g,h);

颜色

颜色由三元组构成 - (r, g, b)。r/g/b必须是[0-1]之间的数值。MetaPost中预定义了几种颜色:red(1,0,0), green(0,1,0), blue(0,0,1), black(0,0,0), white(1,1,1)。

颜色。灰色。

metapost draw color: gray
pickup pencircle scaled 8bp;
for i=0 upto 10:
    draw(i*1cm, 0) withcolor i*0.1*white;
endfor

颜色。RGB调色板(这里蓝色设置为0,只有红绿两色):

metapost draw color: gray
u = 1/2cm;
path sqr;
sqr := unitsquare scaled u;
for i=0 upto 10:
    label.bot(decimal(i/10),((i+1/2)*u, 0));    % x
    label.lft(decimal(i/10),(0, (i+1/2)*u));    % y
    for j=0 upto 10:
        fill sqr shifted(i*u, j*u) withcolor(i*0.1, j*0.1, 0);
        draw sqr shifted(i*u, j*u); % draw grid
    endfor
endfor

变形

变形包括几种:平移(shifted)、旋转(rotated/rotatedaround)、倾斜(slanted)、伸缩(scaled/xscaled/yscaled/zscaled)。常用的变形计算公式如下:

(x,y) shifted (a,b) = (x+a, y+b);
(x,y) rotated (θ) = (xcosθ - ysinθ, xsinθ + ycosθ);
(x,y) slanted a = (x+ay, y);
(x,y) scaled  a = (ax, ay);
(x,y) xscaled a = (ax, y);
(x,y) yscaled a = (x, ay);

我们常用的变形如:

变形说明
yscaled -1上下颠倒
xscaled -1左右反转

数据类型

MetaPost包括10种基础的数据类型:numeric, pair, path, transform, (rgb)color, cmykcolor, string, boolean, picture, pen。

MetaPost宏定义的格式:

def <symbolic token> = <replacement text> enddef

例如我们常见的fill(填充颜色)就是一个宏:

def fill = addto currentpicture contour enddef

宏可以携带参数(formal parameters),它的形式如:

def rotatedaround(expr z,d) = shifted -z rotated d shifted z enddef;

expr表明参数z,d可以是任意表达式。

如下定义了1个宏drawit,每次画一个图,然后向右偏移2cm。

metapost draw color: gray
pair s; s=(2cm,0);
def drawit(expr p) =
    draw p shifted s;
    s := s shifted(2cm,0);
enddef;

picture p;
draw btex Berlinix etex;
draw bbox currentpicture withcolor 0.6white;
p := currentpicture;

drawit(p shifted (1cm,1cm));
drawit(p scaled 0.8);
drawit(p yscaled -1);
drawit(p slanted 0.5);

MetaPost与程序图例

以下是我用Metapost绘制的一些程序图例。

TCP 500ms 定时器

来自《TCP/IP详解卷1》18.2.1 TCP的500ms定时器:

metapost tcp timer
u=1.2cm;
pickup pencircle scaled 2pt;
for i=11 downto 0:
  draw(-i*u,-0.4u)--(-i*u,0.4u);
  label.top(decimal(i),(-i*u,0.7u));
endfor
draw(-13u,0)--(1u,0);
draw(-12u,-0.4u)--(-12u,0.4u);

drawdblarrow(-11u,1.5u)--(0u,1.5u);
label.top(btex 11 ticks * 500ms/tick = 5.5s etex, 1/2[(-11u,1.8u),(0u,1.8u)]);

drawdblarrow(-11.9u,-0.4u)--(-11.1u,-0.4u);
label.bot(btex start timer(6s) etex, 1/2[(-12u,-0.4u),(-11u,-0.4u)]);

drawarrow(0,-1u)--(0,-0.5u);
label.bot(btex start new timer(24s) etex, 1/2[(-2u,-1u),(2u,-1u)]);

drawdblarrow(-7u,-0.6u)--(-6u,-0.6u);
label.bot(btex 1 tick(500ms) etex, 1/2[(-7u,-0.8u),(-6u,-0.8u)]);

32bits 数组

metapost 32bits array
u=0.5cm;

draw(0,0)--(32u,0);
draw(0,1u)--(32u,1u);
draw(0,1.3u)--(32u,1.3u) withcolor .8white;

for i=0 upto 32:
    draw(i*u,0)--(i*u, 1u);
    draw(i*u,1.3u)--(i*u,1.5u) withcolor .8white;
endfor

for i=0 upto 31:
    label.top(decimal(i), 1/2[(i*u,1.4u),((i+1)*u,1.4u)]) withcolor .8white;
endfor

% filled bits

def myf(expr i,v) =
    label.top(decimal(v), 1/2[(i*u,0.1u),((i+1)*u,0.1u)]);
enddef;

for i=0 upto 15:
    myf(i,0);
endfor

for i=16 upto 31:
    myf(i,1);
endfor

% arrow

draw(0,-0.6u)--(0,-1.2u);
draw(16u,-0.6u)--(16u,-1.2u);
drawarrow(4u,-0.9u)--(0,-0.9u);
drawarrow(12u,-0.9u)--(16u,-0.9u);
label.top(btex $filled\ with\ 0$ etex, 1/2[(4u,-1.4u),(12u,-1.4u)]);

参考

王垠的Metapost介绍

分享

0