×
Namespaces

Variants
Actions
(Difference between revisions)

Custom Filter QuickStart for Nokia Imaging SDK

From Nokia Developer Wiki
Jump to: navigation, search
Rob.Kachmar (Talk | contribs)
(Rob.Kachmar - - Grayscale)
Rob.Kachmar (Talk | contribs)
(Rob.Kachmar - - Psychedelic Effect)
Line 499: Line 499:
 
=== Psychedelic Effect ===
 
=== Psychedelic Effect ===
 
Here's a fun look I accidentally stumbled upon while working through the brightness technique.  I was incorrectly trying to cast the new calculated value to an unsigned int ''(uint)'' before the ''Math.Min'' and ''Math.Max'' methods finished their work.  I ended up flipping any values that went below zero all the way to the opposite extreme of 255. :-)
 
Here's a fun look I accidentally stumbled upon while working through the brightness technique.  I was incorrectly trying to cast the new calculated value to an unsigned int ''(uint)'' before the ''Math.Min'' and ''Math.Max'' methods finished their work.  I ended up flipping any values that went below zero all the way to the opposite extreme of 255. :-)
 +
 +
<gallery>
 +
File:CustomFilters_Original2.jpg|Original
 +
File:CustomFilters_PyschedelicEffect.jpg|Psychedelic Effect
 +
</gallery>
 +
 
<code csharp 1>
 
<code csharp 1>
 
using Nokia.Graphics.Imaging;
 
using Nokia.Graphics.Imaging;

Revision as of 00:27, 23 December 2013

Note.pngNote: This is an entry in the Nokia Imaging and Big UI Wiki Competition 2013Q4.

This article will start off covering the basics you need to understand in order to create custom filters. Then we will begin to apply the concepts to create progressively more complex filters. At the end of the discussion, we'll have an appendix where we can add as many custom filters as we want. Among those, we will be trying to recreate filters built into the Nokia Imaging SDK, so we can better understand the techniques to create even more complex filters.

SignpostIcon XAML 40.png
WP Metro Icon WP8.png
Article Metadata
Compatibility
Platform(s):
Windows Phone 8
Article
Created: Rob.Kachmar (13 Dec 2013)
Last edited: Rob.Kachmar (23 Dec 2013)

Contents

Introduction

The Nokia Imaging SDK is a powerful tool in our bag of tricks for Windows Phone development. The image effects we can produce are simply amazing. We're basically only limited by our imagination and our understanding of the SDK. In this article we will focus on improving our understanding of custom filters using the SDK.

Setup

1) First we'll start by loading up the real-time-filter-demo-v1.1.zip project, since it already has the necessary plumbing in place for us to view our custom filters. If you want a detailed explanation of the project, check out the Real-time Filter Demo Wiki Page.

2) After opening the project, we'll add the highlighted line below to the Initialize method in the MainPage.xaml.cs class.

  1. private async void Initialize()
  2. {
  3.     ...
  4.  
  5.     _cameraEffect.PreviousEffect();
  6.     StatusTextBlock.Text = _cameraEffect.EffectName;
  7. }

3) Then we'll open the CustomEffect.cs class and replace the contents of the for loop in the OnProcess method with this line: targetPixels[index] = sourcePixels[index]; The resulting method should look like the following.

  1. protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  2. {
  3.     var sourcePixels = sourcePixelRegion.ImagePixels;
  4.     var targetPixels = targetPixelRegion.ImagePixels; 
  5.  
  6.     sourcePixelRegion.ForEachRow((index, width, position) =>
  7.     {
  8.         for (int x = 0; x < width; ++x, ++index)
  9.         {
  10.             targetPixels[index] = sourcePixels[index];
  11.         }
  12.     });
  13. }

4) At this point we should be able to build the project, and it will launch straight into our custom effect, which at the moment does not change the look of an image.

Note.pngNote: You may need to also uninstall and re-install the Nokia Imaging SDK with the Nuget Package Manger.

Boiler Plate Code

