This script converts images into patterns for Animal Crossing: New Horizons.
This is written in Python 3 and requires NumPy and OpenCV.
Call the script using:
./convert-pattern.py path/to/your/image.png
Your image should be a 32×32 pixel image. It may be in any file-type which OpenCV is
able to open, which includes most common image formats such as .jpg
and .png
.
This will generate a preview image at nh-pattern.png
, and a text file containing
instructions of how to produce the pattern at nh-pattern-instructions.txt
.
The output location of the preview image may be overridden with the -o
/--out
option,
and the location of the instructions file may be overridden with the
-i
/--instructions-out
option. eg.:
./convert-pattern.py input.png --out preview-output.png --instructions-out instructions-output.txt
This file will show you how to draw the generated pattern.
eg.:
Colour palette:
Hue: 6 8 4 11 3 7 4 11 3 4 7 11 4 2 15
Vividness: 5 7 6 9 6 9 7 11 7 6 6 12 5 12 10
Brightness: 11 10 13 6 14 12 14 9 14 14 8 7 13 8 5
This section shows the colour palette to use to create this pattern. Each column
of numbers represents one colour in the palette. Each row represents one channel
in the colour space used by New Horizons. The numbers represent how far along each
slider to go, with 0
being the furthest left value on each slider. For hue, the
furthest right value is 29, while for vividness and brightness, the furthest right
value is 14.
eg.:
Colour 3 [11 9 6]:
c c · · · · · · · c · c · c · · · · · · c c c · · · · · · · · c
· · · · · · · · · · · · · · · · a b a · · · · · c · c · · c · ·
· · · · c · c c c c · c c a b · · # # a · · · · · · · c · · · ·
· · · c c · c b · · · · · · · · · · · · · · · · · c · · · c · ·
· · · · c · a · · · · · · · · · · · · · · · · · · · c c c · c ·
· · · · · · b · · · · · · · · · # # # · · · · · · c c · c · · ·
· · · c · · a · · # · · · # # · · · # · · · · · · · c · c · c ·
c · c · · · · · · # · · · · · · · # # a · c c · · · · · · · c ·
c · c · · · · a · · · · · · · # # # # · c · c c c · · · · · · c
· c · · · · · a · · · # # · # · · · · a c · · c · · · · · · · ·
· · c · · · · a · · · · · · · # · # · · c · c · c · · · · · · ·
c · · · · · · c · · · · · · · · · · · · c · · c · · · · · · c ·
· · · · · · · c · · · · · · · · · # · # · · · · · · · · · · · ·
· · · · c c · a · · · · · · · · · · # # · c · · · · c · c · · ·
· · · · c · · a · · · · · · · · · # · # · # · c c · c · c · · ·
· · · · · c · · · · · · · · · b · b b b b b # # a · · c · · · ·
[...]
The next section in the instructions file is a map of pixels for each colour in
the palette. Pixels represented as a dot (·
) have not yet been filled by any
colour. Pixels represented by a hash (#
) should be filled in with the current
colour. Pixels represented by a lower-case letter should have already been filled
by a previous colour. The first colour is represented by an a
, the second colour
is represented by a b
, and so on.
To choose a palette, the script uses k-means clustering on all the colours present in the image. This chooses a good subset of colours to represent the full set of colours.
Before performing k-means, the colours are converted to the Lab colour space as this provides a better representation of perceptual differences between colours.
After a palette has been generated, it is rounded to get an approximation of the palette, which can be used in New Horizons. The palette is then used map each pixel in the image to a colour in the palette.
Dithering may be used to simulate better colour variation, while creating a slightly more noisy look to the pattern, which may be desirable or undesirable depending on the image. The script can use Floyd-Steinberg dithering to achieve this effect in your patterns.
This option defaults to off, but may be toggled on using the -d
/--dithering
flag.
Sometimes, when a palette is generated, two or more of the colours may be close enough to each other that when converted to New Horizons' colour space, they are rounded to exactly the same value. This is obviously undesirable, as this means one colour is effectively going unused.
The current default policy to handle this situation is simply to output a warning
and continue regardless. Alternatively, you can pass the -r
/--retry-duplicate
flag to automatically increment the RNG seed and retry palette generation until
a palette with no duplicates is created.
A weight map may be used to give higher weights to some pixels when choosing palettes.
A weight map may be passed using the -w
/--weight-map
, and should be a greyscale
image with the same size as the main input image. Areas in the main image corresponding
to brighter values in the weight map will receive greater consideration than darker
areas when performing k-means to choose colours. This can be used to give more detail
to an area if you think it needs it.
The k-means algorithm does not generate the optimal set of clusters for its input data.
Doing so would be an NP-hard problem.
Rather, it is a heuristic which aims to find a "good" solution in a reasonable time.
It also depends on a randomised starting state, and as such, may produce slightly
different results each time it is run. This starting state is determined by the
RNG seed. The script generates and
prints a random seed before starting k-means. If you want to re-run the script exactly
as you previously ran it, you can pass a seed using -s
/--seed
.
The script prints some information about what it is doing to stdout as it runs. If you
want to see some more debug information, you can pass the -v
/--verbose
flag. If you
want to see less information you can pass the -q
/--quiet
flag. This flag also stacks
to hide even more output. Stacking the flag twice (-qq
) hides info and warnings.
Stacking the flag three times (-qqq
) hides info, warnings, and errors.
The instructions file may be written with TTY colour control codes. This defaults to
on if the instructions file is being written directly to a console, but may also be
forced on with -c
/--tty-colours
or forced off with --no-tty-colours
.