#ifndef DISPLAY_H #define DISPLAY_H #include "Color.h" #include "Adafruit_NeoPixel.h" #include "Vector.h" #define SYMBOL_COUNT 15 #define DISPLAY_CHAR_WIDTH 3 #define DISPLAY_CHAR_HEIGHT 5 extern bool SYMBOLS[SYMBOL_COUNT][DISPLAY_CHAR_WIDTH * DISPLAY_CHAR_HEIGHT]; class Display { public: const uint8_t width; const uint8_t height; const size_t pixelCount; const size_t pixelByteCount; bool fpsShow = false; private: Adafruit_NeoPixel leds; unsigned long fpsLastMillis = 0; int fps = 0; Color *buffer = nullptr; uint8_t brightness = 10; public: Display(uint8_t width, uint8_t height) : width(width), height(height), pixelCount(width * height), pixelByteCount(pixelCount * sizeof(Color)), leds(pixelCount, GPIO_NUM_13) { buffer = (Color *) malloc(pixelByteCount); if (buffer == nullptr) { Serial.print("+-----------------------------------------------+\n"); Serial.print("| OUT OF MEMORY: Cannot allocate double-buffer! |\n"); Serial.print("+-----------------------------------------------+\n"); } } ~Display() { if (buffer == nullptr) { return; } free(buffer); buffer = nullptr; } void setup() { leds.begin(); clear(); flush(); } void loop() { calculateFPS(); drawFpsBorder(); if (isDirty()) { flush(); } } void setBrightness(uint8_t value) { brightness = value; } uint8_t getBrightness() const { return brightness; } void clear() { if (buffer == nullptr) { return; } memset(buffer, 0, pixelByteCount); } uint8_t print(uint8_t xPos, uint8_t yPos, uint8_t index, Color color) { if (index >= SYMBOL_COUNT) { Serial.printf("Cannot print symbol #%u.\n", index); index = SYMBOL_COUNT - 1; } bool *symbolBit = SYMBOLS[index]; for (uint8_t y = 0; y < DISPLAY_CHAR_HEIGHT; ++y) { for (uint8_t x = 0; x < DISPLAY_CHAR_WIDTH; ++x) { if (*(symbolBit++)) { set(xPos + x, yPos + y, color); } else { set(xPos + x, yPos + y, BLACK); } } } return DISPLAY_CHAR_WIDTH; } void set(Vector *pos, Color color) { set((uint8_t) round(pos->x), (uint8_t) round(pos->y), color); } void set(uint8_t x, uint8_t y, Color color) { if (x >= width || y >= height) { return; } if ((y % 2) != 0) { x = width - x - 1; } set(y * width + x, color); } void set(uint16_t index, Color color) { if (buffer == nullptr) { return; } buffer[index] = { // yes, correct order is GRB !!! (uint8_t) (color.g * brightness >> 8), (uint8_t) (color.r * brightness >> 8), (uint8_t) (color.b * brightness >> 8) }; } private: void flush() { if (buffer == nullptr) { return; } memcpy(leds.getPixels(), buffer, pixelByteCount); leds.show(); } bool isDirty() const { if (buffer == nullptr) { return false; } return memcmp(leds.getPixels(), buffer, pixelByteCount) != 0; } void calculateFPS() { fps = (int) round(1000.0 / (millis() - fpsLastMillis)); fpsLastMillis = millis(); } void drawFpsBorder() { if (!fpsShow) { return; } int frames = fps; Color color = RED; if (frames > 3 * 76) { frames -= 3 * 76; color = WHITE; } else if (frames > 2 * 76) { frames -= 2 * 76; color = BLUE; } else if (frames > 76) { frames -= 76; color = GREEN; } for (int x = 0; x <= width - 1 && frames-- > 0; x++) { set(x, 0, color); } for (int y = 0; y <= height - 1 && frames-- > 0; y++) { set(width - 1, y, color); } for (int x = width - 1; x >= 0 && frames-- > 0; x--) { set(x, height - 1, color); } for (int y = height - 1; y >= 0 && frames-- > 0; y--) { set(0, y, color); } } }; #endif