From f54bccab0ec36f8c2012c2b0c0c7e34f35196864 Mon Sep 17 00:00:00 2001 From: Kenny Ostrom Date: Wed, 20 Aug 2014 17:24:28 -0500 Subject: [PATCH 1/8] multipage tiff for iterator testing --- Tests/images/test.tif | Bin 0 -> 816 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/test.tif diff --git a/Tests/images/test.tif b/Tests/images/test.tif new file mode 100644 index 0000000000000000000000000000000000000000..7a7f5f89a05f35ebba3e8ff6ce7903c91a955965 GIT binary patch literal 816 zcmb_ayAHxI40IBxP$#H(NTnkSZ0rmSEd2p?29Q`7SXlc#d=?`kGg3JRCj==76=5m$ z)v=r0^~!Qa1;wLFqzox+rdTS#S}^aK04IVC>OIx6vmM|p=otifD41~6Nc#Ke&sE#b zUVz7fufRnxI|bIL;bt^f+TQVowJOT{O_Dk7&r0>?)puqGmPkbvcUJ9Pf{4p e>68J-bkbzJPC0s Date: Wed, 20 Aug 2014 21:43:46 -0700 Subject: [PATCH 2/8] Tests for multipage tiffs --- Tests/images/multipage-lastframe.tif | Bin 0 -> 296 bytes Tests/images/{test.tif => multipage.tiff} | Bin Tests/test_file_libtiff.py | 29 ++++++++++++++++++++++ Tests/test_file_tiff.py | 28 ++++++++++++++++++++- 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 Tests/images/multipage-lastframe.tif rename Tests/images/{test.tif => multipage.tiff} (100%) diff --git a/Tests/images/multipage-lastframe.tif b/Tests/images/multipage-lastframe.tif new file mode 100644 index 0000000000000000000000000000000000000000..aeba534e25a9f0527c5fe1674461d4b484b67159 GIT binary patch literal 296 zcmebD)M9X9pbQ8wFfcMRFaiw`0b)iZHWQQ$l4C| Date: Wed, 20 Aug 2014 21:44:28 -0700 Subject: [PATCH 3/8] Don't close fd for multipage files --- PIL/TiffImagePlugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 9bef30ebefc..d7367c1131f 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -744,7 +744,8 @@ def _load_libtiff(self): self.readonly = 0 # libtiff closed the fp in a, we need to close self.fp, if possible if hasattr(self.fp, 'close'): - self.fp.close() + if not self.__next: + self.fp.close() self.fp = None # might be shared if err < 0: From 99f7dd2afffb4fdec7ba9607bf98140cf1d441bd Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 20 Aug 2014 22:52:12 -0700 Subject: [PATCH 4/8] Allocate a new core image object for each frame, fixes problems with reusing palettes and image sizes --- PIL/TiffImagePlugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index d7367c1131f..85d5609c9cd 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -655,7 +655,9 @@ def _seek(self, frame): self.__next = self.tag.next self.__frame += 1 self._setup() - + #UNDONE - decompresion bomb + self.im = Image.core.new(self.mode, self.size) + def _tell(self): return self.__frame From d5b4f136c522b10ad110638482b2b9c420022379 Mon Sep 17 00:00:00 2001 From: Kenny Ostrom Date: Thu, 21 Aug 2014 13:02:01 -0500 Subject: [PATCH 5/8] added ifd offset to the tiff decoder args, call TIFFSetSubDirectory --- PIL/TiffImagePlugin.py | 3 +++ decode.c | 5 +++-- libImaging/TiffDecode.c | 18 ++++++++++++++---- libImaging/TiffDecode.h | 4 ++-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 85d5609c9cd..4921f0a370e 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -281,6 +281,7 @@ def reset(self): self.tagdata = {} self.tagtype = {} # added 2008-06-05 by Florian Hoech self.next = None + self.offset = None def __str__(self): return str(self.as_dict()) @@ -415,6 +416,7 @@ def load(self, fp): # load tag dictionary self.reset() + self.offset = fp.tell() i16 = self.i16 i32 = self.i32 @@ -708,6 +710,7 @@ def _load_libtiff(self): # (self._compression, (extents tuple), # 0, (rawmode, self._compression, fp)) ignored, extents, ignored_2, args = self.tile[0] + args = args + (self.ifd.offset,) decoder = Image._getdecoder(self.mode, 'libtiff', args, self.decoderconfig) try: diff --git a/decode.c b/decode.c index d5e32938444..e9aa6a387f9 100644 --- a/decode.c +++ b/decode.c @@ -442,8 +442,9 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args) char* rawmode; char* compname; int fp; + int ifdoffset; - if (! PyArg_ParseTuple(args, "sssi", &mode, &rawmode, &compname, &fp)) + if (! PyArg_ParseTuple(args, "sssii", &mode, &rawmode, &compname, &fp, &ifdoffset)) return NULL; TRACE(("new tiff decoder %s\n", compname)); @@ -455,7 +456,7 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args) if (get_unpacker(decoder, mode, rawmode) < 0) return NULL; - if (! ImagingLibTiffInit(&decoder->state, fp)) { + if (! ImagingLibTiffInit(&decoder->state, fp, ifdoffset)) { Py_DECREF(decoder); PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed"); return NULL; diff --git a/libImaging/TiffDecode.c b/libImaging/TiffDecode.c index 787cd4506a9..1d320e9bdc6 100644 --- a/libImaging/TiffDecode.c +++ b/libImaging/TiffDecode.c @@ -21,8 +21,8 @@ #include "TiffDecode.h" void dump_state(const TIFFSTATE *state){ - TRACE(("State: Location %u size %d eof %d data: %p \n", (uint)state->loc, - (int)state->size, (uint)state->eof, state->data)); + TRACE(("State: Location %u size %d eof %d data: %p ifd: %d\n", (uint)state->loc, + (int)state->size, (uint)state->eof, state->data, state->ifd)); } /* @@ -142,7 +142,7 @@ void _tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) { (void) hdata; (void) base; (void) size; } -int ImagingLibTiffInit(ImagingCodecState state, int fp) { +int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset) { TIFFSTATE *clientstate = (TIFFSTATE *)state->context; TRACE(("initing libtiff\n")); @@ -158,6 +158,7 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp) { clientstate->size = 0; clientstate->data = 0; clientstate->fp = fp; + clientstate->ifd = offset; clientstate->eof = 0; return 1; @@ -195,7 +196,6 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int clientstate->loc = 0; clientstate->data = (tdata_t)buffer; clientstate->flrealloc = 0; - dump_state(clientstate); TIFFSetWarningHandler(NULL); @@ -220,6 +220,16 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int return -1; } + if (clientstate->ifd){ + unsigned int ifdoffset = clientstate->ifd; + TRACE(("reading tiff ifd %d\n", ifdoffset)); + int rv = TIFFSetSubDirectory(tiff, ifdoffset); + if (!rv){ + TRACE(("error in TIFFSetSubDirectory")); + return -1; + } + } + size = TIFFScanlineSize(tiff); TRACE(("ScanlineSize: %d \n", size)); if (size > state->bytes) { diff --git a/libImaging/TiffDecode.h b/libImaging/TiffDecode.h index 46c940d1b3d..9875309e3e6 100644 --- a/libImaging/TiffDecode.h +++ b/libImaging/TiffDecode.h @@ -26,6 +26,7 @@ typedef struct { toff_t loc; /* toff_t == uint32 */ tsize_t size; /* tsize_t == int32 */ int fp; + int ifd; /* offset of the ifd, used for multipage */ TIFF *tiff; /* Used in write */ toff_t eof; int flrealloc; /* may we realloc */ @@ -33,7 +34,7 @@ typedef struct { -extern int ImagingLibTiffInit(ImagingCodecState state, int fp); +extern int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset); extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp); extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); @@ -50,5 +51,4 @@ extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); #define TRACE(args) - #endif From 3df822d1ef0693cf242eb9f8bb2d9de914f9a0fa Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 26 Aug 2014 14:16:01 -0700 Subject: [PATCH 6/8] Testing the imagesequence module with the multipage tiffs --- Tests/test_imagesequence.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index fd10e598923..2470bc7328c 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase, lena -from PIL import ImageSequence +from PIL import Image, ImageSequence, TiffImagePlugin class TestImageSequence(PillowTestCase): @@ -22,7 +22,26 @@ def test_sanity(self): self.assertEqual(index, 1) - + def _test_multipage_tiff(self): + im = Image.open('Tests/images/multipage.tiff') + for index, frame in enumerate(ImageSequence.Iterator(im)): + frame.load() + self.assertEqual(index, im.tell()) + frame.convert('RGB') + + def test_tiff(self): + return self._test_multipage_tiff() + + def test_libtiff(self): + codecs = dir(Image.core) + + if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs: + self.skipTest("tiff support not available") + + TiffImagePlugin.READ_LIBTIFF = True + self._test_multipage_tiff() + TiffImagePlugin.READ_LIBTIFF = False + if __name__ == '__main__': unittest.main() From d8b2829f4b5819c4ae09a7aa81088ff3853a3c25 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 26 Aug 2014 15:30:03 -0700 Subject: [PATCH 7/8] Partial fix for test failures, works on py2.x --- PIL/TiffImagePlugin.py | 20 ++++++++++++++------ Tests/test_file_libtiff.py | 2 +- Tests/test_imagesequence.py | 11 ++++++++--- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 4921f0a370e..abf9db7e3bd 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -448,7 +448,11 @@ def load(self, fp): # Get and expand tag value if size > 4: here = fp.tell() + if Image.DEBUG: + print ("Tag Location: %s" %here) fp.seek(i32(ifd, 8)) + if Image.DEBUG: + print ("Data Location: %s" %fp.tell()) data = ImageFile._safe_read(fp, size) fp.seek(here) else: @@ -632,18 +636,20 @@ def _open(self): def seek(self, frame): "Select a given frame as current image" - if frame < 0: frame = 0 self._seek(frame) + # Create a new core image object on second and + # subsequent frames in the image. Image may be + # different size/mode. + Image._decompression_bomb_check(self.size) + self.im = Image.core.new(self.mode, self.size) def tell(self): "Return the current frame number" - return self._tell() def _seek(self, frame): - self.fp = self.__fp if frame < self.__frame: # rewind file @@ -652,16 +658,18 @@ def _seek(self, frame): while self.__frame < frame: if not self.__next: raise EOFError("no more images in TIFF file") + if Image.DEBUG: + print("Seeking to frame %s, on frame %s, __next %s, location: %s"% + (frame, self.__frame, self.__next, self.fp.tell())) self.fp.seek(self.__next) + if Image.DEBUG: + print("Loading tags, location: %s"%self.fp.tell()) self.tag.load(self.fp) self.__next = self.tag.next self.__frame += 1 self._setup() - #UNDONE - decompresion bomb - self.im = Image.core.new(self.mode, self.size) def _tell(self): - return self.__frame def _decoder(self, rawmode, layer, tile=None): diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 121ad5de29d..bdac5f30014 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -332,7 +332,7 @@ def test_multipage(self): self.assertFalse(im.tag.next) self.assertEqual(im.size, (20,20)) self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255)) - + TiffImagePlugin.READ_LIBTIFF = False def test__next(self): diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 2470bc7328c..0682904513d 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -22,15 +22,19 @@ def test_sanity(self): self.assertEqual(index, 1) - def _test_multipage_tiff(self): + def _test_multipage_tiff(self, dbg=False): + # debug had side effect of calling fp.tell. + Image.DEBUG=dbg im = Image.open('Tests/images/multipage.tiff') for index, frame in enumerate(ImageSequence.Iterator(im)): frame.load() self.assertEqual(index, im.tell()) frame.convert('RGB') + Image.DEBUG=False def test_tiff(self): - return self._test_multipage_tiff() + #self._test_multipage_tiff(True) + self._test_multipage_tiff(False) def test_libtiff(self): codecs = dir(Image.core) @@ -39,7 +43,8 @@ def test_libtiff(self): self.skipTest("tiff support not available") TiffImagePlugin.READ_LIBTIFF = True - self._test_multipage_tiff() + #self._test_multipage_tiff(True) + self._test_multipage_tiff(False) TiffImagePlugin.READ_LIBTIFF = False if __name__ == '__main__': From 17ac15543d07a081f5fa357c1e7f89c41fea579d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 2 Sep 2014 15:08:51 -0700 Subject: [PATCH 8/8] fp.tell resyncs the python3 with the file pointer --- PIL/TiffImagePlugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index abf9db7e3bd..50648288ea8 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -661,6 +661,9 @@ def _seek(self, frame): if Image.DEBUG: print("Seeking to frame %s, on frame %s, __next %s, location: %s"% (frame, self.__frame, self.__next, self.fp.tell())) + # reset python3 buffered io handle in case fp + # was passed to libtiff, invalidating the buffer + self.fp.tell() self.fp.seek(self.__next) if Image.DEBUG: print("Loading tags, location: %s"%self.fp.tell())