×
Namespaces

Variants
Actions

Custom Filter QuickStart for Nokia Imaging SDK

From Nokia Developer Wiki
Jump to: navigation, search
Featured Article
18 May
2014

This article provides a quick start for creating custom filters using the Nokia Imaging SDK.

Announcements.pngNokia Original Imaging Effect Wiki Challenge (21 Jul 2014): Winners have been announced for the Nokia Original Imaging Effect Wiki Challenge. The Tweet-Off to decide which authors win a Nokia Lumia 630 starts today. Follow the Nokia Developer Twitter feed and retweet your favorite entries.

Note.pngNote: This article received Honorable Mention in the Nokia Imaging and Big UI Wiki Competition 2013Q4.

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 (04 Jul 2014)

Contents

Introduction

This article is designed to quickly get users up and running creating custom filters using the Nokia Imaging SDK.

The first section shows how to quickly set up a framework in which you can create and test custom filters, while the following sections provide a walk through of the the main boilerplate code in a filter, and an overview of the other fundamental concepts needed to understand how custom filters work.

The concepts are then applied to create progressively more complex filters.

Setup basic custom filter framework

The custom framework is based on the Real-time Filter Demo example in the Lumia Developers' Library. We use this because it already has the necessary plumbing in place for us to insert our own custom filters.

  1. First we'll start by loading up the real-time-filter-demo-v1.1.zip project
  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 CustomEffect.cs and replace the contents of the for loop in the OnProcess method with this line: targetPixels[index] = sourcePixels[index]; as shown below.
    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. Of course the image isn't actually changed, because all we're doing is copying pixels from the in input to the output.

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.

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

Working with Colors

Now we are finally ready to start playing with pixels - which are supplied to the custom filter in the only supported color format: ColorMode.Bgra8888.

The filter below simply pulls out each ARGB component of the pixel and then reassembles 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. }


Note.pngNote: Above we use right and left shifting to extract each of the colour components.

An alternative is to use the more intuitive but less efficient approach of calling ToColor() and FromColor() methods from CustomEffectBase. The same code as above can be written as:

  1. for (int x = 0; x < width; ++x, ++index)
  2. {
  3.        Color c = ToColor(sourcePixels[index]);
  4.        // access individual colour components using c.A, c.R, c.G, c.B
  5.        targetPixels[index] = FromColor(c);
  6. }
As a comparison point, the bit-shift code renders about 16-18fps, while the ToColor()/FromColor() solution achieves 11-12fps on the same device. The rest of this article uses the bit-shift approach.


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.

Note.pngNote: If modulo is throwing you off, you can view many other explanations of it here.

  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. });

The above code definitely does the job of pixelating the image, but there's also a problem; it's not very scalable. What if we want to do a smaller 2 row by 2 column area or a larger 4 row by 4 column area? We could add a bunch of case statements and turn this thing into a real mess, but we should probably go another route. Let's work through this challenge to improve our pixel map navigation skills.

First, let's see another view of the top left section of the pixel map based on pixel size. As you can see from the images below, we expand out an additional row and column as we increase the scale factor of a pixel. This makes it easy for us to use our skip row/column technique above and just have the processEveryNthRow and processEveryNthColumn parameters both equal to whatever pixel scale size we choose.

Next, we need a couple of formulas that will be necessary each time we stop to process another pixel block. We need to identify the center pixel for the block, as well as the very top left pixel of the block.

  • S >>> Every row/column to stop and process, and in our current scenario, will also be the same value as the scale factor.
  • C >>> Every row/column back (offset) from the processing stop (S), which we need to run through our FindIndex method to get the center pixel.
  • T >>> Every row/column back (offset) from the processing stop (S), which we need to run through our FindIndex method to get the top left pixel.
  • C = -(S - 1) / 2
  • T = -(S - 1)

Note.pngNote: The result for C will be a decimal for any even blocks like 2x, 4x, etc. You can either round up or down, because they are both technically in the center. For our code, we'll just let the result get automatically converted to an integer, and let the framework pick the direction.

