Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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};
}
like image 884
Heru S Avatar asked May 30 '16 10:05

Heru S


People also ask

Why does an image captured using camera intent gets rotated on some devices on Android?

Answer: Most phone cameras are landscape, meaning if you take the photo in portrait, the resulting photos will be rotated 90 degrees. In this case, the camera software should populate the Exif data with the orientation that the photo should be viewed in.

How do I rotate an image in Swift?

Step 1 − Open Xcode→SingleViewApplication→name it RotateImage. Step 2 − Open Main. storyboard, add UIImageView and add 2 buttons as shown below name them ROTATE BY 90 DEGREES AND ROTATE BY 45 DEGREES.


2 Answers

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.

like image 23
Kaamel Avatar answered Sep 27 '22 18:09

Kaamel


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;
}
like image 164
Muddassir Ahmed Avatar answered Sep 27 '22 17:09

Muddassir Ahmed