アンチエイリアス付きの自由線描画(3)・手ぶれ補正

手ぶれ補正
前回までは、アンチエイリアス付きの自由線描画ということでやっていましたが、線の描画自体は綺麗なものの、線の軌跡としてはあまり綺麗ではありませんでした。
マウスで描くと、特に線がブレているように感じると思います。

今回は、その線のブレを解消するため、手ぶれ補正について説明します。
やり方
「手ぶれ補正」というのは、つまり、描画する座標位置を補正するということです。
マウスなどから与えられた座標を元に、座標値を補正して、実際にはその新しい座標で描画します。

手ぶれ補正のやり方としては、以下のような方法があると思います。

  • 直近の過去の点を参照して、座標を補正し、直接その位置に描画していく
  • ドラッグ中のすべての座標を記録しておき、ボタンが離された後、すべての座標を元に補正計算して描画し直す

処理として簡単なのは、1番目の方法ですが、軌跡に正しく沿うようには補正されないので、手ぶれ補正としては完璧ではありません。

2番目の方法は、すべての座標を元に計算するので、全体的に綺麗にまとめることができますが、描画途中の線をどう扱うかが問題となります。
また、描画途中の線と実際の描画後の線が異なるので、ササッと描きたい場合には向かず、どちらかというと線の清書向けです。

実際に実装する場合は、描画時の用途別に、複数の方法の手ぶれ補正を用意しておくといいかもしれません。
今回は、1番目の、過去の点を参照する方法をやってみます。
スクリーンショット&ソースファイル


少し左右に揺らして線を描いてみました。

05 キー (テンキーは除く) で、手ぶれ補正の強さを変更できます。
0 で補正なし。1 で過去の1点を参照します。

>> 027_aaline3.c
解説
手ぶれ補正の強さ
手ぶれ補正の強さというのは、ここでは、どれだけ過去の点を参照するか、ということです。
参照する点が多いほど、過去の位置の影響を受けます。

ここでは、smooth_level に、過去の点を参照する数を格納してあります。
また、参照する過去の点として、ptLast[5] に、直近の5つ前までの座標を入れておきます。

なお、今回は強めに補正を掛けたかったので、過去の座標には、補正前の元の座標ではなく、補正後の座標を入れることにしています。
補正された座標を元にさらに補正を掛けることになるので、手ぶれ補正の強さが増します。
座標の補正
まず、ボタンが押された時は、過去の点を初期化する必要があるので、ptLast[5] すべてに、現在の位置を入れています。

そして、ポインタ移動時には、直線の終点位置である現在位置を補正します。
これが、手ぶれ補正の座標補正部分です。

x = ev->mouse.x;
y = ev->mouse.y;

for(i = 0; i < smooth_level; i++)
{
    x += ptLast[i].x;
    y += ptLast[i].y;
}

x /= smooth_level + 1;
y /= smooth_level + 1;

drawline_aa(ptLast[0].x, ptLast[0].y, x, y, line_radius, &line_last_t);

今回は、現在の位置と過去の位置の平均値を、補正後の座標とします。
なので、現在の位置に、smooth_level の数だけ過去の位置を加算し、それを (smooth_level + 1) で割って、平均値を出します。
平均値を使うことで、座標を滑らかにするというわけです。

なお、ptLast は、[0] が直前の位置、[1] がそれより一つ前の位置... という順に格納されています。

これで現在の位置が補正されたので、あとは前回の位置と現在の位置の間で直線を描画します。

その後は、過去の位置をずらして、現在の位置を新しく格納する必要があります。

for(i = 4; i > 0; i--)
    ptLast[i] = ptLast[i - 1];

ptLast[0].x = x;
ptLast[0].y = y;

一番遠い過去の位置を捨て、配列の値をずらし、先頭に現在の位置を入れます。
まとめ
補正の強さを大きくすると、線は遅れて描画されるように見えますが、それは現在位置が過去の位置に引きずられているからです。
過去の点を多く参照するほど、その位置に戻されるような形になるので、これが、過去の点を参照したやり方の欠点となります。

また、真っ直ぐな直線の場合は問題ないのですが、丸い円を描画しようとすると、補正が強いほど、線が円の内側に行くようになります。
軌跡に正しく沿うようには補正されない、というのはこのことです。

もう少し精度を上げたいのであれば、過去の位置に重みを掛けて、遠い位置ほど適用を少なくするようにすれば、多少は引きずられる幅が小さくなります。

しかし、それでも、補正を強くしようと思ったら、それだけ位置がずれることになるので、過去の点を参照するやり方には限界があります。
それを解消しようと思ったら、やはり別のやり方を考えるしかありません。