Skip to content

Commit

Permalink
feat(stdlib) : graphics, read BMPs from SD cards
Browse files Browse the repository at this point in the history
  • Loading branch information
gabbapeople committed May 11, 2020
1 parent 34c7929 commit 6206acf
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 0 deletions.
160 changes: 160 additions & 0 deletions workspace/__ardulib__/Graphics/BitmapReader.h
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
26 changes: 26 additions & 0 deletions workspace/__ardulib__/Graphics/ImageSD.h
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
27 changes: 27 additions & 0 deletions workspace/__ardulib__/Graphics/ImageSD.inl
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);
}
59 changes: 59 additions & 0 deletions workspace/__lib__/xod/graphics/sd-image/patch.cpp
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.
}

}
93 changes: 93 additions & 0 deletions workspace/__lib__/xod/graphics/sd-image/patch.xodp
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"
}
]
}

0 comments on commit 6206acf

Please sign in to comment.