Skip to content

Commit 9dc226e

Browse files
committed
Added G5 image tool + API and example
1 parent c152bd5 commit 9dc226e

9 files changed

Lines changed: 448 additions & 6 deletions

File tree

examples/g5_image/bart_128x64.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// Created with imageconvert, written by Larry Bank
3+
// 128 x 64 x 1-bit per pixel
4+
// compressed image data size = 358 bytes
5+
//
6+
// For non-Arduino systems
7+
#ifndef PROGMEM
8+
#define PROGMEM
9+
#endif
10+
const uint8_t bart_128x64[] PROGMEM = {
11+
0xbf,0xbb,0x80,0x00,0x40,0x00,0x5e,0x01,0xcc,0xa8,0xe9,0xcc,0x9f,0x09,0x1c,0x2c,
12+
0x3c,0x14,0x11,0x0a,0xa0,0xb6,0xe6,0x3e,0xe1,0x04,0xec,0xbc,0x74,0x21,0xf6,0x61,
13+
0x35,0x91,0xc5,0x98,0x25,0x74,0x14,0x1e,0xe0,0x91,0x09,0x98,0x29,0xa5,0x22,0xe6,
14+
0x0a,0x32,0x23,0x84,0x08,0x17,0xce,0x10,0x4e,0xa9,0x88,0xf0,0x49,0x8b,0xb2,0x13,
15+
0xac,0xc2,0x3f,0x85,0x08,0x84,0xec,0x96,0x18,0x51,0x79,0x85,0xcc,0x7f,0x59,0xc2,
16+
0x43,0x43,0x5f,0xfe,0xa0,0xcc,0x1b,0x61,0x75,0x94,0x85,0x84,0x42,0x5f,0xa0,0x47,
17+
0x10,0x09,0x91,0x10,0x88,0x4a,0xff,0xff,0x6c,0xc2,0xfa,0xa6,0x08,0x5e,0xbd,0x90,
18+
0xdf,0xbb,0xfb,0xff,0xfe,0xa6,0x1c,0x3c,0xa4,0x2b,0xff,0xc7,0x08,0x2c,0x76,0x88,
19+
0x55,0xa5,0xeb,0x44,0x26,0x1a,0x11,0x61,0xcc,0x21,0xbf,0x4f,0xe1,0xc8,0x48,0x8c,
20+
0x90,0xc7,0xfd,0xf0,0xcb,0xc3,0x57,0x1f,0xc2,0x46,0x28,0x39,0x82,0x09,0x84,0xd2,
21+
0xca,0xc2,0x3f,0x48,0xc2,0xfb,0xc6,0x2b,0x58,0xf4,0x83,0xee,0xa3,0x5b,0xc8,0xa7,
22+
0x79,0x48,0x58,0x44,0x26,0x08,0x84,0xd9,0x50,0x98,0x41,0xbf,0xc1,0x16,0x04,0x84,
23+
0x13,0x21,0x3f,0x0b,0xdd,0xff,0xf7,0xf4,0x92,0x77,0xf4,0x08,0x84,0xf6,0x42,0xbf,
24+
0x0a,0x9f,0xfa,0xf7,0xfe,0xde,0x4a,0xbb,0x5f,0xfb,0x05,0xf7,0xdd,0xeb,0xaf,0xac,
25+
0x77,0x5f,0x75,0xff,0x82,0x6a,0x96,0xd3,0xe8,0x11,0x0b,0xeb,0x4b,0xe9,0x38,0xfd,
26+
0x06,0x0f,0x58,0xfa,0xde,0xba,0xce,0x10,0x27,0x54,0x38,0x44,0x26,0x1f,0xe2,0x99,
27+
0x0a,0xf5,0x2a,0x09,0x5f,0xbe,0xb6,0x44,0xce,0x10,0x19,0xaa,0x7f,0xa5,0x7d,0x37,
28+
0x24,0x2f,0x09,0x61,0x90,0xef,0x5d,0x78,0x8b,0x55,0x5b,0xfd,0x3d,0x61,0x91,0xad,
29+
0x56,0xff,0x61,0xb2,0x2c,0x9c,0x49,0xac,0x45,0x32,0x19,0xfa,0x1e,0xc3,0xab,0x4f,
30+
0xa5,0x83,0x12,0xa1,0xa1,0x08,0x84,0xea,0x45,0xd6,0x0d,0x3f,0x4a,0xb4,0x85,0x8f,
31+
0x07,0x82,0x05,0x92,0x1f,0x87,0xd3,0xfc,0x6d,0x7f,0xf5,0xff,0xe8,0xe1,0x61,0x7f,
32+
0xff,0x8f,0xf3,0x88,0x05,0x7e,0x94,0xc3,0x52,0xc2,0xfb,0x89,0x82,0x16,0xfd,0x4c,
33+
0x3f,0xc2,0x56,0x86,0x38,0x00};

