Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/XpmImagePlugin.py: 20%

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

99 statements  

1# 

2# The Python Imaging Library. 

3# $Id$ 

4# 

5# XPM File handling 

6# 

7# History: 

8# 1996-12-29 fl Created 

9# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7) 

10# 

11# Copyright (c) Secret Labs AB 1997-2001. 

12# Copyright (c) Fredrik Lundh 1996-2001. 

13# 

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

15# 

16from __future__ import annotations 

17 

18__lazy_modules__ = {"PIL._binary"} 

19 

20import re 

21 

22from . import Image, ImageFile, ImagePalette 

23from ._binary import o8 

24 

25# XPM header 

26xpm_head = re.compile(rb'"([0-9]*)\s+([0-9]*)\s+([0-9]*)\s+([0-9]*)') 

27 

28 

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

30 return prefix.startswith(b"/* XPM */") 

31 

32 

33## 

34# Image plugin for X11 pixel maps. 

35 

36 

37class XpmImageFile(ImageFile.ImageFile): 

38 format = "XPM" 

39 format_description = "X11 Pixel Map" 

40 

41 def _open(self) -> None: 

42 assert self.fp is not None 

43 if not _accept(self.fp.read(9)): 

44 msg = "not an XPM file" 

45 raise SyntaxError(msg) 

46 

47 # skip forward to next string 

48 while True: 

49 line = self.fp.readline() 

50 if not line: 

51 msg = "broken XPM file" 

52 raise SyntaxError(msg) 

53 m = xpm_head.match(line) 

54 if m: 

55 break 

56 

57 self._size = int(m.group(1)), int(m.group(2)) 

58 

59 palette_length = int(m.group(3)) 

60 bpp = int(m.group(4)) 

61 

62 # 

63 # load palette description 

64 

65 palette = {} 

66 transparent_key = None 

67 

68 for _ in range(palette_length): 

69 line = self.fp.readline().rstrip() 

70 

71 c = line[1 : bpp + 1] 

72 s = line[bpp + 1 : -2].split() 

73 

74 for i in range(0, len(s), 2): 

75 if s[i] == b"c": 

76 # process colour key 

77 rgb = s[i + 1] 

78 if rgb == b"None": 

79 transparent_key = c 

80 palette[c] = b"\0\0\0" 

81 elif rgb.startswith(b"#"): 

82 rgb_int = int(rgb[1:], 16) 

83 palette[c] = ( 

84 o8((rgb_int >> 16) & 255) 

85 + o8((rgb_int >> 8) & 255) 

86 + o8(rgb_int & 255) 

87 ) 

88 else: 

89 # unknown colour 

90 msg = "cannot read this XPM file" 

91 raise ValueError(msg) 

92 break 

93 

94 else: 

95 # missing colour key 

96 msg = "cannot read this XPM file" 

97 raise ValueError(msg) 

98 

99 args: tuple[int, dict[bytes, bytes] | tuple[bytes, ...]] 

100 if palette_length > 256: 

101 if transparent_key is not None: 

102 self._mode = "RGBA" 

103 palette = { 

104 c: rgb + (b"\x00" if c == transparent_key else b"\xff") 

105 for c, rgb in palette.items() 

106 } 

107 else: 

108 self._mode = "RGB" 

109 args = (bpp, palette) 

110 else: 

111 self._mode = "P" 

112 self.palette = ImagePalette.raw("RGB", b"".join(palette.values())) 

113 palette_keys = tuple(palette.keys()) 

114 args = (bpp, palette_keys) 

115 if transparent_key is not None: 

116 self.info["transparency"] = palette_keys.index(transparent_key) 

117 

118 self.tile = [ImageFile._Tile("xpm", None, self.fp.tell(), args)] 

119 

120 def load_read(self, read_bytes: int) -> bytes: 

121 # 

122 # load all image data in one chunk 

123 

124 xsize, ysize = self.size 

125 

126 assert self.fp is not None 

127 s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)] 

128 

129 return b"".join(s) 

130 

131 

132class XpmDecoder(ImageFile.PyDecoder): 

133 _pulls_fd = True 

134 

135 def decode(self, buffer: Image.DecoderInput) -> tuple[int, int]: 

136 assert self.fd is not None 

137 

138 data = bytearray() 

139 bpp, palette = self.args 

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

141 if self.mode in {"RGB", "RGBA"}: 

142 dest_length *= len(self.mode) 

143 pixel_header = False 

144 while len(data) < dest_length: 

145 line = self.fd.readline() 

146 if not line: 

147 break 

148 if line.rstrip() == b"/* pixels */" and not pixel_header: 

149 pixel_header = True 

150 continue 

151 line = b'"'.join(line.split(b'"')[1:-1]) 

152 for i in range(0, len(line), bpp): 

153 key = line[i : i + bpp] 

154 try: 

155 if self.mode in {"RGB", "RGBA"}: 

156 data += palette[key] 

157 else: 

158 data += o8(palette.index(key)) 

159 except (KeyError, ValueError): 

160 msg = "unknown color" 

161 raise ValueError(msg) 

162 self.set_as_raw(bytes(data)) 

163 return -1, 0 

164 

165 

166# 

167# Registry 

168 

169 

170Image.register_open(XpmImageFile.format, XpmImageFile, _accept) 

171Image.register_decoder("xpm", XpmDecoder) 

172 

173Image.register_extension(XpmImageFile.format, ".xpm") 

174 

175Image.register_mime(XpmImageFile.format, "image/xpm")