1

I have 2 bitmaps of exactly the same size and I want to find the smallest area of change between the two. Here's a Kotlin equivalent of what I am doing:

var minX = Int.MAX_VALUE
var minY = Int.MAX_VALUE
var maxX = 0
var maxY = 0

for (i in 0 until cols) {
    for (j in 0 until rows) {
        if (bitmapOne.getPixel(i, j) != bitmapTwo.getPixel(i, j)) {
            if (i < minX) minX = i
            if (i > maxX) maxX = i
            if (j < minY) minY = j
            if (j > maxY) maxY = j
        }
    }
}

All I need is the four points of rectangle holding the smallest area of change. Renderscript bitmap iterations are way faster based on some tests I did, so I am trying to learn Renderscript and port this Kotlin code too.

The issue I am facing is that I am unable to pass 2 bitmaps as allocations to any mapping kernel. The documentation says

"If you need more input or output Allocations than the kernel has, those objects should be bound to rs_allocation script globals and accessed from a kernel or invokable function via rsGetElementAt_type() or rsSetElementAt_type()."

But there are not enough examples around this.

The second issue is how to get a return type, since none of the kernels have a return type - the input/output allocation examples I see so far are all of same dimensions. But I need a different dimension as output (just an Int4).

On going through the reduce kernel documentation, it seems like they too can't process 2 allocations together.

I may be wrong in my understanding so far. Would appreciate any help which can get me started.

dev
  • 11,071
  • 22
  • 74
  • 122

1 Answers1

2

Script globals will definitely help solve your situation. Essentially, they are script variables which the Java/Kotlin side can access to set or get. If you are able to use Android 7+ only, you could do this as a reduction kernel (rather than a mapping kernel). Basically, there's no "output" allocation.

Here's a quick write up of doing this via mapping kernel. I've not tried or compiled this, so you may have to tweak it. Assuming you have your two Bitmap objects and they are the same size/format (no error handling here to keep it short), and are working in an Activity, you could set it up like this:

//  You should only create the Rendescript context and script one
//  time in the lifetime of your Activity.  It is an expensive op.
Renderscript rs = Renderscript.create(this);
ScriptC_diffBitmap script = new ScriptC_diffBitmap(rs);

Allocation inAlloc1 =
    Allocation.createFromBitmap(rs,
                                bitmap1,
                                Allocation.MipmapControl.MIPMAP_NONE,
                                Allocation.USAGE_SCRIPT);
Allocation inAlloc2 =
    Allocation.createFromBitmap(rs,
                                bitmap2,
                                Allocation.MipmapControl.MIPMAP_NONE,
                                Allocation.USAGE_SCRIPT);
Allocation outAlloc = Allocation.createTyped(rs,
                                             inAlloc2.getType());
script.set_maxX(0);
script.set_maxY(0);
script.set_minX(Int.MAX_VALUE);
script.set_minY(Int.MAX_VALUE);
script.set_inBitmap1(inAlloc1);
script.foreach_root(inAlloc2, outAlloc);

//  Get back the min/max values and do whatever you need
minX = script.get_minX();
minY = script.get_minY();
maxX = script.get_maxX();
maxY = script.get_mayY();

The Rendescript code to support this (again, using a mapping kernel), named diffBitmap.rs:

#pragma version(1)
#pragma rs java_package_name(com.example.DiffBitmap)

int32_t minX;
int32_t minY;
int32_t maxX;
int32_t maxY;
rs_allocation inBitmap1;

uchar4 RS_KERNEL root(uchar4 inBitmap2Point, uint32_t x, uint32_t y)
{
    uchar4 inBitmap1Point = rsGetElementAt_uchar4(inBitmap1, x, y);

    if ((inBitmap1Point.r != inBitmap2Point.r) ||
        (inBitmap1Point.g != inBitmap2Point.g) ||
        (inBitmap1Point.b != inBitmap2Point.b) ||
        (inBitmap1Point.a != inBitmap2Point.a))
    {
        if (x < minX) minX = x;
        if (x > maxX) maxX = x;
        if (y < minY) minY = y;
        if (y > maxY) maxY = y;
    }

    //  Since we have to return an output, just return bitmap1
    return inBitmap1Point;
}
Larry Schiefer
  • 15,687
  • 2
  • 27
  • 33
  • Thanks for the detailed response. But can you please try compiling it? The first line in the root() function is causing an error as described here: https://stackoverflow.com/questions/46074004/renderscript-not-matching-function-rsgetelementat-uchar4. I am unable to use the proposed solution because unless the script compiles and Java stub is created, I dont get access to the allocation setter. I have tried building with renderscriptTargetApi 24 and 26. – dev Jan 09 '18 at 19:45
  • Why do we use a uchar4 variable with the same name as rs_allocation (inBitmap1)? Wouldn't that confuse the compiler whenever we use inBitmap1? I tried changing the function variable uchar 4 from inBitmap1 to temp, but compilation failed in the if condition with error "statement requires expression of scalar type. vector of 4 'char' values invalid" – dev Jan 09 '18 at 19:51
  • 1
    Yeah, that's a typo on my part and the `if` check was just a quick and dirty attempt from memory. Adjusted above, try that. – Larry Schiefer Jan 09 '18 at 20:55
  • It compiles now, but returns the min/max values as defined originally. I assume rsForEach calls are async in nature, so I also added rs.finish() before using getters in the Java code. Still no luck. For test I am using a plain red image from the web (bitmap1) and adding a small green rectangle at center using image editing tools (bitmap2) – dev Jan 09 '18 at 22:45
  • Yeah, you may need to call `finish()` for the script before calling the getters for the script variables. You will likely need to add some instrumentation via [rsDebug](https://developer.android.com/guide/topics/renderscript/reference/rs_debug.html#android_rs:rsDebug) calls to see what is going on. Just be sure to remove them before you ship as that call has a huge impact on performance. – Larry Schiefer Jan 09 '18 at 23:46
  • Will debug for now. If I find a working solution, I will update your answer and mark it resolved. Thanks! – dev Jan 10 '18 at 01:06