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
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
@private
# 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
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
@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
@private
# 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
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
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 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