You are on page 1of 13

Graphics Programming in C#

Posted by Anand Narayanswamy in Articles | C# Language on December 26, 2001 Tags: .net, Brush objects, C#, ColorDialog Box, Graphics, Pen objects The new improved version of GDI is called GDI+. The .NET framework provides a rich set of classes, methods and events for developing applications with graphical capabilities. 3

inShare

93455

Reader Level: Download Files:

GDIInCSCode.zip

Like Java, C# provides us with a rich set of classes, methods and events for developing applications with graphical capabilities. Since there is not much theory involved, we can straight away jump to an interesting example (Listing - 1), which prints "Welcome to C#" on a form. Relevant explanations are shown as comments: Listing - 1 using System; using System.Windows.Forms; using System.Drawing; public class Hello:Form { public Hello() { this.Paint += new PaintEventHandler(f1_paint); } private void f1_paint(object sender,PaintEventArgs e) { Graphics g = e.Graphics; g.DrawString("Hello C#",new Font("Verdana",20), new SolidBrush(Color.Tomato),40,40); g.DrawRectangle(new Pen(Color.Pink,3),20,20,150,100); } public static void Main() { Application.Run(new Hello()); } // End of class } The method DrawString() takes four arguments as shown in the above example. Every method in the Graphics class have to be accessed by creating an object of that class. You can easily update the above program to render other graphical shapes like Rectangle, Ellipse etc. All you have to do is to apply the relevant methods appropriately. Changing the Unit of Measurement As you may know the default Graphics unit is Pixel. By applying the PageUnit property, you can change the unit of measurement to Inch, Millimeter etc as shown below: Graphics g = e.Graphics; g.PageUnit = GraphicsUnit.Inch Working with ColorDialog Box If you have ever done Visual Basic Programming, you should be aware of predefined dialog boxes like ColorDialog, FontDialog etc. In C#, you or your user can choose a color by applying the ColorDialog class appropriately. Firstly you have to create an object of

