HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Adding filters to images

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
imagesfiltersadding

Problem

Here is some code I wrote as part of a project that I am working on to learn C#. It is a class responsible for applying filters to images. In this case, my code offers filters such as sepia and grayscale (sorry, no Valencia for you Instagram users!). Any input on how it can be improved is appreciated.

```
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace ImageFilter
{
public static class ExtBitmap
{
private static Bitmap GetArgbCopy(Image sourceImage)
{
Bitmap bmpNew = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat.Format32bppArgb);

using (Graphics graphics = Graphics.FromImage(bmpNew))
{
graphics.DrawImage(sourceImage, new Rectangle(0, 0, bmpNew.Width, bmpNew.Height), new Rectangle(0, 0, bmpNew.Width, bmpNew.Height), GraphicsUnit.Pixel);
graphics.Flush();
}

return bmpNew;
}

private static Bitmap ApplyColorMatrix(Image sourceImage, ColorMatrix colorMatrix)
{
Bitmap bmp32BppSource = GetArgbCopy(sourceImage);
Bitmap bmp32BppDest = new Bitmap(bmp32BppSource.Width, bmp32BppSource.Height, PixelFormat.Format32bppArgb);

using (Graphics graphics = Graphics.FromImage(bmp32BppDest))
{
ImageAttributes bmpAttributes = new ImageAttributes();
bmpAttributes.SetColorMatrix(colorMatrix);

graphics.DrawImage(bmp32BppSource, new Rectangle(0, 0, bmp32BppSource.Width, bmp32BppSource.Height),
0, 0, bmp32BppSource.Width, bmp32BppSource.Height, GraphicsUnit.Pixel, bmpAttributes);

}

bmp32BppSource.Dispose();

return bmp32BppDest;
}

public static Bitmap CopyWithTransparency(this Image sourceImage, byte alphaComponent = 100)
{
Bitmap bmpNew = GetArgbCopy(sourceImage);
BitmapData bmpData = b

Solution

In the world of Graphics and Bitmaps you can assume that virtually everything's disposable so you should also try to dispose everything you're working with.

One of such examples would be the ImageAttributes that you use in the ApplyColorMatrix and you don't dispose.

You're also not consistent with the using statement. Once you use it and the other time not like here:

private static Bitmap ApplyColorMatrix(Image sourceImage, ColorMatrix colorMatrix)
{
    Bitmap bmp32BppSource = GetArgbCopy(sourceImage);
    Bitmap bmp32BppDest = new Bitmap(bmp32BppSource.Width, bmp32BppSource.Height, PixelFormat.Format32bppArgb);

    using (Graphics graphics = Graphics.FromImage(bmp32BppDest))
    {
        ImageAttributes bmpAttributes = new ImageAttributes();
        bmpAttributes.SetColorMatrix(colorMatrix);

        graphics.DrawImage(bmp32BppSource, new Rectangle(0, 0, bmp32BppSource.Width, bmp32BppSource.Height),
                         0, 0, bmp32BppSource.Width, bmp32BppSource.Height, GraphicsUnit.Pixel, bmpAttributes);

    }

    bmp32BppSource.Dispose();

    return bmp32BppDest;
}


Ideally it should look like this:

private static Bitmap ApplyColorMatrix(Image sourceImage, ColorMatrix colorMatrix)
{
    using (Bitmap bmp32BppSource = GetArgbCopy(sourceImage))
    {
        Bitmap bmp32BppDest = new Bitmap(bmp32BppSource.Width, bmp32BppSource.Height, PixelFormat.Format32bppArgb);

        using (Graphics graphics = Graphics.FromImage(bmp32BppDest))
        using(ImageAttributes bmpAttributes = new ImageAttributes())
        {
            bmpAttributes.SetColorMatrix(colorMatrix);
            graphics.DrawImage(
                image: bmp32BppSource, 
                destRect: new Rectangle(0, 0, bmp32BppSource.Width, bmp32BppSource.Height),
                srcX: 0, 
                srcY: 0, 
                srcWidth: bmp32BppSource.Width, 
                srcHeight: bmp32BppSource.Height,
                srcUnit: GraphicsUnit.Pixel,
                imageAttrs: bmpAttributes
            );

            return bmp32BppDest;
        }
    }        
}


With such a lengthy parameter list you can use their names and write them in separate lines to improve readability.

In other places like where you use LockBits there is a danger that if an exception is thrown the bits won't get unclocked anymore because there is no try/finally block e.g. the CopyWithTransparency method should be implemented this way:

public static Bitmap CopyWithTransparency(this Image sourceImage, byte alphaComponent = 100)
{
    Bitmap bmpNew = GetArgbCopy(sourceImage);
    BitmapData bmpData = null;

    try
    {
        bmpData = bmpNew.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        IntPtr ptr = bmpData.Scan0;

        byte[] byteBuffer = new byte[bmpData.Stride * bmpNew.Height];

        Marshal.Copy(ptr, byteBuffer, 0, byteBuffer.Length);

        for (int k = 3; k < byteBuffer.Length; k += 4)
        {
            byteBuffer[k] = alphaComponent;
        }

        Marshal.Copy(byteBuffer, 0, ptr, byteBuffer.Length);

        return bmpNew;
    }
    finally
    {
        if (bmpData != null) bmpNew.UnlockBits(bmpData);
    }
}


The other APIs are very similar so the above suggestions apply to all of them.

for (int k = 3; k < byteBuffer.Length; k += 4)
{
    byteBuffer[k] = alphaComponent;
}


Here I don't like the magic 3 and 4 and the variable k. We usually start with i unless the variable has a special meaning. In this case you could even use an a that would be much better (because it stands for alpha) then a k which doesn't mean anything and with two constants the code is much easier to understand:

const int firstAlpha = 3;
const int colorComponentCount = 4;
for (int a = firstAlpha; a < byteBuffer.Length; a += colorComponentCount)
{
    byteBuffer[a] = alphaComponent;
}


The same applies to other loops like here:

for (int k = 0; k < byteBuffer.Length; k += 4)
{
    pixel = ~BitConverter.ToInt32(byteBuffer, k);
    pixelBuffer = BitConverter.GetBytes(pixel);

    byteBuffer[k] = pixelBuffer[0];
    byteBuffer[k + 1] = pixelBuffer[1];
    byteBuffer[k + 2] = pixelBuffer[2];
}


Why + 1 or + 2? Would you know it it two months? Use constnats like greenOffset or blueOffset etc. or create a helper class to store the offsets:

class ColorOffset
{
    public const int Red = 0;
    public const int Green = 1;
    public const int Blue = 2;
    public const int Alpha = 3;
}


that you can use with the arrays:

```
const int colorCount = 4;
for (int i = 0; i < byteBuffer.Length; i += colorCount)
{
pixel = ~BitConverter.ToInt32(byteBuffer, i);
pixelBuffer = BitConverter.GetBytes(pixel);

byteBuffer[i + ColorOffset.Red] = pixelBuffer[ColorOffset.Red];
byteBuffer[i + ColorOffset.Green] = pixelBuffer[ColorOffset.Green];
byteBuffer[i + ColorOffset.Blue] = pi

Code Snippets

private static Bitmap ApplyColorMatrix(Image sourceImage, ColorMatrix colorMatrix)
{
    Bitmap bmp32BppSource = GetArgbCopy(sourceImage);
    Bitmap bmp32BppDest = new Bitmap(bmp32BppSource.Width, bmp32BppSource.Height, PixelFormat.Format32bppArgb);

    using (Graphics graphics = Graphics.FromImage(bmp32BppDest))
    {
        ImageAttributes bmpAttributes = new ImageAttributes();
        bmpAttributes.SetColorMatrix(colorMatrix);

        graphics.DrawImage(bmp32BppSource, new Rectangle(0, 0, bmp32BppSource.Width, bmp32BppSource.Height),
                         0, 0, bmp32BppSource.Width, bmp32BppSource.Height, GraphicsUnit.Pixel, bmpAttributes);

    }

    bmp32BppSource.Dispose();

    return bmp32BppDest;
}
private static Bitmap ApplyColorMatrix(Image sourceImage, ColorMatrix colorMatrix)
{
    using (Bitmap bmp32BppSource = GetArgbCopy(sourceImage))
    {
        Bitmap bmp32BppDest = new Bitmap(bmp32BppSource.Width, bmp32BppSource.Height, PixelFormat.Format32bppArgb);

        using (Graphics graphics = Graphics.FromImage(bmp32BppDest))
        using(ImageAttributes bmpAttributes = new ImageAttributes())
        {
            bmpAttributes.SetColorMatrix(colorMatrix);
            graphics.DrawImage(
                image: bmp32BppSource, 
                destRect: new Rectangle(0, 0, bmp32BppSource.Width, bmp32BppSource.Height),
                srcX: 0, 
                srcY: 0, 
                srcWidth: bmp32BppSource.Width, 
                srcHeight: bmp32BppSource.Height,
                srcUnit: GraphicsUnit.Pixel,
                imageAttrs: bmpAttributes
            );

            return bmp32BppDest;
        }
    }        
}
public static Bitmap CopyWithTransparency(this Image sourceImage, byte alphaComponent = 100)
{
    Bitmap bmpNew = GetArgbCopy(sourceImage);
    BitmapData bmpData = null;

    try
    {
        bmpData = bmpNew.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        IntPtr ptr = bmpData.Scan0;

        byte[] byteBuffer = new byte[bmpData.Stride * bmpNew.Height];

        Marshal.Copy(ptr, byteBuffer, 0, byteBuffer.Length);

        for (int k = 3; k < byteBuffer.Length; k += 4)
        {
            byteBuffer[k] = alphaComponent;
        }

        Marshal.Copy(byteBuffer, 0, ptr, byteBuffer.Length);

        return bmpNew;
    }
    finally
    {
        if (bmpData != null) bmpNew.UnlockBits(bmpData);
    }
}
for (int k = 3; k < byteBuffer.Length; k += 4)
{
    byteBuffer[k] = alphaComponent;
}
const int firstAlpha = 3;
const int colorComponentCount = 4;
for (int a = firstAlpha; a < byteBuffer.Length; a += colorComponentCount)
{
    byteBuffer[a] = alphaComponent;
}

Context

StackExchange Code Review Q#158086, answer score: 3

Revisions (0)

No revisions yet.