Introduction
I do not know how, but every now and then I return to graphics-oriented projects. Maybe it is because I always wanted to get into games programming, and still linger subconsciously to, but alas, here we are with a nice little project.
This article you will read (and hopefully enjoy) today deals with inverting colors in images. There are a few ways in .NET, which I will demonstrate. For the uninformed: Inverting colors means to take the opposite value of each color component and merge them to create the inverted color.
Let’s create a small project.
Create a Windows Forms project in either VB.NET or C#. Once the project has loaded, add 4 buttons, 1 picturebox, and 1 OpenFileDialog control on the form. My design looks like Figure 1.
Figure 1: Design
Double-click the button labeled “Load Image” and enter the following code:
C#
private void btnLoadImage_Click(object sender, EventArgs e) { if (ofdLoad.ShowDialog() == DialogResult.OK) { try { Image imgSource = Image.FromFile(ofdLoad.FileName); picDisplay.Image = imgSource; } catch (Exception) { MessageBox.Show("Invalid image"); } } }
VB.NET
Private Sub btnLoadImage_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles btnLoadImage.Click If ofdLoad.ShowDialog() = DialogResult.OK Then Try Dim imgSource As Image = _ Image.FromFile(ofdLoad.FileName) picDisplay.Image = imgSource Catch __unusedException1__ As Exception MessageBox.Show("Invalid image") End Try End If End Sub
The Load Image button displays the OpenFileDialog, which allows you to browse for an image file. If a file has been selected, the image will be loaded inside the PictureBox.
Add the next code to invert an image using GDI+.
C#
private static Image InvertGDI(Image imgSource) { Bitmap bmpDest = null; using (Bitmap bmpSource = new Bitmap(imgSource)) { bmpDest = new Bitmap(bmpSource.Width, bmpSource.Height); for (int x = 0; x < bmpSource.Width; x++) { for (int y = 0; y < bmpSource.Height; y++) { Color clrPixel = bmpSource.GetPixel(x, y); clrPixel = Color.FromArgb(255 - clrPixel.R, 255 - clrPixel.G, 255 - clrPixel.B); bmpDest.SetPixel(x, y, clrPixel); } } } return (Image)bmpDest; } private void btnGDI_Click(object sender, EventArgs e) { if (picDisplay.Image != null) picDisplay.Image = InvertGDI(picDisplay.Image); }
VB.NET
Private Shared Function InvertGDI(ByVal imgSource As Image) _ As Image Dim bmpDest As Bitmap = Nothing Using bmpSource As Bitmap = New Bitmap(imgSource) bmpDest = New Bitmap(bmpSource.Width, bmpSource.Height) For x As Integer = 0 To bmpSource.Width - 1 For y As Integer = 0 To bmpSource.Height - 1 Dim clrPixel As Color = bmpSource.GetPixel(x, y) clrPixel = Color.FromArgb(255 - clrPixel.R, 255 - _ clrPixel.G, 255 - clrPixel.B) bmpDest.SetPixel(x, y, clrPixel) Next Next End Using Return CType(bmpDest, Image) End Function Private Sub btnGDI_Click(ByVal sender As Object, ByVal e As _ EventArgs) Handles btnGDI.Click If picDisplay.Image IsNot Nothing Then picDisplay.Image = _ InvertGDI(picDisplay.Image) End Sub
Here, you loop through each image pixel and obtain its color. Then, you subtract 255 from the Red, Green, and Blue values of the pixel, thus producing the inverted look. Lastly, you replace the current pixel’s color with the calculated color.
Add the next code to do inversion with the use of the ColorMatrix .NET object.
C#
private static Image InvertColorMatrix(Image imgSource) { Bitmap bmpDest = new Bitmap(imgSource.Width, imgSource.Height); ColorMatrix clrMatrix = new ColorMatrix(new float[][] { new float[] {-1, 0, 0, 0, 0}, new float[] {0, -1, 0, 0, 0}, new float[] {0, 0, -1, 0, 0}, new float[] {0, 0, 0, 1, 0}, new float[] {1, 1, 1, 0, 1} }); using (ImageAttributes attrImage = new ImageAttributes()) { attrImage.SetColorMatrix(clrMatrix); using (Graphics g = Graphics.FromImage(bmpDest)) { g.DrawImage(imgSource, new Rectangle(0, 0, imgSource.Width, imgSource.Height), 0, 0, imgSource.Width, imgSource.Height, GraphicsUnit.Pixel, attrImage); } } return bmpDest; } private void btnInvertMatrix_Click(object sender, EventArgs e) { if (picDisplay.Image != null) picDisplay.Image = InvertColorMatrix(picDisplay.Image); }
VB.NET
Private Shared Function InvertColorMatrix(ByVal imgSource _ As Image) As Image Dim bmpDest As Bitmap = New Bitmap(imgSource.Width, _ imgSource.Height) Dim clrMatrix As ColorMatrix = New ColorMatrix(New Single() _ () {New Single() {-1, 0, 0, 0, 0}, New Single() {0, -1, _ 0, 0, 0}, New Single() {0, 0, -1, 0, 0}, New Single() _ {0, 0, 0, 1, 0}, New Single() {1, 1, 1, 0, 1}}) Using attrImage As ImageAttributes = New ImageAttributes() attrImage.SetColorMatrix(clrMatrix) Using g As Graphics = Graphics.FromImage(bmpDest) g.DrawImage(imgSource, New Rectangle(0, 0, _ imgSource.Width, imgSource.Height), 0, 0, _ imgSource.Width, imgSource.Height, _ GraphicsUnit.Pixel, attrImage) End Using End Using Return bmpDest End Function Private Sub btnInvertMatrix_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles btnInvertMatrix.Click If picDisplay.Image IsNot Nothing Then picDisplay.Image = _ InvertColorMatrix(picDisplay.Image) End Sub
For C#, add the unsafe method. VB.NET does not support unsafe code. For the Unsafe code to work with C#, click Project, Properties, Build, and then check the Allow Unsafe Code checkbox. Add the next code:
C#
private static Image InvertUnsafe(Image imgSource) { Bitmap bmpDest = new Bitmap(imgSource); BitmapData bmpSource = bmpDest.LockBits(new Rectangle(0, 0, bmpDest.Width, bmpDest.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); int intStride = bmpSource.Stride; IntPtr iptrScan0 = bmpSource.Scan0; unsafe // Project, Properties, Build, Allow Unsafe Code { byte* p = (byte*)(void*)iptrScan0; int intOffset = intStride - bmpDest.Width * 4; int intWidth = bmpDest.Width; for (int y = 0; y < bmpDest.Height; y++) { for (int x = 0; x < intWidth; x++) { p[0] = (byte)(255 - p[0]); p[1] = (byte)(255 - p[1]); p[2] = (byte)(255 - p[2]); p += 4; } p += intOffset; } } bmpDest.UnlockBits(bmpSource); return (Image)bmpDest; } private void btnUnsafe_Click(object sender, EventArgs e) { if (picDisplay.Image != null) picDisplay.Image = InvertUnsafe(picDisplay.Image); }
The unsafe method converts each pixel byte for byte. Supposedly, it uses less memory, but with this little example the effect is lost.
Figure 2 displays a normal picture. Figure 3 displays the inverted version of the picture.
Figure 2: Loaded Picture
Figure 3: Inverted
Conclusion
As you can see, there are always more than one option to achieve a goal. With graphics, there is a plethora of options for dealing with color. Maybe in another article, I can play around more, but, until then, code safely!