+++ /dev/null
-# -*- coding: utf-8 -*-
-#######################################################################################
-# Photo is a part of Plinn - http://plinn.org #
-# Copyright (C) 2004-2007 Benoît PIN <benoit.pin@ensmp.fr> #
-# #
-# 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 '<size><width>%d</width><height>%d</height></size>' % 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())