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
« 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
26import io
27import struct
29from . import Image, ImageFile
30from ._binary import i16le as i16
31from ._binary import o16le as o16
33#
34# read MSP files
37def _accept(prefix):
38 return prefix[:4] in [b"DanM", b"LinS"]
41##
42# Image plugin for Windows MSP images. This plugin supports both
43# uncompressed (Windows 1.0).
46class MspImageFile(ImageFile.ImageFile):
48 format = "MSP"
49 format_description = "Windows Paint"
51 def _open(self):
53 # Header
54 s = self.fp.read(32)
55 if not _accept(s):
56 raise SyntaxError("not an MSP file")
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")
65 self.mode = "1"
66 self._size = i16(s, 4), i16(s, 6)
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)]
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
109 _pulls_fd = True
111 def decode(self, buffer):
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
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
146 except struct.error as e:
147 raise OSError(f"Corrupted MSP file in row {x}") from e
149 self.set_as_raw(img.getvalue(), ("1", 0, 1))
151 return -1, 0
154Image.register_decoder("MSP", MspDecoder)
157#
158# write MSP files (uncompressed only)
161def _save(im, fp, filename):
163 if im.mode != "1":
164 raise OSError(f"cannot write mode {im.mode} as MSP")
166 # create MSP header
167 header = [0] * 16
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
175 checksum = 0
176 for h in header:
177 checksum = checksum ^ h
178 header[12] = checksum # FIXME: is this the right field?
180 # header
181 for h in header:
182 fp.write(o16(h))
184 # image body
185 ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 32, ("1", 0, 1))])
188#
189# registry
191Image.register_open(MspImageFile.format, MspImageFile, _accept)
192Image.register_save(MspImageFile.format, _save)
194Image.register_extension(MspImageFile.format, ".msp")