C# graphics flickering - c#

I am working on kind of drawing program but I have a problem with flickering while moving a mouse cursor while drawing a rubberband line. I hope you can help me to remove that flickering of line, here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace GraphicsTest
{
public partial class Form1 : Form
{
int xFirst, yFirst;
Bitmap bm = new Bitmap(1000, 1000);
Graphics bmG;
Graphics xG;
Pen pen = new Pen(Color.Black, 1);
bool draw = false;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
bmG = Graphics.FromImage(bm);
xG = this.CreateGraphics();
bmG.Clear(Color.White);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
xFirst = e.X;
yFirst = e.Y;
draw = true;
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
bmG.DrawLine(pen, xFirst, yFirst, e.X, e.Y);
draw = false;
xG.DrawImage(bm, 0, 0);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (draw)
{
xG.DrawImage(bm, 0, 0);
xG.DrawLine(pen, xFirst, yFirst, e.X, e.Y);
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
xG.DrawImage(bm, 0, 0);
}
}
}

First don't use CreateGraphics() unless you absolutely have to. Bind an event handler to OnPaint and call Invalidate() when you want to refresh the surface.
If you don't want it to flicker you'll need to double buffer your drawing surface. The easiest way to do this is to set your form's DoubleBuffered property to True.
I would highly recommend if you plan on extending this to do your drawing to the PictureBox control. PictureBox is double-buffered by default and allows you to control your drawing region much more simply.
In code:
public partial class Form1 : Form
{
int xFirst, yFirst;
Bitmap bm = new Bitmap(1000, 1000);
Graphics bmG;
Pen pen = new Pen(Color.Black, 1);
bool draw = false;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
bmG = Graphics.FromImage(bm);
bmG.Clear(Color.White);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
xFirst = e.X;
yFirst = e.Y;
draw = true;
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
bmG.DrawLine(pen, xFirst, yFirst, e.X, e.Y);
draw = false;
Invalidate();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (draw)
{
Invalidate();
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (draw) {
e.Graphics.DrawImage(bm, 0, 0);
e.Graphics.DrawLine(pen, xFirst, yFirst, e.X, e.Y);
} else {
e.Graphics.DrawImage(bm, 0, 0);
}
}
}
Edit:
Another issue, you are creating a private Pen member. Pens (and Brushes, as well as many GDI+ objects) represent handles to unmanaged objects that need to be disposed otherwise your program will leak. Either wrap them in using statements (the preferred and exception-safe way) or explicitly dispose of them in the form's Dispose method.
Alternatively in System.Drawing you can access some pre-built Pens and Brushes that don't need to be (and shouldn't be) disposed. Use them like:
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (draw) {
e.Graphics.DrawImage(bm, 0, 0);
e.Graphics.DrawLine(Pens.Black, xFirst, yFirst, e.X, e.Y);
} else {
e.Graphics.DrawImage(bm, 0, 0);
}
}

The reason it is flickering is that you are drawing the background (which is immediately displayed on screen, wiping away the line) and then superimpose the line. Thus the line keeps disappearing and appearing, giving a flickering display.
The best solution to this is called Double Buffering. What you do is draw the whole image to an "offscreen" bitmap, and only show it on the screen when it is completed. Because you only ever display the completed image, there is no flickering effect. You should just be able to set this.DoubleBuffered = true to get WinForms to do all the hard work for you.
NB: You shouldn't really be drawing outside of your paint handler - ideally, you should Invalidate() the area that needs redrawing, and then your paint handler will redraw just that area (with any overlaid lines etc as needed).

Fixed and working code.
public partial class Form1 : Form
{
int x1, y1, x2, y2;
bool drag = false;
Bitmap bm = new Bitmap(1000, 1000);
Graphics bmg;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
bmg = Graphics.FromImage(bm);
}
private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
drag = true;
x1 = e.X;
y1 = e.Y;
}
private void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
drag = false;
bmg.DrawLine(Pens.Black, x1, y1, e.X, e.Y);
pictureBox.Invalidate();
}
private void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (drag)
{
x2 = e.X;
y2 = e.Y;
pictureBox.Invalidate();
}
}
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
if (drag) {
e.Graphics.DrawImage(bm, 0, 0);
e.Graphics.DrawLine(Pens.Black, x1, y1, x2, y2);
}
else {
e.Graphics.DrawImage(bm, 0, 0);
}
}
}