I know you're getting excited about playing with pixels, but let's spend a few more minutes walking through the boiler plate code line by line. The code snippet below is what the complete CustomEffect class should look like.

  • Line 1: This is where we declare the class name and inherit from the CustomEffectBase class, so all the complicated plumbing will be taken care of by the SDK.
  • Line 3: This is where we have our class constructor. The source parameter of type IImageProvider is required by the CustomEffectBase class, so we must have this parameter declared at a minimum in our constructor definition. Additionally, we must also pass this source parameter to the base class we are inheriting from, CustomEffectBase.
  • Line 7: Here we are overriding the OnProcess method of the CustomEffectBase class with our own custom implementation. This method requires a sourcePixelRegion parameter of type PixelRegion and the targetPixelRegion parameter of type PixelRegion as well.
  • Line 9: Here we are putting all the individual pixels from the image source into an array.
  • Line 10: Here we are building an array of all the individual pixels that will make up the final image effect we are producing from our class.
  • Line 12: This is where we begin moving through the pixels of the source image. We are using the ForEachRow method, which allows us to capture the starting index, row width, and starting position (x and y point coordinates) of each row, beginning at the top left position (0, 0) of the image.
  • Line 14: Now we start a standard for loop to iterate over the individual pixels for the given row we have acquired from the outer ForEachRow loop.
  • Line 16: Finally, we have finished working our way through the boiler plate code and made it to our special customized code that makes all the magic happen. Although, at the present moment, we haven't quite added any magic. We're just doing the bare minimum of copying the current pixel from the source image, sourcePixels[index], to the same pixel location in the the target output image, targetPixels[index], which will simply output the exact same image as the source.
  1.     public class CustomEffect : CustomEffectBase
  2.     {
  3.         public CustomEffect(IImageProvider source) : base(source)
  4.         {
  5.         }
  6.  
  7.         protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  8.         {
  9.             var sourcePixels = sourcePixelRegion.ImagePixels;
  10.             var targetPixels = targetPixelRegion.ImagePixels;
  11.  
  12.             sourcePixelRegion.ForEachRow((index, width, position) =>
  13.             {
  14.                 for (int x = 0; x < width; ++x, ++index)
  15.                 {
  16.                     targetPixels[index] = sourcePixels[index];
  17.                 }
  18.             });
  19.         }
  20.     }

Working with Colors

Now we are finally ready to start playing with pixels. First, we'll start with a simple example that pulls out each ARGB component of the pixel, where A = Alpha, R = Red, G = Green, and B = Blue. Then we'll just reassemble them back into a new pixel. Again, there's no magic in the output yet, it's just the same image.

  1. for (int x = 0; x < width; ++x, ++index)
  2. {
  3.     uint currentPixel = sourcePixels[index]; // get the current pixel
  4.  
  5.     uint alpha = (currentPixel & 0xff000000) >> 24; // alpha component
  6.     uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  7.     uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  8.     uint blue = currentPixel & 0x000000ff; // blue color component
  9.  
  10.     uint newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  11.  
  12.     targetPixels[index] = newPixel; // assign the newPixel to the equivalent location in the output image
  13. }

Now let's give one of the color components a little more pop. Let's make the red component 4 times as prominent. Add a line after we finish breaking down the pixel, and before we reassemble everything into the newPixel. Line 4 below, shows the code which multiples the red value by 4. At the same time, we are also taking care not to exceed the allowable range of values for any given color component, which is 0 to 255. If the new multiplied value exceeds 255, the Math.Min() function is there to save the day and just set the value to the maximum allowed. Now run it and see the extra red effect you've just added to the image. Be sure to also switch out the red with green and blue to see those effects as well.

  1. ...
  2.     uint blue = currentPixel & 0x000000ff; // blue color component
  3.  
  4.     red = Math.Min(255, red * 4);
  5.  
  6.     uint newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  7. ...

Here we will perform a simple technique to flip the bits in our newPixel to create a cool effect that looks like a film negative. All we have to do is add a tilde (~) character just before it when we are assigning it to the output.

  1. ...
  2.     uint blue = currentPixel & 0x000000ff; // blue color component
  3.  
  4.     uint newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  5.  
  6.     targetPixels[index] = ~newPixel; // assign the newPixel to the equivalent location in the output image
  7. ...

