Android-Optimize line drawing with Canvas - java

I am drawing a hexagonal grid on my entire phone's screen. I am drawing a hexagon by drawing six lines using Canvas. Too many lines are being drawn which makes the app unresponsive. I had to do android:HardwareAccelerated=false to atleast make it work on my Nexus 4 otherwise the app crashed with this error :
06-22 14:11:46.664: A/libc(5743): Fatal signal 6 (SIGABRT) at 0x0000166f (code=-6), thread 5743 (.nadeem.sensus4)
Although the app doesn't crash now, it takes too much time to draw the grid. This is the code of my CustomView which draws the grid :
public DrawView(Context context, Hexagon hex) {
super(context);
this.context = context;
setLayerType(View.LAYER_TYPE_HARDWARE, null);
this.hex = hex;
}
#Override
public void onDraw(Canvas canvas) {
double xOff = Math.cos(Math.PI / 6) * hex.radius;//radius is 12 for now
double yOff = Math.sin(Math.PI / 6) * hex.radius; // third of the hex
// height
for (int i = 0; i < 60; ++i) {
for (int j = 0; j < 40; ++j) {
double xPos = j * xOff * 2;
if (i % 2 != 0) { // if the current line is not even
xPos += xOff; // extra offset of half the width on x axis
}
double yPos = i * yOff * 3;
createHexagon(xPos, // X pos for hexagon center on the scene
yPos, canvas);
}
}
}
public void createHexagon(double x, double y, Canvas canvas) {
paint.setColor(Color.BLACK);
paint.setStyle(Style.STROKE);
// paint.setStyle(Style.FILL);
for (int i = 0; i < 6; i++) {
double angle = 2 * Math.PI / 6 * (i + 0.5);
double x_i = x + hex.radius * Math.cos(angle);
double y_i = y + hex.radius * Math.sin(angle);
if (i == 0)
wallpath.moveTo((float) x_i, (float) y_i);
else
wallpath.lineTo((float) x_i, (float) y_i);
}
canvas.drawPath(wallpath, paint);
canvas = null;
}
I want to ask if there's way to increase the performance or any other alternate way to achieve this grid.

Do your drawing in layers. The first time you draw, draw all your hexagons to a single bitmap. Then in all future draws, just draw that bitmap to the screen. Then add in whatever else you need to draw on top of that. It will save you 14000 draw line commands.
Your other good option is to move to openGL for drawing. But there is no way you'll ever get 14K lines to draw without hardware acceleration with any real speed.

You can achieve the same effect by drawing straight lines and utilizing DashPathEffect to toggle a line from your background colour to your visible colour.
Where the faint lines don't belong is where that particular line segment would be turned off. Since the pattern is predictable, so is your stroke effect. Because you said in a comment that you also need the coordinates of all the vertices, run a separate loop that calculates from a starting point and propagate your vertices list from there.

Related

Android piechart with icons between bars

