LovyanGFX入門 その7 スプライトの中身

概要

前回はスプライトの概要説明で終わってしまいました。今回は実際に使う上での注意点を説明したいと思っています。

色数

lcd.setColorDepth(16);
sprite.setColorDepth(16);

上記のようにLCDとスプライトはsetColorDepth()にて色数を指定することができます。ほとんどの場合にはデフォルトの16ビットで問題はありませんが、変更することが可能です。

指定モード
11ビット(2色)パレットモード
22ビット(4色)パレットモード
44ビット(16色)パレットモード
8 8ビット(RGB332)モード
1616ビット(RGB565)モード
2424ビット(RGB888)モード

一番色数が多いのが24ビットですが、接続されているパネルが24ビット対応でない場合には意味がありません。多くの場合16ビットまでのパネルが接続されています。

8ビットに設定すると色の階調が減るので、データサイズが半分になります。

ただし、上記の順番で描画していきますので、描画先と描画元の色数が違う場合には変換処理が必要になります。一般的に16ビットモードで統一することで、変換が必要なく最速で描画可能です。

とはいっても、場合によっては大きな画像をオンメモリに保存できないですので、色数を落としてオンメモリに乗せることも必要になってきます。

RGB888

わかりやすいようにグラデーションの画像で、色数の差を確認します。こちらはRGB888の元になった画像です。

RGB565

色の境目が見えますね。こちらがデフォルトの画面となります。

RGB332

さすがに階調がおかしくなってきました。原色などのわかりやすい色だけ使うのであればそれほど違和感は無いはずです。

4Bit(16色パレット)

RGB565より画質が落ちていますね。減色処理していますので、減色のパラメーターによって画質は変わってくると思います。

2Bit(4色パレット)

かなりざらついてきました。

1Bit(2色パレット)

さすがに複数色のグラデーションを2色に減色すると無理があります。グラフなどを単色で描画する場合には問題ないと思います。

保存場所

データの保存場所によっても、かなり転送速度に影響が出てきますので、考慮しながら指定する必要があります。

オンメモリ描画

オンメモリに確保したspriteに描画する場合が最速です。直接メモリに書き込んで終わりなので転送する必要がありません。

オンメモリ転送

直接lcdに書き込む場合や、オンメモリに確保したspriteからlcdに描画する場合です。lcdとはSPIで接続されているので転送時間が必要になります。

PSRAM

SPI接続のPSRAM上に確保したspriteからlcdに描画する場合です。PSRAMからデータを取り出すのと、lcdへの描画で2度SPI転送をする必要があります。

フラッシュ

pushImage()関数でlcdに描画する場合、通常フラッシュにデータが保存されており、フラッシュからデータを取り出すのと、lcdへの描画で2度SPI転送をする必要があります。

SPIFFS

フラッシュと同じルートですが、ファイルシステムがあるのでその分のオーバーヘッドがかかります。そのためフラッシュ直接より速度は遅くなるようです。

SD

おそらくこの中ではSDから読み出しが一番遅くなると思います。とくにM5StackはLCDとSDが同じSPIに接続されているため、同時利用することができません。

保存場所周辺機器周辺機器→CPUCPUCPU→LCDLCD
オンメモリ描画CPU
オンメモリ転送CPUSPI経由lcd
PSRAMPSRAMSPI経由CPUSPI経由lcd
フラッシュフラッシュSPI経由CPUSPI経由lcd
SPIFFSフラッシュSPI経由CPUSPI経由lcd
SDSDSPI経由CPUSPI経由lcd

どこに保存しているデータを、どこに保存したデータに描画するのかを意識することが重要です。ただし、オンメモリ転送でも色数などが異なると変換処理が必要になります。PSRAMを利用して転送が遅くなるのと、色数を減らすことでオンメモリに確保して変換が必要になる場合でどちらが良いかは状況によりますので、実験をして早い方にするのか、PSRAM前提で管理が楽な方にするのかなどの選択が必要になります。

上記で紹介したSPI接続の図なども読み返してみてください。

画面が大型化してきているので、基本的に画面全体をオンメモリに確保することは難しくなってきています。逆にM5StickCは画面が小さいので、比較的ルーズに管理してもオンメモリで利用することができると思います。

エンディアン

通常は意識する必要はありませんが、内部はリトルエンディアン(little endian)と呼ばれる順番でデータが保存されています。

RGB565の場合の赤は2進数だと11111 000000 00000で16進数にするとF8 00になります。ただこれはビッグエンディアンの並びなので、リトルエンディアンだと00 F8と逆になります。

0xF800とビッグエンディアンのデータを与えても、内部では00 F8で保存されてしまいます。そのため、そのまま送信すると00 F8の順番でデータが送信されてしまいます。しかしながらパネルはビッグエンディアンであるF8 00で赤が送信されると思っているので色が化けてしまいます。

#define LGFX_AUTODETECT
#include <LovyanGFX.hpp>
const uint16_t imgWidth = 1;
const uint16_t imgHeight = 1;
// RGB565 Dump
const unsigned short img[1] PROGMEM = {
  0xF800
};
static LGFX lcd;
void setup() {
  Serial.begin(115200);
  lcd.init();
  lcd.setSwapBytes(true);
  unsigned long start = millis();
  for (int i = 0; i < 100000; i++) {
    lcd.pushImage(32, 32, imgWidth, imgHeight, (uint16_t*)img);
  }
  Serial.println(millis() - start);
  start = millis();
  lcd.setSwapBytes(false);
  for (int i = 0; i < 100000; i++) {
    lcd.pushImage(64, 32, imgWidth, imgHeight, (uint16_t*)img);
  }
  Serial.println(millis() - start);
}
void loop() {
}

1ピクセルの赤を10万回描画するサンプルです。データは0xF800ですが、内部ではリトルエンディアンの00 F8で保存されます。このデータを描画する場合にはsetSwapBytes(true)に設定して、データの順番を入れ替える必要があります。(00 F8 → F8 00に入れ替えて転送される)

その後同じデータをデフォルト値のsetSwapBytes(false)に設定して呼び出しています。こちらの場合には赤を描画しているはずが、青っぽい色が描画されているはずです。

1105
1093

実行結果です。1行目がsetSwapBytes(true)の場合で1105ミリ秒。2行目がsetSwapBytes(false)の場合で1093ミリ秒でした。つまり、setSwapBytes()はfalseで使わない方が若干早いです。とはいえ、この例だと差分は1%ぐらいしかないです。

BMPなどの画像形式はビッグエンディアンで保存されていることが多いです。1バイト単位でデータを処理する場合にはそのまま利用できます。しなしながら上記のように2バイト単位でデータを管理する場合には内部的にリトルエンディアンのデータですので、setSwapBytes(true)をセットしてから呼び出してください。

また、画面呼び出し系の関数でもBMPに保存する場合には、setSwapBytes(true)にセットしてデータを入れ替えてから取得したほうがそのまま保存すればよいので楽だと思います。

エンディアンは意識して使う必要がありますが、基本はsetSwapBytes(false)のまま使うようにしたほうがよいと思います。色がおかしい場合にはsetSwapBytes(true)にしてみて、治ったらエンディアンの問題です。データを修正するか、setSwapBytes(true)のままで動かすかは状況に応じて選択してください。

まとめ

またしても概要説明で終わってしまいました。高速描画を説明するための前提になりますので、次回ぐらいから高速描画するための方法を説明したいと思います。

続編

コメント