from cStringIO import StringIO
from athumb.pial.engines.base import EngineBase
try:
from PIL import Image, ImageFile, ImageDraw
except ImportError:
import Image, ImageFile, ImageDraw
class PILEngine(EngineBase):
"""
Python Imaging Library Engine. This implements members of EngineBase.
"""
def get_image(self, source):
"""
Given a file-like object, loads it up into a PIL.Image object
and returns it.
:param file source: A file-like object to load the image from.
:rtype: PIL.Image
:returns: The loaded image.
"""
buf = StringIO(source.read())
return Image.open(buf)
def get_image_size(self, image):
"""
Returns the image width and height as a tuple.
:param PIL.Image image: An image whose dimensions to get.
:rtype: tuple
:returns: Dimensions in the form of (x,y).
"""
return image.size
def is_valid_image(self, raw_data):
"""
Checks if the supplied raw data is valid image data.
:param str raw_data: A string representation of the image data.
:rtype: bool
:returns: ``True`` if ``raw_data`` is valid, ``False`` if not.
"""
buf = StringIO(raw_data)
try:
trial_image = Image.open(buf)
trial_image.verify()
except Exception:
# TODO: Get more specific with this exception handling.
return False
return True
def _colorspace(self, image, colorspace):
"""
Sets the image's colorspace. This is typical 'RGB' or 'GRAY', but
may be other things, depending on your choice of Engine.
:param PIL.Image image: The image whose colorspace to adjust.
:param str colorspace: One of either 'RGB' or 'GRAY'.
:rtype: PIL.Image
:returns: The colorspace-adjusted image.
"""
if colorspace == 'RGB':
if image.mode == 'RGBA':
# RGBA is just RGB + Alpha
return image
if image.mode == 'P' and 'transparency' in image.info:
return image.convert('RGBA')
return image.convert('RGB')
if colorspace == 'GRAY':
return image.convert('L')
return image
def _scale(self, image, width, height):
"""
Given an image, scales the image to the given ``width`` and ``height``.
:param PIL.Image image: The image to scale.
:param int width: The width of the scaled image.
:param int height: The height of the scaled image.
:rtype: PIL.Image
:returns: The scaled image.
"""
return image.resize((width, height), resample=Image.ANTIALIAS)
def _crop(self, image, width, height, x_offset, y_offset):
"""
Crops the ``image``, starting at ``width`` and ``height``, adding the
``x_offset`` and ``y_offset`` to make the crop window.
:param PIL.Image image: The image to crop.
:param int width: The X plane's start of the crop window.
:param int height: The Y plane's start of the crop window.
:param int x_offset: The 'width' of the crop window.
:param int y_offset: The 'height' of the crop window.
:rtype: PIL.Image
:returns: The cropped image.
"""
return image.crop((x_offset, y_offset,
width + x_offset, height + y_offset))
def _get_raw_data(self, image, format, quality):
"""
Returns the raw data from the Image, which can be directly written
to a something, be it a file-like object or a database.
:param PIL.Image image: The image to get the raw data for.
:param str format: The format to save to. If this value is ``None``,
PIL will attempt to guess. You're almost always better off
providing this yourself. For a full list of formats, see the PIL
handbook at:
http://www.pythonware.com/library/pil/handbook/index.htm
The *Appendixes* section at the bottom, in particular.
:param int quality: A quality level as a percent. The lower, the
higher the compression, the worse the artifacts. Check the
format's handbook page for what the different values for this mean.
For example, JPEG's max quality level is 95, with 100 completely
disabling JPEG quantization.
:rtype: str
:returns: A string representation of the image.
"""
ImageFile.MAXBLOCK = 1024 * 1024
buf = StringIO()
try:
# ptimize makes the encoder do a second pass over the image, if
# the format supports it.
image.save(buf, format=format, quality=quality, optimize=1)
except IOError:
# optimize is a no-go, omit it this attempt.
image.save(buf, format=format, quality=quality)
raw_data = buf.getvalue()
buf.close()
return raw_data