X-Git-Url: https://scm.cri.ensmp.fr/git/Photo.git/blobdiff_plain/b0a7e10b4f32cf74864bb53268ca4d3080f23bc0..6c41809185e322ce2d30e98234f71144f78f06c0:/Products/Photo/ppm.py diff --git a/Products/Photo/ppm.py b/Products/Photo/ppm.py new file mode 100755 index 0000000..c1a31df --- /dev/null +++ b/Products/Photo/ppm.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +####################################################################################### +# Plinn - http://plinn.org # +# Copyright © 2009 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. # +####################################################################################### +""" 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()