Custom image attachment variants with Rails Active Storage

Sergii Brytiuk
2 min readJun 6, 2021

--

Foreword

Active Storage facilitates uploading files to a cloud storage service and attaching those files to Active Record objects(https://edgeguides.rubyonrails.org/active_storage_overview.html).

The library provides a variant method to get specific variants per attachment. To resizes the avatar to fit within 400x400` dimensions call variant method with appropriate parameters:

#> User.last.avatar.variant(resize_to_fit: [400, 400])

Variant is a result of a set of transformations applied on the original. Variants rely on ImageProcessing gem for the transformations of the file(https://api.rubyonrails.org/classes/ActiveStorage/Variant.html). Images, by default, processed with ImageMagic or, in our case, libvips. ImageProcessing libvips module has a number of methods for image processing https://github.com/janko/image_processing/blob/master/doc/vips.md#methods

Custom variants

As you know a square image of a tractor looks sad. This one needs a treatment:

Square tractor

Let us see how can you add a custom image processing method to help it to become round by calling something like this:

#> Tractor.last.avatar.variant(crop_circle: [250, 250, 250])

Here is one of the ways to add custom, we called it crop_circle, method to ImageProcessing::Vips. Add a new method to the lib:

# lib/image_processing/vips/processing.rb
require "image_processing"
module ImageProcessing
module Vips
module Processing
extend ActiveSupport::Concern
included do
# @param [Integer] circle center X
# @param [Integer] circle center Y
# @param [Integer] circle radius
def crop_circle(x, y, radius)
circle = ::Vips::Image.new_from_buffer %(
<svg xmlns="http://www.w3.org/2000/svg" width="#{image.width}" height="#{image.height}" version="1.1">
<circle cx="#{x}" cy="#{y}" r="#{radius}" fill="#fff"></circle>
</svg>
), ""
image.bandjoin(circle[3])
end
end
end
end
end
ImageProcessing::Vips::Processor.include(ImageProcessing::Vips::Processing) # or add this line to the initializer

After transforming the image:

#> Tractor.last.avatar.variant(crop_circle: [250, 250, 250])

We get absolutely happy looking tractor:

Happy(round) tractor

By varying center X, Y values you can move the center of the circle when processing the image.

The magic happens when you create a round SVG and call bandjoinon an image to join a set of images bandwise. And this is a topic for another talk.

Happy rounding!

--

--

Sergii Brytiuk

Does it mean you are stupid if you can not answer the question?