Skip to content

Commit

Permalink
Feat[cropper]: more QOL features
Browse files Browse the repository at this point in the history
  • Loading branch information
artdeell committed Dec 23, 2023
1 parent 97668e7 commit 79ea681
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 40 deletions.
1 change: 0 additions & 1 deletion app_pojavlauncher/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,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'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;

Expand All @@ -18,17 +19,23 @@
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();
Expand All @@ -48,9 +55,13 @@ private void init() {
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);
}

Expand Down Expand Up @@ -123,7 +134,7 @@ public boolean onTouchEvent(MotionEvent event) {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(mSelectionFrameRect, mSelectionPaint);
canvas.drawRect(mSelectionHighlight, mSelectionPaint);
}

private int findPointerIndex(MotionEvent event, int id) {
Expand All @@ -134,6 +145,8 @@ private int findPointerIndex(MotionEvent event, int id) {
}

private void pan(int panX, int panY) {
if(horizontalLock) panX = 0;
if(verticalLock) panY = 0;
mTranslateMatrix.postTranslate(panX, panY);
computeImageMatrix();
}
Expand All @@ -151,9 +164,9 @@ private void zoom(float zoomLevel, float midpointX, float midpointY) {
}

private void computeImageMatrix() {
mImageMatrix.reset();
mImageMatrix.preConcat(mTranslateMatrix);
mImageMatrix.preConcat(mZoomMatrix);
mImageMatrix.set(mPrescaleMatrix);
mImageMatrix.postConcat(mZoomMatrix);
mImageMatrix.postConcat(mTranslateMatrix);
setImageMatrix(mImageMatrix);
}

Expand All @@ -169,10 +182,10 @@ public Bitmap crop(int targetMaxSide) {
// 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);
Expand Down Expand Up @@ -215,18 +228,54 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
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;
Expand All @@ -31,14 +32,14 @@ private static void openCropperDialog(Context context, Uri selectedUri,
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));
Expand All @@ -49,17 +50,22 @@ private static void openCropperDialog(Context context, Uri selectedUri,
}
}


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")
Expand Down
48 changes: 43 additions & 5 deletions app_pojavlauncher/src/main/res/layout/dialog_cropper.xml
Original file line number Diff line number Diff line change
@@ -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>
3 changes: 3 additions & 0 deletions app_pojavlauncher/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>

0 comments on commit 79ea681

Please sign in to comment.