Finally, we need to double loop from the top left (T) back down to the bottom right where we stopped (S). We just increment the row and column offsets (T) until we finish off the assignments. Let's take a look at the new scalable code below that will efficiently pixelate an image.

  1.         int scale = 3;
  2.         scale = (scale <= 0) ? 1 : scale; // Protect against divide by zero
  3.         int processEveryNthRow = scale;
  4.         int processEveryNthColumn = scale;
  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.                         // Get the center pixel for the given scale we are working with, and manipulate as desired
  18.                         int centerRowOffset = -1 * ((processEveryNthRow - 1) / 2); // C = -(S - 1) / 2
  19.                         int centerColumnOffset = -1 * ((processEveryNthColumn - 1) / 2); // C = -(S - 1) / 2
  20.                         uint targetPixel = sourcePixels[FindIndex(rowIndex, x, width, centerRowOffset, centerColumnOffset)]; 
  21.  
  22.                         // Get the top left position of the pixel block, given the current scale
  23.                         int topRowOffset = -1 * (processEveryNthColumn - 1); // T = -(S - 1)
  24.                         int leftColumnOffset = -1 * (processEveryNthColumn - 1); // T = -(S - 1)
  25.  
  26.                         // Loop from the top left position down to the bottom right, where we stopped to process
  27.                         for (int y1 = topRowOffset; y1 <= 0; y1++)
  28.                         {
  29.                             for (int x1 = leftColumnOffset; x1 <= 0; x1++)
  30.                             {
  31.                                 targetPixels[FindIndex(rowIndex, x, width, y1, x1)] = targetPixel;
  32.                             }
  33.                         }
  34.                     }
  35.                 }
  36.             }
  37.             rowIndex++;
  38.         });

Let's keep the momentum and move on to another clever pixel map manipulation technique, the MirrorFilter. If you've ever wondered how they achieve the mirror effect in the SDK, then you're in luck, we're about to unlock the mystery. Again, let's start with a visual representation of the pixel indexes.

CustomFilters MirrorPixelMap.PNG

As we can see, the left half of the image is exactly the same. It's not until we reach the center that we have to start making changes, so you can probably guess that we'll need to make note of the row midpoint value. Additionally, we can see that as we move further to the right of the row, past the midpoint, we are decrementing from the index. We continue this pattern until the end of the row when we are finally back down to our original index from the start of the row. Let's continue on to the formulas we'll need to code up this solution.

  • H >>> Horizontal midpoint of a row
  • W >>> Row width
  • x >>> Column index
  • c >>> Column indexes back (offset) from the current index
  • H = (W / 2)
  • c = ((x - H) * 2) - 1


Finally, let's see what the code looks like with our formulas. Remember, we only need to do our special manipulation after the midpoint, so we'll use a simple if statement to determine when we've crossed that point in the row.

  1. sourcePixelRegion.ForEachRow((index, width, position) =>
  2. {
  3.     int horizontalMidPoint = width / 2; // get the horizontal midpoint >>> H = (W / 2)
  4.  
  5.     for (int x = 0; x < width; ++x, ++index)
  6.     {
  7.         if (x < horizontalMidPoint)
  8.         {
  9.             // Just keep the first half of the row as is
  10.             targetPixels[index] = sourcePixels[index];
  11.         }
  12.         else
  13.         {
  14.             // Now we start repeating the mirror image from the first half of the row >>> index - (((x - H) * 2) - 1) 
  15.             targetPixels[index] = sourcePixels[index - ((x - horizontalMidPoint) * 2) - 1];               
  16.         }
  17.     }  
  18. });

Okay, this is cool, but let's take it a step further, and go beyond the out-of-the-box filter in the SDK. Let's do a vertical mirror image. As before, let's work through the formulas first. The approach is very similar to the horizontal method, but the key difference is that each new row increases the index count by the row width; we do not reset to zero with each new row, like we do when iterating the columns.

  • P >>> Total pixels in our image array
  • W >>> Row width
  • L >>> Column length
  • V >>> Vertical midpoint of a column
  • i >>> Row index
  • r >>> Row indexes back (offset) from the current index
  • L = (P / W)
  • V = (L / 2)
  • r >>> ((i - V) * 2 * W) - 1


Finally, let's put it all together in the code below.

  1. int rowIndex = 0;
  2. sourcePixelRegion.ForEachRow((index, width, position) =>
  3. {
  4.     int verticalMidPoint = (sourcePixels.Length / width) / 2; // get the vertical midpoint >>> L = (P / W) >>> V = (L / 2)
  5.  
  6.     for (int x = 0; x < width; ++x, ++index)
  7.     {
  8.         if (rowIndex < verticalMidPoint)
  9.         {
  10.             // Just keep the first half of the column as is
  11.             targetPixels[index] = sourcePixels[index];
  12.         }
  13.         else
  14.         {
  15.             // Now we start repeating the mirror image from the first half of the column >>> index - (((i - V) * 2 * W) - 1) 
  16.             targetPixels[index] = sourcePixels[index - ((rowIndex - verticalMidPoint) * 2 * width) - 1];
  17.         }
  18.     }
  19.     rowIndex++;
  20. });

