概要
Raspberry Pi ZeroでGUIを使わずにCUIで色々やりたいです(非力なRaspberry Pi ZeroにGUIは重すぎる)。
たまに画像を表示したいのですが、そんなときは fbi
を使います。
でも、自力で画像を表示するプログラムを作ってみたい。
ということで作ってみました。
作るのは面倒なので画像を見たいだけなら
こんな感じで fbi
を使うとCUIでも簡単に画像見れます。
fbi
の使い方はググってください。
$ sudo apt install fbi
$ fbi hogehoge.jpg
ここでやること
まず以下の動作確認をしたのは Raspberry Pi Zero WH です。
他の機種でも大きくは変わらないと思いますが、もしかするとうまくいかないかもしれません。
で、以下でやることは、、
- jpeg を読み込む(C言語/libjpeg使用)
- 読み込んだ画像をフレームバッファ用に変換
- フレームバッファに書き出す
です。
フレームバッファの話
Raspberry Pi のフレームバッファは /dev/fb0
なので、ここに所定のフォーマットで値を書き込んでいくとCUIの画面を書き換えることができます。
フレームバッファのフォーマット
0RGB0RGB0RGB... と1ピクセル4バイト(0:1バイト(ダミー?)、R:1バイト、G:1バイト、B:1バイト)とフレームバッファのサイズ分だけ並んでいるようです。
フレームバッファのサイズ
フレームバッファのサイズは fbset
で確認できます。私の環境では 1824x984 でした。
$ fbset
mode "1824x984"
geometry 1824 984 1824 984 32
timings 0 0 0 0 0 0 0
rgba 8/16,8/8,8/0,8/24
endmode
フレームバッファのデータを読み込んでみる(CUI画面のスクリーンショット)
フレームバッファのデータをPPMという形式に直すプログラム。
ヘッダとして P6\n画像の幅 画像の高さ\n最大輝度
を足して、1ピクセル4バイトのデータを3バイト(0RGB0RGB... → RGBRGB...)に直してます。
# include <stdio.h>
# include <stdint.h>
# define WIDTH 1824
# define HEIGHT 984
typedef uint32_t pixel;
int main()
{
pixel p;
printf("P6\n%d %d\n%d\n", WIDTH, HEIGHT, 255);
for(int i=0; i < WIDTH * HEIGHT; i++){
fread(&p, sizeof(pixel), 1, stdin);
fputc((p >> 16) & 0xFF, stdout);
fputc((p >> 8) & 0xFF, stdout);
fputc((p >> 0) & 0xFF, stdout);
}
}
これで以下のようにすると、CUI画面のスクリーンショットを取ることができます。
WIDTH
と HEIGHT
の定義は自分の環境に合わせないと画像が崩れますのでご注意を。
(以下同様)
$ gcc fb2ppm.c -o fb2ppm
$ ./fb2ppm < /dev/fb0 > snapshot.ppm
PPM形式の画像は最初に紹介した fbi
でも見れますし、WindowsではIrfanViewなどのソフトでも見ることができます。
ヘッダを部分を外して4バイト(0x00RRGGBB)に直して /dev/fb0
に書き戻してやるとCUI画面に表示することもできます。
フレームバッファを塗りつぶす
# include <stdio.h>
# include <stdint.h>
# define WIDTH 1824
# define HEIGHT 984
typedef uint32_t pixel;
int main()
{
pixel p = 0x00000000;
for(int i=0; i < WIDTH * HEIGHT; i++){
fwrite(&p, sizeof(pixel), 1, stdout);
}
}
からの
$ gcc fillFb.c -o fillFb
$ fillFb > /dev/fb0
で、画面が真っ黒に。
p
の値は 1バイトずつ 0x00RRGGBB な感じで値を変えてやると好きな色で塗りつぶせます。
いちいち標準出力を /dev/fb0
に渡してやるのも面倒なので、
# include <stdio.h>
# include <stdint.h>
# define WIDTH 1824
# define HEIGHT 984
typedef uint32_t pixel;
int main()
{
pixel p = 0x00000088;
FILE *fp;
fp = fopen("/dev/fb0", "w");
for(int i=0; i < WIDTH * HEIGHT; i++){
fwrite(&p, sizeof(pixel), 1, stdout);
}
fclose(fp);
}
みたいなのももちろんありです。
いよいよJPEGをフレームバッファに書き出してみる
その前に libjpeg
のインストール
$ sudo apt install libjpeg-dev
で、、(ここに全部貼るにはちょっと長いですが、、)
# include <stdio.h>
# include <stdlib.h>
# include <stdint.h>
# include <sys/stat.h>
# include <jpeglib.h>
# define WIDTH 1824
# define HEIGHT 984
typedef uint32_t pixel;
int main(int argc, char * const argv[])
{
// 第1引数は JPEGファイル名(入力)
if(argc < 2){
printf("err: arg cnt\n");
return -1;
}
char *jpgFileName = argv[1];
FILE* fpJpg = fopen(jpgFileName, "rb");
if (fpJpg == NULL) {
printf("err: jpeg filename: %s\n", jpgFileName);
return -1;
}
// JPEGファイル読み込み
struct jpeg_decompress_struct jpgInfo;
jpeg_create_decompress(&jpgInfo);
struct jpeg_error_mgr jpgErr;
jpgInfo.err = jpeg_std_error(&jpgErr); // 既定値
jpeg_stdio_src(&jpgInfo, fpJpg);
jpeg_read_header(&jpgInfo, TRUE);
jpeg_start_decompress(&jpgInfo);
// 読み込みメモリ領域確保
JSAMPARRAY jpgData = (JSAMPARRAY)malloc(sizeof(JSAMPROW) * jpgInfo.output_height);
for(int i = 0; i < jpgInfo.output_height; i++){
jpgData[i] = (JSAMPROW)calloc(sizeof(JSAMPLE), jpgInfo.output_width * jpgInfo.output_components);
}
// 画像データ
while(jpgInfo.output_scanline < jpgInfo.output_height){
jpeg_read_scanlines(&jpgInfo, jpgData + jpgInfo.output_scanline, jpgInfo.output_height - jpgInfo.output_scanline);
}
// JPEGライブラリの後片付け
jpeg_finish_decompress(&jpgInfo);
jpeg_destroy_decompress(&jpgInfo);
fclose(fpJpg);
// フレームバッファ用のメモリ領域確保
pixel *fb = (pixel*)malloc(WIDTH * HEIGHT * sizeof(pixel));
pixel *cfb;
for(int y=0; y<HEIGHT && y<jpgInfo.output_height; y++){
cfb = fb + y * WIDTH;
for(int x=0; x<WIDTH && x<jpgInfo.output_width; x++){
// JPEGデータは1ピクセル3バイト、フレームバッファは4バイト
*cfb = (pixel)((jpgData[y][x*3] << 16) + (jpgData[y][x*3+1] << 8) + jpgData[y][x*3+2]);
cfb++;
}
}
// フレームバッファに書き出し
FILE* fp = fopen("/dev/fb0", "w");
fwrite(fb, sizeof(pixel), WIDTH*HEIGHT, fp);
fclose(fp);
// メモリ解放
free(fb);
for(int i = 0; i < jpgInfo.output_height; ++i){
free(jpgData[i]);
}
free(jpgData);
return 0;
}
エラー処理とかすっ飛ばしてます。
大きい画像は画面からはみ出ます。
libjpeg
についても書くと長くなりすぎるのでまた今度。
要するに JPEG 画像を読み込んで、1ピクセルごとにフレームバッファの 0x00RRGGBB 形式に直して、/dev/fb0
に書き込んでやるという処理になっています。
ビルド時は -ljpeg オプションを付けるのを忘れずに、、
$ gcc viewJpeg.c -o viewJpeg -ljpeg
$ viewJpeg hogehoge.jpg
hogehoge.jpg がCUI画面上に表示されれば成功です。