c# Drawing with gdi32.dll vs System.Drawing.Graphics - c#

So I have some code that creates a highlight effect on top of a picture box with gdi32.dll and I am wondering if there is a simpler way to do it with System.Drawing.Graphics? Basically with the gdi32.dll, I have to capture a screen shot after drawing, post that to my picturebox and then I am able to draw more stuff and change the color of the pen that I use. If I just try to change the pen thickness and color and draw on the screen again, if changes what I already drew.
Now I have a version of this that uses System.Drawing.Graphics and lots of math with FillPolygon but if I draw over an area I already drew on, it just makes the area I drew on darker. It does not do this with gdi32.dll which justr shades as long as you have not already shaded the area with the mouse. Any suggestions?
public partial class Form9 : Form
{
private bool is_mouse_down { get; set; } // Will check if the mouse is down or not.
private Color Pen_Color = new Color();
private int Pen_Type { get; set; }
private int Thickness { get; set; }
private bool Start { get; set; }
List<Point> Points = new List<Point>();
public Form9()
{
InitializeComponent();
pictureBox1.Dock = DockStyle.Fill;
Pen_Color = Color.Blue;
Pen_Type = 13; // Type = 9 for highlighter, Type = 13 for solid.
Thickness = 2;
Start = false;
pictureBox1.MouseDown += pictureBox1_MouseDown;
pictureBox1.MouseUp += pictureBox1_MouseUp;
pictureBox1.MouseMove += pictureBox1_MouseMove;
pictureBox1.Paint += pictureBox1_OnPaint;
}
private void DrawHighlight(Graphics g, Point[] usePoints, int brushSize, int penType, Color brushColor)
{
int useColor = System.Drawing.ColorTranslator.ToWin32(brushColor);
IntPtr pen = GetImage.GDI32.CreatePen(GetImage.GDI32.PS_SOLID, brushSize, (uint)useColor);
IntPtr hDC = g.GetHdc();
IntPtr xDC = GetImage.GDI32.SelectObject(hDC, pen);
GetImage.GDI32.SetROP2(hDC, penType);//GetImage.GDI32.R2_MASKPEN);
for (int i = 1; i <= usePoints.Length - 1; i++)
{
Point p1 = usePoints[i - 1];
Point p2 = usePoints[i];
GetImage.GDI32.MoveToEx(hDC, p1.X, p1.Y, IntPtr.Zero);
GetImage.GDI32.LineTo(hDC, p2.X, p2.Y);
}
GetImage.GDI32.SetROP2(hDC, GetImage.GDI32.R2_COPYPEN);
GetImage.GDI32.SelectObject(hDC, xDC);
GetImage.GDI32.DeleteObject(pen);
g.ReleaseHdc(hDC);
}
private void pictureBox1_OnPaint(object sender, PaintEventArgs e)
{
if (Start)
{
base.OnPaint(e);
if (is_mouse_down)
{
DrawHighlight(e.Graphics, Points.ToArray(), Thickness, Pen_Type, Pen_Color);
}
}
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
Points.Clear();
Start = true;
is_mouse_down = true;
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
is_mouse_down = false;
using (Image img = CaptureScreen())
{
try
{
if (System.IO.File.Exists(Program.ProgramPath + #"\Temp\marked.bmp"))
{
System.IO.File.Delete(Program.ProgramPath + #"\Temp\marked.bmp");
}
}
catch (Exception Ex)
{
MessageBox.Show("File Delete Error" + Environment.NewLine + Convert.ToString(Ex));
}
try
{
img.Save(Program.ProgramPath + #"\Temp\marked.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
}
catch (Exception Ex)
{
MessageBox.Show("Unable to save Screenshot" + Environment.NewLine + Convert.ToString(Ex));
}
}
if (System.IO.File.Exists(Program.ProgramPath + #"\Temp\marked.bmp"))
{
using (FileStream fs = new System.IO.FileStream(Program.ProgramPath + #"\Temp\marked.bmp", System.IO.FileMode.Open, System.IO.FileAccess.Read, FileShare.Read))
{
pictureBox1.Image = Image.FromStream(fs);
}
}
pictureBox1.Invalidate(); // Refreshes picturebox image.
}
public Image CaptureScreen()
{
GetImage gi = new GetImage();
return gi.CaptureWindow(GetImage.User32.GetDesktopWindow());
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (is_mouse_down == true) // Check to see if the mouse button is down while moving over the form.
{
Points.Add(new Point(e.X, e.Y));
pictureBox1.Invalidate(); // Refreshes picturebox image.
}
}
Here are a couple photos of what I am talking about:
Using System.Drawing.Graphics:
Using gdi32.dll:
UPDATE
After testing some of your code...I got a some strange stuff.

This is a way to draw multiple independent stroke of semi-transparent color without piling up the alpha:
It uses tow lists, one for the strokes and one for the current stroke:
List<List<Point>> strokes = new List<List<Point>>();
List<Point> currentStroke = new List<Point>();
They are filled in the usual way
private void canvas_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left))
{
currentStroke.Add(e.Location);
if (currentStroke.Count == 1)
currentStroke.Add(new Point(currentStroke[0].X + 1,
currentStroke[0].Y));
canvasInvalidate();
}
}
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left))
{
currentStroke.Add(e.Location);
canvas.Invalidate();
}
}
private void canvas_MouseUp(object sender, MouseEventArgs e)
{
if (currentStroke.Count > 1)
{
strokes.Add(currentStroke.ToList());
currentStroke.Clear();
}
canvas.Invalidate();
}
In this version we avoid overlay effects of overlapping strokes by drawing all pixels in only one call. All the pixels are painted by creating a GraphicsPath from the strokes and and filling it:
private void canvas_Paint(object sender, PaintEventArgs e)
{
if (strokes.Count > 0 || currentStroke.Count > 0)
{
GraphicsPath gp = new GraphicsPath();
gp.FillMode = FillMode.Winding;
if (currentStroke.Count > 0)
{
gp.AddCurve(currentStroke.ToArray());
gp.CloseFigure();
}
foreach (var stroke in strokes)
{
gp.AddCurve(stroke.ToArray());
gp.CloseFigure();
}
using (SolidBrush b = new SolidBrush(Color.FromArgb(77, 177, 99, 22)))
{
e.Graphics.FillPath(b, gp);
}
}
}
Note that you should take care not to move back onto the current stroke while drawing it or else the croosing path parts will create holes!
A Clear or Save Button are simple, the former Clears the two list and invalidates, the latter would use DrawToBitmap to save the control..
Note: To avoid flicker do make sure the canval Panel is DoubleBuffered!
Update:
Here is another way that uses a Pen to draw the overlay. To avoid the piling up of alpha and changed color values (depending on the PixelFormat) it uses a fast function to modify all set pixels in the overlay to have the same overlay color:
The stroke collection code is the same. The Paint is reduced to calling a function to create an overlay bitmap and drawing it:
private void canvas_Paint(object sender, PaintEventArgs e)
{
using (Bitmap bmp = new Bitmap(canvas.ClientSize.Width,
canvas.ClientSize.Height, PixelFormat.Format32bppPArgb))
{
PaintToBitmap(bmp);
e.Graphics.DrawImage(bmp, 0, 0);
}
The first function does the drawing, pretty much like before, but with simple pen strokes:
private void PaintToBitmap(Bitmap bmp)
{
Color overlayColor = Color.FromArgb(77, 22, 99, 99);
using (Graphics g = Graphics.FromImage(bmp))
using (Pen p = new Pen(overlayColor, 15f))
{
p.MiterLimit = p.Width / 2;
p.EndCap = LineCap.Round;
p.StartCap = LineCap.Round;
p.LineJoin = LineJoin.Round;
g.SmoothingMode = SmoothingMode.AntiAlias;
if (currentStroke.Count > 0)
{
g.DrawCurve(p, currentStroke.ToArray());
}
foreach (var stroke in strokes)
g.DrawCurve(p, stroke.ToArray());
}
SetAlphaOverlay(bmp, overlayColor);
}
It also calls the function that 'flattens' all set pixels to the overlay color:
void SetAlphaOverlay(Bitmap bmp, Color col)
{
Size s = bmp.Size;
PixelFormat fmt = bmp.PixelFormat;
Rectangle rect = new Rectangle(Point.Empty, s);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, fmt);
int size1 = bmpData.Stride * bmpData.Height;
byte[] data = new byte[size1];
System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, data, 0, size1);
for (int y = 0; y < s.Height; y++)
{
for (int x = 0; x < s.Width; x++)
{
int index = y * bmpData.Stride + x * 4;
if (data[index + 0] + data[index + 1] + data[index + 2] > 0)
{
data[index + 0] = col.B;
data[index + 1] = col.G;
data[index + 2] = col.R;
data[index + 3] = col.A;
}
}
}
System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
bmp.UnlockBits(bmpData);
}
It uses LockBits, so it is pretty fast..
Here it is in action:
Update 2:
Just for the fun of it here is an extension of only a few lines that adds the option of drawing filled curves:
The fill mode is stored in a cheapo hack by having the 1st element twice. These are the changes:
In the MouseDown:
currentStroke.Add(e.Location);
if (cbx_Fill.Checked)
currentStroke.Add(e.Location);
And in the PaintToBitmap:
g.SmoothingMode = SmoothingMode.AntiAlias;
if (currentStroke.Count > 0)
{
if (cbx_Fill.Checked)
g.FillClosedCurve(b, currentStroke.ToArray());
else
g.DrawCurve(p, currentStroke.ToArray());
}
foreach (var stroke in strokes)
if (stroke[0]==stroke[1])
g.FillClosedCurve(b, stroke.ToArray());
else
g.DrawCurve(p, stroke.ToArray());
And one more demo:

Related

Close button on Tab pages in Winforms

I am trying to add a close button on the tab pages of TabControl and
change the color of the close button from light gray to black when
mouse hovers over it. However, the color never changes.
The DrawEventArgsCustom class is created to indicate that the mouse is
hovering over the close button. When it's true, the statement to
change the color is executed but color never changes.
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
try
{
Rectangle r = e.Bounds;
r = this.tabControl1.GetTabRect(e.Index);
r.Offset(2, 2);
Brush TitleBrush = new SolidBrush(Color.Black);
Brush CloseBrush = new SolidBrush(Color.Gray);
Brush CloseBrushSelected = new SolidBrush(Color.Black);
Font f = this.Font;
string title = this.tabControl1.TabPages[e.Index].Text;
e.Graphics.DrawString(title, f, TitleBrush, new PointF(r.X, r.Y));
if (e is DrawEventArgsCustom)
{
if (((DrawEventArgsCustom)e) != null && ((DrawEventArgsCustom)e).HoverTrue == true)
e.Graphics.DrawString("x", f, CloseBrushSelected, new PointF
(r.X + (this.tabControl1.GetTabRect(e.Index).Width - _imageLocation.X), _imageLocation.Y));
}
e.Graphics.DrawString("x", f, CloseBrush, new PointF
(r.X + (this.tabControl1.GetTabRect(e.Index).Width - _imageLocation.X), _imageLocation.Y));
}
catch (Exception ex)
{
}
}
private void tabControl1_MouseMove(object sender, MouseEventArgs e)
{
Rectangle mouseRect = new Rectangle(e.X, e.Y, 1, 1);
Graphics graphics = CreateGraphics();
for (int i = 0; i < tabControl1.TabCount; i++)
{
if (tabControl1.GetTabRect(i).IntersectsWith(mouseRect))
{
tabControl1_DrawItem(this, new DrawEventArgsCustom(hoverTrue: true, graphics, this.Font, mouseRect, i, DrawItemState.Focus));
}
}
}
class DrawEventArgsCustom : DrawItemEventArgs
{
public DrawEventArgsCustom(bool hoverTrue, Graphics graphics, Font font, Rectangle rect, int index, DrawItemState drawItemState)
: base(graphics, font, rect, index, drawItemState)
{
this.HoverTrue = hoverTrue;
this.Graph = graphics;
this.Fnt = font;
this.Rect = rect;
this.ind = index;
this.drawItemSt = drawItemState;
}
public bool HoverTrue { get; private set; }
public Graphics Graph { get; private set; }
public Font Fnt { get; private set; }
public Rectangle Rect { get; private set; }
public int ind { get; private set; }
public DrawItemState drawItemSt { get; private set; }
}
No need to create new Graphics objects like that, you should do all the drawings in the DrawItem event. For example in this context:
//a class level variable.
private int HoverIndex = -1;
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
var g = e.Graphics;
var tp = tabControl1.TabPages[e.Index];
var rt = e.Bounds;
var rx = new Rectangle(rt.Right - 20, (rt.Y + (rt.Height - 12)) / 2 + 1, 12, 12);
if ((e.State & DrawItemState.Selected) != DrawItemState.Selected)
{
rx.Offset(0, 2);
}
rt.Inflate(-rx.Width, 0);
rt.Offset(-(rx.Width / 2), 0);
using (Font f = new Font("Marlett", 8f))
using (StringFormat sf = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center,
Trimming = StringTrimming.EllipsisCharacter,
FormatFlags = StringFormatFlags.NoWrap,
})
{
g.DrawString(tp.Text, tp.Font ?? Font, Brushes.Black, rt, sf);
g.DrawString("r", f, HoverIndex == e.Index ? Brushes.Black : Brushes.LightGray, rx, sf);
}
tp.Tag = rx;
}
Note that, now the Tag property of each TabPage control holds a rectangle for the x button.
In the MouseMove event iterate through the TabPages, cast the x rectangle from the Tag property, check if the x rectangle contains the current e.Location, and call Invalidate(); method of the TabControl to update the drawing:
private void tabControl1_MouseMove(object sender, MouseEventArgs e)
{
for (int i = 0; i < tabControl1.TabCount; i++)
{
var rx =(Rectangle)tabControl1.TabPages[i].Tag;
if (rx.Contains(e.Location))
{
//To avoid the redundant calls.
if (HoverIndex != i)
{
HoverIndex = i;
tabControl1.Invalidate();
}
return;
}
}
//To avoid the redundant calls.
if (HoverIndex != -1)
{
HoverIndex = -1;
tabControl1.Invalidate();
}
}
In the MouseLeave event invalidate if necessary:
private void tabControl1_MouseLeave(object sender, EventArgs e)
{
if (HoverIndex != -1)
{
HoverIndex = -1;
tabControl1.Invalidate();
}
}
And to close/dispose a page, handle the MouseUp event:
private void tabControl1_MouseUp(object sender, MouseEventArgs e)
{
for(int i = 0; i < tabControl1.TabCount; i++)
{
var rx = (Rectangle)tabControl1.TabPages[i].Tag;
if (rx.Contains(rx.Location)) //changed e.Location to rx.Location
{
tabControl1.TabPages[i].Dispose();
return;
}
}
}
Related Posts
TabControl with Close and Add Button

