ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【M5StickC Plus/Arduino】M5StickC Plusで連番画像を使ったアニメーション

 過去にpythonを使ってpngの連番画像をBitmap形式のテキストデータに変換する記事を書きました。

 この記事の最後にこの狙いとして「SDカードを使えないM5StickC Plusで画像を表示させる」ことを書いていたので、今回は実際に表示させていきます。

 では、始めます。


1:サンプル画像の準備と変換
 今回やるサンプルとして135*240(M5StickC Plusを縦にした時の画面サイズと同じ)png画像6枚を用意しました。

f:id:rikoubou:20210426153257p:plainf:id:rikoubou:20210426153308p:plainf:id:rikoubou:20210426153315p:plain
f:id:rikoubou:20210426153322p:plainf:id:rikoubou:20210426153329p:plainf:id:rikoubou:20210426153336p:plain

 この画像をそれぞれ番号順に001.png~006.pngという名前で保存して前の記事で書いた「pngToBitmapData.py」を使い、テキストデータに変換した「ImgData.h」を作成します。

 面倒な人はサンプルとして公開しているzipファイルを解凍した中にある「ImgData.h」ファイルをそのまま使ってください。

 これで連番画像をテキストデータに変換したヘッダーファイルが作成できました。


2:テキストデータに変換した画像の表示
 M5Stack系でディスプレイに画像を表示させる方法は2つあります。一つは「M5Display」を使う方法、もう一つは「スプライト」を使う方法です。

 M5Displayで画像を表示させる方法は以下の通りです。

#include <M5StickCPlus.h>

// 普通に表示
M5.Lcd.pushImage([開始X座標], [開始Y座標], [画像横幅], [画像縦幅], [画像データ]);

// ピクセル単位で色を表示
M5.Lcd.drawPixel([X座標], [Y座標], [色]);

// 特定の色を透過して表示
M5.Lcd.pushImage([開始X座標], [開始Y座標], [画像横幅], [画像縦幅], [画像データ], [透過する色]);


 スプライトで画像を表示させる方法は以下の通りです。

#include <M5StickCPlus.h>

TFT_eSprite sprite = TFT_eSprite(&M5.Lcd); // スプライトインスタンス作成
sprite.createSprite([スプライト横幅], [スプライト縦幅]); // スプライトの範囲を作成

// スプライトに画像表示
sprite.pushImage([開始X座標], [開始Y座標], [画像横幅], [画像縦幅], [画像データ]);

// スプライトを画面に表示
sprite.pushSprite([スプライトを表示するX座標], [スプライトを表示するY座標]);

// ピクセル単位で色を表示
sprite.drawPixel([X座標], [Y座標], [色]);

// 特定の色を透過したスプライトを画面に表示
sprite.pushSprite([スプライトを表示するX座標], [スプライトを表示するY座標], [透過する色]);

 どちらの方法を使っても画像を画面に表示させることができます。

 こう見ると手順が多いスプライトを使う理由がないように思えますが、スプライトの場合は最後の「pushSprite」を行うことで初めて画面に表示されるので、様々な処理を行う場合はスプライトを使った方が処理が速くなる場合があります。
 逆にM5Displayで表示させるのは手順が少なく楽ですが、様々な処理を行った後に画像を表示させたいという場合だと処理が遅くなる(画面にちらつきが発生する)場合があります。

 では実際にこの2つの方法を使って1の画像を表示させてみます。


3:連番画像を表示させるサンプル
 連番画像をアニメーションとして表示させるサンプルは以下の通りです。「ImgData.h」の中身についてはファイルサイズがかなり大きいので実際に変換したものを使ってください。

・ImgData.h

const int IMG_WIDTH  = 135;
const int IMG_HEIGHT = 240;
const int IMG_MAX    = 6;

const unsigned short IMG_DATA[IMG_MAX][IMG_WIDTH*IMG_HEIGHT] = {
    {
    0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,
    // ~~~中略~~~
    }
};


・M5StickCPlus_images.ino

#include <M5StickCPlus.h>
#include "ImgData.h"

TFT_eSprite _sprite = TFT_eSprite(&M5.Lcd);

