Quickly draw pixels in WinForm in a container - c#

I have a sizable form and the location in which a pixel is drawn moves, I have a panel which is the desired image size 400,200 - how can I change individual pixels on this panel? I also need the fastest change possible.

GDI+ does not have a method to set a single pixel, however you can set a 1x1 rectangle to achieve the same effect in your OnPaint event. It feels somewhat like a hack, however there is no standard way of doing this.
SolidBrush brush = new SolidBrush(Color.White);
private void panel1_Paint(object sender, PaintEventArgs e)
{
SetPixel(brush, e.Graphics, 10, 10, Color.Red);
}
public void SetPixel(SolidBrush brush, Graphics graphics, int x, int y, Color color)
{
brush.Color = color;
graphics.FillRectangle(brush, x, y, 1, 1);
}
You could also access the bitmap directly and use it's GetPixel and SetPixel methods, however they are generally very slow if your pixels need to be updated quickly.

Related

Speed up adding objects to Canvas In WPF

I have a Canvas that I am using in WPF to draw many colored rectangles to but the program is running really slow when they are being added. I have tried different options, such as adding them to an Array and adding them all at once and using a Image instead of a Canvas to dispay them, but they didn't seem to do much. I have the coding leading up the drawing in a thread, but because of C# rules, I have to have the drawing part in the main thread. I should also note that the problem isn't my computer (its running Intel Core i7 with 14GB DDR2 RAM).
This is the code that adds the rectangles. It is ran over 83,000 times.
private void AddBlock(double left, double top, double width, double height, Brush color)
{
if (this.Dispatcher.Thread != Thread.CurrentThread)
{
this.Dispatcher.Invoke(new Action<double, double, double, double, Brush>(this.AddBlock), left, top, width, height, color);
return;
}
Rectangle rect = new Rectangle() { Width = width, Height = height, Fill = color, SnapsToDevicePixels = true };
this.canvas.Children.Add(rect);
Canvas.SetLeft(rect, left);
Canvas.SetTop(rect, top);
}
NOTE: As I stated in a comment below, I would like something that allows it to run on a seperate thread (even if it involves working with P/Invoke) as there doesn't seem to a viable solution to just using C# and WPF.
Any suggestions?
Using OnRender method
I created a class inheriting Canvas, and override the OnRender method to get the DrawingContext and used it to draw. so in the code I dont add rects to the canvas but to the rect list in new class and call InvalidateVisual(); using Dispatcher once I am done with add.
class MyCanvas:Canvas
{
public class MyRect
{
public Rect Rect;
public Brush Brush;
}
public List<MyRect> rects = new List<MyRect>();
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
base.OnRender(dc);
for (int i = 0; i < rects.Count; i++)
{
MyRect mRect = rects[i];
dc.DrawRectangle(mRect.Brush, null, mRect.Rect);
}
}
}
xaml
<l:MyCanvas x:Name="canvas"/>
to add the rects
private void AddBlock(double left, double top, double width, double height, Brush color)
{
canvas.rects.Add(new MyCanvas.MyRect() { Brush = color, Rect = new Rect(left, top, width, height) });
}
refresh when ready, should be made on dispatcher
canvas.InvalidateVisual();
This seems to be the fastest way to draw in WPF, you may not need to go till GDI+ or pinvoke. During tests in my system original code took approx 500 ms to render 830 rects and geometry took approx 400 ms to render the same, where as this approach rendered 83,000 rects in less then 100 ms
Also I would advice you to add some caching to avoid rendering excessively
A solution using geometry
class level variables
GeometryGroup gGroup;
prepare using the following code
DrawingBrush dBrush= new DrawingBrush();
gGroup = new GeometryGroup();
GeometryDrawing gDrawing = new GeometryDrawing(Brushes.Red, null, gGroup);
dBrush.Drawing = gDrawing;
Canvas.Background = dBrush
then comes your code
private void AddBlock(double left, double top, double width, double height, Brush color)
{
if (this.Dispatcher.Thread != Thread.CurrentThread)
{
this.Dispatcher.Invoke(new Action<double, double, double, double, Brush>(this.AddBlock), left, top, width, height, color);
return;
}
//color need to figure out as it is added in GeometryDrawing
//currently Brushes.Red defined earlier
gGroup.Children.Add(new RectangleGeometry(new Rect(left, top, width, height)));
}
This sample may help you achieve the same. I'll also do some experiment soon to get your desired result in a much faster way.