I Use this to manage the double buffering into a panel:
myPanel.GetType().GetMethod("SetStyle",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic).Invoke(myPanel,
new object[]
{
System.Windows.Forms.ControlStyles.UserPaint |
System.Windows.Forms.ControlStyles.AllPaintingInWmPaint |
System.Windows.Forms.ControlStyles.DoubleBuffer, true
});

Related

I made a rectangle how do I check if the mouse clicked on it?

How do I check if a mouse clicked a rectangle?
Graphics gfx;
Rectangle hitbox;
hitbox = new hitbox(50,50,10,10);
//TIMER AT THE BOTTOM
gfx.Draw(System.Drawing.Pens.Black,hitbox);
Just a sample quick and dirty, if your "gfx" is a "e.Graphics..." from a Form:
public partial class Form1 : Form
{
private readonly Rectangle hitbox = new Rectangle(50, 50, 10, 10);
private readonly Pen pen = new Pen(Brushes.Black);
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(pen, hitbox);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if ((e.X > hitbox.X) && (e.X < hitbox.X + hitbox.Width) &&
(e.Y > hitbox.Y) && (e.Y < hitbox.Y + hitbox.Height))
{
Text = "HIT";
}
else
{
Text = "NO";
}
}
}
Rectangle has several handy but often overlooked functions. In this case using the Rectangle.Contains(Point) function is the best solution:
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (hitbox.Contains(e.Location)) .. // clicked inside
}
To determine if you clicked on the outline you will want to decide on a width, since the user can't easily hit a single pixel.
For this you can use either GraphicsPath.IsOutlineVisible(Point)..
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
GraphicsPath gp = new GraphicsPath();
gp.AddRectanle(hitbox);
using (Pen pen = new Pen(Color.Black, 2f))
if (gp.IsOutlineVisible(e.location), pen) .. // clicked on outline
}
..or stick to rectangles..:
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
Rectangle inner = hitbox;
Rectangle outer = hitbox;
inner.Inflate(-1, -1); // a two pixel
outer.Inflate(1, 1); // ..outline
if (outer.Contains(e.Location) && !innerContains(e.Location)) .. // clicked on outline
}

Draw a fill rectangle dynamically on screen in C#

I would like to draw a fill rectangle dynamically on my screen, with an opacity at 0.1. The problem is that when i move the mouse, the previous rectangles aren't clear.
This is the drawing methods.
private void OnMouseDown(object sender, MouseEventArgs e)
{
isMouseDown = true;
x = e.X;
y = e.Y;
g = this.selectorForm.CreateGraphics();
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (!isMouseDown) return;
this.selectorForm.Invalidate();
g.FillRectangle(brush, this.getRectangle(x, y, e.X, e.Y));
g.DrawRectangle(pen, this.getRectangle(x, y, e.X, e.Y));
}
This is my selectorForm
internal class SelectorForm : Form
{
protected override void OnPaintBackground(PaintEventArgs e)
{
}
}
An example when I draw a rectangle (several overlapping rectangles)
And Invalidate() doesn't work because I override OnPaintBackground. But if I don't do this override, when I do this.selectorForm.Show(), my screen becomes gray.
So how can I draw a rectangle with an opacity 0.1 on my screen?
Thank you !
This is an example that works for me.
The crucial parts are:
using the Paint event and its Graphics
adding a Clear(BackgroundColor) or else I get the same artifacts you see
for transparency the TransparencyKey property should be used. There is a certain choice of colors:
Common colors may conflict with other things on the Form
Fuchsia works if you want to click through the Form
Other not so common colors are suitable; I chose a light green
You may want to adapt to your way of setting up events, I just did it this way for faster testing.
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
DoubleBuffered = true;
Opacity = 0.1f;
// a color that will allow using the mouse on the form:
BackColor = Color.GreenYellow;
TransparencyKey = BackColor;
}
Point mDown = Point.Empty;
Point mCur = Point.Empty;
private void Form2_MouseDown(object sender, MouseEventArgs e)
{
mDown = e.Location;
}
private void Form2_MouseUp(object sender, MouseEventArgs e)
{
mDown = Point.Empty;
}
private void Form2_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
mCur = e.Location;
Invalidate();
}
private void Form2_Paint(object sender, PaintEventArgs e)
{
if (mDown == Point.Empty) return;
Size s = new System.Drawing.Size(Math.Abs(mDown.X - mCur.X),
Math.Abs(mDown.Y - mCur.Y) );
Point topLeft = new Point(Math.Min(mDown.X, mCur.X),
Math.Min(mDown.Y, mCur.Y));
Rectangle r = new Rectangle(topLeft, s);
e.Graphics.Clear(this.BackColor); // <--- necessary!
e.Graphics.FillRectangle(Brushes.Bisque, r ); // <--- pick your..
e.Graphics.DrawRectangle(Pens.Red, r); // <--- colors!
}
}
}
You can try following code:
g.Clear();
g.FillRectangle(brush, this.getRectangle(x, y, e.X, e.Y));
g.DrawRectangle(pen, this.getRectangle(x, y, e.X, e.Y));