Second timer.Start() isn't being triggered by first timer tick

So I have a small logo in the lower right corner of a Form that I want to fade in and out at a preset speed, about 6 seconds per fade. I have tried a few different methods but I can never get the picture to fade back in again once the first timer has finished. Here's my code for the the 2 timers and their respective tick methods.
EDIT The declarations for the timers included now.
Timer fade = new Timer();
Timer fade2 = new Timer();
fade.Interval = (200);
fade.Tick += new EventHandler(fade_Tick);
fade2.Interval = (200);
fade2.Tick += new EventHandler(fade_Tick_Two);
fade.Start();
private void fade_Tick(object sender, EventArgs e)
{
if (alpha < 255)
{
image = picboxPic.Image;
using (Graphics g = Graphics.FromImage(image))
{
Pen pen = new Pen(Color.FromArgb(alpha, 255, 255, 255), image.Width);
g.DrawLine(pen, -1, -1, image.Width, image.Height);
g.Save();
}
picboxPic.Image = image;
this.Invalidate();
alpha++;
}
else
{
fade.Stop();
fade2.Start();
}
}
private void fade_Tick_Two(object sender, EventArgs e)
{
if (alpha > 0)
{
image = picboxPic.Image;
using (Graphics g = Graphics.FromImage(image))
{
Pen pen = new Pen(Color.FromArgb(alpha, 255, 255, 255), image.Width);
g.DrawLine(pen, -1, -1, image.Width, image.Height);
g.Save();
}
picboxPic.Image = image;
this.Invalidate();
alpha--;
}
else
{
fade2.Stop();
fade.Start();
}
}
Any ideas as to why the second timer won't start? I've used breakpoints and the alpha level does reach 255 but then it doesn't trigger the second Tick event.
The method described in the link I quoted works for me:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
test();
}
System.Timers.Timer fade = new System.Timers.Timer(50);
System.Timers.Timer fade2 = new System.Timers.Timer(50);
Image originalImage = Image.FromFile(#"D:\kevin\Pictures\odds&Sods\kitchener.jpg");
int alpha = 100;
void test()
{
fade.Elapsed +=new System.Timers.ElapsedEventHandler(fade_Tick);
fade2.Elapsed+=new System.Timers.ElapsedEventHandler(fade_Tick_Two);
fade.Start();
}
delegate void timerDelegate(object sender, EventArgs e);
private void fade_Tick(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(new timerDelegate(fade_Tick), sender, e);
return;
}
if (alpha >= 0)
{
picboxPic.Image = SetImgOpacity(originalImage, alphaToOpacity(alpha));
picboxPic.Invalidate();
alpha--;
}
if (alpha < 0)
{
alpha = 0;
fade.Stop();
fade2.Start();
}
}
private void fade_Tick_Two(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(new timerDelegate(fade_Tick_Two), sender, e);
return;
}
if (alpha <= 100)
{
picboxPic.Image = SetImgOpacity(originalImage, alphaToOpacity(alpha));
picboxPic.Invalidate();
alpha++;
}
if (alpha > 100)
{
alpha = 100;
fade2.Stop();
fade.Start();
}
}
float alphaToOpacity(int alpha)
{
if (alpha == 0)
return 0.0f;
return (float)alpha / 100.0f;
}
//Setting the opacity of the image
public static Image SetImgOpacity(Image imgPic, float imgOpac)
{
Bitmap bmpPic = new Bitmap(imgPic.Width, imgPic.Height);
Graphics gfxPic = Graphics.FromImage(bmpPic);
ColorMatrix cmxPic = new ColorMatrix();
cmxPic.Matrix33 = imgOpac;
ImageAttributes iaPic = new ImageAttributes();
iaPic.SetColorMatrix(cmxPic, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
gfxPic.DrawImage(imgPic, new Rectangle(0, 0, bmpPic.Width, bmpPic.Height), 0, 0, imgPic.Width, imgPic.Height, GraphicsUnit.Pixel, iaPic);
gfxPic.Dispose();
return bmpPic;
}
}
The code is a bit crude and you will need to take care of the dispose when you close the form, but it fades up and and down and the rest can easily be taken care of - I also made it quicker for testing 'cos life's short :-)

