Handle responsive images selection
authorThierry Florac <thierry.florac@onf.fr>
Tue, 15 Nov 2016 09:55:02 +0100 (2016-11-15)
changeset 31 58686bb1a7b9
parent 30 2f2e70d36b6e
child 32 57b52bddde7f
Handle responsive images selection
src/pyams_file/image.py
src/pyams_file/interfaces/__init__.py
src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.mo
src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.po
src/pyams_file/locales/pyams_file.pot
src/pyams_file/thumbnail.py
src/pyams_file/widget/__init__.py
src/pyams_file/zmi/file.py
src/pyams_file/zmi/image.py
src/pyams_file/zmi/templates/image-pano-thumbnail.pt
src/pyams_file/zmi/templates/image-selection.pt
src/pyams_file/zmi/templates/image-square-thumbnail.pt
--- a/src/pyams_file/image.py	Thu Apr 21 16:40:03 2016 +0200
+++ b/src/pyams_file/image.py	Tue Nov 15 09:55:02 2016 +0100
@@ -14,11 +14,13 @@
 
 
 # import standard library
+import re
+
 from io import BytesIO
 from PIL import Image, ImageFilter
 
 # import interfaces
-from pyams_file.interfaces import IImage, IThumbnailer, IThumbnailGeometry, IThumbnail
+from pyams_file.interfaces import IImage, IThumbnailer, IThumbnailGeometry, IThumbnail, IResponsiveImage
 
 # import packages
 from pyams_utils.adapter import ContextAdapter, adapter_config
@@ -27,10 +29,11 @@
 
 
 WEB_FORMATS = ('JPEG', 'PNG', 'GIF')
+THUMB_SIZE = re.compile('^(?:\w+\:)?([0-9]+)x([0-9]+)$')
 
 
 @implementer(IThumbnailGeometry)
-class ThumbnailGeometrry(object):
+class ThumbnailGeometry(object):
     """Image thumbnail geometry"""
 
     x1 = FieldProperty(IThumbnailGeometry['x1'])
@@ -48,7 +51,7 @@
 
     def get_default_geometry(self, options=None):
         """Default thumbnail geometry"""
-        geometry = ThumbnailGeometrry()
+        geometry = ThumbnailGeometry()
         width, height = self.context.get_image_size()
         geometry.x1 = 0
         geometry.y1 = 0
@@ -56,16 +59,19 @@
         geometry.y2 = height
         return geometry
 
-    def create_thumbnail(self, thumbnail_name, format=None):
+    def create_thumbnail(self, target, format=None):
         # check thumbnail name
-        if isinstance(thumbnail_name, str):
-            width, height = tuple((int(x) for x in thumbnail_name.split('x')))
-        elif isinstance(thumbnail_name, tuple):
-            width, height = thumbnail_name
+        if isinstance(target, str):
+            width, height = tuple((int(x) for x in target.split('x')))
+        elif IThumbnailGeometry.providedBy(target):
+            width = target.x2 - target.x1
+            height = target.y2 - target.y1
+        elif isinstance(target, tuple):
+            width, height = target
         else:
             return None
+        # check format
         image = Image.open(self.context.get_blob(mode='c'))
-        # check format
         if not format:
             format = image.format
         format = format.upper()
@@ -82,13 +88,59 @@
         return new_image, format.lower()
 
 
+class ImageSelectionThumbnailer(ImageThumbnailer):
+    """Image thumbnailer based on user selection"""
+
+    def create_thumbnail(self, target, format=None):
+        # get thumbnail size
+        if isinstance(target, str):
+            geometry = IThumbnail(self.context).get_geometry(target)
+            match = THUMB_SIZE.match(target)
+            if match:
+                selection_name, width, height = match.groups()
+                width, height = int(width), int(height)
+            else:
+                width = abs(geometry.x2 - geometry.x1)
+                height = abs(geometry.y2 - geometry.y1)
+        elif IThumbnailGeometry.providedBy(target):
+            geometry = target
+            width = abs(geometry.x2 - geometry.x1)
+            height = abs(geometry.y2 - geometry.y1)
+        elif isinstance(target, tuple):
+            width, height = target
+            geometry = self.get_default_geometry()
+        else:
+            return None
+        # check format
+        image = Image.open(self.context.get_blob(mode='c'))
+        if not format:
+            format = image.format
+        format = format.upper()
+        if format not in WEB_FORMATS:
+            format = 'JPEG'
+        # check image mode
+        if image.mode == 'P':
+            image = image.convert('RGBA')
+        # generate thumbnail
+        new_image = BytesIO()
+        thumb_size = self.get_thumb_size(width, height, geometry)
+        image.crop((geometry.x1, geometry.y1, geometry.x2, geometry.y2)) \
+             .resize(thumb_size, Image.ANTIALIAS) \
+             .filter(ImageFilter.SHARPEN) \
+             .save(new_image, format)
+        return new_image, format.lower()
+
+    def get_thumb_size(self, width, height, geometry):
+        return width, height
+
+
 @adapter_config(name='square', context=IImage, provides=IThumbnailer)
-class ImageSquareThumbnailer(ContextAdapter):
+class ImageSquareThumbnailer(ImageSelectionThumbnailer):
     """Image square thumbnail adapter"""
 
     def get_default_geometry(self, options=None):
         """Default square thumbnail geometry"""
-        geometry = ThumbnailGeometrry()
+        geometry = ThumbnailGeometry()
         width, height = self.context.get_image_size()
         if width >= height:
             geometry.x1 = round((width / 2) - (height / 2))
@@ -102,45 +154,14 @@
             geometry.y2 = round((height / 2) + (width / 2))
         return geometry
 
