Limit down scaling of a Bitmap in a SurfaceView, Android - android

im having a single Bitmap in a surfaceview.
I am using multi touch to handle gestures such as zoom and drag, the problem is that when i scale the bitmap i dont want it to be able to be down scaled that much it wont cover the whole display(surface view). I have found no way to work around this since i cant find a way to get the current downscaled bitmaps current height or width.
Here's some of my code:
private void multiTouchBehavior(MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_POINTER_2_DOWN)
{
_oldDist = spacing(event);
if(_oldDist > 10f) // just be secure of a bug in the API
{
_saved.set(_matrix);
_mid = midPoint(event);
}
}
else if(event.getAction() == MotionEvent.ACTION_MOVE)
{
_newDist = spacing(event);
if(_newDist > 10f)
{
_matrix.set(_saved);
float scale = _newDist / _oldDist;
_matrix.postScale(scale, scale, _mid.x, _mid.y);
}
}
}
And here is what happens in method spacing(event) :
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
My onDraw(canvas) is just canvas.drawBitmap(myBitmap, _matrix, null)
Anyone know how i could solve this problem?
So just to make sure you get my problem right think of it as a big map of the world that i want to able to zoom in to, and of course zoom out on when iv'e already zoomed in, but NOT allowing it to be out zoomed more than it is at start.

Found my solution, there probably is a better way but now I use:
matrix.mapRadius(1);
that i can compare to a variable initialized to that method-value in beggining.
Edit:
float[] f = new float[9];
matrix.getValues(f);
float xScale = f[0];
float yScale = f[3];
works better and is easier tho.

Related

Android Pinch Zoom on editText

Is there any way to add pinch zoom in zoom out on edit Text?
Although this is a bit of strange user interaction, I believe it should be able to be done by just combining some simple view gesture recognition and changing the font size. You could begin by creating a custom EditText and overriding the onTouchEvent(MotionEvent) method. In onTouchEvent(MotionEvent), you can make use of ScaleGestureDetector (more info here) to detect "pinch-to-zoom" gestures. Also take a look at this Android guide for more info on implementing custom gesture detections in views.
After you detect the zooming gesture, you can simply use setTextSize in EditText to adjust the size of the font relative to the change in zoom. This of course isn't going to give you a smooth zooming gesture like zooming on a website. Another method you could try is taking the zoom gesture and physically adjusting the size (width and height) of the EditText but that's just a thought.
Hope this helps!
This code does the job, you have to add super.onTouchEvent(event); so you don't lose EditText properties
final static float move = 200;
float ratio = 1.0f;
int bastDst;
float baseratio;
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (event.getPointerCount() == 2) {
int action = event.getAction();
int mainaction = action & MotionEvent.ACTION_MASK;
if (mainaction == MotionEvent.ACTION_POINTER_DOWN) {
bastDst = getDistance(event);
baseratio = ratio;
} else {
// if ACTION_POINTER_UP then after finding the distance
// we will increase the text size by 15
float scale = (getDistance(event) - bastDst) / move;
float factor = (float) Math.pow(2, scale);
ratio = Math.min(1024.0f, Math.max(0.1f, baseratio * factor));
text.setTextSize(ratio + 15);
}
}
return true;
}
// get distance between the touch event
private int getDistance(MotionEvent event) {
int dx = (int) (event.getX(0) - event.getX(1));
int dy = (int) (event.getY(0) - event.getY(1));
return (int) Math.sqrt(dx * dx + dy * dy);
}

How to move a Path on a Canvas in Android?

