Compass Magick Tutorial - Part 1

Introduction

We have all been there - you release a cool new website with all the goodies of CSS3 just to find several weeks later your Customer wants you to support Internet Explorer as well. It seems solid backgrounds don’t cut it.

There are also cases where CSS3 isn’t the right solution, whether you have custom shapes or other elements of your design you end up creating images for (SVG is a good solution for these cases, but there’s that big 10-year old elephant sitting in the middle of the room again).

Compass Magick tries to solve these issues by allowing you to dynamically generate images from your Compass projects taking full advantage of variables, mixins, etc.

History

The initial version of the project relied on RMagick for all image manipulation. There were many quirks: gradients were not working properly, per-pixel manipulation was difficult and much more. While I haven’t worked with Ruby myself before, it also became apparent after some time RMagick was a big no-no in Ruby’s land. The project is not actively maintained (last commit at the time of writing this post was on October 25, 2010) and compiling it is a big pain (especially on Windows/Cygwin).

Searching for an alternative solution, I stumbled upon compass-rgba, a simple plugin for generating 1x1 images and saving them as PNG files, the goal being to support IE7 with its missing implementation for rgba. The plugin is using ChunkyPNG, a pure Ruby library for reading and writing PNG images. Upon further investigation, I found it has very good support for per-pixel access and was a breeze to install. The author, Willem van Bergen, is also actively maintaining it, constantly adding new features and releasing new versions.

Installation

Moving away from the brief history lesson, installing Compass Magick is simple via RubyGems. Compass ~> 0.11.beta.5 and ChunkyPNG ~> 1.1.0 are required.

gem install compass-magick

You can optionally install OilyPNG ~> 1.0.0 to speed up ChunkyPNG. Oily is a native mixin:

gem install oily_png

If you have an existing project, you can start using Compass Magick by requiring the plugin from your Compass configuration:

# Add as the the first line in your config.rb
require 'magick'

If you are starting a new Compass project, to include Magick add -r magick to the command line:

compass create -r magick my_project

A Surface

Everything starts with a canvas:

magick-canvas(320px, 200px)

The code above creates a new Magick canvas and initialises its width and height. The canvas is fully transparent. This line by itself doesn’t do much, so let’s add it as a page background:

// intro.scss
body {
  background: transparent magick-canvas(320px, 200px);
}

Once you compile the source intro.scss, have a look at the produced .css - you should see a Base64 encoded Data URL of what is essentially a 320x200 transparent PNG serialised:

body {
  background: transparent url('data:image/png;base64,…');
}

Commands

You can perform drawing operations on a Magick canvas just like any other canvas. Commands are executed in the order they are specified in the source file. There are commands for drawing borders, generating gradients and much more. To keep things simple, let’s turn our transparent canvas yellow:

// intro.scss
body {
  background: transparent magick-canvas(320px, 200px,
    magick-fill(yellow)
  );
}

Example 1

…and just like that we turned our canvas yellow. magick-fill accepts semi-transparent colors and most importantly of all, variables:

// intro.scss
$theme1: yellow;
$theme2: blue;

body {
  background: transparent magick-canvas(320px, 200px,
    magick-fill($theme1)
    magick-fill(rgba($theme2, 0.5))
  );
}

Example 2

What happened there? We created a 320x200 canvas and executed two commands on it:

  1. Fill the entire canvas with $theme1 (yellow)
  2. Fill the entire canvas again with $theme2 (blue), but at 50% opacity

Types

Compass Magick has support for linear gradients. Gradients are not drawing functions, but fill types. You don’t apply a gradient directly on the canvas, rather you use it as an argument to a drawing function, one like magick-fill for example. Let’s create a very simple top-to-bottom gradient and apply it on our canvas from the previous examples:

// intro.scss
$theme1: red;
$theme2: maroon;

body {
  background: transparent magick-canvas(320px, 200px,
    magick-fill(
      magick-linear-gradient(
        $theme1,
        $theme2
      )
    )
  );
}

Example 3

Going Further

Compass Magick has a very powerful set of functions. Adding corners and a border is trivial and we can quickly turn our canvas into a button:

// intro.scss
$theme1: red;
$theme2: maroon;

$button_width:  320px;
$button_height: 24px;

body {
  background: transparent magick-canvas($button_width, $button_height,
    magick-fill(
      magick-linear-gradient(
        $theme1,
        $theme2
      )
    )
    magick-corners(10px)
    magick-border($theme2, 10px, 1px)
  );
}

Example 4

Saving the Canvas

Unfortunately Base64 encoded Data URLs are not supported by all browsers and versions. To save the button we generated on disk, we first need to alter our Compass configuration:

