JavaでStrokeを変更してお絵かきするとプルプルする現象の対策

Java

大分意味不明なタイトルですが、Javaでお絵かきソフトを作る際に少し躓いたところの対策メモです。

まず、MousePressed メソッドと MouseDragged メソッドに、以下のような絵かき処理のコードを書きました。

int mouseX, mouseY, mouseDragX, mouseDragY;
BufferedImage bufImage;

 private void MousePressed(java.awt.event.MouseEvent evt) {                                     
	mouseX = evt.getX();
	mouseY = evt.getY();
 }                                    

 private void MouseDragged(java.awt.event.MouseEvent evt) {                                     
	mouseDragX = evt.getX();
	mouseDragY = evt.getY();
	Graphics2D g = bufImage.createGraphics();
	g.setStroke(new BasicStroke(50));
	g.setColor(Color.black);
	g.drawLine(mouseX, mouseY, mouseDragX, mouseDragY);

	// bufImage を貼り付ける処理(省略)

	mouseX = mouseDragX;
	mouseY = mouseDragY;
 }                                    

マウスがプレスされた瞬間に現在座標を取得し、ドラッグのたびに直線を結び、お絵かきする処理となっています。
ここでは setStroke メソッドを用いることで、線の太さを 50 としています。

一見特に問題無いように思えますが、これを実行してお絵かきしてみると、次のようになります。

purupuru1

ペン形状が正方形の線をマウスで引くと、角度が少し変化する度に正方形の向きが変化するため、プルプルした変な絵になってしまいます。
なんだか気持ちが悪いので、完全なものではないですが修正していきます。

まず、2点間を直線で結ばず、点の描画を連続で行うと、次のようになります。

purupuru2

これだけでも大分滑らかな曲線になりますが、これではマウスのカーソルを動かすスピードが早くなると、飛び飛びの点になってしまいます。
そもそも、点が飛び飛びになるのを防ぐために2点を結んでいたので、この方法ではいけません。

しかしながら、この滑らかさはなかなか捨てがたいので、2点間の距離が大きく空いたときだけ直線で結び、
そうでない場合は点を描けばいいのではと思い、drawLine を以下のように実装しました。

	if (Math.hypot(mouseDragX-mouseX, mouseDragY-mouseY) > 10)
		g.drawLine(mouseX, mouseY, mouseDragX, mouseDragY);
	else
		g.drawLine(mouseDragX, mouseDragY, mouseDragX, mouseDragY);

Math.hypot で2点間の距離を計算し、ある数字以上(ここでは適当に 10 としていますが、これはストロークによって変化させなければいけません)の場合は直線を、そうでない場合は点を描くというようにしています。
実行結果は次のようになります。

purupuru3

完璧とは言い難いですが、大分マシにはなりました。
(ここでは hypot を使っていますが、ここには平方根を求める演算が含まれるため、距離の2乗で比較をしたほうが計算コストが軽くなります。)

また、正方形のペン形状に拘らないのであれば、以下のようにペン形状を円形にすることで、綺麗に曲線を描くことができます。

g.setStroke(new BasicStroke(50, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));

purupuru4

タイトルとURLをコピーしました