I am trying to make an Android paint application for finger painting and I am having trouble with moving the lines I draw.
What I tried to do was offset the path of the currently selected line by the difference between the initial finger press coordinates and the current coordinates in OnTouchEvent during ACTION_MOVE.
case MotionEvent.ACTION_MOVE:
selectline.getLine().offset(x - otherx, y - othery);
otherx and othery are set as the x and y coordinates during ACTION_MOVE and x and y are the current cursor coordinates. My lines are stored as a separate class containing the path, color, thickness and bounding box.
What I got was the shape flying off the screen in the direction of my finger without stopping at the slightest movement. I tried using a matrix to move the path, but the result was the same.
When I tried to insert a "do while" that would check whether the current coordinates would match the path's .computeBounds() rectangle center, but the program crashes as soon as I move my finger.
Any help would be appreciated, thanks.
Most likely that you did not use the right scale for the coordinates.
Source: Get Canvas coordinates after scaling up/down or dragging in android
float px = ev.getX() / mScaleFactor + rect.left;
float py = ev.getY() / mScaleFactor + rect.top;
// where mScaleFactor is the scale use in canvas and rect.left and rect.top is the coordinate of top and left boundary of canvas respectively
Its a bit late but it may solve others problem. I solved this issue like this,
get initial X,Y position on onLongPress
public void onLongPress(MotionEvent motionEvent) {
try {
shapeDrag = true;
SmEventX = getReletiveX(motionEvent);
SmEventY = getReletiveY(motionEvent);
} catch (Exception e) {
e.printStackTrace();
}
and then on onToucn(MotionEvent event)
case MotionEvent.ACTION_MOVE: {
actionMoveEvent(motionEvent);
try {
if (shapeDrag) {
StylePath sp = alStylePaths
.get(alStylePaths.size() - 1);
Path mpath = sp.getPath();
float tempX = getReletiveX(motionEvent) - SmEventX;
float tempY = getReletiveY(motionEvent) - SmEventY;
mpath.offset(tempX, tempY);
SmEventX = getReletiveX(motionEvent);
SmEventY = getReletiveY(motionEvent);
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
I faced the same trouble and in my case it was a very naive mistake. Since the description of the "symptoms" matches exactly (shape flying off the screen in the direction of the finger at the slightest movement, shape moved correctly at ACTION_UP event), I think the reason behind might be the same.
Basically the problem is in the update of the touch position coordinates within the ACTION_MOVE event. If you don't update the last touch position, the calculated distance will be always between the current touch position and the first touch position stored at ACTION_DOWN event: if you apply this offset consecutively to the path, the translation will sum up and consequently the shape will "fly" rapidly off the screen.
The solution is then quite simple: just update the last touch position at the end of the ACTION_MOVE event:
float mLastTouchX, mLastTouchY;
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
// get touch position
final float x = ev.getX();
final float y = ev.getY();
// save the initial touch position
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_MOVE: {
// get touch position
final float x = ev.getX();
final float y = ev.getY();
// calculate the distance moved
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
// here apply translation to the path
// update touch position for the next move event
mLastTouchX = x;
mLastTouchY = y;
break;
}
}
return true;
}
Hope this helps.

How to keep a dot drawn on canvas at a fixed point on screen, even when the canvas is pinch zoomed? - Android

I have a background image as a drawable in my custom view. This drawable may be pinch zoomed or moved.
Currently I need a green dot that is drawn on the image to be stationary relative to the screen. That is, it should be always at the same position with the pin as shown below. (Of course, the pin is simply an ImageView and does NOT move at all!)
I have successfully made it stationary relative to the screen, when the map behind is moved as follows in my custom view, MapView:
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: { // triggered as long as finger movers
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
// update the starting point if the 'Start' button is not yet pressed
// to ensure the screen center (i.e. the pin) is always the starting point
if (!isStarted) {
Constant.setInitialX(Constant.INITIAL_X - dx);
Constant.setInitialY(Constant.INITIAL_Y - dy);
if ((historyXSeries.size() > 0) && (historyYSeries.size() > 0)) {
// new initial starting point
historyXSeries.set(0, Constant.INITIAL_X);
historyYSeries.set(0, Constant.INITIAL_Y);
}
}
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
By doing that above, my green dot stays there, when the background image is moved.
But I have problems in trying to make it stay there, when the background image is zoomed.
Essentially, I don't really understand how canvas.scale(mScaleFactor, mScaleFactor) works, and therefore I cannot move the green dot accordingly like what I have done in the simple moving case.
I think something should be added in the scale listener handler below, could anybody help me fill that part?
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(1f, Math.min(mScaleFactor, 10.0f)); // 1 ~ 10
// HOW TO MOVE THE GREEN DOT HERE??
invalidate();
return true;
}
Or please at least explain how canvas.scale(mScaleFactor, mScaleFactor) works, and how may I move the green dot accordingly?
Keep in mind that the canvas is thought to scale everything according to the scale factor, so while going against the zoom is possible, it is probably not the best approach. However, if this is what you're looking for, I will help you as best as I can.
I am assuming the following:
Scale factor is relative to the current zoom (old zoom is always scale factor 1). If this is not the case, then you should observe the zoom values after scaling roughly 200% two times and seeing if the resulting scale factor is 4 or 3 (exponential or linear). You can achieve the results below by normalizing the scale factor to 2 for a zoom factor of 200%, for example. You'll have to remember the old scale factor in order to do so.
No rotation is performed
If this is the case then following can be said for a marker with respect to the zoom center.
For every horizonal pixel x away from the zoom center after zoom, its original position could be calculated to be: zoom_center_x + *x* / scale_factor (or alternatively zoom_center_x + (marker_x - zoom_center_x) / scale_factor). In other words, if zoom center is (50, 0) and the marker is (100, 0) with a scale factor of 2, then the x position of the marker prior to the zoom was 50 + (100 - 50) / 2 or 75. Obviously, if the marker is in the same position of the zoom center, then the x position will be the same as the zoom center. Similarly, if the scale is 1, then the x position for the marker will be the same as it is now.
The same can be applied to the y axis.
While I can't know exactly how to set the position of your marker, I would expect the code to look something like:
Point zoomCenter = detector.getZoomCenter();
// Set marker variable here
marker.setX(Math.round(zoomCenter.getX() + ((double)(marker.getX() - zoomCenter.getX())) / mScaleFactor));
marker.setY(Math.round(zoomCenter.getY() + ((double)(marker.getY() - zoomCenter.getY())) / mScaleFactor));
I hope that helps.

Android getX/getY interleaves relative/absolute coordinates

There are a lot of discussions of how MotionEvent.getX/.getY are "unreliable" (or other terms) and that we should use the Raw versions of these calls to get coordinates.
On my Nexus 7, I have discovered that .getX/.getY are reliably returning interleaved absolute and relative coordinates. In other words, say a given ACTION_MOVE event returns absolute coordinates when you call .getX and .getY. The next ACTION_MOVE event will then return relative coordinates on its .getX and .getY calls.
This cannot be accidental behavior. It also leads me to believe there must be a way to discern whether a given ACTION_MOVE will be returning absolute or relative coordinates.
Does anyone know how to check a given MotionEvent object to see if it is returning absolute vs. relative coordinates on its .getX and .getY calls?
EDIT: Per your request, here's the code. It's nothing special, just grab the coordinates and move the View object:
public boolean onTouch(View v,MotionEvent event) {
boolean bExitValue = true;
float fX;
float fY;
int iAction;
iAction = event.getActionMasked();
if (MotionEvent.ACTION_MOVE == iAction) {
fX = event.getX();
fY = event.getY();
v.setX(fX);
v.setY(fY);
Log.d("",("X: " + fX + ", Y: " + fY));
}
else if (MotionEvent.ACTION_DOWN != iAction) {
bExitValue = false;
}
return(bExitValue);
}
The Log.d call and standalone floats aren't necessary to make the code work, but they do allow you to see the interleaving of values in the LogCat window.
I have found out, that on the galaxy s 4 getY and getRawY both are wrong. But they change in an orthogonal way. So you can get the right value by the following code:
rawY = event.getRawY() - spaceOverLayout;
normalY = event.getY();
y = 0F;
float prozentPosition = ((rawY + normalY) / 2) / height;
y = (normalY * (1 - prozentPosition)) + (rawY * prozentPosition);
hopefully it will help.

Canvas gamecamera - locationtranslate values messed up by applied canvas scaling

I'm working on an android game and am currently busy with a gamecamera type of component.
The best method seems to be translating the canvas (possibly with a matrix) to move the entire canvas to act like a gamecamera. (please correct me if I'm wrong).
this is all getting along pretty nicely, the gamecamera is moving and the x and y values of the camera are being used as guidelines to translate the canvas location:
public void onMove(float distanceX, float distanceY){
Camera.x += (int) distanceX;
Camera.y += (int) distanceY;
}
protected void onDraw(Canvas canvas) {
canvas.translate(Camera.x, Camera.y);
level.draw(canvas);
//the text is a ui component so shouldn't move with the level
canvas.restore();
canvas.drawText(fps + " fps", 0, 32, textPaint);
}
When trying to interact with the world I simply add the camera's coordinates to the touch's coordinates to get the correct point in the world:
public void onSingleTap(MotionEvent event) {
float mouseX = event.getX() - Camera.x;
float mouseY = event.getY() - Camera.y;
}
However, The user also has the choice to scale the screen, zooming to a point on the world.
case MotionEvent.ACTION_MOVE:
if (MODE == ZOOM && event.getPointerCount() == 2) {
float newDist = spacing(event);
if (newDist > 10f) {
Camera.zoom = newDist / PrevMultiDist;
//the midpoint for the zoom
midPoint(midpoint, event);
Camera.setPivot(midpoint.x, midpoint.y);
translateMatrix.postScale(Camera.zoom, Camera.zoom, Camera.pivotX, Camera.pivotY);
}
}
break;
The zoom is messing up the world-touch translation coordinates since the view is being moved to zoom on the chosen spot. I think if I substract the zoom offset value from the camera location that I will get the correct position in the world again.
So the touchevent becomes something like this:
public void onSingleTap(MotionEvent event) {
//ideally the zoomoffset might be added in the camera's location already?
float mouseX = event.getX() - Camera.x - zoomOffsetX;
float mouseY = event.getY() - Camera.y - zoomOffsetY;
}
I already found this question: Android get Bitmap Rect (left, top, right, bottom) on a Canvas but it doesn't provide the awsner I want and I can't figure out if his calculations can help me.
I feel like I'm missing something really simple. Hope someone can help!

Categories

Resources