Skip to content

Commit 6fa7f3f

Browse files
authored
Merge pull request #2241 from wiredfool/sunrle
SunImagePlugin fixes
2 parents 2e178d7 + 212508b commit 6fa7f3f

File tree

9 files changed

+206
-75
lines changed

9 files changed

+206
-75
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ install:
4141

4242
# libimagequant
4343
- pushd depends && ./install_imagequant.sh && popd
44+
45+
# extra test images
46+
- pushd depends && ./install_extra_test_images.sh && popd
47+
4448

4549
before_script:
4650
# Qt needs a display for some of the tests, and it's only run on the system site packages install

PIL/ImageFile.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,16 @@ def load(self):
150150

151151
if use_mmap:
152152
# try memory mapping
153-
d, e, o, a = self.tile[0]
154-
if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
153+
decoder_name, extents, offset, args = self.tile[0]
154+
if decoder_name == "raw" and len(args) >= 3 and args[0] == self.mode \
155+
and args[0] in Image._MAPMODES:
155156
try:
156157
if hasattr(Image.core, "map"):
157158
# use built-in mapper WIN32 only
158159
self.map = Image.core.map(self.filename)
159-
self.map.seek(o)
160+
self.map.seek(offset)
160161
self.im = self.map.readimage(
161-
self.mode, self.size, a[1], a[2]
162+
self.mode, self.size, args[1], args[2]
162163
)
163164
else:
164165
# use mmap, if possible
@@ -167,7 +168,7 @@ def load(self):
167168
size = os.path.getsize(self.filename)
168169
self.map = mmap.mmap(fp.fileno(), size, access=mmap.ACCESS_READ)
169170
self.im = Image.core.map_buffer(
170-
self.map, self.size, d, e, o, a
171+
self.map, self.size, decoder_name, extents, offset, args
171172
)
172173
readonly = 1
173174
# After trashing self.im, we might need to reload the palette data.

PIL/SunImagePlugin.py

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ class SunImageFile(ImageFile.ImageFile):
3838

3939
def _open(self):
4040

41+
# The Sun Raster file header is 32 bytes in length and has the following format:
42+
43+
# typedef struct _SunRaster
44+
# {
45+
# DWORD MagicNumber; /* Magic (identification) number */
46+
# DWORD Width; /* Width of image in pixels */
47+
# DWORD Height; /* Height of image in pixels */
48+
# DWORD Depth; /* Number of bits per pixel */
49+
# DWORD Length; /* Size of image data in bytes */
50+
# DWORD Type; /* Type of raster file */
51+
# DWORD ColorMapType; /* Type of color map */
52+
# DWORD ColorMapLength; /* Size of the color map in bytes */
53+
# } SUNRASTER;
54+
55+
4156
# HEAD
4257
s = self.fp.read(32)
4358
if i32(s) != 0x59a66a95:
@@ -48,31 +63,71 @@ def _open(self):
4863
self.size = i32(s[4:8]), i32(s[8:12])
4964

5065
depth = i32(s[12:16])
66+
data_length = i32(s[16:20]) # unreliable, ignore.
67+
file_type = i32(s[20:24])
68+
palette_type = i32(s[24:28]) # 0: None, 1: RGB, 2: Raw/arbitrary
69+
palette_length = i32(s[28:32])
70+
5171
if depth == 1:
5272
self.mode, rawmode = "1", "1;I"
73+
elif depth == 4:
74+
self.mode, rawmode = "L", "L;4"
5375
elif depth == 8:
5476
self.mode = rawmode = "L"
5577
elif depth == 24:
56-
self.mode, rawmode = "RGB", "BGR"
78+
if file_type == 3:
79+
self.mode, rawmode = "RGB", "RGB"
80+
else:
81+
self.mode, rawmode = "RGB", "BGR"
82+
elif depth == 32:
83+
if file_type == 3:
84+
self.mode, rawmode = 'RGB', 'RGBX'
85+
else:
86+
self.mode, rawmode = 'RGB', 'BGRX'
5787
else:
58-
raise SyntaxError("unsupported mode")
59-
60-
compression = i32(s[20:24])
61-
62-
if i32(s[24:28]) != 0:
63-
length = i32(s[28:32])
64-
offset = offset + length
65-
self.palette = ImagePalette.raw("RGB;L", self.fp.read(length))
88+
raise SyntaxError("Unsupported Mode/Bit Depth")
89+
90+
if palette_length:
91+
if palette_length > 1024:
92+
raise SyntaxError("Unsupported Color Palette Length")
93+
94+
if palette_type != 1:
95+
raise SyntaxError("Unsupported Palette Type")
96+
97+
offset = offset + palette_length
98+
self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length))
6699
if self.mode == "L":
67-
self.mode = rawmode = "P"
68-
69-
stride = (((self.size[0] * depth + 7) // 8) + 3) & (~3)
70-
71-
if compression == 1:
100+
self.mode = "P"
101+
rawmode = rawmode.replace('L', 'P')
102+
103+
# 16 bit boundaries on stride
104+
stride = ((self.size[0] * depth + 15) // 16) * 2
105+
106+
# file type: Type is the version (or flavor) of the bitmap
107+
# file. The following values are typically found in the Type
108+
# field:
109+
# 0000h Old
110+
# 0001h Standard
111+
# 0002h Byte-encoded
112+
# 0003h RGB format
113+
# 0004h TIFF format
114+
# 0005h IFF format
115+
# FFFFh Experimental
116+
117+
# Old and standard are the same, except for the length tag.
118+
# byte-encoded is run-length-encoded
119+
# RGB looks similar to standard, but RGB byte order
120+
# TIFF and IFF mean that they were converted from T/IFF
121+
# Experimental means that it's something else.
122+
# (http://www.fileformat.info/format/sunraster/egff.htm)
123+
124+
if file_type in (0, 1, 3, 4, 5):
72125
self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))]
73-
elif compression == 2:
126+
elif file_type == 2:
74127
self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)]
75-
128+
else:
129+
raise SyntaxError('Unsupported Sun Raster file type')
130+
76131
#
77132
# registry
78133

