Monday, August 19, 2013

[ C# ] - Understanding GDI+ with Pen and Brushes of Solid, Hatch, Gradient types

1. Introduction

In this article we will have a look at how do we perform the drawing operation using the GDI plus functions available with DotNet. GDI stands for "Graphical Device Interface" and using that you can create rich drawing applications, show useful information on the form as a drawing say for example showing a pie chart of sales on past 5 years.

In this article I will show how do you perform the drawing using Pen and Brushes.



2. What is Pen and Brush in C#?

A "Pen" is a graphics object, which can be used to perform line drawing. A pen has the properties like the color of the Pen and thickness of the pen. A brush is also a graphics object, which can be used to paint the region. Suppose if you want to fill the area you can use the Brush. Think about Painting a door, a wooden plate etc.

In this article I will show how to use the "Plain Solid Brush", "Gradient Brush" and "Hatch brush".



3. About the Finished Example

Below is the Screen shot of the finished application:



The Black screen is the Panel control and it is used in this example as a drawing canvas. When the Fill Brush is not enabled whatever you draw is drawn using only the Pen. That means you see the drawing outline that is Rectangle outline in our example. You can switch between Solid Brush and Advanced brush using the Advanced Brush checkbox. When the checkbox is enabled, you see the advanced brush options that can be toggles between Hatch and Gradient using the Toggle switch shown as seven in the screen shot above.



4. Drawing the Rectangle Object

To draw a rectangle in GDI+ we need its size and position. So in the first part of the development, we placed the controls to collect the Rectangle’s position and the size (Section 1 and 2 in the screenshot above). Then the rectangle is drawn in the Panel named as DrawingCanvas.

1) First the required namespace to perform the 2d drawing is placed in the using directive as shown below. Note that this required when we want to use the rich functionalities of the GDI+

//Sample 00: Required Name Spaces
using System.Drawing.Drawing2D;

2) Then a Rectangle object is created and its size and position are filled by the user-supplied values.
//Sample 01: Create the rect object.
Rectangle rect = new Rectangle();
rect.X = Int32.Parse(txtX.Text);
rect.Y = Int32.Parse(txtY.Text);
rect.Width = Int32.Parse(txtWidth.Text);
rect.Height = Int32.Parse(txtHeight.Text);

3) The black screen that you see in the Sample application screenshot is a Panel Control. The "CreateGraphics() method" will return the graphics object. You can use this method on any window objects like Panel, Form or even controls like textbox, listbox etc. In our case, we asked the Panel Control to retrieve the Graphics object from it. And this object is stored in grp of type Graphics. Once the Graphics object is ready, a Pen is created with a color of Goldenrod, which is a preset color. You can see all the present color by Typing Color dot. Below is the code:

//Sample 02: Get the Graphics from the Canvas(Panel) and Create the pen
Graphics grp = DrawingCanvas.CreateGraphics();
Pen pen = new Pen(Color.Goldenrod);

4) Before performing the Drawing (Note we write all these code in the Draw Button click handler), we clear any previously drawn by calling the Clear method of the Graphics object. Then the Rectangle is drawn with the user supplied Rectangle demotions.

//Sample 03: Clear the Graphics and Draw Rectangle
grp.Clear(Color.Black);
grp.DrawRectangle(pen, rect);

Test Run 1: The Video Explains how the sample looks when user feeds different values for the Rectangle. You can learn how Position and Size of Rectangle used in drawing a rectangle in the below video.

Video 1: Explains how Rectangle is Drawn





5. The GDI+ Pen

Using the pen object we can defines the drawing line color and its thickness. In our example application, controls marked by the number 3 are used to create a pen. In the previous section we created a pen just by specifying the color. This line of code is expanded to use the user selection on the form. Let us have look at the code for creating and using the Pen object:

1) First two variables are declared. The variable pencolor is used specify the color of the pen and this variable will be filled with the user selected color. The variable thickness is used to specify the pen thickness when we create the pen object.

//Sample 03: Create the Pen
Color pencolor;
int thickness;

2) Based on the user selected Pen color using the Radio buttons, the pencolor variable is filled by taking predefines color values like Color.Pink. The code is below:

//3.1: Decide Pen Color
if (radGolden.Checked == true)
    pencolor = Color.PaleGoldenrod;
else
    pencolor = Color.Pink;


3) Similarly, the thickness value also filled by the user selected line thickness combo box option. The pen thickness in our example has three standard thicknesses, but you can specify any float value to create a pen thickness. Below is code that stored the pen thickness:

