All-About調査室 Annex

ふと湧いた疑問や巷を漂うウワサを全部アバウト~に調査・検証
<OCNから漂着 流浪の調査室>

プロトタイプ宣言とRootファンクション

2018-04-20 22:00:19 | 4:4:4 Jpeg保存dll作成

ここまでで必要なサブファンクションは全て完成したので
これらをプロトタイプ宣言して、
全体をコントロールするルートファンクションを作れば
4:4:4 Jpeg保存dllは完成する。
(実際にはサブのバグ取りをしながらチマチマ組んでたんだけどね)

んで、これまでに作ってきたファンクションをズラッと並べて書く。
こんなカンジに…

///// Prototype of Functions /////

short Check
    (long *Pic_W, long *Pic_H, char FName[],
     unsigned char *QC, unsigned char *OWF);
void Adj_Head
    (long *Pic_W, long *Pic_H, unsigned char *QC);
void RGB2YBR
    (unsigned char Inp[], long YBR[][8][3],
     long *DST, long *Num, long *p, long *q);
void DCT
    (long YBR[][8][3]);
void ZigZag
    (long YBR[][8][3], long Zig[][3]);
void Quantize
    (long Zig[][3], long DC[]);
void Huffmann
    (long Zig[][3], unsigned char HD[],
     long *HDL, unsigned long *Buf, long *BFL);
void Compress
    (unsigned char HD[], long *HDL,
     unsigned long *Buf, long *BFL);

プロトタイプ宣言は良くも悪くもこれだけのこと

ほんで、ルートファンクションは以下の通り

///// Root Function /////

short __stdcall SJP444
    (unsigned char Inp[], long Pic_W, long Pic_H,
     char FName[], unsigned char QC, unsigned char OWF){
    /// Error Code
    /// Return   0 : OK
    /// Return   1 : QC Under Warning
    /// Return   2 : QC Over Warning
    /// Return  10 : File Overwrite Warning
    ///
    /// Return  -1 : Illegal Size Error
    /// Return  -2 : Extension Not .jpg Error
    /// Return  -3 : Memory Allocation Error
    /// Return -10 : File Overwrite Error
    /// Return -20 : File Open Error

    long DST, i, p, q, Num, x, y;
    short Rep;
    long YBR[8][8][3];      // YCbCr Data
    long Zig[64][3];        // Zigzag-Scanned Data
    long DC[3];             // for DC Difference
    unsigned char *HD;      // Work Array for Huffmann Data
    long HDL = 0;           // Huffmann Data Counter
    unsigned long Buf = 0;  // 32bit Data Buffer
    long BFL = 0;           // Buffer Counter
    errno_t err;
    FILE *file;

    HD = (unsigned char *)malloc(sizeof(unsigned char) * (78 * Pic_W) + 2);
    if (HD == NULL) return -3;             // Allocation Error
    Rep = Check(&Pic_W, &Pic_H, FName, &QC, &OWF);
    if (Rep < 0) return Rep;               // STOP
    Adj_Head(&Pic_W, &Pic_H, &QC);
    err = fopen_s(&file, FName, "wb");
    if (err != 0) {                        // File Open Error
        free(HD);
        return -20;
    }
    fwrite(JPHead, sizeof(unsigned char), 413, file);  // Write Header

    DST = (3 * Pic_W + 3) & 0xFFFFC;       // Data Stride

    for (y = 0; y < Pic_H; y += 8){        // Convert Loop
        if (y > 0){                        // Insert Restart Marker
            HD[0] = 255;
            HD[1] = 0xD0 | (((y >> 3) - 1) & 0x07);
            HDL = 2;
        }
        for (i = 0; i < 3; i++) DC[i] = 0; // DC直前値初期化
        q = (Pic_H - y) < 8 ? Pic_H - y : 8;
        for (x = 0; x < Pic_W; x += 8){
            Num = DST * y + 3 * x;
            p = (Pic_W - x) < 8 ? Pic_W - x : 8;
            RGB2YBR(Inp, YBR, &DST, &Num, &p, &q);
            DCT(YBR);
            ZigZag(YBR, Zig);
            Quantize(Zig, DC);
            Huffmann(Zig, HD, &HDL, &Buf, &BFL);
        }

        if (BFL > 0){                       // Stuffing bit
            HD[HDL] = (unsigned char)((Buf | (0x00FF0000 << (8 - BFL))) >> 24);
            HDL++;
            Buf = 0;
            BFL = 0;
        }

        if (HD[HDL-1] == 0xFF){             // FFデータには00を付加する
            HD[HDL] = 0;
            HDL++;
        }

        fwrite(HD, sizeof(unsigned char), HDL, file);   // Data Save
        HDL = 0;
    }
    // Insert END Marker FF D9:255 217
    HD[0] = 255;
    HD[1] = 217;
    fwrite(HD, sizeof(unsigned char), 2, file);         // Data Save
    fclose(file);
    // free()でメモリー解放 ////
    free(HD);
    return Rep;
}

/// Error Code以下にこのdllが返すエラーコードを書いたが、
起こり得るエラーがコレだけであることを保証するものではない。
(例えば、保存中にドライブ空き容量がなくなってしまった時などは
フォローしていない。。。 /-_-; )
正常終了した場合は0を返すほか、正値ではjpegファイルを作る。
(1と2は指定Q値が1~100の範囲に無く、
10はファイル上書きしたことを表し、
Q値範囲外とファイル上書きの両方があれば合計値を返す)
負値の場合はjpegファイル作成を断念している。
「Jpeg保存dllの呼び出しと引数」参照)
なお、値はshot型(符号あり16bit整数)で返す。

さて、ハフマン変換したデータの保存用のバイト配列を確保するのが
HD = (unsigned char *)malloc(sizeof(unsigned char) * (78 * Pic_W) + 2);
の部分なのだが、コレには若干の問題意識を持っている。

8x8pixの変換ブロックの水平1段分の変換データをバイト配列に確保して、
コレを逐次ファイルに追記して行くのが本dllの作成方針であり、
バイト配列は最初に容量確保したら「それっきり」で済ます方針だ。
(後で容量が不足した場合のフォローはメンドクサイのでしたくない)
だから、どれだけの大きさで配列を確保するかは重要な問題となる。

で、上記のmalloc文はどのようにして決めたのかと言うと…

まずハフマン変換をLUTにした時のことを振り返ろう。
この時に掲載した表を見ると、
DC成分では最大のデータbit数は11bitで、
このとき有効長9bitのハフマン符号が与えられる。
AC成分では最大のデータbit数は10bitで、
このときはゼロランレングス数がいくつでも
有効長16bitのハフマン符号が与えられるとわかる。

さて、64要素の変換ブロックが上記の最大データで埋まっているなら
DC成分は11 + 9で20 bit、AC成分は(10 + 16) x 63で1638 bitだね。
でYCbCrだから変換ブロック1個は、(20 + 1638) x 3で4974 bitになる。
(実際にそんな画像があり得るかどうかは別にして…)

変換ブロックはタテヨコ8 pixなので画像横幅をPic_Wとするなら
変換ブロック水平1段分は4974 x Pic_W / 8 <bit>なので、
さらに8で割ってByteに直せば、(小数切り上げで)
78 x Pic_W Byteってことになる。
これにリスタートマーカの2 Byte分を加えて
(78 x Pic_W) + 2 という式になるのだが、これで収まるかと言うと多分無理だ。
何故なら、Jpegのルールで0xFFデータの後は0x00を挿入するから。
だからと言って、この分まで見込むというのはいかがなものか…

それじゃぁ今度は…
「変換ブロック全体が最大のハフマンデータで埋まるコトがあるのか?」
ってハナシだ。

例えば、8 x 8 pixのカラービットマップは8 x 8 x 3で192 Byteだね。
やはり画像横幅をPic_Wとするなら192 x Pic_W / 8で計算して
24 x Pic_W Byteのデータ保存バイト配列として、(本dllの1/3位だね)
コレをちょうど使い切ったJpeg変換であったならば
元のBMPファイルと同じファイルサイズのJpegファイルになるワケだ。
(ヘッダ部分は考慮しないコトにして)

ふつう…Jpeg保存すればBMPよりもファイルサイズは小さくなるよね?
(それがJpegの存在意義なんだし)
ワタシの経験では最大でもBMPの45%位だ。
そう考えると(78 x Pic_W) + 2で確保するのはいかにも過剰と言える。
15 x Pic_W Byteくらいあればフツー大丈夫なんじゃないの?とは思う。

…とは思うのだが念のため、
高周波成分がコッテリしてそうな画像を作ってJpeg変換してみよう。
対象画像はRGBをランダムにバラまいて作った…

この画像。
BMP形式でのファイルサイズは約19 kB。

コレをQ値=100でJpeg変換したら約27 kBになった。
「おぉっ、オリジナルよりデカいっ!なるときゃぁなるもんだぁ」
とは言っても、およそ4割増しだ。

だから、このレベルの画像をフォローしたって40 x Pic_W Byteくらいで
ホントーは十分なんだろう…とは思いつつ、
今回は結局、(78 x Pic_W) + 2のままとした。
なんだかなぁ…


リスタートマーカFF Dxのx値を決める部分…
「割り算よりは速いのでは?」と思って
HD[1] = 0xD0 | (((y >> 3) - 1) & 0x07);
としたのだが、何を計算しているのかわかりにくい。

素直に
HD[1] = 0xD0 + ((Y / 8 - 1) % 8);
とか
HD[1] = 208 + ((Y / 8 - 1) % 8);
で良かったかな?


コンパイル&ビルドしてdllの完成と、
.netで呼び出しプログラムを作っての実使用を次回以降に…

- つづく -



最新の画像もっと見る

コメントを投稿

ブログ作成者から承認されるまでコメントは反映されません。