# Add to config.rb
images_dir = 'images'

We can now wrap everything in magick-sprite to write the output on disk:

// intro.scss
/* …snip… */

body {
  background: transparent magick-sprite('button', magick-canvas($button_width, $button_height,
    /* …snip… */
  ));
}

The result will be button.png in the configured images_dir. The file is optimised for best compression, but you could further post-process it with tools like OptiPNG.

Conclusion

This post is just an introduction to Compass Magick. There are many more features available, some of which include image composing, cropping, masking, drop shadows and pattern generation. Check out the list of all available commands for a comprehensive reference.

I hope you found this short walk-through useful. Leave a comment if you have any questions or suggestions and visit the official Github page of the project for up-to-date information and links to other resources.

UPDATE: Part 2 is now available

Yoke - a drop-in, quick and dirty alternative to Sprockets

In local development, I tend to create separate pieces of JavaScript for every controller, model, view, etc. I loved the idea of Sprockets – grab all these files and stitch them together. While it worked well for a while (on the command-line), I got fed up with how slow it runs, especially on Ruby under Cygwin.
So, what to do? Node.js of course. Why? It’s fast, it’s super-easy to install and writing a script on top of it is a breeze. Meet Yoke:

To use, copy the script above on $PATH and then:

yoke [-I path[ -I path]...] input.js > output.js

In most cases, you would just need to replace your existing sprocketize command with yoke. Both <file> and "file" requires are supported.

Extract changed files from Git and prepare a deployment (Bash script)

So, you have moved over to Git. Well done! You have big plans for your future deployment process – it is going to be automated and thoroughly tested… but you still have to support your existing legacy products. If you are like me, you most likely started with simple FTP copies (oh, this takes me back). I moved over from SVN and TortoiseSVN was my primary choice for a desktop client. After each deployment, I would create an SVN tag so next time I can get a list of all changed files. In TortoiseSVN it’s as simple as selecting two revisions while holding down the Shift key and doing a Compare Revisions. From the new window you can export all changed files to a new location. If your project in 100MB in size, you only ever have to deal with modified files on subsequent updates. Simple (or so I thought back then).

Moving over to Git has thought me many things. I used to love my Cygwin console even before the switch and I’ve learnt to do pretty much every daily task in it. But the one thing I really needed and didn’t have with Git was an option to copy modified files from the working tree between two commits. It wasn’t available on the command line, nor in any of the clients I’ve tried so far. So having been playing with shell scripts for a few weeks, I decided to put one together which would do just that:

Paste the above in a script on your $PATH. Using gitk or another tool find the first commit you want to include in your extract. Invoke the script as below:

$ git-extract initial_hash_of_first_commit_to_include..HEAD
Do you wish to remove '.deployments' first? [Y/n]
  Purging '.deployments'...
  Done.
Processing 10 files... 100.00%
Done.

A new directory .deployments will be created with all files that have changed in the given commits range. If files were deleted, a .delete file will be generated and you have to handle these manually.

I haven’t found a better solution yet, but I am sure there is a Git way to do just what I am after. This script is a hack to get the job done.

Leave a comment if you found this post useful. Fork the Gist and modify it to suit your needs.

Vim 7 on Red Hat Enterprise Linux (RHEL)

Following my last post I moved on to installing Vim 7.x as the one that comes with RHEL 4 is pretty outdated (6.x).

Vim 7.2 from SVN

Tested on RHEL 4 i386:

$ cd ~
$ svn co https://vim.svn.sourceforge.net/svnroot/vim/vim7
$ cd vim7
$ ./configure --prefix=/usr --with-features=huge --disable-gui --without-x --enable-rubyinterp --enable-cscope --enable-multibyte
$ make && make install
$ vim --version
VIM - Vi IMproved 7.2 (2008 Aug 9)

Vim 7.3 from Mercurial

Tested on RHEL 5 x86_64:

$ cd ~
$ yum install hg ncurses-devel
$ hg clone https://vim.googlecode.com/hg/ vim
$ cd vim
$ ./configure --prefix=/usr --with-features=huge --disable-gui --without-x --enable-rubyinterp --enable-cscope --enable-multibyte
$ make && make install
$ vim --version
VIM - Vi IMproved 7.3 (2010 Aug 15)

RHEL 4 comes with up2date. I tried -i mercurial and -i hg, but both failed. Vim offers an archived download so you can skip steps 3-4 above and instead use:

# [..]
$ wget ftp://ftp.vim.org/pub/vim/unix/vim-7.3.tar.bz2
$ tar xjvf vim-7.3.tar.bz2
$ cd vim73
# [..]

Let me know if this post helped you or if you had to install any additional libraries.