Image Processing 101 in C# Windows 8 Store Apps, no Libraries Required

How to handle an image and apply filters to it without using libraries.

In this article, I will explain how to deal with an image and apply filters to it. In order to do so, we must understand how the image data is stored so that it becomes easy to apply filters using matrix operations.

In the following example I will demonstrate how to:

  • Load an image from file

  • Store image in a byte matrix

  • Apply Grayscale filter

Step 1

Create a new Windows 8 Store app project, as in:

newproj.jpg

Step 2

 

Add the following code the "MainPage.xaml":
 

<Page

    x:Class="ImageProcessing101.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="using:ImageProcessing101"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    mc:Ignorable="d">

 

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">

        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">

            <StackPanel Orientation="Horizontal">

                <Image x:Name="ImgContainer" MaxWidth="480" MaxHeight="600" Margin="5,0" />

                <Image x:Name="FilteredImgContainer" MaxWidth="480" MaxHeight="600" Margin="5,0" />

            </StackPanel>

            <StackPanel Orientation="Horizontal">

                <Button x:Name="picker" Content="pick file" Click="picker_Click_1" />

                <Button x:Name="grayScale" Content="grayscale" Click="grayScale_Click_1" />

            </StackPanel>

        </StackPanel>

<TextBlock HorizontalAlignment="Right" VerticalAlignment="Bottom" Text="Image Processing 101" FontSize="48" />

    </Grid>

</Page

>

<Page

    x:Class="ImageProcessing101.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="using:ImageProcessing101"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    mc:Ignorable="d">

 

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">

        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">

            <StackPanel Orientation="Horizontal">

                <Image x:Name="ImgContainer" MaxWidth="480" MaxHeight="600" Margin="5,0" />

                <Image x:Name="FilteredImgContainer" MaxWidth="480" MaxHeight="600" Margin="5,0" />

            </StackPanel>

            <StackPanel Orientation="Horizontal">

                <Button x:Name="picker" Content="pick file" Click="picker_Click_1" />

                <Button x:Name="grayScale" Content="grayscale" Click="grayScale_Click_1" />

            </StackPanel>

        </StackPanel>

<TextBlock HorizontalAlignment="Right" VerticalAlignment="Bottom" Text="Image Processing 101" FontSize="48" />

    </Grid>

</Page


Step 3
 

Go to the backend and add the following namespaces to the already existing ones:

 

using

Windows.Storage;

using Windows.Storage.Pickers;

using Windows.Storage.Streams;

using Windows.Graphics.Imaging;

using Windows.UI.Xaml.Media.Imaging;

using System.Runtime.InteropServices.WindowsRuntime;


Step 4
 

Add event handlers to the image picker button:

privateasyncvoid picker_Click_1(object sender, RoutedEventArgs e)

        {

            FileOpenPicker imgPicker = newFileOpenPicker();

            imgPicker.FileTypeFilter.Add(".bmp");

            imgPicker.FileTypeFilter.Add(".jpeg");

            imgPicker.FileTypeFilter.Add(".gif");

            imgPicker.FileTypeFilter.Add(".png");

 

            imgPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;

 

            StorageFile imgFile = await imgPicker.PickSingleFileAsync();

 

            try

            {

                IRandomAccessStreamWithContentType img = await imgFile.OpenReadAsync();

                BitmapImage bmi = newBitmapImage();

               bmi.SetSource(img);

               ImgContainer.Source = bmi; // show original image in MainPage

 

               

                // Fetching pixel data

                using (IRandomAccessStream fileStream = await imgFile.OpenAsync(Windows.Storage.FileAccessMode.Read))

               {

                    //await bmpimg.SetSourceAsync(fileStream);

                    //prevF2.Source = bmpimg;

 

 

                    BitmapDecoder decoder = awaitBitmapDecoder.CreateAsync(fileStream);

                   

                    // Scale image to appropriate size

                    BitmapTransform transform = newBitmapTransform()

                   {

                        ScaledWidth = Convert.ToUInt32(bmi.PixelWidth),

                        ScaledHeight =Convert.ToUInt32(bmi.PixelHeight)

                   };

 

                    /// RGBA8 format:

                    /// Each pixel is store in 4 consecutive bytes:

                    /// 1st byte is Red (no offset)

                    /// 2nd byte is Green (+1)

                    /// 3rd byte is Blue (+2)

                    /// 4th byte is Alpha (+3)

                    PixelDataProvider pixelData = await decoder.GetPixelDataAsync(

                        BitmapPixelFormat.Rgba8,

                        BitmapAlphaMode.Straight,

                        transform,

                        ExifOrientationMode.IgnoreExifOrientation,// This sample ignores Exif orientation

                        ColorManagementMode.DoNotColorManage

 

 

                   );

 

                    // An array containing the decoded image data,

                    // which could be modified before being displayed

                   sourcePixels = pixelData.DetachPixelData();

                   width = decoder.PixelWidth;

                   height= decoder.PixelHeight;

                   grayScale.IsEnabled = true;

               }

            }

            catch

            {

                // do nothing

            }

 

        }

