I need to create a bitmap in c code in NDK - android

So my issue is that I get for a video call the frames in my c code as a byte array of I420. Which I then convert to NV21 and send the byte array to create the bitmap. But because I need to create a YUV Image from the byte array, and then a bitmap from that, I have a conversion overhead and that is causing delays and loss in quality.
I am wondering if there is another way to do this. Somehow so that I can create the bitmap directly in the c code, and maybe even add it to the bitmap, or a surface view from the c code? Or just simply send the bitmap to my function so I can set it there, without needing to create the bitmap in Android.
This is what I do with the byte array in the c code:
if(size == 0)
return;
jboolean isAttached;
JNIEnv *env;
jint jParticipant;
jint jWidth;
jint jHeight;
jbyteArray jRawImageBytes;
env = getJniEnv(&isAttached);
if (env == NULL)
goto FAIL0;
//LOGE(".... **** ....TRYING TO FIND CALLBACK");
LOGI("FrameReceived will reach here 1");
char *modifiedRawImageBytes = malloc(size);
memcpy(modifiedRawImageBytes, rawImageBytes, size);
jint sizeWH = width * height;
jint quarter = sizeWH/4;
jint v0 = sizeWH + quarter;
for (int u = sizeWH, v = v0, o = sizeWH; u < v0; u++, v++, o += 2) {
modifiedRawImageBytes[o] = rawImageBytes[v]; // For NV21, V first
modifiedRawImageBytes[o + 1] = rawImageBytes[u]; // For NV21, U second
}
if(remote)
{
if(frameReceivedRemoteMethod == NULL)
frameReceivedRemoteMethod = getApplicationJniMethodId(env, applicationJniObj, "vidyoConferenceFrameReceivedRemoteCallback", "(III[B)V");
if (frameReceivedRemoteMethod == NULL) {
//LOGE(".... **** ....CALLBACK NOT FOUND");
goto FAIL1;
}
}
This is what I do in the Android java code:
remoteResolution = width + "x" + height;
remoteBAOS = new ByteArrayOutputStream();
remoteYUV = new YuvImage(rawImageBytes, ImageFormat.NV21, width, height, null);
remoteYUV.compressToJpeg(new Rect(0, 0, width, height), 100, remoteBAOS);
remoteBA = remoteBAOS.toByteArray();
remoteBitmap = BitmapFactory.decodeByteArray(remoteBA, 0, remoteBA.length);
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
remoteView.setImageBitmap(remoteBitmap);
}
});
This is how the sample app of the SDK I am using had the sample. but I feel that this is not at all best practice, and there has to be a way to get the Bitmap quicker from the byte array, and preferably in the c code. Any ideas on how to improve this?
EDIT:
I modified my Java code. I know use this library: https://github.com/silvaren/easyrs
so my code will be:
remoteBitmap = Nv21Image.nv21ToBitmap(rs, rawImageBytes, width, height);
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
remoteView.setImageBitmap(remoteBitmap);
}
});
Where nv21ToBitmap does this:
public static Bitmap yuvToRgb(RenderScript rs, Nv21Image nv21Image) {
long startTime = System.currentTimeMillis();
Type.Builder yuvTypeBuilder = new Type.Builder(rs, Element.U8(rs))
.setX(nv21Image.nv21ByteArray.length);
Type yuvType = yuvTypeBuilder.create();
Allocation yuvAllocation = Allocation.createTyped(rs, yuvType, Allocation.USAGE_SCRIPT);
yuvAllocation.copyFrom(nv21Image.nv21ByteArray);
Type.Builder rgbTypeBuilder = new Type.Builder(rs, Element.RGBA_8888(rs));
rgbTypeBuilder.setX(nv21Image.width);
rgbTypeBuilder.setY(nv21Image.height);
Allocation rgbAllocation = Allocation.createTyped(rs, rgbTypeBuilder.create());
ScriptIntrinsicYuvToRGB yuvToRgbScript = ScriptIntrinsicYuvToRGB.create(rs, Element.RGBA_8888(rs));
yuvToRgbScript.setInput(yuvAllocation);
yuvToRgbScript.forEach(rgbAllocation);
Bitmap bitmap = Bitmap.createBitmap(nv21Image.width, nv21Image.height, Bitmap.Config.ARGB_8888);
rgbAllocation.copyTo(bitmap);
Log.d("NV21", "Conversion to Bitmap: " + (System.currentTimeMillis() - startTime) + "ms");
return bitmap;
}
This is faster. but still I feel there still is some delay. Now that I get my bitmap from renderscript instead of using a YUV Image. Is it possible to set it to my imageView somehow faster? or set it on a surfaceView somehow?

