Monday, November 14, 2005

C# - Graphics – Double Buffering

The codes below is to load a gif image from the local path and draw it on the form, and the image will “animated” by moving 1 step down and right at certain interval. The image must exist in the same path as the application executable. This program has a timer component that executes the codes to increase the X and Y values (for the image), and refresh the screen by calling the form’s Invalidate method. Invalidate will trigger the form’s Paint Event to redraw the form’s client area. The Paint Event will draw the image on the form’s client area with the newly updated X and Y values (the horizontal point, X-axis, and the vertical point, Y-axis).
using System;
using System.Windows.Forms;
using System.Drawing;

public class Form1: Form {
 private Timer timer1;
 private Image imgUFO;

 private int x = 0;
 private int y = 0;

 // Constructor
 public Form1() {
  this.SuspendLayout();

  // Load UFO bitmap from the same path as the running application.
  this.imgUFO = new Bitmap(@"ufo.gif");

  // Setting up the Timer control.
  this.timer1 = new Timer();
  this.timer1.Interval = 50;
  this.timer1.Enabled = true;
  this.timer1.Tick += new EventHandler(timer1_Tick);

  // Setting up Form control.
  this.Text = "UFO Program";
  this.BackColor = Color.White;
  this.Paint += new PaintEventHandler(Form1_Paint);

  this.ResumeLayout(false);
 }

 private void timer1_Tick(object sender, EventArgs e) {
  // Update image position
  newPositions();

  // Refresh screen
  Invalidate();
 }

 private void Form1_Paint(object sender, PaintEventArgs e) {
  // Draw bitmap on the form.
  Graphics g = e.Graphics;
  g.DrawImage(this.imgUFO, this.x, this.y); 
 }

 private void newPositions() {
  // Move Right & Down for 1 step.
  this.x += 1;
  this.y += 1;
 }

 // The main entry point for the application.
 public static void Main() {
  Application.Run(new Form1());
 }
}
The program seems to work perfectly. But when notice carefully, the image draw on screen will look a bit flickering. The image flashes on and off most unpleasantly. This is due to the way that the screen is drawn. When the Paint Event redraws the form, the screen is drawn with white, and then the image is drawn separately. This process is very visible and looks terrible.

This problem can be solved by a technique known as double buffering. The idea is that the program doesn't draw on the frame the user can see. Instead, it draws on a buffer in memory. Then, when the picture is complete, it is copied into the visible screen. The copy process is very quick, and the flicker is removed.

The following codes create another bitmap area, draw onto that, and then copy the new bitmap into position:
using System;
using System.Windows.Forms;
using System.Drawing;

public class Form1: Form {
 private Timer timer1;
 private Image imgUFO;

 private int x = 0;
 private int y = 0;

 // Constructor
 public Form1() {
  this.SuspendLayout();

  // Load UFO bitmap from the same path as the running application.
  this.imgUFO = new Bitmap(@"ufo.gif");

  // Setting up the Timer control.
  this.timer1 = new Timer();
  this.timer1.Interval = 50;
  this.timer1.Enabled = true;
  this.timer1.Tick += new EventHandler(timer1_Tick);

  // Setting up Form control.
  this.Text = "UFO Program";
  this.BackColor = Color.White;
  this.Paint += new PaintEventHandler(Form1_Paint);

  this.ResumeLayout(false);
 }

 private void timer1_Tick(object sender, EventArgs e) {
  // Update image position
  newPositions();

  // Refresh screen
  Invalidate();
 }

 // This is a very important method overriding for double buffering.
 // By overriding this method, it will disable the background painting.
 protected override void OnPaintBackground(PaintEventArgs e) {
 }

 private void Form1_Paint(object sender, PaintEventArgs e) {
  // Temporary bitmap to hold the backbuffer image
  Bitmap backBuffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);

  using (Graphics g = Graphics.FromImage(backBuffer)) {
   g.Clear(Color.White);
   g.DrawImage(this.imgUFO, this.x, this.y);
  }

  e.Graphics.DrawImage(backBuffer, 0, 0);
 }

 private void newPositions() {
  // Move Right & Down for 1 step.
  this.x += 1;
  this.y += 1;
 }

 // The main entry point for the application.
 public static void Main() {
  Application.Run(new Form1());
 }
}

No comments: