M5Paper: Screenshot function



  • Someone had asked in another thread whether there was an easy way to take a screenshot of the M5Paper.
    I've put together a function to do that.
    It can take a screenshot in two formats. 16 colour (4bpp) BMP makes the most sense, as that is what the device can actually display, and only takes about 250KB, but full colour (24bpp) BMP is the format that the device can easily read back in again, and takes about 1.5MB.
    This is recording what is stored in the canvas framebuffer, not what is actually displayed on the screen. They will be different if you have called drawing functions and not yet called pushCanvas, or if you are using multiple canvases.
    Taking a full screen 24bpp screenshot is quite slow, taking about 10 seconds on my SD card.

    bool takeScreenshot( M5EPD_Canvas& canvas, String filename, bool bFull24bpp = true, int xPos = 0, int yPos = 0, int xSize = -1, int ySize = -1 )
    {
      File bmpFile = SD.open(filename,FILE_WRITE);
      if( !bmpFile )
      {
        log_e("Failed to open '%s' for write",filename);
        return false;
      }
      
      xSize = max( 1, min( xSize == -1 ? canvas.width() : xSize, canvas.width() - xPos ) ); 
      ySize = max( 1, min( ySize == -1 ? canvas.height() : ySize, canvas.height() - yPos ) ); 
      if( !bFull24bpp && xSize % 2 == 1 ) xSize++;
      
      log_d("Taking %d x %d screenshot to '%s'", xSize, ySize, filename);
    
      size_t bytes_written = 0;
      auto write8 = [&file=bmpFile,&bytes_written]( uint8_t data )
      {
        bytes_written += file.write(data); 
      };
      auto write16 = [&file=bmpFile,&bytes_written]( uint16_t data )
      {
        bytes_written += file.write(((uint8_t *)&data)[0]); 
        bytes_written += file.write(((uint8_t *)&data)[1]);  
      };
      auto write32 = [&file=bmpFile,&bytes_written]( uint32_t data )
      {
        bytes_written += file.write(((uint8_t *)&data)[0]); 
        bytes_written += file.write(((uint8_t *)&data)[1]);  
        bytes_written += file.write(((uint8_t *)&data)[2]);  
        bytes_written += file.write(((uint8_t *)&data)[3]);  
      };
      auto writeN = [&file=bmpFile,&bytes_written]( uint8_t* data, size_t N )
      {
        bytes_written += file.write(data,N); 
      };  
    
      uint8_t bpp = bFull24bpp ? 24 : 4;
      size_t bmpHeaderSize = 14;
      size_t dibHeaderSize = 40;
      size_t colourTableSize = bpp == 24 ? 0 : pow(2,bpp)*4;
      size_t gap1Size = 2;
      size_t rowSize = ceil(bpp * xSize / 32.0) * 4;
      size_t rowGap = rowSize - (bpp * xSize / 32.0) * 4;
      size_t pixelArraySize = rowSize * ySize;
      size_t pixelArrayOffset = bmpHeaderSize + dibHeaderSize + colourTableSize + gap1Size;
      size_t expectedSize = pixelArrayOffset + pixelArraySize;
    //  log_d("Expected size %d, row size %d, row gap %d, pixel array offset %d, pixel array size = %d",expectedSize, rowSize, rowGap, pixelArrayOffset, pixelArraySize);
      
      write16( 0x4D42 );
      write32( expectedSize );
      write32( 0 );
      write32( pixelArrayOffset );
      
      write32( dibHeaderSize );
      write32( xSize );
      write32( ySize );
      write16( 1 );
      write16( bpp );
      write32( 0 );
      write32( 0 );
      write32( 9252 );
      write32( 9252 );
      write32( 0 );
      write32( 0 );
    
      // From M5EPD_Canvas.cpp
      const uint8_t alphamap[16] = {0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255};
      if( bpp == 4 )
      {
        uint8_t buffer[16*4];
        int iCount = 0;
        for( int i = 0 ; i < 16 ; i++ )
        {
          uint8_t value = 255-alphamap[i];
          buffer[iCount++] = value;
          buffer[iCount++] = value;
          buffer[iCount++] = value;
          buffer[iCount++] = 0;
        }
        writeN(buffer,iCount);
      }
      for( int i = 0 ; i < gap1Size ; i++ )
        write8( 0 );
    
      uint8_t buffer[960*3];
    //  log_d("After headers, bytes_written = %d",bytes_written);
      for( int y = ySize + yPos - 1; y >= yPos ; y-- )
      {
        int iCount = 0; 
        for( int x = xPos ; x < xSize + xPos ; x++ )
        {
          uint16_t pixel = canvas.readPixel(x,y);
          uint8_t value = min(pixel,(uint16_t)15); 
          switch( bpp )
          {
            case 24:
            default:
              value = alphamap[15-min(value,(uint8_t)15)];
              buffer[iCount++] = value;
              buffer[iCount++] = value;
              buffer[iCount++] = value;
              break;
            case 4:
              if( (x-xPos) % 2 == 0 )
                buffer[iCount] = (value << 4) & 0xf0;
              else
              {
                buffer[iCount] |= value & 0x0f;          
                iCount++;
              }
              break;
          }
        }
        for( int gap = 0 ; gap < rowGap ; gap++ )
          buffer[iCount++] = 0;
        writeN(buffer,iCount);
      }
    
      bmpFile.close();
    
      if( bytes_written != expectedSize )
        log_e("Total bytes written %d vs expected %d",bytes_written,expectedSize);
      
      log_d("Screenshot done");
      
      return true;
    }
    


  • Thank you for this. I was looking for this!

    Cheers,
    Jan



  • Hello,

    Should I prepare something in the your program to get it running?
    I have hang in the procedure at the en of my refresh to the screen, start it but notting happens!
    Where can I find the output of the log statement?

    Cheers,
    Jan



  • The most likely issue is that the filename you pass doesn't start with '/', which is required by the SD library.

    To view the logging output, assuming you are using the Arduino UI, first do Tools->Core Debug Level->Set to Debug or Verbose. Then do Tools->Serial Monitor.
    You will see debug output from the M5Paper appear in the serial monitor window.



  • Thanks Paul for this tool, it is working great.!!

    ![0_1615906292910_screen.bmp](Uploading 57%)