2
votes

I have a custom imageView (zoomImageView) which has pan and zoom capability. I also have a triangular mask image. When I apply the triangular mask image on the imageView (showing a galaxy image), I see the galaxy image cropped in triangular shape, just as I want.

enter image description here

The problem is, I want the user to pinch zoom and pan the background image (galaxy) without zooming and panning the mask image. currently both the background (galaxy) and mask image is panning and zooming together.

How can I freeze the mask image and make only the background image pan and zoomable? I want the mask image to fuction as a fixed "Window" through which the background image is pan ans zoomable.

I am applying the mask this way:

final zoomImageView img = (zoomImageView)findViewById(R.id.iv_cropImage);
    img.setImageURI(uriValue);

    Canvas canvas = new Canvas();

    try {
        Bitmap mainImage = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uriValue);

        Bitmap mask = BitmapFactory.decodeResource(getResources(), R.mipmap.mask_triangle_perfect);
        Bitmap result = Bitmap.createBitmap(mainImage.getWidth(), mainImage.getHeight(), Bitmap.Config.ARGB_8888);

        canvas.setBitmap(result);
        Paint paint = new Paint();
        paint.setFilterBitmap(false);

        canvas.drawBitmap(mainImage, 0, 0, paint);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        canvas.drawBitmap(mask, 0, 0, paint);
        paint.setXfermode(null);

        img.setImageBitmap(result);
        img.invalidate();
    }
    catch (Exception e)
    {
        Toast.makeText(context, "Error 006 occurred.", Toast.LENGTH_SHORT).show();
    }

and this is my custom imageview class

public class zoomImageView extends ImageView {

private static final int INVALID_POINTER_ID = -1;

private float mPosX;
private float mPosY;

private float mLastTouchX;
private float mLastTouchY;
private float mLastGestureX;
private float mLastGestureY;
private int mActivePointerId = INVALID_POINTER_ID;

private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;

public zoomImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
    mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
}

public zoomImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}

@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: {
            if (!mScaleDetector.isInProgress()) {
                final float x = ev.getX();
                final float y = ev.getY();

                mLastTouchX = x;
                mLastTouchY = y;
                mActivePointerId = ev.getPointerId(0);
            }
            break;
        }


        case MotionEvent.ACTION_MOVE: {

            // Only move if the ScaleGestureDetector isn't processing a gesture.
            if (!mScaleDetector.isInProgress()) {
                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                final float x = ev.getX(pointerIndex);
                final float y = ev.getY(pointerIndex);

                final float dx = x - mLastTouchX;
                final float dy = y - mLastTouchY;

                mPosX += dx;
                mPosY += dy;

                invalidate();

                mLastTouchX = x;
                mLastTouchY = y;
            }
            else{
                final float gx = mScaleDetector.getFocusX();
                final float gy = mScaleDetector.getFocusY();

                final float gdx = gx - mLastGestureX;
                final float gdy = gy - mLastGestureY;

                mPosX += gdx;
                mPosY += gdy;

                invalidate();

                mLastGestureX = gx;
                mLastGestureY = gy;
            }

            break;
        }
        case MotionEvent.ACTION_UP: {
            mActivePointerId = INVALID_POINTER_ID;
            break;
        }
        case MotionEvent.ACTION_CANCEL: {
            mActivePointerId = INVALID_POINTER_ID;
            break;
        }
        case MotionEvent.ACTION_POINTER_UP: {

            final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
                    >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
            final int pointerId = ev.getPointerId(pointerIndex);
            if (pointerId == mActivePointerId) {
                // This was our active pointer going up. Choose a new
                // active pointer and adjust accordingly.
                final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                mLastTouchX = ev.getX(newPointerIndex);
                mLastTouchY = ev.getY(newPointerIndex);
                mActivePointerId = ev.getPointerId(newPointerIndex);
            }
            else{
                final int tempPointerIndex = ev.findPointerIndex(mActivePointerId);
                mLastTouchX = ev.getX(tempPointerIndex);
                mLastTouchY = ev.getY(tempPointerIndex);
            }

            break;
        }
    }

    return true;
}

@Override
public void onDraw(Canvas canvas) {

    canvas.save();

    canvas.translate(mPosX, mPosY);

    if (mScaleDetector.isInProgress()) {
        canvas.scale(mScaleFactor, mScaleFactor, mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
    }
    else{
        canvas.scale(mScaleFactor, mScaleFactor, mLastGestureX, mLastGestureY);
    }

    super.onDraw(canvas);
    canvas.restore();
}

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(0.1f, Math.min(mScaleFactor, 10.0f));

        invalidate();
        return true;
    }
}
1
setXfermode in zoomImageView#onDraw as you did in the first code snippetpskink
or you can use a BitmapShader avoiding porter duff modespskink

1 Answers

0
votes

If you apply the mask at load time by "baking it in" to your main image then it won't be possible to move them separately. Where you are currently applying the mask, load both your images, and set the main image as the image bitmap. Also load the mask image, but store it in a variable in your custom image class:

    Bitmap mainImage = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uriValue);
    img.setImageBitmap(mainImage);
    img.invalidate();

    // store in a variable:
    mMask = BitmapFactory.decodeResource(getResources(), R.mipmap.mask_triangle_perfect);

Get rid of the canvas drawing code here, because it will be moved to onDraw.

Change your onDraw to apply the mask using similar code to how you were previously applying it a load time:

@Override
public void onDraw(Canvas canvas) {

    canvas.save();

    canvas.translate(mPosX, mPosY);

    if (mScaleDetector.isInProgress()) {
        canvas.scale(mScaleFactor, mScaleFactor, mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
    }
    else{
        canvas.scale(mScaleFactor, mScaleFactor, mLastGestureX, mLastGestureY);
    }

    super.onDraw(canvas);
    canvas.restore();

    Paint paint = new Paint();
    paint.setFilterBitmap(false);

    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    canvas.drawBitmap(mMask, 0, 0, paint);
    paint.setXfermode(null);
}

Because the code drawing the mask is placed after canvas.restore(), the zoom and pan transform will be applied to the main image (which is drawn when calling super.onDraw(canvas), but not to the mask image.