2009年08月04日

SVGファイルのPack機能、UseAlpha機能の追加

vectex内臓Blenderにいくつかの機能追加とバグフィックスを行いました。
実行ファイルはこちらです。
QtSVGバージョン
Linux 32bit (Blender_Qt_vectex_Linux32090709.tar.gz)
Linxu 64bit (Blender_Qt_vectex_Linux64090709.tar.gz)
Windows 32bit (Blender_Qt_vectex_win32090709.zip)

ソースファイルのパッチ (Blender_Qt_vectex_patch090804.zip)

追加した機能は、
・SVGファイルのパック機能の追加
・アルファ値の使用に関する機能(premul)の追加

バグフィックスの内容については、この記事の下の方を見てください。
実行ファイルのURLは以前のものと同じで、古いファイルは削除しています。
AGGバージョンも同時に更新していますが、アルファ値の使用に関してはAGGのSVG描画機能がもともとアルファ値に対応していないため、あまり意味がないかもしれません。

AGGバージョンのソースファイルのパッチ(Blender_modified_patch090804.zip)
実行ファイルのURLは、以前のものと同じなので省略します。
pic090804_01.jpg


ここから、更新内容についての解説となります。

○Pack機能
この機能は、テクスチャ用画像ファイルなどを.blendファイルの中に埋め込んで保存するものです。
その分.blendファイルのサイズが大きくなってしまいますが、別のコンピュータなどに移動して使う場合に画像ファイルが見つからなくなるのを防ぐことができます。

この機能を実装するためには、QtSVGなどのライブラリでSVGファイルを開く際に通常はファイル名を渡していますが、これを.blendファイルに保存されたデータのメモリ領域へのポインタを渡すように変更することになります。
こんなことが簡単にできるものなのかとちょっと不安に思いましたが、QtSVGにはまさにこのためにあるのではないかというように、QSvgRender::load(const QByteArray & contents)という機能が用意されていました。
あとは、QByteArrayというデータ領域を扱うクラスが、BlenderのPack機能の作成するデータを直接扱えるかどうか、という点が問題になります。
QByteArrayのコンストラクタには、QByteArray(const char *data, int size)というようにchar型のデータ領域へのポインタとそのサイズを渡すものがあります。
今回は、これを使うことで.blendファイルに保存されたメモリ領域からSVGファイルを読み込むことができました。

ソースコードの修正内容です。
まずは、ボタンを追加します。
今回はvectexパネルで全体のボタンの配置を変更しているため、機能を追加している部分以外もソースコードが修正されています。
パック機能のボタンには専用のアイコンが使われていて、ファイルがパックされているか、されていないかの状態に応じて表示されるアイコンも変更されるようになっています。

blender/source/blender/src/buttons_shading.c (line:798-)
static void texture_panel_vectex(Tex *tex)
{
...
    if (tex->vtex_packedfile) packdummy = 1;
    else packdummy = 0;
    but= uiDefIconButBitI(block, TOG, 1, B_REDR, ICON_PACKAGE, 286,100,24,20, &packdummy, 0, 0, 0, 0, "Toggles Packed status of this SVG file");
    uiButSetFunc(but, vectex_pack_cb, tex, NULL);

...
}

このコードには、パックボタンを押したときにvectex_pack_cb()という関数が実行されるようになっています。
この関数は元々存在していませんので、自分で用意する必要があります。
イメージテクスチャ用のimage_pack_cb()という関数を参考にして作成しました。

blender/source/blender/src/buttons_shading.c (line:250-)
static void vectex_pack_cb(void *tex_v, void *unused)
{
    Tex *tex= tex_v;

    if (tex->vtex_packedfile) {
        if (G.fileflags & G_AUTOPACK) {
            if (okee("Disable AutoPack ?")) {
                G.fileflags &= ~G_AUTOPACK;
            }
        }
       
        if ((G.fileflags & G_AUTOPACK) == 0) {
            unpackVectex(tex, PF_ASK);
            /*BIF_undo_push("Unpack image");*/
        }
    }
    else {
        tex->vtex_packedfile = newPackedFile(tex->vtex_file_name);
        /*BIF_undo_push("Pack image");*/
    }
}

すでにファイルをパック済みか、そうでないかによって処理を分けています。
パック済みであればパックを解除し、パックされていないなら新規パックファイルを作成します。
イメージテクスチャにはアンドゥ処理用のコードが書かれていましたが、今回はアンドゥ処理は省略してあります。

このコードの中のnewPackedFile()関数からの戻り値はPackFile構造体へのポインタです。
これをTex構造体に保持するために、tex->vtex_packedfileという新しいメンバをTex構造体に追加しています。

blender/source/blender/makesdna/DNA_texture_types.h (line:151-)
typedef struct Tex {
...
    struct PackedFile * vtex_packedfile;
...
}
Tex構造体に保持されるPackFile構造体の定義は、DNA_packedFile_types.hに書かれています。

blender/source/blender/makesdna/DNA_packedFile_types.h (line:36-)
typedef struct PackedFile {
    int size;
    int seek;
    int flags;
    int pad;
    void * data;
} PackedFile;
パックされたデータにアクセスするときには、この構造体のsize、dataメンバを使用します。
vectexでSVGファイルを開く処理をしているのは、vtex_create_backend()という関数です。

blender/source/blender/vectex/vectex_agg.cpp (line:53-)
EXT void *vtex_create_backend(Tex *tex)
{
    char *filename = tex->vtex_file_name;
...
    if (tex->vtex_packedfile) {

        QByteArray svgdata((char *)tex->vtex_packedfile->data, tex->vtex_packedfile->size);
        if (backend->svg_render.load(svgdata) != true)
        {
            fprintf (stderr, "Cannot open input file %s\n", filename);
            delete backend;
            return (NULL);
        };       
    }
    else {

...
}

Tex構造体のvtex_packedfileにデータが作成されているときは、QtのQByteArrayというクラスのインスタンスsvgdataを作成します。
QByteArrayのコンストラクタにはいくつかのバリエーションがありますが、char型のポインタとint型のサイズを引数として使用するものを使ってPackFile構造体に保存されたデータを読み込んでいます。

このようにTex構造体のvtex_packedfileというメンバにアクセスするために、vtex_create_backend()関数の引数も修正しました。元々は、
EXT void *vtex_create_backend(char *filename);
というようにファイル名を渡していましたが、Tex構造体のポインタを渡すように修正しファイル名もtex->vtex_file_nameというメンバから取得するように変更しました。
これに対応するvectex.hのプロトタイプ宣言、texture.cのvtex_create_instance()関数でvtex_create_backend()関数を呼び出す部分も修正しています。

blender/source/blender/render/intern/source/texture.c (line:916-)
void vtex_create_instance(Tex *tex)
{
...
    void *backend_data = vtex_create_backend(tex);
...
}
Tex構造体にPackFile構造体へのポインタをメンバとして追加したため、.blendファイルを保存するコードでPackFile構造体を保存する処理を追加する必要があります。

blender/source/blender/blenloader/intern/writefile.c (line:1323-)
static void write_textures(WriteData *wd, ListBase *idbase)
{
    Tex *tex;
    PackedFile * pf;
...
            if (tex->vtex_packedfile) {
                pf = tex->vtex_packedfile;
                writestruct(wd, DATA, "PackedFile", 1, pf);
                writedata(wd, DATA, pf->size, pf->data);
            }

...
}
同じように、.blendファイルを読み込むコードでもPackFile構造体を扱うための処理を追加しました。

blender/source/blender/blenloader/intern/readfile.c (line:2529-)
static void direct_link_texture(FileData *fd, Tex *tex)
{
...
    tex->vtex_packedfile = direct_link_packedfile(fd, tex->vtex_packedfile);
...
}
BlenderのFileメニューには「External Data」->「Pack into .blend file」 / 「Unpack into Files...」というように、ファイル全体の外部ファイルに対してすべてパックする、アンパックする機能があります。
pic090804_02.jpg

これを行っているソースコードはpackedFile.cの中にあります。
ファイルのパック機能はイメージファイル以外にもサウンド、フォントなどで利用でき、packedFile.c中にはそれらのすべてのファイルタイプに対して処理を行う関数が3つあります。
今回、その部分にvectex用のコードを追加しました。

blender/source/blender/blenkernel/intern/packedFile.c
line(123-)
int countPackedFiles()
{
...
    Tex *tex;

...
    tex = G.main->tex.first;
    while (tex) {
        if (tex->vtex_packedfile) {
            count++;
        }
        tex = tex->id.next;
    }

...
}

line(228-)
void packAll()
{
...
    Tex *tex;
...
    tex = G.main->tex.first;
    while (tex) {
        if(tex->vtex_file_name[0] != '\0'){
            if (tex->vtex_packedfile == NULL) {
                tex->vtex_packedfile = newPackedFile(tex->vtex_file_name);
            }
        }
        tex = tex->id.next;
    }

...
}

line(628-)
void unpackAll(int how)
{
...
    Tex *tex;
...
    tex = G.main->tex.first;
    while (tex) {
        if (tex->vtex_packedfile) {
            unpackVectex(tex, how);
        }
        tex = tex->id.next;
    }

...
}

最後のunpackAll()関数では、unpackVectex()という関数を使っています。
この関数は自分で用意する必要がありますので、他のファイルタイプの関数を参考にして新しく作成しています。
(line:603-)
int unpackVectex(struct Tex * tex, int how)
{
    char localname[FILE_MAXDIR + FILE_MAX], fi[FILE_MAX];
    char * newname;
    int ret_value = RET_ERROR;
   
    if (tex != NULL) {
        strcpy(localname, tex->vtex_file_name);
        BLI_splitdirstring(localname, fi);
        sprintf(localname, "//textures/%s", fi);
           
        newname = unpackFile(tex->vtex_file_name, localname, tex->vtex_packedfile, how);
        if (newname != NULL) {
            ret_value = RET_OK;
            freePackedFile(tex->vtex_packedfile);
            tex->vtex_packedfile = NULL;
            strcpy(tex->vtex_file_name, newname);
            MEM_freeN(newname);
            /*BKE_image_signal(ima, NULL, IMA_SIGNAL_RELOAD);*/
        }
    }
   
    return(ret_value);
}

これに対応して、関数のプロトタイプ宣言をBKE_packedFile.hに追加しています。

blender/source/blender/blenkernel/BKE_packedFile.h (line:58)
int unpackVectex(struct Tex * tex, int how);

○UseAlpha機能

vectexのSVG描画ライブラリをAGGからQtSVGに変更した時点でキャッシュされるタイルイメージの画像モードをRGB24からRGBA32に変更していました。
しかし、描画されているアルファ値のデータを参照するコードをまったく書いていなかったので、これまではリソースの無駄遣いにしかなっていませんでした。
もともとvectexの作者のmgmalheirosさんはアルファ値を使えるようにする予定だったらしく、base_colorの配列サイズなどはあらかじめ4バイト確保されていたりしています。
私自身は、SVGファイルには通常のビットマップ画像のように独立したアルファチャンネルを自由に操作する機能がないので、むしろグレースケール画像を別に用意してそちらに透明度を管理させる方がより柔軟に使えるように思うのですが、以上のような理由もあってアルファ値を使えるようにコードを修正しました。

基本的には、イメージテクスチャのコードにあるアルファ値関連の部分をそのままコピーアンドペーストするだけの改造です。
ちょっと苦労したのは、Qtの描画機能を使ってアルファ値まで含めて画面を初期化する方法がよくわからなかったことです。最終的には、Qtの機能を使わずにあらかじめ確保した画像領域にデータを書き込んだ状態でQtに渡すようにコードを修正することでなんとかなりました。

ソースコードの修正内容です。
ボタンを追加します。イメージテクスチャにあるボタン「UseAlpha」「CalcAlpha」「NegAlpha」「Premul」をそのままコピーしています。
ボタンで変更されるデータの実体については、UseAlpha、CalcAlpha、NegAlphaについてはTex構造体のメンバ変数imaflagにビットマスクの形で書き込むようになっています。
もう一つのPremulについては、もともとImage構造体のメンバ変数flagにビットマスクとして書き込まれていますが、vectexからはそのデータを使うことはできないので今回新たにTex->vtex_imaflagというメンバ変数を追加しました。

blender/source/blender/src/buttons_shading.c (line:798-)
static void texture_panel_vectex(Tex *tex)
{
...
    uiDefButBitS(block, TOG, TEX_USEALPHA, B_TEXPRV, "UseAlpha",    10, 45, 100, 20, &tex->imaflag, 0, 0, 0, 0, "Click to use Image's alpha channel");
    uiDefButBitS(block, TOG, TEX_CALCALPHA, B_TEXPRV, "CalcAlpha",    110, 45, 100, 20, &tex->imaflag, 0, 0, 0, 0, "Click to calculate an alpha channel based on Image RGB values");
    uiDefButBitS(block, TOG, TEX_NEGALPHA, B_TEXPRV, "NegAlpha",    210, 45, 100, 20, &tex->flag, 0, 0, 0, 0, "Click to invert the alpha values");

     /* fields */
     uiBlockBeginAlign(block);
     uiDefButBitS(block, TOG, IMA_DO_PREMUL, B_TEXPRV, "Premul",        10, 20, 65, 20, &tex->vtex_imaflag, 0, 0, 0, 0, "Toggles premultiplying alpha");
     uiBlockEndAlign(block);

}

blender/source/blender/makesdna/DNA_texture_types.h (line:151-)
typedef struct Tex {
...
    short vtex_imaflag;
...
}
blender/source/blender/vectex/vectex_agg.cpp (line:105-)
EXT unsigned char *vtex_backend_render_tile(void *backend_data, int resolution,
                                       int tile_size, int tile_row, int tile_col,
                                       double base_r, double base_g, double base_b,
                                       int border)
{
...
    /* Initialize buf with col */
    int r, g, b, a;
    r = (int)(255.0 * base_r);
    g = (int)(255.0 * base_g);
    b = (int)(255.0 * base_b);
    a = 0;
   
    for (int i = 0; i < (width * height * 4) - 4; i += 4)
    {
        buf[i] = b;
        buf[i + 1] = g;
        buf[i + 2] = r;
        buf[i + 3] = a;
    }

...
}

texture.cのvectex()関数の最後には、imagetexute.cのimagewrap()、imagewraposa()関数の中にあるアルファ値関連のコードをそっくりコピーしました。
そのコピーしたコードが動くようにするために、vtex_sample()関数の引数に &a、premulという2つの変数を追加しています。

blender/source/blender/render/intern/source/texture.c
(line:1201-)
static int vectex(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres)
{
...
    int premul;
...
    if (tex->vtex_imaflag & IMA_DO_PREMUL) {
        premul = 1;
    }
    else {
        premul = 0;
    }

...
    double r, g, b, a;
    double r1, g1, b1, a1;
    float val1, val2, val3;
    float val11, val12, val13;
    float texvec0, texvec1;
    texvec0 = texvec[0];
    texvec1 = texvec[1];
    vtex_sample(instance, &texvec0, &texvec1, power, tex->vtex_interpol, &r, &g, &b, &a, premul);
...
                vtex_sample(instance, &texvec0, &texvec1, power, tex->vtex_interpol, &r1, &g1, &b1, &a1, premul);
...
                vtex_sample(instance, &texvec0, &texvec1, power, tex->vtex_interpol, &r1, &g1, &b1, &a1, premul);
...
        double r2, g2, b2, a2;
        texvec0 = texvec[0];
        texvec1 = texvec[1];
        vtex_sample(instance, &texvec0, &texvec1, power - 1, tex->vtex_interpol, &r2, &g2, &b2, &a2, premul);
...
                vtex_sample(instance, &texvec0, &texvec1, power - 1, tex->vtex_interpol, &r1, &g1, &b1, &a1, premul);
...
                vtex_sample(instance, &texvec0, &texvec1, power - 1, tex->vtex_interpol, &r1, &g1, &b1, &a1, premul);
...
        a = a * max_weight + a2 * min_weight;
...
    /*texres->tin = 1.0;*/
    texres->tr = r;
    texres->tg = g;
    texres->tb = b;
    texres->ta = a;

    /* Alpha channel */
    BRICONTRGB;

    if(tex->imaflag & TEX_USEALPHA) {
        if(tex->imaflag & TEX_CALCALPHA);
        else texres->talpha= 1;
    }

    if(texres->talpha) texres->tin= texres->ta;
    else if(tex->imaflag & TEX_CALCALPHA) {
        texres->ta= texres->tin= MAX3(texres->tr, texres->tg, texres->tb);
    }
    else texres->ta= texres->tin= 1.0;
   
    if(tex->flag & TEX_NEGALPHA) texres->ta= 1.0f-texres->ta;

    float fx;
    /* de-premul, this is being premulled in shade_input_do_shade() */
    if(texres->ta!=1.0f && texres->ta>FLT_EPSILON) {
        fx= 1.0f/texres->ta;
        texres->tr*= fx;
        texres->tg*= fx;
        texres->tb*= fx;
    }


    /*return (TEX_INT | TEX_RGB);*/
    return retval;
}

引数を追加したvtex_sample()関数の中では、premulボタンが押されている場合にRGBの各値にアルファ値を乗算するコードを追加しました。
イメージテクスチャのもともとのソースコードでは、画像ファイルを読み込んだ直後にメモリ上のRGB値をアルファ値を乗算したものにすべて変換しているようですが、vectexでタイル画像のメモリ領域の全RGBデータを変換する必要というのは特になさそうなので、vtex_sample()関数の中のタイル画像からRGBデータを読み出す部分に修正を行うようにしています。
この部分のコードは、source/blender/blenkernel/intern/image.cにあるconverttopremus()関数のコードを参考にしています。
line(1066-)
void vtex_sample(VtexInstance *instance, float *texvec0, float *texvec1, int power, int interp,
            double *pr, double *pg, double *pb, double *pa, int premul)
{
...
            /* Constant color tile */
            if (premul) {
                int val= tile[3];
                *pb = (double)(((tile[0]*val)>>8) / 255.0);
                *pg = (double)(((tile[1]*val)>>8) / 255.0);
                *pr = (double)(((tile[2]*val)>>8) / 255.0);
                *pa = (double)(tile[3] / 255.0);
            }
            else {

                *pb = (double)(tile[0] / 255.0);
                *pg = (double)(tile[1] / 255.0);
                *pr = (double)(tile[2] / 255.0);
                *pa = (double)(tile[3] / 255.0);
             }

...
            if (premul) {
                int val= tile[offset + 3];
                   *pb = (double)(((tile[offset]*val)>>8)     / 255.0);
                *pg = (double)(((tile[offset + 1]*val)>>8) / 255.0);
                *pr = (double)(((tile[offset + 2]*val)>>8) / 255.0);
                *pa = (double)(tile[offset + 3] / 255.0);
            }
            else {

                *pb = (double)(tile[offset]     / 255.0);
                *pg = (double)(tile[offset + 1] / 255.0);
                *pr = (double)(tile[offset + 2] / 255.0);
                *pa = (double)(tile[offset + 3] / 255.0);
            }

...
            if (premul) {
                int val= tile[3];
                *pb = (double)(((tile[0]*val)>>8) / 255.0);
                *pg = (double)(((tile[1]*val)>>8) / 255.0);
                *pr = (double)(((tile[2]*val)>>8) / 255.0);
                *pa = (double)(tile[3] / 255.0);
            }
            else {

                *pb = (double)(tile[0] / 255.0);
                *pg = (double)(tile[1] / 255.0);
                *pr = (double)(tile[2] / 255.0);
                *pa = (double)(tile[3] / 255.0);
             }

...
            if (premul) {
                double val= ((tile[tl + 3] * u_opposite + tile[tr + 3] * u_ratio) * v_opposite +
                       (tile[bl + 3] * u_opposite + tile[br + 3] * u_ratio) * v_ratio) / 255.0;
                *pb = ((tile[tl]     * u_opposite + tile[tr]     * u_ratio) * v_opposite +
                       (tile[bl]     * u_opposite + tile[br]     * u_ratio) * v_ratio) * val / 255.0;   
                *pg = ((tile[tl + 1] * u_opposite + tile[tr + 1] * u_ratio) * v_opposite +
                       (tile[bl + 1] * u_opposite + tile[br + 1] * u_ratio) * v_ratio) * val / 255.0;   
                *pr = ((tile[tl + 2] * u_opposite + tile[tr + 2] * u_ratio) * v_opposite +
                       (tile[bl + 2] * u_opposite + tile[br + 2] * u_ratio) * v_ratio) * val / 255.0;
                *pa = val;
            }

            else {

                *pb = ((tile[tl]     * u_opposite + tile[tr]     * u_ratio) * v_opposite +
                       (tile[bl]     * u_opposite + tile[br]     * u_ratio) * v_ratio) / 255.0;   
                *pg = ((tile[tl + 1] * u_opposite + tile[tr + 1] * u_ratio) * v_opposite +
                       (tile[bl + 1] * u_opposite + tile[br + 1] * u_ratio) * v_ratio) / 255.0;   
                *pr = ((tile[tl + 2] * u_opposite + tile[tr + 2] * u_ratio) * v_opposite +
                       (tile[bl + 2] * u_opposite + tile[br + 2] * u_ratio) * v_ratio) / 255.0;
                *pa = ((tile[tl + 3] * u_opposite + tile[tr + 3] * u_ratio) * v_opposite +
                       (tile[bl + 3] * u_opposite + tile[br + 3] * u_ratio) * v_ratio) / 255.0;
            }

        }
    }
}


○バグフィックス

今回、これらの機能追加の他に2つほどバグフィックスを行っています。
一つは、QtSVGのレンダリングを行う部分でタイル画像にSVG画像のどの部分を描画するかを指定するための変換行列の計算ミスの修正です。
タイル画像は256+1ピクセルというように補間計算用に1ピクセル大きめに作成しています。
QtSVGの変換行列の指定方法はAGGのものとは全く違ったものであったため、変換行列の処理をするコードを自力で書き直しているのですが、そのときに1ピクセル大きくタイル画像が作成されていることを考慮していませんでした。

blender/source/blender/vectex/vectex_agg.cpp
EXT unsigned char *vtex_backend_render_tile(void *backend_data, int resolution,
                                       int tile_size, int tile_row, int tile_col,
                                       double base_r, double base_g, double base_b,
                                       int border)
{
...
    QTransform transform;
    transform.reset();
    if (resolution > width) {
        transform.translate(- tile_size * tile_col, - tile_size * tile_row);
        transform.scale(resolution / tile_size, resolution / tile_size);
    }
    transform.scale((double)tile_size / width, (double)tile_size / height);
    painter.setTransform(transform);
...
}

もう一つは、リンク、アペンド機能を使ったときに、ライブラリ側でvectexのテクスチャを相対パスとして使用していた場合、相対パスから絶対パスへの変換が正しく行われていなかったというものです。
リンクのときの処理は、vectex_agg.cppのvtex_create_backend()関数の中で行っています。
EXT void *vtex_create_backend(Tex *tex)
{
...
        BLI_strncpy(str, filename, sizeof(str));
        if(str[0] != '.') {
            if(tex->id.lib)
                BLI_convertstringcode(str, tex->id.lib->filename);
            else

                BLI_convertstringcode(str, G.sce);
        }
...
}
アペンドのときは、library.cの中のall_local()という関数の中で修正をしています。詳しいことはよく分からないのですが、イメージテクスチャの処理をデバッガで追ってみるとこの部分でパスの処理をしていることがわかりました。
イメージテクスチャの処理を参考にして、texture_fix_relative_path()という関数を新しく作りました。

blender/source/blender/blenkernel/intern/library.c
(line:1036-)
void all_local(Library *lib, int untagged_only)
{
...
                        /* relative file patch */
                        if(GS(id->name)==ID_IM)
                            image_fix_relative_path((Image *)id);
                       
                        if(GS(id->name)==ID_TE)
                            texture_fix_relative_path((Tex *)id);

...
}
(line:990-)
static void texture_fix_relative_path(Tex *tex)
{
    if(tex->id.lib==NULL) return;
    if(strncmp(tex->vtex_file_name, "//", 2)==0) {
        BLI_convertstringcode(tex->vtex_file_name, tex->id.lib->filename);
        BLI_makestringcode(G.sce, tex->vtex_file_name);
    }
}

※この記事を書いている途中、いきなりマウス操作できなくなって、あわてて不完全な状態のままで記事を公開してしまいました。
単純にコードレスマウスの電池が切れただけだったのですが、もし途中までしか書かれていない記事をご覧になられた方がいましたら、ご迷惑をおかけしてすみませんでした。
posted by mato at 23:46| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。