How Do We...?

How Gerbils Make Sausage

by Dave Thomas

People often ask how we do what we do. This series
explains…

This month's question:
How do you generate the different cover images on pragprog.com?

We use book covers all over the Pragmatic online store: on the book pages, in the order
summary, on your bookshelf listing, and so on. We display the images in five different sizes, from
40x48 thumbnails up to full-size 72dpi images. And, to make life even more interesting, we
overlay those images with variable little icons to show that the book is in beta, that an eBook
update is available, and so on.

Being pragmatic (read “lazy”) we didn’t want to manage all those combinations manually,
so Mike Clark and John Long cooked up the software and CSS to make it all automatic.

It all starts on my desktop machine, where I use InDesign to create the covers that I’ll
send to the printer. As a part of that process, I export full-size 72dpi and 300dpi JPEG
versions of the front cover. Our production folks check these images out of our repository
and upload them to our admin system.

Perhaps surprisingly for those used to modern Rails, we don’t use a library such
as PaperClip to do this upload. Even if it had been around when we wrote the store, we
do enough custom processing that I’m not sure it would have added much value. Instead
we just use a raw file_field_tag to upload the full-size JPEG.

We save the original covers away—you can view them by clicking on the large cover image on the
main page for each title. We then feed the cover through ImageMagick to create the scaled versions.
We could have done this using library calls, but we were concerned about reported memory
leaks, and so preferred to shell out:

def create_all_sizes

base_name = File.basename(path_to_original)

geometries = ['40x48', '75x90', '120x144', '140x168', '190x228']

geometries.each do |geo|

FileUtils.mkdir_p(File.join(COVERS_DIRECTORY, geo))

cmd = "convert -resize #{geo} #{path_to_original} " +

"#{COVERS_DIRECTORY}/#{geo}/#{base_name}"

system cmd

end

end

Next, we create the versions of the covers that have the eBook, beta, and the PDF-out-of-date stamps.
There’s a version of each stamp for each different cover size (we tried scaling them down from
a large master, but they looked horrible). Here’s the code that
generates the two sizes of cover with the eBook stamp:

def create_ebook_overlays

# small_ebook

create_cover_with_overlay(

:overlay => File.join(IMAGES_DIRECTORY, 'ebook-28.png'),

:file => path_to_small,

:output_dir => File.join(COVERS_DIRECTORY, '40x48', 'ebooks'))

# medium book

create_cover_with_overlay(

:overlay => File.join(IMAGES_DIRECTORY, 'ebook-46.png'),

:file => path_to_medium,

:output_dir => File.join(COVERS_DIRECTORY, '75x90', 'ebooks'))

end

The code that does the overlaying uses some RMagick magic:

def create_cover_with_overlay(options)

FileUtils.mkdir_p(options[:output_dir])

base_name = File.basename(options[:file])

images = Magick::ImageList.new(options[:file], options[:overlay])

x_offset = images[0].columns - images[1].columns

y_offset = images[0].rows - images[1].rows

images[1].page =

Magick::Rectangle.new(images[0].columns, images[0].rows,

x_offset, y_offset)

images.

flatten_images.

write(File.join(options[:output_dir], base_name))

end

A little processing up front, and we have 16 versions of a book cover all ready to serve in
the store. A wee post-deploy hook symlinks the master covers directory into the application
each time we update it.