Поворот растрового изображения с использованием JNI & NDK

История:

Я решил, что, поскольку растровые изображения занимают много памяти, что может привести к ошибкам без учета объема памяти, я положу тяжелую работу по потреблению памяти на код C/С++.

Шагами, которые я использую для поворота растрового изображения, являются:

  • прочитать информацию о битовой карте (ширина, высота)
  • сохранить в массиве растровые пиксели.
  • перезагрузить растровое изображение.
  • создать новый растровый рисунок противоположного размера.
  • помещают пиксели в новое растровое изображение.
  • освободите пиксели и верните растровое изображение.

Проблема:

Несмотря на то, что все, кажется, работает без ошибок, выходное изображение не является поворотным оригиналом. Фактически, он полностью разрушает его.

Вращение должно быть против часовой стрелки, 90 градусов.

Пример (снимок экрана увеличен) того, что я получаю:

enter image description here

Итак, как вы можете видеть, не только цвета стали более странными, но размер не соответствует тому, что я ему задал. Здесь что-то действительно странно.

Может быть, я не читаю/не помещаю данные правильно?

Конечно, это всего лишь пример. Код должен работать нормально на любом растровом изображении, если у устройства достаточно памяти для его хранения. Кроме того, я мог бы делать другие операции над растровым рисунком, кроме вращения.

Код, который я создал:

Файл Android.mk:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := JniTest
LOCAL_SRC_FILES := JniTest.cpp
LOCAL_LDLIBS := -llog
LOCAL_LDFLAGS += -ljnigraphics
include $(BUILD_SHARED_LIBRARY)
APP_OPTIM := debug
LOCAL_CFLAGS := -g

Файл cpp:

#include <jni.h>
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <android/bitmap.h>
#include <cstring>
#include <unistd.h>

#define  LOG_TAG    "DEBUG"
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

extern "C"
  {
  JNIEXPORT jobject JNICALL Java_com_example_jnitest_MainActivity_rotateBitmapCcw90(JNIEnv * env, jobject obj, jobject bitmap);
  }

JNIEXPORT jobject JNICALL Java_com_example_jnitest_MainActivity_rotateBitmapCcw90(JNIEnv * env, jobject obj, jobject bitmap)
  {
  //
  //getting bitmap info:
  //
  LOGD("reading bitmap info...");
  AndroidBitmapInfo info;
  int ret;
  if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0)
    {
    LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
    return NULL;
    }
  LOGD("width:%d height:%d stride:%d", info.width, info.height, info.stride);
  if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
    {
    LOGE("Bitmap format is not RGBA_8888!");
    return NULL;
    }
  //
  //read pixels of bitmap into native memory :
  //
  LOGD("reading bitmap pixels...");
  void* bitmapPixels;
  if ((ret = AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels)) < 0)
    {
    LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    return NULL;
    }
  uint32_t* src = (uint32_t*) bitmapPixels;
  uint32_t* tempPixels = new uint32_t[info.height * info.width];
  int stride = info.stride;
  int pixelsCount = info.height * info.width;
  memcpy(tempPixels, src, sizeof(uint32_t) * pixelsCount);
  AndroidBitmap_unlockPixels(env, bitmap);
  //
  //recycle bitmap - using bitmap.recycle()
  //
  LOGD("recycling bitmap...");
  jclass bitmapCls = env->GetObjectClass(bitmap);
  jmethodID recycleFunction = env->GetMethodID(bitmapCls, "recycle", "()V");
  if (recycleFunction == 0)
    {
    LOGE("error recycling!");
    return NULL;
    }
  env->CallVoidMethod(bitmap, recycleFunction);
  //
  //creating a new bitmap to put the pixels into it - using Bitmap Bitmap.createBitmap (int width, int height, Bitmap.Config config) :
  //
  LOGD("creating new 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, info.height, info.width, bitmapConfig);
  //
  // putting the pixels into the new bitmap:
  //
  if ((ret = AndroidBitmap_lockPixels(env, newBitmap, &bitmapPixels)) < 0)
    {
    LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    return NULL;
    }
  uint32_t* newBitmapPixels = (uint32_t*) bitmapPixels;
  int whereToPut = 0;    
  for (int x = info.width - 1; x >= 0; --x)
    for (int y = 0; y < info.height; ++y)
      {
      uint32_t pixel = tempPixels[info.width * y + x];
      newBitmapPixels[whereToPut++] = pixel;
      }
  AndroidBitmap_unlockPixels(env, newBitmap);
  //
  // freeing the native memory used to store the pixels
  //
  delete[] tempPixels;
  return newBitmap;
  }

java файл:

  static
    {
    System.loadLibrary("JniTest");
    }

  /**
   * rotates a bitmap by 90 degrees counter-clockwise . <br/>
   * notes:<br/>
   * -the input bitmap will be recycled and shouldn't be used anymore <br/>
   * -returns the rotated bitmap . <br/>
   * -could take some time , so do the operation in a new thread
   */
  public native Bitmap rotateBitmapCcw90(Bitmap bitmap);

...
  Bitmap rotatedImage=rotateBitmapCcw90(bitmapToRotate);

EDIT: после того, как я получил свой ответ, я хочу поделиться этим кодом и отметить его всем:

  • чтобы он работал, я заменил в коде каждый экземпляр "uint16_t" на "uint32_t" (что ошибка в моем коде, о котором я просил).

  • битовая карта ввода и вывода должна быть с конфигурацией 8888 (которая является ARGB)

  • входной битмап будет возвращен во время процесса.

  • код поворачивает изображение на 90 градусов против часовой стрелки. Конечно, вы можете изменить его в зависимости от ваших потребностей.


лучшее решение

Я сделал хороший пост с этой функциональностью и другими, здесь.

Ответ 1

Поскольку вы используете формат ARGB_8888, каждый пиксель является uint32_t not uint16_t. Попробуйте изменить созданное растровое изображение, чтобы использовать uint32_t для исходных и целевых массивов, и он должен работать лучше.