Rewrite Cabi README.md with more details

Also added file sample.png in the cabi directory, which is used
for the example sketch in the Cabi README.md file

Also added a note referring to Cabi in the documentation for
drawCompressed()
This commit is contained in:
Scott Allen 2020-07-08 10:27:22 -04:00
parent 566557eab3
commit c74438e377
3 changed files with 201 additions and 68 deletions

View File

@ -1,75 +1,203 @@
# Cabi # Cabi - Compress Arduboy Image
Arduboy encoder:
Monochrome rle encoding by [Zep @lexaloffle](https://twitter.com/lexaloffle)
This tool is used for creating a compressed array from an image (png) A command line program to read a PNG (Portable Network Graphics) 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.
The function to decompress in Arduino (Arduboy) can be found here: Written by zep
https://github.com/TEAMarg/drawCompressed https://www.lexaloffle.com/bbs/?uid=1
to build: https://twitter.com/lexaloffle
```Bash
gcc cabi.c lodepng/lodepng.c -o cabi Contributed to Team A.R.G.
This program uses code from the LodePNG project by Lode Vandevenne to read
and decode PNG files.
https://github.com/lvandeve/lodepng
This version of Cabi is maintained as part of the Arduboy2 library so that it
remains available since the demise of Team A.R.G.
## Building the program
Pre-built executable code is not provided due to the difficulty of maintaining
versions for all the many operating systems that it could be run on.
The code is written in C and should compile properly using any ANSI C99
compatible compiler, such as (but not limited to) gcc or clang.
### Build examples
To build from a copy of the cabi directory tree provided, while in the base
directory containing cabi.c use:
`gcc cabi.c lodepng/lodepng.c -o cabi`
or
`clang cabi.c lodepng/lodepng.c -o cabi`
For Windows, it may be more desirable to name the program `CABI.EXE` by using:
`-o CABI.EXE`
Compiler options for optimization, etc. (such as -O2 or -Os) can be added if
desired but likely won't make much difference for most uses.
## Usage
The binary executable file (cabi or CABI.EXE) should be placed somewhere in the
path for executables on the operating system used, or else include the path as
part of the command given.
Running Cabi without any parameters will just output a brief program
description and the usage syntax:
```text
cabi - Compress Arduboy Image
Convert a PNG file into RLE encoded C/C++ source
for use with Arduboy2 drawCompressed()
usage: cabi in.png [array_name_prefix]
``` ```
to use: For `in.png` substitute the name of the PNG file to be converted. If the file
```Bash isn't in the current directory, the full path and name can be specified.
cabi input_file.png [array_name_prefix]
```
Look at the compress ratio, before using the compressed data, it might be worse in some cases (above 1 is bad) For `[array_name_prefix]` an optional prefix for the names of the arrays created
can be given. If this parameter isn't provided, `compressed_image` will be used
for the prefix.
ouput: If the program is unable to produce proper output, an error message will be
```C++ given and a non-zero exit code will be returned.
// input_file.png width: 128 height: 48
const uint8_t PROGMEM array_name_prefix[] = { ## Input file decoding
0x7f,0x2f,0x30,0x68,0xb7,0xdb,0xed,0xdb,0x53,0x9f,0xfa,0x73,0x97,0xbb,0xd5,0xe7,
0x2e,0x77,0xb9,0xcb,0x5d,0x6e,0xbb,0xbd,0xdc,0xe5,0x2e,0x77,0xb9,0xcb,0x5d,0xee, The input file should be a PNG file containing the image to be converted. The
0x72,0x97,0x5b,0x2b,0xbb,0xdc,0x6e,0xb7,0xdb,0xed,0x76,0xbb,0xdd,0x6e,0xb7,0xdb, image will be translated to a raw array of 32 bit RGBA (Red, Green, Blue, Alpha)
0xed,0x76,0xbb,0xdd,0x6e,0xb7,0xca,0xae,0x43,0x57,0x76,0xbb,0x5d,0x6e,0xb7,0xdb, pixels internally before being processed to output. Ideally, pixels that are to
0xed,0x76,0xbb,0xdd,0x6e,0xb7,0xdb,0xad,0xb2,0xdb,0x75,0xd8,0x5a,0x2a,0xb9,0xdd, be drawn (represented as a 1 in the image output) should be fully white.
0x6e,0xb7,0xeb,0x75,0x74,0xd9,0xe5,0x72,0xf9,0xfd,0xdf,0xea,0x72,0x57,0x77,0x75, Non-drawn (0) pixels should be fully black. Pixels intended to be masked out of
0x97,0x3b,0x6d,0x95,0x93,0xca,0x2a,0xbb,0x0e,0xbf,0x6d,0x6f,0x75,0xab,0xed,0xb9, the image (represented as a 0 in both the image and mask output), should be
0x5c,0x77,0x2a,0xcf,0x9f,0x76,0xda,0xd5,0xad,0x4e,0xf7,0xaa,0x57,0xe9,0x74,0x3a, fully transparent and their color doesn't matter.
0x9d,0xbb,0xa4,0xc2,0xd1,0x76,0xb6,0xb4,0xd3,0x2e,0x9f,0x5e,0x5a,0x85,0xbc,0x74,
0x3a,0x9d,0x4e,0xa7,0x3b,0x67,0x57,0x5b,0xaf,0x93,0x4a,0xb9,0x9d,0x52,0xca,0x2d, However, after translation to RGBA, any pixel with an alpha (opaqueness) value
0x97,0xdb,0x74,0x6c,0x49,0xe5,0x9c,0x92,0x4a,0x2a,0xa9,0xa4,0x53,0x84,0xe9,0xf4, of 127 or less will be set as non-drawn (0) for both the image and the mask.
0xea,0x53,0xbf,0x56,0x2a,0x65,0xed,0x6e,0xa7,0x94,0x52,0x4a,0x46,0x9f,0x5e,0xaf, For the image, after the alpha value is first taken into account, pixels with a
0x97,0xcb,0x6d,0xbc,0x4b,0xe7,0x5c,0xff,0x13,0x7a,0x9f,0xde,0xce,0xbe,0xd6,0xb9, red color value greater than 127 will be set as drawn (1) and others will be
0xee,0xd4,0xae,0x3b,0xb5,0x6e,0x7b,0xab,0x5b,0x8d,0x24,0x25,0x4b,0x29,0x95,0x4a, set as non-drawn (0). For the mask, only the alpha value is used and red is
0x76,0x56,0x3a,0x1d,0xdb,0xce,0xcd,0x96,0x4e,0xa7,0xd3,0xe9,0xec,0x4e,0x4a,0xe9, ignored. Green and blue color values are ignored for both image and mask.
0xec,0x08,0xdb,0x3a,0x67,0xe7,0x52,0xb3,0x29,0x2d,0xf5,0xa8,0x4b,0xa7,0xd3,0xe9,
0x74,0xce,0xb9,0x5b,0x2f,0xdd,0x6a,0x87,0xac,0xa4,0x2a,0x25,0x95,0x54,0x52,0x49, ### To summarize:
0x25,0xed,0x36,0xa4,0xed,0xdc,0x64,0x49,0xa5,0x64,0xc8,0x5a,0x2a,0x1d,0xda,0xc2,
0x1b,0x11,0x06,0xb9,0x2a,0x95,0x4c,0x55,0x2a,0xa2,0xc6,0xd5,0xea,0x56,0xb7,0xdd, For the image:
0x6e,0xb7,0x4a,0x4a,0x55,0xa5,0x2a,0x95,0x52,0x2a,0x55,0xc9,0x54,0xaa,0x92,0xa9,
0x54,0x25,0x53,0x29,0xa5,0xdd,0xf4,0x69,0x25,0xa5,0x54,0xed,0x5c,0xe7,0x3a,0xe7, Green and blue are ignored.
0x9c,0x73,0x4a,0x2a,0x3b,0x15,0x51,0x52,0xf1,0x79,0xa5,0xd3,0x49,0x25,0x95,0xdc,
0x6e,0xb7,0xdb,0xed,0x36,0x9d,0xce,0x6e,0xff,0xff,0xd3,0xa7,0x75,0x3a,0x1d,0xd9, | Alpha | Red | Output |
0xce,0x39,0xe7,0x1c,0xd3,0xce,0x39,0xe7,0x9c,0xeb,0x5c,0xe7,0x9c,0x73,0x6e,0xf2, |:------:|:-------:|:------:|
0xa4,0x54,0x52,0x2a,0xbb,0xdd,0x6e,0x2f,0xad,0xb4,0xd2,0xf9,0xb4,0x72,0x4e,0x6e, | <= 127 | <= 127 | 0 |
0x63,0x95,0xce,0x39,0xa9,0x3e,0xf5,0x6f,0x8f,0x08,0x87,0x64,0xc9,0x54,0x32,0xa2, | <= 127 | > 127 | 0 |
0xb6,0xdb,0xed,0xea,0x56,0xb7,0x3d,0x9d,0x4e,0xa7,0x5b,0x5d,0x77,0x4a,0xb9,0xfe, | > 127 | <= 127 | 0 |
0xa7,0x4a,0xd6,0xd2,0x29,0xd5,0x5d,0xab,0x63,0xeb,0x55,0xaf,0xf2,0x4a,0x72,0xef, | > 127 | > 127 | 1 |
0xdc,0x76,0xbb,0x5d,0x6e,0xb7,0x4a,0x2a,0xe7,0x56,0x3b,0x19,0xd1,0x76,0x2e,0x65,
0xb7,0xdb,0xd5,0xbd,0xea,0x5c,0xe7,0x3a,0x9d,0xdd,0x6e,0xb7,0x5b,0x25,0x9d,0xb3, For the mask:
0xe9,0x94,0x54,0x52,0x49,0x25,0x95,0x54,0xaa,0x94,0x2a,0x25,0x95,0x54,0xd2,0x59,
0x75,0xdb,0xed,0xae,0x5e,0x75,0xae,0x73,0x9d,0xeb,0xd4,0xa7,0x3e,0xf5,0xb9,0xda, Red, green and blue are ignored.
0xee,0xa4,0xb4,0xca,0x6e,0xb7,0xdb,0xad,0x73,0xee,0xda,0x39,0xbb,0xba,0xb7,0x53,
0x25,0x95,0xdc,0x2e,0x95,0x53,0xf6,0x72,0x97,0x93,0x4a,0x3a,0xa9,0xa4,0x3a,0xa5, | Alpha | Output |
0x7a,0x95,0xfa,0x5c,0xa7,0xd3,0x69,0xae,0xed,0x2a,0xa9,0x64,0x25,0x95,0x4e,0xa7, |:------:|:------:|
0xd3,0xe9,0xd0,0x97,0x5e,0x67,0xb7,0xa1,0x69,0x9d,0x1d,0xcb,0xe9,0xed,0x36,0x34, | <= 127 | 0 |
0xea,0x74,0x3a,0x9d,0x56,0x4a,0x2a,0xa9,0x94,0x4e,0xa7,0x53,0x44,0x4a,0x56,0xa9, | > 127 | 1 |
0xaa,0x54,0x2a,0x25,0x75,0x5a,0x6d,0x95,0x74,0xeb,0x74,0xe7,0x8a,0x4a,0xc9,0x2a,
0x55,0x95,0x4a,0xa5,0xa4,0x56,0xd2,0x39,0xe7,0xb6,0xdb,0xed,0xf6,0xed,0x29,0x59, ## Output
0xc9,0x4a,0x2a,0xe2,0x74,0xb4,0xe9,0xd0,0xb5,0xce,0x8e,0xfd,0x74,0x3a,0xb4,0x01
Cabi will send all output to `stdout`, which is usually the console unless
redirected. To save the output, you may be able to copy and paste it into your
editor, or you can redirect `stdout` to a file for importing. For example:
`cabi PlayerSprite.png PlayerSprite > PlayerSprite.out`
If conversion is successful, the output will be text representing C/C++ code
for two arrays, an image and a mask, that can be included in a sketch for use
by the *drawCompressed()* function. The image array will be named the same as
the prefix. The mask name will be the prefix with `_mask` appended to it.
Along with the actual array text, a comment will be included before each array
giving the input file name used and the dimensions of the image. A comment
included after each array will give the size of the array and the compression
ratio compared to the non-compressed equivalent (although the ratio is based
on the compressed array including two bytes for the bitmap dimensions compared
to a non-compressed array without bitmap dimensions).
Note that it's possible that the "compressed" array will actually end up
larger than the equivalent non-compressed one would. This is indicated by
a compression ratio greater than 1. The ratio should be noted and taken into
account when determining whether using Cabi compressed bitmaps is suitable for
the intended purpose.
If masking isn't required, the mask array can be ignored or deleted.
Note that the usage message or any error message will also be sent to `stdout`,
rather than `stderr`. Therefore, if you redirect the output to a file, in this
case the file will contain only that text.
## Using the output with *drawCompressed()*
The Arduboy2 *drawCompressed()* function doesn't natively handle a mask for
"transparent" pixels in an image. However, masking can be accomplished by
calling *drawCompressed()* twice with the same coordinates. The first call
specifies the mask array and the color BLACK. The second call specifies the
image array and the color WHITE.
An example PNG bitmap named `sample.png` is included with the program. Here is
an example Arduboy sketch that draws this bitmap with masking, using the Cabi
output imported into the sketch.
```cpp
#include <Arduboy2.h>
Arduboy2 arduboy;
// ===== Cabi output =====
// sample.png width: 32 height: 32
const PROGMEM uint8_t sample[] = {
0x1f,0x1f,0x68,0x93,0xca,0x39,0xe5,0x9c,0x72,0xca,0xe9,0x74,0x4b,0x25,0x95,0xdc,
0x6e,0xb7,0xdb,0xed,0x56,0x49,0x65,0xb7,0x4a,0x3a,0xa9,0xac,0x92,0x4e,0x3a,0xa9,
0x74,0x94,0x8c,0x6a,0xbb,0xdd,0x6e,0xb7,0x8c,0x76,0xbb,0xdd,0x6e,0xb7,0xdb,0xed,
0x76,0xbb,0xdd,0xf2,0xf1,0xa6,0xb7,0x52,0x79,0xc5,0xa4,0xbc,0x92,0x76,0x1d,0x2f,
0x9f,0xdd,0x6e,0xb7,0xdb,0xed,0x76,0xbb,0xdd,0x6e,0xb7,0x8c,0xf4,0xd9,0x15,0x23,
0x65,0x5a,0x49,0x27,0x9d,0x54,0x56,0x49,0x27,0x95,0xdd,0x2a,0xa9,0xec,0x76,0xbb,
0xdd,0x6e,0x97,0x4a,0x2a,0xb9,0x54,0xce,0x39,0xe5,0x94,0x73,0xca,0x39,0x25,0xa3,
0x05
}; };
// bytes:640 ratio: 0.833 // bytes:113 ratio: 0.883
const PROGMEM uint8_t sample_mask[] = {
const uint8_t PROGMEM array_name_prefix_mask[] = { 0x1f,0x1f,0x68,0x93,0xca,0x39,0x25,0x95,0xdc,0xa6,0xd3,0xa1,0x35,0x9d,0x4e,0x6f,
0x7f,0x2f,0x81,0xff,0x17 0x95,0x54,0xd2,0x39,0xa9,0x74,0x94,0xe8,0xb4,0xdb,0xed,0x76,0xbb,0xdd,0x6e,0xb7,
0xdb,0xed,0x16,0x8f,0x8a,0x49,0xe1,0xd1,0x6e,0xb7,0xdb,0xed,0x76,0xbb,0xdd,0x6e,
0xb7,0x5b,0x74,0x52,0xa6,0x95,0x74,0x4e,0x2a,0xa9,0xec,0x3a,0x9d,0x0e,0xad,0xe9,
0x74,0x76,0xa9,0xa4,0x72,0x4e,0xc9,0x68,0x01
}; };
// bytes:5 ratio: 0.007 // bytes:73 ratio: 0.570
// =======================
void setup() {
arduboy.begin();
}
void loop() {
arduboy.clear();
arduboy.drawCompressed(20, 10, sample_mask, BLACK);
arduboy.drawCompressed(20, 10, sample, WHITE);
arduboy.display();
}
``` ```

