Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pillow-10.4.0-py3.8-linux-x86_64.egg/PIL/BmpImagePlugin.py: 18%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

226 statements  

1# 

2# The Python Imaging Library. 

3# $Id$ 

4# 

5# BMP file handler 

6# 

7# Windows (and OS/2) native bitmap storage format. 

8# 

9# history: 

10# 1995-09-01 fl Created 

11# 1996-04-30 fl Added save 

12# 1997-08-27 fl Fixed save of 1-bit images 

13# 1998-03-06 fl Load P images as L where possible 

14# 1998-07-03 fl Load P images as 1 where possible 

15# 1998-12-29 fl Handle small palettes 

16# 2002-12-30 fl Fixed load of 1-bit palette images 

17# 2003-04-21 fl Fixed load of 1-bit monochrome images 

18# 2003-04-23 fl Added limited support for BI_BITFIELDS compression 

19# 

20# Copyright (c) 1997-2003 by Secret Labs AB 

21# Copyright (c) 1995-2003 by Fredrik Lundh 

22# 

23# See the README file for information on usage and redistribution. 

24# 

25from __future__ import annotations 

26 

27import os 

28from typing import IO 

29 

30from . import Image, ImageFile, ImagePalette 

31from ._binary import i16le as i16 

32from ._binary import i32le as i32 

33from ._binary import o8 

34from ._binary import o16le as o16 

35from ._binary import o32le as o32 

36 

37# 

38# -------------------------------------------------------------------- 

39# Read BMP file 

40 

41BIT2MODE = { 

42 # bits => mode, rawmode 

43 1: ("P", "P;1"), 

44 4: ("P", "P;4"), 

45 8: ("P", "P"), 

46 16: ("RGB", "BGR;15"), 

47 24: ("RGB", "BGR"), 

48 32: ("RGB", "BGRX"), 

49} 

50 

51 

52def _accept(prefix: bytes) -> bool: 

53 return prefix[:2] == b"BM" 

54 

55 

56def _dib_accept(prefix: bytes) -> bool: 

57 return i32(prefix) in [12, 40, 52, 56, 64, 108, 124] 

58 

59 

60# ============================================================================= 

61# Image plugin for the Windows BMP format. 

62# ============================================================================= 

63class BmpImageFile(ImageFile.ImageFile): 

64 """Image plugin for the Windows Bitmap format (BMP)""" 

65 

66 # ------------------------------------------------------------- Description 

67 format_description = "Windows Bitmap" 

68 format = "BMP" 

69 

70 # -------------------------------------------------- BMP Compression values 

71 COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5} 

72 for k, v in COMPRESSIONS.items(): 

73 vars()[k] = v 

74 

75 def _bitmap(self, header=0, offset=0): 

76 """Read relevant info about the BMP""" 

77 read, seek = self.fp.read, self.fp.seek 

78 if header: 

79 seek(header) 

80 # read bmp header size @offset 14 (this is part of the header size) 

81 file_info = {"header_size": i32(read(4)), "direction": -1} 

82 

83 # -------------------- If requested, read header at a specific position 

84 # read the rest of the bmp header, without its size 

85 header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4) 

86 

87 # ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1 

88 # ----- This format has different offsets because of width/height types 

89 # 12: BITMAPCOREHEADER/OS21XBITMAPHEADER 

90 if file_info["header_size"] == 12: 

91 file_info["width"] = i16(header_data, 0) 

92 file_info["height"] = i16(header_data, 2) 

93 file_info["planes"] = i16(header_data, 4) 

94 file_info["bits"] = i16(header_data, 6) 

95 file_info["compression"] = self.RAW 

96 file_info["palette_padding"] = 3 

97 

98 # --------------------------------------------- Windows Bitmap v3 to v5 

99 # 40: BITMAPINFOHEADER 

100 # 52: BITMAPV2HEADER 

101 # 56: BITMAPV3HEADER 

102 # 64: BITMAPCOREHEADER2/OS22XBITMAPHEADER 

103 # 108: BITMAPV4HEADER 

104 # 124: BITMAPV5HEADER 

105 elif file_info["header_size"] in (40, 52, 56, 64, 108, 124): 

106 file_info["y_flip"] = header_data[7] == 0xFF 

107 file_info["direction"] = 1 if file_info["y_flip"] else -1 

108 file_info["width"] = i32(header_data, 0) 

109 file_info["height"] = ( 

110 i32(header_data, 4) 

111 if not file_info["y_flip"] 

112 else 2**32 - i32(header_data, 4) 

113 ) 

114 file_info["planes"] = i16(header_data, 8) 

115 file_info["bits"] = i16(header_data, 10) 

