mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-30 23:47:27 +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