Merge pull request #1261 from radarhere/nframes

Provide n_frames attribute to multi-frame formats
This commit is contained in:
Hugo 2015-06-08 09:07:40 +03:00
commit caae8547b4
19 changed files with 170 additions and 25 deletions

View File

@ -62,6 +62,10 @@ class DcxImageFile(PcxImageFile):
self.__fp = self.fp
self.seek(0)
@property
def n_frames(self):
return len(self._offset)
def seek(self, frame):
if frame >= len(self._offset):
raise EOFError("attempt to seek outside DCX directory")

View File

@ -86,9 +86,10 @@ class FliImageFile(ImageFile.ImageFile):
self.palette = ImagePalette.raw("RGB", b"".join(palette))
# set things up to decode first frame
self.frame = -1
self.__frame = -1
self.__fp = self.fp
self.__rewind = self.fp.tell()
self._n_frames = None
self.seek(0)
def _palette(self, palette, shift):
@ -109,11 +110,35 @@ class FliImageFile(ImageFile.ImageFile):
palette[i] = (r, g, b)
i += 1
def seek(self, frame):
@property
def n_frames(self):
if self._n_frames is None:
current = self.tell()
try:
while True:
self.seek(self.tell() + 1)
except EOFError:
self._n_frames = self.tell() + 1
self.seek(current)
return self._n_frames
if frame != self.frame + 1:
def seek(self, frame):
if frame == self.__frame:
return
if frame < self.__frame:
self._seek(0)
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
def _seek(self, frame):
if frame == 0:
self.__frame = -1
self.__fp.seek(self.__rewind)
self.__offset = 128
if frame != self.__frame + 1:
raise ValueError("cannot seek to frame %d" % frame)
self.frame = frame
self.__frame = frame
# move to next frame
self.fp = self.__fp
@ -128,11 +153,10 @@ class FliImageFile(ImageFile.ImageFile):
self.decodermaxblock = framesize
self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
self.__offset = self.__offset + framesize
self.__offset += framesize
def tell(self):
return self.frame
return self.__frame
#
# registry

View File

@ -87,9 +87,30 @@ class GifImageFile(ImageFile.ImageFile):
self.__fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell()
self.seek(0) # get ready to read first frame
self._n_frames = None
self._seek(0) # get ready to read first frame
@property
def n_frames(self):
if self._n_frames is None:
current = self.tell()
try:
while True:
self.seek(self.tell() + 1)
except EOFError:
self._n_frames = self.tell() + 1
self.seek(current)
return self._n_frames
def seek(self, frame):
if frame == self.__frame:
return
if frame < self.__frame:
self._seek(0)
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
def _seek(self, frame):
if frame == 0:
# rewind

View File

@ -260,6 +260,10 @@ class ImImageFile(ImageFile.ImageFile):
self.tile = [("raw", (0, 0)+self.size, offs,
(self.rawmode, 0, -1))]
@property
def n_frames(self):
return self.info[FRAMES]
def seek(self, frame):
if frame < 0 or frame >= self.info[FRAMES]:

View File

@ -71,6 +71,10 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self.seek(0)
@property
def n_frames(self):
return len(self.images)
def seek(self, frame):
try:

View File

@ -62,6 +62,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
def load_seek(self, pos):
self.__fp.seek(pos)
@property
def n_frames(self):
return self.__framecount
def seek(self, frame):
if frame < 0 or frame >= self.__framecount:
raise EOFError("no more images in MPO file")

View File

@ -132,6 +132,10 @@ class PsdImageFile(ImageFile.ImageFile):
self._fp = self.fp
self.frame = 0
@property
def n_frames(self):
return len(self.layers)
def seek(self, layer):
# seek to given layer (1..max)
if layer == self.frame:

View File

@ -127,12 +127,12 @@ class SpiderImageFile(ImageFile.ImageFile):
if self.istack == 0 and self.imgnumber == 0:
# stk=0, img=0: a regular 2D image
offset = hdrlen
self.nimages = 1
self._nimages = 1
elif self.istack > 0 and self.imgnumber == 0:
# stk>0, img=0: Opening the stack for the first time
self.imgbytes = int(h[12]) * int(h[2]) * 4
self.hdrlen = hdrlen
self.nimages = int(h[26])
self._nimages = int(h[26])
# Point to the first image in the stack
offset = hdrlen * 2
self.imgnumber = 1
@ -154,6 +154,10 @@ class SpiderImageFile(ImageFile.ImageFile):
(self.rawmode, 0, 1))]
self.__fp = self.fp # FIXME: hack
@property
def n_frames(self):
return self._nimages
# 1st image index is zero (although SPIDER imgnumber starts at 1)
def tell(self):
if self.imgnumber < 1:
@ -164,7 +168,7 @@ class SpiderImageFile(ImageFile.ImageFile):
def seek(self, frame):
if self.istack == 0:
return
if frame >= self.nimages:
if frame >= self._nimages:
raise EOFError("attempt to seek past end of file")
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
self.fp = self.__fp

View File

