Friday 22 August 2008

Dynamic Images on Rails

You can serve images through a dedicated method (or action) in your controller, but to use the format facility, you need to register the MIME type. A lot of places say to do that in environment.rb, but leave you stranded after that. What is missing is the require you need in there too:
require 'action_controller/mime_type'

However, I now believe they are wrong (or perhaps out-of-date), and the correct place to put them is in (to ensure they work in the interactive environment):
config/initializers/mime_types.rb
For example:
Mime::Type.register "image/png", :png
Mime::Type.register "image/gif", :gif

In your controller, you can now do this (for the usual post controller example):
# GET /posts/1
# GET /posts/1.xml
# GET /posts/1.gif
# GET /posts/1.png
def show
@post = Post.find(params[:id])
respond_to do format
format.html # show.html.erb
format.xml { render :xml => @post }
format.gif { render :text => @post.get_image_as_gif.to_blob,
:status => 200, :content_type => 'image/gif' }
format.png { render :text => @post.get_image_as_png.to_blob,
:status => 200, :content_type => 'image/png' }
end
end

This will respond to a request in four ways depending on the extension, serving up an image if it ends .gif or .png. The methods for creating the images go in Post.

To embed the image in the web page (note that pages can display slightly faster if you give the size and it is good practice to provide some alt text):

<%= image_tag formatted_post_path(@post, :gif),
:size => "250x250", :alt => "Image title" %>


Using RMagic
RMagick is a Ruby interface for ImageMagick, and seems to be the biggest image drawing package for Ruby.
http://www.imagemagick.org/RMagick/doc/

Unfortunately, there is no documentation with it about using it with Rails, and precious little elsewhere. There is an example program, axes.rb, so by way of illustration, this is how to convert it for use in Rails.

1. Move the require 'RMagick' to the top of the file, as normal.
2. Wrap the program in a method, with def get_image_as_png (or whatever) at the top, and end at the bottom.
3. Edit the bottom from this:
labels.draw(canvas)
#canvas.display
canvas.write("axes.gif")
exit

To this:
labels.draw(canvas)
canvas.format = 'png'
canvas
end

If there is any problem with your code, Rails just refuses to send the image, with no error message anywhere I could find. If there is no image getting through, try using the interactive environment; it will not display images, but it will show the error messages.

Note that images have to be converted to blobs before sending. I have choosen to do that in the controller's show method above. Also, you do have to inform RMagick of your file format before invoking to_blob (get a "no decode delegate for this image format" error otherwise).

Some useful pages:
http://jeremyweiland.com/archives/49
http://nubyonrails.com/articles/dynamic-graphics-with-rails-1-2
http://macdiggs.com/2007/03/08/rmagickrvg-outputting-an-inline-image/

Colour names:
http://www.imagemagick.org/www/color.html

Using RMagick with FileColumn
FileColumn is a plug-in that makes it very easy to allow users to upload images to your site, and then display them. It uses RMagick, but this seems to lead to a name clash. I found it was necessary to use "include Magick" inside any model that I was using RMagick, rather than specifying Magick::Draw, etc., as Rails complained that the latter was un undefined constant in FileColumn otherwise. Strangely this was not the case in the controller.

Struggling with Ruby: Contents Page

Additional: If you use JRuby, you will need to use Jva to create images, see here. That page also explores in more depth how to access images in your views, relevant to RMagick images as well as Java.

No comments: