Image Processing with Rails

Share this article

Collection of images

Images are a crucial part of any application. From a social network to a simple bug tracker, images play an important role. However, managing images is not a trivial task and requires a lot of planning ahead.

In this article allow me to demonstrate how to achieve this in Rails. I’m going to show you how to process your images and create multiple versions in the backend. We’ll also see how to improve the performance of the page by compressing these images without losing quality.

Getting Started

The examples in this tutorial run on Rails 4.2, with a MongoDb database, and HAML to render the views. However the snippets used here should be compatible with any version of Rails, albeit minor config differences.

Setting up the Stage

ImageMagick is the go to library for image processing on POSIX systems. If you don’t have ImageMagick installed on your system, it can be installed with the package manager for your OS. On Ubuntu:

sudo apt-get -y install imagemagick
sudo apt-get -y install libmagic-dev
sudo apt-get -y install libmagickwand-dev

On Mac OS X, I recommend using Homebrew:

brew install imagemagick

Now, we need a Ruby adapter to connect to the native ImageMagick library. I personally prefer MiniMagick, as it is lightweight and does pretty much everything a typical application requires:

# Gemfile

gem 'mini_magick'

Playground

Let’s play around with some of the MiniMagick’s features before building anything serious. Open up the Rails console (rails c) and run the following:

# Open an image from a website

image = MiniMagick::Image.open("https://s3.amazonaws.com/StartupStockPhotos/20140808_StartupStockPhotos/85.jpg")

# Get the Image's width
image.width # 4928

# Get the image's height
image.height #3264

Holy moly, that’s huge. Let’s see if we can resize this to fit our iPad:

image.resize "2048x1536"

# Now get the image's new width and height

p "Width is => #{image.width} and height is => #{image.height}"

Well, that did it. Wait a second, where is this changed file stored?

image.path # temp path

The manipulated image is stored in a temp path and will be washed away. To persist it to disk, simply call the write method:

image.write "public/uploads/test.jpg"

Converting Image

One of the most frequent operations you’ll do is convert images to different formats. MiniMagick makes this very simple:

image.format "png"
image.write "public/uploads/test.png"

Going Crazy

You can also combine multiple operations in a single block:

image.combine_options do |i|
  i.resize "2048x1536"
  i.flip
  i.rotate "-45"
  i.blur "0x15"
end
image.write "public/uploads/blur.png"

![Some weird result](blur.png)

OK, I went a little overboard here. In my defense, I’m just trying to show all the cool stuff that you can do with MiniMagick :).

Now, let’s see how we can tie this up with our Rails app.

Uploading Files

Carrierwave is a wonderful gem which simplifies file uploads in Ruby. It also interacts nicely with MiniMagick, making our lives much simpler.

# Gemfile

gem 'carrierwave'
gem 'carrierwave-mongoid', :require => 'carrierwave/mongoid'

NOTE: If you’re on ActiveRecord or DataMapper, the configurations would be slightly different and the official Carrierwave documentation will show you the way.

Bundle to fetch all these gems:

bundle install

Create our first uploader:

#app/uploaders/image_uploader.rb

class ImageUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this Uploader:
  storage :file
  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/images"
  end
end

The code here is self explanatory. storage :file instructs the server to store the image on the local server and the store_dir specifies the location.

Since, the files are sent over the Internet, as a best practice, always filter the incoming files:

# app/uploaders/image_uploader.rb
...
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_white_list
  %w(jpg jpeg png gif)
end
...

This snippet filters out file types other than the ones specified here. This is by no means foolproof, but it serves as a first level filter against any impediment attack.

Mount this uploader to our image model:

# app/models/image.rb

class Image
  include Mongoid::Document
  include Mongoid::Timestamps
  include Mongoid::Paranoia
  include Mongoid::Attributes::Dynamic
  include Rails.application.routes.url_helpers

  mount_uploader :media, ImageUploader, mount_on: :media_filename
end

Edit the image_uploader.rb to process the uploaded image:

# app/uploaders/image_uploader.rb

#.....
process :resize_to_fill => [200, 200]
process :convert => 'png'
#.....

Try creating a new image from the Rails console:

media = File.open("/Users/test/Desktop/image/jpg")
img = Image.new(:media => media)
img.save

The uploaded image is available under the store_dir. However, the uploaded image is immediately processed and overwritten with the 200×200 image. We won’t have a copy of the original file for any future edits. To avoid this, create multiple versions of the file.

# app/uploaders/image_uploader.rb

#.....
version :thumb do
  process :resize_to_fit => [100, 100]
  process :convert => 'jpg'
end

version :cover   do
  process :resize_to_fit => [240, 180]
  process :convert => 'jpg'
end

#.....

This creates 2 new versions, along with the original image. Check the versions created by Carrierwave:

img.media.versions[:thumb] # returns the thumb image instance
img.media.versions[:cover] # returns the cover image instance

