sanguinecha.me/leon

Bad Apple, but...

13-Jun-2025

So I make Bad Apple videos. Quite a few, actually. Some of them take a quick evening to code and render, while others take… a bit longer than that.

I like Bad Apple memes. They're what motivated me to start making YouTube videos in the first place. I even made one at a hackathon (Bad Apple but it's NUSMods) and got to present it in front of a crowd at school. That's a pretty neat highlight of my life.

Regardless, it's gotten a bit stale now. I would say I've done pretty much everything I wanted to with this meme. So unless I get a funny one-off idea, I probably won't make more any time soon.

The NUSMods one has a separate write-up here. The rest are listed in this post, in chronological order. I've also included a short introduction to Bad Apple in case you have no idea what I've been talking about.

All the code I've written for these videos is in my GitHub repo. I do not apologize for the lack of readable or clean code.


What is Bad Apple?

There are already tons of explanations of Bad Apple available online, but I think most of them take a bottom-up approach. They start from the original game (Touhou), the song, the music video, and so on. That's totally fine, but I think the average "Bad Apple enjoyer" probably doesn't care about all that jazz.

I'm not in the Touhou fandom myself (does listening to Touhou songs count?), but I can appreciate the creativity and effort that goes into making these Bad Apple videos. So let's start from there instead.

This list is surely incomplete, but hopefully you can see how convoluted it gets.

Really, the only thing you need to know about Bad Apple is that it's (more or less) a black-and-white video. So anything that can encode just two colors can be used to display Bad Apple. The video itself is immaterial. It's the "Hello, World!" for nerds with too much time on their hands.


VOI 2023

Of course, I had to start with something extremely obscure. This was the first Bad Apple video I made, though I didn't release it publicly until much later.

