C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[8]: C#でマウスで線を引く


(過去ログ 84 を表示中)

[トピック内 16 記事 (1 - 16 表示)]  << 0 >>

■49422 / inTopicNo.1)  C#でマウスで線を引く
  
□投稿者/ kinton (4回)-(2010/05/04(Tue) 16:14:41)

分類:[.NET 全般] 

マウスでなめらかな線を引きたいのですが。
うまくいきません。C#です。
DrawLine()を使用して実行したとき。
太さを1にすると綺麗にひけるのですが。
太さを50にするとガタガタになってしまいます。

解決策として、FillEllipse()をDrawLine()とサイズを同じにして同時に描画したと所、サイズが大きくても綺麗な線を引けるのですが。
マウスを動かす速さによって線を太さを変えた場合(筆のように)、サイズが1から5など飛んだ数字になるとなめらかに太くならず。ガタガタの線になってしまいます。
C++ならMoveToEx()とLineTo()を使うとうまく行くのですが。
C#の場合綺麗な線を引くにはどうすればいいのでしょうか。

環境:VisualStudio2005 C# Windows7

    public partial class Form1 : Form
    {
        private Image image;
        private Color color;
        Point pic_old, pic_new;
        Brush brush;
        int size = 1;
        int move_x;
        int move_y;
        private Size es;
        
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            if (image != null)
            {
                e.Graphics.DrawImage(image, 0, 0);
            }
        }

        private void PenSize()
        {
	// ペンのサイズ
	// 筆ペン
            move_x = Math.Abs(pic_new.X - pic_old.X);
            move_y = Math.Abs(pic_new.Y - pic_old.Y);
            size = move_x + move_y;
            es = new Size(size, size);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (e.Button == MouseButtons.Left)
            {
                pic_old = pic_new;
                pic_new.X = e.X;
                pic_new.Y = e.Y;
                // ペンのサイズを決定
                PenSize();
            
                Pen m_pen = new Pen(color, es.Width);

                Graphics g = Graphics.FromImage(image);
                g.SmoothingMode = SmoothingMode.AntiAlias;
                Brush brush = new SolidBrush(color);
				
	      // 線の描画
                g.FillEllipse(
                    brush, e.X - (es.Width / 2), e.Y - (es.Height / 2),
                    es.Width, es.Height
                );

                g.DrawLine(m_pen, pic_old, pic_new);

            }
            Invalidate();
        }

        public Form1()
        {
            InitializeComponent();
            
            this.SetStyle(
            ControlStyles.DoubleBuffer |         // 描画をバッファで実行する
            ControlStyles.UserPaint |            // 描画は(OSでなく)独自に行う
            ControlStyles.AllPaintingInWmPaint,  // WM_ERASEBKGND を無視する
            true                                 // 指定したスタイルを適用「する」
            );
            color = Color.Black;
            image = new Bitmap(700, 300);
            
            Graphics g = Graphics.FromImage(image);
            brush = new SolidBrush(Color.White);
            g.FillRectangle(brush, 0, 0, image.Width, image.Height);
            brush = new SolidBrush(Color.Black);
        }

        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            pic_old.X = pic_new.X = e.X;
            pic_old.Y = pic_new.Y = e.Y;
        }

        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            pic_old.X = pic_new.X = e.X;
            pic_old.Y = pic_new.Y = e.Y;
        }
       
    }

引用返信 編集キー/
■49464 / inTopicNo.2)  Re[1]: C#でマウスで線を引く
□投稿者/ Jitta on the way (631回)-(2010/05/06(Thu) 07:41:32)
No49422 (kinton さん) に返信
> マウスでなめらかな線を引きたいのですが。
> うまくいきません。C#です。
> DrawLine()を使用して実行したとき。
> 太さを1にすると綺麗にひけるのですが。
> 太さを50にするとガタガタになってしまいます。
>
> 解決策として、FillEllipse()をDrawLine()とサイズを同じにして同時に描画したと所、サイズが大きくても綺麗な線を引けるのですが。
> マウスを動かす速さによって線を太さを変えた場合(筆のように)、サイズが1から5など飛んだ数字になるとなめらかに太くならず。ガタガタの線になってしまいます。
> C++ならMoveToEx()とLineTo()を使うとうまく行くのですが。
> C#の場合綺麗な線を引くにはどうすればいいのでしょうか。

