Skip to content

Commit da15a23

Browse files
committed
feat(stdlib) : graphics, read BMPs from SD cards
1 parent 34c7929 commit da15a23

File tree

5 files changed

+362
-0
lines changed

5 files changed

+362
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
2+
#ifndef BITMAP_READER_H
3+
#define BITMAP_READER_H
4+
5+
#include <SD.h>
6+
#include "XGraphics.h"
7+
8+
enum BitmapReturnCode { // It is invisible to the enduser but very useful for debugging.
9+
BITMAP_SUCCESS,
10+
BITMAP_ERROR_FILE_OPEN,
11+
BITMAP_ERROR_WRONG_FILE_FORMAT,
12+
BITMAP_ERROR_WRONG_BMP
13+
};
14+
15+
struct BitmapHeader {
16+
BitmapHeader()
17+
: bfOffBits(0)
18+
, biSize(0)
19+
, biWidth(0)
20+
, biHeight(0)
21+
, isFlipped(false)
22+
, biPlanes(0)
23+
, biBitCount(0)
24+
, biCompression(0) {}
25+
26+
uint32_t bfOffBits; // The offset, i.e. starting address, of the byte where the bitmap image data (pixel array) can be found.
27+
uint8_t biSize; // The size of the header.
28+
int16_t biWidth; // The bitmap width in pixels.
29+
int16_t biHeight; // The bitmap height in pixels. Can be < 0.
30+
bool isFlipped; // A BMP is stored bottom-to-top.
31+
uint8_t biPlanes; // The number of color planes, must be 1. Other values used for WIN icons.
32+
uint8_t biBitCount; // the number of bits per pixel, which is the color depth of the image. Typical values are 1, 4, 8, 16, 24 and 32.
33+
uint8_t biCompression; // the compression method being used. Typical values are [0,6].
34+
};
35+
36+
class BitmapReader {
37+
public:
38+
~BitmapReader();
39+
40+
void linkSDDevice(SDClass* sd);
41+
42+
BitmapReturnCode readBitmap(char* bitmapFSPath);
43+
BitmapReturnCode fillScanlineBuffer(char* bitmapFSPath, int16_t scanline, BBox imageBBox, uint16_t* buffer, size_t bufferSize);
44+
45+
private:
46+
SDClass* _sd;
47+
File _file;
48+
49+
BitmapHeader _bitmapHeader;
50+
51+
uint16_t _readLE16();
52+
uint32_t _readLE32();
53+
};
54+
55+
BitmapReader::~BitmapReader() {
56+
if (_file)
57+
_file.close();
58+
}
59+
60+
void BitmapReader::linkSDDevice(SDClass* sd) {
61+
_sd = sd;
62+
}
63+
64+
BitmapReturnCode BitmapReader::readBitmap(char* bitmapFSPath) {
65+
66+
if (!(_file = _sd->open(bitmapFSPath, O_READ))) { // Open a new BMP file.
67+
_file.close();
68+
return BITMAP_ERROR_FILE_OPEN;
69+
}
70+
71+
if (_readLE16() != 0x4D42) { // Check BMP signature.
72+
_file.close();
73+
return BITMAP_ERROR_WRONG_FILE_FORMAT;
74+
}
75+
76+
_readLE32(); // Skip reading bfSize.
77+
_readLE32(); // Skip reading bfReserved.
78+
79+
_bitmapHeader.bfOffBits = _readLE32(); // Read bfOffBits.
80+
_bitmapHeader.biSize = _readLE32(); // Read biSize/bV4Size/bV5Size.
81+
82+
_bitmapHeader.biWidth = _readLE32(); // Read biWidth/bV4Width/bV5Width.
83+
_bitmapHeader.biHeight = _readLE32(); // Read biHeight/bV4Height/bV5Height.
84+
if (_bitmapHeader.biHeight < 0) {
85+
_bitmapHeader.biHeight = -_bitmapHeader.biHeight;
86+
_bitmapHeader.isFlipped = true;
87+
}
88+
89+
_bitmapHeader.biPlanes = _readLE16(); // Read biPlanes/bV4Planes/bV5Planes.
90+
_bitmapHeader.biBitCount = _readLE16(); // Read biBitCount/bV4BitCount/bV5BitCount.
91+
_bitmapHeader.biCompression = _readLE32(); // Read biCompression/bV4V4Compression/bV5Compression.
92+
93+
_file.close();
94+
95+
// Check for only straightforward case.
96+
if (_bitmapHeader.biSize != 40 || _bitmapHeader.biPlanes != 1 || _bitmapHeader.biBitCount != 24 || _bitmapHeader.biCompression != 0) {
97+
_bitmapHeader = BitmapHeader(); // Reset bitmap.
98+
return BITMAP_ERROR_WRONG_BMP;
99+
}
100+
101+
return BITMAP_SUCCESS;
102+
}
103+
104+
BitmapReturnCode BitmapReader::fillScanlineBuffer(char* bitmapFSPath, int16_t scanline, BBox imageBBox, uint16_t* buffer, size_t bufferSize) {
105+
if (!(_file = _sd->open(bitmapFSPath, O_READ))) {
106+
_file.close();
107+
return BITMAP_ERROR_FILE_OPEN;
108+
}
109+
110+
uint16_t imageLine = scanline - imageBBox.pivot.y; // Calculate current line of the image.
111+
112+
if (imageLine > _bitmapHeader.biHeight - 1) // Tile vert. If more than one BMP in the image.
113+
imageLine = imageLine - _bitmapHeader.biHeight * (imageLine / _bitmapHeader.biHeight);
114+
115+
uint16_t bitmapArrayLine = _bitmapHeader.isFlipped ? imageLine : _bitmapHeader.biHeight - imageLine - 1; // Calculate current Bitmap line.
116+
117+
uint8_t emptyBytesCount = _bitmapHeader.biWidth % 4; // The amount of bytes at the BMP scanline must be a multiple of 4 bytes.
118+
uint32_t bitmapLineStart = _bitmapHeader.bfOffBits + bitmapArrayLine * 3 * _bitmapHeader.biWidth + emptyBytesCount * bitmapArrayLine; // The number of starting byte of the line.
119+
120+
for (int16_t x = imageBBox.pivot.x, c = 0; x < imageBBox.pivot.x + imageBBox.width; x++, c++) {
121+
122+
if ((c == 0) || (c % (_bitmapHeader.biWidth - 1) == 0)) // Tile horizontally. If we are at frame.
123+
if (!(_file.seek(bitmapLineStart))) // Return to start.
124+
continue; // If for some case miss the byte, do not read it.
125+
126+
uint16_t color = ((_file.read() >> 3) | ((_file.read() & 0xFC) << 3) | ((_file.read() & 0xF8) << 8)); // in BMP a pixel color is hold as BGR888.
127+
if (x >= 0 && x < bufferSize)
128+
buffer[x] = color;
129+
}
130+
131+
_file.close();
132+
return BITMAP_SUCCESS;
133+
}
134+
135+
uint16_t BitmapReader::_readLE16() {
136+
#if !defined(ESP32) && !defined(ESP8266) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
137+
// Read directly into result. BMP data and variable both little-endian.
138+
uint16_t result;
139+
_file.read(&result, sizeof result);
140+
return result;
141+
#else
142+
// Big-endian or unknown. Byte-by-byte read will perform reversal if needed.
143+
return _file.read() | ((uint16_t)_file.read() << 8);
144+
#endif
145+
}
146+
147+
uint32_t BitmapReader::_readLE32() {
148+
#if !defined(ESP32) && !defined(ESP8266) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
149+
// Read directly into result. BMP data and variable both little-endian.
150+
uint32_t result;
151+
_file.read(&result, sizeof result);
152+
return result;
153+
#else
154+
// Big-endian or unknown. Byte-by-byte read will perform reversal if needed.
155+
return _file.read() | ((uint16_t)_file.read() << 8) | ((uint16_t)_file.read() << 16) | ((uint16_t)_file.read() << 24);
156+
#endif
157+
}
158+
159+
#endif // BITMAP_READER_H
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
#ifndef IMAGE_SD_H
3+
#define IMAGE_SD_H
4+
5+
#include "BitmapReader.h"
6+
#include "XGraphics.h"
7+
8+
class ImageSD : public XGraphics {
9+
private:
10+
BitmapReader _bitmapReader;
11+
BBox _imageBBox;
12+
13+
char* _bitmapFSPath;
14+
15+
public:
16+
ImageSD(XGraphics* parent, SDClass* sd);
17+
18+
bool linkBitmapFSPath(char* bitmapFSPath);
19+
20+
void setImagePosition(int16_t x, int16_t y, int16_t w, int16_t h);
21+
void renderScanline(XRenderer* renderer, int16_t scanline, uint16_t* buffer, size_t bufferSize);
22+
};
23+
24+
#include "ImageSD.inl"
25+
26+
#endif // IMAGE_SD_H
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
ImageSD::ImageSD(XGraphics* parent, SDClass* sd)
3+
: XGraphics(parent) {
4+
_bitmapReader.linkSDDevice(sd);
5+
}
6+
7+
bool ImageSD::linkBitmapFSPath(char* bitmapFSPath) {
8+
_bitmapFSPath = bitmapFSPath;
9+
BitmapReturnCode res = _bitmapReader.readBitmap(_bitmapFSPath);
10+
return res == BITMAP_SUCCESS ? 0 : 1;
11+
}
12+
13+
void ImageSD::setImagePosition(int16_t x, int16_t y, int16_t w, int16_t h) {
14+
_imageBBox.pivot = XVector2<int16_t>(x, y);
15+
_imageBBox.width = w;
16+
_imageBBox.height = h;
17+
}
18+
19+
void ImageSD::renderScanline(XRenderer* renderer, int16_t scanline, uint16_t* buffer, size_t bufferSize) {
20+
21+
if (scanline < _imageBBox.pivot.y || scanline > _imageBBox.pivot.y + _imageBBox.height - 1)
22+
return;
23+
24+
_bitmapReader.fillScanlineBuffer(_bitmapFSPath, scanline, _imageBBox, buffer, bufferSize);
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
2+
// clang-format off
3+
{{#global}}
4+
#include <ImageSD.h>
5+
{{/global}}
6+
// clang-format on
7+
8+
struct State {
9+
uint8_t mem[sizeof(ImageSD)];
10+
ImageSD* imageSD;
11+
int16_t x, y, w, h;
12+
13+
char bitmapFSPath[24]; // A 24 chars maximum filepath.
14+
};
15+
16+
// clang-format off
17+
{{ GENERATED_CODE }}
18+
// clang-format on
19+
20+
void evaluate(Context ctx) {
21+
auto state = getState(ctx);
22+
23+
auto gfx = getValue<input_GFX>(ctx);
24+
auto sd = getValue<input_SD>(ctx);
25+
26+
int16_t x = (int16_t)getValue<input_X>(ctx);
27+
int16_t y = (int16_t)getValue<input_Y>(ctx);
28+
int16_t w = (int16_t)getValue<input_W>(ctx);
29+
int16_t h = (int16_t)getValue<input_H>(ctx);
30+
31+
auto path = getValue<input_FILE>(ctx);
32+
33+
if (isSettingUp()) {
34+
state->imageSD = new (state->mem) ImageSD(gfx, sd);
35+
}
36+
37+
if (isInputDirty<input_GFX>(ctx)) {
38+
emitValue<output_GFXU0027>(ctx, state->imageSD); // If upstream is ok pass it.
39+
}
40+
41+
if (isSettingUp() || x != state->x || y != state->y || w != state->w || h != state->h || isInputDirty<input_FILE>(ctx)) {
42+
state->x = x;
43+
state->y = y;
44+
state->w = w;
45+
state->h = h;
46+
state->imageSD->setImagePosition(x, y, w, h);
47+
48+
memset(state->bitmapFSPath, '\0', 24);
49+
dump(path, state->bitmapFSPath);
50+
51+
if (state->imageSD->linkBitmapFSPath(state->bitmapFSPath)) {
52+
raiseError<output_GFXU0027>(ctx); // Failed to load BMP file or a file has wrong format/version.
53+
return;
54+
}
55+
56+
emitValue<output_GFXU0027>(ctx, state->imageSD); // Pass only is everthing is ok.
57+
}
58+
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{
2+
"nodes": [
3+
{
4+
"id": "B1Egvulc8",
5+
"label": "W",
6+
"position": {
7+
"units": "slots",
8+
"x": 5,
9+
"y": 0
10+
},
11+
"type": "xod/patch-nodes/input-number"
12+
},
13+
{
14+
"id": "B1Z2yB_lq8",
15+
"label": "GFX",
16+
"position": {
17+
"units": "slots",
18+
"x": 0,
19+
"y": 0
20+
},
21+
"type": "@/input-graphics"
22+
},
23+
{
24+
"id": "BkG3ySOe5I",
25+
"position": {
26+
"units": "slots",
27+
"x": 0,
28+
"y": 1
29+
},
30+
"type": "xod/patch-nodes/not-implemented-in-xod"
31+
},
32+
{
33+
"id": "H12Jr_lcI",
34+
"label": "X",
35+
"position": {
36+
"units": "slots",
37+
"x": 3,
38+
"y": 0
39+
},
40+
"type": "xod/patch-nodes/input-number"
41+
},
42+
{
43+
"id": "HySh1rdx9L",
44+
"label": "GFX'",
45+
"position": {
46+
"units": "slots",
47+
"x": 0,
48+
"y": 2
49+
},
50+
"type": "@/output-graphics"
51+
},
52+
{
53+
"id": "SJJSw_gcL",
54+
"label": "FILE",
55+
"position": {
56+
"units": "slots",
57+
"x": 2,
58+
"y": 0
59+
},
60+
"type": "xod/patch-nodes/input-string"
61+
},
62+
{
63+
"id": "r1gh1S_gcL",
64+
"label": "Y",
65+
"position": {
66+
"units": "slots",
67+
"x": 4,
68+
"y": 0
69+
},
70+
"type": "xod/patch-nodes/input-number"
71+
},
72+
{
73+
"id": "r1lmDOlqI",
74+
"label": "SD",
75+
"position": {
76+
"units": "slots",
77+
"x": 1,
78+
"y": 0
79+
},
80+
"type": "xod-dev/sd/input-sd-device"
81+
},
82+
{
83+
"id": "rJeNgwueqU",
84+
"label": "H",
85+
"position": {
86+
"units": "slots",
87+
"x": 6,
88+
"y": 0
89+
},
90+
"type": "xod/patch-nodes/input-number"
91+
}
92+
]
93+
}

0 commit comments

Comments
 (0)