How can I import Array<Mat> to JNI from Kotlin? - c++

I am new to coding JNI,
So my problem was when I debugging my Android Studio, it gives me this "error: call to implicitly-deleted copy constructor of 'cv::Mat'"
And I don't know why. I guess it's because of how I try to change from 'jlong' and 'jlongArray' to 'Mat'. My native-lib.cpp was:
JNIEXPORT jlong JNICALL
Java_com_android_example_panoramacamera_fragments_CameraFragment_imagesPass(JNIEnv *env,
jobject thiz, jlongArray image_in_,
jlong image_out_) {
// TODO: implement imagesPass()
Stitcher::Mode mode = Stitcher::PANORAMA;
Mat *image_in = (Mat*) image_in_, *image_out = (Mat*) image_out_;
// Create a Stitcher class object with mode panoroma
Ptr<Stitcher> stitcher = Stitcher::create(mode, false);
// Command to stitch all the images present in the image array
Stitcher::Status status = stitcher->stitch(*image_in, *image_out);
if(status == Stitcher::OK){
return (jlong) image_out;
}
}
And my kotlin-sript:
private external fun imagesPass(imageIn: LongArray, imageOut: Long): Long
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == pickImageCode && resultCode == RESULT_OK) {
if (data != null) {
if (data.clipData != null) {
val count = data.clipData!!.itemCount
for (i in 0 until count) {
val imageUri = data.clipData!!.getItemAt(i).uri
val imageStream: InputStream? = context?.contentResolver?.openInputStream(imageUri)
val bitmap = BitmapFactory.decodeStream(imageStream)
val mat = Mat()
Utils.bitmapToMat(bitmap, mat)
imagesMat[i] = mat
}
}
else {
val imageUri = data.data
val imageStream: InputStream? = imageUri?.let { context?.contentResolver?.openInputStream(it) }
val bitmap = BitmapFactory.decodeStream(imageStream)
val mat = Mat()
Utils.bitmapToMat(bitmap, mat)
imagesMat[0] = mat
}
}
for(i in imagesMat.indices){
longArray[i] = imagesMat[i].nativeObj
}
long = imagesPass(longArray, imageStitch.nativeObj)
imageStitch = Mat(long)
}
super.onActivityResult(requestCode, resultCode, data)
}
and as you can see, I have tried to import Mat but since jni.h is so limited in its language, I have to convert my Mat to Long so I can use both Array and Mat.
But then my "opencv2/core/mat.inl.hpp" start to show error:
inline Mat _InputArray::getMat(int i) const
{
if( kind() == MAT && i < 0 )
return *(const Mat*)obj; //This line gets error
return getMat_(i);
}
So my question is how can I convert from jlongArray to InputArray? or How can I import Array to JNI from Kotlin?
Thank you very much.

Doing something like
Mat *image_in = (Mat*) image_in_
is incorrect code. For all practical purposes, always treat all JNI objects as opaque objects, making no assumption as to how they store the actual underlying data and instead use the JNI APIs to manipulate these objects, including retrieving the actual data from them. A jlongArray is not equivalent to something like jlong array[] = {1, 2, 3}.
From what I understand, you need access to the underlying native elements from the Java jlongArray. There are 2 possible options:
Get the backing native elements using GetLongArrayElements(). This provides a native array of jlongs which is valid until ReleaseLongArrayElements() is called.
Create a copy of a range of elements from the jlongArray using GetLongArrayRegion() that provides a copy of the buffer into a jlong* buffer. This buffer's life is not tied to the actual jlongArray. If the elements from this buffer need to be copied back to the original jlongArray then SetLongArrayRegion() can be used.
Once you have access to the native buffers, then they can be used in C++ code as usual like an array of longs.
An example for a solution with the 1st approach would look something like:
Java_com_android_example_panoramacamera_fragments_CameraFragment_imagesPass
(JNIEnv * env, jobject thiz, jlongArray imageIn, jlong ImageOut) {
jsize len = env->GetArrayLength(imageIn);
jlong * nativeImageList = env->GetLongArrayElements(imageIn, NULL);
//Now one can do something like
//Mat* image_in = reinterpret_cast<Mat*>(nativeImageList);
//This should give the native version of images
for(jsize idx = 0; idx < len; idx++) {
std::printf("%zd ", nativeImageList[idx]);
}
std::printf("\n");
//Once done with the array, release it back to JVM
env->ReleaseLongArrayElements(imageIn, nativeImageList, 0);
return ImageOut;
}
In the above piece of code, the nativeImageList is an array of jlong which is equivalent to what was passed in from the Java/Kt layer into longArray. Each of the elements in this nativeImageList will be the same as what was stored with the line
longArray[i] = imagesMat[i].nativeObj
Hence nativeImageList[0] shall be the value of imagesMat[0].nativeObj and so on. This is obviously a handle to the underlying image and can be just used as
Mat* image_in = reinterpret_cast<Mat*>(nativeImageList);
Note the difference from
Mat* image_in = (Mat*) image_in_
Here, the native elements are retrieved and then cast into Mat*, not directly from the jlongArray object.
Reference

