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

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

91 statements  

1# 

2# The Python Imaging Library. 

3# $Id$ 

4# 

5# PCX file handling 

6# 

7# This format was originally used by ZSoft's popular PaintBrush 

8# program for the IBM PC. It is also supported by many MS-DOS and 

9# Windows applications, including the Windows PaintBrush program in 

10# Windows 3. 

11# 

12# history: 

13# 1995-09-01 fl Created 

14# 1996-05-20 fl Fixed RGB support 

15# 1997-01-03 fl Fixed 2-bit and 4-bit support 

16# 1999-02-03 fl Fixed 8-bit support (broken in 1.0b1) 

17# 1999-02-07 fl Added write support 

18# 2002-06-09 fl Made 2-bit and 4-bit support a bit more robust 

19# 2002-07-30 fl Seek from to current position, not beginning of file 

20# 2003-06-03 fl Extract DPI settings (info["dpi"]) 

21# 

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

23# Copyright (c) 1995-2003 by Fredrik Lundh. 

24# 

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

26# 

27from __future__ import annotations 

28 

29import io 

30import logging 

31from typing import IO 

32 

33from . import Image, ImageFile, ImagePalette 

34from ._binary import i16le as i16 

35from ._binary import o8 

36from ._binary import o16le as o16 

37 

38logger = logging.getLogger(__name__) 

39 

40 

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

42 return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5] 

43 

44 

45## 

46# Image plugin for Paintbrush images. 

47 

48 

49class PcxImageFile(ImageFile.ImageFile): 

50 format = "PCX" 

51 format_description = "Paintbrush" 

52 

53 def _open(self) -> None: 

54 # header 

55 assert self.fp is not None 

56 

57 s = self.fp.read(68) 

58 if not _accept(s): 

59 msg = "not a PCX file" 

60 raise SyntaxError(msg) 

61 

62 # image 

63 bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1 

64 if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]: 

65 msg = "bad PCX image size" 

66 raise SyntaxError(msg) 

67 logger.debug("BBox: %s %s %s %s", *bbox) 

68 

69 offset = self.fp.tell() + 60 

70 

71 # format 

72 version = s[1] 

73 bits = s[3] 

74 planes = s[65] 

75 provided_stride = i16(s, 66) 

76 logger.debug( 

77 "PCX version %s, bits %s, planes %s, stride %s", 

78 version, 

79 bits, 

80 planes, 

81 provided_stride, 

82 ) 

83 

84 self.info["dpi"] = i16(s, 12), i16(s, 14) 

85 

86 if bits == 1 and planes == 1: 

87 mode = rawmode = "1" 

88 

89 elif bits == 1 and planes in (2, 4): 

90 mode = "P" 

91 rawmode = f"P;{planes}L" 

92 self.palette = ImagePalette.raw("RGB", s[16:64]) 

93 

94 elif version == 5 and bits == 8 and planes == 1: 

95 mode = rawmode = "L" 

96 # FIXME: hey, this doesn't work with the incremental loader !!! 

97 self.fp.seek(-769, io.SEEK_END) 

98 s = self.fp.read(769) 

99 if len(s) == 769 and s[0] == 12: 

100 # check if the palette is linear grayscale 

101 for i in range(256): 

102 if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3: 

103 mode = rawmode = "P" 

104 break 

105 if mode == "P": 

106 self.palette = ImagePalette.raw("RGB", s[1:]) 

107 

108 elif version == 5 and bits == 8 and planes == 3: 

109 mode = "RGB" 

110 rawmode = "RGB;L" 

111 

112 else: 

113 msg = "unknown PCX mode" 

114 raise OSError(msg) 

115 

116 self._mode = mode 

117 self._size = bbox[2] - bbox[0], bbox[3] - bbox[1] 

118 

119 # Don't trust the passed in stride. 

120 # Calculate the approximate position for ourselves. 

121 # CVE-2020-35653 

122 stride = (self._size[0] * bits + 7) // 8 

123 

124 # While the specification states that this must be even, 

125 # not all images follow this 

126 if provided_stride != stride: 

127 stride += stride % 2 

128 

129 bbox = (0, 0) + self.size 

130 logger.debug("size: %sx%s", *self.size) 

131 

132 self.tile = [ImageFile._Tile("pcx", bbox, offset, (rawmode, planes * stride))] 

133 

134 

135# -------------------------------------------------------------------- 

136# save PCX files 

137 

138 

139SAVE = { 

140 # mode: (version, bits, planes, raw mode) 

141 "1": (2, 1, 1, "1"), 

142 "L": (5, 8, 1, "L"), 

143 "P": (5, 8, 1, "P"), 

144 "RGB": (5, 8, 3, "RGB;L"), 

145} 

146 

147 

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

149 try: 

150 version, bits, planes, rawmode = SAVE[im.mode] 

151 except KeyError as e: 

152 msg = f"Cannot save {im.mode} images as PCX" 

153 raise ValueError(msg) from e 

154 

155 # bytes per plane 

156 stride = (im.size[0] * bits + 7) // 8 

157 # stride should be even 

158 stride += stride % 2 

159 # Stride needs to be kept in sync with the PcxEncode.c version. 

160 # Ideally it should be passed in in the state, but the bytes value 

161 # gets overwritten. 

162 

163 logger.debug( 

164 "PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d", 

165 im.size[0], 

166 bits, 

167 stride, 

168 ) 

169 

170 # under windows, we could determine the current screen size with 

171 # "Image.core.display_mode()[1]", but I think that's overkill... 

172 

173 screen = im.size 

174 

175 dpi = 100, 100 

176 

177 # PCX header 

178 fp.write( 

179 o8(10) 

180 + o8(version) 

181 + o8(1) 

182 + o8(bits) 

183 + o16(0) 

184 + o16(0) 

185 + o16(im.size[0] - 1) 

186 + o16(im.size[1] - 1) 

187 + o16(dpi[0]) 

188 + o16(dpi[1]) 

189 + b"\0" * 24 

190 + b"\xff" * 24 

191 + b"\0" 

192 + o8(planes) 

193 + o16(stride) 

194 + o16(1) 

195 + o16(screen[0]) 

196 + o16(screen[1]) 

197 + b"\0" * 54 

198 ) 

199 

200 assert fp.tell() == 128 

201 

202 ImageFile._save( 

203 im, fp, [ImageFile._Tile("pcx", (0, 0) + im.size, 0, (rawmode, bits * planes))] 

204 ) 

205 

206 if im.mode == "P": 

207 # colour palette 

208 fp.write(o8(12)) 

209 palette = im.im.getpalette("RGB", "RGB") 

210 palette += b"\x00" * (768 - len(palette)) 

211 fp.write(palette) # 768 bytes 

212 elif im.mode == "L": 

213 # grayscale palette 

214 fp.write(o8(12)) 

215 for i in range(256): 

216 fp.write(o8(i) * 3) 

217 

218 

219# -------------------------------------------------------------------- 

220# registry 

221 

222 

223Image.register_open(PcxImageFile.format, PcxImageFile, _accept) 

224Image.register_save(PcxImageFile.format, _save) 

225 

226Image.register_extension(PcxImageFile.format, ".pcx") 

227 

228Image.register_mime(PcxImageFile.format, "image/x-pcx")