(New to this and playing around with a basic paint application) Ive found detailed instructions to code a flood-fill but as i am new it is very hard to understand every bits of it, and instead of copying, i would like to try to make my own simple(small scale) flood-fill.
Would it be possible to use fillpath as a flood-fill? i would draw paths and use my mouse to determine my x,y, on screen and have the graphicspath find out if it has borders(points from the drawn paths) and if so, fill these paths with a color?
this is what ive come up with but obviously it doesnt work, so how would i go about to make this working?
namespace WindowsFormsApplication3
{
public partial class Form1 : Form
{
Graphics g;
readonly Pen pen = new Pen(Color.Navy, 2);
Point oldCoords;
GraphicsPath graphicsPaths = new GraphicsPath();
bool spaceFound = false;
public Form1()
{
InitializeComponent();
g = panel1.CreateGraphics();
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
Point mousePt = new Point(e.X, e.Y);
if (e.Button == MouseButtons.Right &&
graphicsPaths.IsVisible(mousePt))
{
spaceFound = true;
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (oldCoords.IsEmpty)
graphicsPaths.StartFigure();
else
{
graphicsPaths.AddLine(oldCoords, new Point(e.X, e.Y));
g.DrawPath(pen, graphicsPaths);
}
oldCoords = new Point(e.X, e.Y);
}
else
oldCoords = Point.Empty;
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
g.DrawPath(pen, graphicsPaths);
if(spaceFound == true)
{
g.FillPath(Brushes.AliceBlue, graphicsPaths);
}
}
}
}
Yes, this is quite possible; of course you would want to store the path in a List<GraphicsPath> in the MouseUp event to allow for more filled shapes..
You need to correct a few issues in your code:
Set the path.FillMode to Winding
Never cache a Graphics object
Never use control.CreateGraphics()
Don't cache Pens or Brushes
Only draw in the Paint event, unless you do not want the drawing to persist
The last point might actually apply here: Maybe you don't want the currently drawing outline to stay visible? In that, and only that case you can stick with drawing it in the MouseMove with a Graphics object created there on the fly.
Here is a corrected version:
Point oldCoords;
GraphicsPath graphicsPaths = new GraphicsPath() { FillMode = FillMode.Winding };
bool spaceFound = false;
private void drawPanel1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right && graphicsPaths.IsVisible(e.Location))
{
spaceFound = true;
drawPanel1.Invalidate();
}
}
private void drawPanel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (oldCoords.IsEmpty) graphicsPaths.StartFigure();
else
{
graphicsPaths.AddLine(oldCoords, new Point(e.X, e.Y));
drawPanel1.Invalidate();
}
oldCoords = new Point(e.X, e.Y);
}
else oldCoords = Point.Empty;
}
private void drawPanel1_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Black, 2f))
e.Graphics.DrawPath(pen, graphicsPaths);
if (spaceFound == true)
{
e.Graphics.FillPath(Brushes.AliceBlue, graphicsPaths);
}
}
Note that it will fill your path but not in the way of a true floodfill, i.e. it will always fill the whole path, not just the innermost segment you have clicked in. For a true floodfill much more involved code is needed that actually goes over all neighbouring pixels starting at the click location..
Examples of a true floodfill are here and here
Related
I'm new to c#, coming from a python / wxpython background. I'm still very inexperienced.
I'm trying to create a user workspace that allows objects that have a representative shape (box or circle) to be placed. I'd like the user to be able to pan and zoom within the workspace. Eventually I want to have the items have attributes and be able to be connected together with lines.
I created a simple c# app that allows me to paint squares over a tiled .png grid background, but I can't figure out to set up a real workspace to pan or zoom.
At this stage some high level suggestions would be great.
Here is my code just FYI (be kind)
public partial class MainForm : Form
{
bool global_draw_redbox = false;
public MainForm()
{
InitializeComponent();
}
private void redbox(Point mousepos)
{
System.Drawing.Graphics graphicsObj;
graphicsObj = CreateGraphics();
Pen myPen = new Pen(System.Drawing.Color.Red, 3);
Rectangle myRectangle = new Rectangle(mousepos.X - 125, mousepos.Y - 100, 250, 200);
graphicsObj.DrawRectangle(myPen, myRectangle);
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
var relativePoint = this.PointToClient(Cursor.Position);
if (global_draw_redbox)
{
//Draw the object based on the mouse position
redbox(relativePoint);
//Reset the flag so we draw just one
global_draw_redbox = false;
}
else
{
MessageBox.Show("No object selected");
}
}
private void Form1_MouseDoubleClick(object sender, MouseEventArgs e)
{
MessageBox.Show("Test Mouse Double Click");
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
var relativePoint = this.PointToClient(Cursor.Position);
XY_Mouse_Position.Text = relativePoint.ToString();
}
private void boxToolStripMenuItem_Click(object sender, EventArgs e)
{
global_draw_redbox = true;
}
}
I'm very new(read 3 weeks exp) to C#(programming in general),started with html/css and javascript and now on my way with C#.
I'm trying to make my own simple 'Paint' application in windows form. But i've encountered an issue and just cant wrap my head around it, doesnt matter how much i read or follow other mans code, i'm stuck. The following code works fine but when resizing the application window the drawing dissappears.
As a solution ive read that declaring the Graphics method within the panel1_Paint event this should be resolved And here is my issue. See last code sample, ive come up with this(yes like i said, im new to this)and its not drawing anything.
ive simply tried to recreate the first example under the panel1_Paint event but i guess something went wrong during the mouseMove event and i cant figure out what it is.
Could someone explain to me what i am missing here, that would be very appreciated. thanks in advance.
[Old code]
namespace Painter
{
public partial class Form1 : Form
{
Graphics graphics;
Pen pen = new Pen(Color.Black, 1);
Point startingPoint = new Point(0, 0);
Point endPoint = new Point(0, 0);
bool mousePaint = false;
public Form1()
{
InitializeComponent();
this.DoubleBuffered = true;
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
startingPoint = e.Location;
if (e.Button == MouseButtons.Left)
{
mousePaint = true;
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if(mousePaint == true)
{
endPoint = e.Location;
graphics = panel1.CreateGraphics();
graphics.DrawLine(pen, startingPoint, endPoint);
}
startingPoint = endPoint;
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
mousePaint = false;
}
}
}
[New Code]
namespace Painter
{
public partial class Form1 : Form
{
Pen pen = new Pen(Color.Black, 1);
Point startingPoint = new Point(0, 0);
Point endPoint = new Point(0, 0);
bool mousePaint = false;
public Form1()
{
InitializeComponent();
this.DoubleBuffered = true;
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics graphics = panel1.CreateGraphics();
if (mousePaint == true)
{
graphics.DrawLine(pen, startingPoint, endPoint);
}
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
startingPoint = e.Location;
if (e.Button == MouseButtons.Left)
{
mousePaint = true;
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if(mousePaint == true)
{
endPoint = e.Location;
}
startingPoint = endPoint;
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
mousePaint = false;
}
}
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics graphics = panel1.CreateGraphics();
This is nonsense! Always and only use the e.Graphics object from the Paint param!!
Also: To trigger the Paint event do a panel1.Invalidate(); whenever your drawing data have changed!
Also: Make sure you understand just what your mousePaint flag is supposed to control: the mouse painting (i.e. adding new shapes to draw) or the regular painting (i. all shape previously drawn)!? Note that all drawing, current and previous needs to be done from the Paint event, whenever necessary i.e. over and over again!
To be able to do so: Collect all the shpes' data in a List<T>..
To Doublebuffer a Panel you need to subclass it. Your code turns on DoubleBuffering for the Form, which fine but won't help the Panel..
Instead simply use a PictureBox, which is control meant for drawing on!
A DoubleBuffered Panel subclass is as simple as this:
class DrawPanel : Panel
{
public DrawPanel()
{
DoubleBuffered = true;
}
}
Update: Instead you can also use a Label (with Autosize=false); it also has the DoubleBuffered property turned on out of the box and supports drawing better than Panels do.
The following code works fine but when resizing the application window the drawing dissappears.
This happens because resizing the application window invalidates portion of your panel which causes the portion to be redrawn.
Reason why your second approach is not working (the one labelled as [NEW CODE]) is because the Paint event is called only when relevant component is redrawn. You could partially solve this by forcing redraw of the panel in your MouseDown/MouseMove event handlers but you would still lose your previously painted stuff.
Possible solution is to create instance of Bitmap and paint there. Then just set this Bitmap as BackgroundImage of the panel. You can find more information on that here. Of course you would need to think about stuff like resizing and what should happen to the bitmap if application window gets shrunk or enlarged.
Here is some code that I quickly put together to get you started:
namespace WinForms_PaintTest
{
public partial class Form1 : Form
{
private Pen pen;
private Bitmap bitmap;
public Form1()
{
InitializeComponent();
this.pen = new Pen(Color.Black, 1);
this.bitmap = new Bitmap(this.panel1.Width, this.panel1.Height);
this.panel1.BackgroundImage = this.bitmap;
}
private void panel1_MouseMove(Object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
using (Graphics g = Graphics.FromImage(this.bitmap))
{
g.DrawRectangle(this.pen, e.Location.X, e.Location.Y, 1, 1);
}
this.panel1.Refresh();
}
}
private void Form1_FormClosed(Object sender, FormClosedEventArgs e)
{
this.pen.Dispose();
this.bitmap.Dispose();
}
}
}
Also regarding this:
this.DoubleBuffered = true;
I believe your intention was to prevent the flickering when relevant control is being redrawn? If that is case you need to set this property against the panel and not against the form itself. It is little bit tricky though because DoubleBuffered property of the panel is protected so you will need to either inherit from the panel or resort to reflection. You can find more information here .
I have a custom PictureBox which can zoom in using MouseWheel event. Now I want to add a panning feature to it. I mean when PictureBox is in zoomed state, if user left clicks and holds the click then move the mouse, the image would pan within the picturebox.
Here is my code but unfortunately it does not work! I don't know where to look anymore...
private Point _panStartingPoint = Point.Empty;
private bool _panIsActive;
private void CurveBox_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Focus();
_panIsActive = true;
_panStartingPoint = e.Location;
}
}
private void CurveBox_MouseUp(object sender, MouseEventArgs e)
{
_panIsActive = false;
}
private void CurveBox_MouseLeave(object sender, EventArgs e)
{
_panIsActive = false;
}
private void CurveBox_MouseMove(object sender, MouseEventArgs e)
{
if(_panIsActive && IsZoomed)
{
var g = CreateGraphics(); //Create graphics from PictureBox
var nx = _panStartingPoint.X + e.X;
var ny = _panStartingPoint.Y + e.Y;
var sourceRectangle = new Rectangle(nx, ny, Image.Width, Image.Height);
g.DrawImage(Image, nx, ny, sourceRectangle, GraphicsUnit.Pixel);
}
}
I am suspecting the MouseMove event...I am not sure if anything happens in this event and/or nx and ny does contain correct point.
Any helps/tips is really appriciated!
I think the math is backwards. Try it like this:
private Point startingPoint = Point.Empty;
private Point movingPoint = Point.Empty;
private bool panning = false;
void pictureBox1_MouseDown(object sender, MouseEventArgs e) {
panning = true;
startingPoint = new Point(e.Location.X - movingPoint.X,
e.Location.Y - movingPoint.Y);
}
void pictureBox1_MouseUp(object sender, MouseEventArgs e) {
panning = false;
}
void pictureBox1_MouseMove(object sender, MouseEventArgs e) {
if (panning) {
movingPoint = new Point(e.Location.X - startingPoint.X,
e.Location.Y - startingPoint.Y);
pictureBox1.Invalidate();
}
}
void pictureBox1_Paint(object sender, PaintEventArgs e) {
e.Graphics.Clear(Color.White);
e.Graphics.DrawImage(Image, movingPoint);
}
You aren't disposing your graphic object, and CreateGraphics is just a temporary drawing anyway (minimizing would erase it) so I moved the drawing code to the Paint event and am just invalidating as the user is panning.
My current code allows me to draw rectangles from a user defined spot but not in the way in whihc i desire. I need it to be like you would do it in paint, here is my current code:
namespace SimpleDraw2
{
///
/// Description of MainForm.
///
public partial class MainForm : Form
{
bool IsMouseDown = false;
Point MousePosition;
int DrawShape = 0;
Bitmap StoredImage;
public MainForm()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
//
// TODO: Add constructor code after the InitializeComponent() call.
//
pictureBox1.Image = new Bitmap (pictureBox1.Width,pictureBox1.Height);
StoredImage = new Bitmap(pictureBox1.Width,pictureBox1.Height);
}
void PictureBox1MouseDown(object sender, MouseEventArgs e)
{
IsMouseDown = true;
MousePosition = e.Location;
Graphics gStored = Graphics.FromImage(StoredImage);
gStored.Clear(Color.Transparent);
gStored.DrawImage(pictureBox1.Image, 0, 0);
}
void PictureBox1MouseUp(object sender, MouseEventArgs e)
{
IsMouseDown = false;
}
void PictureBox1MouseMove(object sender, MouseEventArgs e)
{
Graphics g = Graphics.FromImage(pictureBox1.Image);
if (DrawShape == 0)
{
Pen p = new Pen(Color.Red, 10);
if (IsMouseDown)
{
g.DrawLine(p,MousePosition,e.Location);
MousePosition = e.Location;
}
}
if (DrawShape == 1)
{
g.Clear(Color.Transparent);
g.DrawImage(StoredImage,0,0);
g.DrawRectangle(Pens.Green,MousePosition.X,MousePosition.Y,e.X,e.Y);
}
if (DrawShape == 2)
{
g.Clear(Color.Transparent);
g.DrawImage(StoredImage, 0, 0);
g.DrawEllipse(Pens.HotPink, MousePosition.X, MousePosition.Y, e.X, e.Y);
}
if (DrawShape == 3)
{
g.Clear(Color.Transparent);
g.DrawImage(StoredImage, 0, 0);
g.DrawArc(Pens.Indigo,pictureBox1.Bounds, e.Y, e.X);
}
//if (DrawShape == 4)
//{
// g.Clear(Color.Transparent);
// g.DrawImage(StoredImage, 0, 0);
// g.DrawPolygon(Pens.Indigo, Point[] e.X);
//}
this.Refresh();
}
private void button2_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog() == DialogResult.OK)
{
axWindowsMediaPlayer1.URL = ofd.FileName;
}
}
private void button1_Click(object sender, EventArgs e)
{
axWindowsMediaPlayer1.Ctlcontrols.pause();
Bitmap bmp = new Bitmap(axWindowsMediaPlayer1.Width, axWindowsMediaPlayer1.Height);
Graphics gfx = Graphics.FromImage(bmp);
gfx.CopyFromScreen(PointToScreen(axWindowsMediaPlayer1.Location), new Point(0, 0), axWindowsMediaPlayer1.Bounds.Size, CopyPixelOperation.SourceCopy);
pictureBox1.BackgroundImage = bmp;
//axWindowsMediaPlayer1.Visible = false;
//pictureBox1.Visible = true;
}
private void button3_Click(object sender, EventArgs e)
{
Graphics gg = Graphics.FromImage(pictureBox1.BackgroundImage);
gg.Clear(Color.Transparent);
Graphics gStored = Graphics.FromImage(StoredImage);
gStored.Clear(Color.Transparent);
Graphics g = Graphics.FromImage(pictureBox1.Image);
g.Clear(Color.Transparent);
}
private void button4_Click(object sender, EventArgs e)
{
DrawShape = 1;
}
private void button6_Click(object sender, EventArgs e)
{
DrawShape = 2;
}
private void button8_Click(object sender, EventArgs e)
{
DrawShape = 3;
}
private void button7_Click(object sender, EventArgs e)
{
DrawShape = 0;
}
}
}
If someone could help me edit my code to iron out the issue to make it easy drag and draw system it would me much appreciate.
Thanks in Advance
Chris
From msdn:
Draws a rectangle specified by a
coordinate pair, a width, and a
height.
So your code won't work:
g.DrawRectangle(Pens.Green,MousePosition.X,MousePosition.Y,e.X,e.Y);
Should be something like
g.DrawRectangle(Pens.Green, MousePosition.X, MousePosition.Y, Math.Abs(e.X - MousePosition.X), Math.Abs(e.Y - MousePosition.Y));
The biggest problem I see is that you're trying to draw in the mouse events. This means your drawing will be wiped away the instant you get a refresh event.
Only draw in Paint events, never in mouse events. If you want your app to draw as a result of mouse events, set a point, rectangle, or whatever in the mouse events (like you start to do with IsMouseDown), invalidate the area you want to change in your MouseMoved event, then draw your rectangle or whatever in your Paint event.
I want to create an application which the user is able to manipulate the line he draw. Something like Deleting the line or Selecting it. How should I do that?
Thanks in advance
I managed to do it using a hard coded rectangle. But I don't still have an idea how to do it using the drawLine() Can I use drawPath to do the hit test?
Here is the code:
private bool selectGraph = false;
private Rectangle myrec = new Rectangle(50, 50, 100, 100);
private Graphics g;
private void panel1_Paint(object sender, PaintEventArgs e)
{
SolidBrush sb = new SolidBrush(Color.Blue);
Pen p = new Pen(Color.Blue, 5);
e.Graphics.DrawRectangle(p, myrec);
e.Graphics.FillRectangle(sb, myrec);
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
Point mPT = new Point(e.X, e.Y);
if (e.Button == MouseButtons.Left)
{
if (myrec.Contains(mPT))
{
selectGraph = true;
button1.Enabled = true;
}
else
{
selectGraph = false;
button1.Enabled = false;
}
}
Invalidate();
}
Well you could start with something like a simple Line class:
public class Line
{
public Point Start { get; set; }
public Point End { get; set; }
}
Then you could have your form:
private Line Line = new Line();
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Red, this.Line.Start, this.Line.End);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
this.Line.Start = e.Location;
this.Refresh();
}
else if (e.Button == MouseButtons.Right)
{
this.Line.End = e.Location;
this.Refresh();
}
}
So basically then they could delete the this.Line maybe on "MiddleButton" click or something. This should be enough to get you started.
I've created a sample that shows how this can be done. Set some break points and see how things are done.
There is no easy one line solution for this. You would have to program this yourself.
You have to keep track of every object you have drawn. In the onmousedown event you have to find out if the mouse has clicked on or near an object you want to move/delete by comparing coordinates. Then you need to draw some visual guide that the line is 'selected'. Deleting is now quite easy by removing the object from the collection.
For drag and drop you have to do something similar by changing the coordinates of the object according to the mouse move.