qoi-java icon indicating copy to clipboard operation
qoi-java copied to clipboard

Question: Not worth to use QOI decoding over PNG decoding on Android?

Open AndroidDeveloperLB opened this issue 3 years ago • 8 comments

Whether I use QOIUtil.readFile vs BitmapFactory.decodeFile of Android, or I use QOIUtil.readImage vs BitmapFactory.decodeStream , it seems that Android's implementation is faster to parse PNG files over QOI:

From inputStream of the resource (files are within the app) : QOI took 84 ms PNG took 25 ms

From a file outside the app: QOI took 36 ms PNG took 30 ms

And that's before getting Bitmap instance out of the QOIImage instance, which I don't understand how to do.

Tested on Pixel 4 with Android API 32 on the "wikipedia_008" image files, from here (got from here) : https://qoiformat.org/qoi_test_images.zip

See attached project:

My Application.zip

How could it be? Is there anything that can be improved in the implementation? And how do you convert the output of the function to a working Bitmap instance?

AndroidDeveloperLB avatar Apr 02 '22 20:04 AndroidDeveloperLB

it seems that Android's implementation is faster to parse PNG files over QOI

I don't know exactly, but most probably Android's PNG decoder is a native library, whereas qoi-java decoder is a Java library. To get expected performance boost from using QOI, it would make more sense to use original C library or one of its optimized implementations like in Rust.

Note that I've compared qoi-java with Java AWT PNG parser, and it mostly does make sense, because AWT's PNG parser is mostly written in Java.

Is there anything that can be improved in the implementation?

Java implementation of QOI is already optimized well (although I have some ideas, but implementing them will not give much performance boost). To decode even faster, as I've said above, you need to use native libraries.

Most performant version of QOI decoder should be written in a native language (C/Rust/etc.) and output an int array, suitable to be passed into Bitmap#createBitmap.

And how do you convert the output of the function to a working Bitmap instance?

You need to get RGB(A) values from a QOIImage and set pixels on a Bitmap: https://stackoverflow.com/a/59788350

saharNooby avatar Apr 03 '22 08:04 saharNooby

Have you tested it using C library instead of Java (on Android) that you say it's faster? If so, can you please share the code you've used? Maybe it's better to have a library for Android based on this, instead. How did you measure?

As I remember, on new Android versions, the ART makes Java/Kotlin as good as C/C++ in terms of performance, usually. Here: https://source.android.com/devices/tech/dalvik/jit-compiler They also keep improving Java/Kotlin performance on every version of Android. So I don't think it's a valid argument anymore (on new Android versions).

Have you tested the conversion to Android's Bitmap object?

AndroidDeveloperLB avatar Apr 03 '22 09:04 AndroidDeveloperLB

Have you tested it using C library instead of Java (on Android) that you say it's faster?

No, I just guess -- I assume that C is faster than Java, but of course I can't argue for or against it because no becnhmarks were done.

Have you tested the conversion to Android's Bitmap object?

No -- I'm not an Android developer and do not have Android development environment set up, so I can't do benchmarks/tests. But the conversion is so simple that I do not think anything needs to be tested here -- you can just try.

saharNooby avatar Apr 03 '22 09:04 saharNooby

I see.

Can you please try to have a sample that runs on Java, but uses the C/C++ library you talked about? You can compare what you did here to this one of using C/C++ on a PC instead of Android. I'm sure that Java on PC got improved in speed too, and not just what Google did on Android.

As for the conversion from QOIImage to Bitmap, this worked for me:

fun convertQoiImageToBitmap(qoiImage: QOIImage): Bitmap {
    val width = qoiImage.width
    val height = qoiImage.height
    val bytes = qoiImage.pixelData
    val hasAlpha = qoiImage.channels == 4
    val pixels = IntArray(if (hasAlpha) bytes.size / 4 else bytes.size / 3)
    var j = 0
    for (i in pixels.indices) {
        val red: Int = bytes[j++].toInt() and 0xff
        val green: Int = bytes[j++].toInt() and 0xff
        val blue: Int = bytes[j++].toInt() and 0xff
        val alpha: Int = if (hasAlpha) bytes[j++].toInt() and 0xff else 0xff
        val pixel = alpha shl 24 or (red shl 16) or (green shl 8) or blue
        pixels[i] = pixel
    }
    val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
    return bitmap
}

Now I calculated including the conversion to Bitmap. Somehow for the same input files the time taken has changed (probably some app was/is running in the background, or something) :

From inputStream of the resource (files are within the app) : QOI took 61 ms PNG took 31 ms

From a file outside the app: QOI took 38 ms PNG took 27 ms

Still slower than PNG, of course.

Attached here:

My Application.zip

AndroidDeveloperLB avatar Apr 03 '22 10:04 AndroidDeveloperLB

Can you please try to have a sample that runs on Java, but uses the C/C++ library you talked about?

Probably I can, using JNI/JNR (although there will be overhead, it would be interesting to see). But I can not say for now when I will have time to do it.

saharNooby avatar Apr 04 '22 10:04 saharNooby

Can you please create a Java JNI wrapper and see if it performs better than what you did here?

AndroidDeveloperLB avatar Apr 04 '22 11:04 AndroidDeveloperLB

Can you please create a Java JNI wrapper and see if it performs better than what you did here?

Yea, I can, it's just that I'm busy with my job and don't know when I will have time to work on QOI.

saharNooby avatar Apr 04 '22 12:04 saharNooby

OK thanks. I'm just curious. :)

AndroidDeveloperLB avatar Apr 04 '22 12:04 AndroidDeveloperLB