Skip to content

Archive

Tag: C#

Decreasing the colour depth involves converting colour values to standard values.

Specifying an offset, preferably a value evenly divisible by 256, such as 16, 24 or 32, we then need to make sure that the red, green and blue components’ values get rounded off to multiples of this offset.

For example, using an offset of 16, value colour values would be 0, 15, 31, 47,……, 255. and all values would need to be rounded off to these values for each of the colour components.

To be able to do this, we take the component value, and then add half of the offset to the value. We then subtract the modulo of this value and the offset. This then gives values along the lines of 0, 16, 32, …., 256, so we will need to subtract 1 from this, but will need to put in a correction for 0, since that value needs to remain as it is.

Decreasing the colour depth using an offset of 16

Decreasing the colour depth using an offset of 16


You can download the full code for the sample application which contains code for all the image effects covered in the series here.

public void ApplyDecreaseColourDepth(int offset)
{
    int A, R, G, B;
    Color pixelColor;

    for (int y = 0; y < bitmapImage.Height; y++)
    {
        for (int x = 0; x < bitmapImage.Width; x++)
        {
            pixelColor = bitmapImage.GetPixel(x, y);
            A = pixelColor.A;
            R = ((pixelColor.R + (offset / 2)) - ((pixelColor.R + (offset / 2)) % offset) - 1);
            if (R < 0)
            {
                R = 0;
            }
            G = ((pixelColor.G + (offset / 2)) - ((pixelColor.G + (offset / 2)) % offset) - 1);
            if (G < 0)
            {
                G = 0;
            }
            B = ((pixelColor.B + (offset / 2)) - ((pixelColor.B + (offset / 2)) % offset) - 1);
            if (B < 0)
            {
                B = 0;
            }
            bitmapImage.SetPixel(x, y, Color.FromArgb(A, R, G, B));
        }
    }

}
Share

Back in the old days, and here I am talking about the 19th century old days, it was very common to apply a sepia tinting to photographs. This was a chemical process, which preserved the photos for longer, although it was also used for its aesthetic appeal.

These days, with digital photograpy, we don’t need to preserve photos like in those early days, but it still does give a nice warm tone to a photo, and is great for making a modern photo appear old-fashioned.

Converting a photo to sepia is a relatively easy effect.

For each pixel in the image, we first convert the pixel to greyscale. We then add a value to the green component, and double that value to the red component to give the sepia effect.

The depth of the sepia effect is determined by this value, which we pass here as a parameter. A value of 0 will give a standard greyscale image.

Applying a Sepia value of 20

Applying a Sepia value of 20


You can download the full code for the sample application which contains code for all the image effects covered in the series here.

public void ApplySepia(int depth)
{
    int A, R, G, B;
    Color pixelColor;

    for (int y = 0; y < bitmapImage.Height; y++)
    {
        for (int x = 0; x < bitmapImage.Width; x++)
        {
            pixelColor = bitmapImage.GetPixel(x, y);
            A = pixelColor.A;
            R = (int)((0.299 * pixelColor.R) + (0.587 * pixelColor.G) + (0.114 * pixelColor.B));
            G = B = R;

            R += (depth * 2);
            if (R > 255)
            {
                R = 255;
            }
            G += depth;
            if (G > 255)
            {
                G = 255;
            }

            bitmapImage.SetPixel(x, y, Color.FromArgb(A, R, G, B));
        }
    }
}

Share

One of the most striking effects to apply to an image is embossing. This is effect is a form of edge-detection, and is also done using convolution, which you can read more about on my post on Smoothing using convolution.

The convolution grid we use in embossing is a little different to the others we have looked at so far. It has an offset of 127, and a fixed factor of 4. The offset makes the the image appear on average as a medium gray.

The grid we will use then is

-1 0 -1
0 4 0
-1 0 -1

Embossing

Embossing


You can download the full code for the sample application which contains code for all the image effects covered in the series here.

