Tuesday, February 7, 2012

Drawing pixel directly in windows form using c# and implementing DDA Algorithm to Draw a straight line.

In windows form application pixel operation method is different then C++ builder.There may be many techniques to Draw single pixel and one of them I have presented here.Usually in windows form, code is generated by IDE using drags and drops controls which is specifies by the System.Drawing namespace.We can also draw basic shapes and curve by creating graphics objects and pen objects to paint a various shapes. We will create graphics object but not pen object because we won’t draw a basics shapes only draw pixels.



To do this WinForms gives us the PictureBox control, into which we can easily load images of different formats, have them scaled for us, etc.
But, unfortunately, PictureBox gives us no obvious way to paint it ourself via some SetPixel method.In this article i will thus show you how to implement your own SetPixel method. As you will know, when you directly execute a long-running function, the user interface will be unresponsive (you won't be able to move the window, or click anything on it) until the function has finished running. When painting something, your paint method might be very long running (for example if you render combination of many complex objects ), so we will do our painting in a background thread.In this program background thread is not needed but it is important if our rendering object becomes complex. This will only add a few lines of code, but keep the application running smooth.

In this article, i assume that we have a Windows Forms project, and that the form contains a PictureBox named pictureBox1, and a Button (named button1),also set Dock properties of PictureBox fill.
We begin with some data field declarations inside our main form:
public Bitmap bmp;
public Bitmap pixel;
public Graphics g;
Thread t;
delegate void PixelFunc(int x, int y, Color c);
and an Init function to clear and initialize those fields:

private void Init ()
        {
            if (g!=null) g.Dispose();
            pictureBox1.Image = null;
            if (bmp != null) bmp.Dispose();
            bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);
            if (pixel != null) pixel.Dispose();
            pixel = new Bitmap(1, 1);
            pictureBox1.Image = bmp;
            g = pictureBox1.CreateGraphics();
        }
Bitmap is a class that comes with its own SetPixel method, which is what we will use. For this variant, we need two Bitmaps: the first, bmp, we use as the internal bitmap of our PictureBox. We create it with the same size as the picture box, and assign it to its Image property. The second Bitmap, pixel, is just a 1-pixel bitmap, which we will use to draw on the visible surface, which is represented by the Graphics object g, which we acquire from our PictureBox.
Now that we have our neccessary objects, we can write our SetPixel method:

private void SetPixel(int x, int y, Color c)
        {
            lock (g)
            {
                pixel.SetPixel(0, 0, Color.Blue);
                g.DrawImageUnscaled(pixel, x, y);
                bmp.SetPixel(x, y, Color.Blue);
            }
        }
First we lock g. This is very important if we want to make our program multithreaded, because we will be invoking SetPixel asyncronously, which means the calling thread will not wait until SetPixel is finished before calling it again. To make sure that one SetPixel is only called once the previous call has completed, and no pixels are skipped, we have to lock. You could lock pixel, bmp, or some other object to achieve this, but you need to be aware that in some situations windows will use or query the PictureBox' Graphics object, for example when you drag the window to the edge of the screen, so that PictureBox leaves the screen. Such simultaneous accesses can lead to an exception, which we can easily prevent by locking around g.
Next, we set the single pixel of our Bitmap called 'pixel' to the color we supplied, and then we can draw this pixel to the visible surface of the PictureBox with a call to Graphics.DrawImageUnscaled. Finally, we draw our pixel to bmp. bmp is the 'data storage' of our PictureBox. When PictureBox needs to be refreshed, for example after another window was moved over it, it uses bmp to paint itself. This means that if we omit the call to bmp.SetPixel, our PictureBox would be empty after minimizing and restoring our application window, or after dragging another window over it.
This is  Render function, which will draw straight line  using our SetPixel method and DDA algorithm.


