How to take a snapshot?

@jojo this is my full snapshot code from the project file that is working for me:

#include "lvgl.h"
#include "SdFat.h"
#include "Arduino.h"
#include "screenshot.h"


SdFs sd;
FsFile file;
// Use Teensy SDIO
#define SD_CONFIG  SdioConfig(FIFO_SDIO)
#define LOG_FILE_SIZE (480*320*3) // 480px * 320px * 3bytes per pixle


DMAMEM uint8_t img_buffer[LOG_FILE_SIZE]; //Buffer in RAM2 to place the snapshot
lv_img_dsc_t snapshot_img;  //

struct bmp_header_t
{
    uint32_t bfSize;
    uint32_t bfReserved;
    uint32_t bfOffBits;

    uint32_t biSize;
    int32_t biWidth;
    int32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    int32_t biXPelsPerMeter;
    int32_t biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;

    uint32_t bdMask[3]; // RGB
};


static void gui_get_bitmap_header(uint8_t* buffer, size_t bufsize)
{
    lv_obj_t * scr     = lv_disp_get_scr_act(NULL);
    lv_coord_t width  = lv_obj_get_width(scr);
    lv_coord_t height = lv_obj_get_height(scr);

    const char* bm = "BM";
    memcpy(buffer, bm, strlen(bm));
    buffer += strlen(bm);

    // Bitmap file header
    bmp_header_t* bmp = (bmp_header_t*)buffer;
    bmp->bfSize       = (uint32_t)(width * height * LV_COLOR_DEPTH / 8);
    bmp->bfReserved   = 0;
    bmp->bfOffBits    = bufsize;

    // Bitmap information header
    bmp->biSize          = 40;
    bmp->biWidth         = width;
    bmp->biHeight        = -height;
    bmp->biPlanes        = 1;
    bmp->biBitCount      = LV_COLOR_DEPTH;
    bmp->biCompression   = 3; // BI_BITFIELDS
    bmp->biSizeImage     = bmp->bfSize;
    bmp->biXPelsPerMeter = 2836;
    bmp->biYPelsPerMeter = 2836;
    bmp->biClrUsed       = 0; // zero defaults to 2^n
    bmp->biClrImportant  = 0;

    // BI_BITFIELDS
    bmp->bdMask[0] = 0xF800; // Red bitmask  : 1111 1000 | 0000 0000
    bmp->bdMask[1] = 0x07E0; // Green bitmask: 0000 0111 | 1110 0000
    bmp->bdMask[2] = 0x001F; // Blue bitmask : 0000 0000 | 0001 1111
}

void takeScreenshot(lv_obj_t * screenshot, const char* fileName){
    arm_dcache_flush((uint8_t*)img_buffer, sizeof(img_buffer)); // always flush cache after writing to DMAMEM variable that will be accessed by DMA
    lv_res_t snap = lv_snapshot_take_to_buf(screenshot, LV_IMG_CF_TRUE_COLOR_ALPHA, &snapshot_img, &img_buffer, sizeof(img_buffer)); 

    if (snap == LV_RES_INV){
        //Serial.println("Snapshot failed");
    }
    else{
        //Serial.println("Snapshot Success");

        uint8_t header[sizeof(bmp_header_t) + 2];
        //Serial.println("Starting to build header");
        gui_get_bitmap_header(header, sizeof(header));
        //Serial.println("Finished building header");

    // Initialize the SD.
        if (!sd.begin(SD_CONFIG)) {
            sd.initErrorHalt(&Serial);
        }
        // Open or create file - truncate existing file.
        if (!file.open(fileName, O_RDWR | O_CREAT | O_TRUNC)) {
            //Serial.println("open failed\n");
            return;
        }
        // File must be pre-allocated to avoid huge
        // delays searching for free clusters.
        if (!file.preAllocate((480*320*2)+sizeof(header))) {
            //Serial.println("preAllocate failed\n");
            file.close();
            return;
        }
        //Serial.println("Start write headers to SD");
        file.write(header, sizeof(header));
        //Serial.println("Finish write headers to SD, and start write image to SD");
        for(uint32_t i=0; i<LOG_FILE_SIZE;i+=3){
            file.write(img_buffer[i]);
            file.write(img_buffer[i+1]);
        }
        //Serial.println("Finished writing image data to SD");
        file.close();
        //Serial.println("Snapshot saved to SD");
    }
}
2 Likes

Thanks, I finally got it working by using LV_IMG_CF_TRUE_COLOR, multiplying buffer by 2 instead of 3, and using fwrite(img_buff, 1, LOG_FILE_SIZE, fd);

I also had to invert red and blue bytes order.

1 Like

@jojo Could you share your snapshop code and an example BMP file? I’d like to check the headers and bitmap in a HEX viewer, like 010 Editor, to see if the bitmaps fields can be used.

2 Likes

LVGL screenshot - My projects - LVGL Forum

1 Like

Thank you all for the guidance and working through the key issues to get this working. I want to share my variant that created the snapshot buffer in PSRAM for use in cases where the main heap is too small.

void saveScreenCapture()
{
    int buffSize = lv_snapshot_buf_size_needed(lv_scr_act(), LV_IMG_CF_TRUE_COLOR);
    Serial.printf("Buffer size required: %u \n", buffSize);

    uint8_t *ps_buff = static_cast<uint8_t *>(heap_caps_malloc(buffSize, MALLOC_CAP_SPIRAM));

    lv_img_dsc_t img;
    lv_res_t snap = lv_snapshot_take_to_buf(lv_scr_act(), LV_IMG_CF_TRUE_COLOR, &img, ps_buff, buffSize);

    if (snap == LV_RES_INV)
    {
        ESP_LOGE(TAG, "Snapshot failed");
    }
    else
    {
        ESP_LOGI(TAG, "Snapshot Success %dx%d", img.header.w, img.header.h);

        File file = SD.open("capture.bmp", "w");

        if (file)
        {
            ESP_LOGI(TAG, "Screen capture file opened");

            // generate the header
            uint8_t header[sizeof(bmp_header_t) + 2];
            gui_get_bitmap_header(header, sizeof(header));
            file. Write(header, sizeof(header));

            // write out the image buffer
            file.write(ps_buff, buffSize);

            file.close();

            ESP_LOGI(TAG, "Screen capture file closed.");
        }
    }

    free(ps_buff);
}
1 Like