Feat[cropper]: more QOL features

This commit is contained in:
artdeell
2023-12-23 22:12:22 +03:00
committed by Maksim Belov
parent 0510bf5015
commit 240f5d299d
5 changed files with 135 additions and 40 deletions

View File

@@ -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'

View File

@@ -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;
}

View File

@@ -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")

View File

@@ -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>

View File

@@ -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>