今、調べられる環境ではないので、記憶とカンを頼りに適当な事を書きます。
楕円を描くのではなく、スプライン曲線を描くメソッドを使ってはどうでしょうか。
また、Pen の要素に、滑らかにするというか、アンチエイリアスか何か、そういう要素があったように思います。調べてみて下さい。
引用返信 編集キー/
■49470 / inTopicNo.3)  Re[1]: C#でマウスで線を引く
□投稿者/ よねKEN (492回)-(2010/05/06(Thu) 11:37:59)
2010/05/06(Thu) 13:16:17 編集(投稿者)
2010/05/06(Thu) 11:40:06 編集(投稿者)

■No49422 (kinton さん) に返信
> マウスを動かす速さによって線を太さを変えた場合(筆のように)、
> サイズが1から5など飛んだ数字になるとなめらかに太くならず。ガタガタの線になってしまいます。

1の線を引いて、5の線を引いたらガタガタになりますね。
1の線は既に引かれているので、5の線を引くときに、既に引いた1の線の半分くらいの部分に対して、
2の線、3の線、4の線と上書きで滑らかになるように間を補完する線を引く必要があるのではないでしょうか。

> C++ならMoveToEx()とLineTo()を使うとうまく行くのですが。

理論的にはこちらでもうまく行かないのでは?と思うのですが、
そうでもないのですかねぇ。
(MoveToExやLineToは使ったことがないので詳しくは知りませんが)

> C#の場合綺麗な線を引くにはどうすればいいのでしょうか。

提示のコードをもとに実験してみました。
実験してみた限りではそこそこ綺麗に書けていると思うので、
以下に変更点を記載します。

※考え方としては、数個分の点の履歴を保管しておき、
  過去に遡って新しいペンのサイズで連続した線として描画しなおしています。
※新しいサイズで過去に遡って描画しなおしているだけの簡易的なものなので、
  前回のペンサイズとの間のペンサイズを使って補完するような方法なら、
  もっと滑らかになるのではないかなと思います。
 