//3.2: Decide Pen Thickness
if (rad1.Checked == true)
    thickness = 1;
else if (rad5.Checked == false)
    thickness = 3;
else
    thickness = 5;

4) Finally a pen object is created making use of the pencolor and thickness variables populated previously. This pen object can be supplied to Graphics functions so that the function uses this new pen whenever it performs a line drawing.

Pen pen = new Pen(pencolor, thickness);

Below is some of the Pen and look at the Lines that forms the Rectangle:

Video 2: Creating a Pen




6. The GDI+ Brushes

In this example I am going to show you different types of Brushes. Imagine a brush that you use to paints the walls of your house. The GDI+ brushes can also be used similar to this. We draw something using the pens that defines outer lines and then paints (Using brush) the region inside the region formed by the pens. But you are not restricted like brush can be used only with pens.

The types of brush that you are going to see here are:

  1. Simple Solid Brush
  2. Gradient Brush
  3. Patterned Brush

The Simple Solid brush fills the color in plain solid color. The Gradient Brush fills color between two colors applying the linear transformation from one color to another color. The hatch brushes fills the region with a given pattern. All these types of brushes in effect are shown in the below picture:


OK. Let us see, how do we achieve it. 

1) Below is the CheckedChanged event handler for the checkbox marked as four in the first picture of the article. In this handler we decide to enable or disable the entire GroupBox that belongs brushes.

//Sample 05: Enable the Brush Group Box
private void chkBrushEnable_CheckedChanged(object sender, EventArgs e)
{
    if (chkBrushEnable.Checked  == true)
        grpBrush.Visible = true;
    else
        grpBrush.Visible = false;
}

2) During the Form load, by default only the Solid brush option will be displayed. The user should check the Advanced Brush check box to enable the Gradient/Hatch Brush options. This code is below:

//Sample 06: Disable Advanced Brush Option
private void frmRectDraw_Load(object sender, EventArgs e)
{
    grpBrushAdv.Visible = false;
}

3) When the Advanced brush is displayed, solid brush is hidden. The same way, when the solid brush is displayed the advanced brush is hidden. This is done through CheckedChanged event handler of the Advanced Brush check box. The code for that is shown below:

//Sample 07: To show or Hide Advanced brush option
private void chkAdvBursh_CheckedChanged(object sender, EventArgs e)
{
    if (chkAdvBursh.Checked == true)
    {
        grpBrushAdv.Visible = true;
        grpBrushSolid.Visible = false;
    }
    else
    {
        grpBrushAdv.Visible = false;
        grpBrushSolid.Visible = true;
    }
}

4) In the Example application screenshot, the control item marked as 7 is a Label Control. This label control is used as the toggle button which alternate between and Gradient and Hatch. When the control label shows as Gradient then the user will see controls relevant to Gradient Brush. And, the same hold true for the Hatch brush also. We specify different captions for the color based radio buttons based on the current brush mode (Gradient or Hatch). The controls specific to the hatch brush will be shown/hidden based on the current brush mode. This kind of work is done in the click event handler for the Label control:

//Sample 08: Show the controls relevent for Hatch brush or
// Gradient Brush
private void lblHatchGr_Click(object sender, EventArgs e)
{
    if (lblHatchGr.Text == "Gradient")
    {
        lblHatchGr.Text = "Hatch";
        lblHatPat.Visible = true;
        radHatTypeHorizontal.Visible = true;
        radHatTypeVerticle.Visible = true;
        radHatTypeWave.Visible = true;
        radHatTypeHorizontalBrick.Visible = true;
        HatchGB1.Text = "Forecolor";
        HatchGB2.Text = "BackColor";
    }
    else
    {
        lblHatchGr.Text = "Gradient";
        lblHatPat.Visible = false;
        radHatTypeHorizontal.Visible = false;
        radHatTypeVerticle.Visible = false;
        radHatTypeWave.Visible = false;
        radHatTypeHorizontalBrick.Visible = false;
        HatchGB1.Text = "From";
        HatchGB2.Text = "To";
    }
}

5) Below is the function GetSolidBrushColor_FromUI that gets the color for the solid brush. This function reads the user selected radio button for the solid brush color and assign that to the out parameter passed in as color. Note that the out parameter guarantees the caller that the function will definitely assign a value in the color parameter and the caller no need to initialize the value. The function is below:

//Sample 09: Get Solid Brush color from UI
private void GetSolidBrushColor_FromUI(out Color color)
{
    if (radSolBrRed.Checked == true)
        color = Color.Red;
    else if (radSolBrGreen.Checked == true)
        color = Color.Green;
    else
        color = Color.Blue;
}

