paazmaya.fi

The Website of Juga Paazmaya | Stories about web development, hardware prototyping, and education

Making an image to icon converter CLI tool in Rust

Occasionally, but not too often, I have the need to create the favicon.ico file, which is usually requested from a web server, when a web page is opened.

It is a bit of a relic format, but it has been the standard of showing something of the page next to the address bar and later in the tabs of the browser. Read more about its history at Wikipedia.

Even while there are ways to set up more customized icon structures, sizes, and formats, those tend to take a bit of time, and when launching a new website or just wanting to get done by this single file, then favicon.ico is the approach to take.

While I have been teaching the Rust programming language at Metropolia University of Applied Sciences, one of the examples shown and constructed during the lectures has been related to image handling.

Initially, these examples have been in one monorepo where I keep all of my Rust examples, and taking the git commit history along with the small project out was not too easy.

I wrote about a similar extraction of a certain folder in a monorepo when pulling out the QR code generator and extending it further, so I will mostly focus on the additional challenges faced and then solved.

Not having Git LFS objects available

Once the folder containing this image to icon converter tool was filtered out from the monorepo, the first challenge faced was the missing Git LFS objects.

Pushing the fresh git repository to https://github.com/paazmaya/chinenshichanaka resulted in:

git push
Unable to find source for object 5c804e2bf01aa2bdd66b070fac29194bfd54b2e25edb0e735713da6e17ce5bae (try running `git lfs fetch --all`)
Uploading LFS objects:   0% (0/3), 0 B | 0 B/s, done.
error: failed to push some refs to 'github.com:paazmaya/chinenshichanaka.git'

Fetching those objects did not help, of course, since the origin was the new GitHub repository which does not have them:

git lfs fetch --all
fetch: 14 objects found, done.
fetch: Fetching all references...
[1881fe9c506c970d7866cb4bfc33bda791ce46951a3c39c45ace2ff2b9daf369] Object does not exist on the server: [404] Object does not exist on the server
[a2c44ecf4860acdf03193d41b7d2957637d0b14b8a9e339463b892b0acb9a12f] Object does not exist on the server: [404] Object does not exist on the server
[f715cc2d99740d22d312777e20d9de2b2ecdc250155be8fd3752ce7e8b823521] Object does not exist on the server: [404] Object does not exist on the server
[528c823ae40b83c87de9a69cc207e9995ac103f6718ff4b888497be11e2ea1ff] Object does not exist on the server: [404] Object does not exist on the server
[33a3b622c7cac0762f96089353cd61495f3e993968d133af7871bfc2d5396704] Object does not exist on the server: [404] Object does not exist on the server
[5c804e2bf01aa2bdd66b070fac29194bfd54b2e25edb0e735713da6e17ce5bae] Object does not exist on the server: [404] Object does not exist on the server
[2d88972e9536ba54deed37db380728b3c58eab9bca1d173ae523098b0774cdf5] Object does not exist on the server: [404] Object does not exist on the server
error: failed to fetch some objects from 'https://github.com/paazmaya/chinenshichanaka.git/info/lfs'

At this point, I tried to allow pushing incomplete objects:

git config lfs.allowincompletepush true

But that only removed the local blocker and the remote still did not accept the push due to the missing objects.

Wait a second, perhaps those objects are still in the original monorepo which I did not yet clean?

First, add the original repository as a remote called “gitlab” and fetch the objects:

git remote add gitlab git@gitlab.com:paazmaya/realistic-rust-course-lessons.git
git lfs fetch gitlab
fetch: Fetching reference refs/heads/main

Looked promising until I tried to push to GitHub:

git push
Git LFS upload missing objects:3), 26 KB | 0 B/s
  (missing) icon.png (5c804e2bf01aa2bdd66b070fac29194bfd54b2e25edb0e735713da6e17ce5bae)
Uploading LFS objects:  67% (2/3), 26 KB | 0 B/s, done.
Enumerating objects: 69, done.
Counting objects: 100% (69/69), done.
Delta compression using up to 32 threads
Compressing objects: 100% (45/45), done.
Writing objects: 100% (69/69), 32.00 KiB | 1.03 MiB/s, done.
Total 69 (delta 23), reused 30 (delta 11), pack-reused 0
remote: Resolving deltas: 100% (23/23), done.
remote: error: GH008: Your push referenced at least 1 unknown Git LFS object:
remote:     5c804e2bf01aa2bdd66b070fac29194bfd54b2e25edb0e735713da6e17ce5bae

