mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 07:57:27 +03:00 
			
		
		
		
	Use 1 and not P for basic 1bpp BMP
Readapted some original code.
This commit is contained in:
		
							parent
							
								
									56439b728f
								
							
						
					
					
						commit
						934651427d
					
				|  | @ -23,13 +23,12 @@ | |||
| # See the README file for information on usage and redistribution. | ||||
| # | ||||
| 
 | ||||
| 
 | ||||
| __version__ = "0.7" | ||||
| 
 | ||||
| 
 | ||||
| from PIL import Image, ImageFile, ImagePalette, _binary | ||||
| import math | ||||
| 
 | ||||
| 
 | ||||
| i8 = _binary.i8 | ||||
| i16 = _binary.i16le | ||||
| i32 = _binary.i32le | ||||
|  | @ -56,131 +55,147 @@ def _accept(prefix): | |||
|     return prefix[:2] == b"BM" | ||||
| 
 | ||||
| 
 | ||||
| ## | ||||
| #=============================================================================== | ||||
| # Image plugin for the Windows BMP format. | ||||
| 
 | ||||
| #=============================================================================== | ||||
| class BmpImageFile(ImageFile.ImageFile): | ||||
| 
 | ||||
|     format = "BMP" | ||||
|     """ Image plugin for the Windows Bitmap format (BMP) """ | ||||
|      | ||||
|     #--------------------------------------------------------------- Description | ||||
|     format_description = "Windows Bitmap" | ||||
| 
 | ||||
|     def _bitmap(self, header=0, offset=0): | ||||
|         if header: | ||||
|             self.fp.seek(header) | ||||
| 
 | ||||
|         read = self.fp.read | ||||
| 
 | ||||
|         # CORE/INFO | ||||
|         s = read(4) | ||||
|         s = s + ImageFile._safe_read(self.fp, i32(s)-4) | ||||
| 
 | ||||
|         if len(s) == 12: | ||||
| 
 | ||||
|             # OS/2 1.0 CORE | ||||
|             bits = i16(s[10:]) | ||||
|             self.size = i16(s[4:]), i16(s[6:]) | ||||
|             compression = 0 | ||||
|             lutsize = 3 | ||||
|             colors = 0 | ||||
|             direction = -1 | ||||
| 
 | ||||
|         elif len(s) in [40, 64, 108, 124]: | ||||
| 
 | ||||
|             # WIN 3.1 or OS/2 2.0 INFO | ||||
|             bits = i16(s[14:]) | ||||
|             self.size = i32(s[4:]), i32(s[8:]) | ||||
|             compression = i32(s[16:]) | ||||
|             pxperm = (i32(s[24:]), i32(s[28:]))  # Pixels per meter | ||||
|             lutsize = 4 | ||||
|             colors = i32(s[32:]) | ||||
|             direction = -1 | ||||
|             if i8(s[11]) == 0xff: | ||||
|                 # upside-down storage | ||||
|                 self.size = self.size[0], 2**32 - self.size[1] | ||||
|                 direction = 0 | ||||
| 
 | ||||
|             self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), | ||||
|                                          pxperm)) | ||||
| 
 | ||||
|         else: | ||||
|             raise IOError("Unsupported BMP header type (%d)" % len(s)) | ||||
| 
 | ||||
|         if (self.size[0]*self.size[1]) > 2**31: | ||||
|             # Prevent DOS for > 2gb images | ||||
|             raise IOError("Unsupported BMP Size: (%dx%d)" % self.size) | ||||
| 
 | ||||
|         if not colors: | ||||
|             colors = 1 << bits | ||||
| 
 | ||||
|         # MODE | ||||
|         try: | ||||
|             self.mode, rawmode = BIT2MODE[bits] | ||||
|         except KeyError: | ||||
|             raise IOError("Unsupported BMP pixel depth (%d)" % bits) | ||||
| 
 | ||||