-    def create_thumbnail(self, thumbnail_name, format=None):
-        geometry = IThumbnail(self.context).get_thumbnail_geometry(thumbnail_name)
-        image = Image.open(self.context.get_blob(mode='c'))
-        # get thumbnail size
-        if isinstance(thumbnail_name, str):
-            if ':' in thumbnail_name:
-                thumbnailer_name, size = thumbnail_name.split(':', 1)
-                width, height = tuple((int(x) for x in size.split('x')))
-            else:
-                width, height = tuple((int(x) for x in thumbnail_name.split('x')))
-        elif isinstance(thumbnail_name, tuple):
-            width, height = thumbnail_name
-        else:
-            return None
-        # check format
-        if not format:
-            format = image.format
-        format = format.upper()
-        if format not in WEB_FORMATS:
-            format = 'JPEG'
-        # check image mode
-        if image.mode == 'P':
-            image = image.convert('RGBA')
-        # generate thumbnail
-        new_image = BytesIO()
-        image.crop((geometry.x1, geometry.y1, geometry.x2, geometry.y2)) \
-             .resize((width, height), Image.ANTIALIAS) \
-             .filter(ImageFilter.SHARPEN) \
-             .save(new_image, format)
-        return new_image, format.lower()
-
 
 @adapter_config(name='pano', context=IImage, provides=IThumbnailer)
-class ImagePanoThumbnailer(ContextAdapter):
+class ImagePanoThumbnailer(ImageSelectionThumbnailer):
     """Image panoramic thumbnail adapter"""
 
     def get_default_geometry(self, options=None):
         """Default panoramic thumbnail geometry"""
-        geometry = ThumbnailGeometrry()
+        geometry = ThumbnailGeometry()
         width, height = self.context.get_image_size()
         pano_max_height = width * 9 / 16
         if pano_max_height >= height:
@@ -158,38 +179,33 @@
             geometry.y2 = round((height / 2) + (pano_height / 2))
         return geometry
 
-    def create_thumbnail(self, thumbnail_name, format=None):
-        geometry = IThumbnail(self.context).get_thumbnail_geometry(thumbnail_name)
-        image = Image.open(self.context.get_blob(mode='c'))
-        # get thumbnail size
-        if isinstance(thumbnail_name, str):
-            if ':' in thumbnail_name:
-                thumbnailer_name, size = thumbnail_name.split(':', 1)
-                width, height = tuple((int(x) for x in size.split('x')))
-            else:
-                width, height = tuple((int(x) for x in thumbnail_name.split('x')))
-        elif isinstance(thumbnail_name, tuple):
-            width, height = thumbnail_name
-        else:
-            return None
-        # check aspect ratio
+    def get_thumb_size(self, width, height, geometry):
         thumb_size = abs(geometry.x2 - geometry.x1), abs(geometry.y2 - geometry.y1)
         w_ratio = 1. * width / thumb_size[0]
         h_ratio = 1. * height / thumb_size[1]
         ratio = min(w_ratio, h_ratio)
-        # check format
-        if not format:
-            format = image.format
-        format = format.upper()
-        if format not in WEB_FORMATS:
-            format = 'JPEG'
-        # check image mode
-        if image.mode == 'P':
-            image = image.convert('RGBA')
-        # generate thumbnail
-        new_image = BytesIO()
-        image.crop((geometry.x1, geometry.y1, geometry.x2, geometry.y2)) \
-             .resize((round(ratio * thumb_size[0]), round(ratio * thumb_size[1])), Image.ANTIALIAS) \
-             .filter(ImageFilter.SHARPEN) \
-             .save(new_image, format)
-        return new_image, format.lower()
+        return round(ratio * thumb_size[0]), round(ratio * thumb_size[1])
+
+
+class ResponsiveImageThumbnailer(ImageSelectionThumbnailer):
+    """Responsive image thumbnailer"""
+
+
+@adapter_config(name='xs', context=IResponsiveImage, provides=IThumbnailer)
+class XsImageThumbnailer(ResponsiveImageThumbnailer):
+    """eXtra-Small responsive image thumbnailer"""
+
+
+@adapter_config(name='sm', context=IResponsiveImage, provides=IThumbnailer)
+class SmImageThumbnailer(ResponsiveImageThumbnailer):
+    """SMall responsive image thumbnailer"""
+
+
+@adapter_config(name='md', context=IResponsiveImage, provides=IThumbnailer)
+class MdImageThumbnailer(ResponsiveImageThumbnailer):
+    """MeDiumresponsive image thumbnailer"""
+
+
+@adapter_config(name='lg', context=IResponsiveImage, provides=IThumbnailer)
+class LgImageThumbnailer(ResponsiveImageThumbnailer):
+    """LarGe responsive image thumbnailer"""
--- a/src/pyams_file/interfaces/__init__.py	Thu Apr 21 16:40:03 2016 +0200
+++ b/src/pyams_file/interfaces/__init__.py	Tue Nov 15 09:55:02 2016 +0100
@@ -81,6 +81,10 @@
         """Crop image to given coordinates"""
 
 
+class IResponsiveImage(Interface):
+    """Responsive image marker interface"""
+
+
 class IVideo(IMediaFile):
     """Video object interface"""
 
@@ -160,7 +164,7 @@
     """Image field widget"""
 
 
-class IThumnailImageWidget(IImageWidget):
+class IThumbnailImageWidget(IImageWidget):
     """Image field widget with thumbnail selection"""
 
 
@@ -198,18 +202,19 @@
     def get_default_geometry(self):
         """Get default thumbnail geometry"""
 
-    def create_thumbnail(self, target_size, format=None):
+    def create_thumbnail(self, target, format=None):
         """Create thumbnail of the given source object
 
         Source can be any file which can provide thumbnails (image, video,
-        PDF file...)
-        target_size is the size of the created thumbnail, as an (width, height) tuple.
+        PDF file...).
+        Target, which defines thumbnail size, can be defined as a selection name
+        ('pano', 'square', 'xs'...), as a geometry or as a (width, height) tuple.
 
         If the requested image is of a resolution higher than that of the original file,
         the resulting image resolution will be that of the original file.
 
         If format (JPEG, PNG...) is given, this will be the format of the generated
-        thumbnail; otherwise the selected format
+        thumbnail; otherwise the format will be those of the source image.
         """
 
 
