libtiffによるグラフィックス・プログラミング

TIFF画像を処理するためのCライブラリー

TIFFは、非常に普及してはいるものの、かなり複雑なラスター画像フォーマットです。TIFF仕様を標準ANSI Cで実現しているLibtiffは、無料で提供されており、数多くのオペレーティング・システムで利用できます。本稿では、TIFFの落とし穴をいくつか紹介するとともに、libtiffライブラリーの使い方を案内したいと思います。また、モノクロ画像を処理するときのlibtiffの使い方の例も紹介します。

Michael Still (mikal@stillhq.com), Senior software engineer, Tower Software

Michaelは、数年間オーストラリアの政府機関向けに大規模な画像データベースの管理や開発を行うなど、画像処理の分野に従事してきました。現在は世界最先端のEDMSやTRIMというレコード管理パッケージの製造を行うTower Software社に勤務しています。またオープン・ソースのPDF生成APIであるPandaや、他のオープンソース・コードの開発者でもあります。



2002年 3月 01日

TIFF (Tag Image File Format) は、もともとAdobeによって開発されたラスター画像フォーマットです。ラスター画像フォーマットは、直線や曲線などの基本要素の長さや位置を記録するのではなく、ピクセルの状態を表すビットマップとして絵を保存します。Libtiffは、TIFF仕様の標準的な実装コードの1つであり、その処理速度や処理能力、簡単にソースを入手し参照できることから、今日、広く使用されています。

本稿では、モノクロのTIFF画像を扱うことにしますが、今後の記事でカラー画像について扱うことになるかもしれません。

TIFFの課題

ファイル・フォーマットの仕様は、ほとんどが、そのファイルの表現方法に関する基本的なルールを定義します。たとえば、PNG文書 (TIFFと競合する仕様) は、つねにビッグ・エンディアンです。これに対してTIFFは、そうしたものを規定していません。以下に、基本的事項と思われる仕様で、TIFFが規定していないものをいくつか挙げておきます。

  • バイト順: ビッグ・エンディアンかリトル・エンディアンか
  • 画像バイト中のビットの埋め込み順: MSBが最初かLSBが最初か
  • 白黒のピクセル値の意味: 0は黒か白か

手元にあるデータに何か変換を加える必要がまずありませんので、TIFFファイルを作成するのは、非常に簡単なことです。しかし、このことは、他のアプリケーションで作成されたランダムなTIFFを読み取るのが、非常に難しいものになりうることを意味してもいます。したがって、信頼性のある製品になったことを十分に確信するためには、いろいろな組み合わせのすべてに対応するコードを作成しなければなりません。

それでは、TIFFフォーマットのこれらすべての組み合わせを読み取ることのできるアプリケーションにするには、どんな記述をすればよいのでしょうか。最も重要なことは、読み取ろうとしている画像データのフォーマットについて決して前提を設けてはならないという点です。


TIFFファイルの作成

まずは、TIFFファイルの書き出しの方法から始めます。その後、自分のプログラムにTIFFファイルを読み戻す手順を示します。

書き出しの基礎

ビットマップは、一般に、コード内では文字配列として表現されます。これは、ほとんどのオペレーティング・システムで、1文字が1バイトにうまく対応するからです。リスト1では、libtiffのセットアップを行い、画像を収めるための単純なバッファーを作成しています。後でそのバッファーを使ってディスクへの書き出しを行います。このコードは、write-infrastructure.cとしてダウンロードできます (本稿末の参考文献参照)。

リスト1. 基礎部分のセットアップ (write-infrastructure.c)
#include <stdio.h>
#include <tiffio.h>
int
main (int argc, char *argv[])
{
  char buffer[32 * 9];
}

上のコードは、まったく簡単なものです。libtiffを使うには、ヘッダー・ファイルtiffio.hをインクルードすればよいだけです。コンパイルするには、コマンドgcc foo.c -o foo -ltiff -lm を使用します。-ltiff は、libtiffという名前のライブラリーをインクルードするためのコマンドで、ライブラリーはライブラリー・パスになければなりません。ライブラリーを明示的に指定したときには、数学ライブラリーである-lm も付加する必要があります。ここで定義したcharバッファーはモノクロ画像となるものですので、次に何か画像を定義したいと思います。

画像の書き出し

退屈な例の埋め合わせとして、シドニー・ハーバー・ブリッジの絵として史上最悪かもしれない絵をお見せしたいと思います。リスト2では、画像は最初からバッファーに入れてあり、必要なのは、ファイルをディスクに保存することだけです。この例では、まずTIFF画像を書き出しモードでオープンしておき、そのファイルに画像を入れます。

