0

I am working with the canvas, taking an image as the background and trying to implement the zoom feature into it. I want to Zoom in at the point where fingers are pinched and zoom out will work through the center coordinates as that of Image gallery feature. I went through many tutorials and read many threads about it. Whatever I implemented till now is either supporting zoom in and out at same point or if I am applying it to the image then whatever I draw on canvas is not getting zoomed, only the background image is zooming in and out.

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    onDrawReady = true; 

    // for eraser
    canvas.saveLayerAlpha(0, 0, canvas.getWidth(), canvas.getHeight(), 0xff, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);

    imageRenderedAtLeastOnce = true;

    if (delayedZoomVariables != null) {
        setZoom(delayedZoomVariables.scale, delayedZoomVariables.focusX,
                delayedZoomVariables.focusY, delayedZoomVariables.scaleType);
        delayedZoomVariables = null;
    }

    canvas.concat(matrix);      

    for (DrawObject d : paths) {
        if (d.getType() == MODE_DRAWING) {
            canvas.drawPath(d.getPair().first, d.getPair().second); 
        } else if (d.getType() == MODE_TEXT) {
            canvas.drawText(d.getText(), d.getX(), d.getY(),
                    d.getPair().second);
        } else if (d.getType() == MODE_ARROW) {
            canvas.drawLine(d.getStartX(), d.getStartY(), d.getX(),
                    d.getY(), d.getPair().second);
            fillArrow(canvas, d.getStartX(), d.getStartY(), d.getX(),
                    d.getY(), d.getPair().second);
        } else if (d.getType() == MODE_CIRCLE) {
            RectF oval2 = new RectF(d.getStartX(), d.getStartY(), d.getX(),
                    d.getY());
            canvas.drawOval(oval2, d.getPair().second);
        } else if (d.getType() == MODE_RECTANGLE) {
            canvas.drawRect(d.getStartX(), d.getStartY(), d.getX(),
                    d.getY(), d.getPair().second);
        } else if (d.getType() == MODE_ERASE) {
            canvas.drawPath(d.getPair().first, d.getPair().second);
        }
    }
    canvas.save();
}

This is the Issue

Shubham
  • 165
  • 2
  • 11
  • share what you have tried so far – AnswerDroid Dec 09 '15 at 09:44
  • [TouchImageView Library](https://github.com/MikeOrtiz/TouchImageView) This is one thing that I found and played around it. – Shubham Dec 09 '15 at 09:53
  • you should read more on `Canvas#saveLayerAlpha` and `Canvas#save` – pskink Dec 09 '15 at 11:35
  • @pskink I don't think its the issue of canvas#save. I've added an image of what I am facing. Please have a look if you could help. – Shubham Dec 09 '15 at 12:30
  • @pskink When i am drawing something on the canvas its not on that position where finger is touched. Its shifted bit upwards. – Shubham Dec 09 '15 at 12:33
  • ok see my anwer [here](http://stackoverflow.com/a/21657145/2252830), there i am using `drawBitmap(Bitmap, Matrix, Paint)` but it is the same as `concat(Matrix)` followed by `drawBitmap(Bitmap, float, float, Paint)` – pskink Dec 09 '15 at 12:34
  • @pskink I tried this method by replacing concat(matrix), but it's showing no effect. Now when I am trying to zoom, the layer which I have drawn is not getting zoomed. – Shubham Dec 09 '15 at 12:56
  • did you try my code? does it zoom or not? – pskink Dec 09 '15 at 12:57
  • @pskink i tried to modify my code according to your solution provided [here](http://stackoverflow.com/a/21657145/2252830). But I was not able to understand why have you used `class ViewPort extends View { List layers = new LinkedList(); int[] ids = {R.drawable.layer0, R.drawable.layer1, R.drawable.ic_launcher};`. Please help me buddy I am stuck with this – Shubham Dec 10 '15 at 06:16
  • because i am drawing multiple layers (bitmaps) : in my case three, if you want one layer you can remove this – pskink Dec 10 '15 at 06:53
  • @pskink in my case number of layers will depend upon the number of drawings that is done by user. Actually i m doing image annotations so how to add layers dynamically? – Shubham Dec 10 '15 at 08:53
  • you have `List layers` so it is `List`, then read `java.util.List` documentation – pskink Dec 10 '15 at 08:57
  • @pskink that i know. I m talking about int[] ids. – Shubham Dec 10 '15 at 09:10
  • `ids` array is just to create a new `Layer` object: `new Layer(context, this, BitmapFactory.decodeResource(res, ids[i]));` you can create a new `Layer` as you want, the `Bitmap` parameter doesn't have to be a "resource Bitmap", you can create it in your code as well – pskink Dec 10 '15 at 09:12
  • @pskink is it possible for you to provide working example of your ViewPort class post. – Shubham Dec 10 '15 at 09:34
  • it is working example, just use it in your Activity – pskink Dec 10 '15 at 09:37
  • @pskink I've added a new [thread](http://stackoverflow.com/questions/34217092/touch-screen-is-not-adjusting-according-to-zoomed-image/34217240#34217240). Please have a look if you could help with this. – Shubham Dec 11 '15 at 06:25
  • i gave you a working solution, if you want to use it or not it is up to you... – pskink Dec 11 '15 at 08:04

1 Answers1

1

I dont know how did you implement it but the following code works how you mean:

ZoomActivity:

public class ZoomActivity extends Activity {
    Context context;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.zoom_view);
        ZoomableImageView mIV = (ZoomableImageView)findViewById(R.id.image_view);
        context = this;
        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.background);
        mIV.setImageBitmap(bm);

    }
}

zoom_view.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.se.test.ZoomableImageView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

ZoomableImageView:

public class ZoomableImageView extends ImageView {
    Matrix matrix = new Matrix();

    static final int NONE = 0;
    static final int DRAG = 1;
    static final int ZOOM = 2;
    static final int CLICK = 3;
    int mode = NONE;

    PointF last = new PointF();
    PointF start = new PointF();
    float minScale = 1f;
    float maxScale = 4f;
    float[] m;

    float redundantXSpace, redundantYSpace;
    float width, height;
    float saveScale = 1f;
    float right, bottom, origWidth, origHeight, bmWidth, bmHeight;

    ScaleGestureDetector mScaleDetector;
    Context context;


    public ZoomableImageView(Context context, AttributeSet attr) {
//        super(context, attr);
        super(context, attr);
        super.setClickable(true);
        this.context = context;
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
        matrix.setTranslate(1f, 1f);
        m = new float[9];
        setImageMatrix(matrix);
        setScaleType(ScaleType.MATRIX);

        setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                mScaleDetector.onTouchEvent(event);

                matrix.getValues(m);
                float x = m[Matrix.MTRANS_X];
                float y = m[Matrix.MTRANS_Y];
                PointF curr = new PointF(event.getX(), event.getY());

                switch (event.getAction()) {
                    //when one finger is touching
                    //set the mode to DRAG
                    case MotionEvent.ACTION_DOWN:
                        last.set(event.getX(), event.getY());
                        start.set(last);
                        mode = DRAG;
                        break;
                    //when two fingers are touching
                    //set the mode to ZOOM
                    case MotionEvent.ACTION_POINTER_DOWN:
                        last.set(event.getX(), event.getY());
                        start.set(last);
                        mode = ZOOM;
                        break;
                    //when a finger moves
                    //If mode is applicable move image
                    case MotionEvent.ACTION_MOVE:
                        //if the mode is ZOOM or
                        //if the mode is DRAG and already zoomed
                        if (mode == ZOOM || (mode == DRAG && saveScale > minScale)) {
                            float deltaX = curr.x - last.x;// x difference
                            float deltaY = curr.y - last.y;// y difference
                            float scaleWidth = Math.round(origWidth * saveScale);// width after applying current scale
                            float scaleHeight = Math.round(origHeight * saveScale);// height after applying current scale
                            //if scaleWidth is smaller than the views width
                            //in other words if the image width fits in the view
                            //limit left and right movement
                            if (scaleWidth < width) {
                                deltaX = 0;
                                if (y + deltaY > 0)
                                    deltaY = -y;
                                else if (y + deltaY < -bottom)
                                    deltaY = -(y + bottom);
                            }
                            //if scaleHeight is smaller than the views height
                            //in other words if the image height fits in the view
                            //limit up and down movement
                            else if (scaleHeight < height) {
                                deltaY = 0;
                                if (x + deltaX > 0)
                                    deltaX = -x;
                                else if (x + deltaX < -right)
                                    deltaX = -(x + right);
                            }
                            //if the image doesnt fit in the width or height
                            //limit both up and down and left and right
                            else {
                                if (x + deltaX > 0)
                                    deltaX = -x;
                                else if (x + deltaX < -right)
                                    deltaX = -(x + right);

                                if (y + deltaY > 0)
                                    deltaY = -y;
                                else if (y + deltaY < -bottom)
                                    deltaY = -(y + bottom);
                            }
                            //move the image with the matrix
                            matrix.postTranslate(deltaX, deltaY);
                            //set the last touch location to the current
                            last.set(curr.x, curr.y);
                        }
                        break;
                    //first finger is lifted
                    case MotionEvent.ACTION_UP:
                        mode = NONE;
                        int xDiff = (int) Math.abs(curr.x - start.x);
                        int yDiff = (int) Math.abs(curr.y - start.y);
                        if (xDiff < CLICK && yDiff < CLICK)
                            performClick();
                        break;
                    // second finger is lifted
                    case MotionEvent.ACTION_POINTER_UP:
                        mode = NONE;
                        break;
                }
                setImageMatrix(matrix);
                invalidate();
                return true;
            }

        });
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        bmWidth = bm.getWidth();
        bmHeight = bm.getHeight();
    }

    public void setMaxZoom(float x) {
        maxScale = x;
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            mode = ZOOM;
            return true;
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float mScaleFactor = detector.getScaleFactor();
            float origScale = saveScale;
            saveScale *= mScaleFactor;
            if (saveScale > maxScale) {
                saveScale = maxScale;
                mScaleFactor = maxScale / origScale;
            } else if (saveScale < minScale) {
                saveScale = minScale;
                mScaleFactor = minScale / origScale;
            }
            right = width * saveScale - width - (2 * redundantXSpace * saveScale);
            bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
            if (origWidth * saveScale <= width || origHeight * saveScale <= height) {
                matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2);
                if (mScaleFactor < 1) {
                    matrix.getValues(m);
                    float x = m[Matrix.MTRANS_X];
                    float y = m[Matrix.MTRANS_Y];
                    if (mScaleFactor < 1) {
                        if (Math.round(origWidth * saveScale) < width) {
                            if (y < -bottom)
                                matrix.postTranslate(0, -(y + bottom));
                            else if (y > 0)
                                matrix.postTranslate(0, -y);
                        } else {
                            if (x < -right)
                                matrix.postTranslate(-(x + right), 0);
                            else if (x > 0)
                                matrix.postTranslate(-x, 0);
                        }
                    }
                }
            } else {
                matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
                matrix.getValues(m);
                float x = m[Matrix.MTRANS_X];
                float y = m[Matrix.MTRANS_Y];
                if (mScaleFactor < 1) {
                    if (x < -right)
                        matrix.postTranslate(-(x + right), 0);
                    else if (x > 0)
                        matrix.postTranslate(-x, 0);
                    if (y < -bottom)
                        matrix.postTranslate(0, -(y + bottom));
                    else if (y > 0)
                        matrix.postTranslate(0, -y);
                }
            }
            return true;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getSize(heightMeasureSpec);
        //Fit to screen.
        float scale;
        float scaleX = width / bmWidth;
        float scaleY = height / bmHeight;
        scale = Math.min(scaleX, scaleY);
        matrix.setScale(scale, scale);
        setImageMatrix(matrix);
        saveScale = 1f;

        // Center the image
        redundantYSpace = height - (scale * bmHeight);
        redundantXSpace = width - (scale * bmWidth);
        redundantYSpace /= 2;
        redundantXSpace /= 2;

        matrix.postTranslate(redundantXSpace, redundantYSpace);

        origWidth = width - 2 * redundantXSpace;
        origHeight = height - 2 * redundantYSpace;
        right = width * saveScale - width - (2 * redundantXSpace * saveScale);
        bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
        setImageMatrix(matrix);
    }
}

Hope it helps

Behy
  • 483
  • 7
  • 23
  • Hey buddy, thanx for your quick response. I am trying to implement the same thing for canvas not for imageview. This one I already tried. – Shubham Dec 09 '15 at 10:31
  • @Shubham so if you have a `Matrix` then concat it to the `Canvas` – pskink Dec 09 '15 at 10:48