--- 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()