NekoX/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropAreaView.java

749 lines
28 KiB
Java

package org.telegram.ui.Components.Crop;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.Build;
import androidx.annotation.Keep;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import org.telegram.messenger.AndroidUtilities;
public class CropAreaView extends View {
private enum Control {
NONE, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, TOP, LEFT, BOTTOM, RIGHT
}
interface AreaViewListener {
void onAreaChangeBegan();
void onAreaChange();
void onAreaChangeEnded();
}
private RectF topLeftCorner = new RectF();
private RectF topRightCorner = new RectF();
private RectF bottomLeftCorner = new RectF();
private RectF bottomRightCorner = new RectF();
private RectF topEdge = new RectF();
private RectF leftEdge = new RectF();
private RectF bottomEdge = new RectF();
private RectF rightEdge = new RectF();
private float lockAspectRatio;
private Control activeControl;
private RectF actualRect = new RectF();
private RectF tempRect = new RectF();
private int previousX;
private int previousY;
private float bottomPadding;
private boolean dimVisibile;
private boolean frameVisible;
Paint dimPaint;
Paint shadowPaint;
Paint linePaint;
Paint handlePaint;
Paint framePaint;
AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator();
private float sidePadding;
private float minWidth;
enum GridType {
NONE, MINOR, MAJOR
}
private GridType previousGridType;
private GridType gridType;
private float gridProgress;
private Animator gridAnimator;
private AreaViewListener listener;
private boolean isDragging;
private boolean freeform = true;
private Bitmap circleBitmap;
private Paint eraserPaint;
private Animator animator;
public CropAreaView(Context context) {
super(context);
frameVisible = true;
dimVisibile = true;
sidePadding = AndroidUtilities.dp(16);
minWidth = AndroidUtilities.dp(32);
gridType = GridType.NONE;
dimPaint = new Paint();
dimPaint.setColor(0xcc000000);
shadowPaint = new Paint();
shadowPaint.setStyle(Paint.Style.FILL);
shadowPaint.setColor(0x1a000000);
shadowPaint.setStrokeWidth(AndroidUtilities.dp(2));
linePaint = new Paint();
linePaint.setStyle(Paint.Style.FILL);
linePaint.setColor(0xffffffff);
linePaint.setStrokeWidth(AndroidUtilities.dp(1));
handlePaint = new Paint();
handlePaint.setStyle(Paint.Style.FILL);
handlePaint.setColor(Color.WHITE);
framePaint = new Paint();
framePaint.setStyle(Paint.Style.FILL);
framePaint.setColor(0xb2ffffff);
eraserPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
eraserPaint.setColor(0);
eraserPaint.setStyle(Paint.Style.FILL);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
public boolean isDragging() {
return isDragging;
}
public void setDimVisibility(boolean visible) {
dimVisibile = visible;
}
public void setFrameVisibility(boolean visible) {
frameVisible = visible;
}
public void setBottomPadding(float value) {
bottomPadding = value;
}
public Interpolator getInterpolator() {
return interpolator;
}
public void setListener(AreaViewListener l) {
listener = l;
}
public void setBitmap(Bitmap bitmap, boolean sideward, boolean fform) {
if (bitmap == null || bitmap.isRecycled()) {
return;
}
freeform = fform;
float aspectRatio;
if (sideward) {
aspectRatio = ((float) bitmap.getHeight()) / ((float) bitmap.getWidth());
} else {
aspectRatio = ((float) bitmap.getWidth()) / ((float) bitmap.getHeight());
}
if (!freeform) {
aspectRatio = 1.0f;
lockAspectRatio = 1.0f;
}
setActualRect(aspectRatio);
}
public void setFreeform(boolean fform) {
freeform = fform;
}
public void setActualRect(float aspectRatio) {
calculateRect(actualRect, aspectRatio);
updateTouchAreas();
invalidate();
}
public void setActualRect(RectF rect) {
actualRect.set(rect);
updateTouchAreas();
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
if (freeform) {
int lineThickness = AndroidUtilities.dp(2);
int handleSize = AndroidUtilities.dp(16);
int handleThickness = AndroidUtilities.dp(3);
int originX = (int) actualRect.left - lineThickness;
int originY = (int) actualRect.top - lineThickness;
int width = (int) (actualRect.right - actualRect.left) + lineThickness * 2;
int height = (int) (actualRect.bottom - actualRect.top) + lineThickness * 2;
if (dimVisibile) {
canvas.drawRect(0, 0, getWidth(), originY + lineThickness, dimPaint);
canvas.drawRect(0, originY + lineThickness, originX + lineThickness, originY + height - lineThickness, dimPaint);
canvas.drawRect(originX + width - lineThickness, originY + lineThickness, getWidth(), originY + height - lineThickness, dimPaint);
canvas.drawRect(0, originY + height - lineThickness, getWidth(), getHeight(), dimPaint);
}
if (!frameVisible) {
return;
}
int inset = handleThickness - lineThickness;
int gridWidth = width - handleThickness * 2;
int gridHeight = height - handleThickness * 2;
GridType type = gridType;
if (type == GridType.NONE && gridProgress > 0)
type = previousGridType;
shadowPaint.setAlpha((int) (gridProgress * 26));
linePaint.setAlpha((int) (gridProgress * 178));
for (int i = 0; i < 3; i++) {
if (type == GridType.MINOR) {
for (int j = 1; j < 4; j++) {
if (i == 2 && j == 3)
continue;
canvas.drawLine(originX + handleThickness + gridWidth / 3 / 3 * j + gridWidth / 3 * i, originY + handleThickness, originX + handleThickness + gridWidth / 3 / 3 * j + gridWidth / 3 * i, originY + handleThickness + gridHeight, shadowPaint);
canvas.drawLine(originX + handleThickness + gridWidth / 3 / 3 * j + gridWidth / 3 * i, originY + handleThickness, originX + handleThickness + gridWidth / 3 / 3 * j + gridWidth / 3 * i, originY + handleThickness + gridHeight, linePaint);
canvas.drawLine(originX + handleThickness, originY + handleThickness + gridHeight / 3 / 3 * j + gridHeight / 3 * i, originX + handleThickness + gridWidth, originY + handleThickness + gridHeight / 3 / 3 * j + gridHeight / 3 * i, shadowPaint);
canvas.drawLine(originX + handleThickness, originY + handleThickness + gridHeight / 3 / 3 * j + gridHeight / 3 * i, originX + handleThickness + gridWidth, originY + handleThickness + gridHeight / 3 / 3 * j + gridHeight / 3 * i, linePaint);
}
} else if (type == GridType.MAJOR) {
if (i > 0) {
canvas.drawLine(originX + handleThickness + gridWidth / 3 * i, originY + handleThickness, originX + handleThickness + gridWidth / 3 * i, originY + handleThickness + gridHeight, shadowPaint);
canvas.drawLine(originX + handleThickness + gridWidth / 3 * i, originY + handleThickness, originX + handleThickness + gridWidth / 3 * i, originY + handleThickness + gridHeight, linePaint);
canvas.drawLine(originX + handleThickness, originY + handleThickness + gridHeight / 3 * i, originX + handleThickness + gridWidth, originY + handleThickness + gridHeight / 3 * i, shadowPaint);
canvas.drawLine(originX + handleThickness, originY + handleThickness + gridHeight / 3 * i, originX + handleThickness + gridWidth, originY + handleThickness + gridHeight / 3 * i, linePaint);
}
}
}
canvas.drawRect(originX + inset, originY + inset, originX + width - inset, originY + inset + lineThickness, framePaint);
canvas.drawRect(originX + inset, originY + inset, originX + inset + lineThickness, originY + height - inset, framePaint);
canvas.drawRect(originX + inset, originY + height - inset - lineThickness, originX + width - inset, originY + height - inset, framePaint);
canvas.drawRect(originX + width - inset - lineThickness, originY + inset, originX + width - inset, originY + height - inset, framePaint);
canvas.drawRect(originX, originY, originX + handleSize, originY + handleThickness, handlePaint);
canvas.drawRect(originX, originY, originX + handleThickness, originY + handleSize, handlePaint);
canvas.drawRect(originX + width - handleSize, originY, originX + width, originY + handleThickness, handlePaint);
canvas.drawRect(originX + width - handleThickness, originY, originX + width, originY + handleSize, handlePaint);
canvas.drawRect(originX, originY + height - handleThickness, originX + handleSize, originY + height, handlePaint);
canvas.drawRect(originX, originY + height - handleSize, originX + handleThickness, originY + height, handlePaint);
canvas.drawRect(originX + width - handleSize, originY + height - handleThickness, originX + width, originY + height, handlePaint);
canvas.drawRect(originX + width - handleThickness, originY + height - handleSize, originX + width, originY + height, handlePaint);
} else {
if (circleBitmap == null || circleBitmap.getWidth() != actualRect.width()) {
if (circleBitmap != null) {
circleBitmap.recycle();
circleBitmap = null;
}
try {
circleBitmap = Bitmap.createBitmap((int) actualRect.width(), (int) actualRect.height(), Bitmap.Config.ARGB_8888);
Canvas circleCanvas = new Canvas(circleBitmap);
circleCanvas.drawRect(0, 0, actualRect.width(), actualRect.height(), dimPaint);
circleCanvas.drawCircle(actualRect.width() / 2, actualRect.height() / 2, actualRect.width() / 2, eraserPaint);
circleCanvas.setBitmap(null);
} catch (Throwable ignore) {
}
}
canvas.drawRect(0, 0, getWidth(), (int) actualRect.top, dimPaint);
canvas.drawRect(0, (int) actualRect.top, (int) actualRect.left, (int) actualRect.bottom, dimPaint);
canvas.drawRect((int) actualRect.right, (int) actualRect.top, getWidth(), (int) actualRect.bottom, dimPaint);
canvas.drawRect(0, (int) actualRect.bottom, getWidth(), getHeight(), dimPaint);
if (circleBitmap != null) {
canvas.drawBitmap(circleBitmap, (int) actualRect.left, (int) actualRect.top, null);
}
}
}
private void updateTouchAreas() {
int touchPadding = AndroidUtilities.dp(16);
topLeftCorner.set(actualRect.left - touchPadding, actualRect.top - touchPadding, actualRect.left + touchPadding, actualRect.top + touchPadding);
topRightCorner.set(actualRect.right - touchPadding, actualRect.top - touchPadding, actualRect.right + touchPadding, actualRect.top + touchPadding);
bottomLeftCorner.set(actualRect.left - touchPadding, actualRect.bottom - touchPadding, actualRect.left + touchPadding, actualRect.bottom + touchPadding);
bottomRightCorner.set(actualRect.right - touchPadding, actualRect.bottom - touchPadding, actualRect.right + touchPadding, actualRect.bottom + touchPadding);
topEdge.set(actualRect.left + touchPadding, actualRect.top - touchPadding, actualRect.right - touchPadding, actualRect.top + touchPadding);
leftEdge.set(actualRect.left - touchPadding, actualRect.top + touchPadding, actualRect.left + touchPadding, actualRect.bottom - touchPadding);
rightEdge.set(actualRect.right - touchPadding, actualRect.top + touchPadding, actualRect.right + touchPadding, actualRect.bottom - touchPadding);
bottomEdge.set(actualRect.left + touchPadding, actualRect.bottom - touchPadding, actualRect.right - touchPadding, actualRect.bottom + touchPadding);
}
public float getLockAspectRatio() {
return lockAspectRatio;
}
public void setLockedAspectRatio(float aspectRatio) {
lockAspectRatio = aspectRatio;
}
public void setGridType(GridType type, boolean animated) {
if (gridAnimator != null) {
if (!animated || gridType != type) {
gridAnimator.cancel();
gridAnimator = null;
}
}
if (gridType == type)
return;
previousGridType = gridType;
gridType = type;
final float targetProgress = type == GridType.NONE ? 0.0f : 1.0f;
if (!animated) {
gridProgress = targetProgress;
invalidate();
} else {
gridAnimator = ObjectAnimator.ofFloat(this, "gridProgress", gridProgress, targetProgress);
gridAnimator.setDuration(200);
gridAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
gridAnimator = null;
}
});
if (type == GridType.NONE)
gridAnimator.setStartDelay(200);
gridAnimator.start();
}
}
@Keep
@SuppressWarnings("unused")
private void setGridProgress(float value) {
gridProgress = value;
invalidate();
}
@SuppressWarnings("unused")
private float getGridProgress() {
return gridProgress;
}
public float getAspectRatio() {
return (actualRect.right - actualRect.left) / (actualRect.bottom - actualRect.top);
}
public void fill(final RectF targetRect, Animator scaleAnimator, boolean animated) {
if (animated) {
if (animator != null) {
animator.cancel();
animator = null;
}
AnimatorSet set = new AnimatorSet();
animator = set;
set.setDuration(300);
Animator animators[] = new Animator[5];
animators[0] = ObjectAnimator.ofFloat(this, "cropLeft", targetRect.left);
animators[0].setInterpolator(interpolator);
animators[1] = ObjectAnimator.ofFloat(this, "cropTop", targetRect.top);
animators[1].setInterpolator(interpolator);
animators[2] = ObjectAnimator.ofFloat(this, "cropRight", targetRect.right);
animators[2].setInterpolator(interpolator);
animators[3] = ObjectAnimator.ofFloat(this, "cropBottom", targetRect.bottom);
animators[3].setInterpolator(interpolator);
animators[4] = scaleAnimator;
animators[4].setInterpolator(interpolator);
set.playTogether(animators);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setActualRect(targetRect);
animator = null;
}
});
set.start();
} else {
setActualRect(targetRect);
}
}
public void resetAnimator() {
if (animator != null) {
animator.cancel();
animator = null;
}
}
@Keep
@SuppressWarnings("unused")
private void setCropLeft(float value) {
actualRect.left = value;
invalidate();
}
@SuppressWarnings("unused")
public float getCropLeft() {
return actualRect.left;
}
@Keep
@SuppressWarnings("unused")
private void setCropTop(float value) {
actualRect.top = value;
invalidate();
}
@SuppressWarnings("unused")
public float getCropTop() {
return actualRect.top;
}
@Keep
@SuppressWarnings("unused")
private void setCropRight(float value) {
actualRect.right = value;
invalidate();
}
public float getCropRight() {
return actualRect.right;
}
@Keep
@SuppressWarnings("unused")
private void setCropBottom(float value) {
actualRect.bottom = value;
invalidate();
}
public float getCropBottom() {
return actualRect.bottom;
}
public float getCropCenterX() {
return actualRect.left + ((actualRect.right - actualRect.left) / 2.0f);
}
public float getCropCenterY() {
return actualRect.top + ((actualRect.bottom - actualRect.top) / 2.0f);
}
public float getCropWidth() {
return actualRect.right - actualRect.left;
}
public float getCropHeight() {
return actualRect.bottom - actualRect.top;
}
public RectF getTargetRectToFill() {
RectF rect = new RectF();
calculateRect(rect, getAspectRatio());
return rect;
}
public void calculateRect(RectF rect, float cropAspectRatio) {
float statusBarHeight = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0);
float left, top, right, bottom;
float measuredHeight = (float) getMeasuredHeight() - bottomPadding - statusBarHeight;
float aspectRatio = (float) getMeasuredWidth() / measuredHeight;
float minSide = Math.min(getMeasuredWidth(), measuredHeight) - 2 * sidePadding;
float width = getMeasuredWidth() - 2 * sidePadding;
float height = measuredHeight - 2 * sidePadding;
float centerX = getMeasuredWidth() / 2.0f;
float centerY = statusBarHeight + measuredHeight / 2.0f;
if (Math.abs(1.0f - cropAspectRatio) < 0.0001) {
left = centerX - (minSide / 2.0f);
top = centerY - (minSide / 2.0f);
right = centerX + (minSide / 2.0f);
bottom = centerY + (minSide / 2.0f);
} else if (cropAspectRatio > aspectRatio) {
left = centerX - (width / 2.0f);
top = centerY - ((width / cropAspectRatio) / 2.0f);
right = centerX + (width / 2.0f);
bottom = centerY + ((width / cropAspectRatio) / 2.0f);
} else {
left = centerX - ((height * cropAspectRatio) / 2.0f);
top = centerY - (height / 2.0f);
right = centerX + ((height * cropAspectRatio) / 2.0f);
bottom = centerY + (height / 2.0f);
}
rect.set(left, top, right, bottom);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) (event.getX() - ((ViewGroup) getParent()).getX());
int y = (int) (event.getY() - ((ViewGroup) getParent()).getY());
float statusBarHeight = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0);
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
if (freeform) {
if (this.topLeftCorner.contains(x, y)) {
activeControl = Control.TOP_LEFT;
} else if (this.topRightCorner.contains(x, y)) {
activeControl = Control.TOP_RIGHT;
} else if (this.bottomLeftCorner.contains(x, y)) {
activeControl = Control.BOTTOM_LEFT;
} else if (this.bottomRightCorner.contains(x, y)) {
activeControl = Control.BOTTOM_RIGHT;
} else if (this.leftEdge.contains(x, y)) {
activeControl = Control.LEFT;
} else if (this.topEdge.contains(x, y)) {
activeControl = Control.TOP;
} else if (this.rightEdge.contains(x, y)) {
activeControl = Control.RIGHT;
} else if (this.bottomEdge.contains(x, y)) {
activeControl = Control.BOTTOM;
} else {
activeControl = Control.NONE;
return false;
}
} else {
activeControl = Control.NONE;
return false;
}
previousX = x;
previousY = y;
setGridType(GridType.MAJOR, false);
isDragging = true;
if (listener != null)
listener.onAreaChangeBegan();
return true;
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
isDragging = false;
if (activeControl == Control.NONE)
return false;
activeControl = Control.NONE;
if (listener != null)
listener.onAreaChangeEnded();
return true;
} else if (action == MotionEvent.ACTION_MOVE) {
if (activeControl == Control.NONE)
return false;
tempRect.set(actualRect);
float translationX = x - previousX;
float translationY = y - previousY;
previousX = x;
previousY = y;
switch (activeControl) {
case TOP_LEFT:
tempRect.left += translationX;
tempRect.top += translationY;
if (lockAspectRatio > 0) {
float w = tempRect.width();
float h = tempRect.height();
if (Math.abs(translationX) > Math.abs(translationY)) {
constrainRectByWidth(tempRect, lockAspectRatio);
} else {
constrainRectByHeight(tempRect, lockAspectRatio);
}
tempRect.left -= tempRect.width() - w;
tempRect.top -= tempRect.width() - h;
}
break;
case TOP_RIGHT:
tempRect.right += translationX;
tempRect.top += translationY;
if (lockAspectRatio > 0) {
float h = tempRect.height();
if (Math.abs(translationX) > Math.abs(translationY)) {
constrainRectByWidth(tempRect, lockAspectRatio);
} else {
constrainRectByHeight(tempRect, lockAspectRatio);
}
tempRect.top -= tempRect.width() - h;
}
break;
case BOTTOM_LEFT:
tempRect.left += translationX;
tempRect.bottom += translationY;
if (lockAspectRatio > 0) {
float w = tempRect.width();
if (Math.abs(translationX) > Math.abs(translationY)) {
constrainRectByWidth(tempRect, lockAspectRatio);
} else {
constrainRectByHeight(tempRect, lockAspectRatio);
}
tempRect.left -= tempRect.width() - w;
}
break;
case BOTTOM_RIGHT:
tempRect.right += translationX;
tempRect.bottom += translationY;
if (lockAspectRatio > 0) {
if (Math.abs(translationX) > Math.abs(translationY)) {
constrainRectByWidth(tempRect, lockAspectRatio);
} else {
constrainRectByHeight(tempRect, lockAspectRatio);
}
}
break;
case TOP:
tempRect.top += translationY;
if (lockAspectRatio > 0) {
constrainRectByHeight(tempRect, lockAspectRatio);
}
break;
case LEFT:
tempRect.left += translationX;
if (lockAspectRatio > 0) {
constrainRectByWidth(tempRect, lockAspectRatio);
}
break;
case RIGHT:
tempRect.right += translationX;
if (lockAspectRatio > 0) {
constrainRectByWidth(tempRect, lockAspectRatio);
}
break;
case BOTTOM:
tempRect.bottom += translationY;
if (lockAspectRatio > 0) {
constrainRectByHeight(tempRect, lockAspectRatio);
}
break;
default:
break;
}
if (tempRect.left < sidePadding) {
if (lockAspectRatio > 0) {
tempRect.bottom = tempRect.top + (tempRect.right - sidePadding) / lockAspectRatio;
}
tempRect.left = sidePadding;
} else if (tempRect.right > getWidth() - sidePadding) {
tempRect.right = getWidth() - sidePadding;
if (lockAspectRatio > 0) {
tempRect.bottom = tempRect.top + tempRect.width() / lockAspectRatio;
}
}
float topPadding = statusBarHeight + sidePadding;
float finalBottomPadidng = bottomPadding + sidePadding;
if (tempRect.top < topPadding) {
if (lockAspectRatio > 0) {
tempRect.right = tempRect.left + (tempRect.bottom - topPadding) * lockAspectRatio;
}
tempRect.top = topPadding;
} else if (tempRect.bottom > getHeight() - finalBottomPadidng) {
tempRect.bottom = getHeight() - finalBottomPadidng;
if (lockAspectRatio > 0) {
tempRect.right = tempRect.left + tempRect.height() * lockAspectRatio;
}
}
if (tempRect.width() < minWidth) {
tempRect.right = tempRect.left + minWidth;
}
if (tempRect.height() < minWidth) {
tempRect.bottom = tempRect.top + minWidth;
}
if (lockAspectRatio > 0) {
if (lockAspectRatio < 1) {
if (tempRect.width() <= minWidth) {
tempRect.right = tempRect.left + minWidth;
tempRect.bottom = tempRect.top + tempRect.width() / lockAspectRatio;
}
} else {
if (tempRect.height() <= minWidth) {
tempRect.bottom = tempRect.top + minWidth;
tempRect.right = tempRect.left + tempRect.height() * lockAspectRatio;
}
}
}
setActualRect(tempRect);
if (listener != null) {
listener.onAreaChange();
}
return true;
}
return false;
}
private void constrainRectByWidth(RectF rect, float aspectRatio) {
float w = rect.width();
float h = w / aspectRatio;
rect.right = rect.left + w;
rect.bottom = rect.top + h;
}
private void constrainRectByHeight(RectF rect, float aspectRatio) {
float h = rect.height();
float w = h * aspectRatio;
rect.right = rect.left + w;
rect.bottom = rect.top + h;
}
public void getCropRect(RectF rect) {
rect.set(actualRect);
}
}