mirror of https://github.com/NekoX-Dev/NekoX.git
1002 lines
34 KiB
Java
1002 lines
34 KiB
Java
package org.telegram.ui.Components.Crop;
|
|
|
|
import android.animation.Animator;
|
|
import android.animation.AnimatorListenerAdapter;
|
|
import android.animation.ValueAnimator;
|
|
import android.annotation.SuppressLint;
|
|
import android.content.Context;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapFactory;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Matrix;
|
|
import android.graphics.Paint;
|
|
import android.graphics.PointF;
|
|
import android.graphics.RectF;
|
|
import android.os.Build;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.ViewTreeObserver;
|
|
import android.widget.FrameLayout;
|
|
import android.widget.ImageView;
|
|
|
|
import org.telegram.messenger.AndroidUtilities;
|
|
import org.telegram.messenger.FileLoader;
|
|
import org.telegram.messenger.FileLog;
|
|
import org.telegram.messenger.ImageLoader;
|
|
import org.telegram.messenger.LocaleController;
|
|
import org.telegram.messenger.MediaController;
|
|
import org.telegram.messenger.R;
|
|
import org.telegram.messenger.SharedConfig;
|
|
import org.telegram.messenger.VideoEditedInfo;
|
|
import org.telegram.ui.ActionBar.AlertDialog;
|
|
import org.telegram.ui.Components.PaintingOverlay;
|
|
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.util.ArrayList;
|
|
|
|
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
|
|
|
|
public class CropView extends FrameLayout implements CropAreaView.AreaViewListener, CropGestureDetector.CropGestureListener {
|
|
private static final float EPSILON = 0.00001f;
|
|
private static final int RESULT_SIDE = 1280;
|
|
private static final float MAX_SCALE = 30.0f;
|
|
|
|
private View backView;
|
|
|
|
private CropAreaView areaView;
|
|
private ImageView imageView;
|
|
private Matrix presentationMatrix;
|
|
private Matrix overlayMatrix;
|
|
private PaintingOverlay paintingOverlay;
|
|
|
|
private RectF previousAreaRect;
|
|
private RectF initialAreaRect;
|
|
private float rotationStartScale;
|
|
|
|
private CropRectangle tempRect;
|
|
private Matrix tempMatrix;
|
|
|
|
private Bitmap bitmap;
|
|
private boolean freeform;
|
|
private float bottomPadding;
|
|
|
|
private boolean animating;
|
|
private CropGestureDetector detector;
|
|
|
|
private boolean hasAspectRatioDialog;
|
|
|
|
private class CropState {
|
|
private float width;
|
|
private float height;
|
|
|
|
private float x;
|
|
private float y;
|
|
private float scale;
|
|
private float minimumScale;
|
|
private float baseRotation;
|
|
private float orientation;
|
|
private float rotation;
|
|
private Matrix matrix;
|
|
|
|
private CropState(Bitmap bitmap, int bRotation) {
|
|
width = bitmap.getWidth();
|
|
height = bitmap.getHeight();
|
|
|
|
x = 0.0f;
|
|
y = 0.0f;
|
|
scale = 1.0f;
|
|
baseRotation = bRotation;
|
|
rotation = 0.0f;
|
|
matrix = new Matrix();
|
|
}
|
|
|
|
private void updateBitmap(Bitmap bitmap, int rotation) {
|
|
float ps = width / bitmap.getWidth();
|
|
scale *= ps;
|
|
width = bitmap.getWidth();
|
|
height = bitmap.getHeight();
|
|
updateMinimumScale();
|
|
float[] values = new float[9];
|
|
matrix.getValues(values);
|
|
matrix.reset();
|
|
matrix.postScale(scale, scale);
|
|
matrix.postTranslate(values[2], values[5]);
|
|
updateMatrix();
|
|
}
|
|
|
|
private boolean hasChanges() {
|
|
return Math.abs(x) > EPSILON || Math.abs(y) > EPSILON || Math.abs(scale - minimumScale) > EPSILON
|
|
|| Math.abs(rotation) > EPSILON || Math.abs(orientation) > EPSILON;
|
|
}
|
|
|
|
private float getWidth() {
|
|
return width;
|
|
}
|
|
|
|
private float getHeight() {
|
|
return height;
|
|
}
|
|
|
|
private float getOrientedWidth() {
|
|
return (orientation + baseRotation) % 180 != 0 ? height : width;
|
|
}
|
|
|
|
private float getOrientedHeight() {
|
|
return (orientation + baseRotation) % 180 != 0 ? width : height;
|
|
}
|
|
|
|
private void translate(float x, float y) {
|
|
this.x += x;
|
|
this.y += y;
|
|
matrix.postTranslate(x, y);
|
|
}
|
|
|
|
private float getX() {
|
|
return x;
|
|
}
|
|
|
|
private float getY() {
|
|
return y;
|
|
}
|
|
|
|
private void scale(float s, float pivotX, float pivotY) {
|
|
scale *= s;
|
|
matrix.postScale(s, s, pivotX, pivotY);
|
|
}
|
|
|
|
private float getScale() {
|
|
return scale;
|
|
}
|
|
|
|
private float getMinimumScale() {
|
|
return minimumScale;
|
|
}
|
|
|
|
private void rotate(float angle, float pivotX, float pivotY) {
|
|
rotation += angle;
|
|
matrix.postRotate(angle, pivotX, pivotY);
|
|
}
|
|
|
|
private float getRotation() {
|
|
return rotation;
|
|
}
|
|
|
|
private float getOrientation() {
|
|
return orientation + baseRotation;
|
|
}
|
|
|
|
private float getOrientationOnly() {
|
|
return orientation;
|
|
}
|
|
|
|
private float getBaseRotation() {
|
|
return baseRotation;
|
|
}
|
|
|
|
private void reset(CropAreaView areaView, float orient, boolean freeform) {
|
|
matrix.reset();
|
|
|
|
x = 0.0f;
|
|
y = 0.0f;
|
|
rotation = 0.0f;
|
|
orientation = orient;
|
|
updateMinimumScale();
|
|
scale = minimumScale;
|
|
|
|
matrix.postScale(scale, scale);
|
|
}
|
|
|
|
private void updateMinimumScale() {
|
|
float w = (orientation + baseRotation) % 180 != 0 ? height : width;
|
|
float h = (orientation + baseRotation) % 180 != 0 ? width : height;
|
|
if (freeform) {
|
|
minimumScale = areaView.getCropWidth() / w;
|
|
} else {
|
|
float wScale = areaView.getCropWidth() / w;
|
|
float hScale = areaView.getCropHeight() / h;
|
|
minimumScale = Math.max(wScale, hScale);
|
|
}
|
|
}
|
|
|
|
private void getConcatMatrix(Matrix toMatrix) {
|
|
toMatrix.postConcat(matrix);
|
|
}
|
|
|
|
private Matrix getMatrix() {
|
|
Matrix m = new Matrix();
|
|
m.set(matrix);
|
|
return m;
|
|
}
|
|
}
|
|
|
|
private CropState state;
|
|
|
|
public interface CropViewListener {
|
|
void onChange(boolean reset);
|
|
|
|
void onAspectLock(boolean enabled);
|
|
}
|
|
|
|
private CropViewListener listener;
|
|
|
|
public CropView(Context context) {
|
|
super(context);
|
|
|
|
previousAreaRect = new RectF();
|
|
initialAreaRect = new RectF();
|
|
presentationMatrix = new Matrix();
|
|
overlayMatrix = new Matrix();
|
|
tempRect = new CropRectangle();
|
|
tempMatrix = new Matrix();
|
|
animating = false;
|
|
|
|
backView = new View(context);
|
|
backView.setBackgroundColor(0xff000000);
|
|
backView.setVisibility(INVISIBLE);
|
|
addView(backView);
|
|
|
|
imageView = new ImageView(context);
|
|
imageView.setDrawingCacheEnabled(true);
|
|
imageView.setScaleType(ImageView.ScaleType.MATRIX);
|
|
addView(imageView);
|
|
|
|
detector = new CropGestureDetector(context);
|
|
detector.setOnGestureListener(this);
|
|
|
|
areaView = new CropAreaView(context);
|
|
areaView.setListener(this);
|
|
addView(areaView);
|
|
}
|
|
|
|
@Override
|
|
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
|
|
boolean result = super.drawChild(canvas, child, drawingTime);
|
|
if (child == imageView && paintingOverlay != null) {
|
|
canvas.save();
|
|
canvas.setMatrix(overlayMatrix);
|
|
paintingOverlay.draw(canvas);
|
|
canvas.restore();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public boolean isReady() {
|
|
return !detector.isScaling() && !detector.isDragging() && !areaView.isDragging();
|
|
}
|
|
|
|
public void setListener(CropViewListener l) {
|
|
listener = l;
|
|
}
|
|
|
|
public void setBottomPadding(float value) {
|
|
bottomPadding = value;
|
|
areaView.setBottomPadding(value);
|
|
}
|
|
|
|
public void setAspectRatio(float ratio) {
|
|
areaView.setActualRect(ratio);
|
|
}
|
|
|
|
public void setBitmap(Bitmap b, int rotation, boolean fform, boolean same, PaintingOverlay overlay) {
|
|
freeform = fform;
|
|
paintingOverlay = overlay;
|
|
if (b == null) {
|
|
bitmap = null;
|
|
state = null;
|
|
imageView.setImageDrawable(null);
|
|
} else {
|
|
bitmap = b;
|
|
if (state == null || !same) {
|
|
state = new CropState(bitmap, rotation);
|
|
imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
|
|
@Override
|
|
public boolean onPreDraw() {
|
|
reset();
|
|
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
|
|
return false;
|
|
}
|
|
});
|
|
} else {
|
|
state.updateBitmap(bitmap, rotation);
|
|
}
|
|
imageView.setImageBitmap(bitmap);
|
|
}
|
|
}
|
|
|
|
public void willShow() {
|
|
areaView.setFrameVisibility(true);
|
|
areaView.setDimVisibility(true);
|
|
areaView.invalidate();
|
|
}
|
|
|
|
public void hideBackView() {
|
|
backView.setVisibility(INVISIBLE);
|
|
}
|
|
|
|
public void showBackView() {
|
|
backView.setVisibility(VISIBLE);
|
|
}
|
|
|
|
public void setFreeform(boolean fform) {
|
|
areaView.setFreeform(fform);
|
|
freeform = fform;
|
|
}
|
|
|
|
public void show() {
|
|
backView.setVisibility(VISIBLE);
|
|
imageView.setVisibility(VISIBLE);
|
|
areaView.setDimVisibility(true);
|
|
areaView.setFrameVisibility(true);
|
|
areaView.invalidate();
|
|
}
|
|
|
|
public void hide() {
|
|
backView.setVisibility(INVISIBLE);
|
|
imageView.setVisibility(INVISIBLE);
|
|
areaView.setDimVisibility(false);
|
|
areaView.setFrameVisibility(false);
|
|
areaView.invalidate();
|
|
}
|
|
|
|
public void reset() {
|
|
areaView.resetAnimator();
|
|
|
|
areaView.setBitmap(bitmap, state.getBaseRotation() % 180 != 0, freeform);
|
|
areaView.setLockedAspectRatio(freeform ? 0.0f : 1.0f);
|
|
state.reset(areaView, 0, freeform);
|
|
areaView.getCropRect(initialAreaRect);
|
|
updateMatrix();
|
|
|
|
resetRotationStartScale();
|
|
|
|
if (listener != null) {
|
|
listener.onChange(true);
|
|
listener.onAspectLock(false);
|
|
}
|
|
}
|
|
|
|
public void updateMatrix() {
|
|
presentationMatrix.reset();
|
|
presentationMatrix.postTranslate(-state.getWidth() / 2, -state.getHeight() / 2);
|
|
presentationMatrix.postRotate(state.getOrientation());
|
|
state.getConcatMatrix(presentationMatrix);
|
|
presentationMatrix.postTranslate(areaView.getCropCenterX(), areaView.getCropCenterY());
|
|
imageView.setImageMatrix(presentationMatrix);
|
|
|
|
overlayMatrix.reset();
|
|
if (state.getBaseRotation() == 90 || state.getBaseRotation() == 270) {
|
|
overlayMatrix.postTranslate(-state.getHeight() / 2, -state.getWidth() / 2);
|
|
} else {
|
|
overlayMatrix.postTranslate(-state.getWidth() / 2, -state.getHeight() / 2);
|
|
}
|
|
overlayMatrix.postRotate(state.getOrientationOnly());
|
|
state.getConcatMatrix(overlayMatrix);
|
|
overlayMatrix.postTranslate(areaView.getCropCenterX(), areaView.getCropCenterY());
|
|
invalidate();
|
|
}
|
|
|
|
private void fillAreaView(RectF targetRect, boolean allowZoomOut) {
|
|
final float[] currentScale = new float[]{1.0f};
|
|
float scale = Math.max(targetRect.width() / areaView.getCropWidth(),
|
|
targetRect.height() / areaView.getCropHeight());
|
|
|
|
float newScale = state.getScale() * scale;
|
|
boolean ensureFit = false;
|
|
if (newScale > MAX_SCALE) {
|
|
scale = MAX_SCALE / state.getScale();
|
|
ensureFit = true;
|
|
}
|
|
float statusBarHeight = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0);
|
|
|
|
final float x = (targetRect.centerX() - imageView.getWidth() / 2) / areaView.getCropWidth() * state.getOrientedWidth();
|
|
final float y = (targetRect.centerY() - (imageView.getHeight() - bottomPadding + statusBarHeight) / 2) / areaView.getCropHeight() * state.getOrientedHeight();
|
|
final float targetScale = scale;
|
|
|
|
final boolean animEnsureFit = ensureFit;
|
|
|
|
ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
|
|
animator.addUpdateListener(animation -> {
|
|
float value = (Float) animation.getAnimatedValue();
|
|
float deltaScale = (1.0f + ((targetScale - 1.0f) * value)) / currentScale[0];
|
|
currentScale[0] *= deltaScale;
|
|
state.scale(deltaScale, x, y);
|
|
updateMatrix();
|
|
});
|
|
animator.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
if (animEnsureFit)
|
|
fitContentInBounds(false, false, true);
|
|
}
|
|
});
|
|
areaView.fill(targetRect, animator, true);
|
|
initialAreaRect.set(targetRect);
|
|
}
|
|
|
|
private float fitScale(RectF contentRect, float scale, float ratio) {
|
|
float scaledW = contentRect.width() * ratio;
|
|
float scaledH = contentRect.height() * ratio;
|
|
|
|
float scaledX = (contentRect.width() - scaledW) / 2.0f;
|
|
float scaledY = (contentRect.height() - scaledH) / 2.0f;
|
|
|
|
contentRect.set(contentRect.left + scaledX, contentRect.top + scaledY,
|
|
contentRect.left + scaledX + scaledW, contentRect.top + scaledY + scaledH);
|
|
|
|
return scale * ratio;
|
|
}
|
|
|
|
private void fitTranslation(RectF contentRect, RectF boundsRect, PointF translation, float radians) {
|
|
float frameLeft = boundsRect.left;
|
|
float frameTop = boundsRect.top;
|
|
float frameRight = boundsRect.right;
|
|
float frameBottom = boundsRect.bottom;
|
|
|
|
if (contentRect.left > frameLeft) {
|
|
frameRight += contentRect.left - frameLeft;
|
|
frameLeft = contentRect.left;
|
|
}
|
|
if (contentRect.top > frameTop) {
|
|
frameBottom += contentRect.top - frameTop;
|
|
frameTop = contentRect.top;
|
|
}
|
|
if (contentRect.right < frameRight) {
|
|
frameLeft += contentRect.right - frameRight;
|
|
}
|
|
if (contentRect.bottom < frameBottom) {
|
|
frameTop += contentRect.bottom - frameBottom;
|
|
}
|
|
|
|
float deltaX = boundsRect.centerX() - (frameLeft + boundsRect.width() / 2.0f);
|
|
float deltaY = boundsRect.centerY() - (frameTop + boundsRect.height() / 2.0f);
|
|
|
|
float xCompX = (float) (Math.sin(Math.PI / 2 - radians) * deltaX);
|
|
float xCompY = (float) (Math.cos(Math.PI / 2 - radians) * deltaX);
|
|
|
|
float yCompX = (float) (Math.cos(Math.PI / 2 + radians) * deltaY);
|
|
float yCompY = (float) (Math.sin(Math.PI / 2 + radians) * deltaY);
|
|
|
|
translation.set(translation.x + xCompX + yCompX, translation.y + xCompY + yCompY);
|
|
}
|
|
|
|
public RectF calculateBoundingBox(float w, float h, float rotation) {
|
|
RectF result = new RectF(0, 0, w, h);
|
|
Matrix m = new Matrix();
|
|
m.postRotate(rotation, w / 2.0f, h / 2.0f);
|
|
m.mapRect(result);
|
|
return result;
|
|
}
|
|
|
|
public float scaleWidthToMaxSize(RectF sizeRect, RectF maxSizeRect) {
|
|
float w = maxSizeRect.width();
|
|
float h = (float) Math.floor(w * sizeRect.height() / sizeRect.width());
|
|
if (h > maxSizeRect.height()) {
|
|
h = maxSizeRect.height();
|
|
w = (float) Math.floor(h * sizeRect.width() / sizeRect.height());
|
|
}
|
|
return w;
|
|
}
|
|
|
|
private static class CropRectangle {
|
|
float[] coords = new float[8];
|
|
|
|
CropRectangle() {
|
|
}
|
|
|
|
void setRect(RectF rect) {
|
|
coords[0] = rect.left;
|
|
coords[1] = rect.top;
|
|
coords[2] = rect.right;
|
|
coords[3] = rect.top;
|
|
coords[4] = rect.right;
|
|
coords[5] = rect.bottom;
|
|
coords[6] = rect.left;
|
|
coords[7] = rect.bottom;
|
|
}
|
|
|
|
void applyMatrix(Matrix m) {
|
|
m.mapPoints(coords);
|
|
}
|
|
|
|
void getRect(RectF rect) {
|
|
rect.set(coords[0], coords[1], coords[2], coords[7]);
|
|
}
|
|
}
|
|
|
|
private void fitContentInBounds(boolean allowScale, boolean maximize, boolean animated) {
|
|
fitContentInBounds(allowScale, maximize, animated, false);
|
|
}
|
|
|
|
private void fitContentInBounds(final boolean allowScale, final boolean maximize, final boolean animated, final boolean fast) {
|
|
if (state == null) {
|
|
return;
|
|
}
|
|
float boundsW = areaView.getCropWidth();
|
|
float boundsH = areaView.getCropHeight();
|
|
float contentW = state.getOrientedWidth();
|
|
float contentH = state.getOrientedHeight();
|
|
float rotation = state.getRotation();
|
|
float radians = (float) Math.toRadians(rotation);
|
|
|
|
RectF boundsRect = calculateBoundingBox(boundsW, boundsH, rotation);
|
|
RectF contentRect = new RectF(0.0f, 0.0f, contentW, contentH);
|
|
|
|
float initialX = (boundsW - contentW) / 2.0f;
|
|
float initialY = (boundsH - contentH) / 2.0f;
|
|
|
|
float scale = state.getScale();
|
|
|
|
tempRect.setRect(contentRect);
|
|
|
|
Matrix matrix = state.getMatrix();
|
|
matrix.preTranslate(initialX / scale, initialY / scale);
|
|
|
|
tempMatrix.reset();
|
|
tempMatrix.setTranslate(contentRect.centerX(), contentRect.centerY());
|
|
tempMatrix.setConcat(tempMatrix, matrix);
|
|
tempMatrix.preTranslate(-contentRect.centerX(), -contentRect.centerY());
|
|
tempRect.applyMatrix(tempMatrix);
|
|
|
|
tempMatrix.reset();
|
|
tempMatrix.preRotate(-rotation, contentW / 2.0f, contentH / 2.0f);
|
|
tempRect.applyMatrix(tempMatrix);
|
|
tempRect.getRect(contentRect);
|
|
|
|
PointF targetTranslation = new PointF(state.getX(), state.getY());
|
|
float targetScale = scale;
|
|
|
|
if (!contentRect.contains(boundsRect)) {
|
|
if (allowScale && (boundsRect.width() > contentRect.width() || boundsRect.height() > contentRect.height())) {
|
|
float ratio = boundsRect.width() / scaleWidthToMaxSize(boundsRect, contentRect);
|
|
targetScale = fitScale(contentRect, scale, ratio);
|
|
}
|
|
|
|
fitTranslation(contentRect, boundsRect, targetTranslation, radians);
|
|
} else if (maximize && rotationStartScale > 0) {
|
|
float ratio = boundsRect.width() / scaleWidthToMaxSize(boundsRect, contentRect);
|
|
float newScale = state.getScale() * ratio;
|
|
if (newScale < rotationStartScale)
|
|
ratio = 1.0f;
|
|
targetScale = fitScale(contentRect, scale, ratio);
|
|
|
|
fitTranslation(contentRect, boundsRect, targetTranslation, radians);
|
|
}
|
|
|
|
float dx = targetTranslation.x - state.getX();
|
|
float dy = targetTranslation.y - state.getY();
|
|
|
|
if (animated) {
|
|
final float animScale = targetScale / scale;
|
|
final float animDX = dx;
|
|
final float animDY = dy;
|
|
|
|
if (Math.abs(animScale - 1.0f) < EPSILON
|
|
&& Math.abs(animDX) < EPSILON && Math.abs(animDY) < EPSILON) {
|
|
return;
|
|
}
|
|
|
|
animating = true;
|
|
|
|
final float[] currentValues = new float[]{1.0f, 0.0f, 0.0f};
|
|
ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
|
|
animator.addUpdateListener(animation -> {
|
|
float value = (Float) animation.getAnimatedValue();
|
|
|
|
float deltaX = animDX * value - currentValues[1];
|
|
currentValues[1] += deltaX;
|
|
float deltaY = animDY * value - currentValues[2];
|
|
currentValues[2] += deltaY;
|
|
state.translate(deltaX * currentValues[0], deltaY * currentValues[0]);
|
|
|
|
float deltaScale = (1.0f + ((animScale - 1.0f) * value)) / currentValues[0];
|
|
currentValues[0] *= deltaScale;
|
|
state.scale(deltaScale, 0, 0);
|
|
|
|
updateMatrix();
|
|
});
|
|
animator.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
animating = false;
|
|
|
|
if (!fast)
|
|
fitContentInBounds(allowScale, maximize, animated, true);
|
|
}
|
|
});
|
|
animator.setInterpolator(areaView.getInterpolator());
|
|
animator.setDuration(fast ? 100 : 200);
|
|
animator.start();
|
|
} else {
|
|
state.translate(dx, dy);
|
|
state.scale(targetScale / scale, 0, 0);
|
|
updateMatrix();
|
|
}
|
|
}
|
|
|
|
public void rotate90Degrees() {
|
|
if (state == null) {
|
|
return;
|
|
}
|
|
areaView.resetAnimator();
|
|
|
|
resetRotationStartScale();
|
|
|
|
float orientation = (state.getOrientation() - state.getBaseRotation() - 90.0f) % 360;
|
|
|
|
boolean fform = freeform;
|
|
if (freeform && areaView.getLockAspectRatio() > 0) {
|
|
areaView.setLockedAspectRatio(1.0f / areaView.getLockAspectRatio());
|
|
areaView.setActualRect(areaView.getLockAspectRatio());
|
|
fform = false;
|
|
} else {
|
|
areaView.setBitmap(bitmap, (orientation + state.getBaseRotation()) % 180 != 0, freeform);
|
|
}
|
|
|
|
state.reset(areaView, orientation, fform);
|
|
updateMatrix();
|
|
|
|
if (listener != null)
|
|
listener.onChange(orientation == 0 && areaView.getLockAspectRatio() == 0);
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
if (animating) {
|
|
return true;
|
|
}
|
|
boolean result = false;
|
|
if (areaView.onTouchEvent(event))
|
|
return true;
|
|
|
|
switch (event.getAction()) {
|
|
case MotionEvent.ACTION_DOWN:
|
|
onScrollChangeBegan();
|
|
break;
|
|
case MotionEvent.ACTION_UP:
|
|
case MotionEvent.ACTION_CANCEL:
|
|
onScrollChangeEnded();
|
|
break;
|
|
}
|
|
try {
|
|
result = detector.onTouchEvent(event);
|
|
} catch (Exception ignore) {
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onAreaChangeBegan() {
|
|
areaView.getCropRect(previousAreaRect);
|
|
resetRotationStartScale();
|
|
|
|
if (listener != null) {
|
|
listener.onChange(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onAreaChange() {
|
|
areaView.setGridType(CropAreaView.GridType.MAJOR, false);
|
|
|
|
float x = previousAreaRect.centerX() - areaView.getCropCenterX();
|
|
float y = previousAreaRect.centerY() - areaView.getCropCenterY();
|
|
state.translate(x, y);
|
|
updateMatrix();
|
|
|
|
areaView.getCropRect(previousAreaRect);
|
|
|
|
fitContentInBounds(true, false, false);
|
|
}
|
|
|
|
@Override
|
|
public void onAreaChangeEnded() {
|
|
areaView.setGridType(CropAreaView.GridType.NONE, true);
|
|
fillAreaView(areaView.getTargetRectToFill(), false);
|
|
}
|
|
|
|
public void onDrag(float dx, float dy) {
|
|
if (animating) {
|
|
return;
|
|
}
|
|
|
|
state.translate(dx, dy);
|
|
updateMatrix();
|
|
}
|
|
|
|
public void onFling(float startX, float startY, float velocityX, float velocityY) {
|
|
}
|
|
|
|
public void onScrollChangeBegan() {
|
|
if (animating) {
|
|
return;
|
|
}
|
|
|
|
areaView.setGridType(CropAreaView.GridType.MAJOR, true);
|
|
resetRotationStartScale();
|
|
|
|
if (listener != null) {
|
|
listener.onChange(false);
|
|
}
|
|
}
|
|
|
|
public void onScrollChangeEnded() {
|
|
areaView.setGridType(CropAreaView.GridType.NONE, true);
|
|
fitContentInBounds(true, false, true);
|
|
}
|
|
|
|
public void onScale(float scale, float x, float y) {
|
|
if (animating) {
|
|
return;
|
|
}
|
|
|
|
float newScale = state.getScale() * scale;
|
|
if (newScale > MAX_SCALE)
|
|
scale = MAX_SCALE / state.getScale();
|
|
|
|
float statusBarHeight = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0);
|
|
|
|
float pivotX = (x - imageView.getWidth() / 2) / areaView.getCropWidth() * state.getOrientedWidth();
|
|
float pivotY = (y - (imageView.getHeight() - bottomPadding - statusBarHeight) / 2) / areaView.getCropHeight() * state.getOrientedHeight();
|
|
|
|
state.scale(scale, pivotX, pivotY);
|
|
updateMatrix();
|
|
}
|
|
|
|
public void onRotationBegan() {
|
|
areaView.setGridType(CropAreaView.GridType.MINOR, false);
|
|
if (rotationStartScale < 0.00001f) {
|
|
rotationStartScale = state.getScale();
|
|
}
|
|
}
|
|
|
|
public void onRotationEnded() {
|
|
areaView.setGridType(CropAreaView.GridType.NONE, true);
|
|
}
|
|
|
|
private void resetRotationStartScale() {
|
|
rotationStartScale = 0.0f;
|
|
}
|
|
|
|
public void setRotation(float angle) {
|
|
float deltaAngle = angle - state.getRotation();
|
|
state.rotate(deltaAngle, 0, 0);
|
|
fitContentInBounds(true, true, false);
|
|
}
|
|
|
|
@SuppressLint("WrongThread")
|
|
private void editBitmap(String path, Bitmap b, Canvas canvas, Bitmap canvasBitmap, Bitmap.CompressFormat format, float scale, ArrayList<VideoEditedInfo.MediaEntity> entities, boolean clear) {
|
|
try {
|
|
if (clear) {
|
|
canvasBitmap.eraseColor(0);
|
|
}
|
|
if (b == null) {
|
|
b = BitmapFactory.decodeFile(path);
|
|
}
|
|
float sc = Math.max(b.getWidth(), b.getHeight()) / (float) Math.max(bitmap.getWidth(), bitmap.getHeight());
|
|
Matrix matrix = new Matrix();
|
|
matrix.postTranslate(-b.getWidth() / 2, -b.getHeight() / 2);
|
|
matrix.postScale(1.0f / sc, 1.0f / sc);
|
|
matrix.postRotate(state.getOrientationOnly());
|
|
state.getConcatMatrix(matrix);
|
|
matrix.postScale(scale, scale);
|
|
matrix.postTranslate(canvasBitmap.getWidth() / 2, canvasBitmap.getHeight() / 2);
|
|
canvas.drawBitmap(b, matrix, new Paint(FILTER_BITMAP_FLAG));
|
|
FileOutputStream stream = new FileOutputStream(new File(path));
|
|
canvasBitmap.compress(format, 87, stream);
|
|
stream.close();
|
|
|
|
if (entities != null && !entities.isEmpty()) {
|
|
float[] point = new float[4];
|
|
float newScale = 1.0f / sc * scale * state.scale;
|
|
for (int a = 0, N = entities.size(); a < N; a++) {
|
|
VideoEditedInfo.MediaEntity entity = entities.get(a);
|
|
|
|
point[0] = entity.x * b.getWidth() + entity.viewWidth * entity.scale / 2;
|
|
point[1] = entity.y * b.getHeight() + entity.viewHeight * entity.scale / 2;
|
|
point[2] = entity.textViewX * b.getWidth();
|
|
point[3] = entity.textViewY * b.getHeight();
|
|
matrix.mapPoints(point);
|
|
|
|
float widthScale = b.getWidth() / (float) canvasBitmap.getWidth();
|
|
newScale *= widthScale;
|
|
if (entity.type == 0) {
|
|
entity.viewWidth = entity.viewHeight = canvasBitmap.getWidth() / 2;
|
|
} else if (entity.type == 1) {
|
|
entity.fontSize = canvasBitmap.getWidth() / 9;
|
|
}
|
|
entity.scale *= newScale;
|
|
|
|
entity.x = (point[0] - entity.viewWidth * entity.scale / 2) / canvasBitmap.getWidth();
|
|
entity.y = (point[1] - entity.viewHeight * entity.scale / 2) / canvasBitmap.getHeight();
|
|
entity.textViewX = point[2] / canvasBitmap.getWidth();
|
|
entity.textViewY = point[3] / canvasBitmap.getHeight();
|
|
|
|
entity.width = entity.viewWidth * entity.scale / canvasBitmap.getWidth();
|
|
entity.height = entity.viewHeight * entity.scale / canvasBitmap.getHeight();
|
|
|
|
entity.textViewWidth = entity.viewWidth / (float) canvasBitmap.getWidth();
|
|
entity.textViewHeight = entity.viewHeight / (float) canvasBitmap.getHeight();
|
|
|
|
entity.rotation -= (state.getRotation() + state.getOrientationOnly()) * (Math.PI / 180);
|
|
}
|
|
}
|
|
|
|
b.recycle();
|
|
} catch (Throwable e) {
|
|
FileLog.e(e);
|
|
}
|
|
}
|
|
|
|
public Bitmap getResult(MediaController.MediaEditState editState) {
|
|
if (state == null || !state.hasChanges() && state.getBaseRotation() < EPSILON && freeform) {
|
|
return bitmap;
|
|
}
|
|
|
|
RectF cropRect = new RectF();
|
|
areaView.getCropRect(cropRect);
|
|
RectF sizeRect = new RectF(0, 0, RESULT_SIDE, RESULT_SIDE);
|
|
|
|
float w = scaleWidthToMaxSize(cropRect, sizeRect);
|
|
int width = (int) Math.ceil(w);
|
|
int height = (int) (Math.ceil(width / areaView.getAspectRatio()));
|
|
float scale = width / areaView.getCropWidth();
|
|
|
|
Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
|
|
Matrix matrix = new Matrix();
|
|
matrix.postTranslate(-state.getWidth() / 2, -state.getHeight() / 2);
|
|
matrix.postRotate(state.getOrientation());
|
|
state.getConcatMatrix(matrix);
|
|
matrix.postScale(scale, scale);
|
|
matrix.postTranslate(width / 2, height / 2);
|
|
|
|
Canvas canvas = new Canvas(resultBitmap);
|
|
|
|
if (editState.paintPath != null) {
|
|
editBitmap(editState.paintPath, null, canvas, resultBitmap, Bitmap.CompressFormat.PNG, scale, null, false);
|
|
if (!editState.paintPath.equals(editState.fullPaintPath)) {
|
|
editBitmap(editState.fullPaintPath, null, canvas, resultBitmap, Bitmap.CompressFormat.PNG, scale, editState.mediaEntities, true);
|
|
}
|
|
}
|
|
if (editState.filterPath != null) {
|
|
if (editState.croppedPath == null) {
|
|
File f = new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE), SharedConfig.getLastLocalId() + "_temp.jpg");
|
|
editState.croppedPath = f.getAbsolutePath();
|
|
}
|
|
Bitmap b = ImageLoader.loadBitmap(editState.getPath(), null, bitmap.getWidth(), bitmap.getHeight(), true);
|
|
editBitmap(editState.croppedPath, b, canvas, resultBitmap, Bitmap.CompressFormat.JPEG, scale, null, false);
|
|
}
|
|
|
|
canvas.drawBitmap(bitmap, matrix, new Paint(FILTER_BITMAP_FLAG));
|
|
return resultBitmap;
|
|
}
|
|
|
|
private void setLockedAspectRatio(float aspectRatio) {
|
|
areaView.setLockedAspectRatio(aspectRatio);
|
|
RectF targetRect = new RectF();
|
|
areaView.calculateRect(targetRect, aspectRatio);
|
|
fillAreaView(targetRect, true);
|
|
|
|
if (listener != null) {
|
|
listener.onChange(false);
|
|
listener.onAspectLock(true);
|
|
}
|
|
}
|
|
|
|
public void showAspectRatioDialog() {
|
|
if (state == null) {
|
|
return;
|
|
}
|
|
if (areaView.getLockAspectRatio() > 0) {
|
|
areaView.setLockedAspectRatio(0);
|
|
|
|
if (listener != null) {
|
|
listener.onAspectLock(false);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (hasAspectRatioDialog) {
|
|
return;
|
|
}
|
|
|
|
hasAspectRatioDialog = true;
|
|
|
|
String[] actions = new String[8];
|
|
|
|
final Integer[][] ratios = new Integer[][]{
|
|
new Integer[]{3, 2},
|
|
new Integer[]{5, 3},
|
|
new Integer[]{4, 3},
|
|
new Integer[]{5, 4},
|
|
new Integer[]{7, 5},
|
|
new Integer[]{16, 9}
|
|
};
|
|
|
|
actions[0] = LocaleController.getString("CropOriginal", R.string.CropOriginal);
|
|
actions[1] = LocaleController.getString("CropSquare", R.string.CropSquare);
|
|
|
|
int i = 2;
|
|
for (Integer[] ratioPair : ratios) {
|
|
if (areaView.getAspectRatio() > 1.0f) {
|
|
actions[i] = String.format("%d:%d", ratioPair[0], ratioPair[1]);
|
|
} else {
|
|
actions[i] = String.format("%d:%d", ratioPair[1], ratioPair[0]);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
AlertDialog dialog = new AlertDialog.Builder(getContext())
|
|
.setItems(actions, (dialog12, which) -> {
|
|
hasAspectRatioDialog = false;
|
|
switch (which) {
|
|
case 0: {
|
|
float w = state.getBaseRotation() % 180 != 0 ? state.getHeight() : state.getWidth();
|
|
float h = state.getBaseRotation() % 180 != 0 ? state.getWidth() : state.getHeight();
|
|
setLockedAspectRatio(w / h);
|
|
}
|
|
break;
|
|
|
|
case 1: {
|
|
setLockedAspectRatio(1.0f);
|
|
}
|
|
break;
|
|
|
|
default: {
|
|
Integer[] ratioPair = ratios[which - 2];
|
|
|
|
if (areaView.getAspectRatio() > 1.0f) {
|
|
setLockedAspectRatio(ratioPair[0] / (float) ratioPair[1]);
|
|
} else {
|
|
setLockedAspectRatio(ratioPair[1] / (float) ratioPair[0]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
})
|
|
.create();
|
|
dialog.setCanceledOnTouchOutside(true);
|
|
dialog.setOnCancelListener(dialog1 -> hasAspectRatioDialog = false);
|
|
dialog.show();
|
|
}
|
|
|
|
public void updateLayout() {
|
|
float w = areaView.getCropWidth();
|
|
if (w == 0) {
|
|
return;
|
|
}
|
|
if (state != null) {
|
|
areaView.calculateRect(initialAreaRect, state.getWidth() / state.getHeight());
|
|
areaView.setActualRect(areaView.getAspectRatio());
|
|
areaView.getCropRect(previousAreaRect);
|
|
|
|
float ratio = areaView.getCropWidth() / w;
|
|
state.scale(ratio, 0, 0);
|
|
updateMatrix();
|
|
}
|
|
}
|
|
|
|
public float getCropLeft() {
|
|
return areaView.getCropLeft();
|
|
}
|
|
|
|
public float getCropTop() {
|
|
return areaView.getCropTop();
|
|
}
|
|
|
|
public float getCropWidth() {
|
|
return areaView.getCropWidth();
|
|
}
|
|
|
|
public float getCropHeight() {
|
|
return areaView.getCropHeight();
|
|
}
|
|
}
|