Why is algorithm so slow? - android

I'm creating a line graph, and the code I originally used was so slow at drawing that it was useless. I replaced it with code I found online and it became much faster. I was just curious as to why the original code is so slow. All of the code posted below is inside the onDraw() method of a custom view:
Original slow code:
float yStart = 300f;
for (int i=0; i < values.length; i++){
drawPath.moveTo(xStart, yStart);
drawPath.lineTo(xStart+10, values[i]);
drawPath.close();
canvas.drawPath(drawPath, linePaint);
xStart += 10;
yStart = values[i];
}
Later fast code:
float datalength = values.length;
float colwidth = (width - (2 * border)) / datalength;
float halfcol = colwidth / 2;
float lasth = 0;
for (int i = 0; i < values.length; i++) {
float val = values[i] - min;
float rat = val / diff;
float h = graphHeight * rat;
if (i > 0)
canvas.drawLine(((i - 1) * colwidth) + (horStart + 1) + halfcol, (border - lasth) + graphHeight, (i * colwidth) + (horStart + 1) + halfcol, (border - h) + graphHeight, linePaint);
lasth = h;
I just don't understand why one is so much more efficient than the other. Any ideas?

It is CLEAR
In the first piece, there are three operations on objects {moveTo, lineTo, drawPath, and close}
Int the second piece, it is all float operations except one operation on objects

Using Paths makes the drawing significantly slower than simply telling the Canvas to draw a straight line between two points since Path is a much more complex object than the 2 points that drawLine() uses. Paths are also filled and framed based on the Style in the Paint which could also cause a slowdown.
In general, using objects and calling a lot of methods in a loop slows down your code.

After a bit of search, the problem probably came from where I said : you should only call moveTo for the first point of the graph, and then only call lineTo in the loop. When the path is defined entirely (after the for loop) you may draw it. The path is optimized for your purpose, but you where not using it correctly.
http://developer.android.com/reference/android/graphics/Path.html#lineTo%28float,%20float%29

I think you should do it like this:
float yStart = 300f;
drawPath.moveTo(xStart, yStart);
for (int i=0; i < values.length; i++){
drawPath.lineTo(xStart+10, values[i]);
xStart += 10;
yStart = values[i];
}
drawPath.close();
canvas.drawPath(drawPath, linePaint);
otherwise you will draw on the canvas the "building" of drawPath X times.
Also you can precalculate the path and have only the canvas.drawPath in the onDraw.

Also, you should use path.reset() to clear up the memory. Otherwise, every time you draw a different object using the same path it's gonna draw ALL of the objects you've drawn before using that path. So the complete code, would be.
float yStart = 300f;
drawPath.moveTo(xStart, yStart);
for (int i=0; i < values.length; i++){
drawPath.lineTo(xStart+10, values[i]);
xStart += 10;
yStart = values[i];
}
drawPath.close();
canvas.drawPath(drawPath, linePaint);
drawPath.reset();

Related

Multiple colors for single path Android

Good day.I am creating a siri like wave for android and i encounter an big issue.I need the wave to be in 4 colors.Lets assume i only have one single line which is drawing on the screen accordingly to the voice decibels.Anyway i am able to do it but no way i am able to give 4 different colors for same path.Assume it is 1 single path which moves from screen start to screen end,i need that line to have 4 different colors,mainly i had to divide the path into 4 parts and draw the color for each parts,but neither google,nor any other source give me anything (not even found anything similar to what i want).
Meanwhile i am posting the code where actually i am drawing the lines.
for (int l = 0; l < mWaveCount; ++l) {
float midH = height / 2.0f;
float midW = width / 2.0f;
float maxAmplitude = midH / 2f - 4.0f;
float progress = 1.0f - l * 1.0f / mWaveCount;
float normalAmplitude = (1.5f * progress - 0.5f) * mAmplitude;
float multiplier = (float) Math.min(1.0, (progress / 3.0f * 2.0f) + (1.0f / 3.0f));
if (l != 0) {
mSecondaryPaint.setAlpha((int) (multiplier * 255));
}
mPath.reset();
for (int x = 0; x < width + mDensity; x += mDensity) {
float scaling = 1f - (float) Math.pow(1 / midW * (x - midW), 2);
float y = scaling * maxAmplitude * normalAmplitude * (float) Math.sin(
180 * x * mFrequency / (width * Math.PI) + mPhase) + midH;
// canvas.drawPoint(x, y, l == 0 ? mPrimaryPaint : mSecondaryPaint);
//
// canvas.drawLine(x, y, x, 2*midH - y, mSecondaryPaint);
if (x == 0) {
mPath.moveTo(x, y);
} else {
mPath.lineTo(x, y);
// final float x2 = (x + mLastX) / 2;
// final float y2 = (y + mLastY) / 2;
// mPath.quadTo(x2, y2, x, y);
}
mLastX = x;
mLastY = y;
}
if (l == 0) {
canvas.drawPath(mPath, mPrimaryPaint);
} else {
canvas.drawPath(mPath, mSecondaryPaint);
}
}
I tried to change color on if (l == 0) {
canvas.drawPath(mPath, mPrimaryPaint);
} but if i change it here,no result at all,either the line is separate and not moving at all,but it should,either the color is not applied,propably because i am doing it in loop as i had to and everytime the last color is picked to draw.Anyway can you help me out?Even an small reference is gold for me because really there is nothing at all in the internet.
Anyway even though Matt Horst answer fully correct,i found the simplest and easiest solution...i never thought it would be so easy.Anyway if in world there is someone who need to make an path divided into multiple colors,here is what you can do
int[] rainbow = getRainbowColors();
Shader shader = new LinearGradient(0, 0, 0, width, rainbow,
null, Shader.TileMode.REPEAT);
Matrix matrix = new Matrix();
matrix.setRotate(90);
shader.setLocalMatrix(matrix);
mPrimaryPaint.setShader(shader);
Where getRainbowColors() is an array of colors you wish your line to have and width is the length of the path so the Shader knows how to draw the colors in right way to fit the length of path.Anyway .....easy isnt it?and pretty purged me a lot to get into this simple point.Nowhere in internet you could find only if you are looking for something completelly different,than you might come across this.
It seems to me like you could set up one paint for each section, each with a different color. Then set up one path for each section too. Then as you draw across the screen, wherever the changeover point is between sections, start drawing with the new path. And make sure first to use moveTo() on the new path so it starts off where the old one left off.
For my solution, I tried changing the color of the linePaint in the onDraw Call. But it was drawing a single color.
So i used two different paints for two different colors and draw path on the canvas.
And it worked. Hope it helps someone out there.

More efficient way to draw lines on canvas?

I'm drawing a grid over a bitmap. The user can change the size, aspect ratio, or drag the grid around. Doing so with drawLines causes fairly bad stuttering when these operations occur. Is there a better approach?
private void drawGrid()
{
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
gridOverlayView.invalidate();
int count = 0;
for(int i = 0; i < numColumns +1 ; i++)
{
points[count] = originX + (cellWidth * i);
points[count+1] = originY;
points[count+2] = originX + (cellWidth * i);
points[count+3] = originY + (gridHeight);
count += 4;
}
for(int i = 0; i < numRows +1; i++)
{
points[count] = originX;
points[count+1] = originY + (cellHeight * i);
points[count+2] = originX + (gridWidth);
points[count+3] = originY + (cellHeight * i);
count += 4;
}
canvas.drawLines(points, 0, points.length, paint);
gridOverlayView.setImageBitmap(gridOverlayBitmap);
}
this is being called from:
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
originX += e2.getX() - e1.getX();
if(originX < 0f)
{
originX = 0f;
}
if(originX + gridWidth > imageWidth)
{
originX = imageWidth - gridWidth;
}
originY += e2.getY() - e1.getY();
if(originY < 0f)
{
originY = 0f;
}
if(originY + gridHeight > imageHeight)
{
originY = imageHeight - gridHeight;
}
drawGrid();
return true;
}
Since there's no source code you're calling that function, I cannot find the exact reason of stuttering. I guess you want to change the grids in 'real-time' with user interaction.
I think you should avoid redrawing the entire grid while user is dragging.
Instead, you can translate, scale or rotate(if you need) the previous Bitmap as you wish.
Of course the result image will sometimes not in a good quality, but you can recreate the whole grid bitmap when the user stops interacting.
By this way you can achieve 'real-time-like' grid creating effect.
I solved the problem by doing a couple things: Optimizing the layout following the dev guide. Then I simply scaled down the bitmap I was using to draw the grid, then scaled it back up when I wanted to save it onto the original image. That got everything moving nice and smoothly... (actually too fast)

