use transparency in case of numerical exceptions
[conformal.git] / conformal.py
blob6bf9953f862169da36d5bb37828dba9933f1af97
1 #!/usr/bin/env python
3 # conformal.py
4 # Copyright (C) 2006-2011 Michael J. Gruber <conformal@drmicha.warpmail.net>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, version 2 of the License.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 confversion = "0.3+"
21 # allow access through module and without
22 import math, cmath
23 from math import *
24 from cmath import *
26 from array import array
27 from gimpfu import *
29 # try importing typical math modules
30 try:
31 from fpconst import *
32 import scipy.special
33 except ImportError:
34 pass
36 try:
37 import mpmath
38 except ImportError:
39 pass
42 def conformal_batch(width, height, code, constraint, xl, xr, yt, yb, grid, checkboard, gradient, filename):
43 conformal_core(width, height, code, constraint, xl, xr, yt, yb, grid, checkboard, gradient, filename)
46 def conformal(width, height, code, constraint, xl, xr, yt, yb, grid, checkboard, gradient):
47 conformal_core(width, height, code, constraint, xl, xr, yt, yb, grid, checkboard, gradient, None)
50 def conformal_core(width, height, code, constraint, xl, xr, yt, yb, grid, checkboard, gradient, filename):
51 image = gimp.Image(width, height, RGB)
52 drawables = [ gimp.Layer(image, "Argument", width, height, RGBA_IMAGE, 100, NORMAL_MODE),
53 gimp.Layer(image, "Log. modulus", width, height, RGBA_IMAGE, 35, VALUE_MODE),
54 gimp.Layer(image, "Grid", width, height, RGBA_IMAGE, 10, DARKEN_ONLY_MODE)]
55 image.disable_undo()
56 l = 1
57 for drawable in drawables:
58 image.add_layer(drawable, l)
59 l = -1
61 bpp = drawables[0].bpp
63 gimp.tile_cache_ntiles(2 * (width + 63) / 64)
65 dest_rgns = [ drawable.get_pixel_rgn(0, 0, width, height, True, False) for drawable in drawables ]
66 progress = 0
67 max_progress = width * height
68 if filename is None:
69 gimp.progress_init("Conformally Mapping...")
70 sx = (width-1.0)/(xr-xl)
71 sy = (height-1.0)/(yt-yb)
72 w = complex(0.0)
73 z = complex(0.0)
74 cx, cy = 0, 0
75 mp2 = 2.0*math.pi # no need to do this 500*500 times...
76 ml2 = 2.0*math.log(2) # no need to do this 500*500 times...
77 ml = math.log(2) # no need to do this 500*500 times...
78 compiled=compile(code, "compiled code", "exec", 0, 1)
79 compiledconstraint=compile(constraint, "compiled constraint code", "exec", 0, 1)
81 dests = [ array("B", "\x00" * width*height*bpp) for i in range(3) ]
83 QUANT = 4096
84 args = [ i/(QUANT-1.0) for i in range(QUANT) ]
85 arggradsamples = list(gimp.gradient_get_custom_samples(gradient, args)) + [[0,]*bpp]
86 modgradsamples = list(gimp.gradient_get_custom_samples("Default", args)) + [[0,]*bpp]
87 sqrsamples = [ [0,]*(bpp-1) + [255,], [255,]*(bpp-1) + [255,] , [0,]*bpp ]
88 for col in range(QUANT+1):
89 arggradsamples[col] = [ ((int)(255*arggradsamples[col][i]+0.5)) for i in range(bpp)]
90 modgradsamples[col] = [ ((int)(255*modgradsamples[col][i]+0.5)) for i in range(bpp)]
91 qinf = 1.0 + 1.0/(QUANT-1) # uggely uggely
93 args = [0.0,] * width
94 mods = [0.0,] * width
95 sqrs = [0,] * width
97 for row in range(0, height):
98 for col in range(0, width):
99 z = col/sx + xl + 1j*( yt - row/sy)
100 p = True
101 try:
102 exec(compiledconstraint)
103 except (OverflowError, ValueError):
104 p = False
105 if not p:
106 w = 0.0
107 else:
108 try:
109 exec(compiled)
110 except (OverflowError, ValueError):
111 p = False
112 if not p or isnan(w) or isinf(w):
113 w = 0.0
115 try:
116 logw = cmath.log(w)
117 arg = logw.imag
118 if isnan(arg) or isinf(arg):
119 arg = 0.0
120 p = False
121 elif arg < 0.0:
122 arg = arg + mp2
123 mod = ( logw.real/ml ) % 1.0
124 if isnan(mod) or isinf(mod):
125 mod = 0.0
126 p = False
127 except (OverflowError, ValueError):
128 arg = 0.0
129 mod = 0.0
130 p = False
131 arg = arg/mp2
133 try:
134 sqr = int(w.imag/grid % 2.0) + int(w.real/grid % 2.0)
135 if isnan(sqr) or isinf(sqr):
136 sqr = 0
137 p = False
138 except (OverflowError, ValueError):
139 sqr = 0
140 p = False
142 sqr = sqr % 2
144 if not p:
145 arg = qinf
146 mod = qinf
147 sqr = 2
149 args[col] = arg
150 mods[col] = mod
151 sqrs[col] = sqr
153 dests[0][row*width*bpp : (row+1)*width*bpp] = array("B", [ arggradsamples [int((QUANT-1)*args[col]+0.5)][i] for col in range(0, width) for i in range(bpp) ] )
155 dests[1][row*width*bpp : (row+1)*width*bpp] = array("B", [ modgradsamples[int((QUANT-1)*mods[col]+0.5)][i] for col in range(0, width) for i in range(bpp) ] )
157 dests[2][row*width*bpp : (row+1)*width*bpp]= array("B", [ sqrsamples[sqrs[col]][i] for col in range(0,width) for i in range(bpp) ] )
159 progress = progress + width
160 if filename is None:
161 gimp.progress_update(float(progress) / max_progress)
163 for i in range(3):
164 dest_rgns[i][0:width, 0:height] = dests[i].tostring()
166 for drawable in drawables:
167 drawable.flush()
168 drawable.update(0,0,width,height)
169 if not checkboard:
170 pdb.plug_in_edge(image,drawables[2], 10, 0, 0) # amount, WRAP, SOBEL
171 pdb.plug_in_vinvert(image,drawables[2])
172 if image.parasite_find("gimp-comment"):
173 image.parasite.detach("gimp-comment")
174 image.attach_new_parasite("gimp-comment", PARASITE_PERSISTENT, """# conformal %s
175 code = \"\"\"
177 \"\"\"
178 constraint = \"\"\"
180 \"\"\"
181 xl = %f
182 xr = %f
183 yt = %f
184 yb = %f
185 grid = %f
186 checkboard = %d
187 gradient = "%s"
188 width = %d
189 height = %d
190 """ % (confversion, code, constraint, xl, xr, yt, yb, grid, checkboard, gradient, width, height))
191 if filename is None:
192 image.enable_undo()
193 gimp.Display(image)
194 gimp.displays_flush
195 else:
196 if filename.find('.xcf') > 0:
197 pdb.gimp_xcf_save(1, image, drawables[0], filename, filename)
198 else:
199 flat_layer = pdb.gimp_image_flatten(image)
200 pdb.gimp_file_save(image, flat_layer, filename, filename)
203 register(
204 "conformal_batch",
205 "Colour representation of a conformal map",
206 "Colour representation of a conformal map",
207 "Michael J Gruber",
208 "Michael J Gruber",
209 "2011",
213 (PF_INT, "width", "width", 512),
214 (PF_INT, "height", "height", 512),
215 (PF_TEXT, "code", "code", "w=z"),
216 (PF_TEXT, "constraint", "constraint", "p=True"),
217 (PF_FLOAT, "xl", "x left", -1.0),
218 (PF_FLOAT, "xr", "x right", 1.0),
219 (PF_FLOAT, "yt", "y top", 1.0),
220 (PF_FLOAT, "yb", "y bottom", -1.0),
221 (PF_FLOAT, "grid", "grid spacing", 1.0),
222 (PF_BOOL, "checkboard", "checker board grid", 0),
223 (PF_GRADIENT, "gradient", "gradient", "Full saturation spectrum CCW"),
224 (PF_FILE, "file", "file", "out.xcf.bz2"),
227 conformal_batch)
229 register(
230 "conformal",
231 "Colour representation of a conformal map",
232 "Colour representation of a conformal map",
233 "Michael J Gruber",
234 "Michael J Gruber",
235 "2012",
236 "<Toolbox>/File/Create/_Conformal ...",
239 (PF_INT, "width", "width", 512),
240 (PF_INT, "height", "height", 512),
241 (PF_TEXT, "code", "code", "w=z"),
242 (PF_TEXT, "constraint", "constraint", "p=True"),
243 (PF_FLOAT, "xl", "x left", -1.0),
244 (PF_FLOAT, "xr", "x right", 1.0),
245 (PF_FLOAT, "yt", "y top", 1.0),
246 (PF_FLOAT, "yb", "y bottom", -1.0),
247 (PF_FLOAT, "grid", "grid spacing", 1.0),
248 (PF_BOOL, "checkboard", "checker board grid", 0),
249 (PF_GRADIENT, "gradient", "gradient", "Full saturation spectrum CCW"),
252 conformal)
254 main()