Skip to content

Commit 08a83c8

Browse files
Add rfm-poi project (wip)
1 parent b3fd880 commit 08a83c8

File tree

2 files changed

+504
-0
lines changed

2 files changed

+504
-0
lines changed

rfm-poi/bmp.cpp

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// BMP-reading code. Much of this is adapted from the Adafruit_ImageReader
2+
// library, which is extremely LCD-display-centric and turned out to be
3+
// neither workable nor easily extensible into what was needed here. That's
4+
// okay. For now, I just pulled in minimal parts of that code, with changes
5+
// as needed for this project (and dropping some portability considerations).
6+
7+
#include <SdFat.h>
8+
9+
// Barebones query pixel height of BMP image file. NOT universally portable;
10+
// does some rude straight-to-var little-endian reads.
11+
// Param: Pointer to FAT volume.
12+
// Param: Absolute filename.
13+
// Param: Pointer to int32_t for height result.
14+
// Returns: true on success, false on ANY error, does not distinguish
15+
// (file not found, no BMP signature, etc.).
16+
bool bmpHeight(FatVolume *fs, char *filename, int32_t *height) {
17+
File32 file;
18+
bool status = false; // Assume error until success
19+
if ((file = fs->open(filename, FILE_READ))) {
20+
uint16_t sig;
21+
file.read(&sig, sizeof sig); // Little-endian straight to var
22+
if (sig == 0x4D42) { // BMP signature?
23+
file.seekCur(20); // Skip file size, width, etc.
24+
file.read(height, sizeof(int32_t)); // Little-endian straight to var
25+
if (*height < 0) *height *= -1; // Handle top-to-bottom variant
26+
status = true; // YAY
27+
}
28+
file.close();
29+
}
30+
return status;
31+
}
32+
33+
// Barebones BMP read into RAM. ALL images regardless of BMP format are
34+
// converted as needed into a DotStar-ready 24-bit-per-pixel format. Again
35+
// this is NOT universally portable; rude little-endian reads.
36+
// Param: Pointer to FAT volume.
37+
// Param: Absolute filename.
38+
// Param: Pointer to uint8_t for storing result (destination buffer
39+
// is assumed allocated and cleared, not performed here).
40+
// Param: Max width to clip or pad to (DotStar strip length).
41+
// Param: Red, green, blue byte offsets (0-2) in dest buffer.
42+
// Param: Brightness (0.0 to 255.0).
43+
// Param: Gamma (1.0 = linear, 2.6 = typical LED curve). Gamma is a lossy
44+
// operation; might prefer to pass 1.0 and do adjustment on source
45+
// image instead w/dithering, or perhaps future poi code could do
46+
// this on-the-fly. But for now, on load.
47+
// Returns: true on success, false on ANY error, does not distinguish
48+
// (file not found, no BMP signature, etc.).
49+
bool loadBMP(FatVolume *fs, char *filename, uint8_t *dest,
50+
const uint16_t dest_width, const uint8_t rOffset,
51+
const uint8_t gOffset, const uint8_t bOffset,
52+
const float brightness, const float gamma) {
53+
File32 file;
54+
bool status = false; // Assume error until success
55+
if ((file = fs->open(filename, FILE_READ))) {
56+
uint16_t sig;
57+
file.read(&sig, sizeof sig); // Little-endian straight to var
58+
if (sig == 0x4D42) { // BMP signature?
59+
uint32_t offset; // Start of image data
60+
uint32_t header_size; // Indicates BMP version
61+
int32_t bmp_width, bmp_height; // BMP width & height in pixels
62+
boolean flip = true; // BMP is stored bottom-to-top
63+
uint32_t compression = 0; // BMP compression mode
64+
uint32_t colors = 0; // Number of colors in palette
65+
uint16_t planes;
66+
uint16_t depth;
67+
68+
file.seekCur(8); // Skip file size, creator bytes
69+
file.read(&offset , sizeof offset);
70+
file.read(&header_size, sizeof header_size); // DIB header...
71+
file.read(&bmp_width , sizeof bmp_width);
72+
file.read(&bmp_height , sizeof bmp_height);
73+
// If bmpHeight is negative, image is in top-down order.
74+
// This is not canon but has been observed in the wild.
75+
if (bmp_height < 0) {
76+
bmp_height *= -1;
77+
flip = false;
78+
}
79+
file.read(&planes, sizeof planes);
80+
file.read(&depth , sizeof depth);
81+
// Compression mode is present in later BMP versions (default = none)
82+
if (header_size > 12) {
83+
file.read(&compression, sizeof compression);
84+
file.seekCur(12); // Skip raw bitmap data size, etc.
85+
file.read(&colors, sizeof colors); // # of colors in palette; 0 = 2^depth
86+
file.seekCur(4); // Skip # of colors used
87+
// File position should now be at start of palette (if present)
88+
}
89+
90+
if ((planes == 1) && (compression == 0)) { // Only uncompressed is handled
91+
uint8_t palette[3][256]; // Rude but code's easier than malloc check
92+
uint8_t b; // Byte-holding var used in 1-8 bit modes
93+
94+
if (depth < 16) { // Lower depths include a color palette
95+
if (!colors) colors = 1 << depth;
96+
for (uint16_t i = 0; i < colors; i++) {
97+
uint32_t rgb;
98+
file.read(&rgb, sizeof rgb);
99+
palette[rOffset][i] = (uint8_t)(pow((float)((rgb >> 16) & 0xFF) / 255.0, gamma) * brightness + 0.5);
100+
palette[gOffset][i] = (uint8_t)(pow((float)((rgb >> 8) & 0xFF) / 255.0, gamma) * brightness + 0.5);
101+
palette[bOffset][i] = (uint8_t)(pow((float)( rgb & 0xFF) / 255.0, gamma) * brightness + 0.5);
102+
}
103+
} else {
104+
// But HEY, as long as we have that palette array taking up space
105+
// on the heap...use it to pre-compute a brightness/gamma table,
106+
// saves a TON of floating-point math on every pixel later.
107+
for (uint16_t i = 0; i < 256; i++) {
108+
palette[0][i] = (uint8_t)(pow((float)i / 255.0, gamma) * brightness + 0.5);
109+
}
110+
}
111+
112+
// BMP rows are padded (if needed) to 4-byte boundary,
113+
// width loaded is cropped if needed to DotStar strand length.
114+
uint32_t row_size = ((depth * bmp_width + 31) / 32) * 4;
115+
int load_width = min(bmp_width, dest_width);
116+
117+
for (int row = 0; row < bmp_height; row++) { // For each scanline...
118+
119+
file.seekSet(offset + row_size * (flip ? bmp_height - 1 - row : row));
120+
uint8_t *d2 = dest + row * dest_width * 3;
121+
122+
switch (depth) {
123+
case 32:
124+
for (int col = 0; col < load_width; col++, d2 += 3) {
125+
uint32_t rgba;
126+
file.read(&rgba, sizeof rgba);
127+
d2[rOffset] = palette[0][(rgba >> 16) & 0xFF]; // palette[0] is
128+
d2[gOffset] = palette[0][(rgba >> 8) & 0xFF]; // brightness/gamma
129+
d2[bOffset] = palette[0][ rgba & 0xFF]; // adjustment table
130+
}
131+
break;
132+
case 24:
133+
for (int col = 0; col < load_width; col++, d2 += 3) {
134+
d2[bOffset] = palette[0][file.read()]; // palette[0] is
135+
d2[gOffset] = palette[0][file.read()]; // brightness/gamma
136+
d2[rOffset] = palette[0][file.read()]; // adjustment table
137+
}
138+
break;
139+
case 16:
140+
// Not currently supported but might be nice to have.
141+
// Will require dissecting DIB header bitfields.
142+
break;
143+
case 8:
144+
for (int col = 0; col < load_width; col++, d2 += 3) {
145+
b = file.read();
146+
d2[0] = palette[0][b];
147+
d2[1] = palette[1][b];
148+
d2[2] = palette[2][b];
149+
}
150+
break;
151+
case 4:
152+
for (int col = 0; col < load_width; col++, d2 += 3) {
153+
uint8_t n; // 4-bit pixel value
154+
if (!(col & 1)) {
155+
b = file.read();
156+
n = b >> 4;
157+
} else {
158+
n = b & 0xF;
159+
}
160+
d2[0] = palette[0][n];
161+
d2[1] = palette[1][n];
162+
d2[2] = palette[2][n];
163+
}
164+
break;
165+
// A 2-bit BMP mode exists but is SUPER ESOTERIC (apparently
166+
// a Windows CE thing), not supported here (nor in Photoshop).
167+
case 2:
168+
break;
169+
case 1:
170+
for (int col = 0; col < load_width; col++, d2 += 3) {
171+
if (!(col & 7)) b = file.read();
172+
uint8_t n = (b >> (7 - (col & 7))) & 1;
173+
d2[0] = palette[0][n];
174+
d2[1] = palette[1][n];
175+
d2[2] = palette[2][n];
176+
}
177+
break;
178+
} // end switch
179+
180+
} // end row
181+
status = true; // YAY
182+
} // end planes/compression check
183+
} // end signature
184+
file.close();
185+
} // end file open
186+
return status;
187+
}

0 commit comments

Comments
 (0)