How to efficiently get and set bitmap pixels

I am currently making an app that involves altering the RGB values of pixels in a bitmap and creating a new bitmap after.
My problem is I need help increasing speed of this process. (It can take minutes to process a bitmap with inSampleSize = 2 and forever to process an inSampleSize = 1) Right now, I am using the getPixel and setPixel methods to alter the pixels and believe these two methods are the root of the problem as they are very inefficient. The getPixels method isn't suitable as I am not altering each pixel in order (ex. getting a pixel and changing a radius of 5 pixels around it to the same colour) unless anyone knows of a way to use getPixels (perhaps be able to put the pixels in a 2D array).
This is part of my code:
public static final alteredBitmp(Bitmap bp)
{
//initialize variables
// ..................
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
for (int x = 0; x < width; x++) {
int left = Math.max(0, x - RADIUS);
int right = Math.min(x + RADIUS, width - 1);
for (int y = 0; y < height; ++y) {
int top = Math.max(0, y - RADIUS);
int bottom = Math.min(y + RADIUS, height - 1);
int maxIndex = -1;
for (int j = top; j <= bottom; j++) {
for (int i = left; i <= right; i++) {
pixelColor = bitmap.getPixel(i, j);
//get rgb values
//make changes to those values
}
}
}
}
//set new rgb values
bitmap.setPixel(x, y, Color.rgb(r, g, b));
//return new bitmap
Much thanks in advance!
Consider looking at RenderScript, which is Android's high performance compute framework. As you are iterating over width x height number of pixels and altering each one which in a modern device could be around a million pixels or higher, doing it in a single thread can take minutes. RenderScript can parallelize operations over CPU or the GPU where possible.
http://android-developers.blogspot.com/2012/01/levels-in-renderscript.html
http://developer.android.com/guide/topics/renderscript/index.html
Google IO 2013 session:
https://youtu.be/uzBw6AWCBpU
RenderScript compatibility library: http://android-developers.blogspot.com/2013/09/renderscript-in-android-support-library.html

Drawing and scrolling paths performance issues

Hi I'm doing and endless sidescroller game where the terrain looks like a tunnel which is infinite. I managed to randomly generate the tunnel using this code:
private void createPaths() {
if(startingPath) {
pathBottom.setLastPoint(0, canvasHeight);
pathTop.setLastPoint(0, 0);
slopeWidth = 0;
slopeHeight = generateRandomNumber(canvasHeight / 4, canvasHeight / 2);
lastX = 0;
lastY = canvasHeight - slopeHeight;
newX = lastX;
newY = lastY;
startingPath = false;
} else {
lastX = canvasWidth;
lastY = newY;
newX = lastX;
newY = canvasHeight - slopeHeight;
}
pathBottom.lineTo(lastX, lastY);
pathTop.lineTo(lastX, lastY - OFFSET);
do {
lastX = newX;
lastY = newY;
slopeWidth = generateRandomNumber(canvasWidth / 8, canvasWidth / 2);
newX += slopeWidth;
if(i % 2 == 0) {
slopeHeight = generateRandomNumber(canvasHeight / 12, canvasHeight / 6);
newY = canvasHeight - slopeHeight;
} else {
slopeHeight = generateRandomNumber(canvasHeight / 4, canvasHeight / 2);
newY = canvasHeight - slopeHeight;
}
pathBottom.cubicTo(
interpolateLinear(lastX, newX, 0.333f),
lastY,
interpolateLinear(lastX, newX, 0.666f),
newY,
newX,
newY);
pathTop.cubicTo(
interpolateLinear(lastX, newX, 0.333f),
lastY - OFFSET,
interpolateLinear(lastX, newX, 0.666f),
newY - OFFSET,
newX,
newY - OFFSET);
i++;
} while (newX < canvasWidth * 2);
pathBottom.lineTo(newX, canvasHeight);
pathTop.lineTo(newX, 0);
}
and scroll it using:
public void updateTerrain() {
moveX -= speed;
int pos = newX - canvasWidth + moveX;
if(pos > 0) {
Matrix matrix = new Matrix();
matrix.setTranslate(-speed, 0);
pathBottom.transform(matrix);
pathTop.transform(matrix);
} else {
createPaths();
moveX = 0;
}
}
The problem is: the longer the path is the game becomes more "choppy". I think I should reduce the points that are being draw in the path after some time but to be honest I have no idea how to do it and still let the terrain scroll and generate. I would be grateful if you could help me. Thanks.
This looks like a small piece of a larger piece of logic. The performance issue may lie in some other code not shown here.
The general advice ( according to people like Romain Guy and Chet Haase ) is to avoid object allocation ( aka new ) during onDraw. Any "new" has the potential to trigger GC.
I would re-use the same instance of Matrix and just update it.
Also, as fadden mentioned "fixed-size sliding window structure" ( similar to circular buffer or ring buffer ) the comment above, you should ensure that your Path objects are a fixed size.
Pick a fixed number of points for the path ( let's say 200 )
Keep track of those points in an array and keep a "startindex" variable to track of the "logical" start of the array. When you need to add a new point, increment the index modulo the array size, overwrite the last point ( index - 1 modulo array size ). When you get to the end of the array you have to wrap ( start back at the beginning and go to startindex - 1 ).
Use path.incReserve to preallocate the memory when the view is created.
Use path.rewind to reset the path
Then re-use the same Path instance, to re-add all your points from your array of points ( starting at the "startIndex" )

Detect road lanes with android and opencv

i have problem with detection road lanes with my phone.
i wrote some code for road lanes detection, but him not working for me.
From camera get modifications from normal view to BGR colors and try use GausianBlur and Canny, but i think i not good draw lanes for detection.
Maybe some people have another idea how detection road lanes with OpenCV?
Mat mYuv = new Mat(height + height / 2, width, CvType.CV_8UC1);
Mat mRgba = new Mat(height + height / 2, width, CvType.CV_8UC1);
Mat thresholdImage = new Mat(height + height / 2, width, CvType.CV_8UC1);
mYuv.put(0, 0, data);
Imgproc.cvtColor(mYuv, mRgba, Imgproc.COLOR_YUV420p2BGR, 4);
//convert to grayscale
Imgproc.cvtColor(mRgba, thresholdImage, Imgproc.COLOR_mRGBA2RGBA, 4);
// Perform a Gaussian blur (convolving in 5x5 Gaussian) & detect edges
Imgproc.GaussianBlur(mRgba, mRgba, new Size(5,5), 2.2, 2);
Imgproc.Canny(mRgba, thresholdImage, VActivity.CANNY_MIN_TRESHOLD, VActivity.CANNY_MAX_THRESHOLD);
Mat lines = new Mat();
double rho = 1;
double theta = Math.PI/180;
int threshold = 50;
//do Hough transform to find lanes
Imgproc.HoughLinesP(thresholdImage, lines, rho, theta, threshold, VActivity.HOUGH_MIN_LINE_LENGTH, VActivity.HOUGH_MAX_LINE_GAP);
for (int x = 0; x < lines.cols() && x < 1; x++){
double[] vec = lines.get(0, x);
double x1 = vec[0],
y1 = vec[1],
x2 = vec[2],
y2 = vec[3];
Point start = new Point(x1, y1);
Point end = new Point(x2, y2);
Core.line(mRgba, start, end, new Scalar(255, 0, 0), 3);
}
This approach is fine and I've done something similar, not for road line detection but I did notice that it could be used for that purpose. Some comments:
Not sure why you do:
Imgproc.cvtColor(mRgba, thresholdImage, Imgproc.COLOR_mRGBA2RGBA, 4);
as 1. the comment say convert to greyscale, which is a single channel and 2. thresholdImage will get overwritten with the call to Canny later. You just need to dimension thresholdImage with:
thresholdImage = new Mat(mRgba.size(), CvType.CV_8UC1);
What are your parameter values to the call to Canny? I played about with mine considerably and ended up with values like: threshold1 = 441, threshold2 = 160, aperture = 3.
Likewise Imgproc.HoughLinesP: I use Imgproc.HoughLines rather than Imgproc.HoughLinesP with parameters: threshold = 80, minLen = 30, maxLen = 10.
Also have a look at:
for (int x = 0; x < lines.cols() && x < 1; x++){
&& x < 1 means you will only take the first line that the call to HoughLinesP returns. I'd suggest you remove this and use some other criteria to reduce the number of lines; for example, I was interesting in only horizontal and vertical lines so I used atan2 to calculate line angles and exclude those that deviate too much.
UPDATE
Here is how I get the angle of a line. Assuming coordinates of one point is (x1,y1) and the other (x2, y2) then to get the angle:
double lineAngle = Math.atan2(y2 - y1, x2 - x1);
this should return an angle in radians between -PI/2 and PI/2
With regard Canny parameters then I would experiment - I set up onTouch so that I could adjust the threshold values by touching certain parts of the screen to see the effects in realtime. Note that aperture is rather disappointing parameter: it seems to only like odd values 3, 5 and 7 and 3 is the best that I've found.
Something like in the onTouch method:
int w = mRgba.width();
int h = mRgba.height();
float x = event.getX();
float y = event.getY();
if ((x < w / 3) && (y < h / 2)) t1 += 20;
if ((x < w / 3) && (y >= h / 2)) t1 -= 20;
if ((x > 2 * w / 3) && (y < h / 2)) t2 += 20;
if ((x > 2 * w / 3) && (y >= h / 2)) t2 -= 20;
t1 and t2 being the threshold values passed to the Canny call.

Categories

Resources