绘制数学图形

本书介绍了如何在PostScript中绘制图形, 当然也必然涉及一些几何.

第1章 开始

第1.1节 简单绘图

GS>newpath
GS>144 144 moveto
GS>288 288 lineto
GS>stroke
GS>

以下命令序列可以绘制一个边长为2英寸的正方形.

newpath
144 144 moveto
288 144 lineto
288 288 lineto
144 288 lineto
144 144 lineto
stroke

当然PoscScript中存在许多不同的方式来绘制这个正方形.

newpath
144 144 moveto
144 0 rlineto
0 144 rlineto
-144 0 rlineto
closepath
stroke

rlinetolineto的相对 (relative) 位置版本. closepath通过回到上一个应用moveto的位置以封闭路径.

newpath
144 144 moveto
144 0 rlineto
0 144 rlineto
-144 0 rlineto
closepath
fill

fillstroke有着不同的效果.

0.5 setgray
newpath
144 144 moveto
144 0 rlineto
0 144 rlineto
-144 0 rlineto
closepath
fill
1 0 0 setrgbcolor
newpath
144 144 moveto
144 0 rlineto
0 144 rlineto
-144 0 rlineto
closepath
fill

第1.2节 简单坐标变换

对于生活在北美的人而言, 既然默认的页面尺寸为8.5×11, 与单位英寸打交道通常来得更加容易. 以下命令可以将基本的长度单位转换为英寸.

72 72 scale

当进行缩放的时候, 需要注意默认的线宽是一个单位. 如果什么都不做, 那么现在线宽就是1英寸了.

0.01389 setlinewidth

你可以使用0.01389 setlinewidth将其改回来, 因为0.01389约等于1/72.

1 2 translate

你可以使用translate进行平移, 比如这里将坐标原点右移了1个单位, 上移了2个单位.

72 72 scale
4.25 5.5 translate

如此原点就移动了到了页面的中心.

还有一种简单的坐标变换, 即旋转rotate.

144 144 translate
30 rotate
newpath
0 0 moveto
144 0 lineto
144 144 lineto
0 144 lineto
0 0 lineto
stroke

旋转总是围绕当前原点进行, 注意到PostScript的角度单位是角度制而不是弧度制.

第1.3节 坐标框架

第1.4节 PostScript中做算术

PostScript是一种完备的编程语言.

GS>3 4 add
GS<1>

你可能想问<1>是什么意思, 它指的是栈的深度.

GS>3 4 add
GS<1>=
7
GS>

=移除栈顶的元素, 并将其输出. ===类似, 但是输出在某种意义上更加美观, 所以你总是应该使用==.

GS>3 4 add
GS<1>stack
7
GS<1>

stack输出整个栈, 类似的命令还有pstack. pstack之于stack就像==之于=.

GS>3 3 mul
GS<1>4 4 mul
GS<2>add
GS<1>sqrt
GS<1>=
5.0
GS>

第1.5节 错误

第1.6节

第1.7节 一些细节

linecap样式
0
1
2

默认情况下linecap0, 你可以通过setlinecap来设置这个参数.

9 setlinewidth
% linejoin = 0 by default
newpath
0 0 moveto
0 72 lineto
72 0 lineto
stroke

linejoin是一个与linecap有点联系的参数, 它控制line join的样式, 其值同样仅可能为0, 1, 2, 并使用setlinejoin修改.

第1.8节 消除冗余的一个技巧

newpath
144 144 moveto
144 0 rlineto
0 144 rlineto
-144 0 rlineto
closepath
gsave
1 0 0 setrgbcolor
fill
grestore
0 setgray
stroke

第1.9节 总结

newpath
moveto
lineto
rmoveto
rlineto
closepath
stroke
fill
translate
scale
rotate
setlinewidth
setrgbcolor
setgray
setlinejoin
setlinecap
gsave
grestore
showpage

第2章 初等坐标几何

第2.1节 点和向量

如果P是一个点而v是一个向量, 那么P+v是一个点, 其是满足QP=v的点Q. 我们称QP平移v得到的. 若给定实数t以及点PQ, 我们可以构造一个点P+t(QP). 若0t1, 那么该点在线段PQ上. 当t=1/2时, 该点即PQ的中点. 我们可以将P+t(QP)写成(1t)P+tQ的形式.

第3章 变量和过程

第3.1节 PostScript中的变量

/s 1 def

newpath
0 0 moveto
s 0 rlineto
0 s rlineto
s neg 0 rlineto
closepath
stroke

第3.2节 PostScript中的过程

第4章 坐标和条件式

第4.1节 坐标

PostScript在内部(至少是隐式地)处理三种坐标系统. 第一种是物理坐标系. 物理坐标系的基本单位是像素. 第二种是页面坐标系. 其原点位于页面的左下角, 单位长度为一个Adobe点, 等于1/72英寸. 页面可以被视为一种理想化了的物理设备. 第三种是用户坐标系, 这是用户绘制图像时使用的坐标系. 最初页面坐标系和用户坐标系是相同的, 但是特定的操作, 例如scale, translate, rotate, 可以改变其间的关系.

[xy]=[xy][abcd]+[ef]

仿射变换可以看成是一个线性变换加上一个平移分量, 其具有特定的性质: 直线经过变换仍然是直线, 平行线经常变换仍然是平行的.

第4.2节 PostScript如何存储坐标变换

确定一个仿射坐标变换[xy]=[xy][abcd]+[ef]所需要的数据被存储在一个长度为六的数组[a b c d e f]里, 其被称为matrix. 命令matrix将数组[1 0 0 1 0 0]置于栈上, 此即恒等变换. 若此时再使用currentmatrix命令, 则会将当前变换置于该数组之中.

GS>matrix currentmatrix ==
[1.33333337 0.0 0.0 1.33333337 0.0 0.0]
GS>
[注记: 至少若是在Windows系统之中调整缩放比例, 结果是会改变的, 当然这也是相当合理的.] defaultmatrixcurrentmatrix有些类似, 但是顾名思义, 其将从页面到物理坐标系的变换置于栈顶的数组之中. 如果什么都没动, 这两个命令的效果显然是一样的. [译注: currentmatrix当然就是从用户到物理坐标系的变换了.]

现在我们想要解方程[xy]=[xy][abcd]+[ef]以用xy表达xy. 我们考虑将其写成P=PA+v的形式. 假定A可逆, 我们得到P=PA1vA1也就是说, 这个逆变换同样是仿射变换. 实际上, PostScript拥有一个计算逆变换的命令, 即invertmatrix. 另外, concatmatrix可以用来计算变换的复合. 它们的用法如下.

M matrix invertmatrix
这可以将M的逆变换置于栈上.
A B matrix concatmatrix
这可以将AB的复合置于栈上. [译注: 先施行变换A, 后施行变换B.]

显然, 我们可以藉由这些操作来计算从用户到页面坐标系的变换.

/user-to-page-matrix {
  matrix currentmatrix
  matrix defaultmatrix
  matrix invertmatrix
  matrix concatmatrix
} def
当前变换是userphysics, 默认变换是pagephysics, 逆就是physicspage, 当前变换和{默认变换的逆}复合一下就得到了userpage.

第4.3节 绘制坐标系

第4.4节 移入三维

注记: 本节考虑的不是三维的仿射变换的情形, 而是仿射坐标系的诸多事宜.

实际上, 我们可以在三维的情况下用线性变换表示二维的仿射变换.[xy]=[xy][abcd]+[ef]可以被重写为[xy1]=[xy1][ab0cd0ef1]这样之后, 仿射变换的复合就变成纯粹的矩阵乘法了.

第4.5节 坐标变换是如何进行的

现在我们可以检视如何以矩阵表示先前的几种简单变换.

a b scale
[a000b0001]
x rotate
[cosxsinx0sinxcosx0001]
a b translate
[100010ab1]当然了, 最一般的变换如下.
[a b c d e f] concat
[ab0cd0ef1]以上这些变换的效果都是给当前矩阵左乘其变换矩阵.

练习6. 在变换
72 72 scale
4 5 translate
30 rotate
之后, 从用户到页面坐标系的变换矩阵应该是什么?

在Racket写了点简单的程序算一算.

> (matrix-print
   (matrix* (make-scale 72 72)
            (make-translation 4 5)
            (make-rotation (/ pi 6))))
62.353829072479584 -35.99999999999999 288 
35.99999999999999 62.353829072479584 360 
0 0 1 
当然, 我的这个采用的是通行的左乘而不是PostScript的右乘, 所以结果应该是其转置.
GS>/user-to-page-matrix {
  matrix currentmatrix
  matrix defaultmatrix
  matrix invertmatrix
  matrix concatmatrix
} def
GS>72 72 scale
GS>4 5 translate
GS>30 rotate
GS>user-to-page-matrix
GS<1>==
[62.3538284 36.0 -36.0 62.3538284 288.0 360.0]
GS>
我们再在PostScript的REPL里算一遍, 的确如此.

第5章 绘制多边形: 循环和数组

第6章 曲线