Skip to content

recipes: flet-libwebp 1.6.0 + Pillow WebP support - #119

Merged
ndonkoHenri merged 2 commits into
mainfrom
webp
Sep 3, 2026
Merged

recipes: flet-libwebp 1.6.0 + Pillow WebP support#119
ndonkoHenri merged 2 commits into
mainfrom
webp

Conversation

@ndonkoHenri

Copy link
Copy Markdown

Adds libwebp and rebuilds Pillow against it, so Image.open() and Image.save() handle WebP on device. Requested in flet#6806.

Our Pillow shipped PIL/_webp.pyi and no PIL/_webp.*.so, so opening a WebP warned WEBP support not installed and then raised UnidentifiedImageError. The requester's diagnosis was correct.

Why not the webp package that was asked for

The request names webp (anibali/pywebp). Building it would have satisfied the title and not the complaint: it registers no PIL plugin — zero register_open / register_extension calls in webp/__init__.py — so PIL.Image.open() would still have raised the same error. It also declares conan>=2.0 in build-system.requires, with an arch list containing no Android ABI and the target derived from the build host's platform.machine(), so cross-compiling it means patching conan out and injecting our own libwebp — i.e. writing flet-libwebp anyway and then bolting a second binding on top.

What declining it costs: pywebp's byte-budget encoding (WebPConfig.target_size / passes) and zero-copy decode into a preallocated numpy array. Animated WebP encode and decode were the strongest argument for it, and they don't survive reading the source — WebPImagePlugin.py binds WebPAnimDecoder/WebPAnimEncoder, so Pillow already has both.

Recipe shape

Autotools, not CMake, for an Android reason rather than a stylistic one: libwebp's CMakeLists calls set_version() unconditionally, producing libwebp.so.7, which APK packaging will not carry. configure goes through libtool, whose linux*android* case sets version_type=none and yields a bare libwebp.so. The Google release tarball is a make dist artifact with a pre-generated configure, so there is no autoreconf/autopoint step.

1.6.0 is not the newest for its own sake — it is the version Pillow 12.2.0 pins in depends/install_webp.sh, so we track what upstream tests against.

Two lines in build.sh are load-bearing, each having fixed a real failure:

  • --prefix at configure time, not make install prefix=. The latter passed on all three iOS slices and failed on all three Android ones with cannot install `libwebp.la` to a directory not ending in /usr/local/lib — iOS is static-only so libtool never relinks, while Android's libwebp.la links libsharpyuv.la and trips libtool's rpath consistency check.
  • hardcode_into_libs=no. Without it every Android .so carried RUNPATH /Users/<me>/…/wheel/opt/lib, which on CI would publish /home/runner/work/… inside the wheel. A grep guard follows the sed, because the silent failure mode is a published wheel with build-machine paths in it.

Android ships the shared libraries and iOS the static ones, matching how each resolves them. That split also disposes of sharpyuv without a consumer patch: on Android libwebp.so declares libsharpyuv.so as its own DT_NEEDED so symbols resolve transitively (llvm-readelf shows 0 sharpyuv references in _webp.so), and on iOS Pillow's setup.py already appends -lsharpyuv under sys.platform == "ios".

license_file names COPYING and PATENTS explicitly. Auto-discovery would find COPYING and silently drop PATENTS, which every libwebp source header points at as the additional patent grant.

Pillow

Two lines: the host dep and build.number: 2. No patch change. WebP was never force-disabled the way brotli/raqm/fribidi are in setup-12.x.patch; it was off only because feature detection found no libwebp, and the recipe already feeds CPATH/LIBRARY_PATH at {platlib}/opt/{include,lib} on Android while iOS resolves through sysconfig.

Nine tests, network-free, with three fixtures totalling 382 bytes. They prove more than linkage: lossless decode asserts exact quadrant colours, lossy decode allows a VP8 tolerance, and the round-trips check the RIFF container tag (VP8L lossless, VP8X for the alpha case). The EXIF test exercises libwebpmux and the animation tests exercise libwebpdemux and libwebpmux, so all three sub-libraries are proven at runtime.

