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