void setup() {
  Serial.begin(115200);

  M5.begin();
  M5.Axp.ScreenBreath(10); // 7-12で明るさ設定
  M5.Lcd.setRotation(0);   // 画面を縦向きに(0-3)

  // 画像の大きさのスプライト作成
  _sprite.createSprite(IMG_WIDTH, IMG_HEIGHT);

  Serial.println("setup end");
}

int img_number = 0;

void loop() {
  M5.update();

  /* スプライトを使った場合 */
//  showImageBySprite(img_number); // 画像を表示
  showImageBySpriteBgColor(img_number, TFT_YELLOW); // 背景色を考慮して画像を表示

  /* M5Displayを使った場合 */
//  showImageByM5Display(img_number); // 画像を表示
//  showImageByM5DisplayBgColor(img_number, TFT_YELLOW); // 背景色を考慮して画像を表示

  img_number++; // 画像のカウントアップ

  // 最後の画像までいったら最初に戻る
  if (img_number >= IMG_MAX) {
    img_number = 0;
  }

  delay(100);
}

// スプライトを使って画像を表示させる関数
void showImageBySprite(int number) {
  _sprite.pushImage(0, 0, IMG_WIDTH, IMG_HEIGHT, IMG_DATA[number]);
  _sprite.pushSprite(0, 0);
}

// スプライトを使って背景色を考慮して画像を表示させる関数
void showImageBySpriteBgColor(int number, uint32_t bg_color) {
  // 背景色で塗りつぶし
  _sprite.fillSprite(bg_color);

  int count = 0;
  for (int y = 0; y < IMG_HEIGHT; y++) {
    for (int x = 0; x < IMG_WIDTH; x++) {
      // 背景が白なので背景以外の色をpixel単位で設定
      uint32_t color = IMG_DATA[number][count];
      if (TFT_WHITE != color) {
        _sprite.drawPixel(x, y, color);
      }
      count++;
    }
  }

//  // TFT_WHITEの色を透明にして(0,0)を左上の位置として表示
//  _sprite.pushSprite(0, 0, TFT_WHITE);

  // (0,0)を左上の位置として表示
  _sprite.pushSprite(0, 0);
}

// M5Displayを使って画像を表示させる関数
void showImageByM5Display(int number) {
  M5.Lcd.pushImage(0, 0, IMG_WIDTH, IMG_HEIGHT, IMG_DATA[number]);
}

// M5Displayを使って背景色を考慮して画像を表示させる関数
void showImageByM5DisplayBgColor(int number, uint32_t bg_color) {
  M5.Lcd.fillScreen(bg_color);
  M5.Lcd.pushImage(0, 0, IMG_WIDTH, IMG_HEIGHT, IMG_DATA[number], TFT_WHITE);
}

 このサンプルをM5StickC Plusに書き込むと1で準備した画像がアニメーションとしてループ表示されます。

 特に注目してほしいのが「showImageBySpriteBgColor」関数を使って表示させた場合と「showImageByM5DisplayBgColor」関数を使って表示させた場合です。

 showImageBySpriteBgColor関数はスプライトで表示させているので、スプライト全体を背景色にした後に特定の色を除いたピクセルの色を設定した後に画面に表示させています。
 一方、showImageByM5DisplayBgColor関数はM5Displayで表示させているので、画面全体を背景色で表示させた後に特定の色を透過した画像を画面に表示させています。

 実際にこの2つを比べてみると、showImageBySpriteBgColor関数の方が滑らかに表示されており、showImageByM5DisplayBgColor関数の方はちらついて表示されていると思います。

 このようになる理由は2で説明した通り、スプライトはすべての処理をした後にpushSpriteで画面に表示させている(画面表示は1回のみ行っている)のに対して、M5Displayの方はM5.Lcd.fillScreenで背景色を画面に表示した後にさらに画像を表示させている(画面表示を2回行っている)からです。このためM5Displayの方は背景色だけが表示される瞬間が生まれ、ちらついているように見えてしまいます。

 なので用途に合わせてスプライトかM5Displayを使い分ける必要があります。


 以上がM5StickC Plusで連番画像を使ったアニメーションを表示する方法になります。

 SDカードなど外部記憶装置がなくても画像をテキストデータに変換してしまえば表示させることができるとわかったので、色々と面白いことができそうです。


・参考資料