In Android, bitmaps are manipulated via JNI.

In Android through JNI to call Bitmap, through CMake to edit so dynamic link library, need to add jnigraphics image library.

target_link_libraries( # Specifies the target library.
                       native-operation
                       jnigraphics
                       ${log-lib} )
Copy the code

In Android, JNI Bitmap operations are defined in the bitmap.h header file, mainly three functions, understand their meaning after you can go to practice.

Retrieves Bitmap object information

The AndroidBitmap_getInfo function allows native code to retrieve information about a Bitmap object, such as its size, pixel format, and so on, with the following signature:

/** * Given a java bitmap object, fill out the AndroidBitmapInfo struct for it. * If the call fails, the info parameter will be ignored. */
int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, AndroidBitmapInfo* info);
Copy the code

The first parameter is a JNI interface pointer, the second parameter is a reference to the Bitmap object, and the third parameter is a pointer to the AndroidBitmapInfo structure.

AndroidBitmapInfo structure is as follows:

/** Bitmap info, see AndroidBitmap_getInfo(). */
typedef struct {
    /** The bitmap width in pixels. */
    uint32_t    width;
    /** The bitmap height in pixels. */
    uint32_t    height;
    /** The number of byte per row. */
    uint32_t    stride;
    /** The bitmap pixel format. See {@link AndroidBitmapFormat} */
    int32_t     format;
    /** Unused. */
    uint32_t    flags;      // 0 for now
} AndroidBitmapInfo;
Copy the code

Where, width is the width of the Bitmap, height is the height, format is the format of the image, and stride is the number of bytes for each line.

Image formats are supported as follows:

/** Bitmap pixel format. */
enum AndroidBitmapFormat {
    /** No format. */
    ANDROID_BITMAP_FORMAT_NONE      = 0./** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Alpha: 8 bits. **/
    ANDROID_BITMAP_FORMAT_RGBA_8888 = 1./** Red: 5 bits, Green: 6 bits, Blue: 5 bits. **/
    ANDROID_BITMAP_FORMAT_RGB_565   = 4./** Deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead. **/
    ANDROID_BITMAP_FORMAT_RGBA_4444 = 7./** Alpha: 8 bits. */
    ANDROID_BITMAP_FORMAT_A_8       = 8};Copy the code

AndroidBitmap_getInfo returns 0 if executed successfully, otherwise returns a negative number representing the list of error codes executed as follows:

/** AndroidBitmap functions result code. */
enum {
    /** Operation was successful. */
    ANDROID_BITMAP_RESULT_SUCCESS           = 0./** Bad parameter. */
    ANDROID_BITMAP_RESULT_BAD_PARAMETER     = - 1./** JNI exception occured. */
    ANDROID_BITMAP_RESULT_JNI_EXCEPTION     = 2 -./** Allocation failed. */
    ANDROID_BITMAP_RESULT_ALLOCATION_FAILED = - 3};Copy the code

Access the native pixel cache

The AndroidBitmap_lockPixels function locks the pixel cache to ensure that the pixel’s memory is not moved.

If the Native layer wants to access pixel data and manipulate it, this method returns a Native pointer to the pixel cache,

/** * Given a java bitmap object, attempt to lock the pixel address. * Locking will ensure that the memory for the pixels will not move * until the unlockPixels call, and ensure that, if the pixels had been * previously purged, they will have been restored. * * If this call succeeds, it must be balanced by a call to * AndroidBitmap_unlockPixels, after which time the address of the pixels should * no longer be used. * * If this succeeds, *addrPtr will be set to the pixel address. If the call * fails, addrPtr will be ignored. */
int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);
Copy the code

The first parameter is a JNI interface pointer, the second parameter is a reference to a Bitmap object, and the third parameter is a pointer to the pixel cache address.

AndroidBitmap_lockPixels returns 0 on success, otherwise a negative number is returned.

Release the native pixel cache

AndroidBitmap_unlockPixels should be called once after calling the Bitmap to release the native pixel cache.

After reading or writing to the native pixel cache, it should be released. Once released, the Bitmap Java object can be used in the Java layer again. The function is signed as follows:

/** * Call this to balance a successful call to AndroidBitmap_lockPixels. */
int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);
Copy the code

The first argument is a JNI interface pointer, and the second argument is a reference to a Bitmap object that returns 0 on success and 1 otherwise.

The AndroidBitmap_lockPixels function obtains the cached address of all pixels, and then operates on each pixel value to change the Bitmap.

practice

Experience JNI development by rotating, flipping, and mirroring the Bitmap left to right.

The effect is as follows:

Please refer to my Github project for the specific code. Welcome Star.

Github.com/glumes/Andr…

Rotate the Bitmap through JNI

First define a native function like this:

    // Rotate 90° clockwise
    public native Bitmap rotateBitmap(Bitmap bitmap);
Copy the code

Pass in a Bitmap object and return a Bitmap object.

Then in C++ code, first retrieve the Bitmap information to see if it succeeded.

    AndroidBitmapInfo bitmapInfo;
    int ret;
    if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return NULL;
    }
Copy the code

