Title: Adjust an image's opacity in C#
I recently wanted to reduce an image's contrast by adjusting its opacity. If you draw a semi-transparent image on top of a white background, the result looks washed out so it can act as a background for text or whatever.
Realizing that WPF allows you easily adjust an image's opacity, I decided to write the program in WPF. Everything went fine until I tried to save the image with reduced opacity. I have code to do that, but it doesn't work well with really large images and that's what I had here. I had yet again fallen for WPF's slogan, "Twice as flexible and only ten times as hard." In this example, the Opacity property makes adjusting an image's opacity easy, but WPF is terrible at bitmap manipulation so saving the result was impossible.
Anyway, I switched to a Windows Forms application and the whole thing was easy. Use File > Open to load an image file. Then use the scroll bar to adjust the opacity. When you like the result, use File > Save to save the result. The program only saves files in PNG format because other file formats do not preserve opacity.
The following sections describe the program's key pieces.
- Managing the Scroll Bar
- Displaying the Sample Image
- ShowImage
- MakeCheckerboard
- SetOpacity
Managing the Scroll Bar
Scroll bars only display integer values, but I wanted the opacity to be between 0.00 and 1.00 with values set to the nearest 1/100th. To use scroll bars with fractional values, multiply the minimum and maximum values that you want to allow by a power of ten that converts those values into integers. For this example, I multiplied 0.00 and 1.00 by 100 so the new bounds are between 0 and 100.
Now in the scroll bar's Scroll event handler, divide the value by that same power, in this case 100. The following code shows the opacity's Scroll event handler.
// Change the opacity.
private void scrOpacity_Scroll(object sender, ScrollEventArgs e)
{
OpacityValue = (scrOpacity.Value / 100f);
lblOpacity.Text = OpacityValue.ToString("0.00");
ShowImage();
}
The code divides the scroll bar's value by 100. It uses the floating point value 100f instead of the integer value 100 so the program doesn't perform integer division and discard any digits beyond the decimal point.
The event handler displays the new opacity value in the lblOpacity label. It then calls the ShowImage method described shortly to display the adjusted image.
If you want to allow the user to select a particular largest value in the scroll bar, then you must set Maximum = [desired maximum] + LargeChange - 1. In this example, I want to allow the user to be able to select values between 0 and 100 (which are converted into 0.00 to 1.00). The scroll bar's LargeChange property is 10, so I need to set its Maximum property to 100 + 10 - 1 = 109. Don't ask me what Microsoft was thinking when they decided to do it this way.
Displaying the Sample Image
The example uses the following code to store the originally loaded picture and a version with adjusted opacity.
// The loaded image.
private Bitmap OriginalImage = null;
// The adjusted image.
private Bitmap AdjustedImage = null;
When you load a new picture, the program stores it in the OriginalImage field.
Whenever you adjust the scroll bar or open a new picture, the program adjusts the original image's opacity and stores the result in the AdjustedImage field.
The ShowImage method creates the adjusted image and displays it. That method uses the MakeCheckerboard and SetOpacity methods. The following sections describe those three methods.
ShowImage
The following code shows the ShowImage method.
// Draw the loaded image with the selected opacity.
private void ShowImage()
{
// Make sure we have an input picture.
if (OriginalImage == null) return;
// Create a sample image.
// Make a background image.
Bitmap sample_bm =
MakeCheckerboard(
OriginalImage.Width,
OriginalImage.Height, 64);
// Draw the adjusted image over the checkerboard.
AdjustedImage = SetOpacity(OriginalImage, OpacityValue);
using (Graphics gr = Graphics.FromImage(sample_bm))
{
gr.DrawImage(AdjustedImage, 0, 0);
}
picSample.Image = sample_bm;
}
This method does nothing if you have not yet loaded an image into the OriginalImage field.
If an image is loaded, the method calls the MakeCheckerboard method to creates a blue and yellow checkerboard to fit the original image. It then calls the AdjustOpacity method to create a copy of the original image with its opacity adjusted. The program then draws the adjusted image on top of the checkerboard so you can see the blue and yellow squares showing through the new image.
MakeCheckerboard
The following code shows the MakeCheckerboard method.
// Make a checkerboard bitmap.
private Bitmap MakeCheckerboard(int width, int height, int size)
{
int num_rows = height / size + 1;
int num_cols = width / size + 1;
Bitmap bm = new Bitmap(width, height);
using (Graphics gr = Graphics.FromImage(bm))
{
for (int r = 0; r < num_rows; r++)
{
for (int c = 0; c < num_cols; c++)
{
Rectangle rect = new Rectangle(
c * size, r * size, size, size);
if ((r + c) % 2 == 0)
gr.FillRectangle(Brushes.Blue, rect);
else
gr.FillRectangle(Brushes.Yellow, rect);
}
}
}
return bm;
}
This method calculates the number of rows and columns it needs to fill the desired area with squares of the desired size. It then creates a Bitmap that has the indicated width and height.
Next, the code loops through the rows and columns. For each row/column combination, the method makes a Rectangle in the corresponding location in the bitmap. If the sum of the row plus the column is even, the method makes it blue. If the sum is odd, the method makes it yellow. That makes the squares alternate between blue and yellow.
After if finishes drawing the squares, the method returns the bitmap.
SetOpacity
Probably the most interesting and least obvious part of the program is the following SetOpacity method.
// Set the image's opacity.
private Bitmap SetOpacity(Bitmap input_bm, float opacity)
{
// Make the new bitmap.
Bitmap output_bm = new Bitmap(
input_bm.Width, input_bm.Height);
// Make an associated Graphics object.
using (Graphics gr = Graphics.FromImage(output_bm))
{
// Make a ColorMatrix with the opacity.
ColorMatrix color_matrix = new ColorMatrix();
color_matrix.Matrix33 = opacity;
// Make the ImageAttributes object.
ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(color_matrix,
ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
// Draw the input bitmap onto the Graphics object.
Rectangle rect = new Rectangle(0, 0,
output_bm.Width, output_bm.Height);
gr.DrawImage(input_bm, rect,
0, 0, input_bm.Width, input_bm.Height,
GraphicsUnit.Pixel, attributes);
}
return output_bm;
}
This method creates a new bitmap with the same size as the original one and makes an associated Graphics object.
Next, the code creates a ColorMatrix object. Basically the program will treat each pixel's red, green, blue, and alpha components as a vector and multiply it by the ColorMatrix. Initially that object represents an identity matrix, so if you apply it to a bitmap, the bitmap is unchanged. The [3, 3] position in the matrix corresponds to the scale factor for the alpha component. The code sets that value to the opacity, so when the matrix is applied to the image, each pixel's alpha component is multiplied by that value. If the image starts out opaque, then that sets the image's opacity.
Having set the ColorMatrix object's [3, 3] entry, the method creates a new ImageAttributes object and sets its color matrix to the lone that we just created.
Finally, the method calls the Graphics object's DrawImage method to draw the original image onto the output bitmap while applying the matrix. The method then returns the result.
Conclusion
Download the example to see additional details including how the program loads and saves images. You may also want to experiment with the ColorMatrix object. For example, try leaving the [3, 3] entry equal to 1, and setting one or more of the [0, 0], [1, 1], or [2, 2] entries to 0.
Download the example to experiment with it and to see additional details.
|