eggification
[Photo.git] / Products / Photo / ppm.py
diff --git a/Products/Photo/ppm.py b/Products/Photo/ppm.py
new file mode 100755 (executable)
index 0000000..c1a31df
--- /dev/null
@@ -0,0 +1,198 @@
+# -*- coding: utf-8 -*-
+#######################################################################################
+#   Plinn - http://plinn.org                                                          #
+#   Copyright © 2009  Benoît Pin <pin@cri.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.   #
+#######################################################################################
+""" PPM File support module
+
+
+
+"""
+
+from subprocess import Popen, PIPE
+from tempfile import TemporaryFile
+import os
+from math import ceil
+from PIL.Image import open as imgopen
+from PIL.Image import fromstring
+from PIL.Image import ANTIALIAS
+from cStringIO import StringIO
+
+DGJPEG = 'djpeg'
+RESIZING_TILE_SIZE = 1024
+
+class PPMFile(object) :
+       
+       def __init__(self, f, tileSize=256, isRaw=False) :
+               # convert jpeg -> ppm with djpeg
+               if not isRaw :
+                       # print 'djpeg'
+                       self.fp = TemporaryFile(mode='w+')
+                       p = Popen(DGJPEG, stdin=f, stdout=self.fp, stderr=PIPE, shell=True)
+                       p.wait()
+                       err = p.stderr.read()
+                       if err :
+                               raise SystemError, err
+               else :
+                       self.fp = f
+               
+               # get image specs with PIL
+               self.fp.seek(0)
+               im = imgopen(self.fp)
+               decoder, region, offset, parameters = im.tile[0]
+               x, y, width, height = region
+               del im
+               assert decoder == 'raw'
+               mode = parameters[0]
+               assert mode in ('RGB', 'L'), "Unsupported mode %s" % mode
+
+               if mode == 'RGB' :
+                       sampleSize = 3
+               elif mode == 'L' :
+                       sampleSize = 1
+               
+               self.width = width
+               self.height = height
+               self.offset = offset
+               self.mode = parameters[0]
+               self.sampleSize = sampleSize
+               self._setTileSize(tileSize)
+       
+       def _setTileSize(self, tileSize) :
+               self.tileSize = tileSize
+               self.tilesX = int(ceil(float(self.width) / self.tileSize))
+               self.tilesY = int(ceil(float(self.height) / self.tileSize))
+       
+       def getTile(self, xt, yt) :
+               f = self.fp
+               ss = self.sampleSize
+               x = xt * self.tileSize
+               y = yt * self.tileSize
+               start = (self.width * y + x) * ss + self.offset
+               
+               tw = th = self.tileSize
+
+               bw = self.width - x
+               if bw < self.tileSize  :
+                       tw = bw
+               bh = self.height - y
+               if bh < self.tileSize :
+                       th = bh
+               
+               assert tw > 0 and th > 0, "Tile requested out of image."
+
+               size = (tw, th)
+               tll = tw * ss
+               jump = (self.width - tw) * ss
+               
+               f.seek(start)
+               data = StringIO()
+               
+               for line in xrange(size[1]) :
+                       data.write(f.read(tll))
+                       f.seek(jump, 1)
+               
+               data.seek(0)
+               im = fromstring(self.mode, size, data.read())
+               return im
+       
+       def getTileSequence(self):
+               seq = []
+               for y in xrange(self.tilesY) :
+                       for x in xrange(self.tilesX) :
+                               seq.append((x, y))
+               return seq
+       
+       def resize(self, ratio=None, maxLength=None) :
+               if ratio and maxLength :
+                       raise AttributeError("'ratio' and 'size' are mutually exclusive.")
+               if maxLength :
+                       maxFullLength = max(self.width, self.height)
+                       ratio = float(maxLength) / maxFullLength
+
+               tileSizeBak = self.tileSize
+               
+               self._setTileSize(RESIZING_TILE_SIZE)
+               
+               width = height = 0
+               # cumul des arrondis
+               width = int(round(self.tileSize * ratio)) * (self.tilesX -1)
+               width += int(round((self.width - self.tileSize * (self.tilesX -1)) * ratio))
+               
+               height = int(round(self.tileSize * ratio)) * (self.tilesY -1)
+               height += int(round((self.height - self.tileSize * (self.tilesY -1)) * ratio))
+               
+               magic = self.mode == 'RGB' and 6 or 5
+               head = 'P%d %d %d 255\n' % (magic, width, height)
+               offset = len(head)
+
+               out = TemporaryFile(mode='w+')
+               out.write(head)
+
+               ss = self.sampleSize
+               rTll = int(round(self.tileSize * ratio))
+               
+               for x, y in self.getTileSequence() :
+                       # print 'resize', (x,y)
+                       tile = self.getTile(x,y)
+                       tileSize = tile.size
+                       size = map(lambda l : int(round(l * ratio)), tileSize)
+                       
+                       if size[0] and size[1] :
+                               resized = tile.resize(size, ANTIALIAS)
+                               data = resized.tostring()
+                               
+                               start = (y * width + x) * ss * rTll + offset
+                               jump = (width - size[0]) * ss
+                               
+                               out.seek(start)
+                               tll = size[0] * ss
+                               
+                               # écriture dans le bon ordre (c'est quand même plus agréable à l'œil)
+                               for l in xrange(size[1]) :
+                                       lineData = data[l*tll:(l+1)*tll]
+                                       out.write(lineData)
+                                       out.seek(jump, 1)
+               
+               out.seek(0,2)
+               length = out.tell()
+               assert length - len(head) == width * height * ss, (length - len(head), width * height * ss)
+               out.seek(0)
+
+               self._setTileSize(tileSizeBak)
+               return PPMFile(out, tileSize=tileSizeBak, isRaw=True)
+       
+       def getImage(self) :
+               self.fp.seek(0)
+               return imgopen(self.fp)
+       
+       def __del__(self) :
+               self.fp.close()
+
+
+if __name__ == '__main__' :
+       f = open('/Users/pinbe/Desktop/Chauve_souris.jpg')
+       try :
+               ppm = PPMFile(f, tileSize=256)
+               rppm = ppm.resize(maxLength=800)
+               im = rppm.getImage()
+               im.show()
+               for x, y in ppm.getTileSequence() :
+                       im = ppm.getTile(x, y)
+                       im.save('testoutput/%d_%d.jpg' % (x, y), 'JPEG', quality=90)
+       finally :
+               f.close()