@@ -230,10 +235,10 @@
         source
         """
 
-    def get_thumbnail_geometry(self, thumbnail_name):
+    def get_geometry(self, selection_name):
         """Get geometry of a given thumbnail"""
 
-    def set_thumbnail_geometry(self, thumbnail_name, geometry):
+    def set_geometry(self, selection_name, geometry):
         """Set geometry for given thumbnail"""
 
     def clear_geometries(self):
@@ -242,6 +247,9 @@
     def get_thumbnail_name(self, thumbnail_name, with_size=None):
         """Get matching name for the given thumbnail name or size"""
 
+    def get_selection(self, selection_name, format=None):
+        """Get image for given user selection"""
+
     def get_thumbnail(self, thumbnail_name, format=None, watermark=None):
         """Get requested thumbnail
 
Binary file src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.mo has changed
--- a/src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.po	Thu Apr 21 16:40:03 2016 +0200
+++ b/src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.po	Tue Nov 15 09:55:02 2016 +0100
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-02-10 17:59+0100\n"
+"POT-Creation-Date: 2016-11-14 17:49+0100\n"
 "PO-Revision-Date: 2015-02-06 21:39+0100\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French\n"
@@ -52,23 +52,23 @@
 msgid "Delete content"
 msgstr "Supprimer ce contenu"
 
-#: src/pyams_file/zmi/file.py:41
+#: src/pyams_file/zmi/file.py:42
 msgid "Properties..."
 msgstr "Propriétés..."
 
-#: src/pyams_file/zmi/file.py:52
+#: src/pyams_file/zmi/file.py:53
 msgid "Update file properties"
 msgstr "Mise à jour des propriétés"
 
-#: src/pyams_file/zmi/image.py:56
+#: src/pyams_file/zmi/image.py:61
 msgid "Resize image..."
 msgstr "Redimensionner l'image..."
 
-#: src/pyams_file/zmi/image.py:107 src/pyams_file/zmi/image.py:67
+#: src/pyams_file/zmi/image.py:111 src/pyams_file/zmi/image.py:72
 msgid "Resize image"
 msgstr "Redimensionner l'image"
 
-#: src/pyams_file/zmi/image.py:141
+#: src/pyams_file/zmi/image.py:146
 msgid ""
 "You can use this form to change image dimensions.\n"
 "\n"
@@ -77,51 +77,83 @@
 msgstr ""
 "Vous pouvez utiliser ce formulaire pour changer la taille de l'image.\n"
 "\n"
-"Une nouvelle image ne sera générée que si les dimensions indiquées sont inférieures "
-"à la taille du fichier actuel."
+"Une nouvelle image ne sera générée que si les dimensions indiquées sont "
+"inférieures à la taille du fichier actuel."
 
-#: src/pyams_file/zmi/image.py:152
+#: src/pyams_file/zmi/image.py:161
 msgid "Crop image..."
 msgstr "Recadrer l'image..."
 
-#: src/pyams_file/zmi/image.py:170
+#: src/pyams_file/zmi/image.py:179
 msgid "Crop image"
 msgstr "Recadrer l'image"
 
-#: src/pyams_file/zmi/image.py:231
+#: src/pyams_file/zmi/image.py:245
 msgid "Select square thumbnail..."
 msgstr "Vignette carrée..."
 
-#: src/pyams_file/zmi/image.py:242
+#: src/pyams_file/zmi/image.py:256
 msgid "Select square thumbnail"
 msgstr "Sélection de l'emprise d'une vignette carrée"
 
-#: src/pyams_file/zmi/image.py:290
+#: src/pyams_file/zmi/image.py:309
 msgid "Select panoramic thumbnail..."
 msgstr "Vignette panoramique..."
 
-#: src/pyams_file/zmi/image.py:306
+#: src/pyams_file/zmi/image.py:320
 msgid "Select panoramic thumbnail"
 msgstr "Sélection de l'emprise d'une vignette panoramique"
 
-#: src/pyams_file/zmi/image.py:66 src/pyams_file/zmi/image.py:162
-#: src/pyams_file/zmi/image.py:222
+#: src/pyams_file/zmi/image.py:432
+msgid "Select responsive XS image..."
+msgstr "Image adaptative pour très petits terminaux (XS)..."
+
+#: src/pyams_file/zmi/image.py:444
+msgid "Select image for extra-small (XS) devices"
+msgstr "Sélectionner l'image affichée sur les très petits terminaux (taille XS)"
+
+#: src/pyams_file/zmi/image.py:466
+msgid "Select responsive SM image..."
+msgstr "Image adaptative pour petits terminaux (SM)..."
+
+#: src/pyams_file/zmi/image.py:478
+msgid "Select image for small (SM) devices"
+msgstr "Sélectionner l'image affichée sur les petits terminaux (taille SM)"
+
+#: src/pyams_file/zmi/image.py:500
+msgid "Select responsive MD image..."
+msgstr "Image adaptative pour terminaux moyens (MD)..."
+
+#: src/pyams_file/zmi/image.py:512
+msgid "Select image for medium (MD) devices"
+msgstr "Sélectionner l'image affichée sur les terminaux moyens (taille MD)"
+
+#: src/pyams_file/zmi/image.py:534
+msgid "Select responsive LG image..."
+msgstr "Image adaptative pour grands terminaux (LG)..."
+
+#: src/pyams_file/zmi/image.py:546
+msgid "Select image for large (LG) devices"
+msgstr "Sélectionner l'image affichée sur les grands terminaux (taille LG)"
+
+#: src/pyams_file/zmi/image.py:71 src/pyams_file/zmi/image.py:171
+#: src/pyams_file/zmi/image.py:236 src/pyams_file/zmi/image.py:377
 msgid "Close"
 msgstr "Fermer"
 
-#: src/pyams_file/zmi/image.py:73
+#: src/pyams_file/zmi/image.py:78
 msgid "New image width"
 msgstr "Largeur de l'image"
 
-#: src/pyams_file/zmi/image.py:75
+#: src/pyams_file/zmi/image.py:80
 msgid "New image height"
 msgstr "Hauteur de l'image"
 
-#: src/pyams_file/zmi/image.py:77
+#: src/pyams_file/zmi/image.py:82
 msgid "Keep aspect ratio"
 msgstr "Ne pas déformer l'image"
 
