トップ回答者
お絵かきソフトのズームアップ機能を実現するには。

質問
-
質問させていただきます。
VisualC#ExpressEdition2008でお絵かきソフトの様なものを作ろうと思っています。
Photoshop等のソフトを使ってみると分かりやすいと思うのですが、画像を2倍にズームアップしたときは、その上から新たに線などを描画する場合に、4つ分のピクセルが1ピクセルとして扱われると思います。
逆にいうと、幅が1ピクセルという指定で線を描いた場合は、2ピクセルの線が描画されるということです。
ようするに描画する図形や線すべてが2倍の大きさで扱われれば嬉しいのですが。
pictureBoxコントロールの上で、簡単にそのような機能を実現させることは可能でしょうか?
わりと初心者なので何か思い違いをしているかもしれませんが、よろしくお願いします。
回答
-
線の感じが思われてるのと同じかどうかわかりませんが、
私がやるならこんな感じです。
かなり、てきとーですので、補完してください。イメージだけ掴んでください。
Code Snippetpublic Form1()
{
InitializeComponent();
}private Bitmap orgbmp;
private Bitmap zoombmp;
private int zoomratio;private void Form1_Load(object sender, EventArgs e)
{
orgbmp=new Bitmap(@"c:\test.jpg");
zoomratio = 4;zoombmp = new Bitmap(orgbmp.Width * zoomratio, orgbmp.Height * zoomratio, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(zoombmp);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;g.DrawImage(orgbmp, new Rectangle(0, 0, zoombmp.Width, zoombmp.Height), new Rectangle(0, 0, orgbmp.Width, orgbmp.Height), GraphicsUnit.Pixel);
g.Dispose();this.pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize;
this.pictureBox1.Image = zoombmp;
}
private void pictureBox1_Click(object sender, EventArgs e)
{}
private int startx;
private int starty;
private int endx;
private int endy;private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
int tempx;
int tempy;
startx=System.Math.DivRem(e.X , zoomratio,out tempx);
starty=System.Math.DivRem(e.Y, zoomratio, out tempy);}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
int tempx;
int tempy;
endx=System.Math.DivRem(e.X, zoomratio,out tempx);
endy=System.Math.DivRem(e.Y, zoomratio, out tempy);Graphics g = Graphics.FromImage(orgbmp);
g.DrawLine(Pens.Red, startx, starty, endx, endy);
g.Dispose();
Graphics g2 = Graphics.FromImage(zoombmp);
g2.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;g2.DrawImage(orgbmp, new Rectangle(0, 0, zoombmp.Width, zoombmp.Height), new Rectangle(0, 0, orgbmp.Width, orgbmp.Height), GraphicsUnit.Pixel);
g2.Dispose();
this.pictureBox1.Image = zoombmp;
} -
外池です。作ろうとしている機能のイメージは良く理解できました。
で、さらに細かく分けて考えると、次のような感じになるのではないかと思います。
-
原画のビットマップ画像そのものと、現ズーム画像が原画のどの部分なのかを示す座標の保持。
-
マウスの位置を、原画の位置に変換する機能。
-
ズーム画像を描画する機能。(Paintイベントで再描画する機能)
-
マウスでズーム範囲を指示する機能。(マウスの位置情報をとりつつ、かつ、ズームした範囲を示す四角い枠を表示する)
で、最後のご質問は、たぶん、4に関することだと思いますが? 私も似たようなプログラムを作ったことがありますが、概要は次のような感じです。
MouseDownで、
マウス・ドラッグのモードを示すFragを立てる。
マウス・ドラッグの基点の座標を記録する。
MouseMoveで、
マウス・ドラッグのモードでなければなにもしない。
現時点の座標を記録する。
再描画を指示する。(描画面のFormなり、PictureBoxなりのInvalidateメソッドを呼び出す。この際、Invalidateする範囲を全面にせずに、基点と現時点の座標で囲われる四角形にしておけば、再描画のスピードアップが期待できる。)
MouseUpで、
マウス・ドラッグの終点の座標を記録する。
マウス・ドラッグのモードを示すFragを下げる。
基点と終点の座標を用いて、次のズーム画像を生成する処理を呼び出す。
Paintイベントで、
現ズーム画像を描画する。(マウス・ドラッグのモードでなければ、ここで終わり。)
マウス・ドラッグのモードであれば、マウスドラッグの基点と現時点の座標で囲われる四角を描画する。
ミソはですね・・・、Mouse関連のイベントの中で描画するのではなく、座標を記録するだけ。描画が必要な場合は、Paintイベントが起こるように(Paintイベントを呼び出すわけじゃないのも大切なミソ)、Invalidateを呼び出します。Refreshでも良いかもしれません。描画はすべてPaintイベントに書きます。
-
すべての返信
-
線の感じが思われてるのと同じかどうかわかりませんが、
私がやるならこんな感じです。
かなり、てきとーですので、補完してください。イメージだけ掴んでください。
Code Snippetpublic Form1()
{
InitializeComponent();
}private Bitmap orgbmp;
private Bitmap zoombmp;
private int zoomratio;private void Form1_Load(object sender, EventArgs e)
{
orgbmp=new Bitmap(@"c:\test.jpg");
zoomratio = 4;zoombmp = new Bitmap(orgbmp.Width * zoomratio, orgbmp.Height * zoomratio, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(zoombmp);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;g.DrawImage(orgbmp, new Rectangle(0, 0, zoombmp.Width, zoombmp.Height), new Rectangle(0, 0, orgbmp.Width, orgbmp.Height), GraphicsUnit.Pixel);
g.Dispose();this.pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize;
this.pictureBox1.Image = zoombmp;
}
private void pictureBox1_Click(object sender, EventArgs e)
{}
private int startx;
private int starty;
private int endx;
private int endy;private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
int tempx;
int tempy;
startx=System.Math.DivRem(e.X , zoomratio,out tempx);
starty=System.Math.DivRem(e.Y, zoomratio, out tempy);}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
int tempx;
int tempy;
endx=System.Math.DivRem(e.X, zoomratio,out tempx);
endy=System.Math.DivRem(e.Y, zoomratio, out tempy);Graphics g = Graphics.FromImage(orgbmp);
g.DrawLine(Pens.Red, startx, starty, endx, endy);
g.Dispose();
Graphics g2 = Graphics.FromImage(zoombmp);
g2.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;g2.DrawImage(orgbmp, new Rectangle(0, 0, zoombmp.Width, zoombmp.Height), new Rectangle(0, 0, orgbmp.Width, orgbmp.Height), GraphicsUnit.Pixel);
g2.Dispose();
this.pictureBox1.Image = zoombmp;
} -
ご返信ありがとうございます!
確認できましたが、思った通りの機能でした。
ありがとうございました。
これは、orgbmpに線を描画してから、拡大して再度描画しているということでしょうか。
線の描き方についてですが。
やりたかったのはマウスを動かすだけで線が描画される方法です。
よく参考書に書いてあるMouseMoveイベントの度に線を描画していくというものです。
しかし、書いていただいたソースを変更してMouseMoveイベントで描画してみたのですが、この場合は、どうも処理に時間がかかるようで、スムーズに線が描かれませんでした。
できれば、マウスを動かして線を描く場合にもこのような処理をしたいのですが、基本的には書いていただいた方法でやるしかないのでしょうか?
よろしくお願いします。
-
外池と申します。ひとつ確認させてください。
きょうたさんの仰る「お絵かき」は、ビットマップ画像ですか? ベクトル画像ですか? どちらにするかで、根本的にプログラムが異なってきます。
ビットマップ画像とは、オリジナルのデータ(原寸サイズの画像データ)は、小さな画素、いわゆるピクセルの集合体です。デジカメの画像なんかは、この類です。例えば、ですが、横640ピクセル×縦480ピクセル、というような感じで、もし、「線」を描いたとしても、実際にはピクセルが連なっているだけです。このような画像をズームアップすることは、プログラムとしては簡単なのですが、決定的な欠陥があります。ズームアップすると、輪郭のガタガタが目立つようになります。ガタガタが目立たないようにすることもできますが、その場合は輪郭がボヤケます。「線」を描いたとしても、連なったピクセルとして描きおわったら、どこに線があるのか情報は失われます。Photoshopなどはこのタイプです。
一方で、ベクトル画像とは、オリジナルのデータはすべて「座標」や「大きさ」で格納されています。線(直線)なら、両端の点の座標、線の太さです。円(円周)なら、中心の座標と半径、線の太さです。画像を表示するたびにこれらの線を結んで描画する作業をしなければなりません(そのようなプログラムを書く)。かなり大変ですが、利点があります。ズームアップしても輪郭が汚くなったりボヤけたりしません。Powerpointなどはこのタイプです。CADソフトも。「大変」と書きましたが、基本的な図形を描く機能は.Net Frameworkに備わっていますので・・・、なんとかなると思います。
-
-
外池です。作ろうとしている機能のイメージは良く理解できました。
で、さらに細かく分けて考えると、次のような感じになるのではないかと思います。
-
原画のビットマップ画像そのものと、現ズーム画像が原画のどの部分なのかを示す座標の保持。
-
マウスの位置を、原画の位置に変換する機能。
-
ズーム画像を描画する機能。(Paintイベントで再描画する機能)
-
マウスでズーム範囲を指示する機能。(マウスの位置情報をとりつつ、かつ、ズームした範囲を示す四角い枠を表示する)
で、最後のご質問は、たぶん、4に関することだと思いますが? 私も似たようなプログラムを作ったことがありますが、概要は次のような感じです。
MouseDownで、
マウス・ドラッグのモードを示すFragを立てる。
マウス・ドラッグの基点の座標を記録する。
MouseMoveで、
マウス・ドラッグのモードでなければなにもしない。
現時点の座標を記録する。
再描画を指示する。(描画面のFormなり、PictureBoxなりのInvalidateメソッドを呼び出す。この際、Invalidateする範囲を全面にせずに、基点と現時点の座標で囲われる四角形にしておけば、再描画のスピードアップが期待できる。)
MouseUpで、
マウス・ドラッグの終点の座標を記録する。
マウス・ドラッグのモードを示すFragを下げる。
基点と終点の座標を用いて、次のズーム画像を生成する処理を呼び出す。
Paintイベントで、
現ズーム画像を描画する。(マウス・ドラッグのモードでなければ、ここで終わり。)
マウス・ドラッグのモードであれば、マウスドラッグの基点と現時点の座標で囲われる四角を描画する。
ミソはですね・・・、Mouse関連のイベントの中で描画するのではなく、座標を記録するだけ。描画が必要な場合は、Paintイベントが起こるように(Paintイベントを呼び出すわけじゃないのも大切なミソ)、Invalidateを呼び出します。Refreshでも良いかもしれません。描画はすべてPaintイベントに書きます。
-
-
外池さんありがとうございます。
説明していただいたことですが、これはズームアップの機能ということでしょうか。
Invalidateは参考書なんかでも見たことがなかったので、やってみたいと思います。
線の描画に関しては、はなはなはなさんに教えていただいた様に、画面の一部分だけを小さい画像でビットマップに描いてからその後に、その一部分だけを拡大するようにしてみてみました。
しかし、どうしても数ピクセル場所がずれて描画される場合があります。
これもゆっくり時間をかけて考えないとダメそうです。
たぶん人に聞いても理解できないと思いますので、自分でじっくりやってみたいと思います。
そんなわけで、ひとまずまた、自分でがんばってみようと思います。
ありがとうございました!