This post is mostly just to share an experiment I did when playing around with compressing graphics.
I noticed when looking around, that many PICO-8 carts that do compress graphics were using some form of LZ or RLE compression. Out of curiosity, I decided to do some research on how the Generation 1 Pokemon games compressed an impressive amount of graphics data into the small cart size. It turns out that they use a form of RLE compression. I won't go into detail myself, but instead refer to a great YouTube video that very precisely describes how it works.
Essentially, this compression algorithm stands out because:
- It splits the graphics into bit planes (i.e. 1 bit-per-pixel images)
- It operates on pixel pairs, only encoding runs of pairs of 0's
- It uses several flags to control how the bit planes are mixed in order to increase compression ratios
- The original algorithm compresses 2bpp images (i.e. 4 colours). That being said, there is no reason why it cannot be extended to higher colour depths
Anyway, here is an implementation of a decompression routine for the Pokemon Generation 1 RLE image compression format. Overall, I'm not sure how useful this will be. It costs 400 tokens and decoding the sample 15x15 sprite in the demo cart takes 12 frames@60fps. On the plus side, it fits neatly into 544 bytes (less than a single row in the sprite editor), compared to its decompressed size of 7200 bytes, albeit with only a quarter of the colours. Note that the compression ratio greatly depends on the data. Also, I did not put much effort into optimizing the implementation for neither tokens nor CPU cyles.
In my opinion some of the "cool stuff" that this approach to compression does (that can be applied to other algorithms) is:
- Using a reduced colour depth greatly reduces graphics size
- RLE compressing a bit plane has some neat properties (the linked video goes into some more detail about this)
Well this is great!! I’ve wanted to look into writing encode/decode code since I watched that video.
Do you have other compressed images to demo?
No, I didn't compress any other images out of laziness :P. However, you should be able to extract image data from any of the Gen1 game ROMs (this is what I used for test data). For example, this reads the compressed graphics for a (surprise :P) Pokemon located at offset 183637 and 244 bytes long from Yellow (English) version:
dd if=pkmn-yellow-en.gb of=surprise.pkz ibs=1 skip=183637 count=244 |
The cart makes use of the PICO-8 serial feature for dropped files, so if you have a compressed image, you should be able to load it by just drag-n-dropping it onto the running cart.
Additionally, the creator of the Youtube video I linked also created a web page for compressing/decompressing images. So if you have any 2bpp grayscale images that you want to try out, you should be able to compress them here: http://www.dotsarecool.com/rgme/tech/gen1decompress.html
Thanks for the link to the compress tool!
I already all sprites from gen 1 and 2 around, so I compressed one of these.
Do you have thoughts on adding colours?
I think the most straight forward way to add colours would be to set the palette before drawing the sprite. The current implementation uses colours 0-3.
how would we make this work for values stored in code? I have a data system in my project that involves converting strings to bytes of data
easiest: poke your string bytes into memory, use function unchanged
harder: rewrite function to read bytes from a string param instead of memory locations
moving the bytes into memory shouldn't be too difficult
EDIT: whats an efficient way of porting a large chunk of bytes into general use data and signalling the de-compiler to read it?
oh I meant poke literally: https://www.lexaloffle.com/dl/docs/pico-8_manual.html#POKE
works best if your strings use this format: https://www.lexaloffle.com/bbs/?tid=38692
right now my code looks like this
function load_img() local str="๐ฆ8๐ปใซใ๐ณใณโ๐ธใใณ5โ_ใโชjใซใ โฝ๐ พ๏ธใ๐ถใโคใก๐คใฏโฝ๐ถ๐ตใฝ]¥)j¥ใกใ๐ตใกในใ๐ พ๏ธ+ใใโโชใก์โก๏ธใใ53๐ด๐ฅใ๐ฑโต0ใ:)\"ใซใคใโกในใโใ๐ณใฆใใใฑโฌ ๏ธ๐ตe๐นoใช\nโโ…ใฆใ4ใกโ ใ(ใxใใ๐คxใญใwโกใใใกใใใใใฃใใช๐งใฑ ใฆใญใฏโโ๐จ²โฅใใโใ*๐ฎ¥f๐ตใฃ♥๐ถ๐ทใจ²โช๐งโใ*dใปใซ\nbใโ ²โฅ+ใช]โถc๐ฆ๐ฒ๐ฑใ๐ฌใโถ๐ซ9โ}\nโคใฟ๐๐ฆ~โก๏ธ²ใซใใใk³โต)?โฝ ใ ใ๐บโฝ๐ฒ5โใโใ๐ข๐ข!๐ตใ๐ดใใญ๐ฅr+•๐ณ " poke(0x4300,str) local t=stat(1) w,h,len=pkz(0x4300,0x0000) cpu=stat(1)-t end |
it still doesnt load, i think it may be an issue with either letting the decoder know when to start or setting the actual bits had been done incorrectly by me.
Also, sorry for asking so many questions! ๐
[Please log in to post a comment]