From a72f4474b4deb9a5cfe0450856a297ac0832e7da Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 29 Jun 2012 15:38:10 +0000 Subject: [PATCH] fixing bug #1987(android keeps RGBA in alpha pre-multiplied form, Mat <-> Bitmap should handle this correctly) --- .../include/opencv2/imgproc/imgproc.hpp | 6 +- .../imgproc/include/opencv2/imgproc/types_c.h | 6 +- modules/imgproc/src/color.cpp | 83 +++++++++++++++++++ .../src/org/opencv/test/OpenCVTestCase.java | 5 +- .../org/opencv/test/android/UtilsTest.java | 39 +++++++++ modules/java/generator/src/cpp/utils.cpp | 46 +++++----- .../generator/src/java/android+Utils.java | 63 +++++++++----- 7 files changed, 203 insertions(+), 45 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc/imgproc.hpp index f0a9172ab..d0031bf5d 100644 --- a/modules/imgproc/include/opencv2/imgproc/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc/imgproc.hpp @@ -1008,7 +1008,11 @@ enum COLOR_YUV2GRAY_YUYV = COLOR_YUV2GRAY_YUY2, COLOR_YUV2GRAY_YUNV = COLOR_YUV2GRAY_YUY2, - COLOR_COLORCVT_MAX = 125 + // alpha premultiplication + COLOR_RGBA2mRGBA = 125, + COLOR_mRGBA2RGBA = 126, + + COLOR_COLORCVT_MAX = 127 }; diff --git a/modules/imgproc/include/opencv2/imgproc/types_c.h b/modules/imgproc/include/opencv2/imgproc/types_c.h index d7beed241..4154eb197 100644 --- a/modules/imgproc/include/opencv2/imgproc/types_c.h +++ b/modules/imgproc/include/opencv2/imgproc/types_c.h @@ -306,7 +306,11 @@ enum CV_YUV2GRAY_YUYV = CV_YUV2GRAY_YUY2, CV_YUV2GRAY_YUNV = CV_YUV2GRAY_YUY2, - CV_COLORCVT_MAX = 125 + // alpha premultiplication + CV_RGBA2mRGBA = 125, + CV_mRGBA2RGBA = 126, + + CV_COLORCVT_MAX = 127 }; diff --git a/modules/imgproc/src/color.cpp b/modules/imgproc/src/color.cpp index 42f7009e5..8739e42d0 100644 --- a/modules/imgproc/src/color.cpp +++ b/modules/imgproc/src/color.cpp @@ -3128,6 +3128,57 @@ inline void cvtYUV422toRGBA(Mat& _dst, int _stride, const uchar* _yuv) converter(BlockedRange(0, _dst.rows)); } +/////////////////////////// RGBA <-> mRGBA (alpha premultiplied) ////////////// + +template +struct RGBA2mRGBA +{ + typedef _Tp channel_type; + + void operator()(const _Tp* src, _Tp* dst, int n) const + { + _Tp max_val = ColorChannel<_Tp>::max(); + _Tp half_val = ColorChannel<_Tp>::half(); + for( int i = 0; i < n; i++ ) + { + _Tp v0 = *src++; + _Tp v1 = *src++; + _Tp v2 = *src++; + _Tp v3 = *src++; + + *dst++ = (v0 * v3 + half_val) / max_val; + *dst++ = (v1 * v3 + half_val) / max_val; + *dst++ = (v2 * v3 + half_val) / max_val; + *dst++ = v3; + } + } +}; + + +template +struct mRGBA2RGBA +{ + typedef _Tp channel_type; + + void operator()(const _Tp* src, _Tp* dst, int n) const + { + _Tp max_val = ColorChannel<_Tp>::max(); + for( int i = 0; i < n; i++ ) + { + _Tp v0 = *src++; + _Tp v1 = *src++; + _Tp v2 = *src++; + _Tp v3 = *src++; + _Tp v3_half = v3 / 2; + + *dst++ = (v3==0)? 0 : (v0 * max_val + v3_half) / v3; + *dst++ = (v3==0)? 0 : (v1 * max_val + v3_half) / v3; + *dst++ = (v3==0)? 0 : (v2 * max_val + v3_half) / v3; + *dst++ = v3; + } + } +}; + }//namespace cv ////////////////////////////////////////////////////////////////////////////////////////// @@ -3640,6 +3691,38 @@ void cv::cvtColor( InputArray _src, OutputArray _dst, int code, int dcn ) extractChannel(_src, _dst, code == CV_YUV2GRAY_UYVY ? 1 : 0); } break; + case CV_RGBA2mRGBA: + { + if (dcn <= 0) dcn = 4; + CV_Assert( scn == 4 && dcn == 4 ); + + _dst.create(sz, CV_MAKETYPE(depth, dcn)); + dst = _dst.getMat(); + + if( depth == CV_8U ) + { + CvtColorLoop(src, dst, RGBA2mRGBA()); + } else { + CV_Error( CV_StsBadArg, "Unsupported image depth" ); + } + } + break; + case CV_mRGBA2RGBA: + { + if (dcn <= 0) dcn = 4; + CV_Assert( scn == 4 && dcn == 4 ); + + _dst.create(sz, CV_MAKETYPE(depth, dcn)); + dst = _dst.getMat(); + + if( depth == CV_8U ) + { + CvtColorLoop(src, dst, mRGBA2RGBA()); + } else { + CV_Error( CV_StsBadArg, "Unsupported image depth" ); + } + } + break; default: CV_Error( CV_StsBadFlag, "Unknown/unsupported color conversion code" ); } diff --git a/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java b/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java index d9fdca3d6..59b34edf0 100644 --- a/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java +++ b/modules/java/android_test/src/org/opencv/test/OpenCVTestCase.java @@ -400,12 +400,13 @@ public class OpenCVTestCase extends TestCase { Mat diff = new Mat(); Core.absdiff(expected, actual, diff); + double maxDiff = Core.norm(diff, Core.NORM_INF); if (isEqualityMeasured) - assertTrue("Max difference between expected and actiual Mats is bigger than " + eps, + assertTrue("Max difference between expected and actiual Mats is "+ maxDiff + ", that bigger than " + eps, Core.checkRange(diff, true, 0.0, eps)); else - assertFalse("Max difference between expected and actiual Mats is less than " + eps, + assertFalse("Max difference between expected and actiual Mats is "+ maxDiff + ", that less than " + eps, Core.checkRange(diff, true, 0.0, eps)); } diff --git a/modules/java/android_test/src/org/opencv/test/android/UtilsTest.java b/modules/java/android_test/src/org/opencv/test/android/UtilsTest.java index 8977dca08..43a5c4c29 100644 --- a/modules/java/android_test/src/org/opencv/test/android/UtilsTest.java +++ b/modules/java/android_test/src/org/opencv/test/android/UtilsTest.java @@ -130,4 +130,43 @@ public class UtilsTest extends OpenCVTestCase { } + public void testAlphaPremultiplication() { + final int size = 256; + Bitmap bmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Mat mOrig = new Mat(size, size, CvType.CV_8UC4); + Mat mUnPre = new Mat(size, size, CvType.CV_8UC4); + for(int y=0; y Mat + Mat m1 = new Mat(); + Mat m2 = new Mat(); + + Utils.bitmapToMat(bmp, m1, false); + Imgproc.cvtColor(mOrig, m2, Imgproc.COLOR_RGBA2mRGBA); + assertMatEqual(m1, m2, 1.1); + + Utils.bitmapToMat(bmp, m1, true); + assertMatEqual(m1, mUnPre, 1.1); + + // Mat -> Bitmap + Bitmap bmp1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + + Utils.matToBitmap(mOrig, bmp1, true); + Utils.bitmapToMat(bmp1, m1, true); + //assertMatEqual(m1, mUnPre, 1.1); + Mat diff = new Mat(); + Core.absdiff(m1, mUnPre, diff); + int numDiff = Core.countNonZero(diff.reshape(1)); + assertTrue(numDiff < size * 4); + } + } diff --git a/modules/java/generator/src/cpp/utils.cpp b/modules/java/generator/src/cpp/utils.cpp index 51fca4b1c..fed03b6d6 100644 --- a/modules/java/generator/src/cpp/utils.cpp +++ b/modules/java/generator/src/cpp/utils.cpp @@ -21,14 +21,14 @@ extern "C" { /* * Class: org_opencv_android_Utils - * Method: void nBitmapToMat(Bitmap b, long m_addr) + * Method: void nBitmapToMat(Bitmap b, long m_addr, boolean unPremultiplyAlpha) */ JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nBitmapToMat - (JNIEnv * env, jclass, jobject bitmap, jlong m_addr); + (JNIEnv * env, jclass, jobject bitmap, jlong m_addr, jboolean needUnPremultiplyAlpha); JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nBitmapToMat - (JNIEnv * env, jclass, jobject bitmap, jlong m_addr) + (JNIEnv * env, jclass, jobject bitmap, jlong m_addr, jboolean needUnPremultiplyAlpha) { AndroidBitmapInfo info; void* pixels = 0; @@ -46,7 +46,8 @@ JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nBitmapToMat { LOGD("nBitmapToMat: RGBA_8888 -> CV_8UC4"); Mat tmp(info.height, info.width, CV_8UC4, pixels); - tmp.copyTo(dst); + if(needUnPremultiplyAlpha) cvtColor(tmp, dst, COLOR_mRGBA2RGBA); + else tmp.copyTo(dst); } else { // info.format == ANDROID_BITMAP_FORMAT_RGB_565 LOGD("nBitmapToMat: RGB_565 -> CV_8UC4"); @@ -73,54 +74,55 @@ JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nBitmapToMat /* * Class: org_opencv_android_Utils - * Method: void nMatToBitmap(long m_addr, Bitmap b) + * Method: void nMatToBitmap(long m_addr, Bitmap b, boolean premultiplyAlpha) */ JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nMatToBitmap - (JNIEnv * env, jclass, jlong m_addr, jobject bitmap); + (JNIEnv * env, jclass, jlong m_addr, jobject bitmap, jboolean needPremultiplyAlpha); JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nMatToBitmap - (JNIEnv * env, jclass, jlong m_addr, jobject bitmap) + (JNIEnv * env, jclass, jlong m_addr, jobject bitmap, jboolean needPremultiplyAlpha) { AndroidBitmapInfo info; void* pixels = 0; - Mat& dst = *((Mat*)m_addr); + Mat& src = *((Mat*)m_addr); try { LOGD("nMatToBitmap"); CV_Assert( AndroidBitmap_getInfo(env, bitmap, &info) >= 0 ); CV_Assert( info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 || info.format == ANDROID_BITMAP_FORMAT_RGB_565 ); - CV_Assert( dst.dims == 2 && info.height == (uint32_t)dst.rows && info.width == (uint32_t)dst.cols ); - CV_Assert( dst.type() == CV_8UC1 || dst.type() == CV_8UC3 || dst.type() == CV_8UC4 ); + CV_Assert( src.dims == 2 && info.height == (uint32_t)src.rows && info.width == (uint32_t)src.cols ); + CV_Assert( src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4 ); CV_Assert( AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0 ); CV_Assert( pixels ); if( info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ) { Mat tmp(info.height, info.width, CV_8UC4, pixels); - if(dst.type() == CV_8UC1) + if(src.type() == CV_8UC1) { LOGD("nMatToBitmap: CV_8UC1 -> RGBA_8888"); - cvtColor(dst, tmp, CV_GRAY2RGBA); - } else if(dst.type() == CV_8UC3){ + cvtColor(src, tmp, CV_GRAY2RGBA); + } else if(src.type() == CV_8UC3){ LOGD("nMatToBitmap: CV_8UC3 -> RGBA_8888"); - cvtColor(dst, tmp, CV_RGB2RGBA); - } else if(dst.type() == CV_8UC4){ + cvtColor(src, tmp, CV_RGB2RGBA); + } else if(src.type() == CV_8UC4){ LOGD("nMatToBitmap: CV_8UC4 -> RGBA_8888"); - dst.copyTo(tmp); + if(needPremultiplyAlpha) cvtColor(src, tmp, COLOR_RGBA2mRGBA); + else src.copyTo(tmp); } } else { // info.format == ANDROID_BITMAP_FORMAT_RGB_565 Mat tmp(info.height, info.width, CV_8UC2, pixels); - if(dst.type() == CV_8UC1) + if(src.type() == CV_8UC1) { LOGD("nMatToBitmap: CV_8UC1 -> RGB_565"); - cvtColor(dst, tmp, CV_GRAY2BGR565); - } else if(dst.type() == CV_8UC3){ + cvtColor(src, tmp, CV_GRAY2BGR565); + } else if(src.type() == CV_8UC3){ LOGD("nMatToBitmap: CV_8UC3 -> RGB_565"); - cvtColor(dst, tmp, CV_RGB2BGR565); - } else if(dst.type() == CV_8UC4){ + cvtColor(src, tmp, CV_RGB2BGR565); + } else if(src.type() == CV_8UC4){ LOGD("nMatToBitmap: CV_8UC4 -> RGB_565"); - cvtColor(dst, tmp, CV_RGBA2BGR565); + cvtColor(src, tmp, CV_RGBA2BGR565); } } AndroidBitmap_unlockPixels(env, bitmap); diff --git a/modules/java/generator/src/java/android+Utils.java b/modules/java/generator/src/java/android+Utils.java index 7a3c915a0..6b67f66b3 100644 --- a/modules/java/generator/src/java/android+Utils.java +++ b/modules/java/generator/src/java/android+Utils.java @@ -81,19 +81,28 @@ public class Utils { *
The output Mat is always created of the same size as the input Bitmap and of the 'CV_8UC4' type, * it keeps the image in RGBA format. *
The function throws an exception if the conversion fails. - * - * @param b is a valid input Bitmap object of the type 'ARGB_8888' or 'RGB_565'. - * @param m is a valid output Mat object, it will be reallocated if needed, so it's possible to pass an empty Mat. + * @param bmp is a valid input Bitmap object of the type 'ARGB_8888' or 'RGB_565'. + * @param mat is a valid output Mat object, it will be reallocated if needed, so it's possible to pass an empty Mat. + * @param unPremultiplyAlpha is a flag if the bitmap needs to be converted from alpha premultiplied format (like Android keeps 'ARGB_8888' ones) to regular one. The flag is ignored for 'RGB_565' bitmaps. */ - public static void bitmapToMat(Bitmap b, Mat m) { - if (b == null) - throw new java.lang.IllegalArgumentException("Bitmap b == null"); - if (m == null) - throw new java.lang.IllegalArgumentException("Mat m == null"); - nBitmapToMat(b, m.nativeObj); + public static void bitmapToMat(Bitmap bmp, Mat mat, boolean unPremultiplyAlpha) { + if (bmp == null) + throw new java.lang.IllegalArgumentException("bmp == null"); + if (mat == null) + throw new java.lang.IllegalArgumentException("mat == null"); + nBitmapToMat(bmp, mat.nativeObj, unPremultiplyAlpha); } + /** + * Shortened form of the bitmapToMat(bmp, mat, unPremultiplyAlpha=false) + * @param bmp is a valid input Bitmap object of the type 'ARGB_8888' or 'RGB_565'. + * @param mat is a valid output Mat object, it will be reallocated if needed, so it's possible to pass an empty Mat. + */ + public static void bitmapToMat(Bitmap bmp, Mat mat) { + bitmapToMat(bmp, mat, false); + } + /** * Converts OpenCV Mat to Android Bitmap. *

@@ -102,18 +111,34 @@ public class Utils { *
The output Bitmap object has to be of the same size as the input Mat and of the types 'ARGB_8888' or 'RGB_565'. *
The function throws an exception if the conversion fails. * - * @param m is a valid input Mat object of the types 'CV_8UC1', 'CV_8UC3' or 'CV_8UC4'. - * @param b is a valid Bitmap object of the same size as the Mat m and of type 'ARGB_8888' or 'RGB_565'. + * @param mat is a valid input Mat object of the types 'CV_8UC1', 'CV_8UC3' or 'CV_8UC4'. + * @param bmp is a valid Bitmap object of the same size as the Mat m and of type 'ARGB_8888' or 'RGB_565'. + * @param premultiplyAlpha is a flag if the Mat needs to be converted to alpha premultiplied format (like Android keeps 'ARGB_8888' bitmaps). The flag is ignored for 'RGB_565' bitmaps. */ - public static void matToBitmap(Mat m, Bitmap b) { - if (m == null) - throw new java.lang.IllegalArgumentException("Mat m == null"); - if (b == null) - throw new java.lang.IllegalArgumentException("Bitmap b == null"); - nMatToBitmap(m.nativeObj, b); + public static void matToBitmap(Mat mat, Bitmap bmp, boolean premultiplyAlpha) { + if (mat == null) + throw new java.lang.IllegalArgumentException("mat == null"); + if (bmp == null) + throw new java.lang.IllegalArgumentException("bmp == null"); + nMatToBitmap(mat.nativeObj, bmp, premultiplyAlpha); } - private static native void nBitmapToMat(Bitmap b, long m_addr); + /** + * Shortened form of the matToBitmap(mat, bmp, premultiplyAlpha=false) + * @param mat is a valid input Mat object of the types 'CV_8UC1', 'CV_8UC3' or 'CV_8UC4'. + * @param bmp is a valid Bitmap object of the same size as the Mat m and of type 'ARGB_8888' or 'RGB_565'. + */ + public static void matToBitmap(Mat mat, Bitmap bmp) { + matToBitmap(mat, bmp, false); + } + + + // native stuff + static { + System.loadLibrary("opencv_java"); + } - private static native void nMatToBitmap(long m_addr, Bitmap b); + private static native void nBitmapToMat(Bitmap b, long m_addr, boolean unPremultiplyAlpha); + + private static native void nMatToBitmap(long m_addr, Bitmap b, boolean premultiplyAlpha); }