Now let's dim the brightness of the image by cutting each of the color components down by 50, from their current value. We can also run the same code to brighten the image by changing the value of brightness to 50. Again, we're using our Math.Min and Math.Max functions to save us from exceeding the range.

  1. ...
  2.     uint blue = currentPixel & 0x000000ff; // blue color component
  3.  
  4.     int brightness = -50;
  5.     red = (uint)Math.Max(0, Math.Min(255, (red + brightness)));
  6.     green = (uint)Math.Max(0, Math.Min(255, (green + brightness)));
  7.     blue = (uint)Math.Max(0, Math.Min(255, (blue + brightness)));
  8.  
  9.     uint newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  10. ...

At this point, if you're thinking you've finally gotten the hang of colors, that's great! At the same time, I must regretfully inform you that we've only scratched the surface. We'll finish up colors with a grayscale example to show just how complex things start to get.

At first thought, you might think we just sum up the individual color components and divide by 3 to get the average color used in a grayscale effect. Unfortunately, reality is not so simple. We must use a special algorithm to take a weighted average of each color component, because apparently not all colors are created equal. Go figure... If you want to continue blowing your mind, go check out the Grayscale Wikipedia article.

  1. ...
  2.     uint blue = currentPixel & 0x000000ff; // blue color component
  3.  
  4.     uint grayscaleAverage = (uint)(0.2126 * red + 0.7152 * green + 0.0722 * blue);
  5.     red = green = blue = grayscaleAverage;
  6.  
  7.     uint newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  8. ...

Working with the Pixel Map

Now it's time to turn our attention toward the placement of pixels within the image. Up until now, we've been simply iterating over each pixel from top to bottom, left to right, and only working with the current pixel in our loop. To achieve more sophisticated looks that involve the current pixel, as well as how it compares to the pixels around it, we need to understand how to access other pixels relative to where we are in the image during our loop. We also need to know how to skip over rows and columns, so we don't double process them.

First, let's start off visualizing what the top left corner looks like on an image that is 640 pixels wide. As you can see, the index at row 1, column 1 is equal to 0. Then it continues to increment by one as we progress through the columns. At index 639, we have finally reach column 640 of row 1. Since we have no more columns to go to on row 1, we now progress to the 2nd row, which begins with the next index of 640.

CustomFilters PixelMap.PNG

Now we need to work out the formula necessary to access other pixels relative to the current pixel. Let's say we're at row 3, column 3, and we want to access the pixel to the immediate top left at row 2, column 2. Given our 640 pixel wide image and our current index of 1282, we would get the index value of 641 with this method. However, if you try using this with real-time effects, the performance degradation is too great.

  1. private int FindIndex(int index, int width, int rowOffset, int columnOffset)
  2. {
  3.     int currentRowIndex = (int)Math.Truncate((double)(index / width));
  4.     int currentColumnIndex = (index % width);
  5.     return ((currentRowIndex + rowOffset) * width) + (currentColumnIndex + columnOffset);
  6. }

A work around is to keep track of the row index as we iterate through the ForEachRow method. We already have the column index of x, so now we can eliminate the first 2 calculations in our method.

  1. private int FindIndex(int rowIndex, int columnIndex, int width, int rowOffset, int columnOffset)
  2. {
  3.     return ((rowIndex + rowOffset) * width) + (columnIndex + columnOffset);
  4. }

Now let's look at how to skip rows and columns. The trick is to keep track of the row index, as mentioned above, then take the modulo of the row index based on the rows you want to skip. You would also do the same thing with the column index, x. Let's take a look at the code below, which will only process every 3rd row and every 3rd column. Go ahead and play around with the processEveryNthRow and processEveryNthColumn parameters to see what happens.

  1. int processEveryNthRow = 3;
  2. int processEveryNthColumn = 3;
  3. processEveryNthRow = (processEveryNthRow <= 0) ? 1 : processEveryNthRow; // Protect against divide by zero
  4. processEveryNthColumn = (processEveryNthColumn <= 0) ? 1 : processEveryNthColumn; // Protect against divide by zero
  5. int rowModuloTarget = processEveryNthRow - 1;
  6. int columnModuloTarget = processEveryNthColumn - 1;
  7.  
  8. int rowIndex = 0;
  9. sourcePixelRegion.ForEachRow((index, width, position) =>
  10. {
  11.     if ((rowIndex % processEveryNthRow).Equals(rowModuloTarget)) // only process on every Nth pixel per row
  12.     {
  13.         for (int x = 0; x < width; ++x, ++index)
  14.         {
  15.             if ((x % processEveryNthColumn).Equals(columnModuloTarget)) // only process on every Nth pixel per column
  16.             {
  17.                 targetPixels[index] = sourcePixels[index];
  18.             }
  19.         }
  20.     }
  21.     rowIndex++;
  22. });