Persistent graphics WinForms

I've a WinForms application on wich i have to draw some lines between controls. These lines need to be persistent, so i override the form OnPaint() event.
The problem is that, the re-draw of the lines aren't very smooth.
I'm creating the graphics as follows:
Graphics g;
g = this.CreateGraphics();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.FillRectangle(Brushes.White, this.ClientRectangle);
And drawing the lines as follows:
public void lineDraw(Control L1, Control L2) {
using (Pen pen = new Pen(Color.Black, 4)) {
pen.StartCap = System.Drawing.Drawing2D.LineCap.Flat;
pen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
int x1, x2, y1, y2;
//choose x/y coordinates
g.DrawLine(pen, x1, y1, x2, y2);
}
}
Is there any property i can set to improve the smoothness of the drawn graphics?
Goal
An image is shown on a control (or form).
Invalidation
Any time the control (or form) is resized, minimalized/maximalized, partically obscured or moved around, it must be (partially) redrawn. When this happens the part of the control that must be redrawn is said to be invalidated.
When invalidated the control does something like this:
Call OnPaintBackground: this fills the invalidated region with the background color.
Call OnPaint: this draws the text and graphics on top of the background.
Why OnPaint causes flickering
You have overridden the OnPaint method of the control. Every time the control is redrawn you see a flash of the control with only its background color drawn in it. That is after OnPaintBackground has been called and before OnPaint has been called.
The solutions
If you have a static image (i.e. it never changes):
In the Load event: create a new Bitmap object.
Fill it with the background color and draw the lines and shapes on it.
Assign this Bitmap object to the control's BackgroundImage property.
If you have a static image that must resize when the control resizes:
Override the OnResize method and create the new Bitmap in there. Use the control's ClientSize property for the size of the Bitmap.
Fill it with the background color and draw the lines and shapes on it.
Assign this Bitmap object to the control's BackgroundImage property.
If you have an animated image:
In the Load event set the DoubleBuffered property of the control to true. Setting this prevents the flicker you saw as it uses an invisible buffer to draw the control.
Override the control's OnPaint method. Get the Graphics context of the control and draw the lines and shapes directly on the control.
Create and enable a new Timer object and in its callback method call the control's Invalidate method followed by the Update method (as shown here). Set the timer to fire, for example, every 40 ms.
You probably shouldn't use CreateGraphics here, and more importantly, don't use a local variable to store the graphic object. Use the graphic object obtained from the paint event and invalidate as needed.
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.Clear(Color.White);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (Pen pen = new Pen(Color.Black, 4)) {
pen.StartCap = Drawing2D.LineCap.Flat;
pen.EndCap = Drawing2D.LineCap.ArrowAnchor;
int x1, x2, y1, y2;
//choose x/y coordinates
e.Graphics.DrawLine(pen, x1, y1, x2, y2);
}
base.OnPaint(e);
}
This's my way, its works for me
//FormMain.cs
private const float DisplayRatio = 6;
private Bitmap _bmpDisp; //use an in-memory bitmap to Persistent graphics
private Graphics _grpDisp4Ctl;
private Graphics _grpDisp4Bmp;
private Point _ptOldDsp;
private void FormMain_Shown(object sender, EventArgs e)
{
_grpDisp4Ctl = CreateGraphics();
_grpDisp4Ctl.SetHighQulity();
_bmpDisp = new Bitmap(ClientSize.Width, ClientSize.Height);
_grpDisp4Bmp = Graphics.FromImage(_bmpDisp);
_grpDisp4Bmp.SetHighQulity();
_ptOldDsp = new Point(
(int)((MousePosition.X - SystemInformation.VirtualScreen.Left) / DisplayRatio),
(int)((MousePosition.Y - SystemInformation.VirtualScreen.Top) / DisplayRatio)
);
}
private void UpdateDisplay(MouseHookEvent mhep) //your implement
{
var ptNew = mhep.Position;
ptNew.Offset(new Point(-SystemInformation.VirtualScreen.Left, -SystemInformation.VirtualScreen.Top));
ptNew.X = (int)(ptNew.X / DisplayRatio);
ptNew.Y = (int)(ptNew.Y / DisplayRatio);
_grpDisp4Ctl.DrawLine(Pens.White, _ptOldDsp, ptNew); //draw smooth lines to mem and ui
_grpDisp4Bmp.DrawLine(Pens.White, _ptOldDsp, ptNew);
_ptOldDsp = ptNew;
}
private void FormMain_Paint(object sender, PaintEventArgs e)
{
// like vb6's auto redraw :)
e.Graphics.DrawImage(_bmpDisp, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);
}
//common.cs
internal static void SetHighQulity(this Graphics g)
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
}
I know it's an older post, but you can also try setting DoubleBuffered property of the form to TRUE, read the following:
"Buffered graphics can reduce or eliminate flicker that is caused by
progressive redrawing of parts of a displayed surface. Buffered
graphics require that the updated graphics data is first written to a
buffer. The data in the graphics buffer is then quickly written to
displayed surface memory. The relatively quick switch of the displayed
graphics memory typically reduces the flicker that can otherwise
occur."

Drawing in Winforms

I have written this code, however, it doesn't work. Not only will this not work, but none of the methods I have tried for drawing have worked. I've spent more than an hour or two trying to solve this, but to no success. Ive tried simple programs where all it does is display a small line, but it wont work no matter what i do :c
What am I doing wrong, or what could cause this?
private void pictureBox1_MouseDown(object sender,
MouseEventArgs m,
EventArgs e,
PaintEventArgs q)
{
if (m.Button == System.Windows.Forms.MouseButtons.Left)
{
Point currpoint = System.Windows.Forms.Cursor.Position;
Point origin = new Point(0, 0);
decimal sizee = nud.Value;
int size = Convert.ToInt32(sizee);
Random randonGen = new Random();
Color randomColor = Color.FromArgb(randonGen.Next(255),
randonGen.Next(255),
randonGen.Next(255));
Pen selPen = new Pen(randomColor, size);
Graphics g = Graphics.FromImage(pictureBox1.Image);
g.DrawLine(selPen, 3, 3, 133, 133);
}
}
Try adding a
pictureBox1.Invalidate();
call.
This is not the right way to draw to a picture box:
private void pictureBox1_MouseDown(object sender,
MouseEventArgs m,
EventArgs e,
PaintEventArgs q)
{
if (m.Button == System.Windows.Forms.MouseButtons.Left)
{
Point currpoint = System.Windows.Forms.Cursor.Position;
Point origin = new Point(0, 0);
decimal sizee = nud.Value;
int size = Convert.ToInt32(sizee);
Random randonGen = new Random();
Color randomColor = Color.FromArgb(randonGen.Next(255),
randonGen.Next(255),
randonGen.Next(255));
Pen selPen = new Pen(randomColor, size);
using(Graphics g = pictureBox1.CreateGraphics()) // Use the CreateGraphics method to create a graphic and draw on the picture box. Use using in order to free the graphics resources.
{
g.DrawLine(selPen, 3, 3, 133, 133);
}
}
}
Btw, this method will create a temporary image which is reseted when the control is invalidated. For a more persistent drawing, you need to listen to the Paint event of the picture box and draw your graphics there.
You must draw it from image first. then attach it to pictureBox1
Bitmap canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);
Graphics g = Graphics.FromImage(canvas);
Point currpoint = System.Windows.Forms.Cursor.Position;
Point origin = new Point(0, 0);
decimal sizee = nud.Value;
int size = Convert.ToInt32(sizee);
Random randonGen = new Random();
Color randomColor = Color.FromArgb(randonGen.Next(255),
randonGen.Next(255),
randonGen.Next(255));
Pen selPen = new Pen(randomColor, size);
g.DrawLine(selPen, 3, 3, 133, 133);
pictureBox1.image = canvas;
This is an old question and if anyone else has a similar problem. See below. First let's examine the Ops code.
(1) See code: The first recommended change is to keep the Pen's format simple until we have a better understanding about how the Pen actually works when drawing to graphics. Look at the Op's line where we create graphics from image which is a perfectly good example of how to directly draw ("which means to write") to the supplied bitmap by use of the bitmap's graphics context. Next, the Op provides an excellent example of the Graphics DrawLine method which can draw the defined line to the supplied bitmap.
(2) Due to missing details we have to make the following assumptions about the Op's supplied bitmap and about their method for drawing a line to the bitmap. Assuming there already exists an image inside this pictureBox1; if an image is not set the graphics we get from image will be from a null image or that each pixel will be black just as a footnote:
(a) Is the Pen's color unique to the existing bitmap and is the alpha component of the color high enough to actually see the resultant color when it's drawn (when in doubt use a unique solid color or at least set the alpha channel to 255)?
(b) This line the Op wants to draw is starting Left 3, Top 3 to Left 133 and that is 3-pixels to the right of bitmap's left side where this line has a height of 133 and as such the Pen's line size was changed to a width = 3 for demonstration purposes.
(c) The final consideration, is the pictureBox1.Size sufficient for us to see this drawn line? The line's geometry forms a rectangle similar to this RectangleF(3, 3, 3, 133) structure, so if the pictureBox1 Bounds rectangle intersects with the derived line's rectangle then the area of that intersection is where the line could be drawn and considered visible.
Before we can draw to the pictureBox1 image from graphics we must first convert the pictureBox1 image data back to a usable image type like a bitmap for example. The reason is the picture box stores only pixel data in array format and is not directly usable by GDI/GDI+ without conversion to an image type ie. bitamp, jpeg, png etc..
One can avoid this messy conversion if you handle you own painting by the way of a custom user control and by properly handling the PaintEventArgs OnPaint implementation and/or by using related graphics screen buffer context scenarios.
For those who just want the answer about what's missing:
private void button1_Click(Object sender, EventArgs e)
{
Pen selPen = new Pen(Color.Red, 2); // The Op uses random color which is not good idea for testing so we'll choose a solid color not on the existing bitmap and we'll confine our Pen's line size to 2 until we know what we're doing.
// Unfortionately the picture box "image" once loaded is not directly usable afterwords.
// We need tp recreate the pictureBox1 image to a usable form, being the "newBmp", and for efficiency the bitmap type is chosen
Bitmap newBmp = new Bitmap(pictureBox1.Width, pictureBox1.Height, PixelFormat.Format32bppArgb); // Tip: Using System.Drawing.Imaging for pixel format which uses same pixel format as screen for speed
// We create the graphics from our new and empty bitmap container
Graphics g = Graphics.FromImage(newBmp);
// Next we draw the pictureBox1 data array to our equivelent sized bitmap container
g.DrawImage(pictureBox1.Image, 0, 0);
g.DrawLine(selPen, 3, 3, 3, 133); // Format: (pen, x1, y1, x2, y2)
pictureBox1.Image = newBmp;
// Don't forget to dispose of no longer needed resources
g.Dispose();
selPen.Dispose();
newBmp.Dispose(); // or save newBmp to file before dispose ie. newBmp.Save("yourfilepath", ImageFormat.Jpeg) or in whatever image type you disire;
}
The Op's code so far only draws a line to the bitmap's surface next if we are to "see" this change we must either save bitmap to file to be viewed later in an image viewer or we must draw the updated bitmap to our display monitor, the screen.
There are several methods with which to draw to your monitor's screen. The most common graphics contexts one could use are Control.CreateGraghics, graphics to screen method from (PaintEventArgs) and/or by using a graphics screen buffer sometimes called and used as a manual double buffered graphics context in which all is implemented by the way of DrawImage method from graphics.
The simplest solution, in this case based upon the Op's own code, is to display this newly updated bitmap using the pictureBox1 control. We'll simply update the control's image with the newly updated bitmap of course once first converted to a usage graphics image as seen in the above code descriptions.
Happy coding!

