/* * Copyright (C) EdgeTX * * Based on code named * opentx - https://github.com/opentx/opentx * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "edgetx.h" #if defined(COLORLCD) // Scaling for RBG565 to RGB888 uint8_t rbScale[32] = { 0,8,16,24,32,41,49,57,65,74,82,90,98,106,115,123,131,139,148,156,164,172,180,189,197,205,213,222,230,238,246,255 }; uint8_t gScale[64] = { 0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,85,89,93,97,101,105,109,113,117,121,125, 129,133,137,141,145,149,153,157,161,165,170,174,178,182,186,190,194,198,202,206,210,214,218,222,226,230,234,238,242,246,250,255 }; #endif // Uncomment to enable screenshots to be saved as PNG files // Currently does not work on TX16S Mk3 for unknown reason // #define ENABLE_PNG_SCREENSHOT #if defined(ENABLE_PNG_SCREENSHOT) && defined(COLORLCD) && (defined(STM32H7) || defined(STM32H7RS)) #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb/stb_image_write.h" static void convert_RGB565_to_RGB888(uint8_t* dst, lv_img_dsc_t* src, coord_t w, coord_t h) { for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { lv_color_t pixel = lv_img_buf_get_px_color(src, x, y, {}); *(dst++) = rbScale[pixel.ch.red]; *(dst++) = gScale[pixel.ch.green]; *(dst++) = rbScale[pixel.ch.blue]; } } } static const char* writeResult = nullptr; static void writeScreenToFile(void* context, void* data, int size) { const char* file = (const char*)context; FIL fp; UINT written = 0; FRESULT result = f_open(&fp, file, FA_CREATE_ALWAYS | FA_WRITE); if (result == FR_OK) { result = f_write(&fp, data, size, &written); } f_close(&fp); if (result != FR_OK || (int)written != size) { writeResult = SDCARD_ERROR(result); } } static const char* _writeScreenshot() { char filename[42]; // /SCREENSHOTS/screen-2013-01-01-123540.bmp if (sdIsFull()) { return STR_SDCARD_FULL_EXT; } // check and create folder here char* s = strAppend(filename, SCREENSHOTS_PATH); const char * error = sdCheckAndCreateDirectory(filename); if (error) { return error; } lv_img_dsc_t* snapshot = lv_snapshot_take(lv_scr_act(), LV_IMG_CF_TRUE_COLOR); if (!snapshot) { return "LVGL error creating snapshot"; } s = strAppend(s, "/screen"); s = strAppendDate(s, true); strAppend(s, ".png"); // Open output file FRESULT result = f_open(&fp, file, FA_CREATE_ALWAYS | FA_WRITE); FILE *fout = fopen(filename, "wb"); if (fout == NULL) { perror("Error: fopen"); return EXIT_FAILURE; } // Initialize Tiny PNG Output struct TinyPngOut pngout; enum TinyPngOut_Status status = TinyPngOut_init(&pngout, (uint32_t)WIDTH, (uint32_t)HEIGHT, fout); if (status != TINYPNGOUT_OK) return printError(status); // Write image data status = TinyPngOut_write(&pngout, PIXELS, (size_t)(WIDTH * HEIGHT)); if (status != TINYPNGOUT_OK) return printError(status); // Close output file if (fclose(fout) != 0) { perror("Error: fclose"); return EXIT_FAILURE; } return EXIT_SUCCESS; writeResult = nullptr; // allocate enough for 3 channels auto pixels = LCD_W * LCD_H; auto stride = LCD_W * 3; uint8_t* img = (uint8_t*)malloc(pixels * 3); convert_RGB565_to_RGB888(img, snapshot, LCD_W, LCD_H); stbi_write_png_to_func(writeScreenToFile, filename, LCD_W, LCD_H, 3, img, stride); free(img); lv_snapshot_free(snapshot); return writeResult; } #else #if defined(COLORLCD) constexpr uint16_t BMP_HEADERSIZE = 0x36; constexpr uint8_t BMP_BITS_PER_PIXEL = 32; #else constexpr uint16_t BMP_HEADERSIZE = 0x76; constexpr uint8_t BMP_BITS_PER_PIXEL = 4; #endif constexpr uint32_t BMP_FILESIZE = uint32_t(BMP_HEADERSIZE + (LCD_W * LCD_H) * BMP_BITS_PER_PIXEL / 8); const uint8_t BMP_HEADER[] = { 'B', 'M', /* file size */ BMP_FILESIZE & 0xFF, (BMP_FILESIZE >> 8) & 0xFF, (BMP_FILESIZE >> 16) & 0xFF, (BMP_FILESIZE >> 24) & 0xFF, 0x00, 0x00, 0x00, 0x00, /* header size */ BMP_HEADERSIZE, 0x00, 0x00, 0x00, /* extra header size */ 0x28, 0x00, 0x00, 0x00, /* width */ LCD_W & 0xFF, LCD_W >> 8, 0x00, 0x00, /* height */ LCD_H & 0xFF, LCD_H >> 8, 0x00, 0x00, /* planes */ 0x01, 0x00, /* depth */ BMP_BITS_PER_PIXEL, 0x00, /* compression */ 0x00, 0x00, 0x00, 0x00, /* image size */ 0x00, 0x00, 0x00, 0x00, /* hor res 72dpi */ 0x13, 0x0B, 0x00, 0x00, /* ver res 72 dpi */ 0x13, 0x0B, 0x00, 0x00, /* num colors */ 0x00, 0x00, 0x00, 0x00, /* important colors */ 0x00, 0x00, 0x00, 0x00, #if !defined(COLORLCD) /* grayscale palette */ #if defined(OLED_SCREEN) 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x00, 0x22, 0x22, 0x22, 0x00, 0x33, 0x33, 0x33, 0x00, 0x44, 0x44, 0x44, 0x00, 0x55, 0x55, 0x55, 0x00, 0x66, 0x66, 0x66, 0x00, 0x77, 0x77, 0x77, 0x00, 0x88, 0x88, 0x88, 0x00, 0x99, 0x99, 0x99, 0x00, 0xaa, 0xaa, 0xaa, 0x00, 0xbb, 0xbb, 0xbb, 0x00, 0xcc, 0xcc, 0xcc, 0x00, 0xdd, 0xdd, 0xdd, 0x00, 0xee, 0xee, 0xee, 0x00, 0xff, 0xff, 0xff, 0x00, #else 0xff, 0xff, 0xff, 0x00, 0xee, 0xee, 0xee, 0x00, 0xdd, 0xdd, 0xdd, 0x00, 0xcc, 0xcc, 0xcc, 0x00, 0xbb, 0xbb, 0xbb, 0x00, 0xaa, 0xaa, 0xaa, 0x00, 0x99, 0x99, 0x99, 0x00, 0x88, 0x88, 0x88, 0x00, 0x77, 0x77, 0x77, 0x00, 0x66, 0x66, 0x66, 0x00, 0x55, 0x55, 0x55, 0x00, 0x44, 0x44, 0x44, 0x00, 0x33, 0x33, 0x33, 0x00, 0x22, 0x22, 0x22, 0x00, 0x11, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, #endif #endif }; static const char* _writeScreenshot() { FIL bmpFile; UINT written; char filename[42]; // /SCREENSHOTS/screen-2013-01-01-123540.bmp if (sdIsFull()) { return STR_SDCARD_FULL_EXT; } // check and create folder here strcpy(filename, SCREENSHOTS_PATH); const char * error = sdCheckAndCreateDirectory(filename); if (error) { return error; } #if defined(RTCLOCK) char * tmp = strAppend(&filename[sizeof(SCREENSHOTS_PATH)-1], "/screen"); tmp = strAppendDate(tmp, true); strcpy(tmp, BMP_EXT); #endif FRESULT result = f_open(&bmpFile, filename, FA_CREATE_ALWAYS | FA_WRITE); if (result != FR_OK) { return SDCARD_ERROR(result); } #if defined(COLORLCD) lv_img_dsc_t* snapshot = lv_snapshot_take(lv_scr_act(), LV_IMG_CF_TRUE_COLOR); if (!snapshot) { f_close(&bmpFile); return "LVGL error creating snapshot"; } auto w = snapshot->header.w; auto h = snapshot->header.h; constexpr int BMP_BUF_SIZE = FF_MAX_SS * 4; static uint8_t _bmp_buf[BMP_BUF_SIZE]; memcpy(_bmp_buf, BMP_HEADER, BMP_HEADERSIZE); UINT bpos = BMP_HEADERSIZE; for (int y = h - 1; y >= 0; y--) { for (uint32_t x = 0; x < w; x++) { lv_color_t pixel = lv_img_buf_get_px_color(snapshot, x, y, {}); _bmp_buf[bpos++] = rbScale[pixel.ch.blue]; _bmp_buf[bpos++] = gScale[pixel.ch.green]; // Header size is a multiple of 2, sector size is a multiple of 4 // so we will hit end of buffer half way through a pixel if (bpos == BMP_BUF_SIZE) { result = f_write(&bmpFile, _bmp_buf, bpos, &written); if (result != FR_OK || written != bpos) { lv_snapshot_free(snapshot); f_close(&bmpFile); return SDCARD_ERROR(result); } bpos = 0; } _bmp_buf[bpos++] = rbScale[pixel.ch.red]; _bmp_buf[bpos++] = 0xFF; } } if (bpos > 0) { result = f_write(&bmpFile, _bmp_buf, bpos, &written); if (result != FR_OK || written != bpos) { lv_snapshot_free(snapshot); f_close(&bmpFile); return SDCARD_ERROR(result); } } lv_snapshot_free(snapshot); #else // stdlcd result = f_write(&bmpFile, BMP_HEADER, sizeof(BMP_HEADER), &written); if (result != FR_OK || written != sizeof(BMP_HEADER)) { f_close(&bmpFile); return SDCARD_ERROR(result); } #if LCD_W == 128 pixel_t inv = g_eeGeneral.invertLCD ? 0xFF : 0; #endif for (int y=LCD_H-1; y>=0; y-=1) { for (int x=0; x<8*((LCD_W+7)/8); x+=2) { #if LCD_W == 128 pixel_t byte = (getPixel(x+1, y) | (getPixel(x, y) << 4)) ^ inv; #else pixel_t byte = getPixel(x+1, y) | (getPixel(x, y) << 4); #endif result = f_write(&bmpFile, &byte, 1, &written); if (result != FR_OK || written != 1) { f_close(&bmpFile); return SDCARD_ERROR(result); } } } #endif f_close(&bmpFile); return nullptr; } #endif void writeScreenshot() { const char* res = _writeScreenshot(); if (res) POPUP_WARNING(res); }