vipsgen

repository·main·Indexed 42 days ago

https://github.com/cshum/vipsgen

A Go binding generator for libvips that provides type-safe, idiomatic, and comprehensive Go bindings for the libvips image processing library. It includes a CLI tool for generating custom bindings via GObject introspection and provides pre-generated packages for libvips versions 8.16.x, 8.17.x, and 8.18.x.

Tokens
4.5K
Snippets
14
Records
16
Agent score
54%

What's inside vipsgen

  1. How vipsgen code generation works

    main

    vipsgen uses a multi-layered approach to transform the dynamic, variadic libvips C API into a type-safe, idiomatic Go API. The layers are:

    1. Introspection Analysis: Uses GObject introspection to extract operation metadata, argument types, and enums from libvips.
    2. C Layer (vips.c/vips.h): Generates two types of C wrappers:
      • Basic functions: Handle required arguments only (e.g., vipsgen_resize).
      • With options functions: Use VipsOperation to handle optional parameters via dynamic dispatch (e.g., vipsgen_resize_with_options).
    3. Go Binding Layer (vips.go): Handles CGO complexities including C array/string management, memory cleanup, and error handling.
    4. Go Method Layer (image.go): Provides the final idiomatic API. It uses an Options Pattern where methods on the *Image struct accept an options struct (e.g., ResizeOptions) to manage optional parameters.

    This architecture ensures that Go developers interact with type-safe structs and enums rather than raw C pointers and variadic arguments.

  2. Work with animated and multi-page images

    main

    libvips represents multi-frame images (like animated GIFs or WebP) as a single vertically stacked image. vipsgen provides metadata accessors and specialized multi-page helpers to handle these images efficiently.

    Accessing Page Metadata

    Use these methods to inspect the frames within a multi-page image:

    • img.Pages(): Returns the number of frames.
    • img.PageHeight(): Returns the height of a single frame in pixels.
    • img.PageDelay(): Returns per-frame delays in milliseconds.
    • img.Loop(): Returns the loop count (0 for infinite).

    Multi-page Helpers

    Standard operations like rotation or cropping cannot be applied to a stacked image in a single call. vipsgen provides optimized C helpers that loop over frames internally to avoid CGo overhead:

    • img.RotMultiPage(angle): Rotates all frames.
    • img.ExtractAreaMultiPage(left, top, width, height): Crops all frames to the same region.
    • img.EmbedMultiPage(left, top, newWidth, newHeight, options): Pads/extends all frames to a new canvas.
    // 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},
    })
  3. Use pre-generated vipsgen packages

    main

    vipsgen provides pre-generated bindings for specific libvips versions. You should only import one of these packages in your project, based on the version of libvips installed on your system. You can check your version using vips --version.

    Import Pathlibvips Version
    github.com/cshum/vipsgen/vips8.18.x
    github.com/cshum/vipsgen/vips8178.17.x
    github.com/cshum/vipsgen/vips8168.16.x

    The API is identical across all versions.

    // For libvips 8.18.x (latest - recommended)
    import "github.com/cshum/vipsgen/vips"
    
    // For libvips 8.17.x
    import "github.com/cshum/vipsgen/vips817"
    
    // For libvips 8.16.x
    import "github.com/cshum/vipsgen/vips816"
  4. Install and generate vipsgen bindings

    main

    To use custom-generated bindings, you must first ensure that libvips is built with GObject introspection support.

    1. Install the vipsgen CLI tool:
    go install github.com/cshum/vipsgen/cmd/vipsgen@latest
    1. Generate the bindings into a specific directory (e.g., ./vips):
    vipsgen -out ./vips
    1. Import the generated package in your Go project:
    package main
    
    import (
        "yourproject/vips"
    )
    go install github.com/cshum/vipsgen/cmd/vipsgen@latest
    vipsgen -out ./vips
  5. Install vipsgen and libvips

    main

    To use vipsgen, you must first install vips and pkg-config on your system. On macOS, use Homebrew:

    brew install vips pkg-config

    Note for macOS users: You may need to set the following environment variable to allow compilation:

    export CGO_CFLAGS_ALLOW="-Xpreprocessor"

    Once the system dependencies are installed, you can add the pre-generated library to your Go project:

    go get -u github.com/cshum/vipsgen/vips
    brew install vips pkg-config
    export CGO_CFLAGS_ALLOW="-Xpreprocessor"
    go get -u github.com/cshum/vipsgen/vips
  6. Extract embedded templates from vipsgen

    main

    If you want to inspect or modify the templates used by vipsgen, you can use the -extract flag. This will copy the embedded template files to a local directory and then exit, without performing any code generation.

    vipsgen -extract -extract-dir ./my_templates
  7. Use the Options Pattern for libvips operations in Go

    main

    vipsgen provides an idiomatic Go API by using options structs for operations that have optional parameters in libvips.

    For example, to use the Resize method with specific parameters, you should use a ResizeOptions struct and the DefaultResizeOptions() helper to ensure all necessary fields are initialized.

    // ResizeOptions optional arguments for vips_resize
    type ResizeOptions struct {
        Kernel Kernel
        Gap    float64
        Vscale float64
    }
    
    // DefaultResizeOptions creates default value for vips_resize optional arguments
    func DefaultResizeOptions() *ResizeOptions {
        return &ResizeOptions{
            Kernel: Kernel(5),
            Gap:    2,
        }
    }
    
    // Usage example
    func example(img *vips.Image) error {
        opts := vips.DefaultResizeOptions()
        opts.Gap = 4.0 // Override a default
        return img.Resize(2.0, opts)
    }
    // ResizeOptions optional arguments for vips_resize
    type ResizeOptions struct {
        Kernel Kernel
        Gap    float64
        Vscale float64
    }
    
    func DefaultResizeOptions() *ResizeOptions {
        return &ResizeOptions{
            Kernel: Kernel(5),
            Gap:    2,
        }
    }
    
    func (r *Image) Resize(scale float64, options *ResizeOptions) error {
        if options != nil {
            out, err := vipsgenResizeWithOptions(r.image, scale, options.Kernel, options.Gap, options.Vscale)
            if err != nil {
                return err
            }
            r.setImage(out)
            return nil
        }
        out, err := vipsgenResize(r.image, scale)
        if err != nil {
            return err
        }
        r.setImage(out)
        return nil
    }
  8. Retrieve transformation parameters from Mosaic

    main

    The vips_mosaic operation can provide details about how two images were combined. By using vips.DefaultMosaicOptions(), you can access the following transformation parameters after calling Mosaic:

    • Dx0, Dy0: Integer offsets.
    • Scale1: The detected scale.
    • Angle1: The detected rotation in degrees.
    options := vips.DefaultMosaicOptions()
    err := img1.Mosaic(img2, direction, xref, yref, xsec, ysec, options)
    
    // Access transformation parameters
    fmt.Printf("Integer Offset: dx0=%d, dy0=%d\n", options.Dx0, options.Dy0)
    fmt.Printf("Detected Scale: %.3f\n", options.Scale1)
    fmt.Printf("Detected Rotation: %.3f degrees\n", options.Angle1)
  9. Find position coordinates for Max/Min operations

    main

    When using vips_max or vips_min to find extreme pixel values, you can determine the exact location of those values. Pass a vips.DefaultMaxOptions() (or equivalent for Min) object to the method, and the X and Y coordinates of the extreme pixel will be populated in that options object.

    maxOptions := vips.DefaultMaxOptions()
    maxValue, err := img.Max(maxOptions)
    
    // Access position of maximum value
    fmt.Printf("Maximum value: %.2f at position x=%d, y=%d\n", 
        maxValue, maxOptions.X, maxOptions.Y)
  10. Capture attention coordinates with Smartcrop

    main

    When performing a smart crop using vips_smartcrop, you can retrieve the coordinates where the algorithm detected the most interesting content. To do this, use vips.DefaultSmartcropOptions() and access the AttentionX and AttentionY fields from the options object after the operation is executed.

    options := vips.DefaultSmartcropOptions()
    err := img.Smartcrop(width, height, options)
    
    // Access the attention coordinates
    fmt.Printf("Attention Center: x=%d, y=%d\n", options.AttentionX, options.AttentionY)
  11. Get the affected area bounds from Draw Flood

    main

    The vips_draw_flood operation allows you to identify the bounding box of the area modified by the flood fill. Use vips.DefaultDrawFloodOptions() and inspect the Left, Top, Width, and Height fields after the operation completes.

    options := vips.DefaultDrawFloodOptions()
    err := img.DrawFlood(color, x, y, options)
    
    // Access affected area bounds
    fmt.Printf("Affected area: left=%d, top=%d, width=%d, height=%d\n",
        options.Left, options.Top, options.Width, options.Height)
  12. Complete image processing example

    main

    This example demonstrates fetching an image from the web, creating a streaming source, performing a thumbnail resize, adding a yellow border, and saving the result as a WebP file.

    package main
    
    import (
    	"log"
    	"net/http"
    
    	"github.com/cshum/vipsgen/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:              85,   // Quality factor (0-100)
    		Effort:         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")
    }