116 file_info["compression"] = i32(header_data, 12) 

117 # byte size of pixel data 

118 file_info["data_size"] = i32(header_data, 16) 

119 file_info["pixels_per_meter"] = ( 

120 i32(header_data, 20), 

121 i32(header_data, 24), 

122 ) 

123 file_info["colors"] = i32(header_data, 28) 

124 file_info["palette_padding"] = 4 

125 self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"]) 

126 if file_info["compression"] == self.BITFIELDS: 

127 masks = ["r_mask", "g_mask", "b_mask"] 

128 if len(header_data) >= 48: 

129 if len(header_data) >= 52: 

130 masks.append("a_mask") 

131 else: 

132 file_info["a_mask"] = 0x0 

133 for idx, mask in enumerate(masks): 

134 file_info[mask] = i32(header_data, 36 + idx * 4) 

135 else: 

136 # 40 byte headers only have the three components in the 

137 # bitfields masks, ref: 

138 # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx 

139 # See also 

140 # https://github.com/python-pillow/Pillow/issues/1293 

141 # There is a 4th component in the RGBQuad, in the alpha 

142 # location, but it is listed as a reserved component, 

143 # and it is not generally an alpha channel 

144 file_info["a_mask"] = 0x0 

145 for mask in masks: 

146 file_info[mask] = i32(read(4)) 

147 file_info["rgb_mask"] = ( 

148 file_info["r_mask"], 

149 file_info["g_mask"], 

150 file_info["b_mask"], 

151 ) 

152 file_info["rgba_mask"] = ( 

153 file_info["r_mask"], 

154 file_info["g_mask"], 

155 file_info["b_mask"], 

156 file_info["a_mask"], 

157 ) 

158 else: 

159 msg = f"Unsupported BMP header type ({file_info['header_size']})" 

160 raise OSError(msg) 

161 

162 # ------------------ Special case : header is reported 40, which 

163 # ---------------------- is shorter than real size for bpp >= 16 

164 self._size = file_info["width"], file_info["height"] 

165 

166 # ------- If color count was not found in the header, compute from bits 

167 file_info["colors"] = ( 

168 file_info["colors"] 

169 if file_info.get("colors", 0) 

170 else (1 << file_info["bits"]) 

171 ) 

172 if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8: 

173 offset += 4 * file_info["colors"] 

174 

175 # ---------------------- Check bit depth for unusual unsupported values 

176 self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) 

177 if self.mode is None: 

178 msg = f"Unsupported BMP pixel depth ({file_info['bits']})" 

179 raise OSError(msg) 

180 

181 # ---------------- Process BMP with Bitfields compression (not palette) 

182 decoder_name = "raw" 

183 if file_info["compression"] == self.BITFIELDS: 

184 SUPPORTED = { 

185 32: [ 

186 (0xFF0000, 0xFF00, 0xFF, 0x0), 

187 (0xFF000000, 0xFF0000, 0xFF00, 0x0), 

188 (0xFF000000, 0xFF00, 0xFF, 0x0), 

189 (0xFF000000, 0xFF0000, 0xFF00, 0xFF), 

190 (0xFF, 0xFF00, 0xFF0000, 0xFF000000), 

191 (0xFF0000, 0xFF00, 0xFF, 0xFF000000), 

192 (0xFF000000, 0xFF00, 0xFF, 0xFF0000), 

193 (0x0, 0x0, 0x0, 0x0), 

194 ], 

195 24: [(0xFF0000, 0xFF00, 0xFF)], 

196 16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)], 

197 } 

198 MASK_MODES = { 

199 (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX", 

200 (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR", 

201 (32, (0xFF000000, 0xFF00, 0xFF, 0x0)): "BGXR", 

202 (32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR", 

203 (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA", 

204 (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA", 

205 (32, (0xFF000000, 0xFF00, 0xFF, 0xFF0000)): "BGAR", 

206 (32, (0x0, 0x0, 0x0, 0x0)): "BGRA", 

207 (24, (0xFF0000, 0xFF00, 0xFF)): "BGR", 

208 (16, (0xF800, 0x7E0, 0x1F)): "BGR;16", 

209 (16, (0x7C00, 0x3E0, 0x1F)): "BGR;15", 

210 } 

211 if file_info["bits"] in SUPPORTED: 

212 if ( 

213 file_info["bits"] == 32 

214 and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]] 

215 ): 

216 raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])] 

217 self._mode = "RGBA" if "A" in raw_mode else self.mode 

218 elif ( 

219 file_info["bits"] in (24, 16) 

220 and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]] 

221 ): 

222 raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])] 

223 else: 

224 msg = "Unsupported BMP bitfields layout" 

225 raise OSError(msg) 

226 else: 

227 msg = "Unsupported BMP bitfields layout" 

228 raise OSError(msg) 

229 elif file_info["compression"] == self.RAW: 

230 if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset 

231 raw_mode, self._mode = "BGRA", "RGBA" 

232 elif file_info["compression"] in (self.RLE8, self.RLE4): 

233 decoder_name = "bmp_rle" 

234 else: 

235 msg = f"Unsupported BMP compression ({file_info['compression']})" 

236 raise OSError(msg) 

237 

238 # --------------- Once the header is processed, process the palette/LUT 

239 if self.mode == "P": # Paletted for 1, 4 and 8 bit images 

240 # ---------------------------------------------------- 1-bit images 

241 if not (0 < file_info["colors"] <= 65536): 

242 msg = f"Unsupported BMP Palette size ({file_info['colors']})" 

243 raise OSError(msg) 

244 else: 

245 padding = file_info["palette_padding"] 

246 palette = read(padding * file_info["colors"]) 

247 grayscale = True 

248 indices = ( 

249 (0, 255) 

250 if file_info["colors"] == 2 

251 else list(range(file_info["colors"])) 

252 ) 

253 

254 # ----------------- Check if grayscale and ignore palette if so 

255 for ind, val in enumerate(indices): 

256 rgb = palette[ind * padding : ind * padding + 3] 

257 if rgb != o8(val) * 3: 

258 grayscale = False 

259 

260 # ------- If all colors are gray, white or black, ditch palette 

261 if grayscale: 

262 self._mode = "1" if file_info["colors"] == 2 else "L" 

263 raw_mode = self.mode 

264 else: 

265 self._mode = "P" 

266 self.palette = ImagePalette.raw( 

267 "BGRX" if padding == 4 else "BGR", palette 

268 ) 

269 

270 # ---------------------------- Finally set the tile data for the plugin 

271 self.info["compression"] = file_info["compression"] 

272 args = [raw_mode] 

273 if decoder_name == "bmp_rle": 

274 args.append(file_info["compression"] == self.RLE4) 

275 else: 

276 args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3)) 

277 args.append(file_info["direction"]) 

278 self.tile = [ 

279 ( 

280 decoder_name, 

281 (0, 0, file_info["width"], file_info["height"]), 

282 offset or self.fp.tell(), 

283 tuple(args), 

284 ) 

285 ] 

286 

287 def _open(self) -> None: 

288 """Open file, check magic number and read header""" 

289 # read 14 bytes: magic number, filesize, reserved, header final offset 

290 head_data = self.fp.read(14) 

291 # choke if the file does not have the required magic bytes 

292 if not _accept(head_data): 

293 msg = "Not a BMP file" 

294 raise SyntaxError(msg) 

295 # read the start position of the BMP image data (u32) 

296 offset = i32(head_data, 10) 

297 # load bitmap information (offset=raster info) 

298 self._bitmap(offset=offset) 

299 

300 

301class BmpRleDecoder(ImageFile.PyDecoder): 

302 _pulls_fd = True 

303 

304 def decode(self, buffer: bytes) -> tuple[int, int]: 

305 assert self.fd is not None 

306 rle4 = self.args[1] 

307 data = bytearray() 

308 x = 0 

309 dest_length = self.state.xsize * self.state.ysize 

310 while len(data) < dest_length: 

311 pixels = self.fd.read(1) 

312 byte = self.fd.read(1) 

313 if not pixels or not byte: 

314 break 

315 num_pixels = pixels[0] 

316 if num_pixels: 

317 # encoded mode 

318 if x + num_pixels > self.state.xsize: 

319 # Too much data for row 

320 num_pixels = max(0, self.state.xsize - x) 

321 if rle4: 

322 first_pixel = o8(byte[0] >> 4) 

323 second_pixel = o8(byte[0] & 0x0F) 

324 for index in range(num_pixels): 

325 if index % 2 == 0: 

326 data += first_pixel 

327 else: 

328 data += second_pixel 

329 else: 

330 data += byte * num_pixels 

331 x += num_pixels 

332 else: 

333 if byte[0] == 0: 

334 # end of line 

335 while len(data) % self.state.xsize != 0: 

336 data += b"\x00" 

337 x = 0 

338 elif byte[0] == 1: 

339 # end of bitmap 

340 break 

341 elif byte[0] == 2: 

342 # delta 

343 bytes_read = self.fd.read(2) 

344 if len(bytes_read) < 2: 

345 break 

346 right, up = self.fd.read(2) 

347 data += b"\x00" * (right + up * self.state.xsize) 

