M5Paperでスクリーンショットを撮りたいときありますよね。スマホだったら撮れるんだからM5Paperでもうまく動いた画面を撮りたい。でもEPD Canvasには、pngやjpgを表示する関数はあるけど書き出す関数はなさそうです。
https://docs.m5stack.com/en/api/m5paper/epd_canvas
frameBuffer()でバッファにはアクセスできるので、それを何かビットマップな形式にして保存すればよさそう。一番フォーマットが簡単そうなPGMファイルにしてみます。
コード
// Export EPD Canvas content as PGM file
void savePGM(M5EPD_Canvas canvas) {
uint32_t bufferSize = canvas.getBufferSize();
uint8_t *buffer = (uint8_t *)(canvas.frameBuffer(1));
int width = canvas.width();
int height = canvas.height();
Serial.println(String(width) + " x " + String(height) + " = " + String(width * height) + ", bufferSize = " + String(bufferSize));
// Open file
int fileIndex = 1;
while (SD.exists("/ss" + String(fileIndex) + ".pgm"))
{
fileIndex++;
}
String fileName = "/ss" + String(fileIndex) + ".pgm";
Serial.println("File name = " + fileName);
File pgmFile = SD.open(fileName, FILE_WRITE);
if (!pgmFile) return;
// Write header
pgmFile.print("P5 ");
pgmFile.print(String(width) + " ");
pgmFile.print(String(height) + " ");
pgmFile.print("255 ");
// Write binary
for(uint32_t i=0; i< (bufferSize); i++) {
uint8_t byte = buffer[i];
uint8_t high4bit = 17 * (15 - (byte >> 4));
uint8_t low4bit = 17 * (15 - ((B00001111 & byte)));
pgmFile.write(high4bit);
pgmFile.write(low4bit);
}
pgmFile.close();
Serial.println("File wrote: " + fileName);
}
実際の画像
やってること
同名ファイルが既にあったら番号を進めるようにしたりしてますが、固定ファイル名でもいいはずです。
PGMファイルのフォーマットはこちらを参照しました。
https://netpbm.sourceforge.net/doc/pgm.html
まず先頭に「P5」と書きます。これはグレイスケールのバイナリ形式であることを意味します。
空白文字(LFでもタブでもスペースでもいいらしい)を置いて、横ピクセル数、
また空白文字を置いて縦ピクセル数、また空白文字を置いてグレイスケールの最大値、
最後に空白文字を置いて以降はバイナリを書き込みます。
M5Paperは16階調の電子ペーパーなので4ビットでデータを持っているらしく、バッファから1バイト取得したらそれを4ビットと4ビットに分けます。また、バッファ上の階調は0が白、15が黒と、普通の画像ファイルとは逆になっているので反転します。最後に8ビットグレースケールに変換して、4ビットを1バイトにしてから書き込みます。
PGMファイルは圧縮が無いので、M5Paperの画面サイズだとそれなりにサイズが増えます。PNGだと28KBくらいのところPGMだと518KB。書き出しにも数秒かかります。
課題
- あくまでCanvasの書き出しなので、正確には画面全体のスクリーンショットではありません。全画面更新用のCanvasを使用している場合のみ有効です。
- 518KBはM5Paperには大きくてかわいそう。PNGにしたい