> <\body> \; 图形编程>||>>|>> 因为的介绍资料不多,所以打算边学边写,写一系列用作图的小文章。早先我在自己的博客上写过两篇:使用Scheme在中生成图片<\footnote> 和使用Scheme在中画内核代码结构体关系图<\footnote> ,对其图形系统的文档树有一个大概的了解。这次打算系统性地介绍作图方法并构建用编程作图的配置文件。 本文对读者的基本要求就是熟悉的基本使用和语言的基础。相关的操作和内部原理,我尽量会使用自己的语言阐述清楚,或者给出官方文档的具体位置。另外,本文原始文档托管在Github<\footnote> 上,所使用的版本为 <\remark> 由于文中使用了大量交互式进程,在原始文档中才能够对其求值并作图,所以请使用阅读原始文档。 > 本文的交互式代码的执行假定读者是一次性从上到下读完全文,当然这是不现实的,所以附录的小贴士建议优先阅读,以方便你第二次阅读本文中末节时快速进入状态。 首先,假设我们已经了解到:一篇文档实际上就是一长串代码,通过渲染引擎的加工,这些代码得以展现在我们笔记本的屏幕上。这些代码我们称之为 。为了区分,我们将在中运行的代码称为 。 通过,我们得到一个 。我们定义第一个函数 <\session|scheme|default> <\input|Scheme] > (define (plot l) (stree-\tree l)) ;按下回车,定义这个函数 一串代码对应的结构是一棵树,这里的tree>就是将 树转变成 树,以便在文档中显示。比如,我们知道的内部表示实际上就是。于是,在中,我们就可以通过 <\session|scheme|default> <\folded-io|Scheme] > (plot `(frac 1 2)) ;光标放在这行上,按下回车就能得到1/2 <|folded-io> \; 上面介绍的原语实际上用于数学模式,下面我们介绍图形模式下的原语。先全部列出来: <\big-table|>>>>| 原语 |<\cell> 示例 |<\cell> 功能 >| |<\cell> > |<\cell> 坐标(0,0)处的一个点 >| |<\cell> (line (point \P0\Q \P0\Q) (point \P0\Q \P1\Q) (point \P1\Q \P1\Q)) > |<\cell> (0,0)>(0,1)>(1,1) 的一条折线 >| |<\cell> <\code*> (cline (point \P0\Q \P0\Q) (point \P0\Q \P1\Q) (point \P1\Q \P1\Q)) |<\cell> (0,1)\(1,1)\(0,0)> 的一条闭合折线 >| |<\cell> <\code*> (spline (point \P0\Q \P0\Q) (point \P0\Q \P1\Q) (point \P1\Q \P1\Q)) |<\cell> (0,1)\(1,1)> 的一条样条曲线 >| |<\cell> <\code*> (cspline (point \P0\Q \P0\Q) (point \P0\Q \P1\Q) (point \P1\Q \P1\Q)) |<\cell> (0,1)\(1,1)\(0,0)> 的一条闭合样条曲线 >| |<\cell> <\code*> (arc (point \P0\Q \P0\Q) (point \P0\Q \P1\Q) (point \P1\Q \P1\Q)) |<\cell> 过这三点的一条弧 >| |<\cell> <\code*> (carc (point \P0\Q \P0\Q) (point \P0\Q \P1\Q) (point \P1\Q \P1\Q)) |<\cell> 过这三点的一个圆 >| |<\cell> <\code*> (text-at (texmacs-markup) (point \P0\Q \P0\Q)) |<\cell> 这个原语的重要之处在于提 供了一种在图片上放置 图片的方法,放在其上 的图片所处的位置是点 (0,0)的右边,其竖直方向 上的对称轴正好过点(0,0) >>>>> \; 接着,我们在这些原语<\footnote> 这些原语的代码实现可以在下找到 的基础上构建作图所需的基本元素。首先是点,线段,矩形和圆: <\session|scheme|default> <\input|Scheme] > (define (point x y) \ \ ; number-\string的作用是将树变成文档中表示数据的字符串 \ \ `(point ,(number-\string x) ,(number-\string y))) <\input|Scheme] > (define (point.x point) \ \ (string-\number (list-ref point 1))) <\input|Scheme] > (define (point.y point) \ \ (string-\number (list-ref point 2))) <\input|Scheme] > (define (line . points) \ \ (cond ((nlist? points) `()) \ \ \ \ \ \ \ \ ((== points '()) `()) \ \ \ \ \ \ \ \ (else `(line ,@points)))) <\input|Scheme] > (define (rectangle leftdown rightup) \ \ (let ((leftup (point (point.x leftdown) (point.y rightup))) \ \ \ \ \ \ \ \ (rightdown (point (point.x rightup) (point.y leftdown)))) \ \ \ \ `(cline ,leftdown ,leftup ,rightup ,rightdown))) <\input|Scheme] > (define (circle center radius) \ \ (let ((p1 (point (- (point.x center) radius) (point.y center))) \ \ \ \ \ \ \ \ (p2 (point (point.x center) (+ (point.y center) radius))) \ \ \ \ \ \ \ \ (p3 (point (+ (point.x center) radius) (point.y center)))) \ \ \ \ `(carc ,p1 ,p2 ,p3))) 用绘制点、矩形和圆: <\session|scheme|default> <\unfolded-io|Scheme] > (plot (point 0 0)) <|unfolded-io> > <\unfolded-io|Scheme] > (plot (rectangle (point 0 0) (point 1 1))) <|unfolded-io> |||>> <\unfolded-io|Scheme] > (plot (circle (point 0 0) 1)) <|unfolded-io> ||>> 使用原语可以给对象附上各种属性。比如 <\session|scheme|default> <\unfolded-io|Scheme] > (plot `(with color "red" fill-color "#eeeeee" ,(circle (point 0 0) 1))) <|unfolded-io> ||>>> <\unfolded-io|Scheme] > (plot `(with arrow-begin "\gtr\" dash-style "11100" ,(line (point 0 1) (point 0 0) (point 1 1)))) <|unfolded-io> |dash-style|11100|||>>> <\unfolded-io|Scheme] > (plot `(with point-style "star" ,(point 0 0))) <|unfolded-io> >> 根据源码<\footnote> 中的定义,可以总结出: |||||| 属性 |<\cell> 值 |<\cell> 作用 >| color |<\cell> \; 颜色,如 |<\cell> 对象本身的颜色 >| fill-color |<\cell> \; |<\cell> 填充色 >| magnify |<\cell> 浮点数,如 |<\cell> 放大或缩小的倍率 >| opacity |<\cell> 百分比,如 |<\cell> 透明度 >| point-style |<\cell> |<\cell> 点的样式 >| dash-style |<\cell> |<\cell> 线的样式 >| arrow-begin |<\cell> less\","\less\\|","\less\\less\",> gtr\","\|\gtr\","\gtr\\gtr\"> \; |<\cell> 开始处的箭头 >| arrow-end |<\cell> \; |<\cell> 结束处的箭头 >>>>|部分对象属性> 光看表格中的总结不免失之直观,推荐阅读这章中样式属性详述这一节。 下面,定义一些函数,方便我们操纵上一节中点、圆和矩形的样式。首先是颜色,我们定义来设置背景色,定义来设置前景色。粗糙的想法是在图形对象前增加标签以及相应的属性,标签会怎样呢?> 这个问题可以用函数解决,另外我们定义来设置任意属性: <\session|scheme|default> <\input|Scheme] > (define (merge-with l par val subs) \ \ (cond ((== (length l) 0) '()) \ \ \ \ \ \ \ \ ((== (length l) 1) (append (list par val) l)) \ \ \ \ \ \ \ \ ((== par (car l)) \ \ \ \ \ \ \ \ \ (if subs (set-car! (cdr l) val)) l) \ \ \ \ \ \ \ \ (else\ \ \ \ \ \ \ \ \ \ \ (let ((t (list (car l) (cadr l)))) \ \ \ \ \ \ \ \ \ \ \ \ (append t (merge-with (cddr l) par val subs)))))) <\input|Scheme] > (define (decorate l par val subs) \ \ (cond ((or (nlist? l) (null? l)) '()) \ \ \ \ \ \ \ \ ((list? (car l))\ \ \ \ \ \ \ \ \ \ (append (list (decorate (car l) par val subs))\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (decorate (cdr l) par val subs))) \ \ \ \ \ \ \ \ ((== (car l) 'with)\ \ \ \ \ \ \ \ \ \ (append '(with) (merge-with (cdr l) par val subs))) \ \ \ \ \ \ \ \ ((or (== (car l) 'line) (== (car l) 'cline) (== (car l) 'carc) (== (car l) 'point) (== (car l) 'graphics)) \ \ \ \ \ \ \ \ \ (append '(with) (merge-with (list l) par val subs))))) <\input|Scheme] > (define (fill fig bc) \ \ (decorate fig "fill-color" bc #f)) <\input|Scheme] > (define (force-fill fig bc) \ \ (decorate fig "fill-color" bc #t)) <\input|Scheme] > (define (colorize fig fc) \ \ (decorate fig "color" fc #f)) <\input|Scheme] > (define (force-colorize fig fc) \ \ (decorate fig "color" fc #t)) <\input|Scheme] > (define (arrow-begin fig style) \ \ (decorate fig "arrow-begin" style #f)) <\input|Scheme] > (define (force-arrow-begin fig style) \ \ (decorate fig "arrow-begin" style #t)) <\input|Scheme] > (define (arrow-end fig style) \ \ (decorate fig "arrow-end" style #f)) <\input|Scheme] > (define (force-arrow-end fig style) \ \ (decorate fig "arrow-end" style #t)) <\input|Scheme] > (define (dash-style fig style) \ \ (decorate fig "dash-style" style #f)) <\input|Scheme] > (define (force-dash-style fig style) \ \ (decorate fig "dash-style" style #t)) <\unfolded-io|Scheme] > (plot (dash-style (fill (colorize (circle (point 0 0) 1) "blue") "green") "1111010")) <|unfolded-io> ||>>> <\unfolded-io|Scheme] > (plot (arrow-end (line (point -2 0) (point 0 0) (point 1 1)) "\|\gtr\")) <|unfolded-io> |||>>> 前文所作之图,我们都只是将图形对象生成出来文档树放在进程的输出上,我们观察到坐标的原点就在文档横截线的中点上。用光标选中这个图案,可以看到左边的一大截空白。在上一节作出的箭头图案前输入了单词left后,你可以清晰地看到这些空白。 left|||>> 由此可以知道,在没有画布的情况下,会分配一个动态大小的画布,以适应图形的尺寸。 前文中的图像都只是单个图形对象在默认画布上的显示。引入画布之后,我们就可以将多个图形对象叠加在同一个画布上。通过逆向工程<\footnote> 方法请参考附录中的小贴士 ,可以举出这个例子: <\session|scheme|default> <\input|Scheme] > (define (graphics . objects) \ \ (cond ((nlist? objects) '(graphics "" "")) \ \ \ \ \ \ \ \ ((== objects '()) '(graphics "" "")) \ \ \ \ \ \ \ \ (else `(graphics "" ,@objects)))) <\input|Scheme] > (define (geometry fig x y) \ \ (decorate fig "gr-geometry" `(tuple "geometry" ,x ,y "center") #f)) <\unfolded-io|Scheme] > (plot (geometry (graphics\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (fill (rectangle (point -2 -1) (point 1 1)) "blue") \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (fill (rectangle (point -1 -1) (point 2 1)) "red") \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (dash-style (line (point 1 -1) (point 1 1)) "11100")) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "5cm" "3cm")) <|unfolded-io> ||||>>||||>>||>>>>> 现在我们就能够用函数,将多个图形对象叠加在同一个画布上,而且,图形对象的顺序决定了渲染的顺序,后者会覆盖前者。如上图所示,虚线表示原来蓝色矩形的右边界,现在被红色矩形覆盖了。 而函数可以控制画布的大小。注意,前文中都没有讨论长度单位这一因素。但实际上前文中所有的坐标的单位都是。所以在指定画布的宽度和高度的时候,我们需要加上这个单位,因为这里的默认单位不是。 另外,我们还可以剪裁画布,尽可能减少画布周围的空白。 <\session|scheme|default> <\input|Scheme] > (define (crop fig) \ \ (decorate fig "gr-auto-crop" "true" #f)) <\unfolded-io|Scheme] > (plot (crop (graphics\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (fill (rectangle (point -2 -1) (point 1 1)) "blue") \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (fill (rectangle (point -1 -1) (point 2 1)) "red") \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (dash-style (line (point 1 -1) (point 1 1)) "11100")))) <|unfolded-io> |||>>||||>>||>>>>> 选中最近的这两个一样的图像,你就可以看到区别。 这一章主要利用前文定义好的函数,绘制各种各样有趣的图案。 将半径为R的圆周n等分,然后用直线将各个等分点两两相连。 将半径为>的圆周n等分,然后以每个等分点为圆心,以>为半径画n个圆。 > and triangle<\footnote> > > 表达式求值> 当你刚刚用编辑器打开本文时,如果你跳到中间的某节去执行代码,很有可能会出错,因为当前的代码很有可能依赖上前文中已经出现过的函数和变量。而将前文中所有的代码都执行一遍这个操作实际上非常繁琐。启用,将光标置于本文的某个进程中,然后就可以导出所有的代码到单个文件中。然后,开启一个进程并输入,回车之后,文中所有的代码就都被加载了。 <\itemize> A TeXmacs graphics tutorial<\footnote> , by Henri Lesourd. Turtle schemes<\footnote> , by Ana Caizares Garca and Miguel de Benito Delgado Fractal turtles<\footnote> , by Ana Caizares Garca and Miguel de Benito Delgado \; <\initial> <\collection> <\references> <\collection> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > <\auxiliary> <\collection> <\associate|idx> |插入>||会话>||Scheme>>|> |帮助>||用户手册>||内置作图工具>>|> |工具>||开发菜单>>|> |开发者>||Export Sessions...>>|> |转到>||无标题文件>>|> <\associate|table> |1>|> \; |> |2>||部分对象属性>|> <\associate|toc> 1简介 |.>>>>|> 2基本原理 |.>>>>|> |2.1原语 |.>>>>|> > |2.2操纵样式属性 |.>>>>|> > |2.3摆弄画布 |.>>>>|> > 3画廊 |.>>>>|> |3.1金刚石图案 |.>>>>|> > |3.2圆环图案 |.>>>>|> > |3.3肾形图案 |.>>>>|> > |3.4心脏形图案 |.>>>>|> > |3.5分形图案 |.>>>>|> > |3.5.1|.>>>>|> > |3.5.2Koch snowflake|7><\float|footnote|> ||par-left||par-right||font-shape||dummy||dummy||<\surround|||>|7>. ||7>>> ||>||language||https://en.wikipedia.org/wiki/Koch_snowflake>> >> |>> |.>>>>|> > |3.5.3Sierpinski carpet|10><\float|footnote|> ||par-left||par-right||font-shape||dummy||dummy||<\surround|||>|10>. ||10>>> ||>||language||https://en.wikipedia.org/wiki/Sierpinski_carpet>> >> |>> and triangle|11><\float|footnote|> ||par-left||par-right||font-shape||dummy||dummy||<\surround|||>|11>. ||11>>> ||>||language||https://en.wikipedia.org/wiki/Sierpinski_triangle>> >> |>> |.>>>>|> > |3.5.4Mandelbrot set|16><\float|footnote|> ||par-left||par-right||font-shape||dummy||dummy||<\surround|||>|16>. ||16>>> ||>||language||https://en.wikipedia.org/wiki/Mandelbrot_set>> >> |>> |.>>>>|> > 4附录 |.>>>>|> |4.1小贴士 |.>>>>|> > |4.1.1对本文所有的|Scheme>表达式求值 |.>>>>|> > |4.1.2逆向工程 |.>>>>|> > |4.2参考资料 |.>>>>|> >