| 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, so that PIL can determine | |
| 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 |