6) Look at the sample application screenshot labeled at 6, the GroupBox names “From” and “To” will be changed as ForeColor and BackColor when the Brush mode changes from Gradient to Hatch. The function shown below will return the color values from the same Radio buttons that comes under these two set of radio groups which changes their name based on the brush mode:

//Sample 10: Get Color1 and Color2 from UI
private void Get_Col1_Col2(ref Color color1, ref Color color2)
{
    if (radhatCol1Blue.Checked == true)
        color1 = Color.Blue;
    else
        color1 = Color.Yellow;

    if (radHatCol2Green.Checked == true)
        color2 = Color.Green;
    else
        color2 = Color.Yellow;
}

7) The Inflate_Rect function written below will diminish the rectangular dimensions based on the current pen thickness. Note that the function takes the parameter as the reference type so the caller will expect the changes in the passed-in rectangular dimensions. So for we had looked at the some the helper functions written for the sample. Let us go the Hatch brush and filling techniques.

//Sample 11: Reduce rectangular dimension
private void Inflate_Rect(ref Rectangle rect)
{
    int inflate_to;
    int thickness;

    if (rad1.Checked == true)
        thickness = 1;
    else if (rad5.Checked == false)
        thickness = 3;
    else
        thickness = 5;

    inflate_to = thickness;

    rect.X = rect.X + inflate_to;
    rect.Y = rect.Y + inflate_to;
    rect.Width = rect.Width - inflate_to*2;
    rect.Height = rect.Height - inflate_to*2;
}

8) In the OnPaint handler after drawing the rectangle with the required Pen attributes, we fill the rectangle with the user selection of brush. Note that before making the Fill operation using the brush, the rectangle drawn using the Pen is inflated. You can create a solid brush by specifying the color. The graphics object supports lot of drawing functions with a Fill, say for example something like FillRectangle, FillEllipse etc. In the below code we make sure the Advanced brush option is not selected by the user and then create a SolidBrush by specifying the solid fill color. Once the brush is created it can be used with the Graphics object. In our case we used the solid brush with the FillRectangle function. Below is the code that creates and uses the Solid Brush:

Inflate_Rect(ref rect);
if (chkAdvBursh.Checked == false)
{
    //Sample 12.1: Create Solid Brush and Perform the Fill
    //A. Declaration
    Color brush_color ;
    Brush Solid_brush = null;
    //B. Create Solid Brush
    GetSolidBrushColor_FromUI(out brush_color);
    Solid_brush = new SolidBrush(brush_color);
    //C. Fill the Rectangle
    grp.FillRectangle(Solid_brush, rect);
}

9) Based on the Toggle Label, we create the Gradient Brush. To create gradient brush we need two colors as the gradient interpolates two colors in a linear way. Look at the previous picture for the gradient brush and in that the color interpolation is applied between blue and green in a horizontal way. In our example, we get both the colors required for the gradient effect by making a call to the function Get_Col1_Col2. Then we pass the from_color and to_color to the LinearGradientBrush contractor to create the Gradient_brush object. Once we have Gradient brush in hand we make a call to the function FillRectangle to get the Gradient effect. The code for this is shown below:

if (lblHatchGr.Text == "Gradient")
{
    //Sample 12.2: Create Gradient Brush and Perform the Fill
    //A. Declaration
    Color from_color= Color.White;
    Color to_color = Color.White;
    Brush Gradient_brush = null;
    //B. Create Gradient Brush
    Get_Col1_Col2(ref from_color, ref to_color);
    Gradient_brush = new LinearGradientBrush(rect, from_color, to_color,
        LinearGradientMode.Horizontal);
    //C. Fill the Rectangle
    grp.FillRectangle(Gradient_brush, rect);
}

In the above code, we asked the gradient to be applied horizontally by specifying the LinearGradientMode.Horizontal. The Gradient Modes are shown below:



10) In the else part of the Gradient Brush section we create the pattern brush to fill the rectangle. To see a pattern we should specify the Back Color and Fore Color when creating the pattern brush. After the having these colors (Note we used same function call Get_Col1_Col2), the user selected hatch pattern is stored in the style object. Refer msdn to know other hatch pattern as there are much more patterns available. All these information are passed to HatchBrush constructor to create the object Hatch_brush. Then as usual, this hatch brush is passed to the FillRectangle function of the Graphics object. The code is below which constructs the Pattern brush and uses that to fill the rectangle:

