main: implement saveScreenshot() writing bmp/png from SDL window buffer
When pressing `i` create a screenshot of the current InfiniTime screen by dumping it to a timestamped png or bmp file like `InfiniSim_2022-03-08_203421.png`. Add a new configure option `WITH_PNG` with default `ON`. When switched to `OFF` the screenshots will be created in `bmp` format. Fixes: https://github.com/InfiniTimeOrg/InfiniSim/issues/5
This commit is contained in:
parent
819d9cbdcb
commit
dfbc345511
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -4,3 +4,6 @@
|
|||
[submodule "lv_drivers"]
|
||||
path = lv_drivers
|
||||
url = ../../lvgl/lv_drivers.git
|
||||
[submodule "libpng"]
|
||||
path = libpng
|
||||
url = git@github.com:glennrp/libpng.git
|
||||
|
|
|
@ -208,3 +208,10 @@ set(VERSION_EDIT_WARNING "// Do not edit this file, it is automatically generate
|
|||
configure_file("${InfiniTime_DIR}/src/Version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/Version.h")
|
||||
|
||||
target_include_directories(infinisim PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
option(WITH_PNG "Compile with libpng support to dump current screen as png" ON)
|
||||
if(WITH_PNG)
|
||||
target_compile_definitions(infinisim PRIVATE WITH_PNG)
|
||||
add_subdirectory(libpng EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(infinisim PRIVATE png_static)
|
||||
endif()
|
||||
|
|
|
@ -97,6 +97,7 @@ Using the keyboard the following events can be triggered:
|
|||
- `S` ... decrease step count by 500 steps
|
||||
- `h` ... set heartrate running, and on further presses increase by 10 bpm
|
||||
- `H` ... stop heartrate
|
||||
- `i` ... take screenshot
|
||||
|
||||
## Licenses
|
||||
|
||||
|
|
1
libpng
Submodule
1
libpng
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit a37d4836519517bdce6cb9d956092321eca3e73b
|
103
main.cpp
103
main.cpp
|
@ -57,6 +57,13 @@
|
|||
#include <algorithm>
|
||||
#include <cmath> // std::pow
|
||||
|
||||
// additional includes for 'saveScreenshot()' function
|
||||
#include <date/date.h>
|
||||
#include <chrono>
|
||||
#if defined(WITH_PNG)
|
||||
#include <libpng16/png.h>
|
||||
#endif
|
||||
|
||||
/*********************
|
||||
* DEFINES
|
||||
*********************/
|
||||
|
@ -64,6 +71,98 @@
|
|||
/**********************
|
||||
* TYPEDEFS
|
||||
**********************/
|
||||
// copied from lv_drivers/display/monitor.c to get the SDL_Window for the InfiniTime screen
|
||||
extern "C"
|
||||
{
|
||||
typedef struct {
|
||||
SDL_Window * window;
|
||||
SDL_Renderer * renderer;
|
||||
SDL_Texture * texture;
|
||||
volatile bool sdl_refr_qry;
|
||||
#if MONITOR_DOUBLE_BUFFERED
|
||||
uint32_t * tft_fb_act;
|
||||
#else
|
||||
uint32_t tft_fb[LV_HOR_RES_MAX * LV_VER_RES_MAX];
|
||||
#endif
|
||||
}monitor_t;
|
||||
extern monitor_t monitor;
|
||||
}
|
||||
|
||||
void saveScreenshot()
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
// TODO: timestamped png filename
|
||||
std::string screenshot_filename_base = date::format("InfiniSim_%F_%H%M%S", date::floor<std::chrono::seconds>(now));
|
||||
//std::string screenshot_filename_base = "InfiniSim";
|
||||
|
||||
const int width = 240;
|
||||
const int height = 240;
|
||||
auto renderer = monitor.renderer;
|
||||
|
||||
|
||||
#if defined(WITH_PNG)
|
||||
std::string screenshot_filename = screenshot_filename_base + ".png";
|
||||
|
||||
FILE * fp2 = fopen(screenshot_filename.c_str(), "wb");
|
||||
if (!fp2) {
|
||||
// dealing with error
|
||||
return;
|
||||
}
|
||||
// 1. Create png struct pointer
|
||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
if (!png_ptr){
|
||||
// dealing with error
|
||||
}
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr) {
|
||||
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
|
||||
// dealing with error
|
||||
}
|
||||
int bit_depth = 8;
|
||||
png_init_io(png_ptr, fp2);
|
||||
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, \
|
||||
PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, \
|
||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
// 3. Convert 1d array to 2d array to be suitable for png struct
|
||||
// I assumed the original array is 1d
|
||||
std::array<png_bytep, 240> row_pointers;
|
||||
//png_bytepp row_pointers = (png_bytepp)png_malloc(png_ptr, sizeof(png_bytep) * height);
|
||||
for (int i = 0; i < height; i++) {
|
||||
row_pointers[i] = (png_bytep)png_malloc(png_ptr, width*4);
|
||||
}
|
||||
const Uint32 format = SDL_PIXELFORMAT_RGBA8888;
|
||||
SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, format);
|
||||
SDL_RenderReadPixels(renderer, NULL, format, surface->pixels, surface->pitch);
|
||||
png_bytep pixels = (png_bytep)surface->pixels;
|
||||
for (int hi = 0; hi < height; hi++) {
|
||||
for (int wi = 0; wi < width; wi++) {
|
||||
int c = wi * 4;
|
||||
row_pointers.at(hi)[wi*4+0] = pixels[hi*surface->pitch + wi*4 + 3]; // red
|
||||
row_pointers.at(hi)[wi*4+1] = pixels[hi*surface->pitch + wi*4 + 2]; // greeen
|
||||
row_pointers.at(hi)[wi*4+2] = pixels[hi*surface->pitch + wi*4 + 1]; // blue
|
||||
row_pointers.at(hi)[wi*4+3] = 255; // alpha
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Write png file
|
||||
png_write_info(png_ptr, info_ptr);
|
||||
png_write_image(png_ptr, row_pointers.data());
|
||||
png_write_end(png_ptr, info_ptr);
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
fclose(fp2);
|
||||
|
||||
SDL_FreeSurface(surface);
|
||||
#else
|
||||
std::string screenshot_filename = screenshot_filename_base + ".bmp";
|
||||
const Uint32 format = SDL_PIXELFORMAT_RGB888;
|
||||
SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 24, format);
|
||||
SDL_RenderReadPixels(renderer, NULL, format, surface->pixels, surface->pitch);
|
||||
SDL_SaveBMP(surface, screenshot_filename.c_str());
|
||||
SDL_FreeSurface(surface);
|
||||
#endif
|
||||
std::cout << "InfiniSim: Screenshot created: " << screenshot_filename << std::endl;
|
||||
}
|
||||
|
||||
/**********************
|
||||
* STATIC PROTOTYPES
|
||||
|
@ -450,6 +549,7 @@ public:
|
|||
debounce('p', 'P', state[SDL_SCANCODE_P], key_handled_p);
|
||||
debounce('s', 'S', state[SDL_SCANCODE_S], key_handled_s);
|
||||
debounce('h', 'H', state[SDL_SCANCODE_H], key_handled_h);
|
||||
debounce('i', 'I', state[SDL_SCANCODE_I], key_handled_i);
|
||||
// screen switcher buttons
|
||||
debounce('1', '!'+1, state[SDL_SCANCODE_1], key_handled_1);
|
||||
debounce('2', '!'+2, state[SDL_SCANCODE_2], key_handled_2);
|
||||
|
@ -525,6 +625,8 @@ public:
|
|||
}
|
||||
} else if (key == 'H') {
|
||||
heartRateController.Stop();
|
||||
} else if (key == 'i') {
|
||||
saveScreenshot();
|
||||
} else if (key >= '0' && key <= '9') {
|
||||
this->switch_to_screen(key-'0');
|
||||
} else if (key >= '!'+0 && key <= '!'+9) {
|
||||
|
@ -679,6 +781,7 @@ private:
|
|||
bool key_handled_p = false; // p ... enable print memory usage, P ... disable print memory usage
|
||||
bool key_handled_s = false; // s ... increase step count, S ... decrease step count
|
||||
bool key_handled_h = false; // h ... set heartrate running, H ... stop heartrate
|
||||
bool key_handled_i = false; // i ... take screenshot, I ... not assigned
|
||||
// numbers from 0 to 9 to switch between screens
|
||||
bool key_handled_1 = false;
|
||||
bool key_handled_2 = false;
|
||||
|
|
Loading…
Reference in New Issue
Block a user