private void Render()
        {
            
            int xInitial = 10, yInitial = 20, xFinal = 150, yFinal = 200;
 
            int dx = xFinal - xInitial, dy = yFinal - yInitial, steps, k, xf, yf;
 
            float xIncrement, yIncrement, x = xInitial, y = yInitial;
 
            if (Math.Abs(dx) > Math.Abs(dy)) steps = Math.Abs(dx);
 
            else steps = Math.Abs(dy);
            xIncrement = dx / (float)steps;
            yIncrement = dy / (float)steps;
            PixelFunc func = new PixelFunc(SetPixel);
             for (k = 0; k < steps; k++)
                {
                    x += xIncrement;
                    xf = (int)x;
                    y += yIncrement;
                    yf = (int)y;
                    try
                    {
                        pictureBox1.Invoke(func, xf, yf, Color.Blue);
                    }
                    catch (InvalidOperationException)
                    {
                        return;
                    }
                }
        }

Demerit of this method is slow rendering.

Here is the complete source code which is compiled in visual studio 2010.

Form1.cs


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;
 
namespace pixelOperation
{
   
    public partial class Form1 : Form
    {
        public Bitmap bmp;
        public Bitmap pixel;
        public Graphics g;
        Thread t;
        delegate void PixelFunc(int x, int y, Color c);
 
        private void Init()
        {
            if (g != null) g.Dispose();
            pictureBox1.Image = null;
            if (bmp != null) bmp.Dispose();
            bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);
            if (pixel != null) pixel.Dispose();
            pixel = new Bitmap(1,1);
            pictureBox1.Image = bmp;
            g = pictureBox1.CreateGraphics();
        }
 
        private void SetPixel(int x, int y, Color c)
        {
            lock (g)
            {
                pixel.SetPixel(0, 0, Color.Blue);
                g.DrawImageUnscaled(pixel, x, y);
                bmp.SetPixel(x, y, Color.Blue);
            }
        }
 
        private void Render()
        {
            
            int xInitial = 10, yInitial = 20, xFinal = 150, yFinal = 200;
 
            int dx = xFinal - xInitial, dy = yFinal - yInitial, steps, k, xf, yf;
 
            float xIncrement, yIncrement, x = xInitial, y = yInitial;
 
            if (Math.Abs(dx) > Math.Abs(dy)) steps = Math.Abs(dx);
 
            else steps = Math.Abs(dy);
            xIncrement = dx / (float)steps;
            yIncrement = dy / (float)steps;
            PixelFunc func = new PixelFunc(SetPixel);
             for (k = 0; k < steps; k++)
                {
                    x += xIncrement;
                    xf = (int)x;
                    y += yIncrement;
                    yf = (int)y;
                    try
                    {
                        pictureBox1.Invoke(func, xf, yf, Color.Blue);
                    }
                    catch (InvalidOperationException)
                    {
                        return;
                    }
                }
        }
 
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            Init();
            if (t != null && t.IsAlive) t.Abort();
            t = new Thread(new ThreadStart(Render));
            t.Start();
        }
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            g.Dispose();
            bmp.Dispose();
            pixel.Dispose();
        }
    }
}

Program.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
 
namespace pixelOperation
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Form1.Designer.cs


namespace pixelOperation
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
 
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
 
        #region Windows Form Designer generated code
 
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.pictureBox1 = new System.Windows.Forms.PictureBox();
            this.button1 = new System.Windows.Forms.Button();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
            this.SuspendLayout();
            // 
            // pictureBox1
            // 
            this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.pictureBox1.Location = new System.Drawing.Point(0, 0);
            this.pictureBox1.Name = "pictureBox1";
            this.pictureBox1.Size = new System.Drawing.Size(284, 262);
            this.pictureBox1.TabIndex = 0;
            this.pictureBox1.TabStop = false;
            // 
            // button1
            // 
            this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
            this.button1.Location = new System.Drawing.Point(209, 239);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 1;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 262);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.pictureBox1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
            this.ResumeLayout(false);
 
        }
 
        #endregion
 
        private System.Windows.Forms.PictureBox pictureBox1;
        private System.Windows.Forms.Button button1;
    }
}
 

No comments:

Post a Comment