else
{
    //Sample 12.3: Create Hatch brush and perform the Fill
    //A. Declaration
    Color fore_color = Color.White;
    Color back_color = Color.White;
    Brush Hatch_brush = null;
    HatchStyle style;

    //B. Get Fore Color, Back Color. Also decide hatch style
    Get_Col1_Col2(ref fore_color, ref back_color);

    if (radHatTypeHorizontal.Checked == true)
        style = HatchStyle.Horizontal;
    else if (radHatTypeVerticle.Checked == true)
        style = HatchStyle.Vertical;
    else if (radHatTypeWave.Checked == true)
        style = HatchStyle.Wave;
    else
        style = HatchStyle.HorizontalBrick;

    //C. Create Pattern Brush
    Hatch_brush = new HatchBrush(style, fore_color, back_color);

    //D. Perform Drawing
    grp.FillRectangle(Hatch_brush, rect);
}

You can see the how the Brush works in the sample from the below shown video.


Video 3: Creating and using Brushes



Source Code: Download

Tuesday, August 13, 2013

[ MFC ] - Mapping Modes in VC++ Explained with SetWindowOrg, SetViewPortOrg

1. Introduction


MFC supports two kinds of coordinate systems. One is Device coordinate and the other one is Logical Co-Ordinate. In device coordinate, we specify everything in terms of pixels. In logical coordinate, we measure each unit in terms of Metric standard or British standard. How each unit maps to the logical measure is called the mapping. We can specify the mapping using the mapping modes.

In this article, I will walk you through the examples and explanation videos, which will help you in understanding the Mapping modes and performing the drawing using the device contexts. I will also help in understanding the ViewPort Origin and Window Origin.


2. Create SDI Application


The first step is creating the SDI Application without any document view Architecture support. Once the application is created, you can start your drawing using the OnPaint() handler. OK, Have a look at the below picture:



In the above depiction, the black screen is the desktop (Hided the Icons) and top left corner of the desktop (shown as P1) is the origin of the screen coordinate. The notepad application is displayed on top of the desktop(Screen) area. Point P2 specifies the origin of the notepad window or we can say that P2 is the origin of the window coordinate system. If there are three windows in the desktop area, then three such window coordinate exits (one for each window). The client area of the window is nothing but the working area of the window. In the case of the notepad, the actual working area is the portion of the window in which we actually type our textual content. Point P3 specifies the Origin of the client coordinate system. In all these coordinate systems we use the device coordinate meaning that the location and sizes are specified in terms of pixels.

We will talk more about the device coordinate in detail in some other article. But in the below piece of code, I will start with the client coordinate and then will move to mapping mode and logical coordinate based drawing.

CRect rct;
GetClientRect(&rct);

When you run the SDI application it looks like below (Resized):




The screen marked in blue is termed as client area of the window. The GetClientRect function will help in getting the rectangular marked as blue in the above depiction. We get four values left, top, right and bottom and this is also shown above. Look at the below video that debugs and examines value after calling GetClientRect() function.

3. Mapping Modes


The mapping modes are useful to perform the real world drawing. Say for example; let us say the software utility you are making is useful to perform an engineering drawing. When the drawing is printed out, you want to see the 10 mm line drawn in your monitor, measured exactly as 10 mm in the hard copy, taken from the printer. In this case, we need a way to represent how a single drawing unit measures to the real world measuring standards. MFC supports eight mapping modes which are shown in the below-specified picture:



For MM_TEXT mapping mode, the positive X moves from the default top left corner origin, to the right side. MM_ISOTROPIC and MM_ANISOTROPIC have a user-defined coordinate system. In MM_ISOTROPIC both x, y units are measured equally. But in the MM_ANISOTROPIC mode X and Y can have different units of measure. The above picture also shows for a given "mapping mode" how a single logical unit is measured. The MM_TEXT mapping mode is the default mapping mode used by MFC. Using the SetMapMode function of the underlying Device Context, you can pick any one of the mapping modes shown above.

4. Drawing a Line using mapping Modes


In the OnPaint handler, let us draw a line using MM_TEXT mode. Remember from the previous picture that each unit you specify to the drawing function represents a pixel. For an example, if the length of line 100 means, it is 100 pixels long as the mapping mode is MM_TEXT. Next thing we should remember is that the origin is in the upper-left corner of the window and positive Y goes down. So to see something when you draw, you should specify both x, y units in positive (Look at the picture for reference). OK, let us start drawing the line. Have a look at the below code:

//Sample 02: Draw Lines and see where the Origin is.
dc.MoveTo(0,0);
dc.LineTo(100,100);

