How to take a snapshot?

Description

I’ve been following the documentation as I want to take a snapshot on device and save it to an SD card.
So far I’ve tried to use lv_snapshot_take but it returns NULL all the time as there is not enough RAM in the region it is running in on the Teensy (RAM1)
So I tried to use lv_snapshot_take_to_buf but the watchdog on the Teensy is resetting the device due to a null pointer.

Below is a function I wrote to take a snapshot and save it to SD.
I call it where I want to take a snapshot and pass the lv_scr_act() object to it to take a full screen snapshot.

Can anyone provide an example of how they used lv_snapshot_take_to_buf to make sure i am using it correctly?

What MCU/Processor/Board and compiler are you using?

Teensy MicroMod (IMXRT1062)

What LVGL version are you using?

v8.1

Code to reproduce

#include "../lib/lvgl/lvgl.h"
#include "SdFat.h"
#include "Arduino.h"
SdFs sd;
FsFile file;
// Use Teensy SDIO
#define SD_CONFIG  SdioConfig(FIFO_SDIO)
// Size to log 10 byte lines at 25 kHz for more than ten minutes.

#define LOG_FILE_SIZE (480*320*3) // 480px * 320px * 3bytes per pixle
#define LOG_FILENAME "snapshot.c"

DMAMEM uint16_t buff[LOG_FILE_SIZE/2]; //Buffer in RAM2 to place the snapshot
lv_img_dsc_t * img;

void takeSnapshot(lv_obj_t * screenshot){
    arm_dcache_flush((uint16_t*)buff, sizeof(buff)); // 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, img, &buff, sizeof(buff));
    if (snap == LV_RES_INV){
        Serial.println("Snapshot failed");
    }
    else{
    // Initialize the SD.
  if (!sd.begin(SD_CONFIG)) {
    sd.initErrorHalt(&Serial);
  }
  // Open or create file - truncate existing file.
  if (!file.open(LOG_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(LOG_FILE_SIZE)) {
     Serial.println("preAllocate failed\n");
     file.close();
     return;
  }
  file.write((uint16_t*)buff);
  file.close();
  Serial.println("Snapshot saved");
    }

}



1 Like

Anyone? No…?

Hi @reso ,

I am not familiar with the Teensy or the lv_snapshot feature but looking at your code I think I can see an error, the image descriptor lv_img_dsc_t * img; shouldn’t be a pointer so this will cause your NULL pointer crash. Taking your code it might be worth trying it like this…

#include "../lib/lvgl/lvgl.h"
#include "SdFat.h"
#include "Arduino.h"
SdFs sd;
FsFile file;
// Use Teensy SDIO
#define SD_CONFIG  SdioConfig(FIFO_SDIO)
// Size to log 10 byte lines at 25 kHz for more than ten minutes.

#define LOG_FILE_SIZE (480*320*3) // 480px * 320px * 3bytes per pixle
#define LOG_FILENAME "snapshot.c"

DMAMEM uint16_t buff[LOG_FILE_SIZE/2]; //Buffer in RAM2 to place the snapshot
lv_img_dsc_t img;  // I believe this shouldn't be a pointer!!

void takeSnapshot(lv_obj_t * screenshot){
    arm_dcache_flush((uint16_t*)buff, sizeof(buff)); // always flush cache after writing to DMAMEM variable that will be accessed by DMA
 // In this line we add address of operator '&' to 'img'
    lv_res_t snap = lv_snapshot_take_to_buf(screenshot, LV_IMG_CF_TRUE_COLOR_ALPHA, &img, &buff, sizeof(buff)); 
    if (snap == LV_RES_INV){
        Serial.println("Snapshot failed");
    }
    else{
    // Initialize the SD.
  if (!sd.begin(SD_CONFIG)) {
    sd.initErrorHalt(&Serial);
  }
  // Open or create file - truncate existing file.
  if (!file.open(LOG_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(LOG_FILE_SIZE)) {
     Serial.println("preAllocate failed\n");
     file.close();
     return;
  }
  file.write((uint16_t*)buff);
  file.close();
  Serial.println("Snapshot saved");
    }

}

I would also check the buffer size carefully as it looks like it might be incorrect, if it is lv_snapshot_take_to_buf() should return LV_RES_INV so if that happens I would adjust the size of the buffer accordingly. If you have a debugger you can step into the lv_snapshot_take_to_buf() and see what value it is expecting for length if you are unsure about the size of the buffer.

I hope that is the answer.

Kind Regards,

Pete

@pete-pjb thank you for your response and suggestions.
I checked the size of the object that should be returned by calling lv_snapshot_buf_size_needed and it returned 460.8Kb. Being that the buffer value is a 2 byte unsigned integer, it needs to be half of that, no? Or should I just save an an uint8_t?
It’s not clear what kind of object is retuned to the buffer.

Hi @reso,

Yes I agree with your comments. The buffer can be specified anyway you want as long as the number of bytes is correct :slight_smile:

Kind Regards,

Pete

Thanks @pete-pjb that worked!
Now I need to tinker with getting it converted to a bitmap

I have been able to write it as a bitmap to the SD card but the image is flipped and mirrored, as well as mostly blue.
I know the bitmap config is wrong as it’s set up for RGB555 and not 565, and the pixels are definitely being read in the incorrect order

1 Like

You can set the bitmap height to the negative value to make the client render it mirrored. The BMP spec orders the pixel rows bottom to top by default, but you can also put them top to bottom if you specify a negative height.

I’m using the BITMAPV2INFOHEADER BMP format while setting BI_BITFIELDS to 3 for RGB bitmasks like so:

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
};

/** Set a Bitmap Header.
 *
 * Initializes a header in BMP format for the size of the screen.
 *
 * @note: send header before refreshing the whole screen
 *
 **/
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
}

If you write such header before dumping the LVGL pixels to the file, it should render fine on the client.
I’m using it to take local screenshots on flash and also send the screenshot to a browser via HTTP.

1 Like