Use list.sort's keyword parameters
[joel/kofoto.git] / src / packages / kofoto / gkofoto / pixbufloader.py
1 """This module contains the PixbufLoader class."""
2
3 __all__ = ["PixbufLoader"]
4
5 if __name__ == "__main__":
6     import pygtk
7     pygtk.require("2.0")
8 import gtk
9 import gobject
10 from kofoto.rectangle import Rectangle
11
12 def get_pixbuf_size(path):
13     """Get size of image at a given path.
14
15     Returns (width, height) or None on error.
16     """
17
18     pbl = PixbufLoader()
19     pbl.prepare(path, None)
20     while True:
21         loaded_bytes = pbl.load_some_more()
22         size = pbl.get_original_size()
23         if size is not None or loaded_bytes == 0:
24             break
25     pbl.cancel()
26     return size
27
28 class PixbufLoader:
29     """A pixbuf loader.
30
31     This class is a simple convenience wrapper around
32     gtk.gdk.PixbufLoader.
33     """
34
35     def __init__(self):
36         """Constructor."""
37
38         self._pixbuf_loader = None
39         self._pixbuf = None
40         self._original_size = None
41         self._size_limit = None
42         self._fp = None
43         self._loading_finished = False
44
45     def cancel(self):
46         """Cancel loading."""
47
48         self._clean_up()
49
50     def get_original_size(self):
51         """Get the on-disk size.
52
53         Returns a tuple (width, height) if known, otherwise None.
54         """
55
56         return self._original_size
57
58     def get_pixbuf(self):
59         """Get the pixbuf.
60
61         Returns the pixbuf if loading has finished successfully,
62         otherwise None.
63         """
64
65         return self._pixbuf
66
67     def load_some_more(self):
68         """Load some more of the pixbuf.
69
70         Call this function until it returns 0.
71
72         Returns the number of loaded bytes.
73         """
74
75         if self._loading_finished:
76             return 0
77         try:
78             data = self._fp.read(32768)
79             if data:
80                 self._pixbuf_loader.write(data)
81                 return len(data)
82             else:
83                 try:
84                     self._pixbuf_loader.close()
85                     self._pixbuf = self._pixbuf_loader.get_pixbuf()
86                 finally:
87                     # The loader has already been closed, so make sure
88                     # that it isn't closed again in _clean_up().
89                     self._pixbuf_loader = None
90         except (IOError, gobject.GError):
91             self._pixbuf = None
92             self._original_size = None
93         self._clean_up()
94         self._loading_finished = True
95         return 0
96
97     def prepare(self, path, limit):
98         """Prepare loading of an image into a pixbuf.
99
100         Call the load_some_more() method until it returns 0 to load
101         the pixbuf.
102
103         Arguments:
104
105         path         -- A path to the image.
106         limit        -- A tuple (width, height) with the size limit of
107                         the resulting pixbuf, or None. If None, a
108                         full-size pixbuf will be loaded.
109         """
110
111         if limit is not None:
112             # Avoid GTK bug triggered in self._pixbuf_loader.close() when
113             # using small image sizes:
114             limit = (max(limit[0], 30), max(limit[1], 30))
115         self._clean_up()
116         self._pixbuf = None
117         self._original_size = None
118         try:
119             self._fp = open(path, "rb")
120         except IOError:
121             self._loading_finished = True
122             return
123         self._loading_finished = False
124         self._size_limit = limit
125         self._pixbuf_loader = gtk.gdk.PixbufLoader()
126         self._pixbuf_loader.connect("size-prepared", self._size_prepared_cb)
127
128     def _clean_up(self):
129         if self._pixbuf_loader:
130             try:
131                 self._pixbuf_loader.close()
132             except gobject.GError:
133                 pass
134         self._pixbuf_loader = None
135         if self._fp:
136             self._fp.close()
137             self._fp = None
138
139     def _size_prepared_cb(self, pbloader, full_width, full_height):
140         self._original_size = (full_width, full_height)
141         if self._size_limit is None:
142             # Load full-sized image.
143             return
144         size = Rectangle(full_width, full_height).downscaled_to(
145             Rectangle(*self._size_limit))
146         self._pixbuf_loader.set_size(size.width, size.height)
147
148 ######################################################################
149
150 if __name__ == "__main__":
151     import sys
152
153     loader = PixbufLoader()
154     loader.prepare(sys.argv[1], (300, 200))
155     while loader.load_some_more() > 0:
156         pass
157     pixbuf = loader.get_pixbuf()
158     if pixbuf:
159         (owidth, oheight) = loader.get_original_size()
160         print "Loaded %dx%d pixbuf (original size: %dx%d)" % (
161             pixbuf.get_width(), pixbuf.get_height(), owidth, oheight)
162     else:
163         print "Error while loading pixbuf."
164     gtk.main()