mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2026-04-18 16:46:58 -04:00
Feat[cropper]: more QOL features
This commit is contained in:
@@ -206,7 +206,6 @@ dependencies {
|
||||
implementation 'com.github.Mathias-Boulay:android_gamepad_remapper:a0fe7e72f2'
|
||||
implementation 'com.github.Mathias-Boulay:virtual-joystick-android:2e7aa25e50'
|
||||
|
||||
|
||||
// implementation 'com.intuit.sdp:sdp-android:1.0.5'
|
||||
// implementation 'com.intuit.ssp:ssp-android:1.0.5'
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
@@ -18,17 +19,23 @@ import net.kdt.pojavlaunch.Tools;
|
||||
import top.defaults.checkerboarddrawable.CheckerboardDrawable;
|
||||
|
||||
public class ImageCropperView extends AppCompatImageView {
|
||||
private final Matrix mZoomMatrix = new Matrix();
|
||||
private final Matrix mTranslateMatrix = new Matrix();
|
||||
|
||||
private final Matrix mTranslateInverse = new Matrix();
|
||||
private final Matrix mTranslateMatrix = new Matrix();
|
||||
private final Matrix mPrescaleMatrix = new Matrix();
|
||||
private final Matrix mImageMatrix = new Matrix();
|
||||
private final Matrix mZoomMatrix = new Matrix();
|
||||
private final RectF mSelectionHighlight = new RectF();
|
||||
private final Rect mSelectionRect = new Rect();
|
||||
public boolean horizontalLock, verticalLock;
|
||||
private float mLastTouchX, mLastTouchY;
|
||||
private float mHighlightThickness;
|
||||
private float mLastDistance = -1f;
|
||||
private int mLastTrackedPointer;
|
||||
private final Rect mSelectionFrameRect = new Rect();
|
||||
private Paint mSelectionPaint;
|
||||
private float mSelectionPadding;
|
||||
private Bitmap mOriginalBitmap;
|
||||
private Paint mSelectionPaint;
|
||||
|
||||
public ImageCropperView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
@@ -48,9 +55,13 @@ public class ImageCropperView extends AppCompatImageView {
|
||||
setBackground(new CheckerboardDrawable.Builder().build());
|
||||
setScaleType(ScaleType.MATRIX);
|
||||
mSelectionPadding = Tools.dpToPx(24);
|
||||
mHighlightThickness = Tools.dpToPx(1);
|
||||
mSelectionPaint = new Paint();
|
||||
mSelectionPaint.setColor(Color.RED);
|
||||
mSelectionPaint.setStrokeWidth(Tools.dpToPx(1));
|
||||
mSelectionPaint.setStrokeWidth(mHighlightThickness);
|
||||
// Divide the thickness by 2 since we will be needing only half of it for
|
||||
// rect highlight correction.
|
||||
mHighlightThickness /= 2;
|
||||
mSelectionPaint.setStyle(Paint.Style.STROKE);
|
||||
}
|
||||
|
||||
@@ -123,7 +134,7 @@ public class ImageCropperView extends AppCompatImageView {
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawRect(mSelectionFrameRect, mSelectionPaint);
|
||||
canvas.drawRect(mSelectionHighlight, mSelectionPaint);
|
||||
}
|
||||
|
||||
private int findPointerIndex(MotionEvent event, int id) {
|
||||
@@ -134,6 +145,8 @@ public class ImageCropperView extends AppCompatImageView {
|
||||
}
|
||||
|
||||
private void pan(int panX, int panY) {
|
||||
if(horizontalLock) panX = 0;
|
||||
if(verticalLock) panY = 0;
|
||||
mTranslateMatrix.postTranslate(panX, panY);
|
||||
computeImageMatrix();
|
||||
}
|
||||
@@ -151,9 +164,9 @@ public class ImageCropperView extends AppCompatImageView {
|
||||
}
|
||||
|
||||
private void computeImageMatrix() {
|
||||
mImageMatrix.reset();
|
||||
mImageMatrix.preConcat(mTranslateMatrix);
|
||||
mImageMatrix.preConcat(mZoomMatrix);
|
||||
mImageMatrix.set(mPrescaleMatrix);
|
||||
mImageMatrix.postConcat(mZoomMatrix);
|
||||
mImageMatrix.postConcat(mTranslateMatrix);
|
||||
setImageMatrix(mImageMatrix);
|
||||
}
|
||||
|
||||
@@ -169,10 +182,10 @@ public class ImageCropperView extends AppCompatImageView {
|
||||
// By inverting the matrix we will effectively "divide" our rectangle by it, thus getting
|
||||
// its two points on the bitmap's surface. Math be cool indeed.
|
||||
float[] src = new float[] {
|
||||
mSelectionFrameRect.left,
|
||||
mSelectionFrameRect.top,
|
||||
mSelectionFrameRect.right,
|
||||
mSelectionFrameRect.bottom
|
||||
mSelectionRect.left,
|
||||
mSelectionRect.top,
|
||||
mSelectionRect.right,
|
||||
mSelectionRect.bottom
|
||||
};
|
||||
float[] dst = new float[4];
|
||||
imageInverse.mapPoints(dst, 0, src, 0, 2);
|
||||
@@ -215,18 +228,54 @@ public class ImageCropperView extends AppCompatImageView {
|
||||
// Calculate the corners of the new selection frame. It should always appear at the center of the view.
|
||||
int centerShiftX = (w - lesserDimension) / 2;
|
||||
int centerShiftY = (h - lesserDimension) / 2;
|
||||
mSelectionFrameRect.left = centerShiftX;
|
||||
mSelectionFrameRect.top = centerShiftY;
|
||||
mSelectionFrameRect.right = centerShiftX + lesserDimension;
|
||||
mSelectionFrameRect.bottom = centerShiftY + lesserDimension;
|
||||
mSelectionRect.left = centerShiftX;
|
||||
mSelectionRect.top = centerShiftY;
|
||||
mSelectionRect.right = centerShiftX + lesserDimension;
|
||||
mSelectionRect.bottom = centerShiftY + lesserDimension;
|
||||
// Adjust the selection highlight rectnagle to be bigger than the selection area
|
||||
// by the highlight thickness, to make sure that the entire inside of the selection highlight
|
||||
// will fit into the image
|
||||
mSelectionHighlight.left = mSelectionRect.left - mHighlightThickness;
|
||||
mSelectionHighlight.top = mSelectionRect.top + mHighlightThickness;
|
||||
mSelectionHighlight.right = mSelectionRect.right + mHighlightThickness;
|
||||
mSelectionHighlight.bottom = mSelectionRect.bottom - mHighlightThickness;
|
||||
computePrescaleMatrix();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
/**
|
||||
* Computes a prescale matrix.
|
||||
* This matrix basically centers the source image in the selection rect.
|
||||
* Mainly intended for convenience of implementing a "Reset" button sometime in the future
|
||||
*/
|
||||
private void computePrescaleMatrix() {
|
||||
if(mOriginalBitmap == null) return;
|
||||
int selectionRectWidth = mSelectionRect.width();
|
||||
int selectionRectHeight = mSelectionRect.height();
|
||||
int imageWidth = mOriginalBitmap.getWidth();
|
||||
int imageHeight = mOriginalBitmap.getHeight();
|
||||
float hRatio = (float)selectionRectWidth / imageWidth ;
|
||||
float vRatio = (float)selectionRectHeight / imageHeight;
|
||||
float ratio = Math.min (hRatio, vRatio);
|
||||
float centerShift_x = (selectionRectWidth - imageWidth*ratio) / 2;
|
||||
float centerShift_y = (selectionRectWidth - imageHeight*ratio) / 2;
|
||||
centerShift_x += mSelectionRect.left;
|
||||
centerShift_y += mSelectionRect.top;
|
||||
mPrescaleMatrix.reset();
|
||||
mPrescaleMatrix.postScale(ratio, ratio);
|
||||
mPrescaleMatrix.postTranslate(centerShift_x, centerShift_y);
|
||||
computeImageMatrix();
|
||||
}
|
||||
|
||||
public void resetTransforms() {
|
||||
mTranslateMatrix.reset();
|
||||
mZoomMatrix.reset();
|
||||
mTranslateInverse.reset();
|
||||
mImageMatrix.reset();
|
||||
setImageMatrix(mImageMatrix);
|
||||
computeImageMatrix();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
computePrescaleMatrix();
|
||||
resetTransforms();
|
||||
mLastDistance = -1f;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
@@ -31,14 +32,14 @@ public class CropperUtils {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.cropper_title);
|
||||
builder.setView(R.layout.dialog_cropper);
|
||||
// The default cropper options
|
||||
//cropImageView.setPadding(padding, padding, padding, 0);
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
AlertDialog dialog = builder.show();
|
||||
ImageCropperView cropImageView = dialog.findViewById(R.id.crop_dialog_view);
|
||||
assert cropImageView != null;
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(selectedUri)){
|
||||
cropImageView.loadBitmap(BitmapFactory.decodeStream(inputStream));
|
||||
bindViews(dialog, cropImageView);
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v->{
|
||||
dialog.dismiss();
|
||||
cropperListener.onCropped(cropImageView.crop(256));
|
||||
@@ -49,17 +50,22 @@ public class CropperUtils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static int getDialogPadding(Context context) {
|
||||
TypedValue typedPadding = new TypedValue();
|
||||
if(context.getTheme().resolveAttribute(R.attr.dialogPreferredPadding, typedPadding, true)) {
|
||||
return TypedValue.complexToDimensionPixelSize(
|
||||
typedPadding.data,
|
||||
context.getResources().getDisplayMetrics()
|
||||
);
|
||||
}else {
|
||||
return 0;
|
||||
}
|
||||
private static void bindViews(AlertDialog alertDialog, ImageCropperView imageCropperView) {
|
||||
ToggleButton horizontalLock = alertDialog.findViewById(R.id.crop_dialog_hlock);
|
||||
ToggleButton verticalLock = alertDialog.findViewById(R.id.crop_dialog_vlock);
|
||||
View reset = alertDialog.findViewById(R.id.crop_dialog_reset);
|
||||
assert horizontalLock != null;
|
||||
assert verticalLock != null;
|
||||
assert reset != null;
|
||||
horizontalLock.setOnClickListener(v->
|
||||
imageCropperView.horizontalLock = horizontalLock.isChecked()
|
||||
);
|
||||
verticalLock.setOnClickListener(v->
|
||||
imageCropperView.verticalLock = verticalLock.isChecked()
|
||||
);
|
||||
reset.setOnClickListener(v->
|
||||
imageCropperView.resetTransforms()
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -1,10 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<net.kdt.pojavlaunch.imgcropper.ImageCropperView
|
||||
android:id="@+id/crop_dialog_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/crop_dialog_view"
|
||||
app:layout_constraintStart_toStartOf="@+id/crop_dialog_view"
|
||||
app:layout_constraintEnd_toEndOf="@+id/crop_dialog_view">
|
||||
<ToggleButton
|
||||
android:id="@+id/crop_dialog_hlock"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:textOn="@string/cropper_lock_horizontal"
|
||||
android:textOff="@string/cropper_lock_horizontal"/>
|
||||
<ToggleButton
|
||||
android:id="@+id/crop_dialog_vlock"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:textOn="@string/cropper_lock_vertical"
|
||||
android:textOff="@string/cropper_lock_vertical"/>
|
||||
<Button
|
||||
android:id="@+id/crop_dialog_reset"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/cropper_reset"/>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -372,4 +372,7 @@
|
||||
<string name="newdl_downloading_metadata">Downloading game metadata (%s)</string>
|
||||
<string name="newdl_downloading_game_files">Downloading game files… (%d/%d, %.2f MB)</string>
|
||||
<string name="cropper_title">Select image region</string>
|
||||
<string name="cropper_lock_vertical">V. lock</string>
|
||||
<string name="cropper_lock_horizontal">H. lock</string>
|
||||
<string name="cropper_reset">Reset</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user