Thibault Delavallée (OpenERP) has proposed merging 
lp:~openerp-dev/openobject-server/trunk-tools-image-tde into 
lp:openobject-server.

Requested reviews:
  OpenERP Core Team (openerp)

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-tools-image-tde/+merge/123240

Image cleaning
- moved the avatar colorize part of the image code to tools/image.py, where it 
should have been placed at revs 4373-4375
- added the option for not up-sizing image smaller than the resize limits
- default image_small size is now 64x64 px
- default image_medium size is now 128x128 px
- overall code and comments cleaning
-- 
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-tools-image-tde/+merge/123240
Your team OpenERP R&D Team is subscribed to branch 
lp:~openerp-dev/openobject-server/trunk-tools-image-tde.
=== modified file 'openerp/addons/base/res/res_partner.py'
--- openerp/addons/base/res/res_partner.py	2012-09-04 15:20:02 +0000
+++ openerp/addons/base/res/res_partner.py	2012-09-07 10:01:34 +0000
@@ -21,7 +21,6 @@
 
 import math
 import openerp
-import os
 from osv import osv, fields
 import re
 import tools
@@ -58,20 +57,20 @@
             return super(res_partner_category, self).name_get(cr, uid, ids, context=context)
         if isinstance(ids, (int, long)):
             ids = [ids]
-        reads = self.read(cr, uid, ids, ['name','parent_id'], context=context)
+        reads = self.read(cr, uid, ids, ['name', 'parent_id'], context=context)
         res = []
         for record in reads:
             name = record['name']
             if record['parent_id']:
-                name = record['parent_id'][1]+' / '+name
+                name = record['parent_id'][1] + ' / ' + name
             res.append((record['id'], name))
         return res
 
     def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
         if not args:
-            args=[]
+            args = []
         if not context:
-            context={}
+            context = {}
         if name:
             # Be sure name_search is symetric to name_get
             name = name.split(' / ')[-1]
@@ -85,23 +84,23 @@
         res = self.name_get(cr, uid, ids, context=context)
         return dict(res)
 
-    _description='Partner Categories'
+    _description = 'Partner Categories'
     _name = 'res.partner.category'
     _columns = {
         'name': fields.char('Category Name', required=True, size=64, translate=True),
         'parent_id': fields.many2one('res.partner.category', 'Parent Category', select=True, ondelete='cascade'),
         'complete_name': fields.function(_name_get_fnc, type="char", string='Full Name'),
         'child_ids': fields.one2many('res.partner.category', 'parent_id', 'Child Categories'),
-        'active' : fields.boolean('Active', help="The active field allows you to hide the category without removing it."),
-        'parent_left' : fields.integer('Left parent', select=True),
-        'parent_right' : fields.integer('Right parent', select=True),
+        'active': fields.boolean('Active', help="The active field allows you to hide the category without removing it."),
+        'parent_left': fields.integer('Left parent', select=True),
+        'parent_right': fields.integer('Right parent', select=True),
         'partner_ids': fields.many2many('res.partner', id1='category_id', id2='partner_id', string='Partners'),
     }
     _constraints = [
         (osv.osv._check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
     ]
     _defaults = {
-        'active' : lambda *a: 1,
+        'active': lambda *a: 1,
     }
     _parent_store = True
     _parent_order = 'name'
@@ -113,7 +112,7 @@
     _columns = {
         'name': fields.char('Title', required=True, size=46, translate=True),
         'shortcut': fields.char('Abbreviation', size=16, translate=True),
-        'domain': fields.selection([('partner','Partner'),('contact','Contact')], 'Domain', required=True, size=24)
+        'domain': fields.selection([('partner', 'Partner'), ('contact', 'Contact')], 'Domain', required=True, size=24)
     }
     _defaults = {
         'domain': 'contact',
@@ -129,13 +128,13 @@
 ADDRESS_FIELDS = POSTAL_ADDRESS_FIELDS + ('email', 'phone', 'fax', 'mobile', 'website', 'ref', 'lang')
 
 class res_partner(osv.osv):
-    _description='Partner'
+    _description = 'Partner'
     _name = "res.partner"
 
     def _address_display(self, cr, uid, ids, name, args, context=None):
-        res={}
+        res = {}
         for partner in self.browse(cr, uid, ids, context=context):
-            res[partner.id] =self._display_address(cr, uid, partner, context=context)
+            res[partner.id] = self._display_address(cr, uid, partner, context=context)
         return res
 
     def _get_image(self, cr, uid, ids, name, args, context=None):
@@ -143,7 +142,7 @@
         for obj in self.browse(cr, uid, ids, context=context):
             result[obj.id] = tools.image_get_resized_images(obj.image)
         return result
-    
+
     def _set_image(self, cr, uid, id, name, value, args, context=None):
         return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
 
@@ -151,7 +150,7 @@
     _columns = {
         'name': fields.char('Name', size=128, required=True, select=True),
         'date': fields.date('Date', select=1),
-        'title': fields.many2one('res.partner.title','Title'),
+        'title': fields.many2one('res.partner.title', 'Title'),
         'parent_id': fields.many2one('res.partner', 'Owned by'),
         'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts'),
         'ref': fields.char('Reference', size=64, select=1),
@@ -162,9 +161,9 @@
                  "It is important to set a value for this field. You should use the same timezone "
                  "that is otherwise used to pick and render date and time values: your computer's timezone."),
         'user_id': fields.many2one('res.users', 'Salesperson', help='The internal user that is in charge of communicating with this partner if any.'),
-        'vat': fields.char('TIN',size=32 ,help="Tax Identification Number. Check the box if the partner is subjected to taxes. Used by the some of the legal statements."),
+        'vat': fields.char('TIN', size=32, help="Tax Identification Number. Check the box if the partner is subjected to taxes. Used by the some of the legal statements."),
         'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'),
