--- /dev/null
+# -*- 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()