Title: Load a picture and manipulate pixels in WPF and C#
The post Easily manipulate pixels in WPF and C# explains a BitmapPixelMaker class that you can use to manipulate the pixels in an image in WPF relatively easily. That example creates the image from scratch.
This post extends that class so you can initialize the image from a file and then manipulate its pixels. When it starts, the program loads the file Smiley.png and then adjusts its pixels, making some brighter and others darker.
BitmapPixelMaker Constructors
The previous version of the BitmapPixelMaker class provided only one constructor that made an image with a given width and height. To make class more flexible, I have added two new constructors.
The following code shows the revised version of the earlier constructor.
// Constructor. Width and height required.
public BitmapPixelMaker(int width, int height)
{
InitializeFromDimensions(width, height);
}
// Initialize the object from its width and height.
private void InitializeFromDimensions(int width, int height)
{
// Save the width and height.
Width = width;
Height = height;
// Create the pixel array.
Pixels = new byte[width * height * 4];
// Calculate the stride.
Stride = width * 4;
}
This constructor simply calls the InitializeFromDimensions method. It saves the width and height, creates an array to hold pixel data, and calculates the Stride value. The previous version of the constructor did all of those things directly. I moved that code into a new method so it would be easier to reuse in the following constructor and its helper method.
// Constructor that loads from a WriteableBitmap.
public BitmapPixelMaker(WriteableBitmap wbitmap)
{
InitializeFromWbitmap(wbitmap);
}
// Initialize from a WriteableBitmap.
private void InitializeFromWbitmap(WriteableBitmap wbitmap)
{
// Initialize the basics.
// Use Convert.ToInt32 to round the dimensions to integers.
int wid = Convert.ToInt32(wbitmap.Width);
int hgt = Convert.ToInt32(wbitmap.Height);
InitializeFromDimensions(wid, hgt);
// Get the pixels.
Int32Rect rect = new Int32Rect(0, 0, Width, Height);
wbitmap.CopyPixels(rect, Pixels, Stride, 0);
}
This constructor takes as a parameter a WriteableBitmap and prepares the BitmapPixelMaker class to work with it. The constructor simply calls the InitializeFromWbitmap method. That method converts the bitmap's width and height, which are stored in doubles, into integers. It then calls InitializeFromDimensions to prepare the object to work with the bitmap. It finishes by calling the WriteableBitmap object's CopyPixels method to copy its pixel data into the Pixels array.
The following code shows the new example's final constructor.
// Constructor that loads from a URI.
public BitmapPixelMaker(Uri uri)
{
// Load the file into a WriteableBitmap.
BitmapImage bitmap = new BitmapImage(uri);
WriteableBitmap wbitmap =
new WriteableBitmap(bitmap);
// Initialize the object.
InitializeFromWbitmap(wbitmap);
}
This constructor takes as a parameter a URI indicating an image file to load. The constructor uses the URI to create a BitmapImage and then uses the result to creates a WriteableBitmap. It finishes by calling the InitializeFromWbitmap to prepare the BitmapPixelMaker object to manipulate pixels in the image.
The Main Program
The new constructors are the most interesting part of the example program. The following code shows how the main program creates the image that it displays.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Make the BitmapPixelMaker.
Uri uri = new Uri("Smiley.png", UriKind.Relative);
BitmapPixelMaker bm_maker = new BitmapPixelMaker(uri);
// Brighten and darken the image's pixels.
int wid = bm_maker.Width / 4;
int hgt = bm_maker.Height/ 4;
int num_c = bm_maker.Width / wid;
int num_r = bm_maker.Height / hgt;
for (int row = 0; row < num_r; row++)
{
for (int col = 0; col < num_c; col++)
{
for (int i = 0; i < wid; i++)
{
for (int j = 0; j < hgt; j++)
{
int x = col * wid + j;
int y = row * hgt + i;
if ((x < bm_maker.Width) && (y < bm_maker.Height))
{
byte r, g, b, a;
bm_maker.GetPixel(x, y, out r, out g, out b, out a);
if ((col + row) % 2 == 0)
{
// Lighten the pixel.
r = (byte)(255 - (255 - r) * 0.75);
g = (byte)(255 - (255 - g) * 0.75);
b = (byte)(255 - (255 - b) * 0.75);
}
else
{
// Darken the pixel.
r = (byte)(r * 0.75);
g = (byte)(g * 0.75);
b = (byte)(b * 0.75);
}
bm_maker.SetPixel(x, y, r, g, b, a);
}
}
}
}
}
// Convert the pixel data into a WriteableBitmap.
WriteableBitmap new_wbitmap = bm_maker.MakeBitmap(96, 96);
// Display the result.
imgResult.Source = new_wbitmap;
}
The program first creates a Uri object representing the local file Smiley.png. (At design time I added the file to the project and set its Copy To Output Directory property to Copy If Newer so it is copied into the directory where the executable is located if necessary.)
The code then uses the Uri to create a new BitmapPixelMaker.
The rest of the program uses the BitmapPixelMaker to manipulate pixels. It divides the image into four rows and columns and loops through them (in the row and col loops). For each row and column value, the code loops through the pixels in that part of the image (in the i and j loops).
For each pixel, the code gets the pixel's red, green, blue, and alpha values. Then if the sum of the row and column numbers is even, the program moves the pixel's red, green, and blue values closer to 255, making the pixel more transparent. If the sum is odd, the program moves the pixel's red, green, and blue values closer to 0, making the pixel more transparent.
After it has adjusted the pixel's color components, the code calls the BitmapPixelMaker object's SetPixel method to save the new values in the object's Pixels data.
After it has updated all of the pixels, the program calls the maker's MakeBitmap method to convert the pixel data into a WriteableBitmap. It then displays the result in the program's Image control.
Conclusion
The new version of BitmapPixelMaker lets you manipulate pixels for an image that you load from a file. It's somewhat awkward and non-intuitive, so I still prefer to use Windows Forms when I need to manipulate images, but at least this version makes it possible to manipulate pixels in a WPF application if you need to do so.
Download the example to experiment with it and to see additional details.
|