Skip to content

Commit 4928b68

Browse files
authored
recipe: Pillow 12.3.0 links libtiff (#128)
## What Pillow 12.2.0 → 12.3.0, now linked against `flet-libtiff` 4.7.0. ## Why Compressed TIFFs (JPEG, LZW, deflate, PackBits) opened but failed in `load()` with `OSError: decoder libtiff not available`. Reported in flet-dev/flet#6969: JPEG-compressed (compression 7) radar TIFFs worked on Windows and failed on Android. `flet-libtiff` was already published with `JPEG_SUPPORT` and `ZIP_SUPPORT`; the Pillow recipe just never declared it, so `setup.py` built without `HAVE_LIBTIFF`. The bump also matters on iOS: PyPI now ships official 12.3.0 iOS wheels (cp313+), which outranked our 12.2.0 on version. At equal versions our build tag wins again, so iOS apps get the forge build. ## Changes - `meta.yaml`: `flet-libtiff 4.7.0` under `host` (Android, `.so` as `DT_NEEDED`) and `host_build` (iOS, static, folded into `_imaging`); version 12.3.0, build 1. Patches apply unchanged. - `tests/test_pillow.py`: three tests: libtiff listed as a codec; JPEG-in-TIFF round-trip in RGB and RGBA; LZW / deflate / PackBits exact round-trips. ## Validation - Android arm64-v8a emulator, Python 3.14: 14/14 on device. `_imaging` NEEDs `libtiff.so`, APK ships it beside `libjpeg.so`, wheel declares `Requires-Dist: flet-libtiff (==4.7.0)`. - iOS simulator, Python 3.14: 14/14 on device; bundled wheel confirmed as the forge one (`Build: 1`). `_imaging` is `MH_DYLIB`, defines `TIFFInitJPEG`, no runtime `flet-lib*` deps. It now also links system `/usr/lib/liblzma.5.dylib`, which Pillow 12.3 adds for libtiff on iOS and which ships with the OS. - Desktop (PyPI Pillow 12.3.0): 14/14. - CI matrix: this PR's run. ## Consumer notes Apps that read compressed TIFFs need only a rebuild against the new wheel; `PIL.features.check("libtiff")` returns `True` on both platforms. No config or code changes. Consumer guidance for Pillow lives in `recipes/pillow/README.md` on the `examples-and-docs` branch, which still describes compressed TIFF as unsupported and is updated separately.
1 parent 0fadbfa commit 4928b68

2 files changed

Lines changed: 45 additions & 3 deletions

File tree

‎recipes/pillow/meta.yaml‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package:
22
name: Pillow
3-
version: "12.2.0"
3+
version: "12.3.0"
44

55
requirements:
66
# PNG support is internal: libpng is not used.
77
#
8-
# All three are linked STATICALLY into the extensions on iOS and dynamically on
8+
# All four are linked STATICALLY into the extensions on iOS and dynamically on
99
# Android, so where they belong differs by platform. Under `host` they become a
1010
# runtime Requires-Dist and every consuming app installs the wheel; on iOS that
1111
# is ~16 MB of .a already folded into _imaging.so / _webp.so and unloadable at
@@ -17,11 +17,13 @@ requirements:
1717
- flet-libjpeg 3.0.90
1818
- flet-libfreetype 2.13.3
1919
- flet-libwebp 1.6.0
20+
- flet-libtiff 4.7.0
2021
# {% else %}
2122
host_build:
2223
- flet-libjpeg 3.0.90
2324
- flet-libfreetype 2.13.3
2425
- flet-libwebp 1.6.0
26+
- flet-libtiff 4.7.0
2527
# {% endif %}
2628

2729
# {% if not version or version >= (12,0,0) %}
@@ -41,7 +43,7 @@ patches:
4143
# {% endif %}
4244

4345
build:
44-
number: 2
46+
number: 1
4547
script_env:
4648
# {% if sdk == 'android' %}
4749
# pillow's setup.py manually probes `self.compiler.{include,library}_dirs`

‎recipes/pillow/tests/test_pillow.py‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,43 @@ def test_webp_animation_encode():
197197
for i, want in enumerate([(255, 0, 0), (0, 255, 0), (0, 0, 255)]):
198198
rt.seek(i)
199199
assert rt.convert("RGB").getpixel((4, 4)) == want
200+
201+
202+
def test_libtiff_available():
203+
"""libtiff is linked: Pillow lists it as a codec."""
204+
from PIL import features
205+
206+
assert features.check("libtiff") is True
207+
assert "libtiff" in features.get_supported_codecs()
208+
209+
210+
def test_tiff_jpeg_roundtrip():
211+
"""Write and decode JPEG-compressed TIFFs (compression 7), RGB and RGBA."""
212+
from PIL import Image
213+
214+
for mode, colour in (("RGB", (200, 40, 40)), ("RGBA", (40, 200, 40, 128))):
215+
src = Image.new(mode, (64, 48), colour)
216+
out = io.BytesIO()
217+
src.save(out, "TIFF", compression="jpeg")
218+
219+
rt = Image.open(io.BytesIO(out.getvalue()))
220+
assert rt.tag_v2[259] == 7, f"{mode}: compression tag {rt.tag_v2[259]}"
221+
rt.load() # where a build without libtiff raises "decoder libtiff not available"
222+
assert rt.mode == mode
223+
got = rt.getpixel((32, 24))
224+
assert all(abs(g - w) <= 8 for g, w in zip(got, colour)), f"{mode}: {got}"
225+
226+
227+
def test_tiff_lossless_compressions_roundtrip():
228+
"""LZW, deflate and PackBits TIFFs round-trip exactly."""
229+
from PIL import Image
230+
231+
src = Image.new("RGB", (16, 16))
232+
src.putdata([(x * 16, y * 16, 0) for y in range(16) for x in range(16)])
233+
234+
for compression in ("tiff_lzw", "tiff_adobe_deflate", "packbits"):
235+
out = io.BytesIO()
236+
src.save(out, "TIFF", compression=compression)
237+
rt = Image.open(io.BytesIO(out.getvalue()))
238+
assert rt.info["compression"] == compression
239+
assert rt.tobytes() == src.tobytes(), compression

0 commit comments

Comments
 (0)