アンチエイリアス付きの自由線描画(6)・筆圧

筆圧
ペイントソフトでは、ペンタブレットの筆圧を描画に適用できれば、よりアナログに近い線を描くことができます。
今回は、今までの自由線描画に筆圧を適用してみたいと思います。

ただし、ペンタブレットのない環境でもテストできるように、実際の筆圧を取得するのではなく、擬似的に生成した値を使うことにしています。
ここでは、ペンタブレットから筆圧を取得する方法は説明しません。
筆圧を適用するには
まず、自由線に筆圧を適用するには、どうすればよいでしょうか。

ペンタブレットから得られるのは、現在の座標と筆圧です。
送られてきたその位置と位置の間を線で描画し、さらにその線を描画するために、直線上に複数の点を描画しているのですから、描画する各点において、大きさや濃度をその位置の筆圧に合わせてやれば、筆圧を適用できます。

つまり、点の半径と濃度が筆圧によって変化するものとした場合、筆圧が最小の時は、半径や濃度を0に、筆圧が最大の時は、半径や濃度を最大値にすると良さそうです。
パラメータ
ここで一つ、筆圧に関するパラメータを付けてみたいと思います。

筆圧が最小の時に常に半径や濃度を0にするのでは、応用力がありません。
筆圧を適用させたくない場合や、筆圧最小時でも半径や濃度がある程度欲しい場合などに対応できるように、「筆圧最小時の半径・濃度」をパラメータとして指定できるようにします。

ちなみに、筆圧が最大の場合には、描画する半径と濃度の最大値を使うことになります。
筆圧が最大の場合を基準にして考えると、筆圧が下がるほど、半径や濃度は、最大値を頂点として下がっていくことになります。
ということは、筆圧最小時にどこまで値を下げるか、というパラメータがあれば、筆圧による半径や濃度の下げ幅を変更することができます。

実際の実装では、このパラメータは、描画の最大半径と最大濃度に対する相対的な割合とし、0.0〜1.0 で指定するものとします。
そして、筆圧最小時には、最大半径・濃度にこの値を掛けた数値が、半径・濃度の最小値になるようにします。

つまり、「パラメータが 0 なら、半径・濃度の最小値は 0」で、半径や濃度は、筆圧によって「0〜最大値」まで変化します。
「パラメータが 1.0 の場合は、半径・濃度の最小値は最大値と同じ」で、変化しないということで、半径や濃度は、筆圧の影響を全く受けなくなります。
スクリーンショット&ソースファイル


筆圧は、擬似的に一定間隔で変化します。
濃度の最小は 0.0 です。
1 キーで、半径の最小を 1.0、
2 キーで、半径の最小を 0.5、
3 キーで、半径の最小を 0.0 に変更できます。

>> 030_aaline6.c.txt
解説
筆圧値は、「0.0〜1.0〜0.0...」の値を繰り返すように、擬似的に生成されます。
パラメータ
線の描画に関するパラメータは、以下のようになっています。

double line_max_radius = 5,  /* 最大半径 */
    line_max_opacity = 0.2,  /* 最大濃度 */
    line_interval = 0.15,    /* 間隔 */
    line_min_radius = 0,     /* 最小半径 */
    line_min_opacity = 0,    /* 最小濃度 */

最大半径は px 単位、最大濃度は 0.0〜1.0、筆圧の最小半径・濃度は 0.0〜1.0
点の描画部分
今回は、line_max_opacity で、描画の最大濃度を 0.0〜1.0 の浮動小数点で指定するようにしたので、drawpoint() の引数には、描画する濃度の絶対値として opacity (0.0〜1.0) を渡すようにしました。

なので、実際にイメージに点を描画する際は、描画色のアルファ値を以下のように取得しています。

col.a = (int)(opacity * (cnt / 64.0) * 255 + 0.5);
直線描画部分
次は、直線描画部分です。