>     public partial class Form1 : Form
>     {
>         private Image image;
>         private Color color;
>         Point pic_old, pic_new;
>         Brush brush;
>         int size = 1;
>         int move_x;
>         int move_y;
>         private Size es;

          Queue<Point> oldPoints;          // 点の履歴
         
>         protected override void OnPaint(PaintEventArgs e)
>         {
>             base.OnPaint(e);
> 
>             if (image != null)
>             {
>                 e.Graphics.DrawImage(image, 0, 0);
>             }
>         }
> 
>         private void PenSize()
>         {
> 	// ペンのサイズ
> 	// 筆ペン
>             move_x = Math.Abs(pic_new.X - pic_old.X);
>             move_y = Math.Abs(pic_new.Y - pic_old.Y);
>             size = move_x + move_y;
>             es = new Size(size, size);
>         }
> 
>         protected override void OnMouseMove(MouseEventArgs e)
>         {
>             base.OnMouseMove(e);
>             if (e.Button == MouseButtons.Left)
>             {
>                 pic_old = pic_new;
>                 pic_new.X = e.X;
>                 pic_new.Y = e.Y;
>                 // ペンのサイズを決定
>                 PenSize();
>             
>                 Pen m_pen = new Pen(color, es.Width);

                // 線の両端や結合点を丸く描画するための設定
                m_pen.StartCap = LineCap.Round;
                m_pen.EndCap = LineCap.Round;
                m_pen.LineJoin = LineJoin.Round;

> 
>                 Graphics g = Graphics.FromImage(image);
>                 g.SmoothingMode = SmoothingMode.AntiAlias;
>                 Brush brush = new SolidBrush(color);
> 				
> 	      // 線の描画
>                 g.FillEllipse(
>                     brush, e.X - (es.Width / 2), e.Y - (es.Height / 2),
>                     es.Width, es.Height
>                 );
> 
>                 g.DrawLine(m_pen, pic_old, pic_new);

// 上のFillEllipseやDrawLineの呼び出し箇所はコメントアウトしてください。

                oldPoints.Enqueue(pic_new);
                if (oldPoints.Count > 10)     // 10の部分を適当に調整してください。
                {
                    oldPoints.Dequeue();
                }

                g.DrawLines(m_pen, oldPoints.ToArray());

>             }
>             Invalidate();
>         }
> 
>         public Form1()
>         {
>             InitializeComponent();
>             
>             this.SetStyle(
>             ControlStyles.DoubleBuffer |         // 描画をバッファで実行する
>             ControlStyles.UserPaint |            // 描画は(OSでなく)独自に行う
>             ControlStyles.AllPaintingInWmPaint,  // WM_ERASEBKGND を無視する
>             true                                 // 指定したスタイルを適用「する」
>             );
>             color = Color.Black;
>             image = new Bitmap(700, 300);
>             
>             Graphics g = Graphics.FromImage(image);
>             brush = new SolidBrush(Color.White);
>             g.FillRectangle(brush, 0, 0, image.Width, image.Height);
>             brush = new SolidBrush(Color.Black);

            oldPoints = new Queue<Point>();

>         }
> 
>         private void Form1_MouseDown(object sender, MouseEventArgs e)
>         {
>             pic_old.X = pic_new.X = e.X;
>             pic_old.Y = pic_new.Y = e.Y;

            oldPoints.Enqueue(pic_new);

>         }
> 
>         private void Form1_MouseUp(object sender, MouseEventArgs e)
>         {
>             pic_old.X = pic_new.X = e.X;
>             pic_old.Y = pic_new.Y = e.Y;

            // 一度筆を置いたら、履歴はクリア
            oldPoints.Clear();

>         }
>        
>     }

引用返信 編集キー/
■49492 / inTopicNo.4)  Re[2]: C#でマウスで線を引く
□投稿者/ kinton (5回)-(2010/05/06(Thu) 20:01:54)
>よねKEN さん

実行させていただきました。
だいぶ理想に近くなりました!
もともとC++のソースを参考にして作っているのですがC#ではこの部分はだいぶ動作が異なるようです。
アドバイスを参考に改良していきたいと思います。
ありがとうございます!

>Jittaさん
アドバイスありがとうございます。
スプライン曲線だと線の強弱が付きにくいかなということで除外していました。
また詰まったら、試行錯誤してみます。


解決済み
引用返信 編集キー/
■49498 / inTopicNo.5)  Re[3]: C#でマウスで線を引く
□投稿者/ Jitta (644回)-(2010/05/06(Thu) 21:49:06)
No49492 (kinton さん) に返信
 むっはー!Pen だと思っていたら、Graphics でした。
で、指定されてるやん!
> g.SmoothingMode = SmoothingMode.AntiAlias;

 さらに、ここの所を、まったく読み違えていました。
> マウスを動かす速さによって線を太さを変えた場合(筆のように)、
> サイズが1から5など飛んだ数字になるとなめらかに太くならず。
> ガタガタの線になってしまいます。

        □
        ■
        □
      ■■□■■
      ■■■■■
      ■■■■■
      ■■□■■
■■■■□■■■■■
■■■■■■■■■■
■■■■■■■■■■
■■■■■□■■■■

↑これだと、「ガタガタ」ですね。

        □
        ■■
        ■■■
      ■■■■■
      ■■■■■
    ■■■■■■
    ■■■■■■
  ■■■■■■■■
■■■■■■■■■
■■■■■■■■■■
■■■■■□■■■■

↑この様にする必要があるのでは、ないでしょうか。

        □始点
        ■■
        ■  ■
      ■      ■
      ■      ■
    ■        ■
    ■        ■
  ■            ■
■              ■
■                ■
○■■■■□■■■○
         終点