Working with Multiple Effects

At this point we now have a nice set of custom filters in our portfolio, but we want to do more with our filters. We don't want to just show one filter at a time. We want to combine filters, like you can do with the built-in filters in the SDK. Better yet, we want to combine our custom filters with the built-in filters.

Remember back at the beginning when we were discussing the boiler plate code. The one required input parameter of a custom effect is of type IImageProvider, which just happens to be one of the interfaces that the CustomEffectBase class implements. Since we've been inheriting from CustomEffectBase all along, that means we are already set up to pass the resulting image from one of our custom filters as an input to any other custom filter!

Create some individual custom filter classes using the code snippets above or the ones in Category:Nokia Imaging SDK Custom Effect. Then go to the switch statement in the Initialize() method of the NokiaImagingSDKEffects.cs class and scroll down to the case 10: tier. Here we will see the simplest form of custom filter linking.

  1. ...
  2. case 10:
  3.     {
  4.         EffectName = String.Format(nameFormat, 11, AppResources.Filter_Custom);
  5.         //_customEffect = new CustomEffect(_cameraPreviewImageSource);
  6.  
  7.         // Send the result of your first custom filter effect into a variable of type IImageProvider
  8.         IImageProvider imageEffect = new MirrorEffect(_cameraPreviewImageSource);
  9.  
  10.         // Pass the variable above as the input to the next custom filter effect, and assign the result to the same variable
  11.         // Repeat this same process for any other filters you want to chain before getting to the last effect.
  12.         imageEffect = new Negative(imageEffect);
  13.  
  14.         // With the last effect in your chain, just use it like you did before with 1 filter, but instead use the IImageProvider
  15.         // variable you've been manipulating above as your final filter effect's input.
  16.         _customEffect = new Pixelate(imageEffect, 5, 0);
  17.     }
  18.     break;
  19. ...

What about chaining the built-in filters as well? That's easy too, because the FilterEffect class, which is used with the built-in filters, also implements the IImageProvider interface. Add the built-in filters below to create a simplistic kaleidoscope like effect.

  1. ...
  2. case 10:
  3.     {
  4.         EffectName = String.Format(nameFormat, 11, AppResources.Filter_Custom);
  5.         //_customEffect = new CustomEffect(_cameraPreviewImageSource);
  6.  
  7.         // Send the result of your first custom filter effect into a variable of type IImageProvider
  8.         IImageProvider imageEffect = new MirrorEffect(_cameraPreviewImageSource);
  9.  
  10.         // Pass the variable above as the input to the next custom filter effect, and assign the result to the same variable
  11.         // Repeat this same process for any other filters you want to chain before getting to the last effect.
  12.         imageEffect = new Negative(imageEffect);
  13.  
  14.         // FilterEffect also implements IImageProvider, so the built-in filters are just another link in the chain
  15.         // Here are examples of adding 1 built-in filter at a time.
  16.         imageEffect = new FilterEffect(imageEffect) { Filters = new List<IFilter>() { new AutoEnhanceFilter() } };
  17.         imageEffect = new FilterEffect(imageEffect) { Filters = new List<IFilter>() { new MagicPenFilter() } };
  18.  
  19.         // Here is an example of adding a group of built-in filters in one shot.
  20.         List<IFilter> filterList = new List<IFilter>();
  21.         filterList.Add(new RotationFilter(90));
  22.         filterList.Add(new MirrorFilter());
  23.         filterList.Add(new RotationFilter(90));
  24.         imageEffect = new FilterEffect(imageEffect) { Filters = filterList };
  25.  
  26.         // With the last effect in your chain, just use it like you did before with 1 filter, but instead use the IImageProvider
  27.         // variable you've been manipulating above as your final filter effect's input.
  28.         _customEffect = new Pixelate(imageEffect, 5, 0);
  29.     }
  30.     break;
  31. ...

Tip.pngTip: This is how you do it with the raw SDK. If you want to use default and custom filters consistently, one alternative is the Filter and Custom Filter Management Framework for the Nokia Imaging SDK.


Summary

Now that we know how to manipulate colors, efficiently navigate the pixels of an image, and combine multiple filters, the sky is the limit.

For more examples check out the effects in Category:Nokia Imaging SDK Custom Effect (and the Custom Effect Gallery).

This page was last modified on 4 July 2014, at 21:50.
500 page views in the last 30 days.
×