libvips image provider

Production-grade image transformer backed by libvips via the davidbyttow/govips cgo bindings. Demand-driven streaming with low constant memory.

Overview

The vips provider implements media.ImageTransformerPort against libvips, a demand-driven image processing library. Demand-driven means libvips runs transformations as a pipeline instead of materialising intermediate buffers, so memory usage stays close to constant regardless of input dimensions. That constant-memory property is what makes the provider safe to point at hostile or user-uploaded images. Output formats are JPEG, PNG, lossy WebP, and AVIF. GIF decodes as input but does not encode as output. Modifiers cover greyscale, blur, sharpen, rotate, flip, brightness, contrast, saturation, hue, tint, and gravity or focus crops.

media.ImageTransformerPort is the same port that every image provider satisfies. Switching from imaging to vips is a one-line bootstrap change with no call-site edits, because both register through the identical option path.

The provider inherits input validation from the framework. Transform runs format, dimension, and pixel-count checks from the shared ImageServiceConfig before any decode, decoding through a LimitedReader capped at MaxFileSizeBytes. A semaphore bounds concurrency, sized to runtime.NumCPU() by default and overridable through Config.ConcurrencyLevel, and the semaphore acquire honours context cancellation. The defaults make the provider safe under hostile-upload load without glue code.

Always call Provider.Close() at shutdown. It invokes vips.Shutdown() exactly once via sync.Once, releasing the libvips runtime, decoder caches, and worker threads. Forgetting to call it leaks native memory.

The pure-Go path against the cgo path matters here. Cgo means a libvips 8.10 or later system dependency at both build and runtime, no static-binary distribution, and a slower go build the first time. In return you get a lower memory ceiling plus AVIF and lossy-WebP output that the pure-Go imaging provider does not produce. Reach for vips when uploads are public or volumes are high. Reach for imaging when you want a single static binary for development.

Once registered, the image service drives the provider. Templates reference it from the piko:img component, and predefined variant specs resolve against it. The wiring below is the only step you write by hand.

Requirements

  • Build tag: //go:build vips. Build with go build -tags vips ./....
  • System library: libvips 8.10 or later available at compile and runtime (apt install libvips-dev, brew install vips, or the matching package).
  • A working C toolchain (cgo enabled).

Because the provider needs cgo, it runs in compiled mode only. The interpreted dev-i run mode runs pure-Go providers, so apply the vips tag to the compiled host binary. Build the binary without the tag and you do not get a compile error. A separate //go:build !vips stub satisfies the full port and returns a guided startup error: not available (built without -tags vips; install libvips and rebuild with -tags vips). The host always compiles, and a forgotten tag fails fast at boot with a clear message instead of producing a broken binary.

Configuration

import (
    "piko.sh/piko/wdk/media"
    "piko.sh/piko/wdk/media/image_provider_vips"
)

provider, err := image_provider_vips.NewProvider(image_provider_vips.Config{
    VipsConfig:         nil,                                  // optional, nil uses govips defaults
    ImageServiceConfig: media.DefaultImageServiceConfig(),    // optional, security and size limits
    ConcurrencyLevel:   0,                                    // optional, 0 means runtime.NumCPU()
})
if err != nil {
    return err
}
defer provider.Close() // releases libvips resources, required

NewProvider calls vips.Startup internally. Do not call it yourself. Close is idempotent (guarded by sync.Once).

Bootstrap

ssr := piko.New(
    piko.WithImageProvider("vips", provider),
    piko.WithDefaultImageProvider("vips"),
)

The first provider registered becomes the default. WithDefaultImageProvider is only needed when you register more than one provider and want a non-first default.

For variant catalogues, prefer piko.WithImage(media.Image().Provider("vips", provider).Build()).

See also

Other media providers:

  • imaging, pure-Go image transformer for development and tests.
  • astiav, FFmpeg-based video transcoding.

Framework docs:

External: