mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 09:26:16 +03:00
Use disposal settings from previous frame
This commit is contained in:
parent
ce3d80e713
commit
5e4e0fa6ee
BIN
Tests/images/apng/dispose_op_previous_frame.png
Normal file
BIN
Tests/images/apng/dispose_op_previous_frame.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 582 B |
|
@ -105,6 +105,31 @@ def test_apng_dispose_region():
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apng_dispose_op_previous_frame():
|
||||||
|
# Test that the dispose settings being used are from the previous frame
|
||||||
|
#
|
||||||
|
# Image created with:
|
||||||
|
# red = Image.new("RGBA", (128, 64), (255, 0, 0, 255))
|
||||||
|
# green = red.copy()
|
||||||
|
# green.paste(Image.new("RGBA", (64, 32), (0, 255, 0, 255)))
|
||||||
|
# blue = red.copy()
|
||||||
|
# blue.paste(Image.new("RGBA", (64, 32), (0, 255, 0, 255)), (64, 32))
|
||||||
|
#
|
||||||
|
# red.save(
|
||||||
|
# "Tests/images/apng/dispose_op_previous_frame.png",
|
||||||
|
# save_all=True,
|
||||||
|
# append_images=[green, blue],
|
||||||
|
# disposal=[
|
||||||
|
# PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
||||||
|
# PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
|
||||||
|
# PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS
|
||||||
|
# ],
|
||||||
|
# )
|
||||||
|
with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im:
|
||||||
|
im.seek(im.n_frames - 1)
|
||||||
|
assert im.getpixel((0, 0)) == (255, 0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
def test_apng_dispose_op_background_p_mode():
|
def test_apng_dispose_op_background_p_mode():
|
||||||
with Image.open("Tests/images/apng/dispose_op_background_p_mode.png") as im:
|
with Image.open("Tests/images/apng/dispose_op_background_p_mode.png") as im:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
|
|
|
@ -803,60 +803,76 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self.blend_op = self.info.get("blend")
|
self.blend_op = self.info.get("blend")
|
||||||
self.dispose_extent = self.info.get("bbox")
|
self.dispose_extent = self.info.get("bbox")
|
||||||
self.__frame = 0
|
self.__frame = 0
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
if frame != self.__frame + 1:
|
if frame != self.__frame + 1:
|
||||||
raise ValueError(f"cannot seek to frame {frame}")
|
raise ValueError(f"cannot seek to frame {frame}")
|
||||||
|
|
||||||
# ensure previous frame was loaded
|
# ensure previous frame was loaded
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
self.fp = self.__fp
|
if self.dispose:
|
||||||
|
self.im.paste(self.dispose, self.dispose_extent)
|
||||||
|
self._prev_im = self.im.copy()
|
||||||
|
|
||||||
# advance to the next frame
|
self.fp = self.__fp
|
||||||
if self.__prepare_idat:
|
|
||||||
ImageFile._safe_read(self.fp, self.__prepare_idat)
|
|
||||||
self.__prepare_idat = 0
|
|
||||||
frame_start = False
|
|
||||||
while True:
|
|
||||||
self.fp.read(4) # CRC
|
|
||||||
|
|
||||||
try:
|
# advance to the next frame
|
||||||
cid, pos, length = self.png.read()
|
if self.__prepare_idat:
|
||||||
except (struct.error, SyntaxError):
|
ImageFile._safe_read(self.fp, self.__prepare_idat)
|
||||||
break
|
self.__prepare_idat = 0
|
||||||
|
frame_start = False
|
||||||
|
while True:
|
||||||
|
self.fp.read(4) # CRC
|
||||||
|
|
||||||
if cid == b"IEND":
|
try:
|
||||||
raise EOFError("No more images in APNG file")
|
cid, pos, length = self.png.read()
|
||||||
if cid == b"fcTL":
|
except (struct.error, SyntaxError):
|
||||||
if frame_start:
|
break
|
||||||
# there must be at least one fdAT chunk between fcTL chunks
|
|
||||||
raise SyntaxError("APNG missing frame data")
|
|
||||||
frame_start = True
|
|
||||||
|
|
||||||
try:
|
if cid == b"IEND":
|
||||||
self.png.call(cid, pos, length)
|
raise EOFError("No more images in APNG file")
|
||||||
except UnicodeDecodeError:
|
if cid == b"fcTL":
|
||||||
break
|
|
||||||
except EOFError:
|
|
||||||
if cid == b"fdAT":
|
|
||||||
length -= 4
|
|
||||||
if frame_start:
|
if frame_start:
|
||||||
self.__prepare_idat = length
|
# there must be at least one fdAT chunk between fcTL chunks
|
||||||
break
|
raise SyntaxError("APNG missing frame data")
|
||||||
ImageFile._safe_read(self.fp, length)
|
frame_start = True
|
||||||
except AttributeError:
|
|
||||||
logger.debug("%r %s %s (unknown)", cid, pos, length)
|
|
||||||
ImageFile._safe_read(self.fp, length)
|
|
||||||
|
|
||||||
self.__frame = frame
|
try:
|
||||||
self.tile = self.png.im_tile
|
self.png.call(cid, pos, length)
|
||||||
self.dispose_op = self.info.get("disposal")
|
except UnicodeDecodeError:
|
||||||
self.blend_op = self.info.get("blend")
|
break
|
||||||
self.dispose_extent = self.info.get("bbox")
|
except EOFError:
|
||||||
|
if cid == b"fdAT":
|
||||||
|
length -= 4
|
||||||
|
if frame_start:
|
||||||
|
self.__prepare_idat = length
|
||||||
|
break
|
||||||
|
ImageFile._safe_read(self.fp, length)
|
||||||
|
except AttributeError:
|
||||||
|
logger.debug("%r %s %s (unknown)", cid, pos, length)
|
||||||
|
ImageFile._safe_read(self.fp, length)
|
||||||
|
|
||||||
if not self.tile:
|
self.__frame = frame
|
||||||
raise EOFError
|
self.tile = self.png.im_tile
|
||||||
|
self.dispose_op = self.info.get("disposal")
|
||||||
|
self.blend_op = self.info.get("blend")
|
||||||
|
self.dispose_extent = self.info.get("bbox")
|
||||||
|
|
||||||
|
if not self.tile:
|
||||||
|
raise EOFError
|
||||||
|
|
||||||
|
# setup frame disposal (actual disposal done when needed in the next _seek())
|
||||||
|
if self._prev_im is None and self.dispose_op == APNG_DISPOSE_OP_PREVIOUS:
|
||||||
|
self.dispose_op = APNG_DISPOSE_OP_BACKGROUND
|
||||||
|
|
||||||
|
if self.dispose_op == APNG_DISPOSE_OP_PREVIOUS:
|
||||||
|
self.dispose = self._prev_im.copy()
|
||||||
|
self.dispose = self._crop(self.dispose, self.dispose_extent)
|
||||||
|
elif self.dispose_op == APNG_DISPOSE_OP_BACKGROUND:
|
||||||
|
self.dispose = Image.core.fill(self.mode, self.size)
|
||||||
|
self.dispose = self._crop(self.dispose, self.dispose_extent)
|
||||||
|
else:
|
||||||
|
self.dispose = None
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
@ -939,19 +955,6 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self.png.close()
|
self.png.close()
|
||||||
self.png = None
|
self.png = None
|
||||||
else:
|
else:
|
||||||
# setup frame disposal (actual disposal done when needed in _seek())
|
|
||||||
if self._prev_im is None and self.dispose_op == APNG_DISPOSE_OP_PREVIOUS:
|
|
||||||
self.dispose_op = APNG_DISPOSE_OP_BACKGROUND
|
|
||||||
|
|
||||||
if self.dispose_op == APNG_DISPOSE_OP_PREVIOUS:
|
|
||||||
dispose = self._prev_im.copy()
|
|
||||||
dispose = self._crop(dispose, self.dispose_extent)
|
|
||||||
elif self.dispose_op == APNG_DISPOSE_OP_BACKGROUND:
|
|
||||||
dispose = Image.core.fill(self.im.mode, self.size)
|
|
||||||
dispose = self._crop(dispose, self.dispose_extent)
|
|
||||||
else:
|
|
||||||
dispose = None
|
|
||||||
|
|
||||||
if self._prev_im and self.blend_op == APNG_BLEND_OP_OVER:
|
if self._prev_im and self.blend_op == APNG_BLEND_OP_OVER:
|
||||||
updated = self._crop(self.im, self.dispose_extent)
|
updated = self._crop(self.im, self.dispose_extent)
|
||||||
self._prev_im.paste(
|
self._prev_im.paste(
|
||||||
|
@ -960,10 +963,6 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
if self.pyaccess:
|
if self.pyaccess:
|
||||||
self.pyaccess = None
|
self.pyaccess = None
|
||||||
self._prev_im = self.im.copy()
|
|
||||||
|
|
||||||
if dispose:
|
|
||||||
self._prev_im.paste(dispose, self.dispose_extent)
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user