root/mactorii.py

Revision 76, 12.7 kB (checked in by steve, 7 months ago)

small code changes

  • Property svn:keywords set to Rev
Line 
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 mactorii.py
5
6 Created by Shu Ning Bian on 2007-09-23.
7 Copyright (c) 2007 . All rights reserved.
8 Licensed for distribution under the GPL version 2, check COPYING for details.
9 """
10
11 import sys
12 import os
13 import Image
14 import math
15 import shutil
16 import Tkinter
17 import tkFileDialog
18 import time
19
20 import wavelet
21 import config
22
23 from pyglet.gl import *
24 from pyglet import window
25 from pyglet import image as pyglet_image
26 from pyglet import clock
27 from pyglet.window import key
28 from pyglet.window import mouse
29 from pyglet import font
30
31 version="$Rev$".split(' ')[1]
32
33 class MactoriiApplication:
34         renderables_key_func = lambda x : 0
35
36         files = None
37         win = None
38         # key is the filename, value is another dictionary, with keys
39         # surface, signature, size, cluster key
40         images = dict()
41         unloaded = None
42         renderables = None
43
44         yoffset = 0
45         xoffset = 0
46         xmotion = 0
47
48         rows = 1
49         cols = 1
50                
51         clickx = 0
52         clicky = 0
53
54         hoverx = 0
55         hovery = 0
56
57         selected = None
58         hovering_over = None
59
60         last_deleted = []
61
62         fps_display = None
63
64         display_picture = None
65
66         root = None
67        
68         def on_mouse_motion(self,x,y,dx,dy):
69                 self.hoverx = x
70                 self.hovery = y
71                
72         def toggle_full_view(self):
73                 """enters into full view if possible"""
74                
75                 if self.hovering_over != None:
76                         if self.display_picture != None:
77                                 self.display_picture = None
78                         elif self.hovering_over != None:
79                                 im = Image.open(self.hovering_over)
80                                 im.thumbnail((self.win.width, self.win.height),Image.ANTIALIAS)
81                                 im = im.convert("RGB")
82                                 im = im.transpose(Image.FLIP_TOP_BOTTOM)
83                        
84                                 self.display_picture = pyglet_image.ImageData(im.size[0],im.size[1],"RGB",im.tostring())
85                        
86         def on_mouse_press(self,x, y, button, modifiers):
87                 self.toggle_full_view()
88                        
89         def on_mouse_release(self,x,y,button, modifiers):
90                
91                 return
92                 if button == mouse.LEFT:
93                         self.clickx = -1
94                         self.clicky = -1
95                                
96         def on_key_release(self,symbol, modifier):
97                 if symbol == key.LEFT:
98                         self.xmotion = 0
99                
100                 if symbol == key.RIGHT:
101                         self.xmotion = 0
102                        
103         def on_key_press(self,symbol, modifier):
104                 if symbol == key.LEFT:
105                         self.xmotion = 10
106                                        
107                 if symbol == key.RIGHT:
108                         self.xmotion -= 10
109
110                 if modifier == key.MOD_SHIFT:
111                         self.xmotion*=4
112
113                 if self.hovering_over != None:
114                         if symbol == key.D:
115                                 assert self.images.has_key(self.hovering_over)
116                                 del self.images[self.hovering_over]
117                                 self.update_renderables()
118                                 shutil.move(self.hovering_over, "%s/%s"%(os.path.basename(self.hovering_over),config.trash_dir))
119                                 self.last_deleted.append(self.hovering_over)
120                                
121                                 self.display_picture = None
122                        
123                 if symbol == key.U and len(self.last_deleted) > 0:
124                         self.last = self.last_deleted.pop()
125                         shutil.move("%s/%s"%(config.trash_dir, os.path.basename(last)), self.last)
126                         self.load_file(last)
127                         self.update_renderables()
128                        
129                 if symbol == key.F:
130                         if self.fps_display:
131                                 self.fps_display = None
132                         else:
133                                 self.fps_display = clock.ClockDisplay()         
134                                
135                 if symbol == key.C:
136                         self.cluster_renderables()
137                        
138                 if symbol == key.W and modifier == key.MOD_CTRL:
139                         sys.exit(0)
140                        
141                 if symbol == key.Q and modifier == key.MOD_COMMAND:
142                         sys.exit(0)
143                        
144                 if symbol == key.Q:
145                         sys.exit(0)
146                
147                 if symbol == key.V:
148                         self.toggle_full_view()
149                        
150                 if symbol == key.S:
151                         if self.hovering_over != None:
152                                 print "sorting by %s"%(self.hovering_over)
153                                 self.selected = self.images[self.hovering_over]
154                                 self.renderables_key_func = self.sort_func
155                                 self.update_renderables()
156                                 self.xoffset = 0
157                
158                 if symbol == key.O:
159                         dirname = tkFileDialog.askdirectory(parent=self.root,initialdir="~",title='Please select a directory')
160
161                         if len(dirname ) > 0:
162                                 import dircache
163                                 ls = dircache.listdir(dirname)
164                                 ls = list(ls)
165                                 dircache.annotate(dirname, ls)
166
167                                 files = ["%s%s%s"%(dirname,os.path.sep,e) for e in ls if not e.endswith('/')]
168                                 self.unloaded = list(files)
169                                
170                                 self.images = dict()
171                                        
172                 if symbol == key.R:
173                         self.unloaded = list(files)
174                         self.images = dict()
175                        
176         def strip_width(self):
177                 """returns the width of the strip in pixels"""
178                 return math.ceil(1.0 * len(self.files)/self.rows) * config.crop_size
179                
180         def on_resize(self,width, height):
181                 """Recomputes yoffset and row"""
182                 p = self.xoffset/self.strip_width()
183                
184                 self.rows = int(height/config.crop_size)
185                 self.cols = math.floor(len(self.files)/self.rows)+1
186                
187                 self.yoffset = (height - self.rows * config.crop_size)/2
188                 self.yoffset = height-self.yoffset-config.crop_size
189                
190                 # compute the new xoffset
191                 self.xoffset = p * self.strip_width()
192                
193                 self.toggle_full_view()
194                 self.toggle_full_view()
195
196         def cluster_func(self,item):
197                 return self.images[item[0]]['cluster key']
198
199         def images_to_renderables(self,images):
200                 """returns images.items() as a set of (filename, python surface, image size)"""
201                 return [(filename, (data['surface'], data['size'])) for filename, data in images.items()]
202
203         def cluster_renderables(self):
204                 """cluster renderables by their score against the 2 base lines"""
205                 self.renderables_key_func = self.cluster_func
206                 key = 0
207                 filename_sigs = [(filename, data['signature']) for filename, data in self.images.items()]
208                 seed = filename_sigs[0][0]
209                 del filename_sigs[0]
210
211                 done = False
212                 while len(filename_sigs):
213                         key+=1
214                         sig = self.images[seed]['signature']
215                         filename_sigs.sort(key = lambda x: -wavelet.signature_compare(sig, x[1]))
216                         f = filename_sigs[0][0]
217                         self.images[f]['cluster key']=key
218                         del filename_sigs[0]
219                         seed = f
220                 self.update_renderables()
221                
222         def update_renderables(self,sort=True):
223                 if self.renderables:
224                         n = self.images_to_renderables(self.images)
225                         if len(n) != len(self.renderables):
226                                 # images were added, so just re-assign renderables
227                                 self.renderables = n
228                         elif len(n) < len(self.renderables):
229                                 # images were removed, we need to remove them from renderables too
230                                 self.renderables = n
231                                
232                 else:
233                         self.renderables = self.images_to_renderables(self.images)
234                        
235                 if sort:
236                         self.renderables.sort(key=self.renderables_key_func)
237
238         def sort_func(self,item):
239                 assert self.selected != None
240                
241                 selected_sig = self.selected['signature']
242                 item_sig = self.images[item[0]]['signature']
243                
244                 return -wavelet.signature_compare(selected_sig, item_sig)
245
246                
247         def load_file(self,file):
248                 """loads the files given in the command line"""
249                
250                 #print "processing file: %s"%(file)
251                 
252                 try:
253                         wi = wavelet.open(file)
254                 except:
255                         print "can't load file: %s"%(file)
256                         return
257                        
258                 sig = wi.get_signature()
259                
260                 # get the pre-loaded image from wavelet
261                 im = wi.im
262                 w,h = im.size
263
264                 # resize the image so the smallest dimension is config.crop_size       
265                 s=0
266                 if w > h:
267                         s = 1.0*config.crop_size/h
268                 else:
269                         s = 1.0*config.crop_size/w
270                
271                 w = int(w*s*1.3)
272                 h = int(h*s*1.3)
273
274                 im=im.resize((w, h), Image.ANTIALIAS)
275                
276                 # crop out the centre crop_size square to use a thumbnail
277                 midx = w/2
278                 midy = h/2
279                 box = (midx-config.crop_size/2, midy-config.crop_size/2, midx+config.crop_size/2, midy+config.crop_size/2)
280                 im=im.crop(box)
281                
282                 # make a pyglet image out of it
283                 psurf = image_to_psurf(im)
284                
285                 # add to our dictionary
286                 self.images[ file ] = {'surface':psurf, 'signature':sig, 'size':wi.size, 'cluster key':0}
287         def to_unicode(self,s):
288                 return unicode(s, 'utf-8', 'ignore')
289                
290         def setup_window(self):
291                 """sets up our window"""
292                 win = window.Window(resizable=True, visible=False, caption='mactorii v0.%s'%(version))
293                
294                 win.push_handlers(self.on_resize)
295                 win.push_handlers(self.on_key_press)
296                 win.push_handlers(self.on_key_release)
297                 win.push_handlers(self.on_mouse_press)
298                 win.push_handlers(self.on_mouse_release)
299                 win.push_handlers(self.on_mouse_motion)
300                
301                 glEnable(GL_BLEND)
302                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
303                
304                 return win
305                
306         def setup_font(self):
307                 """sets up fonts"""
308                 return font.load(config.font_name, config.font_size, bold=True)
309
310         def is_over_image(self,x, y, mousex, mousey):
311                 if mousex < 0 or mousey < 0:
312                         return False
313                        
314                 if mousex < x or mousex > x + config.crop_size:
315                         return False
316                        
317                 if mousey < y or mousey > y + config.crop_size:
318                                 return False
319                                        
320                 return True
321                
322         def setup_trash(self):
323                 try:
324                         os.mkdir(config.trash_dir)
325                 except:
326                         return
327                
328         def find_common_signature(self,signatures):
329                 common_sig = [set()]*config.bands
330                
331                 for i in xrange(len(signatures)):
332                         for b in xrange(config.bands):
333                                 if i == 0:
334                                         common_sig[b] = signatures[i][b]
335                                 else:
336                                         common_sig[b] = common_sig[b].intersection(signatures[i-1][b])
337                                                                
338                 return common_sig
339                
340         def main(self):
341                 # order is important here. setup_window must be setup first! Likewise with font
342                 self.win = self.setup_window()
343                 ft = self.setup_font()
344                
345                 self.root = Tkinter.Tk()
346                 self.root.withdraw()
347                
348                 if (sys.platform != "win32") and hasattr(sys, 'frozen'):
349                         self.root.tk.call('console', 'hide')
350                                        
351                 if len(sys.argv) < 2:
352                         dirname = tkFileDialog.askdirectory(parent=self.root,initialdir="~",title='Please select a directory')
353                                                
354                         if len(dirname ) > 0:
355                                 import dircache
356                                 ls = dircache.listdir(dirname)
357                                 ls = list(ls)
358                                 dircache.annotate(dirname, ls)
359                                
360                                 self.files = ["%s%s%s"%(dirname,os.path.sep,e) for e in ls if not e.endswith('/')]
361                         else:
362                                 sys.exit(1)
363                 else:           
364                         self.files = sys.argv[1:]
365                                
366                 self.win.set_visible()
367                 self.setup_trash()     
368                
369                 assert self.win != None
370                 assert ft != None
371                
372                 # generate our oft used black background
373                 image_pattern = pyglet_image.SolidColorImagePattern((0,0,0,1))
374        
375                 # load the resizer graphic
376                 resizer = image_to_psurf(Image.open('resizer.jpg'))
377
378                 # limit fps to reduce cpu usage
379                 clock.set_fps_limit(config.fps)
380        
381                 # start loading files
382                 self.win.set_visible()
383                 self.unloaded = list(self.files)
384                 self.update_renderables()
385                 print "loading %d files"%(len(self.unloaded))
386                 start_time = time.time()
387                 while not self.win.has_exit:
388                         time_passed = clock.tick()
389                        
390                         self.win.dispatch_events()
391                         glClear(GL_COLOR_BUFFER_BIT)
392                        
393                         if len(self.unloaded) > 0:
394                                 f = self.unloaded.pop()
395                                 t = font.Text(ft, self.to_unicode(f), config.text_yoffset, config.text_yoffset)
396                                 t.draw()                       
397                                 #win.flip()
398                                 
399                                 self.load_file(f)
400                                
401                                 if len(self.unloaded) %3 == 0:
402                                         self.update_renderables(sort=False)
403
404                                 if len(self.unloaded) == 0:
405                                         print "loading took %f seconds"%(time.time()-start_time)
406                                        
407                         else:
408                                 # raise Exception
409                                 pass
410                        
411                         # if we need to display a full image, do so
412                         if self.display_picture != None:
413                                 w = self.display_picture.width
414                                 h = self.display_picture.height
415                                 self.display_picture.blit((self.win.width-w)/2,(self.win.height-h)/2)
416                                 self.win.flip()
417                                 continue
418                        
419                         # adjust the xoffset if required               
420                         if self.xmotion < 0:
421                                 if self.strip_width() + self.xoffset > self.win.width:
422                                         self.xoffset+=self.xmotion * time_passed / config.xmotion_time
423                                        
424                         if self.xmotion > 0:
425                                 if self.xoffset < 0:
426                                         self.xoffset+=self.xmotion * time_passed / config.xmotion_time
427
428                         # clamp xoffset to 0 if xoffset is > 0
429                         self.xoffset = min(self.xoffset, 0)
430                        
431                         # render the tiles
432                         x = self.xoffset
433                         y = self.yoffset
434                         pix_name = None
435                         pix_size = None
436                         drawn = 0
437                         self.hovering_over = None
438                         blit_position = ()
439                         for filename, image in self.renderables:
440                                 img = image[0]
441                                
442                                 if ( x >= -config.crop_size and x < self.win.width):
443                                         img.blit(x,y)                           
444                                        
445                                         # if the cursor is over this tile, render some information
446                                         if self.is_over_image(x,y,self.hoverx, self.hovery) and (not pix_name or filename != pix_name.text):
447                                                 # draw some information
448                                                 pix_size = font.Text(ft,"%dx%d"%(image[1][0], image[1][1]), x, y+config.text_yoffset)
449                                                 pix_name = font.Text(ft, self.to_unicode(os.path.basename(filename)), x, y+config.text_yoffset+int(pix_size.height))                                           
450                                                
451                                                 # calculate the black background required to make text show up
452                                                 # width needs to be in integer multiples of tile size
453                                                 w = max(pix_size.width, pix_name.width)
454                                                 w = max(config.crop_size, (w/config.crop_size+1)*config.crop_size)
455                                                        
456                                                 w = int(w)
457                                                 h = int(pix_name.height+pix_size.height+config.text_yoffset)
458                                                 text_bg = image_pattern.create_image(w,h)
459                                                 blit_position=(x, y)
460                                        
461                                                 # remember which image we are hovering over so when a click is seen
462                                                 # we know which image to display
463                                                 self.hovering_over = filename
464
465                                 drawn+=1
466                                 y-=config.crop_size
467                                
468                                 if drawn % self.rows == 0:
469                                         x+=config.crop_size
470                                         y = self.yoffset
471
472                         # everything drawn here is drawn over everything else
473                         if self.fps_display:
474                                 self.fps_display.draw()
475                        
476                         if self.hovering_over:
477                                 text_bg.blit(blit_position[0], blit_position[1])
478                                 pix_name.draw()
479                                 pix_size.draw()
480                
481                         w = self.win.width
482                         w = w - resizer.width
483                         resizer.blit(w,0)
484
485                         self.win.flip()
486        
487 def     image_to_psurf(im):
488         im = im.transpose(Image.FLIP_TOP_BOTTOM)
489         return pyglet_image.ImageData(im.size[0],im.size[1],"RGB",im.tostring())
490
491 if __name__ == '__main__':
492         app = MactoriiApplication()
493         app.main()
Note: See TracBrowser for help on using the browser.