2011年12月5日月曜日

( Qt C++ )バイナリの生データの取り出し


ほんと景気悪いですね最近。近くのスーパーの店員がポンポン消えていってます。あぁクビになったんだなぁとこのクソ経営者爆発しろとしみじみ思っている今日このごろです。

今日は表題のとおりバイナリ生データを扱っていきます。C++ GUI Programming with Qt4にサンプルが載っていなかったのでAchtung! Binary and  Character Dataに記載されたサンプルを一部変えて使用します。というのもサンプルではreadRawBytes()やat()等を使っていますが,これが何故か使えません。(バージョン違いだと思います。そのほか、理由がわかり次第こちらに載せます。)
とりあえず、まずはコードを

QSize getPngImageSize( QIODevice *device )
{
    quint32 width = 0;
    quint32 height = 0;
    char signature[8];
    char chunkType[4];

    QDataStream in( device );
    in.readRawData( signature, 8 );//引数は*char, length。*charは先に確保しとく

    if ( memcmp(signature, "\211PNG\r\n\32\n", 8) == 0 ) {
        while ( !in.atEnd() ) {
            device->seek(device->pos() + 4);
            in.readRawData( chunkType, 4 );//readRawByteの代替
           if ( memcmp(chunkType, "IHDR", 4) == 0 ) {
                in >> width >> height;
                break;
            }

            // jump to the next chunk
            device->seek(device->pos() + 4);//次のポジションに移動
        }
    }
    return QSize( width, height );
}

この関数は引数がPNGの場合にその画像サイズを取得して返すというものです。(ちなみにQIODeviceのサブクラスの一つがQFileです。)
宣言部の説明はそのまんまなので省きます。この後QDataStreamを引数のdeviceで作成します。このdeviceはQFileの実体を渡しています。(下記sample()のとおり)
作成後、readRawData(...)より生データを取り出していきます。一応リファレンスより

int QDataStream::readRawData ( char * s, int len )
最大lenの長さのバイトをsに読み取る。読み取ったバイト数を返す。もしエラーなら-1を返す。sは事前に割り当てる必要がある。データはエンコードされない。(リファレンスへ)

話を戻します。その後、memcmpでメモリの内容を比較。(要はPNGか?っていう比較)
PNGフォーマットならループに入ります。in.atEnd()は終わりじゃないかどうかを確認しています。
次のdevice->seek(device->pos()+4)は読み取り位置を移動しています。device->posで現在位置を求められるので、それに4足すことで現在地から4進んだ位置に進めています。進めた後再度readRawDataで読み取り。ここでまたmemcmpでchunkTypeと"IHDR"を比較しています。もし同じなら縦横のサイズを取得し、ループを抜けます。違うなら再度device->seekで読み取り位置を移動させてループを続けます。
最後にPNGイメージのサイズを返します。

以下がgetPngImageSizeの使用例です。

void sample()//上のgetPngImageSizeを使用する関数
{

    QFile file(tr("/home/ubuntu001/io.png"));
    if ( file.open(QIODevice::ReadOnly) )//読込のみで開けたかチェック
   {
        QSize size = getPngImageSize( &file );//サイズ取得 
        ui->lineEdit->setText(tr("%1: %L2 x %L3").arg("io.png").arg(size.width()).arg(size.height()));/*size表示*/
    }
    else
    {
        ui->lineEdit->setText("error");//オープン失敗
    }
}

まずQFileを作成しています。tr()は入門書によると翻訳に使われるようなのでつける癖をつけておいたほうがいいようです。(翻訳不要なら普通の""でいいそうです。)
次にfile.open(...)で開けたかチェックし、エラーが無ければ先ほど作成したQFileを引数にしてgetPngImageSizeを実行しています。最後にGUI部品のlineEditにサイズを表示させています。なお、tr().arg().arg()...はtr内に記述された書式に引数として指定しています。つまり.argは書式に引数を指定しているということです。(この辺りは後の記事で詳しく説明)
以上です。
次回はバイナリ生データの書き込みをやります。