Optimizing PNGs in GitHub Actions using Oxipng

My datasette-screenshots repository generates screenshots of Datasette using my shot-scraper tool, for people who need them for articles or similar.

Jacob Weisz suggested optimizing these images as they were quite big. I want them to be as high quality as possible (I even take them using --retina mode), but that didn't mean I couldn't use lossless compression on them.

I often use squoosh.app to run a version of Oxipng compiled to WebAssembly in my browser. I decided to figure out how to run that same program in GitHub Actions.

Installing Rust apps using Cargo

Surprisingly there isn't yet a packaged version of Oxipng for Ubuntu - so I needed another way of installing it.

The project README suggests installing it using cargo install oxipng.

I used the tmate trick to try that out in a GitHub Actions worker - the cargo command is available by default but it took over a minute to fetch and compile all of the dependencies.

I didn't want to do this on every run, so I looked into ways to cache the built program. Thankfully the actions/cache action documents how to use it with Rust.

The full recipe for installing Oxipng in GitHub Actions looks like this:

    - name: Cache Oxipng
      uses: actions/cache@v3
      with:
        path: ~/.cargo/
        key: ${{ runner.os }}-cargo
    - name: Install Oxipng
      run: |
        which oxipng || cargo install oxipng

The first time the action runs it does a full compile of Oxipng - but on subsequent runs the which oxipng command succeeds and skips the cargo install step entirely.

Running Oxipng in an Action

All of the PNGs that I wanted to optimize were in the root of my checkout, so I added this step:

    - name: Optimize PNGs
      run: |-
        oxipng -o 4 -i 0 --strip safe *.png

The -o 4 is the highest recommended level of optimization.

-i 0 causes it to remove interlacing - "Interlacing can add 25-50% to the size of an optimized image" according to the README.

--strip safe strips out any image metadata that is guaranteed not to affect how the image is rendered.

Oxipng updates the specified images in place, hence the *.png at the end.

Testing this in a branch first

I tested this all in a branch first so that I could see if it was working correctly.

Since my workflow usually pushes any changed files back to the same GitHub repository, I added a check to that step which caused it to only run on pushes to the main branch:

    - name: Commit and push
      if: github.ref == 'refs/heads/main'
      run: |-
        git config user.name "Automated"
        ...

But I wanted to preview the generated images - so I added this step in the branch to save them to an artifact zip file that I could then inspect:

    - name: Artifacts
      uses: actions/upload-artifact@v3
      with:
        name: screenshots
        path: "*.png"

Once I got it working, I squash-merged this pull request back into main.

The result

Oxipng worked really well!

It reduced the size of all three of my screenshots:

This commit shows the difference and lets you compare both images.

Created 2022-05-18T18:45:23-07:00, updated 2022-09-12T14:22:14-07:00 · History · Edit