1

I am developing an application using JNI and a third party engine (Unreal Engine 4) in charge of managing the graphics pipeline/rendering.

The third party engine is written in C++, thus the need of using JNI to bridge it with Android.

The app requires to save a screenshot on the device of what is being displayed on the screen (in other words a dump of the framebuffer). The third party engine exposes an API that calls a custom handler, passing in the width, height and color data of the screen. colordata is a custom container of uint8 representing RGBA components.

I successfully managed to convert the colorData to a jbyteArray and pass it as an argument to a function on the JAVA side. On the java side things are simpler: I create a bitmap from the byteArray, flip it and save it as a jpg/png via a custom AsyncTask.

The problem: The code works marvellously o Samsung Galaxy S4/Note3 (Both Android 5.0), whereas on a Nexus 10 Android version 5.1.1 the png that gets saved is blank. I am afraid that the problem with this lies on a depper level than the ones I have access to, i.e. graphics card/drivers/OS version, but I am not an expert in that field, so I would like to know if someone has already experienced a similar issue or could shed some light on what is causing it.

This is the code used to bridge the engine with Java (I started c++ with this project so maybe there are ownership/memory issues in this snippet. You are more than welcome to correct me in case :))

void AndroidInterface::SaveBitmap(const TArray<FColor>& colorData, int32 width, int32 height) {

    JNIEnv* env = FAndroidApplication::GetJavaEnv(true);

    TArray<FColor> bitmap = colorData;

    TArray<uint8> compressedBitmap;

    FImageUtils::CompressImageArray(width, height, bitmap, compressedBitmap);


    size_t len = width*height*compressedBitmap.GetTypeSize();

    LOGD("===========Width: %i, height: %i - Len of bitmap element: %i==========", width, height, len);

    jbyteArray bitmapData = env->NewByteArray(len);

    LOGD("===========Called new byte array==========");

    env->SetByteArrayRegion(bitmapData, 0, len, (const jbyte*)compressedBitmap.GetData() );

    LOGD("===========Populated byte array==========");

    check (bitmapData != NULL && "Couldn't create byte array");

    jclass gameActivityClass = FAndroidApplication::FindJavaClass("com/epicgames/ue4/GameActivity");
    check (gameActivityClass != nullptr && "GameActivityClassNotFound");

    //get the method signature to take a game screenshot
    jmethodID saveScreenshot = env->GetMethodID(gameActivityClass, "saveScreenshot", "([BII)V");

    env->CallVoidMethod(AndroidInterface::sGameActivity, saveScreenshot, bitmapData, width, height);

    env->DeleteLocalRef(bitmapData);

}

This is the java code in charge of converting from byte[] to Bitmap:

public void saveScreenshot(final byte[] colors, int width, int height) {

            android.util.Log.d("GameActivity", "======saveScreenshot called. Width: " + width + " height: " + height + "=======");
            android.util.Log.d("GameActivity", "Color content---->\n " + Arrays.toString(colors));
            final BitmapFactory.Options opts = new BitmapFactory.Options();
            opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
            final Bitmap bitmap = BitmapFactory.decodeByteArray(colors, 0, colors.length, opts);

            final FlipBitmap flipBitmapTask = new FlipBitmap();
            flipBitmapTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, bitmap);

        }

FlipBitmap is the AsyncTask in charge of saving the bitmap to a file:

private class FlipBitmap extends AsyncTask<Bitmap, Void, File> {

        @Override
        protected File doInBackground(Bitmap... params) {
            final Bitmap src = params[0];
            final File file = new File(MainActivity.SCREENSHOT_FOLDER + "screenshot" + System.currentTimeMillis() + ".png");
            final Matrix matrix = new Matrix();
            matrix.setScale(1, -1);  
            final Bitmap dst = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, false);
            try {
                final FileOutputStream out = new FileOutputStream(file);
                dst.compress(Bitmap.CompressFormat.PNG, 90, out);
                out.flush();
                out.close();   
            } catch (Exception e) {
                e.printStackTrace();
            }

            return file;
        }

        @Override
        protected void onPostExecute(File file) {
            android.util.Log.d("GameActivity", "FlipBitmap onPostExecute");

            if (file.exists()) {
                final Intent i = new Intent(Intent.ACTION_SENDTO);
                i.setData(Uri.parse("mailto:" + Globals.Network.MAIL_TO));
                i.putExtra(Intent.EXTRA_SUBJECT, Globals.Network.MAIL_SUBJECT);
                i.putExtra(Intent.EXTRA_TEXT, mBodyEmail);
                i.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + file.getAbsolutePath()));
                startActivity(Intent.createChooser(i, "Invia via email"));
            }

        }
    }

Thanks in advance!

Luigitni
  • 572
  • 10
  • 18
  • You've shown code for converting `byte[]` to Bitmap, and saving the Bitmap to a file. At which stage are you holding nothing but zeroes? Looks like you're dumping the `byte[]` to the log; have you manually checked the contents of the Bitmap? – fadden Oct 20 '15 at 16:42
  • @fadden thanks for the reply. You are right, sorry I didn't mention it. The `byte[]` is actually non all zeroed out (some of its content in the last log: `[-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 3, -103, 0, 0, 2, 64, 8, 6, 0, 0, 0, 7, 16, -94, 117, .....]`). After the call to `BitmapFactory.decodeByteArray` though the bitmap is `sameAs` an empty Bitmap of the same sizes. That's why I am afraid it depends on the underlying implementation of `decodeByteArray`... – Luigitni Oct 20 '15 at 17:39
  • That looks like the right magic number for a PNG in your `byte[]`. I would have expected `decodeByteArray()` to throw an exception if the PNG data were bad. – fadden Oct 20 '15 at 18:10
  • @fadden sorry for the late reply. So I further investigated the issue and opened a "report" on the Unreal Engine 4 Answer Hub ([here](https://answers.unrealengine.com/questions/320165/nexus-10-blank-screenshot.html)), thinking it could be related to the engine. They saw the whole code but couldn't test it on a Nexus 10 device. According to them there seems to be a glitch or an error with the `glReadPixels` of the Nexus 10. – Luigitni Nov 04 '15 at 17:28
  • `glReadPixels()` worked fine on the Nexus 10 last I tried it. I think it's more likely that your original assessment was correct, and there's a bug in the game engine, but either way you're stuck if UE4 is sending you blank frames. – fadden Nov 04 '15 at 17:55

0 Answers0