Related

returning a char array from android NDK [duplicate]

I am attempting to use the android NDK.
Is there a way to return an array (in my case an int[]) created in JNI to Java? If so, please provide a quick example of the JNI function that would do this.
-Thanks
If you've examined the documentation and still have questions that should be part of your initial question. In this case, the JNI function in the example creates a number of arrays. The outer array is comprised of an 'Object' array creating with the JNI function NewObjectArray(). From the perspective of JNI, that's all a two dimensional array is, an object array containing a number of other inner arrays.
The following for loop creates the inner arrays which are of type int[] using the JNI function NewIntArray(). If you just wanted to return a single dimensional array of ints, then the NewIntArray() function is what you'd use to create the return value. If you wanted to create a single dimensional array of Strings then you'd use the NewObjectArray() function but with a different parameter for the class.
Since you want to return an int array, then your code is going to look something like this:
JNIEXPORT jintArray JNICALL Java_ArrayTest_initIntArray(JNIEnv *env, jclass cls, int size)
{
jintArray result;
result = (*env)->NewIntArray(env, size);
if (result == NULL) {
return NULL; /* out of memory error thrown */
}
int i;
// fill a temp structure to use to populate the java int array
jint fill[size];
for (i = 0; i < size; i++) {
fill[i] = 0; // put whatever logic you want to populate the values here.
}
// move from the temp structure to the java structure
(*env)->SetIntArrayRegion(env, result, 0, size, fill);
return result;
}
if someone would like to know how to return String[] array:
java code
private native String[] data();
native export
JNIEXPORT jobjectArray JNICALL Java_example_data() (JNIEnv *, jobject);
native code
JNIEXPORT jobjectArray JNICALL
Java_example_data
(JNIEnv *env, jobject jobj){
jobjectArray ret;
int i;
char *message[5]= {"first",
"second",
"third",
"fourth",
"fifth"};
ret= (jobjectArray)env->NewObjectArray(5,
env->FindClass("java/lang/String"),
env->NewStringUTF(""));
for(i=0;i<5;i++) {
env->SetObjectArrayElement(
ret,i,env->NewStringUTF(message[i]));
}
return(ret);
}
from link:
http://www.coderanch.com/t/326467/java/java/Returning-String-array-program-Java
Based on the asked question, this is already explained in the first answer that how can we pass int[] via jobjectArray. But Here is an example how we can return a jobjectArray which contains lists of data. This can be helpful for situations for example: when someone needs to return data in 2D format to draw some line with x and y points. The below example shows how a jobjectArray can return data in the form of following format:
Java input to the JNI:
Array[Arraylist of x float points][Arraylist of y float points]
JNI output to java:
jobjectArray[Arraylist of x float points] [Arraylist of y float points]
extern "C" JNIEXPORT jobjectArray JNICALL
_MainActivity_callOpenCVFn(
JNIEnv *env, jobject /* this */,
jobjectArray list) {
//Finding arrayList class and float class(2 lists , one x and another is y)
static jclass arrayListCls = static_cast<jclass>(env->NewGlobalRef(env->FindClass("java/util/ArrayList")));
jclass floatCls = env->FindClass("java/lang/Float");
//env initialization of list object and float
static jmethodID listConstructor = env->GetMethodID(arrayListCls, "<init>", "(I)V");
jmethodID alGetId = env->GetMethodID(arrayListCls, "get", "(I)Ljava/lang/Object;");
jmethodID alSizeId = env->GetMethodID(arrayListCls, "size", "()I");
static jmethodID addElementToList = env->GetMethodID(arrayListCls, "add", "(Ljava/lang/Object;)Z");
jmethodID floatConstructor = env->GetMethodID( floatCls, "<init>", "(F)V");
jmethodID floatId = env->GetMethodID(floatCls,"floatValue", "()F");
//null check(if null then return)
if (arrayListCls == nullptr || floatCls == nullptr) {
return 0;
}
// Get the value of each Float list object in the array
jsize length = env->GetArrayLength(list);
//If empty
if (length < 1) {
env->DeleteLocalRef(arrayListCls);
env->DeleteLocalRef(floatCls);
return 0;
}
// Creating an output jObjectArray
jobjectArray outJNIArray = env->NewObjectArray(length, arrayListCls, 0);
//taking list of X and Y points object at the time of return
jobject xPoint,yPoint,xReturnObject,yReturnObject;
//getting the xList,yList object from the array
jobject xObjFloatList = env->GetObjectArrayElement(list, 0);
jobject yObjFloatList = env->GetObjectArrayElement(list, 1);
// number of elements present in the array object
int xPointCounts = static_cast<int>(env->CallIntMethod(xObjFloatList, alSizeId));
static jfloat xReturn, yReturn;
jobject xReturnArrayList = env->NewObject(arrayListCls,listConstructor,0);
jobject yReturnArrayList = env->NewObject(arrayListCls,listConstructor,0);
for (int j = 0; j < xPointCounts; j++) {
//Getting the x points from the x object list in the array
xPoint = env->CallObjectMethod(xObjFloatList, alGetId, j);
//Getting the y points from the y object list in the array
yPoint = env->CallObjectMethod(yObjFloatList, alGetId, j);
//Returning jobjectArray(Here I am returning the same x and points I am receiving from java side, just to show how to make the returning `jobjectArray`)
//float x and y values
xReturn =static_cast<jfloat >(env->CallFloatMethod(xPoint, floatId,j));
yReturn =static_cast<jfloat >(env->CallFloatMethod(yPoint, floatId,j));
xReturnObject = env->NewObject(floatCls,floatConstructor,xReturn);
yReturnObject = env->NewObject(floatCls,floatConstructor,yReturn);
env->CallBooleanMethod(xReturnArrayList,addElementToList,xReturnObject);
env->CallBooleanMethod(yReturnArrayList,addElementToList,yReturnObject);
env->SetObjectArrayElement(outJNIArray,0,xReturnArrayList);
env->SetObjectArrayElement(outJNIArray,1,yReturnArrayList);
__android_log_print(ANDROID_LOG_ERROR, "List of X and Y are saved in the array","%d", 3);
}
return outJNIArray;
Simple solution is that write the array data in a file from C,and then access the file from Java

How to pass to image buffer to wasm

I'm trying to pass array buffer js to wasm OpenCV but some times It throws an exception or some time blank array when using imdecode function.
Simple HTML:
<input type='file' id accept='image/*' onchange='openFile(event)'>
Javascript code
var openFile = function (e) {
const fileReader = new FileReader();
fileReader.onload = (event) => {
const uint8Arr = new Uint8Array(event.target.result);
passToWasm(uint8Arr);
};
fileReader.readAsArrayBuffer(e.target.files[0]);
};
function passToWasm(uint8ArrData) {
// copying the uint8ArrData to the heap
const numBytes = uint8ArrData.length * uint8ArrData.BYTES_PER_ELEMENT;
const dataPtr = Module._malloc(numBytes);
const dataOnHeap = new Uint8Array(Module.HEAPU8.buffer, dataPtr, numBytes);
dataOnHeap.set(uint8ArrData);
// calling the Wasm function
const res = Module._image_input(dataOnHeap.byteOffset, uint8ArrData.length);
}
C++ code:
extern "C"
{
int image_input(uint8_t* buffer, size_t nSize) //query image input
{
Mat raw_data = cv::Mat(1, nSize, CV_8UC1, buffer);
img_object = cv::imdecode(raw_data, cv::IMREAD_UNCHANGED);
cout << img_object << endl;
return 1
}
}
Please help me I have spent many days to solve this problem.
I'm trying the same with the help of the following question.
How to pass image frames camera to a function in wasm (C++)?
malloc() and HEAP8.set() can be used to achieve that. Surma's article describes how to do this indetail. Another example is this decode() function, which sets values in Wasm from a JavaScript ArrayBuffer.

Golang CGO with large char pointer - SEGSERV

I have a large amount of data being read from TagLib library and passed to GoLang (mpeg image data).
Here is where data is fetched:
void audiotags_mpeg_artwork(TagLib::MPEG::File *mpegFile, int id) {
TagLib::ID3v2::Tag *id3v2 = mpegFile->ID3v2Tag(false);
if (id3v2!=nullptr) {
const TagLib::ID3v2::FrameList frameList = id3v2->frameListMap()["APIC"];
for(auto it = frameList.begin(); it != frameList.end(); it++) {
TagLib::ID3v2::AttachedPictureFrame * frame = (TagLib::ID3v2::AttachedPictureFrame *)(*it);
if (frame!=nullptr && frame->size() > 0) {
const auto &apicBase64 = frame->picture().toBase64();
auto len = apicBase64.size();
if (len > 0) {
// Generate memory for key
char* key = new char[5];
memcpy(key, "APIC", 4);
key[4]='\0';
// Generate memory for picture data
char* val = new char[len];
memcpy (val, apicBase64.data(), len);
// Send to GoLang
go_map_audiotags(id, key, val);
// Free memory
delete[] key;
delete[] val;
}
}
}
}
}
At this point, go_map_autotags works (I use a similar method for other data). This also works for other picture data, however depending on the size this will crash with:
unexpected fault address 0x766a000
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x1 addr=0x766a000 pc=0x404530b]
Within GoLang, I have the following export:
//export go_map_audiotags
func go_map_audiotags(id C.int, key *C.char, val *C.char) {
m := maps[int(id)]
k := strings.ToLower(C.GoString(key))
log.Println("go_map_audiotags k:", k) // <--- works
v := C.GoString(val) // <--- crashes
log.Println("go_map_audiotags v:", v) // <--- Does not reach
m[k] = v
}
Is there a bette way I should be transporting this data? I assume what's happening is:
1) The C.char limit be being reached
2) C++ is, for some reason, recycling the memory before setting v in GoLang
The data stored in val is not null-terminated. In your C code, when you make a copy using memcpy, the null terminator is not included. In the C code, change the code to:
// Generate memory for picture data
char* val = new char[len+1];
memcpy (val, apicBase64.data(), len);
val[len] = '\0';

