输入任意的包含x和y两个变量的Lua表达式就可以绘制出对应的图像。Lua支持的运算符如下:
算术 | + - * / % ^ |
比较 | == ~= < > <= >= |
逻辑 | and or not |
其中%是求余,^是乘方;需要注意的是相等用==,写=则报语法错误。 Lua预定义的数学函数参见Reference Manual的mathlib。软件界面如下:
绘图的算法很简单,遍历每一个像素点,如果对应的坐标区域满足表达式给出的条件就给这个像素着色。
需要特别说明的是Pixel Split的含义,每个像素点并不是对应一对坐标值,而是对应一个矩形区域,因此对于某些函数需要做细分才能画准确一点。比如输入8表示细分为8x8=64个网格点,只要有一个格点满足表达式给出的条件就给像素着色。细分的数字越大所需的计算量也就越大,建议取1到50之间的数字。
在表达式中使用逻辑运算符可以将一些图形组合起来显示,比如:
画布的大小和一些参数的默认值可以在配置文件中修改,配置文件中还包括了收藏夹的定义,预置一些比较有趣的表达式,文件名是Favorites.lua,可以用记事本编辑,然后重新打开程序生效。
做这个软件的想法是看了《几个令人惊叹的函数图像》一文,文中提到作图软件GrafEq比较古老,很多系统不兼容了。另外最近正在学Lua,正好实践一下。而且用Lua很适合,提供了以下几个便利:
1. 可以把用户输入的表达式文本转化为一个函数:
local func, err = loadstring ( " return function (x,y) return " .. expr.. " end " )
if func == nil then iup.Message( " Error " , " Expression has syntax errors:\r\n " ..err) return end
equation = func()
2. 一句话就能载入配置文件:
3. 配置文件本身是一个合法的Lua程序,因而用户可以在里面自定义一些函数。
比如这个图用到了自定义的belongto函数:
return x >= minValue and x <= maxValue
end
function belongto(x, ranges)
for _,range in ipairs (ranges) do
if between(x, range[ 1 ], range[ 2 ]) then return true end
end
return false
end
表达式为:belongto(sin(sqrt((x+5)^2+y^2))*cos(8*atan(y/(x+5)))*sin(sqrt((x-5)^2+(y-5)^2))*cos(8*atan((y-5)/(x-5)))*sin(sqrt(x^2+(y+5)^2))*cos(8*atan((y+5)/x)), {{-0.1,0},{0.2,math.huge}})
这个软件有两种玩法:一是输入表达式观察对应的图形,二是预想一个图案,然后设计表达式生成想要的图形。
就实用性而言,一是对学数学的中学生会很有用,二是生成的图案可以用于其他的平面或3D绘图软件。
可以设定画布的背景色和画笔的颜色,可以保存图片到文件中。
下面是程序的源代码,只有204行:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2 -- Plot any Lua expression in two variables x and y
3 -- Junfeng Liu @ 2011-06-27
4
5 require " cdlua "
6 require " iuplua "
7 require " iupluacd "
8
9 setmetatable ( _G , { __index = math })
10 e = exp( 1 )
11
12 -- Load Config and Favorites
13 dofile ( " Favorites.lua " )
14 pixels = {}
15 for r = 1 ,Config.Height do
16 pixels[r] = {}
17 for c = 1 ,Config.Width do
18 pixels[r][c] = false
19 end
20 end
21
22 cnv = iup.canvas{ rastersize = string.format ( " %dx%d " , Config.Width, Config.Height) }
23 treeFavorite = iup.tree{ size = " 100x100 " }
24
25 canvasColor = cd.EncodeColor( 255 , 255 , 255 )
26 penColor = cd.EncodeColor( 0 , 0 , 0 )
27
28 function selectColor(self)
29 r,g,b = iup.GetColor(iup.CENTER,iup.CENTER)
30 self.bgcolor = string.format ( " %d %d %d " ,r,g,b)
31 if self.name == " btnPen " then penColor = cd.EncodeColor(r,g,b)
32 else canvasColor = cd.EncodeColor(r,g,b) end
33 end
34
35 dlg = iup.dialog
36 {
37 title = " Plot graphs of equations and inequalities in two vairables " ,
38 resize = " NO " ,
39 minbox = " NO " ,
40 iup.hbox
41 {
42 iup.vbox
43 {
44 iup.frame
45 {
46 title = " Color " ,
47 iup.hbox
48 {
49 iup.label{title = " Canvas: " },
50 iup.button{ name = " btnCanvas " , size = " 22x10 " , bgcolor = " 255 255 255 " , flat = " YES " , action = selectColor},
51 iup.label{title = " Pen: " },
52 iup.button{ name = " btnPen " , size = " 22x10 " , bgcolor = " 0 0 0 " , flat = " YES " , action = selectColor},
53 alignment = " ACENTER "
54 }
55 },
56 iup.frame
57 {
58 title = " Range " ,
59 iup.vbox
60 {
61 iup.hbox
62 {
63 iup.label{title = " X: " },
64 iup.text{ name = " txtXMin " , size = " 38x10 " , value = Config.Xmin},
65 iup.label{title = " ~ " },
66 iup.text{ name = " txtXMax " , size = " 38x10 " , value = Config.Xmax}
67 },
68 iup.hbox
69 {
70 iup.label{title = " Y: " },
71 iup.text{ name = " txtYMin " , size = " 38x10 " , value = Config.Ymin},
72 iup.label{title = " ~ " },
73 iup.text{ name = " txtYMax " , size = " 38x10 " , value = Config.Ymax}
74 }
75 }
76 },
77 iup.hbox
78 {
79 iup.label{ title = " Pixel Split: " },
80 iup.text{ name = " txtSplit " , value = Config.Split},
81 alignment = " ACENTER "
82 },
83 treeFavorite,
84 margin = " 2x2 "
85 },
86 iup.vbox
87 {
88 iup.hbox
89 {
90 iup.label{title = " Expression: " },
91 iup.text{ name = " exprtext " , rastersize = (Config.Width - 120 ).. " x22 " , value = " abs(x - y) % 20 < 0.0001 " },
92 iup.button{title = " Plot " , size = " 34x12 " , action = function () plotExpression() end },
93 alignment = " ACENTER "
94 },
95 cnv,
96 margin = " 2x2 "
97 }
98 }
99 }
100
101 function getNumber(name)
102 return tonumber (iup.GetDialogChild(dlg, name).value)
103 end
104
105 function setTextValue(name, value)
106 iup.GetDialogChild(dlg, name).value = value
107 end
108
109 function plotExpression()
110 local expr = iup.GetDialogChild(dlg, " exprtext " ).value
111 local func, err = loadstring ( " return function (x,y) return " .. expr.. " end " )
112 if func == nil then iup.Message( " Error " , " Expression has syntax errors:\r\n " ..err) return end
113 equation = func()
114 drawing = true
115 iup.Redraw(cnv, 0 )
116 end
117
118 function cnv:map_cb()
119 canvas = cd.CreateCanvas(cd.IUP, self)
120 end
121
122 drawing = false
123 function cnv:action()
124 canvas:Activate()
125 canvas:Background(canvasColor)
126 canvas:Clear()
127 local width = Config.Width
128 local height = Config.Height
129 if drawing == false then
130 for r = height, 1 , - 1 do
131 for c = 1 ,width do
132 if pixels[r][c] then
133 canvas:Pixel(c, r, penColor)
134 end
135 end
136 end
137 return
138 end
139
140 dlg.active = " NO "
141 local xMin = getNumber( " txtXMin " )
142 local xMax = getNumber( " txtXMax " )
143 local yMin = getNumber( " txtYMin " )
144 local yMax = getNumber( " txtYMax " )
145 local split = getNumber( " txtSplit " )
146 local DX = (xMax - xMin) / width
147 local DY = (yMax - yMin) / height
148 local dx = DX / split
149 local dy = DY / split
150 local x = xMin
151 local y = yMax
152 for r = height, 1 , - 1 do
153 for c = 1 ,width do
154 local fill = isSolution(x,y,dx,dy,split)
155 pixels[r][c] = fill
156 if fill then
157 canvas:Pixel(c, r, penColor)
158 end
159 x = x + DX
160 end
161 x = xMin
162 y = y - DY
163 end
164 drawing = false
165 dlg.active = " YES "
166 end
167
168 function isSolution(x0,y0,dx,dy,split)
169 local x = x0
170 local y = y0
171 for i = 1 ,split do
172 for j = 1 ,split do
173 if equation(x, y) then return true end
174 x = x + dx
175 end
176 x = x0
177 y = y - dy
178 end
179 return false
180 end
181
182 function treeFavorite:selection_cb(id, status)
183 if status == 1 then
184 node = Favorites[id]
185 setTextValue( " exprtext " , node.tip)
186 if node.config ~= nil then
187 setTextValue( " txtXMin " , node.config.Xmin)
188 setTextValue( " txtXMax " , node.config.Xmax)
189 setTextValue( " txtYMin " , node.config.Ymin)
190 setTextValue( " txtYMax " , node.config.Ymax)
191 setTextValue( " txtSplit " , node.config.Split)
192 end
193 end
194 end
195
196 function dlg:close_cb()
197 canvas:Kill()
198 self:destroy()
199 return iup.IGNORE
200 end
201
202 dlg:show()
203 iup.TreeAddNodes(treeFavorite, Favorites)
204 iup.MainLoop()
最后程序用wsrlua工具做了打包,不需要安装Lua环境就能运行,由于是GUI程序需要msvcr100.dll,没有的话另外下载。