Perhaps not all of the objects were fetched due to local path matching?

There is this --all option, which might help:

git lfs fetch gitlab --all
fetch: 14 objects found, done.
fetch: Fetching all references...

Now looking much better since getting all the objects from the monorepo and when pushing to GitHub, the missing objects are found locally:

git push
Uploading LFS objects: 100% (3/3), 48 KB | 0 B/s, done.
Enumerating objects: 69, done.
Counting objects: 100% (69/69), done.
Delta compression using up to 32 threads
Compressing objects: 100% (45/45), done.
Writing objects: 100% (69/69), 32.00 KiB | 1.39 MiB/s, done.
Total 69 (delta 23), reused 30 (delta 11), pack-reused 0
remote: Resolving deltas: 100% (23/23), done.
To github.com:paazmaya/chinenshichanaka.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

Packaging dep package did not accept the PNG file

Now that the project was successfully moved and renamed as chinenshichanaka, which I intend at this point to publish to crates.io and provide installers at GitHub Actions summary, the challenge now faced was that the icon images were not accepted by cargo-packager.

The packaging ended on an error at Ubuntu on GitHub Actions stating:

 INFO Packaging chinenshichanaka_0.1.0_amd64.deb (/home/runner/work/chinenshichanaka/chinenshichanaka/packages/chinenshichanaka_0.1.0_amd64.deb)
ERROR Format error decoding Png: Invalid PNG signature.

Invalid signature? Tried to investigate with PNG file chunk inspector at nayuki.io what might be the issue, but nothing came up.

Could it be that I use pngquant to optimize PNG images?

The icon in question identifies with GraphicsMagick like this:

gm identify icon-64x64.png
icon-64x64.png PNG 64x64+0+0 PseudoClass 12c 8-bit 924 0.000u 0m:0.000001s

There were only 12 colors used. Saving with another tool to use “normal” minimal colorspace, which shows up as 16 colors:

gm identify icon-64x64.png
icon-64x64.png PNG 64x64+0+0 PseudoClass 16c 8-bit 908 0.000u 0m:0.000001s

Still no. The same issue at GitHub Actions with packaging fails on the invalid PNG signature.

How does Tauri achieve this, it has its own command line option to create icons?

The following command was made in a project where Tauri was already in use, as the icon command requires an existing project:

cargo tauri icon chinenshichanaka_icon_huge.png

This resulted in plenty of PNG files of varying sizes. Seeing how GraphicsMagick identifies one of them:

gm identify 32x32.png
32x32.png PNG 32x32+0+0 DirectClass +opacity 8-bit 2.0Ki 0.000u 0m:0.000002s

Placing this icon in the project and using it as the icon…

Still no luck.

Now would be a good time to use Docker, to reproduce the problem locally.

The ${PWD} in the command below is the way to get the current working directory in PowerShell:

docker run --rm -it --volume ${PWD}:/work rust:1.83.0-alpine3.21

Then running the required commands in the terminal opened to that running container:

cd /work # Go to the directory where the project is linked
cargo install cargo-packager # Install the packager tool
cargo packager --release --verbose # More verbosity to see what exactly happens

Everything worked. So if I cannot reproduce this locally, what could be the issue?

After sleeping on it, the issue became clear. It was the Git LFS issue all along, since the GitHub Action was not pulling those objects which I was just tackling earlier.

The fix in the workflow was done in two steps. First, tell the actions/checkout to fetch the Git LFS information, then pull the relevant objects:

- name: Checkout code
  # https://github.com/actions/checkout
  uses: actions/checkout@v4
  with:
    lfs: true

- name: Pull LFS data
  # Checkout will only fetch the data but will not expand the files
  run: git lfs pull

First release (v0.1.0) out

Now the installers were created for all platforms, visible in the v0.1.0 release commit at https://github.com/paazmaya/chinenshichanaka/actions/runs/12486623640.

The chinenshichanaka tool itself is also available at crates.io and can be installed with the command:

cargo install chinenshichanaka

Right now it will only accept PNG images as input and will create a favicon.ico file as output.

chinenshichanaka logo.png

Feedback and Pull Requests are welcome!