| LEFT | RIGHT | 
|---|
| (no file at all) |  | 
| 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 |  | 
| LEFT | RIGHT | 
|---|