如何放大/缩放图像的某一部分

14

我正在制作一个应用程序,用户将能够单击图像的某个部分,在 WebView 的角落中获取放大版本。我设法制作了一个 Paint,用于制作缩放版本,但它显示的位置不正确,好像存在一些偏移。

我知道这个问题已经被问了很多次,并且已经有答案,但似乎没有那些解决方案对我有帮助。

这是我使用的代码:

  @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);

        }

    }

从技术上讲,它应该在左上角绘制一个圆圈,并显示我手指所在区域的放大图像,它确实绘制了一个圆圈,但放大功能偏移了。

最终结果应该看起来像这样:

输入图片说明

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);
        }
    }
}

编辑:

如您所见,新代码更好了,但手指的位置仍有一些偏差-黑点。

输入图像描述


我很难确切理解你的问题,但是这里有一些指针供你参考:由于您的触摸位置是左上角,您可能会因偏移不正确而感到困惑,请考虑在MotionEvent上使用getSize来检测粗触摸并将自己放置在其中心。其次,如果您需要触摸的原始(屏幕)位置,您可能需要查看getRawX/getRawY。 - JohanShogun
这个问题和我的非常相似,也许它能为你解决一些问题。事实上,我用它来编写我的代码。问题是它显示的位置不正确。 - Oleksandr Firsov
@OleksandrFirsov,你能否分享整个项目或其Github链接让我查看吗?我发现了一些问题,但是有完整的源代码可以帮助我进行调试和测试。如果不行,我也可以尝试 :) - Parker
@OleksandrFirsov,另外,你的问题是圆形放置吗?还是出现在角落的缩放框中?当你触摸时,圆形是否正常工作? - Parker
圆形运作正常。我只有在绘制时遇到问题。 - Oleksandr Firsov
1个回答

7
似乎问题在于您使用矩阵的方式。(1)您现在正在使用原始图像作为着色器,然后在围绕中心点(2)进行后缩放,这就像在某个点(3)周围进行缩放一样,但没有居中于该点(4)! (例如,打开谷歌地图并使用鼠标放大地图-点会围绕枢轴放大,但枢轴不会居中)

enter image description here

使用RectRect 方法将是实现您想要的更简单的方法。例如,您想从原始图像(5)中取出一个小区域并将其绘制到较大的区域(6)中。

enter image description here

这里是代码示例:

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);

还有一些要点:

  • 尽量不要在onTouch中创建new对象——它被调用很多次,对性能不利。相反,只需创建一次并重复使用。
  • getAction()在屏幕上有多个手指时会出现问题,因为它是动作ID和指针ID。取而代之的是使用getActionMasked()getActionIndex()
  • 不要使用硬编码值(样例代码中的50/100)——这些是像素,不知道屏幕密度。使用缩放大小,如dp

我已经更新了我的ZoomView。现在,角落里的圆圈显示我触摸的区域,但没有进行缩放。 - Oleksandr Firsov
这很可能是因为srcdstRectF具有相同的尺寸。将src缩小或将dst放大以获得缩放效果。 - JHobern
我通过添加 postScale(2f, 2f) 实现了相同的效果。我们两个人的代码都产生了相同的结果。请查看我的问题编辑以获取解释。 - Oleksandr Firsov
如果您使用我建议的代码仍然无法正常工作(具有偏移量),这意味着您获取的点(x,y)不正确。从新的屏幕截图中可以看出,缩放部分是在实际源图像之外绘制的。这意味着您使用的位图未填充屏幕。因此,您获取的X、Y是屏幕上的,而不是相对于图像大小的。解决这个问题,你就成功了。 - Eyal Biran
好的。我该怎么做? - Oleksandr Firsov
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接