Tests/images/sunraster.im1

2.06 KB
Binary file not shown.

Tests/images/sunraster.im1.png

1.73 KB
Loading

Tests/test_file_sun.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
from helper import unittest, PillowTestCase
1+
from helper import unittest, PillowTestCase, hopper
22

33
from PIL import Image, SunImagePlugin
44

5+
import os
6+
7+
EXTRA_DIR = 'Tests/images/sunraster'
58

69
class TestFileSun(PillowTestCase):
710

@@ -16,10 +19,32 @@ def test_sanity(self):
1619
# Assert
1720
self.assertEqual(im.size, (128, 128))
1821

22+
self.assert_image_similar(im, hopper(), 5) # visually verified
23+
1924
invalid_file = "Tests/images/flower.jpg"
2025
self.assertRaises(SyntaxError,
2126
lambda: SunImagePlugin.SunImageFile(invalid_file))
2227

23-
28+
def test_im1(self):
29+
im = Image.open('Tests/images/sunraster.im1')
30+
target = Image.open('Tests/images/sunraster.im1.png')
31+
self.assert_image_equal(im, target)
32+
33+
34+
@unittest.skipIf(not os.path.exists(EXTRA_DIR),
35+
"Extra image files not installed")
36+
def test_others(self):
37+
files = (os.path.join(EXTRA_DIR, f) for f in
38+
os.listdir(EXTRA_DIR) if os.path.splitext(f)[1]
39+
in ('.sun', '.SUN', '.ras'))
40+
for path in files:
41+
with Image.open(path) as im:
42+
im.load()
43+
self.assertIsInstance(im, SunImagePlugin.SunImageFile)
44+
target_path = "%s.png" % os.path.splitext(path)[0]
45+
#im.save(target_file)
46+
with Image.open(target_path) as target:
47+
self.assert_image_equal(im, target)
48+
2449
if __name__ == '__main__':
2550
unittest.main()

