ruby-on-railsruby-on-rails-5imagemagickshrinevips

Vips - add text on top of image, after resize in Ruby


I'm using Vips to resize images via Shrine, hoping it's possible to use the Vips library to merge a layer of text on top of the image.

ImageProcessing::Vips.source(image).resize_to_fill!(width, height)

This code works great, how can I add a layer of text after resize_to_fill?

The goal is to write 'Hello world' in white text, with a CSS text-shadow in the center of the image.

I've tried writing something like this, but I'm only getting errors so far:

Vips\Image::text('Hello world!', ['font' => 'sans 120', 'width' => $image->width - 100]);

Solution

  • Your example looks like PHP -- in Ruby you'd write something like:

    text = Vips::Image.text 'Hello world!', font: 'sans 120', width: image.width - 100
    

    I made a demo for you:

    #!/usr/bin/ruby
    
    require "vips"
    
    image = Vips::Image.new_from_file ARGV[0], access: :sequential
    
    text_colour = [255, 128, 128]
    shadow_colour = [128, 255, 128]
    h_shadow = 2
    v_shadow = 5
    blur_radius = 10
    
    # position to render the top-left of the text
    text_left = 100
    text_top = 200
    
    # render some text ... this will make a one-band uchar image, with 0 
    # for black, 255 for white and intermediate values for anti-aliasing
    text_mask = Vips::Image.text "Hello world!", dpi: 300
    
    # we need to enlarge the text mask before we blur so that the soft edges 
    # don't get clipped
    shadow_mask = text_mask.embed(blur_radius, blur_radius,
                                  text_mask.width + 2 * blur_radius,
                                  text_mask.height + 2 * blur_radius)
    
    # gaussblur() takes sigma as a parameter -- approximate as radius / 2
    shadow_mask = shadow_mask.gaussblur(blur_radius / 2) if blur_radius > 0.1
    
    # make an RGB image the size of the text mask with each pixel set to the
    # constant, then attach the text mask as the alpha
    rgb = text_mask.new_from_image(text_colour).copy(interpretation: "srgb")
    text = rgb.bandjoin(text_mask)
    
    rgb = shadow_mask.new_from_image(shadow_colour).copy(interpretation: "srgb")
    shadow = rgb.bandjoin(shadow_mask)
    
    # composite the three layers together
    image = image.composite([shadow, text], "over",            
                            x: [text_left + h_shadow, text_left],
                            y: [text_top + v_shadow, text_top]) 
    
    image.write_to_file ARGV[1]
    

    Run like this:

    $ ./try319.rb ~/pics/PNG_transparency_demonstration_1.png x.png
    

    To make:

    enter image description here