Did you notice that these images are generated instantaneously? This means that the image conversion happens in the same thread and execution is blocked until it completes. In a production application, it would be undesirable to create multiple versions of an image in the same thread. Instead, we should be handling this conditionally.

# app/uploaders/image_uploader/rb

#....
version :cover, :if => :is_live? do
  process :resize_to_fit => [240, 180]
  process :convert => 'jpg'
end

def is_live?(img = nil)
  @is_live
end

def is_live=(value)
  @is_live = value
end
#....

Now, when we try creating a new image, the cover version won’t be generated. We can manually trigger this when needed by simply running:

img.media.is_live = true
img.save
img.media.recreate_versions! :cover

This code also runs in the foreground and is a blocking operation, but at least it’s deferred till the last moment possible. We can take this a step further by running this in the background with Resque:

# lib/resque/image_queue.rb
class ImageQueue
  @queue = :image_queue
  def self.perform(image_id)
    image = Image.find image_id
    img.media.is_live= true
    img.save
    img.media.recreate_versions! :cover
  end
end

and, enqueue it:

Resque.enqueue(ImageQueue, img.id.to_s)

Improving Performance

Images are heavy and tend to slow down the website. One way to reduce the page weight is to compress these images. Carrierwave Image Optimizer helps us compress our images on the fly without any hassles.

Add this to your Gemfile:

gem 'carrierwave-imageoptimizer'

And edit the image_uploader

# app/uploaders/image_uploader.rd

#.....

include CarrierWave::ImageOptimizer
process :optimize
process :quality => 100
#....

This compresses all the images without any visual loss. The way this works is all the meta information about the image is stripped out. On average, this reduces the size around 20-30%.

Wrapping Up

Image processing is a huge vertical, and we’ve barely scratched the surface. We can build so many cool things with it. I hope I’ve pecked your interest with this article. Please share your thoughts in the comments.

Frequently Asked Questions on Image Processing in Rails

What is the role of Image Processing in Rails?

Image processing in Rails is a crucial aspect of web development. It allows developers to manipulate images for various purposes such as resizing, cropping, and changing the format. This is particularly useful in applications where users need to upload images, such as social media platforms, e-commerce sites, or photo galleries. By processing images, developers can ensure that the images fit the design and layout of the application, improve load times, and enhance the overall user experience.

How can I install the Image Processing gem in Rails?

To install the Image Processing gem in Rails, you need to add the following line to your application’s Gemfile: gem 'image_processing', '~> 1.12'. Then, run the bundle install command in your terminal. This will install the gem and its dependencies in your Rails application.

How can I resize an image using Image Processing in Rails?

Resizing an image using Image Processing in Rails is straightforward. You can use the resize_to_limit method, which takes two arguments: the width and height. Here’s an example: image = ImageProcessing::Vips.resize_to_limit(image, 800, 800). This will resize the image to fit within an 800×800 pixel box.

Can I process multiple images at once in Rails?

Yes, you can process multiple images at once in Rails. You can use the each method to iterate over an array of images and apply the desired processing methods. This is particularly useful when you need to process a batch of images in the same way, such as resizing all images to a certain size or converting all images to a specific format.

How can I convert an image to a different format in Rails?

Converting an image to a different format in Rails is easy with Image Processing. You can use the convert method, which takes one argument: the desired format. Here’s an example: image = ImageProcessing::Vips.convert(image, "jpg"). This will convert the image to JPEG format.

What are the benefits of using Image Processing in Rails?

Image Processing in Rails offers several benefits. It simplifies the process of manipulating images, saving developers time and effort. It also provides a wide range of processing options, from basic resizing and cropping to more advanced features like color adjustments and filters. Furthermore, it supports multiple image formats, making it a versatile tool for any Rails application.

Can I use Image Processing with Active Storage in Rails?

Yes, Image Processing is fully compatible with Active Storage in Rails. Active Storage is a built-in feature of Rails that facilitates uploading and managing files. When used together, Image Processing and Active Storage provide a powerful solution for handling image files in Rails applications.

How can I crop an image using Image Processing in Rails?

Cropping an image using Image Processing in Rails is simple. You can use the crop method, which takes four arguments: the left, top, width, and height. Here’s an example: image = ImageProcessing::Vips.crop(image, 100, 100, 200, 200). This will crop the image to a 200×200 pixel box starting at the point (100,100).

What image formats does Image Processing support in Rails?

Image Processing in Rails supports a wide range of image formats, including JPEG, PNG, TIFF, and many others. This makes it a versatile tool for handling different types of image files in your Rails application.

Can I adjust the quality of an image using Image Processing in Rails?

Yes, you can adjust the quality of an image using Image Processing in Rails. You can use the quality method, which takes one argument: the desired quality level. Here’s an example: image = ImageProcessing::Vips.quality(image, 90). This will adjust the quality of the image to 90%.

Hola! I'm a Fullstack developer and a strong advocate of Mobile first design. I'm running a digital children's startup for kids and I lead the engineering efforts there. In my free time I ramble about technology, and consult startups.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week