http://rubygems.org/gems/ruby-vips
To celebrate, here's a tiny demo program in Ruby. It's yet another version of vipsthumbnail, though less complete.
#!/usr/bin/env ruby
require 'rubygems'
require 'vips'
include VIPS
# sample image shrinker for ruby-vips
# target size we shrink to ... the image will fit inside a size x size box
size = 100
# we apply a slight sharpen to thumbnails to make then "pop" a bit
mask = [
[-1, -1, -1],
[-1, 32, -1,],
[-1, -1, -1]
]
m = Mask.new mask, 24, 0
# show what we do
# verbose = true
verbose = false
ARGV.each do |filename|
puts "loop #{filename} ..."
# get the image size ... ,new() only decompresses pixels on access, just
# opening and getting properties is fast
#
# this will decode small images to a memory buffer, large images to a
# temporary disc file which is then mapped
#
# vips also supports sequential mode access, where the image is directly
# streamed from the source, through the decode library, to the destination,
# but ruby-vips does not yet expose this, unfortunately
#
# see
#
# http://libvips.blogspot.co.uk/2012/02/sequential-mode-read.html
#
# enabling this mode would help speed up tiff and png thumbnailing and
# reduce disc traffic, must get around to adding it to ruby-vips
a = Image.new(filename)
# largest dimension
d = [a.x_size, a.y_size].max
shrink = d / size.to_f
puts "shrink of #{shrink}" if verbose
# jpeg images can be shrunk very quickly during load, by a factor of 2,
# 4 or 8
#
# if this is a jpeg, turn on shrink-on-load
#
# a better file type test would be good here :-( vips has a nice one, but
# it's not exposed in ruby-vips yet
if filename.end_with? ".jpg"
if shrink >= 8
load_shrink = 8
elsif shrink >= 4
load_shrink = 4
elsif shrink >= 2
load_shrink = 4
end
puts "jpeg shrink on load of #{load_shrink}" if verbose
a = Image.jpeg(filename, :shrink_factor => load_shrink)
# and recalculate the shrink we need, since the dimensions have changed
d = [a.x_size, a.y_size].max
shrink = d / size.to_f
end
# we shrink in two stages: we use a box filter (each pixel in output is the
# average of a m x n box of pixels in the input) to shrink by the largest
# integer factor we can, then an affine transform to get down to the exact
# size we need
#
# if you just shrink with the affine, you'll get bad aliasing for large
# shrink factors (more than x2)
ishrink = shrink.to_i
# size after int shrink
id = (d / ishrink).to_i
# therefore residual float scale (note! not shrink)
rscale = size.to_f / id
puts "block shrink by #{ishrink}" if verbose
puts "residual scale by #{rscale}" if verbose
# vips has other interpolators, eg. :nohalo ... see the output of "vips list
# classes" at the command-line
#
# :bicubic is well-known and mostly good enough
a = a.shrink(ishrink).affinei_resize(:bicubic, rscale)
# this will look a little "soft", apply a gentle sharpen
a = a.conv(m)
# finally ... write to the output
#
# this call will run the pipeline we have built above across all your CPUs,
# though for a simple pipeline like this you'll be spending most of your
# time in the file import / export libraries, which are generally
# single-threaded
a = JPEGWriter.new(a, {:quality => 50})
a.write('test.jpg')
# force the GC to run and free up any memory vips is hanging on to
#
# without this you'll see memuse slowly climb until ruby runs the GC
# for you
#
# something to make ruby-vips drop refs to vips objects explicitly would
# be nice
GC.start
end
Speed and memory use
I tested the program like this:$ for i in {1..100}; do cp ~/pics/wtc.jpg t_$i.jpg; done(where wtc.jpg is a 10,000 x 10,000 pixel RGB JPEG image)
$ soak.rb t_*.jpg
On my laptop (6,1 macbook running ubuntu 12.04, ruby-vips 0.1.1, libvips 7.28.7) I see:
ie. about 0.5s real time to do a high-quality shrink of a 10k x 10k rgb jpg$ time ./soak.rb t_*.jpgreal 0m47.809suser 0m33.626ssys 0m1.516s
I see a steady ~180mb of memuse as this program runs (watching RES in top). 100mb of this is the vips operation cache, we could disable this to get memuse down further if necessary.
Timing vs. ImageMagick
ImageMagick is also pretty quick on jpg images, since it does the shrink-on-load trick as well:However on formats that don't support shrink-on-load, you'll see a large speed difference:$ time convert -define jpeg:size=256x256 t_1.jpg -resize 100x100 test.jpgreal 0m0.424s
user 0m0.468s
sys 0m0.028s
$ time ./soak.rb wtc.tifImageMagick has the -thumbnail option, which is quicker, but it does not do the block average and therefore will suffer from moire. I've used -resize since it's closer to what ruby-vips is doing.
real 0m1.560s
user 0m1.096s
sys 0m0.876s
$ time convert wtc.tif -resize 100x100 test.jpg
real 0m11.416s
user 0m9.361s
sys 0m5.192s
A new version of the gem is now done: 0.2.0.
ReplyDeleteThis disables the operation cache (saving around 100mb from the memuse) and adds support for sequential mode, speeding it up and reducing disc traffic.
ruby-vips looks really neat, but having very little experience with image processing, I am still struggling. I am wondering if you can help me out a little. I'm trying to create a ruby version of the daltonize algorithm for converting images for different forms of colour blindness. ruby-vips seems like a perfect candidate, but I'm stuck at the basics...
ReplyDeleteI posted a question on stackoverflow with some links and a little bit about the things I'm stuck with. I guess at this point I'm not even sure which *questions* to ask :)
http://stackoverflow.com/questions/13801073/first-steps-using-ruby-vips
Any help would be much appreciated.
Hi Yoav, sorry, I didn't get a mail from blogger telling me that a comment needed moderation, I've only just seen this. I'll check my settings.
ReplyDeleteI'll add some stuff to your stackoverflow question, thanks for pointing me to it.