Arduboy2/extras/cabi/cabi.c

386 lines
7.3 KiB
C
Raw Normal View History

/*
cabi - Compress Arduboy Image
A command line program to read a PNG file containing a bitmap image, compress
it using RLE encoding and convert it to C/C++ code suitable for use with the
Team A.R.G. drawCompressed() function. This function is included in the
Arduboy2 library.
Written by zep
https://www.lexaloffle.com/bbs/?uid=1
https://twitter.com/lexaloffle
Contributed to Team A.R.G.
Modifications by Scott Allen - July 2020, September 2020
To the extent possible under law, the author(s) have dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
You should have received a copy of the CC0 Public Domain Dedication along with
this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
Usage:
cabi in.png [array_name_prefix]
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <memory.h>
#include "lodepng/lodepng.h"
// alternative pixel order mapping
//#define READING_ORDER 1
unsigned reading_order = 0;
// one byte encodes a 1x8 stick; low byte at top
// for testing
void draw_sprite_ascii(const uint8_t *dat, unsigned w, unsigned h)
{
unsigned x, y;
unsigned row, bit;
2020-07-02 20:10:36 +00:00
for (y = 0; y < h; y ++)
{
row = y/8;
bit = y&7;
2020-07-02 20:10:36 +00:00
for (x = 0; x < w; x++)
{
if (dat[x + (row*w)] & (1 << bit))
printf("#");
else
printf(".");
}
printf("\n");
}
}
// ----------------------------------------------------------------------------
// :: Decompress
// ----------------------------------------------------------------------------
// compression / decompression session state
typedef struct CSESSION{
unsigned byte;
unsigned bit;
const uint8_t *src;
uint8_t *dest;
unsigned src_pos;
unsigned out_pos;
unsigned w, h;
}CSESSION;
static CSESSION cs;
// get an n-bit number from the compressed data stream
static unsigned getval(unsigned bits)
{
unsigned val = 0;
unsigned i;
for (i = 0; i < bits; i++)
{
if (cs.bit == 0x100)
{
cs.bit = 0x1;
cs.byte = cs.src[cs.src_pos];
cs.src_pos ++;
}
if (cs.byte & cs.bit)
val += (1 << i);
cs.bit <<= 1;
}
return val;
}
// decompress_rle
// if not NULL, w and h give back the size of the sprite.
void draw_compressed_sprite_ascii(const uint8_t *src)
{
unsigned col;
unsigned bl, len;
unsigned i;
unsigned w, h;
unsigned x, y;
unsigned total = 0;
2020-07-02 20:10:36 +00:00
memset(&cs, 0, sizeof(cs));
cs.src = src;
cs.bit = 0x100;
cs.src_pos = 0;
2020-07-02 20:10:36 +00:00
// header
2020-07-02 20:10:36 +00:00
w = getval(8) + 1;
h = getval(8) + 1;
col = getval(1); // starting colour
2020-07-02 20:10:36 +00:00
x = y = 0;
2020-07-02 20:10:36 +00:00
while (y < h)
{
bl = 1;
while (!getval(1))
bl += 2;
2020-07-02 20:10:36 +00:00
len = getval(bl)+1; // span length
2020-07-02 20:10:36 +00:00
for (i = 0; i < len; i++)
{
//if ((x%8) == 0) // every 8th bit (format test)
printf("%s", col ? "#":".");
2020-07-02 20:10:36 +00:00
if (col) total++;
x++;
if (x >= w)
{
printf("\n");
y ++;
x = 0;
}
2020-07-02 20:10:36 +00:00
//if ((x+y*w)%(w*8) == 0) printf("\n"); // print every 8th line (format test)
2020-07-02 20:10:36 +00:00
}
2020-07-02 20:10:36 +00:00
col = 1-col; // toggle
}
printf("\ntotal: %u\n", total);
}
// ----------------------------------------------------------------------------
// :: Compress
// ----------------------------------------------------------------------------
/*
getcol
2020-07-02 20:10:36 +00:00
pos is the index of the pixel: 0 .. w*h-1
*/
static unsigned getcol(unsigned pos)
{
unsigned x, y;
2020-07-02 20:10:36 +00:00
// display order
if (reading_order == 0)
{
if (cs.src[pos/8] & (1 << (pos&7))) return 1;
return 0;
}
2020-07-02 20:10:36 +00:00
// reading order (compresses slightly better but harder to optimize sprite blit)
// or use this after loading png into display order (no need for extra conversion)
x = (pos % cs.w);
y = (pos / cs.w);
if (cs.src[x + ((y/8)*cs.w)] & (1 << (y&7))) return 1;
return 0;
}
2020-07-02 20:10:36 +00:00
static unsigned find_rlen(unsigned pos, unsigned plen)
{
unsigned col;
unsigned pos0;
2020-07-02 20:10:36 +00:00
col = getcol(pos);
pos0 = pos;
while(getcol(pos) == col && pos < plen)
pos ++;
2020-07-02 20:10:36 +00:00
return pos-pos0;
}
// write a bit to the stream. non-zero val means 1, otherwise 0.
static void putbit(unsigned val)
{
if (val) cs.byte |= cs.bit;
cs.bit <<= 1;
if (cs.bit == 0x100)
{
//output byte
if (cs.out_pos != 0) printf(",");
if (cs.out_pos % 16 == 0) printf("\n");
printf("0x%02x", cs.byte);
2020-07-02 20:10:36 +00:00
cs.out_pos ++;
cs.bit = 0x1;
cs.byte = 0;
2020-07-02 20:10:36 +00:00
}
}
// write an n-bit (bits) number (val) to the output steam
static void putval(unsigned val, unsigned bits)
{
unsigned i;
2020-07-02 20:10:36 +00:00
if (bits <= 0) return;
for (i = 0; i < bits; i++)
putbit(val & (1 << i));
}
// write a span length
// a string of bits encoding the number of bits needed to encode the length,
// and then the length.
static void putsplen(unsigned len)
{
unsigned blen = 1; // how bits needed to encode length
while ((unsigned)(1 << blen) <= len) {
blen += 2;
}
// write number of bits (1-terminated string of zeroes)
putval(0,(blen-1)/2);
putval(1,1); // terminator
// write length
putval(len, blen);
}
/*
comp
2020-07-02 20:10:36 +00:00
compress plen 1-bit pixels from src to dest
2020-07-02 20:10:36 +00:00
*/
unsigned compress_rle(const uint8_t *src, unsigned w, unsigned h, char *prefix, char *suffix)
{
unsigned pos;
unsigned rlen;
2020-07-02 20:10:36 +00:00
printf("const PROGMEM uint8_t %s%s[] = {", prefix, suffix);
fflush(stdout);
2020-07-02 20:10:36 +00:00
memset(&cs, 0, sizeof(cs));
cs.src = src;
cs.bit = 1;
cs.w = w;
cs.h = h;
2020-07-02 20:10:36 +00:00
// header
putval(w-1, 8);
putval(h-1, 8);
putval(getcol(0), 1); // first colour
2020-07-02 20:10:36 +00:00
pos = 0;
2020-07-02 20:10:36 +00:00
// span data
2020-07-02 20:10:36 +00:00
while (pos < w*h)
{
rlen = find_rlen(pos, w*h);
pos += rlen;
putsplen(rlen-1);
}
2020-07-02 20:10:36 +00:00
// pad with zeros and flush
while (cs.bit != 0x1)
putbit(0);
2020-07-02 20:10:36 +00:00
printf("\n};\n");
2020-07-02 20:10:36 +00:00
return cs.out_pos; // bytes
}
int main(int argc, char **argv)
{
unsigned compressed_len;
unsigned w, h;
unsigned char *bmp = NULL;
unsigned char *bmp0 = NULL;
unsigned char *bmp1 = NULL;
unsigned result;
unsigned rawlen;
unsigned x, y;
unsigned row, bit;
char default_prefix[] = "compressed_image";
char *prefix = default_prefix;
2020-07-02 20:10:36 +00:00
if (argc < 2)
{
printf("cabi - Compress Arduboy Image\n");
printf("Convert a PNG file into RLE encoded C/C++ source\n");
printf("for use with Arduboy2 drawCompressed()\n\n");
printf("usage: cabi in.png [array_name_prefix]\n");
exit(1);
}
if (argc >= 3) {
prefix = argv[2];
}
2020-07-02 20:10:36 +00:00
result = lodepng_decode32_file(&bmp, &w, &h, argv[1]);
2020-07-02 20:10:36 +00:00
if (result != 0) {
printf("error %u: file %s: %s\n", result, argv[1], lodepng_error_text(result));
free(bmp);
exit(result);
}
2020-07-02 20:10:36 +00:00
if (h % 8 != 0) {
printf("error 120: file %s: image height must be a multiple of 8 but is %u\n", argv[1], h);
free(bmp);
exit(120);
}
// generate sprite and mask
2020-07-02 20:10:36 +00:00
rawlen = w * (h+7) / 8;
2020-07-02 20:10:36 +00:00
bmp0 = (unsigned char *)malloc(rawlen); memset(bmp0, 0, rawlen);
bmp1 = (unsigned char *)malloc(rawlen); memset(bmp1, 0, rawlen);
2020-07-02 20:10:36 +00:00
printf("// %s width: %u height: %u\n", argv[1], w, h);
2020-07-02 20:10:36 +00:00
for (y = 0; y < h; y++)
{
for (x = 0; x < w; x++)
{
row = y/8;
bit = y&7;
if (bmp[(x+y*w)*4 + 3] > 127) // need to be opaque to count
if (bmp[(x+y*w)*4 + 0] > 127)
{
// set sprite
bmp0[x + (row*w)] |= (1 << bit);
}
if (bmp[(x+y*w)*4 + 3] > 127)
{
// set mask
bmp1[x + (row*w)] |= (1 << bit);
}
2020-07-02 20:10:36 +00:00
}
}
2020-07-02 20:10:36 +00:00
compressed_len = compress_rle(bmp0, w, h, prefix, "");
printf("// bytes:%u ratio: %3.3f\n\n", compressed_len, (float)(compressed_len * 8)/ (float)(w*h));
compressed_len = compress_rle(bmp1, w, h, prefix, "_mask");
printf("// bytes:%u ratio: %3.3f\n\n", compressed_len, (float)(compressed_len * 8)/ (float)(w*h));
2020-07-02 20:10:36 +00:00
free(bmp);
free(bmp0);
free(bmp1);
2020-07-02 20:10:36 +00:00
return 0;
}