始点での幅と、終点での幅を求め、始点から終点の幅に対して、太さ1の線を引き、
できあがった三角形の中を塗れば、良いのではないでしょうか。

って、書き置きしていたので投稿。

解決済み
引用返信 編集キー/
■49508 / inTopicNo.6)  Re[2]: C#でマウスで線を引く
□投稿者/ .SHO (1325回)-(2010/05/06(Thu) 23:54:24)
No49470 (よねKEN さん) に返信

>>C++ならMoveToEx()とLineTo()を使うとうまく行くのですが。
>
> 理論的にはこちらでもうまく行かないのでは?と思うのですが、
> そうでもないのですかねぇ。
> (MoveToExやLineToは使ったことがないので詳しくは知りませんが)

これもうまくいかないと思います。

解決済み
引用返信 編集キー/
■49512 / inTopicNo.7)  Re[2]: C#でマウスで線を引く
□投稿者/ 匿名で (2回)-(2010/05/07(Fri) 00:50:55)
2010/05/07(Fri) 00:54:09 編集(投稿者)

No49470 (よねKEN さん) に返信
> 1の線を引いて、5の線を引いたらガタガタになりますね。
> 1の線は既に引かれているので、5の線を引くときに、既に引いた1の線の半分くらいの部分に対して、
> 2の線、3の線、4の線と上書きで滑らかになるように間を補完する線を引く必要があるのではないでしょうか。

ペンサイズ1からペンサイズ5に状態が変わるときに、
移動距離を5等分して、順にペンサイズ1、2、3、4、5の太さで描画するようなコードを実験的に
組んでみましたが、単にこのようにしただけではあまり滑らかにはならないですね。

太い線は長方形のため、線を線として描画したのでは、思ったようには行きません。
考え方的には、No49498 のJittaさんの指摘のように三角形で考えるべきなのかもしれません。

解決済み
引用返信 編集キー/
■49513 / inTopicNo.8)  Re[3]: C#でマウスで線を引く
□投稿者/ よねKEN (495回)-(2010/05/07(Fri) 00:54:59)
2010/05/07(Fri) 00:55:53 編集(投稿者)

> ■No49512 (匿名で さん) に返信

#ギャー・・・ハンドル戻し忘れたorz
名前があれですが、よねKENです。

<修正>
さらに解決済みチェックをうっかりはずしてしまったので修正orz
</修正>


解決済み
引用返信 編集キー/
■49539 / inTopicNo.9)  Re[4]: C#でマウスで線を引く
□投稿者/ Jitta on the way (633回)-(2010/05/07(Fri) 18:20:29)
もうひとつ。PageUnit が指定されていないように思います。これを、Pixel にしておきましょう。
デフォルトは、ミリメートルとかそんなで、「5ピクセル」の太さであるつもりが「5ミリメートル(等の、別の単位)」になっている可能性があります。
解決済み
引用返信 編集キー/
■49543 / inTopicNo.10)  Re[3]: C#でマウスで線を引く
□投稿者/ よねKEN (496回)-(2010/05/07(Fri) 22:10:40)
2010/05/07(Fri) 23:02:09 編集(投稿者)
もう少し綺麗にならないかと試行錯誤してみて、よさそうな方法を見つけたので掲載します。
1回のMouseMoveイベントでのペンサイズの変化量に上限を持たせる方法です。
(味気ない筆跡になってしまうので、これがよいかどうかはわかりませんが、
 ガタガタになるという問題点の解決策の一つとしてありかと思います)

スレ主さんの提示コードを元にしていますが、
変数名とか細々とした記述とかは大幅にいじっています(ただし、本筋には関係なし)ので、
要点だけコードから読み取ってもらえればと思います。(重要なところは★マークを入れています)

