diff --git a/PIL/MspImagePlugin.py b/PIL/MspImagePlugin.py index 11d31880c..60a4f01f8 100644 --- a/PIL/MspImagePlugin.py +++ b/PIL/MspImagePlugin.py @@ -1,6 +1,5 @@ # # The Python Imaging Library. -# $Id$ # # MSP file handling # @@ -9,9 +8,11 @@ # History: # 95-09-05 fl Created # 97-01-03 fl Read/write MSP images +# 17-02-21 es Fixed RLE interpretation # # Copyright (c) Secret Labs AB 1997. # Copyright (c) Fredrik Lundh 1995-97. +# Copyright (c) Eric Soroos 2017. # # See the README file for information on usage and redistribution. # @@ -20,9 +21,11 @@ # Figure 205. Windows Paint Version 1: "DanM" Format # Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03 # +# See also: http://www.fileformat.info/format/mspaint/egff.htm from . import Image, ImageFile from ._binary import i16le as i16, o16le as o16 +import struct, io __version__ = "0.1" @@ -64,7 +67,91 @@ class MspImageFile(ImageFile.ImageFile): if s[:4] == b"DanM": self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))] else: - self.tile = [("msp", (0, 0)+self.size, 32+2*self.size[1], None)] + 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 + # 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 + # + # 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): + + img = io.BytesIO() + blank_line = bytearray((0xff,)*((self.state.xsize+7)//8)) + try: + last_pos = 0 + 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: + runtype = row[idx] + 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 + + except struct.error: + raise IOError("Corrupted MSP file in row %d" %x) + + self.set_as_raw(img.getvalue(), ("1", 0, 1)) + + return 0,0 + + +Image.register_decoder('MSP', MspDecoder) + # # write MSP files (uncompressed only) diff --git a/_imaging.c b/_imaging.c index aa2e04778..ddb56c7ab 100644 --- a/_imaging.c +++ b/_imaging.c @@ -3290,7 +3290,6 @@ extern PyObject* PyImaging_JpegDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_MspDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_PackbitsDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_PcdDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_PcxDecoderNew(PyObject* self, PyObject* args); @@ -3368,7 +3367,6 @@ static PyMethodDef functions[] = { {"libtiff_decoder", (PyCFunction)PyImaging_LibTiffDecoderNew, 1}, {"libtiff_encoder", (PyCFunction)PyImaging_LibTiffEncoderNew, 1}, #endif - {"msp_decoder", (PyCFunction)PyImaging_MspDecoderNew, 1}, {"packbits_decoder", (PyCFunction)PyImaging_PackbitsDecoderNew, 1}, {"pcd_decoder", (PyCFunction)PyImaging_PcdDecoderNew, 1}, {"pcx_decoder", (PyCFunction)PyImaging_PcxDecoderNew, 1}, diff --git a/decode.c b/decode.c index 91de1075e..d7fe02fae 100644 --- a/decode.c +++ b/decode.c @@ -557,27 +557,6 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args) #endif -/* -------------------------------------------------------------------- */ -/* MSP */ -/* -------------------------------------------------------------------- */ - -PyObject* -PyImaging_MspDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; - - decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) - return NULL; - - if (get_unpacker(decoder, "1", "1") < 0) - return NULL; - - decoder->decode = ImagingMspDecode; - - return (PyObject*) decoder; -} - /* -------------------------------------------------------------------- */ /* PackBits */ diff --git a/libImaging/MspDecode.c b/libImaging/MspDecode.c deleted file mode 100644 index b611098d8..000000000 --- a/libImaging/MspDecode.c +++ /dev/null @@ -1,91 +0,0 @@ -/* - * The Python Imaging Library. - * $Id$ - * - * decoder for MSP version 2 data. - * - * history: - * 97-01-03 fl Created - * - * Copyright (c) Fredrik Lundh 1997. - * Copyright (c) Secret Labs AB 1997. - * - * See the README file for information on usage and redistribution. - */ - - -#include "Imaging.h" - - -int -ImagingMspDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ - int n; - UINT8* ptr; - - ptr = buf; - - for (;;) { - - if (bytes < 1) - return ptr - buf; - - if (ptr[0] == 0) { - - /* Run (3 bytes block) */ - if (bytes < 3) - break; - - n = ptr[1]; - - if (state->x + n > state->bytes) { - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } - - memset(state->buffer + state->x, ptr[2], n); - - ptr += 3; - bytes -= 3; - - } else { - - /* Literal (1+n bytes block) */ - n = ptr[0]; - - if (bytes < 1 + n) - break; - - if (state->x + n > state->bytes) { - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } - - memcpy(state->buffer + state->x, ptr + 1, n); - - ptr += 1 + n; - bytes -= 1 + n; - - } - - state->x += n; - - if (state->x >= state->bytes) { - - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->buffer, - state->xsize); - - state->x = 0; - - if (++state->y >= state->ysize) { - /* End of file (errcode = 0) */ - return -1; - } - } - - } - - return ptr - buf; -} diff --git a/setup.py b/setup.py index 1dc146d26..4c20810ef 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ _LIB_IMAGING = ( "Draw", "Effects", "EpsEncode", "File", "Fill", "Filter", "FliDecode", "Geometry", "GetBBox", "GifDecode", "GifEncode", "HexDecode", "Histo", "JpegDecode", "JpegEncode", "LzwDecode", "Matrix", "ModeFilter", - "MspDecode", "Negative", "Offset", "Pack", "PackDecode", "Palette", "Paste", + "Negative", "Offset", "Pack", "PackDecode", "Palette", "Paste", "Quant", "QuantOctree", "QuantHash", "QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point", "RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode", "TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask",