--- /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. #
+#######################################################################################
+""" Tile support module
+
+
+
+"""
+
+from AccessControl import ClassSecurityInfo
+from AccessControl import Unauthorized
+from AccessControl import getSecurityManager
+from AccessControl.Permissions import view, change_images_and_files
+from PIL import Image as PILImage
+from math import ceil
+from blobbases import Image
+from xmputils import TIFF_ORIENTATIONS
+from cache import memoizedmethod
+from BTrees.OOBTree import OOBTree
+from BTrees.IOBTree import IOBTree
+from ppm import PPMFile
+from threading import Lock
+from subprocess import Popen, PIPE
+from tempfile import TemporaryFile
+
+JPEG_ROTATE = 'jpegtran -rotate %d'
+JPEG_FLIP = 'jpegtran -flip horizontal'
+
+def runOnce(lock):
+ """ Decorator. exit if already running """
+
+ def wrapper(f):
+ def method(*args, **kw):
+ if not lock.locked() :
+ lock.acquire()
+ try:
+ return f(*args, **kw)
+ finally:
+ lock.release()
+ else :
+ return False
+ return method
+ return wrapper
+
+
+
+class TileSupport :
+ """ Mixin class to generate tiles from image """
+
+ security = ClassSecurityInfo()
+ tileSize = 256
+ tileGenerationLock = Lock()
+
+ def __init__(self) :
+ self._tiles = OOBTree()
+
+ security.declarePrivate('makeTilesAt')
+ @runOnce(tileGenerationLock)
+ def makeTilesAt(self, zoom):
+ """generates tiles at zoom level"""
+
+ if self._tiles.has_key(zoom) :
+ return True
+
+ assert zoom <= 1, "zoom arg must be <= 1 found: %s" % zoom
+
+ ppm = self._getPPM()
+ if zoom < 1 :
+ ppm = ppm.resize(ratio=zoom)
+
+ self._makeTilesAt(zoom, ppm)
+ return True
+
+ def _getPPM(self) :
+ bf = self._getJpegBlob()
+ f = bf.open('r')
+
+ orientation = self.tiffOrientation()
+ rotation, flip = TIFF_ORIENTATIONS.get(orientation, (0, False))
+
+ if rotation and flip :
+ tf = TemporaryFile(mode='w+')
+ pRot = Popen(JPEG_ROTATE % rotation
+ , stdin=f
+ , stdout=PIPE
+ , shell=True)
+ pFlip = Popen(JPEG_FLIP
+ , stdin=pRot.stdout
+ , stdout=tf
+ , shell=True)
+ pFlip.wait()
+ f.close()
+ tf.seek(0)
+ f = tf
+
+ elif rotation :
+ tf = TemporaryFile(mode='w+')
+ pRot = Popen(JPEG_ROTATE % rotation
+ , stdin=f
+ , stdout=tf
+ , shell=True)
+ pRot.wait()
+ f.close()
+ tf.seek(0)
+ f = tf
+
+ elif flip :
+ tf = TemporaryFile(mode='w+')
+ pFlip = Popen(JPEG_FLIP
+ , stdin=f
+ , stdout=tf
+ , shell=True)
+ pFlip.wait()
+ f.close()
+ tf.seek(0)
+ f = tf
+
+ ppm = PPMFile(f, tileSize=self.tileSize)
+ f.close()
+ return ppm
+
+ def _makeTilesAt(self, zoom, ppm):
+ hooks = self._getAfterTilingHooks()
+ self._tiles[zoom] = IOBTree()
+ bgColor = getattr(self, 'tiles_background_color', '#fff')
+
+ for x in xrange(ppm.tilesX) :
+ self._tiles[zoom][x] = IOBTree()
+ for y in xrange(ppm.tilesY) :
+ tile = ppm.getTile(x, y)
+ for hook in hooks :
+ hook(self, tile)
+
+ # fill with solid color
+ if min(tile.size) < self.tileSize :
+ blankTile = PILImage.new('RGB', (self.tileSize, self.tileSize), bgColor)
+ box = (0,0) + tile.size
+ blankTile.paste(tile, box)
+ tile = blankTile
+
+ zImg = Image('tile', 'tile', '', content_type='image/jpeg')
+ out = zImg.open('w')
+ tile.save(out, 'JPEG', quality=90)
+ zImg.updateFormat(out.tell(), tile.size, 'image/jpeg')
+ out.close()
+
+ self._tiles[zoom][x][y] = zImg
+
+ def _getAfterTilingHooks(self) :
+ return []
+
+
+ security.declareProtected(view, 'getAvailableZooms')
+ def getAvailableZooms(self):
+ zooms = list(self._tiles.keys())
+ zooms.sort()
+ return zooms
+
+ security.declareProtected(view, 'getTile')
+ def getTile(self, REQUEST, RESPONSE, zoom=1, x=0, y=0):
+ """ publishes tile
+ """
+ zoom, x, y = float(zoom), int(x), int(y)
+ if not self._tiles.has_key(zoom) :
+ sm = getSecurityManager()
+ if not sm.checkPermission(change_images_and_files, self) :
+ raise Unauthorized("Tiling arbitrary zoom unauthorized")
+ if self.makeTilesAt(zoom) :
+ tile = self._tiles[zoom][x][y]
+ return tile.index_html(REQUEST=REQUEST, RESPONSE=RESPONSE)
+ else :
+ tile = self._tiles[zoom][x][y]
+ return tile.index_html(REQUEST=REQUEST, RESPONSE=RESPONSE)
+