[update: and here it is again in ruby-vips 1.0]
I made a tiny watermarking example for someone, I thought it might be useful as a blog post.
The aim of watermarking is to add a visible identifier to an image. It needs to be clearly visible, but not too distracting. A simple technique is to draw some faint text over the image area.
To install ruby-vips, first install libvips, then use "gem install ruby-vips". Install libvips on OS X with homebrew, install on linux with your package manager. I've not tried ruby-vips on Windows.
ruby-vips has quite good documentation, but strangely most of it doesn't appear in the ruby-gems API page, I guess there's some rdoc configuration issue. If you install ruby-vips you'll find the docs appear locally, try "gem server".
In ruby-vips you render text like this:
text = VIPS::Image.text("hello world!", "Sans 12", 500, 1, 300)That draws the text into a 500-pixel wide image, using your computer's best sans-serif 12-point font, at 300 dpi. The 1 means to centre multi-line text: use 0 for left and 2 for right alignment.
Text is drawn as a one-band image with 0 for black, 255 for white, and intermediate values for anti-aliasing. We want rather faint text for a watermark, so we need to scale it down:
text = text.lin(0.3, 0).lin(a,b) calculates a linear transform: output pixels are input pixels times a plus b. 255 times 0.3 is 76.5, so we now have a float image in the range 0 to 76.5.
Float images are slower to process than int ones, so for speed we chop the image back to unsigned char (8-bit):
text = text.clip2fmt(:uchar)Next, we add a large border. .embed() puts an image inside a larger image, with the new pixels made in one of several ways. :black means "fill the new area black".
text = text.embed(:black, 100, 100, text.x_size() + 200, text.y_size() + 200)We need to repeat this tile many times horizontally and vertically to cover the whole of our input image, then from the huge image cut out exactly the sized bit we need:
im = VIPS::Image.new(ARGV[0])Next we need to make a background image. We will use the text image as a stencil to fade pixels between our input image and the this background.
text = text.replicate(1 + im.x_size() / text.x_size(), 1 + im.y_size() / text.y_size())
text = text.extract_area(0, 0, im.x_size(), im.y_size())
You could make many sorts of background image, we'll just have a constant red colour here.
background = VIPS::Image.black(1, 1, 3).lin([1,1,1], [255,0,0]).clip2fmt(:uchar)We make a 1 x 1 pixel, three band black image, then use .lin() with a pair of array constants to add 255 to band 1. As before, .lin() makes a float image, so we chop back to uchar for speed. We use the :extend option to .embed() to copy-paste that one pixel across the whole of the input image.
background = background.embed(:extend, 0, 0, im.x_size(), im.y_size())
Now we have our input image, a stencil for the watermark, and a background. We use .blend() to paint the background into our image:
im = text.blend(background, im)Here's the whole thing as a command-line program:
#!/usr/bin/rubyOn this laptop on a 10,000 x 10,000 pixel RGB jpeg it runs in about 1.5s and needs 40MB of memory:
require 'rubygems'
require 'vips'
# we can stream the image, turn on sequential access
im = VIPS::Image.jpeg(ARGV[0], :sequential => true)
text = VIPS::Image.text(ARGV[2], "Sans 12", 500, 1, 300)
text = text.lin(0.3, 0).clip2fmt(:uchar)
text = text.embed(:black, 100, 100, text.x_size() + 200, text.y_size() + 200)
text = text.replicate(1 + im.x_size() / text.x_size(),
1 + im.y_size() / text.y_size())
text = text.extract_area(0, 0, im.x_size(), im.y_size())
background = VIPS::Image.black(1, 1, 3).lin([1,1,1], [255,0,0]).clip2fmt(:uchar)
background = background.embed(:extend, 0, 0, im.x_size(), im.y_size())
im = text.blend(background, im)
im.write(ARGV[1])
$ time ./watermark.rb ~/pics/wtc.jpg x.jpg helloThe laptop has a two-core hyperthreaded CPU, so a 2.5x speedup from threading is about right.
real 0m1.439s
user 0m3.636s
sys 0m0.128s
Hi, it can be a dumb question, but is there some way how to watermark images in C by libVips?
ReplyDeleteThanks in advance
I updated the python example, it does mono, rgb and cmyk images now. It won't do 16-bit, but that'd be easy to add if you need it.
Delete... mono, rgb and cmyk, with and without alpha, I meant to say.
DeleteSure, just use the C API, it's very close to this.
ReplyDeletehttp://www.vips.ecs.soton.ac.uk/supported/7.42/doc/html/libvips/
I'll make a tiny example.
Any example would be great! I am trying to create my own function for watermarking but I have problems with transparent (4 band) png (I need transparent watermark) combined with jpg image, etc..
DeleteI've make vips8 C and Python versions and linked them at the top. They won't work for images with transparency though, I'll add that.
Delete