Which part of the code make Drawing on bitmap slow...C# [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
Code running slow.. the code generates multiple circles have a center can be specified by first click then change the radius by moving the pointer to desired circle size then click to finish drawing the circle.. and repeat to draw another circle..I solved the problem by very simple step see the comments down
public partial class Form1 : Form
{
public class Seat
{
private string _SeatKey;
private Rectangle _SeatRectangle;
public Seat(string seatKey, Rectangle seatRectangle)
{
_SeatKey = seatKey;
_SeatRectangle = seatRectangle;
}
public string SeatKey
{
get { return _SeatKey; }
}
public Rectangle SeatRectangle
{
get { return _SeatRectangle; }
set { _SeatRectangle = value; }
}
}
List<Seat> _Seats = new List<Seat>();
List<Seat> _center = new List<Seat>();
public Form1()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
for (int i = 0; i < 30; i = i + 1)
{
string mystring = "regctangle" + i.ToString();
_Seats.Add(new Seat(mystring, new Rectangle(50, 50, 50, 50)));
}
for (int i = 0; i < 30; i = i + 1)
{
string mystring = "center" + i.ToString();
_center.Add(new Seat(mystring, new Rectangle(50 , 50 , 3, 3)));
}
}
Bitmap background;
Graphics scG;
private Point clickCurrent = Point.Empty;
private Point clickPrev = Point.Empty;
int clikno = 1;
int xpos;
int ypos;
int clicknew = 0;
int radius=0;
int recH;
int recW;
int xcen;
int ycen;
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
clikno = clikno + 1;
clicknew = clicknew + 1;
}
private void Form1_Load(object sender, EventArgs e)
{
background = new Bitmap(Width, Height);
scG = Graphics.FromImage(background);
}
protected override void OnPaint(PaintEventArgs pe)
{
pe.Graphics.DrawImage(Draw(), 0, 0);
}
public Bitmap Draw()
{
Graphics scG = Graphics.FromImage(background);
Pen myPen = new Pen(System.Drawing.Color.Red, 1/2);
Pen mPen = new Pen(System.Drawing.Color.Black, 3);
scG.Clear(SystemColors.Control);
_Seats[clikno].SeatRectangle = new Rectangle(xpos, ypos, recH, recW);
_center[clikno].SeatRectangle = new Rectangle(xcen, ycen, 3, 3);
for (int i = 0; i < clikno+1; i = i + 1)
{
scG.DrawEllipse(myPen, _Seats[i].SeatRectangle);
scG.DrawEllipse(mPen, _center[i].SeatRectangle);
}
Refresh();//This what cause the code running slow
return background;
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
AutoSize = true;
clickCurrent = this.PointToClient(Cursor.Position);
clickPrev = clickCurrent;
if (clickPrev == Point.Empty) return;
Refresh();
clickCurrent = Point.Empty;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
double oradius = Math.Sqrt((Math.Pow(clickPrev.X - e.X, 2)) + (Math.Pow(clickPrev.Y - e.Y, 2)));
radius = Convert.ToInt32(oradius);
if (clicknew == 1)
{
recH = radius;
recW = radius;
xpos = clickPrev.X - recW / 2;
ypos = clickPrev.Y - recH / 2;
xcen = clickPrev.X - 3 / 2;
ycen = clickPrev.Y - 3 / 2;
Refresh();
}
if (clicknew == 2)
clicknew = 0;
Refresh();
}
}
The part that is so slow is your MouseMove.
Usually one checks for the left mousebutton to be pressed:
private void yourDrawingControl_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left) )
{
// draw stuff
}
}
If that is not a condition that applies you should check for having moved for at least more than one pixel:
Point oldLocation = Point.Empty;
private void yourDrawingControl_MouseMove(object sender, MouseEventArgs e)
{
int minStep = 3;
if ( (Math.Abs(oldLocation.X - e.X) + Math.Abs(oldLocation.Y - e.Y) > minStep) )
{
// draw stuff
}
oldLocation = e.Location;
}
Also: While the graphics is being built up, do not draw into a Bitmap which you then draw with DrawImage in your Paint event. Instead in the Paint event draw directly onto the surfce of the control from a List<yourDrawingshapeClass> !
Drawing hundreds of Rectangles is extremely fast compared to drawing even one of your Bitmaps..
Also: It looks as if you are drawing onto the Form? Better draw onto a dedicated control with just the right size; the natural choice is a PictureBox, which is made for this and is double-buffered out-of-the-box. Instead your enforce the form to refresh all it controls..
Finally: Even if you refuse to change your convoluted code to something well-proven, at least make it not call Refresh in the MouseMove unconditionally (in the last line), even if nothing at all has changed!

