Using Ruby to Compress images with TinyPNG
What is TinyPNG?
TinyPNG is an image tool for compressing and resizing png and jpg images. You can use it up to 500 times per month for free. They have client libraries for a lot of the common languages. I’m going to use the example of using this service to compress images that are served as a part of a Jekyll site, so the examples here will be in Ruby. This example assumes a working ruby environment.
Why compress images?
With the compression we are doing, the quality will still be very high, so for a normal user, this works great. Secondly, by shrinking images by about 50% or more, you’re making it so your pages will load more quickly for clients. And lastly, if the images are smaller, your storage and bandwidth bills will also be 50% cheaper. So in a bulleted list:
- Faster page load times
- Savings on storage
- Savings on bandwidth
Why not use ImageMagick?
You could use ImageMagick and other tools to automate the compression of your images. There’s no sensible reason not to. In fact, if you’re doing a significant amount of compression, you may want to switch over to do it yourself. In this case since the number of compressions per month will be well under 500, which is the free level, I’m going to use the service.
Step 1: Sign up
Go to the Developer API section and put in your name and email address. I’ll email you a link to your TinyPNG dashboard where you can get your API key.
Step 2: Get your API Key Setup
I prefer to set my keys in my environment. In this example, I’ve added the following to ~/.profile
:
export TINYPNG_API_KEY=<API key from TinyPNG>
Now every time I login, the key will be loaded into my environment. If you are using your CI/CD tool chain to manage the compression of images, that tool will need this setting.
The reason we’re not keeping this in _config.yml
is that it’s a secret. I don’t want to commit any secrets in git. Committing secrets, even if it’s only being pushed to a private repo is not a good habit to start.
Step 3: Update your Gemfile
Add tinify
to your Gemfile
:
gem 'tinify'
And install the dependencies:
bundle
Step 4: Create directories
I’m going to create two directories, one for the raw images, and one for the converted. I’m going to start the raw directory name with an underscore so Jekyll doesn’t automatically pull it in when building the site.
mkdir _raw_images
mkdir images
Add these directories as your source and destination directories in your Jekyll _config.yml
:
# TinyPNG Configuration
tinypng_src_dir: "_raw_images"
tinypng_dest_dir: "images"
Step 5: Create your rake task
There is a little bit of work required to build this rake task. I’m going to just give you the code show you an example of using it, and call it a day. I realize that this is a bit like the “draw an owl” exercise:
Here’s what a Rakefile could look like that’s using TinyPNG to compress images. It uses the configuration we’ve setup in step 4, and the environment variable from step 2:
# coding: utf-8
require 'find'
require 'jekyll'
require 'tinify'
config_file = '_config.yml' # Name of Jekyll config file
def fetch_jekyll_config(config_file)
site = Jekyll::Configuration.new
site.read_config_file(config_file)
end
site_configuration = fetch_jekyll_config(config_file)
task :image_compress do
# Read in directories for `_config.yml`
source_dir = site_configuration['tinypng_src_dir']
dest_dir = site_configuration['tinypng_dest_dir']
images_to_compress = find_images_to_compress(source_dir, dest_dir)
tinify_images(images_to_compress, dest_dir)
end
#### Misc Methods
def find_files_in_directory(directory)
files = {}
Find.find(directory) do |path|
if File.file?(path)
relative_path = path[directory.length, path.length]
files[relative_path] = path
end
end
files
end
def find_images_to_compress(source_dir, dest_dir)
# Only compress images that have not already been compressed.
src_files = find_files_in_directory(source_dir)
dst_files = find_files_in_directory(dest_dir)
results = src_files.keys - dst_files.keys
images = {}
results.each do |result|
images[result] = src_files[result]
end
images
end
def tinify_images(images, dest_dir)
Tinify.key = ENV['TINYPNG_API_KEY']
images.each do |k, v|
puts 'Compressing image: ' + v
source = Tinify.from_file(v)
source.to_file(File.join(dest_dir, k))
end
rescue => e
puts 'Something related to Tinify blew up: ' + e.message
end
Now lets see this rake task thing in action:
$ bundle exec rake image_compress
Configuration file: _config.yml
Compressing image: _raw_images/draw.an.owl.jpg
How much benefit you’ll see from compressing an image will vary. In the example of the owl image we have in this post, it saved just under 50%:
$ ls -alh *images/draw.an.owl.jpg
-rw-r--r--@ 1 brint staff 35K Apr 2 14:14 _raw_images/draw.an.owl.jpg
-rw-r--r-- 1 brint staff 18K Apr 2 14:15 images/draw.an.owl.jpg
Step 6 (optional): Rake task to check quota
If you are using TinyPNG to do a lot of compressions, you may want a job to check how you are doing relative to your quota:
require 'tinify'
task :tinypng_quota do
Tinify.key = ENV['TINYPNG_API_KEY']
Tinify.validate!
puts 'TinyPNG Compressions used this month: ' + Tinify.compression_count.to_s
end
Here it is in action:
$ bundle exec rake tinypng_quota
TinyPNG Compressions used this month: 3