Related

RenderScript remove pixels with alpha at bitmap

I've got a bitmap and I need to remove all pixels that have alpha. Sounds easy, but I'm stuck with it.
I've got this Java code:
public static Bitmap overdrawAlphaBits(Bitmap image, int color) {
Bitmap coloredBitmap = image.copy(Bitmap.Config.ARGB_8888, true);
for (int y = 0; y < coloredBitmap.getHeight(); y++) {
for (int x = 0; x < coloredBitmap.getWidth(); x++) {
int pixel = coloredBitmap.getPixel(x, y);
if (pixel != 0) {
coloredBitmap.setPixel(x, y, color);
}
}
}
return coloredBitmap;
}
And it works fine, but slowly, processing of one bitmap takes around 2 second.
I'my trying with RenderScript. It works fast, but not stable.
here is my code:
public static Bitmap overdrawAlphaBits(Bitmap image, Context context) {
Bitmap blackbitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), image.getConfig());
RenderScript mRS = RenderScript.create(context);
ScriptC_replace_with_main_green_color script = new ScriptC_replace_with_main_green_color(mRS);
Allocation allocationRaster0 = Allocation.createFromBitmap(mRS, image, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
Allocation allocationRaster1 = Allocation.createTyped(mRS, allocationRaster0.getType());
script.forEach_root(allocationRaster0, allocationRaster1);
allocationRaster1.copyTo(blackbitmap);
allocationRaster0.destroy();
allocationRaster1.destroy();
script.destroy();
mRS.destroy();
return blackbitmap;
}
And my .rs file:
void root(const uchar4 *v_in, uchar4 *v_out) {
uint32_t rValue = v_in->r;
uint32_t gValue = v_in->g;
uint32_t bValue = v_in->b;
uint32_t aValue = v_in->a;
if(rValue!=0 || gValue!=0 || bValue!=0 || aValue!=0){
v_out->r = 0x55;
v_out->g = 0xED;
v_out->b = 0x69;
}
}
So I use this method on multiple bitmaps - at first bitmap is works fine, but than I receive corrupted images. By the way when I apply this method again on first bitmap it also corrupts it.
Looks like there is not closed memory allocation or shared resources, idk.
Any ideas, please?
Maybe there is an easier solution?
Thanks everyone in advance!
Actually you can use getPixels method to read all pixels in array and than manipulate them. It works fast enough. The problem is that getPixel works slow.
So here is the code:
public static Bitmap overdrawAlphaBits(Bitmap image, int color) {
int[] pixels = new int[image.getHeight() * image.getWidth()];
image.getPixels(pixels, 0, image.getWidth(), 0, 0, image.getWidth(), image.getHeight());
for (int i = 0; i < image.getWidth() * image.getHeight(); i++) {
if (pixels[i] != 0) {
pixels[i] = color;
}
}
image.setPixels(pixels, 0, image.getWidth(), 0, 0, image.getWidth(), image.getHeight());
return image;
}
In your .rs file, I think the rValue, gValue, etc should be of type uchar, not uint32_t. Also the if-statement is missing an else-clause where the v_in values are copied to v_out, otherwise you get undefined output values. Note that in your Java code, the output bitmap is initialised as a copy. This is not the case in the renderscript code, where you create an output allocation of the same type as the input, but the values are not copied. Therefore you need to copy the input values in the kernel.

Imebra - Change color contrasts

I am able to load dicom image using imebra, and want to change the colors of image, but cant figure out a way. I want to achieve functionality as in Dicomite app.
Following is my code:
public void loadDCM() {
com.imebra.DataSet loadedDataSet = com.imebra.CodecFactory.load(dicomPath.getPath());
com.imebra.VOIs voi = loadedDataSet.getVOIs();
com.imebra.Image image = loadedDataSet.getImageApplyModalityTransform(0);
// com.imebra.Image image = loadedDataSet.getImage(0);
String colorSpace = image.getColorSpace();
long width = image.getWidth();
long height = image.getHeight();
TransformsChain transformsChain = new TransformsChain();
com.imebra.DrawBitmap drawBitmap = new com.imebra.DrawBitmap(transformsChain);
com.imebra.TransformsChain chain = new com.imebra.TransformsChain();
if (com.imebra.ColorTransformsFactory.isMonochrome(image.getColorSpace())) {
// Allocate a VOILUT transform. If the DataSet does not contain any pre-defined
// settings then we will find the optimal ones.
VOILUT voilutTransform = new VOILUT();
// Retrieve the VOIs (center/width pairs)
com.imebra.VOIs vois = loadedDataSet.getVOIs();
// Retrieve the LUTs
List < LUT > luts = new ArrayList < LUT > ();
for (long scanLUTs = 0;; scanLUTs++) {
try {
luts.add(loadedDataSet.getLUT(new com.imebra.TagId(0x0028, 0x3010), scanLUTs));
} catch (Exception e) {
break;
}
}
if (!vois.isEmpty()) {
voilutTransform.setCenterWidth(vois.get(0).getCenter(), vois.get(0).getWidth());
} else if (!luts.isEmpty()) {
voilutTransform.setLUT(luts.get(0));
} else {
voilutTransform.applyOptimalVOI(image, 0, 0, width, height);
}
chain.addTransform(voilutTransform);
com.imebra.DrawBitmap draw = new com.imebra.DrawBitmap(chain);
// Ask for the size of the buffer (in bytes)
long requestedBufferSize = draw.getBitmap(image, drawBitmapType_t.drawBitmapRGBA, 4, new byte[0]);
byte buffer[] = new byte[(int) requestedBufferSize]; // Ideally you want to reuse this in subsequent calls to getBitmap()
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
// Now fill the buffer with the image data and create a bitmap from it
drawBitmap.getBitmap(image, drawBitmapType_t.drawBitmapRGBA, 4, buffer);
Bitmap renderBitmap = Bitmap.createBitmap((int) image.getWidth(), (int) image.getHeight(), Bitmap.Config.ARGB_8888);
renderBitmap.copyPixelsFromBuffer(byteBuffer);
image_view.setImageBitmap(renderBitmap);
}
If you are dealing with a monochrome image and you want to modify the presentation luminosity/contrast, then you have to modify the parameters of the VOILUT transform (voilutTransform variable in your code).
You can get the center and width that the transform is applying to the image before calculating the bitmap to be displayed, then modify them before calling drawBitmap.getBitmap again.
E.g., to double the contrast:
voilutTransform.setCenterWidth(voilutTransform.getCenter(), voilutTransform.getWidth() / 2);
// Now fill the buffer with the image data and create a bitmap from it
drawBitmap.getBitmap(image, drawBitmapType_t.drawBitmapRGBA, 4, buffer);
See this answer for more details about the center/width

Images getting as too noise when applying filters in Android

I am developing an application which includes filters and crop too. Here I am using cropping library. Here I used 8*8 luts like sample lut. Here I want to CROP the filtered image(8*8 lut)
Here is the logic to crop the image.
Bitmap cropbitmap = ivCropimageView.getCroppedImage();
Using this bitmap I generate a thumbnail bitmap like below.
Bitmap thumbImage = ThumbnailUtils.extractThumbnail(cropbitmap, 190, 250);
When I am trying to generate thumbnails for all filters then the thumbnails are displaying as too noise like this.
This result is when I implemented the answer from renderscript.
So if anyone has ab idea please help me..
I'm working on a LUT applier library which eases the use of LUT images in Android. Now it also guesses the color axes of the LUT:
https://github.com/dntks/easyLUT/wiki
It uses the algorythm I mentioned in the other post
u can go through this, hope it will help you to get the right process.
photo is the main bitmap here.
mLut3D is the array of LUT images stored in drawable
RenderScript mRs;
Bitmap mLutBitmap, mBitmap;
ScriptIntrinsic3DLUT mScriptlut;
Bitmap mOutputBitmap;
Allocation mAllocIn;
Allocation mAllocOut;
Allocation mAllocCube;
int mFilter = 0;
mRs = RenderScript.create(yourActivity.this);
public Bitmap filterapply() {
int redDim, greenDim, blueDim;
int w, h;
int[] lut;
if (mScriptlut == null) {
mScriptlut = ScriptIntrinsic3DLUT.create(mRs, Element.U8_4(mRs));
}
if (mBitmap == null) {
mBitmap = photo;
}
mOutputBitmap = Bitmap.createBitmap(mBitmap.getWidth(),
mBitmap.getHeight(), mBitmap.getConfig());
mAllocIn = Allocation.createFromBitmap(mRs, mBitmap);
mAllocOut = Allocation.createFromBitmap(mRs, mOutputBitmap);
// }
mLutBitmap = BitmapFactory.decodeResource(getResources(),
mLut3D[mFilter]);
w = mLutBitmap.getWidth();
h = mLutBitmap.getHeight();
redDim = w / h;
greenDim = redDim;
blueDim = redDim;
int[] pixels = new int[w * h];
lut = new int[w * h];
mLutBitmap.getPixels(pixels, 0, w, 0, 0, w, h);
int i = 0;
for (int r = 0; r < redDim; r++) {
for (int g = 0; g < greenDim; g++) {
int p = r + g * w;
for (int b = 0; b < blueDim; b++) {
lut[i++] = pixels[p + b * h];
}
}
}
Type.Builder tb = new Type.Builder(mRs, Element.U8_4(mRs));
tb.setX(redDim).setY(greenDim).setZ(blueDim);
Type t = tb.create();
mAllocCube = Allocation.createTyped(mRs, t);
mAllocCube.copyFromUnchecked(lut);
mScriptlut.setLUT(mAllocCube);
mScriptlut.forEach(mAllocIn, mAllocOut);
mAllocOut.copyTo(mOutputBitmap);
return mOutputBitmap;
}
you increase the mFilter value to get different filter effect with different LUT images, you have, check it out.
you can go through the this link on github for more help, i got the answer from here:-
https://github.com/RenderScript/RsLutDemo
hope it will help

Android Nexus 10 blank png from JNI ByteArray

I am developing an application using JNI and a third party engine (Unreal Engine 4) in charge of managing the graphics pipeline/rendering.
The third party engine is written in C++, thus the need of using JNI to bridge it with Android.
The app requires to save a screenshot on the device of what is being displayed on the screen (in other words a dump of the framebuffer).
The third party engine exposes an API that calls a custom handler, passing in the width, height and color data of the screen.
colordata is a custom container of uint8 representing RGBA components.
I successfully managed to convert the colorData to a jbyteArray and pass it as an argument to a function on the JAVA side.
On the java side things are simpler: I create a bitmap from the byteArray, flip it and save it as a jpg/png via a custom AsyncTask.
The problem:
The code works marvellously o Samsung Galaxy S4/Note3 (Both Android 5.0), whereas on a Nexus 10 Android version 5.1.1 the png that gets saved is blank.
I am afraid that the problem with this lies on a depper level than the ones I have access to, i.e. graphics card/drivers/OS version, but I am not an expert in that field, so I would like to know if someone has already experienced a similar issue or could shed some light on what is causing it.
This is the code used to bridge the engine with Java (I started c++ with this project so maybe there are ownership/memory issues in this snippet. You are more than welcome to correct me in case :))
void AndroidInterface::SaveBitmap(const TArray<FColor>& colorData, int32 width, int32 height) {
JNIEnv* env = FAndroidApplication::GetJavaEnv(true);
TArray<FColor> bitmap = colorData;
TArray<uint8> compressedBitmap;
FImageUtils::CompressImageArray(width, height, bitmap, compressedBitmap);
size_t len = width*height*compressedBitmap.GetTypeSize();
LOGD("===========Width: %i, height: %i - Len of bitmap element: %i==========", width, height, len);
jbyteArray bitmapData = env->NewByteArray(len);
LOGD("===========Called new byte array==========");
env->SetByteArrayRegion(bitmapData, 0, len, (const jbyte*)compressedBitmap.GetData() );
LOGD("===========Populated byte array==========");
check (bitmapData != NULL && "Couldn't create byte array");
jclass gameActivityClass = FAndroidApplication::FindJavaClass("com/epicgames/ue4/GameActivity");
check (gameActivityClass != nullptr && "GameActivityClassNotFound");
//get the method signature to take a game screenshot
jmethodID saveScreenshot = env->GetMethodID(gameActivityClass, "saveScreenshot", "([BII)V");
env->CallVoidMethod(AndroidInterface::sGameActivity, saveScreenshot, bitmapData, width, height);
env->DeleteLocalRef(bitmapData);
}
This is the java code in charge of converting from byte[] to Bitmap:
public void saveScreenshot(final byte[] colors, int width, int height) {
android.util.Log.d("GameActivity", "======saveScreenshot called. Width: " + width + " height: " + height + "=======");
android.util.Log.d("GameActivity", "Color content---->\n " + Arrays.toString(colors));
final BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
final Bitmap bitmap = BitmapFactory.decodeByteArray(colors, 0, colors.length, opts);
final FlipBitmap flipBitmapTask = new FlipBitmap();
flipBitmapTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, bitmap);
}
FlipBitmap is the AsyncTask in charge of saving the bitmap to a file:
private class FlipBitmap extends AsyncTask<Bitmap, Void, File> {
#Override
protected File doInBackground(Bitmap... params) {
final Bitmap src = params[0];
final File file = new File(MainActivity.SCREENSHOT_FOLDER + "screenshot" + System.currentTimeMillis() + ".png");
final Matrix matrix = new Matrix();
matrix.setScale(1, -1);
final Bitmap dst = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, false);
try {
final FileOutputStream out = new FileOutputStream(file);
dst.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
#Override
protected void onPostExecute(File file) {
android.util.Log.d("GameActivity", "FlipBitmap onPostExecute");
if (file.exists()) {
final Intent i = new Intent(Intent.ACTION_SENDTO);
i.setData(Uri.parse("mailto:" + Globals.Network.MAIL_TO));
i.putExtra(Intent.EXTRA_SUBJECT, Globals.Network.MAIL_SUBJECT);
i.putExtra(Intent.EXTRA_TEXT, mBodyEmail);
i.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + file.getAbsolutePath()));
startActivity(Intent.createChooser(i, "Invia via email"));
}
}
}
Thanks in advance!

[cocos2dx android]Rendering CCSprite using raw data from Bitmap

I am trying to fetch an image from a URL into a Bitmap and then using the raw data from the Bitmap am trying to create a CCSprite. The issue here is that the image is corrupted when I display the sprite. I created a standalone android only application(no cocos2dx) and used the same code to fetch and display the Bitmap and its displayed correctly. Any reason why the image is not being properly rendered in cocos2dx?
My code to fetch the image from the URL is:
String urlString = "http://www.mathewingram.com/work/wp-content/themes/thesis/rotator/335f69c5de_small.jpg";//http://graph.facebook.com/"+user.getId()+"/picture?type=large";
Bitmap pic = null;
pic = BitmapFactory.decodeStream((InputStream) new URL(urlString).getContent());
int[] pixels = new int[pic.getWidth() * pic.getHeight()];
pic.getPixels(pixels, 0, pic.getWidth(), 0, 0,pic.getWidth(),pic.getHeight());
int len = pic.getWidth()* pic.getHeight();
nativeFbUserName(pixels,len,pic.getWidth(), pic.getHeight());
The function "nativeFbUserName" is a call to a native c++ function which is :
void Java_com_WBS_Test0001_Test0001_nativeFbUserName(JNIEnv *env, jobject thiz,jintArray name, jint len, jint width, jint height) {
jint *jArr = env->GetIntArrayElements(name,NULL);
int username[len];
for (int i=0; i<len; i++){
username[i] = (int)jArr[i];
}
HelloWorld::getShared()->picLen = (int)len;
HelloWorld::getShared()->picHeight = (int)height;
HelloWorld::getShared()->picWidth = (int)width;
HelloWorld::getShared()->saveArray(username);
HelloWorld::getShared()->schedule(SEL_SCHEDULE(&HelloWorld::addSprite),0.1);
}
void HelloWorld::saveArray(int *arrayToSave)
{
arr = new int[picLen];
for(int i = 0; i < picLen; i++){
arr[i] = arrayToSave[i];
}
}
void HelloWorld::addSprite(float time)
{
this->unschedule(SEL_SCHEDULE(&HelloWorld::addSprite));
CCTexture2D *tex = new CCTexture2D();
bool val = tex->initWithData(arr,(cocos2d::CCTexture2DPixelFormat)0,picWidth,picHeight, CCSizeMake(picWidth,picHeight));
CCLog("flag is %d",val);
CCSprite *spriteToAdd = CCSprite::createWithTexture(tex);
spriteToAdd->setPosition(ccp(500, 300));
this->addChild(spriteToAdd);
}
Edit:
So I found this link Access to raw data in ARGB_8888 Android Bitmap that states that it might be a bug. Has anyone found a solution to this?
EDIT
So I just noticed a corruption of images on the lower right corner of the image.I am not sure why this is happening and how to fix it. Any ideas?
EDIT END
Answering my own question, I obtained a byte array from the bitmap using:
byte[] data = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pic.compress(Bitmap.CompressFormat.JPEG, 100, baos);
data = baos.toByteArray();
And then passed this byte array to the native code.

Categories

Resources