X-Git-Url: https://scm.cri.minesparis.psl.eu/git/Photo.git/blobdiff_plain/b0a7e10b4f32cf74864bb53268ca4d3080f23bc0..6c41809185e322ce2d30e98234f71144f78f06c0:/Photo.py?ds=inline diff --git a/Photo.py b/Photo.py deleted file mode 100755 index 787e4ee..0000000 --- a/Photo.py +++ /dev/null @@ -1,399 +0,0 @@ -# -*- coding: utf-8 -*- -####################################################################################### -# Photo is a part of Plinn - http://plinn.org # -# Copyright (C) 2004-2007 Benoît PIN # -# # -# This program is free software; you can redistribute it and/or # -# modify it under the terms of the GNU General Public License # -# as published by the Free Software Foundation; either version 2 # -# of the License, or (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program; if not, write to the Free Software # -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # -####################################################################################### -""" Photo zope object - - - -""" - -from Globals import InitializeClass, DTMLFile -from AccessControl import ClassSecurityInfo -from AccessControl.Permissions import manage_properties, view -from metadata import Metadata -from TileSupport import TileSupport -from xmputils import TIFF_ORIENTATIONS -from BTrees.OOBTree import OOBTree -from cache import memoizedmethod - -from blobbases import Image, cookId, getImageInfo -import PIL.Image -import string -from math import floor -from types import StringType -from logging import getLogger -console = getLogger('Photo.Photo') - - - -def _strSize(size) : - return str(size[0]) + '_' + str(size[1]) - -def getNewSize(fullSize, maxNewSize) : - fullWidth, fullHeight = fullSize - maxWidth, maxHeight = maxNewSize - - widthRatio = float(maxWidth) / fullWidth - if int(fullHeight * widthRatio) > maxWidth : - heightRatio = float(maxHeight) / fullHeight - return (int(fullWidth * heightRatio) , maxHeight) - else : - return (maxWidth, int(fullHeight * widthRatio)) - - - - - - -class Photo(Image, TileSupport, Metadata): - "Photo éditable en ligne" - - meta_type = 'Photo' - - security = ClassSecurityInfo() - - manage_editForm = DTMLFile('dtml/photoEdit',globals(), - Kind='Photo', kind='photo') - manage_editForm._setName('manage_editForm') - manage = manage_main = manage_editForm - view_image_or_file = DTMLFile('dtml/photoView',globals()) - - manage_options=( - {'label':'Edit', 'action':'manage_main', - 'help':('OFSP','Image_Edit.stx')}, - {'label':'View', 'action':'view_image_or_file', - 'help':('OFSP','Image_View.stx')},) + Image.manage_options[2:] - - - filters = ['NEAREST', 'BILINEAR', 'BICUBIC', 'ANTIALIAS'] - - _properties = Image._properties[:2] + ( - {'id' : 'height', 'type' : 'int', 'mode' : 'w'}, - {'id' : 'width', 'type' : 'int', 'mode' : 'w'}, - {'id' : 'auto_update_thumb', 'type' : 'boolean', 'mode' : 'w'}, - {'id' : 'tiles_available', 'type' : 'int', 'mode' : 'r'}, - {'id' : 'thumb_height', 'type' : 'int', 'mode' : 'w'}, - {'id' : 'thumb_width', 'type' : 'int', 'mode' : 'w'}, - {'id' : 'prop_filter', - 'label' : 'Filter', - 'type' : 'selection', - 'select_variable' : 'filters', - 'mode' : 'w'}, - ) - - - security.declareProtected(manage_properties, 'manage_editProperties') - def manage_editProperties(self, REQUEST=None, no_refresh = 0, **kw): - "Save Changes and update the thumbnail" - Image.manage_changeProperties(self, REQUEST, **kw) - - if hasattr(self, 'thumbnail') and self.auto_update_thumb == 1 and no_refresh == 0 : - self.makeThumbnail() - - if REQUEST: - message="Saved changes." - return self.manage_propertiesForm(self,REQUEST, - manage_tabs_message=message) - - - def __init__(self, id, title, file, content_type='', precondition='', **kw) : - # 0 means: tiles are not generated - # 1 means: tiles are all generated - # 2 means: tiling is not available is this photo (deliberated choice of the owner) - # -1 means: no data tiles cannot be generated - self.tiles_available = 0 - self.auto_update_thumb = kw.get('auto_update_thumb', 1) - self.thumb_height = kw.get('thumb_height', 180) - self.thumb_width = kw.get('thumb_width', 180) - self.prop_filter = kw.get('prop_filter', 'ANTIALIAS') - super(Photo, self).__init__(id, title, file, content_type='', precondition='') - - defaultBlankThumbnail = kw.get('defaultBlankThumbnail', None) - if defaultBlankThumbnail : - blankThumbnail = Image('thumbnail', '', - getattr(defaultBlankThumbnail, '_data', getattr(defaultBlankThumbnail, 'data', None))) - self.thumbnail = blankThumbnail - - self._methodResultsCache = OOBTree() - TileSupport.__init__(self) - - def update_data(self, file, content_type=None) : - super(Photo, self).update_data(file, content_type) - - if self.content_type != 'image/jpeg' and self.size : - raw = self.open('r') - im = PIL.Image.open(raw) - self.content_type = 'image/%s' % im.format.lower() - self.width, self.height = im.size - - if im.mode not in ('L', 'RGB'): - im = im.convert('RGB') - - jpeg_image = Image('jpeg_image', '', '', content_type='image/jpeg') - out = jpeg_image.open('w') - im.save(out, 'JPEG', quality=90) - jpeg_image.updateFormat(out.tell(), im.size, 'image/jpeg') - out.close() - self.jpeg_image = jpeg_image - - self._methodResultsCache = OOBTree() - self._v__methodResultsCache = OOBTree() - - self._tiles = OOBTree() - if self.tiles_available in [1, -1]: - self.tiles_available = 0 - - if hasattr(self, 'thumbnail') and self.auto_update_thumb == 1 : - self.makeThumbnail() - - - - def _getJpegBlob(self) : - if self.size : - if self.content_type == 'image/jpeg' : - return self.bdata - else : - return self.jpeg_image.bdata - else : - return None - - security.declareProtected(view, 'getJpegImage') - def getJpegImage(self, REQUEST, RESPONSE) : - """ return JPEG formated image """ - if self.content_type == 'image/jpeg' : - return self.index_html(REQUEST, RESPONSE) - elif self.jpeg_image : - return self.jpeg_image.index_html(REQUEST, RESPONSE) - - security.declareProtected(view, 'tiffOrientation') - @memoizedmethod() - def tiffOrientation(self) : - tiffOrientation = self.getXmpValue('tiff:Orientation') - if tiffOrientation : - return int(tiffOrientation) - else : - # TODO : falling back to legacy Exif metadata - return 1 - - def _rotateOrFlip(self, im) : - orientation = self.tiffOrientation() - rotation, flip = TIFF_ORIENTATIONS.get(orientation, (0, False)) - if rotation : - im = im.rotate(-rotation) - if flip : - im = im.transpose(PIL.Image.FLIP_LEFT_RIGHT) - return im - - @memoizedmethod('size', 'keepAspectRatio') - def _getResizedImage(self, size, keepAspectRatio) : - """ returns a resized version of the raw image. - """ - - fullSizeFile = self._getJpegBlob().open('r') - fullSizeImage = PIL.Image.open(fullSizeFile) - if fullSizeImage.mode not in ('L', 'RGB'): - fullSizeImage.convert('RGB') - fullSize = fullSizeImage.size - - if (keepAspectRatio) : - newSize = getNewSize(fullSize, size) - else : - newSize = size - - fullSizeImage.thumbnail(newSize, PIL.Image.ANTIALIAS) - fullSizeImage = self._rotateOrFlip(fullSizeImage) - - for hook in self._getAfterResizingHooks() : - hook(self, fullSizeImage) - - - resizedImage = Image(self.getId() + _strSize(size), 'resized copy of %s' % self.getId(), '') - out = resizedImage.open('w') - fullSizeImage.save(out, "JPEG", quality=90) - resizedImage.updateFormat(out.tell(), fullSizeImage.size, 'image/jpeg') - out.close() - return resizedImage - - def _getAfterResizingHooks(self) : - """ returns a list of hook scripts that are executed - after the image is resized. - """ - return [] - - - security.declarePrivate('makeThumbnail') - def makeThumbnail(self) : - "make a thumbnail from jpeg data" - b = self._getJpegBlob() - if b is not None : - # récupération des propriétés de redimentionnement - thumb_size = [] - if int(self.width) >= int(self.height) : - thumb_size.append(self.thumb_height) - thumb_size.append(self.thumb_width) - else : - thumb_size.append(self.thumb_width) - thumb_size.append(self.thumb_height) - thumb_size = tuple(thumb_size) - - if thumb_size[0] <= 1 or thumb_size[1] <= 1 : - thumb_size = (180, 180) - thumb_filter = getattr(PIL.Image, self.prop_filter, PIL.Image.ANTIALIAS) - - # create a thumbnail image file - original_file = b.open('r') - image = PIL.Image.open(original_file) - if image.mode not in ('L', 'RGB'): - image = image.convert('RGB') - - image.thumbnail(thumb_size, thumb_filter) - image = self._rotateOrFlip(image) - - thumbnail = Image('thumbnail', 'Thumbail', '', 'image/jpeg') - out = thumbnail.open('w') - image.save(out, "JPEG", quality=90) - thumbnail.updateFormat(out.tell(), image.size, 'image/jpeg') - out.close() - original_file.close() - self.thumbnail = thumbnail - return True - else : - return False - - security.declareProtected(view, 'getThumbnail') - def getThumbnail(self, REQUEST, RESPONSE) : - "Return the thumbnail image and create it before if it does not exist yet." - if not hasattr(self, 'thumbnail') : - self.makeThumbnail() - return self.thumbnail.index_html(REQUEST=REQUEST, RESPONSE=RESPONSE) - - security.declareProtected(view, 'getThumbnailSize') - def getThumbnailSize(self) : - """ return thumbnail size dict - """ - if not hasattr(self, 'thumbnail') : - if not self.width : - return {'height' : 0, 'width' : 0} - else : - thumbMaxFrame = [] - if int(self.width) >= int(self.height) : - thumbMaxFrame.append(self.thumb_height) - thumbMaxFrame.append(self.thumb_width) - else : - thumbMaxFrame.append(self.thumb_width) - thumbMaxFrame.append(self.thumb_height) - thumbMaxFrame = tuple(thumbMaxFrame) - - if thumbMaxFrame[0] <= 1 or thumbMaxFrame[1] <= 1 : - thumbMaxFrame = (180, 180) - - th = self.height * thumbMaxFrame[0] / float(self.width) - # resizing round limit is not 0.5 but seems to be strictly up to 0.75 - # TODO check algorithms - if th > floor(th) + 0.75 : - th = int(floor(th)) + 1 - else : - th = int(floor(th)) - - if th <= thumbMaxFrame[1] : - thumbSize = (thumbMaxFrame[0], th) - else : - tw = self.width * thumbMaxFrame[1] / float(self.height) - if tw > floor(tw) + 0.75 : - tw = int(floor(tw)) + 1 - else : - tw = int(floor(tw)) - thumbSize = (tw, thumbMaxFrame[1]) - - if self.tiffOrientation() <= 4 : - return {'width':thumbSize[0], 'height' : thumbSize[1]} - else : - return {'width':thumbSize[1], 'height' : thumbSize[0]} - - else : - return {'height' : self.thumbnail.height, 'width' :self.thumbnail.width} - - - security.declareProtected(view, 'getResizedImageSize') - def getResizedImageSize(self, REQUEST=None, size=(), keepAspectRatio=True, asXml=False) : - """ return the reel image size the after resizing """ - if not size : - size = REQUEST.SESSION.get('preferedImageSize', (600, 600)) - elif type(size) == StringType : - size = tuple([int(n) for n in size.split('_')]) - - resizedImage = self._getResizedImage(size, keepAspectRatio) - size = (resizedImage.width, resizedImage.height) - - if asXml : - REQUEST.RESPONSE.setHeader('content-type', 'text/xml; charset=utf-8') - return '%d%d' % size - else : - return size - - - security.declareProtected(view, 'getResizedImage') - def getResizedImage(self, REQUEST, RESPONSE, size=(), keepAspectRatio=True) : - """ - Return a volatile resized image. - The 'preferedImageSize' tuple (width, height) is looked up into SESSION data. - Default size is 600 x 600 px - """ - if not size : - size = REQUEST.SESSION.get('preferedImageSize', (600, 600)) - elif type(size) == StringType : - size = size.split('_') - if len(size) == 1 : - i = int(size[0]) - size = (i, i) - keepAspectRatio = True - else : - size = tuple([int(n) for n in size]) - - return self._getResizedImage(size, keepAspectRatio).index_html(REQUEST=REQUEST, RESPONSE=RESPONSE) - - -InitializeClass(Photo) - - -# Factories -def addPhoto(dispatcher, id, file='', title='', - precondition='', content_type='', REQUEST=None, **kw) : - """ - Add a new Photo object. - Creates a new Photo object 'id' with the contents of 'file'. - """ - id=str(id) - title=str(title) - content_type=str(content_type) - precondition=str(precondition) - - id, title = cookId(id, title, file) - parentContainer = dispatcher.Destination() - - parentContainer._setObject(id, Photo(id,title,file,content_type, precondition, **kw)) - - if REQUEST is not None: - try: url=dispatcher.DestinationURL() - except: url=REQUEST['URL1'] - REQUEST.RESPONSE.redirect('%s/manage_main' % url) - return id - -# creation form -addPhotoForm = DTMLFile('dtml/addPhotoForm', globals())