-        'website': fields.char('Website',size=64, help="Website of Partner or Company"),
+        'website': fields.char('Website', size=64, help="Website of Partner or Company"),
         'comment': fields.text('Notes'),
         'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'),   # should be removed in version 7, but kept until then for backward compatibility
         'category_id': fields.many2many('res.partner.category', id1='partner_id', id2='category_id', string='Tags'),
@@ -175,8 +174,8 @@
         'supplier': fields.boolean('Supplier', help="Check this box if the partner is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."),
         'employee': fields.boolean('Employee', help="Check this box if the partner is an Employee."),
         'function': fields.char('Job Position', size=128),
-        'type': fields.selection( [('default','Default'), ('invoice','Invoice'),
-                                   ('delivery','Delivery'), ('contact','Contact'),
+        'type': fields.selection([('default', 'Default'), ('invoice', 'Invoice'),
+                                   ('delivery', 'Delivery'), ('contact', 'Contact'),
                                    ('other', 'Other')], 'Address Type',
             help="Used to select automatically the right address according to the context in sales and purchases documents."),
         'street': fields.char('Street', size=128),
@@ -193,25 +192,24 @@
         'birthdate': fields.char('Birthdate', size=64),
         'is_company': fields.boolean('Company', help="Check if the contact is a company, otherwise it is a person"),
         'use_parent_address': fields.boolean('Use Company Address', help="Select this if you want to set company's address information  for this contact"),
+        # image: all image fields are base64 encoded and PIL-supported
         'image': fields.binary("Image",
-            help="This field holds the image used as avatar for the "\
-                 "partner. The image is base64 encoded, and PIL-supported. "\
-                 "It is limited to a 1024x1024 px image."),
+            help="This field holds the image used as avatar for the partner, limited to 1024x1024px"),
         'image_medium': fields.function(_get_image, fnct_inv=_set_image,
             string="Medium-sized image", type="binary", multi="_get_image",
-            store = {
+            store={
                 'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
             },
             help="Medium-sized image of the partner. It is automatically "\
-                 "resized as a 180x180 px image, with aspect ratio preserved. "\
+                 "resized as a 128x128px image, with aspect ratio preserved. "\
                  "Use this field in form views or some kanban views."),
         'image_small': fields.function(_get_image, fnct_inv=_set_image,
             string="Small-sized image", type="binary", multi="_get_image",
-            store = {
+            store={
                 'res.partner': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
             },
             help="Small-sized image of the partner. It is automatically "\
-                 "resized as a 50x50 px image, with aspect ratio preserved. "\
+                 "resized as a 64x64px image, with aspect ratio preserved. "\
                  "Use this field anywhere a small image is required."),
         'company_id': fields.many2one('res.company', 'Company', select=1),
         'color': fields.integer('Color Index'),
@@ -230,40 +228,28 @@
         if is_company:
             image = open(openerp.modules.get_module_resource('base', 'static/src/img', 'company_image.png')).read()
         else:
-            from PIL import Image
-            from StringIO import StringIO
-            color = (255,255,255)
-            if colorize:
-                from random import random
-                color = (int(random() * 192 + 32), int(random() * 192 + 32), int(random() * 192 + 32))
-            face = Image.open(openerp.modules.get_module_resource('base', 'static/src/img', 'avatar.png'))
-            avatar = Image.new('RGB', face.size)
-            avatar.paste(color)
-            avatar.paste(face, mask=face)
-            buffer = StringIO()
-            avatar.save(buffer, 'PNG')
-            image = buffer.getvalue()
-        return image.encode('base64')
+            image = tools.image_colorize(open(openerp.modules.get_module_resource('base', 'static/src/img', 'avatar.png')).read())
+        return tools.image_resize_image_big(image.encode('base64'))
 
     _defaults = {
         'active': True,
-        'lang': lambda self, cr, uid, context: context.get('lang', 'en_US'),
-        'tz': lambda self, cr, uid, context: context.get('tz', False),
+        'lang': lambda self, cr, uid, ctx: ctx.get('lang', 'en_US'),
+        'tz': lambda self, cr, uid, ctx: ctx.get('tz', False),
         'customer': True,
         'category_id': _default_category,
-        'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=c),
+        'company_id': lambda self, cr, uid, ctx: self.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=ctx),
         'color': 0,
         'is_company': False,
         'type': 'default',
         'use_parent_address': True,
-        'image': lambda self, cr, uid, context: self._get_default_image(cr, uid, context.get('default_is_company', False), context),
+        'image': lambda self, cr, uid, ctx: self._get_default_image(cr, uid, ctx.get('default_is_company', False), ctx),
     }
 
     def copy(self, cr, uid, id, default=None, context=None):
         if default is None:
             default = {}
         name = self.read(cr, uid, [id], ['name'], context)[0]['name']
-        default.update({'name': _('%s (copy)')%(name)})
+        default.update({'name': _('%s (copy)') % (name)})
         return super(res_partner, self).copy(cr, uid, id, default, context)
 
     def onchange_type(self, cr, uid, ids, is_company, context=None):

=== modified file 'openerp/addons/base/res/res_users.py'
--- openerp/addons/base/res/res_users.py	2012-09-03 09:55:27 +0000
+++ openerp/addons/base/res/res_users.py	2012-09-07 10:01:34 +0000
@@ -244,14 +244,14 @@
         return result
 
     _defaults = {
-        'password' : '',
-        'active' : True,
+        'password': '',
+        'active': True,
         'customer': False,
         'menu_id': _get_menu,
         'company_id': _get_company,
         'company_ids': _get_companies,
         'groups_id': _get_group,
-        'image': lambda self, cr, uid, context: self.pool.get('res.partner')._get_default_image(cr, uid, False, context, colorize=True),
+        'image': lambda self, cr, uid, ctx={}: self.pool.get('res.partner')._get_default_image(cr, uid, False, ctx, colorize=True),
     }
 
     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):

=== modified file 'openerp/tools/image.py'
--- openerp/tools/image.py	2012-08-07 11:08:34 +0000
+++ openerp/tools/image.py	2012-09-07 10:01:34 +0000
@@ -20,8 +20,11 @@
 ##############################################################################
 
 import io
+import StringIO
+
 from PIL import Image
-import StringIO
+from PIL import ImageFilter
+from random import random
 
 # ----------------------------------------
 # Image resizing
@@ -47,15 +50,17 @@
           image.
         - past the thumbnail on the transparent background and center
           it.
-        
+
         :param base64_source: base64-encoded version of the source
-            image
+            image; if False, returns False
         :param size: tuple(height, width)
         :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.
     """
+    if not base64_source:
+        return False
     image_stream = io.BytesIO(base64_source.decode(encoding))
     image = Image.open(image_stream)
     # check image size: do not create a thumbnail if avoiding smaller images
@@ -63,6 +68,7 @@
         return base64_source
     # create a thumbnail: will resize and keep ratios
     image.thumbnail(size, Image.ANTIALIAS)
+    image = image.filter(ImageFilter.SHARPEN)
     # create a transparent image for background
     background = Image.new('RGBA', size, (255, 255, 255, 0))
     # past the resized image on the background
@@ -72,42 +78,57 @@
     background.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.
-    """
-    if not base64_source:
-        return False
-    return image_resize_image(base64_source, size, encoding, filetype)
+        :param size, encoding, filetype, avoid_if_small: refer to image_resize_image
+    """
+    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(io.BytesIO(original))
+    image = Image.new('RGB', original.size)
+    # generate the background color, past it as background
+    if randomize:
+        color = (int(random() * 192 + 32), int(random() * 192 + 32), int(random() * 192 + 32))
+    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 +137,22 @@
         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

_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help   : https://help.launchpad.net/ListHelp

Reply via email to