mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-30 23:47:27 +03:00 
			
		
		
		
	Merge pull request #6102 from radarhere/bmp_rle8
This commit is contained in:
		
						commit
						a921fcbf75
					
				
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_rle8.bmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/hopper_rle8.bmp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 26 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper_rle8_row_overflow.bmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/hopper_rle8_row_overflow.bmp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 26 KiB | 
|  | @ -40,6 +40,7 @@ def test_questionable(): | |||
|         "rgb32fakealpha.bmp", | ||||
|         "rgb24largepal.bmp", | ||||
|         "pal8os2sp.bmp", | ||||
|         "pal8rletrns.bmp", | ||||
|         "rgb32bf-xbgr.bmp", | ||||
|     ] | ||||
|     for f in get_files("q"): | ||||
|  |  | |||
|  | @ -4,7 +4,12 @@ import pytest | |||
| 
 | ||||
| from PIL import BmpImagePlugin, Image | ||||
| 
 | ||||
| from .helper import assert_image_equal, assert_image_equal_tofile, hopper | ||||
| from .helper import ( | ||||
|     assert_image_equal, | ||||
|     assert_image_equal_tofile, | ||||
|     assert_image_similar_tofile, | ||||
|     hopper, | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| def test_sanity(tmp_path): | ||||
|  | @ -125,6 +130,42 @@ def test_rgba_bitfields(): | |||
|     assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp") | ||||
| 
 | ||||
| 
 | ||||
| def test_rle8(): | ||||
|     with Image.open("Tests/images/hopper_rle8.bmp") as im: | ||||
|         assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12) | ||||
| 
 | ||||
|     # This test image has been manually hexedited | ||||
|     # to have rows with too much data | ||||
|     with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im: | ||||
|         assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12) | ||||
| 
 | ||||
|     # Signal end of bitmap before the image is finished | ||||
|     with open("Tests/images/bmp/g/pal8rle.bmp", "rb") as fp: | ||||
|         data = fp.read(1063) + b"\x01" | ||||
|         with Image.open(io.BytesIO(data)) as im: | ||||
|             with pytest.raises(ValueError): | ||||
|                 im.load() | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.parametrize( | ||||
|     "file_name,length", | ||||
|     ( | ||||
|         # EOF immediately after the header | ||||
|         ("Tests/images/hopper_rle8.bmp", 1078), | ||||
|         # EOF during delta | ||||
|         ("Tests/images/bmp/q/pal8rletrns.bmp", 3670), | ||||
|         # EOF when reading data in absolute mode | ||||
|         ("Tests/images/bmp/g/pal8rle.bmp", 1064), | ||||
|     ), | ||||
| ) | ||||
| def test_rle8_eof(file_name, length): | ||||
|     with open(file_name, "rb") as fp: | ||||
|         data = fp.read(length) | ||||
|         with Image.open(io.BytesIO(data)) as im: | ||||
|             with pytest.raises(ValueError): | ||||
|                 im.load() | ||||
| 
 | ||||
| 
 | ||||
| def test_offset(): | ||||
|     # This image has been hexedited | ||||
|     # to exclude the palette size from the pixel data offset | ||||
|  |  | |||
|  | @ -24,6 +24,8 @@ | |||
| # | ||||
| 
 | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| from . import Image, ImageFile, ImagePalette | ||||
| from ._binary import i16le as i16 | ||||
| from ._binary import i32le as i32 | ||||
|  | @ -167,6 +169,7 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|             raise OSError(f"Unsupported BMP pixel depth ({file_info['bits']})") | ||||
| 
 | ||||
