Image transparency darkened when saved using OpenCv

android-ndk, opencv

Solution

There are two ways representing alpha in an RGBA pixel, premultiplied or not. With premultiplication, the R, G, and B values are multiplied by the percentage of alpha: color = (color * alpha) / 255. This simplifies a lot of blending calculations and is often used internally in imaging libraries. Before saving out to a format that doesn't use premultiplied alpha, such as PNG, the color values must be "unmultiplied": color = (255 * color) / alpha. If it is not, the colors will look too dark; the more transparent the color, the darker it will be. That looks like the effect you're seeing here.

Problem

I created a drawing application where I allow the user to draw and save the image to later reload to continue drawing. Essentially, I'm passing the drawing as a bitmap to the JNI layer to be saved and the same to load a previous drawing. I'm using OpenCv to write and read to png file. I'm noticing something weird in terms of the transparencies of the image. It almost seems as the transparency is being calculated against a black color on OpenCv? Take a look a the images attached, the contain lines that have transparencies. Correct transparency by passing int array to native code, no color conversion needed: Darkened transparency by passing Bitmap object to native code, color conversion needed: What could potentially be happening? Saving image using native Bitmap get pixel methods: ``` if ((error = AndroidBitmap_getInfo(pEnv, jbitmap, &info)) < 0) { LOGE("AndroidBitmap_getInfo() failed! error:%d",error); } if (0 == error) { if ((error = AndroidBitmap_lockPixels(pEnv, jbitmap, &pixels)) < 0) { LOGE("AndroidBitmap_lockPixels() failed ! error=%d", error); } } if (0 == error) { if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) { LOGI("ANDROID_BITMAP_FORMAT_RGBA_8888"); } else { LOGI("ANDROID_BITMAP_FORMAT %d",info.format); } Mat bgra(info.height, info.width, CV_8UC4, pixels); Mat image; //bgra.copyTo(image); // fix pixel order RGBA -> BGRA cvtColor(bgra, image, COLOR_RGBA2BGRA); vector<int> compression_params; compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION); compression_params.push_back(3); // save image if (!imwrite(filePath, image, compression_params)) { LOGE("saveImage() -> Error saving image!"); error = -7; } // release locked pixels AndroidBitmap_unlockPixels(pEnv, jbitmap); } ``` Saving image using native int pixel array methods: ``` JNIEXPORT void JNICALL Java_com_vblast_smasher_Smasher_saveImageRaw (JNIEnv *pEnv, jobject obj, jstring jFilePath, jintArray jbgra, jint options, jint compression) { jint* _bgra = pEnv->GetIntArrayElements(jbgra, 0); const char *filePath = pEnv->GetStringUTFChars(jFilePath, 0); if (NULL != filePath) { Mat image; Mat bgra(outputHeight, outputWidth, CV_8UC4, (unsigned char *)_bgra); bgra.copyTo(image); if (0 == options) { // replace existing cache value mpCache->insert(filePath, image); } vector<int> compression_params; compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION); compression_params.push_back(compression); // save image if (!imwrite(filePath, image)) { LOGE("saveImage() -> Error saving image!"); } } pEnv->ReleaseIntArrayElements(jbgra, _bgra, 0); pEnv->ReleaseStringUTFChars(jFilePath, filePath); } ``` Update 05/25/12: After a little more research I'm finding out that this issue does not happen if I get the int array of pixels from the bitmap and pass that directly to the JNI as opposed to what I do currently which is pass the entire Bitmap to the JNI layer then get the pixels and use `cvtColor` to convert pixels properly. Am I using the right pixel conversion?

Original source