From 6206acf55681c324f9659228edd19ff1169e95d9 Mon Sep 17 00:00:00 2001 From: gabbapeople Date: Mon, 11 May 2020 18:33:39 +0300 Subject: [PATCH] feat(stdlib) : graphics, read BMPs from SD cards --- workspace/__ardulib__/Graphics/BitmapReader.h | 160 ++++++++++++++++++ workspace/__ardulib__/Graphics/ImageSD.h | 26 +++ workspace/__ardulib__/Graphics/ImageSD.inl | 27 +++ .../__lib__/xod/graphics/sd-image/patch.cpp | 59 +++++++ .../__lib__/xod/graphics/sd-image/patch.xodp | 93 ++++++++++ 5 files changed, 365 insertions(+) create mode 100644 workspace/__ardulib__/Graphics/BitmapReader.h create mode 100644 workspace/__ardulib__/Graphics/ImageSD.h create mode 100644 workspace/__ardulib__/Graphics/ImageSD.inl create mode 100644 workspace/__lib__/xod/graphics/sd-image/patch.cpp create mode 100644 workspace/__lib__/xod/graphics/sd-image/patch.xodp diff --git a/workspace/__ardulib__/Graphics/BitmapReader.h b/workspace/__ardulib__/Graphics/BitmapReader.h new file mode 100644 index 000000000..a3200337d --- /dev/null +++ b/workspace/__ardulib__/Graphics/BitmapReader.h @@ -0,0 +1,160 @@ + +#ifndef BITMAP_READER_H +#define BITMAP_READER_H + +#include +#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 diff --git a/workspace/__ardulib__/Graphics/ImageSD.h b/workspace/__ardulib__/Graphics/ImageSD.h new file mode 100644 index 000000000..734bb8563 --- /dev/null +++ b/workspace/__ardulib__/Graphics/ImageSD.h @@ -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 diff --git a/workspace/__ardulib__/Graphics/ImageSD.inl b/workspace/__ardulib__/Graphics/ImageSD.inl new file mode 100644 index 000000000..e2a3ab36b --- /dev/null +++ b/workspace/__ardulib__/Graphics/ImageSD.inl @@ -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(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); +} diff --git a/workspace/__lib__/xod/graphics/sd-image/patch.cpp b/workspace/__lib__/xod/graphics/sd-image/patch.cpp new file mode 100644 index 000000000..829881493 --- /dev/null +++ b/workspace/__lib__/xod/graphics/sd-image/patch.cpp @@ -0,0 +1,59 @@ + +// clang-format off +{{#global}} +#include +{{/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(ctx); + auto sd = getValue(ctx); + + int16_t x = (int16_t)getValue(ctx); + int16_t y = (int16_t)getValue(ctx); + int16_t w = (int16_t)getValue(ctx); + int16_t h = (int16_t)getValue(ctx); + + auto path = getValue(ctx); + + if (isSettingUp()) { + state->imageSD = new (state->mem) ImageSD(gfx, sd); + } + + if (isInputDirty(ctx)) { + emitValue(ctx, state->imageSD); // If upstream is ok pass it. + } + + if (isSettingUp() || x != state->x || y != state->y || w != state->w || h != state->h || isInputDirty(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(ctx); // Failed to load BMP file or a file has wrong format/version. + return; + } + + emitValue(ctx, state->imageSD); // Pass only is everthing is ok. + } + +} diff --git a/workspace/__lib__/xod/graphics/sd-image/patch.xodp b/workspace/__lib__/xod/graphics/sd-image/patch.xodp new file mode 100644 index 000000000..b8aad3d81 --- /dev/null +++ b/workspace/__lib__/xod/graphics/sd-image/patch.xodp @@ -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" + } + ] +}