A picture control that supports rotation, scaling, and panning of large pictures

Preface: At present, it is difficult to find a picture control on the Internet that can support the rotation and scaling of large pictures. Found only supports panning. There is no way but to write one yourself.

Implementation principle:

1. Incoming image path

2. Calculate the actual width and height of the picture, compare the width and height of the control, calculate the initial CenterMatrix, and let the picture center and fill the view adaptively

3. Set pan, zoom, and rotate gesture monitoring, and use gestureMatrix to record the transformation

4. When onDraw, calculate the mBitmapRect of the image display area based on CenterMatrix + gestureMatrix

5.bitmap = mDecoder.decodeRegion(mBitmapRect, mDecodeOptions); Only read the image area that needs to be displayed

6. Determine whether the size of the bitmap exceeds the specified size, OOM will occur if it exceeds the size, so when the size is exceeded, you need to do image compression

7. After compression, the processing of Matrix needs to be increased to become ScaleMatrix + CenterMatrix + gestureMatrix

8. The final result is that when the full image is reduced, the Bitmap needs to be compressed to save memory, and when the image is enlarged, only part of the image is loaded to save memory

package com.ange.bigimageview;

import static com.ange.bigimageview.MathUtils.rectFTake;

import android. content. Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import androidx.annotation.Nullable;
import com.almeros.android.multitouch.RotateGestureDetector;

import java.io.IOException;
import java.io.InputStream;

public class BigImageView extends View {
    private final static String TAG="BigImageView";
    private float mBigImgWidth;
    private float mBigImgHeight;
    private BitmapRegionDecoder mDecoder;
     private static final BitmapFactory.Options mDecodeOptions = new BitmapFactory.Options();
    private final Rect mBitmapRect = new Rect();//The position of the picture displayed in the View
    private Matrix centerMatrix ;//The picture is centered at the beginning
    private final Matrix currentMatrix=new Matrix();//Summary of all matrix transformations currently accumulated
    private final Matrix gestureMatrix=new Matrix();//Matrix transformation generated by gesture
    private final Matrix imgScale=new Matrix();//image occupation
    /**
     * Maximum image size
     */
    private static final int BITMAP_MAX_SIZE =3000;
    static {
        mDecodeOptions.inPreferredConfig = Bitmap.Config.RGB_565;
    }
    private GestureDetector mMoveDetector;
    private RotateGestureDetector mRotateGestureDetector;
    private ScaleGestureDetector mScaleGestureDetector;
    public BigImageView(Context context) {
        super(context);
        init(context);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

   public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context){
        mMoveDetector = new GestureDetector(context, new GestureDetector. SimpleOnGestureListener(){

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                gestureMatrix.postTranslate(-distanceX,-distanceY);
                invalidate();
                return true;
            }
        });
        mRotateGestureDetector = new RotateGestureDetector(context, new RotateGestureDetector.SimpleOnRotateGestureListener(){
            @Override
            public boolean onRotate(RotateGestureDetector detector) {
                gestureMatrix.postRotate(-detector.getRotationDegreesDelta(),getWidth()/2,getHeight()/2);
                Log.i(TAG,"onRotate");
                invalidate();
                return true;
            }
        });
        mScaleGestureDetector=new ScaleGestureDetector(context,new ScaleGestureDetector.SimpleOnScaleGestureListener(){
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                float scaleFactor = detector. getScaleFactor();
                gestureMatrix.postScale(scaleFactor,scaleFactor,detector.getFocusX(),detector.getFocusY());
                return true;
            }
        });
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(centerMatrix==null){
            centerMatrix = getCenterMatrix();
        }
        if(mDecoder!=null){
            Matrix matrix = getCurrentImageMatrix();
            RectF imgShowRect = rectFTake();
            imgShowRect.top=0;
            imgShowRect.bottom=getHeight();
            imgShowRect.left=0;
            imgShowRect.right=getWidth();
            Log.i(TAG,String.format("view,width=%1$s,height=%2$s",getWidth(),getHeight()));
            Log.i(TAG,imgShowRect.toString());
            MathUtils.calculateImageRegion(imgShowRect, getWidth(), getHeight(), mBigImgWidth, mBigImgHeight, matrix, mBitmapRect);
            Log.i(TAG,mBitmapRect.toString());
            if(isOutRect(mBitmapRect,mBigImgWidth,mBigImgHeight)){
                Log.i(TAG,"Out of display range");
                return;
            }
// When the full image is displayed, it is judged that the size is too large and compressed
            int rectW=mBitmapRect.right-mBitmapRect.left;
            int rectH=mBitmapRect.bottom-mBitmapRect.top;
            int inSampleSize = 1;
            while (rectW*rectH/inSampleSize>BITMAP_MAX_SIZE*BITMAP_MAX_SIZE){
                inSampleSize*=2;
            }
            Log.i(TAG,"inSampleSize=" + inSampleSize);
            mDecodeOptions.inSampleSize=inSampleSize;
            Bitmap bitmap = mDecoder.decodeRegion(mBitmapRect, mDecodeOptions);
            Log.i(TAG,"bitmap.size=" + bitmap.getByteCount()/1024/1024);
if(inSampleSize>1){
                imgScale. reset();
                imgScale. postScale(inSampleSize, inSampleSize);
                imgScale. postConcat(matrix);
                matrix=imgScale;
                MathUtils.calculateImageRegion(imgShowRect, getWidth(), getHeight(), mBigImgWidth, mBigImgHeight, matrix, mBitmapRect);
                Log.i(TAG,mBitmapRect.toString());
                if(isOutRect(mBitmapRect,mBigImgWidth,mBigImgHeight)){
                    Log.i(TAG,"Out of display range");
                    return;
                }
            }
            canvas.concat(matrix);
            canvas. save();
            canvas.drawBitmap(bitmap, mBitmapRect.left, mBitmapRect.top, null);
            MathUtils.rectFGiven(imgShowRect);
        }

    }

 @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        mRotateGestureDetector.onTouchEvent(event);
        mMoveDetector.onTouchEvent(event);
        mScaleGestureDetector.onTouchEvent(event);
        return true;
    }




    private Matrix getCurrentImageMatrix() {
        currentMatrix. reset();
        currentMatrix. postConcat(centerMatrix);
        currentMatrix. postConcat(gestureMatrix);
        return currentMatrix;
    }