348 x = len(data) % self.state.xsize 

349 else: 

350 # absolute mode 

351 if rle4: 

352 # 2 pixels per byte 

353 byte_count = byte[0] // 2 

354 bytes_read = self.fd.read(byte_count) 

355 for byte_read in bytes_read: 

356 data += o8(byte_read >> 4) 

357 data += o8(byte_read & 0x0F) 

358 else: 

359 byte_count = byte[0] 

360 bytes_read = self.fd.read(byte_count) 

361 data += bytes_read 

362 if len(bytes_read) < byte_count: 

363 break 

364 x += byte[0] 

365 

366 # align to 16-bit word boundary 

367 if self.fd.tell() % 2 != 0: 

368 self.fd.seek(1, os.SEEK_CUR) 

369 rawmode = "L" if self.mode == "L" else "P" 

370 self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1])) 

371 return -1, 0 

372 

373 

374# ============================================================================= 

375# Image plugin for the DIB format (BMP alias) 

376# ============================================================================= 

377class DibImageFile(BmpImageFile): 

378 format = "DIB" 

379 format_description = "Windows Bitmap" 

380 

381 def _open(self) -> None: 

382 self._bitmap() 

383 

384 

385# 

386# -------------------------------------------------------------------- 

387# Write BMP file 

388 

389 

390SAVE = { 

391 "1": ("1", 1, 2), 

392 "L": ("L", 8, 256), 

393 "P": ("P", 8, 256), 

394 "RGB": ("BGR", 24, 0), 

395 "RGBA": ("BGRA", 32, 0), 

396} 

397 

398 

399def _dib_save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: 

400 _save(im, fp, filename, False) 

401 

402 

403def _save( 

404 im: Image.Image, fp: IO[bytes], filename: str | bytes, bitmap_header: bool = True 

405) -> None: 

406 try: 

407 rawmode, bits, colors = SAVE[im.mode] 

408 except KeyError as e: 

409 msg = f"cannot write mode {im.mode} as BMP" 

410 raise OSError(msg) from e 

411 

412 info = im.encoderinfo 

413 

414 dpi = info.get("dpi", (96, 96)) 

415 

416 # 1 meter == 39.3701 inches 

417 ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi) 

418 

419 stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3) 

420 header = 40 # or 64 for OS/2 version 2 

421 image = stride * im.size[1] 

422 

423 if im.mode == "1": 

424 palette = b"".join(o8(i) * 4 for i in (0, 255)) 

425 elif im.mode == "L": 

426 palette = b"".join(o8(i) * 4 for i in range(256)) 

427 elif im.mode == "P": 

428 palette = im.im.getpalette("RGB", "BGRX") 

429 colors = len(palette) // 4 

430 else: 

431 palette = None 

432 

433 # bitmap header 

434 if bitmap_header: 

435 offset = 14 + header + colors * 4 

436 file_size = offset + image 

437 if file_size > 2**32 - 1: 

438 msg = "File size is too large for the BMP format" 

439 raise ValueError(msg) 

440 fp.write( 

441 b"BM" # file type (magic) 

442 + o32(file_size) # file size 

443 + o32(0) # reserved 

444 + o32(offset) # image data offset 

445 ) 

446 

447 # bitmap info header 

448 fp.write( 

449 o32(header) # info header size 

450 + o32(im.size[0]) # width 

451 + o32(im.size[1]) # height 

452 + o16(1) # planes 

453 + o16(bits) # depth 

454 + o32(0) # compression (0=uncompressed) 

455 + o32(image) # size of bitmap 

456 + o32(ppm[0]) # resolution 

457 + o32(ppm[1]) # resolution 

458 + o32(colors) # colors used 

459 + o32(colors) # colors important 

460 ) 

461 

462 fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) 

463 

464 if palette: 

465 fp.write(palette) 

466 

467 ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))]) 

468 

469 

470# 

471# -------------------------------------------------------------------- 

472# Registry 

473 

474 

475Image.register_open(BmpImageFile.format, BmpImageFile, _accept) 

476Image.register_save(BmpImageFile.format, _save) 

477 

478Image.register_extension(BmpImageFile.format, ".bmp") 

479 

480Image.register_mime(BmpImageFile.format, "image/bmp") 

481 

482Image.register_decoder("bmp_rle", BmpRleDecoder) 

483 

484Image.register_open(DibImageFile.format, DibImageFile, _dib_accept) 

485Image.register_save(DibImageFile.format, _dib_save) 

486 

487Image.register_extension(DibImageFile.format, ".dib") 

488 

489Image.register_mime(DibImageFile.format, "image/bmp")