Now let's see how we can combine the FindIndex method and the row/column skipping technique to efficiently create an effect that will intentionally pixelate an image. Let's say we want to pixelate in groups of 9, like our pixel map above is segmented. We'll need to find a way to get the color value of the middle pixel in the group, and then apply it to all the surrounding pixels.

Looking at the pixel map above, the middle value of the first group is 641. Whatever color value is in pixel 641 is what we want to apply to the surrounding pixels 0, 1, 2, 640, 642, 1280, 1281, and 1282. One approach to solve this challenge is to skip to every 3rd row and column, which would put us at pixel 1282 for our first group. Once we get there, we need to get the value of the center pixel. Using our FindIndex method, we would go 1 row up and 1 column back. Then we would get the pixel at the targetIndex and apply it to the other pixels relative to our current location.

  1. int processEveryNthRow = 3;
  2. int processEveryNthColumn = 3;
  3. processEveryNthRow = (processEveryNthRow <= 0) ? 1 : processEveryNthRow; // Protect against divide by zero
  4. processEveryNthColumn = (processEveryNthColumn <= 0) ? 1 : processEveryNthColumn; // Protect against divide by zero
  5. int rowModuloTarget = processEveryNthRow - 1;
  6. int columnModuloTarget = processEveryNthColumn - 1;
  7.  
  8. int rowIndex = 0;
  9. sourcePixelRegion.ForEachRow((index, width, position) =>
  10. {
  11.     if ((rowIndex % processEveryNthRow).Equals(rowModuloTarget)) // only process on every other Nth pixel per row
  12.     {
  13.         for (int x = 0; x < width; ++x, ++index)
  14.         {
  15.             if ((x % processEveryNthColumn).Equals(columnModuloTarget)) // only process on every other Nth pixel per column
  16.             {
  17.                 int targetIndex = FindIndex(rowIndex, x, width, -1, -1); // Get the index of the center pixel from the group of 9
  18.                 if (targetIndex > 0) // Move foward processing if in a valid range
  19.                 {
  20.                     uint targetPixel = sourcePixels[targetIndex]; // Get the actual center pixel from the group of 9
  21.  
  22.                     // Assign the center pixel to all 9 pixels in the group
  23.                     targetPixels[FindIndex(rowIndex, x, width, -2, -2)] = targetPixel; // Top left
  24.                     targetPixels[FindIndex(rowIndex, x, width, -2, -1)] = targetPixel; // Top center
  25.                     targetPixels[FindIndex(rowIndex, x, width, -2, 0)] = targetPixel; // Top right
  26.                     targetPixels[FindIndex(rowIndex, x, width, -1, -2)] = targetPixel; // Middle left
  27.                     targetPixels[FindIndex(rowIndex, x, width, -1, -1)] = targetPixel; // Center
  28.                     targetPixels[FindIndex(rowIndex, x, width, -1, 0)] = targetPixel; // Middle right
  29.                     targetPixels[FindIndex(rowIndex, x, width, 0, -2)] = targetPixel; // Bottom left
  30.                     targetPixels[FindIndex(rowIndex, x, width, 0, -1)] = targetPixel; // Bottom center
  31.                     targetPixels[index] = targetPixel; // Bottom right - where we stopped to process
  32.                 }
  33.             }
  34.         }
  35.     }
  36.     rowIndex++;
  37. });

Summary

Now that we know how to manipulate colors and efficiently navigate the pixels of an image, the sky is the limit. Let your imagination run wild!

Appendix

Brightness

