diff --git a/app_pojavlauncher/build.gradle b/app_pojavlauncher/build.gradle index d53d80841a..49d842fd65 100644 --- a/app_pojavlauncher/build.gradle +++ b/app_pojavlauncher/build.gradle @@ -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' diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/ImageCropperView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/ImageCropperView.java index 43c4f07dc5..d2068ab0d4 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/ImageCropperView.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/ImageCropperView.java @@ -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; @@ -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(); @@ -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); } @@ -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) { @@ -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(); } @@ -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); } @@ -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); @@ -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; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/CropperUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/CropperUtils.java index 35c0361dac..8e66744a95 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/CropperUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/CropperUtils.java @@ -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; @@ -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)); @@ -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") diff --git a/app_pojavlauncher/src/main/res/layout/dialog_cropper.xml b/app_pojavlauncher/src/main/res/layout/dialog_cropper.xml index 3fce4e5d87..abfa4db5e7 100644 --- a/app_pojavlauncher/src/main/res/layout/dialog_cropper.xml +++ b/app_pojavlauncher/src/main/res/layout/dialog_cropper.xml @@ -1,10 +1,48 @@ - + android:layout_height="wrap_content"> + - \ No newline at end of file + 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" /> + + + +