ColorDialog class as shown below: ColorDialog cd = new ColorDialog(); Using the above object call ShowDialog() method to display the color dialog box. Finally invoke the Color property and apply it appropriately as shown in Listing - 2: Listing - 2 using System; using System.Drawing; using System.Windows.Forms; public class Clr:Form { Button b1 = new Button(); TextBox tb = new TextBox(); ColorDialog clg = new ColorDialog(); public Clr() { b1.Click += new EventHandler(b1_click); b1.Text = "OK"; tb.Location = new Point(50,50); this.Controls.Add(b1); this.Controls.Add(tb); } public void b1_click(object sender, EventArgs e) { clg.ShowDialog(); tb.BackColor = clg.Color; } public static void Main() { Application.Run(new Clr()); } // End of class } Here the background color of the form will change as you select a color from the dialog box. Working with FontDialog Box You can easily create a Font selection dialog box by following the same steps as in the previous listing and by affecting some minor changes. Listing - 3 shown below examines the usage of this useful tool: Listing - 3 using System; using System.Drawing; using System.Windows.Forms; public class Fonts:Form {

Button b1 = new Button(); TextBox tb = new TextBox(); FontDialog flg = new FontDialog(); public Fonts() { b1.Click += new EventHandler(b1_click); b1.Text = "OK"; tb.Location = new Point(50,50); this.Controls.Add(b1); this.Controls.Add(tb); } public void b1_click(object sender, EventArgs e) { clg.ShowDialog(); tb.FontName = flg.Font; } public static void Main() { Application.Run(new Fonts()); } // End of class } Using System.Drawing.Drawing2D Namespace The System.Drawing.Drawing2D namespace provides advanced techniques for manipulating Pen and Brush objects. For example, you can change the look and feel of lines by applying the values of DashStyle Enumerator (like Dash, DashDot etc). Also by making use of various Brush classes like SolidBrush, HatchStyleBrush etc you can modify the appearance of filled shapes. For instance, a rectangle can be filled with Vertical and Horizontal lines. As already examined a normal shape (Using DrawXXX() method) accepts a Pen class argument besides the floating point values while a filled shape (Using FillXXX() methods) accepts a Brush class argument. Working with Pen objects Listing - 4 given below examines the usage of various DrawXXX() methods by making use of some properties in the System.Drawing.Drawing2D namespace: Listing - 4 using System; using System.Windows.Forms; using System.Drawing; using System.Drawing.Drawing2D; public class Drawgra:Form { public Drawgra() { this.Text = "Illustrating DrawXXX() methods"; this.Size = new Size(450,400); this.Paint += new PaintEventHandler(Draw_Graphics);

} public void Draw_Graphics(object sender,PaintEventArgs e) { Graphics g = e.Graphics; Pen penline = new Pen(Color.Red,5); Pen penellipse = new Pen(Color.Blue,5); Pen penpie = new Pen(Color.Tomato,3); Pen penpolygon = new Pen(Color.Maroon,4); /*DashStyle Enumeration values are Dash, DashDot,DashDotDot,Dot,Solid etc*/ penline.DashStyle = DashStyle.Dash; g.DrawLine(penline,50,50,100,200); //Draws an Ellipse penellipse.DashStyle = DashStyle.DashDotDot; g.DrawEllipse(penellipse,15,15,50,50); //Draws a Pie penpie.DashStyle = DashStyle.Dot; g.DrawPie(penpie,90,80,140,40,120,100); //Draws a Polygon g.DrawPolygon(penpolygon,new Point[]{ new Point(30,140), new Point(270,250), new Point(110,240), new Point(200,170), new Point(70,350), new Point(50,200)}); } public static void Main() { Application.Run(new Drawgra()); } // End of class } Working with Brush objects Brush class is used to fill the shapes with a given color, pattern or Image. There are four types of Brushes like SolidBrush (Default Brush), HatchStyleBrush, GradientBrush and TexturedBrush. The listings given below show the usage of each of these brushes by applying them in a FillXXX() method: Using SolidBrush Listing - 5 using System; using System.Windows.Forms; using System.Drawing; public class Solidbru:Form { public Solidbru() { this.Text = "Using Solid Brushes";

this.Paint += new PaintEventHandler(Fill_Graph); } public void Fill_Graph(object sender,PaintEventArgs e) { Graphics g = e.Graphics; //Creates a SolidBrush and fills the rectangle SolidBrush sb = new SolidBrush(Color.Pink); g.FillRectangle(sb,50,50,150,150); } public static void Main() { Application.Run(new Solidbru()); } // End of class } Using HatchBrush Listing - 6 using System; using System.Windows.Forms; using System.Drawing; using System.Drawing.Drawing2D; public class Hatchbru:Form { public Hatchbru() { this.Text = "Using Solid Brushes"; this.Paint += new PaintEventHandler(Fill_Graph); } public void Fill_Graph(object sender,PaintEventArgs e) { Graphics g = e.Graphics; //Creates a Hatch Style,Brush and fills the rectangle /*Various HatchStyle values are DiagonalCross,ForwardDiagonal, Horizontal, Vertical, Solid etc. */ HatchStyle hs = HatchStyle.Cross; HatchBrush sb = new HatchBrush(hs,Color.Blue,Color.Red); g.FillRectangle(sb,50,50,150,150); } public static void Main() { Application.Run(new Hatchbru()); } // End of class } Using GradientBrush Listing - 7 using System;

using System.Windows.Forms; using System.Drawing; using System.Drawing.Drawing2D; public class Texturedbru:Form { Brush bgbrush; public Texturedbru() { Image bgimage = new Bitmap("dotnet.gif"); bgbrush = new TextureBrush(bgimage); this.Paint+=new PaintEventHandler(Text_bru); } public void Text_bru(object sender,PaintEventArgs e) { Graphics g = e.Graphics; g.FillEllipse(bgbrush,50,50,500,300); } public static void Main() { Application.Run(new Texturedbru()); } // End of class } Working with Images You can easily insert images by following the procedure given below 1) Create an object of Bitmap class as shown below: Image img = new Bitmap("image1.bmp"); 2) Apply the above object in DrawImage() method g.DrawImage(img,20,20,100,90); Conclusion In this article, I've examined two core namespaces System.Drawing and System.Drawing.Drawing2D by showing the usage of various methods and properties with the help of numerous listings.

Using the Code


The downloadable code provides a VS2010 C# 4.0 console application including the Cudafy libraries. For more information on the Cudafy.NET SDK, please visit the website. The application does some basic operations on the GPU. A single reference is added to the Cudafy.NET.dll. For translation to CUDA C, it relies on the excellent ILSpy.NET decompiler from SharpDevelop and Mono.Cecil from JB Evian. The libraries ICSharpCode.Decompiler.dll,ICSharpCode.NRefactory.dll, IlSpy.dll and Mono.Cecil.dll cont

ain this functionality. Cudafy.NET currently relies on very slightly modified versions of the ILSpy 1.0.0.822 libraries. There are various namespaces to contend with. In Progam.cs, we use the following:
Collapse | Copy Code

using Cudafy; using Cudafy.Host; using Cudafy.Translator;

To specify which functions you wish to run on the GPU, you apply the Cudafy attribute. The simplest possible function you can run on your GPU is illustrated by the method kernel and a slightly more complex and useful one is also shown:
Collapse | Copy Code

[Cudafy] public static void kernel() { } [Cudafy] public static void add(int a, int b, int[] c) { c[0] = a + b; }

These methods can be converted into GPU code from within the same application by use of CudafyTranslator. This is a wrapper around the ILSpy derived CUDA language and simply converts .NET code into CUDA C and encapsulates this along with reflection information into a CudafyModule. The CudafyModule has a Compilemethod that wraps the NVIDIA NVCC compiler. The output of the NVCC compilation is what NVIDIA call PTX. This is a form of intermediate language for the GPU and allows multiple generations of GPU to work with the same applications. This is also stored in the CudafyModule. A CudafyModule can also be serialized and deserialized to/from XML. The default extension of such files is *.cdfy. Such XML files are useful since they enable us to speed things up and avoid unnecessary compilation on future runs. The CudafyModule has methods for checking the checksum - essentially we test to see if the .NET code has changed since the cached XML file was created. If not, then we can safely assume the deserialized CudafyModule instance and the .NET code are in sync. In this example project, we use the 'smart' Cudafy() method on the CudafyTranslator which does caching and type inference automagically. It does the equivalent of the following steps:
Collapse | Copy Code

CudafyModule km = CudafyModule.TryDeserialize(typeof(Program).Name); if (km == null || !km.TryVerifyChecksums())

{ km = CudafyTranslator.Cudafy(typeof(Program)); km.Serialize(); }

In the first line, we attempt to deserialize from an XML file called Program.cdfy. The extension is added automatically if it is not explicitly specified. If the file does not exist or fails for some reason, then null is returned. In contrast, theDeserialize method throws an exception if it fails. If null is not returned, then we verify the checksums usingTryVerifyChecksums(). This method returns false if the file was created using an assembly with a different checksum. If both this and the prior check fail, then we cudafy again. This time, we explicitly pass the type we wish to cudafy. Multiple types can be specified here. Finally, we serialize this for future use. Now we have a valid module, we can proceed. To load the module, we need first to get a handle to the desired GPU. This is done as follows. GetDevice() is overloaded and can include a device id for specifying which GPU in systems with multiple, and it returns an abstract GPGPU instance. The eGPUType enumerator can be Cuda or Emulator. Loading the module is done fairly obviously in the next line. CudaGPU and EmulatedGPU derive from GPGPU.
Collapse | Copy Code

_gpu = CudafyHost.GetDevice(eGPUType.Cuda); _gpu.LoadModule(km);

To run the method kernel we need to use the Launch method on the GPU instance (Launch is a fancy GPU way of saying start or in .NET parlance Invoke). There are many overloaded versions of this method. The most straightforward and cleanest to use are those that take advantage of .NET 4.0's dynamic language run-time (DLR).
Collapse | Copy Code

_gpu.Launch().kernel();

Launch takes in this case zero arguments which means one thread is started on the GPU and it runs the kernelmethod which also takes zero arguments. An alternative non-dynamic way of launching is shown below. Advantages are that is faster first time round. The DLR can add up to 50ms doing its wizardry. The two arguments of value 1 refer to the number of threads (basically 1 * 1), but more on this later.
Collapse | Copy Code

_gpu.Launch(1, 1, "kernel");

There are a number of other examples provided including the compulsory 'Hello, world' (written in Unicode on a GPU). Of greater interest is the Add vectors code since working on large data sets is the bread and butter of GPGPU. Our method addVector is defined as:
Collapse | Copy Code

[Cudafy] public static void addVector(GThread thread, int[] a, int[] b, int[] c) { // Get the id of the thread. addVector is called N times in parallel, so we need // to know which one we are dealing with. int tid = thread.blockIdx.x; // To prevent reading beyond the end of the array we check that // the id is less than Length if (tid < a.Length) c[tid] = a[tid] + b[tid]; }

Parameters a and b are the input vectors and c is the resultant vector. GThread is the surprise component. Since the GPU will launch many threads of addVector in parallel we need to be able to identify within the method which thread we're dealing with. This is achieved through CUDA built-in variables which in Cudafy are accessible viaGThread. If you have an array a of length N on the host and you want to process it on the GPU, then you need to transfer the data there. We use the CopyToDevice method of the GPU instance.
Collapse | Copy Code

int[] a = new int[N]; int[] dev_a = _gpu.CopyToDevice(a);

What is interesting here is the return value of CopyToDevice. It looks like an array of integers. However if you were to hover your mouse over it in the debugger, you'd see it has length of zero, not N. What has been returned is a pointer to the data on the GPU. It is only valid in GPU code (the methods you marked with the Cudafy attribute). The GPU instance stores these pointers. Transferring data to the GPU is all very well but we also may need memory on the GPU for result or intermediate data. For this, we use the Allocate method. Below is the code to allocate Nintegers on the GPU.
Collapse | Copy Code

int[] dev_c = _gpu.Allocate<int />(N);

Launching the addVector method is more complex and requires arguments to specify how many threads, as well as arguments for the target method itself.
Collapse | Copy Code

_gpu.Launch(N, 1).addVector(dev_a, dev_b, dev_c);

Threads are grouped in Blocks. Blocks are grouped in a Grid. Here we launch N Blocks where each block contains 1 thread. Note addVector contains a GThread arg - there is no need to pass this as an argument. As stated earlier,GThread is the Cudafy equivalent of the built-in CUDA variables and we use it to identify thread id. The diagram below shows an example with a grid containing a 2D array of blocks where each block contains a 2D array of threads.

Another interesting point of note is the FreeAll method on the GPU instance. Memory on a GPU is typically more limited than that of the host so use it wisely. You need to free memory explicitly, however if the GPU instance goes out of scope, then its destructor will clear up GPU memory. The final example is somewhat more complex and illustrates the use of structures and multidimensional arrays. In file Struct.cs ComplexFloat is defined:
Collapse | Copy Code

[Cudafy] public struct ComplexFloat { public ComplexFloat(float r, float i) { Real = r; Imag = i; } public float Real; public float Imag; public ComplexFloat Add(ComplexFloat c) {

return new ComplexFloat(Real + c.Real, Imag + c.Imag); } }

The complete structure will be translated. It is not necessary to put attributes on the members. We can freely make use of this structure in both the host and GPU code. In this case, we initialize the 3D array of these on the host and then transfer to the GPU. On the GPU, we do the following:
Collapse | Copy Code

[Cudafy] public static void struct3D(GThread thread, ComplexFloat[,,] result) { int x = thread.blockIdx.x; int y = thread.blockIdx.y; int z = 0; while (z < result.GetLength(2)) { result[x, y, z] = result[x, y, z].Add(result[x, y, z]); z++; } }

The threads are launched this time in a 2D grid so each thread is identified by an x and y component (we launch a grid of threads equal to the size of the x and y dimensions of the array). Each thread then handles all the elements in the z dimension. The length of the dimension is obtained by the GetLength method of the .NET array. The calculation adds each element to itself.

License
The Cudafy.NET SDK is available as a dual license software library. The LGPL version is suitable for the development of proprietary or open source applications if you can comply with the terms and conditions contained in the GNU LGPL version 2.1. Thanks.

Final Words
I hope you've been encouraged to look further into the world of GPGPU programming. Most PCs already have a massively powerful co-processor in your PC that can compliment the CPU and provide potentially huge performance gains. In fact, the gains are sometimes so large that they no longer make any sense to the uninitiated. I know of developers who exaggerate the improvement downwards... The trouble has been in making use of the power. Through the efforts of NVIDIA with CUDA, this is getting easier. Cudafy.NET takes this further and allows.NET developers to access this world too.

There is much more to all this than what is covered in this short article. Please obtain a copy of a good book on the subject or look up the tutorials on NVIDIA's website. The background knowledge will let you understand Cudafy better and allow the building of more complex algorithms. Also do visit the Cudafy website to download the SDK. The SDK includes two large example projects featuring amongst others ray tracing, ripple effects and fractals.

History
o o o o

27th May, 2011 First submission 16th June, 2011 Updated to use Cudafy.NET V1.2 Added some photos 12th July, 2011 Updated to use Cudafy.NET V1.4

You might also like