@ -648,6 +648,8 @@ class TiffImageFile(ImageFile.ImageFile):
self.__first = self.__next = self.ifd.i32(ifh, 4)
self.__frame = -1
self.__fp = self.fp
self._frame_pos = []
self._n_frames = None
if Image.DEBUG:
print("*** TiffImageFile._open ***")
@ -657,28 +659,30 @@ class TiffImageFile(ImageFile.ImageFile):
# and load the first frame
self._seek(0)
@property
def n_frames(self):
if self._n_frames is None:
current = self.tell()
try:
while True:
self._seek(self.tell() + 1)
except EOFError:
self._n_frames = self.tell() + 1
self.seek(current)
return self._n_frames
def seek(self, frame):
"Select a given frame as current image"
if frame < 0:
frame = 0
self._seek(frame)
self._seek(max(frame, 0)) # Questionable backwards compatibility.
# 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
self.__frame = -1
self.__next = self.__first
while self.__frame < frame:
while len(self._frame_pos) <= frame:
if not self.__next:
raise EOFError("no more images in TIFF file")
if Image.DEBUG:
@ -688,14 +692,19 @@ class TiffImageFile(ImageFile.ImageFile):
# was passed to libtiff, invalidating the buffer
self.fp.tell()
self.fp.seek(self.__next)
self._frame_pos.append(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.fp.seek(self._frame_pos[frame])
self.tag.load(self.fp)
self.__frame = frame
self._setup()
def _tell(self):
def tell(self):
"Return the current frame number"
return self.__frame
def _decoder(self, rawmode, layer, tile=None):

BIN
Tests/images/hopper.im Normal file

Binary file not shown.

Binary file not shown.

View File

@ -30,6 +30,10 @@ class TestFileDcx(PillowTestCase):
# Assert
self.assertEqual(frame, 0)
def test_n_frames(self):
im = Image.open(TEST_FILE)
self.assertEqual(im.n_frames, 1)
def test_seek_too_far(self):
# Arrange
im = Image.open(TEST_FILE)

View File

@ -18,6 +18,10 @@ class TestFileFli(PillowTestCase):
self.assertEqual(im.size, (128, 128))
self.assertEqual(im.format, "FLI")
def test_n_frames(self):
im = Image.open(test_file)
self.assertEqual(im.n_frames, 2)
if __name__ == '__main__':
unittest.main()

View File

@ -134,6 +134,10 @@ class TestFileGif(PillowTestCase):
except EOFError:
self.assertEqual(framecount, 5)
def test_n_frames(self):
im = Image.open("Tests/images/iss634.gif")
self.assertEqual(im.n_frames, 43)
def test_dispose_none(self):
img = Image.open("Tests/images/dispose_none.gif")
try:

33
Tests/test_file_im.py Normal file
View File

@ -0,0 +1,33 @@
from helper import unittest, PillowTestCase, hopper
from PIL import Image
# sample im
TEST_IM = "Tests/images/hopper.im"
class TestFileIm(PillowTestCase):
def test_sanity(self):
im = Image.open(TEST_IM)
im.load()
self.assertEqual(im.mode, "RGB")
self.assertEqual(im.size, (128, 128))
self.assertEqual(im.format, "IM")
def test_n_frames(self):
im = Image.open(TEST_IM)
self.assertEqual(im.n_frames, 1)
def test_roundtrip(self):
out = self.tempfile('temp.im')
im = hopper()
im.save(out)
reread = Image.open(out)
self.assert_image_equal(reread, im)
if __name__ == '__main__':
unittest.main()
# End of file

View File

@ -95,6 +95,10 @@ class TestFileMpo(PillowTestCase):
im.seek(0)
self.assertEqual(im.tell(), 0)
def test_n_frames(self):
im = Image.open("Tests/images/sugarshack.mpo")
self.assertEqual(im.n_frames, 2)
def test_image_grab(self):
for test_file in test_files:
im = Image.open(test_file)

View File

@ -16,6 +16,13 @@ class TestImagePsd(PillowTestCase):
self.assertEqual(im.size, (128, 128))
self.assertEqual(im.format, "PSD")
def test_n_frames(self):
im = Image.open("Tests/images/hopper_merged.psd")
self.assertEqual(im.n_frames, 1)
im = Image.open(test_file)
self.assertEqual(im.n_frames, 2)
if __name__ == '__main__':
unittest.main()

View File

@ -42,6 +42,10 @@ class TestImageSpider(PillowTestCase):
# Assert
self.assertEqual(index, 0)
def test_n_frames(self):
im = Image.open(TEST_FILE)
self.assertEqual(im.n_frames, 1)
def test_loadImageSeries(self):
# Arrange
not_spider_file = "Tests/images/hopper.ppm"

View File

@ -150,6 +150,13 @@ class TestFileTiff(PillowTestCase):
self.assertEqual(
im.getextrema(), (-3.140936851501465, 3.140684127807617))
def test_n_frames(self):
im = Image.open('Tests/images/multipage-lastframe.tif')
self.assertEqual(im.n_frames, 1)
im = Image.open('Tests/images/multipage.tiff')
self.assertEqual(im.n_frames, 3)
def test_multipage(self):
# issue #862
im = Image.open('Tests/images/multipage.tiff')