Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
3.8 kB · 120 lines
C++
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121#include "ScreenshotUtil.h"
#include <Arduino.h>#include <BitmapHelpers.h>#include <GfxRenderer.h>#include <HalStorage.h>#include <Logging.h>
#include <string>
#include "Bitmap.h" // Required for BmpHeader struct definition
void ScreenshotUtil::takeScreenshot(GfxRenderer& renderer) { const uint8_t* fb = renderer.getFrameBuffer(); if (fb) { String filename_str = "/screenshots/screenshot-" + String(millis()) + ".bmp"; if (ScreenshotUtil::saveFramebufferAsBmp(filename_str.c_str(), fb, renderer.getDisplayWidth(), renderer.getDisplayHeight())) { LOG_DBG("SCR", "Screenshot saved to %s", filename_str.c_str()); } else { LOG_ERR("SCR", "Failed to save screenshot"); } } else { LOG_ERR("SCR", "Framebuffer not available"); }
// Display a border around the screen to indicate a screenshot was taken if (renderer.storeBwBuffer()) { renderer.drawRect(6, 6, renderer.getDisplayHeight() - 12, renderer.getDisplayWidth() - 12, 2, true); renderer.displayBuffer(); delay(1000); renderer.restoreBwBuffer(); renderer.displayBuffer(HalDisplay::RefreshMode::HALF_REFRESH); }}
bool ScreenshotUtil::saveFramebufferAsBmp(const char* filename, const uint8_t* framebuffer, int width, int height) { if (!framebuffer) { return false; }
// Note: the width and height, we rotate the image 90d counter-clockwise to match the default display orientation int phyWidth = height; int phyHeight = width;
std::string path(filename); size_t last_slash = path.find_last_of('/'); if (last_slash != std::string::npos) { std::string dir = path.substr(0, last_slash); if (!Storage.exists(dir.c_str())) { if (!Storage.mkdir(dir.c_str())) { return false; } } }
FsFile file; if (!Storage.openFileForWrite("SCR", filename, file)) { LOG_ERR("SCR", "Failed to save screenshot"); return false; }
BmpHeader header;
createBmpHeader(&header, phyWidth, phyHeight, BmpRowOrder::BottomUp);
bool write_error = false; if (file.write(reinterpret_cast<uint8_t*>(&header), sizeof(header)) != sizeof(header)) { write_error = true; }
if (write_error) { // Explicitly close() file before calling Storage.remove() file.close(); Storage.remove(filename); return false; }
const uint32_t rowSizePadded = (phyWidth + 31) / 32 * 4; // Max row size for 528px height (X3) after rotation = 68 bytes; use fixed buffer to avoid VLA constexpr size_t kMaxRowSize = 68; if (rowSizePadded > kMaxRowSize) { LOG_ERR("SCR", "Row size %u exceeds buffer capacity", rowSizePadded); // Explicitly close() file before calling Storage.remove() file.close(); Storage.remove(filename); return false; }
// rotate the image 90d counter-clockwise on-the-fly while writing to save memory uint8_t rowBuffer[kMaxRowSize]; memset(rowBuffer, 0, rowSizePadded);
for (int outY = 0; outY < phyHeight; outY++) { for (int outX = 0; outX < phyWidth; outX++) { // 90d counter-clockwise: source (srcX, srcY) // BMP rows are bottom-to-top, so outY=0 is the bottom of the displayed image int srcX = width - 1 - outY; // phyHeight == width int srcY = phyWidth - 1 - outX; // phyWidth == height int fbIndex = srcY * (width / 8) + (srcX / 8); uint8_t pixel = (framebuffer[fbIndex] >> (7 - (srcX % 8))) & 0x01; rowBuffer[outX / 8] |= pixel << (7 - (outX % 8)); } if (file.write(rowBuffer, rowSizePadded) != rowSizePadded) { write_error = true; break; } memset(rowBuffer, 0, rowSizePadded); // Clear the buffer for the next row }
// Explicitly close() file before calling Storage.remove() file.close();
if (write_error) { Storage.remove(filename); return false; }
return true;}