One-Off GFX
To display fullscreen images with a PRINT command. It doesn't take any space in your spritesheet, it uses p8scii one-off characters all in one string (~3000chars if used properly).
This is an old school method widely used in the 80s (e.g. on ZX Spectrum): each 8x8 block had a foreground (ink) and background (paper) colour, monochrome but using the colours well would deliver excellent pictures for title screens.
Now, One-Off GFX is based on that method but could also have more than two colours in a single block (at the expense of extra chars).
So -differently from other image compression methods- it doesn't really matter:
- how complex the pixel patterns are in the gfx (dithered or plain)
- how many colours are present overall on the screen
It just depends on how many colours are present in each 8x8 block; better keep it less than 4 and possibly just 2 in most of the blocks choosing wisely while drawing or editing the pic. A good drawing tool to improve monochrome skills is Multipaint (choose c64 hires mode)
Ok we have an optimised gfx, save it as .PNG, what now?
Spritesheet to P8scii string converter cart
Download this cart and use it as converter tool:
How to use:
- (optional) edit the code and choose your transparent color (default 0);
- go to GFX editor, select the first tile marked with 'x'
- drag and drop your PNG file;
- you should see your image in the spritesheet;
- RUN the cart
- you see your image and how many CHARS the string will be long (if higher than 10k better optimise it further, e.g. kill stray pixels);
- if the char count satisfies your expectations, press Ctrl+C to copy the image data (a string) in the clipboard;
Use it in your project/game cart:
- use Ctrl+P to enter Puny font mode (to respect pasted binary data)
- just paste the copied string in your code like
scr="(paste here)"
- and print your string on screen:
?scr,0,0
- (you may need to use a
cls(n)
with the same colour code as the transparent one declared when converting)
Comic example (3 full screens)
A -not so well optimised- example of a comic:
The comic example cart is here
Taking advantage of P8scii
Wide,Tall, Striped and Pinball modes
See comment below
(More to mention... using smaller blocks as sprites, transparency, etc...)
This is - interesting what you are doing here, @Heracleum. I see now what you were mentioning earlier in a comment on my thread.
From a mechanical viewpoint what you did is genius. From actual industry and usage, it's still pretty neat.
When I was working on my own picture to P8SCII:
https://www.lexaloffle.com/bbs/?tid=49536
I did seriously consider making use of one-off characters, yet I ran into the same limitation that you did. Where you can only plot a single bit of pixels into the 8x8 frame, let alone you had control over foreground and background, 9-bytes per 8x8 tile yielding a maximum screen size of 2304-bytes.
Now my challenge to YOU is to be able to take a picture, any picture in the spritesheet, and convert it intelligently so it will take a full spritesheet of 16-colors or so, and by determining which pixels appear MORE in the picture, and which colors match best.
Use that in a converted process so you are still left with 1-bit per 8x8 frame yet your code would automatically determine the "best" 8x8 frame and its 2-colors (single byte) for that area for the picture.
I might be able to do this myself but you have come so far in your code in doing this conversion I don't think there is much left for you to do ! :)
It is interesting. It reminds me a bit of ANSI graphics like they had for BBSs years ago, where you were in fact limited to 8-colors for background and 16-colors for foreground, and the colors were fixed, you could not change them.
ANSI if you are unfamiliar was making use of standard 256- IBM-pc characters in an 80x25 field, the characters taller than wider across, usually 8x12 in pixels, fixed, and fixed colors.
Marvelous work ! Gold star all the way. I am curious to see this expanded upon.
@dw817
> to be able to take a picture, any picture in the spritesheet, and convert it intelligently so it will take a full spritesheet of 16-colors or so, and by determining which pixels appear MORE in the picture, and which colors match best.
Oh ok so that it's the converter deciding what's best rather than you, sounds like a good tool variant (especially for those not familiar with the oldschool method).
It would be beneficial also to declare beforehand how many colours max you want in a block (e.g. set 2 for the strict oldschool 1-bit, otherwise 3 for one extra, possibly with some sort of tolerance, say if it's just 1 to 4 stray pixel just skip/kill them for good).
It could sound quite easy to determine (just by colour pixel count in a block) but I'm sure it'll be hard for the overall result to be aesthetically pleasant (AI).
BTW the non-AI minimal implementation could just kill the stray pixels and that alone would reduce a random image chars count sensibly. Stray pixels are this method's worst enemy (1 extra pixel only and it would weigh the same as a full block).
Example pic of a stray pixels hell (and I actually added that as 3rd screen in the comicx3 example cart. Look at that final comma after "the way." that's madness, you have 3 colours wasted (plus bg) each of 1 pixel.
@aced thanks and sorry I still didn't comment on your approach I'm definitely going to!
Yep, I think you got it @Heracleum. I know when I wrote the 256-color picture generator for Pico-8 I had to record manually the R G B components of each of the 16-colors, finding a match closest to a true 24-bit 128x128 picture.
You may need to do so here. And YES I would love to see this take a 128x128x16 color picture and make a "one-off" with 2-colors maximum per 8x8 tile yielding a fixed size of 2304-bytes per picture.
I know there are ANSI tools in DOS that already do this, taking a GIF picture usually and converting it to 16-color ANSI.
Yet no tool like this yet exists for Pico-8. You would be the first. :)
Regarding a stray pixel search, that would be relatively easy. Scan each of the 16-colors entirely for 128x128. See if any single color is "by itself." If so, change it to the most predominant color that appears in its 9x9 field (y-1 to y+1 and x-1 to x+1), continue until screen is done. Then add one to color search and rescan the 128x128 until all 16-colors are done.
Essentially:
for i=0,15 do -- colors for j=0,127 do -- vertical pixels for k=0,127 do -- horizontal pixels . . code . . end end end |
Yes just for clarity (to other readers) the 'hunt for strays' of course should be initiated solely by the user as an option (in case they feed a non-optimised image). Stray pixels are -yes- the worst enemy but the converter for now relies on the fact that you know what you're doing and if there are -let's say- 2 yellow pixels alone somewhere in the image those are maybe two eyes lurking in the dark, something you decided to put there for a reason.
I didn't mention so far but it doesn't only work on fullscreens, you might want to convert a smaller portion of the spritesheet to have a 'sprite-like' image as string to print instead of spr() (this is why a transparent colour is supported).
But I see your point, we're just discussing extra tools to help auto-optimise the input image.
Wide, Tall, Striped and 'Pinball'
I still need to mention other advantages in the main post: since it's p8scii you can use the Wide, Tall, Striped and 'Pinball' modes.
For instance, using Pinball (prepending \^P
) for roughly 1/4 of the char size (converting an 8x8 blocks area) you could fill the entire screen with a nice dark-tone mosaic good for backgrounds like:
Or some cheap scanlines effect using \^t\^=
for half of the chars (converting a 16x8 blocks area):
Also some extreme 60fps flicker on scanlines to have the illusion of a background in darker tones?:
I think you've cornered the programming market for compressing low-color images, @Heracleum. :)
Yet I still think it would be interesting to see you write a tool that takes a 16-color 128x128 picture and breaks it down to 2-colors per 8x8 field.
Hi. I also noticed.
Here is a bit of my picture your program converts:
?"⁶-b⁶x8⁶y8 \n |
I think you can save space by not having those spaces between the y8
and \n
May make for a tighter compression.
Yes for now it only does what it's told and sequentially scan 8x8 blocks without overall evaluation on rows or other stuff.
By the way they look like entirely empty rows: if you're converting a smaller crop of the screen you could restrict the bounds of the area table declared at the beginning (yep editing code for now... it really needs an ui with options on screen and future commands to skip stray pixels etc 👍)
Hi @Heracleum:
You mentioned your program can work with smaller dimensions than the full-screen.
I am seeing your function, function cmprs(str,ck)
How do I enter X1, Y1, X2, Y2 coordinates to test, please ?
@dw817 no that cmprs is junk to delete.
To define the crop there's the area
table definition at the very first lines, just below --set transparent color
(which is the other main option to edit)
I'll add a decent UI for options one day.
this is purely amazing work, wish I could double-star it!
@ironchestgames thanks!
I should actually take some time to expand the basic concept and add a proper ui and tools...
Also interested to know from people who used this in their cart 👍
I love the simplicity of it. And if I'd like some stray pixels I just do it layered, np!
I am commenting because I noticed that I have created something similar in purpose to the content of this post.
My code is rather scattered and hard to understand.
So I will summarize what I think is different in my P8OOCCapture features. I hope you find it useful and hints for improvement.
- Draws at once to the final print position for each color, including line feed code, and returns to the print start position.
- If the same character or One-Off is followed, use P8SCII for repetitive processing.
- Substitute
" "
for whitespace. - Omit whitespace following a line break.
- Optionally, store the One-Off dictionary in the custom font area. (This increases the frequency of drawing 8x8 in a single character.)
Making a title screen for an upcoming game and was wondering if there was a way to do half-screen? Would it save characters?
@arrowonionbelly awesome gfx! I love the TRON-esque look 💯
Yes to have just half screen: say you've put your 128x64 image on the top half of spritesheet, just manually edit the 3rd line at the top of cart code and shorten the area height to 64px:
area={x=0,y=0,w=128,h=128} --fullscreen
To:
area={x=0,y=0,w=128,h=64} --top half
[Please log in to post a comment]