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:

Draw the owl

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