-#: src/pyams_file/zmi/image.py:78
+#: src/pyams_file/zmi/image.py:83
 msgid ""
 "Check to keep original aspect ratio; image will be resized as large as "
 "possible within given limits"
@@ -130,34 +162,38 @@
 "L'image sera redimensionnée (sans jamais être agrandie !) pour être aussi "
 "grande que possible en fonction des contraintes indiquées."
 
-#: src/pyams_file/zmi/image.py:163
+#: src/pyams_file/zmi/image.py:172
 msgid "Crop"
 msgstr "Recadrer l'image"
 
-#: src/pyams_file/zmi/image.py:223
+#: src/pyams_file/zmi/image.py:237
 msgid "Select thumbnail"
 msgstr "Sélectionner cette vignette"
 
-#: src/pyams_file/interfaces/__init__.py:95
+#: src/pyams_file/zmi/image.py:378
+msgid "Select image"
+msgstr "Sélectionner l'image"
+
+#: src/pyams_file/interfaces/__init__.py:99
 msgid "Title"
 msgstr "Titre"
 
-#: src/pyams_file/interfaces/__init__.py:98
+#: src/pyams_file/interfaces/__init__.py:102
 msgid "Description"
 msgstr "Description"
 
-#: src/pyams_file/interfaces/__init__.py:101
+#: src/pyams_file/interfaces/__init__.py:105
 msgid "Save file as..."
 msgstr "Enregistrer sous..."
 
-#: src/pyams_file/interfaces/__init__.py:102
+#: src/pyams_file/interfaces/__init__.py:106
 msgid "Name under which the file will be saved"
 msgstr "Nom proposé automatiquement lors de l'enregistrement du fichier"
 
-#: src/pyams_file/interfaces/__init__.py:105
+#: src/pyams_file/interfaces/__init__.py:109
 msgid "Language"
 msgstr "Langue"
 
-#: src/pyams_file/interfaces/__init__.py:106
+#: src/pyams_file/interfaces/__init__.py:110
 msgid "File's content language"
 msgstr "Langue du contenu du fichier"
--- a/src/pyams_file/locales/pyams_file.pot	Thu Apr 21 16:40:03 2016 +0200
+++ b/src/pyams_file/locales/pyams_file.pot	Tue Nov 15 09:55:02 2016 +0100
@@ -1,12 +1,12 @@
 # 
 # SOME DESCRIPTIVE TITLE
 # This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-02-10 17:59+0100\n"
+"POT-Creation-Date: 2016-11-14 17:49+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -52,104 +52,140 @@
 msgid "Delete content"
 msgstr ""
 
-#: ./src/pyams_file/zmi/file.py:41
+#: ./src/pyams_file/zmi/file.py:42
 msgid "Properties..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/file.py:52
+#: ./src/pyams_file/zmi/file.py:53
 msgid "Update file properties"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:56
+#: ./src/pyams_file/zmi/image.py:61
 msgid "Resize image..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:107 ./src/pyams_file/zmi/image.py:67
+#: ./src/pyams_file/zmi/image.py:111 ./src/pyams_file/zmi/image.py:72
 msgid "Resize image"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:141
+#: ./src/pyams_file/zmi/image.py:146
 msgid ""
 "You can use this form to change image dimensions.\n"
 "\n"
 "This will generate a new image only if requested size is smaller than the original one."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:152
+#: ./src/pyams_file/zmi/image.py:161
 msgid "Crop image..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:170
+#: ./src/pyams_file/zmi/image.py:179
 msgid "Crop image"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:231
+#: ./src/pyams_file/zmi/image.py:245
 msgid "Select square thumbnail..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:242
+#: ./src/pyams_file/zmi/image.py:256
 msgid "Select square thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:290
+#: ./src/pyams_file/zmi/image.py:309
 msgid "Select panoramic thumbnail..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:306
+#: ./src/pyams_file/zmi/image.py:320
 msgid "Select panoramic thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:66 ./src/pyams_file/zmi/image.py:162
-#: ./src/pyams_file/zmi/image.py:222
+#: ./src/pyams_file/zmi/image.py:432
+msgid "Select responsive XS image..."
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:444
+msgid "Select image for extra-small (XS) devices"
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:466
+msgid "Select responsive SM image..."
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:478
+msgid "Select image for small (SM) devices"
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:500
+msgid "Select responsive MD image..."
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:512
+msgid "Select image for medium (MD) devices"
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:534
+msgid "Select responsive LG image..."
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:546
+msgid "Select image for large (LG) devices"
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:71 ./src/pyams_file/zmi/image.py:171
+#: ./src/pyams_file/zmi/image.py:236 ./src/pyams_file/zmi/image.py:377
 msgid "Close"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:73
+#: ./src/pyams_file/zmi/image.py:78
 msgid "New image width"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:75
+#: ./src/pyams_file/zmi/image.py:80
 msgid "New image height"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:77
+#: ./src/pyams_file/zmi/image.py:82
 msgid "Keep aspect ratio"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:78
+#: ./src/pyams_file/zmi/image.py:83
 msgid ""
 "Check to keep original aspect ratio; image will be resized as large as "
 "possible within given limits"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:163
+#: ./src/pyams_file/zmi/image.py:172
 msgid "Crop"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:223
+#: ./src/pyams_file/zmi/image.py:237
 msgid "Select thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/interfaces/__init__.py:95
+#: ./src/pyams_file/zmi/image.py:378
+msgid "Select image"
+msgstr ""
+
+#: ./src/pyams_file/interfaces/__init__.py:99
 msgid "Title"
 msgstr ""
 
-#: ./src/pyams_file/interfaces/__init__.py:98
+#: ./src/pyams_file/interfaces/__init__.py:102
 msgid "Description"
 msgstr ""
 
-#: ./src/pyams_file/interfaces/__init__.py:101
+#: ./src/pyams_file/interfaces/__init__.py:105
 msgid "Save file as..."
 msgstr ""
 
-#: ./src/pyams_file/interfaces/__init__.py:102
+#: ./src/pyams_file/interfaces/__init__.py:106
 msgid "Name under which the file will be saved"
 msgstr ""
 
-#: ./src/pyams_file/interfaces/__init__.py:105
+#: ./src/pyams_file/interfaces/__init__.py:109
 msgid "Language"
 msgstr ""
 
-#: ./src/pyams_file/interfaces/__init__.py:106
+#: ./src/pyams_file/interfaces/__init__.py:110
 msgid "File's content language"
 msgstr ""
--- a/src/pyams_file/thumbnail.py	Thu Apr 21 16:40:03 2016 +0200
+++ b/src/pyams_file/thumbnail.py	Tue Nov 15 09:55:02 2016 +0100
@@ -39,9 +39,9 @@
 THUMBNAIL_ANNOTATIONS_KEY = 'pyams_file.image.thumbnails'
 THUMBNAIL_GEOMETRY_KEY = 'pyams_file.image.geometry'
 
-THUMB_WIDTH = re.compile('^w([0-9]+)$')
-THUMB_HEIGHT = re.compile('^h([0-9]+)$')
-THUMB_SIZE = re.compile('^([0-9]+)x([0-9]+)$')
+THUMB_WIDTH = re.compile('^(?:\w+\:)?w([0-9]+)$')
+THUMB_HEIGHT = re.compile('^(?:\w+\:)?h([0-9]+)$')
+THUMB_SIZE = re.compile('^(?:\w+\:)?([0-9]+)x([0-9]+)$')
 
 
 @adapter_config(context=IImage, provides=IThumbnail)
@@ -88,30 +88,31 @@
         else:
             return None
 
-    def get_thumbnail_geometry(self, thumbnail_name):
+    def get_geometry(self, selection_name):
         annotations = IAnnotations(self.image)
         geometries = annotations.get(THUMBNAIL_GEOMETRY_KEY, {})
         # get default geometry for custom thumbnails
-        if ':' in thumbnail_name:
-            thumbnailer_name, options = thumbnail_name.split(':', 1)
+        if ':' in selection_name:
+            selection_name, options = selection_name.split(':', 1)
         else:
-            thumbnailer_name = thumbnail_name
+            selection_name = selection_name
             options = None
-        if thumbnailer_name in geometries:
-            return geometries[thumbnailer_name]
+        if selection_name in geometries:
+            return geometries[selection_name]
         registry = check_request().registry
-        thumbnailer = registry.queryAdapter(self.image, IThumbnailer, name=thumbnailer_name)
+        thumbnailer = registry.queryAdapter(self.image, IThumbnailer, name=selection_name)
         if thumbnailer is not None:
             return thumbnailer.get_default_geometry(options)
 
-    def set_thumbnail_geometry(self, thumbnail_name, geometry):
+    def set_geometry(self, selection_name, geometry):
         annotations = IAnnotations(self.image)
         geometries = annotations.get(THUMBNAIL_GEOMETRY_KEY)
         if geometries is None:
             geometries = annotations[THUMBNAIL_GEOMETRY_KEY] = PersistentDict()
-        geometries[thumbnail_name] = geometry
+        geometries[selection_name] = geometry
         for current_thumbnail_name in self.thumbnails.copy():
-            if current_thumbnail_name.startswith(thumbnail_name):
+            if (current_thumbnail_name == selection_name) or \
+               current_thumbnail_name.startswith(selection_name + ':'):
                 self.delete_thumbnail(current_thumbnail_name)
 
     def clear_geometries(self):
@@ -131,6 +132,32 @@
         else:
             return None, None
 
+    def get_selection(self, selection_name, format=None):
+        if selection_name in self.thumbnails:
+            return self.thumbnails[selection_name]
+        geometry = self.get_geometry(selection_name)
+        registry = get_current_registry()
+        thumbnailer = registry.queryAdapter(self.image, IThumbnailer, name=selection_name)
+        if thumbnailer is not None:
+            selection = thumbnailer.create_thumbnail(geometry, format)
+            if selection is not None:
+                if isinstance(selection, tuple):
+                    selection, format = selection
+                else:
+                    format = 'jpeg'
+                selection = FileFactory(selection)
+                alsoProvides(selection, IThumbnailFile)
+                registry.notify(ObjectCreatedEvent(selection))
+                self.thumbnails[selection_name] = selection
+                selection_size = selection.get_image_size()
+                locate(selection, self.image,
+                       '++thumb++{0}:{1}x{2}.{3}'.format(selection_name,
+                                                         selection_size[0],
+                                                         selection_size[1],
+                                                         format))
+                registry.notify(ObjectAddedEvent(selection))
+                return selection
+
     def get_thumbnail(self, thumbnail_name, format=None, watermark=None):
         # check for existing thumbnail
         if thumbnail_name in self.thumbnails:
@@ -151,7 +178,7 @@
                 thumbnailer_name, options = thumbnail_name.split(':', 1)
             else:
                 thumbnailer_name = thumbnail_name
-            options = name = thumbnail_name
+                options = name = thumbnail_name
         # generate and store thumbnail
         registry = get_current_registry()
         thumbnailer = registry.queryAdapter(self.image, IThumbnailer, name=thumbnailer_name)
@@ -212,6 +239,11 @@
             thumbnail_name = name
             format = None
         thumbnails = IThumbnail(self.context)
+        if ':' in thumbnail_name:
+            selection_name, thumbnail_name = thumbnail_name.split(':', 1)
+            selection = thumbnails.get_selection(selection_name, format)
+            transaction.commit()
+            thumbnails = IThumbnail(selection)
         result = thumbnails.get_thumbnail(thumbnail_name, format)
         transaction.commit()
         return result
--- a/src/pyams_file/widget/__init__.py	Thu Apr 21 16:40:03 2016 +0200
+++ b/src/pyams_file/widget/__init__.py	Tue Nov 15 09:55:02 2016 +0100
@@ -18,7 +18,7 @@
 
 # import interfaces
 from pyams_file.interfaces import IFileField, IFileWidget, IImageField, IImageWidget, \
-    IThumnailImageWidget, IThumbnailImageField, DELETED_FILE
+    IThumbnailImageWidget, IThumbnailImageField, DELETED_FILE
 from pyams_form.interfaces.form import IFormLayer
 from z3c.form.interfaces import NOT_CHANGED, IFieldWidget, IDataConverter
 