BIN
extras/cabi/sample.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

View File

@ -700,14 +700,19 @@ class Arduboy2Base : public Arduboy2Core
* (optional; defaults to WHITE). * (optional; defaults to WHITE).
* *
* \details * \details
* Draw a bitmap starting at the given coordinates from an array that has * Draw a bitmap starting at the given coordinates using an array that has
* been compressed using an algorthm implemented by Team A.R.G. * been compressed using an RLE algorthm implemented by Team A.R.G.
* *
* Bits set to 1 in the provided bitmap array will have their corresponding * Bits set to 1 in the provided bitmap array (after decoding) will have
* pixel set to the specified color. For bits set to 0 in the array, the * their corresponding pixel set to the specified color. For bits set to 0
* corresponding pixel will be left unchanged. * in the array, the corresponding pixel will be left unchanged.
* *
* The array must be located in program memory by using the PROGMEM modifier. * The array must be located in program memory by using the PROGMEM modifier.
*
* \note
* C source code for a command line program named `Cabi`, which can convert
* a PNG bitmap image file to source code suitable for use with
* `drawCompressed()`, is included in the `extras` directory of the library.
*/ */
static void drawCompressed(int16_t sx, int16_t sy, const uint8_t *bitmap, uint8_t color = WHITE); static void drawCompressed(int16_t sx, int16_t sy, const uint8_t *bitmap, uint8_t color = WHITE);