Simple all sizes favicon generator for Rails

These days there are plenty of acceptable favicon formats. You are no longer restricted to 16x16 image/x-icon file, as long as support of IE < 11 is optional. Variety of mobile devices support other types of icons, that are used instead or with favicons, namely apple-touch-icon and mstile. Range of sizes varies from 16x16 to 192x192 pixels.

Manual scaling for dozen of different sizes is strait violation of DRY principle, so here comes a possible solution for Rails application.

ImageMagick

Chances are you already happened to use ImageMagick. It is a very powerfull image manipulation suite. There’s also a gem called MiniMagic which wraps calls to ImageMagic with a nice Ruby interface.

Technically, convert command from ImageMagick is one that does all the heavy lifting. If you feel more comfortable to or just should use bash, then you could just rewrite following rake task with it.

Ruby way

But I prefer Ruby. In context of Rails, there are two components: view used to render links and image set itself. Let’s keep it simple and just use partial to render links in the <head> section and a rake task to generate various sizes of base image.

Rake task

To create custom rake task for Rails project, put file with it’s definition to lib/tasks.

# lib/tasks/favicons.rake

namespace :favicon do
  require 'mini_magick'

  FAVICONS_DIR = File.join(Rails.root, 'app', 'assets', 'images', 'favicons')
  VARIANTS = {
    'apple-touch-icon' => [[57,57], [72,72], [114,114], [120,120], [144,144], [152,152]],
    'mstile' => [[144,144]],
    'favicon' => [[16,16], [32,32]]
  }
  L = Logger.new(STDOUT)

  desc %Q{Generate various versions of favicon. Original file should be app/assets/images/favicons/orig.png and be at least 152x152px. }
  task :generate => File.join(FAVICONS_DIR, 'orig.png') do
    Dir.chdir(FAVICONS_DIR) do
      img = MiniMagick::Image.open('orig.png')
      fail "Image is too small" unless img.dimensions.all? { |dim, all| dim >= 152 }
      VARIANTS.each do |name, dims|
        dims.each do |dim|
          img = MiniMagick::Image.open('orig.png')
          img.resize dim.join('x')
          img.density 150
          img.quality 100
          oname = "#{name}-#{dim.join('x')}.png"
          img.write oname
          L.info "Written: #{File.join(FAVICONS_DIR, oname)}"
        end
      end
    end
  end
end

First, you should namespace your tasks:

    namespace :favicon do

Use MiniMagick:

    require 'mini_magick'

To prevent caching issues with new versions of favicons, you should put them inside app/images directory:

    FAVICONS_DIR = File.join(Rails.root, 'app', 'assets', 'images', 'favicons')

Here come variants of the picture. I think 192x192 is pretty unnesessary, so I’ll stick to 152 and no more:

    VARIANTS = {
      'apple-touch-icon' => [[57,57], [72,72], [114,114], [120,120], [144,144], [152,152]],
      'mstile' => [[144,144]],
      'favicon' => [[16,16], [32,32]]
    }

Define the task and also require original file (app/images/favicons/orig.png) to exist:

    task :generate => File.join(FAVICONS_DIR, 'orig.png') do

Here comes the interesting part.

Open original file:

    img = MiniMagick::Image.open('orig.png')

The original image obviously should be at least as big as largest favicon variant:

    fail "Image is too small" unless img.dimensions.all? { |dim, all| dim >= 152 }

For each variant, open original image, resize it to current variant, make quality 100, name output file <variant-name>-<W>x<H>.png and save to favicons directory:

      VARIANTS.each do |name, dims|
        dims.each do |dim|
          img = MiniMagick::Image.open('orig.png')
          img.resize dim.join('x')
          img.density 150
          img.quality 100
          oname = "#{name}-#{dim.join('x')}.png"
          img.write oname
          L.info "Written: #{File.join(FAVICONS_DIR, oname)}"
        end
      end

View

I use partial named app/shared/_favicons.html.haml to render links in the <head> section.

-# app/views/layous/application.html.haml
...
%head
  ...
  = render partial: 'shared/favicons'
...
-# app/views/shared/_favicons.html.haml
%link{rel: "apple-touch-icon-precomposed",  sizes: "57x57",    href: image_url("favicons/apple-touch-icon-57x57.png")}
%link{rel: "apple-touch-icon-precomposed",  sizes: "114x114",  href: image_url("favicons/apple-touch-icon-114x114.png")}
%link{rel: "apple-touch-icon-precomposed",  sizes: "72x72",    href: image_url("favicons/apple-touch-icon-72x72.png")}
%link{rel: "apple-touch-icon-precomposed",  sizes: "144x144",  href: image_url("favicons/apple-touch-icon-144x144.png")}
%link{rel: "apple-touch-icon-precomposed",  sizes: "120x120",  href: image_url("favicons/apple-touch-icon-120x120.png")}
%link{rel: "apple-touch-icon-precomposed",  sizes: "152x152",  href: image_url("favicons/apple-touch-icon-152x152.png")}
%link{rel: "icon",  type: "image/png",  href: image_url("favicons/favicon-32x32.png"), sizes: "32x32"}
%link{rel: "icon",  type: "image/png",  href: image_url("favicons/favicon-16x16.png"), sizes: "16x16"}

-# Also make pretty android or WP navigation bar background with primary color (optional):
%meta{name: "application-name",  content: "YourApp" }
%meta{name: "msapplication-TileColor",  content: "#FFFFFF" }
%meta{name: "msapplication-TileImage",  content: image_url("favicons/mstile-144x144.png") }
SHOW COMMENTS (0)