How to draw position sign?

I writing my own textbox control in C# for Winforms.
One thing i can't figure out: how to draw the text position sign in various sizes?
It is called the 'caret'. The winapi functions are not wrapped by winforms, you'll have to pinvoke them. Start reading here. You'll find code in my answer here.
Try this.
I created a method which is meant to be called from the paint handler of whichever control you're drawing in. For simplicity I just used the form itself in mine. You probably have a panel or some other control.
The method accepts the graphics object, the scale of the cursor and the upper/left position of where to start drawing. The scale is just the height but all the math is performed relative to the height. You can tweak those numbers any way you want.
private void Form1_Paint(object sender, PaintEventArgs e)
{
DrawCaret(e.Graphics, 30, new Point(20, 20));
DrawCaret(e.Graphics, 50, new Point(100, 100));
}
private static void DrawCaret(Graphics g, int scale, Point loc)
{
g.SmoothingMode = SmoothingMode.HighQuality;
int height = scale;
int width = scale/10;
int rectBase = scale/5;
g.FillRectangle(Brushes.Black, loc.X, loc.Y, width, height);
var path = new GraphicsPath();
path.AddPolygon(new[]
{
new Point(loc.X+width, loc.Y),
new Point(loc.X+width+rectBase/2, loc.Y+rectBase/2),
new Point(loc.X+width, loc.Y+rectBase),
});
g.FillPath(Brushes.Black, path);
}
This sample produces the following output:

C#: Why is drawing slow?

Im drawing a linear gradient manually by drawing lines with changing colors. However, this is very slow, and i seems to update, when i resize the window. How do i make it faster? The color scale is linear in this example, but later i wan't to make non-linear gradients.
protected override void OnPaintBackground(PaintEventArgs paintEvnt)
{
SuspendLayout();
// Get the graphics object
Graphics gfx = paintEvnt.Graphics;
// Create a new pen that we shall use for drawing the line
// Loop and create a horizontal line 10 pixels below the last one
for (int i = 0; i <= 500; i++)
{
Pen myPen = new Pen(Color.FromArgb(i/2,0,0));
gfx.DrawLine(myPen, 0, i, 132, i);
}
ResumeLayout();
}
The problem is that GDI+ is incredibly slow.
You should use high level constructs with GDI+ which are relatively fast (relative to drawing lines like you do now). See http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.lineargradientbrush.aspx for more information about e.g. the LinearGradientBrush. There are much more of these brushes and pens which should help you increase your performance.
One more thing: the Suspend/ResumeLayout doesn't do anything in your example. These methods only apply when you are doing layout by e.g. adding Controls to the current form or changing properties on existing Controls like the Dock property or the Height and Width.
If you want to paint it once and only once, without resizing, I suggest you write this to a Bitmap object once, and then draw this bitmap to the background. Also, you can enable double buffering on the form. this should be a property called DoubleBuffering, or something similar. This should reduce the flashing you get when redrawing your form.
You could pre-compute the color values so you won't have to do it on every redraw. Other than that, there's not much more you can do without resorting to more lowlevel APIs, like XNA.
Update: it is perfectly feasible to host XNA within WinForms controls. There's some nice links forward in this question.
Perhaps specifying a ColorBlend to use with the LinearGradientBrush suggested by Pieter will address your concerns about being able to paint non-linear gradients in the future?
You can create a ColorBlend object that specifies the colors of your choice and an arbitrary position for each. By setting the InterpolationColors property of the LinearGradientBrush to your ColorBlend object, you should be able to get any effect that you want.
MSDN gives the following sample:
protected override void OnPaint(PaintEventArgs e)
{
//Draw ellipse using ColorBlend.
Point startPoint2 = new Point(20, 110);
Point endPoint2 = new Point(140, 110);
Color[] myColors = {Color.Green, Color.Yellow, Color.Yellow, Color.Blue, Color.Red, Color.Red};
float[] myPositions = {0.0f,.20f,.40f,.60f,.80f,1.0f};
ColorBlend myBlend = new ColorBlend();
myBlend.Colors = myColors;
myBlend.Positions = myPositions;
LinearGradientBrush lgBrush2 = new LinearGradientBrush(startPoint2, endPoint2, Color.Green, Color.Red);
lgBrush2.InterpolationColors = myBlend;
Rectangle ellipseRect2 = new Rectangle(20, 110, 120, 80);
e.Graphics.FillEllipse(lgBrush2, ellipseRect2);
}

Categories