In the above code, first, we moved the drawing pen to the Origin 0,0. Then asked to draw the line by specifying the end point as 100,100. When you run the Example you will see a line from origin 0,0 to the point at 100,100. This can be illustrated something like the below one:


For More explanation look at the video: 



What happens if I change the mapping mode from the default MM_TEXT to MM_LOMETRIC? The first thing one should be aware is that the Unit of measure changes from pixels to a millimeter. The second thing is that the Positive Y axis shifts its direction. Below is the Example that uses the MM_LOMETRIC as the mapping mode.

//Sample 03: Set New Mapping Mode
dc.SetMapMode(MM_LOMETRIC);
dc.MoveTo(0,0);
dc.LineTo(100,-100);
For More explanation look at the video: 


5.  Shifting the Origin using SetViewPortOrg


In the previous examples, we saw that how can we set the mapping modes on the device context. Also in the previous examples, the drawing origin is kept in the top left corner of the window, which is default origin. Device context has the capability of changing the origin from the default left corner using the SetViewPortOrg function call.
This function expects the new location in terms of pixel and shifts the drawing origin from top left corner to the new location. Have a look at the below code:

//Sample 01: Client Rectangle.
CRect rct;
GetClientRect(&rct);
//Sample 04.1: Shift the Origin to Screen Center
dc.SetViewportOrg(rct.right/2, rct.bottom/2);

In the OnPaint() handler, first the Origin is shifted from the top left corner to the center of the window. The rct is the CRect object, which has the Client Area dimension in device Co-Ordinate. So we specify the center of the client area in terms of a pixel to the SetWindowOrigin. Below is the picture which shows the shifted Origin:


The code in effect is shown in the above picture. The Red one is the origin before the shift and the blue one is the origin after the shift. Remember, again that we specified the new origin in terms of pixels. In the below code a blue pen is created and then a line is drawn. As there is no mapping mode set yet, the device context takes MM_TEXT as the mapping mode.

//Sample 04.2: Draw a blue Line MM_TEXT
CPen pen(PS_SOLID, 2, RGB(0,0,255));
CPen* OldPen = dc.SelectObject(&pen);
dc.MoveTo(0,0);
dc.LineTo(100,100);

After drawing the blue line, the red line is drawn in the MM_LOENGLISH mapping mode. At this stage, since you have gained a good idea of mapping modes, I am not going to explain that once again.

//Sample 04.2: Draw a Red Line using MM_LOENGLISH
dc.SetMapMode(MM_LOMETRIC);
CPen pen1(PS_SOLID, 10, RGB(255,0,0));
dc.SelectObject(&pen1);
dc.MoveTo(0,0);
dc.LineTo(100,100);
dc.SelectObject(OldPen);
Now running the entire code makes your drawing look like the one shown below:


For More explanation look at the video: 


6. Using the SetWindowOrg


At this stage, you know what the SetViewportOrg does. In a short summary again, the SetViewPortOrg sets the drawing origin by specifying the values in terms of pixels. The SetWindowOrg function set the given logical point as Lower Left Corner of the client Area Window.

Note the difference, View Port Origin is specified in terms of pixels and Window Origin is specified in terms of Logical units, which depends on the currently set mapping mode of the device context. Have a look at the below Example:

//Sample 05.1 Set the Mapping Mode and Viewport origin
dc.SetMapMode(MM_LOMETRIC);
dc.SetViewportOrg(rct.left, rct.bottom);

//Sample 05.2 Set this new location as top left of the client area
dc.SetWindowOrg(-100 , -100);

//Sample 05.3 Draw the Verticle and Horizontal line from the Origin (Viewport)
CPen pen1(PS_SOLID, 1, RGB(255,0,0));
CPen* OldPen = dc.SelectObject(&pen1);
dc.MoveTo(0,0);
dc.LineTo(0,500);
dc.MoveTo(0,0);
dc.LineTo(500,0);
dc.SelectObject(OldPen);

In this example first we set the mapping mode, as MM_LOMETRIC and I do not want to tell what it is. Then we shifted the drawing origin to lower left corner of the screen by specifying a location to be shifted as pixels. Then using the SetWindowOrg function, we specify the logical unit -100, -100 should be at the lower left corner of the window. Once this is done, we draw two lines using Red color Solid Pen. In those lines, one moves from the drawing origin(View Port Origin) to 500 unit in the positive, X direction and other one moves 500 units in positive, Y direction. Have a look at the below picture to understand this as it confuses most of MFC professionals:



For More explanation look at the video: 


Source Code : Download
Like this site? Tell it to your Firend :)