2012年2月22日水曜日

小規模組み込みシステムでも使えるBMPライブラリ(Tiny BMP I/O)

はじめに

昨年の今頃に着手したBlacktank LPC1769からは複数の派生プロジェクトが生まれています。
Natural Tiny Shell (NT-Shell)もその一つですが、今回は小規模組み込みシステムでも使えるBMPライブラリ(Tiny BMP I/O)について触れます。


これは何?




Tiny BMP I/Oは、BMP Data StreamからBMPイメージを読み込んでPixel Storageにピクセル値を格納したり、Pixel Storageからピクセル値を読み込んで、BMP Data StreamにBMPイメージを書き込んだりすることのできるライブラリです。

一般に書かれたライブラリでは、「BMPデータがどこに格納されているのか=ファイルに格納されている」、「ピクセルデータがどこに格納されているのか=メモリ」という暗黙の前提が使われている事が多いのですが、Tiny BMP I/Oはそれらをシステム依存として捉えて設計してあります。

システムに依存する部分をきちんと切り出す事で「1つのライブラリでSDからも、フラッシュからもSRAMからだって読める!」、「カメラから読んだ画像を簡単にBMPファイルにできる!」など、色々な用途で使用する事が可能になります。

例えば、「SDカードにあるファイルを読み込んで液晶に表示したい」とか「カメラから取り出した映像をファイルに書き込みたい」などはすぐに考える事のできる応用例の1つです。


また、それだけではつまらないのでbmpimg_tというキャンバスに対して描画可能なプチユティリティも装備しました。描きたいパターンをさっと実現してファイル化する事が可能です。


int bmpimg_draw_box(bmpimg_t *p, const int x1, const int y1, const int x2, const int y2, bmpcol_t *color);
int bmpimg_fill_box(bmpimg_t *p, const int x1, const int y1, const int x2, const int y2, bmpcol_t *color);
int bmpimg_draw_string(bmpimg_t *p, const int x, const int y, const int size, const char *text, bmpcol_t *color);
int bmpimg_draw_line(bmpimg_t *p, const int x1, const int y1, const int x2, const int y2, bmpcol_t *color);
int bmpimg_draw_pixel(bmpimg_t *p, const int x, const int y, bmpcol_t *color);

機能
  • 24ビットBMPファイルの入出力に対応。
  • 特定プラットフォームに非依存。
  • 便利な描画用ユティリティ付属。
  • その他。
簡単に使えます

Tiny BMP I/Oはシステムに依存する以下の機能を実装するだけで簡単に使えます。
  • データのリード、ライト関数
  • ピクセルのリード、ライト関数
データのリード、ライト関数は、BMPのイメージが直列化されて格納されている領域を、ストリームとして入出力するための機能を提供するものです。
ピクセルのリードライト関数は、画像を実際に表示したりするための入出力機能を提供するものです。

ポイント

この手のライブラリは世の中に溢れています。
「また車輪の再開発か」と言われそうですが、必ずしもそうではありません。

ここではTiny BMP I/Oで考慮されている点を挙げてみます。
  1. データがどこに格納されているのか?に依存しない。
  2. 画像ピクセルがどこに、どのように格納されているのか?に依存しない。
データがどこに格納されているのか?に依存しない。

小規模組み込みシステムでは、データが必ずファイルシステム上に存在するとは限りません。
一般に流通しているライブラリの多くは「データはファイルシステム上のファイルとして存在する事」を前提に設計実装されています。これらのライブラリを使用する場合、小規模組み込みシステムにファイルシステムとlibcをポートする事になります。

Tiny BMP I/Oは、データがどこに格納されいるのか?についての前提を持ちません。
それがファイルシステム上であろうが、フラッシュメモリ上であろうが、SRAM上であろうが、何の問題もありません。どこに格納されていても

画像ピクセルがどこに、どのように格納されているのか?に依存しない。

ここで言う「画像ピクセル」とは、データを読み込んで解釈した映像の1つ1つのピクセルを指します。
「画像ピクセルがどこに格納されているのか?」ですが、普通に考えるとメモリ上に格納するわけですが、ちょっとした画像ファイルでも結構な容量になってしまいます。例えば、1920x1080ピクセルでR, G, Bの各レイヤーが8ビットだったとすると、1920x1080x1x3=6,220,800[Bytes] (約6[MB])になってしまいます。潤沢なメモリが対象システムに存在するかどうかはシステム次第です。

読み込んだデータをそのままディスプレイに表示させたいだけの場合もあります。
この場合、わざわざ読み込んだデータをメモリ上に展開する必要はありません。
そのまま表示してしまえば良いのです。

巷に出回っているBMPライブラリの多くは、画像ピクセルデータをメモリに展開する事を前提で書かれているため、小規模組み込みシステムでそのまま使用することは難しい事があります。
Tiny BMP I/Oはシステムに対するインターフェースを規定して設計してあります。
このインターフェースさえ守れば、画像ピクセルがどこにどのように格納されていても問題ありません。

ライブラリとサンプルプログラムのダウンロード

ライブラリとサンプルプログラムはここからダウンロードできます。

サンプルプログラムの動作とその出力画像

サンプルプログラムでは、インターフェースの動作を的確に示すために、汎用プラットフォームで動作するコードを示しています。このコードを見れば、Tiny BMP I/Oが規定している入出力インターフェースを簡単に理解することができます。

実際にTiny BMP I/Oを使って出力したサンプル画像を示します。



サンプルプログラムでは、汎用プラットフォーム向けの実装を提供していますが、Tiny BMP I/Oのインターフェースに従った実装をプラットーフォーム向けに提供しているだけです。