public void ApplyEmboss(double weight)
{
    ConvolutionMatrix matrix = new ConvolutionMatrix(3);
    matrix.SetAll(1);
    matrix.Matrix[0, 0] = -1;
    matrix.Matrix[1, 0] = 0;
    matrix.Matrix[2, 0] = -1;
    matrix.Matrix[0, 1] = 0;
    matrix.Matrix[1, 1] = weight;
    matrix.Matrix[2, 1] = 0;
    matrix.Matrix[0, 2] = -1;
    matrix.Matrix[1, 2] = 0;
    matrix.Matrix[2, 2] = -1;
    matrix.Factor = 4;
    matrix.Offset = 127;
    bitmapImage = Convolution3x3(bitmapImage, matrix);

}
Share

Sharpening an image is merely the inverse of what we did to blur the image. It also uses the convolution function, which you can read in my post about Smoothing using convolution.

Here we supply a convolution matrix set up as follows:

0 -2 0
-2 11 -2
0 -2 0

Here our weighting factor would total 3, since the negative numbers get subtracted from the weighting total.

Shapening our image

Shapening our image


You can download the full code for the sample application which contains code for all the image effects covered in the series here.

public void ApplySharpen(double weight)
{
    ConvolutionMatrix matrix = new ConvolutionMatrix(3);
    matrix.SetAll(1);
    matrix.Matrix[0, 0] = 0;
    matrix.Matrix[1, 0] = -2;
    matrix.Matrix[2, 0] = 0;
    matrix.Matrix[0, 1] = -2;
    matrix.Matrix[1, 1] = weight;
    matrix.Matrix[2, 1] = -2;
    matrix.Matrix[0, 2] = 0;
    matrix.Matrix[1, 2] = -2;
    matrix.Matrix[2, 2] = 0;
    matrix.Factor = weight - 8;
    bitmapImage = Convolution3x3(bitmapImage, matrix);

}
Share

Gaussian blur also uses convolution to create the effect. If you have not yet read my blog post on Smoothing using convolution, I would advise you to read that post first, as in that post I explain how the convolution function works.

Gaussian blur works very similarly to the smoothing function, in that both are a type of blur, but the Gaussian blur gives a more natural looking blur.

It achieves this by not have a flat weighting, but instead has a weighting based on a Gaussian curve, thus for our 3×3 grid, the values we want to supply the convolution function is as follows:

1 2 1
2 4 2
1 2 1

The factor parameter is set to the sum of each pixel in the grid, which in this case, is again, 16, and the offset is set to 0.

Now we get a nice blur effect.

Applying a guassian blur

Applying a guassian blur


You can download the full code for the sample application which contains code for all the image effects covered in the series here.

public void ApplyGaussianBlur(double peakValue)
{
    ConvolutionMatrix matrix = new ConvolutionMatrix(3);
    matrix.SetAll(1);
    matrix.Matrix[0, 0] = peakValue / 4;
    matrix.Matrix[1, 0] = peakValue / 2;
    matrix.Matrix[2, 0] = peakValue / 4;
    matrix.Matrix[0, 1] = peakValue / 2;
    matrix.Matrix[1, 1] = peakValue;
    matrix.Matrix[2, 1] = peakValue / 2;
    matrix.Matrix[0, 2] = peakValue / 4;
    matrix.Matrix[1, 2] = peakValue / 2;
    matrix.Matrix[2, 2] = peakValue / 4;
    matrix.Factor = peakValue * 4;
    bitmapImage = Convolution3x3(bitmapImage, matrix);

}
Share

Smoothing, which is just a type of blurring is based on a concept called convolution.

How this works is that you take a square section of pixels, often 3×3 but can be any size, although the larger you make the grid, the less pixels that will get affected by the effect around the edges.