public void setBigImage(InputStream inputStream) throws IOException {
        BitmapFactory. Options options = new BitmapFactory. Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(inputStream, null, options);
        mBigImgWidth = options. outWidth;
        mBigImgHeight = options. outHeight;
        mBitmapRect. left = (int) 0;
        mBitmapRect.top=(int) 0;
        mBitmapRect.right= (int) mBigImgWidth;
        mBitmapRect.bottom=(int) mBigImgHeight;
        Log.d(TAG, String.format("mBigImgWidth=%1$s,mBigImgHeight=%2$s", mBigImgWidth, mBigImgHeight));
        mDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
        requestLayout();
        invalidate();
    }
/**
     * Judging whether it is out of bounds
     * @return
     */
    private boolean isOutRect(Rect imgRect, float imgWidth, float imgHeight){
        if(imgRect.top<0||imgRect.bottom<0
                ||imgRect.top>imgHeight
                ||imgRect.bottom>imgHeight
                ||imgRect.left<0
                ||imgRect.right<0
                ||imgRect.right>imgWidth
                ||imgRect.left>imgWidth){
            return true;
        }
        return false;
    }


    /**
     * Center the picture
     * @return
     */
    private Matrix getCenterMatrix(){
        Matrix matrix = new Matrix();
        float dx;
        float dy;
        float scale;
        scale = Math.min(getWidth() / mBigImgWidth, getHeight() / mBigImgHeight);
        matrix.setScale(scale, scale);
        dx = (getWidth() - mBigImgWidth*scale ) * 0.5f;
        dy = (getHeight() - mBigImgHeight*scale) * 0.5f;
        matrix. postTranslate(dx, dy);
        return matrix;
    }
}

Complete project code: https://gitee.com/niangegelaile/bigImage