Getting started with image processing in Ruby on Rails, rubyrails
Images can be said to be a vital part of any application. From social networks to a simple Bug tracker, images play an important role. However, managing images is not an easy task. It takes a lot of time and effort to plan images in advance.
This article demonstrates how to achieve this in Rail. How to process your images and create multiple versions in the background? How to Improve page performance by compressing images without compromising image quality? Let's hear this article.
Getting started
This tutorial is run on Rails 4.2 and presents the view through the MongoDb database and HAML. However, the snippets shown in this article should be compatible with any Rails version (despite some configuration differences ).
Stage Arrangement
ImageMagick is a powerful, stable, and open-source toolkit. you can install it on your computer through package management.
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, homemade programs are recommended:
brew install imagemagick
Now we need a Ruby adapter connected to the local ImageMagick library. You can use MiniMagick because it is lightweight:
# Gemfile
gem 'mini_magick'
MiniMagick Features
Before the official start, let us first understand some of the features of MiniMagick to avoid unnecessary errors.
Open the Rails console (Rails c) and run the following code:
# 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
Let's adjust it 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}"
Where are the changed files stored?
image.path # temp path
There is a danger that manipulating images stored to a temporary path will disappear. So to put it on the disk, a simple call writing method is as follows:
image.write "public / uploads / test.jpg"
Convert image
Perhaps one of your most common tasks is to convert images to different formats. MiniMagick can simplify this process:
image.format "png"
image.write "public / uploads / test.png"
You can also put multiple operations in the same module:
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)
So far, let's take a look at how to link the above with our Rails application.
upload files
Carrierwave simplifies uploading files in Ruby, and it also interacts well with MiniMagick.
# Gemfile
gem 'carrierwave'
gem 'carrierwave-mongoid',: require => 'carrierwave / mongoid'
Note: If you are on ActiveRecord or DataMapper, the configuration will be slightly different. The official Carrierwave documentation describes the correct method, click here to enter.
Obtain:
bundle install
Create the first upload:
# 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
This code is self-explanatory, storage: file instructs the server to store the image on the local server, and store_dir specifies the location.
Since files are transmitted over the Internet, incoming files are always filtered:
# 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
...
Put this upload into 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 image_uploader.rb to process the uploaded image:
# app / uploaders / image_uploader.rb
# .....
process: resize_to_fill => [200, 200]
process: convert => 'png'
# .....
Try to create a new image from the Rails console:
media = File.open ("/ Users / test / Desktop / image / jpg")
img = Image.new (: media => media)
img.save
Uploaded images are available under store_dir. However, the uploaded image is processed immediately and is overwritten by the 200 × 200 image. We do not have a copy of the original document for future editing. So to avoid this, we need to 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
# .....
Shown below are two versions of the previous code, check the version 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 were generated instantly? This means that the image conversion happens in the same thread, and execution is blocked until it completes. In production applications, creating multiple versions of an image in the same thread is undesirable. Instead, we should deal with this situation 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
# ....
This way, when we create a new image, a copy will not be generated. We can manually trigger when needed, run the following code:
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 can be postponed to the last minute. We can run further in the background through 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
Then line up:
Resque.enqueue (ImageQueue, img.id.to_s)
Performance improvement
The images are heavy and tend to slow down the website. One way to reduce page load is to compress these images. Carrierwave image optimizer can help us compress images quickly.
Add the following paragraph to the Gemfile:
gem 'carrierwave-imageoptimizer'
Then edit image_uploader:
# app / uploaders / image_uploader.rd
# .....
include CarrierWave :: ImageOptimizer
process: optimize
process: quality => 100
# ....
Such compression will not cause visual loss.
The image processing is a huge vertical, and we almost only touch the surface. We can build a lot of cool things. If you are interested in this article, please share your thoughts in the comments.