Canvas.drawLines() produces gaps between lines - android

I use drawLines() on my dataset of points. It's drawn perfectly when it starts from (0,0), but when the dataset starts from another point it produces some gaps.
Here the chart starting with (0, 0) (added small line at left bottom):
Here it starts from from (83, 56) and produces gap:
What I've tried:
Setting on/off anti-alias.
Different datasets (they all produce gap at the top y point if there is nothing drawn at (0, 0).
I've read about trying drawPath(), but since I need to draw lots of lines it was said to be not as effecient as drawLines().
Here are some snippets from my project:
My dataset:
x = {
0,12,83,84,84,121,121,128,128,151,151,173,173,203,203,217,217,224,224,229,229,253,253,294,294,305,305,331,331,355,355,364,364,409,409,411,411,416,416,431,431,448,448,497,497,504,504,508,508,536,536,582,582,586,586,630,630,646,646,660,660,689,689,728,728,761,761,768,768,798,798,822,822,860,860,894,894,908,908,952,952,996,996,1039,1039,1085,1085,1085,1085,1099,1099,1119,1119,1133,1133,1169
}
y = {
0,12,56,115,115,220,220,232,232,170,170,350,350,117,117,157,157,205,205,290,290,184,184,127,127,181,181,231,231,210,210,278,278,142,142,120,120,299,299,29,29,290,290,50,50,258,258,127,127,203,203,168,168,27,27,27,27,83,83,116,116,228,228,295,295,62,62,299,299,121,121,216,216,266,266,164,164,234,234,116,116,182,182,130,130,208,208,218,218,202,202,85,85,59,59,114
}
How I set my Paint:
private void init() {
chartPaint = new Paint();
chartPaint.setStyle(Paint.Style.STROKE);
chartPaint.setStrokeWidth(4);
chartPaint.setColor(color);
}
My onDraw():
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
// let chart start from the most left without gaps
canvas.translate(0, viewHeight);
// TODO: find place to move calcs outside onDraw()
final float xScale = (float) viewWidth / (Util.getMax(x) - Util.getMin(x));
final float yScale = (float) -viewHeight / (Util.getMax(y) - Util.getMin(y));
valuesScaled = new float[values.length];
for (int i = 0; i < values.length; i++) {
valuesScaled[i] = (i%2==0) ? values[i] * xScale : values[i] * yScale;
}
canvas.drawLines(valuesScaled, chartPaint);
canvas.restore();
}
values array is where x and y arrays are stored alternatively, i.e. first 4 elements of values are {x[0], y[0], x[1], y[1], ...}
viewWidth and viewHeight are taken from onSizeChange().

Related

Move a ball on Grid path randomly in Canvas Android

Requirement is to keep a ball moving on the Grid path generated in Canvas. I have generated a Grid in canvas but not able to understand how to move the ball randomly means starting point show be different on the path. I am sharing what I have done. I have also plotted the ball in the screen but not getting the point how to put the ball exactly on the grid line randomly and start moving it
public class PixelGridView extends View {
//number of row and column
int horizontalGridCount = 11;
private Drawable horiz;
private Drawable vert;
private final float width;
long mInterpolateTime;
PointF mImageSource = new PointF();
public PixelGridView(#NonNull Context context) {
this(context, null);
}
public PixelGridView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
horiz = new ColorDrawable(Color.WHITE);
horiz.setAlpha(160);
vert = new ColorDrawable(Color.WHITE);
vert.setAlpha(160);
width = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, .9f, context.getResources().getDisplayMetrics());
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
horiz.setBounds(left, 0, right, (int) width);
vert.setBounds(0, top, (int) width, bottom);
}
private float getLinePosition(int lineNumber) {
int lineCount = horizontalGridCount;
return (1f / (lineCount + 1)) * (lineNumber + 1f);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//drawTask.start();
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
canvas.drawCircle(120, 110, 10, paint);
int count = horizontalGridCount;
for (int n = 0; n < count; n++) {
float pos = getLinePosition(n);
// Draw horizontal line
canvas.translate(0, pos * getHeight());
Log.e("Position1", "" + pos * getHeight());
horiz.draw(canvas);
canvas.translate(0, -pos * getHeight());
// Draw vertical line
canvas.translate(pos * getHeight(), 0);
Log.e("Position2", "" + pos * getHeight());
vert.draw(canvas);
canvas.translate(-pos * getHeight(), 0);
}
}
}[![Canvas Image][1]][1]
//MainActivity
public class PathAnimationActivity extends AppCompatActivity {
LinearLayout rlLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_path);
rlLayout=findViewById(R.id.rlLayout);
PixelGridView pixelGrid = new PixelGridView(this);
rlLayout.addView(pixelGrid);
}
}
First thing i have noticed it that you haven't used invalidate (); at the end because thats critical in order to animate the canvas ( redraw the frames ) so please include that .
there may be several ways to achieve what you want follows is my idea
this canvas need to be divided into multiple x , y divided planes as follows and save them in array of points which you can randomize and give those points to ball to move ,
Step 1, get the canvas size
step 2, divide is in x and y coordinate depending on size of each device varies so you need to control that factor via Screen size
step 3, save the coordinates in matrix or array
step 4, set position of balls from those arrays values ( randomly can you define the random limits as per the max and min values of x and y from the coordinates division .
example , function move will take ball object and x, y are the positions move (ball, x, y ); and you can randomize the x and y based on max and min limits of your coordinates division example total y lines and total x lines values
in order to get an idea about how to move the ball on canvas you can see this code here : https://github.com/pintspin/ball_animation

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.

how can we define dynamic(in parabola curve) Path of ViewObject(Bitmap)

I am Currently working on One 2D Android Game,
In this game One ViewObject(Bitmap) is moving Across Screen On Parabola Path Like in this Image, But this Path is Static, the Static path is getting throught the Drawing with Fingure on canvas,
As Same as signature Drawing.
The Bitmap Move code On this Static Path is
//animation step
private static int iMaxAnimationStep = 900;
private int iCurStep = 0;
private Path ptCurve = new Path(); //curve
private PathMeasure pm; //curve measure
private float fSegmentLen; //curve segment length
//init smooth curve
PointF point = aPoints.get(0);
ptCurve.moveTo(point.x, point.y);
for(int i = 0; i < aPoints.size() - 1; i++){
point = aPoints.get(i);
PointF next = aPoints.get(i+1);
ptCurve.quadTo(point.x, point.y, (next.x + point.x) / 2, (point.y + next.y) / 2);
}
pm = new PathMeasure(ptCurve, false);
fSegmentLen = pm.getLength() / iMaxAnimationStep;//20 animation steps
//animate the Bitmap
Matrix mxTransform = new Matrix();
if (iCurStep <= iMaxAnimationStep)
{
pm.getMatrix(fSegmentLen * iCurStep, mxTransform,
PathMeasure.POSITION_MATRIX_FLAG);
mxTransform.preTranslate(-Bitmap.getWidth(), -Bitmap.getHeight());
canvas.drawBitmap(Bitmap, mxTransform, null);
iCurStep++; //advance to the next step
mPauseViewHandler.post(mPauseViewRunnable);
} else {
iCurStep = 0;
}
But My Problem is I want to Move This ViewObject(Bitmap) On Dynamic Path(in parabola curve)
& that Dynamic curved path will work in Any Device.
I have searched Lot but i can't Find Solution How to get Dynamic Path (in parabola curve).
help! If you have Any Solution,Suggestion, idea ,tutorial regarding this post is Mostly Appreciated.
It's simple enough to fill aPoints array based on your screen size, and get a parabolic path based on those points. I've removed all your bitmap/animation code, this code below will calculate the path and draw it on the screen.
We need a new variable to set how many curves we want in the screen. If you prefer it's easy to change the math and define the size of the curve instead.
private int numberOfCurves = 5;
With that it's simple to calculate 3 points for each parabola:
public void calculatePoints(){
float w = v.getWidth(); //Screen width
float h = v.getHeight(); //Screen height
float curveSize = w/numberOfCurves; // Curve size
float curveHeight = (h/100) * 20; //80% of the screen size
Log.d(TAG,"h:"+h +" - w:" + w);
float lastX = 0; //last used X coordinate
for (int i=0;i<numberOfCurves;i++){ //for each curve we'll need 3 points
float newX = lastX + curveSize;
PointF p = new PointF(lastX, h); //first point is the last point
PointF p1 = new PointF((lastX + newX)/2, curveHeight); //the middle point is halfway between the last and the new point
PointF p2 = new PointF(newX,h); // the new point is last point + the size of our curve
aPoints.add(p); //fill in the array
aPoints.add(p1);
aPoints.add(p2);
lastX = newX; //update last point
}
//log our points
for (PointF p : aPoints){
Log.d(TAG,p.x +"-"+p.y);
}
}
Now we have a set of points defining each parabola, we need to draw it. Instead of using quadTo, use cubicTo. It takes 3 points and draws a curve connecting them. Put it onDraw, and you have your parabolas drawn on the screen.
private Path ptCurve = new Path(); //curve
#Override
public void onDraw(Canvas canvas) {
calculatePoints();
Log.d(TAG,"DRAWING");
PointF point = aPoints.get(0);
ptCurve.moveTo(point.x, point.y);
for(int i = 0; i < aPoints.size() - 1; i+=3){
point = aPoints.get(i);
PointF middle = aPoints.get(i+1);
PointF next = aPoints.get(i+2);
ptCurve.cubicTo(point.x, point.y, middle.x,middle.y, next.x , next.y);
}
canvas.drawPath(ptCurve, paint);
}
So your ptCurve variable is now filled with a parabolic path, with as many curves as you've defined earlier, and it will work on any screen size.

How to get total area covered while drawing path on canvas android?

Im using below code to draw line on bitmap canvas while finger touch move... here i posted partial code and it is working fine..
As shown in below image, the black and white bitmap erased on touch drag.. I made canvas transparent so the parent layout background(color image) is getting visible.
I want to know , how much area is erased(like 50% or 60% of bitmap ).. is there any way to find that?
//Erasing paint
mDrawPaint = new Paint();
mDrawPaint.setAntiAlias(true);
mDrawPaint.setDither(true);
mDrawPaint.setStyle(Paint.Style.STROKE);
mDrawPaint.setStrokeJoin(Paint.Join.ROUND);
mDrawPaint.setStrokeCap(Paint.Cap.ROUND);
mDrawPaint.setStrokeWidth(50);
mDrawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
BlurMaskFilter mBlur = new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL);
mDrawPaint.setMaskFilter(mBlur);
private void doDraw(Canvas c) {
c.drawBitmap(mBitmap, 0, 0,null );
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 1;
void touch_start(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
mX = x;
mY = y;
}
canvas.drawPath(mPath, mDrawPaint ); //Erasing Black and white image
}
void touch_up() {
mPath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mPath, mDrawPaint);
// kill this so we don't double draw
mPath.reset();
}
Try to use Monte Carlo method to estimate percentage of transparent area. I think it is a fastest and easiest way to do this. Take about 50 (depends on accuracy you need) random pixels on your transparency mask and check their color. Then calc ans = TransparentPixelsCount/TestPixelCount.
It is very hard to calculate square of user's drawings using path coordinates. And it's quite long to iterate over all pixels. So, IMHO Monte Carlo is your choise.
To get an exact (and slow) answer, you need to inspect every pixel and count the number are transparent and divide by the total number of pixels. If your requirements allow for some estimation, it is probably best to sample the image.
You could downsize the image and run and the above procedure on the smaller image. That has the disadvantage that the scaling operation might be going through all the pixels making it slow. I would recommend a grid sampling, it is similar to downsizing, but skips over pixels. Basically, we evenly space x sample points on a grid over the image. Then count the number of sample points that are transparent. The estimate of transparent percentage is the total transparent samples/number of transparent samples. You can get reasonable accuracy (usually within 5%) with a small number, say 100, samples. Here is a code function that implements this method -- bm is the Bitmap and scale is the number of samples per axis, so setting scale = 10 gives 100 total samples (10x10 sampling grid over the image).
static public float percentTransparent(Bitmap bm, int scale) {
final int width = bm.getWidth();
final int height = bm.getHeight();
// size of sample rectangles
final int xStep = width/scale;
final int yStep = height/scale;
// center of the first rectangle
final int xInit = xStep/2;
final int yInit = yStep/2;
// center of the last rectangle
final int xEnd = width - xStep/2;
final int yEnd = height - yStep/2;
int totalTransparent = 0;
for(int x = xInit; x <= xEnd; x += xStep) {
for(int y = yInit; y <= yEnd; y += yStep) {
if (bm.getPixel(x, y) == Color.TRANSPARENT) {
totalTransparent++;
}
}
}
return ((float)totalTransparent)/(scale * scale);
}
For reference, the slow method that would give you the results by counting every pixel is below. It can be used for reference on testing the above estimator.
static public float percentTransparent(Bitmap bm) {
final int width = bm.getWidth();
final int height = bm.getHeight();
int totalTransparent = 0;
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
if (bm.getPixel(x, y) == Color.TRANSPARENT) {
totalTransparent++;
}
}
}
return ((float)totalTransparent)/(width * height);
}
A different approach on this: you can calculate the size of each path using ComputeBounds. Then it should be simple to compare this with the size of your view and decide the % of the drawing.
Jus you need to keep in mind that the path can be drawn over itself, so you need to be careful and handle that in the calculation.
Store all point x and y value in two different sorted sets, one for x value of point and other for y value of point.
The final value of your bound will be point(min_x,min_y) and point(max_x,max_y).
You need to detect the points lying inside the drawn polygon.
Here is the functions which takes array that contains all the drawn point, and second parameter are the points itself i.e. x ,y.
// Return true if the dot { x,y } is within any of the polygons in the list
function pointInPolygons( polygons, dot )
for (i=1, [polygons count] i++)
{
if (pointInPolygon( polygons[i], dot ))
return true
}
return false
end
// Returns true if the dot { x,y } is within the polygon
//defined by points table { {x,y},- --{x,y},{x,y},... }
function pointInPolygon( points, dot )
local i, j = #points, #points
local oddNodes = false
for i=1, #points do
if ((points[i].y < dot.y and points[j].y>=dot.y
or points[j].y< dot.y and points[i].y>=dot.y) and (points[i].x<=dot.x
or points[j].x<=dot.x)) then
if (points[i].x+(dot.y-points[i].y)/(points[j].y-points[i].y)*(points[j].x-points[i].x)<dot.x) then
oddNodes = not oddNodes
end
end
j = i
end
return oddNodes
end