I want to create a pie chart with images in between the legend bars. I am adding the screenshot below for better understanding, i tried using one canvas and then created one arc and tried to add images to it, but it was not working. For now i am using below pie chart library to show the bars with center text. Any suggestion will be helpful. Thanks :)
https://github.com/PhilJay/MPAndroidChart
enter image description here
Regards,
Rohit Garg
You can do that by extending PieChartRenderer.
If you look at the implementation of PieChartRenderer.drawRoundedSlices(Canvas c) you can get an example of how to get the starting coordinates of each slice.
Then just use drawBitmap or drawPicture to render your image between the pie slices. (I used Utils.drawImage in the example to mimic the source of PieChartRenderer)
As an example, i copied drawRoundedSlices and renamed it drawImageBeforeSlice. Instead of drawing the arcs, i draw bitmaps.
To make the renderer use the new method, i override drawExtras and stick a call to the new method on the end.
class PieChartRendererWithImages extends PieChartRenderer
{
protected Drawable mImage;
public PieChartRendererWithImages(PieChart chart, ChartAnimator animator, ViewPortHandler viewPortHandler, Drawable image) {
super(chart, animator, viewPortHandler);
mImage = image;
}
/**
* This draws an image before all pie-slices
*
* #param c
*/
protected void drawImageBeforeSlice(Canvas c) {
IPieDataSet dataSet = mChart.getData().getDataSet();
if (!dataSet.isVisible())
return;
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
MPPointF center = mChart.getCenterCircleBox();
float r = mChart.getRadius();
// calculate the radius of the "slice-circle"
float circleRadius = (r - (r * mChart.getHoleRadius() / 100f)) / 2f;
float[] drawAngles = mChart.getDrawAngles();
float angle = mChart.getRotationAngle();
for (int j = 0; j < dataSet.getEntryCount(); j++) {
float sliceAngle = drawAngles[j];
Entry e = dataSet.getEntryForIndex(j);
// draw only if the value is greater than zero
if ((Math.abs(e.getY()) > Utils.FLOAT_EPSILON)) {
float x = (float) ((r - circleRadius)
* Math.cos(Math.toRadians((angle + sliceAngle)
* phaseY)) + center.x);
float y = (float) ((r - circleRadius)
* Math.sin(Math.toRadians((angle + sliceAngle)
* phaseY)) + center.y);
// draw image instead of arcs
Utils.drawImage(
c,
mImage,
(int)x,
(int)y,
mImage.getIntrinsicWidth(),
mImage.getIntrinsicHeight());
}
angle += sliceAngle * phaseX;
}
MPPointF.recycleInstance(center);
}
#Override
public void drawExtras(Canvas c) {
super.drawExtras(c);
// use drawImageBeforeSlice in last step of rendering process
drawImageBeforeSlice(c);
}
}
Don't forget to set your new renderer on your PieChart:
myPieChart.setRenderer(new PieChartRendererWithImages(myPieChart, myPieChart.getAnimator(), myPieChart.getViewPortHandler(), getResources().getDrawable(R.drawable.my_image)));
Verified to work by putting it in the MPAndroidChart example:

Processing - rendering shapes is too slow

I have been doing a small little project using Processing, and the effect I wanted to achieve was a kind of "mountains" forming and moving, using Perlin Noise with the noise() function, with 2 parameters.
I was originally using a image for the background, but for illustrational purposes, I made the background black, and it's basically the same effect.
My issue is that I want to have a "history" of the mountains because they should fade away after some time, and so I made a history of PShapes, and draw the history and update it each frame.
Updating it is no issue, but drawing the PShapes seems to take a lot of time, reducing the frame rate from 60 to 10 when the length of the history is 100 elements.
Below is the code I used :
float noise_y = 0;
float noise_increment = 0.01;
// increment x in the loop by this amount instead of 1
// makes the drawing faster, since the PShapes have less vertices
// however, mountains look sharper, not as smooth
// bigger inc = better fps
final int xInc = 1;
// maximum length of the array
// bigger = less frames :(
final int arrLen = 100;
int lastIndex = 0;
PShape[] history = new PShape[arrLen];
boolean full = false;
// use this to add shapes in the history
PShape aux;
void setup() {
size(1280, 720);
}
void draw() {
background(0);
// create PShape object
aux = createShape();
aux.beginShape();
aux.noFill();
aux.stroke(255);
aux.strokeWeight(0.5);
for (float x = 0; x < width + xInc; x = x + xInc) {
float noise = noise(x / 150, noise_y) ;
// get the actual y coordinate
float y = map(noise, 0, 1, height / 2, 0);
// create vertex of shape at x, y
aux.vertex(x, y);
}
aux.endShape();
// push the current one in the history
history[lastIndex++] = aux;
// if it reached the maximum length, start it over ( kinda works like a queue )
if (lastIndex == arrLen) {
lastIndex = 0;
full = true;
}
// draw the history
// this part takes the MOST TIME to draw, need to fix it.
// without it is running at 60 FPS, with it goes as low as 10 FPS
if (full) {
for (int i = 0; i < arrLen; i++) {
shape(history[i]);
}
} else {
for (int i = 0; i < lastIndex; i++) {
shape(history[i]);
}
}
noise_y = noise_y - noise_increment;
println(frameRate);
}
I have tried to use different ways of rendering the "mountains" : I tried writing my own class of a curve and draw lines that link the points, but I get the same performance. I tried grouping the PShapes into a PShape group object like
PShape p = new PShape(GROUP);
p.addChild(someShape);
and I got the same performance.
I was thinking of using multiple threads to render each shape individually, but after doing some research, there's only one thread that is responsible with rendering - the Animation Thread, so that won't do me any good, either.
I really want to finish this, it seems really simple but I can't figure it out.
One possible solution would be, not to draw all the generated shapes, but to draw only the new shape.
To "see" the shapes of the previous frames, the scene can't be cleared at the begin of the frame, of course.
Since the scene is never cleared, this would cause, that the entire view is covered, by shapes over time. But if the scene would be slightly faded out at the begin of a new frame, instead of clearing it, then the "older" shapes would get darker and darker by time. This gives a feeling as the "older" frames would drift away into the depth by time.
Clear the background at the initlization:
void setup() {
size(1280, 720);
background(0);
}
Create the scene with the fade effect:
void draw() {
// "fade" the entire view
blendMode(DIFFERENCE);
fill(1, 1, 1, 255);
rect(0, 0, width, height);
blendMode(ADD);
// create PShape object
aux = createShape();
aux.beginShape();
aux.stroke(255);
aux.strokeWeight(0.5);
aux.noFill();
for (float x = 0; x < width + xInc; x = x + xInc) {
float noise = noise(x / 150, noise_y) ;
// get the actual y coordinate
float y = map(noise, 0, 1, height / 2, 0);
// create vertex of shape at x, y
aux.vertex(x, y);
}
aux.endShape();
// push the current one in the history
int currentIndex = lastIndex;
history[lastIndex++] = aux;
if (lastIndex == arrLen)
lastIndex = 0;
// draw the newes shape
shape(history[currentIndex]);
noise_y = noise_y - noise_increment;
println(frameRate, full ? arrLen : lastIndex);
}
See the preview:

