Wow, my second blog post already! I'm itching to get this one out, because my current project is also pretty awesome, and I don't want to get behind now that I'm on a roll, so let's dive into another fun side project I decided to do, shall we? This one I'm just going to call CatchEmAll (also on Github) because, "All 152^2 Generation One Pokefusions Megaposter" doesn't quite roll off the tongue very well. In fact, neither does "First-Gen Pokefusions Webcrawler and Posterizer".
I was very much inspired by the Pokemon Fusion website I stumbled upon while I was grinding the Elite Four for experience in HeartGold. In fact, I had to put my game aside, as the waves of laughter that I was experiencing began to require my full attention. Needless to say, I spent the rest of the day bugging everyone claiming, "I found an even better one this time, I'm not even joking."
Fun and games aside, it quickly dawned on me that it would be difficult to peruse every fusion possible in its current form. Repeatedly clicking "random" can get tiring after a while, so I took it upon myself to think of a better way. I thought back to the poster of all the official Pokemon I had taped to my bedroom door when I was a kid. That's when I realized that I should make one too. After all, posters make great conversation pieces, and this one would be no exception.
Now, I heard today that the forecast for the rest of this post would be, "Technical, With A Chance Of Learning", so let's be sure to put on our fancy thinking caps, polymonocles, and Sunday moustaches before we leave this paragraph and venture out into the wild world of computer science.
But where should we begin? Ah, I know, let's learn about…
Making A Simple Web Crawler
Nope, not that kind of web crawler. I mean the other kind. You know, the one that scours the internet for all the memes and shit.
First, we need to figure out what language we would like to write this in. I'm a bit biased and lazy, so python will do nicely. If python isn't your thing, the process should still be the same no matter what language you use, so feel free to wander around and peruse the wares a bit while I talk.
You had me at #!/usr/bin/python
What you see above is the entire program I used to grab every fusion. It would be way more complicated if I had to grab the names of each fusion as well, but a little inspection of the html code on the Pokemon Fusion website yielded a portion of code that included every prefix, postfix, and full Pokemon name.
The site makes fusion work as follows: the Pokemon on the left contributes the prefix of its name, its head, and the colouring of the sprite to the final result. The Pokemon on the right contributes its body and the postfix of its name to the final result. Easy peasy.
Now we need to figure out how the site creates the names for its fusions, so we can get the correct fusion whenever we create the link to the file. The fusion image links all look like .../fused/<integer 1>/<integer 1>.<integer 2>.png, where the first integer is the Pokemon to the right side of the fusion. This makes things a little confusing when we download the files, as I sort them based on the Pokemon on the left side of the fusion (it's a little bit more appealing this way on the poster.)
Basically, when we fuse 001. Bulbasaur (left) with 002. Ivysaur (right), we should get Bulbysaur. Logically speaking, if the left makes the prefix and the right the postfix of the final name, then the file should be numbered something like 1.2.png, right? Nope. It's flipped on the site, remember? They sort them based on body first, head second, so Bulbysaur is actually 2.1.png. Drats. No matter, that just means we need to be careful when we rename them to reflect that switch. That's the reason we have a slightly confusing i and j usage in the code.
First, we want to use a handy url getter, which I call getter. "Very imaginative, Alex." Thanks, I guess. Anyway, whenever we feed getter a url and a filename, it will get the file located at the url and name it to that filename. Now we just need to build the url, the filename, get the file, then rinse and repeat until we have all the files saved inside our handy Pokemon folder.
To make things easy, I use two for loops that force variables i and j to step through all the values between 0 and 151, inclusively. Wait a minute, there aren't any Pokemon with a number 0, so what's with the 0? This is a special case, where if you look at the arrays that store all the Pokemon names, prefixes, and postfixes, you see that there's a Missingno. hidden at index 0 (the first value in an array.) I happened to notice that Missingno. was also a fusion contender on the website, so I just had to add it.
It is also a special case on the site, where any number outside the accepted 1-151 range will just use Missingno. in that Pokemon's stead. In the code you may have already seen me force i and j to 152 if they ever equal 0, and then back to 0 again. This is due to the fact that I want to place Missingno. at the far ends of the poster, so I need to make sure that the filename reflects this positioning. Another special case here is when the two Pokemon coming into the fusion are the same. We should just generate the same Pokemon again, so it takes the original name instead of mashing the postfix and the prefix together as we usually do (this can create a slightly different name than the original one, as sometimes the prefix and postfix share letters, e.g. Ivysaur's prefix ends with y, and the postfix begins with y.)
Whew. The last thing we need to do here is run it in the command line/terminal. This might take a little while, so while that runs, we can finally get into the next section of this post.
Making A Huge, 2.11 Gigapixel Poster
That's 2,110,000,000 pixels! Wow, but why is it so big? Well, let's use some math to see why: we have 152 Pokemon fused with 152 other Pokemon, which results in 152 x 152, or just 152 squared fusions. Remember that squares grow really fast, so you shouldn't be surprised that 152 x 152 = 23,104 fusions!
That still doesn't tell us why we end up with 2.11 Gigapixels, so assuming that every square that a Pokemon takes up is exactly 300 pixels wide x 300 high = 90,000 pixels/sprite. 90,000 pixels/sprite x 23,104 sprites = 2,059,360,000 pixels. Hmm, that's not right.
Oh, I forgot that we also need to include a topmost row and leftmost column for the original Pokemon (this way you can reference fusions just by following the row for the head and moving over to the column with the desired body.) Alright, now we end up with 153 rows x 153 columns = 23,409 sprites. 23,409 sprites x 90,000 pixels/sprite = 2,106,810,000 pixels. That's more like it! GigaPan likes to use only 3 significant digits, so it rounds up to 2,110,000,000 pixels.
PIL is the shiznit
Above, you will find some more code, but this time it's from the posterizer half of this project. Why don't we walk through all the variables and what they do: pokemonCount is pretty simple, it tells us how many Pokemon we want to have fused in the square. A value of 6 ends up making the miniature poster of the first six starter Pokemon that I have at the top of this post, for example.
My friend, Andrew Tinits, recommended that I use Python Image Library for this project. This was definitely the way to go, as I had already tried a thousand other ways of making the poster come together. All these other ways ended up either making files that were way too large to even be able to save them in other formats (I'm looking at you, Photoshop), or just didn't include the names or any other styling that I wanted. I couldn't settle for less, so that's why I went with Python Image Library. Shortly after I began reading the documentation, I started to realize just how convenient PIL would really be, and soon you will too!
As you can see, the first thing we use PIL for is to initialize the font that the text will have under the sprites. Next, we need to load the 300x300 background that we'll tile behind each sprite, and then initialize the final poster image based on the dimensions that we set. In order to draw objects or text onto an image object, PIL requires a draw object to be initialized with the image object that will be changed. The draw object renders any drawn shapes and text onto the image object for you.
Finally, the meat and gravy to this project's three-course meal.
For safety reasons, I've included a catch at the top so that we never actually create an invalid poster. It's not really necessary right now, at least not until I let it accept user input for how many fusions they want on the poster. Again, we can see that I used another double for loop, which just goes through all the fusions, plus one extra row and column for the original fusion ingredients. Inside this loop is a condition that makes sure we don't continue unless i and j don't equal zero at the same time. We don't have a Pokemon for that corner, so we'll have to leave it blank until we add the logo I made a little later on.
The next condition handles the topmost row and leftmost column, which hold the reference Pokemon for the overall fusion poster. We set the red value for the text to the highest amount (used later when we draw the Pokemon name and number), which will distinguish the actual Pokemon from the fusions. If you were paying attention to the prefixes, postfixes and Pokemon names from the code before this snippet, you would have noticed that Missingno. isn't at the beginning of each of the arrays anymore. I moved it to the end, that way it corresponds to the proper index when i and/or j are 152.
The next condition handles a similar situation, where the fusion ingredients are the same Pokemon. The text will be red, and the name will be of the original Pokemon. The condition after this one handles the rest of the cases, where i and j are different. Now we need to concatenate the appropriate postfix and prefix, which again means we need to use i and j in opposite order. This is due to the way that we are displaying the fusions in order of their faces first (rows), and then their bodies (columns).
Cool, now we need to open the actual sprite file into an image object, paste the background into position, and then paste the sprite overtop it after being offset appropriately. For those wondering why I add current into the paste method twice; the last current is there to make sure that the transparency is maintained, otherwise our background will be cancelled out where the sprite is pasted. We definitely don't want that.
Wait, let's not forget to label the fusion with its name! We first need to measure the dimensions of the text box using the textsize method, that way we can accurately draw the text so that it's centred horizontally, and vertically offset to just above the bottom of the cell. The colour is determined by how red our red will be (either 255 or 0), while the alpha is always full.
Once all the sprites are finished being added, we can paste the logo to fill in the gap we left for it, and finally save the poster! For those curious about the resulting size; it ended up being around 270MB large. This doesn't seem like much, but we will most likely want to put the result up onto the internets, which opens up a whole new can of worms.
Hosting An Incredibly Massive Poster
Massive is a bit of an understatement here. Each side is 300 pixels/sprite * 153 sprites / 72 pixels/inch = 637.5 inches = 16.19 meters. The area is 2,822.19 square feet (262.19 meters squared!) Where does one go with a poster who's size is larger than the average Australian house (214 meters squared = 2,303.48 square feet), or just over one tennis court in size? Don't bother with imgur, instagram, or any of the other traditional places you'd put your poster. Those sites don't support files this big, nor will they allow you to zoom in and look closely at the sprites. Bummer.
Not good. We can't even make out the individual sprites like this.
But wait, there has to be a solution to all of those things! There are a plethora of websites that host panoramas, including Gigapixel sized ones, so why don't we just put it up on one of those? Good idea! I'm glad we were wearing our fancy thinking caps today. After some research, I've concluded that the solution to this problem ends up (almost always) being GigaPan.
All in all, it took me over two weeks of on-off dedication to complete. But why so long? The first half was spent trying to find a way to put it together, which was before I decided to try using Python Image Library. Then it was smooth sailing until I hit another roadblock; where do I store it? That took me a few more days of trial and error, which included a few attempts at reducing the image size. But it was pretty awesome when I finally solved that problem; not only was I able to upload the full file in a lossless format, but I could also zoom into the poster relatively quickly and painlessly. High fives all around!