I have a control which draws some items on a bitmap.
I am using the Microsoft gesture library to scroll and pan the bitmap.
While doing panning or scrolling my bitmap flickers a lot.
I am drawing just a portion of bitmap in OnPaint method depending on scroll/panned cordinates.
Following is the sample code:
protected override void OnPaint(PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
if (!_painted)
{
// drawing items first time
InitilizeBitmap(g);
_painted = true;
}
Rectangle rec = new Rectangle(0, _bitmapLocation.Y, ClientRectangle.Width, ClientRectangle.Height);
using (Graphics gCached = Graphics.FromImage(_cachedBitmap))
{
gCached.Clear(BackColor);
gCached.DrawImage(_bmpControl, 0, 0, rec, GraphicsUnit.Pixel);
}
g.DrawImage(_cachedBitmap, 0, 0);
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
Is there a way to avoid flickering ?
The problem is you're only using 1 buffer, so draw operations appear immediately. What you need is page flipping/double buffering.
What you want to do is set up a new graphics object that you do all your drawing on, and then draw the finished object when it's done. This should eliminate any flickering because the bitmap that is facing the screen is only updated once, and all in one go.
Related
I am drawing images in a C# Winforms panel with:
private void DrawPanel_Paint(object sender, PaintEventArgs e)
{
DrawOnPanel(e.Graphics);
}
The called method takes an existing image from my resources (myImage), gives it to another method which resizes the image and returns the resized image so it can be drawn.
public static void DrawOnPanel(Graphics g)
{
var _resizedImage = ResizeImage(Resources.myImage);
g.DrawImage(_resizedImage, destX, destY);
// ... several other images resized & manipulated then drawn
}
The resize image function is:
public Bitmap ResizeImage(Bitmap image)
{
int scale = 3;
var destImage= new Bitmap(image.Width * scale, image.Height * scale);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
graphics.SmoothingMode = SmoothingMode.HighSpeed;
graphics.PixelOffsetMode = PixelOffsetMode.None;
using var wrapMode = new ImageAttributes();
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
return destImage;
}
The program keeps calling DrawPanel.Invalidate() in its loop.
I am detecting a memory leak each time DrawPanel.Invalidate() is called. The memory consumption is rising steadily until the GC takes care of it. While this isn't a game breaking problem, I'm still wondering where and how should I dispose of my objects in the above code.
I tried using using var _resizedImage = ResizeImage(Resources.myImage); in the above DrawOnPanel method but the program returns an error System.ArgumentException: 'Parameter is not valid.'. If I remove using there is no error.
Every time that ResizeImage is called, you create a new Bitmap. This Bitmap should be Disposed as soon as you know that it is not needed anymore. It seems that you don't need the resized image after you've drawn it on the Graphics. Therefore I suggest the following change:
public static void DrawOnPanel(Graphics g)
{
using (Image_resizedImage = ResizeImage(Resources.myImage))
{
g.DrawImage(_resizedImage, destX, destY);
}
// resizedImage is Disposed
// ... several other images resized & manipulated then drawn
}
Room for improvement
Your current design will create a new Resized Bitmap every time DrawOnPanel is called. It seems to me that most of time that Resources.myImage is resized the resized Bitmap will be the same. Why don't you just remember it until the parameters that influence the resized Bitmap are changed?
If I look at the code it seems that you always create the same resized image from an original image. So you could even put all your resized images into one Dictionary for fast lookup:
// get all Image resources that you will resize and put them in a Dictionary
// Key original Bitmap, Value: resized Bitmap
private Dictionary<Bitmap, BitMap> images = this.FetchImages()
.ToDictionary(
// Parameter keySelector: key is original image
image => image,
// Parameter valueSelector: value is the resized image
imgage => ResizeImage(original));
Displaying the resized images will now be much faster: only a Lookup.
public static void DrawOnPanel(Graphics g)
{
var _resizedImage = this.Images[Resources.myImage];
g.DrawImage(_resizedImage, destX, destY);
// ... several other images resized & manipulated then drawn
}
Don't forget to Dispose the bitmaps when your Form is Closed, or at its latest when your Form is Disposed:
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (var resizedImage in images.Values)
resizedImage.Dispose();
}
}
I have a grid already drawn to a picturebox and I wish to draw a rectangle in the cell the mouse is clicked in. I'm trying to do this by drawing a rectangle in the top left corner of the cell and having it fill the entire cell.
However when I click the grid disappears.
How do I make it so the grid stays?
and is there an obvious better method for doing this?
The only way to redraw the grid is by pressing the button again but I want it to stay there.
Thanks.
You need to create a Graphics
Image bm;// Or Bitmap
using (var graphics = Graphics.FromImage(bm))
{
Draw(rects, graphics);
}
private static void Draw(List<Rectangle> rectangles, Graphics graphics)
{
Pen redPen = new Pen(Color.Red, 1);
foreach (var rect in rectangles)
{
graphics.DrawRectangle(redPen, rect);
}
}
At every mouse click in the mouse_click event at first you dispose the image of the picture box and then again assign the bitmap to the picturebox image otherwise you might face out of memory exception if you click on the grid too many times
private void InitializePictureBoxImage(PictureBox pictureBox)
{
if(pictureBox.Image != null)
{
var image = pictureBox.Image; // cache it to dispose
pictureBox.Image = null; // don't dispose an used object
image.Dispose(); // and dispose of it
}
Bitmap bitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
pictureBox.Image = bitmap; // assign bitmap to image
}
Then call the method in the mouse_click event which you used to draw the picture box when you press the Generate Grid button in your form. Afterwards use graphics inside the mouse_click event to colour the grid which you pressed taking the X-Y coordinate from MouseEventArgs.
Altogether I think it will be something like this
private void Mouse_ClickEvent(object sender, MouseEventArgs e)
{
InitializePictureBoxImage(pictureBox);
DrawThePictureBoxImage(pictureBox);
using (Graphics graphics = Graphics.FromImage(pictureBox.Image))
{
Color color = Color.Blue;
color = Color.FromArgb(150, color.R, color.G, color.B); // lower the opacity so the writing in the grid is visible
var brush = new SolidBrush(color);
// Calculate the starting X-Y coordinate and Width,Height of the grid to color
graphics.FillRectangle(brush, startX, startY,
width, height);
}
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."
I am currently working on a curved progress bar in a smart device project. I override the OnPaint function.
In Override OnPaint() function, I draw the curved progress bar in dark gray and draw a part of the progress bar in yellow to reflect the changing value(such as 30%) of the progress bar. However, When the value is changing continuously, I can see that part of the progress bar's colour changes to yellow. However, the curved progress bar itself is also redrawn. Does anyone know a way of how to avoid redrawing the orginal curved progress bar while the value is changing? So that when the value is changing, it only redraw a part of the progress bar with yellow colour, not the orginal progress bar with gray. Here is the code I use in OnPaint function.
protected override void OnPaint(PaintEventArgs e)
{
gx = e.Graphics;
// Draw the original curved progress bar
int intPosition1 = m_NumberOfSpoke;
for (int intCounter1 = 0; intCounter1 < m_NumberOfSpoke; intCounter1++)
{
intPosition1 = intPosition1 % m_NumberOfSpoke;
DrawLine(e.Graphics,
GetCoordinate(m_CenterPoint, m_InnerCircleRadius, m_Angles[intPosition1]),
GetCoordinate(m_CenterPoint, m_OuterCircleRadius, m_Angles[intPosition1]),
Color.DarkGray, m_SpokeThickness);
intPosition1++;
}
// Draw a part of the progress bar to reflect the changing current value(such as 30%)
int intPosition = CurrentValue;
for (int intCounter1 = 0; intCounter1 < CurrentValue; intCounter1++)
{
intPosition = intPosition % CurrentValue;
DrawLine(gx,
GetCoordinate(m_CenterPoint, m_InnerCircleRadius, m_Angles[intPosition]),
GetCoordinate(m_CenterPoint, m_OuterCircleRadius, m_Angles[intPosition]),
Color.Yellow, m_SpokeThickness);
intPosition++;
}
base.OnPaint(e);
}
I have tried to use Override OnBackgroundPaint to draw the orginal curved progress bar as a background to avoid redraw it again in OnPaint, but it doesn't work. I cannot see any things when the form is loaded. Any ideas?
Thanks for any help in advance.
Regards
That's a load of DrawLine calls to the actual screen, and it's very likely going to cause flickering. You should double buffer by creating a back buffer, do all you drawing to it, then blit that to the screen with a single call to DrawBitmap along these lines:
protected override void OnPaint(PaintEventArgs e)
{
using(var buffer = new Bitmap(this.Width, this.Height))
using(var gx = Graphics.FromImage(buffer))
{
// for loops to draw to gx
....
e.Graphics.DrawBitmap(buffer, ...);
}
}
I'd also be highly inclined to not do exactly what's above, but cache that buffer to prevent the garbage generation of a Bitmap on every call.
Bitmap m_buffer;
Gramphic m_gx;
protected override void OnPaint(PaintEventArgs e)
{
if(m_buffer == null)
{
m_buffer = new Bitmap(this.Width, this.Height))
m_gx = Graphics.FromImage(buffer))
}
// clear the backbuffer with a FillRect
// for loops to draw to m_gx
....
e.Graphics.DrawBitmap(m_buffer, ...);
}
I'd probably even take this a step further, if the grey part of the control is always the same and do a "triple buffer", keeping a cached version of the image with the grey drawn, then in OnPaint, blit that to the graphics, draw the yellow, then blit to the screen.
Bitmap m_buffer;
Bitmap m_backimage;
Gramphic m_gx;
protected override void OnPaint(PaintEventArgs e)
{
if(m_buffer == null)
{
m_backimage = new Bitmap(this.Width, this.Height);
var g = Graphics.FromImage(m_backImage);
// for loop to draw the grey stuff to g
....
m_buffer = new Bitmap(this.Width, this.Height))
m_gx = Graphics.FromImage(buffer))
}
m_gx.DrawImage(m_backImage);
// for loop to draw *just the yellow* to m_gx
....
e.Graphics.DrawBitmap(m_buffer, ...);
}
You're going to probably have to override OnResize and some other stuff too, plus extend Dispose to clean up these member-level GDI objects, but perf will be way better and it's not going to flicker.
I'm trying to solve a problem with a custom control ported from a VCL C++ application. The idea is that individual parts of the control are drawn first on a new Graphics object and then merged with the Graphics object from the control's paint method.
I've created a simplified example:
using System.Drawing;
using System.Windows.Forms;
namespace Test
{
public class Form1 : Form
{
private PictureBox pictureBox;
public Form1()
{
pictureBox = new PictureBox();
pictureBox.Dock = DockStyle.Fill;
pictureBox.Paint += new PaintEventHandler(pictureBox_Paint);
ClientSize = new Size(100, 50);
Controls.Add(pictureBox);
}
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
SolidBrush blackBrush = new SolidBrush(Color.Black);
Bitmap bitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
Graphics graphics = Graphics.FromImage(bitmap);
Font font = new Font(pictureBox.Font, FontStyle.Regular);
graphics.DrawString("simple test", font, Brushes.Black, 0, 0);
e.Graphics.DrawImage(bitmap, 0, 0);
}
}
}
Running the above code results in the text being drawn too thick:
When i modify the code to draw the text directly to the Graphics object of the PictureBox i get the correct result:
This problem only occurs with text rendering. Lines and other shapes are rendered correctly. Any ideas how to solve this?
Thanks in advance.
This happens because you forgot to initialize the bitmap pixels. By default they are Color.Transparent, which is black with an alpha of 0. This goes wrong when you draw anti-aliased text into the bitmap, the aliasing algorithm will draw pixels that blend from the foreground (black) to the background (also black). Blobby letters that are not anti-aliased is the result.
This is not a problem in the 2nd screenshot because the background pixels were drawn to battleship gray by the default Form.OnPaintBackground() method. Simply add the Graphics.Clear() method to fix your problem:
using (var bitmap = new Bitmap(pictureBox.Width, pictureBox.Height)) {
using (var graphics = Graphics.FromImage(bitmap)) {
graphics.Clear(this.BackColor); // <== NOTE: added
graphics.DrawString("simple test", pictureBox1.Font, Brushes.Black, 0, 0);
}
e.Graphics.DrawImage(bitmap, 0, 0);
}
With using statements added to prevent your program from crashing when the garbage collector doesn't run often enough.