Draw on Panel, save as Bitmap

I'm trying to make a simple application where the user can draw on the Panel and save it to their computer as a bitmap. When I proceed to the save part, however, all I get is an empty (white) bitmap.
I've been browsing many other solutions and I am pretty sure I am saving the bitmap the correct way, so I am starting to wonder if my drawing process is incorrect. What exactly is wrong here?
public partial class Form1 : Form
{
SolidBrush brush;
Pen pen;
Point[] points = new Point[3];
Graphics display;
Bitmap bmap;
public Form1()
{
InitializeComponent();
display = panel1.CreateGraphics();
bmap = new Bitmap(panel1.Width, panel1.Height);
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
brush = new SolidBrush(Color.Black);
pen = new Pen(Color.Black);
display.FillEllipse(brush, e.X, e.Y, 10, 10);
panel1.DrawToBitmap(bmap, new Rectangle(0, 0, panel1.Width, panel1.Height));
//this.Invalidate();
}
private void clearToolStripMenuItem_Click(object sender, EventArgs e)
{
Graphics display = panel1.CreateGraphics();
display.Clear(panel1.BackColor);
}
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
bmap.Save(#"C:\Temp\Test.bmp");
}
}
EDIT
With this revision, I just get a black bmp and I don't even see elipses being created anymore on my screen. Although I did notice that if I put invalidate and Draw to bitmap back in the mousedown event, then the save button will save the last ellipse, while there is still nothing appearing on my screen.
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
mousedown = true;
x = e.X;
y = e.Y;
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
//Graphics g = e.Graphics;
if(mousedown==true)
{
brush = new SolidBrush(Color.Black);
pen = new Pen(Color.Black);
Graphics.FromImage(bmap).FillEllipse(brush, x, y, 10, 10);
panel1.Invalidate();
//panel1.DrawToBitmap(bmap, new Rectangle(0, 0, panel1.Width, panel1.Height));
//panel1.Invalidate();
}
}
As Hans did most of the work in his comment, here is how your code should probably look:
public partial class Form1 : Form {
Bitmap bmap;
public Form1() {
InitializeComponent();
bmap = new Bitmap(panel1.ClientWidth, panel1.ClientHeight);
panel1.MouseDown += panel1_MouseDown;
panel1.Paint += panel1_Paint;
}
void panel1_Paint(object sender, PaintEventArgs e) {
e.Graphics.DrawImage(bmap, Point.Empty);
}
void panel1_MouseDown(object sender, MouseEventArgs e) {
using (Graphics g = Graphics.FromImage(bmap)) {
g.FillEllipse(Brushes.Black, e.X, e.Y, 10, 10);
}
panel1.Invalidate();
}
private void clearToolStripMenuItem_Click(object sender, EventArgs e) {
using (Graphics g = Graphics.FromImage(bmap)) {
g.Clear(Color.White);
}
panel1.Invalidate();
}
private void saveToolStripMenuItem_Click(object sender, EventArgs e) {
bmap.Save(#"c:\temp\bmap.bmp");
}
}
CreateGraphics is just a temporary canvas, so you rarely, if ever, use that for drawing purposes, especially since you are trying to save an image.
This will works fine. I tested it and worked well
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace drawing
{
public partial class Form2 : Form
{
Graphics g;
bool startPaint = false;
int? initX = null;
int? initY = null;
bool drawSquare = false;
bool drawRectangle = false;
bool drawCircle = false;
public Form2()
{
InitializeComponent();
bmp = new Bitmap(panel1.ClientSize.Width, panel1.ClientSize.Height);
}
Bitmap bmp;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
}
void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (startPaint)
{
using ( g = Graphics.FromImage(bmp))
{
// g.FillEllipse(Brushes.Black, new Rectangle(e.X, e.Y , 5, 5));
Pen p = new Pen(btn_PenColor.BackColor, float.Parse(cmb_PenSize.Text));
g.DrawLine(p, new Point(initX ?? e.X, initY ?? e.Y), new Point(e.X, e.Y));
initX = e.X;
initY = e.Y;
//g.DrawImage(bmp, new Rectangle(e.X - 4, e.Y - 4, 8, 8));
}
panel1.Invalidate();
}
}
private void pnl_Draw_MouseDown(object sender, MouseEventArgs e)
{
startPaint = true;
if (drawSquare)
{
//Use Solid Brush for filling the graphic shapes
SolidBrush sb = new SolidBrush(btn_PenColor.BackColor);
//setting the width and height same for creating square.
//Getting the width and Heigt value from Textbox(txt_ShapeSize)
g.FillRectangle(sb, e.X, e.Y, int.Parse(txt_ShapeSize.Text), int.Parse(txt_ShapeSize.Text));
//setting startPaint and drawSquare value to false for creating one graphic on one click.
startPaint = false;
drawSquare = false;
}
if (drawRectangle)
{
SolidBrush sb = new SolidBrush(btn_PenColor.BackColor);
//setting the width twice of the height
g.FillRectangle(sb, e.X, e.Y, 2 * int.Parse(txt_ShapeSize.Text), int.Parse(txt_ShapeSize.Text));
startPaint = false;
drawRectangle = false;
}
if (drawCircle)
{
SolidBrush sb = new SolidBrush(btn_PenColor.BackColor);
g.FillEllipse(sb, e.X, e.Y, int.Parse(txt_ShapeSize.Text), int.Parse(txt_ShapeSize.Text));
startPaint = false;
drawCircle = false;
}
}
private void pnl_Draw_MouseUp(object sender, MouseEventArgs e)
{
startPaint = false;
initX = null;
initY = null;
}
void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(bmp, Point.Empty);
}
private void button1_Click(object sender, EventArgs e)
{
bmp.Save("D://filename.jpg", ImageFormat.Png);
}
}
}