The centre pixel of the grid is the main pixel we are interested in. To use convolution, what we do is assign weightings to each pixel in the grid, depending on what effect we are trying to achieve, and then assign the result to the centre pixel.

The basic data structure we use for the convolution is a class which contains the 2D array, as well as a weighting factor, and an offset.

    public class ConvolutionMatrix
    {
        public int MatrixSize = 3;

        public double[,] Matrix;
        public double Factor = 1;
        public double Offset = 1;

        public ConvolutionMatrix(int size)
        {
            MatrixSize = 3;
            Matrix = new double[size, size];
        }

        public void SetAll(double value)
        {
            for (int i = 0; i < MatrixSize; i++)
            {
                for (int j = 0; j < MatrixSize; j++)
                {
                    Matrix[i, j] = value;
                }
            }
        }
    }

Now, having this class, we can implement the convolution algorithm

public Bitmap Convolution3x3(Bitmap b, ConvolutionMatrix m)
{
    Bitmap newImg = (Bitmap)b.Clone();
    Color[,] pixelColor = new Color[3, 3];
    int A, R, G, B;

    for (int y = 0; y < b.Height - 2; y++)
    {
        for (int x = 0; x < b.Width - 2; x++)
        {
            pixelColor[0, 0] = b.GetPixel(x, y);
            pixelColor[0, 1] = b.GetPixel(x, y + 1);
            pixelColor[0, 2] = b.GetPixel(x, y + 2);
            pixelColor[1, 0] = b.GetPixel(x + 1, y);
            pixelColor[1, 1] = b.GetPixel(x + 1, y + 1);
            pixelColor[1, 2] = b.GetPixel(x + 1, y + 2);
            pixelColor[2, 0] = b.GetPixel(x + 2, y);
            pixelColor[2, 1] = b.GetPixel(x + 2, y + 1);
            pixelColor[2, 2] = b.GetPixel(x + 2, y + 2);

            A = pixelColor[1, 1].A;

            R = (int)((((pixelColor[0, 0].R * m.Matrix[0, 0]) +
                         (pixelColor[1, 0].R * m.Matrix[1, 0]) +
                         (pixelColor[2, 0].R * m.Matrix[2, 0]) +
                         (pixelColor[0, 1].R * m.Matrix[0, 1]) +
                         (pixelColor[1, 1].R * m.Matrix[1, 1]) +
                         (pixelColor[2, 1].R * m.Matrix[2, 1]) +
                         (pixelColor[0, 2].R * m.Matrix[0, 2]) +
                         (pixelColor[1, 2].R * m.Matrix[1, 2]) +
                         (pixelColor[2, 2].R * m.Matrix[2, 2]))
                                / m.Factor) + m.Offset);

            if (R < 0)
            {
                R = 0;
            }
            else if (R > 255)
            {
                R = 255;
            }

            G = (int)((((pixelColor[0, 0].G * m.Matrix[0, 0]) +
                         (pixelColor[1, 0].G * m.Matrix[1, 0]) +
                         (pixelColor[2, 0].G * m.Matrix[2, 0]) +
                         (pixelColor[0, 1].G * m.Matrix[0, 1]) +
                         (pixelColor[1, 1].G * m.Matrix[1, 1]) +
                         (pixelColor[2, 1].G * m.Matrix[2, 1]) +
                         (pixelColor[0, 2].G * m.Matrix[0, 2]) +
                         (pixelColor[1, 2].G * m.Matrix[1, 2]) +
                         (pixelColor[2, 2].G * m.Matrix[2, 2]))
                                / m.Factor) + m.Offset);

            if (G < 0)
            {
                G = 0;
            }
            else if (G > 255)
            {
                G = 255;
            }
           
            B = (int)((((pixelColor[0, 0].B * m.Matrix[0, 0]) +
                         (pixelColor[1, 0].B * m.Matrix[1, 0]) +
                         (pixelColor[2, 0].B * m.Matrix[2, 0]) +
                         (pixelColor[0, 1].B * m.Matrix[0, 1]) +
                         (pixelColor[1, 1].B * m.Matrix[1, 1]) +
                         (pixelColor[2, 1].B * m.Matrix[2, 1]) +
                         (pixelColor[0, 2].B * m.Matrix[0, 2]) +
                         (pixelColor[1, 2].B * m.Matrix[1, 2]) +
                         (pixelColor[2, 2].B * m.Matrix[2, 2]))
                                / m.Factor) + m.Offset);

            if (B < 0)
            {
                B = 0;
            }
            else if (B > 255)
            {
                B = 255;
            }
            newImg.SetPixel(x+1, y+1, Color.FromArgb(A, R, G, B));
        }
    }
    return newImg;
}

