#374 Image Manipulation
- Download:
- source codeProject Files in Zip (54.2 KB)
- mp4Full Size H.264 Video (30.4 MB)
- m4vSmaller H.264 Video (14.5 MB)
- webmFull Size VP8 Video (15.3 MB)
- ogvFull Size Theora Video (30.3 MB)
A few years ago Ryan Bates wrote an application called Daily Stamp. With it we can enter something that we want to accomplish daily. We’re then shown a calendar and a stamper that we can use to stamp the days that we accomplish that task.
What we’ll focus on in this episode is the ability to customize the stamp. The app has a page where we can select from a preset list of stamps or upload an image of our own to turn into a custom stamp. Whatever image we upload is resized and has effects applied to make it look like a rubber stamp. We can also choose from a number of colours for our stamp. In this episode we’ll show you how to add this type of image manipulation. A key ingredient of this is ImageMagick. A lot of people have a love-hate relationship with this as it can be difficult to set up and use, although it is a powerful piece of software once it’s up and running. If we want to perform image manipulation this can take us quite far. A good place to start is with its command-line tools. These will give you a good idea of what ImageMagick is capable of and what steps are required to process an image with it.
Installing ImageMagick can be tricky. If you’re running Mac OS X it’s a good idea to use Homebrew to install it as then all that’s needed is to run the command brew install imagemagick
. This should give us a convert command, along with several others, for processing images.
In addition to the source image we’ll be working with, which is a PNG of Github’s Octocat, We also have another image, which is an overlay texture made up of a white image with some transparency to give it the appearance of a rubber stamp. Our first objective is to resize and crop the source image so that it composites nicely with our overlay texture. If we look at the documentation for the convert
command we’ll see the list of options that can be passed in, including options to crop or resize an image. Both of these options accept a geometry parameter and the documentation for this tells us the different ways that we can format the argument to change its behaviour. This is handy if you’re trying to make a square thumbnail from an oddly-shaped image like we are. We’ll use the option with a caret symbol which will allow us to fill the stamp image while maintaining the aspect ratio. We’ll start off in the terminal and resize the image to 70x70 pixels. We’ll also crop it to the same dimensions and supply an offset value then save the file as source.png
.
$ convert octocat.png -resize '70x70^' -crop '70x70+0+0' source.png
The resulting image looks like this. It’s dimensions are correct but it is off-centre. We want it to crop in the middle instead. This can be accomplished with the gravity option which we set before crop. Setting it to center will keep the image centered as it’s cropped.
$ convert octocat.png -resize '70x70^' -gravity center -crop '70x70+0+0' source.png
This crops the image correctly so we already have a command we can use to turn any image into a thumbnail. We want to turn this thumbnail into a greyscale image and also add some contrast so that it can easily be converted into a stamp. We can do this by adding some more options such as quantize
to set the colour palette to greyscale, colors
to do the conversion and contrast
to make the image pop a little more. Note that the option passed to quantize
is in uppercase.
$ convert octocat.png -resize '70x70^' -gravity center -crop '70x70+0+0' -quantize GRAY -colors 256 -contrast source.png
We now have a greyscale image that we can composite the overlay on top of to give it the texture of a stamp. We can do this by using the composite
command which will overlay one image on top of another. We’ll composite the overlay image on to our source image and overwrite the source image with the new image.
$ composite stamp_overlay.png source.png source.png
The image now has the stamp texture overlaid on top and the next thing we want to do is add some colour. Not only can the convert
command be used to process existing images we can also use it to create images from scratch. We’ll make a new 70x70 pixel image that we’ll colour red. We do this by using the canvas
option, although note that some versions of ImageMagick expect as cx
option instead.
$ convert -size 70x70 canvas:red colour.png
This generates a square red image that we can composite with the source image. Instead of having to manage another file, however, we can handle to compositing in the same convert command. We’ll load in the source image and configure how it should be composited with the other one. We’ll use the copy-opacity
opacity setting and tell it to composite the source image with the red canvas.
$ convert -size 70x70 canvas:red source.png -compose copy-opacity -composite stamp.png
The generated image is close to what we want but it looks like we need to invert the source image to get the result we want. We can do this by passing in parentheses when we load in the image and by using the negate option to tell it to invert the colours on the source image when it’s loaded in.
convert -size 70x70 canvas:red \( source.png -negate \) -compose copy-opacity -composite stamp.png
The image that’s generated now looks exactly like we want it to. It’s easy to configure our command to change the colour for different stamps. Now that we have this figured out we can merge the entire thing into one command so that the image is resized and make into a stamp in one go. This is a long and rather complicated looking command but it does exactly what we want.
convert -size 70x70 canvas:red \( octocat.png -resize '70x70^' -gravity center -crop '70x70+0+0' -quantize GRAY -colors 256 -contrast stamp_overlay.png -composite -negate \) -compose copy-opacity -composite stamp.png
Doing This in a Rails Application
The next step is to get this working in a Rails application. One option is just to execute this command from within Ruby and this isn’t such a bad idea. There are a couple of gems available that will do just this. Image Sorcery is a thin wrapper around the command-line too and MiniMagick is similar. The interface to both of these gems can be a little cumbersome when we have to deal with extensive image manipulation so keeping the whole command in a string and executing it is often the best way to go. There’s also the classic RMagick gem. This hasn’t been updated recently and some people criticize it for leaking memory but it is a valid option if you’re looking for an alternative to the command-line interface. It wraps the C library so its interface isn’t always a one-to-one mapping with the command line but it does have a good user guide. Let’s see what’s involved in using RMagick to generate the same stamp image. First we’ll need to install its gem.
$ gem install rmagick
We’ll get the image manipulation working in a simple Ruby script before we move it over to our Rails application. Here it is:
require "rmagick" source = Magick::Image.read("octocat.png").first source = source.resize_to_fill(70, 70).quantize(256, Magick::GRAYColorspace).contrast(true) overlay = Magick::Image.read("stamp_overlay.png").first source.composite!(overlay, 0, 0, Magick::OverCompositeOp) colored = Magick::Image.new(70, 70) { self.background_color = "red" } colored.composite!(source.negate, 0, 0, Magick::CopyOpacityCompositeOp) colored.write("stamp.png")
First we require rmagick
and then we load in the source image. Reading an image gives us an array of layer images and so we pick the first. We can then use the resize_to_fill
method to crop and resize the image and call quantize
on the thumbnail image that’s returned to covert it to greyscale. Calling contrast
and passing true
will increase the image’s contrast.
Next we composite the overlay image. We load it in in the same way as the main image then call composite!
on the source image to composite the overlay on the source. We then create a new image with a red background and composite it on to the negated source image. Note the Magick::CopyCapacityCompositeOp
argument which is the same as the -compose copy-opacity
option we used on the command line. Finally we write the image out.
We can try this out now. If we run our script the image that’s returned looks like the one we generated on the command line so it seems that our script works. For comparison here’s how the other approach to generating this script in Ruby looks:
# Or through the command line: system <<-COMMAND convert -size 70x70 canvas:red \\( octocat.png \ -resize '70x70^' -gravity center -crop '70x70+0+0' \ -quantize GRAY -colors 256 -contrast stamp_overlay.png \ -composite -negate \ \\) -compose copy-opacity -composite stamp.png COMMAND
If we use this we need to take care to watch for shell injection attacks if we’re accepting parameters from the user. Using RMagick feels more verbose but it’s also more dynamic and we need to watch out for potential memory leaks.
We’ve already created a simple Rails application to demonstrate how an uploaded image can be converted. This has a page with a file upload dialogue that will covert any uploaded image to a stamp. We’ve used RMagick for this example along with CarrierWave for handling file uploads. (CarrierWave was covered in episode 253.)
gem 'rmagick' gem 'carrierwave'
We also have a Stamp
model which mounts a CarrierWave uploader called StampUploader
.
class Stamp < ActiveRecord::Base attr_accessible :image mount_uploader :image, StampUploader end
The StampUploader
class is fairy standard apart from the processing code.
# encoding: utf-8 class StampUploader < CarrierWave::Uploader::Base include CarrierWave::RMagick include Sprockets::Helpers::RailsHelper include Sprockets::Helpers::IsolatedHelper storage :file def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end def extension_white_list %w(jpg jpeg gif png) end %w[red green blue purple black].each do |color| version(color) { process stamp: color } end def stamp(color) manipulate! format: "png" do |source| overlay_path = Rails.root.join("app/assets/images/stamp_overlay.png") overlay = Magick::Image.read(overlay_path).first source = source.resize_to_fill(70, 70).quantize(256, Magick::GRAYColorspace).contrast(true) source.composite!(overlay, 0, 0, Magick::OverCompositeOp) colored = Magick::Image.new(70, 70) { self.background_color = color } colored.composite(source.negate, 0, 0, Magick::CopyOpacityCompositeOp) end end end
The processing is done in the stamp
method. This is all that’s needed to handle the stamped versions. What we do is loop through a list of colours and make a version for each one. The stamp method calls manipulate!
which is a custom CarrierWave method and tells it that we want to use RMagick to generate a PNG image. This method takes a block that we pass the source image into. The code in the block should return the modified image and is basically the same code we used earlier with a couple of variables thrown in. If we were using this code in a production app we should move it off into a background process, but that’s out of the scope of this episode. Since each colour is a different version we can easily change the colour of the stamp that’s displayed in the view by passing in a different version.
<h1>Stamps</h1> <% @stamps.each do |stamp| %> <div class="stamp"> <%= image_tag stamp.image_url(:blue) %> </div> <% end %> <div class="clear"></div> <p><%= link_to "New Stamp", new_stamp_path %></p>
Now our stamps are a different colour.