14

I'm making an app where user will be able to click on part of the image and get a magnified version in the corner of WebView. I managed to make a Paint that would make a zoom version, but it displays wrong location, like there's some offset.

I know this question has been asked a lot of times and was already answered, but it appears non of those solutions helped.

Here's code I've used:

  @Override
        public boolean onTouchEvent(@NonNull MotionEvent event) {
            zoomPos = new PointF();
            zoomPos.x = event.getX();
            zoomPos.y = event.getY();


        matrix = new Matrix();
        mShader = new BitmapShader(MainActivity.mutableBitmap, TileMode.CLAMP, TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setShader(mShader);
        outlinePaint = new Paint(Color.BLACK);
        outlinePaint.setStyle(Paint.Style.STROKE);

        int action = event.getAction(); 

        switch (action) { 
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            zooming = true;
            this.invalidate();
            break; 
        case MotionEvent.ACTION_UP: 
            Point1 = true;
            zooming = false;
            this.invalidate();
            break;
        case MotionEvent.ACTION_CANCEL:
            zooming = false;
            this.invalidate();
            break; 

        default: 
            break; 
        }



     return true;
    }

    @Override
    protected void onDraw(@NonNull Canvas canvas) {
        super.onDraw(canvas);
        if (zooming) {
            matrix.reset();
            matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
            mPaint.getShader().setLocalMatrix(matrix);


            canvas.drawCircle(100, 100, 100, mPaint);

        }

    }

Technically it should draw a circle at upper-left corner and display zoomed image of area where my finger is, it draws a circle, but again, zoom is shifted.

Final result should look something like this:

enter image description here

MainActivity.java

public class MainActivity extends Activity {
static ImageView takenPhoto;
static PointF zoomPos;
Paint shaderPaint;
static BitmapShader mShader;
BitmapShader shader;
Bitmap bmp;
static Bitmap mutableBitmap;
static Matrix matrix;
Canvas canvas;
static Paint mPaint;
static Paint Paint;
static boolean zooming;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        File file = new File(Environment.getExternalStorageDirectory() + "/Pictures/boxes.jpg");

        String fileString = file.getPath();
        takenPhoto = (ZoomView) findViewById(R.id.imageView1);
        bmp = BitmapFactory.decodeFile(fileString);
        mutableBitmap = bmp.copy(Bitmap.Config.ARGB_8888, true);
        takenPhoto.setImageBitmap(mutableBitmap);    
        matrix = new Matrix();
        mShader = new BitmapShader(mutableBitmap, TileMode.CLAMP, TileMode.CLAMP); 
        mPaint = new Paint();
        mPaint.setShader(mShader);
        zoomPos = new PointF();
        Paint = new Paint(Color.RED);  
    }
}

ZoomView.java

public class ZoomView extends ImageView {

    private PointF zoomPos;
    PointF fingerPos;
    private Paint paint = new Paint(Color.BLACK);
    boolean zooming;
    Matrix matrix;
    BitmapShader mShader;
    Paint mPaint;
    Paint outlinePaint;
    boolean Point1;

    public ZoomView(Context context) {
        super(context);
    }

    public ZoomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ZoomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        zoomPos = new PointF();
        zoomPos.x = event.getX();
        zoomPos.y = event.getY();


        matrix = new Matrix();
        mShader = new BitmapShader(MainActivity.mutableBitmap, TileMode.CLAMP, TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setShader(mShader);
        outlinePaint = new Paint(Color.BLACK);
        outlinePaint.setStyle(Paint.Style.STROKE);

        int action = event.getAction(); 

        switch (action) { 
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            zooming = true;
            this.invalidate();
            break; 
        case MotionEvent.ACTION_UP: 
            Point1 = true;
            zooming = false;
            this.invalidate();
            break;
        case MotionEvent.ACTION_CANCEL:
            zooming = false;
            this.invalidate();
            break; 

        default: 
            break; 
        }



     return true;
    }

    @Override
    protected void onDraw(@NonNull Canvas canvas) {
        super.onDraw(canvas);
        if (zooming) {
            matrix.reset();
        matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
        mPaint.getShader().setLocalMatrix(matrix);
        RectF src = new RectF(zoomPos.x-50, zoomPos.y-50, zoomPos.x+50, zoomPos.y+50);
        RectF dst = new RectF(0, 0, 100, 100);
        matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
        matrix.postScale(2f, 2f);
        mPaint.getShader().setLocalMatrix(matrix);


        canvas.drawCircle(100, 100, 100, mPaint);
        canvas.drawCircle(zoomPos.x, zoomPos.y, 100, mPaint);
        canvas.drawCircle(zoomPos.x-110, zoomPos.y-110, 10, outlinePaint);

        }
        if(Point1){
            canvas.drawCircle(zoomPos.x, zoomPos.y, 10, paint);
        }
    }
}

