当前位置:网站首页>Custom picture crop box
Custom picture crop box
2022-07-22 09:02:00 【cjzcjl】
What I finally achieved :
Record
A few rules :
0、 When the width to height value is large, the clipping box is square , When the height is larger than the width, it is a rectangle perpendicular to the navigation bar
1、 The selected part uses the original brightness ( Or transparency ), The unselected part is translucent
2、 Drag is required 、 Zoom function
In addition to the third need a little space imagination , Other functions can be realized through canvas + path + paint Combination boxing , It's not hard . Instead of spending time from GitHub Find the same item on and modify a lot of codes , I think it's not as flexible as using basic modules directly , And what I wrote , New features can be added or modified quickly no matter how the requirements change , Then start rolling directly .
Two important steps :
step 0: Inherit View, rewrite onMeasure Realization :
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int w = getMySize(widthMeasureSpec);
int h = getMySize(heightMeasureSpec);
if (w != mWidth || h != mHeight) { // already onMeasuer once , Do not reinitialize unless the interface size changes view
mWidth = getMySize(widthMeasureSpec);
mHeight = getMySize(heightMeasureSpec);
switch (mSetPicWay) {
case 0: {// Select the size of the crop box according to the picture
float wRatio = 0.9f;
int cutW = (int) (mWidth * wRatio);
int cutH = (int) (mWidth * wRatio * 4 / 3); // Default H:W = 4:3
if (mBmp != null && !mBmp.isRecycled()) {
// float bmpRatio = mBmp.getWidth() >= mBmp.getHeight() ? (float) mBmp.getWidth() / mBmp.getHeight() : (float) mBmp.getHeight() / mBmp.getWidth();
float bmpRatio = (float) mBmp.getWidth() / mBmp.getHeight();
if (bmpRatio > 1.3f) { // If the picture is long , Use the square selection box
if (cutW >= cutH) {
cutW = cutH;
} else {
cutH = cutW;
}
} // Otherwise, use the long bar selection box
}
mCutRect = new Rect((mWidth - cutW) / 2, (mHeight - cutH) / 2, (mWidth - cutW) / 2 + cutW, (mHeight - cutH) / 2 + cutH);
if (mCanChangeRect) {
mCutRectInitedClone = new Rect(mCutRect);
}
break;
}
case 2:
mCutRect = new Rect((mWidth - mCutW) / 2, (mHeight - mCutH) / 2, (mWidth - mCutW) / 2 + mCutW, (mHeight - mCutH) / 2 + mCutH);
if (mCanChangeRect) {
mCutRectInitedClone = new Rect(mCutRect);
}
break;
case 3: {
float wRatio = 0.9f;
int cutW = (int) (mWidth * wRatio);
int cutH;
if (mUse4div3rect) {
cutH = (int) (mWidth * wRatio * 4 / 3); // Default H:W = 4:3
} else {
cutH = cutW;
}
mCutRect = new Rect((mWidth - cutW) / 2, (mHeight - cutH) / 2, (mWidth - cutW) / 2 + cutW, (mHeight - cutH) / 2 + cutH);
if (mCanChangeRect) {
mCutRectInitedClone = new Rect(mCutRect);
}
break;
}
}
resetView();
}
}
This step is to onMeasure Get the actual width and height value of the control , And use this value to initialize some controls .
step1、 rewrite onDraw function :
@Override
protected void onDraw(Canvas canvas) {
if (mCutRect != null) {
if (mBmp != null && !mBmp.isRecycled()) {
Paint p = new Paint();
p.setAlpha(128);
canvas.drawBitmap(mBmp, mMatrix, p);
p.setAlpha(255);
canvas.save();
canvas.clipRect(mCutRect); // Only select the box for opaque rendering
canvas.drawBitmap(mBmp, mMatrix, p);
canvas.restore(); // Restore the drawing area
}
Paint p = new Paint();
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(3);
p.setColor(Color.WHITE);
canvas.drawRect(mCutRect, p);
p.setStrokeWidth(9);
p.setStrokeCap(Paint.Cap.SQUARE);
float cornerLength = mCutRect.width() > mCutRect.height() ? mCutRect.width() * 0.05f : mCutRect.height() * 0.05f;
// top left corner
canvas.drawLine((float) mCutRect.left, (float) mCutRect.top, mCutRect.left + cornerLength, mCutRect.top, p);
canvas.drawLine((float) mCutRect.left, (float) mCutRect.top, mCutRect.left, mCutRect.top + cornerLength, p);
// The lower left corner
canvas.drawLine((float) mCutRect.left, (float) mCutRect.bottom, mCutRect.left + cornerLength, mCutRect.bottom, p);
canvas.drawLine((float) mCutRect.left, (float) mCutRect.bottom, mCutRect.left, mCutRect.bottom - cornerLength, p);
// Upper right corner
canvas.drawLine((float) mCutRect.right, (float) mCutRect.top, mCutRect.right - cornerLength, mCutRect.top, p);
canvas.drawLine((float) mCutRect.right, (float) mCutRect.top, mCutRect.right, mCutRect.top + cornerLength, p);
// The lower right corner
canvas.drawLine((float) mCutRect.right, (float) mCutRect.bottom, mCutRect.right - cornerLength, mCutRect.bottom, p);
canvas.drawLine((float) mCutRect.right, (float) mCutRect.bottom, mCutRect.right, mCutRect.bottom - cornerLength, p);
// If you are stretching and moving the crop box , Then the Jiugong grid is displayed :
if (!mCanMoveAndScale) {
Paint nineRectPaint = new Paint(p);
nineRectPaint.setAlpha(100);
nineRectPaint.setStrokeWidth(3);
canvas.drawLine((float) mCutRect.left, (float) mCutRect.top + mCutRect.height() * 1 / 3, (float) mCutRect.left + mCutRect.width(), (float) mCutRect.top + mCutRect.height() * 1 / 3, nineRectPaint);
canvas.drawLine((float) mCutRect.left, (float) mCutRect.top + mCutRect.height() * 2 / 3, (float) mCutRect.left + mCutRect.width(), (float) mCutRect.top + mCutRect.height() * 2 / 3, nineRectPaint);
canvas.drawLine((float) mCutRect.left + mCutRect.width() * 1 / 3, (float) mCutRect.top, (float) mCutRect.left + mCutRect.width() * 1 / 3, (float) mCutRect.top + mCutRect.height(), nineRectPaint);
canvas.drawLine((float) mCutRect.left + mCutRect.width() * 2 / 3, (float) mCutRect.top, (float) mCutRect.left + mCutRect.width() * 2 / 3, (float) mCutRect.top + mCutRect.height(), nineRectPaint);
}
// Draw prompt :
String tipsStr = String.format(" Intercepted resolution :%d * %d", (int) (mCutRect.width() / mTotalScale), (int) (mCutRect.height() / mTotalScale));
String tipsStr2 = " Suggested size ≥600*800";
if (mCutRect.width() == mCutRect.height()) {
tipsStr2 = " Suggested size ≥600*600";
}
Paint tipsPaint = new Paint();
tipsPaint.setTextSize(convertDpToPixel(12, getContext()));
tipsPaint.setColor(Color.WHITE);
tipsPaint.setStyle(Paint.Style.FILL_AND_STROKE);
tipsPaint.setStrokeWidth(1);
float textLen = tipsPaint.measureText(tipsStr);
canvas.drawText(tipsStr, (mWidth - textLen) / 2, mCutRect.bottom + tipsPaint.measureText("1") * 2, tipsPaint);
tipsPaint.setColor(0xFFCCCCCC);
textLen = tipsPaint.measureText(tipsStr2);
canvas.drawText(tipsStr2, (mWidth - textLen) / 2, mCutRect.bottom + tipsPaint.measureText("1") * 6, tipsPaint);
if (mDebug) {
p.setColor(Color.RED);
float matrix[] = new float[9];
mMatrix.getValues(matrix);
canvas.drawRect(new Rect((int) matrix[2], (int) matrix[5], (int) (matrix[2] + mBmp.getWidth() * mTotalScale), (int) (matrix[5] + mBmp.getHeight() * mTotalScale)), p);
}
}
}
among paint First draw a darker picture , This is it. paint In limine alpha by 128 Why , Then draw the selection box , Inside the frame 255 Draw the transparent chart again , You can highlight the image part inside the selection box .
Mobile logic :
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mBmp == null || mBmp.isRecycled()) {
return true;
}
int offset = (int) convertDpToPixel(50, getContext()); // Frame 4 horn 4 To what extent can a point accept touch events to achieve drag and zoom
int smallestEdgeSize = (int) convertDpToPixel(100, getContext()); // How small can the clipping box be at least
Rect leftTop = new Rect(mCutRect.left - offset, mCutRect.top - offset, mCutRect.left + offset, mCutRect.top + offset);
Rect rightTop = new Rect(mCutRect.right - offset, mCutRect.top - offset, mCutRect.right + offset, mCutRect.top + offset);
Rect leftBottom = new Rect(mCutRect.left - offset, mCutRect.bottom - offset, mCutRect.left + offset, mCutRect.bottom + offset);
Rect rightBottom = new Rect(mCutRect.right - offset, mCutRect.bottom - offset, mCutRect.right + offset, mCutRect.bottom + offset);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mCutRectLeftTopIsDragging = false;
mCutRectLeftBottomIsDragging = false;
mCutRectRightTopIsDragging = false;
mCutRectRightBottomIsDragging = false;
mPrevDistance = 0;
mPrevPointCount = event.getPointerCount();
// Calculate the coordinates of the moving center 、 Distance between points
for (int i = 0; i < event.getPointerCount(); i++) {
mAvergeX += event.getX(i);
mAvergeY += event.getY(i);
if (i + 1 < event.getPointerCount()) {
mPrevDistance += Math.sqrt(Math.pow(event.getX(i + 1) - event.getX(i), 2) + Math.pow(event.getY(i + 1) - event.getY(i), 2));
}
}
mAvergeX /= event.getPointerCount();
mAvergeY /= event.getPointerCount();
if (mPrevCurrentCenter == null) {
mPrevCurrentCenter = new PointF(mAvergeX, mAvergeY);
} else {
mPrevCurrentCenter.set(mAvergeX, mAvergeY);
}
if (mCanChangeRect && event.getPointerCount() == 1) { // If you are allowed to drag the crop box and the number of fingers is only 1
// Judge whether it is a choice rect Of 4 Around an angle
if (leftTop.contains((int) event.getX(), (int) event.getY())) {
mCanMoveAndScale = false;
mCutRectLeftTopIsDragging = true;
} else if (rightTop.contains((int) event.getX(), (int) event.getY())) {
mCanMoveAndScale = false;
mCutRectRightTopIsDragging = true;
} else if (leftBottom.contains((int) event.getX(), (int) event.getY())) {
mCanMoveAndScale = false;
mCutRectLeftBottomIsDragging = true;
} else if (rightBottom.contains((int) event.getX(), (int) event.getY())) {
mCanMoveAndScale = false;
mCutRectRightBottomIsDragging = true;
}
}
break;
case MotionEvent.ACTION_MOVE:
mAvergeX = 0;
mAvergeY = 0;
float nowDistance = 0;
// Calculate the coordinates of the moving center 、 Distance between points
for (int i = 0; i < event.getPointerCount(); i++) {
mAvergeX += event.getX(i);
mAvergeY += event.getY(i);
if (i + 1 < event.getPointerCount()) {
nowDistance += Math.sqrt(Math.pow(event.getX(i + 1) - event.getX(i), 2) + Math.pow(event.getY(i + 1) - event.getY(i), 2));
}
}
// Now the distance between points Divide Distance between last points This time you get the zoom
mAvergeX /= event.getPointerCount();
mAvergeY /= event.getPointerCount();
if ((mPrevPointCount != event.getPointerCount()) || event.getPointerCount() <= 1 || mPrevPointCount <= 1) { // Touch points suddenly change perhaps The touch point does not exceed 2, No scaling allowed
mPrevDistance = nowDistance = 0;
}
// Check the distance between fingers last time mPrevDistance And this time nowDistance The length difference between , With this / Last time, the so-called zoom ratio . If the scaling data is valid , Then average smoothing and scaling
if (mPrevDistance > 0 && nowDistance > 0) {
mTouchDistanceQueue.add(nowDistance / mPrevDistance);
if (mTouchDistanceQueue.size() >= 6) {
Float point[] = new Float[mTouchDistanceQueue.size()];
mTouchDistanceQueue.toArray(point);
float avergDistance = 0;
for (int i = 0; i < point.length; i++) {
avergDistance += point[i];
}
avergDistance /= point.length;
if (mCanMoveAndScale) {
scale((float) Math.sqrt(avergDistance), mAvergeX, mAvergeY);
}
while (mTouchDistanceQueue.size() > 6) {
mTouchDistanceQueue.poll();
}
}
}
mPrevPointCount = event.getPointerCount();
mPrevDistance = nowDistance;
// Current coordinates - Last coordinate = Offset value , Then perform position offset
if (mPrevCurrentCenter == null) {
mPrevCurrentCenter = new PointF(mAvergeX, mAvergeY);
} else if (event.getPointerCount() > 0) { // At least make sure that there is really a point to press and move
if (mCanChangeRect && event.getPointerCount() == 1 && !mCanMoveAndScale) { // If you are allowed to drag the crop box and the number of fingers is only 1
Rect mCutRectBeforeChange = new Rect(mCutRect);
// Judge whether it is a choice rect Of 4 Around an angle , Yes, just follow your fingers to move and modify
float wTohRatioBeforeChange = (float) mCutRectInitedClone.width() / mCutRectInitedClone.height();
float hTowRatioBeforeChange = 1 / wTohRatioBeforeChange;
if (leftTop.contains((int) event.getX(), (int) event.getY()) || mCutRectLeftTopIsDragging) {
mCutRect.left += event.getX(0) - mPrevCurrentCenter.x;
mCutRect.top += event.getY(0) - mPrevCurrentCenter.y;
mCutRect.bottom = (int) (mCutRect.top + mCutRect.width() * hTowRatioBeforeChange); // By resetting the height that conforms to the proportion of the clipping box at initialization , To maintain proportion
} else if (rightTop.contains((int) event.getX(), (int) event.getY()) || mCutRectRightTopIsDragging) {
mCutRect.right += event.getX(0) - mPrevCurrentCenter.x;
mCutRect.top += event.getY(0) - mPrevCurrentCenter.y;
mCutRect.bottom = (int) (mCutRect.top + mCutRect.width() * hTowRatioBeforeChange); // By resetting the height that conforms to the proportion of the clipping box at initialization , To maintain proportion
} else if (leftBottom.contains((int) event.getX(), (int) event.getY()) || mCutRectLeftBottomIsDragging) {
mCutRect.left += event.getX(0) - mPrevCurrentCenter.x;
mCutRect.bottom += event.getY(0) - mPrevCurrentCenter.y;
mCutRect.top = (int) (mCutRect.bottom - mCutRect.width() * hTowRatioBeforeChange); // By resetting the height that conforms to the proportion of the clipping box at initialization , To maintain proportion
} else if (rightBottom.contains((int) event.getX(), (int) event.getY()) || mCutRectRightBottomIsDragging) {
mCutRect.right += event.getX(0) - mPrevCurrentCenter.x;
mCutRect.bottom += event.getY(0) - mPrevCurrentCenter.y;
mCutRect.top = (int) (mCutRect.bottom - mCutRect.width() * hTowRatioBeforeChange); // By resetting the height that conforms to the proportion of the clipping box at initialization , To maintain proportion
}
// If the width or height has reached the minimum area or the maximum area , Invalidate the modification
if (mCutRect.width() > mCutRectInitedClone.width() || mCutRect.width() < smallestEdgeSize
|| mCutRect.height() > mCutRectInitedClone.height() || mCutRect.height() < smallestEdgeSize) {
mCutRect = new Rect(mCutRectBeforeChange);
}
// It is not allowed to push the box out of the picture :
if (mMatrix != null && mBmp != null && !mBmp.isRecycled()) {
int bmpW = mBmp.getWidth();
int bmpH = mBmp.getHeight();
float matrix[] = new float[9];
mMatrix.getValues(matrix);
float currentX = matrix[2];
float currentY = matrix[5];
float scale = matrix[0];
if (mCutRect.left < currentX || mCutRect.top < currentY || mCutRect.right > currentX + bmpW * scale
|| mCutRect.bottom > currentY + bmpH * scale) {
mCutRect = mCutRectBeforeChange;
}
}
invalidate();
}
// If no control is selected 4 Drag the picture directly at three angles
if (mCanMoveAndScale) {
translate(event.getX(0) - mPrevCurrentCenter.x, event.getY(0) - mPrevCurrentCenter.y);
}
mPrevCurrentCenter.set(event.getX(0), event.getY(0));
}
break;
case MotionEvent.ACTION_UP:
// lift , Clean up the data
mAvergeX = 0;
mAvergeY = 0;
mTouchDistanceQueue.clear();
if (mCanChangeRect) { // Press zoom after raising your hand rect Zoom size
int scalePx = mCutRect.centerX();
int scalePy = mCutRect.centerY();
float scale = (float) mCutRectInitedClone.width() / mCutRect.width();
scale(scale, scalePx, scalePy);
translate((scalePx - mCutRect.centerX()) * scale, (scalePy - mCutRect.centerY()) * scale);
mCutRect = new Rect(mCutRectInitedClone);
invalidate();
}
mCanMoveAndScale = true;
break;
}
return true;
}
For details, please refer to Android matrix The definition of , Then the main content of the code is to prevent the image from going beyond the boundary , Or the selection box is beyond the picture boundary , And the offset value when moving .
Complete code :
package com.example.piccut;
/**@author Chen Jiezhu
* 2021.6 month **/
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
public class PicCutView extends View {
private PointF mPrevCurrentCenter = null;
private float mPrevDistance = Float.MIN_VALUE;
private float mTotalScale = 1f;
/**
* resetView Size when initializing pictures , unchanged
**/
private float mInitScale = 1f;
/**
* Touch dot pitch queue
**/
private Queue<Float> mTouchDistanceQueue = new LinkedBlockingQueue<>();
private Bitmap mBmp;
private Matrix mMatrix;
private float mAvergeX = 0, mAvergeY = 0;
private int mPrevPointCount = 0;
/**
* setPic The way
**/
private int mSetPicWay = 0;
/**
* setPicWay = 1 Rely on the center selection box
**/
private int mCutW, mCutH;
private float mdX = 0f, mdY = 0f;
private int mWidth, mHeight;
private Rect mCutRect = null;
private Rect mCutRectInitedClone = null;
/**
* The default minimum scale is at the time of initialization 100%, If lower 1, There will be blank
**/
private float mScaleMin = 1f;
/**
* By default, the maximum scale is 2 times
**/
private float mScaleMax = 2.0f;
/**
* The lateral movement shall not be less than x Percentage of shaft length
**/
private float mBorderXMin = 0.2f;
/**
* Lateral movement shall not be greater than x Percentage of shaft length
**/
private float mBorderXMax = 0.8f;
/**
* The lateral movement shall not be less than y Percentage of shaft length
**/
private float mBorderYMin = 0.2f;
/**
* Lateral movement shall not be greater than y Percentage of shaft length
**/
private float mBorderYMax = 0.8f;
private boolean mDebug = false;
/**
* Is there any 4 Than 3 Frame
**/
private boolean mUse4div3rect = true;
/**
* Whether the zoom box can be modified
**/
private boolean mCanChangeRect = true;
/**
* Can I move and zoom
**/
private boolean mCanMoveAndScale = true;
/**
* Lift the animation after the clipping box is modified
**/
private ValueAnimator mTransFinishAnim;
/**
* Which corner of the clipping box is currently being dragged
**/
private boolean mCutRectLeftTopIsDragging = false;
private boolean mCutRectLeftBottomIsDragging = false;
private boolean mCutRectRightTopIsDragging = false;
private boolean mCutRectRightBottomIsDragging = false;
private void init() {
if (mMatrix == null) {
mMatrix = new Matrix();
}
}
public PicCutView(Context context) {
super(context);
init();
}
public PicCutView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PicCutView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void setPic(Bitmap bmp) { // Use the default 3:4 Crop box
mBmp = bmp;
mSetPicWay = 0;
resetView();
}
public void setPic(Bitmap bmp, Rect rect) { // Customize the size and position of the selection box
mBmp = bmp;
setCutPicRect(rect);
mSetPicWay = 1;
resetView();
}
public void setPic(Bitmap bmp, int cutW, int cutH) { // Customize the width and height of the selection box, but
mBmp = bmp;
this.mCutW = cutW;
this.mCutH = cutH;
if (mWidth > 0 && mHeight > 0) {
mCutRect = new Rect((mWidth - mCutW) / 2, (mHeight - mCutH) / 2, (mWidth - mCutW) / 2 + mCutW, (mHeight - mCutH) / 2 + mCutH);
if (mCanChangeRect) {
mCutRectInitedClone = new Rect(mCutRect);
}
}
// etc. onMeasuer Set when Rect
mSetPicWay = 2;
resetView();
}
public void setPic(Bitmap bmp, boolean use4div3rect) { // Use 3:4 Crop box or not , The default value is true
mBmp = bmp;
mUse4div3rect = use4div3rect;
mSetPicWay = 3;
resetView();
}
/**
* Set the minimum scale that can be scaled to the initialization scale
*
* @param scaleMin What is the minimum scale to the initialization ratio
**/
public void setScaleMin(float scaleMin) {
this.mScaleMin = scaleMin;
}
/**
* Set the maximum scale that can be scaled to the initialization scale
*
* @param scaleMax The maximum zoom to the initialization ratio
**/
public void setScaleMax(float scaleMax) {
this.mScaleMax = scaleMax;
}
/**
* Set the lateral movement not less than x Percentage of shaft length
*
* @param borderXMin The lateral movement shall not be less than x Percentage of shaft length
**/
public void setBorderXMin(float borderXMin) {
this.mBorderXMin = borderXMin;
}
/**
* Set the lateral movement not to be greater than x Percentage of shaft length
*
* @param borderXMax Lateral movement shall not be greater than x Percentage of shaft length
**/
public void setBorderXMax(float borderXMax) {
this.mBorderXMax = borderXMax;
}
/**
* Set the lateral movement not less than y Percentage of shaft length
*
* @param borderYMin The lateral movement shall not be less than y Percentage of shaft length
**/
public void setBorderYMin(float borderYMin) {
this.mBorderYMin = borderYMin;
}
/**
* Set the lateral movement not to be greater than y Percentage of shaft length
*
* @param borderYMax Lateral movement shall not be greater than y Percentage of shaft length
**/
public void setBorderYMax(float borderYMax) {
this.mBorderYMax = borderYMax;
}
/**
* Every time you load an image, you need to reinitialize all parameters such as zoom , Because the length and width parameters of each picture are different
**/
private void resetView() {
if (mBmp != null && !mBmp.isRecycled() && mWidth > 0 && mHeight > 0 && mCutRect != null) {
int bmpH = mBmp.getHeight();
int bmpW = mBmp.getWidth();
// Restore parameters
mMatrix = new Matrix();
mTotalScale = 1f;
mdX = mdY = 0;
// The middle part of the picture is placed on the control
float scale;
// Zoom the picture to view The width and height of can accommodate
if (bmpW >= bmpH) {//bmpH When it is short side
scale = (float) mCutRect.height() / bmpH;
mdX -= (bmpW * scale - mWidth) / 2; // Move to view In the middle of
mdY = mCutRect.top; // Move to Rect Upper boundary
} else {//bmpW When it is short side
scale = (float) mCutRect.width() / bmpW;
mdX = mCutRect.left; // Move to Rect Left boundary
mdY -= (bmpH * scale - mHeight) / 2;
}
// Start with the upper left corner 0,0 Zoom to the target ratio
mMatrix.postScale(scale, scale, 0, 0);
// And then put it on the center line
mMatrix.postTranslate(mdX, mdY); // Equivalent to mMatrix.postScale(scale, scale, mHeight / 2, mWidth / 2)( Zoom with the center line as the zoom Center )
mTotalScale = scale;
mInitScale = scale;
invalidate();
}
}
/**
* Scaling function
*
* @param scale This scaling amount
* @param px Zoom Center x coordinate
* @param py Zoom Center y coordinate
**/
public void scale(float scale, float px, float py) {
if (mCutRect == null) {
return;
}
float relatedTotalScale = mTotalScale / mInitScale; // because resetView In order to make the picture just be wrapped by the screen , I have done scaling myself , therefore mTotalScale Not for 1, But for the convenience of scaling conversion , Regard the initialized proportion as 1.
if (scale < 1f && relatedTotalScale * scale < mScaleMin) { // If you are trying to shrink , However, the calculated reduced proportion value will be less than the minimum limit proportion , Then cancel the zoom
return;
}
if (scale >= 1f && relatedTotalScale * scale > mScaleMax && !mCanChangeRect) {
return;
}
if (mMatrix != null && mBmp != null && !mBmp.isRecycled()) {
mMatrix.postScale(scale, scale, px, py);
mTotalScale *= scale;
int bmpW = mBmp.getWidth();
int bmpH = mBmp.getHeight();
// It is not allowed to frame when zooming
float matrix[] = new float[9];
mMatrix.getValues(matrix);
if (matrix[2] < mCutRect.left && matrix[2] + bmpW * mTotalScale < mCutRect.right) {
matrix[2] += mCutRect.right - (matrix[2] + bmpW * mTotalScale);
}
if (matrix[2] > mCutRect.left && matrix[2] + bmpW * mTotalScale > mCutRect.right) {
matrix[2] = mCutRect.left;
}
if (matrix[5] < mCutRect.top && matrix[5] + bmpH * mTotalScale < mCutRect.bottom) {
matrix[5] += mCutRect.bottom - (matrix[5] + bmpH * mTotalScale);
}
if (matrix[5] > mCutRect.top && matrix[5] + bmpH * mTotalScale > mCutRect.bottom) {
matrix[5] = mCutRect.top;
}
mMatrix.setValues(matrix);
invalidate();
}
Log.i(" The zoom ", String.format(" percentage :%f", mTotalScale));
}
/**
* Moving functions
*
* @param distanceX The distance of this move x component
* @param distanceY The distance of this move y component
**/
private void translate(float distanceX, float distanceY) {
if (mMatrix != null && mBmp != null && !mBmp.isRecycled() && mCutRect != null) {
// Users are not allowed to push the picture completely out of the frame :
float matrix[] = new float[9];
int bmpW = mBmp.getWidth();
int bmpH = mBmp.getHeight();
mMatrix.getValues(matrix);
float currentX = matrix[2];
float currentY = matrix[5];
// If this time distance Value will make the picture out of the specified range , Then remove the mathematical meaning of the incoming value , Return to 0
if (currentX + bmpW * mTotalScale + distanceX < mCutRect.left + mCutRect.width() || currentX + distanceX > mCutRect.left) {
distanceX = 0;
}
if (currentY + bmpH * mTotalScale + distanceY < mCutRect.top + mCutRect.height() || currentY + distanceY > mCutRect.top) {
distanceY = 0;
}
mdX += distanceX;
mdY += distanceY;
mMatrix.postTranslate(distanceX, distanceY);
invalidate();
}
Log.i(" Move ", String.format("x Displacement :%f, y Displacement :%f", distanceX, distanceY));
}
/**
* Cut the picture
**/
public Bitmap cutPic() {
if (mBmp != null && !mBmp.isRecycled() && mCutRect != null) {
int width = mCutRect.width();
int height = mCutRect.height();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Matrix tempMatrix = new Matrix(mMatrix);
tempMatrix.postTranslate(-mCutRect.left, -mCutRect.top); // For example, I want to capture the right half of the visible part of the picture , Equivalent to my selection box does not move , Move the picture to the left by the corresponding distance . therefore left The distance to the left is the same as the distance to the left of the picture
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
canvas.drawBitmap(mBmp, tempMatrix, paint);
Bitmap scaledBmp = Bitmap.createScaledBitmap(bitmap, (int) (width / mTotalScale), (int) (height / mTotalScale), true);
bitmap.recycle();
return scaledBmp;
}
return null;
}
/**
* Set the clipping range of the picture
**/
private void setCutPicRect(Rect rect) {
this.mCutRect = new Rect(rect);
if (mCanChangeRect) {
this.mCutRectInitedClone = new Rect(mCutRect);
}
}
/* Get the measurement size */
private int getMySize(int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
result = specSize;// Exact size , So give the size to view
return result;
}
private float convertDpToPixel(float dp, Context context) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float px = dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
return px;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mBmp == null || mBmp.isRecycled()) {
return true;
}
int offset = (int) convertDpToPixel(50, getContext()); // Frame 4 horn 4 To what extent can a point accept touch events to achieve drag and zoom
int smallestEdgeSize = (int) convertDpToPixel(100, getContext()); // How small can the clipping box be at least
Rect leftTop = new Rect(mCutRect.left - offset, mCutRect.top - offset, mCutRect.left + offset, mCutRect.top + offset);
Rect rightTop = new Rect(mCutRect.right - offset, mCutRect.top - offset, mCutRect.right + offset, mCutRect.top + offset);
Rect leftBottom = new Rect(mCutRect.left - offset, mCutRect.bottom - offset, mCutRect.left + offset, mCutRect.bottom + offset);
Rect rightBottom = new Rect(mCutRect.right - offset, mCutRect.bottom - offset, mCutRect.right + offset, mCutRect.bottom + offset);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mCutRectLeftTopIsDragging = false;
mCutRectLeftBottomIsDragging = false;
mCutRectRightTopIsDragging = false;
mCutRectRightBottomIsDragging = false;
mPrevDistance = 0;
mPrevPointCount = event.getPointerCount();
// Calculate the coordinates of the moving center 、 Distance between points
for (int i = 0; i < event.getPointerCount(); i++) {
mAvergeX += event.getX(i);
mAvergeY += event.getY(i);
if (i + 1 < event.getPointerCount()) {
mPrevDistance += Math.sqrt(Math.pow(event.getX(i + 1) - event.getX(i), 2) + Math.pow(event.getY(i + 1) - event.getY(i), 2));
}
}
mAvergeX /= event.getPointerCount();
mAvergeY /= event.getPointerCount();
if (mPrevCurrentCenter == null) {
mPrevCurrentCenter = new PointF(mAvergeX, mAvergeY);
} else {
mPrevCurrentCenter.set(mAvergeX, mAvergeY);
}
if (mCanChangeRect && event.getPointerCount() == 1) { // If you are allowed to drag the crop box and the number of fingers is only 1
// Judge whether it is a choice rect Of 4 Around an angle
if (leftTop.contains((int) event.getX(), (int) event.getY())) {
mCanMoveAndScale = false;
mCutRectLeftTopIsDragging = true;
} else if (rightTop.contains((int) event.getX(), (int) event.getY())) {
mCanMoveAndScale = false;
mCutRectRightTopIsDragging = true;
} else if (leftBottom.contains((int) event.getX(), (int) event.getY())) {
mCanMoveAndScale = false;
mCutRectLeftBottomIsDragging = true;
} else if (rightBottom.contains((int) event.getX(), (int) event.getY())) {
mCanMoveAndScale = false;
mCutRectRightBottomIsDragging = true;
}
}
break;
case MotionEvent.ACTION_MOVE:
mAvergeX = 0;
mAvergeY = 0;
float nowDistance = 0;
// Calculate the coordinates of the moving center 、 Distance between points
for (int i = 0; i < event.getPointerCount(); i++) {
mAvergeX += event.getX(i);
mAvergeY += event.getY(i);
if (i + 1 < event.getPointerCount()) {
nowDistance += Math.sqrt(Math.pow(event.getX(i + 1) - event.getX(i), 2) + Math.pow(event.getY(i + 1) - event.getY(i), 2));
}
}
// Now the distance between points Divide Distance between last points This time you get the zoom
mAvergeX /= event.getPointerCount();
mAvergeY /= event.getPointerCount();
if ((mPrevPointCount != event.getPointerCount()) || event.getPointerCount() <= 1 || mPrevPointCount <= 1) { // Touch points suddenly change perhaps The touch point does not exceed 2, No scaling allowed
mPrevDistance = nowDistance = 0;
}
// Check the distance between fingers last time mPrevDistance And this time nowDistance The length difference between , With this / Last time, the so-called zoom ratio . If the scaling data is valid , Then average smoothing and scaling
if (mPrevDistance > 0 && nowDistance > 0) {
mTouchDistanceQueue.add(nowDistance / mPrevDistance);
if (mTouchDistanceQueue.size() >= 6) {
Float point[] = new Float[mTouchDistanceQueue.size()];
mTouchDistanceQueue.toArray(point);
float avergDistance = 0;
for (int i = 0; i < point.length; i++) {
avergDistance += point[i];
}
avergDistance /= point.length;
if (mCanMoveAndScale) {
scale((float) Math.sqrt(avergDistance), mAvergeX, mAvergeY);
}
while (mTouchDistanceQueue.size() > 6) {
mTouchDistanceQueue.poll();
}
}
}
mPrevPointCount = event.getPointerCount();
mPrevDistance = nowDistance;
// Current coordinates - Last coordinate = Offset value , Then perform position offset
if (mPrevCurrentCenter == null) {
mPrevCurrentCenter = new PointF(mAvergeX, mAvergeY);
} else if (event.getPointerCount() > 0) { // At least make sure that there is really a point to press and move
if (mCanChangeRect && event.getPointerCount() == 1 && !mCanMoveAndScale) { // If you are allowed to drag the crop box and the number of fingers is only 1
Rect mCutRectBeforeChange = new Rect(mCutRect);
// Judge whether it is a choice rect Of 4 Around an angle , Yes, just follow your fingers to move and modify
float wTohRatioBeforeChange = (float) mCutRectInitedClone.width() / mCutRectInitedClone.height();
float hTowRatioBeforeChange = 1 / wTohRatioBeforeChange;
if (leftTop.contains((int) event.getX(), (int) event.getY()) || mCutRectLeftTopIsDragging) {
mCutRect.left += event.getX(0) - mPrevCurrentCenter.x;
mCutRect.top += event.getY(0) - mPrevCurrentCenter.y;
mCutRect.bottom = (int) (mCutRect.top + mCutRect.width() * hTowRatioBeforeChange); // By resetting the height that conforms to the proportion of the clipping box at initialization , To maintain proportion
} else if (rightTop.contains((int) event.getX(), (int) event.getY()) || mCutRectRightTopIsDragging) {
mCutRect.right += event.getX(0) - mPrevCurrentCenter.x;
mCutRect.top += event.getY(0) - mPrevCurrentCenter.y;
mCutRect.bottom = (int) (mCutRect.top + mCutRect.width() * hTowRatioBeforeChange); // By resetting the height that conforms to the proportion of the clipping box at initialization , To maintain proportion
} else if (leftBottom.contains((int) event.getX(), (int) event.getY()) || mCutRectLeftBottomIsDragging) {
mCutRect.left += event.getX(0) - mPrevCurrentCenter.x;
mCutRect.bottom += event.getY(0) - mPrevCurrentCenter.y;
mCutRect.top = (int) (mCutRect.bottom - mCutRect.width() * hTowRatioBeforeChange); // By resetting the height that conforms to the proportion of the clipping box at initialization , To maintain proportion
} else if (rightBottom.contains((int) event.getX(), (int) event.getY()) || mCutRectRightBottomIsDragging) {
mCutRect.right += event.getX(0) - mPrevCurrentCenter.x;
mCutRect.bottom += event.getY(0) - mPrevCurrentCenter.y;
mCutRect.top = (int) (mCutRect.bottom - mCutRect.width() * hTowRatioBeforeChange); // By resetting the height that conforms to the proportion of the clipping box at initialization , To maintain proportion
}
// If the width or height has reached the minimum area or the maximum area , Invalidate the modification
if (mCutRect.width() > mCutRectInitedClone.width() || mCutRect.width() < smallestEdgeSize
|| mCutRect.height() > mCutRectInitedClone.height() || mCutRect.height() < smallestEdgeSize) {
mCutRect = new Rect(mCutRectBeforeChange);
}
// It is not allowed to push the box out of the picture :
if (mMatrix != null && mBmp != null && !mBmp.isRecycled()) {
int bmpW = mBmp.getWidth();
int bmpH = mBmp.getHeight();
float matrix[] = new float[9];
mMatrix.getValues(matrix);
float currentX = matrix[2];
float currentY = matrix[5];
float scale = matrix[0];
if (mCutRect.left < currentX || mCutRect.top < currentY || mCutRect.right > currentX + bmpW * scale
|| mCutRect.bottom > currentY + bmpH * scale) {
mCutRect = mCutRectBeforeChange;
}
}
invalidate();
}
// If no control is selected 4 Drag the picture directly at three angles
if (mCanMoveAndScale) {
translate(event.getX(0) - mPrevCurrentCenter.x, event.getY(0) - mPrevCurrentCenter.y);
}
mPrevCurrentCenter.set(event.getX(0), event.getY(0));
}
break;
case MotionEvent.ACTION_UP:
// lift , Clean up the data
mAvergeX = 0;
mAvergeY = 0;
mTouchDistanceQueue.clear();
if (mCanChangeRect) { // Press zoom after raising your hand rect Zoom size
int scalePx = mCutRect.centerX();
int scalePy = mCutRect.centerY();
float scale = (float) mCutRectInitedClone.width() / mCutRect.width();
scale(scale, scalePx, scalePy);
translate((scalePx - mCutRect.centerX()) * scale, (scalePy - mCutRect.centerY()) * scale);
mCutRect = new Rect(mCutRectInitedClone);
invalidate();
}
mCanMoveAndScale = true;
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
if (mCutRect != null) {
if (mBmp != null && !mBmp.isRecycled()) {
Paint p = new Paint();
p.setAlpha(128);
canvas.drawBitmap(mBmp, mMatrix, p);
p.setAlpha(255);
canvas.save();
canvas.clipRect(mCutRect); // Only select the box for opaque rendering
canvas.drawBitmap(mBmp, mMatrix, p);
canvas.restore(); // Restore the drawing area
}
Paint p = new Paint();
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(3);
p.setColor(Color.WHITE);
canvas.drawRect(mCutRect, p);
p.setStrokeWidth(9);
p.setStrokeCap(Paint.Cap.SQUARE);
float cornerLength = mCutRect.width() > mCutRect.height() ? mCutRect.width() * 0.05f : mCutRect.height() * 0.05f;
// top left corner
canvas.drawLine((float) mCutRect.left, (float) mCutRect.top, mCutRect.left + cornerLength, mCutRect.top, p);
canvas.drawLine((float) mCutRect.left, (float) mCutRect.top, mCutRect.left, mCutRect.top + cornerLength, p);
// The lower left corner
canvas.drawLine((float) mCutRect.left, (float) mCutRect.bottom, mCutRect.left + cornerLength, mCutRect.bottom, p);
canvas.drawLine((float) mCutRect.left, (float) mCutRect.bottom, mCutRect.left, mCutRect.bottom - cornerLength, p);
// Upper right corner
canvas.drawLine((float) mCutRect.right, (float) mCutRect.top, mCutRect.right - cornerLength, mCutRect.top, p);
canvas.drawLine((float) mCutRect.right, (float) mCutRect.top, mCutRect.right, mCutRect.top + cornerLength, p);
// The lower right corner
canvas.drawLine((float) mCutRect.right, (float) mCutRect.bottom, mCutRect.right - cornerLength, mCutRect.bottom, p);
canvas.drawLine((float) mCutRect.right, (float) mCutRect.bottom, mCutRect.right, mCutRect.bottom - cornerLength, p);
// If you are stretching and moving the crop box , Then the Jiugong grid is displayed :
if (!mCanMoveAndScale) {
Paint nineRectPaint = new Paint(p);
nineRectPaint.setAlpha(100);
nineRectPaint.setStrokeWidth(3);
canvas.drawLine((float) mCutRect.left, (float) mCutRect.top + mCutRect.height() * 1 / 3, (float) mCutRect.left + mCutRect.width(), (float) mCutRect.top + mCutRect.height() * 1 / 3, nineRectPaint);
canvas.drawLine((float) mCutRect.left, (float) mCutRect.top + mCutRect.height() * 2 / 3, (float) mCutRect.left + mCutRect.width(), (float) mCutRect.top + mCutRect.height() * 2 / 3, nineRectPaint);
canvas.drawLine((float) mCutRect.left + mCutRect.width() * 1 / 3, (float) mCutRect.top, (float) mCutRect.left + mCutRect.width() * 1 / 3, (float) mCutRect.top + mCutRect.height(), nineRectPaint);
canvas.drawLine((float) mCutRect.left + mCutRect.width() * 2 / 3, (float) mCutRect.top, (float) mCutRect.left + mCutRect.width() * 2 / 3, (float) mCutRect.top + mCutRect.height(), nineRectPaint);
}
// Draw prompt :
String tipsStr = String.format(" Intercepted resolution :%d * %d", (int) (mCutRect.width() / mTotalScale), (int) (mCutRect.height() / mTotalScale));
String tipsStr2 = " Suggested size ≥600*800";
if (mCutRect.width() == mCutRect.height()) {
tipsStr2 = " Suggested size ≥600*600";
}
Paint tipsPaint = new Paint();
tipsPaint.setTextSize(convertDpToPixel(12, getContext()));
tipsPaint.setColor(Color.WHITE);
tipsPaint.setStyle(Paint.Style.FILL_AND_STROKE);
tipsPaint.setStrokeWidth(1);
float textLen = tipsPaint.measureText(tipsStr);
canvas.drawText(tipsStr, (mWidth - textLen) / 2, mCutRect.bottom + tipsPaint.measureText("1") * 2, tipsPaint);
tipsPaint.setColor(0xFFCCCCCC);
textLen = tipsPaint.measureText(tipsStr2);
canvas.drawText(tipsStr2, (mWidth - textLen) / 2, mCutRect.bottom + tipsPaint.measureText("1") * 6, tipsPaint);
if (mDebug) {
p.setColor(Color.RED);
float matrix[] = new float[9];
mMatrix.getValues(matrix);
canvas.drawRect(new Rect((int) matrix[2], (int) matrix[5], (int) (matrix[2] + mBmp.getWidth() * mTotalScale), (int) (matrix[5] + mBmp.getHeight() * mTotalScale)), p);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int w = getMySize(widthMeasureSpec);
int h = getMySize(heightMeasureSpec);
if (w != mWidth || h != mHeight) { // already onMeasuer once , Do not reinitialize unless the interface size changes view
mWidth = getMySize(widthMeasureSpec);
mHeight = getMySize(heightMeasureSpec);
switch (mSetPicWay) {
case 0: {// Select the size of the crop box according to the picture
float wRatio = 0.9f;
int cutW = (int) (mWidth * wRatio);
int cutH = (int) (mWidth * wRatio * 4 / 3); // Default H:W = 4:3
if (mBmp != null && !mBmp.isRecycled()) {
// float bmpRatio = mBmp.getWidth() >= mBmp.getHeight() ? (float) mBmp.getWidth() / mBmp.getHeight() : (float) mBmp.getHeight() / mBmp.getWidth();
float bmpRatio = (float) mBmp.getWidth() / mBmp.getHeight();
if (bmpRatio > 1.3f) { // If the picture is long , Use the square selection box
if (cutW >= cutH) {
cutW = cutH;
} else {
cutH = cutW;
}
} // Otherwise, use the long bar selection box
}
mCutRect = new Rect((mWidth - cutW) / 2, (mHeight - cutH) / 2, (mWidth - cutW) / 2 + cutW, (mHeight - cutH) / 2 + cutH);
if (mCanChangeRect) {
mCutRectInitedClone = new Rect(mCutRect);
}
break;
}
case 2:
mCutRect = new Rect((mWidth - mCutW) / 2, (mHeight - mCutH) / 2, (mWidth - mCutW) / 2 + mCutW, (mHeight - mCutH) / 2 + mCutH);
if (mCanChangeRect) {
mCutRectInitedClone = new Rect(mCutRect);
}
break;
case 3: {
float wRatio = 0.9f;
int cutW = (int) (mWidth * wRatio);
int cutH;
if (mUse4div3rect) {
cutH = (int) (mWidth * wRatio * 4 / 3); // Default H:W = 4:3
} else {
cutH = cutW;
}
mCutRect = new Rect((mWidth - cutW) / 2, (mHeight - cutH) / 2, (mWidth - cutW) / 2 + cutW, (mHeight - cutH) / 2 + cutH);
if (mCanChangeRect) {
mCutRectInitedClone = new Rect(mCutRect);
}
break;
}
}
resetView();
}
}
}
Hasty time , For the time being, write briefly . I especially like this kind of original control .
边栏推荐
- 自定义图片裁剪框
- Multithreading and thread pool
- 基础不牢地动山摇之牛客刷题《二》
- Postman configures the global variable postman sets the global token
- 指针的深度解刨《五》
- "Everest" architecture strongly enables the third-generation Roewe rx5/ super hybrid erx5 to refresh the "ceiling" of product power
- 刨根问底丨落后的技术,能否造出好卖的产品?
- ASP. Net core deployment Manual: 2 Hyper-V virtual machine
- 给OES纹理添加雪花特效
- Quantitative transaction Diary - summary in January 2021
猜你喜欢
v-7
一篇让理解你Mysql存储引擎
文件操作《二》(5000字总结篇)
基础不牢地动山摇之牛客刷题《二》
C textbox password box setting
Two methods of redis installation and configuration in Windows Environment
三星6818LED驱动的编写
In quantitative trading, it is judged by the moving average system that the upward (downward) momentum is weakened
RK3288关于LVDS信号配置和1080p视频信号的详解
Module build failed: Error: Plugin/Preset files are not allowed to export objects, only functions.
随机推荐
ASP. Net core deployment Manual: 2 Hyper-V virtual machine
E-commerce promotion relies on RPA to get rid of repeated work and rapidly improve efficiency
李宏毅《机器学习》丨5. Tips for neural network design(神经网络设计技巧)
OSPF republish
JS object deep copy
三星6818LED驱动的编写
JS时间和时间戳的转换
一篇让理解你Mysql存储引擎
[三星6818]gpio模拟spi信号编写门禁卡识别模块驱动
js 对象深拷贝
海思Hi3531||瑞芯微RK1109用rtsp客户端实现h264拉流
QML drag pictures and objects across windows
openlayers 使用canvas绘制圆形头像图标
2022年华泰开户网上办理安全吗?
Qml-跨窗口拖动图片、物体
【OAuth2】二、OAuth2.1的已知变动
Samsung 6818 water lamp program based on uboot
Windows环境下Redis安装与配置的两种方式
【踩坑】npm安装报错解决办法
C textbox password box setting