diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java index d1ec63b11d..161ac61a55 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.util.Base64; import android.util.Base64OutputStream; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -53,7 +54,7 @@ public class ProfileEditorFragment extends Fragment implements CropperUtils.Crop private EditText mDefaultName, mDefaultJvmArgument; private TextView mDefaultPath, mDefaultVersion, mDefaultControl; private ImageView mProfileIcon; - private ActivityResultLauncher mCropperLauncher = CropperUtils.registerCropper(this, this); + private final ActivityResultLauncher mCropperLauncher = CropperUtils.registerCropper(this, this); private List mRenderNames; @@ -131,9 +132,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat })); // Set up the icon change click listener - mProfileIcon.setOnClickListener(v ->{ - CropperUtils.startCropper(mCropperLauncher, v.getContext()); - }); + mProfileIcon.setOnClickListener(v -> CropperUtils.startCropper(mCropperLauncher)); @@ -235,6 +234,7 @@ private void save(){ @Override public void onCropped(Bitmap contentBitmap) { mProfileIcon.setImageBitmap(contentBitmap); + Log.i("bitmap", "w="+contentBitmap.getWidth() +" h="+contentBitmap.getHeight()); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try (Base64OutputStream base64OutputStream = new Base64OutputStream(byteArrayOutputStream, Base64.NO_WRAP)) { contentBitmap.compress(Bitmap.CompressFormat.PNG, 60, base64OutputStream); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/CropperView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/CropperView.java index 3be2591edd..5c31875e7d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/CropperView.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/CropperView.java @@ -28,7 +28,7 @@ public class CropperView extends View { private float mSelectionPadding; private int mLastTrackedPointer; private Paint mSelectionPaint; - public CropperBehaviour cropperBehaviour = CropperBehaviour.DUMMY; + private CropperBehaviour mCropperBehaviour = CropperBehaviour.DUMMY; public CropperView(Context context) { super(context); @@ -74,7 +74,7 @@ public boolean dispatchGenericMotionEvent(MotionEvent event) { float multiplier = 0.005f; float midpointX = (x1 + x2) / 2; float midpointY = (y1 + y2) / 2; - cropperBehaviour.zoom(1 + distanceDelta * multiplier, midpointX, midpointY); + mCropperBehaviour.zoom(1 + distanceDelta * multiplier, midpointX, midpointY); } mLastDistance = distance; return true; @@ -106,7 +106,7 @@ public boolean dispatchGenericMotionEvent(MotionEvent event) { } if(trackedIndex != -1) { // If we still track out current pointer, pan the image by the movement delta - cropperBehaviour.pan(x1 - mLastTouchX, y1 - mLastTouchY); + mCropperBehaviour.pan(x1 - mLastTouchX, y1 - mLastTouchY); } else { // Otherwise, mark the new tracked pointer without panning. mLastTrackedPointer = event.getPointerId(0); @@ -121,7 +121,7 @@ public boolean dispatchGenericMotionEvent(MotionEvent event) { protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); - cropperBehaviour.drawPreHighlight(canvas); + mCropperBehaviour.drawPreHighlight(canvas); canvas.restore(); canvas.drawRect(mSelectionHighlight, mSelectionPaint); } @@ -150,7 +150,7 @@ protected void onSizeChanged(int w, int h, int oldW, int oldH) { mSelectionRect.top = centerShiftY; mSelectionRect.right = centerShiftX + lesserDimension; mSelectionRect.bottom = centerShiftY + lesserDimension; - cropperBehaviour.onSelectionRectUpdated(); + mCropperBehaviour.onSelectionRectUpdated(); // Adjust the selection highlight rectangle 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 @@ -169,7 +169,7 @@ protected void onMeasure(int widthSpec, int heightSpec) { setMeasuredDimension(widthSize, heightSize); return; } - int biggestAllowedDimension = cropperBehaviour.getLargestImageSide(); + int biggestAllowedDimension = mCropperBehaviour.getLargestImageSide(); if(widthMode == MeasureSpec.EXACTLY) biggestAllowedDimension = widthSize; if(heightMode == MeasureSpec.EXACTLY) biggestAllowedDimension = heightSize; setMeasuredDimension( @@ -191,12 +191,21 @@ private int pickDesiredDimension(int mode, int size, int desired) { return desired; } + public void setCropperBehaviour(CropperBehaviour cropperBehaviour) { + this.mCropperBehaviour = cropperBehaviour; + cropperBehaviour.onSelectionRectUpdated(); + } + + public void resetTransforms() { + mCropperBehaviour.resetTransforms(); + } + @CallSuper protected void reset() { mLastDistance = -1; } public Bitmap crop(int targetMaxSide) { - return cropperBehaviour.crop(targetMaxSide); + return mCropperBehaviour.crop(targetMaxSide); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/RegionDecoderCropBehaviour.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/RegionDecoderCropBehaviour.java index b63ac46fc7..050ec73480 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/RegionDecoderCropBehaviour.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/RegionDecoderCropBehaviour.java @@ -8,6 +8,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.os.Handler; +import android.os.Looper; import net.kdt.pojavlaunch.PojavApplication; import net.kdt.pojavlaunch.modloaders.modpacks.SelfReferencingFuture; @@ -21,7 +22,7 @@ public class RegionDecoderCropBehaviour extends BitmapCropBehaviour { private final RectF mOverlayDst = new RectF(0, 0, 0, 0); private boolean mRequiresOverlayBitmap; private final Matrix mDecoderPrescaleMatrix = new Matrix(); - private final Handler mHiresLoadHandler = new Handler(); + private final Handler mHiresLoadHandler = new Handler(Looper.getMainLooper()); private Future mDecodeFuture; private final Runnable mHiresLoadRunnable = ()->{ RectF subsectionRect = new RectF(0,0, mHostView.getWidth(), mHostView.getHeight()); 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 e674ac64a8..98521041c4 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 @@ -15,7 +15,9 @@ import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; +import net.kdt.pojavlaunch.PojavApplication; import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.imgcropper.BitmapCropBehaviour; import net.kdt.pojavlaunch.imgcropper.CropperBehaviour; import net.kdt.pojavlaunch.imgcropper.CropperView; @@ -43,48 +45,62 @@ private static void openCropperDialog(Context context, Uri selectedUri, builder.setNegativeButton(android.R.string.cancel, null); AlertDialog dialog = builder.show(); CropperView cropImageView = dialog.findViewById(R.id.crop_dialog_view); + View finishProgressBar = dialog.findViewById(R.id.crop_dialog_progressbar); assert cropImageView != null; - try { - try (InputStream inputStream = contentResolver.openInputStream(selectedUri)) { - if(inputStream == null) return; // The provider has crashed, there is no point in trying again. - try { - BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(inputStream, false); - RegionDecoderCropBehaviour cropBehaviour = new RegionDecoderCropBehaviour(cropImageView); - cropBehaviour.loadRegionDecoder(regionDecoder); - finishViewSetup(dialog, cropImageView, cropBehaviour, cropperListener); - return; - }catch (IOException e) { - // Catch IOE here to detect the case when BitmapRegionDecoder does not support this image format. - // If it does not, we will just have to load the bitmap in full resolution using BitmapFactory. - Log.w("CropperUtils", "Failed to load image into BitmapRegionDecoder", e); - } - } - // We can safely re-open the stream here as ACTION_OPEN_DOCUMENT grants us long-term access - // to the file that we have picked. - try (InputStream inputStream = contentResolver.openInputStream(selectedUri)) { - if(inputStream == null) return; - Bitmap originalBitmap = BitmapFactory.decodeStream(inputStream); - BitmapCropBehaviour cropBehaviour = new BitmapCropBehaviour(cropImageView); - cropBehaviour.loadBitmap(originalBitmap); - finishViewSetup(dialog, cropImageView, cropBehaviour, cropperListener); - } - }catch (Exception e){ - cropperListener.onFailed(e); + assert finishProgressBar != null; + bindViews(dialog, cropImageView); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v->{ dialog.dismiss(); + // I chose 70 dp here because it resolves to 192x192 on my device + // (which has a typical screen density of 395 dpi) + cropperListener.onCropped(cropImageView.crop((int) Tools.dpToPx(70))); + }); + PojavApplication.sExecutorService.execute(()->{ + try { + loadBehaviour(cropImageView, contentResolver, selectedUri); + Tools.runOnUiThread(()->finishProgressBar.setVisibility(View.GONE)); + }catch (Exception e){ Tools.runOnUiThread(()->{ + cropperListener.onFailed(e); + dialog.dismiss(); + });} + }); + + } + + + private static void loadBehaviour(CropperView cropImageView, + ContentResolver contentResolver, + Uri selectedUri) throws Exception { + try (InputStream inputStream = contentResolver.openInputStream(selectedUri)) { + if(inputStream == null) return; // The provider has crashed, there is no point in trying again. + try { + BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(inputStream, false); + RegionDecoderCropBehaviour cropBehaviour = new RegionDecoderCropBehaviour(cropImageView); + cropBehaviour.loadRegionDecoder(regionDecoder); + finishViewSetup(cropImageView, cropBehaviour); + return; + }catch (IOException e) { + // Catch IOE here to detect the case when BitmapRegionDecoder does not support this image format. + // If it does not, we will just have to load the bitmap in full resolution using BitmapFactory. + Log.w("CropperUtils", "Failed to load image into BitmapRegionDecoder", e); + } + } + // We can safely re-open the stream here as ACTION_OPEN_DOCUMENT grants us long-term access + // to the file that we have picked. + try (InputStream inputStream = contentResolver.openInputStream(selectedUri)) { + if(inputStream == null) return; + Bitmap originalBitmap = BitmapFactory.decodeStream(inputStream); + BitmapCropBehaviour cropBehaviour = new BitmapCropBehaviour(cropImageView); + cropBehaviour.loadBitmap(originalBitmap); + finishViewSetup(cropImageView,cropBehaviour); } } - private static void finishViewSetup(AlertDialog dialog, - CropperView cropImageView, - CropperBehaviour cropBehaviour, - CropperListener cropperListener) { - cropImageView.cropperBehaviour = cropBehaviour; - cropImageView.requestLayout(); - bindViews(dialog, cropImageView); - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v->{ - dialog.dismiss(); - cropperListener.onCropped(cropImageView.crop(256)); + private static void finishViewSetup(CropperView cropImageView, CropperBehaviour cropBehaviour) { + Tools.runOnUiThread(()->{ + cropImageView.setCropperBehaviour(cropBehaviour); + cropImageView.requestLayout(); }); } @@ -102,12 +118,12 @@ private static void bindViews(AlertDialog alertDialog, CropperView imageCropperV imageCropperView.verticalLock = verticalLock.isChecked() ); reset.setOnClickListener(v-> - imageCropperView.cropperBehaviour.resetTransforms() + imageCropperView.resetTransforms() ); } @SuppressWarnings("unchecked") - public static void startCropper(ActivityResultLauncher resultLauncher, Context context) { + public static void startCropper(ActivityResultLauncher resultLauncher) { ActivityResultLauncher realResultLauncher = (ActivityResultLauncher) resultLauncher; realResultLauncher.launch(new String[]{"image/*"}); diff --git a/app_pojavlauncher/src/main/res/layout/dialog_cropper.xml b/app_pojavlauncher/src/main/res/layout/dialog_cropper.xml index d50f756b71..57aa601334 100644 --- a/app_pojavlauncher/src/main/res/layout/dialog_cropper.xml +++ b/app_pojavlauncher/src/main/res/layout/dialog_cropper.xml @@ -1,30 +1,25 @@ - - + android:layout_height="wrap_content" + android:orientation="vertical"> + android:layout_marginBottom="8dp" /> + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:layout_marginBottom="8dp"> - - \ No newline at end of file + + \ No newline at end of file