Left: | ||
Right: |
OLD | NEW |
---|---|
(Empty) | |
1 # coding: utf-8 | |
2 | |
3 # This Source Code Form is subject to the terms of the Mozilla Public | |
4 # License, v. 2.0. If a copy of the MPL was not distributed with this | |
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
6 | |
7 import os | |
8 import subprocess | |
9 import threading | |
10 import errno | |
11 import logging | |
12 from StringIO import StringIO | |
13 | |
14 try: | |
15 from PIL import Image | |
16 except ImportError: | |
17 import Image | |
18 | |
19 use_pngout = True | |
20 | |
21 class Pngout: | |
22 def __init__(self, image): | |
23 args = ['pngout', '-', '-', '-q'] | |
24 | |
25 # Preserve mode for grayscale images. pngout tends to convert | |
26 # everyting to palette. However, the toolbar icons for Safari | |
27 # require the grayscale+alpha mode. Moreover, pngout seems to | |
28 # generate smaller files when forced to preserve grayscale mode. | |
29 if image.mode == 'LA' and any(px < 0xff for px in image.split()[1].getdata() ): | |
30 args.append('-c4') # grayscale+alpha | |
31 elif Image.getmodebase(image.mode) == 'L': | |
32 args.append('-c0') # grayscale | |
33 | |
34 self._pngout = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subproce ss.PIPE) | |
35 | |
36 # Writing will block when the buffer is full until we read more data | |
37 # from the output. Reading the output will block when the input isn't | |
38 # complete yet. So we have to use threads to do both at the same time. | |
39 self._thread = threading.Thread(target=self._run_thread, args=(image,)) | |
40 self._thread.daemon = True | |
41 self._thread.start() | |
42 | |
43 # This is supposed to be a file-like object, reading the compressed PNG file. | |
44 # So proxy methods like read() to the stdout of the underlying subprocess. | |
45 def __getattr__(self, name): | |
46 return getattr(self._pngout.stdout, name) | |
47 | |
48 def _run_thread(self, image): | |
49 image.save(self._pngout.stdin, 'PNG') | |
50 self._pngout.stdin.close() | |
51 | |
52 def close(self): | |
53 self._thread.join() | |
54 self._pngout.stdout.close() | |
55 self._pngout.wait() | |
56 | |
57 class ImageCompressor: | |
58 use_pngout = True | |
59 | |
60 def make_uncompressed_file(self, image, filename): | |
61 file = StringIO() | |
62 file.name = filename # Set the 'name' attribute, that PIL can determine | |
kzar
2015/02/10 09:29:54
Nitpick: Comment should read "...so that PIL can..
Sebastian Noack
2015/02/10 09:57:38
Done.
| |
63 # the correct image type based on the file extension | |
64 image.save(file) | |
65 file.seek(0) | |
66 | |
67 return file | |
68 | |
69 def make_file(self, image, filename): | |
70 if self.use_pngout and os.path.splitext(filename)[1].lower() == '.png': | |
71 try: | |
72 return Pngout(image) | |
73 except OSError, e: | |
74 if e.errno != errno.ENOENT: | |
75 raise | |
76 | |
77 logging.warning("Couldn't find 'pngout', can't compress images") | |
78 self.use_pngout = False | |
79 | |
80 return self.make_uncompressed_file(image, filename) | |
81 | |
82 image_to_file = ImageCompressor().make_file | |
OLD | NEW |