Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: imageCompression.py

Issue 4663693082099712: Issue 1897 - Compress auto-generated images with pngout (Closed)
Patch Set: Created Jan. 28, 2015, 9:40 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | imageConversion.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « no previous file | imageConversion.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld