Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to magnify/zoom part of image

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

like image 595
Oleksandr Firsov Avatar asked Aug 17 '15 16:08

Oleksandr Firsov


1 Answers

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.
like image 59
Eyal Biran Avatar answered Oct 04 '22 15:10

Eyal Biran