Exc access error

Very new to C++ and having problems returning a vector. I put a breakpoint and the array is correct (populated with all the objects I would expect from the query). But when it returns I get an error:
EXC_BAD_ACCESS
on line m_pComponentContainer->removeAll();
from CCNode.cpp
Which is strange since this is a base class (does NOT inherit from any kind of CC object) although I am extensively using the Cocos2dx framework, its not included in this class.
Im fairly sure this is because something is being deallocated. However like I said Im very new to C++ and not really sure where the problem is. I was hoping to get a little further in development before I had to start worrying about memory management.
int numberOfCards = DatabaseHelper::getNumberOfCards();
//cant be zero
assert(numberOfCards);
std::vector<CardSlot> returnArray(numberOfCards);
sqlite3_stmt * statement;
if (sqlite3_open(this->dbpath.c_str(),&this->cardWarsDB) == SQLITE_OK)
{
const char* query_stmt = "select ID, HP, MP, AbilityText from Cards WHERE ID IN (SELECT DISTINCT cardsID FROM Deck WHERE name = 'All')";
if (sqlite3_prepare_v2(this->cardWarsDB, query_stmt, -1, &statement, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
CardSlot *aCard;
const char* cardID = (const char*)sqlite3_column_text(statement, 0);
const char* cardHP = (const char*)sqlite3_column_text(statement, 1);
const char* cardMP = (const char*)sqlite3_column_text(statement, 2);
const char* cardAbility = (const char*)sqlite3_column_text(statement, 3);
if (cardID != NULL) {
std::string imageName = ".png";
imageName = cardID + imageName;
aCard = (CardSlot *)CardSlot::spriteWithFile(imageName.c_str());
}
if (cardID != NULL) {
aCard->cardID = std::string(cardID);
cocos2d::CCLog("DB returned results, cardID: %s",aCard->cardID.c_str());
}
if (cardHP != NULL) {
aCard->cardHP = std::string(cardHP);
cocos2d::CCLog("DB returned results, cardHP: %s",aCard->cardHP.c_str());
}
if (cardMP != NULL) {
aCard->cardMP = std::string(cardMP);
cocos2d::CCLog("DB returned results, cardMP: %s",aCard->cardMP.c_str());
}
if (cardAbility != NULL) {
aCard->cardAbility = std::string(cardAbility);
cocos2d::CCLog("DB returned results, cardAbility: %s",aCard->cardAbility.c_str());
}
numberOfCards--;
returnArray[numberOfCards] = *aCard;
}
sqlite3_finalize(statement);
}
sqlite3_close(this->cardWarsDB);
return returnArray;
}
Here is a screenshot of the stack trace. I was just looking at it, and it seems that it is the CardSlot objects are the culprits.
But still dont know how to "retain" them, but Ill look at some Cocos documentation.
NOTE1
It looks like your CardSlot is not safe to copy. You copy CardSlots in at least two places:
aCard = * CardSlot::spriteWithFile(imageName.c_str()); (also a memory leak assuming spriteWithFile returns CardSlot *; the "temporary" is not destructed)
returnArray[numberOfCards] = aCard;
From what I can tell, you are probably keeping a CCSprite pointer in CardSlot and destroying it (with delete) in your CardSlot destructor. However, this pointer gets destroyed multiple times because of the copies, which causes your crash.
You need to redesign your class so it can either be safely copied, or refactor your code so that you make no copies (e.g. by using a vector<shared_ptr<CardSlot> > to hold pointers to the instances).
I have edited the code to use more pointers rather then passing around and filling my array with objects. However I think a big thing that helped fix it was using cocos2d::CCArray instead of a std::vector. Most of my classes are children of Cocos2d classes (CCSprites, and CCLayers) and so using its own array data type makes sense.
cocos2d::CCArray DatabaseHelper::getAllCards()
{
int numberOfCards = DatabaseHelper::getNumberOfCards();
//cant be zero
assert(numberOfCards);
cocos2d::CCArray returnArray(numberOfCards);
sqlite3_stmt * statement;
if (sqlite3_open(this->dbpath.c_str(),&this->cardWarsDB) == SQLITE_OK)
{
const char* query_stmt = "select ID, HP, MP, AbilityText from Cards WHERE ID IN (SELECT DISTINCT cardsID FROM Deck WHERE name = 'All')";
if (sqlite3_prepare_v2(this->cardWarsDB, query_stmt, -1, &statement, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
CardSlot* aCard;
const char* cardID = (const char*)sqlite3_column_text(statement, 0);
const char* cardHP = (const char*)sqlite3_column_text(statement, 1);
const char* cardMP = (const char*)sqlite3_column_text(statement, 2);
const char* cardAbility = (const char*)sqlite3_column_text(statement, 3);
if (cardID != NULL) {
std::string imageName = ".png";
imageName = cardID + imageName;
aCard = CardSlot::spriteWithFile(imageName.c_str());
}
if (cardID != NULL) {
aCard->cardID = std::string(cardID);
cocos2d::CCLog("DB returned results, cardID: %s",aCard->cardID.c_str());
}
if (cardHP != NULL) {
aCard->cardHP = std::string(cardHP);
cocos2d::CCLog("DB returned results, cardHP: %s",aCard->cardHP.c_str());
}
if (cardMP != NULL) {
aCard->cardMP = std::string(cardMP);
cocos2d::CCLog("DB returned results, cardMP: %s",aCard->cardMP.c_str());
}
if (cardAbility != NULL) {
aCard->cardAbility = std::string(cardAbility);
cocos2d::CCLog("DB returned results, cardAbility: %s",aCard->cardAbility.c_str());
}
numberOfCards--;
returnArray.addObject(aCard);
}
sqlite3_finalize(statement);
}
sqlite3_close(this->cardWarsDB);
return returnArray;
}
//incase sql fails, close db and created a "FAILED" card
sqlite3_close(this->cardWarsDB);
cocos2d::CCLog("DB returned error: cant open char catagories file");
cocos2d::CCArray failedReturnArray(1);
CardSlot * aCard;
aCard->cardID = std::string("FAILED");
aCard->cardHP = std::string("FAILED");
aCard->cardMP = std::string("FAILED");
aCard->cardAbility = std::string("FAILED");
failedReturnArray.addObject(aCard);
return failedReturnArray;
}
Also in case anyone cares here is CardSlot (not much to it, only built the constructor at this time):
CardSlot * CardSlot::spriteWithFile(const char *pszFileName)
{
CCLOG("CardSlot::spriteWithFile");
CardSlot * aCard = new CardSlot();
if (aCard && aCard->initWithFile(pszFileName))
{
aCard->cardID = pszFileName;
aCard->scheduleUpdate();
aCard->autorelease();
return aCard;
}
CC_SAFE_DELETE(aCard);
return NULL;
}
The only thing Im concerned about is that I think my CCArray should be a pointer. But its working now and learning all the memory management "tricks of the trade" will come in time, the more I work with C++
Thanks #nneonneo for all the help Im sure your fix would have worked and I tried but no matter what I did couldnt get the vector to work. I 1up'd you as much as I could but really this is the "Answer" I implemented.
The returnArray is declared local to the function, it will be deallocated when the function returns. You would need to either declare it as static or move the declaration to outside the function.

