Background
I'm new to renderscript, and I would like to try some experiments with it (but small ones and not the complex ones we find in the SDK), so I thought of an exercise to try out, which is based on a previous question of mine (using NDK).
What I want to do
In short, I would like to pass a bitmap data to renderscript, and then I would like it to copy the data to another bitmap that has the dimensions opposite to the previous one, so that the second bitmap would be a rotation of the first one.
For illustration:
From this bitmap (width:2 , height:4):
01
23
45
67
I would like it to rotate (counter clock-wise of 90 degrees) to:
1357
0246
The problem
I've noticed that when I try to change the signature of the root function, Eclipse gives me errors about it.
Even making new functions creates new errors. I've even tried the same code written on Google's blog (here ), but I couldn't find out how he got to create the functions he used, and how come I can't change the filter function to have the input and output bitmap arrays.
What can I do in order to customize the parameters I send to renderscript, and use the data inside it?
Is it ok not to use "filter" or "root" functions (API 11 and above)? What can I do in order to have more flexibility about what I can do there?
You are asking a bunch of separate questions here, so I will answer them in order.
1) You want to rotate a non-square bitmap. Unfortunately, the bitmap model for Renderscript won't allow you to do this easily. The reason for this is that that input and output allocations must have the same shape (i.e. same number of dimensions and values of those dimensions, even if the Types are different). In order to get the effect you want, you should use a root function that only has an output allocation of the new shape (i.e. input columns X input rows). You can create an rs_allocation global variable for holding your input bitmap (which you can then create/bind on the Java side). The kernel then merely needs to set the output cell to the result of rsGetElementAt(globalInAlloc, y, x).
2) If you are using API 11, you can't adjust the signature of the root() function (you can pass NULL allocations as input, output on the Java side if you are not using them). You also can't create more than 1 kernel per source file on these older API levels, so you are forced to only have a single "root()" function. If you want to use more kernels per source file, consider targeting a higher API level.
Related
My app uses a proprietary implementation of Canny edge detection based on RenderScript. I tested this on numerous devices with various APIs and it worked very reliably. Now I got the new Samsung S7 working on API23. Here (and only here) I encountered a rather ugly problem. Some of the edge pictures are studded with thousands of artifacts that stem from the Magnitude gradient calculation kernel and are NOT based on actual image information. After trying with all kind of TargetAPIs, taking renderscript.support.mode on and off, etc. I finally found that the problem only arises, when the RenderScript (and Script) instances are used for the second or more times. It does not arise when using them for the first time.
For efficiency reasons I created the RenderScript and Script instances only once in the onCreate method of MainActivity and used it repeatedly thereafter. Of course I don't like to change that.
Does anyone have a solution of this problem? Thanks.
UPDATE: Crazy things are going on here. It seems that freshly created Allocations are NOT empty from the outset! When running:
Type.Builder typeUCHAR1 = new Type.Builder(rs, Element.U8(rs));
typeUCHAR1.setX(width).setY(height);
Allocation alloc = Allocation.createTyped(rs, typeUCHAR1.create());
byte se[] = new byte[width*height];
alloc.copyTo(se);
for (int i=0;i<width*height;i++){
if (se[i]!=0){
Log.e("content: ", String.valueOf(se[i]));
}
}
... the byte Array se is full of funny numbers.... HELP! Any idea, what is going on here?
UPDATE2: I stumbled over my own ignorance here - and really don't deserve a point for this masterpiece.... However, to my defense I must say that the problem was slightly more subtle that it appears here. The context was, that I needed to assign a global allocation (Byte/U8) which initially should be empty (i.e. zero) and then, within the kernel getting partially set to 1 (only where the edges are) via rsSetElementAt_uchar(). Since this worked for many months, I was not aware anymore of the fact, that I didn't explicitely assign the zeros in this allocation.... This only had consequences in API 23, so maybe this can help others not to fall into this trap.... So, note: other than numerical Arrays that are filled with 0 (as by Java default), Allocations cannot assumed to be full of zeros at initiation. Thanks, Sakridge.
Allocation data for primitive types (non-struct/object) is not initialized by default when an Allocation is created unless passed a bitmap using the createFromBitmap api. If you are expecting this then possibly you have a bug in your app which is not exposed when the driver initializes to 0s. It would help if you can post example code which reproduces the problem.
Initialize your allocations by copying from a bitmap or Java array.
I want to combine two Renderscript scripts into a scriptGroup. The first one is the ScriptIntrinsicBlur. Based on the blurred U8 allocation as input the second script calculates two things: gradient and gradient-direction. The latter is the formal out-Allocation of the second kernel. The first one is a global allocation filled via rsSetElementAt_float(). Now, I find this second allocation is returned empty after execution of the scriptGroup.
Question: Is my assumption correct that with a scriptGroup you cannot use script globals - or at least not change them via rsSetElementAt_(...)?
UPDATE: I realized that the performance gain by using U8 both as output of the ScriptIntrinsicBlur and as input of the proprietary kernel is already more than satisfactory, even in a simple sequential set-up of both scripts. This is primarily because it avoids to copyTo the ScriptIntrinsicBlur's out-Allocation first into a Java-array before passing it as a separate input-allocation to the 2nd kernel.
Before, I used U8_4 (i.e. Bitmap equivalent) as output of ScriptIntrinsicBlur, and then converted it to a one-dimensional greyscale int[] array, before passing it as in-Allocation to the proprietary kernel... Now I convert to greyscale byte[] (i.e. U8) already before entering the allocation into ScriptIntrinsicBlur and use U8 also as input for the 2nd kernel.
This is what I realize again and again when working with RS: it is really worth to simplify data flows to the extent possible, the speed gains are fantastic. (maybe I will check the Scriptgroup question at a later stage, as for now I am happy with the result).
There should be no issue with using a script global like this. It's not as efficient as the output allocation, but is possible. You mentioned the out allocation is empty, what are you seeing in the script global?
I cannot find any way to retrieve a Path object representing a string. Does it exist? A list of the necessary points would be enough, but I guess, internally, a path is used.
For example in GDI+ there is:
GraphicsPath p = new GraphicsPath();
p.AddString("string");
From there any point of the "drawn string" can be accessed and modified.
PS: I do not mean drawing a text along a path.
I've spent quite a long time solving this problem (to draw vectorized text in OpenGL) and had to dig deep into libSkia sources. And it turned out pretty simple:
Indeed, canvas uses paths internally and it converts text to vector paths using SkPaint's getTextPath() method. Which is, luckily, exposed at Java side in public API as android.graphics.Paint.getTextPath(). After that, you can read Path back with android.graphics.PathMeasure.
Unfortunately, you can't read exact drawing commands, but you can sample the curve pretty closely using something like bisection.
I need to convert 32bpp images in Android , an instance of Bitmap class with a Bitmap.Config as ARGB_8888.
1. How can I reduce the color depth image to 8bpp and 4bpp?
2. Does android provide any java helper classes to achieve the same?
Use the copy()-method of your bitmap. Here you can specify the resulting color-depth from one of the ones available throught Bitmap.Config (16 or 8 bpp, I have seen a few other configurations in various fields in Android but the only ones that seems compatible with the Bitmap are the ones in Bitmap.Config).
You need to use an external image processing library to do that kind of color quantization. I would prefer use Leptonica. Its written in C but you can find Android Java bindings in this project
In a game I need to keeps tabs of which of my pooled sprites are in use. When "active" multiple sprites at once I want to transfer them from my passivePool to activePool both of which are immutable HashSets (ok, i'll be creating new sets each time to be exact). So my basic idea is to along the lines of:
activePool ++= passivePool.take(5)
passivePool = passivePool.drop(5)
but reading the scala documentation I'm guessing that the 5 that I take might be different that the 5 I then drop. Which is definitely not what I want. I could also say something like:
val moved = passivePool.take(5)
activePool ++= moved
passivePool --= moved
but as this is something I need to do pretty much every frame in realtime on a limited device (Android phone) I guess this would be much slower as I will have to search one by one each of the moved sprites from the passivePool.
Any clever solutions? Or am I missing something basic? Remember the efficiency is a primary concern here. And I can't use Lists instead of Sets because I also need random-access removal of sprites from activePools when the sprites are destroyed in the game.
There's nothing like benchmarking for getting answers to these questions. Let's take 100 sets of size 1000 and drop them 5 at a time until they're empty, and see how long it takes.
passivePool.take(5); passivePool.drop(5) // 2.5 s
passivePool.splitAt(5) // 2.4 s
val a = passivePool.take(5); passivePool --= a // 0.042 s
repeat(5){ val a = passivePool.head; passivePool -= a } // 0.020 s
What is going on?
The reason things work this way is that immutable.HashSet is built as a hash trie with optimized (effectively O(1)) add and remove operations, but many of the other methods are not re-implemented; instead, they are inherited from collections that don't support add/remove and therefore can't get the efficient methods for free. They therefore mostly rebuild the entire hash set from scratch. Unless your hash set has only a handful of elements in it, this is bad idea. (In contrast to the 50-100x slowdown with sets of size 1000, a set of size 100 has "only" a 6-10x slowdown....)
So, bottom line: until the library is improved, do it the "inefficient" way. You'll be vastly faster.
I think there may be some mileage in using splitAt here, which will give you back both the five sprites to move and the trimmed pool in a single method invocation:
val (moved, newPassivePool) = passivePool.splitAt(5)
activePool ++= moved
passivePool = newPassivePool
Bonus points if you can assign directly back to passivePool on the first line, though I don't think it's possible in a short example where you're defining the new variable moved as well.