1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2012-today OpenERP s.a. (<http://openerp.com>).
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.
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.
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/>.
20 ##############################################################################
23 import cStringIO as StringIO
28 from PIL import ImageEnhance
29 from random import randint
31 # ----------------------------------------
33 # ----------------------------------------
35 def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype=None, avoid_if_small=False):
36 """ Function to resize an image. The image will be resized to the given
37 size, while keeping the aspect ratios, and holes in the image will be
38 filled with transparent background. The image will not be stretched if
39 smaller than the expected size.
40 Steps of the resizing:
41 - Compute width and height if not specified.
42 - if avoid_if_small: if both image sizes are smaller than the requested
43 sizes, the original image is returned. This is used to avoid adding
44 transparent content around images that we do not want to alter but
45 just resize if too big. This is used for example when storing images
46 in the 'image' field: we keep the original image, resized to a maximal
47 size, without adding transparent content around it if smaller.
48 - create a thumbnail of the source image through using the thumbnail
49 function. Aspect ratios are preserved when using it. Note that if the
50 source image is smaller than the expected size, it will not be
51 extended, but filled to match the size.
52 - create a transparent background that will hold the final image.
53 - paste the thumbnail on the transparent background and center it.
55 :param base64_source: base64-encoded version of the source
56 image; if False, returns False
57 :param size: 2-tuple(width, height). A None value for any of width or
58 height mean an automatically computed value based respectivelly
59 on height or width of the source image.
60 :param encoding: the output encoding
61 :param filetype: the output filetype, by default the source image's
62 :type filetype: str, any PIL image format (supported for creation)
63 :param avoid_if_small: do not resize if image height and width
64 are smaller than the expected size.
68 if size == (None, None):
70 image_stream = StringIO.StringIO(base64_source.decode(encoding))
71 image = Image.open(image_stream)
72 # store filetype here, as Image.new below will lose image.format
73 filetype = (filetype or image.format).upper()
77 }.get(filetype, filetype)
79 asked_width, asked_height = size
80 if asked_width is None:
81 asked_width = int(image.size[0] * (float(asked_height) / image.size[1]))
82 if asked_height is None:
83 asked_height = int(image.size[1] * (float(asked_width) / image.size[0]))
84 size = asked_width, asked_height
86 # check image size: do not create a thumbnail if avoiding smaller images
87 if avoid_if_small and image.size[0] <= size[0] and image.size[1] <= size[1]:
90 if image.size != size:
91 image = image_resize_and_sharpen(image, size)
92 if image.mode not in ["1", "L", "P", "RGB", "RGBA"]:
93 image = image.convert("RGB")
95 background_stream = StringIO.StringIO()
96 image.save(background_stream, filetype)
97 return background_stream.getvalue().encode(encoding)
99 def image_resize_and_sharpen(image, size, preserve_aspect_ratio=False, factor=2.0):
101 Create a thumbnail by resizing while keeping ratio.
102 A sharpen filter is applied for a better looking result.
104 :param image: PIL.Image.Image()
105 :param size: 2-tuple(width, height)
106 :param preserve_aspect_ratio: boolean (default: False)
107 :param factor: Sharpen factor (default: 2.0)
109 if image.mode != 'RGBA':
110 image = image.convert('RGBA')
111 image.thumbnail(size, Image.ANTIALIAS)
112 if preserve_aspect_ratio:
114 sharpener = ImageEnhance.Sharpness(image)
115 resized_image = sharpener.enhance(factor)
116 # create a transparent image for background and paste the image on it
117 image = Image.new('RGBA', size, (255, 255, 255, 0))
118 image.paste(resized_image, ((size[0] - resized_image.size[0]) / 2, (size[1] - resized_image.size[1]) / 2))
121 def image_save_for_web(image, fp=None, format=None):
123 Save image optimized for web usage.
125 :param image: PIL.Image.Image()
126 :param fp: File name or file object. If not specified, a bytestring is returned.
127 :param format: File format if could not be deduced from image.
129 opt = dict(format=image.format or format)
130 if image.format == 'PNG':
131 opt.update(optimize=True)
132 if image.mode != 'P':
133 # Floyd Steinberg dithering by default
134 image = image.convert('RGBA').convert('P', palette=Image.WEB, colors=256)
135 elif image.format == 'JPEG':
136 opt.update(optimize=True, quality=80)
138 image.save(fp, **opt)
140 img = StringIO.StringIO()
141 image.save(img, **opt)
142 return img.getvalue()
144 def image_resize_image_big(base64_source, size=(1204, 1024), encoding='base64', filetype=None, avoid_if_small=True):
145 """ Wrapper on image_resize_image, to resize images larger than the standard
146 'big' image size: 1024x1024px.
147 :param size, encoding, filetype, avoid_if_small: refer to image_resize_image
149 return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
151 def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype=None, avoid_if_small=False):
152 """ Wrapper on image_resize_image, to resize to the standard 'medium'
154 :param size, encoding, filetype, avoid_if_small: refer to image_resize_image
156 return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
158 def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype=None, avoid_if_small=False):
159 """ Wrapper on image_resize_image, to resize to the standard 'small' image
161 :param size, encoding, filetype, avoid_if_small: refer to image_resize_image
163 return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
165 # ----------------------------------------
167 # ---------------------------------------
169 def image_colorize(original, randomize=True, color=(255, 255, 255)):
170 """ Add a color to the transparent background of an image.
171 :param original: file object on the original image file
172 :param randomize: randomize the background color
173 :param color: background-color, if not randomize
175 # create a new image, based on the original one
176 original = Image.open(StringIO.StringIO(original))
177 image = Image.new('RGB', original.size)
178 # generate the background color, past it as background
180 color = (randint(32, 224), randint(32, 224), randint(32, 224))
182 image.paste(original, mask=original)
183 # return the new image
184 buffer = StringIO.StringIO()
185 image.save(buffer, 'PNG')
186 return buffer.getvalue()
188 # ----------------------------------------
190 # ---------------------------------------
192 def image_get_resized_images(base64_source, return_big=False, return_medium=True, return_small=True,
193 big_name='image', medium_name='image_medium', small_name='image_small',
194 avoid_resize_big=True, avoid_resize_medium=False, avoid_resize_small=False):
195 """ Standard tool function that returns a dictionary containing the
196 big, medium and small versions of the source image. This function
197 is meant to be used for the methods of functional fields for
200 Default parameters are given to be used for the getter of functional
201 image fields, for example with res.users or res.partner. It returns
202 only image_medium and image_small values, to update those fields.
204 :param base64_source: base64-encoded version of the source
205 image; if False, all returnes values will be False
206 :param return_{..}: if set, computes and return the related resizing
208 :param {..}_name: key of the resized image in the return dictionary;
209 'image', 'image_medium' and 'image_small' by default.
210 :param avoid_resize_[..]: see avoid_if_small parameter
211 :return return_dict: dictionary with resized images, depending on
216 return_dict[big_name] = image_resize_image_big(base64_source, avoid_if_small=avoid_resize_big)
218 return_dict[medium_name] = image_resize_image_medium(base64_source, avoid_if_small=avoid_resize_medium)
220 return_dict[small_name] = image_resize_image_small(base64_source, avoid_if_small=avoid_resize_small)
224 if __name__=="__main__":
227 assert len(sys.argv)==3, 'Usage to Test: image.py SRC.png DEST.png'
229 img = file(sys.argv[1],'rb').read().encode('base64')
230 new = image_resize_image(img, (128,100))
231 file(sys.argv[2], 'wb').write(new.decode('base64'))