Profile
Back to NewsBack
GitHub Trending 10 min
Reader Mode
cshum/vipsgen: Go binding generator for libvips image processing library

cshum/vipsgen: Go binding generator for libvips image processing library

15 hours ago

vipsgen

Go Reference</a> CI</a>

vipsgen is a Go binding generator for libvips - a fast and efficient image processing library.

libvips is generally 4-8x faster than ImageMagick with low memory usage, thanks to its demand-driven, horizontally threaded architecture.

Existing Go libvips bindings rely on manually written code that is often incomplete, error-prone, and difficult to maintain as libvips evolves. vipsgen solves this by generating type-safe, robust, and fully documented Go bindings using GObject introspection.

  • Comprehensive: Bindings for around 300 libvips operations
  • Type-Safe: Proper Go types for all libvips C enums and structs
  • Idiomatic: Clean Go APIs that feel natural to use
  • Streaming: VipsSource and VipsTarget integration with Go io.Reader and io.Writer for streaming
You can use vipsgen in two ways:
  • Import directly: Use the pre-generated library github.com/cshum/vipsgen/v2/vips for the latest default installation of libvips, or see pre-generated packages
  • Generate custom bindings: Run the vipsgen command to create bindings for your specific libvips version and installation

Quick Start

Use homebrew to install vips and pkg-config:

brew install vips pkg-config

On MacOS, vipsgen may not compile without first setting an environment variable:

export CGO_CFLAGS_ALLOW="-Xpreprocessor"

Use the package directly:

go get -u github.com/cshum/vipsgen/v2/vips

All operations take their required arguments directly as Go arguments, and their optional arguments through an options struct, maintaining direct equivalence with the libvips API.

  • A pointer field, such as int, string or *vips.Kernel, takes vips.Ptr(v); any other
field takes the value itself. Each field comment gives its range and default.
  • An options struct needs only the fields you want to change: a pointer field left nil leaves
the option to libvips, and vips.Ptr(v) passes the value. MIGRATION.md if you are upgrading from v1.

package main

import ( "log" "net/http"

"github.com/cshum/vipsgen/v2/vips" )