erasing parts of the loaded image by mouse

I have loaded an image in my panel.I want to erase the parts of that image by using the mouse(dragging on the panel).Here is my code to load my image:
private void drawP_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(myImage, new Point(0, 0));
}
How can I do it?
Thanks in advance.
Updated:
sorry not to say earlier,I have set another image(image2) as background of the panel and I want it to be seen after erasing myImage(image loaded with the code above).
Hi I'm going to assume that you want this feature to work like the eraser on paint.
there are 3 events you are going to need
1.mousedown - to call the first erase and open up the mousemove event method.
2.mouseup - to stop the mousemove event method
3.mousemove - just to call the erase method
Code: //part pseudo as im not in visual studio right now :(
//global vars
bool enable = false;
void erase(Point mousepoint)
{
Point f = (mousepoint.X - yourpanel.left?, mousepoint.Y - yourpanel.top?);
//gets mouse position on accual picture;
yourImageGraphics.fillreactangle( f.X - 10, f.Y+10, 20,20 ,Color.White)
// int X , int Y, width , height, color
}
void mousedown(?)
{
enable=true;
erase(Cursor.Position //but you get this from e?);
}
void mouseup(?);
{
enable=false;
}
void mousemove(?)
{
if (enable)
erase(e.Position?);
}
Also it looks like you are going to have to make a graphics object for your panel :(
I hope this helps because question was a bit vague.
Here I created simple example. Of course it can be done better, but I just wondering how to do it... so sharing my results.
public partial class mainForm : Form
{
private Bitmap image;
private Rectangle imgRect;
public mainForm()
{
InitializeComponent();
BackColor = Color.Chartreuse;
image = new Bitmap(#"C:\image.jpg");
imgRect = new Rectangle(0,0,image.Width, image.Height);
}
private void main_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(image, 0, 0);
}
private void main_MouseMove(object sender, MouseEventArgs e)
{
if(e.Button == MouseButtons.Left && e.X < image.Width && e.Y < image.Height)
{
image.SetPixel(e.X, e.Y, Color.Magenta);//change pixel color;
image.MakeTransparent(Color.Magenta);//Make image transparent
Invalidate(imgRect);
}
}
}
...and lets test
Ha! scared that I deleted his eye :)
A TextureBrush on a pen can be used for erasing.
Working example (image1 and image2 are the same size images):
Bitmap bmp1;
TextureBrush tb;
Point _LastPoint;
public Form1()
{
InitializeComponent();
this.DoubleBuffered = true;
bmp1 = new Bitmap(#"c:\image1.png");
tb = new TextureBrush(new Bitmap(#"c:\image2.png"));
}
private void Form1_MouseMove(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
if (!_LastPoint.IsEmpty) {
using (Graphics g = Graphics.FromImage(bmp1))
using (Pen p = new Pen(tb, 15)) {
p.StartCap = LineCap.Round;
p.EndCap = LineCap.Round;
g.DrawLine(p, _LastPoint, e.Location);
}
}
_LastPoint = e.Location;
this.Invalidate();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e) {
_LastPoint = Point.Empty;
}
private void Form1_Paint(object sender, PaintEventArgs e) {
e.Graphics.DrawImage(bmp1, new Point(0, 0));
}

selected area to cut an image

Hi I would do the selected area to cut an image on picturebox control.
I have the following code:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Rectangle rect;
private Pen p;
public Form1()
{
InitializeComponent();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (this.p == null)
this.p = new Pen(Color.FromArgb(100, 200, 200, 200), 5);
if (this.rect.Width > 0)
e.Graphics.DrawRectangle(this.p, this.rect);
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.X < this.rect.X)
{
this.rect.Width = this.rect.X - e.X;
this.rect.X = e.X;
}
else
{
this.rect.Width = e.X - this.rect.X;
}
if (e.Y < this.rect.Y)
{
this.rect.Height = this.rect.Y - e.Y;
this.rect.Y = e.Y;
}
else
{
this.rect.Height = e.Y - this.rect.Y;
}
this.Invalidate(this.rect);
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
this.rect.X = e.X;
this.rect.Y = e.Y;
}
}
}
It returns an error here:
Application.Run(new Form1());
Why?
thanks for all replies ;p
You shouldn't dispose of the Graphics object that is passed as part of the PaintEventArgs. That is probably what is causing your issue.
Try using this optimized code, if you still get error post it here (edit your original question) and we'll see.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (this.p == nulll)
this.p = new Pen(Color.FromArgb(100, 200, 200, 200), 5);
if (this.rect.Width > 0)
e.Graphics.DrawRectangle(this.p, this.rect);
}
what is the error?
you are leaking Pen's. For every paint message you create a new pen and throw the old without disposing.
Off the top of my head I can't remember if you should dispose of the graphics object you get from the event args

Categories