EDIT:

As you can see new code is way better, still there is some offset - black dot - position of the finger.

enter image description here

Oleksandr Firsov
  • 1,428
  • 1
  • 21
  • 48
  • I have a hard time understanding _exactly_ what you're asking, but, here are some pointers for you: you may be offset incorrectly due to your touching location being the left-top corner, consider using getSize on MotionEvent to detect fat touches and place yourself in the middle of it. Secondly, you may want to look into the getRawX/getRawY if you require the raw (screen) location of the touches. – JohanShogun Aug 23 '15 at 15:24
  • This is very similar question to mine, maybe it will clear something out for [you](http://stackoverflow.com/questions/13864480/android-how-to-circular-zoom-magnify-part-of-image). In fact, I used it to make my code. The problem is it displays wrong location. – Oleksandr Firsov Aug 23 '15 at 16:21
  • @OleksandrFirsov would you feel comfortable sharing the full project / the github of it? I think I see a couple issues, but the full source would let me debug and test it. If not, I can still try :) – Parker Aug 24 '15 at 19:16
  • @OleksandrFirsov, also, is your issue the circle placement? Or what appears in the zoombox in the corner? Is the circle working properly when you touch? – Parker Aug 24 '15 at 19:18
  • Circle works fine. I only have problem with its paint. – Oleksandr Firsov Aug 26 '15 at 06:52

1 Answers1

7

Seems that the issue is with how you are using the matrix.

Now you are using the original image (1) as a shader which is then being post scaled up around a pivot point (2), which is like doing a zoom around a point (3) - but not centering the point (4) ! (For example, open google maps and zoom in on the map with your mouse - the point is zoomed around the pivot but the pivot is not centered)

enter image description here

What will be an easier way to achieve what you want is by using the Rect to Rect method. I.E. you want to take a small area from the original image (5) and draw it to a larger area (6) .

enter image description here

And here is a code sample:

RectF src = new RectF(zoomPos.x-50, zoomPos.y-50, zoomPos.x+50, zoomPos.y+50);
RectF dst = new RectF(0, 0, 200, 200);
matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);

Some more points:

  • Try not to create new object in the onTouch - it is being called many times and it is not good on performance . Instead, create once and reuse.
  • getAction() will have issues when there are more than one finger on screen since it is the action ID and the pointer ID. Instead use getActionMasked() and getActionIndex().
  • Do not use hardcoded values (50/100 in the sample code) - these are pixels and are not aware of screen density. Use scaled size like dp.
Eyal Biran
  • 5,656
  • 1
  • 28
  • 29
  • I've updated my ZoomView in question. Now circle in the corner displays area that I touch, but it is not zoomed. – Oleksandr Firsov Aug 26 '15 at 11:35
  • That would most probably be because the `src` and `dst` `RectF`s have the same dimensions. Make the `src` one smaller, or the `dst` one bigger for a zoom effect. – JHobern Aug 26 '15 at 11:42
  • Indeed, I edited the code - the target rect should be bigger. In the sample X2 bigger. (was X1 that is why you had no zoom) – Eyal Biran Aug 26 '15 at 12:10
  • I achieved the same with adding `postScale(2f, 2f)`. Both of our codes produce same result. Look at edit of my question for explanation. – Oleksandr Firsov Aug 26 '15 at 12:45
  • If you use the code I suggested and it still does not work (has offset) this means that the point (x,y) you are getting is not correct. From the new screenshot it seems that the Zoom part is drawn outside of the actual src image. This means that the bitmap you use does not fill the screen. So, the X, Y you are getting is of the screen and not relative to the image size. Fix that and you are golden. – Eyal Biran Aug 26 '15 at 12:51
  • Okay. How can I do that? – Oleksandr Firsov Aug 26 '15 at 15:13
  • Well, you have many options. It really depends on what you are trying to do. So, in the screenshot you added it looks like you are having the imageview as match parent and the image centered. So it will be hard to find the image X,Y that way. In that case better to have the imageview wrap content. If you will do that, the imageview x,y will be of the inner bitmap. Then you simply need to do the math. Again, it all depends on what you are trying to do. – Eyal Biran Aug 26 '15 at 15:26
  • What if I'm using custom `WebView`? Because in my original app, I draw `Canvas` there. I suppose getting coordinates from there should be easier. I don't know how, though, since it has Zoom option and all. – Oleksandr Firsov Aug 26 '15 at 15:52
  • And my image view has `wrap content`, yet offset still there – Oleksandr Firsov Aug 26 '15 at 16:12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/88024/discussion-between-oleksandr-firsov-and-eyal-biran). – Oleksandr Firsov Aug 26 '15 at 16:16