Monday, November 11, 2013

[ MFC ] - Creating custom Mapping Mode in Visual C++ using MM_ISOTROPIC

1. Introduction


In the previous MFC Article, we examined how the Mapping Modes works while performing the drawing operation. In this example, we will see how do we set the custom mapping modes. Out goal is below:
1) Set mapping mode so that the horizontal logical unit is 1 cm. Simply, 1 Unit = 1 c.m in X axis
2) Also, 1 Unit = 1 c.m in Axis.
3) Positive X is towards left
4) Positive Y is going Upwards
5) The Drawing origin should be in screen center


2. The Custom Mapping Mode


To achieve the above we need to use the Custom Mapping mode. From the previous article, you may now know that the mapping modes MM_ISOTROPIC, MM_ANISOTROPIC are custom mapping modes. In MM_ISOTROPIC mode, both X and Y-axis represents the same unit of measure. That is if X-axis represents 1 logical unit as 1 m.m then Y-axis also represent 1 logical unit as 1 m.m. In our example, we are going to use the MM_ANISOTROPIC mode.


3. About the Example


Have a look at the below depiction:




We are going to draw X Axis and Y Axis and then draw an ellipse. The ellipse will have 8 cm major axis and 4 cm minor axis when measured after having the hardcopy. So to achieve this drawing, we should set a mapping mode that will see one unit as 1 centimeter. Also, note that the Origin should be at the center of the screen.

OK. Let us walk through how do we create this example.


4. Coding the Example


1) Create a Single document interface application (SDI) and in the OnPaint Handler, we are going to perform the drawing specified above. First, we need to set the mapping mode as ISOTROPIC and ANISOTROPIC. In this example I specified the ISOTROPIC mapping mode as both X and Y-axis has the same unit of measure that is 1 logical drawing unit is 1 centimeter. Below is code that set the mapping mode:

CPaintDC dc(this); // device context for painting
dc.SetMapMode(MM_ISOTROPIC);


2) Once the mapping mode is set the screen resolution in terms of mm is returned using the GetDeviceCaps function by passing the HORZSIZE, VERTSIZE. The size returned in mm is based on the current resolution. The screen size in mm is stored in the variables width_in_mm and height_in_mm.

//Sample 01: Returns the Dimension in MM
int width_in_mm = dc.GetDeviceCaps(HORZSIZE);
int height_in_mm = dc.GetDeviceCaps(VERTSIZE);

The picture shown below tells that the 1 pixel is represented as 0.3525 mm approximately.

3) Once we have Screen resolution in mm, we need to get the screen resolution in pixels. We use the same GetDeviceCaps function to get the screen resolution in pixels. Below is the code:

//Sample 02: Return the dimension in pixels
int width_in_pixels = dc.GetDeviceCaps(HORZRES);
int height_in_pixels = dc.GetDeviceCaps(VERTRES);

4) Have a look at the below picture:
From the above picture you can see how the Window and "Viewport Extends" works together. Imagine that the gray lines 1 unit and 1 unit is one pixel. The Red line shows how the window is divided by logical measure, say, for example, imagine like this, the screen dimension of 7x5 mm is divided as 4 horizontal extends and 3 vertical extends. This same technique can be used in the below-given code.

//Sample 03: How the screen divided in terms of Logical Units.
//For Example, if horz size returned is 500 mm,
//the window is divided into 50 logical extents.
dc.SetWindowExt(width_in_mm/10, (-1 * height_in_mm)/10); // 10 MM = 1 Unit or 1 CM = 1 Unit

//Sample 04: How the screen divided in terms of pixels
dc.SetViewportExt(width_in_pixels, height_in_pixels);

In the above code we specified the following unit of measures:
Device Coordinate system’s unit of measure is done by the SetViewportExt function call. Let us say the resolution is 800x600, so the screen is divided as "800 extents" horizontally and "600 extents" vertically. That means each unit represents pixels; for example, if I say 10 unit in device coordinate then it is 10 pixels. For same 800x600 resolution, the screen size measured is 282x212 mm. Now, using the SetWindowExt function call, we divided the screen width as 28 extents and screen height as 21 extents. This way each unit measures 1 cm in the logical unit (Put down your calculator and I agree there is a slight precision loss).

OK. What is the importance of specifying the extents? Let us start with a detailed explanation that may help you understand the coordinate transformation in terms of Pixels versus Logical units. From the above picture, the Device Coordinate System uses the gray lines and the Logical Co-ordinate system uses Red lines as a unit of measures. MFC Framework and Win32 API uses both the coordinate systems. For example, the WM_LBUTTONUP uses the device coordinate system to specify the mouse cursor location in terms of (x, y) pixels. Whereas, the drawing functions exposed by the CDC (Device Context) expects the Logical coordinate system. Now, think how a user can draw a 10 cm line by using the mouse.

In this case, a coordinate conversion is required. Your code may go like this:
1) The mouse events capture the Line start and end
2) Start point and end points are converted to Logical unit from Device unit
3) These converted points (Dimensions) passed to the Line Drawing functions

Note that at the first step itself showing the line dimension (In logical unit) helps the user to draw 10 cm line. Now you may have an idea of coordinate space conversion. Two kinds of conversion are possible and they are:
a) Device Space to Logical Space
b) Logical Space to Device Space

In my previous article I specified that except MM_ISOTROPIC and MM_ANISOTROPIC, all co-ordinate have the valid mappings. These two mapping modes are custom mapping modes and that means you decide the mapping between Logical and Physical unit. I am moving to the next coding part and at the end of the article, we will derive the coordinate transformation formula.


5) Next, we shifted the viewport origin towards the center of the screen. Below is the code for it.

//Sample 05: Set Viewport Orgin to the Screen Center
CRect rct;
GetClientRect(&rct);
dc.SetViewportOrg(rct.right/2, rct.bottom/2);


6) Once the drawing origin is shifted to the screen center, the x-axis and y-axis are drawn using the MoveTo, LineTo functions. Next using the device context an ellipse is drawn to the screen. Now all the units specified here are in Centimeter.

//Sample 06: Draw the Axis
dc.MoveTo(-10,0);
dc.LineTo(10,0);
dc.MoveTo(0,-8 );
dc.LineTo(0,8);

//Sample 07: Draw the Ellispse
dc.Ellipse(0, 4, 8, 0);


 Source Code : Download



Like this site? Tell it to your Firend :)