I have used this PhotoView library for custom ImageView. I want to scale the image at particular point. Here is the method I found is setScale(float scale, float focalX, float focalY, boolean animate)
I am wondering what can I pass a value of focalX and focalY , I have X and Y coordinate which I am passing currently and it scales to very different position.
Here is a snippet,
intResultX = intTotalX / intArraySize;
intResultY = intTotalY / intArraySize;
mMap.setScale(5, intResultX, intResultY, true);
To zoom at particular XY coordinate in Imageview you can pass a value of focalX and focalY along with scale (must be between max scale an min scale of PhotoView) and boolean value to set animation.
Code to get max-min scales:
mPhotoView.getMinimumScale();
mPhotoView.getMaximumScale();
focalX and focalY It can be any points on screen, here I have taken two examples one is center of the screen and other is top-left corner. following is the code for both cases.
Code:
Random r = new Random();
float minScale = mPhotoView.getMinimumScale();
float maxScale = mPhotoView.getMaximumScale();
float randomScale = minScale + (r.nextFloat() * (maxScale - minScale));
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int height = displayMetrics.heightPixels;
int width = displayMetrics.widthPixels;
int centerX=width/2;
int centerY =height/2;
/*pass a value of focalX and focalY to scale image to center*/
//mPhotoView.setScale(randomScale, centerX, centerY, true);
/*pass a value of focalX and focalY to scale image to top left corner*/
mPhotoView.setScale(randomScale, 0, 0, true);
Set zoom to the specified scale. Image will be centered around the point
(focusX, focusY). These floats range from 0 to 1 and denote the focus point
as a fraction from the left and top of the view. For example, the top left
corner of the image would be (0, 0). And the bottom right corner would be (1, 1).
public void setZoom(float scale, float focusX, float focusY, ScaleType scaleType) {
/*setZoom can be called before the image is on the screen, but at this point,
image and view sizes have not yet been calculated in onMeasure. Thus, we should
delay calling setZoom until the view has been measured.*/
if (!onDrawReady) {
delayedZoomVariables = new ZoomVariables(scale, focusX, focusY, scaleType);
return;
}
if (scaleType != mScaleType) {
setScaleType(scaleType);
}
resetZoom();
scaleImage(scale, viewWidth / 2, viewHeight / 2, true);
matrix.getValues(m);
m[Matrix.MTRANS_X] = -((focusX * getImageWidth()) - (viewWidth * 0.5f));
m[Matrix.MTRANS_Y] = -((focusY * getImageHeight()) - (viewHeight * 0.5f));
matrix.setValues(m);
fixTrans();
setImageMatrix(matrix);
}
Hope this helps. Happy coding.
Related
I'm trying to make a simple image editor. At the beginning I've thought that it'll be a good idea to simply save view state as Bitmap but, as it turned out, there is a wide range of screen resolutions and that leads to huge quality (and memory usage) fluctuations.
Now I'm trying to make a module that renders views state translated to desired resolution.
In the code below I'm trying to recreate current state of the views in canvas:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.id.test_1_1);
bitmap = Bitmap.createScaledBitmap(bitmap, parentView.getMeasuredWidth(), parentView.getMeasuredHeight(), true);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
for (View rootView : addedViews) {
ImageView imageView = rootView.findViewById(R.id.sticker);
float[] viewPosition = new float[2];
transformToAncestor(viewPosition, parentView, imageView);
Bitmap originalBitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
Matrix adjustMatrix = new Matrix();
adjustMatrix.postTranslate(viewPosition[0], viewPosition[1]);
adjustMatrix.postScale(
rootView.getScaleX(),
rootView.getScaleY(),
rootView.getWidth() / 2,
rootView.getHeight() / 2);
adjustMatrix.postRotate(rootView.getRotation(),
rootView.getWidth() / 2,
rootView.getHeight() / 2);
canvas.drawBitmap(originalBitmap, adjustMatrix, paint);
}
transformToAncestor function is from here.
public static void transformToAncestor(float[] point, final View ancestor, final View descendant) {
final float scrollX = descendant.getScrollX();
final float scrollY = descendant.getScrollY();
final float left = descendant.getLeft();
final float top = descendant.getTop();
final float px = descendant.getPivotX();
final float py = descendant.getPivotY();
final float tx = descendant.getTranslationX();
final float ty = descendant.getTranslationY();
final float sx = descendant.getScaleX();
final float sy = descendant.getScaleY();
point[0] = left + px + (point[0] - px) * sx + tx - scrollX;
point[1] = top + py + (point[1] - py) * sy + ty - scrollY;
ViewParent parent = descendant.getParent();
if (descendant != ancestor && parent != ancestor && parent instanceof View) {
transformToAncestor(point, ancestor, (View) parent);
}
}
(author wrote a note that his function does not support rotation, but there's not much rotation in my example so I don't think that important for now).
My problem is:
First image is generated via saving the parent view state. Second one is generated by translating views position, rotation and scale onto canvas.
As you can see, on the canvas, not scaled stickers are positioned properly, but scaled are incorrectly positioned.
How to position those scaled views properly?
I've managed to fix the issue myself.
It turned out my solution was nearly OK but I did not took into consideration that my manipulation of a matrix does change the arrangement of the original points, so my
rootView.getWidth() / 2,
rootView.getHeight() / 2
is no longer applicable as a center of the view after calling Matrix.postScale or Matrix.postRotation.
I wanted to:
apply scale with pivot on top left corner,
apply rotation with pivot on the center of the view.
Given the assumptions, here's the working code:
// setup variables for sizing and transformation
float position[] = new float[2];
transformToAncestor(position, rootView, imageView);
float desiredRotation = imageView.getRotation();
float sizeDeltaX = imageView.getMeasuredWidth() / (float) imageBitmap.getWidth();
float sizeDeltaY = imageView.getMeasuredHeight() / (float) imageBitmap.getHeight();
float desiredScaleX = imageView.getScaleX() * sizeDeltaX * scaleX;
float desiredScaleY = imageView.getScaleY() * sizeDeltaY * scaleY;
float imageViewWidth = imageView.getMeasuredWidth() * imageView.getScaleX();
float imageViewHeight = imageView.getMeasuredHeight() * imageView.getScaleY();
float rootViewWidth = rootView.getMeasuredWidth();
float rootViewHeight = rootView.getMeasuredHeight();
float percentXPos = position[0] / rootViewWidth;
float percentYPos = position[1] / rootViewHeight;
float percentXCenterPos = (position[0] + imageViewWidth/2)
/ rootViewWidth;
float percentYCenterPos = (position[1] + imageViewHeight/2)
/ rootViewHeight;
float desiredPositionX = background.getWidth() * percentXPos;
float desiredPositionY = background.getHeight() * percentYPos;
float desiredCenterX = background.getWidth() * percentXCenterPos;
float desiredCenterY = background.getHeight() * percentYCenterPos;
// apply above variables to matrix
Matrix matrix = new Matrix();
float[] points = new float[2];
matrix.postTranslate(
desiredPositionX,
desiredPositionY);
matrix.mapPoints(points);
matrix.postScale(
desiredScaleX,
desiredScaleY,
points[0],
points[1]);
matrix.postRotate(
desiredRotation,
desiredCenterX,
desiredCenterY);
// apply matrix to bitmap, then draw it on canvas
canvas.drawBitmap(imageBitmap, matrix, paint);
As you can see, the mapPoints method was the answer for my question - it simply returns points after tranformation.
I have game field which is a Group with Actors. The Group locketed in Table wich is locketed in ScrollPane. I have two Buttons to zoom in and zoom out the game field. Here is my code how I do it:
TextButton zoomInBtn = new TextButton("+", menuBtnStyle);
zoomInBtn.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
float width = fieldGroup.getWidth();
float height = fieldGroup.getHeight();
float newWidth = width + width * 0.1f;
if (newWidth > myWorld.getMaxWidth()) {
newWidth = myWorld.getMaxWidth();
}
float newHeight = height * newWidth / width;
fieldGroup.setWidth(newWidth);
fieldGroup.setHeight(newHeight);
myWorld.setWidth(Math.round(newWidth));
fieldGroup.reinitialiseChildren();
Cell cell = fieldTable.getCell(fieldGroup);
cell.clearActor();
cell.setActor(fieldGroup);
}
});
TextButton zoomOutBtn = new TextButton("-", menuBtnStyle);
zoomOutBtn.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
float width = fieldGroup.getWidth();
float height = fieldGroup.getHeight();
float newWidth = width - width * 0.1f;
if (newWidth < myWorld.getMinWidth()) {
newWidth = myWorld.getMinWidth();
}
float newHeight = height * newWidth / width;
Actor widget = scrollPane.getWidget();
fieldGroup.setWidth(newWidth);
fieldGroup.setHeight(newHeight);
myWorld.setWidth(Math.round(newWidth));
fieldGroup.reinitialiseChildren();
Cell cell = fieldTable.getCell(fieldGroup);
cell.clearActor();
cell.setActor(fieldGroup);
}
});
I change the size of my fieldGroup with Image Actors in it. And then readding it to Table.
The problem is: when I zoom with buttons it always zoom around left corner. I want it to zoom from canter of ScrollPane. I know that I can do it with Ortographic Camera, but it would be difficult, I think, to make it movements so smooth as ScrollPane. So maybe there is some way to do it with ScrollPane.
Every time you zoom in or out you would have to change the x and y position of the group relative to the zoom. You can do this using the setScrollx (and y) of your scroll pane.
You need to set so that the middle of the part you are viewing stays in the middle of the scrollpane. You can work out middle of newWidth by dividing it by 2. If the scrollPane is fullScreen you can half the value obtained by Gdx.graphics.getwidth();. The difference between these two is then the value which will keep the middles aligned.
newXvalue =(newWidth/2) - ((Gdx.graphics.getWidth())/2);
scrollPane.setScrollx(newXvalue);
The same should be done for y.
Note: if you want to zoom and move at same time, this will not work, I would recommend using orthographic camera with a gesture listener for that.
So, I have a custom BoardView class extends View. I implemented drawing board, lines and drawing "O" drawable when user press on the cell.
But, I could not implement following problems correctly:
1. Zoom BoardView when user doing pinch(multi touch).
2. Scroll BoardView to left, right, top, bottom if BoardView bigger than BoardView initial width or height.
3. Find right cell coordinate when user pressed on the cell after zooming or scrolling.
This is my first game project, please help me if anybody know how to solve this problem.
I tried but did not work properly. BoardView width equal width screen width and BoardView height equal to BoardView width. It is square board view.
I give 200 bounty to implementing this problem.
Here is my project, everyone can download and edit: https://drive.google.com/file/d/0BxNIUTd_m1x8cUQ2NGpSMDBuVVE/view?usp=sharing
Github: https://github.com/boyfox/GameTicTacToe
BoardView code: http://pastie.org/10109253 or http://pastebin.com/TRU8Ybds
I found solution self, but need improving code, you can answer to my question with your solution!
Could you please move your code to github? It would be much easier to download, edit and propose changes.
If you're looking for a generic implementation of two finger zoom/rotate, take a look at my game (https://github.com/ZieIony/Gravity). The most interesting part is the GamePanel view and the dispatchTouchEvent method:
private PointF prevPos = new PointF(), prevPos2 = new PointF();
float scale = 1;
final float MIN_SCALE = 0.2f, MAX_SCALE = 2.0f;
float rotation = 0;
Matrix matrix = new Matrix();
private float prevDist;
public boolean dispatchTouchEvent(android.view.MotionEvent event) {
if (event.getPointerCount() == 2) {
float d = dist(event.getX(0), event.getY(0), event.getX(1),
event.getY(1));
float pivotX = (event.getX(0) + event.getX(1)) / 2;
float pivotY = (event.getY(0) + event.getY(1)) / 2;
float prevPivotX = (prevPos.x + prevPos2.x) / 2;
float prevPivotY = (prevPos.y + prevPos2.y) / 2;
if (event.getAction() == MotionEvent.ACTION_MOVE) {
float newScale = scale * d / prevDist;
newScale = Math.max(MIN_SCALE,
Math.min(newScale, MAX_SCALE));
float scaleFactor = newScale / scale;
scale = newScale;
matrix.postScale(scaleFactor, scaleFactor, pivotX, pivotY);
float prevAngle = (float) Math.atan2(
prevPos.x - prevPos2.x, prevPos.y - prevPos2.y);
float angle = (float) Math.atan2(
event.getX(0) - event.getX(1), event.getY(0)
- event.getY(1));
rotation += prevAngle - angle;
matrix.postRotate(
(float) ((prevAngle - angle) * 180.0f / Math.PI),
pivotX, pivotY);
matrix.postTranslate(-prevPivotX + pivotX, -prevPivotY
+ pivotY);
}
prevPos.x = event.getX(0);
prevPos.y = event.getY(0);
prevPos2.x = event.getX(1);
prevPos2.y = event.getY(1);
prevDist = d;
}
return true;
}
This method produces a transformation matrix, which you should use for drawing.
protected void dispatchDraw(Canvas canvas) {
canvas.save();
canvas.setMatrix(matrix);
// do your drawing here
canvas.restore();
}
Have a look at my answer here. If the answer seems satisfactory have a look at my github code. I think it is not difficult to implement your third point
"3. Find right cell coordinate when user pressed on the cell after zooming or scrolling.",
because the scaleFactor is available in MyView.java and scroll offsets could be obtained by getScrollX() and getScrollY(), by just doing simple math.
In my app I need to let users to check the eyes at some photo.
In OnTouchListener.onTouch(...) I get the coordinates of the ImageView.
How can I convert this coordinates to the point at the bitmap that was touched?
this works for me at least with API 10+:
final float[] getPointerCoords(ImageView view, MotionEvent e)
{
final int index = e.getActionIndex();
final float[] coords = new float[] { e.getX(index), e.getY(index) };
Matrix matrix = new Matrix();
view.getImageMatrix().invert(matrix);
matrix.postTranslate(view.getScrollX(), view.getScrollY());
matrix.mapPoints(coords);
return coords;
}
Okay, so I've not tried this, but giving it a bit of thought, here's what I've got as a suggestion:
ImageView imageView = (ImageView)findViewById(R.id.imageview);
Drawable drawable = imageView.getDrawable();
Rect imageBounds = drawable.getBounds();
//original height and width of the bitmap
int intrinsicHeight = drawable.getIntrinsicHeight();
int intrinsicWidth = drawable.getIntrinsicWidth();
//height and width of the visible (scaled) image
int scaledHeight = imageBounds.height();
int scaledWidth = imageBounds.width();
//Find the ratio of the original image to the scaled image
//Should normally be equal unless a disproportionate scaling
//(e.g. fitXY) is used.
float heightRatio = intrinsicHeight / scaledHeight;
float widthRatio = intrinsicWidth / scaledWidth;
//do whatever magic to get your touch point
//MotionEvent event;
//get the distance from the left and top of the image bounds
int scaledImageOffsetX = event.getX() - imageBounds.left;
int scaledImageOffsetY = event.getY() - imageBounds.top;
//scale these distances according to the ratio of your scaling
//For example, if the original image is 1.5x the size of the scaled
//image, and your offset is (10, 20), your original image offset
//values should be (15, 30).
int originalImageOffsetX = scaledImageOffsetX * widthRatio;
int originalImageOffsetY = scaledImageOffsetY * heightRatio;
Give this idea a try and see if it works for you.
besides considering the offset due to padding (margin is part of the layout, it's space outside the view and doesn't have to be considered), if the image is scaled you can get the image matrix (ImageView.getImageMatrix()) to scale coordinates.
EDIT:
You can get x/y scaling factor and translation amount getting the values array and using respective index constants:
float[] values;
matrix.getValues(values);
float xScale = values[Matrix.MSCALE_X];
note that translation doesn't include padding, you still would have to consider that separately. translation is used for instance in FIT_CENTER scaling when there's some "blank" space.
I'd say you probably need to offset the coordinates from the ImageView with any padding or margins in the layout to get the correct coordinates of the BitMap.
To add to kcoppock's answer, I just want to add that:
//original height and width of the bitmap
int intrinsicHeight = drawable.getIntrinsicHeight();
int intrinsicWidth = drawable.getIntrinsicWidth();
may return an answer you're not expecting. These values depend on the dpi of the drawable folder you load the image from. For instance, you might get a different value if you load the image from /drawable vs /drawable-hdpi vs /drawable-ldpi.
Get floor Width and height
float floorWidth = floorImage.getWidth();
float floorHeight = floorImage.getHeight();
Calculate protionate value
float proportionateWidth = bitmapWidth / floorWidth;
float proportionateHeight = bitmapHeight / floorHeight;
Your X & Y
float x = 315;
float y = 119;
Multiple with PropotionateValue
x = x * proportionateWidth;
y = y * proportionateHeight;
As I came accross this question and tried it out myself, here is my solution.
It seems to work with stretched and centered images.
class MyEditableImageView(context: Context, attrs: AttributeSet) :
androidx.appcompat.widget.AppCompatImageView(context, attrs) {
override fun onTouchEvent(event: MotionEvent): Boolean {
val image = drawable.toBitmap().copy(Bitmap.Config.ARGB_8888, true)
val xp = (event.x - imageMatrix.values()[Matrix.MTRANS_X]) / imageMatrix.values()[Matrix.MSCALE_X]
val yp = (event.y - imageMatrix.values()[Matrix.MTRANS_Y]) / imageMatrix.values()[Matrix.MSCALE_Y]
if (xp >= 0 && xp < image.width && yp >= 0 && yp < image.height) {
doSomethingOnImage(image, xp, yp)
setImageBitmap(image)
}
return super.onTouchEvent(event)
}
...
}
In my app I need to let users to check the eyes at some photo.
In OnTouchListener.onTouch(...) I get the coordinates of the ImageView.
How can I convert this coordinates to the point at the bitmap that was touched?
this works for me at least with API 10+:
final float[] getPointerCoords(ImageView view, MotionEvent e)
{
final int index = e.getActionIndex();
final float[] coords = new float[] { e.getX(index), e.getY(index) };
Matrix matrix = new Matrix();
view.getImageMatrix().invert(matrix);
matrix.postTranslate(view.getScrollX(), view.getScrollY());
matrix.mapPoints(coords);
return coords;
}
Okay, so I've not tried this, but giving it a bit of thought, here's what I've got as a suggestion:
ImageView imageView = (ImageView)findViewById(R.id.imageview);
Drawable drawable = imageView.getDrawable();
Rect imageBounds = drawable.getBounds();
//original height and width of the bitmap
int intrinsicHeight = drawable.getIntrinsicHeight();
int intrinsicWidth = drawable.getIntrinsicWidth();
//height and width of the visible (scaled) image
int scaledHeight = imageBounds.height();
int scaledWidth = imageBounds.width();
//Find the ratio of the original image to the scaled image
//Should normally be equal unless a disproportionate scaling
//(e.g. fitXY) is used.
float heightRatio = intrinsicHeight / scaledHeight;
float widthRatio = intrinsicWidth / scaledWidth;
//do whatever magic to get your touch point
//MotionEvent event;
//get the distance from the left and top of the image bounds
int scaledImageOffsetX = event.getX() - imageBounds.left;
int scaledImageOffsetY = event.getY() - imageBounds.top;
//scale these distances according to the ratio of your scaling
//For example, if the original image is 1.5x the size of the scaled
//image, and your offset is (10, 20), your original image offset
//values should be (15, 30).
int originalImageOffsetX = scaledImageOffsetX * widthRatio;
int originalImageOffsetY = scaledImageOffsetY * heightRatio;
Give this idea a try and see if it works for you.
besides considering the offset due to padding (margin is part of the layout, it's space outside the view and doesn't have to be considered), if the image is scaled you can get the image matrix (ImageView.getImageMatrix()) to scale coordinates.
EDIT:
You can get x/y scaling factor and translation amount getting the values array and using respective index constants:
float[] values;
matrix.getValues(values);
float xScale = values[Matrix.MSCALE_X];
note that translation doesn't include padding, you still would have to consider that separately. translation is used for instance in FIT_CENTER scaling when there's some "blank" space.
I'd say you probably need to offset the coordinates from the ImageView with any padding or margins in the layout to get the correct coordinates of the BitMap.
To add to kcoppock's answer, I just want to add that:
//original height and width of the bitmap
int intrinsicHeight = drawable.getIntrinsicHeight();
int intrinsicWidth = drawable.getIntrinsicWidth();
may return an answer you're not expecting. These values depend on the dpi of the drawable folder you load the image from. For instance, you might get a different value if you load the image from /drawable vs /drawable-hdpi vs /drawable-ldpi.
Get floor Width and height
float floorWidth = floorImage.getWidth();
float floorHeight = floorImage.getHeight();
Calculate protionate value
float proportionateWidth = bitmapWidth / floorWidth;
float proportionateHeight = bitmapHeight / floorHeight;
Your X & Y
float x = 315;
float y = 119;
Multiple with PropotionateValue
x = x * proportionateWidth;
y = y * proportionateHeight;
As I came accross this question and tried it out myself, here is my solution.
It seems to work with stretched and centered images.
class MyEditableImageView(context: Context, attrs: AttributeSet) :
androidx.appcompat.widget.AppCompatImageView(context, attrs) {
override fun onTouchEvent(event: MotionEvent): Boolean {
val image = drawable.toBitmap().copy(Bitmap.Config.ARGB_8888, true)
val xp = (event.x - imageMatrix.values()[Matrix.MTRANS_X]) / imageMatrix.values()[Matrix.MSCALE_X]
val yp = (event.y - imageMatrix.values()[Matrix.MTRANS_Y]) / imageMatrix.values()[Matrix.MSCALE_Y]
if (xp >= 0 && xp < image.width && yp >= 0 && yp < image.height) {
doSomethingOnImage(image, xp, yp)
setImageBitmap(image)
}
return super.onTouchEvent(event)
}
...
}