@@ -101,7 +101,7 @@
     return FieldWidget(field, ImageWidget(request))
 
 
-@implementer_only(IThumnailImageWidget)
+@implementer_only(IThumbnailImageWidget)
 class ThumbnailImageWidget(ImageWidget):
     """Image widget with thumbnail images selection"""
 
--- a/src/pyams_file/zmi/file.py	Thu Apr 21 16:40:03 2016 +0200
+++ b/src/pyams_file/zmi/file.py	Tue Nov 15 09:55:02 2016 +0100
@@ -62,7 +62,8 @@
 
     def updateWidgets(self, prefix=None):
         super(FilePropertiesEditForm, self).updateWidgets()
-        self.widgets['description'].label_css_class = 'textarea'
+        if 'description' in self.widgets:
+            self.widgets['description'].widget_css_class = 'textarea'
 
 
 @view_config(name='properties.json', context=IFile, request_type=IPyAMSLayer,
--- a/src/pyams_file/zmi/image.py	Thu Apr 21 16:40:03 2016 +0200
+++ b/src/pyams_file/zmi/image.py	Tue Nov 15 09:55:02 2016 +0100
@@ -16,7 +16,7 @@
 # import standard library
 
 # import interfaces
-from pyams_file.interfaces import IImage, IThumnailImageWidget, IThumbnail
+from pyams_file.interfaces import IImage, IThumbnail, IResponsiveImage, IImageWidget
 from pyams_form.interfaces.form import IWidgetsPrefixViewletsManager, IFormHelp
 from pyams_skin.interfaces.viewlet import IContextActions
 from pyams_skin.layer import IPyAMSLayer
@@ -24,7 +24,7 @@
 from pyams_zmi.layer import IAdminLayer
 
 # import packages
-from pyams_file.image import ThumbnailGeometrry
+from pyams_file.image import ThumbnailGeometry
 from pyams_form.form import AJAXEditForm
 from pyams_form.help import FormHelp
 from pyams_form.schema import CloseButton
@@ -43,7 +43,7 @@
 from pyams_file import _
 
 
-@viewlet_config(name='image.resize.divider', context=IImage, layer=IPyAMSLayer, view=Interface,
+@viewlet_config(name='image.resize.divider', context=IImage, layer=IPyAMSLayer, view=IImageWidget,
                 manager=IContextActions, permission=MANAGE_PERMISSION, weight=19)
 class ImageDividerAction(ToolbarMenuDivider):
     """Image divider action"""
@@ -53,7 +53,7 @@
 # Image resize
 #
 
-@viewlet_config(name='image.resize.action', context=IImage, layer=IPyAMSLayer, view=Interface,
+@viewlet_config(name='image.resize.action', context=IImage, layer=IPyAMSLayer, view=IImageWidget,
                 manager=IContextActions, permission=MANAGE_PERMISSION, weight=20)
 class ImageResizeAction(ToolbarMenuItem):
     """Image resize action"""
@@ -153,7 +153,7 @@
 # Image crop
 #
 
-@viewlet_config(name='image.crop.action', context=IImage, layer=IPyAMSLayer, view=Interface,
+@viewlet_config(name='image.crop.action', context=IImage, layer=IPyAMSLayer, view=IImageWidget,
                 manager=IContextActions, permission=MANAGE_PERMISSION, weight=21)
 class ImageCropAction(ToolbarMenuItem):
     """Image crop action"""
@@ -224,7 +224,7 @@
 # Image square thumbnail selection
 #
 
-@viewlet_config(name='image.thumb.divider', context=IImage, layer=IPyAMSLayer, view=IThumnailImageWidget,
+@viewlet_config(name='image.thumb.divider', context=IImage, layer=IPyAMSLayer, view=IImageWidget,
                 manager=IContextActions, permission=MANAGE_PERMISSION, weight=30)
 class ImageThumbnailsDividerAction(ToolbarMenuDivider):
     """Image divider action"""
@@ -237,7 +237,7 @@
     crop = button.Button(name='crop', title=_("Select thumbnail"))
 
 
-@viewlet_config(name='image.thumb.square.action', context=IImage, layer=IPyAMSLayer, view=IThumnailImageWidget,
+@viewlet_config(name='image.thumb.square.action', context=IImage, layer=IPyAMSLayer, view=IImageWidget,
                 manager=IContextActions, permission=MANAGE_PERMISSION, weight=31)
 class ImageSquareThumbnailAction(ToolbarMenuItem):
     """Square thumbnail image selection"""
@@ -277,12 +277,12 @@
     """Image square thumbnail edit form, AJAX renderer"""
 
     def update_content(self, content, data):
-        geometry = ThumbnailGeometrry()
+        geometry = ThumbnailGeometry()
         geometry.x1 = int(self.request.params.get('selection.x1'))
         geometry.y1 = int(self.request.params.get('selection.y1'))
         geometry.x2 = int(self.request.params.get('selection.x2'))
         geometry.y2 = int(self.request.params.get('selection.y2'))
-        IThumbnail(self.context).set_thumbnail_geometry('square', geometry)
+        IThumbnail(self.context).set_geometry('square', geometry)
 
     def get_ajax_output(self, changes):
         return {'status': 'success',
@@ -301,7 +301,7 @@
 # Image panoramic thumbnail selection
 #
 
-@viewlet_config(name='image.thumb.pano.action', context=IImage, layer=IAdminLayer, view=IThumnailImageWidget,
+@viewlet_config(name='image.thumb.pano.action', context=IImage, layer=IAdminLayer, view=IImageWidget,
                 manager=IContextActions, permission=MANAGE_PERMISSION, weight=32)
 class ImagePanoThumbnailAction(ToolbarMenuItem):
     """Panoramic thumbnail image selection"""
@@ -312,11 +312,6 @@
     url = 'pano-thumbnail.html'
     modal_target = True
 
-    def updateActions(self):
-        super(ImageSquareThumbnailEditForm, self).updateActions()
-        if 'crop' in self.actions:
-            self.actions['crop'].addClass('btn-primary')
-
 
 @pagelet_config(name='pano-thumbnail.html', context=IImage, layer=IPyAMSLayer, permission=MANAGE_PERMISSION)
 class ImagePanoThumbnailEditForm(AdminDialogEditForm):
@@ -346,12 +341,12 @@
     """Image panoramic thumbnail edit form, AJAX renderer"""
 
     def update_content(self, content, data):
-        geometry = ThumbnailGeometrry()
+        geometry = ThumbnailGeometry()
         geometry.x1 = int(self.request.params.get('selection.x1'))
         geometry.y1 = int(self.request.params.get('selection.y1'))
         geometry.x2 = int(self.request.params.get('selection.x2'))
         geometry.y2 = int(self.request.params.get('selection.y2'))
-        IThumbnail(self.context).set_thumbnail_geometry('pano', geometry)
+        IThumbnail(self.context).set_geometry('pano', geometry)
 
     def get_ajax_output(self, changes):
         return {'status': 'success',
@@ -364,3 +359,198 @@
 @template_config(template='templates/image-pano-thumbnail.pt')
 class ImagePanoThumbnailViewletsPrefix(Viewlet):
     """Image panoramic thumbnail viewlets prefix"""
+
+
+#
+# Image responsive selections
+#
+
+@viewlet_config(name='responsive-image.selection.divider', context=IResponsiveImage, layer=IPyAMSLayer,
+                view=IImageWidget, manager=IContextActions, permission=MANAGE_PERMISSION, weight=40)
+class ResponsiveImageSelectionDividerAction(ToolbarMenuDivider):
+    """Image divider action"""
+
+
+class IResponsiveImageSelectionFormButtons(Interface):
+    """Responsive image selection form buttons"""
+
+    close = CloseButton(name='close', title=_("Close"))
+    select = button.Button(name='select', title=_("Select image"))
+
+
+class ResponsiveImageSelectionForm(AdminDialogEditForm):
+    """Base responsive image selection edit form"""
+
+    dialog_class = 'modal-large'
+
+    fields = field.Fields(Interface)
+    buttons = button.Buttons(IResponsiveImageSelectionFormButtons)
+
+    @property
+    def title(self):
+        return self.context.title or self.context.filename
+
+    def updateActions(self):
+        super(ResponsiveImageSelectionForm, self).updateActions()
+        if 'select' in self.actions:
+            self.actions['select'].addClass('btn-primary')
+
+
+class ResponsiveImageSelectionAJAXForm(AJAXEditForm):
+    """Base responsive image selection edit form, JSON renderer"""
+
+    def update_content(self, content, data):
+        geometry = ThumbnailGeometry()
+        geometry.x1 = int(self.request.params.get('selection.x1'))
+        geometry.y1 = int(self.request.params.get('selection.y1'))
+        geometry.x2 = int(self.request.params.get('selection.x2'))
+        geometry.y2 = int(self.request.params.get('selection.y2'))
+        IThumbnail(self.context).set_geometry(self.selection_size, geometry)
+
+    def get_ajax_output(self, changes):
+        return {'status': 'success',
+                'smallbox': self.request.localizer.translate(self.successMessage),
+                'smallbox_status': 'success'}
+
+
+@viewlet_config(name='responsive-image.selection.widgets-prefix', context=IResponsiveImage, layer=IAdminLayer,
+                view=ResponsiveImageSelectionForm, manager=IWidgetsPrefixViewletsManager)
+@template_config(template='templates/image-selection.pt')
+class ResponsiveImageSelectionViewletsPrefix(Viewlet):
+    """Responsive image selection viewlets prefix"""
+
+
+#
+# Extra-small devices selection
+#
+
+@viewlet_config(name='responsive-image.selection.xs.action', context=IResponsiveImage, layer=IAdminLayer,
+                view=IImageWidget, manager=IContextActions, permission=MANAGE_PERMISSION, weight=41)
+class ResponsiveImageXsSelectionAction(ToolbarMenuItem):
+    """Responsive image XS selection"""
+
+    label = _("Select responsive XS image...")
+    label_css_class = 'fa fa-fw fa-mobile'
+
+    url = 'selection-xs.html'
+    modal_target = True
+
+
+@pagelet_config(name='selection-xs.html', context=IResponsiveImage, layer=IPyAMSLayer,
+                permission=MANAGE_PERMISSION)
+class ResponsiveImageXsSelectionForm(ResponsiveImageSelectionForm):
+    """Responsive image XS selection edit form"""
+
+    legend = _("Select image for extra-small (XS) devices")
+    icon_css_class = 'fa fa-fw fa-mobile'
+
+    selection_size = 'xs'
+    ajax_handler = 'selection-xs.json'
+
+
+@view_config(name='selection-xs.json', context=IResponsiveImage, request_type=IPyAMSLayer,
+             permission=MANAGE_PERMISSION, renderer='json', xhr=True)
+class ResponsiveImageXsSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageXsSelectionForm):
+    """Responsive image XS selection edit form, JSON renderer"""
+
+
+#
+# Small devices selection
+#
+
+@viewlet_config(name='responsive-image.selection.sm.action', context=IResponsiveImage, layer=IAdminLayer,
+                view=IImageWidget, manager=IContextActions, permission=MANAGE_PERMISSION, weight=42)
+class ResponsiveImageSmSelectionAction(ToolbarMenuItem):
+    """Responsive image SM selection"""
+
+    label = _("Select responsive SM image...")
+    label_css_class = 'fa fa-fw fa-tablet'
+
+    url = 'selection-sm.html'
+    modal_target = True
+
+
+@pagelet_config(name='selection-sm.html', context=IResponsiveImage, layer=IPyAMSLayer,
+                permission=MANAGE_PERMISSION)
+class ResponsiveImageSmSelectionForm(ResponsiveImageSelectionForm):
+    """Responsive image SM selection edit form"""
+
+    legend = _("Select image for small (SM) devices")
+    icon_css_class = 'fa fa-fw fa-tablet'
+
+    selection_size = 'sm'
+    ajax_handler = 'selection-sm.json'
+
+
+@view_config(name='selection-sm.json', context=IResponsiveImage, request_type=IPyAMSLayer,
+             permission=MANAGE_PERMISSION, renderer='json', xhr=True)
+class ResponsiveImageSmSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageSmSelectionForm):
+    """Responsive image SM selection edit form, JSON renderer"""
+
+
+#
+# Medium devices selection
+#
+
+@viewlet_config(name='responsive-image.selection.md.action', context=IResponsiveImage, layer=IAdminLayer,
+                view=IImageWidget, manager=IContextActions, permission=MANAGE_PERMISSION, weight=43)
+class ResponsiveImageMdSelectionAction(ToolbarMenuItem):
+    """Responsive image MD selection"""
+
+    label = _("Select responsive MD image...")
+    label_css_class = 'fa fa-fw fa-desktop'
+
+    url = 'selection-md.html'
+    modal_target = True
+
+
+@pagelet_config(name='selection-md.html', context=IResponsiveImage, layer=IPyAMSLayer,
+                permission=MANAGE_PERMISSION)
+class ResponsiveImageMdSelectionForm(ResponsiveImageSelectionForm):
+    """Responsive image MD selection edit form"""
+
+    legend = _("Select image for medium (MD) devices")
+    icon_css_class = 'fa fa-fw fa-desktop'
+
+    selection_size = 'md'
+    ajax_handler = 'selection-md.json'
+
+
+@view_config(name='selection-md.json', context=IResponsiveImage, request_type=IPyAMSLayer,
+             permission=MANAGE_PERMISSION, renderer='json', xhr=True)
+class ResponsiveImageMdSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageMdSelectionForm):
+    """Responsive image MD selection edit form, JSON renderer"""
+
+
+#
+# Large devices selection
+#
+
+@viewlet_config(name='responsive-image.selection.lg.action', context=IResponsiveImage, layer=IAdminLayer,
+                view=IImageWidget, manager=IContextActions, permission=MANAGE_PERMISSION, weight=44)
+class ResponsiveImageLgSelectionAction(ToolbarMenuItem):
+    """Responsive image LG selection"""
+
+    label = _("Select responsive LG image...")
+    label_css_class = 'fa fa-fw fa-television'
+
+    url = 'selection-lg.html'
+    modal_target = True
+
+
+@pagelet_config(name='selection-lg.html', context=IResponsiveImage, layer=IPyAMSLayer,
+                permission=MANAGE_PERMISSION)
+class ResponsiveImageLgSelectionForm(ResponsiveImageSelectionForm):
+    """Responsive image LG selection edit form"""
+
+    legend = _("Select image for large (LG) devices")
+    icon_css_class = 'fa fa-fw fa-television'
+
+    selection_size = 'lg'
+    ajax_handler = 'selection-lg.json'
+
+
+@view_config(name='selection-lg.json', context=IResponsiveImage, request_type=IPyAMSLayer,
+             permission=MANAGE_PERMISSION, renderer='json', xhr=True)
+class ResponsiveImageLgSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageLgSelectionForm):
+    """Responsive image LG selection edit form, JSON renderer"""
--- a/src/pyams_file/zmi/templates/image-pano-thumbnail.pt	Thu Apr 21 16:40:03 2016 +0200
+++ b/src/pyams_file/zmi/templates/image-pano-thumbnail.pt	Tue Nov 15 09:55:02 2016 +0100
@@ -2,7 +2,7 @@
 				 image thumbnails.get_thumbnail('800x600', 'jpeg');
 				 size context.get_image_size();
 				 thumb_size image.get_image_size();
-				 geometry thumbnails.get_thumbnail_geometry('pano');">
+				 geometry thumbnails.get_geometry('pano');">
 	<input type="hidden" name="selection.x1" tal:attributes="value geometry.x1" />
 	<input type="hidden" name="selection.y1" tal:attributes="value geometry.x2" />
 	<input type="hidden" name="selection.x2" tal:attributes="value geometry.y1" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_file/zmi/templates/image-selection.pt	Tue Nov 15 09:55:02 2016 +0100
@@ -0,0 +1,22 @@
+<tal:var define="thumbnails extension:thumbnails(context);
+				 image thumbnails.get_thumbnail('800x600', 'jpeg');
+				 size context.get_image_size();
+				 thumb_size image.get_image_size();
+				 geometry thumbnails.get_geometry(view.__parent__.selection_size);">
+	<input type="hidden" name="selection.x1" tal:attributes="value geometry.x1" />
+	<input type="hidden" name="selection.y1" tal:attributes="value geometry.x2" />
+	<input type="hidden" name="selection.x2" tal:attributes="value geometry.y1" />
+	<input type="hidden" name="selection.y2" tal:attributes="value geometry.y2" />
+ 	<img class="imgareaselect"
+		 data-ams-imgareaselect-parent=".modal-dialog"
+		 data-ams-imgareaselect-target-field="selection."
+		 tal:attributes="src extension:absolute_url(image);
+						 width thumb_size[0];
+						 height thumb_size[1];
+						 data-ams-imgareaselect-image-width size[0];
+						 data-ams-imgareaselect-image-height size[1];
+						 data-ams-imgareaselect-x1 geometry.x1;
+						 data-ams-imgareaselect-y1 geometry.y1;
+						 data-ams-imgareaselect-x2 geometry.x2;
+						 data-ams-imgareaselect-y2 geometry.y2;" />
+</tal:var>
--- a/src/pyams_file/zmi/templates/image-square-thumbnail.pt	Thu Apr 21 16:40:03 2016 +0200
+++ b/src/pyams_file/zmi/templates/image-square-thumbnail.pt	Tue Nov 15 09:55:02 2016 +0100
@@ -2,7 +2,7 @@
 				 image thumbnails.get_thumbnail('800x600', 'jpeg');
 				 size context.get_image_size();
 				 thumb_size image.get_image_size();
-				 geometry thumbnails.get_thumbnail_geometry('square');">
+				 geometry thumbnails.get_geometry('square');">
 	<input type="hidden" name="selection.x1" tal:attributes="value geometry.x1" />
 	<input type="hidden" name="selection.y1" tal:attributes="value geometry.x2" />
 	<input type="hidden" name="selection.x2" tal:attributes="value geometry.y1" />