I've become unhealthily interested in image compression on Pico8.
Hit the "z" key to see the next image.
What we have here is something that might resemble the JPEG compression scheme if you squint.
Compression goes like this:
--Discrete Cosine Transform + Quantatization
--Zigzag encoding of blocks
--Run Length Encoding
--Conversion to a base 64 character set
--Overall compresses to around 1/5 of original 8-bit grayscale size.
What didn't work well:
Image quality is kinda low. There are only about 16 levels of gray that are discernible on pico8 with dithering. Not sure that it makes a lot of sense to encode 256 shades of gray in that case. Not to mention that the image quality at 64x64 is pretty lame to begin with.
This path may be a dead-end because I can only get about 12 64X64 images in here before hitting compressed code limit. If I drop the base 64 coding, I could fit maybe 10 images into the sprite area... Because Pico8 compresses data as well (definitely with better algorithms), my compression may be working at cross purposes to Zep's compression. I still need to do an experiment to see whether the base 64 encoding is any better than the base 16 coding.
I've got some glitching in the bottom right blocks.
Here's the compression script: https://github.com/electricgryphon/pico_8_jpg
Perhaps this will inspire someone to greater image compression heights :-D.
By the way, the best I've done with image compression of an actual image (like, a photo) is to convert it to PICO-8's palette using ordered dithering, then compress it with a custom LZ-style history lookup that works at the level of nybbles. I got 128x128 images down to about 3-6kchars typically (in lua string format with zep's supplied charset), as long as the dithering didn't get too crazy about color selection or error diffusion. Non-ordered dithering with error diffusion is just disastrous for ratios.
I'm still playing around with lossy compression using something that would feel a bit like DXT1 to anyone who knows the format, though it's really a lot more similar to the ancestor of the DXTn formats, something called CCC (color cell compression).
It's tough to compress effectively when your end result will be displayed in a 4-bit palette. Merely quantizing your original image to that palette and storing it raw is already a 6:1 compression vs. R8G8B8, so making a big dent in it beyond that is tricky.
Regardless of the efficiency—the effect of the vibrating dithered color on JPEG-compressed, low resolution images is quite interesting. Like Atkinson dithering, it conveys an odd, dreamy feeling all its own. Nicely done!
As for other compression ideas—for the Pico-8 resolution/color depth, maybe it would be more efficient to write an algorithm that would automatically "vectorize" an image into polygonal areas by intensity, then coarsely sample the color inside those polygonal areas and lerp between them?
Wow, that's really neat! I wonder if there's a way to exchange some loss of detail for a bit less dither -- I find the amount of dithering pretty distracting right now. Contrast stretching feels like it might be worthwhile. Might help the ratio, too!
@luchak: Contrast stretching or compressing? I noticed when doing some flicker-viewer testing of my own that the classic "Lenna" test pic, which is somewhat washed-out and low-contrast, survives the conversion to the pico-8 palette much better than many other pics that need darker colors.
For reference, this is the original "Lenna" image:
And here's the cart, not sure how well it will work on the BBS:
You can see the low-contrast nature of the pic really helps with PICO-8's palette.
Worth noting is that the somewhat-lazy method I used to make the two images (see comments in code if interested) results in a lot of horizontal lines. One button in the cart pauses the flicker and the other frames forward if you want to see it more clearly. I think this does indeed reduce the pain of the flicker, but only a bit. It'd probably help with pixel-based run-length compression, if nothing else.
BTW that image turned out well, but many more didn't, and I wasn't bothering at all with compression, so image quality in this one instance may be an easy win, but it's a cheap one. The image conversion was all done by someone else's code (gimp), and it also uses almost the entire cart space to store the image, so this is really no competition for gryph's superior multi-image effort+cart.
@Felice: Interesting! I did mean contrast stretching/expansion, but you're right that the Lena image comes across very clearly. I don't know if that's a contrast thing or a color thing, though, since you're also giving yourself hue variation to play with there, and the PICO-8 palette is quite colorful. I'm a little curious if 1-channel Lena would work similarly well.
My hunch is still that I'd prefer contrast-stretched grayscale images, but I haven't actually run the experiments. :)
My best image compression has been for 4-colour cartoon images. Basically writing a drawing engine and the compressed image is a list of instructions to the engine (draw line colour X 23 pixels, draw box 10x18 pixels etc).
Kind of lost interest in it though when I realsied I wasn't going to get amazing compression out of it.
Of all the cleverness going on here, I'm gonna steal your base_64_string_data string. Yoink.
I was trying to do base64 stuff a few days ago and i didn't realise there were actually enough characters to do that (since there's only one case of A-Z characters that you can easily use).
I have a suggestion for the base64 string. It's what I do, anyway. (And boy, I sure do like to tell people what I do, don't I? That's right, I know I do that too much. It's a flaw. Uh, anyway...)
If you move the initial space in the string to after the alpha characters, or somewhere up there anyway, your base64 table can double as a hex table.
I'm curious to see if I can get 120fps to remove that flicker.
Seems to work. Might be something you can do with your code, @electricgryphon.
Oh, and a belated STAR for your sterling work here.
[Please log in to post a comment]