|         # ---------------- Process BMP with Bitfields compression (not palette) | ||||
|         decoder_name = "raw" | ||||
|         if file_info["compression"] == self.BITFIELDS: | ||||
|             SUPPORTED = { | ||||
|                 32: [ | ||||
|  | @ -208,6 +211,8 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|         elif file_info["compression"] == self.RAW: | ||||
|             if file_info["bits"] == 32 and header == 22:  # 32-bit .cur offset | ||||
|                 raw_mode, self.mode = "BGRA", "RGBA" | ||||
|         elif file_info["compression"] == self.RLE8: | ||||
|             decoder_name = "bmp_rle" | ||||
|         else: | ||||
|             raise OSError(f"Unsupported BMP compression ({file_info['compression']})") | ||||
| 
 | ||||
|  | @ -247,7 +252,7 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|         self.info["compression"] = file_info["compression"] | ||||
|         self.tile = [ | ||||
|             ( | ||||
|                 "raw", | ||||
|                 decoder_name, | ||||
|                 (0, 0, file_info["width"], file_info["height"]), | ||||
|                 offset or self.fp.tell(), | ||||
|                 ( | ||||
|  | @ -271,6 +276,57 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|         self._bitmap(offset=offset) | ||||
| 
 | ||||
| 
 | ||||
| class BmpRleDecoder(ImageFile.PyDecoder): | ||||
|     _pulls_fd = True | ||||
| 
 | ||||
|     def decode(self, buffer): | ||||
|         data = bytearray() | ||||
|         x = 0 | ||||
|         while len(data) < self.state.xsize * self.state.ysize: | ||||
|             pixels = self.fd.read(1) | ||||
|             byte = self.fd.read(1) | ||||
|             if not pixels or not byte: | ||||
|                 break | ||||
|             num_pixels = pixels[0] | ||||
|             if num_pixels: | ||||
|                 # encoded mode | ||||
|                 if x + num_pixels > self.state.xsize: | ||||
|                     # Too much data for row | ||||
|                     num_pixels = max(0, self.state.xsize - x) | ||||
|                 data += byte * num_pixels | ||||
|                 x += num_pixels | ||||
|             else: | ||||
|                 if byte[0] == 0: | ||||
|                     # end of line | ||||
|                     while len(data) % self.state.xsize != 0: | ||||
|                         data += b"\x00" | ||||
|                     x = 0 | ||||
|                 elif byte[0] == 1: | ||||
|                     # end of bitmap | ||||
|                     break | ||||
|                 elif byte[0] == 2: | ||||
|                     # delta | ||||
|                     bytes_read = self.fd.read(2) | ||||
|                     if len(bytes_read) < 2: | ||||
|                         break | ||||
|                     right, up = self.fd.read(2) | ||||
|                     data += b"\x00" * (right + up * self.state.xsize) | ||||
|                     x = len(data) % self.state.xsize | ||||
|                 else: | ||||
|                     # absolute mode | ||||
|                     bytes_read = self.fd.read(byte[0]) | ||||
|                     data += bytes_read | ||||
|                     if len(bytes_read) < byte[0]: | ||||
|                         break | ||||
|                     x += byte[0] | ||||
| 
 | ||||
|                     # align to 16-bit word boundary | ||||
|                     if self.fd.tell() % 2 != 0: | ||||
|                         self.fd.seek(1, os.SEEK_CUR) | ||||
|         self.set_as_raw(bytes(data), ("P", 0, self.args[-1])) | ||||
|         return -1, 0 | ||||
| 
 | ||||
| 
 | ||||
| # ============================================================================= | ||||
| # Image plugin for the DIB format (BMP alias) | ||||
| # ============================================================================= | ||||
|  | @ -372,6 +428,8 @@ Image.register_extension(BmpImageFile.format, ".bmp") | |||
| 
 | ||||
| Image.register_mime(BmpImageFile.format, "image/bmp") | ||||
| 
 | ||||
| Image.register_decoder("bmp_rle", BmpRleDecoder) | ||||
| 
 | ||||
| Image.register_open(DibImageFile.format, DibImageFile, _dib_accept) | ||||
| Image.register_save(DibImageFile.format, _dib_save) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user