用Lua做了个绘制二元等式或不等式的图像的软件
输入任意的包含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行:
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,没有的话另外下载。