The next step is to get the Bitmap’s pixel cache pointer:

    // Read the pixel contents of the bitmap into native memory
    void *bitmapPixels;
    if ((ret = AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return NULL;
    }
Copy the code

This pointer points to the Bitmap pixel content, which is a one-dimensional array that holds the values of all the pixels, but when we define the Bitmap image, we will define the width and height, which is relative to a two-dimensional image, so there is how the Bitmap pixel content into the pointer pointing to the one-dimensional content. Is it row or column?

In this case, it’s arranged by rows, and the rows are arranged from left to right, and the columns are arranged from top to bottom, starting at the upper left corner just like the origin of the screen coordinates.

Using the AndroidBitmap_lockPixels method, the bitmapPixels pointer points to the pixel content of a Bitmap, and its length is the product of the width and height of the Bitmap.

The Bitmap can be rotated either by directly changing the value of the pixels pointed to by the bitmapPixels pointer, or by creating a new Bitmap and filling it with pixel values. The latter is selected here.

Start by creating a new Bitmap object as mentioned in the previous article: Android accesses Java fields and method calls through JNI.

In Java code, a Bitmap can be created using the createBitmap method, as follows:

 Bitmap.createBitmap(int width, int height, @NonNull Config config)`
Copy the code

So in JNI you need to call static methods of Bitmap to create a Bitmap object.

jobject generateBitmap(JNIEnv *env, uint32_t width, uint32_t height) {

    jclass bitmapCls = env->FindClass("android/graphics/Bitmap");
    jmethodID createBitmapFunction = env->GetStaticMethodID(bitmapCls,
                                                            "createBitmap"."(IILandroid/graphics/Bitmap$Config;) Landroid/graphics/Bitmap;");
    jstring configName = env->NewStringUTF("ARGB_8888");
    jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config");
    jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID(
            bitmapConfigClass, "valueOf"."(Ljava/lang/String;) Landroid/graphics/Bitmap$Config;");

    jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass,
                                                       valueOfBitmapConfigFunction, configName);

    jobject newBitmap = env->CallStaticObjectMethod(bitmapCls,
                                                    createBitmapFunction,
                                                    width,
                                                    height, bitmapConfig);
    return newBitmap;
}
Copy the code

First find the Config class using the FindClass method and get a configuration of ARGB_8888. Then get the Bitmap class and call its static method createBitmap to create a new Bitmap object, as described in the previous article.

Here we pass in the width and height of the new Bitmap. This width and height is also calculated according to different operations after getting the original width and height from AndroidBitmap_getInfo.

	// The width of the new Bitmap is equal to the height of the old Bitmap
   uint32_t newWidth = bitmapInfo.height;
    uint32_t newHeight = bitmapInfo.width;
Copy the code

With the new Bitmap object and the old Bitmap pixel pointer, the next step is to create a new pixel pointer, fill the pixel content, and then fill the pixel content into the Bitmap.

    // Create a new array pointer and fill the new array pointer with pixel values
	uint32_t *newBitmapPixels = new uint32_t[newWidth * newHeight];
    int whereToGet = 0;
    for (int y = 0; y < newHeight; ++y) {
        for (int x = newWidth - 1; x >= 0; x--) {
            uint32_t pixel = ((uint32_t*) bitmapPixels)[whereToGet++]; newBitmapPixels[newWidth * y + x] = pixel; }}Copy the code

In both of these for loops, we take the pixel value from the original pixel pointer and fill it with the value of the corresponding position in the new pixel pointer in a specific order. Here, the pixel pointer is arranged by row, starting from the upper left corner of the Bitmap.

    void *resultBitmapPixels;
    if ((ret = AndroidBitmap_lockPixels(env, newBitmap, &resultBitmapPixels)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return NULL;
    }
    int pixelsCount = newWidth * newHeight;
    memcpy((uint32_t *) resultBitmapPixels, newBitmapPixels, sizeof(uint32_t) * pixelsCount);
    AndroidBitmap_unlockPixels(env, newBitmap);
Copy the code

Create a resultBitmapPixels pointer again and call AndroidBitmap_lockPixels to retrieve the pixel pointer cache of the new Bitmap. Then call memcpy. Fill the pixel pointer to be filled into resultBitmapPixels, thus completing the pixel assignment. Finally, call AndroidBitmap_unlockPixels method to release the pixel pointer cache and complete the entire assignment process.

This completes the JNI operation on the Bitmap by reading the pixel contents of the original Bitmap, performing the operation and assigning the value to the new Bitmap.

Flip the Bitmap upside down and mirror it left and right through JNI

Flipping a Bitmap up and down and mirroring it left and right and rotating it are similar, except for the pixel pointer.

Up-and-down operation:

    int whereToGet = 0;
    for (int y = 0; y < newHeight; ++y) {
        for (int x = 0; x < newWidth; x++) {
            uint32_t pixel = ((uint32_t *) bitmapPixels)[whereToGet++];
            newBitmapPixels[newWidth * (newHeight - 1- y) + x] = pixel; }}Copy the code

Left and right mirror operation:

    int whereToGet = 0;
    for (int y = 0; y < newHeight; ++y) {
        for (int x = newWidth - 1; x >= 0; x--) {
            uint32_t pixel = ((uint32_t*) bitmapPixels)[whereToGet++]; newBitmapPixels[newWidth * y + x] = pixel; }}Copy the code

All other operations are the same, the details are still to see the project code.

Welcome to pay attention to wechat public number: [paper talk], get the latest article push ~~~