#
##############################################################################
-import io
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
from PIL import Image
-import StringIO
+from PIL import ImageOps
+from random import randint
# ----------------------------------------
# Image resizing
# ----------------------------------------
-def resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype='PNG', avoid_if_small=False):
- """ Function to resize an image. The image will be resized to the
- given size, while keeping the aspect ratios, and holes in the
- image will be filled with transparent background. The image
- will not be stretched if smaller than the expected size.
+def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype='PNG', avoid_if_small=False):
+ """ Function to resize an image. The image will be resized to the given
+ size, while keeping the aspect ratios, and holes in the image will be
+ filled with transparent background. The image will not be stretched if
+ smaller than the expected size.
Steps of the resizing:
- - create a thumbnail of the source image through using the
- thumbnail function. Aspect ratios are preserved when using
- it. Note that if the source image is smaller than the expected
- size, it will not be extended, but filled to match the size.
- - create a transparent background that will hold the final
- image.
- - past the thumbnail on the transparent background and center
- it.
-
+ - Compute width and height if not specified.
+ - if avoid_if_small: if both image sizes are smaller than the requested
+ sizes, the original image is returned. This is used to avoid adding
+ transparent content around images that we do not want to alter but
+ just resize if too big. This is used for example when storing images
+ in the 'image' field: we keep the original image, resized to a maximal
+ size, without adding transparent content around it if smaller.
+ - create a thumbnail of the source image through using the thumbnail
+ function. Aspect ratios are preserved when using it. Note that if the
+ source image is smaller than the expected size, it will not be
+ extended, but filled to match the size.
+ - create a transparent background that will hold the final image.
+ - paste the thumbnail on the transparent background and center it.
+
:param base64_source: base64-encoded version of the source
- image
- :param size: tuple(height, width)
+ image; if False, returns False
+ :param size: 2-tuple(width, height). A None value for any of width or
+ height mean an automatically computed value based respectivelly
+ on height or width of the source image.
:param encoding: the output encoding
:param filetype: the output filetype
:param avoid_if_small: do not resize if image height and width
- are smaller than the expected size.
+ are smaller than the expected size.
"""
- image_stream = io.BytesIO(base64_source.decode(encoding))
+ if not base64_source:
+ return False
+ if size == (None, None):
+ return base64_source
+ image_stream = StringIO.StringIO(base64_source.decode(encoding))
image = Image.open(image_stream)
+
+ asked_width, asked_height = size
+ if asked_width is None:
+ asked_width = int(image.size[0] * (float(asked_height) / image.size[1]))
+ if asked_height is None:
+ asked_height = int(image.size[1] * (float(asked_width) / image.size[0]))
+ size = asked_width, asked_height
+
# check image size: do not create a thumbnail if avoiding smaller images
if avoid_if_small and image.size[0] <= size[0] and image.size[1] <= size[1]:
return base64_source
- # create a thumbnail: will resize and keep ratios
- image.thumbnail(size, Image.ANTIALIAS)
- # create a transparent image for background
- background = Image.new('RGBA', size, (255, 255, 255, 0))
- # past the resized image on the background
- background.paste(image, ((size[0] - image.size[0]) / 2, (size[1] - image.size[1]) / 2))
- # return an encoded image
+
+ if image.size != size:
+ # If you need faster thumbnails you may use use Image.NEAREST
+ image = ImageOps.fit(image, size, Image.ANTIALIAS)
+
background_stream = StringIO.StringIO()
- background.save(background_stream, filetype)
+ image.save(background_stream, filetype)
return background_stream.getvalue().encode(encoding)
-def resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG'):
- """ Wrapper on resize_image, to resize to the standard 'big' image
- size: 1024x1024.
- :param base64_source: base64 encoded source image. If False,
- the function returns False.
+def image_resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG', avoid_if_small=True):
+ """ Wrapper on image_resize_image, to resize images larger than the standard
+ 'big' image size: 1024x1024px.
+ :param size, encoding, filetype, avoid_if_small: refer to image_resize_image
"""
- if not base64_source:
- return False
- return resize_image(base64_source, size, encoding, filetype, True)
+ return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
-def resize_image_medium(base64_source, size=(180, 180), encoding='base64', filetype='PNG'):
- """ Wrapper on resize_image, to resize to the standard 'medium'
+def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype='PNG', avoid_if_small=False):
+ """ Wrapper on image_resize_image, to resize to the standard 'medium'
image size: 180x180.
- :param base64_source: base64 encoded source image. If False,
- the function returns False.
+ :param size, encoding, filetype, avoid_if_small: refer to image_resize_image
"""
- if not base64_source:
- return False
- return resize_image(base64_source, size, encoding, filetype)
-
-def resize_image_small(base64_source, size=(50, 50), encoding='base64', filetype='PNG'):
- """ Wrapper on resize_image, to resize to the standard 'small' image
+ return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
+
+def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype='PNG', avoid_if_small=False):
+ """ Wrapper on image_resize_image, to resize to the standard 'small' image
size: 50x50.
- :param base64_source: base64 encoded source image. If False,
- the function returns False.
+ :param size, encoding, filetype, avoid_if_small: refer to image_resize_image
"""
- if not base64_source:
- return False
- return resize_image(base64_source, size, encoding, filetype)
+ return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
+
+# ----------------------------------------
+# Colors
+# ---------------------------------------
+
+def image_colorize(original, randomize=True, color=(255, 255, 255)):
+ """ Add a color to the transparent background of an image.
+ :param original: file object on the original image file
+ :param randomize: randomize the background color
+ :param color: background-color, if not randomize
+ """
+ # create a new image, based on the original one
+ original = Image.open(StringIO.StringIO(original))
+ image = Image.new('RGB', original.size)
+ # generate the background color, past it as background
+ if randomize:
+ color = (randint(32, 224), randint(32, 224), randint(32, 224))
+ image.paste(color)
+ image.paste(original, mask=original)
+ # return the new image
+ buffer = StringIO.StringIO()
+ image.save(buffer, 'PNG')
+ return buffer.getvalue()
# ----------------------------------------
# Misc image tools
# ---------------------------------------
-def get_resized_images(base64_source, big_name='image', medium_name='image_medium', small_name='image_small'):
+def image_get_resized_images(base64_source, return_big=False, return_medium=True, return_small=True,
+ big_name='image', medium_name='image_medium', small_name='image_small',
+ avoid_resize_big=True, avoid_resize_medium=False, avoid_resize_small=False):
""" Standard tool function that returns a dictionary containing the
big, medium and small versions of the source image. This function
is meant to be used for the methods of functional fields for
models using images.
-
- :param base64_source: if set to False, other values are set to
- False also. The purpose is to be linked
- to the fields that hold images in
- OpenERP and that are binary fields.
- :param big_name: name related to the big version of the image;
- 'image' by default
- :param medium_name: name related to the medium version of the
- image; 'image_medium' by default
- :param small_name: name related to the small version of the
- image; 'image_small' by default
+
+ Default parameters are given to be used for the getter of functional
+ image fields, for example with res.users or res.partner. It returns
+ only image_medium and image_small values, to update those fields.
+
+ :param base64_source: base64-encoded version of the source
+ image; if False, all returnes values will be False
+ :param return_{..}: if set, computes and return the related resizing
+ of the image
+ :param {..}_name: key of the resized image in the return dictionary;
+ 'image', 'image_medium' and 'image_small' by default.
+ :param avoid_resize_[..]: see avoid_if_small parameter
+ :return return_dict: dictionary with resized images, depending on
+ previous parameters.
"""
- return {
- big_name: resize_image_big(base64_source),
- medium_name: resize_image_medium(base64_source),
- small_name: resize_image_small(base64_source),
- }
+ return_dict = dict()
+ if return_big:
+ return_dict[big_name] = image_resize_image_big(base64_source, avoid_if_small=avoid_resize_big)
+ if return_medium:
+ return_dict[medium_name] = image_resize_image_medium(base64_source, avoid_if_small=avoid_resize_medium)
+ if return_small:
+ return_dict[small_name] = image_resize_image_small(base64_source, avoid_if_small=avoid_resize_small)
+ return return_dict
+
+
+if __name__=="__main__":
+ import sys
+
+ assert len(sys.argv)==3, 'Usage to Test: image.py SRC.png DEST.png'
+
+ img = file(sys.argv[1],'rb').read().encode('base64')
+ new = image_resize_image(img, (128,100))
+ file(sys.argv[2], 'wb').write(new.decode('base64'))
+