用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 expr = iup.GetDialogChild(dlg, "exprtext").value
    
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. 一句话就能载入配置文件:

dofile("Favorites.lua")

3. 配置文件本身是一个合法的Lua程序,因而用户可以在里面自定义一些函数。

比如这个图用到了自定义的belongto函数:

function between(x, minValue, maxValue)
    
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行:

View Code
  1 -- GraphPlot.wlua
  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 = 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,没有的话另外下载。

下载地址1

下载地址2

posted @ 2011-06-28 00:04 刘俊峰 阅读(...) 评论(...) 编辑 收藏