When I redid my site to use Hugo I also redid my favicon . I wanted this favicon to also serve as the Add to Home Screen functionality provided by Chrome . I expected Chrome to automatically scale up the favicon. When I designed it I made it pixelated on purpose, so some very crude scaling was just what I wanted. Unfortunately Chrome doesn’t scale automatically, so my add to home screen icon was just a blue square with a white S set in the center.
It turns out these are called Touch Icons, and they’re more complicated than I
expected.
This blog post
does a
good job with an overview. Basically, Chrome uses a conventional <link rel="icon" sizes="192x192x" href="/touch-icon-192x192.png">
syntax and Apple
uses a number of rel="apple-touch-icon-precomposed"
with different sizes
values.
I wanted a simple command to scale up my favicon, keeping it pixelated. All the tools I could find to scale up an icon tried to smooth it out and ended up making it look trippy. First I used Preview to export the favicon as a PNG so that tools would have an easier time interacting with it.
ImageMagick’s convert
command:
convert -resize 192x192 favicon.png favicon_imagemagick.png
Scale2x
’s scalex
command, installed from their
github
, tried to round off the edges,
which isn’t what I wanted.
scalex -k 4 favicon.png favicon_scalex.png
Note that because
scalex
only permits certain scaling factors, the inline image above is further scaled to 192x192 using the technique described below. The original output of the command had dimensions of 16 * 4 = 64, and can be seen here .
Can you do this with something like Inkscape or Preview? Probably, but it’s a lot of annoying dragging and dropping.
Despite my best efforts, I couldn’t find a simple “scale and keep it looking
pixelated” solution online. It must exist, possibly even as an ImageMagick
filter, but that documentation is confusing. It was easy enough to make my own.
All I needed to do was access the pixels of the PNG and amplify each one by my
scaling factor, making it essentially take up a new square pseudo-pixel in the
image. This new pseudo-pixel would be a square of factor
true pixels on each
side.
virtualenv
To access PNG byte’s I used
PyPNG
.
I wasn’t able to install it globally for some reason, so I used virtualenv
.
mkdir virtualenv_png
cd virtualenv_png
virtualenv .
source bin/activate
pip install pypng
Now you can import png
. Don’t forget that to exit the virtual environment and
get back to regular Python you need to type deactivate
.
This script loads the PNG and amplifies each pixel as described above. The result is a perfectly scaled, pixelated icon.
# -*- coding: utf-8 -*-
"""
Scale a PNG by an integer factor, keeping it pixelated.
"""
import png
from sys import argv
# We're assuming rgba for for ints per pixel. Looking at the pypng docs there
# are other options this might be, but this is all I needed.
VAL_PER_PIXEL = 4 # rgba
if __name__ == '__main__':
# we expect scale_factor, in_file, out_file
if len(argv) != 4:
raise ValueError('Usage: scale_factor, in_file, out_file')
factor = int(argv[1])
in_file = argv[2]
out_file = argv[3]
reader = png.Reader(in_file)
content = reader.asDirect()
rows = content[2]
# content[2][0] assumes at least one row. There are original_width pixels in
# this row, and each pixel has VAL_PER_PIXEL ints for each pixel.
original_width = len(content[2][0]) / VAL_PER_PIXEL
original_height = len(content[2])
out_width = original_width * factor
out_height = original_height * factor
all_rows = []
# Iterate over each row. Each pixel is represented by several ints (the
# exact number depends on the file and is defined by VAL_PER_PIXEL). Each
# pixel must be grown by factor, and each row must then be appended factor
# number of times to all_rows.
for row in rows:
new_row = []
for num_pixel in range(len(row) / VAL_PER_PIXEL):
# / 4 for rgba
# if a single pixel row was [255, 128, 25, 0], we would want to
# expand that times 4.
pixel_start = num_pixel * VAL_PER_PIXEL
pixel_values = row[pixel_start:pixel_start + VAL_PER_PIXEL]
larger_values = list(pixel_values) * factor
new_row = new_row + larger_values
for i in range(factor):
# we've enlarged the row, now add it.
all_rows.append(new_row)
with open(out_file, 'w') as f:
# alpha = True for rgba. This will have to be updated for PNGs without
# alpha.
w = png.Writer(out_width, out_height, alpha=True)
w.write(f, all_rows)
With this script, we can take the 16x16 favicon png and scale it up by a factor
of 12. First copy or
download
the script and save it
in your virtualenv
directory that has pypng
installed, then run it. In this
case, we’ve saved the scrip as scale.py
. Here is the same script in a
gist
. Note
that I’ve only tested this with my single favicon, but for that it works
just fine. PNGs in general, non-square PNGs, etc – no promises.
python scale.py 12 favicon.png favicon_scaled.png
And there we have it:
Now that we have the largest size and we know it’s perfect, we can use ImageMagick to convert to the other sizes. 120x120, or instance, is:
convert -resize 120x120 favicon_scaled.png favicon_120.png
Then add the <link>
tags to your <head>
and be happy they’re so pretty.