#include #include #include #include #include #include #include #define LOG_TAG "Applog" #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" { //store JNIEXPORT jobject JNICALL Java_com_basic_security_utils_BitmapHolder_jniStoreBitmapData( JNIEnv * env, jobject obj, jobject bitmap); //get JNIEXPORT jobject JNICALL Java_com_basic_security_utils_BitmapHolder_jniGetBitmapFromStoredBitmapData( JNIEnv * env, jobject obj, jobject handle); //free JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniFreeBitmapData( JNIEnv * env, jobject obj, jobject handle); //rotate 90 degrees CCW JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniRotateBitmapCcw90( JNIEnv * env, jobject obj, jobject handle); //rotate 90 degrees CW JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniRotateBitmapCw90( JNIEnv * env, jobject obj, jobject handle); //rotate 180 degrees JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniRotateBitmap180( JNIEnv * env, jobject obj, jobject handle); //crop JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniCropBitmap( JNIEnv * env, jobject obj, jobject handle, uint32_t left, uint32_t top, uint32_t right, uint32_t bottom); //scale using nearest neighbor JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniScaleNNBitmap( JNIEnv * env, jobject obj, jobject handle, uint32_t newWidth, uint32_t newHeight); //scale using Bilinear Interpolation JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniScaleBIBitmap( JNIEnv * env, jobject obj, jobject handle, uint32_t newWidth, uint32_t newHeight); JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniFlipBitmapHorizontal( JNIEnv * env, jobject obj, jobject handle); JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniFlipBitmapVertical( JNIEnv * env, jobject obj, jobject handle); } class JniBitmap { public: uint32_t* _storedBitmapPixels; AndroidBitmapInfo _bitmapInfo; JniBitmap() { _storedBitmapPixels = NULL; } }; typedef struct { uint8_t alpha, red, green, blue; } ARGB; int32_t convertArgbToInt(ARGB argb) { return (argb.alpha) | (argb.red << 24) | (argb.green << 16) | (argb.blue << 8); } void convertIntToArgb(uint32_t pixel, ARGB* argb) { argb->red = ((pixel >> 24) & 0xff); argb->green = ((pixel >> 16) & 0xff); argb->blue = ((pixel >> 8) & 0xff); argb->alpha = (pixel & 0xff); } /**crops the bitmap within to be smaller. note that no validations are done*/ // JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniCropBitmap( JNIEnv * env, jobject obj, jobject handle, uint32_t left, uint32_t top, uint32_t right, uint32_t bottom) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap == NULL || jniBitmap->_storedBitmapPixels == NULL) return; uint32_t* previousData = jniBitmap->_storedBitmapPixels; uint32_t oldWidth = jniBitmap->_bitmapInfo.width; uint32_t newWidth = right - left, newHeight = bottom - top; uint32_t* newBitmapPixels = new uint32_t[newWidth * newHeight]; uint32_t* whereToGet = previousData + left + top * oldWidth; uint32_t* whereToPut = newBitmapPixels; for (int y = top; y < bottom; ++y) { memcpy(whereToPut, whereToGet, sizeof(uint32_t) * newWidth); whereToGet += oldWidth; whereToPut += newWidth; } //done copying , so replace old data with new one delete[] previousData; jniBitmap->_storedBitmapPixels = newBitmapPixels; jniBitmap->_bitmapInfo.width = newWidth; jniBitmap->_bitmapInfo.height = newHeight; } /**rotates the inner bitmap data by 90 degrees counter clock wise*/ // JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniRotateBitmapCcw90( JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap == NULL || jniBitmap->_storedBitmapPixels == NULL) return; uint32_t* previousData = jniBitmap->_storedBitmapPixels; uint32_t newWidth = jniBitmap->_bitmapInfo.height; uint32_t newHeight = jniBitmap->_bitmapInfo.width; jniBitmap->_bitmapInfo.width = newWidth; jniBitmap->_bitmapInfo.height = newHeight; uint32_t* newBitmapPixels = new uint32_t[newWidth * newHeight]; int whereToGet = 0; // XY. ... ... ..X // ...>Y..>...>..Y // ... X.. .YX ... for (int x = 0; x < newWidth; ++x) for (int y = newHeight - 1; y >= 0; --y) { //take from each row (up to bottom), from left to right uint32_t pixel = previousData[whereToGet++]; newBitmapPixels[newWidth * y + x] = pixel; } delete[] previousData; jniBitmap->_storedBitmapPixels = newBitmapPixels; } /**rotates the inner bitmap data by 90 degrees clock wise*/ // JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniRotateBitmapCw90( JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap == NULL || jniBitmap->_storedBitmapPixels == NULL) return; uint32_t* previousData = jniBitmap->_storedBitmapPixels; uint32_t newWidth = jniBitmap->_bitmapInfo.height; uint32_t newHeight = jniBitmap->_bitmapInfo.width; jniBitmap->_bitmapInfo.width = newWidth; jniBitmap->_bitmapInfo.height = newHeight; uint32_t* newBitmapPixels = new uint32_t[newWidth * newHeight]; int whereToGet = 0; // XY. ..X ... ... // ...>..Y>...>Y.. // ... ... .YX X.. jniBitmap->_storedBitmapPixels = newBitmapPixels; for (int x = newWidth - 1; x >= 0; --x) for (int y = 0; y < newHeight; ++y) { //take from each row (up to bottom), from left to right uint32_t pixel = previousData[whereToGet++]; newBitmapPixels[newWidth * y + x] = pixel; } delete[] previousData; } /**rotates the inner bitmap data by 180 degrees (*/ // JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniRotateBitmap180( JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap == NULL || jniBitmap->_storedBitmapPixels == NULL) return; uint32_t* pixels = jniBitmap->_storedBitmapPixels; uint32_t* pixels2 = jniBitmap->_storedBitmapPixels; uint32_t width = jniBitmap->_bitmapInfo.width; uint32_t height = jniBitmap->_bitmapInfo.height; //no need to create a totally new bitmap - it's the exact same size as the original // 1234 fedc // 5678>ba09 // 90ab>8765 // cdef 4321 int whereToGet = 0; for (int y = height - 1; y >= height / 2; --y) for (int x = width - 1; x >= 0; --x) { //take from each row (up to bottom), from left to right uint32_t tempPixel = pixels2[width * y + x]; pixels2[width * y + x] = pixels[whereToGet]; pixels[whereToGet] = tempPixel; ++whereToGet; } //if the height isn't even, flip the middle row : if (height % 2 == 1) { int y = height / 2; whereToGet = width * y; int lastXToHandle = width % 2 == 0 ? (width / 2) : (width / 2) - 1; for (int x = width - 1; x >= lastXToHandle; --x) { uint32_t tempPixel = pixels2[width * y + x]; pixels2[width * y + x] = pixels[whereToGet]; pixels[whereToGet] = tempPixel; ++whereToGet; } } } /**free bitmap*/ // JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniFreeBitmapData( JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap == NULL || jniBitmap->_storedBitmapPixels == NULL) return; delete[] jniBitmap->_storedBitmapPixels; jniBitmap->_storedBitmapPixels = NULL; delete jniBitmap; } /**restore java bitmap (from JNI data)*/ // JNIEXPORT jobject JNICALL Java_com_basic_security_utils_BitmapHolder_jniGetBitmapFromStoredBitmapData( JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap == NULL || jniBitmap->_storedBitmapPixels == NULL) { LOGD("no bitmap data was stored. returning null..."); return NULL; } // //creating a new bitmap to put the pixels into it - using Bitmap Bitmap.createBitmap (int width, int height, Bitmap.Config config) : // 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, jniBitmap->_bitmapInfo.width, jniBitmap->_bitmapInfo.height, bitmapConfig); // // putting the pixels into the new bitmap: // int ret; void* bitmapPixels; if ((ret = AndroidBitmap_lockPixels(env, newBitmap, &bitmapPixels)) < 0) { LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); return NULL; } uint32_t* newBitmapPixels = (uint32_t*) bitmapPixels; int pixelsCount = jniBitmap->_bitmapInfo.height * jniBitmap->_bitmapInfo.width; memcpy(newBitmapPixels, jniBitmap->_storedBitmapPixels, sizeof(uint32_t) * pixelsCount); AndroidBitmap_unlockPixels(env, newBitmap); //LOGD("returning the new bitmap"); return newBitmap; } /**store java bitmap as JNI data*/ // JNIEXPORT jobject JNICALL Java_com_basic_security_utils_BitmapHolder_jniStoreBitmapData( JNIEnv * env, jobject obj, jobject bitmap) { AndroidBitmapInfo bitmapInfo; uint32_t* storedBitmapPixels = NULL; //LOGD("reading bitmap info..."); int ret; if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) { LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); return NULL; } //LOGD("width:%d height:%d stride:%d", bitmapInfo.width, bitmapInfo.height, bitmapInfo.stride); if (bitmapInfo.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; storedBitmapPixels = new uint32_t[bitmapInfo.height * bitmapInfo.width]; int pixelsCount = bitmapInfo.height * bitmapInfo.width; memcpy(storedBitmapPixels, src, sizeof(uint32_t) * pixelsCount); AndroidBitmap_unlockPixels(env, bitmap); JniBitmap *jniBitmap = new JniBitmap(); jniBitmap->_bitmapInfo = bitmapInfo; jniBitmap->_storedBitmapPixels = storedBitmapPixels; return env->NewDirectByteBuffer(jniBitmap, 0); } /**scales the image using the fastest, simplest algorithm called "nearest neighbor" */ // JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniScaleNNBitmap( JNIEnv * env, jobject obj, jobject handle, uint32_t newWidth, uint32_t newHeight) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap == NULL || jniBitmap->_storedBitmapPixels == NULL) return; uint32_t oldWidth = jniBitmap->_bitmapInfo.width; uint32_t oldHeight = jniBitmap->_bitmapInfo.height; uint32_t* previousData = jniBitmap->_storedBitmapPixels; uint32_t* newBitmapPixels = new uint32_t[newWidth * newHeight]; int x2, y2; int whereToPut = 0; for (int y = 0; y < newHeight; ++y) { for (int x = 0; x < newWidth; ++x) { x2 = x * oldWidth / newWidth; if (x2 < 0) x2 = 0; else if (x2 >= oldWidth) x2 = oldWidth - 1; y2 = y * oldHeight / newHeight; if (y2 < 0) y2 = 0; else if (y2 >= oldHeight) y2 = oldHeight - 1; newBitmapPixels[whereToPut++] = previousData[(y2 * oldWidth) + x2]; //same as : newBitmapPixels[(y * newWidth) + x] = previousData[(y2 * oldWidth) + x2]; } } delete[] previousData; jniBitmap->_storedBitmapPixels = newBitmapPixels; jniBitmap->_bitmapInfo.width = newWidth; jniBitmap->_bitmapInfo.height = newHeight; } /**scales the image using a high-quality algorithm called "Bilinear Interpolation" * code is based on old university code I've made in Java: http://stackoverflow.com/questions/23230047/trying-to-convert-bilinear-interpolation-code-from-java-to-c-c-on-android/23302384#23302384 * */ // JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniScaleBIBitmap( JNIEnv * env, jobject obj, jobject handle, uint32_t newWidth, uint32_t newHeight) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap == NULL || jniBitmap->_storedBitmapPixels == NULL) return; uint32_t oldWidth = jniBitmap->_bitmapInfo.width; uint32_t oldHeight = jniBitmap->_bitmapInfo.height; uint32_t* previousData = jniBitmap->_storedBitmapPixels; uint32_t* newBitmapPixels = new uint32_t[newWidth * newHeight]; // position of the top left pixel of the 4 pixels to use interpolation on int xTopLeft, yTopLeft; int x, y, lastTopLefty; float xRatio = (float) newWidth / (float) oldWidth, yratio = (float) newHeight / (float) oldHeight; // Y color ratio to use on left and right pixels for interpolation float ycRatio2 = 0, ycRatio1 = 0; // pixel target in the src float xt, yt; // X color ratio to use on left and right pixels for interpolation float xcRatio2 = 0, xcratio1 = 0; ARGB rgbTopLeft, rgbTopRight, rgbBottomLeft, rgbBottomRight, rgbTopMiddle, rgbBottomMiddle, result; for (x = 0; x < newWidth; ++x) { xTopLeft = (int) (xt = x / xRatio); // when meeting the most right edge, move left a little if (xTopLeft >= oldWidth - 1) xTopLeft--; if (xt <= xTopLeft + 1) { // we are between the left and right pixel xcratio1 = xt - xTopLeft; // color ratio in favor of the right pixel color xcRatio2 = 1 - xcratio1; } for (y = 0, lastTopLefty = -30000; y < newHeight; ++y) { yTopLeft = (int) (yt = y / yratio); // when meeting the most bottom edge, move up a little if (yTopLeft >= oldHeight - 1) --yTopLeft; if (lastTopLefty == yTopLeft - 1) { // we went down only one rectangle rgbTopLeft = rgbBottomLeft; rgbTopRight = rgbBottomRight; rgbTopMiddle = rgbBottomMiddle; //rgbBottomLeft=startingImageData[xTopLeft][yTopLeft+1]; convertIntToArgb( previousData[((yTopLeft + 1) * oldWidth) + xTopLeft], &rgbBottomLeft); //rgbBottomRight=startingImageData[xTopLeft+1][yTopLeft+1]; convertIntToArgb( previousData[((yTopLeft + 1) * oldWidth) + (xTopLeft + 1)], &rgbBottomRight); rgbBottomMiddle.alpha = rgbBottomLeft.alpha * xcRatio2 + rgbBottomRight.alpha * xcratio1; rgbBottomMiddle.red = rgbBottomLeft.red * xcRatio2 + rgbBottomRight.red * xcratio1; rgbBottomMiddle.green = rgbBottomLeft.green * xcRatio2 + rgbBottomRight.green * xcratio1; rgbBottomMiddle.blue = rgbBottomLeft.blue * xcRatio2 + rgbBottomRight.blue * xcratio1; } else if (lastTopLefty != yTopLeft) { // we went to a totally different rectangle (happens in every loop start,and might happen more when making the picture smaller) //rgbTopLeft=startingImageData[xTopLeft][yTopLeft]; convertIntToArgb(previousData[(yTopLeft * oldWidth) + xTopLeft], &rgbTopLeft); //rgbTopRight=startingImageData[xTopLeft+1][yTopLeft]; convertIntToArgb( previousData[((yTopLeft + 1) * oldWidth) + xTopLeft], &rgbTopRight); rgbTopMiddle.alpha = rgbTopLeft.alpha * xcRatio2 + rgbTopRight.alpha * xcratio1; rgbTopMiddle.red = rgbTopLeft.red * xcRatio2 + rgbTopRight.red * xcratio1; rgbTopMiddle.green = rgbTopLeft.green * xcRatio2 + rgbTopRight.green * xcratio1; rgbTopMiddle.blue = rgbTopLeft.blue * xcRatio2 + rgbTopRight.blue * xcratio1; //rgbBottomLeft=startingImageData[xTopLeft][yTopLeft+1]; convertIntToArgb( previousData[((yTopLeft + 1) * oldWidth) + xTopLeft], &rgbBottomLeft); //rgbBottomRight=startingImageData[xTopLeft+1][yTopLeft+1]; convertIntToArgb( previousData[((yTopLeft + 1) * oldWidth) + (xTopLeft + 1)], &rgbBottomRight); rgbBottomMiddle.alpha = rgbBottomLeft.alpha * xcRatio2 + rgbBottomRight.alpha * xcratio1; rgbBottomMiddle.red = rgbBottomLeft.red * xcRatio2 + rgbBottomRight.red * xcratio1; rgbBottomMiddle.green = rgbBottomLeft.green * xcRatio2 + rgbBottomRight.green * xcratio1; rgbBottomMiddle.blue = rgbBottomLeft.blue * xcRatio2 + rgbBottomRight.blue * xcratio1; } lastTopLefty = yTopLeft; if (yt <= yTopLeft + 1) { // color ratio in favor of the bottom pixel color ycRatio1 = yt - yTopLeft; ycRatio2 = 1 - ycRatio1; } // prepared all pixels to look at, so finally set the new pixel data result.alpha = rgbTopMiddle.alpha * ycRatio2 + rgbBottomMiddle.alpha * ycRatio1; result.blue = rgbTopMiddle.blue * ycRatio2 + rgbBottomMiddle.blue * ycRatio1; result.red = rgbTopMiddle.red * ycRatio2 + rgbBottomMiddle.red * ycRatio1; result.green = rgbTopMiddle.green * ycRatio2 + rgbBottomMiddle.green * ycRatio1; newBitmapPixels[(y * newWidth) + x] = convertArgbToInt(result); } } //get rid of old data, and replace it with new one delete[] previousData; jniBitmap->_storedBitmapPixels = newBitmapPixels; jniBitmap->_bitmapInfo.width = newWidth; jniBitmap->_bitmapInfo.height = newHeight; } /**flips a bitmap horizontally, as such: * * 123 321 * 456 => 654 * 789 987 * * */ // JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniFlipBitmapHorizontal( JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap == NULL || jniBitmap->_storedBitmapPixels == NULL) return; uint32_t* previousData = jniBitmap->_storedBitmapPixels; int width = jniBitmap->_bitmapInfo.width, middle = width / 2, height = jniBitmap->_bitmapInfo.height; for (int y = 0; y < height; ++y) { //for each row, switch between the first pixels and the last ones uint32_t* idx1 = previousData + width * y; uint32_t* idx2 = previousData + width * (y + 1) - 1; for (int x = 0; x < middle; ++x) { uint32_t pixel = *idx1; //pixel= previousData[rowStart + x]; *idx1 = *idx2; //previousData[rowStart + x] =previousData[rowStart + (width - x - 1)]; *idx2 = pixel; //previousData[rowStart + (width - x - 1)] = pixel; ++idx1; --idx2; } } } /**flips a bitmap vertically, as such: * * 123 789 * 456 => 456 * 789 123 * * */ // JNIEXPORT void JNICALL Java_com_basic_security_utils_BitmapHolder_jniFlipBitmapVertical( JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap == NULL || jniBitmap->_storedBitmapPixels == NULL) return; uint32_t* previousData = jniBitmap->_storedBitmapPixels; int width = jniBitmap->_bitmapInfo.width, height = jniBitmap->_bitmapInfo.height, middle = height / 2; for (int y = 0; y < middle; ++y) { //for each row till the middle row, switch its pixels with the one at the bottom uint32_t* idx1 = previousData + width * y; uint32_t* idx2 = previousData + width * (height - y - 1); for (int x = 0; x < width; ++x) { uint32_t pixel =*idx1; *idx1=*idx2; *idx2=pixel; ++idx2; ++idx1; } } }