mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 01:46:18 +03:00
Merge pull request #1261 from radarhere/nframes
Provide n_frames attribute to multi-frame formats
This commit is contained in:
commit
caae8547b4
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
BIN
Tests/images/hopper.im
Normal file
Binary file not shown.
BIN
Tests/images/hopper_merged.psd
Normal file
BIN
Tests/images/hopper_merged.psd
Normal file
Binary file not shown.
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
33
Tests/test_file_im.py
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue
Block a user