JNI: How to convert a group of data from c++ to Java

I'm trying to send some data from c++ to java using JNI.
In c++ I have:
Array[0]:
string name = "myName"
int iterations = 16
float value = 15
... etc
So I want to use JNI to return all data on Java, I'm trying this, but don't work
JNIEXPORT jobjectArray JNICALL Java_com_testing_data_MainActivity_getDATA(JNIEnv *env, jobject obj)
{
// 1º Create a temp object
jobject dataClass
{
jstring name;
jint iterations;
jfloat value;
};
jobject tempObject = env->NewObject();
// Get data in c++ format int temp object type
std::vector<dataClass > data = getDataClass(); // First error, must be a c++ class, how could i get it?
// How much memory i need?
int dataSize = data.size();
// Reserve memory in java format
jint tempValues[dataSize];
jobjectArray tempArray = env->NewObjectArray(dataSize,dataClass,0); // 2º Error, it doesn 't create the class
// Temporal store data in jarray
for (int i = 0; i < dataSize ; i++)
{
tempArray[i].name = data[i].name;
tempArray[i].iterations = data[i].iterations;
tempArray[i].value = data[i].value;
}
return tempArray; // return temp array
}
Are correct this steps to return a structure/object with data? How is possible to fix the errors?
Converting everything to JNI types is not a good idea. Generally, it is better to create a peer object, i.e. a handle pointer to the native resource - like the hWnd in the Windows GUI Programming.
You can use a string to store all data as a sequence data. Field are separated by a separator (":" for example), like this:
std::string sequenceData = "my name" + ":" + "16" + ":" + "15" + ...;
Pass this sequence to java then split it to get desired value. Use String.split() or StringTokenizer.