Sunday, January 6, 2013

Draw a Line with the Exact Real-world Length on Screen



Introduction

I played an educational game which teaches kids how to use a ruler to draw and measure a straight line. However, the unit length of the ruler widget on the computer does not equal to the actual length in the real world. For example, if you use the ruler widget to draw a 2 cm line, and then use a real ruler to measure it on the screen, the length is normally much greater than 2 cm. Although the game is for educational purpose and it is acceptable not to strictly follow the real length, it would be better if we can provide some ruler widget that has the same unit length with the real world ruler. Therefore, the problem we are trying to solve in this article is: Draw a  line with length of 1 centimeter on the computer screen, regardless of the screen’s physical size and  configurations, such as resolutions, orientation, etc.
  

Solution

The math part of the solution is pretty simple. Suppose the screen resolution is 1680×1024 and the computer screen’s physical size is 47cm×30cm (a typical 22-inch display), then horizontally, there are 1680/47 pixels per centimeter. Therefore, to draw a 1 cm line horizontally, we can just draw a line with 1680/47 pixels. The following is a C# code for the drawing part.

//Draw a horizontal line on the graphics g with color clr.
//The starting coordinate of the line defined in x, y.
//The length (in cm) of the line is defined in length.
//The screen's horizontal resolution (in pixels) is defined in horizoantalResolution
//The screen's physical width (in cm) is defined in screenWidth.
private void DrawHorizontalLine(Graphics g, Color clr, int x, int y, float length, int horizoantalResolution, int screenWidth)
{
   float pixelpercm = (float)horizoantalResolution / screenWidth;
   g.DrawLine(new Pen(new SolidBrush(clr), 1), x, y, x + length * pixelpercm, y);
}

It is easy to get the screen resolution in C#. The following code can get the resolution of the primary screen (a computer can connect to multiple screens).

int w = Screen.AllScreens[0].Bounds.Width;
int h = Screen.AllScreens[0].Bounds.Height;

It is a little bit hard to get the physical size of the computer screen. One technology we can use is to read the extended display identification data (EDID) of a connected display. The data structure of EDID can be found in the following Wikipedia page: http://en.wikipedia.org/wiki/Extended_display_identification_data . The display physical width and height are defined in the bytes 21 and 22 of EDID.

In Windows, the EDID are stored in the system registry under [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\DISPLAY\$DISPLAY_NAME\$DISPLAY_ID\Device Parameters], where $DISPLAY_NAME and $DISPLAY_ID are display specific strings. The following C# code example shows how to read the EDID from the registry and extract the physical size of the display from EDID. Note that there may exists more than one EDID entries if the computer has multiple displays connected. Also, we can read other display properties, such as model name, from EDID. Please refer to  http://en.wikipedia.org/wiki/Extended_display_identification_data on data format of EDID.

private void GetDisplays()
{
   RegistryKey key = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Enum\\DISPLAY");
   if (key == null)
      return;
   //sName is the display name
      foreach (string sName in key.GetSubKeyNames()) 
  { 
      if (sName == "Default_Monitor") 
         continue;
       RegistryKey keyDisplay = key.OpenSubKey(sName);
      //sDisplayID is the display ID
              foreach (string sDisplayID in keyDisplay.GetSubKeyNames())
      {
         RegistryKey keyDevice = keyDisplay.OpenSubKey(sDisplayID);
         if (keyDevice == null)
            continue;
         //sDriver is the display driver name
                    string  sDriver = (string)keyDevice.GetValue("Driver");
         RegistryKey keyParameter = keyDevice.OpenSubKey("Device Parameters");
         if (keyParameter == null)
         { 
            keyDevice.Close();
            continue;
         }
         byte[] EDID = (byte[])keyParameter.GetValue("EDID");
         keyParameter.Close();
         keyDevice.Close();
         if (EDID == null)
            continue;
         //Display's physical height and width
         int width = EDID[21];
         int height = EDID[22];
     }
     keyDisplay.Close();
   }
   key.Close();
}

Now we have enough information to draw a line, the screen resolution and the physical size of the screen. We can feed these parameters to the DrawHorizontalLine function.  Also it is not hard to extend the above horizontal solution to the vertical case or to the line with any slope.
   

Future Works 

  • The above solution is for Windows only. For other platforms, such as Mac, Linux, it should be pretty easy to get current screen's resolution. However, we need to do some more research on where EDID are stored in these platforms.
  • If the computer has multiple connected displays, we need extra code to determine in which display current application is running and then use the corresponding width and height parameters.
  • This solution is also useful for a new "Print Preview" feature, which can produce the preview exactly the same as what will be printed. Imagine the computer shows an A4 paper on the screen with the exact width and height as a real A4 paper, and all the content displayed on the paper has the same size as the content printed on a real A4 paper.

No comments:

Post a Comment