Files
edgetx-gx12/radio/src/gui/screenshot.cpp
T

307 lines
8.7 KiB
C++

/*
* 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);
}