appveyor.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ install:
1919
- git clone https://github.com/python-pillow/pillow-depends.git c:\pillow-depends
2020
- xcopy c:\pillow-depends\*.zip c:\pillow\winbuild\
2121
- xcopy c:\pillow-depends\*.tar.gz c:\pillow\winbuild\
22+
- xcopy /s c:\pillow-depends\test_images\* c:\pillow\tests\images
2223
- cd c:\pillow\winbuild\
2324
- c:\python34\python.exe c:\pillow\winbuild\build_dep.py
2425
- c:\pillow\winbuild\build_deps.cmd
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
# install extra test images
3+
4+
if [ ! -f test_images.tar.gz ]; then
5+
wget -O 'test_images.tar.gz' 'https://github.com/python-pillow/pillow-depends/blob/master/test_images.tar.gz?raw=true'
6+
fi
7+
8+
rm -r test_images
9+
tar -xvzf test_images.tar.gz
10+
11+
cp -r test_images/* ../Tests/images

libImaging/SunRleDecode.c

Lines changed: 86 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* decoder for SUN RLE data.
88
*
99
* history:
10-
* 97-01-04 fl Created
10+
* 97-01-04 fl Created
1111
*
1212
* Copyright (c) Fredrik Lundh 1997.
1313
* Copyright (c) Secret Labs AB 1997.
@@ -24,88 +24,122 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
2424
{
2525
int n;
2626
UINT8* ptr;
27+
UINT8 extra_data = 0;
28+
UINT8 extra_bytes = 0;
2729

2830
ptr = buf;
2931

3032
for (;;) {
3133

32-
if (bytes < 1)
33-
return ptr - buf;
34+
if (bytes < 1)
35+
return ptr - buf;
3436

35-
if (ptr[0] == 0x80) {
37+
if (ptr[0] == 0x80) {
3638

37-
if (bytes < 2)
38-
break;
39+
if (bytes < 2)
40+
break;
3941

40-
n = ptr[1];
42+
n = ptr[1];
4143

42-
if (n == 0) {
4344

44-
/* Literal 0x80 (2 bytes) */
45-
n = 1;
45+
if (n == 0) {
4646

47-
state->buffer[state->x] = 0x80;
47+
/* Literal 0x80 (2 bytes) */
48+
n = 1;
4849

49-
ptr += 2;
50-
bytes -= 2;
50+
state->buffer[state->x] = 0x80;
5151

52-
} else {
52+
ptr += 2;
53+
bytes -= 2;
5354

54-
/* Run (3 bytes) */
55-
if (bytes < 3)
56-
break;
55+
} else {
5756

58-
if (state->x + n > state->bytes) {
59-
/* FIXME: is this correct? */
60-
state->errcode = IMAGING_CODEC_OVERRUN;
61-
return -1;
62-
}
57+
/* Run (3 bytes) */
58+
if (bytes < 3)
59+
break;
6360

64-
memset(state->buffer + state->x, ptr[2], n);
61+
/* from (http://www.fileformat.info/format/sunraster/egff.htm)
6562
66-
ptr += 3;
67-
bytes -= 3;
63+
For example, a run of 100 pixels with the value of
64+
0Ah would encode as the values 80h 64h 0Ah. A
65+
single pixel value of 80h would encode as the
66+
values 80h 00h. The four unencoded bytes 12345678h
67+
would be stored in the RLE stream as 12h 34h 56h
68+
78h. 100 pixels, n=100, not 100 pixels, n=99.
6869
69-
}
70+
But Wait! There's More!
71+
(http://www.fileformat.info/format/sunraster/spec/598a59c4fac64c52897585d390d86360/view.htm)
7072
71-
} else {
73+
If the first byte is 0x80, and the second byte is
74+
not zero, the record is three bytes long. The
75+
second byte is a count and the third byte is a
76+
value. Output (count+1) pixels of that value.
7277
73-
/* Literal (1+n bytes block) */
74-
n = ptr[0];
78+
2 specs, same site, but Imagemagick and GIMP seem
79+
to agree on the second one.
80+
*/
81+
n += 1;
7582

76-
if (bytes < 1 + n)
77-
break;
83+
if (state->x + n > state->bytes) {
84+
extra_bytes = n; /* full value */
85+
n = state->bytes - state->x;
86+
extra_bytes -= n;
87+
extra_data = ptr[2];
88+
}
7889

79-
if (state->x + n > state->bytes) {
80-
/* FIXME: is this correct? */
81-
state->errcode = IMAGING_CODEC_OVERRUN;
82-
return -1;
83-
}
90+
memset(state->buffer + state->x, ptr[2], n);
8491

85-
memcpy(state->buffer + state->x, ptr + 1, n);
92+
ptr += 3;
93+
bytes -= 3;
8694

87-
ptr += 1 + n;
88-
bytes -= 1 + n;
95+
}
8996

90-
}
97+
} else {
9198

92-
state->x += n;
99+
/* Literal byte */
100+
n = 1;
93101

94-
if (state->x >= state->bytes) {
102+
state->buffer[state->x] = ptr[0];
95103

96-
/* Got a full line, unpack it */
97-
state->shuffle((UINT8*) im->image[state->y + state->yoff] +
98-
state->xoff * im->pixelsize, state->buffer,
99-
state->xsize);
104+
ptr += 1;
105+
bytes -= 1;
100106

101-
state->x = 0;
107+
}
102108

103-
if (++state->y >= state->ysize) {
104-
/* End of file (errcode = 0) */
105-
return -1;
106-
}
107-
}
109+
for (;;) {
110+
state->x += n;
111+
112+
if (state->x >= state->bytes) {
108113

114+
/* Got a full line, unpack it */
115+
state->shuffle((UINT8*) im->image[state->y + state->yoff] +
116+
state->xoff * im->pixelsize, state->buffer,
117+
state->xsize);
118+
119+
state->x = 0;
120+
121+
if (++state->y >= state->ysize) {
122+
/* End of file (errcode = 0) */
123+
return -1;
124+
}
125+
}
126+
127+
if (extra_bytes == 0) {
128+
break;
129+
}
130+
131+
if (state->x > 0) {
132+
break; // assert
133+
}
134+
135+
if (extra_bytes >= state->bytes) {
136+
n = state->bytes;
137+
} else {
138+
n = extra_bytes;
139+
}
140+
memset(state->buffer + state->x, extra_data, n);
141+
extra_bytes -= n;
142+
}
109143
}
110144

111145
return ptr - buf;

0 commit comments

Comments
 (0)