From dfbc3455119d9b0f0cc6e2c9b7d888b2bef21853 Mon Sep 17 00:00:00 2001 From: Reinhold Gschweicher Date: Wed, 2 Mar 2022 22:05:28 +0100 Subject: [PATCH] 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 --- .gitmodules | 3 ++ CMakeLists.txt | 7 ++++ README.md | 1 + libpng | 1 + main.cpp | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 160000 libpng diff --git a/.gitmodules b/.gitmodules index d98fed9..2170d40 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 7927d3c..56f1067 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/README.md b/README.md index 96c2961..abfd7d5 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/libpng b/libpng new file mode 160000 index 0000000..a37d483 --- /dev/null +++ b/libpng @@ -0,0 +1 @@ +Subproject commit a37d4836519517bdce6cb9d956092321eca3e73b diff --git a/main.cpp b/main.cpp index 600ccd1..b090f6c 100644 --- a/main.cpp +++ b/main.cpp @@ -57,6 +57,13 @@ #include #include // std::pow +// additional includes for 'saveScreenshot()' function +#include +#include +#if defined(WITH_PNG) +#include +#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(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 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;