Qt on Android: How to convert Qt images to Android Images and vice-versa
Sometimes we need to exchange images from Android world to Qt world and vice-versa, therefore in this article we’re going to see how to convert a QImage to an Android Bitmap and how to convert an Android bitmap and an Android Drawable into a QImage.
To get access to Android Bitmap data & info we’re going to use NDK’s support, so we need to make sure jnigraphics library is in libraries dependency in our .pro file:
LIBS += -ljnigraphics
Now let’s see what the convert functions looks like:
#include <QAndroidJniEnvironment> #include <QAndroidJniObject> #include <android/bitmap.h> QImage toImage(const QAndroidJniObject &bitmap) { QAndroidJniEnvironment env; AndroidBitmapInfo info; if (AndroidBitmap_getInfo(env, bitmap.object(), &info) != ANDROID_BITMAP_RESULT_SUCCESS) return QImage(); QImage::Format format; switch (info.format) { case ANDROID_BITMAP_FORMAT_RGBA_8888: format = QImage::Format_RGBA8888; break; case ANDROID_BITMAP_FORMAT_RGB_565: format = QImage::Format_RGB16; break; case ANDROID_BITMAP_FORMAT_RGBA_4444: format = QImage::Format_ARGB4444_Premultiplied; break; case ANDROID_BITMAP_FORMAT_A_8: format = QImage::Format_Alpha8; break; default: return QImage(); } void *pixels; if (AndroidBitmap_lockPixels(env, bitmap.object(), &pixels) != ANDROID_BITMAP_RESULT_SUCCESS) return QImage(); QImage image(info.width, info.height, format); if (info.stride == uint32_t(image.bytesPerLine())) { memcpy((void*)image.constBits(), pixels, info.stride * info.height); } else { uchar *bmpPtr = static_cast<uchar *>(pixels); const unsigned width = std::min(info.width, (uint)image.width()); const unsigned height = std::min(info.height, (uint)image.height()); for (unsigned y = 0; y < height; y++, bmpPtr += info.stride) memcpy((void*)image.constScanLine(y), bmpPtr, width); } if (AndroidBitmap_unlockPixels(env, bitmap.object()) != ANDROID_BITMAP_RESULT_SUCCESS) return QImage(); return image; }
This function doesn’t have any JNI magic, and it’s quite simple. We try to find the best QImage::Format (To be honest I’m not very sure about ANDROID_BITMAP_FORMAT_RGBA_4444), then we copy the image data from Android bitmap to our QImage.
Being able to convert any Android Drawable to a QImage is quite useful, therefore, let’s check how to “convert” (actually draw) an Android drawable into a QImage.
QAndroidJniObject createBitmap(int width, int height) { QAndroidJniObject config = QAndroidJniObject::getStaticObjectField("android/graphics/Bitmap$Config", "ARGB_8888", "Landroid/graphics/Bitmap$Config;"); return QAndroidJniObject::callStaticObjectMethod("android/graphics/Bitmap", "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;", width, height, config.object()); } QImage toImage(const QAndroidJniObject &drawable, const QRect &bounds) { QAndroidJniObject bitmap = createBitmap(bounds.width(), bounds.height()); QAndroidJniObject canvas("android/graphics/Canvas", "(Landroid/graphics/Bitmap;)V", bitmap.object()); drawable.callMethod<void>("setBounds", "(IIII)V", bounds.left(), bounds.top(), bounds.right(), bounds.bottom()); drawable.callMethod<void>("draw", "(Landroid/graphics/Canvas;)V", canvas.object()); return toImage(bitmap); }
First function (createBitmap) is used to create an RGBA_32 bitmap, we’re going to use this function in the next snippet too. Second function (toImage) draws the drawable into a bitmap canvas, then it converts the bitmap using previous toImage function.
Finally, let’s see how to convert a QImage to an Android Bitmap
QAndroidJniObject toAndroidBitmap(const QImage &img) { QImage image = img.format() == QImage::Format_RGBA8888 ? img : img.convertToFormat(QImage::Format_RGBA8888); QAndroidJniObject bitmap = createBitmap(img.width(), img.height()); QAndroidJniEnvironment env; AndroidBitmapInfo info; if (AndroidBitmap_getInfo(env, bitmap.object(), &info) != ANDROID_BITMAP_RESULT_SUCCESS) return QAndroidJniObject(); if (info.format!= ANDROID_BITMAP_FORMAT_RGBA_8888) return QAndroidJniObject(); void *pixels; if (AndroidBitmap_lockPixels(env, bitmap.object(), &pixels) != ANDROID_BITMAP_RESULT_SUCCESS) return QAndroidJniObject(); if (info.stride == uint32_t(image.bytesPerLine())) { memcpy(pixels, image.constBits(), info.stride * info.height); } else { uchar *bmpPtr = static_cast<uchar *>(pixels); const unsigned width = std::min(info.width, (uint)image.width()); const unsigned height = std::min(info.height, (uint)image.height()); for (unsigned y = 0; y < height; y++, bmpPtr += info.stride) memcpy(bmpPtr, image.constScanLine(y), width); } if (AndroidBitmap_unlockPixels(env, bitmap.object()) != ANDROID_BITMAP_RESULT_SUCCESS) return QAndroidJniObject(); return bitmap; }
This function is quite simple, we’re using createBitmap from a previous snippet to create an Android Bitmap object, then we copy the QImage content to Android Bitmap.
How to convert Java `Drawable` to QImage or pixmap?