Sunday, June 12, 2016

Erasing a rotated image in Android does not show erase the correct path

Leave a Comment

We are building an Android application that involves image editing. A few of the features include rotating an image and erasing part of the image.

We are using the following library: https://github.com/nimengbo/StickerView

We have successfully created a function to rotate and erase the image. However, when we tried to perform the following actions:

  1. Rotating an image at a certain degree.
  2. Then, erasing the image.

We found the following bug:

  1. When we tried to erase the rotated image, the erased path did not reflect the path which our finger traced on the screen.

enter image description here

From the above image, the yellow line is the actual movement of the finger (straight down vertical across the sticker). But, the resulting erased path was found to be diagonal.

This problem only exists when the image is rotated. It does not exist when the image is not rotated.

After further debugging, we have a few assumptions from the above problems:

  1. Due to the rotated image, the x and y absolution position is changed. Thus, the path does not reflect the correct path by the touch routes.

How can we ensure that the path is still referencing the right path on what the finger is touching even after being rotated?

Here is the code we have in our StickerView.java class that extends ImageView class.

onTouchEvent

@Override public boolean onTouchEvent(MotionEvent event) {     int action = MotionEventCompat.getActionMasked(event);      float[] pointXY = new float[2];      pointXY = getAbsolutePosition(event.getX(0),event.getY(0));     float xPoint = pointXY[0];     float yPoint = pointXY[1];       switch (action) {         case MotionEvent.ACTION_DOWN:              // first touch             // if it is inside the image             if (isInBitmap(event)) {                 // set isInSide to true                 isInSide = true;                  // if it is a scratch                 if(doScratch){                     // start creating the scratch path                     mScratchPath = new Path();                     mScratchPath.moveTo(xPoint, yPoint);                     mScratchPath.lineTo(xPoint, yPoint);                     paths.add(new Pair<Path, Paint>(mScratchPath, mScratchCurrentPaint));                 }             }             break;         case MotionEvent.ACTION_MOVE:              // if two fingers touch and is not a scratch,             // then it means we can rotate / resize / pan             if (isPointerDown && !doScratch) {                 // reset matrix                 matrix.reset();                 // get the center point                 scaledImageCenterX = (mImageWidth * mScaleFactor) / 2 ;                 scaledImageCenterY = (mImageHeight * mScaleFactor) / 2;                  // ROTATE THE IMAGE !!!                 matrix.postRotate(lastRotateDegree, scaledImageCenterX, scaledImageCenterY);                  // done to call onDraw                 invalidate();             }             break;     }      if (operationListener != null) {         operationListener.onEdit(this);     }      // if it is a scratch     if(doScratch){         // then for every point, create a scratch path         mScratchPath.lineTo(xPoint, yPoint);         invalidate();     }else{         mScaleDetector.onTouchEvent(event);         mRotateDetector.onTouchEvent(event);         mMoveDetector.onTouchEvent(event);         mShoveDetector.onTouchEvent(event);     }     return handled; } 

onDraw

@Override protected void onDraw(Canvas canvas) {     super.onDraw(canvas);      // if the image exists     if (mBitmap != null) {         // save canvas         canvas.save();          // if it is a scratch         if(doScratch){             // scratch the image             mFillCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);              // Draw our surface, nice an pristine             final Drawable surface = mScratchSurface;             if(surface != null) {                 surface.draw(mFillCanvas);             }             //Scratch the surface             if(paths != null) {                 for (Pair<Path, Paint> p : paths) {                     mFillCanvas.drawPath(p.first,p.second);                 }             }             mBitmap = mFillCache;         }          canvas.drawBitmap(mBitmap, matrix, bitmapPaint);         canvas.restore();     } } 

getAbsolutePosition function

public float[] getAbsolutePosition(float Ax, float Ay) {    float[] mMatrixValues = new float[9];    matrix.getValues(mMatrixValues);     float x = mImageWidth - ((mMatrixValues[Matrix.MTRANS_X] - Ax) / mMatrixValues[Matrix.MSCALE_X]) - (mImageWidth - getTranslationX());     float y = mImageHeight - ((mMatrixValues[Matrix.MTRANS_Y] - Ay) / mMatrixValues[Matrix.MSCALE_X]) - (mImageHeight - getTranslationY());     return new float[] { x, y}; } 

2 Answers

Answers 1

You need to apply the rotation matrics on your coordinates. Here theta is the angle by which the image is rotated. You Need to Apply Matrix rotation

Image source wikipedia

In code it will be something like this

int getAbsoluteX(int x, int y, double theta) {    int x_new = (int)(x*Math.cos(theta) - y*Math.sin(theta));    return x_new; }  int getAbsoluteY(int x, int y, double theta)  {    int y_new = (int)(x*Math.sin(theta) + y*Math.cos(theta));    return y_new; } 

Answers 2

You are rotating the entire view in the onDraw method. Not only this affects the performance (you are repeating the same transformation every time), it affects the outcome as you noticed since it rotates everything in the view, including the paths.

If for some reason you must do it this way (not recommended), then you need to rotate the paths in the opposite direction by the same amount. I strongly suggest you don't do that. Try to find a way around rotating the background inside the onDraw, instead. For example, whenever the background is rotated, you can create a new background (outside the onDraw method) and use that background in the onDraw method.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment