Porter & Duff のテストプログラム

DirectFB の gfxdriver を開発するにあたって、Porter & Duff について軽く調べたのでメモ。
Porter & Duff の説明は wikipedia:アルファチャンネル に詳しいので、ここでは C で書いた簡単なテストプログラムのみを載せる。
Wikipedia を見ると、TeX じゃないと書けないような計算式が一見大義そうな印象を受けるが、実は、ただの掛け算と足し算であった。

サンプルプログラムは、引数で src color, src alpha, dst color, dst alpha の値を入れてやる。
(それぞれ 0 から 255 の範囲)
Porter & Duff のオペレーションをして、ブレンドした結果の色と alpha 値が表示される、という単純なもの。

#include 
#include 

enum opsv {
    ZERO,   // 0
    ONE,    // 1
    AS,     // src alpha
    AD,     // dst alpha
    INV_AS, // 1 - src alpha
    INV_AD, // 1 - dst alpha
};

typedef struct _ops {
    char v1;
    char v2;
} ops_t;

char *op_name = {
    "CLEAR   ",
    "SOURCE  ",
    "DEST    ",
    "SRC_OVER",
    "DST_OVER",
    "SRC_IN  ",
    "DST_IN  ",
    "SRC_OUT ",
    "DST_OUT ",
    "SRC_ATOP",
    "DST_ATOP",
    "XOR     ",
    "PLUS    ",
};

ops_t ops_table = {
    { ZERO,     ZERO    },  // CLEAR
    { ONE,      ZERO    },  // SRC
    { ZERO,     ONE     },  // DST
    { ONE,      INV_AS  },  // SRC_OVER
    { INV_AD,   ONE     },  // DST_OVER
    { AD,       ZERO    },  // SRC_IN
    { ZERO,     AS      },  // DST_IN
    { INV_AD,   ZERO    },  // SRC_OUT
    { ZERO,     INV_AS  },  // DST_OUT
    { AD,       INV_AS  },  // SRC_ATOP
    { INV_AD,   AS      },  // DST_ATOP
    { INV_AD,   INV_AS  },  // XOR
    { ONE,      ONE     },  // PLUS
};

double
calc_each_op( double as, double ad, enum opsv op )
{
    switch ( op ) {
        case ZERO:
            return 0;
        case ONE:
            return 1;
        case AS:
            return as;
        case AD:
            return ad;
        case INV_AS:
            return (1 - as);
        case INV_AD:
            return (1 - ad);
    }
    assert( 0 );
    return 0;
}

unsigned char
calc_cd( unsigned char cs, double as,
         unsigned char cd, double ad, int op )
{
    ops_t ops = ops_table[op];
    return cs * as * calc_each_op(as, ad, ops.v1) +
           cd * ad * calc_each_op(as, ad, ops.v2);
}

double
calc_ad( double as, double ad, int op )
{
    ops_t ops = ops_table[op];
    return as * calc_each_op(as, ad, ops.v1) +
           ad * calc_each_op(as, ad, ops.v2);
}

int
main( int argc, char *argv[] )
{
    int i;
    unsigned char sc, dc;
    double sa, da;

    if ( argc <= 4 ) {
        fprintf( stderr, "usage : porter_duff_calc src_color src_alpha dst_color dst_alpha\n" );
        fprintf( stderr, "\t color : 0 .. 255\n" );
        fprintf( stderr, "\t alpha : 0 .. 255\n" );
        return 0;
    }

    sc = atoi( argv[1] );
    sa = atoi( argv[2] ) / 255.0;
    dc = atoi( argv[3] );
    da = atoi( argv[4] ) / 255.0;

    printf( "operation \t color    alpha\n" );

    for ( i = 0; i < (sizeof(op_name) / sizeof( char * ) ) ; i++ ) {
        printf( "%s \t  %d \t %f\n",
            op_name[i],
            calc_cd( sc, sa, dc, da, i ),
            calc_ad( sa, da, i ) );
    }
}


動かしたところ:

$ ./porter_duff_calc 128 255 192 128
operation        color    alpha
CLEAR             0      0.000000
SOURCE            128    1.000000
DEST              96     0.501961
SRC_OVER          128    1.000000
DST_OVER          160    1.000000
SRC_IN            64     0.501961
DST_IN            96     0.501961
SRC_OUT           63     0.498039
DST_OUT           0      0.000000
SRC_ATOP          64     0.501961
DST_ATOP          160    1.000000
XOR               63     0.498039
PLUS              224    1.501961