Rotating Polygon Objects

Rotating Asteroids ( Polygons )
I am trying to rotate asteroids(polygons) so that they look nice. I am doing this through multiple mathematical equations. To start I give the individual asteroid a rotation velocity:
rotVel = ((Math.random()-0.5)*Math.PI/16);
Then I create the polygon shape,
this.shape = new Polygon();
Followed by generating the points,
for (j = 0; j < s; j++) {
theta = 2 * Math.PI / s * j;
r = MIN_ROCK_SIZE + (int) (Math.random() * (MAX_ROCK_SIZE - MIN_ROCK_SIZE));
x = (int) -Math.round(r * Math.sin(theta)) + asteroidData[0];
y = (int) Math.round(r * Math.cos(theta)) + asteroidData[1];
shape.addPoint(x, y);
}
Finally, in a loop a method is being called in which it attempts to move the polygon and its points down as well as rotating them. (I'm just pasting the rotating part as the other one is working)
for (int i = 0; i < shape.npoints; i++) {
// Subtract asteroid's x and y position
double x = shape.xpoints[i] - asteroidData[0];
double y = shape.ypoints[i] - asteroidData[1];
double temp_x = ((x * Math.cos(rotVel)) - (y * Math.sin(rotVel)));
double temp_y = ((x * Math.sin(rotVel)) + (y * Math.cos(rotVel)));
shape.xpoints[i] = (int) Math.round(temp_x + asteroidData[0]);
shape.ypoints[i] = (int) Math.round(temp_y + asteroidData[1]);
}
now, the problem is that when it prints to the screen the asteroids appear to 'warp' or rather the x and y positions on some of the polygon points 'float' off course.
I've noticed that when I make 'rotVel' be a whole number the problem is solved however the asteroid will rotate at mach speeds. So I've concluded that the problem has to be in the rounding but no matter what I do I can't seem to find a way to get it to work as the Polygon object requires an array of ints.
Does anyone know how to fix this?
Currently your asteroids rotate around (0 , 0) as far as i can see. Correct would be to rotate them around the center of the shape, which would be (n , m), where n is the average of all x-coordinates of the shape, and m is the average of all y-coordinates of the shape.
Your problem is definitely caused by rounding to int! The first improvement is to make all shape coordinates to be of type double. This will solve most of your unwanted 'effects'.
But even with double you might experience nasty rounding errors in case you do a lot of very small updates of the coordinates. The solution is simple: Just avoid iterative updates of the asteroid points. Every time, you update the coordinates based on the previous coordinates, the rounding error will get worse.
Instead, add a field for the rotation angle to the shape and increment it instead of the points themselves. Not until drawing the shape, you compute the final positions by applying the rotation to the points. Note that this will never change the points themselves.
You can extend this concept to other transformations (e.g. translation) too. What you get is some kind of local coordinate system for every shape/object. The points of the shape are defined in the local coordinate system. By moving and rotating this system, you can reposition the entire object anywhere in space.
public class Shape {
// rotation and position of the local coordinate system
private double rot, x, y;
// points of the shape in local coordinate system
private double[] xp, yp;
private int npoints;
// points of the shape in world coordinates
private int[][] wxp, wyp;
private boolean valid;
public void setRotation(double r) { this.rot = r; valid = false; }
public void setPosition(double x, double y) { this.x = x; this.y = y; valid = false; }
public void addPoint(double x, double y) {
// TODO: add point to xp, yp
valid = false;
}
public void draw(...) {
if (!valid) {
computeWorldCoordinates(wxp, wyp);
valid = true;
}
// TODO: draw shape at world coordaintes wxp and wyp
}
protected void computeWorldCoordinates(int[] xcoord, int[] ycoord) {
for (int i = 0; i < npoints; i++) {
double temp_x = xp[i] * Math.cos(rot) - yp[i] * Math.sin(rot);
double temp_y = xp[i] * Math.sin(rot) + yp[i] * Math.cos(rot);
xcoord[i] = (int) Math.round(x + temp_x);
ycoord[i] = (int) Math.round(y + temp_y);
}
}
}

Android - Canvas onDraw with zoomed camara following character movements

Some Context:
I am creating an Android game which draws a maze onto a canvas against a background image, this uses a square block and therefore the maze is always automtically zoomed in to a 5 x 5 square, of a maze that may be 20 x 20. The maze is drawn by simply running through a set of for loops and then drawing lines in the relevent places. This is all working perfectly, however I am having an issue with getting my onDraw to work smoothly. This is occuring due to the fact that everytime I invalidate I have to re-run the for look and run various if statements to check positions (unfortunetly this process cannot be improved).
The Question:
I am looking to re-write the way my maze is drawn onto the canvas, below are the 3 main features I need to implement:
1 - Draw the whole maze onto the canvas (this is easy enough)
2 - Zoom in on the maze so only a 5 x 5 is shown
3 - Move the character (who is always centered on the screen) and draw the next seciton of the maze
Now as mentioned above drawing the whole maze is easy enough and will make the onDraw signifcatly quicker as their is no need to run the for loop on invaldate, it can be done once when the level is first loaded up.
In terms of point 2 & 3, the way I see this working is the charcacter to be drawn in the middle of the canvas then a 2d birdseye view camera to be attached / linked to the characters movement. This camera would also need to be zoomed in to an extent that it only displays a 5 x 5 of the overall maze grid. Then as the charcater moves the camera moves with the character and displays the next section of the maze which has already been drawn. I have tried a few things and done some reasearch however I have never worked with canvas before and no idea really where to start and if this is even possible.
So to sum up the main part of the question is whether their is a way to link a birdseye view camera to a character which is zoomed in and moves with the character image.
Below is a snippet as to how the maze is currently drawn, using 2 sets of for loops checking against 2 sets of boolean arrays[][], which simply store the vertical and horixaonl lines to be drawn.
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawRect(0, 0, width, 100, background);
RectBackground.set(0,0, FullScreenWidth, FullScreenWidth);
canvas.drawBitmap(gameBackground, null, RectBackground, null);
for(int i = 0; i < 5; i++)
{
for(int j = 0; j < 5; j++)
{
float x = j * totalCellWidth;
float y = i * totalCellHeight;
indexY = i + currentY;
indexX = j + currentX;
// Draw Verticle line (y axis)
if (indexY < vLength && indexX < vLines[indexY].length && vLines[indexY][indexX])
{
RectOuterBackground.set((int)x + (int)cellWidth, (int)y, (int)x + (int)cellWidth + 15, (int)y + (int)cellHeight + 15);
canvas.drawBitmap(walls, null, RectOuterBackground, null);
}
// Draws Horizontal lines (x axis)
if (indexY < hLength && indexX < hLines[indexY].length && hLines[indexY][indexX])
{
RectOuterBackground.set((int)x, (int)y + (int)cellHeight,(int)x + (int)cellWidth + 15,(int)y + (int)cellHeight + 15);
canvas.drawBitmap(walls, null, RectOuterBackground, null);
}
}
}
}
To make the drawing faster, you can double buffer the canvas by drawing directly into a bitmap and flashing the bitmap into the canvas like this. It's very fast.
private void init()
{
//variables below are class-wide variables
b = Bitmap.createBitmap(cwidth, cheight, Bitmap.Config.ARGB_8888);
bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmapPaint.setStyle(Paint.Style.STROKE);
bitmapPaint.setStrokeWidth(3);
myPaint.setColor(0xff000000);
myCanvas = new Canvas(b);
}
protected void onDraw(Canvas c)
{
super.onDraw(c);
c.drawBitmap(b, 0, 0, myPaint);
for(int i = 0; i < number lines; i++)
{
//draw here using myPath
}
if(b != null)
c.drawBitmap(b, 0, 0, myPaint);
myCanvas.drawPath(myPath, bitmapPaint);
}
Then, to "move around" I would suggest using a crop box. What this means is that a 1:1 scale, the image is larger than the viewport displayed on the screen. Really what's happening when someone "moves" is the bitmap is being moved beneath the crop box.
You could use BitmapRegionDecoder and display only the region the character is in (this might be slowish) or by using
public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
The src parameter here allows you to specify a small region of the bitmap to display. It is effectively a crop box.
To zoom in and out you need to multiply the coordinates by a number( zoom factor)
int x1 = (int)x + (int)cellWidth;
int y1 = (int)y;
int x2 = (int)x + (int)cellWidth + 15;
int y2 = (int)y + (int)cellHeight + 15;
RectOuterBackground.set(x1*zoom, y1*zoom, x2*zoom, y2*zoom);
note that zoom must be a floating point number, using zoom=2 that makes everything twice as big.
if you want to put the camera on top of the cell (xc, yc) you need to do this:
RectOuterBackground.set( (x1-xc)*zoom, (y1-yc)*zoom, (x2-xc)*zoom, (y2-yc)*zoom);
Try first drawing the whole maze, and soon you will figure out how to only draw the bit of the maze that is only inside the screen
I hope this helps and let me know if you have any questions :)