プラットフォームに依存した部分を書き換えれば色々なプラットフォームで使うことができます。

/**
 * @file sample.c
 * @author Shinichiro Nakamura
 * @brief 小規模組み込みシステム向けBMP I/Oのサンプル実装。
 */

/*
 * ===============================================================
 *  Tiny BMP I/O Module
 *  Version 0.0.1
 * ===============================================================
 * Copyright (c) 2010-2011 Shinichiro Nakamura
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * ===============================================================
 */

#include 
#include 
#include "pattern.h"

int main(int argc, char **argv);

/**
 * @brief 独自に規定したキャンバス構造。
 * @details
 * このサンプルでは、汎用プラットフォーム向けなのでメモリ上に画像を格納する。
 */
typedef struct {
    int w;              /**< 横方向サイズ。 */
    int h;              /**< 縦方向サイズ。 */
    bmpcol_t *buffer;   /**< バッファへのポインタ。 */
} canvas_t;

/**
 * @brief ストリームからデータを読み込む。
 * @details
 * インターフェースでは、何からどのように読み込むかについて一切感知していない。
 * この関数では、何からどのように読み込むかについて解決する。
 *
 * @param buf バッファへのポインタ。
 * @param size 読み込みバイトサイズ。
 * @param extobj ユーザが指定した拡張オブジェクト。
 */
int func_fread(void *buf, const unsigned int size, void *extobj)
{
    FILE *fp = (FILE *)extobj;
    return fread(buf, size, 1, fp);
}

/**
 * @brief ストリームへデータを書き込む。
 * @details
 * インターフェースでは、何にどのように書き込むかについて一切感知していない。
 * この関数では、何にどのように書き込むかについて解決する。
 *
 * @param buf バッファへのポインタ。
 * @param size 書き込みバイトサイズ。
 * @param extobj ユーザが指定した拡張オブジェクト。
 */
int func_fwrite(const void *buf, const unsigned int size, void *extobj)
{
    FILE *fp = (FILE *)extobj;
    return fwrite(buf, size, 1, fp);
}

/**
 * @brief ピクセル値を書き込む。
 * @details
 * インターフェースでは、何にどのように書き込むかについて一切感知していない。
 * この関数では、何にどのように書き込むかについて解決する。
 *
 * @param x X座標。
 * @param y Y座標。
 * @param r 赤。
 * @param g 緑。
 * @param b 青。
 * @param extobj ユーザが指定した拡張オブジェクト。
 */
void func_pixel_write(const int x, const int y, const uint8_t r, const uint8_t g, const uint8_t b, void *extobj)
{
    canvas_t *canvas = (canvas_t *)extobj;
    bmpcol_t *buffer = canvas->buffer + (canvas->w * y) + x;
    buffer->r = r;
    buffer->g = g;
    buffer->b = b;
}

/**
 * @brief ピクセル値を読み込む。
 * @details
 * インターフェースでは、何からどのように読み込むかについて一切感知していない。
 * この関数では、何からどのように読み込むかについて解決する。
 *
 * @param x X座標。
 * @param y Y座標。
 * @param r 赤。
 * @param g 緑。
 * @param b 青。
 * @param extobj ユーザが指定した拡張オブジェクト。
 */
void func_pixel_read(const int x, const int y, uint8_t *r, uint8_t *g, uint8_t *b, void *extobj)
{
    canvas_t *canvas = (canvas_t *)extobj;
    bmpcol_t *buffer = canvas->buffer + (canvas->w * y) + x;
    *r = buffer->r;
    *g = buffer->g;
    *b = buffer->b;
}

int main(int argc, char **argv)
{
    const int imgw = 1280;
    const int imgh = 720;
    canvas_t canvas;
    bmpimg_t bmpimg;
    FILE *fp;

    /*
     * 画像ピクセルを格納する領域を確保する。
     */
    canvas.w = imgw;
    canvas.h = imgh;
    canvas.buffer = (bmpcol_t *)malloc(sizeof(bmpcol_t) * imgw * imgh);

    /*
     * 開始処理。
     *
     * ピクセル入出力関数を渡して初期化する。
     * ユーザが指定可能な拡張オブジェクトに、独自に規定したキャンバスを渡しておく。
     */
    bmpimg_open(&bmpimg, imgw, imgh, func_pixel_write, &canvas, func_pixel_read, &canvas);

    /*
     * サンプルの実装では、ファイルから読み込む。
     */
    fp = fopen("input.bmp", "rb");
    if (fp != NULL) {
        bmpimg_bmp_read(&bmpimg, func_fread, fp);
        fclose(fp);
    }

    {
        /*
         * パターン1を書き込む。
         */
        pattern_sample1(&bmpimg);

        /*
         * サンプルの実装では、ファイルに書き込む。
         */
        fp = fopen("sample1.bmp", "wb");
        if (fp != NULL) {
            bmpimg_bmp_write(&bmpimg, func_fwrite, fp);
            fclose(fp);
        }
    }

    {
        /*
         * パターン2を書き込む。
         */
        pattern_sample2(&bmpimg);

        /*
         * サンプルの実装では、ファイルに書き込む。
         */
        fp = fopen("sample2.bmp", "wb");
        if (fp != NULL) {
            bmpimg_bmp_write(&bmpimg, func_fwrite, fp);
            fclose(fp);
        }
    }

    /*
     * 終了処理。
     */
    bmpimg_close(&bmpimg);

    /*
     * 画像ピクセルを格納する領域を破棄する。
     */
    free(canvas.buffer);

    return 0;
}