Log In  


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)

Cart #pkz_01-0 | 2021-02-24 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
11

11


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]