0

I am working on a simple image processing tool that should be able to apply filters on a given image. I was wondering how I would solve this in a functional way with lambda expressions and streams.

I wrote a method that returns a kernel as ArrayList with argb values, that can then be further processed depending on which filter I'd like to apply. For example if I'd like to apply a box filter, I'd have to sum all elements and divide it by the number of elements. But of course I have to do this with all rgb values separately.

I used to get the values as followed:

// Get rgb values:  
    r = (argb >> 16) & 0xff;  
    g = (argb >> 8) & 0xff;  
    b = argb & 0xff;  
// Write back to argb:  
    argb = (0xFF << 24) | (r << 16) | (g << 8) | b;

But I am not sure how I would write this with streams, or what to insert here to split the values and process them independently and put the results back together.

IntStream.range(0, src.length).parallel().forEach(i ->
    dst[i] = getKernelValues(3, src, i, width, height).stream()
                    .mapToInt(Integer::intValue)
                    .sum() / 9);

What I wrote here is totally wrong, because it takes the whole argb value. But maybe you guys can help me to change that pice to a working example I can use for other filters later.

Thanks in advance.

edit:
In the meantime I got roundabout the problem by splitting the values as followed. This works, but still has duplicated code. If someone knows how to this in less code, I'd be happy to know.

Thanks anyway.

IntStream.range(0, src.length).parallel().forEach(i -> {
    ArrayList<Integer> values = getKernelValues(filterSize, src, i, width, height);
        int r = values
            .stream()
            .map(j -> (j >> 16) & 0xff)
            .reduce(0, (j, k) -> j + k);
        r /= (filterSize * filterSize);

        int g = values
            .stream()
            .map(j -> (j >> 8) & 0xff)
            .reduce(0, (j, k) -> j + k);
        g /= (filterSize * filterSize);

        int b = values
            .stream()
            .map(j -> j & 0xff)
            .reduce(0, (j, k) -> j + k);
        b /= (filterSize * filterSize);

        dst[i] = 0xff << 24 | r << 16 | g << 8 | b;
});
amaridev
  • 31
  • 4
  • What is wrong with your current code? – Tunaki Apr 17 '16 at 14:56
  • As mentioned in the last paragraph getKernelValues() returns an ArrayList of argb integer values. But of course you need to process all color channels separately. – amaridev Apr 17 '16 at 14:59
  • It's still not clear what kind of output you're expecting. Can you post an example? – Tunaki Apr 17 '16 at 15:01
  • Hmm, still not sure how I can explain it any better. Maybe it helps to give an example what values I get from getKernelValues(). This example is for a 9x9 filter size at a random position in my picture. int: -8224126 /// hex: 0xff828282 int: -8092540 /// hex: 0xff848484 int: -8224126 /// hex: 0xff828282 int: -8224126 /// hex: 0xff828282 int: -8092540 /// hex: 0xff848484 int: -8224126 /// hex: 0xff828282 int: -8224126 /// hex: 0xff828282 int: -8092540 /// hex: 0xff848484 int: -8224126 /// hex: 0xff828282 Sorry, it seams enter does not work here – amaridev Apr 17 '16 at 15:28

1 Answers1

0

It sounds to me you need to average all of the r values, g values, and b values. If I'm correct, the first thing to note is that you can't sum each value within its 8 bit space in the int, since the sum will exceed 8 bits before you divide the result by the number of values. So the first thing you need to do is to extract the r, g, and b values into separate integer values to hold the sums. You could create an RGB class with integer fields, but an array of integers will do:

static int[] unpackARGB(int argb){
    return new int[] { (argb >> 16) & 0xff, (argb >> 8) & 0xff, argb & 0xff };
}

static int packARGB(int[] ints){
    return (0xff << 24 + ints[0] & 0xff) << 16 + (ints[1] & 0xff) << 8 + ints[2] & 0xff;
}

You want to use streams, so if we are using an array of ints to do the summing, we will need to convert the stream to a Stream, and reduce the individual items using a summing operation:

static int[] sumRGB(int[] arr1, int[] arr2){
    return new int[] { arr1[0] + arr2[0], arr1[1] + arr2[1], arr1[2] + arr2[2] };
}

Later we will need to divide each value to get it back into its 8 bit range, so this convenience method will help:

static int[] divRGB(int[] arr1, int divisor){
    return new int[] { arr1[0]/divisor, arr1[1]/divisor, arr1[2]/divisor };
}

We can then create a function that will perform the average operation:

public static int averageRGB(IntStream ints, int size) {
    int[] sum = ints
            .parallel()
            .mapToObj(i -> unpackARGB(i))
            .reduce(new int[]{0,0,0}, (a, v) -> sumRGB(a, v));
    return packARGB(divRGB(sum, size));
} 

From your List you could invoke it like this:

int result = averageRGB(integerList.stream().mapToInt(i->i), integerList.size());
Hank D
  • 6,271
  • 2
  • 26
  • 35