Optimize your PNG’s with oxipng and pre-commit

Artist’s impression of a PNG optimizer.

PNG’s can be losslessly optimized to reduce their file size, by picking a better compression algorithm for the given image. The savings can be significant, like 50% (especially for macOS screenshots). This is an easy way to make web sites load faster.

For a long time I used the pngcrush. It works okay, but its CLI is a bit clunky, and it doesn’t support nice features like parallel processing.

I recently came across oxipng as a modern, Rust-based alternative. It’s fantastic! I found it can shrink PNG’s further, and faster thanks to its use of threading.

I wanted to run oxipng under pre-commit. This way, every time I added a PNG to my repositories, it would be optimized before being committed. To create the pre-commit hook, I took a path of configuring it in three different ways.

(If you just want to set up oxipng, skip to level 3.)

Level 1: Local Repository Hook

Initially I installed oxipng with cargo, and used a pre-commit system hook:

repos:
-   repo: local
    hooks:
    -   id: oxipng
        name: oxipng
        entry: oxipng
        language: system
        types: [png]

This worked well for individual use, but it isn’t ideal for teams. Every developer has to install and update oxipng themselves, and different developers could use different versions. Also the whole hook definition needs copying to each repository.

So it’s better to use configuration in a separate repository, which allows pre-commit to handle isolated installation and updates. This is what I did next.

Level 2: Mirror Repository

I set up a “mirror” repository on my account, adamchainz/pre-commit-oxipng. This contains a .pre-commit-hooks.yaml file that tells pre-commit to install oxipng in a separate environment, and how to run the hook. Project repositories could then use it like so:

-   repo: https://github.com/adamchainz/pre-commit-oxipng
    rev: v5.0.1
    hooks:
    -   id: oxipng

I made this repository with pre-commit-mirror-maker. This is a small tool that can create the hook configuration, and update it when new versions of the underlying tool are released. It’s used for “official” mirror repositories like mirrors-mypy.

My oxipng mirror worked well, but it’s a shame that it requires a stanadalone repository with the mirror maker running daily to pull in new versions. It’s easier if the upstream tool repository can maintain the configuration...

Level 3: Upstream Inclusion

I opened an issue on the official oxipng repository, asking if it could include .pre-commit-hooks.yaml. The creator, Josh Holmer, replied yes, so I made my first pull request to a Rust project!

So now you can use oxipng under pre-commit like so:

- repo: https://github.com/shssoichiro/oxipng
  rev: acdd66b4c5a5fda8b08281ad9b3216327b6847b4
  hooks:
  - id: oxipng

This requires Rust—the standard way to install it is with rustup.

The above rev is the commit SHA that added the hook configuration. When there’s a new oxipng version, pre-commit autoupdate will update the SHA to the new tag.

One thing to note: optimizing all the PNG’s in your repository is expensive and fairly pointless after they’ve been optimized. You probably want to skip oxipng on your CI system, and run it only on commit. On pre-commit.ci declare it to be skipped like so:

ci:
  skip:
  - oxipng

On other CI systems, or when running locally, use pre-commit’s SKIP environment variable:

$ SKIP=oxipng pre-commit run --all-files

Fin

I enjoyed this open source exercise. I saw how pre-commit’s configuration model (YAML in Git repositories) is flexible. It gives us a different ways to try out tools as hooks, and make them “official”.

May your PNG’s be well optimized,

—Adam


Learn how to make your tests run quickly in my book Speed Up Your Django Tests.


Subscribe via RSS, Twitter, Mastodon, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: