Added digit dimming and support for .clk format (see https://github.c… (#2555)
* Added digit dimming and support for .clk format (see https://github.com/aly-fly/EleksTubeHAX) * Small fixes and improvements, dimming optional Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
This commit is contained in:
		| @@ -12,6 +12,7 @@ class TFTs : public TFT_eSPI { | ||||
| private: | ||||
|   uint8_t digits[NUM_DIGITS]; | ||||
|  | ||||
|  | ||||
|   // These read 16- and 32-bit types from the SD card file. | ||||
|   // BMP data is stored little-endian, Arduino is little-endian too. | ||||
|   // May need to reverse subscript order if porting elsewhere. | ||||
| @@ -41,41 +42,70 @@ private: | ||||
|  | ||||
|   //// BEGIN STOLEN CODE | ||||
|  | ||||
|   // Draw directly from file stored in RGB565 format | ||||
|   // Draw directly from file stored in RGB565 format. Fastest | ||||
|   bool drawBin(const char *filename) { | ||||
|     fs::File bmpFS; | ||||
|  | ||||
|  | ||||
|     // Open requested file on SD card | ||||
|     bmpFS = WLED_FS.open(filename, "r"); | ||||
|  | ||||
|     if (!bmpFS) | ||||
|     { | ||||
|       Serial.print(F("File not found: ")); | ||||
|       Serial.println(filename); | ||||
|     size_t sz = bmpFS.size(); | ||||
|     if (sz > 64800) { | ||||
|       bmpFS.close(); | ||||
|       return(false); | ||||
|     } | ||||
|  | ||||
|     size_t sz = bmpFS.size(); | ||||
|     if (sz <= 64800) | ||||
|     { | ||||
|       bool oldSwapBytes = getSwapBytes(); | ||||
|       setSwapBytes(true); | ||||
|     uint16_t r, g, b, dimming = 255; | ||||
|     int16_t w, h, y, row, col; | ||||
|  | ||||
|       int16_t h = sz / (135 * 2); | ||||
|     //draw img that is shorter than 240pix into the center | ||||
|     w = 135; | ||||
|     h = sz / (w * 2); | ||||
|     y = (height() - h) /2; | ||||
|      | ||||
|     uint8_t lineBuffer[w * 2]; | ||||
|  | ||||
|       //draw img that is shorter than 240pix into the center | ||||
|       int16_t y = (height() - h) /2; | ||||
|     if (!realtimeMode || realtimeOverride) strip.service(); | ||||
|  | ||||
|       bmpFS.read((uint8_t *) output_buffer,sz); | ||||
|     #ifdef ELEKSTUBE_DIMMING | ||||
|     dimming=bri; | ||||
|     #endif | ||||
|  | ||||
|       if (!realtimeMode || realtimeOverride) strip.service(); | ||||
|     // 0,0 coordinates are top left | ||||
|     for (row = 0; row < h; row++) { | ||||
|  | ||||
|       pushImage(0, y, 135, h, (uint16_t *)output_buffer); | ||||
|  | ||||
|       setSwapBytes(oldSwapBytes); | ||||
|       bmpFS.read(lineBuffer, sizeof(lineBuffer)); | ||||
|       uint8_t PixM, PixL; | ||||
|        | ||||
|       // Colors are already in 16-bit R5, G6, B5 format | ||||
|       for (col = 0; col < w; col++) | ||||
|       { | ||||
|         if (dimming == 255) { // not needed, copy directly | ||||
|           output_buffer[row][col] = (lineBuffer[col*2+1] << 8) | (lineBuffer[col*2]); | ||||
|         } else { | ||||
|           // 16 BPP pixel format: R5, G6, B5 ; bin: RRRR RGGG GGGB BBBB | ||||
|           PixM = lineBuffer[col*2+1]; | ||||
|           PixL = lineBuffer[col*2]; | ||||
|           // align to 8-bit value (MSB left aligned) | ||||
|           r = (PixM) & 0xF8; | ||||
|           g = ((PixM << 5) | (PixL >> 3)) & 0xFC; | ||||
|           b = (PixL << 3) & 0xF8; | ||||
|           r *= dimming; | ||||
|           g *= dimming; | ||||
|           b *= dimming; | ||||
|           r = r >> 8; | ||||
|           g = g >> 8; | ||||
|           b = b >> 8; | ||||
|           output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     bool oldSwapBytes = getSwapBytes(); | ||||
|     setSwapBytes(true); | ||||
|     pushImage(0, y, 135, h, (uint16_t *)output_buffer); | ||||
|     setSwapBytes(oldSwapBytes); | ||||
|  | ||||
|     bmpFS.close(); | ||||
|  | ||||
|     return(true); | ||||
| @@ -87,16 +117,9 @@ private: | ||||
|     // Open requested file on SD card | ||||
|     bmpFS = WLED_FS.open(filename, "r"); | ||||
|  | ||||
|     if (!bmpFS) | ||||
|     { | ||||
|       Serial.print(F("File not found: ")); | ||||
|       Serial.println(filename); | ||||
|       return(false); | ||||
|     } | ||||
|  | ||||
|     uint32_t seekOffset; | ||||
|     int16_t w, h, row; | ||||
|     uint8_t  r, g, b; | ||||
|     uint16_t r, g, b, dimming = 255; | ||||
|  | ||||
|     uint16_t magic = read16(bmpFS); | ||||
|     if (magic == 0xFFFF) { | ||||
| @@ -104,13 +127,6 @@ private: | ||||
|       bmpFS.close(); | ||||
|       return(false); | ||||
|     } | ||||
|      | ||||
|     if (magic != 0x4D42) { | ||||
|       Serial.print(F("File not a BMP. Magic: ")); | ||||
|       Serial.println(magic); | ||||
|       bmpFS.close(); | ||||
|       return(false); | ||||
|     } | ||||
|  | ||||
|     read32(bmpFS); | ||||
|     read32(bmpFS); | ||||
| @@ -128,8 +144,6 @@ private: | ||||
|     //draw img that is shorter than 240pix into the center | ||||
|     int16_t y = (height() - h) /2; | ||||
|  | ||||
|     bool oldSwapBytes = getSwapBytes(); | ||||
|     setSwapBytes(true); | ||||
|     bmpFS.seek(seekOffset); | ||||
|  | ||||
|     uint16_t padding = (4 - ((w * 3) & 3)) & 3; | ||||
| @@ -142,16 +156,29 @@ private: | ||||
|       bmpFS.read(lineBuffer, sizeof(lineBuffer)); | ||||
|       uint8_t*  bptr = lineBuffer; | ||||
|        | ||||
|       #ifdef ELEKSTUBE_DIMMING | ||||
|       dimming=bri; | ||||
|       #endif | ||||
|       // Convert 24 to 16 bit colours while copying to output buffer. | ||||
|       for (uint16_t col = 0; col < w; col++) | ||||
|       { | ||||
|         b = *bptr++; | ||||
|         g = *bptr++; | ||||
|         r = *bptr++; | ||||
|         if (dimming != 255) { // only dimm when needed | ||||
|           b *= dimming; | ||||
|           g *= dimming; | ||||
|           r *= dimming; | ||||
|           b = b >> 8; | ||||
|           g = g >> 8; | ||||
|           r = r >> 8; | ||||
|         } | ||||
|         output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     bool oldSwapBytes = getSwapBytes(); | ||||
|     setSwapBytes(true); | ||||
|     pushImage(0, y, w, h, (uint16_t *)output_buffer); | ||||
|     setSwapBytes(oldSwapBytes); | ||||
|  | ||||
| @@ -159,6 +186,83 @@ private: | ||||
|     return(true); | ||||
|   } | ||||
|  | ||||
|   bool drawClk(const char *filename) { | ||||
|     fs::File bmpFS; | ||||
|  | ||||
|     // Open requested file on SD card | ||||
|     bmpFS = WLED_FS.open(filename, "r"); | ||||
|  | ||||
|     if (!bmpFS) | ||||
|     { | ||||
|       Serial.print("File not found: "); | ||||
|       Serial.println(filename); | ||||
|       return(false); | ||||
|     } | ||||
|  | ||||
|     uint16_t  r, g, b, dimming = 255, magic; | ||||
|     int16_t w, h, row, col; | ||||
|      | ||||
|     magic = read16(bmpFS); | ||||
|     if (magic != 0x4B43) { // look for "CK" header | ||||
|       Serial.print(F("File not a CLK. Magic: ")); | ||||
|       Serial.println(magic); | ||||
|       bmpFS.close(); | ||||
|       return(false); | ||||
|     } | ||||
|  | ||||
|     w = read16(bmpFS); | ||||
|     h = read16(bmpFS); | ||||
|     int16_t x = (width() - w) / 2; | ||||
|     int16_t y = (height() - h) / 2; | ||||
|      | ||||
|     uint8_t lineBuffer[w * 2]; | ||||
|      | ||||
|     #ifdef ELEKSTUBE_DIMMING | ||||
|     dimming=bri; | ||||
|     #endif | ||||
|      | ||||
|     if (!realtimeMode || realtimeOverride) strip.service(); | ||||
|  | ||||
|     // 0,0 coordinates are top left | ||||
|     for (row = 0; row < h; row++) { | ||||
|  | ||||
|       bmpFS.read(lineBuffer, sizeof(lineBuffer)); | ||||
|       uint8_t PixM, PixL; | ||||
|        | ||||
|       // Colors are already in 16-bit R5, G6, B5 format | ||||
|       for (col = 0; col < w; col++) | ||||
|       { | ||||
|         if (dimming == 255) { // not needed, copy directly | ||||
|           output_buffer[row][col+x] = (lineBuffer[col*2+1] << 8) | (lineBuffer[col*2]); | ||||
|         } else { | ||||
|           // 16 BPP pixel format: R5, G6, B5 ; bin: RRRR RGGG GGGB BBBB | ||||
|           PixM = lineBuffer[col*2+1]; | ||||
|           PixL = lineBuffer[col*2]; | ||||
|           // align to 8-bit value (MSB left aligned) | ||||
|           r = (PixM) & 0xF8; | ||||
|           g = ((PixM << 5) | (PixL >> 3)) & 0xFC; | ||||
|           b = (PixL << 3) & 0xF8; | ||||
|           r *= dimming; | ||||
|           g *= dimming; | ||||
|           b *= dimming; | ||||
|           r = r >> 8; | ||||
|           g = g >> 8; | ||||
|           b = b >> 8; | ||||
|           output_buffer[row][col+x] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     bool oldSwapBytes = getSwapBytes(); | ||||
|     setSwapBytes(true); | ||||
|     pushImage(0, y, w, h, (uint16_t *)output_buffer); | ||||
|     setSwapBytes(oldSwapBytes); | ||||
|  | ||||
|     bmpFS.close(); | ||||
|     return(true); | ||||
|   } | ||||
|  | ||||
|  | ||||
| public: | ||||
|   TFTs() : TFT_eSPI(), chip_select() | ||||
|     { for (uint8_t digit=0; digit < NUM_DIGITS; digit++) digits[digit] = 0; } | ||||
| @@ -184,25 +288,30 @@ public: | ||||
|     chip_select.setDigit(digit); | ||||
|  | ||||
|     if (digits[digit] == blanked) { | ||||
|       fillScreen(TFT_BLACK); | ||||
|       fillScreen(TFT_BLACK); return; | ||||
|     } | ||||
|     else { | ||||
|       // Filenames are no bigger than "255.bmp\0" | ||||
|       char file_name[10]; | ||||
|       sprintf(file_name, "/%d.bmp", digits[digit]); | ||||
|       if (WLED_FS.exists(file_name)) { | ||||
|         drawBmp(file_name); | ||||
|       } else { | ||||
|         sprintf(file_name, "/%d.bin", digits[digit]); | ||||
|         drawBin(file_name); | ||||
|       } | ||||
|  | ||||
|     // Filenames are no bigger than "255.bmp\0" | ||||
|     char file_name[10]; | ||||
|     // Fastest, raw RGB565 | ||||
|     sprintf(file_name, "/%d.bin", digits[digit]); | ||||
|     if (WLED_FS.exists(file_name)) { | ||||
|       drawBin(file_name); return; | ||||
|     } | ||||
|   } | ||||
|     // Fast, see https://github.com/aly-fly/EleksTubeHAX on how to create this clk format | ||||
|     sprintf(file_name, "/%d.clk", digits[digit]); | ||||
|     if (WLED_FS.exists(file_name)) { | ||||
|       drawClk(file_name); return; | ||||
|     } | ||||
|     // Slow, regular RGB888 bmp | ||||
|     sprintf(file_name, "/%d.bmp", digits[digit]); | ||||
|     drawBmp(file_name); | ||||
|   }  | ||||
|  | ||||
|   void setDigit(uint8_t digit, uint8_t value, show_t show=yes) { | ||||
|     uint8_t old_value = digits[digit]; | ||||
|     digits[digit] = value; | ||||
|    | ||||
|     digits[digit] = value;  | ||||
|  | ||||
|     if (show != no && (old_value != value || show == force)) { | ||||
|       showDigit(digit); | ||||
|     } | ||||
|   | ||||
| @@ -21,6 +21,8 @@ class ElekstubeIPSUsermod : public Usermod { | ||||
|           set[i] = false; //display HHMMSS time | ||||
|         } | ||||
|       } | ||||
|  | ||||
|        | ||||
|       uint8_t hr = hour(localTime); | ||||
|       uint8_t hrTens = hr/10; | ||||
|       uint8_t mi = minute(localTime); | ||||
| @@ -37,6 +39,9 @@ class ElekstubeIPSUsermod : public Usermod { | ||||
|     unsigned long lastTime = 0; | ||||
|   public: | ||||
|  | ||||
|     uint8_t lastBri; | ||||
|     TFTs::show_t fshow=TFTs::yes; | ||||
|  | ||||
|     void setup() { | ||||
|       tfts.begin(); | ||||
|       tfts.fillScreen(TFT_BLACK); | ||||
| @@ -49,7 +54,14 @@ class ElekstubeIPSUsermod : public Usermod { | ||||
|     void loop() { | ||||
|       if (toki.isTick()) { | ||||
|         updateLocalTime(); | ||||
|         updateClockDisplay(); | ||||
|         #ifdef ELEKSTUBE_DIMMING | ||||
|         if (bri != lastBri) { | ||||
|             fshow=TFTs::force; | ||||
|             lastBri = bri; | ||||
|         }    | ||||
|         #endif | ||||
|         updateClockDisplay(fshow); | ||||
|         fshow=TFTs::yes; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 RedNax67
					RedNax67