Skip to content
Tubeman, The edited this page Apr 17, 2016 · 1 revision

Better operators + 2D filtering support

Looking at the Nokia PureView Imaging Competition page, I noticed that a lot of contributions were about image effects very similar to what imagelib does today. Unfortunately, most of the time, the code submitted was neither clean nor efficient… Which reminded me of some my own code in imagelib!

The cute and inefficient filter function

My old filter function in imagelib is based around a callback class that is called once for every pixel: :

// ImageFxUtils.java 
public interface ImageFilter
{
    int apply(int [][]pixels, int offset, int x, int y);
}    

If you recall from my previous articles, an ImageFilter subclass is feed into the following function which then magically handles the all boring details for you and all you have to do is to crunch pixels (one at a time) in your subclass: :

Image ImageFxUtils.applyFilter(Image image, int filter_w, 
int filter_h, ImageFxUtils.ImageFilter filter) 

While this was very nice and clean, it wasn't implemented very efficiently. I decided to re-write this code, which eventually resulted in a new interface.

A better filter interface

To speed up things, I decided to change the ImageFilter interface such that each call can process a complete scanline instead of a single pixel. While that worked fine (and hopefully performed better), it didn’t felt quite as clean as the old one-call-per-pixel code. So I decided to extend the API to support both variants.

So now you have a class (not interface) you must sub-class, where you must override one of the two existing functions. If you use the first one, you will work one per-line basis while the second one is the old per-pixel function: :

// ImageFxUtils.java
public static class ImageFilter
{
    public void apply(int [][]pixels, int []output, int offset, int count, int y)
    {
        for(int i = 0; i < count; i++)  
            output[i] = apply(pixels, offset + i, i, y);
    }
    public int apply(int [][]pixels, int offset, int x, int y)
    {
        return 0;
    }
}

I have also improved the memory usage of the filter function by allocating a few small buffers instead of one giant bitmap, but that change is not visible at API level.

An example…

Let me show you how per-line part of the new interface works. Assume you have a 1x10 pixel picture (i.e. it contains only one line), and a 3x3 filter (yes, I know this is an stupid example):

input

Now, the algorithm will call your filter function once (since there is only one line) with the following arguments: :

apply(pixels, output, 1, 10, 0);

Where the buffers have the following arrangement:

args

Notice that the input buffers (pixels) has been enlarged to match filter dimensions (the added pixels will contain zeros). This should significantly simplify your filter code.

So how does your filter work with this? Well, for a simple 2D matrix would mean that for each output pixels (starting at position 0) you compute the sum of the input (starting at position 0,0) multiplied with the filter matrix:

pixel1

Implementing 2D filters

One of the main uses of the filter interfaces was to implement 2D filters. So I decided to make everyones life simpler by throwing in a support function that did all the matrix multiplication work for you: :

// ImageFxUtils.java
public static Image applyFilter(Image image, final int [][] multiplier, final int divider)

So how does this work? Well, you just construct your filter matrix and call this function :) There is a tiny little twist here, since we are working with integers, your matrix is composed of two parts: an integer multiplier and an integer divider. For example, consider the following filter: :

[  0   -1/4  0   ]
[ -1/4  2   -1/4 ]
[  0   -1/4  0   ]

This must be re-written as :

[  0  -1  0  ]
[ -1   8 -1  ] / 4
[  0  -1  0  ]

This is of course DSP 101 stuff, so I guess you wont have any problems with it. Anyway, the actual function call will look like this then: :

int [][] matrix  = {                
    {  0, -1,  0 },
    { -1,  8, -1 },
    {  0, -1,  0 } 
};
img_filtered = ImageFxUtils.applyFilter(iimage_source, matrix, 4);

This makes using imagelib filters very easy. Beware however that the filter function currently has terrible performance :(

The pixel modifier

The pixel modifier is basically a 1x1 version of the filter function. I decided to update that one too. The new pixel modifier interface now looks like this: :

// ImageFxUtils.java
public static class PixelModifier
{
    public void apply(int [] pixel, int [] output, int count, int y)
    {
        for(int i = 0; i < count; i++)
            output[i] = apply(pixel[i], i, y);
    }
    public int apply(int pixel, int x, int y) 
    { 
        return pixel; 
    }
}

Again, you sub-class PixelModifier and then override one of the two functions.

Color temperature functions

While I was updating the filter functions, I decided to also add color temperature functions. Since we already have color curve transformation functions, I just added a wrapper function that calls it with correct parameters. The new function looks like this: :

// ImageFxUtils.java
public static Image transformARGB(Image image, int delta_alpha, 
    int delta_red, int delta_green, int delta_blue);

It simply creates linear maps for ARGB components with an offset of given delta and passes those to the old transformARGB function. The results looks like this:

color

img_warm = transformARGB(img_original, 0, 30, 30, 0) // warm
img_cold = transformARGB(img_original, 0, 0, 0, 30) // cold

Summary

Here is a quick summary of the latest changes to the library:

The following functions have been added :

// ImageFxUtils.
public static Image applyFilter(Image image, final int [][] multiplier, final int divider)
public static Image transformARGB(Image image, int delta_alpha, int delta_red, 
    int delta_green, int delta_blue)

The following interfaces have been updated :

//ImageFxUtils.java
public static class ImageFilter
public static class PixelModifier

And thats it! Enjoy your new and improved filter function. And go make a great mobile app and sell it to Facebook for 1 billion dollars for :)