Android image with clickable areas

I need an advice how to achieve the following functionality under Android:
I need an image that represents something like a graph (from discrete math), with vertices and edges, where I can click every vertice or edge and fire a different action.
Please advise me how to achieve this (maybe with imagebuttons) or another approach to represent this functionality.
I was bored, so I coded up this crude example...
It assumes straight edges between points.
public class App extends Activity
{
PlotView plot;
#Override
public void onCreate(Bundle sis)
{
super.onCreate(sis);
plot = new PlotView(this);
setContentView(plot);
}
public class PlotView extends View
{
Paint paint1 = new Paint();
Paint paint2 = new Paint();
Point[] points = new Point[10];
public PlotView(Context context)
{
super(context);
paint1.setColor(Color.RED);
paint2.setColor(Color.BLUE);
for (int i = 0; i < points.length; i++)
{
points[i] = new Point();
points[i].x = (float) (Math.random() * 320);
points[i].y = (float) (Math.random() * 480);
}
Arrays.sort(points);
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.WHITE);
for (int i = 0; i < points.length; i++)
{
if (i < points.length - 1)
{
canvas.drawLine(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y, paint2);
}
canvas.drawCircle(points[i].x, points[i].y, 5, paint1);
}
super.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
float x = event.getX();
float y = event.getY();
int hitPoint = -1;
int closestLeft = -1;
int closestRight = -1;
for (int i = 0; i < points.length; i++)
{
float dx = x - points[i].x;
float dy = y - points[i].y;
if(i < points.length - 1)
{
if(points[i].x < x && x < points[i + 1].x)
{
closestLeft = i;
closestRight = i + 1;
}
}
if (Math.abs(dx) <= 16.0f && Math.abs(dy) <= 16.0f)
{
hitPoint = i;
break;
}
}
if (hitPoint != -1)
{
Toast.makeText(getContext(), "Hit Point: " + hitPoint, Toast.LENGTH_SHORT).show();
}
else
if(closestLeft != -1 && closestRight != -1)
{
float dx = points[closestLeft].x - points[closestRight].x;
float dy = points[closestLeft].y - points[closestRight].y;
final float u = ((x - points[closestLeft].x) * dx + (y - points[closestLeft].y) * dy) / (dx * dx + dy * dy);
float px = points[closestLeft].x + u * dx;
float py = points[closestLeft].y + u * dy;
if (Math.abs(x - px) <= 16.0f && Math.abs(y - py) <= 16.0f)
{
Toast.makeText(getContext(), "Hit Line Between: " + closestLeft + " & " + closestRight, Toast.LENGTH_SHORT).show();
}
}
}
}
return super.onTouchEvent(event);
}
public class Point implements Comparable<Point>
{
float x;
float y;
#Override
public int compareTo(Point other)
{
if (x < other.x) return -1;
if (x > other.x) return 1;
return 0;
}
}
}
}
I can imagine how to do this with SurfaceView:
create a Vertex class, which among other things, has an x,y coordinate representing where to draw the vertex. If your vertex was a png image of a circle, then the top-left x,y coordinates of the image are stored in the Vertex class.
Have all your verticies in a List, and iterate through and draw each vertex.
the edges are more complicated since they might criss-cross or curve around.
assuming they are straight lines, then you can have a Edge class that contains the starting x,y and ending x,y coordinates.
you can iterate through a List of Edges and draw the lines accordingly
In order to detect when a user clicks on them, you should override the onTouch method and check the event.rawX() and event.rawY() values to see if they match up to a Vertex or Edge class.
for a Vertex class, you can check if x <= event.rawX <= x + image_width and y <= event.rawY <= y + image_height
for an Edge, you can check if the event.rawX, event.rawY coordinates are found in the line formed by the two sets of coordinates you stored in the Edge class.
I've used a similar method to draw a set of nodes in a game. I'm not so sure how to do the edges though - the method I outline would only work if they were straight and do not criss-cross.
I am sure there is a better way to do this using openGL, but I have not used openGL before.
Hopefully you can get some ideas out of this.
I think you might be best off with a SurfaceView:
http://developer.android.com/reference/android/view/SurfaceView.html
And handling the onTouchEvent() as a whole for the surface, and mapping that to underlying entities in the image. If you're calculating the drawing the graph as you go should be easy to also create a map of tapable areas and grabbing the X and Y of the touch event to figure out if it corresponds to an element in the image.
If you literally have an image, as an already processed PNG for example, you would need some way to also carry in the touch event areas. Depends where that image comes in from.
According to android help, "drawing to a View, is your best choice when you want to draw simple graphics that do not need to change dynamically and are not part of a performance-intensive game." This is the right way to go when making a snake or a chess game, for instance. So I don't see a point in suggesting using a SurfaceView for this, it will just overcomplicate things.
For clickable areas you override public boolean onTouchEvent(MotionEvent event) where you manage x and y coordinates of the click for identifying the clicked area.

Categories

Resources