How to get total area covered while drawing path on canvas android? - 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

Related

How to move an object in "X" and "Y" direction

I am working on android studio and I want to create the effect of throwing an object, in this case a circle drawn on the canvas. But I'm having problems. Can someone guide me?
I take the "X" and the "Y" value where the user touch but how can i make that the circle move in that direction?
Thanks
Game class:
public class Game extends SurfaceView implements View.OnTouchListener {
Paint paint;
int x, y, radius = 100, speedX = 5, speedY = 5, touchY, touchX;
boolean move = false;
boolean one_time = true;
public Game(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOnTouchListener(this);
setFocusable(true);
paint = new Paint();
}
public void onDraw(Canvas canvas){
paint.setColor(Color.WHITE);
canvas.drawRect(0,0,canvas.getWidth(),canvas.getHeight(),paint);
if (one_time == true){
x = canvas.getWidth()/2;
y = canvas.getHeight()/2;
one_time = false;
}
paint.setColor(Color.BLACK);
canvas.drawCircle(x, y, radius, paint);
if (move == true){
if (x >= canvas.getWidth() - radius) {
speedX = -5;
}
if (x <= radius) {
speedX = 5;
}
if (y >= canvas.getHeight() - radius) {
speedY = -5;
}
if (y <= radius) {
speedY = 5;
}
x = x + speedX;
y = y + speedY;
}
invalidate();
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case (MotionEvent.ACTION_DOWN):
touchX = (int) motionEvent.getX();
touchY = (int) motionEvent.getY();
move = true;
return true;
default:
return super.onTouchEvent(motionEvent);
}
}
}
Gonna need to be an x+=speedX or y+=speedY in there somewhere so that the x or y value changes causing the ball to move. If your trying to implement some physics in there ur gonna need some maths.
You need a way to change the (x, y) values at a certain interval. You can do this with the Timer class, but then you get into some difficult multithreading code. I suggest looking at using OpenGL ES or a higher level library such as LibGDX. Both of these provide an event loop which allow you to update objects that will be drawn.
Basically you want to move the circle to the place the user touched the screen, using some type of constant speed?
If all you want is to move a circle the Timer can do it - or you could use something like: https://github.com/MasayukiSuda/FPSAnimator
Finally, are you always looking for a linear straight line constant speed movement? Then your math is fine. If your looking for something with gravity etc. then you can reference this: https://developer.android.com/guide/topics/graphics/physics-based-animation.html and consider this: https://github.com/google/liquidfun
This will move the circle exactly to the point the user touched:
theta = atan2(touchY - y,touchX - x)
speedX = cos(theta)
speedY = sin(theta)
x += speedX
y += speedY
What happens once the circle reaches the point depends on how the calculation is implemented. If you want the circle to continue on its path and travel the direction infinitely, you must only calculate the velocity of x and y once. That way the same x and y velocity will always be used to update the position of the circle.
If the velocities are recalculated every time the position of the circle is to be updated, the circle will continuously move to the point even once it has technically reached it and must manually be stopped.
The velocities can be increased by multiplying them by a value greater than 1. To maintain the correct direction, the value should be the same for both velocities.
Example:
theta = atan2(touchY - y,touchX - x)
speedX = maxSpeed*cos(theta)
speedY = maxSpeed*sin(theta)
x += speedX
y += speedY

How to get the draw element size on a cavans?

I was working on a "draw with mask" app. When the user drag on the screen , it cleans part of the mask.
I implemented it through cavans and with setXfermode Clear
// Specify that painting will be with fat strokes:
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeWidth(canvas.getWidth() / 15);
// Specify that painting will clear the pixels instead of paining new ones:
drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
cv.drawPath(path, drawPaint);
The problem is , how can I get the percentage of space cleaned?, it doesn't necessary to be accurate, just roughly detect when more than half of screen size is clean. Thanks for helping
What you need to do is to convert your canvas in to bitmap and count the number of black pixels in it. Using simple math you can divide the number of black pixels to the number of pixels in the canvas which will give you the percentage of black pixels.
sample taken from this post:
public float percentTransparent(Bitmap bm) { //pass the converted bitmap of canvas
final int width = bm.getWidth();
final int height = bm.getHeight();
int totalBlackPixels = 0;
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
if (bm.getPixel(x, y) == Color.BLACK) {
totalBlackPixels ++;
}
}
}
return ((float)totalBlackPixels )/(width * height); //returns the percentage of black pixel on screen
}

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.

android path shifted once finger is removed

I am working on a drawing app and allows users to import image to further draw on it. Images bigger than the drawing area will then be scaled down such that to meet the max screenwidth or screenheight.
The imported image would be placed at center of the drawingView using canvas.drawBitmap(bitmap, x_adjustment, y_adjustment, paintScreen);
In this way there would be blank spaces on left, right or top, down of the imported image. The adjustment would be counting from (0,0)
x_adjustment and y_adjustment
Coding:
onDraw
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawBitmap(bitmap, x_adjustment, y_adjustment, paintScreen);
for (Integer key : pathMap.keySet())
canvas.drawPath(pathMap.get(key), paintLine); // draw line
}
touchStarted:
private void touchStarted(float x, float y, int lineID)
{
Path path; // used to store the path for the given touch id
Point point; // used to store the last point in path
path = new Path(); // create a new Path
pathMap.put(lineID, path); // add the Path to Map
point = new Point();
previousPointMap.put(lineID, point);
path.moveTo(x, y);
point.x = (int) x;
point.y = (int) y;
}
touchMoved:
// called when the user drags along the screen
private void touchMoved(MotionEvent event)
{
// for each of the pointers in the given MotionEvent
for (int i = 0; i < event.getPointerCount(); i++)
{
// get the pointer ID and pointer index
int pointerID = event.getPointerId(i);
int pointerIndex = event.findPointerIndex(pointerID);
if (pathMap.containsKey(pointerID))
{
// get the new coordinates for the pointer
float newX = event.getX(pointerIndex);
float newY = event.getY(pointerIndex);
// get the Path and previous Point associated with this pointer
Path path = pathMap.get(pointerID);
Point point = previousPointMap.get(pointerID);
float deltaX = Math.abs(newX - point.x);
float deltaY = Math.abs(newY - point.y);
if (deltaX >= TOUCH_TOLERANCE || deltaY >= TOUCH_TOLERANCE)
{
path.quadTo(point.x, point.y, ((newX + point.x)/2),((newY + point.y)/2));
// store the new coordinates
point.x = (int) newX ;
point.y = (int) newY ;
}
}
}
}
touchEnded:
private void touchEnded(int lineID)
{
Path path = pathMap.get(lineID);
bitmapCanvas.drawPath(path, paintLine);
path.reset();
}
Question:
Since the imported image is placed at center but not (0,0), for every line drawn, though when it is showing properly when it is drawing and screen-touching, when the user removes the finger, i.e. Touch ended, the finalized line would be shifted by x_adjustment and y_adjustment.
e.g. If the scaled image width < screenwidth, there are blank space on left and right, when drawing the line is showing correctly, yet when the finger is removed, the line will wrongly immediately shift to right by x_adjustment;
I know it is because of the adjustments that make the error. I know it is to save the path's coordinates by a x,y shift. But I dont know how to modify for the codes and I have tried to add adjustment to the paths but still fails. Could anybody be kindly help to give me some guides? Many thanks!

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