10 January 2025
What is the easiest way to create a ROM with a slideshow of your own images for your Game Boy? How about opening a website, dragging your images into the site and hitting a generate button? That’s it. That’s what I created here.
Check out this rather short Youtube video to see the tool in action or just look at the top of this post to try it out yourself.
Please note that this article contains affiliate links, i.e. links that tell the target site that I sent you there and that earn me a share of their revenue in return, i.e. as an Amazon Associate I earn from qualifying purchases. In contrast to regular external links, such affiliate links are marked with a dollar sign instead of a box.
I think that the tool is mostly self-explanatory, so here it is. You can find further info in an FAQ style below the tool. You can also open it on its own page.
No, the entire tool runs in your browser using JavaScript. In fact, you can download the tool and run it from your local drive without any internet connection (see next question).
Go to github and download the entire project (if you are new to github: Green button labelled “Code” and then “Download ZIP”). If you downloaded it as a zip file, make sure to extract all files. Then simply open gbslideshow.html
in your webbrowser (on most systems just a double click).
You can load your images into the tool by either dragging them onto the tool or by pressing the “Add image” button and selecting images from your machine. Each image appears as a group of three previews: The original file you uploaded (scaled down to the Game Boy’s resolution), a preview of the grayscale image for the original Game Boy or Game Boy Pocket (or similar) and a preview for the color image for the Game Boy Color or Game Boy Advance (acting as a Game Boy Color). You can move or remove images with the three buttons in the bottom left corner (arrows and cross).
You can adjust brightness and contrast to your liking and toggle dithering for the grayscale images. In most cases a dithered image looks better as the Game Boy only has four shades of gray (or green), but for logos and drawings an undithered image can look better if you adjust brightness and contrast.
Below each image group is a setting “Seconds before next image”. This determines the duration for which an image is shown. If set to zero it will not advance automatically, but only if you select the next/previous image by pressing a button on the Game Boy. On the Game Boy every image can be advanced by pressing the D-pad to the right or the A button and you can always go back to the previous image by pressing left or the B button.
When you are happy with your selection of images, you can simply press “Generate ROM” at the bottom and your ROM will be downloaded directly in your browser. (If you don’t know what to do with the ROM, read How do I get the ROM onto a real Game Boy? further below.)
You can always press the D-pad right or left to go the the next or previous image. The same works with the A and B button. If you don’t want the slideshow to advance on its own, set “Seconds before next image” to zero on all images.
In the current version each image takes up nearly 16kiB, such that it makes sense to store one image per memory bank. The first bank is used for the code and the image index, so for N images the tool will use 16kiB*(N+1). However, it will pad the file to create the common ROM sizes of 32kiB, 64kiB, 128kiB, 256kiB, 512kiB, 1MiB, 2MiB or 4MiB. The tool is currently limited to 254 images.
You can directly use the ROM in an emulator or on modern devices that can handle ROM files directly (like the Analogue Pocket), but if you want to run it on original Game Boy hardware, you will need some kind of flash cartridge. It should work on any kind of flash cartridge as long as it is large enough for your ROM and properly handles common memory bank controllers (see below). I have tested the following:
Pretty much any flash cartridge should work as long as it is large enough for your generated ROM. If you upload more than one image, the cartridge needs to properly handle memory bank controllers (MBCs), but the ROM generated here should be compatible with all of the common Nintendo MBCs. The tool will declare in the ROM’s header that MBC-5 is required (unless you generate a 32kiB ROM, in which case it declares a ROM-only cartridge), but if your ROM fits the cartridge it should also run on an MBC-1 or MBC-3 cart (2MiB). If your flasher gives you a warning about the wrong MBC, you can simply ignore it. If declaring a specific MBC is really necessary for any cart, let me know and I will add an option for this.
The tool consists of a template ROM that I have written in assembly. This ROM is embedded into some JavaScript code which converts your images and then injects them into the template ROM. The template ROM uses two hardcoded addresses at which it expects a list of images. One for grayscale mode and one for color images. The image list consists of addresses (memory bank and memory address) and the display time for each image, so the JavaScript tool is simply responsible to write the images somewhere into the ROM (contained within a memory bank) and store the addresses there. In principle, different numbers of grayscale or color images could be supported, but that is not implemented in the JavaScript tool.
The JavaScript code simply scales the images onto a 160x144 canvas (native resolution of the original Game Boy and Game Boy Color). From there it directly processes the images following the limitations of each Game Boy, so the preview images are exactly what you get on your Game Boy (except maybe for the fact that colors on the Game Boy Color can look a bit different).
The grayscale images are stored as 360 tiles and simply displayed by switching the tile data address halfway through the frame. Otherwise it is just the normal 2bit “color” palette of the original Game Boy, so you end up with a 160x144 image with four shades of gray. The tool offers dithering to mitigate this limitation (Sierra dithering to be exact).
The tile data works similar as for the grayscale images as there are 360 tiles with four colors per pixel. However, the four colors can refer to different color palettes on the Game Boy Color, so to use this, the tile data of the color images is not identical to the grayscale images.
The Game Boy Color only supports eight color palettes with four colors each. Also, not every pixel can just pick a random palette, but all pixels within an 8x8 tile are assigned to the same palette. Therefore, with a naive approach, the Game Boy Color could only display 32 colors at a time and in practice many of these colors would be duplicates because for example black is typically required in more than a single palette.
The trick for getting more colors is that one can edit the color palettes during hblank of each scanline. This technique has been demonstrated many times before and is commonly referred to as “hicolour”. Depending on the implementation for each line of pixels you can either replace four entire palettes or fewer random colors within any of the eight palettes. The problem with this is that the assignment of color palettes to each tile remains fixed or you would have to pay more bandwidth to also change these assignments. This leads to several possible strategies and rather unusual rules when converting images for the Game Boy Color.
In case of my tool, the assignment of color palettes is not updated each line, but optimized for each image and stored along with the tile data. On each line four entire palettes are replaced in an alternating pattern (palettes 0-3 on odd lines and palettes 4-7 on even lines).
To generate optimized images for this, the tool first uses k-means clustering to find which tiles should share the same palette indices. Then it proceeds to generate palettes line by line. Since each palette is used by some pixels of the previous or next line, palette generation takes into account all pixels in the two neighboring lines that are part of a tile which references the palette to be generated. This limitation is the most common source for artifacts as available colors can be too limited for strong contrasts along lines that run diagonally, forcing color changes within a tile and grouping of neighboring tiles.
Finally, tile data is written based on the already fixed palette assignments and generated palettes with the closest matching color available. So, in total the color image also has a resolution of 160x144 pixels and can theoretically hold up to 2320 colors with 5bit per RGB channel - but that claim about the number of colors leaves out a very important chunk of information on how the colors can be used.
By the way: Since the k-means analysis in the tools is based on random initialization, playing with the brightness/contrast sliders can lead to different results using the same settings. For some tricky images this can help to get lucky with a better result.
Since the grayscale images are smaller than the color images and since there is a bit of extra room in bank 0 after the code, a purely grayscale cartridge could hold multiple images without a memory bank controller and the template ROM supports images in random memory locations (as long as they do not go beyond bank boundaries). I just have not implemented this for the JavaScript code as I do not expect many people to be limited to 32kiB ROMs while having a strong use case for grayscale-only slideshows. Let me know if you need this nevertheless.