[IMP] don't reimplement randint manually
[odoo/odoo.git] / openerp / tools / image.py
index 1caafb2..d6a9e3b 100644 (file)
 #
 ##############################################################################
 
-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
@@ -33,6 +38,7 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
         filled with transparent background. The image will not be stretched if
         smaller than the expected size.
         Steps of the resizing:
+        - 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
@@ -43,71 +49,96 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
           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.
-        
+        - 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.
     """
-    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 image_resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG'):
+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 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 image_resize_image(base64_source, size, encoding, filetype, True)
+    return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
 
-def image_resize_image_medium(base64_source, size=(180, 180), encoding='base64', filetype='PNG'):
+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 image_resize_image(base64_source, size, encoding, filetype)
-    
-def image_resize_image_small(base64_source, size=(50, 50), encoding='base64', filetype='PNG'):
+    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 image_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 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'):
+    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
@@ -116,24 +147,33 @@ def image_get_resized_images(base64_source, return_big=False, return_medium=True
         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: 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 return_big: if set, return_dict contains the 'big_name' entry
-        :param return_medium: if set, return_dict contains the 'medium_name' entry
-        :param return_small: if set, return_dict contains the 'small_name' entry
-        :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.
+
+        :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_dict = dict()
-    if return_big: return_dict[big_name] = image_resize_image_big(base64_source)
-    if return_medium: return_dict[medium_name] = image_resize_image_medium(base64_source)
-    if return_small: return_dict[small_name] = image_resize_image_small(base64_source)
-    return return_dict
\ No newline at end of file
+    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'))
+