eggification
[Photo.git] / Products / Photo / TileSupport.py
diff --git a/Products/Photo/TileSupport.py b/Products/Photo/TileSupport.py
new file mode 100644 (file)
index 0000000..5b777cc
--- /dev/null
@@ -0,0 +1,190 @@
+# -*- 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)
+