LoginSignup
2
4

More than 3 years have passed since last update.

Raspberry PiのCUI画面に直接JPEG画像を表示する(libjpeg)

Posted at

概要

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...)に直してます。

fb2ppm.c
#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画面のスクリーンショットを取ることができます。
WIDTHHEIGHT の定義は自分の環境に合わせないと画像が崩れますのでご注意を。
(以下同様)

$ gcc fb2ppm.c -o fb2ppm
$ ./fb2ppm < /dev/fb0 > snapshot.ppm

PPM形式の画像は最初に紹介した fbi でも見れますし、WindowsではIrfanViewなどのソフトでも見ることができます。
ヘッダを部分を外して4バイト(0x00RRGGBB)に直して /dev/fb0 に書き戻してやるとCUI画面に表示することもできます。

フレームバッファを塗りつぶす

fillFb.c
#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 に渡してやるのも面倒なので、

fillFb2.c
#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

で、、(ここに全部貼るにはちょっと長いですが、、)

viewJpeg.c
#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画面上に表示されれば成功です。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4