C# rectangle drawing and screenshot

So, let me just start out by showing you the code I have now:
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
currentPos = startPos = e.Location;
drawing = true;
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
currentPos = e.Location;
//Calculate X Coordinates
if (e.X < startPos.X)
{
CurrentTopLeft.X = e.X;
}
else
{
CurrentTopLeft.X = startPos.X;
}
//Calculate Y Coordinates
if (e.Y < startPos.Y)
{
CurrentTopLeft.Y = e.Y;
}
else
{
CurrentTopLeft.Y = startPos.Y;
}
if (drawing)
this.Invalidate();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (drawing)
{
this.Hide();
SaveScreen();
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Color col = Color.FromArgb(75, 100, 100, 100);
SolidBrush b = new SolidBrush(col);
if (drawing)
e.Graphics.FillRectangle(b, getRectangle());
}
My SaveScreen function:
private void SaveScreen()
{
ScreenShot.CaptureImage(CurrentTopLeft, Point.Empty, getRectangle());
}
The CaptureImage function:
public static void CaptureImage(Point SourcePoint, Point DestinationPoint, Rectangle SelectionRectangle)
{
string FilePath = "temp.jpg";
using (Bitmap bitmap = new Bitmap(SelectionRectangle.Width, SelectionRectangle.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(SourcePoint, DestinationPoint, SelectionRectangle.Size);
}
bitmap.Save(FilePath, ImageFormat.Jpeg);
}
string Filename = String.Format("{0:yyyy-M-d-HH-mm-ss}", DateTime.Now) + ".jpg";
string Server = "";
System.Net.WebClient Client = new System.Net.WebClient();
Client.Headers.Add("Content-Type", "image/jpeg");
byte[] result = Client.UploadFile(Server + "upload.php?filename=" + Filename + "", "POST", FilePath);
string s = System.Text.Encoding.UTF8.GetString(result, 0, result.Length);
Program.mainForm.Notify(Server + Filename);
File.Delete(FilePath);
}
This is just the basic code I have for drawing a rectangle on the screen. When the rectangle is drawn, it takes an image, works perfectly.
The problem is, that the drawing of the rectangle is not smooth at all. I have enabled doublebuffering and pretty much tried everything, but with no luck.
Also, I would like to grab the current screen, or freeze it, and then be able to draw on that frozen screen, instead of just drawing on top of the active screen if you understand me. How would this be done?
Any help is much appreciated!
Maybe that post will help you:
How to draw directly on the Windows desktop, C#?
You could try something like this:
int width =
Screen.PrimaryScreen.Bounds.Width,
height = Screen.PrimaryScreen.Bounds.Height;
Bitmap screen = default( Bitmap );
try
{
screen = new Bitmap
(
width,
height,
Screen.PrimaryScreen.BitsPerPixel == 32 ?
PixelFormat.Format32bppRgb :
PixelFormat.Format16bppRgb565
);
using (Graphics graphics = Graphics.FromImage(screen))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.CopyFromScreen
(
new Point() { X = 0, Y = 0 },
new Point() { X = 0, Y = 0 },
new Size() { Width = width, Height = height },
CopyPixelOperation.SourceCopy
);
// Draw over the "capture" with Graphics object
}
}
finally
{
if (screen != null)
{
screen.Dispose();
}
}

