Optimize your PNG’s with oxipng and pre-commit
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.)
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.
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...
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.
rev is the commit SHA that added the hook configuration. When there’s a new
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
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,
Learn how to make your tests run quickly in my book Speed Up Your Django Tests.
One summary email a week, no spam, I pinky promise.