---
title: Turn Your Code Into Pixel Art
teaser: Your code can be art. Literally.
tags: ruby,interpreters,art,fun
author: Matheus Richard
published_on: 2024-01-02
---

In 2022, I found out what [the average programming language color][] was. Today,
I want to transform code itself into pixel art (in less than 100 LOC).

## What?

The plan is very simple: I'll parse a Ruby file to collect all token types,
assign each token a color, and then render each one as a pixel in a grid. Sounds
like a plan, right?

If you're only interested in the script, [here it goes][]. Just run `ruby
code_picture.rb your-file.rb`, and you'll get your _abstract pixel art_. I'll
be going over some of the most interesting parts of that code. Stick around, and
you might learn something cool.

## Parsing Ruby

The whole reason this silly idea exists is me trying out the new Ruby parser:
[Prism][]. So, that's what I'm going to use to parse code. It's pretty cool that
it parses Ruby itself (at least [since CRuby 3.3][]) and also works as a gem.

Anyway, to use this bad boy in a single-file script, I'll use an [inline
Gemfile]. It will install any dependencies needed when we run the code. I'll
also install Nokogiri because it will be useful soon.

```rb
require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "prism"
  gem "nokogiri"
end
```

After that, using the [Prism Ruby API] is pretty straightforward:

```rb
result = Prism.lex_file(file)
if result.failure?
  errors = result.errors.map { "  - #{_1.message}" }
  abort("Failed to parse file:\n#{errors.join("\n")}")
end
```

<aside class="warn">
  <strong>Fail early</strong>
  <p>
    Bugs often come from the boundaries of your code.
    <a href="https://thoughtbot.com/blog/debugging-at-the-boundaries">Failing fast</a>
    is a good way to prevent them from spreading to the rest of your app.
  </p>
</aside>

Note that I am only lexing the file (instead of fully parsing it) since we don't
need an [Abstract Syntax Tree][].

## Creating Pixels

If parsing is successful, our tokens will be on `result.value`. Now, I want to
create a `Pixel` object out of them. First of all, let's create a data object
that holds that information.

Using Ruby 3.2's [Data class][]  makes defining [Value objects][] a walk in the
park:

```rb
Pixel = Data.define(:color, :token_type, :token_value)
```

So, each `Pixel` has a color, the token type (e.g. `KEYWORD_DEF`,
`PARENTHESIS_LEFT`), and its value (i.e., the variable name, the string
contents, etc). While we're at it, let's create a function that returns random
colors and a THEME (a mapping of token types to colors):

```rb
# nicer colors than just pure random
def rand_color
  "hsl(#{rand(0..360)},#{rand(70..110)}%,#{rand(40..90)}%)"
end

# This ensures random colors while also ensuring the same
# color is used for the same token type.
random_colors = {}
RANDOM_COLORS_THEME = ->(token_type) {
  random_colors[token_type] ||= rand_color
}
```

Cool! Now let's create the pixel objects. I'll also filter out some undesired
tokens (things like EOF and new lines) in the way. Our friend [`filter_map`][]
is here to help with that:

```rb
# Prism returns a token and its location in an array.
# I'm using argument destructuring to ignore the second
# value in the array
pixels = result.value.filter_map do |(token, _)|
  next if token.type == :WORDS_SEP
  next if token.type == :IGNORED_NEWLINE
  next if token.type == :NEWLINE
  next if token.type == :EOF
  next if token.type == :MISSING
  next if token.type == :NOT_PROVIDED

  Pixel.new(THEME[token.type], token.type, token.value)
end
```

<aside class="info">
  <strong>Missing token?</strong>
  <p>
    It might be strange to see a token type called <code>MISSING</code> or
    <code>NOT_PROVIDED</code>, so I want to take a second to explain that. One
    of the goals of Prism is to be error-tolerant so that it can be used in IDEs.
  </p>
  <p>
    Think about when you write code in your editor. While you're writing it, the
    code itself is invalid. For example, you are creating a method but haven't
    typed the <code>end</code> keyword yet, so the code is technically invalid
    at that point. The trick that some parsers do (including Prism) is to
    <a href="https://github.com/ruby/prism/blob/a590c031130ab024488177fa7ccf18c970dce20d/docs/design.md?plain=1#L51">
      add "fake" tokens
    </a> (and AST nodes) to make up for the missing ones and keep parsing the
    code, even if it's invalid.
</aside>

Now, we're ready to render HTML.

## Rendering HTML

For the fun of keeping it all in Ruby, I decided to use [Nokogiri][] to generate
HTML. To keep all the pixel rows about the same size, I used our old friend
**Math**.

```rb
row_size = Math.sqrt(pixels.size).ceil
```

Who would've thought square roots were _actually_ useful?

I also used [CSS's attr][] to pass data from HTML to CSS. The template looks
something like this:

```rb

builder = Nokogiri::HTML4::Builder.new do |doc|
  doc.html do
    doc.style do
      # Some CSS here
    end
    doc.body do
      pixels.each_slice(row_size).each do |row|
        doc.div(class: "row") do
          row.each do |pixel|
            doc.span(
              class: "pixel",
              style: "background-color: #{pixel.color}",
              "data-content": "Type: #{pixel.type}; Value: #{pixel.value.inspect}"
            )
          end
        end
      end
    end
  end
end
```

Then, it writes the HTML to a file and opens it with the (MacOS-specific)
`open`command:

```rb
File.write("code-picture.html", builder.to_html)
`open code-picture.html`
```

And... done! Here's what the `code_picture.rb` script itself looks like:

![A square grid of pixels with random colors](https://images.thoughtbot.com/sb4yckj8ebnhk22lenlx4zhlo9ox_code-picture.png)

Hopefully, it's not underwhelming! 😅

## A Challenge For You!

If you like this, I have a polished version of this script [released as a gem].
It has several customization options so you can define your own themes, control
the pixel size, the number of pixels per row, and more! Give it a try!

Now, I want to make a challenge for you. Can you write valid Ruby code that also
renders as cool pixel art? Feel free to use the customization options above to
achieve your goal! If you create something cool, please share it on Twitter/X or
[Mastodon] using the hashtag #codepicture (you can also tag me [@MatheusRich])!
I'd love to see what you come up with!

[the average programming language color]: https://thoughtbot.com/blog/pipelining-without-pipes-in-ruby
[Value objects]: https://thoughtbot.com/blog/value-object-semantics-in-ruby
[here it goes]: https://gist.github.com/MatheusRich/82967d906057c734361a3dde2762b218
[Prism]: https://github.com/ruby/prism
[since CRuby 3.3]: https://www.ruby-lang.org/en/news/2023/12/11/ruby-3-3-0-rc1-released/
[inline Gemfile]: https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html
[Prism Ruby API]: https://github.com/ruby/prism/blob/v0.19.0/docs/ruby_api.md
[Abstract Syntax Tree]: https://thoughtbot.com/blog/rubocop-custom-cops-for-custom-needs#grepping-the-what
[`filter_map`]: https://docs.ruby-lang.org/en/3.2/Enumerable.html#method-i-filter_map
[nokogiri]: https://nokogiri.org/
[CSS's attr]: https://developer.mozilla.org/en-US/docs/Web/CSS/attr
[Data class]: https://docs.ruby-lang.org/en/3.2/Data.html
[released as a gem]: https://github.com/MatheusRich/code_picture
[Mastodon]: https://thoughtbot.social/
[@MatheusRich]: https://twitter.com/MatheusRich