Adds libwebp so Pillow (and anything else that wants it) can read and write WebP on
device. 1.6.0 is not the newest for its own sake — it is the version Pillow 12.2.0 pins
in depends/install_webp.sh, so we track what upstream tests against.

Autotools, not CMake, and the reason is Android rather than taste. libwebp's
CMakeLists sets SOVERSION unconditionally, producing libwebp.so.7, which APK packaging
will not carry; configure goes through libtool, whose linux*android* case sets
version_type=none and yields a bare libwebp.so. The Google release tarball is a
`make dist` artifact with a pre-generated configure, so there is no autoreconf or
autopoint step to go wrong.

Two lines in build.sh are load-bearing, each having fixed a real failure:

  --prefix at configure time, not `make install prefix=`. The latter passed on all
  three iOS slices and failed on all three Android ones with "cannot install
  'libwebp.la' to a directory not ending in /usr/local/lib" — iOS is static-only so
  libtool never relinks, while Android's libwebp.la links libsharpyuv.la and trips
  libtool's rpath consistency check.

  hardcode_into_libs=no. Without it every Android .so carried
  RUNPATH /Users/<me>/.../wheel/opt/lib, which on CI would publish /home/runner/work/...
  inside the wheel. A grep guard follows the sed, because the silent failure mode is a
  published wheel with build-machine paths in it.

Android ships the shared libraries and iOS the static ones, matching how each platform
resolves them: flet stages .so files into jniLibs, while iOS absorbs the archives into
the consuming extension. That split also disposes of the sharpyuv question — on Android
libwebp.so declares libsharpyuv.so as its own DT_NEEDED so the symbols resolve
transitively, and on iOS Pillow's setup.py already appends -lsharpyuv under
`sys.platform == "ios"`. No consumer patch is needed on either.

licence_file names COPYING and PATENTS explicitly: PATENTS is the additional IP grant
every source header points at, and the automatic search matches COPYING* but not it.

6/6 slices green, including armeabi-v7a.
Answers flet-dev/flet#6806. Our Pillow shipped
PIL/_webp.pyi and no PIL/_webp.*.so, so Image.open() on a WebP warned "WEBP support not
installed" and then raised UnidentifiedImageError — the requester's diagnosis was
correct.

Two lines. WebP was never force-disabled the way brotli, raqm and fribidi are in
setup-12.x.patch; it was off only because feature detection found no libwebp. Declaring
the host dep is enough — the recipe already feeds CPATH/LIBRARY_PATH at
{platlib}/opt/{include,lib} on Android and iOS resolves through sysconfig — so the patch
is untouched.

Nine tests, network-free, with three fixtures totalling 382 bytes. They cover more than
"it links": lossless decode asserts exact quadrant colours, lossy decode allows a VP8
tolerance, and the round-trip tests check the RIFF container tag (VP8L for lossless,
VP8X for the alpha case). The EXIF test exercises libwebpmux and the animation tests
exercise libwebpdemux and libwebpmux, so all three sub-libraries are proven at runtime
rather than merely present on the link line.

Verified 11/11 EXIT 0 on an Android arm64 emulator and an iOS 18.6 simulator, and
11/11 on desktop Pillow so the assertions are not device-specific. The Android
_webp.so links libwebp/libwebpmux/libwebpdemux and the four libraries stage into
lib/arm64-v8a/; the iOS extension absorbs them statically, 634 KB against Android's
27 KB, linking only system libraries.

build.number 2: the upstream version is unchanged, the recipe is not.
@ndonkoHenri
ndonkoHenri merged commit f27b337 into main Sep 3, 2026
11 of 23 checks passed
@ndonkoHenri
ndonkoHenri deleted the webp branch September 3, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant