module Vips

This module provides a set of overrides for the [vips image processing library](www.vips.ecs.soton.ac.uk) used via the [gobject-introspection gem](rubygems.org/gems/gobject-introspection).

It needs vips-8.2 or later to be installed, and ‘Vips-8.0.typelib`, the vips typelib, needs to be on your `GI_TYPELIB_PATH`.

# Example

“‘ruby require ’vips8’

if ARGV.length < 2

raise "usage: #{$PROGRAM_NAME}: input-file output-file"

end

im = Vips::Image.new_from_file ARGV, :access => :sequential

im *= [1, 2, 1]

mask = Vips::Image.new_from_array [

[-1, -1, -1],
[-1, 16, -1],
[-1, -1, -1]], 8

im = im.conv mask

im.write_to_file ARGV “‘

This example loads a file, boosts the green channel (I’m not sure why), sharpens the image, and saves it back to disc again.

Reading this example line by line, we have:

“‘ruby im = Vips::Image.new_from_file ARGV, :access => :sequential “`

{Image.new_from_file} can load any image file supported by vips. In this example, we will be accessing pixels top-to-bottom as we sweep through the image reading and writing, so ‘:sequential` access mode is best for us. The default mode is `:random`, this allows for full random access to image pixels, but is slower and needs more memory. See {Access} for full details on the various modes available. You can also load formatted images from memory buffers or create images that wrap C-style memory arrays.

The next line:

“‘ruby im *= [1, 2, 1] “`

Multiplying the image by an array constant uses one array element for each image band. This line assumes that the input image has three bands and will double the middle band. For RGB images, that’s doubling green.

Next we have:

“‘ruby mask = Vips::Image.new_from_array [

[-1, -1, -1],
[-1, 16, -1],
[-1, -1, -1]], 8

im = im.conv mask “‘

{Image.new_from_array} creates an image from an array constant. The 8 at the end sets the scale: the amount to divide the image by after integer convolution. See the libvips API docs for ‘vips_conv()` (the operation invoked by {Vips::Image.conv}) for details on the convolution operator.

Finally:

“‘ruby im.write_to_file ARGV “`

{Vips::Image.write_to_file} writes an image back to the filesystem. It can write any format supported by vips: the file type is set from the filename suffix. You can also write formatted images to memory buffers, or dump image data to a raw memory array.

# How it works

The C sources to libvips include a set of specially formatted comments which describe its interfaces. When you compile the library, gobject-introspection generates ‘Vips-8.0.typelib`, a file describing how to use libvips.

The ‘gobject-introspection` gem loads this typelib and uses it to let you call functions in libvips directly from Ruby. However, the interface you get from raw gobject-introspection is rather ugly, so the `ruby-vips8` gem adds a set of overrides which try to make it nicer to use.

The API you end up with is a Ruby-ish version of the [VIPS C API](www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/). Full documentation on the operations and what they do is there, you can use it directly. This document explains the extra features of the Ruby API and lists the available operations very briefly.

# Automatic wrapping

‘ruby-vips8` adds a {Image.method_missing} handler to {Image} and uses it to look up vips operations. For example, the libvips operation `add`, which appears in C as `vips_add()`, appears in Ruby as {Vips::Image.add}.

The operation’s list of required arguments is searched and the first input image is set to the value of ‘self`. Operations which do not take an input image, such as {Image.black}, appear as class methods. The remainder of the arguments you supply in the function call are used to set the other required input arguments. If the final supplied argument is a hash, it is used to set any optional input arguments. The result is the required output argument if there is only one result, or an array of values if the operation produces several results. If the operation has optional output objects, they are returned as a final hash.

For example, {Vips::Image.min}, the vips operation that searches an image for the minimum value, has a large number of optional arguments. You can use it to find the minimum value like this:

“‘ruby min_value = image.min “`

You can ask it to return the position of the minimum with ‘:x` and `:y`.

“‘ruby min_value, opts = min :x => true, :y => true x_pos = opts y_pos = opts “`

Now ‘x_pos` and `y_pos` will have the coordinates of the minimum value. There’s actually a convenience function for this, {Vips::Image.minpos}.

You can also ask for the top n minimum, for example:

“‘ruby min_value, opts = min :size => 10, :x_array => true, :y_array => true x_pos = opts y_pos = opts “`

Now ‘x_pos` and `y_pos` will be 10-element arrays.

Because operations are member functions and return the result image, you can chain them. For example, you can write:

“‘ruby result_image = image.real.cos “`

to calculate the cosine of the real part of a complex image. There are also a full set of arithmetic operator overloads, see below.

libvips types are also automatically wrapped. The override looks at the type of argument required by the operation and converts the value you supply, when it can. For example, {Vips::Image.linear} takes a ‘VipsArrayDouble` as an argument for the set of constants to use for multiplication. You can supply this value as an integer, a float, or some kind of compound object and it will be converted for you. You can write:

“‘ruby result_image = image.linear 1, 3 result_image = image.linear 12.4, 13.9 result_image = image.linear [1, 2, 3], [4, 5, 6] result_image = image.linear 1, [4, 5, 6] “`

And so on. A set of overloads are defined for {Vips::Image.linear}, see below.

It does a couple of more ambitious conversions. It will automatically convert to and from the various vips types, like ‘VipsBlob` and `VipsArrayImage`. For example, you can read the ICC profile out of an image like this:

“‘ruby profile = im.get_value “icc-profile-data” “`

and profile will be a byte array.

If an operation takes several input images, you can use a constant for all but one of them and the wrapper will expand the constant to an image for you. For example, {Vips::Image.ifthenelse} uses a condition image to pick pixels between a then and an else image:

“‘ruby result_image = condition_image.ifthenelse then_image, else_image “`

You can use a constant instead of either the then or the else parts and it will be expanded to an image for you. If you use a constant for both then and else, it will be expanded to match the condition image. For example:

“‘ruby result_image = condition_image.ifthenelse [0, 255, 0], [255, 0, 0] “`

Will make an image where true pixels are green and false pixels are red.

This is useful for {Vips::Image.bandjoin}, the thing to join two or more images up bandwise. You can write:

“‘ruby rgba = rgb.bandjoin 255 “`

to append a constant 255 band to an image, perhaps to add an alpha channel. Of course you can also write:

“‘ruby result_image = image1.bandjoin image2 result_image = image1.bandjoin [image2, image3] result_image = Vips::Image.bandjoin [image1, image2, image3] result_image = image1.bandjoin [image2, 255] “`

and so on.

# Automatic YARD documentation

The bulk of these API docs are generated automatically by {Vips::generate_yard}. It examines libvips and writes a summary of each operation and the arguments and options that operation expects.

Use the [C API docs](www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips) for more detail.

# Exceptions

The wrapper spots errors from vips operations and raises the {Vips::Error} exception. You can catch it in the usual way.

# Draw operations

Paint operations like {Vips::Image.draw_circle} and {Vips::Image.draw_line} modify their input image. This makes them hard to use with the rest of libvips: you need to be very careful about the order in which operations execute or you can get nasty crashes.

The wrapper spots operations of this type and makes a private copy of the image in memory before calling the operation. This stops crashes, but it does make it inefficient. If you draw 100 lines on an image, for example, you’ll copy the image 100 times. The wrapper does make sure that memory is recycled where possible, so you won’t have 100 copies in memory.

If you want to avoid the copies, you’ll need to call drawing operations yourself.

# Overloads

The wrapper defines the usual set of arithmetic, boolean and relational overloads on image. You can mix images, constants and lists of constants (almost) freely. For example, you can write:

“‘ruby result_image = ((image * [1, 2, 3]).abs < 128) | 4 “`

# Expansions

Some vips operators take an enum to select an action, for example {Vips::Image.math} can be used to calculate sine of every pixel like this:

“‘ruby result_image = image.math :sin “`

This is annoying, so the wrapper expands all these enums into separate members named after the enum. So you can write:

“‘ruby result_image = image.sin “`

# Convenience functions

The wrapper defines a few extra useful utility functions: {Vips::Image.get_value}, {Vips::Image.set_value}, {Vips::Image.bandsplit}, {Vips::Image.maxpos}, {Vips::Image.minpos}, {Vips::Image.median}.

Constants

LOG_DOMAIN

@private

Public Class Methods

call(name, *args) click to toggle source

This is the public entry point for the vips8 binding. {call} will run any vips operation, for example:

“‘ruby out = Vips::call “black”, 100, 100, :bands => 12 “`

will call the C function

“‘C vips_black( &out, 100, 100, “bands”, 12, NULL ); “`

There are {Image#method_missing} hooks which will run {call} for you on {Image} for undefined instance or class methods. So you can also write:

“‘ruby out = Vips::Image.black 100, 100, :bands => 12 “`

Or perhaps:

“‘ruby x = Vips::Image.black 100, 100 y = x.invert “`

to run the ‘vips_invert()` operator.

There are also a set of operator overloads and some convenience functions, see {Image}.

If the operator needs a vector constant, {call} will turn a scalar into a vector for you. So for ‘x.linear(a, b)`, which calculates `x * a + b` where `a` and `b` are vector constants, you can write:

“‘ruby x = Vips::Image.black 100, 100, :bands => 3 y = x.linear(1, 2) y = x.linear(, 4) y = x.linear([1, 2, 3], 4) “`

or any other combination. The operator overloads use this facility to support all the variations on:

“‘ruby x = Vips::Image.black 100, 100, :bands => 3 y = x * 2 y = x + [1,2,3] y = x % [1] “`

Similarly, whereever an image is required, you can use a constant. The constant will be expanded to an image matching the first input image argument. For example, you can write:

“‘ x = Vips::Image.black 100, 100, :bands => 3 y = x.bandjoin(255) “`

to add an extra band to the image where each pixel in the new band has the constant value 255.

# File lib/vips8/call.rb, line 298
def self.call(name, *args)
    Vips::call_base name, nil, "", args
end
const_missing(name) click to toggle source

@private

Calls superclass method
# File lib/vips8.rb, line 44
def const_missing(name)
    log "Vips::const_missing: #{name}"

    init()
    if const_defined?(name)
        const_get(name)
    else
      super
    end
end
generate_yard() click to toggle source

This method generates yard comments for all the dynamically bound vips operations.

Regenerate with something like:

ruby > methods.rb
require 'vips8'; Vips::generate_yard
^D
# File lib/vips8/image.rb, line 1249
def self.generate_yard
    # these have hand-written methods, see above
    no_generate = ["bandjoin", "ifthenelse"]

    generate_operation = lambda do |op|
        flags = op.flags
        return if (flags & :deprecated) != 0
        nickname = Vips::nickname_find op.gtype

        return if no_generate.include? nickname

        all_args = op.get_args.select {|arg| not arg.isset}

        # separate args into various categories
 
        required_input = all_args.select do |arg|
            (arg.flags & :input) != 0 and
            (arg.flags & :required) != 0 
        end

        optional_input = all_args.select do |arg|
            (arg.flags & :input) != 0 and
            (arg.flags & :required) == 0 
        end

        required_output = all_args.select do |arg|
            (arg.flags & :output) != 0 and
            (arg.flags & :required) != 0 
        end

        # required input args with :modify are copied and appended to
        # output
        modified_required_input = required_input.select do |arg|
            (arg.flags & :modify) != 0 
        end
        required_output += modified_required_input

        optional_output = all_args.select do |arg|
            (arg.flags & :output) != 0 and
            (arg.flags & :required) == 0 
        end

        # optional input args with :modify are copied and appended to
        # output
        modified_optional_input = optional_input.select do |arg|
            (arg.flags & :modify) != 0 
        end
        optional_output += modified_optional_input

        # find the first input image, if any ... we will be a method of this
        # instance
        member_x = required_input.find do |x|
            x.gtype.type_is_a? GLib::Type["VipsImage"]
        end
        if member_x != nil
            required_input.delete member_x
        end

        print "# @!method "
        print "self." if not member_x 
        print "#{nickname}("
        print required_input.map(&:name).join(", ")
        puts ", opts = {})"

        puts "#   #{op.description.capitalize}."

        required_input.each do |arg| 
            puts "#   @param #{arg.name} [#{arg.type}] #{arg.blurb}"
        end

        puts "#   @param [Hash] opts Set of options"
        optional_input.each do |arg| 
            puts "#   @option opts [#{arg.type}] :#{arg.name} #{arg.blurb}"
        end
        optional_output.each do |arg| 
            print "#   @option opts [#{arg.type}] :#{arg.name}"
            puts " Output #{arg.blurb}"
        end

        print "#   @return ["
        if required_output.length == 0 
            print "nil" 
        elsif required_output.length == 1 
            print required_output[0].type
        elsif 
            print "Array<" 
            print required_output.map(&:type).join(", ")
            print ">" 
        end
        if optional_output.length > 0
            print ", Hash<Symbol => Object>"
        end
        print "] "
        print required_output.map(&:blurb).join(", ")
        if optional_output.length > 0
            print ", " if required_output.length > 0
            print "Hash of optional output items"
        end
        puts ""

        puts ""
    end

    generate_class = lambda do |gtype|
        begin
            # can fail for abstract types
            # can't find a way to get to #abstract? from a gtype
            op = Vips::Operation.new gtype.name
        rescue
            op = nil
        end

        generate_operation.(op) if op

        gtype.children.each do |x|
            generate_class.(x)
        end
    end

    puts "module Vips"
    puts "  class Image"
    puts ""

    # gobject-introspection 3.0.7 crashes a lot if it GCs while doing
    # something
    GC.disable

    generate_class.(GLib::Type["VipsOperation"])

    puts "  end"
    puts "end"
end
init(*argv) click to toggle source

@private

# File lib/vips8.rb, line 68
def init(*argv)
    log "Vips::init: #{argv}"

    class << self
        remove_method(:init)
        remove_method(:const_missing)
        remove_method(:method_missing)
    end

    loader = Loader.new(self, argv)
    begin
        loader.load("Vips")
    rescue 
        puts "Unable to load Vips"
        puts "  Check that the vips library has been installed and is"
        puts "  on your library path."
        puts "  Check that the typelib `Vips-8.0.typelib` has been "
        puts "  installed, and that it is on your GI_TYPELIB_PATH."
        raise
    end

    require 'vips8/error'
    require 'vips8/argument'
    require 'vips8/operation'
    require 'vips8/call'
    require 'vips8/image'
end
method_missing(name, *args, &block) click to toggle source

@private

Calls superclass method
# File lib/vips8.rb, line 56
def method_missing(name, *args, &block)
    log "Vips::method_missing: #{name}, #{args}, #{block}"

    init()
    if respond_to?(name)
        __send__(name, *args, &block)
    else
        super
    end
end
set_debug(dbg) click to toggle source

Turn debug logging on and off.

@param dbg [Boolean] Set true to print debug log messages

# File lib/vips8.rb, line 38
def self.set_debug dbg 
    $vips_debug = dbg
end

Private Class Methods

call_base(name, instance, option_string, supplied_values) click to toggle source

run call_base_nogc, with the GC disabled

# File lib/vips8/call.rb, line 218
def self.call_base(name, instance, option_string, supplied_values)
    gc_was_enabled = GC.disable
    begin
        result = call_base_nogc name, instance, 
            option_string, supplied_values
    ensure
        GC.enable if gc_was_enabled
    end

    return result
end
call_base_nogc(name, instance, option_string, supplied_values) click to toggle source

call a vips operation … this will crash if there’s a GC during the call, I’ve really no idea why :-(

Workaround: don’t call this directly, use call_base (see below) instead. This will disable the GC, call this operation, then reenable it.

# File lib/vips8/call.rb, line 10
def self.call_base_nogc(name, instance, option_string, supplied_values)
    log "in Vips::call_base"
    log "name = #{name}"
    log "instance = #{instance}"
    log "option_string = #{option_string}"
    log "supplied_values are:"
    supplied_values.each {|x| log "   #{x}"}

    if supplied_values.last.is_a? Hash
        optional_values = supplied_values.last
        supplied_values.delete_at -1
    else
        optional_values = {}
    end

    begin
        op = Vips::Operation.new name
    rescue
        raise Vips::Error, "no operator '#{name}'"
    end

    # set string options first
    log "setting string options ..."
    if option_string
        if op.set_from_string(option_string) != 0
            raise Error
        end
    end

    all_args = op.get_args

    # the instance, if supplied, must be a vips image ... we use it for
    # match_image, below
    if instance and not instance.is_a? Vips::Image
        raise Vips::Error, "@instance is not a Vips::Image."
    end

    # if the op needs images but the user supplies constants, we expand
    # them to match the first input image argument ... find the first
    # image
    log "searching for first image argument ..."
    match_image = instance
    if match_image == nil
        match_image = supplied_values.find {|x| x.is_a? Vips::Image}
    end
    if match_image == nil
        match = optional_values.find do |name, value|
            value.is_a? Vips::Image
        end
        # if we found a match, it'll be [name, value]
        if match
            match_image = match[1]
        end
    end

    # find unassigned required input args
    log "finding unassigned required input arguments ..."
    required_input = all_args.select do |arg|
        not arg.isset and
        (arg.flags & :input) != 0 and
        (arg.flags & :required) != 0 
    end

    # do we have a non-nil instance? set the first image arg with this
    if instance != nil
        log "setting first image arg with instance ..."
        x = required_input.find do |arg|
            gtype = GLib::Type["VipsImage"]
            vtype = arg.prop.value_type

            vtype.type_is_a? gtype
        end
        if x == nil
            raise Vips::Error, 
                "No #{instance.class} argument to #{name}."
        end
        x.set_value match_image, instance
        required_input.delete x
    end

    if required_input.length != supplied_values.length
        raise Vips::Error, 
            "Wrong number of arguments. '#{name}' requires " +
            "#{required_input.length} arguments, you supplied " +
            "#{supplied_values.length}."
    end

    log "setting required input arguments ..."
    required_input.zip(supplied_values).each do |arg, value|
        arg.set_value match_image, value
    end

    # find optional unassigned input args
    log "finding optional unassigned input arguments ..."
    optional_input = all_args.select do |arg|
        not arg.isset and
        (arg.flags & :input) != 0 and
        (arg.flags & :required) == 0 
    end

    # make a hash from name to arg
    optional_input = Hash[
        optional_input.map(&:name).zip(optional_input)]

    # find optional unassigned output args
    log "finding optional unassigned output arguments ..."
    optional_output = all_args.select do |arg|
        not arg.isset and
        (arg.flags & :output) != 0 and
        (arg.flags & :required) == 0 
    end
    optional_output = Hash[
        optional_output.map(&:name).zip(optional_output)]

    # set all optional args
    log "setting optional values ..."
    optional_values.each do |name, value|
        # we are passed symbols as keys
        name = name.to_s
        if optional_input.has_key? name
            log "setting #{name} to #{value}"
            optional_input[name].set_value match_image, value
        elsif optional_output.has_key? name and value != true
            raise Vips::Error, 
                "Optional output argument #{name} must be true."
        elsif not optional_output.has_key? name 
            raise Vips::Error, "No such option '#{name}',"
        end
    end

    log "building ..."

    op2 = Vips::cache_operation_lookup op
    if op2
        log "cache hit"
        op = op2

        all_args = op.get_args

        # find optional output args
        optional_output = all_args.select do |arg|
            (arg.flags & :output) != 0 and
            (arg.flags & :required) == 0 
        end
        optional_output = Hash[
            optional_output.map(&:name).zip(optional_output)]
    else
        log "cache miss ... building"
        if not op.build
            raise Vips::Error
        end
        showall

        log "adding to cache ... "
        Vips::cache_operation_add op
    end

    log "fetching outputs ..."

    # gather output args
    out = []

    all_args.each do |arg|
        # required output
        if (arg.flags & :output) != 0 and
            (arg.flags & :required) != 0 
            log "fetching required output #{arg.name}"
            out << arg.get_value
        end

        # modified input arg ... this will get the result of the
        # copy() we did in Argument.set_value above
        if (arg.flags & :input) != 0 and
            (arg.flags & :modify) != 0 
            log "fetching modified input arg ..."
            out << arg.get_value
        end
    end

    opts = {}
    optional_values.each do |name, value|
        # we are passed symbols as keys
        name = name.to_s
        if optional_output.has_key? name
            log "fetching optional output arg ..."
            opts[name] = optional_output[name].get_value
        end
    end
    out << opts if opts != {}

    if out.length == 1
        out = out[0]
    elsif out.length == 0
        out = nil
    end

    log "unreffing outputs ..."
    op.unref_outputs
    op = nil
    # showall

    log "success! #{name}.out = #{out}"

    return out
end