examples/g5_image/g5_image.ino

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// OneBitDisplay compressed image example
3+
//
4+
#include <OneBitDisplay.h>
5+
#include "bart_128x64.h"
6+
#include "smiley.h" // 100x100
7+
ONE_BIT_DISPLAY obd;
8+
9+
void setup() {
10+
int rc;
11+
float f;
12+
obd.I2Cbegin(OLED_128x64);
13+
obd.fillScreen(OBD_WHITE);
14+
// OneBitDisplay can draw compressed graphics directly onto a display
15+
// without any local framebuffer, but there are limitations
16+
for (int i=0; i<50; i++) {
17+
f = 0.02f * (float)i;
18+
// Draw the Bart image scaled and inverted
19+
rc = obd.loadG5Image(bart_128x64, 0, 0, OBD_WHITE, OBD_BLACK, f);
20+
}
21+
delay(3000);
22+
// Setting the foreground and background colors equal tells it to draw the background as transparent
23+
// but... it can only do that when drawing into memory
24+
obd.allocBuffer(); // allocate a framebuffer
25+
obd.fillScreen(OBD_WHITE);
26+
obd.loadG5Image(bart_128x64, 0, 0, OBD_WHITE, OBD_BLACK); // draw at 1.0 scale
27+
// Draw the 100x100 smiley as 50x50 transparently on top of Bart
28+
obd.loadG5Image(smiley, (128-50)/2, (64-50)/2, OBD_WHITE, OBD_WHITE, 0.5f);
29+
// Since we're using a back buffer, we need to tell OneBitDisplay to
30+
// explicitly copy the pixels from memory to the physical display
31+
obd.display();
32+
} /* setup() */
33+
34+
void loop()
35+
{
36+
37+
}

examples/g5_image/smiley.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// Created with imageconvert, written by Larry Bank
3+
// 100 x 100 x 1-bit per pixel
4+
// compressed image data size = 239 bytes
5+
//
6+
// For non-Arduino systems
7+
#ifndef PROGMEM
8+
#define PROGMEM
9+
#endif
10+
const uint8_t smiley[] PROGMEM = {
11+
0xbf,0xbb,0x64,0x00,0x64,0x00,0xe7,0x00,0x32,0xff,0x3a,0x82,0x93,0xa3,0x3d,0x04,
12+
0x0e,0x10,0x70,0x83,0x84,0x72,0x62,0x83,0x84,0x72,0x05,0x43,0xa3,0x8d,0x38,0xe1,
13+
0x02,0x06,0x1d,0x04,0x1b,0xa0,0x83,0x70,0x82,0x0d,0xd2,0x61,0xd2,0x6e,0x82,0x0d,
14+
0xd2,0x6e,0x93,0xe9,0xba,0x46,0x5a,0x5b,0xa4,0x61,0xf4,0x20,0xdb,0xa4,0x9a,0x6d,
15+
0xe9,0x6d,0xe9,0x5a,0xb7,0x55,0xbb,0xd5,0xab,0xd2,0xf6,0xf5,0xef,0x55,0xbb,0xd5,
16+
0xab,0xd7,0xef,0x5e,0xf5,0xfb,0xd7,0xbf,0xfe,0xbf,0x7a,0xdd,0xfb,0x5f,0xfa,0xff,
17+
0xbd,0x2f,0xbe,0xd3,0xff,0xaf,0xb4,0xff,0xeb,0xd3,0x4d,0x3f,0x11,0xb5,0xff,0xff,
18+
0xf3,0x10,0x46,0x68,0xbd,0x34,0xea,0xf6,0xbe,0xd6,0xd7,0xff,0xd6,0xfe,0xb7,0xfa,
19+
0x7e,0xda,0xda,0x5d,0x26,0xfe,0xb7,0xfb,0x09,0xfb,0x41,0xaa,0xde,0xd6,0xbb,0x25,
20+
0x4c,0x22,0x77,0xf6,0xd3,0x0d,0x26,0x12,0xdc,0x43,0x69,0x0d,0x6d,0x86,0x12,0x5b,
21+
0xb6,0x95,0x6d,0x86,0x12,0x5b,0xb0,0xc2,0x55,0xb6,0x0c,0x10,0x4b,0x6c,0x33,0x8e,
22+
0x10,0x92,0xdb,0x14,0xb7,0x6a,0xad,0x86,0x12,0x5b,0x61,0x2d,0xb0,0xd2,0x56,0xc1,
23+
0x82,0x4a,0xd8,0x30,0x49,0x5b,0x15,0xda,0x56,0xd2,0xb0,0xc2,0x56,0xd2,0x86,0xd2,
24+
0xb0,0xc2,0x0a,0xc3,0x09,0x58,0x61,0x28,0x60,0xc1,0x05,0x67,0x20,0x51,0x43,0x39,
25+
0x31,0x41,0x43,0x10,0xa1,0x85,0x0c,0x28,0x30,0x53,0xa8,0x29,0x32,0xef,0x00};

