[Zope3-checkins] CVS: zopeproducts/photo - LICENSE:1.1 README:1.1
TODO:1.1 __init__.py:1.1 configure.zcml:1.1 interfaces.py:1.1
utilities.py:1.1
Bjorn Tillenius
bjorn at codeworks.lt
Fri Aug 15 09:10:52 EDT 2003
Update of /cvs-repository/zopeproducts/photo
In directory cvs.zope.org:/tmp/cvs-serv25590
Added Files:
LICENSE README TODO __init__.py configure.zcml interfaces.py
utilities.py
Log Message:
First checkin of the photo product.
This is intended to be similar to the photo product in Zope 2. It's not
quite finished yet, though it's already usable. Read the README for more
information.
=== Added File zopeproducts/photo/LICENSE ===
Zope Public License (ZPL) Version 2.0
-----------------------------------------------
This software is Copyright (c) Zope Corporation (tm) and
Contributors. All rights reserved.
This license has been certified as open source. It has also
been designated as GPL compatible by the Free Software
Foundation (FSF).
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:
1. Redistributions in source code must retain the above
copyright notice, this list of conditions, and the following
disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions, and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
3. The name Zope Corporation (tm) must not be used to
endorse or promote products derived from this software
without prior written permission from Zope Corporation.
4. The right to distribute this software or to use it for
any purpose does not give you the right to use Servicemarks
(sm) or Trademarks (tm) of Zope Corporation. Use of them is
covered in a separate agreement (see
http://www.zope.com/Marks).
5. If any files are modified, you must cause the modified
files to carry prominent notices stating that you changed
the files and the date of any change.
Disclaimer
THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS''
AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL ZOPE CORPORATION OR ITS CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
This software consists of contributions made by Zope
Corporation and many individuals on behalf of Zope
Corporation. Specific attributions are listed in the
accompanying credits file.
=== Added File zopeproducts/photo/README ===
Photo Product
Overview
This product enables you to have multiple sizes of an image. You
only have to supply it with an image and it will automatically
generate the size you ask it for. It also can store some meta
data about the image.
It is much influenced by the Photo product in Zope 2, but it
is not a direct port of it.
Display Sizes
Right now it contains a static set of different sizes of the image.
This will change though, so that you can specify the sizes you
want.
Image Utilities
In order to resize the image it uses an image utility. It will give
you a list of available image utilities. You can choose any utility
which implements IImageResizeUtility. Two utilites are provided in
this package. One which uses ImageMagick and another which uses PIL.
Note that in order to use the ImageMagick utility the 'convert'
program has to be in your PATH. To use the PIL utility the PIL
module has to be installed.
Uploading Photos
There are two ways of uploading photos. One is to do it through
the browser by adding a photo in the ZMI. Though if you want to
upload many photos you're better off creating a photo aware folder,
like the PhotoSlide product. You can also create a normal folder
and mark it with the IPhotoFolder interface. Then every image
you upload to that folder will becom a photo.
Current Status
At the moment this product is quite usable, though there's still
a lot to be done. See the TODO for more information
Other Notes
This product was created by Bjorn Tillenius, if you have any
questions you can reach me at <bjorn(at)codeworks.lt>.
=== Added File zopeproducts/photo/TODO ===
* Provide a nice icon
Currently there's no icon associated with this product.
* Support for external storage
Storing a lot of images inside ZODB makes it unnecessary big and
slow. It would be better to only store meta data there and store
the actual images somewhere else, like the file system or an
external database.
* Fix generation of new image sizes
At the moment new image sizes only get created when someone requests
it and the new image never gets deleted. There should be an option
to pre-generate certain sizes. There should also be some kind of
cache timeout, so that the images get deleted after a while.
* Customization of display sizes
Now there are hard coded list of display sizes. It should be possible
to edit this list.
* EXIF support
Extract EXIF data from the image.
* Run images through filters while resizing
It would be nice to say, for example, that the thumbnails should
have rounded corners. My main idea here is that for each display
size you can specify a number of filter through which the resized
image will be processed.
* Put PILImageUtility and ImageMagickUtility into seperate packages
Those are not specific to this product and could be useful for others
to use. Of course they would need some more functionality as well.
* More functional tests
I noticed that quite often when I developed this product, all the unit
tests passed, but in reality the product was broken. Would be nice to
have some tests which can tell that it is broken.
=== Added File zopeproducts/photo/__init__.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""The Photo package
$Id: __init__.py,v 1.1 2003/08/15 12:10:43 BjornT Exp $
"""
from zope.interface import implements
from zope.app.container.btree import BTreeContainer
from zope.app.content.image import Image
from zope.app.interfaces.content.file import IFileContent
from zope.app.interfaces.file import IDirectoryFactory, IFileFactory
from zope.app.interfaces.size import ISized
from zope.app.interfaces.container import IContainer
from zope.app.interfaces.dublincore import ICMFDublinCore
from zope.app import zapi
from zope.i18n import MessageIDFactory
from zopeproducts.photo.interfaces import IPhoto, IImageResizeUtility
from zopeproducts.photo.interfaces import IPhotoContainer
_ = MessageIDFactory("photo")
defaultDisplayIds = {u'thumbnail': (128,128),
u'xsmall': (200,200),
u'small': (320,320),
u'medium': (480,480),
u'large': (768,768),
u'xlarge': (1024,1024),
u'original': (0,0)
}
defaultImageResizer = 'ImageMagick Image Utility'
class Photo(BTreeContainer):
implements(IPhoto, IContainer, IFileContent)
# ATTRIBUTES
useParentOptions = True
# CONSTRUCTOR
def __init__(self):
super(Photo, self).__init__()
self._displayIds = defaultDisplayIds
self.resizeUtility = defaultImageResizer
# PROPERTIES
def getTitle(self):
"""Gets the title of the photo.
Since we are annotatable we use the title in the dublin core"""
return zapi.getAdapter(self, ICMFDublinCore).title
def setTitle(self, title):
"""Sets the title of the photo
Since we are annotatable we use the title in the dublin core"""
zapi.getAdapter(self, ICMFDublinCore).title = title
title = property(getTitle, setTitle)
def getDescription(self):
"""Gets the description of the photo
Since we are annotatable we use the description in the dublin core"""
return zapi.getAdapter(self, ICMFDublinCore).description
def setDescription(self, description):
"""Sets the description of the photo
Since we are annotatable we use the description in the dublin core"""
zapi.getAdapter(self, ICMFDublinCore).description = description
description = property(getDescription, setDescription)
_data = ''
def getData(self):
"""Gets the data of the original photo"""
return self._data
def setData(self, data):
"""Sets the data of the original photo"""
self._data = data
self._deleteGeneratedDisplays()
im = Image(data)
self.setObject(u'original', im)
self._displayIds['original'] = im.getImageSize()
data = property(getData, setData)
_currentDisplayId = 'medium'
def setCurrentDisplayId(self, displayId):
"""Set the current display id, so it's not necessary to specify
it to getImage() every time.
"""
if displayId in self.getDisplayIds():
self._currentDisplayId = displayId
def getCurrentDisplayId(self):
"""Gets the current display id"""
return self._getPhotoOption('currentDisplayId')
currentDisplayId = zapi.ContextProperty(getCurrentDisplayId,
setCurrentDisplayId)
_resizeUtility = defaultImageResizer
def setResizeUtility(self, val):
"""Sets the resize utility."""
self._resizeUtility = val
def getResizeUtility(self):
"""Gets the resize utility."""
return self._getPhotoOption('resizeUtility')
resizeUtility = zapi.ContextProperty(getResizeUtility,
setResizeUtility)
# PUBLIC METHODS
def getDisplayIds(self):
"""See IPhoto"""
result = self._displayIds.keys()
result.reverse()
return result
def getDisplaySize(self, displayId):
"""See IPhoto"""
if not self._displayIds.has_key(displayId):
return None
else:
return self._displayIds[displayId]
def getImage(self, displayId = None):
"""See IPhoto"""
if not self._data:
return
if displayId == None:
displayId = self.currentDisplayId
if displayId not in self.getDisplayIds():
return None
if not self.__contains__(displayId):
self._generateDisplay(displayId)
return self.get(displayId)
getImage = zapi.ContextMethod(getImage)
# PRIVATE METHODS
def _getPhotoOption(self, option):
"""Returns the photo option, considering useParentOptions."""
if self.useParentOptions:
pc = zapi.getParent(self)
if IPhotoContainer.isImplementedBy(pc):
return getattr(pc, option)
else:
return getattr(self, '_' + option)
else:
return getattr(self, '_' + option)
_getPhotoOption = zapi.ContextMethod(_getPhotoOption)
def _deleteGeneratedDisplays(self):
for dispId in self.getDisplayIds():
if self.__contains__(dispId):
self.__delitem__(dispId)
def _generateDisplay(self, displayId):
"""Generate a new size of the image"""
im_size = self._displayIds[displayId]
resizer = zapi.getUtility(self, IImageResizeUtility,
self.resizeUtility)
image = resizer.resize(Image(self.data), im_size, keep_aspect=True)
self.setObject(displayId, image)
_generateDisplay = zapi.ContextMethod(_generateDisplay)
class PhotoSized:
implements(ISized)
def __init__(self, photo):
self._photo = photo
def sizeForSorting(self):
"""See ISized"""
image = Image(self._photo.data)
return ('byte', image.getSize())
def sizeForDisplay(self):
"""See ISized"""
image = Image(self._photo.data)
length = len(self._photo.data)
size = image.getSize()
if size <= 0:
return u''
else:
# XXX size should be localized (the formatting of the number)
if size > 1024*1024:
size = "%0.02f" % (size / (1024*1024.0))
unit = _('MB')
elif size > 1024:
size = "%0.02f" % (size / 1024.0)
unit = _('kB')
else:
unit = _('bytes')
x, y = image.getImageSize()
result = '${size} ${unit} ${x}x${y}'
result = _(result)
result.mapping = {'size': size, 'unit': unit, 'x': x, 'y': y}
return result
class PhotoFactory(object):
"""Creates photos in the file system representaion.
This class can create photos instead of both directories and
files in the file system representation.
"""
implements(IDirectoryFactory, IFileFactory)
def __call__(self, name, content_type='', data=None):
photo = Photo()
if data:
photo.data = data
return photo
=== Added File zopeproducts/photo/configure.zcml ===
<configure
xmlns="http://namespaces.zope.org/zope"
i18n_domain="photo"
>
<content class=".Photo">
<implements
interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
/>
<factory
id="Photo"
permission="zope.ManageContent"
description="Photo"
/>
<require
permission="zope.View"
interface=".interfaces.IPhoto"
/>
<require
permission="zope.ManageContent"
set_schema=".interfaces.IPhoto"
/>
</content>
<!-- ADAPTERS -->
<adapter
factory=".PhotoSized"
provides="zope.app.interfaces.size.ISized"
for=".interfaces.IPhoto"
/>
<adapter
factory=".PhotoFactory"
provides="zope.app.interfaces.file.IDirectoryFactory"
for=".interfaces.IPhotoFolder"
permission="zope.ManageContent"
/>
<adapter
factory=".PhotoFactory"
provides="zope.app.interfaces.file.IFileFactory"
for=".interfaces.IPhotoFolder"
permission="zope.ManageContent"
/>
<adapter
for=".interfaces.IPhoto"
provides="zope.app.interfaces.file.IReadDirectory"
factory="zope.app.container.directory.noop"
permission="zope.ManageContent"
/>
<!-- UTILITIES -->
<utility
factory=".utilities.PILImageUtility"
provides=".interfaces.IImageResizeUtility"
name="PIL Image Utility"
/>
<utility
factory=".utilities.ImageMagickUtility"
provides=".interfaces.IImageResizeUtility"
name="ImageMagick Image Utility"
/>
<include package=".browser" />
</configure>
=== Added File zopeproducts/photo/interfaces.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Interface descriptions for the Photo package
$Id: interfaces.py,v 1.1 2003/08/15 12:10:43 BjornT Exp $
"""
from zope.app.interfaces.container import IContainer
from zope.app.services.servicenames import Utilities
from zope.context import ContextProperty
from zope.component import getService, ComponentLookupError
from zope.interface import Interface, Attribute
from zope.schema import Text, TextLine, Bytes, EnumeratedTextLine, Bool
from zope.i18n import MessageIDFactory
_ = MessageIDFactory("photo")
dispIds = [u'thumbnail',
u'xsmall',
u'small',
u'medium',
u'large',
u'xlarge',
u'original'
]
class ResizeUtilityName(TextLine):
"""Field which lists all available resize utilities."""
def __allowed(self):
"""Finds all resize utility names and returns them.
Note that this method works only if the Field is context wrapped.
"""
resizeUtilities = []
try:
us = getService(self, Utilities)
for resize_util_reg in us.getUtilitiesFor(IImageResizeUtility):
resizeUtilities.append(resize_util_reg[0])
except ComponentLookupError:
resizeUtilities = []
return resizeUtilities
allowed_values = ContextProperty(__allowed)
class IPhotoFolder(IContainer):
"""Marker interface in order to make it easier to upload photos.
If a container is marked with this interface it will create
photos when an images are uploaded in it.
"""
class IPhotoContainer(Interface):
"""Schema for certain photo options.
Objects that will contain photos should implement this schema.
"""
useParentOptions = Bool(
title=_('Use Parent Options'),
description=
_("If inside a photo container, ignore the following options."),
default=True,
required=True
)
currentDisplayId = EnumeratedTextLine(
title=_('Default Size'),
description=_('The defualt size of the photo(s).'),
allowed_values=dispIds,
default=_('medium'),
required=True
)
resizeUtility = ResizeUtilityName(
title=_('Resize Utility'),
description=_('The utility used for resizing the images'),
required=True
)
class IPhoto(IContainer, IPhotoContainer):
"""Provides several sizes of an Image.
"""
title = TextLine(title=_('Title'),
description=_('The title of the photo'),
default=u'',
required=False)
description = Text(title=_('Description'),
description=_('The description of the photo'),
default=u'',
required=False)
data = Bytes(title=_('Image File'),
description=_('The original image'),
required=True)
def getDisplayIds():
"""Gets a list of available display ids"""
def getDisplaySize(dispId):
"""Gets the geometry of the specified display id"""
def getImage(dispId):
"""Returns the image of the specified display id"""
class IImageResizeUtility(Interface):
"""Resizes an given image to a give size"""
def resize(image, size, keep_aspect):
"""Returns a new image resized to the given size
If keep_aspect is true the image should be resized as close
as possible to size while retaining its aspect ratio.
"""
class IPILImageUtility(IImageResizeUtility):
"""An image utility using PIL.
For now it only resizes images.
"""
class IImageMagickUtility(IImageResizeUtility):
"""An image utility using ImageMagick.
For now it only resizes images.
"""
=== Added File zopeproducts/photo/utilities.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Utilities for resizing images.
$Id: utilities.py,v 1.1 2003/08/15 12:10:43 BjornT Exp $
"""
import popen2
from zope.interface import implements
from zope.app.content.image import Image
from zopeproducts.photo.interfaces import IPILImageUtility, IImageMagickUtility
from PIL import Image as PILImage
from cStringIO import StringIO
class PILImageUtility:
"""An image utility which uses PIL.
"""
implements(IPILImageUtility)
def resize(self, image, size, keep_aspect=False):
"""See IPILImageResizeUtility"""
im = PILImage.open(StringIO(image.getData()))
fmt = im.format
new_size = self._getNewSize(image.getImageSize(), size, keep_aspect)
new_data = StringIO()
(im.resize(new_size)).save(new_data, fmt)
return Image(new_data.getvalue())
def _getNewSize(self, image_size, desired_size, keep_aspect):
"""Resizes image_size to desired_size, optionally keeping the
aspect ratio.
"""
if keep_aspect:
x_ratio = float(desired_size[0])/image_size[0]
y_ratio = float(1.0*desired_size[1])/image_size[1]
if x_ratio < y_ratio:
new_size = (round(x_ratio*image_size[0]),
round(x_ratio*image_size[1]))
else:
new_size = (round(y_ratio*image_size[0]),
round(y_ratio*image_size[1]))
return new_size
else:
return desired_size
class ImageMagickUtility:
"""An image utility which uses ImageMagick.
It needs the convert (or convert.exe) to be in the path.
"""
implements(IImageMagickUtility)
def resize(self, image, size, keep_aspect=False):
"""See IImageMacickResizeUtility"""
instream, outstream = self._getConvertResizePipe(size, keep_aspect)
outstream.write(image.getData())
outstream.close()
new_data = StringIO()
new_data.write(instream.read())
return Image(new_data.getvalue())
def _getConvertResizePipe(self, size, keep_aspect):
"""Returns a pipe communicating with the convert program."""
# XXX this code isn't tested much, may not work on every system
if keep_aspect:
convert_call = 'convert -resize %sx%s - -' % size
else:
convert_call = 'convert -resize %sx%s! - -' % size
convert = popen2.Popen3(convert_call,
True)
if convert.poll() == -1:
return popen2.popen2(convert_call, mode='b')
else:
# XXX here we should raise some error since we couldn't find the
# convert executable.
pass
More information about the Zope3-Checkins
mailing list