Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/PIL/MspImagePlugin.py: 21%

77 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1# 

2# The Python Imaging Library. 

3# 

4# MSP file handling 

5# 

6# This is the format used by the Paint program in Windows 1 and 2. 

7# 

8# History: 

9# 95-09-05 fl Created 

10# 97-01-03 fl Read/write MSP images 

11# 17-02-21 es Fixed RLE interpretation 

12# 

13# Copyright (c) Secret Labs AB 1997. 

14# Copyright (c) Fredrik Lundh 1995-97. 

15# Copyright (c) Eric Soroos 2017. 

16# 

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

18# 

19# More info on this format: https://archive.org/details/gg243631 

20# Page 313: 

21# Figure 205. Windows Paint Version 1: "DanM" Format 

22# Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03 

23# 

24# See also: https://www.fileformat.info/format/mspaint/egff.htm 

25 

26import io 

27import struct 

28 

29from . import Image, ImageFile 

30from ._binary import i16le as i16 

31from ._binary import o16le as o16 

32 

33# 

34# read MSP files 

35 

36 

37def _accept(prefix): 

38 return prefix[:4] in [b"DanM", b"LinS"] 

39 

40 

41## 

42# Image plugin for Windows MSP images. This plugin supports both 

43# uncompressed (Windows 1.0). 

44 

45 

46class MspImageFile(ImageFile.ImageFile): 

47 

48 format = "MSP" 

49 format_description = "Windows Paint" 

50 

51 def _open(self): 

52 

53 # Header 

54 s = self.fp.read(32) 

55 if not _accept(s): 

56 raise SyntaxError("not an MSP file") 

57 

58 # Header checksum 

59 checksum = 0 

60 for i in range(0, 32, 2): 

61 checksum = checksum ^ i16(s, i) 

62 if checksum != 0: 

63 raise SyntaxError("bad MSP checksum") 

64 

65 self.mode = "1" 

66 self._size = i16(s, 4), i16(s, 6) 

67 

68 if s[:4] == b"DanM": 

69 self.tile = [("raw", (0, 0) + self.size, 32, ("1", 0, 1))] 

70 else: 

71 self.tile = [("MSP", (0, 0) + self.size, 32, None)] 

72 

73 

74class MspDecoder(ImageFile.PyDecoder): 

75 # The algo for the MSP decoder is from 

76 # https://www.fileformat.info/format/mspaint/egff.htm 

77 # cc-by-attribution -- That page references is taken from the 

78 # Encyclopedia of Graphics File Formats and is licensed by 

79 # O'Reilly under the Creative Common/Attribution license 

80 # 

81 # For RLE encoded files, the 32byte header is followed by a scan 

82 # line map, encoded as one 16bit word of encoded byte length per 

83 # line. 

84 # 

85 # NOTE: the encoded length of the line can be 0. This was not 

86 # handled in the previous version of this encoder, and there's no 

87 # mention of how to handle it in the documentation. From the few 

88 # examples I've seen, I've assumed that it is a fill of the 

89 # background color, in this case, white. 

90 # 

91 # 

92 # Pseudocode of the decoder: 

93 # Read a BYTE value as the RunType 

94 # If the RunType value is zero 

95 # Read next byte as the RunCount 

96 # Read the next byte as the RunValue 

97 # Write the RunValue byte RunCount times 

98 # If the RunType value is non-zero 

99 # Use this value as the RunCount 

100 # Read and write the next RunCount bytes literally 

101 # 

102 # e.g.: 

103 # 0x00 03 ff 05 00 01 02 03 04 

104 # would yield the bytes: 

105 # 0xff ff ff 00 01 02 03 04 

106 # 

107 # which are then interpreted as a bit packed mode '1' image 

108 

109 _pulls_fd = True 

110 

111 def decode(self, buffer): 

112 

113 img = io.BytesIO() 

114 blank_line = bytearray((0xFF,) * ((self.state.xsize + 7) // 8)) 

115 try: 

116 self.fd.seek(32) 

117 rowmap = struct.unpack_from( 

118 f"<{self.state.ysize}H", self.fd.read(self.state.ysize * 2) 

119 ) 

120 except struct.error as e: 

121 raise OSError("Truncated MSP file in row map") from e 

122 

123 for x, rowlen in enumerate(rowmap): 

124 try: 

125 if rowlen == 0: 

126 img.write(blank_line) 

127 continue 

128 row = self.fd.read(rowlen) 

129 if len(row) != rowlen: 

130 raise OSError( 

131 "Truncated MSP file, expected %d bytes on row %s", (rowlen, x) 

132 ) 

133 idx = 0 

134 while idx < rowlen: 

135 runtype = row[idx] 

136 idx += 1 

137 if runtype == 0: 

138 (runcount, runval) = struct.unpack_from("Bc", row, idx) 

139 img.write(runval * runcount) 

140 idx += 2 

141 else: 

142 runcount = runtype 

143 img.write(row[idx : idx + runcount]) 

144 idx += runcount 

145 

146 except struct.error as e: 

147 raise OSError(f"Corrupted MSP file in row {x}") from e 

148 

149 self.set_as_raw(img.getvalue(), ("1", 0, 1)) 

150 

151 return -1, 0 

152 

153 

154Image.register_decoder("MSP", MspDecoder) 

155 

156 

157# 

158# write MSP files (uncompressed only) 

159 

160 

161def _save(im, fp, filename): 

162 

163 if im.mode != "1": 

164 raise OSError(f"cannot write mode {im.mode} as MSP") 

165 

166 # create MSP header 

167 header = [0] * 16 

168 

169 header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1 

170 header[2], header[3] = im.size 

171 header[4], header[5] = 1, 1 

172 header[6], header[7] = 1, 1 

173 header[8], header[9] = im.size 

174 

175 checksum = 0 

176 for h in header: 

177 checksum = checksum ^ h 

178 header[12] = checksum # FIXME: is this the right field? 

179 

180 # header 

181 for h in header: 

182 fp.write(o16(h)) 

183 

184 # image body 

185 ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 32, ("1", 0, 1))]) 

186 

187 

188# 

189# registry 

190 

191Image.register_open(MspImageFile.format, MspImageFile, _accept) 

192Image.register_save(MspImageFile.format, _save) 

193 

194Image.register_extension(MspImageFile.format, ".msp")