Here's how you can adjust Brightness like you can with the BrightnessFilter in the Nokia Imaging SDK.

  1. using Nokia.Graphics.Imaging;
  2. using System;
  3.  
  4. public class Brightness : CustomEffectBase
  5. {
  6.     private double m_Brightness = 0;
  7.  
  8.     public Brightness(IImageProvider source, double brightness) : base(source)
  9.     {
  10.         m_Brightness = brightness * 100;
  11.     }
  12.  
  13.     protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  14.     {
  15.         var sourcePixels = sourcePixelRegion.ImagePixels;
  16.         var targetPixels = targetPixelRegion.ImagePixels;
  17.  
  18.         sourcePixelRegion.ForEachRow((index, width, position) =>
  19.         {
  20.             for (int x = 0; x < width; ++x, ++index)
  21.             {
  22.                 uint currentPixel = sourcePixels[index]; // get the current pixel
  23.                 uint alpha = (currentPixel & 0xff000000) >> 24; // alpha component
  24.                 uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  25.                 uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  26.                 uint blue = currentPixel & 0x000000ff; // blue color component
  27.  
  28.                 red = (uint)Math.Max(0, Math.Min(255, (red + m_Brightness)));
  29.                 green = (uint)Math.Max(0, Math.Min(255, (green + m_Brightness)));
  30.                 blue = (uint)Math.Max(0, Math.Min(255, (blue + m_Brightness)));
  31.  
  32.                 uint newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  33.                 targetPixels[index] = newPixel; // assign the newPixel to the equivalent location in the output image
  34.             }
  35.         });
  36.     }
  37. }

Color Adjust

Here's how you can adjust Color like you can with the ColorAdjustFilter in the Nokia Imaging SDK.

  1. using Nokia.Graphics.Imaging;
  2. using System;
  3.  
  4. public class ColorAdjust : CustomEffectBase
  5. {
  6.     private double m_red = 0;
  7.     private double m_green = 0;
  8.     private double m_blue = 0;
  9.  
  10.     public ColorAdjust(IImageProvider source, double red = 0, double green = 0, double blue = 0) : base(source)
  11.     {
  12.         m_red = red * 100;
  13.         m_green = green * 100;
  14.         m_blue = blue * 100;
  15.     }
  16.  
  17.     protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  18.     {
  19.         var sourcePixels = sourcePixelRegion.ImagePixels;
  20.         var targetPixels = targetPixelRegion.ImagePixels;
  21.  
  22.         sourcePixelRegion.ForEachRow((index, width, position) =>
  23.         {
  24.             for (int x = 0; x < width; ++x, ++index)
  25.             {
  26.                 uint currentPixel = sourcePixels[index]; // get the current pixel
  27.                 uint alpha = (currentPixel & 0xff000000) >> 24; // alpha component
  28.                 uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  29.                 uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  30.                 uint blue = currentPixel & 0x000000ff; // blue color component
  31.  
  32.                 red = (uint)Math.Max(0, Math.Min(255, (red + m_red)));
  33.                 green = (uint)Math.Max(0, Math.Min(255, (green + m_green)));
  34.                 blue = (uint)Math.Max(0, Math.Min(255, (blue + m_blue)));
  35.  
  36.                 uint newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  37.                 targetPixels[index] = newPixel; // assign the newPixel to the equivalent location in the output image
  38.             }
  39.         });
  40.     }
  41. }

Grayscale

Here's how you can show a Grayscale effect like you can with the GrayscaleFilter in the Nokia Imaging SDK. Looking at the details, you can start to see just how complex colors can be when you have to start using special weighted average algorithms.

  1. using Nokia.Graphics.Imaging;
  2. using System;
  3.  
  4. public class Grayscale : CustomEffectBase
  5. {
  6.     public Grayscale(IImageProvider source) : base(source)
  7.     {
  8.     }
  9.  
  10.     protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  11.     {
  12.         var sourcePixels = sourcePixelRegion.ImagePixels;
  13.         var targetPixels = targetPixelRegion.ImagePixels;
  14.  
  15.         sourcePixelRegion.ForEachRow((index, width, position) =>
  16.         {
  17.             for (int x = 0; x < width; ++x, ++index)
  18.             {
  19.                 uint currentPixel = sourcePixels[index]; // get the current pixel
  20.                 uint alpha = (currentPixel & 0xff000000) >> 24; // alpha component
  21.                 uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  22.                 uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  23.                 uint blue = currentPixel & 0x000000ff; // blue color component
  24.  
  25.                 // Calculate the weighted avearge of all the color components and assign the result to each component
  26.                 // REFERENCE: http://en.wikipedia.org/wiki/Grayscale
  27.                 uint grayscaleAverage = (uint)(0.2126 * red + 0.7152 * green + 0.0722 * blue);
  28.                 red = green = blue = grayscaleAverage;
  29.  
  30.                 uint newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  31.                 targetPixels[index] = newPixel; // assign the newPixel to the equivalent location in the output image
  32.             }
  33.         });
  34.     }
  35. }

