class Vips::Operation

Public Class Methods

call(name, supplied, optional = {}) click to toggle source

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

“‘ruby out = Vips::Operation.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 [1], 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, wherever 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/vips/operation.rb, line 358
def self.call name, supplied, optional = {}, option_string = ""
  GLib.logger.debug("Vips::VipsOperation.call") {
    "name = #{name}, supplied = #{supplied}, " \
      "optional = #{optional}, option_string = #{option_string}"
  }

  introspect = Introspect.get name
  required_input = introspect.required_input
  required_output = introspect.required_output
  optional_input = introspect.optional_input
  optional_output = introspect.optional_output
  destructive = introspect.destructive

  unless supplied.is_a? Array
    raise Vips::Error, "unable to call #{name}: " \
      "argument array is not an array"
  end
  unless optional.is_a? Hash
    raise Vips::Error, "unable to call #{name}: " \
      "optional arguments are not a hash"
  end

  if supplied.length != required_input.length
    raise Vips::Error, "unable to call #{name}: " \
      "you supplied #{supplied.length} arguments, " \
      "but operation needs #{required_input.length}."
  end

  # all supplied_optional keys should be in optional_input or
  # optional_output
  optional.each do |key, _value|
    arg_name = key.to_s

    unless optional_input.has_key?(arg_name) ||
        optional_output.has_key?(arg_name)
      raise Vips::Error, "unable to call #{name}: " \
        "unknown option #{arg_name}"
    end
  end

  # the first image arg is the thing we expand constants to match ...
  # we need to find it
  #
  # look inside array and hash arguments, since we may be passing an
  # array of images
  #
  # also enforce the rules around mutable and non-mutable images
  match_image = nil
  flat_find(supplied) do |value|
    if match_image
      # no non-first image arg can ever be mutable
      if value.is_a?(MutableImage)
        raise Vips::Error, "unable to call #{name}: " \
          "only the first image argument can be mutable"
      end
    elsif destructive
      if value.is_a?(Image)
        raise Vips::Error, "unable to call #{name}: " \
          "first image argument to a destructive " \
          "operation must be mutable"
      elsif value.is_a?(MutableImage)
        match_image = value
      end
    elsif value.is_a?(MutableImage)
      # non destructive operation, so no mutable images
      raise Vips::Error, "unable to call #{name}: " \
        "must not pass mutable images to " \
        "non-destructive operations"
    elsif value.is_a?(Image)
      match_image = value
    end

    # keep looping
    false
  end

  op = Operation.new introspect.vips_name

  # set any string args first so they can't be overridden
  unless option_string.nil?
    if Vips.vips_object_set_from_string(op, option_string) != 0
      raise Vips::Error
    end
  end

  # dedupe all input references here
  deduped_references = Set.new

  add_reference = lambda do |x|
    if x.is_a?(Vips::Image)
      deduped_references.merge x.references
    end
    false
  end

  # set all required inputs
  required_input.each_index do |i|
    details = required_input[i]
    arg_name = details[:arg_name]
    flags = details[:flags]
    gtype = details[:gtype]
    value = supplied[i]

    flat_find value, &add_reference
    op.set arg_name, value, match_image, flags, gtype, destructive
  end

  # set all optional inputs
  optional.each do |key, value|
    next if value.nil?

    arg_name = key.to_s

    if optional_input.has_key? arg_name
      details = optional_input[arg_name]
      flags = details[:flags]
      gtype = details[:gtype]

      flat_find value, &add_reference
      op.set arg_name, value, match_image, flags, gtype, destructive
    end
  end

  op = op.build

  # we need an array of references for output objects
  references = deduped_references.to_a

  # attach all input refs to output x
  set_reference = lambda do |x|
    # stop early if there are no refs to attach
    return true if references == []

    if x.is_a? Vips::Image
      references.each { |i| x.references << i }
    end

    false
  end

  # get all required results
  result = []
  required_output.each do |details|
    value = op.get(details[:arg_name])
    flat_find value, &set_reference
    result << value
  end

  # fetch all optional ones
  optional_results = {}
  optional.each do |key, _value|
    arg_name = key.to_s

    if optional_output.has_key? arg_name
      value = op.get arg_name
      flat_find value, &set_reference
      optional_results[arg_name] = value
    end
  end

  result << optional_results if optional_results != {}

  if result.length == 1
    result = result.first
  elsif result.length == 0
    result = nil
  end

  GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }

  Vips.vips_object_unref_outputs op

  result
end
flat_find(object) { |object| ... } click to toggle source

Search an object for the first element to match a predicate. Search inside subarrays and sub-hashes. Equlvalent to x.flatten.find{}.

# File lib/vips/operation.rb, line 243
def self.flat_find object, &block
  if object.respond_to? :each
    object.each do |x|
      result = flat_find x, &block
      return result unless result.nil?
    end
  elsif yield object
    return object
  end

  nil
end
imageize(match_image, value) click to toggle source

expand a constant into an image

# File lib/vips/operation.rb, line 257
def self.imageize match_image, value
  return value if value.is_a?(Image) || value.is_a?(MutableImage)

  # 2D array values become tiny 2D images
  # if there's nothing to match to, we also make a 2D image
  if (value.is_a?(Array) && value[0].is_a?(Array)) || match_image.nil?
    Image.new_from_array value
  else
    # we have a 1D array ... use that as a pixel constant and
    # expand to match match_image
    match_image.new_from_image value
  end
end
new(value) click to toggle source
Calls superclass method GObject::GObject::new
# File lib/vips/operation.rb, line 213
def initialize value
  # allow init with a pointer so we can wrap the return values from
  # things like _build
  if value.is_a? String
    value = Vips.vips_operation_new value
    raise Vips::Error if value.null?
  end

  super(value)
end

Public Instance Methods

argument_map(&block) click to toggle source
# File lib/vips/operation.rb, line 234
def argument_map &block
  fn = proc do |_op, pspec, argument_class, argument_instance, _a, _b|
    block.call pspec, argument_class, argument_instance
  end
  Vips.vips_argument_map self, fn, nil, nil
end
build() click to toggle source
# File lib/vips/operation.rb, line 224
def build
  op = Vips.vips_cache_operation_build self
  if op.null?
    Vips.vips_object_unref_outputs self
    raise Vips::Error
  end

  Operation.new op
end
set(name, value, match_image, flags, gtype, destructive) click to toggle source

set an operation argument, expanding constants and copying images as required

Calls superclass method Vips::Object#set
# File lib/vips/operation.rb, line 273
def set name, value, match_image, flags, gtype, destructive
  if gtype == IMAGE_TYPE
    value = Operation.imageize match_image, value

    # in non-destructive mode, make sure we have a unique copy
    if (flags & ARGUMENT_MODIFY) != 0 &&
        !destructive
      value = value.copy.copy_memory
    end
  elsif gtype == ARRAY_IMAGE_TYPE
    value = value.map { |x| Operation.imageize match_image, x }
  end

  super(name, value)
end