privateasyncvoid picker_Click_1(object sender, RoutedEventArgs e)

        {

            FileOpenPicker imgPicker = newFileOpenPicker();

            imgPicker.FileTypeFilter.Add(".bmp");

            imgPicker.FileTypeFilter.Add(".jpeg");

            imgPicker.FileTypeFilter.Add(".gif");

            imgPicker.FileTypeFilter.Add(".png");

 

            imgPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;

 

            StorageFile imgFile = await imgPicker.PickSingleFileAsync();

 

            try

            {

                IRandomAccessStreamWithContentType img = await imgFile.OpenReadAsync();

                BitmapImage bmi = newBitmapImage();

                bmi.SetSource(img);

                ImgContainer.Source = bmi; // show original image in MainPage

 

               

                // Fetching pixel data

                using (IRandomAccessStream fileStream = await imgFile.OpenAsync(Windows.Storage.FileAccessMode.Read))

                {

  

 

                    BitmapDecoder decoder = awaitBitmapDecoder.CreateAsync(fileStream);

                   

                    // Scale image to appropriate size

                    BitmapTransform transform = newBitmapTransform()

                    {

                        ScaledWidth = Convert.ToUInt32(bmi.PixelWidth),

                        ScaledHeight = Convert.ToUInt32(bmi.PixelHeight)

                    };

 

                    /// RGBA8 format:

                    /// Each pixel is store in 4 consecutive bytes:

                    /// 1st byte is Red (no offset)

                    /// 2nd byte is Green (+1)

                    /// 3rd byte is Blue (+2)

                    /// 4th byte is Alpha (+3)

                    PixelDataProvider pixelData = await decoder.GetPixelDataAsync(

                        BitmapPixelFormat.Rgba8,

                        BitmapAlphaMode.Straight,

                        transform,

                        ExifOrientationMode.IgnoreExifOrientation,// This sample ignores Exif orientation

                        ColorManagementMode.DoNotColorManage

 

 

                    );

 

                    // An array containing the decoded image data,

                    // which could be modified before being displayed

                    sourcePixels = pixelData.DetachPixelData();

                    width = decoder.PixelWidth;

                    height= decoder.PixelHeight;

                    grayScale.IsEnabled = true;

                }

            }

            catch

            {

                // do nothing

            }

        }

 

Step 5
 

This is where we implement the grayscale filter. This filter works simply by averaging the value of the red, green and blue at each pixel of the image, then sets this value to all 3 channels. This is demonstrated in the following loop:

 

privateasyncvoid grayScale_Click_1(object sender, RoutedEventArgs e)

        {

            byte[] grayscaled = newbyte[width * height * 4];

 

            try

            {

                // loop through pixels not through byte array

                for (int i = 0; i < width; i++)

               {

                    for (int j = 0; j < height; j++)

                   {

                        short r = sourcePixels[(i * height + j) * 4]

                        , g = sourcePixels[(i * height + j) * 4 + 1]

                        , b = sourcePixels[(i * height + j) * 4 + 2];

                       

                        int avg = (r + g + b) / 3;

 

                        grayscaled[(i * height + j) * 4] = (byte)avg;

                        grayscaled[(i * height + j) * 4 + 1] = (byte)avg;

                        grayscaled[(i * height + j) * 4 + 2] = (byte)avg;

                        grayscaled[(i * height + j) * 4 + 3] = (byte)255;

 

                   }

               }

                WriteableBitmap grayscaledImage = newWriteableBitmap((int)width, (int)height);

                using (Stream stream = grayscaledImage.PixelBuffer.AsStream())

                   {

                        await stream.WriteAsync(grayscaled, 0, grayscaled.Length);

                        FilteredImgContainer.Source = grayscaledImage;

                   }

            }

            catch

            {

                // do nothing

            }

        }

 

 

privateasyncvoid grayScale_Click_1(object sender, RoutedEventArgs e)

        {

            byte[] grayscaled = newbyte[width * height * 4];

 

            try

            {

                // loop through pixels not through byte array

                for (int i = 0; i < width; i++)

                {

                    for (int j = 0; j < height; j++)

                    {

                        short r = sourcePixels[(i * height + j) * 4]

                        , g = sourcePixels[(i * height + j) * 4 + 1]

                        , b = sourcePixels[(i * height + j) * 4 + 2];

                       

                        int avg = (r + g + b) / 3;

 

                        grayscaled[(i * height + j) * 4] = (byte)avg;

                        grayscaled[(i * height + j) * 4 + 1] = (byte)avg;

                        grayscaled[(i * height + j) * 4 + 2] = (byte)avg;

                        grayscaled[(i * height + j) * 4 + 3] = (byte)255;

 

                    }

                }

                WriteableBitmap grayscaledImage = newWriteableBitmap((int)width, (int)height);

                using (Stream stream = grayscaledImage.PixelBuffer.AsStream())

                    {

                        await stream.WriteAsync(grayscaled, 0, grayscaled.Length);

                        FilteredImgContainer.Source = grayscaledImage;

                    }

            }

            catch

            {

                // do nothing

            }

        }


The following is a sample result:

result.jpg