net.hydex11.opencvinteropexample.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for net.hydex11.opencvinteropexample.MainActivity.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 - Alberto Marchetti <alberto.marchetti@hydex11.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.hydex11.opencvinteropexample;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v8.renderscript.*;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;

import org.opencv.android.Utils;
import org.opencv.core.Mat;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/*
What this sample project does is:
    
1. Loads a custom drawable file, bundled together with the app, inside a Bitmap.
2. Creates an OpenCV mat from the Bitmap.
3. Instantiates a RenderScript allocation, which points to the OpenCV mat data pointer.
4. Instantiates another RS allocation, which points to another OpenCV mat, to be used as output.
5. Executes a kernel over the input allocation.
6. Converts the OpenCV output allocation to a Bitmap. **Note:** the output allocation is DIRECTLY
bound to the output OpenCV allocation because they share the same memory address.
    
Note: please, refer to the `README.md`, which comes together with the example, to have it work
correctly. It requires you to download the OpenCV SDK.
    
 */
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        System.loadLibrary("opencv_java3");

        example();
    }

    /*
    The concept of this example is based upon the RS ability to instantiate an Allocation using
    a user-provided data pointer.
        
    The ability to support a user-provided data pointer is dependent upon the RS device-specific
    driver. When tested on a Galaxy Note 3 (Android 5.1), it didn't work.
    There is a high chance that later versions of Android provide good support for this functionality.
        
    This functionality works by enabling the RS support library at the cost of using a
    non-performant RS driver.
        
    ---
        
    The process of binding a RS allocation to a user-provided pointer can be achieved by directly
    calling the native RS function `RenderScript.nAllocationCreateTyped` and passing it, as last
    argument, the pointer address to the user data (in this case, the OpenCV mat data pointer).
        
    This process, however, requires the usage of Java reflection:
        
    1) Retrieve the RenderScript.nAllocationCreateTyped hidden method.
    2) Invoke it to obtain an Allocation pointer.
    3) Instantiate a new Allocation class, using the hidden Allocation() constructor.
        
     */
    private void example() {
        RenderScript mRS = RenderScript.create(this);

        // Loads input image
        Bitmap inputBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.houseimage);

        // Puts input image inside an OpenCV mat
        Mat inputMat = new Mat();
        Utils.bitmapToMat(inputBitmap, inputMat);

        Mat outputMat = new Mat(inputMat.size(), inputMat.type());

        // Testing bitmap, used to test that the OpenCV mat actually has bitmap data inside
        Bitmap initialBitmap = Bitmap.createBitmap(inputMat.width(), inputMat.height(), Bitmap.Config.ARGB_8888);
        Utils.matToBitmap(inputMat, initialBitmap);

        // Retrieve OpenCV mat data address
        long inputMatDataAddress = inputMat.dataAddr();
        long outputMatDataAddress = outputMat.dataAddr();

        // Creates a RS type that matches the input mat one.
        Element element = Element.RGBA_8888(mRS);
        Type.Builder tb = new Type.Builder(mRS, element);
        tb.setX(inputMat.width());
        tb.setY(inputMat.height());

        Type inputMatType = tb.create();

        // Creates a RenderScript allocation that uses directly the OpenCV input mat address
        Allocation inputAllocation = createTypedAllocationWithDataPointer(mRS, inputMatType, inputMatDataAddress);
        Allocation outputAllocation = createTypedAllocationWithDataPointer(mRS, inputMatType, outputMatDataAddress);

        // Define a simple convolve script
        // Note: here, ANY kernel can be applied!
        ScriptIntrinsicConvolve3x3 convolve3x3 = ScriptIntrinsicConvolve3x3.create(mRS, element);

        float convolveCoefficients[] = new float[9];
        convolveCoefficients[0] = 1;
        convolveCoefficients[2] = 1;
        convolveCoefficients[5] = 1;
        convolveCoefficients[6] = 1;
        convolveCoefficients[8] = 1;
        convolve3x3.setCoefficients(convolveCoefficients);

        convolve3x3.setInput(inputAllocation);
        convolve3x3.forEach(outputAllocation);

        mRS.finish();

        // Converts the result to a bitmap
        Bitmap cvOutputBitmap = Bitmap.createBitmap(outputMat.width(), outputMat.height(), Bitmap.Config.ARGB_8888);
        Utils.matToBitmap(outputMat, cvOutputBitmap);

        // Testing bitmap, used to test the RenderScript ouput allocation contents
        // Note: it is placed here because the copyTo function clears the input buffer
        Bitmap rsOutputBitmap = Bitmap.createBitmap(outputMat.width(), outputMat.height(), Bitmap.Config.ARGB_8888);
        outputAllocation.copyTo(rsOutputBitmap);

        // Testing bitmap, used to test that RenderScript input allocation pointed to the OpenCV mat
        // Note: it is placed here because the copyTo function clears the input buffer
        Bitmap rsInitialBitmap = Bitmap.createBitmap(inputMat.width(), inputMat.height(), Bitmap.Config.ARGB_8888);
        inputAllocation.copyTo(rsInitialBitmap);

        // Display input and output
        ImageView originalImageIV = (ImageView) findViewById(R.id.imageView);
        ImageView inputRSImageIV = (ImageView) findViewById(R.id.imageView2);
        ImageView outputRSImageIV = (ImageView) findViewById(R.id.imageView3);
        ImageView outputCVIV = (ImageView) findViewById(R.id.imageView4);

        originalImageIV.setImageBitmap(initialBitmap);
        inputRSImageIV.setImageBitmap(rsInitialBitmap);
        outputRSImageIV.setImageBitmap(rsOutputBitmap);
        outputCVIV.setImageBitmap(cvOutputBitmap);

    }

    // Reflection side, used to create a custom allocation that targets a certain data pointer

    // Uses reflection to extract the method used to create a RS allocation in the C++ side.
    //
    // synchronized long nAllocationCreateTyped(long type, int mip, int usage, long pointer)
    //
    Method getnAllocationCreateTyped() {
        Method nAllocationCreateTyped = null;
        try {
            nAllocationCreateTyped = RenderScript.class.getDeclaredMethod("nAllocationCreateTyped", long.class,
                    int.class, int.class, long.class);
            nAllocationCreateTyped.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Could not access nAllocationCreateTyped method");
        }

        return nAllocationCreateTyped;
    }

    // Get Allocation private constructor
    Constructor<Allocation> getAllocationConstructor() {
        // Allocation(long id, RenderScript rs, Type t, int usage)
        Constructor<Allocation> constructor;

        try {
            constructor = Allocation.class.getDeclaredConstructor(long.class, RenderScript.class, Type.class,
                    int.class);
            constructor.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Could not access nAllocationCreateTyped method");
        }

        return constructor;
    }

    // Gets a RenderScript type native pointer
    long getTypePointer(Type rsType) {
        Field typeIDField = null;
        long TypeID = 0;
        try {
            typeIDField = rsType.getClass().getSuperclass().getDeclaredField("mID");
            typeIDField.setAccessible(true);
            TypeID = (long) typeIDField.getLong(rsType);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Could not access Type ID");
        } catch (NoSuchFieldException e) {
            throw new RuntimeException("Could not find Type ID");
        }

        if (TypeID == 0) {
            throw new RuntimeException("Invalid rsType ID");
        }
        return TypeID;
    }

    // Mimic the Allocation.createTypedAllocationWithDataPointer method
    public Allocation createTypedAllocationWithDataPointer(RenderScript mRS, Type type, long dataPtr) {
        Allocation allocation = null;

        try {

            long typePointer = getTypePointer(type);

            int usage = Allocation.USAGE_SCRIPT | Allocation.USAGE_SHARED;

            Object idObj = getnAllocationCreateTyped().invoke(mRS, typePointer, 0, usage, dataPtr);

            long id = (long) idObj;

            if (id == 0) {
                throw new RSRuntimeException("Allocation creation failed.");
            }

            // Allocation(long id, RenderScript rs, Type t, int usage)
            allocation = getAllocationConstructor().newInstance(id, mRS, type, usage);

        } catch (InvocationTargetException e) {
            throw new RuntimeException("Could not create new Allocation");
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Could not access Allocation constructor");
        } catch (InstantiationException e) {
            throw new RuntimeException("Could not instantiate new Allocation");
        }
        return allocation;
    }
}