Search code examples
ruby-on-railsrails-activestorage

How do I create a non-trivial ActiveStorage variant for watermarking images?


I have written the following ImageMagick command which takes a watermark image, scales it, and composites it over a base image, tiling it:

convert image.png \( watermark.png -resize 5% -write mpr:watermark +delete \) \( +clone -tile mpr:watermark -draw "color 0,0 reset" \)  -compose over -composite watermarked_image.png

I want to define this as an Active Storage variant, so that I can watermark images in certain contexts, but I am coming up short. I can only find very simple transformation examples. Is this possible as an Active Storage variant?


Solution

  • I finally figured this out and wanted to share the answer in case anyone else lands here looking for help. I found the following article, which explains the basic premise. From there it was only a matter of translating the ImageMagick command into the correct MiniMagick API calls.

    require "image_processing"
    
    module ImageProcessing
      module MiniMagick
        module Processing
          extend ActiveSupport::Concern
    
          included do
            def watermark(scale: 0.5, opacity: 0.5)
              watermark_path = Rails.root.join('lib', 'watermark.png')
              formatted_scale = "%0.0f%%" % [scale* 100]
    
              magick.stack do |stack|
                stack << watermark_path
    
                stack.alpha('on')
                stack.channel('a')
                stack.evaluate('multiply', opacity.to_f)
                stack.channel.+
    
                stack.resize(formatted_scale)
                stack.write('mpr:watermark')
                stack.delete.+
              end
    
              magick.stack do |stack|
                stack.clone.+
                stack.tile('mpr:watermark')
                stack.draw('color 0,0 reset')
              end
    
              magick.compose('over')
              magick.composite
            end
          end
        end
      end
    end
    
    ImageProcessing::MiniMagick::Processor.include(
      ImageProcessing::MiniMagick::Processing
    ) 
    

    Then, assuming you have a model with an image attachment, just call the new watermark variant with:

    design.image.variant(
      watermark: { scale: 0.25, opacity: 0.2 },
    )