After Nokia Imaging SDK graduates from beta, custom filter introduced. This is most exciting feature, that allow us to create our own filters. In this article, I'll be explaining how to implement custom filters for different purposes by comparing differences in implemantation that described in Core Concepts document.
Sample Filters are implemented for my framework which is described in "Filter and Custom Filter Management Framework for The Nokia Imaging SDK". So that some of complex filters may not be working well without my framework. But it's simple to fix, I'll be explaining in the article.
I'm categorizing my sample custom filters in 3 category; calculation purpose, simple ones and complex ones. I'll try to explain over sample filters.
HistogramFilter
is the only sample for this type of filter. This filters calculates Red, Green, Blue and Luminance of image. These parameters can be used as filter parameters then.
Trick is here, IsInPlace
property in base constructor is set to true, so that no need to set targetPixelRegion.ImagePixels.
public class HistogramFilter : CustomEffectBase
{
public int[] Red { get; set; }
public int[] Green { get; set; }
public int[] Blue { get; set; }
public int[] Luminance { get; set; }
public HistogramFilter(IImageProvider source, out int[] red, out int[] green, out int[] blue, out int[] luminance)
: base(source, true)
{
Red = new int[byte.MaxValue + 1];
Green = new int[byte.MaxValue + 1];
Blue = new int[byte.MaxValue + 1];
Luminance = new int[byte.MaxValue + 1];
red = Red;
green = Green;
blue = Blue;
luminance = Luminance;
}
protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
{
sourcePixelRegion.ForEachRow((index, width, pos) =>
{
for (int x = 0; x < width; ++x, ++index)
{
Color c = ToColor(sourcePixelRegion.ImagePixels[index]);
Red[c.R]++;
Green[c.G]++;
Blue[c.B]++;
double luminance = 0.299 * c.R + 0.587 * c.G + 0.114 * c.B;
if (luminance < byte.MinValue)
luminance = byte.MinValue;
else if (luminance > byte.MaxValue)
luminance = byte.MaxValue;
Luminance[(byte)luminance]++;
}
});
}
IsInPlace
property is true here. Which meanssourcePixel
andtargetPixel
is same array. Documentation says that it's more efficient. But you have to be careful if you want to process pixel and don't want to change source pixel.
ThresholdFilter
is one of the sample for this type of filter. It's the same implementation as described here
public class ThresholdFilter : CustomEffectBase
{
private byte _thresholdR = 0;
private byte _thresholdG = 0;
private byte _thresholdB = 0;
public ThresholdFilter(IImageProvider source, byte R, byte G, byte B)
: base(source)
{
_thresholdR = R;
_thresholdG = G;
_thresholdB = B;
}
protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
{
sourcePixelRegion.ForEachRow((index, width, pos) =>
{
for (int x = 0; x < width; ++x, ++index)
{
Color c = ToColor(sourcePixelRegion.ImagePixels[index]);
if (c.R < _thresholdR)
c.R = 0;
if (c.G < _thresholdG)
c.G = 0;
if (c.B < _thresholdB)
c.B = 0;
targetPixelRegion.ImagePixels[index] = FromColor(c);
}
});
}
}
IsInPlace
property didn't set here. Default value of it is false. Which meanssourcePixel
,targetPixel
is different array and you have to get pixel fromsourcePixel
, process it and set totargetPixel
. Also note that If you don't settargetPixel
, you'll see black image.
PixelInterpolation
is one of the sample for this type of filter. This filter pixelates image without changing the image size. It divides to image to rectangles like table and get top left pixel of each cell. Then changes every pixels in cell to corner. For that purpose, iteration every pixels with ForEachRow
is not efficient. First of all, use following to get width and height of an image.
int width = (int)sourcePixelRegion.Bounds.Width;
int height = (int)sourcePixelRegion.Bounds.Height;
Then, it iterates only corner pixels.
int offsetX = _pixelSize / 2;
int offsetY = _pixelSize / 2;
for (int x = 0; x < width; x += _pixelSize)
{
for (int y = 0; y < height; y += _pixelSize)
{
//calculation here
}
}
Final code looks as below
public class PixelInterpolation : CustomEffectBase
{
private int _pixelSize = 0;
public PixelInterpolation(IImageProvider source, int PixelSize)
: base(source)
{
_pixelSize = PixelSize;
}
protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
{
int offsetX = _pixelSize / 2;
int offsetY = _pixelSize / 2;
int width = (int)sourcePixelRegion.Bounds.Width;
int height = (int)sourcePixelRegion.Bounds.Height;
for (int x = 0; x < width; x += _pixelSize)
{
for (int y = 0; y < height; y += _pixelSize)
{
// make sure that the offset is within the boundry of the image
while (x + offsetX >= width) offsetX--;
while (y + offsetY >= height) offsetY--;
// get the pixel color in the center of the soon to be pixelated area
var pixel = sourcePixelRegion.ImagePixels[DimensionConverter.To1D(x + offsetX, y + offsetY, width)];
// for each pixel in the pixelate size, set it to the center color
for (int i = x; i < x + _pixelSize && i < width; i++)
for (int j = y; j < y + _pixelSize && j < height; j++)
targetPixelRegion.ImagePixels[DimensionConverter.To1D(i, j, width)] = pixel;
}
}
}
}
ForEachRow
method inPixelRegion
class used for iterate over the pixels consecutively. If you want to iterate every pixels consecutively, don't use for loop,ForEachRow
method is the most efficient way. Unless you want to iterate some pixels, not all of them or multiple iteration for each pixel as shown in the code above, use for loop.
IsInPlace
property didn't set here. Default value of it is false. Which meanssourcePixel
,targetPixel
is different array and you have to get pixel fromsourcePixel
, process it and set totargetPixel
. Also note that If you don't settargetPixel
, you'll see black image.
QuadTransformation
filter is transforms quadratic shape to rectangular form.
As you seen in the image, result will be smaller than input image. But custom filter implementation doesn't allow us to change output size. I solved that problem in my framework by using IPreProcess and IPostProcess interfaces.
public class QuadTransformation : CustomEffectBase, IPostProcess
{
.
.
protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
{
.
.
}
public FactoryBase PostProcessFactory()
{
var factory = new NokiaFilterFactory(null);
factory.Filters.Add(new ReframingFilter(new Rect(0, 0, Size.Width, Size.Height), 0));
return factory;
}
}
With this implemantation, after QuadTranformation
processed, ReframingFilter
applied to image.
To use this custom filter without my framework, you've to build pipeline between QuadTranformation
and ReframingFilter
as below.The result will be the as same as with using my framework.
var customFilter = new QuadTransformation(source, new Windows.Foundation.Size(300, 300), QuadDirection.QuadToRect, EdgePoints);
var effects = new FilterEffect(customFilter);
effects.Filters = new IFilter[]
{
new ReframingFilter(new Rect(0, 0, Size.Width, Size.Height), 0))
};
var renderer = new WriteableBitmapRenderer(effects, outputBitmap);
await renderer.RenderAsync();
ThresholdFilter
ColorPaletteFilter
Pixel Interpolation
QuadTransformation (gets quadratic shape using corner points, then transforms shape to rectangular form)
CornerDetection (detects corners of quadratical shape in black&white image)
HistogramFilter (calculates Red, Green, Blue and Luminance data for histogram of image)
Histogram filter doesn't render image, it's for calculation purpose.
As you've seen in article, it's easy to implement your own custom filters. I implemented some filters which are used for image processing and I'll constantly adding new filters for that purpose. If you request me a custom filter implementation, please add a comment. I'll try my best and also you can contribute to project, follow github page.