Finally, after all that, we are ready to implement a smoothing function. To smooth an image, we assign a particular value to the centre pixel, and then 1 to all the surrounding pixels, so the array would look something like this

1 1 1
1 8 1
1 1 1

The factor parameter is set to the sum of each pixel in the grid, which in this case would be 16, and the offset is set to 0. The offset shifts all the values of the colour components by the specified amount.

Now all that is left is calling the convolution function.

Smoothing

Smoothing


You can download the full code for the sample application which contains code for all the image effects covered in the series here.

        public void ApplySmooth(double weight)
        {
            ConvolutionMatrix matrix = new ConvolutionMatrix(3);
            matrix.SetAll(1);
            matrix.Matrix[1,1] = weight;
            matrix.Factor = weight + 8;
            bitmapImage = Convolution3x3(bitmapImage, matrix);

        }
Share

Gamma correction has a history based on the properties of CRT’s and was first noted in televisions.

The light intensity of a CRT is not directly proportional to the input voltage, but is rather proportional to the input voltage raised to a particular power. This power was known as gamma, and varied with CRT. The value was usually around 2.5.

This means that different CRT’s (televisions and monitors before the flatscreen days) all showed the same images slightly differently, and gamma correction was implement to correct this.

A gamma filter works by creating an array of 256 values called a gamma ramp for each value of the red, blue and green components. The gamma value must be between 0.2 and 5.

The formula for calculating the gamma ramp is
255 * (i / 255)1/gamma + 0.5.

If this value is greater than 255, then it is clamped to 255. It is possible to have a different gamma value for each of the 3 colour components.

Then for each pixel in the image, we can substitute the value in this array for the original value of that component at that pixel.

Applying a gamma correction of 0.6

Applying a gama correction of 0.6


You can download the full code for the sample application which contains code for all the image effects covered in the series here.

        public void ApplyGamma(double r, double g, double b)
        {
            byte A, R, G, B;
            Color pixelColor;

            byte[] redGamma = new byte[256];
            byte[] greenGamma = new byte[256];
            byte[] blueGamma = new byte[256];

            for (int i = 0; i < 256; ++i)
            {
                redGamma[i] = (byte)Math.Min(255, (int)((255.0
                    * Math.Pow(i / 255.0, 1.0 / r)) + 0.5));
                greenGamma[i] = (byte)Math.Min(255, (int)((255.0
                    * Math.Pow(i / 255.0, 1.0 / g)) + 0.5));
                blueGamma[i] = (byte)Math.Min(255, (int)((255.0
                    * Math.Pow(i / 255.0, 1.0 / b)) + 0.5));
            }

            for (int y = 0; y < bitmapImage.Height; y++)
            {
                for (int x = 0; x < bitmapImage.Width; x++)
                {
                    pixelColor = bitmapImage.GetPixel(x, y);
                    A = pixelColor.A;
                    R = redGamma[pixelColor.R];
                    G = greenGamma[pixelColor.G];
                    B = blueGamma[pixelColor.B];
                    bitmapImage.SetPixel(x, y, Color.FromArgb((int)A, (int)R, (int)G, (int)B));
                }
            }
        }
Share