動作確認はVisual Studio 2010 Express Edition、Windows 7 Ultimate 64bitで行っています。
(VS2005で利用できる構文しか使っていませんので特に問題ないと思います)
--
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace PaintApp
{
    public partial class Form1 : Form
    {
        private Image image;
        private Color color;
        Point oldPoint, newPoint;
        int oldPenWidth, newPenWidth;
        Graphics g;
        Queue<Point> oldPoints;

        public Form1()
        {
            InitializeComponent();
        }

        private int GetPenWidth(Point point1, Point point2)
        {
            // 筆ペン - ペンのサイズ
            int penWidth = Math.Abs(point2.X - point1.X) + Math.Abs(point2.Y - point1.Y);
            return penWidth > 0 ? penWidth : 1;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (image != null)
            {
                e.Graphics.DrawImage(image, 0, 0);
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                oldPoint = newPoint;
                newPoint = e.Location;
                oldPoints.Enqueue(newPoint);
                if (oldPoints.Count > 10)
                {
                    oldPoints.Dequeue();
                }

                // ★ 前回のペンサイズを保管しておき、
                // ペンサイズの変化量の幅を最大で5に押さえるようにしています。
                oldPenWidth = newPenWidth;
                int delta = GetPenWidth(oldPoint, newPoint) - oldPenWidth;
                int sign = Math.Sign(delta);
                newPenWidth = oldPenWidth + sign * Math.Min(5, Math.Abs(delta));

                using (Pen pen = new Pen(color, newPenWidth))
                {
                    pen.StartCap = LineCap.Round;
                    pen.EndCap = LineCap.Round;
                    pen.LineJoin = LineJoin.Round;

                    g.DrawLines(pen, oldPoints.ToArray());
                }
                this.Invalidate();
            }
        }

        protected override void OnLoad(EventArgs e)
        {
            this.DoubleBuffered = true;
            color = Color.Black;
            image = new Bitmap(800, 600);

            g = Graphics.FromImage(image);
            g.SmoothingMode = SmoothingMode.AntiAlias;

            // ★補完まわりの品質向上のために以下の2行を指定しています。
            g.PixelOffsetMode = PixelOffsetMode.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;

            g.FillRectangle(Brushes.White, 0, 0, image.Width, image.Height);
            oldPoints = new Queue<Point>();
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                oldPoint = newPoint = e.Location;
                oldPenWidth = newPenWidth = 1;

                oldPoints.Enqueue(newPoint);
            }
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                oldPoints.Clear();
            }
        }
    }
}

//------ ここまで -------------------------------

<修正> 
OnMouseDown/OnMouseUpにif (e.Button == MouseButtons.Left) {}の条件制御を追加しました。
</修正>

引用返信 編集キー/
■49547 / inTopicNo.11)  Re[4]: C#でマウスで線を引く
□投稿者/ Jitta on the way (634回)-(2010/05/08(Sat) 09:38:14)
No49543 (よねKEN さん) に返信

これ、ひとつ思ったのが、ペンの太さが前回からの移動量に比例していますよね。それって、どうなんですかね?筆だと、単位時間あたりの移動量が多いほど、細くなるんじゃないかなぁ?もしくは、時間の経過と太さが関係するとか。
まぁ、マウスでは「筆圧」に相当するものを検出出来ないけど。

# 解決チェックは、はずしていいのかな?
引用返信 編集キー/
■49549 / inTopicNo.12)  Re[5]: C#でマウスで線を引く
□投稿者/ よねKEN (497回)-(2010/05/08(Sat) 12:42:45)
No49547 (Jitta on the way さん) に返信
> ■No49543 (よねKEN さん) に返信
>
> これ、ひとつ思ったのが、ペンの太さが前回からの移動量に比例していますよね。それって、どうなんですかね?筆だと、単位時間あたりの移動量が多いほど、細くなるんじゃないかなぁ?もしくは、時間の経過と太さが関係するとか。

そうですね。私も「ゆっくり筆を動かした場合は太くて、はやく筆を動かした場合は細くなる」方がたぶん自然だと思います。
その場合は、GetPenWidthメソッド(スレ主様のコードで言うとPenSizeメソッド)のロジックをいじって
太さの変化のルールを変えれば実現できるんじゃないかと思います。
※ちなみにより自然と思われる方のルールも私なりに考えて見たのですが、
 今のところしっくりくる見栄えはできていません。

