mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 09:56:17 +03:00
Merge pull request #885 from wiredfool/tiff-seek-rebase
Fix for reading multipage TIFFs
This commit is contained in:
commit
e09d7790a0
|
@ -281,6 +281,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
self.tagdata = {}
|
self.tagdata = {}
|
||||||
self.tagtype = {} # added 2008-06-05 by Florian Hoech
|
self.tagtype = {} # added 2008-06-05 by Florian Hoech
|
||||||
self.next = None
|
self.next = None
|
||||||
|
self.offset = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.as_dict())
|
return str(self.as_dict())
|
||||||
|
@ -415,6 +416,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
# load tag dictionary
|
# load tag dictionary
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
|
self.offset = fp.tell()
|
||||||
|
|
||||||
i16 = self.i16
|
i16 = self.i16
|
||||||
i32 = self.i32
|
i32 = self.i32
|
||||||
|
@ -446,7 +448,11 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
# Get and expand tag value
|
# Get and expand tag value
|
||||||
if size > 4:
|
if size > 4:
|
||||||
here = fp.tell()
|
here = fp.tell()
|
||||||
|
if Image.DEBUG:
|
||||||
|
print ("Tag Location: %s" %here)
|
||||||
fp.seek(i32(ifd, 8))
|
fp.seek(i32(ifd, 8))
|
||||||
|
if Image.DEBUG:
|
||||||
|
print ("Data Location: %s" %fp.tell())
|
||||||
data = ImageFile._safe_read(fp, size)
|
data = ImageFile._safe_read(fp, size)
|
||||||
fp.seek(here)
|
fp.seek(here)
|
||||||
else:
|
else:
|
||||||
|
@ -630,18 +636,20 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
"Select a given frame as current image"
|
"Select a given frame as current image"
|
||||||
|
|
||||||
if frame < 0:
|
if frame < 0:
|
||||||
frame = 0
|
frame = 0
|
||||||
self._seek(frame)
|
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):
|
def tell(self):
|
||||||
"Return the current frame number"
|
"Return the current frame number"
|
||||||
|
|
||||||
return self._tell()
|
return self._tell()
|
||||||
|
|
||||||
def _seek(self, frame):
|
def _seek(self, frame):
|
||||||
|
|
||||||
self.fp = self.__fp
|
self.fp = self.__fp
|
||||||
if frame < self.__frame:
|
if frame < self.__frame:
|
||||||
# rewind file
|
# rewind file
|
||||||
|
@ -650,14 +658,21 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
while self.__frame < frame:
|
while self.__frame < frame:
|
||||||
if not self.__next:
|
if not self.__next:
|
||||||
raise EOFError("no more images in TIFF file")
|
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()))
|
||||||
|
# reset python3 buffered io handle in case fp
|
||||||
|
# was passed to libtiff, invalidating the buffer
|
||||||
|
self.fp.tell()
|
||||||
self.fp.seek(self.__next)
|
self.fp.seek(self.__next)
|
||||||
|
if Image.DEBUG:
|
||||||
|
print("Loading tags, location: %s"%self.fp.tell())
|
||||||
self.tag.load(self.fp)
|
self.tag.load(self.fp)
|
||||||
self.__next = self.tag.next
|
self.__next = self.tag.next
|
||||||
self.__frame += 1
|
self.__frame += 1
|
||||||
self._setup()
|
self._setup()
|
||||||
|
|
||||||
def _tell(self):
|
def _tell(self):
|
||||||
|
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
def _decoder(self, rawmode, layer, tile=None):
|
def _decoder(self, rawmode, layer, tile=None):
|
||||||
|
@ -706,6 +721,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# (self._compression, (extents tuple),
|
# (self._compression, (extents tuple),
|
||||||
# 0, (rawmode, self._compression, fp))
|
# 0, (rawmode, self._compression, fp))
|
||||||
ignored, extents, ignored_2, args = self.tile[0]
|
ignored, extents, ignored_2, args = self.tile[0]
|
||||||
|
args = args + (self.ifd.offset,)
|
||||||
decoder = Image._getdecoder(self.mode, 'libtiff', args,
|
decoder = Image._getdecoder(self.mode, 'libtiff', args,
|
||||||
self.decoderconfig)
|
self.decoderconfig)
|
||||||
try:
|
try:
|
||||||
|
@ -744,7 +760,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
# libtiff closed the fp in a, we need to close self.fp, if possible
|
# libtiff closed the fp in a, we need to close self.fp, if possible
|
||||||
if hasattr(self.fp, 'close'):
|
if hasattr(self.fp, 'close'):
|
||||||
self.fp.close()
|
if not self.__next:
|
||||||
|
self.fp.close()
|
||||||
self.fp = None # might be shared
|
self.fp = None # might be shared
|
||||||
|
|
||||||
if err < 0:
|
if err < 0:
|
||||||
|
|
BIN
Tests/images/multipage-lastframe.tif
Normal file
BIN
Tests/images/multipage-lastframe.tif
Normal file
Binary file not shown.
BIN
Tests/images/multipage.tiff
Normal file
BIN
Tests/images/multipage.tiff
Normal file
Binary file not shown.
|
@ -312,6 +312,35 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
self.assertRaises(OSError, lambda: os.fstat(fn))
|
self.assertRaises(OSError, lambda: os.fstat(fn))
|
||||||
self.assertRaises(OSError, lambda: os.close(fn))
|
self.assertRaises(OSError, lambda: os.close(fn))
|
||||||
|
|
||||||
|
def test_multipage(self):
|
||||||
|
# issue #862
|
||||||
|
TiffImagePlugin.READ_LIBTIFF = True
|
||||||
|
im = Image.open('Tests/images/multipage.tiff')
|
||||||
|
# file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue
|
||||||
|
|
||||||
|
im.seek(0)
|
||||||
|
self.assertEqual(im.size, (10,10))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,128,0))
|
||||||
|
self.assertTrue(im.tag.next)
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
self.assertEqual(im.size, (10,10))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (255,0,0))
|
||||||
|
self.assertTrue(im.tag.next)
|
||||||
|
|
||||||
|
im.seek(2)
|
||||||
|
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):
|
||||||
|
TiffImagePlugin.READ_LIBTIFF = True
|
||||||
|
im = Image.open('Tests/images/lena.tif')
|
||||||
|
self.assertFalse(im.tag.next)
|
||||||
|
im.load()
|
||||||
|
self.assertFalse(im.tag.next)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from helper import unittest, PillowTestCase, lena, py3
|
from helper import unittest, PillowTestCase, lena, py3
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image, TiffImagePlugin
|
||||||
|
|
||||||
|
|
||||||
class TestFileTiff(PillowTestCase):
|
class TestFileTiff(PillowTestCase):
|
||||||
|
@ -141,6 +141,32 @@ class TestFileTiff(PillowTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
im.getextrema(), (-3.140936851501465, 3.140684127807617))
|
im.getextrema(), (-3.140936851501465, 3.140684127807617))
|
||||||
|
|
||||||
|
def test_multipage(self):
|
||||||
|
# issue #862
|
||||||
|
im = Image.open('Tests/images/multipage.tiff')
|
||||||
|
# file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue
|
||||||
|
|
||||||
|
im.seek(0)
|
||||||
|
self.assertEqual(im.size, (10,10))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,128,0))
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
im.load()
|
||||||
|
self.assertEqual(im.size, (10,10))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (255,0,0))
|
||||||
|
|
||||||
|
im.seek(2)
|
||||||
|
im.load()
|
||||||
|
self.assertEqual(im.size, (20,20))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255))
|
||||||
|
|
||||||
|
def test_multipage_last_frame(self):
|
||||||
|
im = Image.open('Tests/images/multipage-lastframe.tif')
|
||||||
|
im.load()
|
||||||
|
self.assertEqual(im.size, (20,20))
|
||||||
|
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255))
|
||||||
|
|
||||||
|
|
||||||
def test___str__(self):
|
def test___str__(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
file = "Tests/images/pil136.tiff"
|
file = "Tests/images/pil136.tiff"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from helper import unittest, PillowTestCase, lena
|
from helper import unittest, PillowTestCase, lena
|
||||||
|
|
||||||
from PIL import ImageSequence
|
from PIL import Image, ImageSequence, TiffImagePlugin
|
||||||
|
|
||||||
|
|
||||||
class TestImageSequence(PillowTestCase):
|
class TestImageSequence(PillowTestCase):
|
||||||
|
@ -22,7 +22,31 @@ class TestImageSequence(PillowTestCase):
|
||||||
|
|
||||||
self.assertEqual(index, 1)
|
self.assertEqual(index, 1)
|
||||||
|
|
||||||
|
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):
|
||||||
|
#self._test_multipage_tiff(True)
|
||||||
|
self._test_multipage_tiff(False)
|
||||||
|
|
||||||
|
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(True)
|
||||||
|
self._test_multipage_tiff(False)
|
||||||
|
TiffImagePlugin.READ_LIBTIFF = False
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
5
decode.c
5
decode.c
|
@ -442,8 +442,9 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args)
|
||||||
char* rawmode;
|
char* rawmode;
|
||||||
char* compname;
|
char* compname;
|
||||||
int fp;
|
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;
|
return NULL;
|
||||||
|
|
||||||
TRACE(("new tiff decoder %s\n", compname));
|
TRACE(("new tiff decoder %s\n", compname));
|
||||||
|
@ -455,7 +456,7 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args)
|
||||||
if (get_unpacker(decoder, mode, rawmode) < 0)
|
if (get_unpacker(decoder, mode, rawmode) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (! ImagingLibTiffInit(&decoder->state, fp)) {
|
if (! ImagingLibTiffInit(&decoder->state, fp, ifdoffset)) {
|
||||||
Py_DECREF(decoder);
|
Py_DECREF(decoder);
|
||||||
PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed");
|
PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
#include "TiffDecode.h"
|
#include "TiffDecode.h"
|
||||||
|
|
||||||
void dump_state(const TIFFSTATE *state){
|
void dump_state(const TIFFSTATE *state){
|
||||||
TRACE(("State: Location %u size %d eof %d data: %p \n", (uint)state->loc,
|
TRACE(("State: Location %u size %d eof %d data: %p ifd: %d\n", (uint)state->loc,
|
||||||
(int)state->size, (uint)state->eof, state->data));
|
(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;
|
(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;
|
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
|
||||||
|
|
||||||
TRACE(("initing libtiff\n"));
|
TRACE(("initing libtiff\n"));
|
||||||
|
@ -158,6 +158,7 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp) {
|
||||||
clientstate->size = 0;
|
clientstate->size = 0;
|
||||||
clientstate->data = 0;
|
clientstate->data = 0;
|
||||||
clientstate->fp = fp;
|
clientstate->fp = fp;
|
||||||
|
clientstate->ifd = offset;
|
||||||
clientstate->eof = 0;
|
clientstate->eof = 0;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -195,7 +196,6 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
|
||||||
clientstate->loc = 0;
|
clientstate->loc = 0;
|
||||||
clientstate->data = (tdata_t)buffer;
|
clientstate->data = (tdata_t)buffer;
|
||||||
clientstate->flrealloc = 0;
|
clientstate->flrealloc = 0;
|
||||||
|
|
||||||
dump_state(clientstate);
|
dump_state(clientstate);
|
||||||
|
|
||||||
TIFFSetWarningHandler(NULL);
|
TIFFSetWarningHandler(NULL);
|
||||||
|
@ -220,6 +220,16 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
|
||||||
return -1;
|
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);
|
size = TIFFScanlineSize(tiff);
|
||||||
TRACE(("ScanlineSize: %d \n", size));
|
TRACE(("ScanlineSize: %d \n", size));
|
||||||
if (size > state->bytes) {
|
if (size > state->bytes) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ typedef struct {
|
||||||
toff_t loc; /* toff_t == uint32 */
|
toff_t loc; /* toff_t == uint32 */
|
||||||
tsize_t size; /* tsize_t == int32 */
|
tsize_t size; /* tsize_t == int32 */
|
||||||
int fp;
|
int fp;
|
||||||
|
int ifd; /* offset of the ifd, used for multipage */
|
||||||
TIFF *tiff; /* Used in write */
|
TIFF *tiff; /* Used in write */
|
||||||
toff_t eof;
|
toff_t eof;
|
||||||
int flrealloc; /* may we realloc */
|
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 ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
|
||||||
extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);
|
extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);
|
||||||
|
|
||||||
|
@ -50,5 +51,4 @@ extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);
|
||||||
|
|
||||||
#define TRACE(args)
|
#define TRACE(args)
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue
Block a user