Manipulating an image and updating the pictureBox has some issues

I could not fit exactly what I wanted to say in the title, it would be too long. Okay this is a multi-threaded app. What my app does is looks at a picture, find the edges of the picture, and finds the shape of that object from the edges. While it finds the shape, it constantly updates the image so we can get some sort of visual representation. I have created a very short (40 seconds) video demonstrating the issue: http://phstudios.com/projects/Programming/MultiThreadIssue/
As you can see, everything is working fine until the minute I move the window. This is always the case. I ran the program several times without moving the window and it ran fine. However, the minute I move the window, it will come up with that error. As you see, I am locking the specific image I would like to work with. Is the forms OnPaint overriding that somehow? Is there any way I can fix that?
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;
using System.Threading;
using System.Drawing.Imaging;
namespace LineRecognition
{
public enum Shape
{
Unknown,
Quadrilateral,
Circle,
Triangle
}
public partial class Form1 : Form
{
Bitmap image, original, shapes;
List<Point> outlines;
Shape shape;
ShapeDetection detector;
public Form1()
{
InitializeComponent();
edgeDetection.WorkerReportsProgress = true;
shapeDetection.WorkerReportsProgress = true;
shape = Shape.Unknown;
}
private void Form1_Load(object sender, EventArgs e)
{
original = new Bitmap("photo1.png");
image = new Bitmap("photo1.png");
shapes = new Bitmap(image.Width, image.Height);
pictureBox1.Image = (Image)original;
}
private void findLines_Click(object sender, EventArgs e)
{
if (edgeDetection.IsBusy != true)
{
lblStatus.Text = "Finding Edges";
edgeDetection.RunWorkerAsync();
}
}
private void justTheOutlines(Bitmap image, List<Point> pixels, BackgroundWorker worker)
{
lock (image)
{
for (int i = 0; i < pixels.Count; i++)
{
image.SetPixel(pixels[i].X, pixels[i].Y, Color.Red);
worker.ReportProgress((int)((float)i * 100 / (float)pixels.Count));
}
}
}
private List<Point> outlineLines(Bitmap image, BackgroundWorker worker)
{
int w = image.Width;
int h = image.Height;
int alpha = 800000;
List<Point> changes = new List<Point>();
lock (image)
{
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
Color selected = image.GetPixel(i, j);
Color nextRight = selected;
Color nextDown = selected;
if (i < w - 1)
nextRight = image.GetPixel(i + 1, j);
if (j < h - 1)
nextDown = image.GetPixel(i, j + 1);
int iSelected = selected.ToArgb();
int iNextRight = nextRight.ToArgb();
int iNextDown = nextDown.ToArgb();
if (Math.Abs(iSelected - iNextRight) > alpha)
{
if (iSelected < iNextRight)
{
Point p = new Point(i, j);
if(!ContainsPoint(changes, p)) changes.Add(p);
}
else
{
Point p = new Point(i + 1, j);
if (!ContainsPoint(changes, p)) changes.Add(p);
}
}
if (Math.Abs(iSelected - iNextDown) > alpha)
{
if (iSelected < iNextDown)
{
Point p = new Point(i, j);
if (!ContainsPoint(changes, p)) changes.Add(p);
}
else
{
Point p = new Point(i, j + 1);
if (!ContainsPoint(changes, p)) changes.Add(p);
}
}
image.SetPixel(i, j, Color.White);
}
worker.ReportProgress((int)(((float)i / (float)w) * 100));
}
}
return changes;
}
private bool ContainsPoint(List<Point> changes, Point p)
{
foreach (Point n in changes)
{
if (n.Equals(p)) return true;
}
return false;
}
private void edgeDetection_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
outlines = outlineLines(image, worker);
justTheOutlines(image, outlines, worker);
pictureBox2.Image = (Image)image;
Thread.Sleep(100);
image.Save("photo-lines.jpg");
}
private void edgeDetection_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
algorithmProgress.Value = e.ProgressPercentage;
}
private void edgeDetection_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
algorithmProgress.Value = 0;
findLines.Enabled = false;
determineShape.Enabled = true;
lblStatus.Text = "";
}
private void determineShape_Click(object sender, EventArgs e)
{
if (shapeDetection.IsBusy != true)
{
pictureBox1.Image = (Image)image;
lblStatus.Text = "Running Shape Detection: Circle -> Quadrilateral";
shapeDetection.RunWorkerAsync();
}
}
private void ShapeDetection_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
detector = new ShapeDetection(outlines, 40, 10);
detector.Worker = worker;
detector.circleChange += new ShapeDetection.CircleChangeEventHandler(circleChange);
if (detector.IsCircle())
{
MessageBox.Show("Object is a circle");
shape = Shape.Circle;
}
else if (detector.IsQuadrilateral())
{
MessageBox.Show("Object is a quadrilateral", "Number of edges: " + detector.Summits);
shape = Shape.Quadrilateral;
}
else
{
int sides = detector.Summits.Count;
if (sides == 3)
{
MessageBox.Show("Object is a triangle");
shape = Shape.Triangle;
}
else
{
MessageBox.Show("Number of edges: " + detector.Summits.Count, "Unknown");
}
}
BitmapDrawing.DrawLines(detector.Summits, shapes);
BitmapDrawing.DrawSummits(detector.Summits, shapes);
pictureBox2.Image = (Image)shapes;
Thread.Sleep(100);
}
private void ShapeDetection_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (detector != null)
{
lblSummits.Text += detector.Summits.Count;
lblType.Text += shape.ToString();
determineShape.Enabled = false;
lblStatus.Text = "";
}
}
void circleChange(object sender, CircleChangeEventArgs e)
{
lock (shapes)
{
Point p = detector.visited[detector.visited.Count - 1];
shapes.SetPixel(p.X, p.Y, Color.Blue);
pictureBox2.Image = (Image)shapes;
Thread.Sleep(10);
detector.Worker.ReportProgress((int)(e.percent * 100));
}
}
private void shapeDetection_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
algorithmProgress.Value = e.ProgressPercentage;
}
}
}
Update
What Nick said before worked fine. I added that to my CircleChange event and it works. Can somebody explain why the invoke makes it work instead of setting the picturebox2.Image to the shapes image? I mean I call it after I call setpixel, so I should be done modifying the image right?
void circleChange(object sender, CircleChangeEventArgs e)
{
Point p = detector.visited[detector.visited.Count - 1];
shapes.SetPixel(p.X, p.Y, Color.Blue);
Image copyForPictureBox = shapes.Clone() as Image;
BeginInvoke(new Action(() => pictureBox2.Image = copyForPictureBox));
Thread.Sleep(15);
detector.Worker.ReportProgress((int)(e.percent * 100));
}
It appears that you're operating on the shapes bitmap on a thread separate to the GUI thread. When you move the window the OnPaint routine will run which will also access the image.
What you need to do to solve this is operate on a separate bitmap in your worker thread, and then pass a copy of that to the GUI thread using Invoke on the form. That way you're guaranteed only to have one thread accessing the picture box image at a time.
Edit:
void MyThreadFunction( )
{
Bitmap localThreadImage;
...
Image copyForPictureBox = localThreadImage.Clone( ) as Image;
BeginInvoke( new Action( () => pictureBox.Image = copyForPictureBox ) );
....
}
So the idea is that you create a Bitmap on the thread which is only ever accessed by that thread (i.e. your background worker thread). And when you get to a point when you want to update the image in the PictureBox you invoke onto the the GUI thread using BeginInvoke (which doesn't block your worker thread) passing a copy of the Bitmap to the PictureBox.
Locking the shapes object at only one point in your application does not accomplish anything. You also use this bitmap to draw to the window, and my guess is that you are not locking it for drawing. You can either lock it in OnPaint as well, or use a different bitmap for manipulation and display.

Categories