なお、簡単のために、画像の実際の16進コードは省略してあります。関心のある方のために、ダウンロード版のwrite.cにはそれも含めています (参考文献参照)。

リスト2. 書き出しのコード (write.c)
#include <stdio.h>
#include <tiffio.h>
int main(int argc, char *argv[]){
  // Define an image
  char buffer[25 * 144] = { /* boring hex omitted */ };
  TIFF *image;
  // Open the TIFF file
  if((image = TIFFOpen("output.tif", "w")) == NULL){
    printf("Could not open output.tif for writing\n");
    exit(42);
  }
  // We need to set some values for basic tags before we can add any data
  TIFFSetField(image, TIFFTAG_IMAGEWIDTH, 25 * 8);
  TIFFSetField(image, TIFFTAG_IMAGELENGTH, 144);
  TIFFSetField(image, TIFFTAG_BITSPERSAMPLE, 1);
  TIFFSetField(image, TIFFTAG_SAMPLESPERPIXEL, 1);
  TIFFSetField(image, TIFFTAG_ROWSPERSTRIP, 144);
  TIFFSetField(image, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX4);
  TIFFSetField(image, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
  TIFFSetField(image, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB);
  TIFFSetField(image, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
  TIFFSetField(image, TIFFTAG_XRESOLUTION, 150.0);
  TIFFSetField(image, TIFFTAG_YRESOLUTION, 150.0);
  TIFFSetField(image, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
  // Write the information to the file
  TIFFWriteEncodedStrip(image, 0, buffer, 25 * 144);
  // Close the file
  TIFFClose(image);
}

(筆者のLinuxマシンでは、xview コマンドを使って出力画像を表示することはできません。実は、このプログラムで表示できるようなG4ファックスの圧縮モノクロ画像の例を見つけ出すことができなかったというのが本当のところです。本稿の発行時になっても、この問題を解決することはできませんでした。)

いずれにせよ、このサンプル・コードがlibtiffのAPIの使い方の基本です。以下、いくつかの点について解説しておきます。

  • libtiffに与えたりlibtiffから返されるバッファーは、どれも1バイトで8ピクセルを表しています。その条件で、処理したいピクセルを抜き出さなければなりません。このとき、マスクを使ったり、右シフト、左シフト演算子を使うと便利です。
  • TIFFOpen 関数は、おなじみのfopen 関数とよく似ています。
  • 画像を書き出すには、まず、いろいろなフィールドに値を設定する必要があります。これらのフィールドは、libtiffに画像のサイズや形、あるいは画像内のデータの圧縮方法についての情報を指示します。これらのフィールドは、画像データを最初にlibtiffに渡す前に設定する必要があります。値を設定できるフィールドは他にもたくさんあるのですが、ここでは、ほぼ最低限のものだけ使用しています。
  • TIFFWriteEncodedStrip が、実際に画像をファイルに挿入するための関数呼び出しです。この呼び出しは、圧縮されていない画像データをファイルに挿入します。つまり、libtiffが、ファイルに画像データを書き出す際に、代わりに画像データの圧縮を行ってくれるというわけです。すでにデータが圧縮済みなのなら、TIFFWriteRawStrip を使います。
  • 最後に、TIFFClose でファイルを閉じます。

シドニー・ハーバー・ブリッジの見映えを知りたい方のために、図1にそれを示しておきます。(実はこれはゴマカシで、JPEGに変換した絵です。ほとんどのWebブラウザーはTIFFをサポートしていないため。)

libtiff関数呼び出しについての補足

本稿で取り上げたlibtiffの関数呼び出しについて、もっと詳しく知りたい方は、ライブラリーに添付されている詳細なオンライン・マニュアル (man pages) を参照してください。

オンライン・マニュアルでは大文字・小文字が区別されますので、関数名の大文字・小文字を間違わないようにしてください。たとえば、TIFFOpen が正しく、tiffopen ではありません。

図1. シドニー・ハーバー・ブリッジ。Michael Still作
図1

TIFFファイルの読み取り

TIFFファイルを確実に読み取る作業は、書き出しよりも格段に難しくなります。残念ながら、重要な問題をすべて論じるだけのスペースが本稿にはありませんので、いくつかの問題は、今後の別の記事に残しておくこととします。Web上にも、こうした問題を紹介しているページがたくさんあります。稿末の参考文献に、筆者が重宝しているサイトをいくつか示しておきます。

モノクロのTIFF画像の読み取りを最も複雑にしている問題は、TIFFファイル自体がさまざまな格納方法を許していることにあります。Libtiffは、これらの方法に関して、あまり手助けをしてくれませんので、自分で処理する必要があります。TIFFがサポートしている方法は、single-strip画像、stripped画像、およびtiled画像の3種類です。

single-strip画像
これは、名前からわかるように、stripped画像の特殊ケースです。この画像では、すべてのビットマップが、1個の大きなブロックとして格納されます。筆者の経験では、single-strip画像は、Windowsマシンで信頼性に問題があります。一般に、1本のstripが未圧縮の状態で8 Kバイト以上のデータをとらないことが推奨されており、したがって、モノクロ画像の場合、1本のstipでは65,536ピクセルまでという制約が生じることになります。
stripped (すなわちmultiple-strip) 画像
画像の水平方向のブロックは、いっしょに格納されます。2本以上のstripを垂直方向に合併することで、全体のビットマップを構成します。この考え方を示したのが、 図2 です。
図2. stripに分割されたシドニー・ハーバー・ブリッジ
図2
tiled画像
浴室の壁のように、この画像はtileで構成されます。これを表したのが図3で、ものすごく大きな画像を表す場合に便利です。tileは、画像内の小さな部分を単位として処理を加えたいときにとくに便利です。
図3. tileに分割されたシドニー・ハーバー・ブリッジ
図3

tiled画像は、それほど一般的ではありませんので、本稿ではstripped画像に焦点を絞ることにします。single-strip画像は、multi-strip画像の部分集合にすぎません。

読み取りの基礎

TIFF画像を読み取るときに最も重要なことは、柔軟でなければならないということです。読み取りの例 (下のリスト3 ) も、基本的な考え方は、書き出しの例 (先のリスト2 ) と同じで、大きな違いは、読み取りの場合、入力画像のいろいろな可能性に対処する必要があるという点です。strip分割やtile分割の問題はさておき、最も柔軟でなければならないのは、光度測定の解釈 (photometric interpretation) についてです。幸い、モノクロ画像の場合、2つの光度測定の解釈について考慮するだけで済みます。カラー画像の場合、それにグレースケール画像にもある程度あてはまることですが、もっとずっと多くの解釈があります。

ところで、光度測定の解釈とは、どういうことなのでしょうか。バッファー内の画像の表現は、実際のところ非常に任意性の高いものです。筆者だったら、0を黒として (TIFFTAG_MINISBLACK) ビットマップをコード化するでしょうが、みなさんは、1が黒であるとする (TIFFTAG_MINISWHITE) かもしれません。TIFFでは両方とも許されますので、プログラムは両方の場合を処理できなくてはなりません。下の例では、内部バッファーをMINISWHITE にする必要があるものとし、そのためMINISBLACK の画像を変換することを想定しています。

もう一つ頭に入れておく必要のあることは、埋め込みの順序、すなわち、バイト内の最初のビットが最高位の値なのか最低位の値なのかという点です。リスト3は、これについても、両方を正しく処理しています。筆者は、このバッファーではMSBが先頭にくるようにしています。TIFF画像は、ビッグ・エンディアンでもリトル・エンディアンでもよいことになっていますが、それは、libtiffが処理してくれます。ありがたいことにlibtiffは、さまざまな圧縮アルゴリズムもサポートしていますので、みなさんが、そういったことに頭を煩わす必要はありません。圧縮の問題は、TIFFで格段に難しい領域ですので、この点についてもlibtiffの利用価値があるわけです。リスト3は、read.cとしてダウンロードできます (参考文献参照)。

リスト3. 読み取りのコード (read.c)
#include <stdio.h>
#include <tiffio.h>
int main(int argc, char *argv[]){
  TIFF *image;
  uint16 photo, bps, spp, fillorder;
  uint32 width;
  tsize_t stripSize;
  unsigned long imageOffset, result;
  int stripMax, stripCount;
  char *buffer, tempbyte;
  unsigned long bufferSize, count;
  // Open the TIFF image
  if((image = TIFFOpen(argv[1], "r")) == NULL){
    fprintf(stderr, "Could not open incoming image\n");
    exit(42);
  }
  // Check that it is of a type that we support
  if((TIFFGetField(image, TIFFTAG_BITSPERSAMPLE, &bps) == 0) || (bps != 1)){
    fprintf(stderr, "Either undefined or unsupported number of bits per sample\n");
    exit(42);
  }
  if((TIFFGetField(image, TIFFTAG_SAMPLESPERPIXEL, &spp) == 0) || (spp != 1)){
    fprintf(stderr, "Either undefined or unsupported number of samples per pixel\n");
    exit(42);
  }
  // Read in the possibly multiple strips
  stripSize = TIFFStripSize (image);
  stripMax = TIFFNumberOfStrips (image);
  imageOffset = 0;
  bufferSize = TIFFNumberOfStrips (image) * stripSize;
  if((buffer = (char *) malloc(bufferSize)) == NULL){
    fprintf(stderr, "Could not allocate enough memory for the uncompressed image\n");
    exit(42);
  }
  for (stripCount = 0; stripCount < stripMax; stripCount++){
    if((result = TIFFReadEncodedStrip (image, stripCount,
                                      buffer + imageOffset,
                                      stripSize)) == -1){
      fprintf(stderr, "Read error on input strip number %d\n", stripCount);
      exit(42);
    }
    imageOffset += result;
  }
  // Deal with photometric interpretations
  if(TIFFGetField(image, TIFFTAG_PHOTOMETRIC, &photo) == 0){
    fprintf(stderr, "Image has an undefined photometric interpretation\n");
    exit(42);
  }
  if(photo != PHOTOMETRIC_MINISWHITE){
    // Flip bits
    printf("Fixing the photometric interpretation\n");
    for(count = 0; count < bufferSize; count++)
      buffer[count] = ~buffer[count];
  }
  // Deal with fillorder
  if(TIFFGetField(image, TIFFTAG_FILLORDER, &fillorder) == 0){
    fprintf(stderr, "Image has an undefined fillorder\n");
    exit(42);
  }
  if(fillorder != FILLORDER_MSB2LSB){
    // We need to swap bits -- ABCDEFGH becomes HGFEDCBA
    printf("Fixing the fillorder\n");
    for(count = 0; count < bufferSize; count++){
      tempbyte = 0;
      if(buffer[count] & 128) tempbyte += 1;
      if(buffer[count] & 64) tempbyte += 2;
      if(buffer[count] & 32) tempbyte += 4;
      if(buffer[count] & 16) tempbyte += 8;
      if(buffer[count] & 8) tempbyte += 16;
      if(buffer[count] & 4) tempbyte += 32;
      if(buffer[count] & 2) tempbyte += 64;
      if(buffer[count] & 1) tempbyte += 128;
      buffer[count] = tempbyte;
    }
  }
     // Do whatever it is we do with the buffer -- we dump it in hex
  if(TIFFGetField(image, TIFFTAG_IMAGEWIDTH, &width) == 0){
    fprintf(stderr, "Image does not define its width\n");
    exit(42);
  }
  for(count = 0; count < bufferSize; count++){
    printf("%02x", (unsigned char) buffer[count]);
    if((count + 1) % (width / 8) == 0) printf("\n");
    else printf(" ");
  }
  TIFFClose(image);
}

このコードでは、まず画像をオープンし、処理可能な画像かどうかを調べます。次に、画像のすべてのstripを1つの大きなメモリー・ブロックに読み込みながらアペンドしていきます。必要なら、光度測定の解釈が処理可能なものになるまで、ビットの反転を行ったり、埋め込みの順序が違っている場合には、ビットのスワップを行ったりもします。最後に、画像を16進数の羅列として出力していきます。もちろん、各16進数が画像の8ピクセルぶんを表しています。


まとめ

本稿では、libtiffを使って簡単なモノクロ画像の書き出しと読み取りを行う方法を示すとともに、いくつか注意すべきキーとなる問題を紹介しました。libtiffを使ってコーディングを行うときには、自分の画像を圧縮するのに使用されるアルゴリズムについても、少し頭の片隅に入れておくとよいでしょう。モノクロ画像はG4ファックスでよいのですが、カラー画像にどんなアルゴリズムを使うかは、それぞれのニーズによって違ってきます。

参考文献

  • 本稿で示したソース・ファイルの完全版はダウンロードできます:write-infrastructure.cwrite.cread.c
  • libtiff のWebサイトからは、libtiffのソースをダウンロードできます。それぞれのオペレーティング・システムに対応するバイナリー・パッケージを入手することもできるでしょう。
  • 他のサイトがどこもうまくいかないときは、Adobe TIFF仕様が役に立ちます。
  • wxWindowsの概要 (developerWorks、2001年2月) では、ソフトウェア開発者のMarkus Neifer氏がwxWindows C++ およびPython GUIのツールキットを紹介しています。
  • Javaのグラフィックスをお探しですか ?Javaアプリケーションでのイメージ作成 (developerWorks、2001年2月) では、Ivor Horton氏が低負荷で、簡単なグラフィック・イメージの描画とレンダリングを行う方法を紹介しています。
  • プリンター・ドライバーをお探しの方は、IBM Linuxテクノロジー・センターのOmni printer driverを覗いてみてください。400機種以上のプリンターがサポートされています。
  • developerWorksに掲載されている他のLinux参考文献もご覧ください。
  • developerWorksの他のオープン・ソース関連記事もご覧ください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Open source
ArticleID=228938
ArticleTitle=libtiffによるグラフィックス・プログラミング
publish-date=03012002