|         if compression == 3: | ||||
|             # BI_BITFIELDS compression | ||||
|             mask = i32(read(4)), i32(read(4)), i32(read(4)) | ||||
|             if bits == 32 and mask == (0xff0000, 0x00ff00, 0x0000ff): | ||||
|                 rawmode = "BGRX" | ||||
|             elif bits == 16 and mask == (0x00f800, 0x0007e0, 0x00001f): | ||||
|                 rawmode = "BGR;16" | ||||
|             elif bits == 16 and mask == (0x007c00, 0x0003e0, 0x00001f): | ||||
|                 rawmode = "BGR;15" | ||||
|             else: | ||||
|                 # print bits, map(hex, mask) | ||||
|                 raise IOError("Unsupported BMP bitfields layout") | ||||
|         elif compression != 0: | ||||
|             raise IOError("Unsupported BMP compression (%d)" % compression) | ||||
| 
 | ||||
|         # LUT | ||||
|         if self.mode == "P": | ||||
|             palette = [] | ||||
|             greyscale = 1 | ||||
|             if colors == 2: | ||||
|                 indices = (0, 255) | ||||
|             elif colors > 2**16 or colors <= 0:  # We're reading a i32. | ||||
|                 raise IOError("Unsupported BMP Palette size (%d)" % colors) | ||||
|             else: | ||||
|                 indices = list(range(colors)) | ||||
|             for i in indices: | ||||
|                 rgb = read(lutsize)[:3] | ||||
|                 if rgb != o8(i)*3: | ||||
|                     greyscale = 0 | ||||
|                 palette.append(rgb) | ||||
|             if greyscale: | ||||
|                 if colors == 2: | ||||
|                     self.mode = rawmode = "1" | ||||
|                 else: | ||||
|                     self.mode = rawmode = "L" | ||||
|             else: | ||||
|                 self.mode = "P" | ||||
|                 self.palette = ImagePalette.raw( | ||||
|                     "BGR", b"".join(palette) | ||||
|                     ) | ||||
| 
 | ||||
|         if not offset: | ||||
|             offset = self.fp.tell() | ||||
| 
 | ||||
|         self.tile = [("raw", | ||||
|                      (0, 0) + self.size, | ||||
|                      offset, | ||||
|                      (rawmode, ((self.size[0]*bits+31) >> 3) & (~3), | ||||
|                       direction))] | ||||
| 
 | ||||
|         self.info["compression"] = compression | ||||
|     format = "BMP" | ||||
|     #---------------------------------------------------- BMP Compression values | ||||
|     COMPRESSIONS = {'RAW': 0, 'RLE8': 1, 'RLE4': 2, 'BITFIELDS': 3, 'JPEG': 4, 'PNG': 5} | ||||
|     RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5 | ||||
| 
 | ||||
|     def _open(self): | ||||
| 
 | ||||
|         # HEAD | ||||
|         s = self.fp.read(14) | ||||
|         if s[:2] != b"BM": | ||||
|             raise SyntaxError("Not a BMP file") | ||||
|         offset = i32(s[10:]) | ||||
| 
 | ||||
|         """ Open file, check magic number and read header """ | ||||
|         # read 14 bytes: magic number, filesize, reserved, header final offset | ||||
|         head_data = self.fp.read(14) | ||||
|         # choke if the file does not have the required magic bytes | ||||
|         if head_data[0:2] != b"BM": | ||||
|             raise SyntaxError("Expected a BMP file.") | ||||
|         # read the start position of the BMP image data (u32)  | ||||
|         offset = i32(head_data[10:14]) | ||||
|         # load bitmap information (offset=raster info) | ||||
|         self._bitmap(offset=offset) | ||||
| 
 | ||||
