CAUSE
This behavior occurs because the
Image.Size property in the .NET Framework or the
Image.Width() and
Image.Height() methods in GDI+ return the pixel dimensions of the
image.
However, the "unscaled" image drawing methods of the
Graphics class, by default, draw the image as it should be drawn for the
image's
logical size (as determined by its internal resolution). This default behavior
for the unscaled image drawing methods is by design, so that they conform to
the resolution-independent intent of GDI+.
This result can be very
confusing, because the resulting pixel dimension of the image that is drawn by
the
Graphics class does not match the pixel dimension that is reported by the
Image class.
Consider the following C# sample code, which
demonstrates this opportunity for confusion:
protected override void OnPaint(PaintEventArgs pea)
{
Pen pen = new Pen(Color.Black);
Size ImageSize;
ImageSize = image.Size;
int x = 50;
int y = 50;
pea.Graphics.PageUnit = GraphicsUnit.Pixel;
// Draw a line offset from the Image origin
// Line length to be the same as Image dimension
pea.Graphics.DrawLine(pen, x, y-5,x+ImageSize.Width-1, y-5);
pea.Graphics.DrawLine(pen, x-5, y, x-5, y+ImageSize.Height-1);
// Draw the Image
pea.Graphics.DrawImageUnscaled(image, x, y);
}
The intent of this code, if one assumes that the results of
DrawImageUnscaled() are consistent with the dimensions of
ImageSize, should be two black lines drawn along the top and side of the
image. The length of these lines are intended to be the same as the dimensions
of the image.
However, for most images, you will notice a discrepancy
between the dimensions of the lines and those of the image. To understand why,
consider the case of a specific image.
An image has an inherently
defined dimension that is comprised of the number of pixels along the
horizontal and vertical axes. Image resolution is also often expressed as
dots per inch (dpi). The dpi value relates the pixel size of the image to its
logical size, as measured in real time and space.
For example, it is
possible that a picture with a pixel size of 600 by 300 dots can be defined as
having a dpi value of 300. Therefore, the logical size of the image is 2 inches
by 1 inch ( its physical size, as a photo perhaps ).
If displayed on
a computer monitor that has a dpi of 100, you can use one of two methods to
draw the image:
- Display it as 600 by 300 pixels, which is 6 inches by 3
inches on the 100-dpi screen.
-or- - Use the 300-dpi value in the image to convert the image
size to a pixel size of 2 inches by 1 inch on the 100-dpi screen.
The image size returned by
Image.Size or from the
Image class
Width() and
Height() methods indicate that the image is 600 pixels by 300 pixels. This
is correct if the
DrawImage or
DrawImageUnscaled methods use the first of the preceding two methods to draw the
image.
However, the second method is used to preserve the 2-inch by
1-inch logical size of the image on the 100-dpi screen. As a result, the image
that is drawn would actually paint a field of pixels that is 200 pixels by 100
pixels. The image remains at its unscaled 2-inch by 1-inch logical size, but
its dimensions in pixels have actually shrunk.
If the dpi of the
screen is 300, the two methods are equivalent, because 600 by 300 pixels on a
300-dpi screen is equivalent to 2 inches by 1 inch.
It is a good
practice to use the logical size value of an image, as defined by its dpi,
because this maintains consistency in the image size when it is viewed by a
user. GDI+ is designed to be resolution-independent, which is the reason for
the preceding GDI+ methods, as well as for the .NET Framework default to the
logical size when an image is drawn unscaled.
RESOLUTION
If you need to know the pixel dimensions of an image, you
can determine the pixel size in the same way that the
DrawImage method draws the image. To determine the pixel dimensions of the
logical size of the image, perform a unit conversion on the image dimensions by
using the dpi of the image and the dpi of the
Graphics object.
The following sample C# code demonstrates how
this is done:
protected override void OnPaint(PaintEventArgs pea)
{
Pen pen = new Pen(Color.Black);
Size ImageSize;
int cx;
int cy;
ImageSize = image.Size;
// calculate the image dimensions in pixels from its logical size
cx = (int)((float)ImageSize.Width/image.HorizontalResolution * pea.Graphics.DpiX);
cy = (int)((float)ImageSize.Height/image.VerticalResolution * pea.Graphics.DpiY);
int x = 50;
int y = 50;
pea.Graphics.PageUnit = GraphicsUnit.Pixel;
// Draw a line offset from the Image origin
// Line length to be the same as Image dimension
pea.Graphics.DrawLine(pen, x, y-5,x+cx-1, y-5);
pea.Graphics.DrawLine(pen, x-5, y, x-5, y+cy-1);
// Draw the Image
pea.Graphics.DrawImageUnscaled(image, x, y);
}