GPiCASE にアナログ時計を表示する

  フレームバッファを使えば、GPiCASE の RetroPie で画面に表示させることが出来るとわかりました。SDL で開発すれば、GPiCASE のフレームバッファに出してくれることもわかりました。

 そこで、試しに GPiCASE に表示する時計を作りました。デジタル時計ではなく、アナログ時計を作ります。

 320 x 240 のサイズで時計の文字盤をビットマップで作り、それを表示するだけのプログラムを作ってみました。テストは普通のRaspberry Pi のデスクトップで確認します。

#include <SDL/SDL.h>

int main(int argc, char* argv[]){
	SDL_Surface* image;
	SDL_Rect rect, scr_rect;

	SDL_Init(SDL_INIT_EVERYTHING);

	SDL_SetVideoMode(320, 240, 32, SDL_HWSURFACE);

	/* 画像読み込み */
	image = SDL_LoadBMP("clock.bmp");

	/* 画像の矩形情報設定 */
	rect.x = 0;
	rect.y = 0;
	rect.w = image->w;
	rect.h = image->h;

	/* 画像配置位置情報の設定 */
	scr_rect.x = 0;
	scr_rect.y = 0;

	/* サーフェスの複写 */
	SDL_BlitSurface(image, &rect, SDL_GetVideoSurface(), &scr_rect);

	/* サーフェスフリップ */
	SDL_Flip(SDL_GetVideoSurface());

	SDL_Delay(3000);

	SDL_FreeSurface(image);

	SDL_Quit();

	return 0;
}

 3 秒だけ時計の文字盤が表示されます。GPiCASE の Raspberry Pi Zero へコピーして実行すると、GPiCASE に文字盤が表示されました。大丈夫です。

 さて、現在時刻を取得して、時計の針を表示しなければなりません。現在時刻の取得は普通の C で取得できますが、秒針の描画をどうしましょうか。

 方法としては、いくつか考えられます。

  • 全ての状態の秒針、分針、時針の画像を準備して重ね合わせる。
  • 針の画像を準備して回転させる。
  • ラインで描画する。

 どれが簡単でしょう。針の画像を回転させて表示するのがいいと思いましたが、SDL + OPENGL で作るか、SDL2 あたりを使うかになるでしょう。SDL だけでは無理です。

 針の描画は後にして、SDL2 に変更してタイマーを使い、デジタルで時刻を表示してみることにしました。

#include <stdio.h>
#include <time.h>
#include <SDL.h>
#include <SDL_ttf.h>

static SDL_Surface* image = NULL;
static SDL_Window *window = NULL;
static TTF_Font *font = NULL;
static SDL_Color gray = {0x60, 0x60, 0x60};

char *get_time(int *h, int *m, int *s) {
    time_t timer;
    struct tm *local;
    static char result[20];
    int ts;

    timer = time(NULL);
    local = localtime(&timer);

    *h = local->tm_hour;
    *m = local->tm_min;
    *s = local->tm_sec;

    sprintf(result, "%02d:%02d:%02d", *h, *m, *s);

    return result;
}

void *redraw(void *param) {
    SDL_Rect rect;
    SDL_Surface *text;
    int h, m, s;

    /* 文字盤の表示 */
    SDL_BlitSurface(image, NULL, SDL_GetWindowSurface(window), NULL);

    /* 時刻の表示 */
    text = TTF_RenderUTF8_Blended(font, get_time(&h, &m, &s), gray);
    rect.x = 87;
    rect.y = 200;
    rect.w = text->w;
    rect.h = text->h;
    SDL_BlitSurface(text, NULL, SDL_GetWindowSurface(window), &rect);
    SDL_FreeSurface(text);
    rect.x = rect.y = 0;
    rect.w = image->w, rect.h = image->w;
    SDL_UpdateWindowSurfaceRects(window, &rect, 1);
}

Uint32 callbackfunc(Uint32 interval, void *param)
{
    SDL_Event event;
    SDL_UserEvent userevent;

    /* コールバックでSDL_USEREVENTイベントをキューに入れる。
    このコールバック関数は一定の周期で再び呼ばれる */

    userevent.type = SDL_USEREVENT;
    userevent.code = 0;
    userevent.data1 = &redraw;
    userevent.data2 = param;

    event.type = SDL_USEREVENT;
    event.user = userevent;

    SDL_PushEvent(&event);
    return(interval);
}

int main(int argc, char* argv[]){
    SDL_Event event;
    SDL_TimerID my_timer_id;

    /* 初期化 */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
        fprintf(stderr, "SDL_Init(): %s\n", SDL_GetError());
        exit(1);
    }
    if (SDL_Init(SDL_INIT_JOYSTICK) >= 0 ) {
        if( SDL_JoystickOpen(0) != NULL )
            SDL_ShowCursor(SDL_DISABLE);
    }
    TTF_Init();

    /* フォント取得 */
    font = TTF_OpenFont("/usr/share/fonts/truetype/fonts-japanese-gothic.ttf", 24);
    if( font == NULL ) {
        printf("Can not open font\n");
        exit(1);
    }
    /* 画像読み込み */
    image = SDL_LoadBMP("clock.bmp");
    if( image == NULL ) {
        printf("Can not load image\n");
        exit(1);
    }

    /* タイマーを作る */
    my_timer_id = SDL_AddTimer(1000, callbackfunc, NULL);

    /* ウィンドウ作成 */
    window = SDL_CreateWindow("SDL clock", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 320, 240, SDL_WINDOW_OPENGL);
    if( window == NULL ) {
        printf("Can not create window\n");
        exit(1);
    }

    /* イベントループ */
    while(1) {
        if( SDL_PollEvent(&event) ) {
            switch (event.type) {
            case SDL_WINDOWEVENT:
                switch (event.window.event) {
                case SDL_WINDOWEVENT_EXPOSED:
                    SDL_UpdateWindowSurface(window);
                    break;
                }
                break;

            case SDL_USEREVENT: {
                void (*p) (void*) = event.user.data1;
                p(event.user.data2);
                break;
            }

            case SDL_JOYBUTTONDOWN:
                if( event.jbutton.button != 7 ) break;
            case SDL_QUIT:
                SDL_Quit();
                exit(0);
                break;
            }
        } else
            SDL_Delay(500);
    }
    /* ここにはこない */
    SDL_Quit();

    return 0;
}

 デジタルでの時計表示はできました。GPiCASE では、スタートボタンで終了します。後はアナログ時計の針の描画です。

 SDL2 の回転は、renderer を使うようです。renderer は初めて使いましたが、画像の表示も renderer を使って表示します。

 針は、矩形にしました。文字盤の画像の左上のピクセルを使って、矩形にコピーして回転する、SDL_RenderCopyEx を使うことにしました。

 また、ただ単純に時計を表示するだけではつまらないので、タイマー機能も追加しました。ソースは整理していません。あとで、別ページでダウンロードできるようにします。

(2021.09.24 追記)

SDL でアナログ時計を作り、GPiCASE で動かす からソースのダウンロードができます。

GPiCASE

Posted by sirius