Initial code commit
[darcs-mirror-distanceview.git] / distanceview.py
1 #!/usr/bin/python2.5
2 # -*- coding: utf-8 -*-
3
4 #
5 # © 2008 Joachim Breitner <mail@joachim-breitner.de>
6 #
7 #     This program is free software: you can redistribute it and/or modify
8 #     it under the terms of the GNU General Public License as published by
9 #     the Free Software Foundation, either version 3 of the License, or
10 #     (at your option) any later version.
11
12 #     This program is distributed in the hope that it will be useful,
13 #     but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #     GNU General Public License for more details.
16
17 #     You should have received a copy of the GNU General Public License
18 #     along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 #
21 # TODO:
22 # * Faster geometric algorithms (nearest point, containing facet)
23 # * Faster drawing (copying pixbufs to the X server)
24 # * Better morphs
25 # * Better interpolation
26
27
28
29 import pygtk
30 import gtk
31 import gtk.glade
32 import gobject
33
34 import Numeric
35 import math
36 import pickle
37 import time
38 import os.path
39
40 selection_distance = 10
41
42 # wir = (294, 123)
43 # feth = (256, 203)
44 # gack = (267, 252)
45 # mitte = (394, 186)
46 # vor_inge = (370,107)
47 # klspieli = (396, 32)
48 # fuchs = (318, 49)
49 # polizeimensch = (360,225)
50 # doelker = (390,261)
51 # preiss_oben = (402,324)
52 # tunnel = (518,356)
53
54 # start = mitte
55
56 # points = [
57 #     wir,
58 #     feth,
59 #     gack,
60 #     mitte,
61 #     klspieli,
62 #     fuchs,
63 #     polizeimensch,
64 #     doelker,
65 #     preiss_oben,
66 #     tunnel,
67 #     ]
68
69 # graph = [
70 #     (wir,feth),
71 #     (feth,gack),
72 #     (gack,polizeimensch),
73 #     (polizeimensch,mitte),
74 #     (mitte,vor_inge),
75 #     (vor_inge,klspieli),
76 #     (fuchs,klspieli),
77 #     (fuchs,wir),
78 #     (polizeimensch,doelker),
79 #     (doelker,preiss_oben),
80 #     (preiss_oben, tunnel),
81 #     (tunnel,mitte),
82 #     ]
83
84 def dist2((x1,y1),(x2,y2)):
85     return ((x1-x2)**2 + (y1-y2)**2)
86 def dist((x1,y1),(x2,y2)):
87     return math.sqrt((x1-x2)**2 + (y1-y2)**2)
88 def find_footpoint((p1,p2),(x,y)):
89     (x1,y1) = p1
90     (x2,y2) = p2
91     u = float((x-x1)*(x2-x1) + (y - y1)*(y2 - y1)) / float((x1-x2)**2 + (y1-y2)**2)
92     if u<0: u=0
93     if u>1: u=1
94     return (int(round(x1 + u*(x2-x1))),
95             int(round(y1 + u*(y2-y1))))
96 def convex(r, (r1,g1,b1), (r2,g2,b2)):
97     return ((1-r) * r1 + r * r2,
98             (1-r) * g1 + r * g2,
99             (1-r) * b1 + r * b2)
100
101 class Graph(object):
102     def __init__(self):
103         self.vertices = []
104         self.edges = []
105         self.start = None
106
107     def dump(self):
108         return (self.vertices, self.edges, self.start)
109
110     def load(self,dump):
111         (self.vertices, self.edges, self.start) = dump
112
113     def nearest_point(self, p):
114         if self.vertices:
115             return min(self.vertices, key=lambda v: dist(p,v))
116         else:
117             return (-100,-100)
118
119     def alone(self,p):
120         for (p1,p2) in self.edges:
121             if p1 == p or p2 == p:
122                 return False
123         return True
124
125     def delete_vertex(self,p):
126         assert self.alone(p)
127         self.vertices.remove(p)
128
129     def add_vertex(self, p):
130         assert not p in self.vertices
131         assert type(p[0]) == int and type(p[1]) == int
132         if not self.vertices:
133             self.start = p
134         self.vertices.append(p)
135
136     def has_edge(self,(p1,p2)):
137         return (p1,p2) in self.edges or (p2,p1) in self.edges
138
139     def toggle_edge(self,(p1,p2)):
140         assert p1 in self.vertices and p2 in self.vertices
141         if (p1,p2) in self.edges:
142             self.edges.remove((p1,p2))
143         elif (p2,p1) in self.edges:
144             self.edges.remove((p2,p1))
145         else:
146             self.edges.append((p1,p2))
147         
148 class DistanceView:
149     def __init__(self):
150         self.width = 300
151         self.height = 300
152         
153         self.selected_d = 0
154         self.last_update = 0
155         self.point_selected = None
156         self.hover_point = None
157
158         self.image = gtk.DrawingArea()
159         self.image.set_size_request(self.width, self.height)
160         self.image.add_events(gtk.gdk.BUTTON_PRESS_MASK |
161                         gtk.gdk.BUTTON_RELEASE_MASK |
162                         gtk.gdk.EXPOSURE_MASK |
163                         gtk.gdk.POINTER_MOTION_MASK)
164         self.image.connect("expose_event",self.do_expose_event_orig)
165         self.image.connect("button_press_event",self.do_button_press_event)
166         self.image.connect("motion_notify_event",self.do_motion_notify_event)
167
168         self.moved = gtk.DrawingArea()
169         self.moved.set_size_request(self.width, self.height)
170         self.moved.add_events(gtk.gdk.BUTTON_PRESS_MASK |
171                         gtk.gdk.BUTTON_RELEASE_MASK |
172                         gtk.gdk.EXPOSURE_MASK |
173                         gtk.gdk.POINTER_MOTION_MASK)
174         self.moved.connect("expose_event",self.do_expose_event_moved)
175
176         hbox1 = gtk.HBox()
177         hbox1.add(self.image)
178         hbox1.add(self.moved)
179
180         self.graph_edit = gtk.CheckButton('Edit graph')
181         edit_help = gtk.Button('Edit help')
182         edit_help.connect("clicked", self.show_edit_help)
183         vbox_edit = gtk.VBox()
184         vbox_edit.add(self.graph_edit)
185         vbox_edit.add(edit_help)
186
187         do_open = gtk.Button('Open Image')
188         do_save = gtk.Button('Save graph')
189         do_open.connect("clicked",self.do_open_dialog)
190         do_save.connect("clicked",self.do_save_dialog)
191         do_distance = gtk.Button('Calc Dist.')
192         do_heightmap = gtk.Button('Calc Heightmap.')
193         do_morph = gtk.Button('Calc Morph.')
194         do_all = gtk.Button('Calc All.')
195         do_distance.connect("clicked",self.do_recalc,self.recalc_distance)
196         do_heightmap.connect("clicked",self.do_recalc,self.recalc_heightmap)
197         do_morph.connect("clicked",self.do_recalc,self.recalc_morph)
198         do_all.connect("clicked",self.do_recalc,self.recalc_all)
199         self.do_buttons = [do_open, do_save, do_distance,
200                            do_heightmap, do_all, do_morph]
201
202         self.zoom = gtk.SpinButton()
203         self.zoom.set_range(1,10)
204         self.zoom.set_digits(1)
205         self.zoom.set_increments(1,1)
206         self.zoom.set_value(1)
207         hbox_zoom = gtk.HBox()
208         hbox_zoom.add(gtk.Label('Zoom:'))
209         hbox_zoom.add(self.zoom)
210
211         self.interpolator = gtk.combo_box_new_text()
212         self.interpolators = {
213                 'Stripes': self.interpolate_stripes,
214                 'Blocks': self.interpolate_blocks,
215                 'None': self.interpolate_none,
216             }
217         keys = self.interpolators.keys()
218         keys.sort()
219         for k in keys:
220             self.interpolator.append_text(k)
221         self.interpolator.set_active(keys.index('None'))
222         hbox_interpolator = gtk.HBox()
223         hbox_interpolator.add(gtk.Label('Interpolation:'))
224         hbox_interpolator.add(self.interpolator)
225         
226         vbox_morph = gtk.VBox()
227         vbox_morph.add(hbox_interpolator)
228         vbox_morph.add(hbox_zoom)
229
230         self.penalty = gtk.SpinButton()
231         self.penalty.set_range(1,10)
232         self.penalty.set_increments(1,1)
233         self.penalty.set_value(2)
234         hbox_penalty = gtk.HBox()
235         hbox_penalty.add(gtk.Label('Offroad penalty:'))
236         hbox_penalty.add(self.penalty)
237
238         vbox_dist = gtk.VBox()
239         vbox_dist.add(hbox_penalty)
240
241         self.show_heigthmap = gtk.CheckButton('Show heightmap')
242         self.show_heigthmap.props.active = True
243         self.show_heigthmap.connect('toggled', self.queue_draw)
244         vbox_heightmap = gtk.VBox()
245         vbox_heightmap.add(self.show_heigthmap)
246
247         hbox2 = gtk.HBox()
248         hbox2.pack_start(do_open, expand=False)
249         hbox2.pack_start(do_save, expand=False)
250         hbox2.pack_start(vbox_edit, expand=False)
251         hbox2.pack_start(vbox_dist, expand=False)
252         hbox2.pack_start(do_distance, expand=False)
253         hbox2.pack_start(vbox_heightmap, expand=False)
254         hbox2.pack_start(do_heightmap, expand=False)
255         hbox2.pack_start(vbox_morph, expand=False)
256         hbox2.pack_start(do_morph, expand=False)
257         hbox2.pack_start(do_all, expand=False)
258         
259         self.status = gtk.Label("Status")
260         
261         self.progress = gtk.ProgressBar()
262         self.reset_progress()
263
264         hbox3 = gtk.HBox()
265         hbox3.add(self.status)
266         hbox3.add(self.progress)
267
268         self.slider = gtk.HScale()
269         self.slider.set_range(0,self.width+self.height)
270         self.slider.connect("change_value",self.do_change_value)
271
272         vbox = gtk.VBox()
273         vbox.pack_start(hbox1, expand=True,fill=True)
274         vbox.pack_start(self.slider, expand=False)
275         vbox.pack_start(hbox2, expand=False)
276         vbox.pack_start(hbox3, expand=False)
277
278         self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
279         self.window.add(vbox)
280
281         
282         self.graph = Graph()
283         self.d = None
284         self.pixbuf = None
285         self.pixbuf_heightmap = None
286         self.pixbuf_moved = None
287         self.moved_zoom = None
288         if os.path.exists('Ehbühl.jpg'):
289             self.load_files('Ehbühl.jpg')
290         else:
291             self.do_open_dialog(None)
292         
293         self.window.show_all()
294
295     def do_expose_event_orig(self, widget, event):
296         gc = widget.window.new_gc()
297         gc.set_clip_rectangle(event.area)
298
299         if self.pixbuf:
300             widget.window.draw_pixbuf(gc, self.pixbuf, 0,0,0,0,-1,-1)
301         if self.pixbuf_heightmap and self.show_heigthmap.props.active:
302             widget.window.draw_pixbuf(gc, self.pixbuf_heightmap, 0,0,0,0,-1,-1)
303
304         if (self.d
305                 and not self.graph_edit.props.active
306                 and not self.progress.props.sensitive):
307             pb = self.equilines()
308             widget.window.draw_pixbuf(gc, pb, 0,0,0,0,-1,-1)
309
310
311         cr = widget.window.cairo_create()
312         cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
313         cr.clip()
314
315         cr.set_source_rgba(0,0.8,0,0.8)
316         for (s,t) in self.graph.edges:
317             cr.move_to(*s)
318             cr.line_to(*t)
319             cr.stroke()
320         for x,y in self.graph.vertices:
321             cr.arc(x,y,2,0, 2 * math.pi)
322             cr.fill()
323         if self.graph.start:
324             x,y = self.graph.start
325             cr.arc(x,y,5,0, 2 * math.pi)
326             cr.fill()
327
328         if self.graph_edit.props.active and self.point_selected:
329             x,y = self.point_selected
330             cr.set_source_rgba(0,0,1,1)
331             cr.arc(x,y,5,0, 2 * math.pi)
332             cr.fill()
333         
334         if self.graph_edit.props.active and self.hover_point:
335             x,y = self.hover_point
336             cr.set_source_rgba(0.5,0.5,1,1)
337             cr.arc(x,y,5,0, 2 * math.pi)
338             cr.fill()
339
340             if self.point_selected:
341                 if self.graph.has_edge((self.point_selected, self.hover_point)):
342                     cr.set_source_rgba(1,0.5,0.5,1)
343                 else:
344                     cr.set_source_rgba(0.5,0.5,1,1)
345                 cr.move_to(*self.point_selected)
346                 cr.line_to(*self.hover_point)
347                 cr.stroke()
348
349     def do_expose_event_moved(self, widget, event):
350         gc = widget.window.new_gc()
351         gc.set_clip_rectangle(event.area)
352
353         if self.pixbuf_moved:
354             widget.window.draw_pixbuf(gc, self.pixbuf_moved, 0,0,0,0,-1,-1)
355
356         cr = widget.window.cairo_create()
357         cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
358         cr.clip()
359         
360         if self.moved_zoom:
361             z = self.moved_zoom
362
363             cr.set_source_rgba(0,1,1,0.5)
364             cr.arc(self.width/2, self.height/2, self.selected_d / z, 0, 2 * math.pi)
365             cr.stroke()
366
367     def do_button_press_event(self, widget, event):
368         if self.graph_edit.props.active:
369             p = (int(round(event.x)),int(round(event.y)))
370             n = self.graph.nearest_point(p)
371
372             if event.button == 1:  #left click
373                 if dist(n,p) > selection_distance:
374                     self.graph.add_vertex(p)
375                     self.point_selected = p
376                 else:
377                     if self.point_selected == n:
378                         self.point_selected = None
379                     else:
380                         self.point_selected = n
381
382             elif event.button == 2: #middle click
383                 if dist(n,p) > selection_distance:
384                     pass
385                 else:
386                     self.graph.start = n
387
388             elif event.button == 3: #right click
389                 if self.point_selected:
390                     if dist(n,p) > selection_distance:
391                         self.graph.add_vertex(p)
392                         self.graph.toggle_edge((self.point_selected, p))
393                         self.point_selected = p
394                     else:
395                         if n == self.point_selected:
396                             if self.graph.alone(n):
397                                 self.graph.delete_vertex(n)
398                                 self.point_selected = None
399                         else:
400                             self.graph.toggle_edge((self.point_selected, n))
401             self.queue_draw()
402
403     def do_recalc(self, widget, func):
404         func()
405         self.queue_draw()
406
407     def equilines(self):
408         assert self.d
409         pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, self.width, self.height)
410         el = pb.get_pixels_array()
411         my_d = self.selected_d
412         (s_x,s_y) = self.graph.start
413         for x in range(max(0,int(s_x - my_d)), min(int(s_x + my_d), self.width)):
414             for y in range(max(0,int(s_y - my_d)), min(int(s_y + my_d), self.height)):
415                 if my_d - 5 <=  self.d[x,y] <= my_d + 5:
416                     a = 150
417                     if my_d - 3 <=  self.d[x,y] <= my_d + 3:
418                         a = 200
419                         if my_d - 1 <=  self.d[x,y] <= my_d + 1:
420                             a = 250
421                     el[y,x,:]= (0,255,255,a)
422         return pb
423
424     def do_motion_notify_event(self, widget, event):
425         if 0<=event.x<self.width and 0<=event.y<self.height:
426             p = (int(round(event.x)),int(round(event.y)))
427             if self.d:
428                 self.selected_d = self.d[p]
429                 self.status.set_text("(%d,%d): %d" % (event.x, event.y, self.selected_d))
430                 self.slider.set_value(self.selected_d)
431                
432                 if not self.graph_edit.props.active and not self.progress.props.sensitive:
433                     self.queue_draw()
434         
435             if self.graph_edit.props.active:
436                 n = self.graph.nearest_point(p)
437                 if dist(n,p) < selection_distance:
438                     self.hover_point = n
439                 else:
440                     self.hover_point = None
441                 self.queue_draw()
442     
443     def do_change_value(self, widget, scroll, value):
444         value = int(value)
445         if value != self.selected_d:
446             self.selected_d = value
447             self.status.set_text("Selected: %d" % (self.selected_d))
448             self.queue_draw()
449         return False
450
451     def show_edit_help(self,widget):
452         help = gtk.MessageDialog(parent = self.window,
453                                 type = gtk.MESSAGE_INFO,
454                                 buttons = gtk.BUTTONS_OK)
455         help.props.text = '''Graph editing:
456 Left click to select/unselect a vertex.
457 Left click any where else to add a new vertex.
458 Middle click to select center vertex.
459 Right click on the selected vertex to delete it, if it has no edges anymore.
460 Right click on another vertex to add or remove the edge.
461 Right click anywhere ot adda vertex and an edge in one go.'''
462         help.run()
463         help.destroy()
464
465     def queue_draw(self, widget=None):
466         self.image.queue_draw()
467         self.moved.queue_draw()
468
469     def update_gui(self, pulse=False):
470         now = time.time()
471         if now - self.last_update > 0.1:
472             if pulse:
473                 self.progress.pulse()
474             while gtk.events_pending():
475                 gtk.main_iteration(False)
476             self.last_update = now
477
478     def prepare_progress(self):
479         self.progress.props.sensitive = True
480         self.progress.set_text('')
481         self.progress.set_fraction(0)
482
483         for button in self.do_buttons:
484             button.props.sensitive = False
485
486     def reset_progress(self):
487         self.progress.props.sensitive = False
488         self.progress.set_text('...idle...')
489         self.progress.set_fraction(0)
490
491         for button in self.do_buttons:
492             button.props.sensitive = True
493         self.update_gui()
494
495     def recalc_distance(self):
496         far = self.penalty.get_value_as_int() * (self.width + self.height)
497
498         self.prepare_progress()
499         self.progress.set_text('Preparing array')
500         self.update_gui()
501         d = Numeric.zeros((self.width,self.height), 'i')
502         for x in range(self.width):
503             self.update_gui(True)
504             for y in range(self.height):
505                 d[x,y] = far
506
507         d[self.graph.start] = 0
508         todo = [self.graph.start]
509
510         # unoptimized djikstra
511         self.progress.set_text('Djikstra')
512         self.update_gui()
513
514         while todo:
515             s = min(todo, key=lambda e: d[e])
516             todo.remove(s)
517             for t in ([t for (s2,t) in self.graph.edges if s2 == s ] +
518                       [t for (t,s2) in self.graph.edges if s2 == s ]):
519                 if d[s] + dist(s,t) < d[t]:
520                     d[t] = d[s] + dist(s,t)
521                     todo.append(t)
522             self.update_gui(True)
523
524         self.progress.set_text('Off-Graph')
525         for x in range(self.width):
526             self.progress.set_fraction(float(x)/float(self.width))
527             self.update_gui()
528             for y in range(self.height):
529                 p = (x,y)
530                 if d[p]==far:
531                     # Nearest footpoint:
532                     #(p1,p2) = min(graph, key = lambda e: dist2(find_footpoint(e,p),p))
533                     #footpoint = find_footpoint((p1,p2),p)
534                     #if d[footpoint] == far:
535                     #    d[footpoint] = min(d[p1] + dist(p1,footpoint),
536                     #                       d[p2] + dist(p2,footpoint))
537                     #d[p] = d[footpoint] + dist(p, footpoint)
538
539                     # Best point:
540                     #d[x,y] = min(map (lambda p1: d[p1] + 5*dist(p,p1), points))
541
542                     # Best footpoint:
543                     for (p1,p2) in self.graph.edges:
544                         f = find_footpoint((p1,p2),p)
545                         if d[f] == far:
546                             d[f] = min(d[p1] + dist(p1,f), d[p2] + dist(p2,f))
547                         d[p] = min(d[p], d[f] + self.penalty.get_value_as_int() * dist(f,p))
548
549         #self.progress.set_text('Dumping data')
550         #self.progress.set_fraction(0)
551         #self.update_gui()
552         #pickle.dump(d, file('distance_map.data','w'))
553
554         self.d = d
555         
556         self.reset_progress()
557     
558     def recalc_heightmap(self):
559         if self.d is None:
560             self.recalc_distance()
561         d = self.d
562         
563         self.prepare_progress()
564         self.progress.set_text('Recreating heightmap')
565         self.pixbuf_heightmap = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, self.width, self.height)
566         i = self.pixbuf_heightmap.get_pixels_array()
567         #i = Numeric.zeros((self.height,self.width,4), 'b')
568         for x in range(self.width):
569             self.progress.set_fraction(float(x)/float(self.width))
570             self.update_gui()
571             for y in range(self.height):
572                 a = 255 - min(d[x,y]//3,255)
573                 i[y,x,:]= (255,0,0,a)
574
575         #self.progress.set_text('Writing height data')
576         #self.update_gui()
577         #pickle.dump(i, file('height_map.data','w'))
578
579         self.reset_progress()
580
581     def recalc_morph(self):
582         if self.d is None:
583             self.recalc_distance()
584         d = self.d
585
586         z = self.zoom.get_value()
587
588         self.prepare_progress()
589         self.progress.set_text('Calculating transformation')
590         f = Numeric.zeros((self.width,self.height,2),'i')
591         (cx,cy) = (self.width/2, self.height/2)
592         (sx,sy) = self.graph.start
593         for x in range(self.width):
594             self.progress.set_fraction(float(x)/float(self.width))
595             self.update_gui()
596             for y in range(self.height):
597                 p = (x,y)
598                 size = dist(p,self.graph.start) * z
599                 if size>0.05:
600                     npx = int(round(cx + (x-sx)*d[p]/size))
601                     npy = int(round(cy + (y-sy)*d[p]/size))
602                     if 0<= npx < self.width and 0<= npy < self.height:
603                         f[npx,npy,:] = (y,x)
604                 else:
605                     f[cx,cy,:] = (y,x)
606         
607         #self.progress.set_text('Writing transformation data')
608         #self.update_gui()
609         #pickle.dump(f,file('function.data','w'))
610
611         self.f = f
612
613         self.reset_progress()
614
615         self.prepare_progress()
616         self.progress.set_text('Calculating Morphed Image')
617         m = Numeric.zeros((self.height,self.width,3),'b')
618         o = self.pixbuf.get_pixels_array()
619
620         self.interpolate(o,m,f)
621
622         #self.progress.set_text('Writing morphed image')
623         #self.update_gui()
624         #pickle.dump(m,file('output.data','w'))
625
626
627         self.moved_zoom = z
628         self.m = m
629         self.pixbuf_moved = gtk.gdk.pixbuf_new_from_array(m, gtk.gdk.COLORSPACE_RGB, 8)
630         self.reset_progress()
631
632     def interpolate(self, o, m, f):
633         choice = self.interpolator.get_active_text()
634         self.interpolators[choice](o, m, f)
635
636     def interpolate_blocks(self, o, m, f):
637         for x in range(self.width):
638             self.progress.set_fraction(float(x)/float(self.width))
639             self.update_gui()
640
641             for y in range(self.height):
642                 if f[x, y] != (0,0):
643                     m[y,x] = o[f[x,y]]
644                     #print "Pixel data found directly"
645                 else:
646                     done = False
647                     for i in range(1,3):
648                         for tx in range(x-i,x+i+1):
649                             if 0 <= tx < self.width:
650                                 for ty in range(y-i,y+i+1):
651                                     if 0 <= ty < self.height:
652                                         if f[tx, ty] != (0,0):
653                                             m[y,x] = o[f[tx,ty]]
654                                             done = True
655                                             #print "Neighboring pixels asked (%d)" % i
656                                             break
657                             if done: break
658                         if done: break
659                     #if not done:
660                     #    print "Could not find pixel to take color from."
661                     #    m[y,x] = (255,255,0)
662
663     def interpolate_stripes(self, o, m, f):
664         for x in range(self.width):
665             self.progress.set_fraction(float(x)/float(self.width))
666             self.update_gui()
667
668             prev = None
669             for y in range(self.height):
670                 if f[x,y] != (0,0):
671                     m[y,x] = o[f[x,y]]
672                     if prev:
673                         for my in range(prev+1,y):
674                             m[my,x] = convex(
675                                         float(my-prev)/float(y-prev),
676                                         m[prev,x],
677                                         m[y,x])
678                     prev = y
679
680     def interpolate_none(self, o, m, f):
681         for x in range(self.width):
682             self.progress.set_fraction(float(x)/float(self.width))
683             self.update_gui()
684
685             for y in range(self.height):
686                 if f[x,y] != (0,0):
687                     m[y,x] = o[f[x,y]]
688
689     def recalc_all(self):
690         self.recalc_distance()
691         self.recalc_heightmap()
692         self.recalc_morph()
693
694     def do_open_dialog(self, widget):
695         dialog = gtk.FileChooserDialog(title = "Open Image",
696                                parent =  self.window,
697                                action = gtk.FILE_CHOOSER_ACTION_OPEN,
698                                buttons = (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
699                                           gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
700
701         if dialog.run():
702             filename = dialog.get_filename()
703             if filename:
704                 self.load_files(filename)
705         dialog.destroy()
706
707     def do_save_dialog(self, widget):
708         assert self.filename, "No file open at the moment"
709         self.save_files()
710         help = gtk.MessageDialog(parent = self.window,
711                                 type = gtk.MESSAGE_INFO,
712                                 buttons = gtk.BUTTONS_OK)
713         help.props.text = 'Graph and calculated data saved'
714         help.run()
715         help.destroy()
716
717     def load_files(self, filename):
718         self.pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
719         self.width = self.pixbuf.props.width
720         self.height = self.pixbuf.props.height
721         self.filename = filename
722
723         self.graph = Graph()
724         self.d = None
725         self.pixbuf_heightmap = None
726         self.pixbuf_moved = None
727         self.moved_zoom = None
728
729         if os.path.exists(filename+'.graph'):
730             data = pickle.load(file(filename + '.graph'))
731             self.graph.load(data)
732
733         if os.path.exists(filename+'.data'):
734             data = pickle.load(file(filename + '.data'))
735
736             if 'd' in data:
737                 self.d = data['d']
738             if 'i' in data and data['i']:
739                 self.pixbuf_heightmap = gtk.gdk.pixbuf_new_from_array(data['i'],
740                                          gtk.gdk.COLORSPACE_RGB, 8)
741             if 'm' in data and data['m']:
742                 self.m = data['m']
743                 self.pixbuf_moved = gtk.gdk.pixbuf_new_from_array(self.m,
744                         gtk.gdk.COLORSPACE_RGB, 8)
745             if 'mz' in data:
746                 self.moved_zoom = data['mz']
747             if 'penalty' in data:
748                 self.penalty.set_value(data['penalty'])
749
750     def save_files(self):
751         assert self.filename, "No file open at the moment"
752
753         pickle.dump(self.graph.dump(), file(self.filename + '.graph','w'))
754
755         data = {}
756         data['d'] = self.d
757         if self.pixbuf_heightmap:
758             data['i'] = self.pixbuf_heightmap.get_pixels_array()
759         if self.pixbuf_moved:
760             # Re-extracting pixel array from RGB without alpha
761             # and re-inserting causes strange shift, so let’s
762             # remember the array directly
763             data['m'] = self.m
764         data['mz'] = self.moved_zoom
765         data['penalty'] = self.penalty.get_value_as_int()
766
767         pickle.dump(data, file(self.filename + '.data','w'))
768
769     def main(self):
770         gtk.main()
771
772
773 if __name__ == "__main__":
774     app = DistanceView()
775     app.main()
776
777 # vim:ts=4:sw=4:sts=4:et
778