func main() { // Fetch an image from http.Get resp, err := http.Get("https://raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png") if err != nil { log.Fatalf("Failed to fetch image: %v", err) } defer resp.Body.Close()

// Create source from io.ReadCloser source := vips.NewSource(resp.Body) defer source.Close() // source needs to remain available during image lifetime

// Shrink-on-load via creating image from thumbnail source with options image, err := vips.NewThumbnailSource(source, 800, &vips.ThumbnailSourceOptions{ Height: 1000, FailOn: vips.FailOnError, // Fail on first error }) if err != nil { log.Fatalf("Failed to load image: %v", err) } defer image.Close() // always close images to free memory

// Add a yellow border using vips_embed border := 10 if err := image.Embed( border, border, image.Width()+border*2, image.Height()+border*2, &vips.EmbedOptions{ Extend: vips.ExtendBackground, // extend with colour from the background property Background: []float64{255, 255, 0, 255}, // Yellow border }, ); err != nil { log.Fatalf("Failed to add border: %v", err) }

log.Printf("Processed image: %dx%d\n", image.Width(), image.Height())

// Save the result as WebP file with options err = image.Webpsave("resized-gopher.webp", &vips.WebpsaveOptions{ Q: vips.Ptr(85), // Quality factor (0-100) Effort: vips.Ptr(4), // Compression effort (0-6) SmartSubsample: true, // Better chroma subsampling }) if err != nil { log.Fatalf("Failed to save image as WebP: %v", err) } log.Println("Successfully saved processed images") }

Pre-generated Packages

vipsgen provides pre-generated bindings for the following libvips versions. All packages use the same vips package name and API - only the import path differs.

| Import Path | libvips Version | |-------------|----------------| | github.com/cshum/vipsgen/v2/vips | 8.18.x | | github.com/cshum/vipsgen/v2/vips817 | 8.17.x | | github.com/cshum/vipsgen/v2/vips816 | 8.16.x |

Important: Only import ONE of these packages in your project. Choose based on your installed libvips version.

Check your libvips version with vips --version, then use the corresponding import:

// For libvips 8.18.x (latest - recommended)
import "github.com/cshum/vipsgen/v2/vips"

// For libvips 8.17.x import "github.com/cshum/vipsgen/v2/vips817"

// For libvips 8.16.x import "github.com/cshum/vipsgen/v2/vips816"

func main() { // API is identical across all versions img, err := vips.NewImageFromFile("input.jpg", nil) if err != nil { log.Fatal(err) } defer img.Close() err = img.Resize(0.5, nil) // ... }

Image Loaders

Generic loaders — NewImageFromFile, NewImageFromBuffer, NewImageFromSource — automatically detect the image format and accept LoadOptions, a generic options struct covering common options across formats. Since not every format supports every option, use the format-specific loaders — NewGifload, NewJpegloadBuffer, NewPngloadSource, etc. — for precise, type-safe control. A few common examples:

Animated GIF — N: -1 loads all frames (full example):

image, err := vips.NewGifload("animation.gif", &vips.GifloadOptions{
    N: -1, // -1 = load all frames
})

JPEG auto-rotation — rotate by EXIF orientation on load:

source := vips.NewSource(reader)
defer source.Close()

image, err := vips.NewJpegloadSource(source, &vips.JpegloadSourceOptions{ Autorotate: true, })

Working with Animated Images

libvips represents multi-frame images (animated GIF, WebP) as a single vertically stacked image where each frame occupies one page of height PageHeight. vipsgen exposes the page metadata and provides dedicated helpers for operations that must process each frame individually.

Metadata

img.Pages()                 // number of frames
img.PageHeight()            // height of a single frame in pixels
delays, _ := img.PageDelay() // per-frame delay in milliseconds
img.Loop()                  // loop count (0 = infinite)

Multi-page Helpers

Some operations — rotate, crop, embed — cannot be expressed as a single libvips pipeline call across a stacked image. vipsgen ships hand-written C helpers that loop over frames internally, so there is no per-frame CGo overhead from Go:

// Rotate all frames
err = img.RotMultiPage(vips.AngleD90)

// Crop all frames to the same region err = img.ExtractAreaMultiPage(left, top, width, height)

// Embed (pad/extend) all frames to a new canvas err = img.EmbedMultiPage(left, top, newWidth, newHeight, &vips.EmbedMultiPageOptions{ Extend: vips.ExtendBackground, Background: []float64{0, 0, 0, 0}, })

These methods automatically fall through to the equivalent single-frame operation when the image has only one page.

Code Generation

Code generation requires libvips to be built with GObject introspection support.

go install github.com/cshum/vipsgen/v2/cmd/vipsgen@latest

Generate the bindings:

vipsgen -out ./vips

Use your custom-generated code:

package main

import ( "yourproject/vips" )

Command Line Options

Usage of vipsgen:
  -debug
        Enable debug json output
  -extract
        Extract embedded templates to a directory
  -extract-dir string
        Directory to extract templates to (default "./templates")
  -include-test
        Include test files in generated output
  -out string
        Output directory (default "./vips")
  -templates string
        Template directory (uses embedded templates if not specified)

How Code Generation Works

The generation process involves multiple layers to provide a type-safe, idiomatic Go API:

  1. Introspection Analysis: vipsgen uses GObject introspection to analyze the libvips API, extracting operation metadata, argument types, and enum definitions.
  1. Multi-Layer Generation: To create type-safe, idiomatic Go APIs from libvips dynamic parameter system, vipsgen creates a layered approach that handles both required and optional parameters.
  1. Type-Safe Bindings: The generated code is fully type-safe with proper Go types, structs, and enums based on centralized introspection data.
┌─────────────────────────────────────────────────────────────┐
│                    Go Method Layer                          │
│  • Methods on *Image struct                                 │
│  • Go enums and structs                                     │  
│  • Options structs for optional parameters                  │
│  • Type conversions (Go <-> C)                              │
└─────────────────────────────────────────────────────────────┘
                               │
┌─────────────────────────────────────────────────────────────┐
│                   Go Binding Layer                          │
│  • vipsgenAbc() - required parameters only                  │
│  • vipsgenAbcWithOptions() - with optional parameters       │
│  • C array handling and memory management                   │
│  • String conversions and cleanup                           │
│  • Error handling and resource management                   │
└─────────────────────────────────────────────────────────────┘
                               │
┌─────────────────────────────────────────────────────────────┐
│                     C Layer                                 │
│  • vipsgen_abc() - required args only                       │
│  • vipsgen_abc_with_options() - all parameters              │
│  • VipsOperation dynamic dispatch                           │
│  • Proper VipsArray creation and cleanup                    │
└─────────────────────────────────────────────────────────────┘
                               │
┌─────────────────────────────────────────────────────────────┐
│                    libvips                                  │
│  • vips_abc() - original variadic functions                 │
│  • VipsOperation object system                              │
│  • GObject introspection metadata                           │
└─────────────────────────────────────────────────────────────┘

1. C Layer (vips.c/vips.h)

Problem: libvips dynamic parameter system with variadic functions like vips_resize(in, &out, scale, "kernel", kernel, ...) does not translate well to type-safe, idiomatic Go APIs.

Solution: Generate two types of C wrapper functions:

// Basic function - required arguments only, calls vips_resize directly
int vipsgen_resize(VipsImage in, VipsImage* out, double scale) {
    return vips_resize(in, out, scale, NULL);
}

// With options - uses VipsOperation for optional parameters. An option that libvips // could read as zero carries a set flag, so an explicit zero is passed on instead of // being mistaken for "not supplied". int vipsgen_resize_with_options(VipsImage in, VipsImage* out, double scale, VipsKernel kernel, int kernel_set, double gap, int gap_set, double vscale) { VipsOperation *operation = vips_operation_new("resize"); if (!operation) return 1; if ( vips_object_set(VIPS_OBJECT(operation), "in", in, NULL) || vips_object_set(VIPS_OBJECT(operation), "scale", scale, NULL) || (kernel_set && vips_object_set(VIPS_OBJECT(operation), "kernel", kernel, NULL)) || (gap_set && vips_object_set(VIPS_OBJECT(operation), "gap", gap, NULL)) || vipsgen_set_double(operation, "vscale", vscale) ) { g_object_unref(operation); return 1; } int result = vipsgen_operation_execute(operation, "out", out, NULL); return result; }

This layer handles VipsArray creation/cleanup, VipsOperation lifecycle management, type-specific setters.

2. Go Binding Layer (vips.go)

Problem: C arrays, string management, and complex type conversions.

Solution: Generate Go wrapper functions that handle CGO complexity:

// vipsgenResize vips_resize resize an image
func vipsgenResize(in C.VipsImage, scale float64) (C.VipsImage, error) {
    var out *C.VipsImage
    if err := C.vipsgen_resize(in, &out, C.double(scale)); err != 0 {
        return nil, handleImageError(out)
    }
    return out, nil
}

// vipsgenResizeWithOptions vips_resize resize an image with optional arguments func vipsgenResizeWithOptions(in C.VipsImage, scale float64, kernel Kernel, gap float64, vscale float64) (C.VipsImage, error) { var out *C.VipsImage ckernel, kernelSet := optionInt(kernel) cgap, gapSet := optionFloat(gap) if err := C.vipsgen_resize_with_options(in, &out, C.double(scale), C.VipsKernel(ckernel), kernelSet, C.double(cgap), gapSet, C.double(vscale)); err != 0 { return nil, handleImageError(out) } return out, nil }

This layer handles C array conversion, string conversion with cleanup, memory management, error handling, and type conversion between Go and C. optionInt, optionFloat and optionString turn a nil field into "not supplied".

3. Go Method Layer (image.go)

Problem: Provide idiomatic Go API with proper encapsulation.

Solution: Generate methods on *Image struct that encapsulate the two-function approach with options pattern:

// ResizeOptions optional arguments for vips_resize
type ResizeOptions struct {
    // Kernel Resampling kernel (default KernelLanczos3)
    Kernel *Kernel
    // Gap Reducing gap (range 0-1000000, default 2)
    Gap *float64
    // Vscale Vertical scale image by this factor (range 0-10000000)
    Vscale float64
}

// DefaultResizeOptions creates default value for vips_resize optional arguments func DefaultResizeOptions() *ResizeOptions { return &ResizeOptions{ Kernel: Ptr(KernelLanczos3), Gap: Ptr(2), } }

// Resize vips_resize resize an image func (r Image) Resize(scale float64, options ResizeOptions) (error) { if options != nil { // Use the WithOptions variant when options are provided out, err := vipsgenResizeWithOptions(r.image, scale, options.Kernel, options.Gap, options.Vscale) if err != nil { return err } r.setImage(out) return nil } // Use the basic variant for required parameters only out, err := vipsgenResize(r.image, scale) if err != nil { return err } r.setImage(out) return nil }

This layer provides idiomatic Go methods, options structs for optional parameters, Go type system integration.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Development Workflow

Do not commit generated code in vips*/ directories. Generated bindings are created automatically by CI.

For contributors: Submit PRs with source code changes only. Maintainers will regenerate bindings after merge.

For maintainers: After merging a fork PR, manually run the CI workflow from the Actions tab to regenerate bindings.

Generator conventions live in AGENTS.md.

License

MIT

Chat with me