Search code examples
androidopengl-esjava-native-interfacestb-image

Create bitmap on Java and send it to C++ using JNI and render it by OpenGL, black


On Android, I use JNI to send a string from C++ to Java then draw it to a bitmap then return the byte array back to C++ to draw it to OpenGL, I get a black rectangle .

First I create the bitmap in Java by this way:

public static byte[] DrawString(String text) {
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(Color.rgb(110, 110, 110));
    float scale = ActivityGame.getCurrentActivity().getResources().getDisplayMetrics().density;
    paint.setTextSize((int) (24 * scale));
    Rect bounds = new Rect();
    paint.getTextBounds(text, 0, text.length(), bounds);

    Bitmap bitmap = Bitmap.createBitmap((int) (bounds.width() * scale), (int) (bounds.height() * scale), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    int x = (bitmap.getWidth() - bounds.width()) / 6;
    int y = (bitmap.getHeight() + bounds.height()) / 5;
    canvas.drawText(text, x * scale, y * scale, paint);

    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
    byte[] byteArray = stream.toByteArray();
    bitmap.recycle();
    return byteArray;
}

then in C++ I call the previous function by JNI by this way:

static unsigned char* DrawString(std::string text,int* length){
    JNIEnv *env= nullptr;
    JNIUtils::JVM->AttachCurrentThread(&env, NULL);
    jclass jniUtilsCls = env->FindClass("com/moussa/mightypolygons/JNIUtils");
    jmethodID drawStringMethodId = env->GetStaticMethodID(jniUtilsCls, "DrawString", "(Ljava/lang/String;)[B");
    jstring _text=env->NewStringUTF(text.c_str());
    jbyteArray bmpArr=(jbyteArray)env->CallStaticObjectMethod(jniUtilsCls,drawStringMethodId,_text);
    jsize num_bytes = env->GetArrayLength( bmpArr);
    jbyte* elements = env->GetByteArrayElements(bmpArr, NULL);
    unsigned char* bmp=new unsigned char[num_bytes];
    if (elements) {
        for(int i = 0; i < num_bytes; i++) {
            bmp[i] = elements[i];
        }
        *length=(int)num_bytes;
        return bmp;
    }
    return NULL;
}

Finally I get the char array and the length now I create a texture in OpenGL:

Texture::Texture(unsigned char *bmp,int len, TextureTypes type) {
image = stbi_load_from_memory(bmp,len, &width, &height, &nrComponents, 4);
glGenTextures(1, &textureObj);
if (image) {
    GLenum format;
    if (nrComponents == 1)
        format = GL_RED;
    else if (nrComponents == 3)
        format = GL_RGB;
    else if (nrComponents == 4)
        format = GL_RGBA;
    else
        format = GL_RGBA;
    glBindTexture(GL_TEXTURE_2D, textureObj);
    glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
                    format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
                    format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    stbi_image_free(image);
} else {
    stbi_image_free(image);
}
}

I get a black rectangle with same width and height of the bitmap.

Edit:

I saved the bitmap as PNG from Java side then reloaded it from C++ it was rendered perfect with same code, so is the problem is in bytes array or GL parameters or something else?

Edit 2:

I noticed in Java the ByteArray contains signed bytes with negative values when it is cast to unsigned char* in C++ it value changed, for example the first byte in the bytes array in Java is -119 it becomes 137 if cast to unsigned char


Solution

  • I used a different method to send the Bitmap object as it is from Java and read its pixels using android/bitmap.h

    AndroidBitmapInfo androidBitmapInfo ;
    void* pixels;
    AndroidBitmap_getInfo(env, bitmap, &androidBitmapInfo);
    AndroidBitmap_lockPixels(env, bitmap, &pixels);
    unsigned char* pixelsChar = (unsigned char*) pixels;
    AndroidBitmap_unlockPixels(env, bitmap);
    

    it works fine now.