Before IOI 2023, I had to participate in a few selection contests. The first was VOI 2023 (Vietnamese Olympiad in Informatics, although the translation isn't official). The 32 highest-scoring students would move on to the next selection round.

I screwed up pretty badly. Problem 1 was easy, but I wasn't able to come up with a complete solution for it. Due to the format of the contest, the results wouldn't be announced until a month later. I had already accepted my fate. I probably wasn't going to make it, so I decided to do other things with my life.

One of them was Bad Apple. So, to be really funny, I turned Problem 1 into Bad Apple. The input for Problem 1 was a string consisting of the characters A, T, G, X, and ?. That meant that, by picking the right characters, I could shape the strings to look like the frames of Bad Apple.

To be more specific:

  1. Choose a font, take a screenshot of each of the 5 characters, and convert them into a binary matrix (0 for black, 1 for white)
  2. For each of the 6572 (that's the magic number) frames of Bad Apple, convert it into a binary matrix and slice it up into small sections with the size of a single character.
  3. In each slice, pick the character that most resembles it. I just compared how many pixels matched.
  4. Once I had the characters for the input string, I ran it through the code to get the corresponding output.

Reasonably straightforward, and nothing too technical. I purely made this as a "haunting" reminder of what could've been.

Fortunately, I somehow still ended up in the top 32.

Made out of Bad Apple

This is a fun one. What if the frames of Bad Apple could make up Bad Apple itself?

The original Bad Apple video has a resolution of 480 × 360 pixels. So the idea is to create a 10 × 10 mosaic of frames, each scaled down to 48 × 36, so that together they resemble the original video.

The steps are simple, if a little computationally expensive. First, resize every frame down to 48 × 36. Then, for each full-size frame, divide into 10 × 10 sections and find the resized frame that best matches each section.

How do I match the sections? Well, here's where it gets a bit dubious. The video is in black and white, which means each pixel has a value from 0 to 255. For a section and a candidate frame, I compute the absolute difference in value for each pair of corresponding pixels. Then, I add up these differences to get a candidate's score. The candidate with the lowest score is selected.

This heuristic is pretty bad, since it doesn't account for where the individual pixels are and just treats them as a collective. Nonetheless, the result was decent enough for me.

There's no intelligent pruning here either. I just brute-forced it. With 6572 frames, each with 100 sections, and each section requiring 6572 comparisons, it took ages to render everything.

This has been done before, and much better than I have. Oh well.

Tupper's Self-Referential Formula

Tupper's Self-Referential Formula is one of those things that, personally, I don't think really lives up to its name.

Essentially, the formula can plot every single binary image of height at most 17 (its width doesn't have to be exactly 106, despite what many sources claim). The idea is simple: just treat the entire image as a huge binary number. Each vertical slice of length 17 along the y-axis represents some image.

When Jeff Tupper first included this formula in his 2001 paper, I assume he picked a slice that looked like the formula itself just for fun. At no point does he claim it's "self-referential". I'm not sure where that name came from, but it makes the whole thing sound much more magnificent than it is. Still, the formula is short, clever, and cute.

Oh, right. Bad Apple.

I figured the effect would work better if I used a pre-existing site that plots the image for you. I used Tupper's Self-Referential Formula Playground.

Resizing the images to 106 × 17 (the site requires the width to be 106) and converting them into the corresponding binary values was easy enough. Actually entering the number into the site was a bit harder. I ran some Javascript code in the console to do it automatically. I also had a script that took a screenshot of the site after each frame was rendered.

Surprisingly, I was the first one to do this. So there's that.

Lines and Rectangles

Arguably one of the weaker entries. It is what it is, though.

For each frame, there are some horizontal and vertical lines that divide the screen into rectangular cells. These lines don't have to be evenly spaced, so it's something like a spreadsheet. Then, each cell is shaded black or white to look like the original frame.

The "challenge" is to use as few dividing lines as possible. In other words, the goal is to decompose the frame into as few rectangles as possible, while still (sort of) resembling the original.

Here's how that's done:

  1. Choose the background color of the frame (so the rectangles will be the opposite color). This is just the color with the majority of pixels. For the rest of the steps, I'll assume the background is black.
  2. From the remaining white pixels, find the largest rectangle.
  3. Remove that rectangle, then find the next largest rectangle from the remaining white pixels. Repeat this process about 10 times.
  4. Each rectangle has two horizontal edges and two vertical edges. These are the dividing lines, which are extended to the edges of the screen.
  5. Determine the width of each dividing line using some arbitrary formula, just to make it look more varied.

That's pretty much it. The only slightly tricky part is finding the largest rectangle in a binary matrix, but this is a classic problem that has been solved to death online.

Wordle

It's a game. It's got squares. Green squares. Yellow squares. Gray squares. I had to do it.

Each frame is broken up into 15 separate Wordle games. All games are valid: the answer is a valid word, and the coloring follows standard Wordle rules. That being said, some of these games involve guessing the same (wrong) word multiple times, so they're not exactly "realistic". But it's Bad Apple, so I don't think anyone cares that much.

Wordle word lists are publicly available online, and picking the right words to get the right colors isn't too hard either, because of a secret fact you might not know: English has a lot of words! There are a ton of games that would satisfy the constraints, so I just randomly generated one each time.

By the way, all these games are played in normal mode. A hard mode version is left as an exercise for the reader.

Sorting

Secret unreleased video. I no longer remember what my code does, but from watching the rendered output, I think the idea was that the video is divided into vertical slices that are randomly shuffled, and throughout the video, the slices are swapped around and gradually become sorted.

To get a cool effect, I picked an algorithm where you could really see the video getting sorted. Apparently, I implemented Shellsort, something that I just had to re-Google after looking at my code.

The final result looks kinda cool, but unfortunately not cool enough for an upload.

Update (06-Dec-2025): You should check out this video by jan Misali instead.

APIO 2021

More obscure things that only one person (me) will find somewhat amusing. I think the original video description is fine, but a bit too technical.

The last selection test before IOI (at least in my country) is the Asia-Pacific Informatics Olympiad (APIO), so naturally, I solved all the APIO problem sets before APIO 2023.

Hexagonal Territory from APIO 2021 is a problem that exists. I think it's far too difficult for a 5-hour contest setting. But anyway, it's a problem.

The specifics of the problem are not really relevant, and the video itself doesn't satisfy the original constraints, anyway.

Each frame is rendered as a hexagonal grid with some cells shaded blue. These shaded cells form connected components (or "islands"). For each island, a source cell is selected and colored red. Then, each cell is labeled with the shortest distance from the source to that cell, staying within the island.

Once you have the hexagonal grid, it's not too hard to compute the distances, although hexagonal geometry can get a little confusing. What's much more interesting is how I mapped each frame to a grid, and it's brilliantly stupid:

  1. Draw a hexagonal grid in Matplotlib.
  2. Fill each cell with a unique random color and store these colors in a list.
  3. Export the grid as an image.
  4. For each color in the list, find all the pixels in the image with that color.

Congratulations, you now have a list of pixels within a cell without having to do any annoying calculations!

Morse code

It's amazing how boring writing about the same thing (just in slightly different configurations) can get. But this is the last one for now. Fantastic.

First, I needed to pick a Morse code website, so I went with the first result on Google. Then it was a matter of finding a message that encodes into the right sequence of dots and dashes.

One difficulty is that the position of a character's Morse code depends on all the characters before it. That means I can't easily fix the position of a specific character in advance. Also, because the Morse code is displayed in a rectangular text box, I had to deal with line breaks and other formatting issues, which was a real headache.

Essentially, I add one character at a time, generate the corresponding Morse code output, and see how well it resembles the desired frame. To make it efficient, I can cache results that I've already computed. So if the current output has X dots and dashes, I don't have to explore that state again.

In other words, this is dynamic programming (which I did iteratively, not recursively). I store a score representing how well the current Morse code matches the frame, something like dp[X] = score.

This is a gross over-simplification, but I do think this was one of the more computationally challenging Bad Apple videos to make. So do check out the code if you're interested.