X-Git-Url: https://scm.cri.minesparis.psl.eu/git/Photo.git/blobdiff_plain/b0a7e10b4f32cf74864bb53268ca4d3080f23bc0..6c41809185e322ce2d30e98234f71144f78f06c0:/Products/Photo/TileSupport.py diff --git a/Products/Photo/TileSupport.py b/Products/Photo/TileSupport.py new file mode 100644 index 0000000..5b777cc --- /dev/null +++ b/Products/Photo/TileSupport.py @@ -0,0 +1,190 @@ +# -*- 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. # +####################################################################################### +""" 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) +