#元々の質問でも筆っぽいものを想定されていることは書かれていますが、
#だからといって別にリアルな筆さばきを追求しているのかどうかはわかりませんので、
#必要であればリアルな筆さばきを実現するロジックを追求すればいいかなと思います。

> # 解決チェックは、はずしていいのかな?

新案を思いついたのではずしてみたのですが、スレ主がまだご覧になっているかは
わからないので、やっぱり戻しておきます(^^;
解決済み
引用返信 編集キー/
■49557 / inTopicNo.13)  Re[4]: C#でマウスで線を引く
□投稿者/ 未記入 (3回)-(2010/05/08(Sat) 20:41:27)
No49498 (Jitta さん) に返信
> □始点
> ■■
> ■ ■
> ■ ■
> ■ ■
> ■ ■
> ■ ■
> ■ ■
> ■ ■
> ■ ■
> ○■■■■□■■■○
> 終点
>
> 始点での幅と、終点での幅を求め、始点から終点の幅に対して、太さ1の線を引き、
> できあがった三角形の中を塗れば、良いのではないでしょうか。
>

ちょww
なにこのいい加減な回答www

だめにきまってんじゃんww
引用返信 編集キー/
■49719 / inTopicNo.14)  Re[6]: C#でマウスで線を引く
□投稿者/ kinton (6回)-(2010/05/13(Thu) 23:38:47)
こんにちは、
見直しに来てみればなんだかいつの間に書き込みが。
気付けなくて申し訳ありませんでした。

>よねKEN さん
>Jitta さん

> これ、ひとつ思ったのが、ペンの太さが前回からの移動量に比例していますよね。それって、どうなんですかね?筆だと、単位時間あたりの移動量が多いほど、細くなるんじゃないかなぁ?もしくは、時間の経過と太さが関係するとか。

その通りです。私も逆になってることに後で気がつきました。
一応以下のような式で実行しているところです。

            // 筆ペン - ペンのサイズ
            int max = 50; // ペン最大サイズ
            int min = 1;  // ペン最小サイズ
            int distance =  Math.Abs(point2.X - point1.X) + Math.Abs(point2.Y - point1.Y);
            if (distance > max) distance = max;
            if (distance < min) distance = min;
            int penWidth = max - distance + min;

> 始点での幅と、終点での幅を求め、始点から終点の幅に対して、太さ1の線を引き、
> できあがった三角形の中を塗れば、良いのではないでしょうか。

三角形、そんな発想はありませんでした。考えてみます。
ありがとうございます。



> 新案を思いついたのではずしてみたのですが、スレ主がまだご覧になっているかは
> わからないので、やっぱり戻しておきます(^^;

よろしければ参考に教えていただけると嬉しいです!
よろしくお願いいたします。

引用返信 編集キー/
■49721 / inTopicNo.15)  Re[7]: C#でマウスで線を引く
□投稿者/ よねKEN (499回)-(2010/05/14(Fri) 00:27:35)
No49719 (kinton さん) に返信
>>新案を思いついたのではずしてみたのですが、スレ主がまだご覧になっているかは
>>わからないので、やっぱり戻しておきます(^^;
>
> よろしければ参考に教えていただけると嬉しいです!
> よろしくお願いいたします。

その新案は、No49543 の投稿のコードのことを指しております(^^;
引用返信 編集キー/
■49749 / inTopicNo.16)  Re[8]: C#でマウスで線を引く
□投稿者/ kinton (7回)-(2010/05/14(Fri) 22:28:29)
No49721 (よねKEN さん) に返信
> ■No49719 (kinton さん) に返信
> >>新案を思いついたのではずしてみたのですが、スレ主がまだご覧になっているかは
> >>わからないので、やっぱり戻しておきます(^^;
>>
>>よろしければ参考に教えていただけると嬉しいです!
>>よろしくお願いいたします。
>
> その新案は、No49543 の投稿のコードのことを指しております(^^;

あ、そうだったのですね、文脈を読み違えていました。
試してみます。ありがとうございました!
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -