[IMP] res_partner: moved the avatar colorize part of the image code to tools/image...
[odoo/odoo.git] / openerp / tools / image.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2012-today OpenERP s.a. (<http://openerp.com>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import io
23 import StringIO
24
25 from PIL import Image
26 from PIL import ImageFilter
27 from random import random
28
29 # ----------------------------------------
30 # Image resizing
31 # ----------------------------------------
32
33 def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype='PNG', avoid_if_small=False):
34     """ Function to resize an image. The image will be resized to the given
35         size, while keeping the aspect ratios, and holes in the image will be
36         filled with transparent background. The image will not be stretched if
37         smaller than the expected size.
38         Steps of the resizing:
39         - if avoid_if_small: if both image sizes are smaller than the requested
40           sizes, the original image is returned. This is used to avoid adding
41           transparent content around images that we do not want to alter but
42           just resize if too big. This is used for example when storing images
43           in the 'image' field: we keep the original image, resized to a maximal
44           size, without adding transparent content around it if smaller.
45         - create a thumbnail of the source image through using the thumbnail
46           function. Aspect ratios are preserved when using it. Note that if the
47           source image is smaller than the expected size, it will not be
48           extended, but filled to match the size.
49         - create a transparent background that will hold the final
50           image.
51         - past the thumbnail on the transparent background and center
52           it.
53
54         :param base64_source: base64-encoded version of the source
55             image
56         :param size: tuple(height, width)
57         :param encoding: the output encoding
58         :param filetype: the output filetype
59         :param avoid_if_small: do not resize if image height and width
60             are smaller than the expected size.
61     """
62     image_stream = io.BytesIO(base64_source.decode(encoding))
63     image = Image.open(image_stream)
64     # check image size: do not create a thumbnail if avoiding smaller images
65     if avoid_if_small and image.size[0] <= size[0] and image.size[1] <= size[1]:
66         return base64_source
67     # create a thumbnail: will resize and keep ratios
68     image.thumbnail(size, Image.ANTIALIAS)
69     image = image.filter(ImageFilter.SHARPEN)
70     # create a transparent image for background
71     background = Image.new('RGBA', size, (255, 255, 255, 0))
72     # past the resized image on the background
73     background.paste(image, ((size[0] - image.size[0]) / 2, (size[1] - image.size[1]) / 2))
74     # return an encoded image
75     background_stream = StringIO.StringIO()
76     background.save(background_stream, filetype)
77     return background_stream.getvalue().encode(encoding)
78
79 def image_resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG'):
80     """ Wrapper on image_resize_image, to resize images larger than the standard
81         'big' image size: 1024x1024px.
82         :param base64_source: base64 encoded source image. If False,
83             the function returns False.
84     """
85     if not base64_source:
86         return False
87     return image_resize_image(base64_source, size, encoding, filetype, True)
88
89 def image_resize_image_medium(base64_source, size=(180, 180), encoding='base64', filetype='PNG'):
90     """ Wrapper on image_resize_image, to resize to the standard 'medium'
91         image size: 180x180.
92         :param base64_source: base64 encoded source image. If False,
93             the function returns False.
94     """
95     if not base64_source:
96         return False
97     return image_resize_image(base64_source, size, encoding, filetype)
98
99 def image_resize_image_small(base64_source, size=(50, 50), encoding='base64', filetype='PNG'):
100     """ Wrapper on image_resize_image, to resize to the standard 'small' image
101         size: 50x50.
102         :param base64_source: base64 encoded source image. If False,
103             the function returns False.
104     """
105     if not base64_source:
106         return False
107     return image_resize_image(base64_source, size, encoding, filetype)
108
109 # ----------------------------------------
110 # Colors
111 # ---------------------------------------
112
113 def image_colorize(original, randomize=True, color=(255, 255, 255)):
114     """ Add a color to the transparent background of an image.
115         :param original: file object on the original image file
116         :param randomize: randomize the background color
117         :param color: background-color, if not randomize
118     """
119     # create a new image, based on the original one
120     original = Image.open(io.BytesIO(original))
121     image = Image.new('RGB', original.size)
122     # generate the background color, past it as background
123     if randomize:
124         color = (int(random() * 192 + 32), int(random() * 192 + 32), int(random() * 192 + 32))
125     image.paste(color)
126     image.paste(original, mask=original)
127     # return the new image
128     buffer = StringIO.StringIO()
129     image.save(buffer, 'PNG')
130     return buffer.getvalue()
131
132 # ----------------------------------------
133 # Misc image tools
134 # ---------------------------------------
135
136 def image_get_resized_images(base64_source, return_big=False, return_medium=True, return_small=True,
137     big_name='image', medium_name='image_medium', small_name='image_small'):
138     """ Standard tool function that returns a dictionary containing the
139         big, medium and small versions of the source image. This function
140         is meant to be used for the methods of functional fields for
141         models using images.
142
143         Default parameters are given to be used for the getter of functional
144         image fields,  for example with res.users or res.partner. It returns
145         only image_medium and image_small values, to update those fields.
146
147         :param base64_source: if set to False, other values are set to False
148             also. The purpose is to be linked to the fields that hold images in
149             OpenERP and that are binary fields.
150         :param return_big: if set, return_dict contains the 'big_name' entry
151         :param return_medium: if set, return_dict contains the 'medium_name' entry
152         :param return_small: if set, return_dict contains the 'small_name' entry
153         :param big_name: name related to the big version of the image;
154             'image' by default.
155         :param medium_name: name related to the medium version of the
156             image; 'image_medium' by default.
157         :param small_name: name related to the small version of the
158             image; 'image_small' by default.
159         :return return_dict: dictionary with resized images, depending on
160             previous parameters.
161     """
162     return_dict = dict()
163     if return_big:
164         return_dict[big_name] = image_resize_image_big(base64_source)
165     if return_medium:
166         return_dict[medium_name] = image_resize_image_medium(base64_source)
167     if return_small:
168         return_dict[small_name] = image_resize_image_small(base64_source)
169     return return_dict