mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-06-18 12:03:12 +03:00
Merge branch 'main' into context_manager
This commit is contained in:
commit
3abce858c3
|
@ -1 +1 @@
|
||||||
cibuildwheel==2.23.1
|
cibuildwheel==2.23.2
|
||||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -70,7 +70,7 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: Quansight-Labs/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
allow-prereleases: true
|
allow-prereleases: true
|
||||||
|
|
8
.github/workflows/wheels-dependencies.sh
vendored
8
.github/workflows/wheels-dependencies.sh
vendored
|
@ -38,11 +38,15 @@ ARCHIVE_SDIR=pillow-depends-main
|
||||||
|
|
||||||
# Package versions for fresh source builds
|
# Package versions for fresh source builds
|
||||||
FREETYPE_VERSION=2.13.3
|
FREETYPE_VERSION=2.13.3
|
||||||
HARFBUZZ_VERSION=10.4.0
|
HARFBUZZ_VERSION=11.0.0
|
||||||
LIBPNG_VERSION=1.6.47
|
LIBPNG_VERSION=1.6.47
|
||||||
JPEGTURBO_VERSION=3.1.0
|
JPEGTURBO_VERSION=3.1.0
|
||||||
OPENJPEG_VERSION=2.5.3
|
OPENJPEG_VERSION=2.5.3
|
||||||
XZ_VERSION=5.6.4
|
if [[ $MB_ML_VER == 2014 ]]; then
|
||||||
|
XZ_VERSION=5.6.4
|
||||||
|
else
|
||||||
|
XZ_VERSION=5.8.0
|
||||||
|
fi
|
||||||
TIFF_VERSION=4.7.0
|
TIFF_VERSION=4.7.0
|
||||||
LCMS2_VERSION=2.17
|
LCMS2_VERSION=2.17
|
||||||
ZLIB_NG_VERSION=2.2.4
|
ZLIB_NG_VERSION=2.2.4
|
||||||
|
|
BIN
Tests/images/drawing_emf_ref_72_144.png
Normal file
BIN
Tests/images/drawing_emf_ref_72_144.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 984 B |
260
Tests/images/full_gimp_palette.gpl
Normal file
260
Tests/images/full_gimp_palette.gpl
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
GIMP Palette
|
||||||
|
Name: fullpalette
|
||||||
|
Columns: 4
|
||||||
|
#
|
||||||
|
0 0 0 Index 0
|
||||||
|
1 1 1 Index 1
|
||||||
|
2 2 2 Index 2
|
||||||
|
3 3 3 Index 3
|
||||||
|
4 4 4 Index 4
|
||||||
|
5 5 5 Index 5
|
||||||
|
6 6 6 Index 6
|
||||||
|
7 7 7 Index 7
|
||||||
|
8 8 8 Index 8
|
||||||
|
9 9 9 Index 9
|
||||||
|
10 10 10 Index 10
|
||||||
|
11 11 11 Index 11
|
||||||
|
12 12 12 Index 12
|
||||||
|
13 13 13 Index 13
|
||||||
|
14 14 14 Index 14
|
||||||
|
15 15 15 Index 15
|
||||||
|
16 16 16 Index 16
|
||||||
|
17 17 17 Index 17
|
||||||
|
18 18 18 Index 18
|
||||||
|
19 19 19 Index 19
|
||||||
|
20 20 20 Index 20
|
||||||
|
21 21 21 Index 21
|
||||||
|
22 22 22 Index 22
|
||||||
|
23 23 23 Index 23
|
||||||
|
24 24 24 Index 24
|
||||||
|
25 25 25 Index 25
|
||||||
|
26 26 26 Index 26
|
||||||
|
27 27 27 Index 27
|
||||||
|
28 28 28 Index 28
|
||||||
|
29 29 29 Index 29
|
||||||
|
30 30 30 Index 30
|
||||||
|
31 31 31 Index 31
|
||||||
|
32 32 32 Index 32
|
||||||
|
33 33 33 Index 33
|
||||||
|
34 34 34 Index 34
|
||||||
|
35 35 35 Index 35
|
||||||
|
36 36 36 Index 36
|
||||||
|
37 37 37 Index 37
|
||||||
|
38 38 38 Index 38
|
||||||
|
39 39 39 Index 39
|
||||||
|
40 40 40 Index 40
|
||||||
|
41 41 41 Index 41
|
||||||
|
42 42 42 Index 42
|
||||||
|
43 43 43 Index 43
|
||||||
|
44 44 44 Index 44
|
||||||
|
45 45 45 Index 45
|
||||||
|
46 46 46 Index 46
|
||||||
|
47 47 47 Index 47
|
||||||
|
48 48 48 Index 48
|
||||||
|
49 49 49 Index 49
|
||||||
|
50 50 50 Index 50
|
||||||
|
51 51 51 Index 51
|
||||||
|
52 52 52 Index 52
|
||||||
|
53 53 53 Index 53
|
||||||
|
54 54 54 Index 54
|
||||||
|
55 55 55 Index 55
|
||||||
|
56 56 56 Index 56
|
||||||
|
57 57 57 Index 57
|
||||||
|
58 58 58 Index 58
|
||||||
|
59 59 59 Index 59
|
||||||
|
60 60 60 Index 60
|
||||||
|
61 61 61 Index 61
|
||||||
|
62 62 62 Index 62
|
||||||
|
63 63 63 Index 63
|
||||||
|
64 64 64 Index 64
|
||||||
|
65 65 65 Index 65
|
||||||
|
66 66 66 Index 66
|
||||||
|
67 67 67 Index 67
|
||||||
|
68 68 68 Index 68
|
||||||
|
69 69 69 Index 69
|
||||||
|
70 70 70 Index 70
|
||||||
|
71 71 71 Index 71
|
||||||
|
72 72 72 Index 72
|
||||||
|
73 73 73 Index 73
|
||||||
|
74 74 74 Index 74
|
||||||
|
75 75 75 Index 75
|
||||||
|
76 76 76 Index 76
|
||||||
|
77 77 77 Index 77
|
||||||
|
78 78 78 Index 78
|
||||||
|
79 79 79 Index 79
|
||||||
|
80 80 80 Index 80
|
||||||
|
81 81 81 Index 81
|
||||||
|
82 82 82 Index 82
|
||||||
|
83 83 83 Index 83
|
||||||
|
84 84 84 Index 84
|
||||||
|
85 85 85 Index 85
|
||||||
|
86 86 86 Index 86
|
||||||
|
87 87 87 Index 87
|
||||||
|
88 88 88 Index 88
|
||||||
|
89 89 89 Index 89
|
||||||
|
90 90 90 Index 90
|
||||||
|
91 91 91 Index 91
|
||||||
|
92 92 92 Index 92
|
||||||
|
93 93 93 Index 93
|
||||||
|
94 94 94 Index 94
|
||||||
|
95 95 95 Index 95
|
||||||
|
96 96 96 Index 96
|
||||||
|
97 97 97 Index 97
|
||||||
|
98 98 98 Index 98
|
||||||
|
99 99 99 Index 99
|
||||||
|
100 100 100 Index 100
|
||||||
|
101 101 101 Index 101
|
||||||
|
102 102 102 Index 102
|
||||||
|
103 103 103 Index 103
|
||||||
|
104 104 104 Index 104
|
||||||
|
105 105 105 Index 105
|
||||||
|
106 106 106 Index 106
|
||||||
|
107 107 107 Index 107
|
||||||
|
108 108 108 Index 108
|
||||||
|
109 109 109 Index 109
|
||||||
|
110 110 110 Index 110
|
||||||
|
111 111 111 Index 111
|
||||||
|
112 112 112 Index 112
|
||||||
|
113 113 113 Index 113
|
||||||
|
114 114 114 Index 114
|
||||||
|
115 115 115 Index 115
|
||||||
|
116 116 116 Index 116
|
||||||
|
117 117 117 Index 117
|
||||||
|
118 118 118 Index 118
|
||||||
|
119 119 119 Index 119
|
||||||
|
120 120 120 Index 120
|
||||||
|
121 121 121 Index 121
|
||||||
|
122 122 122 Index 122
|
||||||
|
123 123 123 Index 123
|
||||||
|
124 124 124 Index 124
|
||||||
|
125 125 125 Index 125
|
||||||
|
126 126 126 Index 126
|
||||||
|
127 127 127 Index 127
|
||||||
|
128 128 128 Index 128
|
||||||
|
129 129 129 Index 129
|
||||||
|
130 130 130 Index 130
|
||||||
|
131 131 131 Index 131
|
||||||
|
132 132 132 Index 132
|
||||||
|
133 133 133 Index 133
|
||||||
|
134 134 134 Index 134
|
||||||
|
135 135 135 Index 135
|
||||||
|
136 136 136 Index 136
|
||||||
|
137 137 137 Index 137
|
||||||
|
138 138 138 Index 138
|
||||||
|
139 139 139 Index 139
|
||||||
|
140 140 140 Index 140
|
||||||
|
141 141 141 Index 141
|
||||||
|
142 142 142 Index 142
|
||||||
|
143 143 143 Index 143
|
||||||
|
144 144 144 Index 144
|
||||||
|
145 145 145 Index 145
|
||||||
|
146 146 146 Index 146
|
||||||
|
147 147 147 Index 147
|
||||||
|
148 148 148 Index 148
|
||||||
|
149 149 149 Index 149
|
||||||
|
150 150 150 Index 150
|
||||||
|
151 151 151 Index 151
|
||||||
|
152 152 152 Index 152
|
||||||
|
153 153 153 Index 153
|
||||||
|
154 154 154 Index 154
|
||||||
|
155 155 155 Index 155
|
||||||
|
156 156 156 Index 156
|
||||||
|
157 157 157 Index 157
|
||||||
|
158 158 158 Index 158
|
||||||
|
159 159 159 Index 159
|
||||||
|
160 160 160 Index 160
|
||||||
|
161 161 161 Index 161
|
||||||
|
162 162 162 Index 162
|
||||||
|
163 163 163 Index 163
|
||||||
|
164 164 164 Index 164
|
||||||
|
165 165 165 Index 165
|
||||||
|
166 166 166 Index 166
|
||||||
|
167 167 167 Index 167
|
||||||
|
168 168 168 Index 168
|
||||||
|
169 169 169 Index 169
|
||||||
|
170 170 170 Index 170
|
||||||
|
171 171 171 Index 171
|
||||||
|
172 172 172 Index 172
|
||||||
|
173 173 173 Index 173
|
||||||
|
174 174 174 Index 174
|
||||||
|
175 175 175 Index 175
|
||||||
|
176 176 176 Index 176
|
||||||
|
177 177 177 Index 177
|
||||||
|
178 178 178 Index 178
|
||||||
|
179 179 179 Index 179
|
||||||
|
180 180 180 Index 180
|
||||||
|
181 181 181 Index 181
|
||||||
|
182 182 182 Index 182
|
||||||
|
183 183 183 Index 183
|
||||||
|
184 184 184 Index 184
|
||||||
|
185 185 185 Index 185
|
||||||
|
186 186 186 Index 186
|
||||||
|
187 187 187 Index 187
|
||||||
|
188 188 188 Index 188
|
||||||
|
189 189 189 Index 189
|
||||||
|
190 190 190 Index 190
|
||||||
|
191 191 191 Index 191
|
||||||
|
192 192 192 Index 192
|
||||||
|
193 193 193 Index 193
|
||||||
|
194 194 194 Index 194
|
||||||
|
195 195 195 Index 195
|
||||||
|
196 196 196 Index 196
|
||||||
|
197 197 197 Index 197
|
||||||
|
198 198 198 Index 198
|
||||||
|
199 199 199 Index 199
|
||||||
|
200 200 200 Index 200
|
||||||
|
201 201 201 Index 201
|
||||||
|
202 202 202 Index 202
|
||||||
|
203 203 203 Index 203
|
||||||
|
204 204 204 Index 204
|
||||||
|
205 205 205 Index 205
|
||||||
|
206 206 206 Index 206
|
||||||
|
207 207 207 Index 207
|
||||||
|
208 208 208 Index 208
|
||||||
|
209 209 209 Index 209
|
||||||
|
210 210 210 Index 210
|
||||||
|
211 211 211 Index 211
|
||||||
|
212 212 212 Index 212
|
||||||
|
213 213 213 Index 213
|
||||||
|
214 214 214 Index 214
|
||||||
|
215 215 215 Index 215
|
||||||
|
216 216 216 Index 216
|
||||||
|
217 217 217 Index 217
|
||||||
|
218 218 218 Index 218
|
||||||
|
219 219 219 Index 219
|
||||||
|
220 220 220 Index 220
|
||||||
|
221 221 221 Index 221
|
||||||
|
222 222 222 Index 222
|
||||||
|
223 223 223 Index 223
|
||||||
|
224 224 224 Index 224
|
||||||
|
225 225 225 Index 225
|
||||||
|
226 226 226 Index 226
|
||||||
|
227 227 227 Index 227
|
||||||
|
228 228 228 Index 228
|
||||||
|
229 229 229 Index 229
|
||||||
|
230 230 230 Index 230
|
||||||
|
231 231 231 Index 231
|
||||||
|
232 232 232 Index 232
|
||||||
|
233 233 233 Index 233
|
||||||
|
234 234 234 Index 234
|
||||||
|
235 235 235 Index 235
|
||||||
|
236 236 236 Index 236
|
||||||
|
237 237 237 Index 237
|
||||||
|
238 238 238 Index 238
|
||||||
|
239 239 239 Index 239
|
||||||
|
240 240 240 Index 240
|
||||||
|
241 241 241 Index 241
|
||||||
|
242 242 242 Index 242
|
||||||
|
243 243 243 Index 243
|
||||||
|
244 244 244 Index 244
|
||||||
|
245 245 245 Index 245
|
||||||
|
246 246 246 Index 246
|
||||||
|
247 247 247 Index 247
|
||||||
|
248 248 248 Index 248
|
||||||
|
249 249 249 Index 249
|
||||||
|
250 250 250 Index 250
|
||||||
|
251 251 251 Index 251
|
||||||
|
252 252 252 Index 252
|
||||||
|
253 253 253 Index 253
|
||||||
|
254 254 254 Index 254
|
||||||
|
255 255 255 Index 255
|
Binary file not shown.
Before Width: | Height: | Size: 533 B After Width: | Height: | Size: 533 B |
|
@ -15,25 +15,19 @@ from .helper import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_sanity(tmp_path: Path) -> None:
|
@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB"))
|
||||||
def roundtrip(im: Image.Image) -> None:
|
def test_sanity(mode: str, tmp_path: Path) -> None:
|
||||||
outfile = tmp_path / "temp.bmp"
|
outfile = tmp_path / "temp.bmp"
|
||||||
|
|
||||||
im.save(outfile, "BMP")
|
im = hopper(mode)
|
||||||
|
im.save(outfile, "BMP")
|
||||||
|
|
||||||
with Image.open(outfile) as reloaded:
|
with Image.open(outfile) as reloaded:
|
||||||
reloaded.load()
|
reloaded.load()
|
||||||
assert im.mode == reloaded.mode
|
assert im.mode == reloaded.mode
|
||||||
assert im.size == reloaded.size
|
assert im.size == reloaded.size
|
||||||
assert reloaded.format == "BMP"
|
assert reloaded.format == "BMP"
|
||||||
assert reloaded.get_format_mimetype() == "image/bmp"
|
assert reloaded.get_format_mimetype() == "image/bmp"
|
||||||
|
|
||||||
roundtrip(hopper())
|
|
||||||
|
|
||||||
roundtrip(hopper("1"))
|
|
||||||
roundtrip(hopper("L"))
|
|
||||||
roundtrip(hopper("P"))
|
|
||||||
roundtrip(hopper("RGB"))
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file() -> None:
|
def test_invalid_file() -> None:
|
||||||
|
@ -230,3 +224,13 @@ def test_offset() -> None:
|
||||||
# to exclude the palette size from the pixel data offset
|
# to exclude the palette size from the pixel data offset
|
||||||
with Image.open("Tests/images/pal8_offset.bmp") as im:
|
with Image.open("Tests/images/pal8_offset.bmp") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/bmp/g/pal8.bmp")
|
assert_image_equal_tofile(im, "Tests/images/bmp/g/pal8.bmp")
|
||||||
|
|
||||||
|
|
||||||
|
def test_use_raw_alpha(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
with Image.open("Tests/images/bmp/g/rgb32.bmp") as im:
|
||||||
|
assert im.info["compression"] == BmpImagePlugin.BmpImageFile.COMPRESSIONS["RAW"]
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
|
||||||
|
monkeypatch.setattr(BmpImagePlugin, "USE_RAW_ALPHA", True)
|
||||||
|
with Image.open("Tests/images/bmp/g/rgb32.bmp") as im:
|
||||||
|
assert im.mode == "RGBA"
|
||||||
|
|
|
@ -1167,6 +1167,12 @@ def test_append_images(tmp_path: Path) -> None:
|
||||||
assert isinstance(reread, GifImagePlugin.GifImageFile)
|
assert isinstance(reread, GifImagePlugin.GifImageFile)
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
|
# Test append_images without save_all
|
||||||
|
im.copy().save(out, append_images=ims)
|
||||||
|
|
||||||
|
with Image.open(out) as reread:
|
||||||
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
# Tests appending using a generator
|
# Tests appending using a generator
|
||||||
def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
|
def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
|
||||||
yield from ims
|
yield from ims
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL.GimpPaletteFile import GimpPaletteFile
|
from PIL.GimpPaletteFile import GimpPaletteFile
|
||||||
|
@ -14,17 +16,20 @@ def test_sanity() -> None:
|
||||||
GimpPaletteFile(fp)
|
GimpPaletteFile(fp)
|
||||||
|
|
||||||
with open("Tests/images/bad_palette_file.gpl", "rb") as fp:
|
with open("Tests/images/bad_palette_file.gpl", "rb") as fp:
|
||||||
with pytest.raises(SyntaxError):
|
with pytest.raises(SyntaxError, match="bad palette file"):
|
||||||
GimpPaletteFile(fp)
|
GimpPaletteFile(fp)
|
||||||
|
|
||||||
with open("Tests/images/bad_palette_entry.gpl", "rb") as fp:
|
with open("Tests/images/bad_palette_entry.gpl", "rb") as fp:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="bad palette entry"):
|
||||||
GimpPaletteFile(fp)
|
GimpPaletteFile(fp)
|
||||||
|
|
||||||
|
|
||||||
def test_get_palette() -> None:
|
@pytest.mark.parametrize(
|
||||||
|
"filename, size", (("custom_gimp_palette.gpl", 8), ("full_gimp_palette.gpl", 256))
|
||||||
|
)
|
||||||
|
def test_get_palette(filename: str, size: int) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
with open("Tests/images/custom_gimp_palette.gpl", "rb") as fp:
|
with open("Tests/images/" + filename, "rb") as fp:
|
||||||
palette_file = GimpPaletteFile(fp)
|
palette_file = GimpPaletteFile(fp)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
@ -32,4 +37,36 @@ def test_get_palette() -> None:
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert mode == "RGB"
|
assert mode == "RGB"
|
||||||
assert len(palette) / 3 == 8
|
assert len(palette) / 3 == size
|
||||||
|
|
||||||
|
|
||||||
|
def test_frombytes() -> None:
|
||||||
|
# Test that __init__ stops reading after 260 lines
|
||||||
|
with open("Tests/images/custom_gimp_palette.gpl", "rb") as fp:
|
||||||
|
custom_data = fp.read()
|
||||||
|
custom_data += b"#\n" * 300 + b" 0 0 0 Index 12"
|
||||||
|
b = BytesIO(custom_data)
|
||||||
|
palette = GimpPaletteFile(b)
|
||||||
|
assert len(palette.palette) / 3 == 8
|
||||||
|
|
||||||
|
# Test that __init__ only reads 256 entries
|
||||||
|
with open("Tests/images/full_gimp_palette.gpl", "rb") as fp:
|
||||||
|
full_data = fp.read()
|
||||||
|
data = full_data.replace(b"#\n", b"") + b" 0 0 0 Index 256"
|
||||||
|
b = BytesIO(data)
|
||||||
|
palette = GimpPaletteFile(b)
|
||||||
|
assert len(palette.palette) / 3 == 256
|
||||||
|
|
||||||
|
# Test that frombytes() can read beyond that
|
||||||
|
palette = GimpPaletteFile.frombytes(data)
|
||||||
|
assert len(palette.palette) / 3 == 257
|
||||||
|
|
||||||
|
# Test that __init__ raises an error if a comment is too long
|
||||||
|
data = full_data[:-1] + b"a" * 100
|
||||||
|
b = BytesIO(data)
|
||||||
|
with pytest.raises(SyntaxError, match="bad palette file"):
|
||||||
|
palette = GimpPaletteFile(b)
|
||||||
|
|
||||||
|
# Test that frombytes() can read the data regardless
|
||||||
|
palette = GimpPaletteFile.frombytes(data)
|
||||||
|
assert len(palette.palette) / 3 == 256
|
||||||
|
|
|
@ -43,7 +43,7 @@ def test_save() -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
dummy_fp = BytesIO()
|
dummy_fp = BytesIO()
|
||||||
dummy_filename = "dummy.filename"
|
dummy_filename = "dummy.h5"
|
||||||
|
|
||||||
# Act / Assert: stub cannot save without an implemented handler
|
# Act / Assert: stub cannot save without an implemented handler
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
|
|
|
@ -1055,6 +1055,17 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
with Image.open("Tests/images/old-style-jpeg-compression.tif") as im:
|
with Image.open("Tests/images/old-style-jpeg-compression.tif") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
||||||
|
|
||||||
|
def test_old_style_jpeg_orientation(self) -> None:
|
||||||
|
with open("Tests/images/old-style-jpeg-compression.tif", "rb") as fp:
|
||||||
|
data = fp.read()
|
||||||
|
|
||||||
|
# Set EXIF Orientation to 2
|
||||||
|
data = data[:102] + b"\x02" + data[103:]
|
||||||
|
|
||||||
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
||||||
|
|
||||||
def test_open_missing_samplesperpixel(self) -> None:
|
def test_open_missing_samplesperpixel(self) -> None:
|
||||||
with Image.open(
|
with Image.open(
|
||||||
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
|
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
|
||||||
|
|
|
@ -43,6 +43,11 @@ def roundtrip(tmp_path: Path, mode: str) -> None:
|
||||||
|
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
converted = open_with_magick(magick, tmp_path, outfile)
|
converted = open_with_magick(magick, tmp_path, outfile)
|
||||||
|
if mode == "P":
|
||||||
|
assert converted.mode == "P"
|
||||||
|
|
||||||
|
im = im.convert("RGB")
|
||||||
|
converted = converted.convert("RGB")
|
||||||
assert_image_equal(converted, im)
|
assert_image_equal(converted, im)
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,7 +60,6 @@ def test_monochrome(tmp_path: Path) -> None:
|
||||||
roundtrip(tmp_path, mode)
|
roundtrip(tmp_path, mode)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="Palm P image is wrong")
|
|
||||||
def test_p_mode(tmp_path: Path) -> None:
|
def test_p_mode(tmp_path: Path) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
mode = "P"
|
mode = "P"
|
||||||
|
|
|
@ -71,24 +71,26 @@ def test_invalid_file() -> None:
|
||||||
SgiImagePlugin.SgiImageFile(invalid_file)
|
SgiImagePlugin.SgiImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
def test_write(tmp_path: Path) -> None:
|
def roundtrip(img: Image.Image, tmp_path: Path) -> None:
|
||||||
def roundtrip(img: Image.Image) -> None:
|
out = tmp_path / "temp.sgi"
|
||||||
out = tmp_path / "temp.sgi"
|
img.save(out, format="sgi")
|
||||||
img.save(out, format="sgi")
|
assert_image_equal_tofile(img, out)
|
||||||
|
|
||||||
|
out = tmp_path / "fp.sgi"
|
||||||
|
with open(out, "wb") as fp:
|
||||||
|
img.save(fp)
|
||||||
assert_image_equal_tofile(img, out)
|
assert_image_equal_tofile(img, out)
|
||||||
|
|
||||||
out = tmp_path / "fp.sgi"
|
assert not fp.closed
|
||||||
with open(out, "wb") as fp:
|
|
||||||
img.save(fp)
|
|
||||||
assert_image_equal_tofile(img, out)
|
|
||||||
|
|
||||||
assert not fp.closed
|
|
||||||
|
|
||||||
for mode in ("L", "RGB", "RGBA"):
|
@pytest.mark.parametrize("mode", ("L", "RGB", "RGBA"))
|
||||||
roundtrip(hopper(mode))
|
def test_write(mode: str, tmp_path: Path) -> None:
|
||||||
|
roundtrip(hopper(mode), tmp_path)
|
||||||
|
|
||||||
# Test 1 dimension for an L mode image
|
|
||||||
roundtrip(Image.new("L", (10, 1)))
|
def test_write_L_mode_1_dimension(tmp_path: Path) -> None:
|
||||||
|
roundtrip(Image.new("L", (10, 1)), tmp_path)
|
||||||
|
|
||||||
|
|
||||||
def test_write16(tmp_path: Path) -> None:
|
def test_write16(tmp_path: Path) -> None:
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from glob import glob
|
|
||||||
from itertools import product
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -15,14 +13,27 @@ _TGA_DIR = os.path.join("Tests", "images", "tga")
|
||||||
_TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common")
|
_TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common")
|
||||||
|
|
||||||
|
|
||||||
_MODES = ("L", "LA", "P", "RGB", "RGBA")
|
|
||||||
_ORIGINS = ("tl", "bl")
|
_ORIGINS = ("tl", "bl")
|
||||||
|
|
||||||
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
|
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", _MODES)
|
@pytest.mark.parametrize(
|
||||||
def test_sanity(mode: str, tmp_path: Path) -> None:
|
"size_mode",
|
||||||
|
(
|
||||||
|
("1x1", "L"),
|
||||||
|
("200x32", "L"),
|
||||||
|
("200x32", "LA"),
|
||||||
|
("200x32", "P"),
|
||||||
|
("200x32", "RGB"),
|
||||||
|
("200x32", "RGBA"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("origin", _ORIGINS)
|
||||||
|
@pytest.mark.parametrize("rle", (True, False))
|
||||||
|
def test_sanity(
|
||||||
|
size_mode: tuple[str, str], origin: str, rle: str, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
def roundtrip(original_im: Image.Image) -> None:
|
def roundtrip(original_im: Image.Image) -> None:
|
||||||
out = tmp_path / "temp.tga"
|
out = tmp_path / "temp.tga"
|
||||||
|
|
||||||
|
@ -36,33 +47,26 @@ def test_sanity(mode: str, tmp_path: Path) -> None:
|
||||||
|
|
||||||
assert_image_equal(saved_im, original_im)
|
assert_image_equal(saved_im, original_im)
|
||||||
|
|
||||||
png_paths = glob(os.path.join(_TGA_DIR_COMMON, f"*x*_{mode.lower()}.png"))
|
size, mode = size_mode
|
||||||
|
png_path = os.path.join(_TGA_DIR_COMMON, size + "_" + mode.lower() + ".png")
|
||||||
|
with Image.open(png_path) as reference_im:
|
||||||
|
assert reference_im.mode == mode
|
||||||
|
|
||||||
for png_path in png_paths:
|
path_no_ext = os.path.splitext(png_path)[0]
|
||||||
with Image.open(png_path) as reference_im:
|
tga_path = "{}_{}_{}.tga".format(path_no_ext, origin, "rle" if rle else "raw")
|
||||||
assert reference_im.mode == mode
|
|
||||||
|
|
||||||
path_no_ext = os.path.splitext(png_path)[0]
|
with Image.open(tga_path) as original_im:
|
||||||
for origin, rle in product(_ORIGINS, (True, False)):
|
assert original_im.format == "TGA"
|
||||||
tga_path = "{}_{}_{}.tga".format(
|
assert original_im.get_format_mimetype() == "image/x-tga"
|
||||||
path_no_ext, origin, "rle" if rle else "raw"
|
if rle:
|
||||||
)
|
assert original_im.info["compression"] == "tga_rle"
|
||||||
|
assert original_im.info["orientation"] == _ORIGIN_TO_ORIENTATION[origin]
|
||||||
|
if mode == "P":
|
||||||
|
assert original_im.getpalette() == reference_im.getpalette()
|
||||||
|
|
||||||
with Image.open(tga_path) as original_im:
|
assert_image_equal(original_im, reference_im)
|
||||||
assert original_im.format == "TGA"
|
|
||||||
assert original_im.get_format_mimetype() == "image/x-tga"
|
|
||||||
if rle:
|
|
||||||
assert original_im.info["compression"] == "tga_rle"
|
|
||||||
assert (
|
|
||||||
original_im.info["orientation"]
|
|
||||||
== _ORIGIN_TO_ORIENTATION[origin]
|
|
||||||
)
|
|
||||||
if mode == "P":
|
|
||||||
assert original_im.getpalette() == reference_im.getpalette()
|
|
||||||
|
|
||||||
assert_image_equal(original_im, reference_im)
|
roundtrip(original_im)
|
||||||
|
|
||||||
roundtrip(original_im)
|
|
||||||
|
|
||||||
|
|
||||||
def test_palette_depth_8() -> None:
|
def test_palette_depth_8() -> None:
|
||||||
|
|
|
@ -8,7 +8,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFile, WmfImagePlugin
|
from PIL import Image, ImageFile, WmfImagePlugin
|
||||||
|
|
||||||
from .helper import assert_image_similar_tofile, hopper
|
from .helper import assert_image_equal_tofile, assert_image_similar_tofile, hopper
|
||||||
|
|
||||||
|
|
||||||
def test_load_raw() -> None:
|
def test_load_raw() -> None:
|
||||||
|
@ -44,6 +44,15 @@ def test_load_zero_inch() -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_render() -> None:
|
||||||
|
with open("Tests/images/drawing.emf", "rb") as fp:
|
||||||
|
data = fp.read()
|
||||||
|
b = BytesIO(data[:808] + b"\x00" + data[809:])
|
||||||
|
with Image.open(b) as im:
|
||||||
|
if hasattr(Image.core, "drawwmf"):
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/drawing.emf")
|
||||||
|
|
||||||
|
|
||||||
def test_register_handler(tmp_path: Path) -> None:
|
def test_register_handler(tmp_path: Path) -> None:
|
||||||
class TestHandler(ImageFile.StubHandler):
|
class TestHandler(ImageFile.StubHandler):
|
||||||
methodCalled = False
|
methodCalled = False
|
||||||
|
@ -89,6 +98,20 @@ def test_load_set_dpi() -> None:
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
|
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/drawing.emf") as im:
|
||||||
|
assert im.size == (1625, 1625)
|
||||||
|
|
||||||
|
if not hasattr(Image.core, "drawwmf"):
|
||||||
|
return
|
||||||
|
im.load(im.info["dpi"])
|
||||||
|
assert im.size == (1625, 1625)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/drawing.emf") as im:
|
||||||
|
im.load((72, 144))
|
||||||
|
assert im.size == (82, 164)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/drawing_emf_ref_72_144.png")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("ext", (".wmf", ".emf"))
|
@pytest.mark.parametrize("ext", (".wmf", ".emf"))
|
||||||
def test_save(ext: str, tmp_path: Path) -> None:
|
def test_save(ext: str, tmp_path: Path) -> None:
|
||||||
|
|
|
@ -1705,7 +1705,7 @@ def test_discontiguous_corners_polygon() -> None:
|
||||||
BLACK,
|
BLACK,
|
||||||
)
|
)
|
||||||
expected = os.path.join(IMAGES_PATH, "discontiguous_corners_polygon.png")
|
expected = os.path.join(IMAGES_PATH, "discontiguous_corners_polygon.png")
|
||||||
assert_image_similar_tofile(img, expected, 1)
|
assert_image_equal_tofile(img, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_polygon2() -> None:
|
def test_polygon2() -> None:
|
||||||
|
|
|
@ -131,6 +131,26 @@ class TestImageFile:
|
||||||
|
|
||||||
assert_image_equal(im1, im2)
|
assert_image_equal(im1, im2)
|
||||||
|
|
||||||
|
def test_tile_size(self) -> None:
|
||||||
|
with open("Tests/images/hopper.tif", "rb") as im_fp:
|
||||||
|
data = im_fp.read()
|
||||||
|
|
||||||
|
reads = []
|
||||||
|
|
||||||
|
class FP(BytesIO):
|
||||||
|
def read(self, size: int | None = None) -> bytes:
|
||||||
|
reads.append(size)
|
||||||
|
return super().read(size)
|
||||||
|
|
||||||
|
fp = FP(data)
|
||||||
|
with Image.open(fp) as im:
|
||||||
|
assert len(im.tile) == 7
|
||||||
|
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
# Despite multiple tiles, assert only one tile caused a read of maxblock size
|
||||||
|
assert reads.count(im.decodermaxblock) == 1
|
||||||
|
|
||||||
def test_raise_oserror(self) -> None:
|
def test_raise_oserror(self) -> None:
|
||||||
with pytest.warns(DeprecationWarning):
|
with pytest.warns(DeprecationWarning):
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
|
|
|
@ -235,8 +235,9 @@ following options are available::
|
||||||
im.save(out, save_all=True, append_images=[im1, im2, ...])
|
im.save(out, save_all=True, append_images=[im1, im2, ...])
|
||||||
|
|
||||||
**save_all**
|
**save_all**
|
||||||
If present and true, all frames of the image will be saved. If
|
If present and true, or if ``append_images`` is not empty, all frames of
|
||||||
not, then only the first frame of a multiframe image will be saved.
|
the image will be saved. Otherwise, only the first frame of a multiframe
|
||||||
|
image will be saved.
|
||||||
|
|
||||||
**append_images**
|
**append_images**
|
||||||
A list of images to append as additional frames. Each of the
|
A list of images to append as additional frames. Each of the
|
||||||
|
@ -723,8 +724,8 @@ Saving
|
||||||
|
|
||||||
When calling :py:meth:`~PIL.Image.Image.save` to write an MPO file, by default
|
When calling :py:meth:`~PIL.Image.Image.save` to write an MPO file, by default
|
||||||
only the first frame of a multiframe image will be saved. If the ``save_all``
|
only the first frame of a multiframe image will be saved. If the ``save_all``
|
||||||
argument is present and true, then all frames will be saved, and the following
|
argument is present and true, or if ``append_images`` is not empty, all frames
|
||||||
option will also be available.
|
will be saved.
|
||||||
|
|
||||||
**append_images**
|
**append_images**
|
||||||
A list of images to append as additional pictures. Each of the
|
A list of images to append as additional pictures. Each of the
|
||||||
|
@ -934,7 +935,8 @@ Saving
|
||||||
|
|
||||||
When calling :py:meth:`~PIL.Image.Image.save`, by default only a single frame PNG file
|
When calling :py:meth:`~PIL.Image.Image.save`, by default only a single frame PNG file
|
||||||
will be saved. To save an APNG file (including a single frame APNG), the ``save_all``
|
will be saved. To save an APNG file (including a single frame APNG), the ``save_all``
|
||||||
parameter must be set to ``True``. The following parameters can also be set:
|
parameter should be set to ``True`` or ``append_images`` should not be empty. The
|
||||||
|
following parameters can also be set:
|
||||||
|
|
||||||
**default_image**
|
**default_image**
|
||||||
Boolean value, specifying whether or not the base image is a default image.
|
Boolean value, specifying whether or not the base image is a default image.
|
||||||
|
@ -1163,7 +1165,8 @@ Saving
|
||||||
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
|
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
|
||||||
|
|
||||||
**save_all**
|
**save_all**
|
||||||
If true, Pillow will save all frames of the image to a multiframe tiff document.
|
If true, or if ``append_images`` is not empty, Pillow will save all frames of the
|
||||||
|
image to a multiframe tiff document.
|
||||||
|
|
||||||
.. versionadded:: 3.4.0
|
.. versionadded:: 3.4.0
|
||||||
|
|
||||||
|
@ -1313,8 +1316,8 @@ Saving sequences
|
||||||
|
|
||||||
When calling :py:meth:`~PIL.Image.Image.save` to write a WebP file, by default
|
When calling :py:meth:`~PIL.Image.Image.save` to write a WebP file, by default
|
||||||
only the first frame of a multiframe image will be saved. If the ``save_all``
|
only the first frame of a multiframe image will be saved. If the ``save_all``
|
||||||
argument is present and true, then all frames will be saved, and the following
|
argument is present and true, or if ``append_images`` is not empty, all frames
|
||||||
options will also be available.
|
will be saved, and the following options will also be available.
|
||||||
|
|
||||||
**append_images**
|
**append_images**
|
||||||
A list of images to append as additional frames. Each of the
|
A list of images to append as additional frames. Each of the
|
||||||
|
@ -1616,15 +1619,14 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
|
||||||
**save_all**
|
**save_all**
|
||||||
If a multiframe image is used, by default, only the first image will be saved.
|
If a multiframe image is used, by default, only the first image will be saved.
|
||||||
To save all frames, each frame to a separate page of the PDF, the ``save_all``
|
To save all frames, each frame to a separate page of the PDF, the ``save_all``
|
||||||
parameter must be present and set to ``True``.
|
parameter should be present and set to ``True`` or ``append_images`` should not be
|
||||||
|
empty.
|
||||||
|
|
||||||
.. versionadded:: 3.0.0
|
.. versionadded:: 3.0.0
|
||||||
|
|
||||||
**append_images**
|
**append_images**
|
||||||
A list of :py:class:`PIL.Image.Image` objects to append as additional pages. Each
|
A list of :py:class:`PIL.Image.Image` objects to append as additional pages. Each
|
||||||
of the images in the list can be single or multiframe images. The ``save_all``
|
of the images in the list can be single or multiframe images.
|
||||||
parameter must be present and set to ``True`` in conjunction with
|
|
||||||
``append_images``.
|
|
||||||
|
|
||||||
.. versionadded:: 4.2.0
|
.. versionadded:: 4.2.0
|
||||||
|
|
||||||
|
|
|
@ -534,7 +534,6 @@ You can create animated GIFs with Pillow, e.g.
|
||||||
# Save the images as an animated GIF
|
# Save the images as an animated GIF
|
||||||
images[0].save(
|
images[0].save(
|
||||||
"animated_hopper.gif",
|
"animated_hopper.gif",
|
||||||
save_all=True,
|
|
||||||
append_images=images[1:],
|
append_images=images[1:],
|
||||||
duration=500, # duration of each frame in milliseconds
|
duration=500, # duration of each frame in milliseconds
|
||||||
loop=0, # loop forever
|
loop=0, # loop forever
|
||||||
|
|
|
@ -286,6 +286,14 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
|
|
||||||
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
||||||
|
|
||||||
|
.. py:attribute:: media_white_point
|
||||||
|
:type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
|
||||||
|
|
||||||
|
This tag specifies the media white point and is used for
|
||||||
|
generating absolute colorimetry.
|
||||||
|
|
||||||
|
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
||||||
|
|
||||||
.. py:attribute:: media_white_point_temperature
|
.. py:attribute:: media_white_point_temperature
|
||||||
:type: float | None
|
:type: float | None
|
||||||
|
|
||||||
|
|
|
@ -4,21 +4,12 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
TODO
|
Undefined shift when loading compressed DDS images
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
When loading some compressed DDS formats, an integer was bitshifted by 24 places to
|
||||||
|
generate the 32 bits of the lookup table. This was undefined behaviour, and has been
|
||||||
:cve:`YYYY-XXXXX`: TODO
|
present since Pillow 3.4.0.
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Backwards Incompatible Changes
|
|
||||||
==============================
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
@ -36,10 +27,14 @@ an :py:class:`PIL.ImageFile.ImageFile` instance.
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
|
||||||
TODO
|
``append_images`` no longer requires ``save_all``
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
Previously, ``save_all`` was required to in order to use ``append_images``. Now,
|
||||||
|
``save_all`` will default to ``True`` if ``append_images`` is not empty and the format
|
||||||
|
supports saving multiple frames::
|
||||||
|
|
||||||
|
im.save("out.gif", append_images=ims)
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
@ -73,11 +68,3 @@ Compressed DDS images can now be saved using a ``pixel_format`` argument. DXT1,
|
||||||
DXT5, BC2, BC3 and BC5 are supported::
|
DXT5, BC2, BC3 and BC5 are supported::
|
||||||
|
|
||||||
im.save("out.dds", pixel_format="DXT1")
|
im.save("out.dds", pixel_format="DXT1")
|
||||||
|
|
||||||
Other Changes
|
|
||||||
=============
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
|
@ -48,6 +48,8 @@ BIT2MODE = {
|
||||||
32: ("RGB", "BGRX"),
|
32: ("RGB", "BGRX"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
USE_RAW_ALPHA = False
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return prefix.startswith(b"BM")
|
return prefix.startswith(b"BM")
|
||||||
|
@ -243,7 +245,9 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
msg = "Unsupported BMP bitfields layout"
|
msg = "Unsupported BMP bitfields layout"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
elif file_info["compression"] == self.COMPRESSIONS["RAW"]:
|
elif file_info["compression"] == self.COMPRESSIONS["RAW"]:
|
||||||
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
|
if file_info["bits"] == 32 and (
|
||||||
|
header == 22 or USE_RAW_ALPHA # 32-bit .cur offset
|
||||||
|
):
|
||||||
raw_mode, self._mode = "BGRA", "RGBA"
|
raw_mode, self._mode = "BGRA", "RGBA"
|
||||||
elif file_info["compression"] in (
|
elif file_info["compression"] in (
|
||||||
self.COMPRESSIONS["RLE8"],
|
self.COMPRESSIONS["RLE8"],
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
from io import BytesIO
|
||||||
from typing import IO
|
from typing import IO
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,13 +25,18 @@ class GimpPaletteFile:
|
||||||
|
|
||||||
rawmode = "RGB"
|
rawmode = "RGB"
|
||||||
|
|
||||||
def __init__(self, fp: IO[bytes]) -> None:
|
def _read(self, fp: IO[bytes], limit: bool = True) -> None:
|
||||||
if not fp.readline().startswith(b"GIMP Palette"):
|
if not fp.readline().startswith(b"GIMP Palette"):
|
||||||
msg = "not a GIMP palette file"
|
msg = "not a GIMP palette file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
|
|
||||||
palette: list[int] = []
|
palette: list[int] = []
|
||||||
for _ in range(256):
|
i = 0
|
||||||
|
while True:
|
||||||
|
if limit and i == 256 + 3:
|
||||||
|
break
|
||||||
|
|
||||||
|
i += 1
|
||||||
s = fp.readline()
|
s = fp.readline()
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
|
@ -38,7 +44,7 @@ class GimpPaletteFile:
|
||||||
# skip fields and comment lines
|
# skip fields and comment lines
|
||||||
if re.match(rb"\w+:|#", s):
|
if re.match(rb"\w+:|#", s):
|
||||||
continue
|
continue
|
||||||
if len(s) > 100:
|
if limit and len(s) > 100:
|
||||||
msg = "bad palette file"
|
msg = "bad palette file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
|
|
||||||
|
@ -48,8 +54,19 @@ class GimpPaletteFile:
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
palette += (int(v[i]) for i in range(3))
|
palette += (int(v[i]) for i in range(3))
|
||||||
|
if limit and len(palette) == 768:
|
||||||
|
break
|
||||||
|
|
||||||
self.palette = bytes(palette)
|
self.palette = bytes(palette)
|
||||||
|
|
||||||
|
def __init__(self, fp: IO[bytes]) -> None:
|
||||||
|
self._read(fp)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def frombytes(cls, data: bytes) -> GimpPaletteFile:
|
||||||
|
self = cls.__new__(cls)
|
||||||
|
self._read(BytesIO(data), False)
|
||||||
|
return self
|
||||||
|
|
||||||
def getpalette(self) -> tuple[bytes, str]:
|
def getpalette(self) -> tuple[bytes, str]:
|
||||||
return self.palette, self.rawmode
|
return self.palette, self.rawmode
|
||||||
|
|
|
@ -292,6 +292,8 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
def seek(self, frame: int) -> None:
|
def seek(self, frame: int) -> None:
|
||||||
if not self._seek_check(frame):
|
if not self._seek_check(frame):
|
||||||
return
|
return
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
|
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
|
|
||||||
|
|
|
@ -548,7 +548,6 @@ class Image:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
# FIXME: take "new" parameters / other image?
|
# FIXME: take "new" parameters / other image?
|
||||||
# FIXME: turn mode and size into delegating properties?
|
|
||||||
self._im: core.ImagingCore | DeferredError | None = None
|
self._im: core.ImagingCore | DeferredError | None = None
|
||||||
self._mode = ""
|
self._mode = ""
|
||||||
self._size = (0, 0)
|
self._size = (0, 0)
|
||||||
|
@ -617,6 +616,8 @@ class Image:
|
||||||
more information.
|
more information.
|
||||||
"""
|
"""
|
||||||
if getattr(self, "map", None):
|
if getattr(self, "map", None):
|
||||||
|
if sys.platform == "win32" and hasattr(sys, "pypy_version_info"):
|
||||||
|
self.map.close()
|
||||||
self.map: mmap.mmap | None = None
|
self.map: mmap.mmap | None = None
|
||||||
|
|
||||||
# Instead of simply setting to None, we're setting up a
|
# Instead of simply setting to None, we're setting up a
|
||||||
|
@ -2510,13 +2511,6 @@ class Image:
|
||||||
# only set the name for metadata purposes
|
# only set the name for metadata purposes
|
||||||
filename = os.fspath(fp.name)
|
filename = os.fspath(fp.name)
|
||||||
|
|
||||||
# may mutate self!
|
|
||||||
self._ensure_mutable()
|
|
||||||
|
|
||||||
save_all = params.pop("save_all", False)
|
|
||||||
self.encoderinfo = {**getattr(self, "encoderinfo", {}), **params}
|
|
||||||
self.encoderconfig: tuple[Any, ...] = ()
|
|
||||||
|
|
||||||
preinit()
|
preinit()
|
||||||
|
|
||||||
filename_ext = os.path.splitext(filename)[1].lower()
|
filename_ext = os.path.splitext(filename)[1].lower()
|
||||||
|
@ -2531,9 +2525,20 @@ class Image:
|
||||||
msg = f"unknown file extension: {ext}"
|
msg = f"unknown file extension: {ext}"
|
||||||
raise ValueError(msg) from e
|
raise ValueError(msg) from e
|
||||||
|
|
||||||
|
# may mutate self!
|
||||||
|
self._ensure_mutable()
|
||||||
|
|
||||||
|
save_all = params.pop("save_all", None)
|
||||||
|
self.encoderinfo = {**getattr(self, "encoderinfo", {}), **params}
|
||||||
|
self.encoderconfig: tuple[Any, ...] = ()
|
||||||
|
|
||||||
if format.upper() not in SAVE:
|
if format.upper() not in SAVE:
|
||||||
init()
|
init()
|
||||||
if save_all:
|
if save_all or (
|
||||||
|
save_all is None
|
||||||
|
and params.get("append_images")
|
||||||
|
and format.upper() in SAVE_ALL
|
||||||
|
):
|
||||||
save_handler = SAVE_ALL[format.upper()]
|
save_handler = SAVE_ALL[format.upper()]
|
||||||
else:
|
else:
|
||||||
save_handler = SAVE[format.upper()]
|
save_handler = SAVE[format.upper()]
|
||||||
|
|
|
@ -34,7 +34,6 @@ import itertools
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import sys
|
|
||||||
from typing import IO, TYPE_CHECKING, Any, NamedTuple, cast
|
from typing import IO, TYPE_CHECKING, Any, NamedTuple, cast
|
||||||
|
|
||||||
from . import ExifTags, Image
|
from . import ExifTags, Image
|
||||||
|
@ -289,8 +288,6 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
self.map: mmap.mmap | None = None
|
self.map: mmap.mmap | None = None
|
||||||
use_mmap = self.filename and len(self.tile) == 1
|
use_mmap = self.filename and len(self.tile) == 1
|
||||||
# As of pypy 2.1.0, memory mapping was failing here.
|
|
||||||
use_mmap = use_mmap and not hasattr(sys, "pypy_version_info")
|
|
||||||
|
|
||||||
assert self.fp is not None
|
assert self.fp is not None
|
||||||
readonly = 0
|
readonly = 0
|
||||||
|
@ -357,7 +354,7 @@ class ImageFile(Image.Image):
|
||||||
self.tile, lambda tile: (tile[0], tile[1], tile[3])
|
self.tile, lambda tile: (tile[0], tile[1], tile[3])
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
for decoder_name, extents, offset, args in self.tile:
|
for i, (decoder_name, extents, offset, args) in enumerate(self.tile):
|
||||||
seek(offset)
|
seek(offset)
|
||||||
decoder = Image._getdecoder(
|
decoder = Image._getdecoder(
|
||||||
self.mode, decoder_name, args, self.decoderconfig
|
self.mode, decoder_name, args, self.decoderconfig
|
||||||
|
@ -370,8 +367,13 @@ class ImageFile(Image.Image):
|
||||||
else:
|
else:
|
||||||
b = prefix
|
b = prefix
|
||||||
while True:
|
while True:
|
||||||
|
read_bytes = self.decodermaxblock
|
||||||
|
if i + 1 < len(self.tile):
|
||||||
|
next_offset = self.tile[i + 1].offset
|
||||||
|
if next_offset > offset:
|
||||||
|
read_bytes = next_offset - offset
|
||||||
try:
|
try:
|
||||||
s = read(self.decodermaxblock)
|
s = read(read_bytes)
|
||||||
except (IndexError, struct.error) as e:
|
except (IndexError, struct.error) as e:
|
||||||
# truncated png/gif
|
# truncated png/gif
|
||||||
if LOAD_TRUNCATED_IMAGES:
|
if LOAD_TRUNCATED_IMAGES:
|
||||||
|
|
|
@ -116,9 +116,6 @@ _COMPRESSION_TYPES = {"none": 0xFF, "rle": 0x01, "scanline": 0x00}
|
||||||
|
|
||||||
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
# we assume this is a color Palm image with the standard colormap,
|
|
||||||
# unless the "info" dict has a "custom-colormap" field
|
|
||||||
|
|
||||||
rawmode = "P"
|
rawmode = "P"
|
||||||
bpp = 8
|
bpp = 8
|
||||||
version = 1
|
version = 1
|
||||||
|
@ -172,12 +169,11 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
compression_type = _COMPRESSION_TYPES["none"]
|
compression_type = _COMPRESSION_TYPES["none"]
|
||||||
|
|
||||||
flags = 0
|
flags = 0
|
||||||
if im.mode == "P" and "custom-colormap" in im.info:
|
if im.mode == "P":
|
||||||
assert im.palette is not None
|
flags |= _FLAGS["custom-colormap"]
|
||||||
flags = flags & _FLAGS["custom-colormap"]
|
colormap = im.im.getpalette()
|
||||||
colormapsize = 4 * 256 + 2
|
colors = len(colormap) // 3
|
||||||
colormapmode = im.palette.mode
|
colormapsize = 4 * colors + 2
|
||||||
colormap = im.getdata().getpalette()
|
|
||||||
else:
|
else:
|
||||||
colormapsize = 0
|
colormapsize = 0
|
||||||
|
|
||||||
|
@ -196,22 +192,11 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
||||||
# now write colormap if necessary
|
# now write colormap if necessary
|
||||||
|
|
||||||
if colormapsize > 0:
|
if colormapsize:
|
||||||
fp.write(o16b(256))
|
fp.write(o16b(colors))
|
||||||
for i in range(256):
|
for i in range(colors):
|
||||||
fp.write(o8(i))
|
fp.write(o8(i))
|
||||||
if colormapmode == "RGB":
|
fp.write(colormap[3 * i : 3 * i + 3])
|
||||||
fp.write(
|
|
||||||
o8(colormap[3 * i])
|
|
||||||
+ o8(colormap[3 * i + 1])
|
|
||||||
+ o8(colormap[3 * i + 2])
|
|
||||||
)
|
|
||||||
elif colormapmode == "RGBA":
|
|
||||||
fp.write(
|
|
||||||
o8(colormap[4 * i])
|
|
||||||
+ o8(colormap[4 * i + 1])
|
|
||||||
+ o8(colormap[4 * i + 2])
|
|
||||||
)
|
|
||||||
|
|
||||||
# now convert data to raw form
|
# now convert data to raw form
|
||||||
ImageFile._save(
|
ImageFile._save(
|
||||||
|
|
|
@ -171,6 +171,8 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
def seek(self, layer: int) -> None:
|
def seek(self, layer: int) -> None:
|
||||||
if not self._seek_check(layer):
|
if not self._seek_check(layer):
|
||||||
return
|
return
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
|
|
||||||
# seek to given layer (1..max)
|
# seek to given layer (1..max)
|
||||||
_, mode, _, tile = self.layers[layer - 1]
|
_, mode, _, tile = self.layers[layer - 1]
|
||||||
|
|
|
@ -180,6 +180,8 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
raise EOFError(msg)
|
raise EOFError(msg)
|
||||||
if not self._seek_check(frame):
|
if not self._seek_check(frame):
|
||||||
return
|
return
|
||||||
|
if isinstance(self._fp, DeferredError):
|
||||||
|
raise self._fp.ex
|
||||||
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
|
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
|
||||||
|
|
||||||
if isinstance(self._fp, DeferredError):
|
if isinstance(self._fp, DeferredError):
|
||||||
|
|
|
@ -81,8 +81,6 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
format_description = "Windows Metafile"
|
format_description = "Windows Metafile"
|
||||||
|
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
self._inch = None
|
|
||||||
|
|
||||||
# check placable header
|
# check placable header
|
||||||
assert self.fp is not None
|
assert self.fp is not None
|
||||||
s = self.fp.read(80)
|
s = self.fp.read(80)
|
||||||
|
@ -91,10 +89,11 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
# placeable windows metafile
|
# placeable windows metafile
|
||||||
|
|
||||||
# get units per inch
|
# get units per inch
|
||||||
self._inch = word(s, 14)
|
inch = word(s, 14)
|
||||||
if self._inch == 0:
|
if inch == 0:
|
||||||
msg = "Invalid inch"
|
msg = "Invalid inch"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
self._inch: tuple[float, float] = inch, inch
|
||||||
|
|
||||||
# get bounding box
|
# get bounding box
|
||||||
x0 = short(s, 6)
|
x0 = short(s, 6)
|
||||||
|
@ -105,8 +104,8 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
# normalize size to 72 dots per inch
|
# normalize size to 72 dots per inch
|
||||||
self.info["dpi"] = 72
|
self.info["dpi"] = 72
|
||||||
size = (
|
size = (
|
||||||
(x1 - x0) * self.info["dpi"] // self._inch,
|
(x1 - x0) * self.info["dpi"] // inch,
|
||||||
(y1 - y0) * self.info["dpi"] // self._inch,
|
(y1 - y0) * self.info["dpi"] // inch,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.info["wmf_bbox"] = x0, y0, x1, y1
|
self.info["wmf_bbox"] = x0, y0, x1, y1
|
||||||
|
@ -140,6 +139,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
self.info["dpi"] = xdpi
|
self.info["dpi"] = xdpi
|
||||||
else:
|
else:
|
||||||
self.info["dpi"] = xdpi, ydpi
|
self.info["dpi"] = xdpi, ydpi
|
||||||
|
self._inch = xdpi, ydpi
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg = "Unsupported file format"
|
msg = "Unsupported file format"
|
||||||
|
@ -155,13 +155,17 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
def _load(self) -> ImageFile.StubHandler | None:
|
def _load(self) -> ImageFile.StubHandler | None:
|
||||||
return _handler
|
return _handler
|
||||||
|
|
||||||
def load(self, dpi: int | None = None) -> Image.core.PixelAccess | None:
|
def load(
|
||||||
if dpi is not None and self._inch is not None:
|
self, dpi: float | tuple[float, float] | None = None
|
||||||
|
) -> Image.core.PixelAccess | None:
|
||||||
|
if dpi is not None:
|
||||||
self.info["dpi"] = dpi
|
self.info["dpi"] = dpi
|
||||||
x0, y0, x1, y1 = self.info["wmf_bbox"]
|
x0, y0, x1, y1 = self.info["wmf_bbox"]
|
||||||
|
if not isinstance(dpi, tuple):
|
||||||
|
dpi = dpi, dpi
|
||||||
self._size = (
|
self._size = (
|
||||||
(x1 - x0) * self.info["dpi"] // self._inch,
|
int((x1 - x0) * dpi[0] / self._inch[0]),
|
||||||
(y1 - y0) * self.info["dpi"] // self._inch,
|
int((y1 - y0) * dpi[1] / self._inch[1]),
|
||||||
)
|
)
|
||||||
return super().load()
|
return super().load()
|
||||||
|
|
||||||
|
|
|
@ -687,6 +687,14 @@ PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
#define GET32(p, o) ((DWORD *)(p + o))[0]
|
#define GET32(p, o) ((DWORD *)(p + o))[0]
|
||||||
|
|
||||||
|
static int CALLBACK
|
||||||
|
enhMetaFileProc(
|
||||||
|
HDC hdc, HANDLETABLE *lpht, const ENHMETARECORD *lpmr, int nHandles, LPARAM data
|
||||||
|
) {
|
||||||
|
PlayEnhMetaFileRecord(hdc, lpht, lpmr, nHandles);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyImaging_DrawWmf(PyObject *self, PyObject *args) {
|
PyImaging_DrawWmf(PyObject *self, PyObject *args) {
|
||||||
HBITMAP bitmap;
|
HBITMAP bitmap;
|
||||||
|
@ -767,10 +775,7 @@ PyImaging_DrawWmf(PyObject *self, PyObject *args) {
|
||||||
/* FIXME: make background transparent? configurable? */
|
/* FIXME: make background transparent? configurable? */
|
||||||
FillRect(dc, &rect, GetStockObject(WHITE_BRUSH));
|
FillRect(dc, &rect, GetStockObject(WHITE_BRUSH));
|
||||||
|
|
||||||
if (!PlayEnhMetaFile(dc, meta, &rect)) {
|
EnumEnhMetaFile(dc, meta, enhMetaFileProc, NULL, &rect);
|
||||||
PyErr_SetString(PyExc_OSError, "cannot render metafile");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* step 4: extract bits from bitmap */
|
/* step 4: extract bits from bitmap */
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ typedef struct {
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
UINT16 c0, c1;
|
UINT16 c0, c1;
|
||||||
UINT32 lut;
|
|
||||||
} bc1_color;
|
} bc1_color;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -40,13 +39,10 @@ typedef struct {
|
||||||
|
|
||||||
#define LOAD16(p) (p)[0] | ((p)[1] << 8)
|
#define LOAD16(p) (p)[0] | ((p)[1] << 8)
|
||||||
|
|
||||||
#define LOAD32(p) (p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24)
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
bc1_color_load(bc1_color *dst, const UINT8 *src) {
|
bc1_color_load(bc1_color *dst, const UINT8 *src) {
|
||||||
dst->c0 = LOAD16(src);
|
dst->c0 = LOAD16(src);
|
||||||
dst->c1 = LOAD16(src + 2);
|
dst->c1 = LOAD16(src + 2);
|
||||||
dst->lut = LOAD32(src + 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static rgba
|
static rgba
|
||||||
|
@ -70,7 +66,7 @@ static void
|
||||||
decode_bc1_color(rgba *dst, const UINT8 *src, int separate_alpha) {
|
decode_bc1_color(rgba *dst, const UINT8 *src, int separate_alpha) {
|
||||||
bc1_color col;
|
bc1_color col;
|
||||||
rgba p[4];
|
rgba p[4];
|
||||||
int n, cw;
|
int n, o, cw;
|
||||||
UINT16 r0, g0, b0, r1, g1, b1;
|
UINT16 r0, g0, b0, r1, g1, b1;
|
||||||
bc1_color_load(&col, src);
|
bc1_color_load(&col, src);
|
||||||
|
|
||||||
|
@ -103,9 +99,11 @@ decode_bc1_color(rgba *dst, const UINT8 *src, int separate_alpha) {
|
||||||
p[3].b = 0;
|
p[3].b = 0;
|
||||||
p[3].a = 0;
|
p[3].a = 0;
|
||||||
}
|
}
|
||||||
for (n = 0; n < 16; n++) {
|
for (n = 0; n < 4; n++) {
|
||||||
cw = 3 & (col.lut >> (2 * n));
|
for (o = 0; o < 4; o++) {
|
||||||
dst[n] = p[cw];
|
cw = 3 & ((src + 4)[n] >> (2 * o));
|
||||||
|
dst[n * 4 + o] = p[cw];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -501,55 +501,49 @@ polygon_generic(
|
||||||
// Needed to draw consistent polygons
|
// Needed to draw consistent polygons
|
||||||
xx[j] = xx[j - 1];
|
xx[j] = xx[j - 1];
|
||||||
j++;
|
j++;
|
||||||
} else if (current->dx != 0 && j % 2 == 1 &&
|
} else if ((ymin == current->ymin || ymin == current->ymax) &&
|
||||||
roundf(xx[j - 1]) == xx[j - 1]) {
|
current->dx != 0) {
|
||||||
// Connect discontiguous corners
|
// Connect discontiguous corners
|
||||||
for (k = 0; k < i; k++) {
|
for (k = 0; k < i; k++) {
|
||||||
Edge *other_edge = edge_table[k];
|
Edge *other_edge = edge_table[k];
|
||||||
if ((current->dx > 0 && other_edge->dx <= 0) ||
|
if ((ymin != other_edge->ymin && ymin != other_edge->ymax) ||
|
||||||
(current->dx < 0 && other_edge->dx >= 0)) {
|
other_edge->dx == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Check if the two edges join to make a corner
|
// Check if the two edges join to make a corner
|
||||||
if (xx[j - 1] ==
|
if (roundf(xx[j - 1]) ==
|
||||||
(ymin - other_edge->y0) * other_edge->dx + other_edge->x0) {
|
roundf(
|
||||||
|
(ymin - other_edge->y0) * other_edge->dx +
|
||||||
|
other_edge->x0
|
||||||
|
)) {
|
||||||
// Determine points from the edges on the next row
|
// Determine points from the edges on the next row
|
||||||
// Or if this is the last row, check the previous row
|
// Or if this is the last row, check the previous row
|
||||||
int offset = ymin == ymax ? -1 : 1;
|
int offset = ymin == current->ymax ? -1 : 1;
|
||||||
adjacent_line_x =
|
adjacent_line_x =
|
||||||
(ymin + offset - current->y0) * current->dx +
|
(ymin + offset - current->y0) * current->dx +
|
||||||
current->x0;
|
current->x0;
|
||||||
adjacent_line_x_other_edge =
|
if (ymin + offset >= other_edge->ymin &&
|
||||||
(ymin + offset - other_edge->y0) * other_edge->dx +
|
ymin + offset <= other_edge->ymax) {
|
||||||
other_edge->x0;
|
adjacent_line_x_other_edge =
|
||||||
if (ymin == current->ymax) {
|
(ymin + offset - other_edge->y0) * other_edge->dx +
|
||||||
if (current->dx > 0) {
|
other_edge->x0;
|
||||||
xx[k] =
|
if (xx[j - 1] > adjacent_line_x + 1 &&
|
||||||
fmax(
|
xx[j - 1] > adjacent_line_x_other_edge + 1) {
|
||||||
|
xx[j - 1] =
|
||||||
|
roundf(fmax(
|
||||||
adjacent_line_x, adjacent_line_x_other_edge
|
adjacent_line_x, adjacent_line_x_other_edge
|
||||||
) +
|
)) +
|
||||||
1;
|
1;
|
||||||
} else {
|
} else if (xx[j - 1] < adjacent_line_x - 1 &&
|
||||||
xx[k] =
|
xx[j - 1] < adjacent_line_x_other_edge - 1) {
|
||||||
fmin(
|
xx[j - 1] =
|
||||||
|
roundf(fmin(
|
||||||
adjacent_line_x, adjacent_line_x_other_edge
|
adjacent_line_x, adjacent_line_x_other_edge
|
||||||
) -
|
)) -
|
||||||
1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (current->dx > 0) {
|
|
||||||
xx[k] = fmin(
|
|
||||||
adjacent_line_x, adjacent_line_x_other_edge
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
xx[k] =
|
|
||||||
fmax(
|
|
||||||
adjacent_line_x, adjacent_line_x_other_edge
|
|
||||||
) +
|
|
||||||
1;
|
1;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,6 +299,7 @@ _decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img.orientation = ORIENTATION_TOPLEFT;
|
||||||
img.req_orientation = ORIENTATION_TOPLEFT;
|
img.req_orientation = ORIENTATION_TOPLEFT;
|
||||||
img.col_offset = 0;
|
img.col_offset = 0;
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,7 @@ V = {
|
||||||
"BROTLI": "1.1.0",
|
"BROTLI": "1.1.0",
|
||||||
"FREETYPE": "2.13.3",
|
"FREETYPE": "2.13.3",
|
||||||
"FRIBIDI": "1.0.16",
|
"FRIBIDI": "1.0.16",
|
||||||
"HARFBUZZ": "10.4.0",
|
"HARFBUZZ": "11.0.0",
|
||||||
"JPEGTURBO": "3.1.0",
|
"JPEGTURBO": "3.1.0",
|
||||||
"LCMS2": "2.17",
|
"LCMS2": "2.17",
|
||||||
"LIBIMAGEQUANT": "4.3.4",
|
"LIBIMAGEQUANT": "4.3.4",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user