imageconvert/Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
all: imgconvert
2+
3+
CC = gcc
4+
CFLAGS = -Wall
5+
6+
imgconvert: main.c ../src/Group5.h ../src/g5enc.inl
7+
$(CC) $(CFLAGS) $< -o $@
8+
strip $@
9+
10+
clean:
11+
rm -f imgconvert

imageconvert/imgconvert

49.6 KB
Binary file not shown.

imageconvert/main.c

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
//
2+
// Group5 Image Compressor tool
3+
// Written by Larry Bank
4+
// Copyright (c) 2024 BitBank Software, Inc.
5+
//
6+
7+
#include <stdio.h>
8+
#define MAX_IMAGE_FLIPS 256
9+
#include "../src/Group5.h"
10+
#include "../src/g5enc.inl"
11+
//
12+
// Read a Windows BMP file into memory
13+
//
14+
uint8_t * ReadBMP(const char *fname, int *width, int *height, int *bpp, unsigned char *pPal)
15+
{
16+
int y, w, h, bits, offset;
17+
uint8_t *s, *d, *pTemp, *pBitmap;
18+
int pitch, bytewidth;
19+
int iSize, iDelta;
20+
FILE *infile;
21+
22+
infile = fopen(fname, "r+b");
23+
if (infile == NULL) {
24+
printf("Error opening input file %s\n", fname);
25+
return NULL;
26+
}
27+
// Read the bitmap into RAM
28+
fseek(infile, 0, SEEK_END);
29+
iSize = (int)ftell(infile);
30+
fseek(infile, 0, SEEK_SET);
31+
pBitmap = (uint8_t *)malloc(iSize);
32+
pTemp = (uint8_t *)malloc(iSize);
33+
fread(pTemp, 1, iSize, infile);
34+
fclose(infile);
35+
36+
if (pTemp[0] != 'B' || pTemp[1] != 'M' || pTemp[14] < 0x28) {
37+
free(pBitmap);
38+
free(pTemp);
39+
printf("Not a Windows BMP file!\n");
40+
return NULL;
41+
}
42+
w = *(int32_t *)&pTemp[18];
43+
h = *(int32_t *)&pTemp[22];
44+
bits = *(int16_t *)&pTemp[26] * *(int16_t *)&pTemp[28];
45+
if (bits <= 8 && pPal != NULL) { // it has a palette, copy it
46+
uint8_t *p = pPal;
47+
for (int i=0; i<(1<<bits); i++)
48+
{
49+
*p++ = pTemp[54+i*4];
50+
*p++ = pTemp[55+i*4];
51+
*p++ = pTemp[56+i*4];
52+
}
53+
}
54+
offset = *(int32_t *)&pTemp[10]; // offset to bits
55+
if (bits == 1) {
56+
bytewidth = (w+7) >> 3;
57+
} else {
58+
bytewidth = (w * bits) >> 3;
59+
}
60+
pitch = (bytewidth + 3) & 0xfffc; // DWORD aligned
61+
// move up the pixels
62+
d = pBitmap;
63+
s = &pTemp[offset];
64+
iDelta = pitch;
65+
if (h > 0) {
66+
iDelta = -pitch;
67+
s = &pTemp[offset + (h-1) * pitch];
68+
} else {
69+
h = -h;
70+
}
71+
for (y=0; y<h; y++) {
72+
if (bits == 32) {// need to swap red and blue
73+
for (int i=0; i<bytewidth; i+=4) {
74+
d[i] = s[i+2];
75+
d[i+1] = s[i+1];
76+
d[i+2] = s[i];
77+
d[i+3] = s[i+3];
78+
}
79+
} else {
80+
memcpy(d, s, bytewidth);
81+
}
82+
d += bytewidth;
83+
s += iDelta;
84+
}
85+
*width = w;
86+
*height = h;
87+
*bpp = bits;
88+
free(pTemp);
89+
return pBitmap;
90+
91+
} /* ReadBMP() */
92+
93+
//
94+
// Create the comments and const array boilerplate for the hex data bytes
95+
//
96+
void StartHexFile(FILE *f, int iLen, int w, int h, const char *fname)
97+
{
98+
int i;
99+
char szTemp[256];
100+
fprintf(f, "//\n// Created with imageconvert, written by Larry Bank\n");
101+
fprintf(f, "// %d x %d x 1-bit per pixel\n", w, h);
102+
fprintf(f, "// compressed image data size = %d bytes\n//\n", iLen);
103+
fprintf(f, "// For non-Arduino systems\n#ifndef PROGMEM\n#define PROGMEM\n#endif\n");
104+
strcpy(szTemp, fname);
105+
i = strlen(szTemp);
106+
if (szTemp[i-2] == '.') szTemp[i-2] = 0; // get the leaf name for the data
107+
fprintf(f, "const uint8_t %s[] PROGMEM = {\n", szTemp);
108+
} /* StartHexFile() */
109+
//
110+
// Add N bytes of hex data to the output
111+
// The data will be arranged in rows of 16 bytes each
112+
//
113+
void AddHexBytes(FILE *f, void *pData, int iLen, int bLast)
114+
{
115+
static int iCount = 0; // number of bytes processed so far
116+
int i;
117+
uint8_t *s = (uint8_t *)pData;
118+
for (i=0; i<iLen; i++) { // process the given data
119+
fprintf(f, "0x%02x", *s++);
120+
iCount++;
121+
if (i < iLen-1 || !bLast) fprintf(f, ",");
122+
if ((iCount & 15) == 0) fprintf(f, "\n"); // next row of 16
123+
}
124+
if (bLast) {
125+
fprintf(f, "};\n");
126+
}
127+
} /* AddHexBytes() */
128+
//
129+
// The user passed a file with the wrong bit depth (not 1)
130+
// convert to 1-bpp by converting each color to 0 or 1 based on the gray level
131+
//
132+
void ConvertTo1Bpp(uint8_t *pBMP, int w, int h, int iBpp, uint8_t *palette)
133+
{
134+
int g, x, y, iDelta, iPitch, iDestPitch;
135+
uint8_t *s, *d, *pPal, u8, count;
136+
137+
iPitch = (w * iBpp)/8;
138+
iPitch = (iPitch + 3) & 0xfffc;
139+
iDestPitch = (w+7)/8;
140+
iDestPitch = (iDestPitch + 3) & 0xfffc; // round to nearest "DWORD"
141+
iDelta = iBpp/8;
142+
for (y=0; y<h; y++) {
143+
s = &pBMP[iPitch * y];
144+
d = &pBMP[iDestPitch * y]; // overwrite the original data as we change it
145+
count = 8; // bits in a byte
146+
u8 = 0; // start with all black
147+
for (x=0; x<w; x++) { // slower code, but less code :)
148+
u8 <<= 1;
149+
switch (iBpp) {
150+
case 24:
151+
case 32:
152+
g = (s[0] + s[1]*2 + s[2])/4; // gray value
153+
s += iDelta;
154+
break;
155+
case 16:
156+
g = s[1] & 0xf8; // red
157+
g += ((s[0] | s[1] << 8) << 2) & 0x1f0; // green x 2
158+
g += (s[0] << 3) & 0xf8; // blue
159+
g /= 4;
160+
s += 2;
161+
break;
162+
case 8:
163+
pPal = &palette[s[0] * 4];
164+
g = (pPal[0] + pPal[1]*2 + pPal[2])/4;
165+
s++;
166+
break;
167+
case 4:
168+
if (x & 1) {
169+
pPal = &palette[(s[0] & 0xf) * 4];
170+
g = (pPal[0] + pPal[1]*2 + pPal[2])/4;
171+
s++;
172+
} else {
173+
pPal = &palette[(s[0]>>4) * 4];
174+
g = (pPal[0] + pPal[1]*2 + pPal[2])/4;
175+
}
176+
break;
177+
} // switch on bpp
178+
if (g >= 128) u8 |= 1; // white
179+
count--;
180+
if (count == 0) { // byte is full, move on
181+
*d++ = u8;
182+
u8 = 0;
183+
count = 8;
184+
}
185+
} // for x
186+
} // for y
187+
} /* ConvertTo1Bpp() */
188+
189+
int main(int argc, const char * argv[]) {
190+
uint8_t *s, *pBMP, *pOut;
191+
int rc, w, h, y, bpp;
192+
int iOutSize, iPitch;
193+
G5ENCIMAGE g5enc;
194+
BB_BITMAP bbbm;
195+
uint8_t palette[1024];
196+
int bHFile; // flag indicating if the output will be a .H file of hex data
197+
198+
printf("Group5 image conversion tool\n");
199+
if (argc != 3) {
200+
printf("Usage: ./imgconvert <WinBMP image> <g5 compressed image>\n");
201+
return -1;
202+
}
203+
pOut = (uint8_t *)argv[2] + strlen(argv[2]) - 1;
204+
bHFile = (pOut[0] == 'H' || pOut[0] == 'h'); // output an H file?
205+
206+
pBMP = ReadBMP(argv[1], &w, &h, &bpp, palette);
207+
if (bpp != 1) { // need to convert it to 1-bpp
208+
printf("Converting from %d-bpp to 1-bpp\n", bpp);
209+
ConvertTo1Bpp(pBMP, w, h, bpp, palette);
210+
}
211+
s = pBMP;
212+
printf("Bitmap size: %d x %d\n", w, h);
213+
iPitch = (w+7) >> 3;
214+
pOut = (uint8_t *)malloc(iPitch * h);
215+
rc = g5_encode_init(&g5enc, w, h, pOut, iPitch * h);
216+
for (y=0; y<h && rc == G5_SUCCESS; y++) {
217+
rc = g5_encode_encodeLine(&g5enc, s);
218+
s += iPitch;
219+
}
220+
if (rc == G5_ENCODE_COMPLETE) {
221+
FILE *f;
222+
iOutSize = g5_encode_getOutSize(&g5enc);
223+
printf("Input data size: %d bytes, compressed file size: %d bytes\n", iPitch*h, iOutSize);
224+
printf("Compression ratio: %2.1f:1\n", (float)(iPitch*h) / (float)iOutSize);
225+
bbbm.u16Marker = BB_BITMAP_MARKER;
226+
bbbm.width = w;
227+
bbbm.height = h;
228+
bbbm.size = iOutSize;
229+
f = fopen(argv[2], "w+b");
230+
if (!f) {
231+
printf("Error opening: %s\n", argv[2]);
232+
} else {
233+
if (bHFile) { // generate HEX file to include in a project
234+
StartHexFile(f, iOutSize+sizeof(BB_BITMAP), w, h, argv[2]);
235+
AddHexBytes(f, &bbbm, sizeof(BB_BITMAP), 0);
236+
AddHexBytes(f, pOut, iOutSize, 1);
237+
printf(".H file created successfully!\n");
238+
} else { // generate a binary file
239+
fwrite(&bbbm, 1, sizeof(BB_BITMAP), f);
240+
fwrite(pOut, 1, iOutSize, f);
241+
printf("Binary file created successfully!\n");
242+
}
243+
fflush(f);
244+
fclose(f);
245+
}
246+
} else {
247+
printf("Error encoding image: %d\n", rc);
248+
}
249+
free(pBMP);
250+
return 0;
251+
} /* main() */

