Search code examples
rubyvips

How to calculate image average colors using ruby-vips v 1.0?


Before ruby-vips 1.0, I can get the array of RGB average colors using:

im.stats[1][4].to_i, im.stats[2][4].to_i, im.stats[3][4].to_i

But after update to ruby-vips 1.0, stats method does not return a ruby array any more. I noticed there is an im.avg() method, but it returns a scalar value, not separate values for RGB channels.

How to calculate image average colors using the new API?


Solution

  • For historical reasons libvips used to have its own array type. It had a whole set of functions for manipulating these arrays, all slightly different from the functions for manipulating images.

    libvips8 has fixed this by removing the array type and using one-band double images instead. So the result of stats is now a small image you can just read pixels values from.

    Use getpoint to read pixels from images:

    http://www.rubydoc.info/gems/ruby-vips/1.0.0/Vips/Image#getpoint-instance_method

    For example:

    $ irb
    irb(main):001:0> require 'vips'
    => true
    irb(main):002:0> im = Vips::Image.new_from_file "/home/john/pics/k2.jpg"
    => #<Vips::Image:0x1c83830 ptr=0x1fb7020>
    irb(main):003:0> im.getpoint(10, 10)
    => [10.0, 0.0, 3.0]
    irb(main):004:0> im.stats
    => #<Vips::Image:0x20d1950 ptr=0x1fb7e30>
    

    The layout of the image you get from stats is in the C docs (ruby-vips is a thin layer over the C API, so all those docs work):

    http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/libvips-arithmetic.html#vips-stats

    So you want column 4, rows 1, 2, 3 for the RGB averages. getpoint returns an array, so for a one-band image, use [0] to get the first element.

    irb(main):007:0> im.stats.getpoint(4, 1)[0]
    => 110.87133688038793
    irb(main):008:0> im.stats.getpoint(4, 2)[0]
    => 101.0471090382543
    irb(main):009:0> im.stats.getpoint(4, 3)[0]
    => 96.45809502963363
    

    This does seem a bit clumsy. Perhaps Image should have a to_a method? It would only make sense for very small images, of course.