 Issue 4663693082099712:
  Issue 1897 - Compress auto-generated images with pngout  (Closed)
    
  
    Issue 4663693082099712:
  Issue 1897 - Compress auto-generated images with pngout  (Closed) 
  | Index: imageCompression.py | 
| =================================================================== | 
| new file mode 100644 | 
| --- /dev/null | 
| +++ b/imageCompression.py | 
| @@ -0,0 +1,82 @@ | 
| +# coding: utf-8 | 
| + | 
| +# This Source Code Form is subject to the terms of the Mozilla Public | 
| +# License, v. 2.0. If a copy of the MPL was not distributed with this | 
| +# file, You can obtain one at http://mozilla.org/MPL/2.0/. | 
| + | 
| +import os | 
| +import subprocess | 
| +import threading | 
| +import errno | 
| +import logging | 
| +from StringIO import StringIO | 
| + | 
| +try: | 
| + from PIL import Image | 
| +except ImportError: | 
| + import Image | 
| + | 
| +use_pngout = True | 
| + | 
| +class Pngout: | 
| + def __init__(self, image): | 
| + args = ['pngout', '-', '-', '-q'] | 
| + | 
| + # Preserve mode for grayscale images. pngout tends to convert | 
| + # everyting to palette. However, the toolbar icons for Safari | 
| + # require the grayscale+alpha mode. Moreover, pngout seems to | 
| + # generate smaller files when forced to preserve grayscale mode. | 
| + if image.mode == 'LA' and any(px < 0xff for px in image.split()[1].getdata()): | 
| + args.append('-c4') # grayscale+alpha | 
| + elif Image.getmodebase(image.mode) == 'L': | 
| + args.append('-c0') # grayscale | 
| + | 
| + self._pngout = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) | 
| + | 
| + # Writing will block when the buffer is full until we read more data | 
| + # from the output. Reading the output will block when the input isn't | 
| + # complete yet. So we have to use threads to do both at the same time. | 
| + self._thread = threading.Thread(target=self._run_thread, args=(image,)) | 
| + self._thread.daemon = True | 
| + self._thread.start() | 
| + | 
| + # This is supposed to be a file-like object, reading the compressed PNG file. | 
| + # So proxy methods like read() to the stdout of the underlying subprocess. | 
| + def __getattr__(self, name): | 
| + return getattr(self._pngout.stdout, name) | 
| + | 
| + def _run_thread(self, image): | 
| + image.save(self._pngout.stdin, 'PNG') | 
| + self._pngout.stdin.close() | 
| + | 
| + def close(self): | 
| + self._thread.join() | 
| + self._pngout.stdout.close() | 
| + self._pngout.wait() | 
| + | 
| +class ImageCompressor: | 
| + use_pngout = True | 
| + | 
| + def make_uncompressed_file(self, image, filename): | 
| + file = StringIO() | 
| + 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.
 | 
| + # the correct image type based on the file extension | 
| + image.save(file) | 
| + file.seek(0) | 
| + | 
| + return file | 
| + | 
| + def make_file(self, image, filename): | 
| + if self.use_pngout and os.path.splitext(filename)[1].lower() == '.png': | 
| + try: | 
| + return Pngout(image) | 
| + except OSError, e: | 
| + if e.errno != errno.ENOENT: | 
| + raise | 
| + | 
| + logging.warning("Couldn't find 'pngout', can't compress images") | 
| + self.use_pngout = False | 
| + | 
| + return self.make_uncompressed_file(image, filename) | 
| + | 
| +image_to_file = ImageCompressor().make_file |