| Class | ActionController::Macros::ImageMagick::RecipeSet |
| In: |
actionpack/lib/action_controller/macros/image_magick.rb
|
| Parent: | Hash |
It is possible to write your own commands to use with imagemagick_tag. You do this by creating recipes. Your own recipes can have arguments and you can mix them with normal ImageMagick methods. Such as:
<%= imagemagick_tag 'picture.jpg', 'customthumbnail+resize(100)+myborder(f00)' %>
There are multiple ways to write your own recipes: as a String of normal commands, as an inline method (Proc), as a method reference (Symbol) or as an external Class.
The easiest way is with a String. If you always render your profile images with ‘resize(100)+border(1,1,fff)’, you can shorten that with this recipe:
# in the controller
class UserController < ApplicationController
imagemagick_for_directory '/var/lib/profiles'
imagemagick_recipe :userprofile, 'resize(100)+border(1,1,fff)'
end
# in the view
<%= imagemagick_tag 'john.jpg', 'userprofile' %>
As you see, you’ve now created your own ‘command’. userprofile will be expanded to resize(100)+border(1,1,fff) when rendering.
A more sophisticated method is by using a Proc. You can use this if you want more control than you can get with the String method. And if you need custom parameters for your recipes, you simply can’t use a String.
With a proc, adding a recipe works like this:
# in the controller
class UserController < ApplicationController
imagemagick_for_directory '/var/lib/profiles'
imagemagick_recipe :square, Proc.new { |image, size| image.resize!(size.to_i, size.to_i) }
end
# in the view
<%= imagemagick_tag 'john.jpg', 'square(100)' %>
This example renders the image as a 100x100 pixel square. (If you look closely, you’ll notice that we’re passing the size of 100 pixel as a parameter to the recipe.) Proc-recipes work by accepting an image object and perhaps some parameters. In this case, the only parameter is size. The image object is the real RMagick::Image-object of the image that is currently being processed. In your proc, you can execute any of the RMagick methods you like.
There are two caveats you should keep in mind when using Proc recipes:
If you don’t like providing the recipe as a Proc, you can also give your recipes the form of methods of the controller, or as a separate class.
If using a method, give the method name as a Symbol:
class UserController < ApplicationController
imagemagick_for_directory '/var/lib/profiles'
imagemagick_recipe :ownresize, :my_own_resize_method
private
def my_own_resize_method(image, width, height)
image.resize!(width.to_i, height.to_i)
end
end
If using a class, it should have a class method called execute_recipe_on. Provide the class to imagemagick_recipe:
class MyResizeRecipe
def self.execute_recipe_on(image, width, height)
image.resize!(width.to_i, height.to_i)
end
end
class UserController < ApplicationController
imagemagick_for_directory '/var/lib/profiles'
imagemagick_recipe :ownresize, MyResizeRecipe
end
As for the arguments and the expected return, the method and class approaches are exactly the same as with a proc.
The ImageMagick extension uses three different levels of recipes. Each level has it own RecipeSet, which is a Hash-like object that contains the recipes of that level.
At the deepest level, there are the BuiltinRecipes. Those recipes implement the standard commands that come with ImageMagick. These recipes are available in every controller. (You normally shouldn’t have to change the recipes at this level.)
At a little higher level, you have the GlobalRecipes. This is a list of recipes that are specific to the application, i.e. written by you. If you want your recipes to be available in every controller, you can add them to the GlobalRecipes RecipeSet.
At the highest level are the local recipes of a controller. These recipes are only available in one controller. Recipes defined with imagemagick_recipe are local recipes.
Creating global recipes works a lot like creating routes. You should do it somewhere in your +environment.rb+, or in a file that is included by +environment.rb+. Example:
ActionController::Macros::ImageMagick::GlobalRecipes.add do |recipes|
recipes.add :customthumbnail, 'resize(100x100)'
recipes.add :myborder, Proc.new { |image| image.border(1, 1, '#f00") }
end
You can define your global recipes as a String, as a Proc or as a Class (see above). Defining them as a Method of the controller is not possible.
You can define local recipes in your controller with the imagemagick_recipe method. Example:
class < ApplicationController
imagemagick_for '/var/lib/profiles'
imagemagick_recipe :customthumbnail, 'resize(100x100)'
end
You can define your local recipes as a String, as a Proc, as a Method or as a Class (see above).
It’s also possible to add local recipes on the fly. For example, if you have stored your recipes a database, you write a method to retrieve and add them. To do this, you can add the recipe-adding method as a filter for the imagemagick action. For example:
class < ApplicationController
imagemagick_for '/var/lib/profiles'
# add a filter that is executed before the imagemagick action
before_filter :add_local_recipes, :only_action=>:imagemagick
private
def add_local_recipes
imagemagick_local_recipes.add { |recipes|
recipes.add :customthumbnail, 'resize(100x100)'
recipes.add :myborder, Proc.new { |image| image.border(1, 1, '#f00") }
end
end
end
It is possible to limit the commands that one can execute on an image. You can restrict it to only accept the recipes from the local recipe set, or only from the local and global recipe sets. Set this :max_recipe_level as an option for imagemagick_for:
class < ApplicationController
# if you want only local recipes
imagemagick_for '/var/lib/profiles', :max_recipe_level=>:local
# if you want only local and global recipes
imagemagick_for '/var/lib/profiles', :max_recipe_level=>:global
end
If you don’t specify a :max_recipe_level, recipes from all three levels (:local, :global and :builtin) may be used.
(This only applies if you’re using the cache.) Combining custom recipes with the image cache can lead to problems. In the cache, the images are stored with the names of the recipes they were generated with. Now, if you change a recipe but do not change the name, the cached images will not be updated. The extension will still serve images that were rendered with the old recipe.
The solution to this can be very simple: remember to empty the cache directory whenever you change your recipes. This is a perfect way to solve the problem. But there is another option, which is a little more sophisticated: add version numbers to your recipes, and update the number when you change the recipe.
Recipes can be numbers or strings. The order is not important (version 3 can be older than version 1). As long as you’re using new version numbers that you haven’t used before for the same recipe, you’ll be fine.
You can add the version number when you define the recipe. For example:
class < ApplicationController
imagemagick_for '/var/lib/profiles', :cache=>'/my/cache'
imagemagick_recipe :myborder, Proc.new({ |image| image.border(1, 1, '#f00") }), 'version1'
end
The rendered images are now cached with the version number, ‘version1’. Now if you want to change the recipe, just be sure to bump the version number:
class < ApplicationController
imagemagick_for '/var/lib/profiles', :cache=>'/my/cache'
imagemagick_recipe :myborder, Proc.new({ |image| image.border(2, 2, '#f00") }), 'version2'
end
That’s about all there is to recipe versions. Just two last notes:
Adds a custom recipe with the given name and version. recipe can be a String, a Proc, a Class or a Symbol. If recipe is a String and the version is nil, version will be set to the String.
If a block is given, the block will be executed with this self as the only argument. (This can be used as a way to add many recipes in a row.)