Custom image attachment variants with Rails Active Storage
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:
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::Concernincluded 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
endImageProcessing::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:
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 bandjoin
on an image to join a set of images bandwise. And this is a topic for another talk.
Happy rounding!