M5Stackの画面のキャプチャー方法について説明します。
M5Stack良いのアプリケーションの説明や、プログを書くときに画面のキャプチャーが欲しいときがあります。その様な時に、便利なキャプチャ・ツールをご紹介します。
M5Stack 画面キャプチャ・ツール
marutsuのHPで、M5Stack 画面キャプチャ・ツール「Screen_Capture_BMP」が紹介されています。
ライブラリ化はされておらず、ヘッダファイルとして公開されていますので、自由に改変が可能と説明されています。表示データを読み出し、BMPフォーマットの画像データを作成してファイルに保存します。このツールは、「M5Unified」でも使用できます。
基本仕様
以下、M5Stack 画面キャプチャ・ツール「Screen_Capture_BMP」の基本仕様です。
- 関数名:void Screen_Capture_BMP(char *file_name)
※全画面をキャプチャし、file_nameで指定されたファイル名でTF(microSD)カードにBMP形式で保存 - 関数が呼ばれた時点でTF(microSD)カードが挿入されていない場合は、画面にエラー表示し動作停止
- ファイルが何らかの原因でオープンできなかった場合も画面にエラー表示をし動作停止
- キャプチャ中は画面バックライトの明るさを5%まで暗くする
※キャプチャ中であることを示すためと、使用するTF(microSD)カードによって書き込み中の電圧変動により、
書き込みに失敗する例があったため実施(バックライトを暗くすることにより解消された)
使用方法
以下、M5Stack 画面キャプチャ・ツール「Screen_Capture_BMP」を使用する手順です。
- ヘッダーファイル”Capture.h”のインクルード
- void Screen_Capture_BMP(char *file_name)関数を呼び出す
- 通常、ボタンを押されたときに呼び出します。
ヘッダーファイルのインクルード
#include "Capture.h"
サンプルコード
何らかのトリガーで、Screen_Capture_BMP関数を呼び出します。
以下は、ボタンA 及びデュアルボタンユニット [U025] を使用した例です。
ボタンAが押されたら画面をキャプチャします
#include <Arduino.h>
#include <M5Unified.h>
#include "Capture.h"
int counter;
char fn[100];
void setup()
{
M5.begin();
M5.Lcd.fillScreen(TFT_BLACK);
M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
M5.Lcd.setTextDatum(TL_DATUM);
M5.Lcd.setTextFont(4);
M5.Lcd.setTextSize(1);
counter = 0;
}
void loop()
{
delay(100); //100msウェイト
M5.update();
M5.Lcd.setCursor(10, 30);
M5.Lcd.printf("Counter = %d", counter);
if(M5.BtnA.isPressed())
{
sprintf(fn, "/Counter%d.bmp", counter);
Screen_Capture_BMP(fn);
counter++;
}
}M5Stack用デュアルボタンユニット [U025]を使用
必要な部分のコードのみ、抜き出しています。
#include <Capture.h>
#define RED_BUTTON 26
#define BLUE_BUTTON 36
int last_value_red = 0;
int cur_value_red = 0;
int last_value_blue = 0;
int cur_value_blue = 0;
void setup()
{
// ------------------------------------------------
pinMode(RED_BUTTON, INPUT);
pinMode(BLUE_BUTTON, INPUT);
// ------------------------------------------------
cur_value_red = digitalRead(RED_BUTTON);
cur_value_blue = digitalRead(BLUE_BUTTON);
last_value_blue = cur_value_blue;
last_value_red = cur_value_red;
}
char fn[100];
int CapNum = 1;
void loop(void)
{
M5.delay(1);
M5.update();
// ------------------------------------------------
cur_value_red = digitalRead(RED_BUTTON);
cur_value_blue = digitalRead(BLUE_BUTTON);
if(cur_value_blue != last_value_blue)
{
sprintf(fn, "/Counter%d.bmp", CapNum);
Screen_Capture_BMP(fn);
// M5.Speaker.tone(523.251, 100);
CapNum++;
last_value_blue = cur_value_blue;
}
if(cur_value_red != last_value_red)
{
last_value_red = cur_value_red;
}
}画面キャプチャ・ツール「Screen_Capture_BMP」の修正
画面キャプチャ・ツール「Screen_Capture_BMP」を修正して、縦長の画面表示の時にも正しくBMPファイルを正しく保存できるように修正しました。w_widthとw_heightの定義を追加し、条件コンパイルする形としています。
// M5Stack Screen Capture
// Copyright (c) 2022- @logic_star
#if 0
#define w_width 320
#define w_height 240
#else
#define w_width 240
#define w_height 320
#endif
#define BitImageSize (w_width * w_height *3) //画面キャプチャーイメージサイズ
#define PixelPerMeter (w_width / 4 * 100) //画面解像度
//Function entry
void Screen_Capture_BMP(char *file_name)
{
uint8_t FrameBuffer[320*3];
File fp;
int x,y,i;
const uint16_t BMP_header[14 + 40] = //BMPファイルヘッダー定義
{ //File header 14 bytes
'B','M', //uint16_t bfType
(BitImageSize & 0xff),((BitImageSize>>8) & 0xff),((BitImageSize>>16) & 0xff),0, //uint32_t bfSize
0,0, //uint16_t bfReserved1
0,0, //uint16_t bfReserved2
14+40,0,0,0,//uint32_t bfOffBits
//Information header 40 bytes
40,0,0,0, //uint32_t biSize
(w_width & 0xff),((w_width >> 8)& 0xff),0,0, //int32_t biWidth
(w_height & 0xff), ((w_height >> 8)& 0xff), 0,0, //int32_t biHeight
1,0, //uint16_t biPlanes
24,0, //uint16_t biBitCount
0,0,0,0, //uint32_t biCompression
(BitImageSize & 0xff),((BitImageSize>>8) & 0xff),((BitImageSize>>16) & 0xff),0, //uint32_t biSizeImage
(PixelPerMeter & 0xff),((PixelPerMeter>>8) & 0xff),0,0, //int32_t biXPelsPerMeter
(PixelPerMeter & 0xff),((PixelPerMeter>>8) & 0xff),0,0, //int32_t biYPelsPerMeter
0,0,0,0, //uint32_t biClrUsed
0,0,0,0 }; //uint32_t biClrImportant
Serial.printf("Start %s\n", file_name);
M5.Lcd.setBrightness(10); //バックライトを暗くする
if (!SD.begin(4))
{
M5.Lcd.println("NO SD CARD.");
M5.Lcd.setBrightness(200);
while (1) ;
}
fp = SD.open(file_name, FILE_WRITE);
if(!fp)
{
M5.Lcd.println("File open error.");
M5.Lcd.setBrightness(200);
while (1) ;
}
for(i=0;i<(14+40);i++) fp.write(BMP_header[i]); //BMPファイルヘッダーの出力
// for(y=239;y>=0;y--){ //画面の下から上へスキャン
// for(y=(w_height -1);y>=0;y--)
for(y=319;y>=0;y--)
{ //画面の下から上へスキャン
M5.Lcd.readRectRGB(0, y, w_width, 1, FrameBuffer); //1ライン分の画面データの取得
for(x=0;x<w_width*3;x+=3)
{ //1ライン分のデータ出力
fp.write(FrameBuffer[x+2]); //Blue
fp.write(FrameBuffer[x+1]); //Green
fp.write(FrameBuffer[x]); //Red
}
}
fp.close();
M5.Lcd.setBrightness(200); //バックライトの明るさを戻す
Serial.printf("end %s\n", file_name);
}saveToSD_24bit関数
LovyanGFXのサンプルの中に、「SaveBMP.ino」があります。この中のsaveToSD_24bit関数を使用しても画面のキャプチャーが可能です。以下は、saveToSD_24bit関数をベースに、一部修正したコードです。
- examples/Standard/SaveBMP/SaveBMP.ino
#include <Arduino.h>
#include <M5Unified.h>
#include "FS.h"
#include <SD.h> // If you use SD card, write this.
// #include <SPIFFS.h> // If you use SPIFFS, write this.
#include <SPI.h>
bool saveToSD_24bit(char *filename)
{
bool result = false;
M5.Lcd.setBrightness(10); //バックライトを暗くする
if (!SD.begin(4))
{
M5.Lcd.println("NO SD CARD.");
M5.Lcd.setBrightness(200);
while (1) ;
}
File file = SD.open(filename, "w");
if (file)
{
int width = M5.Lcd.width();
int height = M5.Lcd.height();
int rowSize = (3 * width + 3) & ~ 3;
lgfx::bitmap_header_t bmpheader;
bmpheader.bfType = 0x4D42;
bmpheader.bfSize = rowSize * height + sizeof(bmpheader);
bmpheader.bfOffBits = sizeof(bmpheader);
bmpheader.biSize = 40;
bmpheader.biWidth = width;
bmpheader.biHeight = height;
bmpheader.biPlanes = 1;
bmpheader.biBitCount = 24;
bmpheader.biCompression = 0;
file.write((std::uint8_t*)&bmpheader, sizeof(bmpheader));
std::uint8_t buffer[rowSize];
memset(&buffer[rowSize - 4], 0, 4);
for (int y = M5.Lcd.height() - 1; y >= 0; y--)
{
M5.Lcd.readRect(0, y, M5.Lcd.width(), 1, (lgfx::rgb888_t*)buffer);
file.write(buffer, rowSize);
}
file.close();
result = true;
} else {
Serial.print("error:file open failure\n");
}
M5.Lcd.setBrightness(200); //バックライトの明るさを戻す
return result;
}PNGファイルに保存
LovyanGFXのサンプルの中に、「SavePNG.ino」があります。この中のsaveToSD関数を使用して画面をキャプチャーして、PNGファイルに保存することが可能です。
以下のコメントありますので、注意してご使用ください。尚、PSRAMをもつM5Stack Core2では、画面全体のキャプチャーが可能でした。
ESP32の場合 192×192程度が上限です。
メモリ使用状況によってさらに縮みます。
ESP32でPSRAMが有効な場合は大きなサイズでも保存できる可能性があります。
以下は、saveToSD関数をベースに、一部修正したコードです。
- examples/Standard/SavePNG/SavePNG.ino
#include <Arduino.h>
#include <M5Unified.h>
#include "FS.h"
#include <SD.h> // If you use SD card, write this.
// #include <SPIFFS.h> // If you use SPIFFS, write this.
#include <SPI.h>
bool savePNGToSD(char *filename)
{
M5.Lcd.setBrightness(10); //バックライトを暗くする
if (!SD.begin(4))
{
M5.Lcd.println("NO SD CARD.");
M5.Lcd.setBrightness(200);
while (1) ;
}
// createPng関数で指定範囲の画像からPNG形式のデータを生成します。
// SAMD51の場合 172x172程度が上限です。
// ESP32の場合 192x192程度が上限です。
// メモリ使用状況によってさらに縮みます。
// ESP32でPSRAMが有効な場合は大きなサイズでも保存できる可能性があります。
std::size_t dlen;
std::uint8_t* png = (std::uint8_t*)M5.Lcd.createPng(&dlen, 0, 0, M5.Lcd.width(), M5.Lcd.height());
if (!png)
{
Serial.print("error:createPng\n");
return false;
}
Serial.print("success:createPng\n");
bool result = false;
File file = SD.open(filename, "w");
if (file)
{
file.write((std::uint8_t*)png, dlen);
file.close();
result = true;
} else {
Serial.print("error:file open failure\n");
}
free(png);
M5.Lcd.setBrightness(200); //バックライトの明るさを戻す
return result;
}


