Pillow/PIL/MspImagePlugin.py

193 lines
5.4 KiB
Python
Raw Normal View History

2010-07-31 06:52:47 +04:00
#
# The Python Imaging Library.
#
# MSP file handling
#
# This is the format used by the Paint program in Windows 1 and 2.
#
# History:
# 95-09-05 fl Created
# 97-01-03 fl Read/write MSP images
2017-02-21 16:09:32 +03:00
# 17-02-21 es Fixed RLE interpretation
2010-07-31 06:52:47 +04:00
#
# Copyright (c) Secret Labs AB 1997.
# Copyright (c) Fredrik Lundh 1995-97.
2017-02-21 16:09:32 +03:00
# Copyright (c) Eric Soroos 2017.
2010-07-31 06:52:47 +04:00
#
# See the README file for information on usage and redistribution.
#
2017-01-29 23:28:41 +03:00
# More info on this format: https://archive.org/details/gg243631
# Page 313:
# Figure 205. Windows Paint Version 1: "DanM" Format
# Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03
#
2017-02-21 16:09:32 +03:00
# See also: http://www.fileformat.info/format/mspaint/egff.htm
2010-07-31 06:52:47 +04:00
from . import Image, ImageFile
2017-02-21 17:36:26 +03:00
from ._binary import i16le as i16, o16le as o16, i8
2017-04-20 14:14:23 +03:00
import struct
import io
2010-07-31 06:52:47 +04:00
2015-08-25 15:27:18 +03:00
__version__ = "0.1"
2010-07-31 06:52:47 +04:00
#
# read MSP files
2014-08-26 17:47:10 +04:00
2010-07-31 06:52:47 +04:00
def _accept(prefix):
return prefix[:4] in [b"DanM", b"LinS"]
2010-07-31 06:52:47 +04:00
2014-08-26 17:47:10 +04:00
2010-07-31 06:52:47 +04:00
##
# Image plugin for Windows MSP images. This plugin supports both
# uncompressed (Windows 1.0).
class MspImageFile(ImageFile.ImageFile):
format = "MSP"
format_description = "Windows Paint"
def _open(self):
# Header
s = self.fp.read(32)
if s[:4] not in [b"DanM", b"LinS"]:
raise SyntaxError("not an MSP file")
2010-07-31 06:52:47 +04:00
# Header checksum
2015-04-02 11:45:24 +03:00
checksum = 0
2010-07-31 06:52:47 +04:00
for i in range(0, 32, 2):
2015-04-02 11:45:24 +03:00
checksum = checksum ^ i16(s[i:i+2])
if checksum != 0:
raise SyntaxError("bad MSP checksum")
2010-07-31 06:52:47 +04:00
self.mode = "1"
self.size = i16(s[4:]), i16(s[6:])
if s[:4] == b"DanM":
2014-08-26 17:47:10 +04:00
self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
2010-07-31 06:52:47 +04:00
else:
2017-02-21 16:09:32 +03:00
self.tile = [("MSP", (0, 0)+self.size, 32, None)]
class MspDecoder(ImageFile.PyDecoder):
# The algo for the MSP decoder is from
# http://www.fileformat.info/format/mspaint/egff.htm
# cc-by-attribution -- That page references is taken from the
# Encyclopedia of Graphics File Formats and is licensed by
# O'Reilly under the Creative Common/Attribution license
#
# For RLE encoded files, the 32byte header is followed by a scan
# line map, encoded as one 16bit word of encoded byte length per
# line.
#
# NOTE: the encoded length of the line can be 0. This was not
# handled in the previous version of this encoder, and there's no
# mention of how to handle it in the documentation. From the few
# examples I've seen, I've assumed that it is a fill of the
# background color, in this case, white.
#
#
# Pseudocode of the decoder:
# Read a BYTE value as the RunType
2017-02-23 13:20:25 +03:00
# If the RunType value is zero
# Read next byte as the RunCount
# Read the next byte as the RunValue
# Write the RunValue byte RunCount times
# If the RunType value is non-zero
# Use this value as the RunCount
# Read and write the next RunCount bytes literally
2017-04-20 14:14:23 +03:00
#
2017-02-21 16:09:32 +03:00
# e.g.:
# 0x00 03 ff 05 00 01 02 03 04
# would yield the bytes:
# 0xff ff ff 00 01 02 03 04
#
# which are then interpreted as a bit packed mode '1' image
_pulls_fd = True
def decode(self, buffer):
2017-04-20 14:14:23 +03:00
2017-02-21 16:09:32 +03:00
img = io.BytesIO()
blank_line = bytearray((0xff,)*((self.state.xsize+7)//8))
try:
self.fd.seek(32)
rowmap = struct.unpack_from("<%dH" % (self.state.ysize),
self.fd.read(self.state.ysize*2))
except struct.error:
raise IOError("Truncated MSP file in row map")
for x, rowlen in enumerate(rowmap):
try:
if rowlen == 0:
img.write(blank_line)
continue
row = self.fd.read(rowlen)
if len(row) != rowlen:
raise IOError("Truncated MSP file, expected %d bytes on row %s",
(rowlen, x))
idx = 0
while idx < rowlen:
2017-02-21 17:36:26 +03:00
runtype = i8(row[idx])
2017-02-21 16:09:32 +03:00
idx += 1
if runtype == 0:
(runcount, runval) = struct.unpack("Bc", row[idx:idx+2])
img.write(runval * runcount)
idx += 2
else:
runcount = runtype
img.write(row[idx:idx+runcount])
idx += runcount
2017-04-20 14:14:23 +03:00
2017-02-21 16:09:32 +03:00
except struct.error:
2017-04-20 14:14:23 +03:00
raise IOError("Corrupted MSP file in row %d" % x)
2017-02-21 16:09:32 +03:00
self.set_as_raw(img.getvalue(), ("1", 0, 1))
2017-04-20 14:14:23 +03:00
return 0, 0
2017-02-21 16:09:32 +03:00
Image.register_decoder('MSP', MspDecoder)
2017-04-20 14:14:23 +03:00
2010-07-31 06:52:47 +04:00
#
# write MSP files (uncompressed only)
2014-08-26 17:47:10 +04:00
2010-07-31 06:52:47 +04:00
def _save(im, fp, filename):
if im.mode != "1":
raise IOError("cannot write mode %s as MSP" % im.mode)
2010-07-31 06:52:47 +04:00
# create MSP header
header = [0] * 16
2014-08-26 17:47:10 +04:00
header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1
2010-07-31 06:52:47 +04:00
header[2], header[3] = im.size
header[4], header[5] = 1, 1
header[6], header[7] = 1, 1
header[8], header[9] = im.size
2015-04-02 11:45:24 +03:00
checksum = 0
2010-07-31 06:52:47 +04:00
for h in header:
2015-04-02 11:45:24 +03:00
checksum = checksum ^ h
header[12] = checksum # FIXME: is this the right field?
2010-07-31 06:52:47 +04:00
# header
for h in header:
fp.write(o16(h))
# image body
2014-08-26 17:47:10 +04:00
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
2010-07-31 06:52:47 +04:00
2017-01-29 23:28:41 +03:00
2010-07-31 06:52:47 +04:00
#
# registry
Image.register_open(MspImageFile.format, MspImageFile, _accept)
Image.register_save(MspImageFile.format, _save)
2010-07-31 06:52:47 +04:00
Image.register_extension(MspImageFile.format, ".msp")