2006 年 1 月 31 日 19 時 8 分

実践


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


今まで出てきた画像処理をコード化してみよう。
BMP を読み込み、処理し、BMP を書き出す流れとなる。

まずは、BMP を読み込むルーチンを作ろう。
処理の特性上、24、32 ビットの BMP に限定することとする。
面倒なので、標準入力から BMP を読み込むこととしよう。

RGB を独立してデータを持たせると非常に大きくなるので、
1 ピクセルは、RGB を格納した 32 ビット値とし、
1 行は、ピクセルの配列とする。
データ全体は、行のリファレンスの配列としよう。
つまり、関数は戻り値として、行配列のリファレンスを返す。

my $data = read_bmp;

こうしておけば、scalar(@$data) で画像の高さが、
$data->[0] で先頭行へのリファレンスが、
scalar(@{$data->[0]}) で、先頭行の幅(=画像の幅)が、
$data->[0][0] で、左上のピクセル値が取り出せるわけだ。


sub read_bmp () {
    use integer;

    my $chunk;
    binmode(STDIN);

    # BITMAPFILEHEADER を読み込む。
    read STDIN, $chunk, 14 or die $!;
    my ($signature, $data_offset) = unpack('a2x8V', $chunk);

    # フォーマットのチェック。
    $signature eq 'BM' or die 'Invalid BITMAP.';

    # BITMAPINFOHEADER を読み込む。
    read STDIN, $chunk, 40 or die $!;
    my ($bih_size, $width, $height, $planes, $bit_count, $compression) = unpack('VVVvvVx20', $chunk);

    # フォーマットのチェック。
    $planes == 1 or die 'Invalid BITMAP.';
    $bit_count == 24 or $bit_count == 32 or die 'Not supported bit count.';
    $compression == 0 or die 'Not supported compression.';

    # 残りのヘッダを無視し、データ部まで読み飛ばす。
    if ($data_offset - 54) {
        read STDIN, $chunk, $data_offset - 54 or die $!;
    }

    # データ読み取り開始。
    my @data = ();

    # ピクセルデータのサイズ+詰め物サイズ=行のサイズ。
    my $part_size = $width * $bit_count / 8;
    my $padding_size = (-$part_size) & 0x3;

    # 解説していなかったが、高さが負の場合、
    # トップダウンの BMP である。(上から下に格納)
    my $is_top_down  = $height < 0;

    # 高さを正の値に直しておく。
    $height = -$height if $is_top_down;

    foreach (1 .. $height) {
        # ピクセルデータ読み出し。
        read STDIN, $chunk, $part_size or die $!;

        # 1 行を RGB の 32 ビット整数配列にデコード。
        my $line;
        if ($bit_count == 32) {
            # 32 ビットなので自然にデコードできる。
            $line = [ unpack('V*', $chunk) ];
        } else {
            $line = [];
            # 3 バイトずつ切り出し、最後に 0 のバイトを
            # つけて 32 ビット長とし、デコード。
            push @$line, unpack('V', $& . "\0")
              while $chunk =~ /.{3}/sg;
        }
        if ($is_top_down) {
            # トップダウンの場合は先頭行から。
            push @data, $line;
        } else {
            # ボトムアップ(通常)の場合は末尾行から。
            unshift @data, $line;
        }

        # 詰め物を読み捨てる。
        if ($padding_size) {
            read STDIN, $chunk, $padding_size or die $!;
        }
    }

    \@data;
}

ネガポジ反転なら、以下のように書ける様になる。

use integer;

my $data = read_bmp;

foreach my $line (@$data) {
    foreach my $pixel (@$line) {
        my ($red, $green, $blue) =
          unpack('xCCC', pack('N', $pixel));

        $red = 255 - $red;
        $green = 255 - $green;
        $blue = 255 - $blue;

        $pixel =
          unpack('N', pack('xCCC', $red, $green, $blue));
    }
}



Copyright (c) 1994-2007 Project Loafer. All rights reserved.