-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(stdlib) : graphics, read BMPs from SD cards
- Loading branch information
1 parent
34c7929
commit 6206acf
Showing
5 changed files
with
365 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
|
||
#ifndef BITMAP_READER_H | ||
#define BITMAP_READER_H | ||
|
||
#include <SD.h> | ||
#include "XGraphics.h" | ||
|
||
enum BitmapReturnCode { // It is invisible to the enduser but very useful for debugging. | ||
BITMAP_SUCCESS, | ||
BITMAP_ERROR_FILE_OPEN, | ||
BITMAP_ERROR_WRONG_FILE_FORMAT, | ||
BITMAP_ERROR_WRONG_BMP | ||
}; | ||
|
||
struct Bitmap { | ||
Bitmap() | ||
: bfOffBits(0) | ||
, biSize(0) | ||
, biWidth(0) | ||
, biHeight(0) | ||
, isFlipped(false) | ||
, biPlanes(0) | ||
, biBitCount(0) | ||
, biCompression(0) {} | ||
|
||
uint32_t bfOffBits; // The offset, i.e. starting address, of the byte where the bitmap image data (pixel array) can be found. | ||
uint8_t biSize; // The size of the header. | ||
int16_t biWidth; // The bitmap width in pixels. | ||
int16_t biHeight; // The bitmap height in pixels. Can be < 0. | ||
bool isFlipped; // A BMP is stored bottom-to-top. | ||
uint8_t biPlanes; // The number of color planes, must be 1. Other values used for WIN icons. | ||
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. | ||
uint8_t biCompression; // the compression method being used. Typical values are [0,6]. | ||
}; | ||
|
||
class BitmapReader { | ||
public: | ||
~BitmapReader(); | ||
|
||
void linkSDDevice(SDClass* sd); | ||
|
||
BitmapReturnCode readBitmap(char* bitmapFSPath); | ||
BitmapReturnCode fillScanlineBuffer(char* bitmapFSPath, int16_t scanline, BBox imageBBox, uint16_t* buffer, size_t bufferSize); | ||
|
||
private: | ||
SDClass* _sd; | ||
File _file; | ||
|
||
Bitmap _bitmap; | ||
|
||
uint16_t _readLE16(); | ||
uint32_t _readLE32(); | ||
}; | ||
|
||
BitmapReader::~BitmapReader() { | ||
if (_file) | ||
_file.close(); | ||
} | ||
|
||
void BitmapReader::linkSDDevice(SDClass* sd) { | ||
_sd = sd; | ||
} | ||
|
||
BitmapReturnCode BitmapReader::readBitmap(char* bitmapFSPath) { | ||
|
||
if (!(_file = _sd->open(bitmapFSPath, O_READ))) { // Open a new BMP file. | ||
_file.close(); | ||
return BITMAP_ERROR_FILE_OPEN; | ||
} | ||
|
||
if (_readLE16() != 0x4D42) { // Check BMP signature. | ||
_file.close(); | ||
return BITMAP_ERROR_WRONG_FILE_FORMAT; | ||
} | ||
|
||
_readLE32(); // Skip reading bfSize. | ||
_readLE32(); // Skip reading bfReserved. | ||
|
||
_bitmap.bfOffBits = _readLE32(); // Read bfOffBits. | ||
_bitmap.biSize = _readLE32(); // Read biSize/bV4Size/bV5Size. | ||
|
||
_bitmap.biWidth = _readLE32(); // Read biWidth/bV4Width/bV5Width. | ||
_bitmap.biHeight = _readLE32(); // Read biHeight/bV4Height/bV5Height. | ||
if (_bitmap.biHeight < 0) { | ||
_bitmap.biHeight = -_bitmap.biHeight; | ||
_bitmap.isFlipped = true; | ||
} | ||
|
||
_bitmap.biPlanes = _readLE16(); // Read biPlanes/bV4Planes/bV5Planes. | ||
_bitmap.biBitCount = _readLE16(); // Read biBitCount/bV4BitCount/bV5BitCount. | ||
_bitmap.biCompression = _readLE32(); // Read biCompression/bV4V4Compression/bV5Compression. | ||
|
||
_file.close(); | ||
|
||
// Check for only straightforward case. | ||
if (_bitmap.biSize != 40 || _bitmap.biPlanes != 1 || _bitmap.biBitCount != 24 || _bitmap.biCompression != 0) { | ||
_bitmap = Bitmap(); // Reset bitmap. | ||
return BITMAP_ERROR_WRONG_BMP; | ||
} | ||
|
||
return BITMAP_SUCCESS; | ||
} | ||
|
||
BitmapReturnCode BitmapReader::fillScanlineBuffer(char* bitmapFSPath, int16_t scanline, BBox imageBBox, uint16_t* buffer, size_t bufferSize) { | ||
if (!(_file = _sd->open(bitmapFSPath, O_READ))) { | ||
_file.close(); | ||
return BITMAP_ERROR_FILE_OPEN; | ||
} | ||
|
||
uint16_t imageLine = scanline - imageBBox.pivot.y; // Calculate current line of the image. | ||
|
||
if (imageLine > _bitmap.biHeight - 1) // Tile vert. If more than one BMP in the image. | ||
imageLine = imageLine - _bitmap.biHeight * (imageLine / _bitmap.biHeight); | ||
|
||
uint16_t bitmapArrayLine = _bitmap.isFlipped ? imageLine : _bitmap.biHeight - imageLine - 1; // Calculate current Bitmap line. | ||
|
||
uint8_t emptyBytesCount = _bitmap.biWidth % 4; // The amount of bytes at the BMP scanline must be a multiple of 4 bytes. | ||
uint32_t bitmapLineStart = _bitmap.bfOffBits + bitmapArrayLine * 3 * _bitmap.biWidth + emptyBytesCount * bitmapArrayLine; // The number of starting byte of the line. | ||
|
||
if (_file.seek(bitmapLineStart)) { | ||
for (int16_t x = imageBBox.pivot.x, c = 0; x < imageBBox.pivot.x + imageBBox.width; x++, c++) { | ||
|
||
if (c == _bitmap.biWidth - 1) // Tile horizontally. | ||
_file.seek(bitmapLineStart); | ||
|
||
uint16_t color = ((_file.read() >> 3) | ((_file.read() & 0xFC) << 3) | ((_file.read() & 0xF8) << 8)); // in BMP a pixel color is hold as BGR888. | ||
if (x >= 0 && x < bufferSize) | ||
buffer[x] = color; | ||
} | ||
} | ||
|
||
_file.close(); | ||
return BITMAP_SUCCESS; | ||
} | ||
|
||
uint16_t BitmapReader::_readLE16() { | ||
#if !defined(ESP32) && !defined(ESP8266) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) | ||
// Read directly into result. BMP data and variable both little-endian. | ||
uint16_t result; | ||
_file.read(&result, sizeof result); | ||
return result; | ||
#else | ||
// Big-endian or unknown. Byte-by-byte read will perform reversal if needed. | ||
return _file.read() | ((uint16_t)_file.read() << 8); | ||
#endif | ||
} | ||
|
||
uint32_t BitmapReader::_readLE32() { | ||
#if !defined(ESP32) && !defined(ESP8266) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) | ||
// Read directly into result. BMP data and variable both little-endian. | ||
uint32_t result; | ||
_file.read(&result, sizeof result); | ||
return result; | ||
#else | ||
// Big-endian or unknown. Byte-by-byte read will perform reversal if needed. | ||
return _file.read() | ((uint16_t)_file.read() << 8) | ((uint16_t)_file.read() << 16) | ((uint16_t)_file.read() << 24); | ||
#endif | ||
} | ||
|
||
#endif // BITMAP_READER_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
|
||
#ifndef IMAGE_SD_H | ||
#define IMAGE_SD_H | ||
|
||
#include "BitmapReader.h" | ||
#include "XGraphics.h" | ||
|
||
class ImageSD : public XGraphics { | ||
private: | ||
BitmapReader _bitmapReader; | ||
BBox _imageBBox; | ||
|
||
char* _bitmapFSPath; | ||
|
||
public: | ||
ImageSD(XGraphics* parent, SDClass* sd); | ||
|
||
bool linkBitmapFSPath(char* bitmapFSPath); | ||
|
||
void setImagePosition(int16_t x, int16_t y, int16_t w, int16_t h); | ||
void renderScanline(XRenderer* renderer, int16_t scanline, uint16_t* buffer, size_t bufferSize); | ||
}; | ||
|
||
#include "ImageSD.inl" | ||
|
||
#endif // IMAGE_SD_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
|
||
ImageSD::ImageSD(XGraphics* parent, SDClass* sd) | ||
: XGraphics(parent) { | ||
_bitmapReader.linkSDDevice(sd); | ||
} | ||
|
||
bool ImageSD::linkBitmapFSPath(char* bitmapFSPath) { | ||
_bitmapFSPath = bitmapFSPath; | ||
|
||
BitmapReturnCode res = _bitmapReader.readBitmap(_bitmapFSPath); | ||
|
||
return res == BITMAP_SUCCESS ? 0 : 1; | ||
} | ||
|
||
void ImageSD::setImagePosition(int16_t x, int16_t y, int16_t w, int16_t h) { | ||
_imageBBox.pivot = XVector2<int16_t>(x, y); | ||
_imageBBox.width = w; | ||
_imageBBox.height = h; | ||
} | ||
|
||
void ImageSD::renderScanline(XRenderer* renderer, int16_t scanline, uint16_t* buffer, size_t bufferSize) { | ||
|
||
if (scanline < _imageBBox.pivot.y || scanline > _imageBBox.pivot.y + _imageBBox.height - 1) | ||
return; | ||
|
||
_bitmapReader.fillScanlineBuffer(_bitmapFSPath, scanline, _imageBBox, buffer, bufferSize); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
|
||
// clang-format off | ||
{{#global}} | ||
#include <ImageSD.h> | ||
{{/global}} | ||
// clang-format on | ||
|
||
struct State { | ||
uint8_t mem[sizeof(ImageSD)]; | ||
ImageSD* imageSD; | ||
int16_t x, y, w, h; | ||
|
||
char bitmapFSPath[24]; // A 24 chars maximum filepath. | ||
}; | ||
|
||
// clang-format off | ||
{{ GENERATED_CODE }} | ||
// clang-format on | ||
|
||
void evaluate(Context ctx) { | ||
auto state = getState(ctx); | ||
|
||
auto gfx = getValue<input_GFX>(ctx); | ||
auto sd = getValue<input_SD>(ctx); | ||
|
||
int16_t x = (int16_t)getValue<input_X>(ctx); | ||
int16_t y = (int16_t)getValue<input_Y>(ctx); | ||
int16_t w = (int16_t)getValue<input_W>(ctx); | ||
int16_t h = (int16_t)getValue<input_H>(ctx); | ||
|
||
auto path = getValue<input_FILE>(ctx); | ||
|
||
if (isSettingUp()) { | ||
state->imageSD = new (state->mem) ImageSD(gfx, sd); | ||
} | ||
|
||
if (isInputDirty<input_GFX>(ctx)) { | ||
emitValue<output_GFXU0027>(ctx, state->imageSD); // If upstream is ok pass it. | ||
} | ||
|
||
if (isSettingUp() || x != state->x || y != state->y || w != state->w || h != state->h || isInputDirty<input_FILE>(ctx)) { | ||
state->x = x; | ||
state->y = y; | ||
state->w = w; | ||
state->h = h; | ||
state->imageSD->setImagePosition(x, y, w, h); | ||
|
||
memset(state->bitmapFSPath, '\0', 24); | ||
dump(path, state->bitmapFSPath); | ||
|
||
if (state->imageSD->linkBitmapFSPath(state->bitmapFSPath)) { | ||
raiseError<output_GFXU0027>(ctx); // Failed to load BMP file or a file has wrong format/version. | ||
return; | ||
} | ||
|
||
emitValue<output_GFXU0027>(ctx, state->imageSD); // Pass only is everthing is ok. | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
{ | ||
"nodes": [ | ||
{ | ||
"id": "B1Egvulc8", | ||
"label": "W", | ||
"position": { | ||
"units": "slots", | ||
"x": 5, | ||
"y": 0 | ||
}, | ||
"type": "xod/patch-nodes/input-number" | ||
}, | ||
{ | ||
"id": "B1Z2yB_lq8", | ||
"label": "GFX", | ||
"position": { | ||
"units": "slots", | ||
"x": 0, | ||
"y": 0 | ||
}, | ||
"type": "@/input-graphics" | ||
}, | ||
{ | ||
"id": "BkG3ySOe5I", | ||
"position": { | ||
"units": "slots", | ||
"x": 0, | ||
"y": 1 | ||
}, | ||
"type": "xod/patch-nodes/not-implemented-in-xod" | ||
}, | ||
{ | ||
"id": "H12Jr_lcI", | ||
"label": "X", | ||
"position": { | ||
"units": "slots", | ||
"x": 3, | ||
"y": 0 | ||
}, | ||
"type": "xod/patch-nodes/input-number" | ||
}, | ||
{ | ||
"id": "HySh1rdx9L", | ||
"label": "GFX'", | ||
"position": { | ||
"units": "slots", | ||
"x": 0, | ||
"y": 2 | ||
}, | ||
"type": "@/output-graphics" | ||
}, | ||
{ | ||
"id": "SJJSw_gcL", | ||
"label": "FILE", | ||
"position": { | ||
"units": "slots", | ||
"x": 2, | ||
"y": 0 | ||
}, | ||
"type": "xod/patch-nodes/input-string" | ||
}, | ||
{ | ||
"id": "r1gh1S_gcL", | ||
"label": "Y", | ||
"position": { | ||
"units": "slots", | ||
"x": 4, | ||
"y": 0 | ||
}, | ||
"type": "xod/patch-nodes/input-number" | ||
}, | ||
{ | ||
"id": "r1lmDOlqI", | ||
"label": "SD", | ||
"position": { | ||
"units": "slots", | ||
"x": 1, | ||
"y": 0 | ||
}, | ||
"type": "xod-dev/sd/input-sd-device" | ||
}, | ||
{ | ||
"id": "rJeNgwueqU", | ||
"label": "H", | ||
"position": { | ||
"units": "slots", | ||
"x": 6, | ||
"y": 0 | ||
}, | ||
"type": "xod/patch-nodes/input-number" | ||
} | ||
] | ||
} |