Rand Stats

Vips::Native

zef:apogee

Actions Status

NAME

Vips::Native - Raku FFI wrapper for libvips with bundled prebuilts

SYNOPSIS

use Vips::Native;

# 1. Smart-crop + resize to fixed output dimensions, saved as PNG.
smart-resize("input.png", "thumb.png", 128, 128);

# 2. Letterbox-resize to a fixed square + return raw 8-bit RGB pixels.
#    Aspect ratio preserved; gap filled with the chosen background.
#    Output is suitable to feed to ML inference engines.
my Buf[uint8] $bytes = letterbox-to-buffer(
    'photo.png'.IO,
    :size(448),
    :bg(255, 255, 255),
);
say $bytes.bytes;   # 448 × 448 × 3 = 602112

DESCRIPTION

Vips::Native is a Raku NativeCall wrapper around libvips, shipped with prebuilt platform bundles so the common case (install, import, run) doesn't require a working libvips toolchain on the host.

The module exposes two high-level helpers:

The full (variadic) libvips C API is available via direct is native bindings under Vips::Native::FFI — see the module's SYNOPSIS for the bindings exported. Most of those go through a tiny non-variadic C shim (src/vips_native_shim.c) for ABI safety on Apple arm64 / Linux aarch64, where Raku's NativeCall can't marshal C variadics correctly.

INSTALLATION

Vips::Native tries three lookup paths at install + load time, in order:

Supported platforms

Every supported platform ships a working bundle with the varargs shim included.

x86_64 macOS has no prebuilt: neither an Intel Mac nor an x86_64 Raku running under Rosetta on Apple silicon (which reports itself as x86_64). There, installing falls back to a system libvips, and that libvips has to be x86_64 too — an arm64 one, such as Homebrew's on Apple silicon, cannot load into an x86_64 process. Install an x86_64 libvips (MacPorts, or your own build) and Vips::Native finds it in the usual library directories, or point VIPS_NATIVE_LIB_DIR at it. The install says so when it happens. An arm64 Raku on the same Mac gets the prebuilt.

Build-time environment

BINARY DISTRIBUTIONS AND LICENSING

Vips::Native itself is Artistic-2.0. The prebuilt packs it downloads are something else: binary redistributions of libvips and the libraries it depends on, built by third parties and repackaged here unmodified apart from their load paths. The only code of Vips::Native's own in them is the varargs shim (libvips_shim, Artistic-2.0).

The prebuilt packs contain GPL-licensed code, as well as LGPL-licensed code, code under the weak-copyleft MPL-2.0, and code under permissive licences:

