tl;dr
I’ve written {pixeltrix}, an R package that lets you select ‘pixels’ interactively from a plot window and returns your final image as a matrix. You could use it to design sprites, for example.
Pixel perfect
I’ve written before about creating very simple pixel art in R. To create a sprite of Link from The Legend of Zelda I had to write out by hand a vector that encoded its pixel values. It was tedious.
There are, however, a couple of options in R to take an image and extract the pixels from it: see Florian Privé’s Shiny app in the {pixelart} package and Mike Cheng’s (AKA coolbutuseless) blog post that also describes how to animate them.1
But what if you want to create a sprite from scratch? It would be great if you could click pixels interactively and be returned a matrix encoding your image.
I couldn’t find an R package to do this, so I decided to make a very simple one: {pixeltrix} (as in ‘pixel’ and ‘matrix’, but also as in ‘tricks’2, lol).
It’s written entirely in base R (no Shiny or server needed) and can be run in the R console. It’s basically a repeat
loop that runs image()
to plot squares3 (hereafter ‘pixels’) and locator()
4 to let you click those pixels on and off. The coordinates of each click are matched to the nearest pixel centre, the pixel’s value is incremented by 1 (or wrapped back to zero) and the image is redrawn.
The package is still in development, but I think it’s reached a useable state for my own purposes.
Update
I lied. The package has been updated since this post. You can read about the changes in a more recent blogpost. Highlight: you can make animations now.
Enter the matrix
The package is available for download from GitHub. I have some ideas on how to improve it; go ahead and add your own ideas to the issues tracker.
# install.packages(remotes) # if not yet installed
remotes::install_github("matt-dray/pixeltrix") # v0.1 in this post
library(pixeltrix)
The main function is click_pixels()
, to which you pass plot dimensions (how many pixels tall and wide), the number of pixel ‘states’ (the number of values a pixel can take, so binary would be 2
) and whether you want to put a grid over the plot (makes it easier to see where the pixels are).
click_pixels(
n_rows = 3,
n_cols = 3,
n_states = 2,
grid = TRUE
) -> x
This opens a plot window. Repeatedly click a pixel to cycle it through the number of states you asked for. For example, n_states = 4
means you cycle it through values of 0, 1, 2 and 3 (wrapping back to 0), which will be manifested in the plot as different shades of grey.
Note that you can only click one pixel at a time, so you’ll have to do a lot of clicking if your n_states
value is high. Colouring stuff in really slowly is called ‘mindfulness’, I believe; good for your wellbeing.
When you’re done, you press the Esc key, or the ‘Finish’ button in the plot window of RStudio. I saved all the images below via the ‘Export’ button in RStudio.
You’re returned a matrix that contains the value of each pixel in your image. So if you had set n_states = 3
, a twice-clicked pixel gets the value 2, an unclicked pixel defaults to a value of 0, etc.
x
# [,1] [,2] [,3]
# [1,] 1 0 1
# [2,] 0 1 0
# [3,] 1 0 1
This matrix is basically a blueprint of the image you created. You can take this and do other things with it. Maybe you’ll make art by passing it to ggplot()
to match each of the pixel-state values to a colour. Maybe you’ll use it to plan your crochet or cross-stitch5, or to teach spatial epidemiology (!).
If you want to edit your matrix, you can pass it into edit_pixels()
. This means you don’t have to start over from scratch with click_pixels()
if you only want to change something small. Note that you can provide a higher n_states
value to edit_pixels()
than the current maximum in the matrix you provided.
Sprite club
My main purpose for the package is to create simple sprites.
I used {pixeltrix} to make each of the sprites below. They took about a minute each. It would’ve taken much longer to write their matrices by hand and to keep passing them to image()
to visuliase them and make sure there weren’t any mistakes.
Tamagotchi
Here’s a 1-bit original kuchipatchi sprite from the original 90s Tamagotchi digital pets. It uses the default of two pixel states (binary): so 0 for white and 1 for grey.
click_pixels(14, 14) -> tam_sprite
Click to expand matrix
tam_sprite
# [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14]
# [1,] 0 0 0 0 1 1 1 1 1 1 0 0 0 0
# [2,] 0 0 0 1 0 0 0 0 0 0 1 0 0 0
# [3,] 0 1 1 0 1 0 0 0 0 1 0 1 0 0
# [4,] 1 0 0 0 0 0 0 0 0 0 0 0 1 0
# [5,] 0 1 1 1 0 0 0 0 0 0 0 0 1 0
# [6,] 1 0 0 0 0 0 0 0 0 0 0 0 1 0
# [7,] 0 1 1 1 0 0 0 0 0 0 0 0 1 0
# [8,] 0 0 0 1 0 0 0 0 1 0 1 0 0 1
# [9,] 0 0 0 1 0 0 0 0 1 0 1 0 0 1
# [10,] 0 0 0 1 0 0 0 0 0 1 0 0 0 1
# [11,] 0 0 0 0 1 0 0 0 0 0 0 0 1 0
# [12,] 0 0 0 0 0 1 0 1 1 1 0 1 0 0
# [13,] 0 0 0 0 0 1 0 1 0 1 0 1 0 0
# [14,] 0 0 0 0 0 0 1 0 0 0 1 0 0 0
Pokémon
Here’s the player character from the first generation of Pokémon games on the Game Boy. It uses three states (n_states = 3
): value 0 is white, 1 is light grey and 2 is dark grey.
click_pixels(14, 16, 3) -> poke_sprite
Click to expand matrix
poke_sprite
# [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14]
# [1,] 0 0 0 0 2 2 2 2 2 2 0 0 0 0
# [2,] 0 0 0 2 1 1 1 1 1 1 2 0 0 0
# [3,] 0 0 2 1 1 1 1 1 1 1 1 2 0 0
# [4,] 0 0 2 1 1 1 1 1 1 1 1 2 0 0
# [5,] 0 2 2 2 1 0 0 0 0 1 2 2 2 0
# [6,] 0 2 2 0 2 2 2 2 2 2 0 2 2 0
# [7,] 2 0 2 0 0 0 0 0 0 0 0 2 0 2
# [8,] 2 0 0 0 0 2 0 0 2 0 0 0 0 2
# [9,] 0 2 2 0 0 2 0 0 2 0 0 2 2 0
# [10,] 0 2 2 2 0 0 1 1 0 0 2 2 2 0
# [11,] 2 0 0 2 2 2 2 2 2 2 2 0 0 2
# [12,] 2 0 0 2 2 2 2 2 2 2 2 0 0 2
# [13,] 0 2 2 2 1 1 2 2 1 1 2 2 2 0
# [14,] 0 0 2 1 2 2 1 1 2 2 1 2 0 0
# [15,] 0 0 2 1 1 1 2 2 1 1 1 2 0 0
# [16,] 0 0 0 2 2 2 0 0 2 2 2 0 0 0
Why?
Turns out the {pixeltrix} package is actually yak-shaving for another package I’m developing: {tamRgo}.
{tamRgo} is a (very much work-in-progress) conceptual package for a Tamagotchi-like experience in the R console. You get a persistent interactive digital pet on your computer whose stats update in ‘real time’ while you’re away.
I want to print a largeish canvas of pixels to visualise multiple pet ‘species’ and for the various interactions you can have (playing, feeding, cleaning). {pixeltrix} makes it much easier to design these scenes and returns matrices that I can add directly to {tamRgo}.
Update
I’ve now written a post about {tamRgo}, where you can see how {pixeltrix} was used for the character sprites.
Session info
## ─ Session info ───────────────────────────────────────────────────────────────
## setting value
## version R version 4.2.0 (2022-04-22)
## os macOS Big Sur/Monterey 10.16
## system x86_64, darwin17.0
## ui X11
## language (EN)
## collate en_US.UTF-8
## ctype en_US.UTF-8
## tz Europe/London
## date 2022-12-22
## pandoc 2.19.2 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/ (via rmarkdown)
##
## ─ Packages ───────────────────────────────────────────────────────────────────
## package * version date (UTC) lib source
## blogdown 1.9 2022-03-28 [1] CRAN (R 4.2.0)
## bookdown 0.26 2022-04-15 [1] CRAN (R 4.2.0)
## bslib 0.3.1 2021-10-06 [1] CRAN (R 4.2.0)
## cli 3.3.0 2022-04-25 [1] CRAN (R 4.2.0)
## digest 0.6.29 2021-12-01 [1] CRAN (R 4.2.0)
## evaluate 0.15 2022-02-18 [1] CRAN (R 4.2.0)
## fastmap 1.1.0 2021-01-25 [1] CRAN (R 4.2.0)
## fontawesome 0.2.2 2021-07-02 [1] CRAN (R 4.2.0)
## htmltools 0.5.2 2021-08-25 [1] CRAN (R 4.2.0)
## jquerylib 0.1.4 2021-04-26 [1] CRAN (R 4.2.0)
## jsonlite 1.8.0 2022-02-22 [1] CRAN (R 4.2.0)
## knitr 1.39 2022-04-26 [1] CRAN (R 4.2.0)
## magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.2.0)
## pixeltrix * 0.2.0 2022-12-22 [1] local
## R6 2.5.1 2021-08-19 [1] CRAN (R 4.2.0)
## rlang 1.0.2 2022-03-04 [1] CRAN (R 4.2.0)
## rmarkdown 2.14 2022-04-25 [1] CRAN (R 4.2.0)
## rstudioapi 0.14 2022-08-22 [1] CRAN (R 4.2.0)
## sass 0.4.1 2022-03-23 [1] CRAN (R 4.2.0)
## sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.2.0)
## stringi 1.7.6 2021-11-29 [1] CRAN (R 4.2.0)
## stringr 1.4.0 2019-02-10 [1] CRAN (R 4.2.0)
## xfun 0.30 2022-03-02 [1] CRAN (R 4.2.0)
## yaml 2.3.5 2022-02-21 [1] CRAN (R 4.2.0)
##
## [1] /Library/Frameworks/R.framework/Versions/4.2/Resources/library
##
## ──────────────────────────────────────────────────────────────────────────────
You can even go into the third dimension (i.e. voxels) with Mike’s {isocubes} and its extension {oblicubes} by Trevor L Davies. I used these in a demo of ‘3D’ dungeon-making with my {r.oguelike} package.↩︎
Illusions, Michael.↩︎
The only awkward part is that
image()
doesn’t plot with bounds of 0 to 1. There’s an overhang dependent on the number of squares you want to draw. This results in some small but awkward calculations so that the clicks can be mapped properly to the nearest pixel and so the grid overlay can be placed correctly.↩︎I’ve written before about using the
locator()
function to select points on fictitious maps.↩︎But see also Sharon Machlis’s ‘Overlay Mosaic Crochet Pattern Chart Generator’ Shiny app for crochet patterns and Ben Vigreux’s {embroidr} for planning embroidery projects.↩︎