|     def _bitmap(self, header=0, offset=0): | ||||
|         """ Read relevant info about the BMP """ | ||||
|         read, seek = self.fp.read, self.fp.seek | ||||
|         seek(2) | ||||
|         start_data = read(12) | ||||
|         file_info = dict() | ||||
|         file_info['filesize'] = i32(start_data[0:4]) # file size @offset 2 (offsets 4, 12 are reserved for OS/2 Icons) | ||||
|         file_info['image_offset'] = i32(start_data[8:12]) # file size @offset 2 (offsets 4, 12 are reserved for OS/2 Icons) | ||||
|         file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size) | ||||
|         file_info['direction'] = -1 | ||||
|         header_data = ImageFile._safe_read(self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size | ||||
|         #---------------------------------------------------- IBM OS/2 Bitmap v1 | ||||
|         #------- This format has different offsets because of width/height types | ||||
|         if file_info['header_size'] == 12: | ||||
|             file_info['width'] = i16(header_data[0:2]) | ||||
|             file_info['height'] = i16(header_data[2:4]) | ||||
|             file_info['planes'] = i16(header_data[4:6]) | ||||
|             file_info['bits'] = i16(header_data[6:8]) | ||||
|             file_info['compression'] = self.RAW | ||||
|             file_info['palette_padding'] = 3 | ||||
|         #----------------------------------------------- Windows Bitmap v2 to v5 | ||||
|         elif file_info['header_size'] in {40, 64, 108, 124}: # v3, OS/2 v2, v4, v5 | ||||
|             if file_info['header_size'] >= 64: | ||||
|                 file_info['r_mask'] = i32(header_data[36:40]) | ||||
|                 file_info['g_mask'] = i32(header_data[40:44]) | ||||
|                 file_info['b_mask'] = i32(header_data[44:48]) | ||||
|                 file_info['a_mask'] = i32(header_data[48:52]) | ||||
|                 file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask']) | ||||
|                 file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask']) | ||||
|             if file_info['header_size'] >= 40:  # v3 and OS/2 | ||||
|                 file_info['y_flip'] = i8(header_data[7]) == 0xff | ||||
|                 file_info['direction'] = 1 if file_info['y_flip'] else -1 | ||||
|                 file_info['width'] = i32(header_data[0:4]) | ||||
|                 file_info['height'] = i32(header_data[4:8]) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8]) | ||||
|                 file_info['planes'] = i16(header_data[8:10]) | ||||
|                 file_info['bits'] = i16(header_data[10:12]) | ||||
|                 file_info['compression'] = i32(header_data[12:16]) | ||||
|                 file_info['data_size'] = i32(header_data[16:20])  # byte size of pixel data | ||||
|                 file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28]))  | ||||
|                 file_info['colors'] = i32(header_data[28:32]) | ||||
|                 file_info['palette_padding'] = 4 | ||||
|                 #------------------- Special case : header is reported 40, which | ||||
|                 #----------------------- is shorter than real size for bpp >= 16 | ||||
|                 if file_info['header_size'] < 64 and file_info['bits'] >= 16: | ||||
|                     file_info['r_mask'] = i32(read(4)) | ||||
|                     file_info['g_mask'] = i32(read(4)) | ||||
|                     file_info['b_mask'] = i32(read(4)) | ||||
|                     file_info['a_mask'] = i32(read(4)) | ||||
|                     file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask']) | ||||
|                     file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask']) | ||||
|                      | ||||
|                 self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), file_info['pixels_per_meter'])) | ||||
|         else: | ||||
|             raise IOError("BMP images with a {0} byte header are not supported".format(file_info['header_size'])) | ||||
|         self.size = file_info['width'], file_info['height'] | ||||
|         #--------- If color count was not found in the header, compute from bits | ||||
|         file_info['colors'] = file_info['colors'] if file_info.get('colors', 0) else (1 << file_info['bits']) | ||||
|         #--------------------------------- Check abnormal values for DOS attacks | ||||
|         if file_info['width'] * file_info['height'] > 2**31: | ||||
|             raise IOError("BMP images with more than 2 billion pixels are not supported (here {0} pixels)".format(file_info['width'] * file_info['height'])) | ||||
|          | ||||
|         #------------------------ Check bit depth for unusual unsupported values | ||||
|         self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None)) | ||||
|         if self.mode is None: | ||||
|             raise IOError("BMP images with a {0}-bit pixel depth are not supported".format(file_info['bits'])) | ||||
|          | ||||
|         #------------------ Process BMP with Bitfields compression (not palette) | ||||
|         if file_info['compression'] == self.BITFIELDS: | ||||
|             SUPPORTED = {32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000)], 24: [(0xff0000, 0xff00, 0xff)], 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]} | ||||
|             MASK_MODES = {(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (24, (0xff0000, 0xff00, 0xff)): "BGR", (16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"} | ||||
|             if file_info['bits'] in SUPPORTED: | ||||
|                 if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]: | ||||
|                     raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])] | ||||
|                     self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode | ||||
|                 elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]: | ||||
|                     raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])] | ||||
|                 else: | ||||
|                     raise IOError("BMP images with the provided bitfield mask are not supported") | ||||
|             else: | ||||
|                 raise IOError("BMP images with the provided bitfield information are not supported") | ||||
|         elif file_info['compression'] != self.RAW: | ||||
|             raise IOError("BMP files with RLE (1/2), JPEG (4) and PNG (5) compression are not supported") | ||||
|          | ||||
|         #----------------- Once the header is processed, process the palette/LUT | ||||
|         if self.mode == "P":  # Paletted for 1, 4 and 8 bit images | ||||
|             #------------------------------------------------------ 1-bit images | ||||
|             if not (0 < file_info['colors'] <= 65536): | ||||
|                 raise IOError("BMP palette must have between 1 and 256 colors") | ||||
|             else: | ||||
|                 padding = file_info['palette_padding'] | ||||
|                 palette = read(padding * file_info['colors']) | ||||
|                 greyscale = True | ||||
|                 indices = (0, 255) if file_info['colors'] == 2 else list(range(file_info['colors'])) | ||||
|                 #------------------- Check if greyscale and ignore palette if so | ||||
|                 for ind, val in enumerate(indices): | ||||
|                     rgb = palette[ind*padding:ind*padding + 3] | ||||
|                     if rgb != o8(val) * 3: | ||||
|                         greyscale = False | ||||
|                 #--------- If all colors are grey, white or black, ditch palette | ||||
|                 if greyscale: | ||||
|                     self.mode = "1" if file_info['colors'] == 2 else "L" | ||||
|                     raw_mode = self.mode | ||||
|                 else: | ||||
|                     self.mode = "P" | ||||
|                     self.palette = ImagePalette.raw("BGRX" if padding == 4 else "BGR", b"".join(palette)) | ||||
|          | ||||
|         #------------------------------ Finally set the tile data for the plugin | ||||
|         self.info['compression'] = file_info['compression'] | ||||
|         self.tile = [('raw', (0, 0, file_info['width'], file_info['height']), file_info['image_offset'], | ||||
|                       (raw_mode, ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), file_info['direction'])   | ||||
|                       )] | ||||
| 
 | ||||
| 
 | ||||
| #=============================================================================== | ||||
| # Image plugin for the DIB format (BMP alias) | ||||
| #=============================================================================== | ||||
| class DibImageFile(BmpImageFile): | ||||
| 
 | ||||
|     format = "DIB" | ||||
|  | @ -189,6 +204,8 @@ class DibImageFile(BmpImageFile): | |||
|     def _open(self): | ||||
|         self._bitmap() | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| # | ||||
| # -------------------------------------------------------------------- | ||||
| # Write BMP file | ||||
|  | @ -198,6 +215,7 @@ SAVE = { | |||
|     "L": ("L", 8, 256), | ||||
|     "P": ("P", 8, 256), | ||||
|     "RGB": ("BGR", 24, 0), | ||||
|     "RGBA": ("BGRA", 32, 0), | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user