PythonSTB commited on
Commit
05e6feb
·
verified ·
1 Parent(s): 4d52b15

Delete pillow/Test_Pillow.py

Browse files
Files changed (1) hide show
  1. pillow/Test_Pillow.py +0 -629
pillow/Test_Pillow.py DELETED
@@ -1,629 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- test_pillow.py — standalone pure-Python functional test for the android wheels.
4
-
5
- Works with both Pillow (12.3.0) and Pillow-SIMD (9.5.0.post2). Only uses the
6
- Python stdlib plus the PIL package itself (no numpy / third-party deps).
7
-
8
- Run on the device/emulator:
9
- python /path/to/test_pillow.py
10
-
11
- Exit code: 0 = all critical tests passed, 1 = at least one failure.
12
- Optional/skippable tests (missing font, Tk/Qt, missing codec feature) are
13
- reported as SKIP and do not affect the exit code.
14
-
15
- Generated by RIMI
16
- """
17
-
18
- import os
19
- import sys
20
- import tempfile
21
- import traceback
22
- import io
23
-
24
- # --------------------------------------------------------------------------
25
- # tiny test harness
26
- # --------------------------------------------------------------------------
27
-
28
- RESULTS = [] # (kind, name, detail)
29
- KIND_OK = "ok"
30
- KIND_FAIL = "fail"
31
- KIND_SKIP = "skip"
32
-
33
-
34
- def check(name, fn, *args, **kwargs):
35
- try:
36
- fn(*args, **kwargs)
37
- RESULTS.append((KIND_OK, name, ""))
38
- except SkipTest as e:
39
- RESULTS.append((KIND_SKIP, name, str(e)))
40
- except Exception:
41
- RESULTS.append((KIND_FAIL, name, traceback.format_exc().strip()))
42
-
43
-
44
- class SkipTest(Exception):
45
- pass
46
-
47
-
48
- def require(cond, why):
49
- if not cond:
50
- raise SkipTest(why)
51
-
52
-
53
- def ok_or_skip(cond, why):
54
- if not cond:
55
- raise SkipTest(why)
56
-
57
-
58
- # --------------------------------------------------------------------------
59
- # 1. imports
60
- # --------------------------------------------------------------------------
61
-
62
- CORE_MODULES = [
63
- "PIL", "PIL._version", "PIL._binary", "PIL._util", "PIL._deprecate",
64
- "PIL.Image", "PIL.ImageMode", "PIL.ImageFile", "PIL.ImageFilter",
65
- "PIL.ImageDraw", "PIL.ImageDraw2", "PIL.ImageFont", "PIL.ImageColor",
66
- "PIL.ImageChops", "PIL.ImageEnhance", "PIL.ImageOps", "PIL.ImageStat",
67
- "PIL.ImageMath", "PIL.ImagePath", "PIL.ImageSequence", "PIL.ImageMorph",
68
- "PIL.ImageTransform", "PIL.ImagePalette", "PIL.ImageShow", "PIL.ImageGrab",
69
- "PIL.ExifTags", "PIL.TiffTags",
70
- "PIL.features", "PIL.BdfFontFile", "PIL.FontFile", "PIL.PcfFontFile",
71
- "PIL.TarIO", "PIL.WalImageFile",
72
- # plugins
73
- "PIL.BlpImagePlugin", "PIL.BmpImagePlugin", "PIL.BufrStubImagePlugin",
74
- "PIL.CurImagePlugin", "PIL.DcxImagePlugin", "PIL.DdsImagePlugin",
75
- "PIL.EpsImagePlugin", "PIL.FliImagePlugin",
76
- "PIL.FtexImagePlugin",
77
- "PIL.GbrImagePlugin", "PIL.GdImageFile", "PIL.GifImagePlugin",
78
- "PIL.GimpGradientFile", "PIL.GimpPaletteFile", "PIL.GribStubImagePlugin",
79
- "PIL.Hdf5StubImagePlugin", "PIL.IcnsImagePlugin", "PIL.IcoImagePlugin",
80
- "PIL.ImImagePlugin", "PIL.ImtImagePlugin", "PIL.IptcImagePlugin",
81
- "PIL.Jpeg2KImagePlugin", "PIL.JpegImagePlugin", "PIL.JpegPresets",
82
- "PIL.McIdasImagePlugin", "PIL.MpegImagePlugin",
83
- "PIL.MpoImagePlugin", "PIL.MspImagePlugin", "PIL.PalmImagePlugin",
84
- "PIL.PcdImagePlugin", "PIL.PcxImagePlugin", "PIL.PdfImagePlugin",
85
- "PIL.PdfParser", "PIL.PixarImagePlugin", "PIL.PngImagePlugin",
86
- "PIL.PpmImagePlugin", "PIL.PsdImagePlugin", "PIL.PSDraw",
87
- "PIL.QoiImagePlugin", "PIL.SgiImagePlugin", "PIL.SpiderImagePlugin",
88
- "PIL.SunImagePlugin", "PIL.TgaImagePlugin", "PIL.TiffImagePlugin",
89
- "PIL.WebPImagePlugin", "PIL.WmfImagePlugin", "PIL.XbmImagePlugin",
90
- "PIL.XpmImagePlugin", "PIL.XVThumbImagePlugin",
91
- ]
92
-
93
- OPTIONAL_MODULES = [
94
- # these may legitimately fail to import on a headless/android build
95
- ("PIL.ImageQt", "Qt bindings not installed"),
96
- ("PIL.ImageTk", "tkinter not available"),
97
- ("PIL.ImageWin", "Windows-only"),
98
- ("PIL.MicImagePlugin", "requires olefile (optional dep)"),
99
- ("PIL.FitsStubImagePlugin", "stub module absent in Pillow 12.3"),
100
- ("PIL.FpxImagePlugin", "requires olefile (optional dep)"),
101
- ]
102
-
103
- FAILED_IMPORTS = []
104
-
105
-
106
- def test_imports():
107
- for name in CORE_MODULES:
108
- try:
109
- __import__(name)
110
- except Exception:
111
- FAILED_IMPORTS.append(name)
112
- RESULTS.append((KIND_FAIL, "import " + name, traceback.format_exc().strip()))
113
- for name, why in OPTIONAL_MODULES:
114
- try:
115
- __import__(name)
116
- except Exception:
117
- RESULTS.append((KIND_SKIP, "import " + name, why))
118
-
119
-
120
- # --------------------------------------------------------------------------
121
- # 2. version / features
122
- # --------------------------------------------------------------------------
123
-
124
- def test_version_and_features():
125
- import PIL
126
- from PIL import features
127
-
128
- ver = getattr(PIL, "__version__", "?")
129
- print(f"[info] PIL package: {ver}")
130
- print(f"[info] PIL.__file__: {PIL.__file__}")
131
- print(f"[info] PIL.__version__: {ver}")
132
-
133
- feature_names = features.get_supported()
134
- print("[info] supported features: " + ", ".join(sorted(feature_names)))
135
- for feat in ("jpg", "jpg_2000", "zlib", "libtiff", "webp", "webp_mux",
136
- "libimagequant", "raqm", "freetype2", "littlecms2", "png"):
137
- try:
138
- avail = features.check(feat)
139
- except Exception:
140
- avail = None
141
- print(f"[info] feature {feat}: {avail}")
142
-
143
- # sanity: extension modules were actually imported (native build present)
144
- from PIL import Image as _Img
145
- core = _Img.core
146
- assert hasattr(core, "blend"), "_imaging native module not loaded"
147
- from PIL import _imagingft, _imagingcms, _webp, _imagingmath, _imagingmorph
148
- for mod, pyinit in ((_imagingft, "getfont"), (_imagingcms, "createProfile"),
149
- (_webp, "WebPEncode"), (_imagingmath, "unop"),
150
- (_imagingmorph, "apply")):
151
- assert any(hasattr(mod, n) for n in (pyinit,)), f"{mod.__name__} looks empty — attrs: {[a for a in dir(mod) if not a.startswith('_')][:10]}"
152
- RESULTS.append((KIND_OK, "version+features+native modules", ""))
153
-
154
-
155
- # --------------------------------------------------------------------------
156
- # helpers
157
- # --------------------------------------------------------------------------
158
-
159
- def make_test_image(mode="RGB", size=(64, 48)):
160
- from PIL import Image
161
- im = Image.new(mode, size)
162
- px = im.load()
163
- w, h = size
164
- for y in range(h):
165
- for x in range(w):
166
- if mode == "L":
167
- px[x, y] = (x * 3 + y * 5) % 256
168
- elif mode == "RGB":
169
- px[x, y] = ((x * 4) % 256, (y * 6) % 256, (x * y) % 256)
170
- elif mode == "RGBA":
171
- px[x, y] = ((x * 4) % 256, (y * 6) % 256, (x * y) % 256, (x + y) % 256)
172
- elif mode == "1":
173
- px[x, y] = (x + y) % 2
174
- elif mode == "P":
175
- px[x, y] = (x + y) % 256
176
- else:
177
- px[x, y] = (x * y) % 256
178
- return im
179
-
180
-
181
- def roundtrip(name, ext, save_kwargs, load_kwargs=None, mode="RGB", check_size=None):
182
- from PIL import Image
183
- im = make_test_image(mode)
184
- tmpdir = tempfile.mkdtemp(prefix="pillow_test_")
185
- try:
186
- path = os.path.join(tmpdir, "img" + ext)
187
- im.save(path, **save_kwargs)
188
- with Image.open(path, **(load_kwargs or {})) as out:
189
- out.load()
190
- sz = check_size or im.size
191
- assert out.size == sz, f"size {out.size} != {sz}"
192
- assert out.format, "format not detected"
193
- with open(path, "rb") as fh:
194
- buf = io.BytesIO(fh.read())
195
- with Image.open(buf) as out2:
196
- out2.load()
197
- assert out2.size == sz
198
- RESULTS.append((KIND_OK, "roundtrip " + ext, ""))
199
- finally:
200
- import shutil
201
- shutil.rmtree(tmpdir, ignore_errors=True)
202
-
203
-
204
- def roundtrip_assert_approx(im1, im2, tol=6, frac=0.05):
205
- """Check pixel match within tolerance on a sampled grid."""
206
- from PIL import Image
207
- assert im1.size == im2.size, f"size mismatch {im1.size} vs {im2.size}"
208
- p1 = im1.convert("RGB").load()
209
- p2 = im2.convert("RGB").load()
210
- w, h = im1.size
211
- mism = 0
212
- total = 0
213
- for y in range(0, h, max(1, h // 8)):
214
- for x in range(0, w, max(1, w // 8)):
215
- total += 1
216
- c1 = p1[x, y]
217
- c2 = p2[x, y]
218
- if max(abs(a - b) for a, b in zip(c1, c2)) > tol:
219
- mism += 1
220
- assert mism <= max(1, total * frac), f"{mism}/{total} pixels differ > {tol}"
221
-
222
-
223
- # --------------------------------------------------------------------------
224
- # 3. codec round-trips (needs merged libpillow_codecs.so + zlib)
225
- # --------------------------------------------------------------------------
226
-
227
- def test_codecs():
228
- from PIL import features
229
-
230
- def codec(cond, why):
231
- if not cond:
232
- raise SkipTest(why)
233
-
234
- codec(features.check("zlib") or True, "") # png needs zlib (always present)
235
-
236
- codec(features.check("jpg"), "no jpeg")
237
- roundtrip("jpeg", ".jpg", {"quality": 90})
238
- roundtrip("jpeg-gray", ".jpg", {"quality": 90}, mode="L")
239
-
240
- codec(features.check("zlib"), "no zlib")
241
- roundtrip("png", ".png", {})
242
- roundtrip("png-rgba", ".png", {}, mode="RGBA")
243
- roundtrip("png-palette", ".png", {}, mode="P")
244
-
245
- codec(features.check("zlib"), "no zlib")
246
- roundtrip("gif", ".gif", {}, mode="P")
247
- roundtrip("tiff-lzw", ".tiff", {"compression": "tiff_lzw"})
248
- roundtrip("tiff-packbits", ".tiff", {"compression": "packbits"})
249
-
250
- codec(features.check("libtiff"), "no libtiff")
251
- roundtrip("tiff-none", ".tiff", {})
252
-
253
- codec(features.check("webp"), "no webp")
254
- roundtrip("webp-lossless", ".webp", {"lossless": True})
255
- roundtrip("webp-lossy", ".webp", {"quality": 80})
256
-
257
- codec(features.check("jpg_2000"), "no openjpeg")
258
- roundtrip("jpeg2000", ".jp2", {"irreversible": False})
259
-
260
- roundtrip("bmp", ".bmp", {})
261
- roundtrip("ppm", ".ppm", {})
262
- roundtrip("pcx", ".pcx", {})
263
- roundtrip("tga", ".tga", {})
264
- roundtrip("dib", ".dib", {})
265
- roundtrip("sgi", ".sgi", {}, mode="L")
266
- roundtrip("xbm", ".xbm", {}, mode="1")
267
-
268
- # ICO / ICO with embedded PNG
269
- from PIL import Image as _Image
270
- ico = make_test_image("RGB").resize((64, 48))
271
- _tmp_ico = io.BytesIO()
272
- ico.save(_tmp_ico, format="ICO", sizes=[(16, 16), (32, 32)])
273
- _tmp_ico.seek(0)
274
- ico_back = _Image.open(_tmp_ico)
275
- assert ico_back.size[0] <= 64 and ico_back.size[1] <= 48 and ico_back.size[0] > 0
276
- assert ico_back.load()[0, 0] is not None
277
-
278
- RESULTS.append((KIND_OK, "codec round-trips", ""))
279
-
280
-
281
- # --------------------------------------------------------------------------
282
- # 4. core image API
283
- # --------------------------------------------------------------------------
284
-
285
- def test_core_api():
286
- from PIL import (Image, ImageFilter, ImageChops, ImageEnhance, ImageStat,
287
- ImagePalette, ImageOps)
288
-
289
- im = make_test_image("RGB")
290
- w, h = im.size
291
-
292
- # copy / crop / resize / rotate / transpose
293
- im.copy().crop((0, 0, 10, 10)).load()
294
- r = im.resize((32, 24))
295
- assert r.size == (32, 24)
296
- r = im.resize((16, 12), getattr(Image, "Resampling", object).LANCZOS)
297
- assert r.size == (16, 12)
298
- assert im.rotate(90, expand=True).size == (h, w)
299
- assert im.transpose(Image.Transpose.FLIP_LEFT_RIGHT).size == (w, h)
300
-
301
- # point / convert / quantize / palette
302
- g = im.convert("L")
303
- g.point(lambda v: 255 - v)
304
- g.convert("1")
305
- q = im.quantize(colors=16, method=Image.Quantize.MEDIANCUT)
306
- assert q.mode == "P"
307
- if hasattr(Image.Quantize, "LIBIMAGEQUANT"):
308
- try:
309
- q2 = im.quantize(colors=16, method=Image.Quantize.LIBIMAGEQUANT)
310
- assert q2.mode == "P"
311
- except Exception:
312
- raise SkipTest("LIBIMAGEQUANT quantize failed at runtime")
313
-
314
- # split / merge
315
- bands = im.split()
316
- merged = Image.merge("RGB", bands)
317
- assert merged.size == im.size
318
-
319
- # paste / composite / alpha
320
- im2 = Image.new("RGB", im.size, (255, 0, 0))
321
- im2.paste(im, (0, 0))
322
- composite = Image.composite(im, im2, Image.new("L", im.size, 128))
323
- assert composite.size == im.size
324
- overlay = make_test_image("RGBA")
325
- base = Image.new("RGBA", im.size, (0, 0, 0, 255))
326
- base.alpha_composite(overlay)
327
- assert base.size == im.size
328
-
329
- # filters
330
- for f in (ImageFilter.BLUR, ImageFilter.GaussianBlur(2.0),
331
- ImageFilter.BoxBlur(2), ImageFilter.SMOOTH,
332
- ImageFilter.SHARPEN, ImageFilter.EDGE_ENHANCE,
333
- ImageFilter.FIND_EDGES, ImageFilter.EMBOSS,
334
- ImageFilter.CONTOUR, ImageFilter.UnsharpMask(radius=2, percent=120),
335
- ImageFilter.MaxFilter(3), ImageFilter.MedianFilter(3),
336
- ImageFilter.MinFilter(3), ImageFilter.ModeFilter(3)):
337
- fimg = im.filter(f)
338
- assert fimg.size == im.size
339
- # convolution kernel
340
- kern = [1 / 9] * 9
341
- assert im.filter(ImageFilter.Kernel((3, 3), kern)).size == im.size
342
-
343
- # histogram / stat / getbbox
344
- im.histogram()
345
- ImageStat.Stat(im)
346
- assert im.getbbox() is not None
347
-
348
- # enhancers / chops
349
- ImageEnhance.Brightness(im).enhance(1.2)
350
- ImageEnhance.Contrast(im).enhance(1.2)
351
- ImageEnhance.Color(im).enhance(1.2)
352
- ImageEnhance.Sharpness(im).enhance(1.5)
353
- ImageChops.add(im, im2)
354
- ImageChops.subtract(im, im2)
355
- ImageChops.difference(im, im2)
356
- ImageChops.multiply(im, im2)
357
- ImageChops.offset(im, 5, 5)
358
- ImageChops.invert(im)
359
-
360
- # pixel access, getdata/putdata, getpixel/putpixel
361
- pa = im.load()
362
- px0 = pa[0, 0]
363
- pa[0, 0] = (0, 0, 0)
364
- pa[0, 0] = px0
365
- im.putpixel((1, 1), im.getpixel((1, 1)))
366
- im.getdata()
367
-
368
- # ops
369
- ImageOps.invert(g)
370
- ImageOps.autocontrast(g)
371
- ImageOps.equalize(g)
372
- ImageOps.grayscale(im)
373
- ImageOps.flip(im)
374
- ImageOps.mirror(im)
375
- ImageOps.crop(im, border=2)
376
- ImageOps.scale(im, 0.5)
377
- ImageOps.fit(im, (20, 20))
378
- ImageOps.pad(im, (20, 20))
379
- ImageOps.expand(im, border=2, fill=0)
380
-
381
- # transform
382
- im.transform((32, 24), Image.Transform.AFFINE, (1, 0, 0, 0, 1, 0))
383
- im.transform((32, 24), Image.Transform.QUAD, (0, 0, 0, h, w, h, w, 0))
384
-
385
- # info / metadata style
386
- assert im.format is None
387
- assert im.mode == "RGB"
388
- assert im.size == (w, h)
389
- RESULTS.append((KIND_OK, "core image API", ""))
390
-
391
-
392
- # --------------------------------------------------------------------------
393
- # 5. ImageDraw + ImageFont (freetype)
394
- # --------------------------------------------------------------------------
395
-
396
- def find_font():
397
- candidates = [
398
- "/system/fonts/Roboto-Regular.ttf",
399
- "/system/fonts/Roboto-Medium.ttf",
400
- "/system/fonts/DroidSans.ttf",
401
- "/system/fonts/NotoSans-Regular.ttf",
402
- "/system/fonts/NotoSansCJK-Regular.ttc",
403
- ]
404
- import glob
405
- for c in candidates:
406
- if os.path.isfile(c):
407
- return c
408
- hits = sorted(glob.glob("/system/fonts/*.ttf"))
409
- return hits[0] if hits else None
410
-
411
-
412
- def test_draw_font():
413
- from PIL import Image, ImageDraw, ImageFont
414
-
415
- font_path = find_font()
416
- im = Image.new("RGB", (200, 100), "white")
417
- d = ImageDraw.Draw(im)
418
-
419
- d.ellipse((10, 10, 50, 50), fill="red", outline="blue")
420
- d.rectangle((60, 10, 120, 50), fill="green")
421
- d.line((130, 10, 190, 50), fill="black", width=3)
422
- d.polygon([(10, 60), (60, 90), (110, 60)], fill="orange")
423
- d.point((150, 80), fill="purple")
424
- d.arc((160, 60, 190, 90), start=0, end=180, fill="black")
425
- d.rounded_rectangle((10, 60, 80, 95), radius=5, fill="cyan")
426
-
427
- if font_path is None:
428
- raise SkipTest("no system font available")
429
-
430
- font = ImageFont.truetype(font_path, 18)
431
- assert hasattr(font, "getbbox") or hasattr(font, "getsize")
432
- d.text((10, 10), "Hello PIL", font=font, fill="black")
433
- d.multiline_text((10, 40), "Line1\nLine2", font=font, fill="black")
434
- if hasattr(d, "textbbox"):
435
- d.textbbox((10, 10), "Hello", font=font)
436
- if hasattr(d, "textlength"):
437
- d.textlength("Hello", font=font)
438
- if hasattr(d, "textsize"):
439
- d.textsize("Hello", font=font)
440
-
441
- # draw2
442
- from PIL import ImageDraw2
443
- im2 = Image.new("RGB", (100, 60), "white")
444
- d2 = ImageDraw2.Draw(im2)
445
- d2.polygon([(10, 10), (40, 50), (70, 10)], ImageDraw2.Brush("blue"))
446
- d2.line([(0, 0), (90, 50)], ImageDraw2.Pen("black", 2))
447
- RESULTS.append((KIND_OK, "ImageDraw + ImageFont", ""))
448
-
449
-
450
- # --------------------------------------------------------------------------
451
- # 6. ImageMath / ImagePath / ImageMorph / ImageSequence
452
- # --------------------------------------------------------------------------
453
-
454
- def test_misc_modules():
455
- from PIL import Image, ImageMath, ImagePath, ImageSequence
456
-
457
- l1 = make_test_image("L")
458
- l2 = l1.point(lambda v: v // 2 + 10)
459
- if hasattr(ImageMath, 'eval'):
460
- res = ImageMath.eval("a + b", a=l1, b=l2)
461
- elif hasattr(ImageMath, 'unsafe_eval'):
462
- res = ImageMath.unsafe_eval("a + b", a=l1, b=l2)
463
- else:
464
- raise SkipTest("ImageMath.eval/unsafe_eval not available")
465
- assert res.size == l1.size
466
-
467
- p = ImagePath.Path([(0, 0), (10, 0), (10, 10), (0, 10)])
468
- assert len(p) == 4
469
- p.transform((1, 0, 1, 0, 1, 1))
470
-
471
- seq = list(ImageSequence.Iterator(l1))
472
- assert len(seq) == 1
473
-
474
- # ImageMorph
475
- from PIL import ImageMorph
476
- mm = ImageMorph.MorphOp(op_name="dilation4")
477
- bc, out = mm.apply(l1.convert("L").point(lambda v: 255 if v > 128 else 0))
478
- assert out.size == l1.size
479
- mm2 = ImageMorph.MorphOp(lut=mm.lut)
480
- _, out2 = mm2.apply(l1.convert("L").point(lambda v: 255 if v > 128 else 0))
481
- assert out2.size == l1.size
482
- mm.match(l1.convert("L").point(lambda v: 255 if v > 128 else 0))
483
- RESULTS.append((KIND_OK, "ImageMath/Path/Morph/Sequence", ""))
484
-
485
-
486
- # --------------------------------------------------------------------------
487
- # 7. ImageCms (littlecms2)
488
- # --------------------------------------------------------------------------
489
-
490
- def test_cms():
491
- try:
492
- from PIL import ImageCms
493
- except ImportError:
494
- raise SkipTest("no PIL.ImageCms")
495
- from PIL import Image
496
-
497
- sRGB = ImageCms.createProfile("sRGB")
498
- lab = ImageCms.createProfile("LAB")
499
- tf = ImageCms.buildTransform(sRGB, lab, "RGB", "LAB")
500
- im = make_test_image("RGB")
501
- out = ImageCms.applyTransform(im, tf)
502
- assert out.size == im.size
503
- try:
504
- prof = ImageCms.ImageCmsProfile(sRGB)
505
- assert prof.profile.profile_id
506
- except Exception:
507
- pass
508
- RESULTS.append((KIND_OK, "ImageCms (littlecms)", ""))
509
-
510
-
511
- # --------------------------------------------------------------------------
512
- # 8. numpy interop (optional, only if numpy installed)
513
- # --------------------------------------------------------------------------
514
-
515
- def test_numpy():
516
- try:
517
- import numpy as np
518
- except ImportError:
519
- raise SkipTest("numpy not installed")
520
- from PIL import Image
521
-
522
- arr = np.zeros((32, 32, 3), dtype=np.uint8)
523
- arr[..., 0] = 255
524
- im = Image.fromarray(arr)
525
- assert im.size == (32, 32) and im.mode == "RGB"
526
- back = np.asarray(im)
527
- assert back.shape == (32, 32, 3)
528
- assert back[0, 0, 0] == 255
529
- RESULTS.append((KIND_OK, "numpy interop", ""))
530
-
531
-
532
- # --------------------------------------------------------------------------
533
- # 9. pillow-simd specific exercise (resample/boxblur/reduce paths)
534
- # --------------------------------------------------------------------------
535
-
536
- def test_simd_paths():
537
- from PIL import Image, ImageFilter
538
-
539
- im = make_test_image("RGB", (256, 192))
540
- Res = getattr(Image, "Resampling", None)
541
- if Res is None:
542
- raise SkipTest("Resampling enum missing")
543
-
544
- for filt in (Res.BOX, Res.HAMMING, Res.BILINEAR, Res.BICUBIC, Res.LANCZOS):
545
- small = im.resize((48, 36), filt)
546
- assert small.size == (48, 36)
547
- up = small.resize((256, 192), filt)
548
- assert up.size == (256, 192)
549
-
550
- # reduce via thumbnail
551
- th = im.copy()
552
- th.thumbnail((32, 32))
553
- assert max(th.size) <= 32
554
-
555
- # box blur
556
- for n in (1, 2, 3):
557
- assert im.filter(ImageFilter.BoxBlur(n)).size == im.size
558
-
559
- # unsharp mask (uses Reduce-like / convolution paths in SIMD builds)
560
- assert im.filter(ImageFilter.UnsharpMask(radius=3, percent=150)).size == im.size
561
-
562
- # rank / min / max filters
563
- for f in (ImageFilter.MinFilter(3), ImageFilter.MaxFilter(3), ImageFilter.MedianFilter(3)):
564
- assert im.filter(f).size == im.size
565
-
566
- # ImageOps scale/fit (uses resize internally)
567
- ImageOps_scale = __import__("PIL.ImageOps", fromlist=["scale"])
568
- ImageOps_scale.scale(im, 0.5)
569
- ImageOps_scale.fit(im, (24, 24))
570
-
571
- # color LUT / point LUT path
572
- lut = list(range(256)) * 3
573
- assert im.point(lut).size == im.size
574
- RESULTS.append((KIND_OK, "SIMD/exercise paths", ""))
575
-
576
-
577
- # --------------------------------------------------------------------------
578
- # main
579
- # --------------------------------------------------------------------------
580
-
581
- def main():
582
- print("=" * 64)
583
- print("Pillow / Pillow-SIMD functional test")
584
- print("python:", sys.version.split()[0])
585
- print("platform:", sys.platform)
586
- print("=" * 64)
587
-
588
- test_imports()
589
- check("version+features", test_version_and_features)
590
- check("codecs", test_codecs)
591
- check("core API", test_core_api)
592
- check("draw+font", test_draw_font)
593
- check("misc modules", test_misc_modules)
594
- check("ImageCms", test_cms)
595
- check("numpy interop", test_numpy)
596
- check("SIMD paths", test_simd_paths)
597
-
598
- print()
599
- print("=" * 64)
600
- ok = sum(1 for k, _, _ in RESULTS if k == KIND_OK)
601
- fail = sum(1 for k, _, _ in RESULTS if k == KIND_FAIL)
602
- skip = sum(1 for k, _, _ in RESULTS if k == KIND_SKIP)
603
- print(f"RESULT: {ok} ok, {fail} failed, {skip} skipped (of {len(RESULTS)} total)")
604
-
605
- # print any skips
606
- for k, name, detail in RESULTS:
607
- if k == KIND_SKIP:
608
- print(f" [skip] {name}: {detail}")
609
-
610
- # print failures with traceback
611
- for k, name, detail in RESULTS:
612
- if k == KIND_FAIL:
613
- print()
614
- print(f"### FAIL: {name}")
615
- print(detail)
616
-
617
- # separate top-level import failures
618
- if FAILED_IMPORTS:
619
- print()
620
- print("### FAILED MODULE IMPORTS:")
621
- for n in FAILED_IMPORTS:
622
- print(" " + n)
623
-
624
- print("=" * 64)
625
- sys.exit(1 if fail else 0)
626
-
627
-
628
- if __name__ == "__main__":
629
- main()