Every pack carries a THIRD-PARTY.md and a LICENSES/ directory, staged beside the libraries in $XDG_DATA_HOME/Vips-Native/<binary-tag>/lib/ (installation refuses an archive without them). THIRD-PARTY.md maps every library file in the pack to the package or component it came from, with its version, its licence and where its source lives, and calls out the licences above; LICENSES/ holds the licence texts, including the GPL-3.0 text the LGPL-3.0 incorporates, and the notices that go with them (Lerc's NOTICE on macOS and Linux, and in every pack the notices of FreeType's BDF and PCF font drivers). The Windows packs' upstream archive carries only libvips's own licence text, so every component's licence texts are taken from the source it was built from, each checked against a pinned SHA-256 when the pack is built — the components built into another's DLL (the mingw-w64 runtime and LLVM's compiler-rt in every DLL, proxy-libintl in GLib's, the Rust crates and standard library in librsvg's, Imath in OpenEXR's, uthash in libdicom's) among them. A pack is not built if a package in it declares a copyleft licence this section does not name.

Where to get the source. Each pack's THIRD-PARTY.md lists, under Where to get the source, the source archive of every GPL, LGPL and MPL component in it, with the SHA-256 the build checked it against, and the recipe that built it (the build script and any patches). The MPL-2.0 code's source is also at its upstreams: NSS at https://ftp.mozilla.org/pub/security/nss/releases/ and NSPR at https://ftp.mozilla.org/pub/nspr/releases/ (macOS and Linux), and each Rust crate at https://crates.io/crates/<name>/<version> — on Windows cssparser 0.37.0, cssparser-color 0.5.0, dtoa-short 0.3.5 and selectors 0.40.0; on macOS and Linux, the versions in that pack's librsvg, which its THIRD-PARTY.md names.

Acknowledgements. The licences of FreeType, of the IJG-derived JPEG libraries (libjpeg-turbo on macOS and Linux, mozjpeg on Windows) and of libultrahdr (Windows) ask the documentation of a binary distribution to carry these statements, and each pack's THIRD-PARTY.md carries the ones for what it holds:

Tables derived from the Unicode Character Database are compiled into GLib, FriBidi and HarfBuzz, in every pack; the Unicode License v3 that covers them is in each pack as LICENSES/Unicode-3.0.txt.

The macOS and Linux packs carry no ImageMagick. conda-forge's libvips depends on it, so it is in the environment those packs are cut from, but they leave out libvips's ImageMagick loader and everything only it needs — ImageMagick's own libraries, OpenEXR, and Graphviz, whose EPL-1.0 is, on the Free Software Foundation's reading, incompatible with the GPL code beside it. There is no magickload or magicksave on those platforms, so files only ImageMagick reads do not open: BMP, ICO and CUR, PSD, TGA, OpenEXR, DDS, DPX, PCX, QOI, SGI, Sun raster, XBM, XPM, PAM, MIFF, MNG and JNG, MATLAB and VIFF files among them. The Windows packs keep ImageMagick (under its own licence), and with it all of those formats.

Where the packs come from:

Each macOS and Linux pack's THIRD-PARTY.md lists every package of the environment the pack was cut from, by version and build string — those that contribute no file to it too, ImageMagick among them (pinned to conda-forge's imagemagick_* builds, which declare ImageMagick's own licence, rather than the agpl_* builds of the same libraries) — and, for every package in the pack, the conda-forge recipe at the commit that built it and the upstream source archive.

To use no prebuilt at all, install with VIPS_NATIVE_PREFER_SYSTEM=1: nothing is downloaded, Vips::Native loads the libvips you installed yourself (see INSTALLATION), and the varargs shim is compiled from its source with your C compiler. VIPS_NATIVE_LIB_DIR points it at a libvips of your own build instead. Either way it must be libvips 8.12 or later. Platforms without a prebuilt (including x86_64 macOS) always take that path.

EXTERNAL API

smart-resize

sub smart-resize(
    Str $in-path,
    Str $out-path,
    Int $out-width,
    Int $out-height,
    --> Bool
) is export

Smart-crop the source image to match the output aspect ratio (using libvips's VIPS_INTERESTING_ATTENTION saliency model — picks the visually-important rectangle, not just the centre), then Lanczos3- resize the cropped image to $out-width × $out-height and save as PNG to $out-path.

Returns True on success; fails with the underlying error if libvips errors out.

A vips_smart_resize shell binary is also installed:

vips_smart_resize --in=/path/to/image.png --out=/path/to/save.png \
                  --width=180 --height=180

letterbox-to-buffer

sub letterbox-to-buffer(
    IO::Path() $path,
    UInt :$size = 448,
    :@bg = (255, 255, 255),
    --> Buf[uint8]
) is export

Resize $path to fit inside a $size × $size square while preserving aspect ratio, pad the unfilled region with the @bg colour, composite alpha over that colour for RGBA inputs, and return the raw 8-bit RGB pixel buffer in NHWC byte order. Length is always exactly $size × $size × 3.

The preprocess pipeline image classifiers want: deterministic gap fill, fixed input dimensions, no GC entanglement with libvips's heap. Pair with ONNX::Native or your inference engine of choice to feed RGB pixels directly.

The buffer is owned by the caller — internally we copy from the vips_image_write_to_memory result into a Raku-owned buf8 and release the source via vips_shim_free, so there's no lifetime trap.

Inputs may be 3-band RGB or 4-band RGBA (UCHAR pixels only — 16-bit PNG / HDR / float images are out of scope for v1). Greyscale and CMYK fail loudly with details.

Vips::Native::FFI

The complete (variadic + non-variadic) libvips C bindings are exported from Vips::Native::FFI:

vips_flatten and vips_embed require the libvips_shim (their options take a VipsArrayDouble * that's awkward to construct directly from Raku). Every prebuilt bundle includes it; if you're running against system libvips, the shim is compiled at install time via the platform's C toolchain and falls back gracefully if no toolchain is available.

A staged shim is kept only while it matches the shim source of the release being installed: a libvips_shim.<ext>.srchash file beside it records the SHA-256 of the source it was built from. When it does not match (a bundle cut before the source gained a function, say), install refreshes it from a content-addressed cache, or compiles it, and keeps the old one with a note only when it can do neither. On Windows that compile uses a MinGW toolchain such as MSYS2's UCRT64 (VIPS_NATIVE_CC to choose another), links straight against the bundle's DLLs, and replaces the staged shim only after nm shows it exports every vips_shim_* function the source defines.

AUTHOR

COPYRIGHT AND LICENSE

Copyright 2025-2026 Matt Doughty.

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.