Commits

Christian Tismer committed 1445128

tiff multi-page read and write with PIL.
This was very hard, due to limitations and bugs in PIL.
Works, see demo file (5 times with reversed pages)
Finishing the form display tomorrow at home.

  • Participants
  • Parent commits fa666d9

Comments (0)

Files changed (1)

tool/tiff_reader/piltest.py

+import sys, os
+from ddc import rootpath
+
+"""
+Fake Imaging without PIL decoder
+--------------------------------
+
+based upon information from
+http://www.awaresystems.be/imaging/tiff/faq.html
+
+This little hack finally enables the usage of PIL for the DDC
+project without compiling CCITT G4 support at all.
+
+The idea is simply to avoid any recoding of the tiff file.
+Instead, the position of the first IFD is changed to another
+one (the second by default). No other changes are made to the tiff
+data. Fortunately Qt is reading the IFP data correctly.
+
+"""
+
+from ddc.tool.tiff_reader import Image
+
+def check_pil_fake():
+    imfile = os.path.join(rootpath, 'ddc/config/data/demo-g4-2pages.tiff')
+    im = Image.open(imfile)
+    
+    class TraceIo(file):
+        def write(self, *args):
+            tell = super(TraceIo, self).tell
+            seek = super(TraceIo, self).seek
+            write = super(TraceIo, self).write
+            pos = tell()
+            seek(0, 2)
+            size = tell()
+            seek(pos)
+            print 'before write, pos', pos, 'size', size
+            write(*args)
+            pos = tell()
+            seek(0, 2)
+            size = tell()
+            seek(pos)
+            print 'after  write, pos', pos, 'size', size 
+            if pos != size:
+                print '@@@ size has changed!'
+        def seek(self, *args):
+            print 'seek is called now', args
+            super(TraceIo, self).seek(*args)
+    
+    if 1:
+        #Image.DEBUG = 2
+        monkey_patch()
+        #im.seek(1)
+        #im.save('look.tiff')
+        #cls = TraceIo
+        cls = file
+        with cls('look_page2.tiff', 'wb') as imf:
+            for i in range(5):
+                im.seek(1)
+                im.save(imf, 'tiff')
+                im.seek(0)
+                im.save(imf, 'tiff')
+        return
+    pos = im._TiffImageFile__first
+    next = im._TiffImageFile__next
+    fp = im._TiffImageFile__fp
+    fp.seek(0)
+    data = fp.read()
+    ifd = next#-4
+    import struct
+    ifdbytes = struct.pack('I', ifd)
+    data = data[:4] + ifdbytes + data[8:]
+    file('look_page2.tiff', 'wb').write(data)
+    
+class FakeCore:
+    __module__ = 'Image.core'
+    
+    class FakeImage:
+        def pixel_access(self, readonly):
+            return 4711
+    
+    def new(self, mode, size):
+        im = self.FakeImage()
+        im.mode = mode
+        im.size = size
+        return im
+
+    class FakeDecoder:
+        def __init__(self, mode, *args):
+            self.mode = mode
+            self.args = args
+            
+        def setimage(self, im, *args):
+            self.im = im
+            self.args = args
+            
+        def decode(self, block):
+            self.im.saved_data = block
+            return -1, 0
+    
+    def group4_decoder(self, mode, *args):
+        return self.FakeDecoder(mode, *args)
+
+    class FakeEncoder:
+        def __init__(self, mode, *strips):
+            self.mode = mode
+            self.strips = strips
+            
+        def setimage(self, im, *args):
+            self.im = im
+            self.args = args
+
+        def encode_to_file(self, fh, bufsize):
+            buf = self.im.saved_data
+            for ofs, lng in self.strips:
+                # we ignore ofs for now (only one strip)
+                # improve later
+                assert len(buf) >= lng
+                os.write(fh, buf[:lng])
+                buf = buf[lng:]
+            return 0
+        
+    def blindcopy_encoder(self, mode, args):
+        """ this encoder blindly copies what it gets from the (equally blind)
+        decoder """
+        return self.FakeEncoder(mode, args)
+
+Image.core = FakeCore()
+
+def monkey_patch():
+    
+    from ddc.tool.tiff_reader import Image, TiffImagePlugin
+
+    def _save_as_is(im, fp, filename):
+        try:
+            rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
+        except KeyError:
+            raise IOError, "cannot write mode %s as TIFF" % im.mode
+    
+        ifd = ImageFileDirectory(prefix)
+        
+        # copy the original ifd.
+        # it cannot be used directly, bug in the _save handler!
+        # we also clean the strings from trailing nulls
+        stripoffsets, stripbytecounts = None, None
+        for tag, value in im.ifd.items():
+            if type(value[0]) == type(()) and len(value) == 1:
+                assert tag in (X_RESOLUTION, Y_RESOLUTION)
+                value = value[0]
+            elif tag == STRIPOFFSETS:
+                # don't carry over the old offsets
+                stripoffsets = value
+                value = (0,)
+            elif tag == STRIPBYTECOUNTS:
+                stripbytecounts = value
+            elif isinstance(value, str):
+                value = value.rstrip('\0')
+            ifd[tag] = value
+            
+        assert stripoffsets and stripbytecounts
+        assert len(stripoffsets) == len(stripbytecounts)
+        im.encoderconfig = tuple(zip(stripoffsets, stripbytecounts))
+    
+        # -- multi-page -- skip TIFF header on subsequent pages
+        is_multipage = fp.tell() != 0
+        if not is_multipage:
+            # tiff header (write via IFD to get everything right)
+            # PIL always starts the first IFD at offset 8
+            fp.write(ifd.prefix + ifd.o16(42) + ifd.o32(8))
+
+        savepos = fp.tell()
+        offset = ifd.save(fp)
+        print '### offset after ifd.save', offset, 'savepop', savepos
+    
+        ImageFile._save(im, fp, [
+            ("blindcopy", (0,0)+im.size, offset, ())
+            ])
+        
+        # A bigger problem with PIL was that it uses the file handle internally,
+        # and therefore the fp is not updated. We do that now:
+        fp.seek(0, 2)
+    
+        # -- helper for multi-page save --
+        if is_multipage:
+            holdpos = fp.tell()
+            print 'is multi, last', im.last_linkoffset, 'save', savepos, 'ofs', offset, 'hold', holdpos
+            fp.seek(im.last_linkoffset)
+            fp.write(ifd.o32(savepos))
+            fp.seek(holdpos)
+
+        # -- hack to find ifd's link position --
+        from StringIO import StringIO
+        class FakeWriter(StringIO):
+            def write(self, data):
+                if data == '\0\0\0\0':
+                    raise EOFError
+                StringIO.write(self, data)
+        buf = FakeWriter()
+        try:
+            ifd.save(buf)
+        except EOFError:
+            linkoffset = buf.tell()
+        else:
+            raise SystemError, 'implementation error'
+        im.last_linkoffset = linkoffset + savepos
+
+    _save_as_is.__globals__.update(TiffImagePlugin._save.__globals__)
+    TiffImagePlugin._save_as_is = _save_as_is
+    Image.register_save("TIFF", _save_as_is)
+    
+if __name__ == '__main__':
+    check_pil_fake()