Grayscale Negative

Here's how you can show a Grayscale Negative effect like you can with the GrayscaleNegativeFilter in the Nokia Imaging SDK.

  1. using Nokia.Graphics.Imaging;
  2. using System;
  3.  
  4. public class GrayscaleNegative : CustomEffectBase
  5. {
  6.     public GrayscaleNegative(IImageProvider source) : base(source)
  7.     {
  8.     }
  9.  
  10.     protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  11.     {
  12.         var sourcePixels = sourcePixelRegion.ImagePixels;
  13.         var targetPixels = targetPixelRegion.ImagePixels;
  14.  
  15.         sourcePixelRegion.ForEachRow((index, width, position) =>
  16.         {
  17.             for (int x = 0; x < width; ++x, ++index)
  18.             {
  19.                 uint currentPixel = sourcePixels[index]; // get the current pixel
  20.                 uint alpha = (currentPixel & 0xff000000) >> 24; // alpha component
  21.                 uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  22.                 uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  23.                 uint blue = currentPixel & 0x000000ff; // blue color component
  24.  
  25.                 // Calculate the weighted avearge of all the color components and assign the result to each component
  26.                 // REFERENCE: http://en.wikipedia.org/wiki/Grayscale
  27.                 uint grayscaleAverage = (uint)(0.2126 * red + 0.7152 * green + 0.0722 * blue);
  28.                 red = green = blue = grayscaleAverage;
  29.  
  30.                 uint newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  31.                 targetPixels[index] = ~newPixel; // assign the newPixel to the equivalent location in the output image
  32.             }
  33.         });
  34.     }
  35. }

Negative

Here's how you can show a Negative effect like you can with the NegativeFilter in the Nokia Imaging SDK.

  1. using Nokia.Graphics.Imaging;
  2. using System;
  3.  
  4. public class Negative : CustomEffectBase
  5. {
  6.     public Negative(IImageProvider source) : base(source)
  7.     {
  8.     }
  9.  
  10.     protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  11.     {
  12.         var sourcePixels = sourcePixelRegion.ImagePixels;
  13.         var targetPixels = targetPixelRegion.ImagePixels;
  14.  
  15.         sourcePixelRegion.ForEachRow((index, width, position) =>
  16.         {
  17.             for (int x = 0; x < width; ++x, ++index)
  18.             {
  19.                 targetPixels[index] = ~sourcePixels[index];
  20.             }
  21.         });
  22.     }
  23. }

Psychedelic Effect

