I am trying to allow the user to touch the image and then basically a cirular magnifier will show that will allow the user to better select a certain area on the image. When the user releases the touch the magnified portion will dissapear. This is used on several photo editing apps and I am trying to implement my own version of it. The code I have below does magnify a circular portion of the imageview but does not delete or clear the zoom once I release my finger. I currently set a bitmap to a canvas using canvas = new Canvas(bitMap); and then set the imageview using takenPhoto.setImageBitmap(bitMap); I am not sure if I am going about it the right way. The onTouch code is below:
zoomPos = new PointF(0,0);
takenPhoto.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
zoomPos.x = event.getX();
zoomPos.y = event.getY();
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
shader.setLocalMatrix(matrix);
canvas.drawCircle(zoomPos.x, zoomPos.y, 20, shaderPaint);
takenPhoto.invalidate();
break;
case MotionEvent.ACTION_MOVE:
zoomPos.x = event.getX();
zoomPos.y = event.getY();
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
canvas.drawCircle(zoomPos.x, zoomPos.y, 20, shaderPaint);
takenPhoto.invalidate();
break;
case MotionEvent.ACTION_UP:
//clear zoom here?
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
return true;
}
});
Adapting your code, I was able to get the following approach working.
In the onTouch function, set a global point for determining where the user has touched, and set a boolean to indicate whether zooming is currently active or not:
#Override
public boolean onTouch(View view, MotionEvent event) {
int action = event.getAction();
zoomPos.x = event.getX();
zoomPos.y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
zooming = true;
this.invalidate();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
zooming = false;
this.invalidate();
break;
default:
break;
}
return true;
}
Then, in the onDraw method, you use your code for drawing the zoomed in portion:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (zooming) {
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
mPaint.getShader().setLocalMatrix(matrix);
canvas.drawCircle(zoomPos.x, zoomPos.y, 100, mPaint);
}
}
Note that for the shader, I used a bitmap shader as described here, which was created with:
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
mShader = new BitmapShader(mBitmap, TileMode.CLAMP, TileMode.CLAMP);
mPaint = new Paint();
mPaint.setShader(mShader);
The best way to revert any changes made to the image will be to reload the image from the source file. Or alternatively, keep the a copy original matrix variable before transformations begun, during MotionEvent.ACTION_UP load the original matrix.
Some people asked for a fixed place magnifier position, I experimented it and came up with the solution:
// bitmapWidth is the width of bitmap used for BitmapShader
// bitmapHeight is the height of bitmap used for BitmapShader
// canvasWidth is the width of canvas where the zoom touch events are tracked (usually has the same image as shader but can be different size)
// canvasHeight is the height of canvas where the zoom touch events are tracked
// touchPoint is the point on the canvas which area should be shown in zoom circle
// fixedZoomPoint is the center of the zoom circle (different from touch point)
// ZOOM_SCALE is the zooming ratio (e.g.: 2f)
// ZOOM_RADIUS is the radius of the zoom circle
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (zooming) {
val widthRatio = bitmapWidth / canvasWidth // This can be omitted if 1.0
val heightRatio = bitmapHeight / canvasHeight // This can be omitted if 1.0
matrix.reset()
matrix.postScale(ZOOM_SCALE, ZOOM_SCALE, touchPoint.x * widthRatio, touchPoint.y * heightRatio)
matrix.postTranslate(fixedZoomPoint.x - touchPoint.x * widthRatio, fixedZoomPoint.y - touchPoint.y * heightRatio)
paint.getShader().setLocalMatrix(matrix)
drawCircle(fixedZoomPoint.x, fixedZoomPoint.y, ZOOM_RADIUS, paint)
}
}
Related
I am drawing many images to canvas using bitmap.. I want those bitmaps to be clickable.. so i draw them using rects so i can later check if the touched point is within those rects or not in OnTouch method..
but rect.contains(x,y) method always returns false if i touched anywhere on the bitmap.. while it returns true if the touched point was above the bitmap it self.. it seems like the bitmap and rect are sharing the same point (left, top) but the bitmap is drawn downward and the rect is drawn upward..
Can you enlighten me please?
here is my drawCanvas code:
private void drawCanvas(int position) {
Bitmap icon9 = BitmapFactory.decodeResource(getResources(), R.drawable.camera_icon);
Bitmap icon9Scaled = Bitmap.createScaledBitmap(icon9, 60, 60, true);
icon9.recycle();
rect9 = new Rect();
rect9.set(15, 330, 15 + icon9Scaled.getWidth(), 330 + icon9Scaled.getHeight());
// src9 = new Rect(0, 0, 60, 60);
canvas.drawBitmap(icon9Scaled, null, rect9, null);
}
And here is my OnTouche method:
#Override
public boolean onTouch(View v, MotionEvent event) {
//super.onTouchEvent(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
// Toast.makeText(getApplicationContext(), "Is TOUCHED", Toast.LENGTH_LONG).show();
if(rect9.contains(x,y))
Toast.makeText(getApplicationContext(), "Icon 9 Is TOUCHED", Toast.LENGTH_LONG).show();
Log.d("Is Icon 9 Touched", Boolean.toString(rect9.contains(x,y)));
Log.d("Is Icon 9 Touched X", Integer.toString(x));
Log.d("Is Icon 9 Touched Y", Integer.toString(y));
break;
}
return true;
}
I want to draw at specific coordinates of an image which is displayed in an imageview. I use the src attribute of a imageview to load the image into the view. This code is used in an custom imageview to draw on the image:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float scaleh =(float)canvas.getHeight()/(float)orginalheight();
float scalew = ((float)canvas.getWidth()/(float)orginalwidth());
canvas.drawRect(10*scaleh,9*scalew,20*scaleh,30*,scalewpaint);
}
This code draws the rectangle at the wrong location. What is wrong?
You should get the dimensions of the View from the ImageView itself rather than Canvas especially when the ulterior goal is to Draw on Image inside ImageView.
A Canvas works for you as a pretense, or interface, to the actual
surface upon which your graphics will be drawn — it holds all of your
"draw" calls. Via the Canvas, your drawing is actually performed upon
an underlying Bitmap, which is placed into the window.
Firstly, in order to get the Coordinates, implement onTouchListener for ImageView. This will give you X and Y co ordinates to draw the Canvas on.
imageView.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
xDown = event.getX();
yDown = event.getY();
break;
case MotionEvent.ACTION_MOVE:
xUp = event.getX();
uUp = event.getY();
canvas.drawLine(xDown, yDown, xUp, uUp, paint);
imageView.invalidate();
xDown = xUp;
yDown = uUp;
break;
case MotionEvent.ACTION_UP:
xUp = event.getX();
uUp = event.getY();
canvas.drawLine(xDown, yDown, xUp, uUp, paint);
imageView.invalidate();
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
return true;
}});
Then create a Canvas to draw on top of ImageView.
//Create a new image bitmap and attach a brand new canvas to it
Bitmap tempBitmap = Bitmap.createBitmap(myBitmap.getWidth(), myBitmap.getHeight(), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(tempBitmap);
paint = new Paint();
paint.setColor(YOUR_DESIRED_COLOR);
paint.setStrokeWidth(INT_VALUE);
matrix = new Matrix();
canvas.drawBitmap(bmp, matrix, paint);
//Attach the canvas to the ImageView
imageViewsetImageBitmap(tempBitmap);
you can also use canvas.drawRect(left,top,right,bottom,paint); if you want to draw Rectangle simply.
Hope it helps.
I am working on a function that drawing some transparent line on the canvas, the problem is , there is some "ball shape" on the line as the screenshot show.
Here is my code:
canvas = new Canvas(alteredBitmap);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(width);
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAlpha(alpha);
matrix_draw = new Matrix();
canvas.drawBitmap(bmp, matrix_draw, paint);
setImageBitmap(alteredBitmap);
press the button to set alpha
public void setAlpha(int alpha) {
this.alpha = alpha;
paint.setAlpha(alpha);
}
And the listener
drawListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downx = getPointerCoords(event)[0];// event.getX();
downy = getPointerCoords(event)[1];// event.getY();
break;
case MotionEvent.ACTION_MOVE:
upx = getPointerCoords(event)[0];// event.getX();
upy = getPointerCoords(event)[1];// event.getY();
canvas.drawLine(downx, downy, upx, upy, paint);
invalidate();
downx = upx;
downy = upy;
break;
case MotionEvent.ACTION_UP:
// upx = getPointerCoords(event)[0];// event.getX();
// upy = getPointerCoords(event)[1];// event.getY();
// canvas.drawLine(downx, downy, upx, upy, paint);
// invalidate();
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
return true;
}
};
final float[] getPointerCoords(MotionEvent e) {
final int index = e.getActionIndex();
final float[] coords = new float[] { e.getX(index), e.getY(index) };
Matrix matrix = new Matrix();
getImageMatrix().invert(matrix);
matrix.postTranslate(getScrollX(), getScrollY());
matrix.mapPoints(coords);
return coords;
}
Thanks a lot for helping
The ball shapes are where each line segment overlaps the previous one. You can fix this by using a second image overlaid on top of the image that you are editing.
Initialize the overlay image to completely transparent and make it the same size as the image you are editing.
ImageView overlayImageView = findViewById(R.id.overlay);
Bitmap overlayBitmap = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
overlayBitmap.erase(0x00000000); // transparent
overlayImageView.setImageBitmap(overlayBitmap);
Inside setAlpha() set the alpha of the overlay image to the alpha value.
overlayImageView.setImageAlpha((float)alpha / 255.0f);
When the user is drawing a line, in the case MotionEvent.ACTION_MOVE block, draw the line onto the overlay image instead, but at full opacity. Because all line segments are drawn at full opacity, there won't be any ball shapes where they overlap, but the line will still appear transparent because of the alpha value applied to the overlay image.
In case MotionEvent.ACTION_UP, transfer the line onto the image by drawing the overlay image onto the target image using canvas draw calls, using the alpha value set in setAlpha(), and then clear the overlay image to transparent.
I am creating a coaching board. I aim to draw a line between two points. Initial position is in the image below:
If I drag the chip away from the initial position, it should draw a line. When I move the chip, it should always draw a line.
This is what I tried:
case MotionEvent.ACTION_UP:
Toast.makeText(this, "here", Toast.LENGTH_SHORT).show();
Bitmap bitmap = Bitmap.createBitmap((int) getWindowManager()
.getDefaultDisplay().getWidth(), (int) getWindowManager()
.getDefaultDisplay().getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStrokeWidth(10);
int startx = 50;
int starty = 100;
int endx = 150;
int endy = 210;
canvas.drawLine(startx, starty, endx, endy, paint);
break;
I put that code inside the ontouchlistener of the chip.
You can download the project here: https://www.dropbox.com/s/ggfbsbkaokj9vxi/CoachingBoard.rar?dl=0
After further examining your code, I believe I have achieved what you'd like.
We're going to go to DrawingView and define a getter for drawCanvas, so we can access our canvas outside of the DrawingView class.
Next we're going to head to Basketball and do the following:
float startX;
float startY;
public boolean onTouch(View view, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getRawX();
startY = event.getRawY();
...
break;
case MotionEvent.ACTION_UP:
Paint paint = new Paint(); //set this as a field in drawView with another getter to avoid garbage collection penalties
paint.setStrokeWidth(15f);
paint.setColor(Color.BLACK);
drawView.getCanvas().drawLine(startX, startY, event.getRawX(), event.getRawY(), paint);
drawView.invalidate();
break;
}
}
What this does: when you pick up the chip it will save the starting coordinates, and when you drop the chip it will draw a line in your drawView canvas from start to end.
You can even draw the lines as a continuous Path so the lines always touch, but that is outside the context of this answer.
the best solution to draw a line by dragging is to get x and y positions from ACTION_DOWN and then get ever next x and y position from ACTION_MOVE, outside the switch statement, invalidate the canvas. draw the line with this starting x,y and ending x,y positions.
Code of these all explanation is below:
public class TouchEventView extends View {
float downxpos;
float downypos;
float upxpos;
float upypos;
private Paint paint = new Paint();
private Path path = new Path()
public TouchEventView(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setColor(Color.GREEN);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawLine(downxpos, downypos, upxpos, upypos, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downxpos = event.getX();
downypos = event.getY();
case MotionEvent.ACTION_MOVE:
upxpos = event.getX();
upypos = event.getY();
break;
default:
return false;
}
invalidate();
return true;
}
}
hope this will help you and other community if you need more description feel free to ask.
In Android I have an scaled and centered custom imageview.
On the imageview an image is drawn via canvas and the imageview is scaled to the scaled bitmap.
I want to move an Rect inside this custom view.
The problem is, that moving the Rectangle only works with raw events and so the rect disappears before the bottom is reached.
(because the imageview thinks that the touchevent is outside)
i overided the onDraw method:
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mImageBitmap, 0, 0, mImagePaint);
canvas.setMatrix(matrix);
Rect rt = new Rect((int)rawEventX-50, (int)rawEventY-50, (int) rawEventX+50, (int) rawEventY + 50);
// works, but disappears before bottom of imageview is reached.. (because the imageview thinks that the touchevent is outside)
//Rect rt = new Rect((int)eventX-50, (int)eventY-50, (int) eventX+50, (int) eventY + 50);
// This rect is draw over and left of the finger :-(
canvas.drawRect(rt, paintText);
}
The scaling of image and custom imageview:
protected void scaleBitmapToImageViewSizeWithAntiAliashing(Bitmap bitmap) {
getLayoutParams().width = scaledWidth;
getLayoutParams().height = scaledHeight;
refreshDrawableState();
mImageBitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
mImageCanvas = new Canvas(mImageBitmap);
}
I also have an onTouchEvent:
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
eventX = event.getX();
eventY = event.getY();
rawEventX = event.getRawX();
rawEventY = event.getRawY();
savedMatrix.set(matrix);
//start.set(event.getRawX(), event.getRawY());
start.set(eventX, eventY);
mode = Constants.DRAG;
break;
case MotionEvent.ACTION_MOVE:
matrix.set(savedMatrix);
eventX = event.getX();
eventY = event.getY();
rawEventX = event.getRawX();
rawEventY = event.getRawY();
break;
}
invalidate();
return true;
}
Is it possible to use only the imageview coordinates inside the custom imageview for the onTouchEvent? How the OnTouchEvent is generelly used in an Imageview?
I did not found any solution via google and i am really frustrated.
The problem was:
if you want to map from pixel based View coords to ImageView draw coords then use getImageMatrix() invert() it and then mapPoints() on inverted Matrix – thanks to pskink