void drawline_aa(double x1,double y1,double x2,double y2,
    double press_st,double press_ed,double *last_t)
{
    double dx,dy,len,t,t_interval,x,y,dtmp,press,press_r,press_o,radius;
    
    /* 線の長さ */
    
    dx = x2 - x1;
    dy = y2 - y1;
    
    len = sqrt(dx * dx + dy * dy);
    if(len == 0) return;
    
    /* 線形補間 */
        
    t_interval = line_interval / len;
    
    t = *last_t / len;
    
    while(t < 1.0)
    {
        x = x1 + dx * t;
        y = y1 + dy * t;
        
        press = press_st + (press_ed - press_st) * t;
        
        press_r = line_min_radius + (1.0 - line_min_radius) * press;
        press_o = line_min_opacity + (1.0 - line_min_opacity) * press;
        
        radius = line_max_radius * press_r;
        
        drawpoint(x, y, radius, line_max_opacity * press_o);
    
        /* 次の位置 */
    
        dtmp = radius * t_interval;
        if(dtmp < 0.0001) dtmp = 0.0001;
    
        t += dtmp;
    }
    
    *last_t = len * (t - 1.0);
}

まず、線の始点と終点の筆圧値 (0.0〜1.0) を、引数で渡すようにしました。
press_st が始点の筆圧、press_ed が終点の筆圧です。

直線上の各点の筆圧は、座標と同じように線形補間で得ます。
press が、現在の点における筆圧値です。

press = press_st + (press_ed - press_st) * t;

では、この筆圧値を、半径と濃度に適用します。

半径・濃度の最大値に筆圧最小のパラメータを掛けたものが、実際の半径・濃度の最小値となるので、その最小値と最大値の幅に筆圧を適用して、値を得ることになりますが、

min_radius = line_max_radius * line_min_radius;
radius = min_radius + (line_max_radius - min_radius) * press;

このように、実際に最小値を計算してそれを使う方法の他に、以下のように、最大値に掛ける値を求める方法があります。

press_r = line_min_radius + (1.0 - line_min_radius) * press;
radius = line_max_radius * press_r;

press_r は、半径の最大値に掛ける、筆圧の適用割合です。
筆圧最小のパラメータと筆圧の値を掛けあわせて、一つの値にしています。
半径の最大値に掛ける値は、筆圧によって、「line_min_radius〜1.0」の範囲で変化するので、最小値を line_min_radius、最大値を 1.0 として、その範囲の値を求めてやります。

以上のように、描画濃度も同様に求めてやり、drawpoint() で、筆圧が適用された半径と濃度で点を描画します。

なお、間隔を適用して次の点の位置を求める際には、半径は、実際に描画された半径サイズを使って計算します。

dtmp = radius * t_interval;
if(dtmp < 0.0001) dtmp = 0.0001;

t += dtmp;
筆圧値の補正
ところで、ペイントソフトの中には、筆圧を曲線のグラフで調整できるようにしたものがあり、そのグラフの形を変更すると、筆圧の硬さ・柔らかさをソフトウェア上で補正して、描き味を変えることが出来るようになります。
(ここでは、複数の操作点を置いて操作するようなグラフは除く)

実は、あの曲線のグラフというのは、「ガンマ補正」です。
ガンマ補正は、画像の色補正としてよく名前を聞きます。
ガンマ補正の計算を、筆圧値の補正として使うと、簡単に値を補正することができます。

ガンマ補正には、pow() 関数を使います。
pow() 関数は、x の y 乗の値を返します。
以下が、ガンマ補正の計算です。

press = pow(press, 1.0 / gamma)

補正対象の値は、0.0〜1.0 の範囲にします。結果は、0.0〜1.0 の範囲になります。

gamma が、ガンマ補正のパラメータ値です。
1.0 で値は変化せず、0 より大きい値でグラフは膨らむ形となり、0 以下 (小数) の値でグラフはへこむ形となります。
値の上限・下限は決まっていませんが、大体 100〜0.17 くらいな感じで使うと良いでしょう。