Here's a fun look I accidentally stumbled upon while working through the brightness technique. I was incorrectly trying to cast the new calculated value to an unsigned int (uint) before the Math.Min and Math.Max methods finished their work. I ended up flipping any values that went below zero all the way to the opposite extreme of 255. :-)

  1. using Nokia.Graphics.Imaging;
  2. using System;
  3.  
  4. public class PsychedelicEffect : CustomEffectBase
  5. {
  6.     private byte m_factor = 50;
  7.  
  8.     public PsychedelicEffect(IImageProvider source, byte factor = 50) : base(source)
  9.     {
  10.         m_factor = factor;
  11.     }
  12.  
  13.     protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  14.     {
  15.         var sourcePixels = sourcePixelRegion.ImagePixels;
  16.         var targetPixels = targetPixelRegion.ImagePixels;
  17.  
  18.         sourcePixelRegion.ForEachRow((index, width, position) =>
  19.         {
  20.             for (int x = 0; x < width; ++x, ++index)
  21.             {
  22.                 uint currentPixel = sourcePixels[index]; // get the current pixel
  23.                 uint alpha = (currentPixel & 0xff000000) >> 24; // alpha component
  24.                 uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  25.                 uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  26.                 uint blue = currentPixel & 0x000000ff; // blue color component
  27.  
  28.                 // Original accidental code
  29.                 //red = Math.Max(0, Math.Min(255, (uint)(int)(red - m_factor)));
  30.                 //green = Math.Max(0, Math.Min(255, (uint)(int)(green - m_factor)));
  31.                 //blue = Math.Max(0, Math.Min(255, (uint)(int)(blue - m_factor)));
  32.  
  33.                 red = (uint)((red - m_factor < 0) ? 255 : Math.Max(0, (red - m_factor)));
  34.                 green = (uint)((green - m_factor < 0) ? 255 : Math.Max(0, (green - m_factor)));
  35.                 blue = (uint)((blue - m_factor < 0) ? 255 : Math.Max(0, (blue - m_factor)));
  36.  
  37.                 uint newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  38.                 targetPixels[index] = newPixel; // assign the newPixel to the equivalent location in the output image
  39.             }
  40.         });
  41.     }
  42. }

Skip Pixel Effect

Here's an interesting effect you can use to skip every other n rows or columns of pixels. It's also combined with the brightness technique, since the remaining pixels could use a little extra pop.

  1. using System;
  2. using Nokia.Graphics.Imaging;
  3.  
  4. public class SkipPixelEffect : CustomEffectBase
  5. {
  6.     private int m_ProcessEveryNthRow = 1;
  7.     private int m_ProcessEveryNthColumn = 1;
  8.     private int m_RowModuloTarget = 0;
  9.     private int m_ColumnModuloTarget = 0;
  10.     private double m_Brightness = 0;
  11.  
  12.     public SkipPixelEffect(IImageProvider source, int processEveryNthRow = 1, int processEveryNthColumn = 1, 
  13.         double brightness = 0) : base(source)
  14.     {
  15.         m_ProcessEveryNthRow = (processEveryNthRow <= 0) ? 1 : processEveryNthRow; // Protect against divide by zero
  16.         m_ProcessEveryNthColumn = (processEveryNthColumn <= 0) ? 1 : processEveryNthColumn; // Protect against divide by zero
  17.         m_RowModuloTarget = m_ProcessEveryNthRow - 1;
  18.         m_ColumnModuloTarget = m_ProcessEveryNthColumn - 1;
  19.         m_Brightness = brightness * 100;
  20.     }
  21.  
  22.     protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  23.     {
  24.         var sourcePixels = sourcePixelRegion.ImagePixels;
  25.         var targetPixels = targetPixelRegion.ImagePixels;
  26.  
  27.         int rowIndex = 0;
  28.         sourcePixelRegion.ForEachRow((index, width, position) =>
  29.         {
  30.             if ((rowIndex % m_ProcessEveryNthRow).Equals(m_RowModuloTarget)) // only process on every Nth pixel per row
  31.             {
  32.                 for (int x = 0; x < width; ++x, ++index)
  33.                 {
  34.                     if ((x % m_ProcessEveryNthColumn).Equals(m_ColumnModuloTarget)) // only process on every Nth pixel per column
  35.                     {
  36.                         uint currentPixel = sourcePixels[index]; // get the current pixel
  37.                         uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  38.                         uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  39.                         uint blue = currentPixel & 0x000000ff; // blue color component
  40.  
  41.                         red = (uint)Math.Max(0, Math.Min(255, (red + m_Brightness)));
  42.                         green = (uint)Math.Max(0, Math.Min(255, (green + m_Brightness)));
  43.                         blue = (uint)Math.Max(0, Math.Min(255, (blue + m_Brightness)));
  44.  
  45.                         uint newPixel = 0xff000000 | (red << 16) | (green << 8) | blue; // reassembling each component back into a pixel
  46.                         targetPixels[index] = newPixel; // assign the newPixel to the equivalent location in the output image
  47.                     }
  48.                 }
  49.             }
  50.             rowIndex++;
  51.         });
  52.     }
  53. }
623 page views in the last 30 days.