src/OneBitDisplay.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,16 +207,15 @@ void ONE_BIT_DISPLAY::setCursor(int x, int y)
207207
_obd.iCursorY = y;
208208
} /* setCursor() */
209209

210+
int ONE_BIT_DISPLAY::loadG5Image(const uint8_t *pG5, int x, int y, int iFG, int iBG, float fScale)
211+
{
212+
return obdLoadG5(&_obd, pG5, x, y, iFG, iBG, fScale);
213+
}
210214
int ONE_BIT_DISPLAY::loadBMP(const uint8_t *pBMP, int x, int y, int iFG, int iBG)
211215
{
212216
return obdLoadBMP(&_obd, pBMP, x, y, iFG, iBG);
213217
} /* loadBMP() */
214218

215-
int ONE_BIT_DISPLAY::loadBMP3(const uint8_t *pBMP, int x, int y)
216-
{
217-
return obdLoadBMP3(&_obd, pBMP, x, y);
218-
} /* loadBMP3() */
219-
220219
void ONE_BIT_DISPLAY::setFont(int iFont)
221220
{
222221
_obd.iFont = iFont;

src/OneBitDisplay.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ class ONE_BIT_DISPLAY
207207
void setCursor(int x, int y);
208208
void setPower(bool bOn);
209209
int loadBMP(const uint8_t *pBMP, int x, int y, int iFG, int iBG);
210-
int loadBMP3(const uint8_t *pBMP, int x, int y);
210+
int loadG5Image(const uint8_t *pG5, int x, int y, int iFG = OBD_BLACK, int iBG = OBD_WHITE, float fScale = 1.0f);
211211
int16_t getCursorX(void);
212212
int16_t getCursorY(void);
213213
void wake(void);

0 commit comments

Comments
 (0)