Added watermark utility
authorThierry Florac <thierry.florac@onf.fr>
Wed, 02 Sep 2015 15:30:59 +0200
changeset 20 b3631582e30c
parent 19 188b50ad1d39
child 21 43c056a88f08
Added watermark utility
src/pyams_file/interfaces/__init__.py
src/pyams_file/thumbnail.py
src/pyams_file/watermark.py
--- a/src/pyams_file/interfaces/__init__.py	Wed Jul 22 16:56:53 2015 +0200
+++ b/src/pyams_file/interfaces/__init__.py	Wed Sep 02 15:30:59 2015 +0200
@@ -238,7 +238,7 @@
     def get_thumbnail_name(self, thumbnail_name, with_size=None):
         """Get matching name for the given thumbnail name or size"""
 
-    def get_thumbnail(self, thumbnail_name, format=None):
+    def get_thumbnail(self, thumbnail_name, format=None, watermark=None):
         """Get requested thumbnail
 
         Display can be specified as:
@@ -253,3 +253,10 @@
 
     def clear_thumbnails(self):
         """Remove all thumbnails from object annotations"""
+
+
+class IWatermarker(Interface):
+    """Interface of utility used to add image watermark"""
+
+    def add_watermark(self, image, watermark, position='scale', opacity=1, format=None):
+        """Add watermark to given image"""
--- a/src/pyams_file/thumbnail.py	Wed Jul 22 16:56:53 2015 +0200
+++ b/src/pyams_file/thumbnail.py	Wed Sep 02 15:30:59 2015 +0200
@@ -18,7 +18,7 @@
 import transaction
 
 # import interfaces
-from pyams_file.interfaces import IImage, IThumbnail, IThumbnailer, IFileModifiedEvent
+from pyams_file.interfaces import IImage, IThumbnail, IThumbnailer, IFileModifiedEvent, IWatermarker
 from pyams_utils.interfaces.tales import ITALESExtension
 from zope.annotation.interfaces import IAnnotations
 from zope.traversing.interfaces import ITraversable
@@ -27,6 +27,7 @@
 from persistent.dict import PersistentDict
 from pyams_file.file import FileFactory
 from pyams_utils.adapter import ContextAdapter, ContextRequestViewAdapter, adapter_config
+from pyams_utils.registry import query_utility
 from pyams_utils.request import check_request
 from pyramid.events import subscriber
 from pyramid.threadlocal import get_current_registry
@@ -130,7 +131,7 @@
         else:
             return None, None
 
-    def get_thumbnail(self, thumbnail_name, format=None):
+    def get_thumbnail(self, thumbnail_name, format=None, watermark=None):
         # check for existing thumbnail
         if thumbnail_name in self.thumbnails:
             return self.thumbnails[thumbnail_name]
@@ -161,6 +162,13 @@
                     thumbnail_image, format = thumbnail_image
                 else:
                     format = 'jpeg'
+                # check watermark
+                if watermark is not None:
+                    watermarker = query_utility(IWatermarker)
+                    if watermarker is not None:
+                        thumbnail_image.seek(0)
+                        thumbnail_image, format = watermarker.add_watermark(thumbnail_image, watermark)
+                # create final image
                 thumbnail_image = FileFactory(thumbnail_image)
                 registry.notify(ObjectCreatedEvent(thumbnail_image))
                 self.thumbnails[name] = thumbnail_image
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_file/watermark.py	Wed Sep 02 15:30:59 2015 +0200
@@ -0,0 +1,93 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import os.path
+
+from io import StringIO, BytesIO
+
+# import interfaces
+from pyams_file.interfaces import IWatermarker, IImage
+
+# import packages
+from PIL import Image, ImageEnhance
+from pyams_utils.registry import utility_config
+
+
+@utility_config(provides=IWatermarker)
+class ImageWatermarker(object):
+    """Image watermarker utility"""
+
+    def _reduce_opacity(self, image, opacity):
+        """Returns an image with reduced opacity."""
+        assert 0 <= opacity <= 1
+        if image.mode != 'RGBA':
+            image = image.convert('RGBA')
+        else:
+            image = image.copy()
+        alpha = image.split()[3]
+        alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
+        image.putalpha(alpha)
+        return image
+
+    def add_watermark(self, image, watermark, position='scale', opacity=1, format=None):
+        """Adds a watermark to an image and return a new image"""
+        # init image
+        if IImage.providedBy(image):
+            image = image.data
+        if isinstance(image, bytes):
+            image = BytesIO(image)
+        elif isinstance(image, str) and not os.path.exists(image):
+            image = StringIO(image)
+        image = Image.open(image)
+        # check format
+        if not format:
+            format = image.format
+        format = format.upper()
+        if format not in ('GIF', 'JPEG', 'PNG'):
+            format = 'JPEG'
+        # check RGBA mode
+        if image.mode != 'RGBA':
+            image = image.convert('RGBA')
+        # init watermark
+        if isinstance(watermark, str) and os.path.exists(watermark):
+            watermark = Image.open(watermark)
+        else:
+            if IImage.providedBy(watermark):
+                watermark = Image.open(StringIO(watermark.data))
+            else:
+                watermark = Image.open(watermark)
+        if opacity < 1:
+            watermark = self._reduce_opacity(watermark, opacity)
+        # create a transparent layer the size of the image and draw the
+        # watermark in that layer.
+        layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
+        if position == 'tile':
+            for y in range(0, image.size[1], watermark.size[1]):
+                for x in range(0, image.size[0], watermark.size[0]):
+                    layer.paste(watermark, (x, y))
+        elif position == 'scale':
+            # scale, but preserve the aspect ratio
+            ratio = min(float(image.size[0]) / watermark.size[0], float(image.size[1]) / watermark.size[1])
+            w = int(watermark.size[0] * ratio)
+            h = int(watermark.size[1] * ratio)
+            watermark = watermark.resize((w, h))
+            layer.paste(watermark, (int((image.size[0] - w) / 2), int((image.size[1] - h) / 2)))
+        else:
+            layer.paste(watermark, position)
+        # composite the watermark with the layer
+        new = BytesIO()
+        Image.composite(layer, image, layer).save(new, format)
+        return new.getvalue(), format.lower()