Title: Use PixelOffsetMode in C#
This example lets you see how different PixelOffsetMode values work when you enlarge an image. When you resize an image, the graphics method examines the pixels in the result image. It maps each output pixel back to a position in the input image and uses that position to calculate the output pixel's resulting color.
How it calculates the output pixel's color depends on the Graphics object's InterpolationMode property. For images, you normally want to use Bicubic or Bilinear interpolation because that reduces pixelation and makes the result smooth (although somewhat fuzzy).
For geometric images, where you want to be able to control each pixel's value, you may want to use NearestNeighbor interpolation. In that case, each output pixel is mapped to the input pixel closest to its corresponding location. For example, suppose you're enlarging an image by a factor of 10 in the X and Y directions and consider the output pixel at position (4, 0). The corresponding input position is (4 / 10, 0 / 10) = (0.4, 0). The nearest integer location (the nearest neighbor) is (0, 0), so that output pixel gets the color of the input pixel at (0, 0).
Now consider the single pixel at (0, 0) in the input image. If we're scaling by a factor of 10, that should map to the rectangle 0 ≤ x < 10, 0 ≤ y < 10. Consider the output pixel at (5, 0). It maps back to position (0.5, 0). If we round down, the nearest neighbor is at (0, 0). Next consider the output pixel at (6, 0). It maps back to position (0.6, 0) which has nearest neighbor (1, 0). This shows that the rectangle doesn't map back to the pixel at (0, 0) as we would like. Instead the resulting image is made up of rectangles that are centered over the corresponding input pixels.
You can see that if you look at the picture at the top of this post. The little triangle on the left shows the original image and the large triangle made of rectangles in the middle shows the triangle enlarged by a factor of 20. In the enlarged picture, each rectangle should be 20×20 pixels, but the topmost and leftmost rectangles are only half as large. Basically their other halves are off the top and left edges of the enlarged image.
This means when you enlarge the image, some of the top and leftmost pixels don't have the colors you would expect. It also means some of the bottom and rightmost pixels are mapped back to positions that lie outside of the input image so they aren't colored correctly, either. (They are yellow in the picture at the top of the post.)
This is where PixelOffsetMode finally comes in. It lets you adjust the output image with an offset to fix this problem.
The Graphics object's PixelOffsetMode property can take several values, but they only have two outcomes.
- None, Default, and HighSpeed do not adjust the image so you get the result shown at the top of this post.
- Half and HighQuality adjust the image by half of the scale factor so the output rectangles all fit on the image. You can see the result in the image on the right.
The following code shows how the example program produces the scaled image.
// Return a scaled version of the input image.
private Bitmap MakeScaledImage(Image image,
int scale, PixelOffsetMode mode)
{
int wid = scale * image.Width;
int hgt = scale * image.Height;
Bitmap bm = new Bitmap(wid, hgt);
using (Graphics gr = Graphics.FromImage(bm))
{
gr.Clear(Color.Yellow);
Rectangle src_rect = new Rectangle(0, 0,
image.Width, image.Height);
Rectangle dest_rect = new Rectangle(0, 0,
wid, hgt);
gr.PixelOffsetMode = mode;
gr.InterpolationMode = InterpolationMode.NearestNeighbor;
gr.DrawImage(image, dest_rect, src_rect,
GraphicsUnit.Pixel);
}
return bm;
}
This method calculates the enlarged image's width and height, and uses them to create an output bitmap. It then creates input and output rectangles to enlarge the entire input image into the output image. Next it sets the Graphics object's PixelOffsetMode and InterpolationMode properties. Finally it draws the input image onto the output image and returns the result.
Aside: I think Microsoft made two bad decisions here. First, the default should be Half. Making the default None leads to a very confusing bug that you can't solve unless you stumble across the PixelOffsetMode property.
Second, Microsoft often fills its enumerations with lots of synonyms. In this case, None, Default, and HighSpeed all mean the same thing; and Half and HighQuality mean the same thing. If you're trying to figure out your options, that means you need to study and understand more possibilities. It would be better to have just two options, None and Half, and then explain in the documentation that None may provide a miniscule performance improvement. (Okay, you could add Default, but then you should explain that the default is Half.)
Download the example to experiment with it and to see additional details.
|