Rendering an isometric map in java with individual rectangles

I've been researching for the past hour or so and I can't seem to render an isometric map. I want to achieve something like this.
But I am getting this.... I am storing my map as tiles in a 1 dimensional array like so:
private final int width, height;
private final int tileWidth, length;
private int[] tiles;
public Level(int width, int height) {
this.width = width;
this.height = height;
tiles = new int[width * height];
tileWidth = 68;
length = 48;
}
I am passing through 10, 10 as the parameters for width and height. And I render the map like so:
public void render(Graphics g) {
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
g.setColor(Color.red);
if (x % 2 == 0)
g.drawRect(x * tileWidth, y * length / 2, tileWidth, length);
else
g.fillRect((x * tileWidth) + (tileWidth / 2), (y * length / 2), width, length);
}
}
}
Any help would be really appreciated, I've wanted to learn to make isometric games but have been stuck with flat 2D for a while.
For just tiles, you could use a shear transform:
Graphics2D g2d = (Graphics2D) g;
AffineTransform at = AffineTransform.getShearInstance(1, 0);
g2d.transform(at);
// rest of your drawing code here
You may also want to set the shear anchor point:
double sa_x = 100, sa_y = 100; // or whatever
AffineTransform at = new AffineTransform();
// S3: Move back to original origin
at.translate(sa_x, sa_y);
// S2: Shear
at.shear(1, 0);
// S1: Set origin
at.translate(-sa_x, -sa_y);
You can vary the shear factor 1 to get different amounts of shear.
Instead of drawing rects, you need to draw lines at isometric angles.
The angles in isometric geometry are 30 degrees, 90 degrees, 150 degrees, 210 degrees and 270 degrees (in radians: pi/6, pi/2, 5pi/6, 7pi/6, 3pi/2, 11pi/6.).
cos(pi/6) is sqrt(3)/2 or 0.866... and sin(pi/6) is 1/2 or 0.5. (This is meaningful because of http://en.wikipedia.org/wiki/File:Sin-cos-defn-I.png )
This means that if you want to draw a line at the angle pi/6 that is D pixels long starting at x1,y1:
x2 = x1+cos(pi/6)*D e.g. x1+D*sqrt(3)/2
y2 = y1+sin(pi/6)*D e.g. y1+D/2
and draw from x1,y1 to x2,y2.
All the other angles are either reflections of this (one dimension or both are made negative) or straight up and down (trivial to draw).
To calculate where on the screen to draw an isometric object, consider that isometric geometry has three dimensions: X, Y, Z. Movement by Z will just make you draw D higher or D lower. Movement by X or Y will move you in one isometric angled direction or the other, by the same x and y as the distance of drawing one tile line in that direction (so similar formula to the above).

Categories