mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2025-09-09 04:32:27 -04:00
Feat[cropper]: asynchronously load the image and change output resolution
This commit is contained in:
parent
11ec17b410
commit
bc1fdaf3ec
@ -5,6 +5,7 @@ import android.graphics.Bitmap;
|
||||
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<String> mRenderNames;
|
||||
|
||||
@ -131,9 +132,7 @@ public class ProfileEditorFragment extends Fragment implements CropperUtils.Crop
|
||||
}));
|
||||
|
||||
// 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 @@ public class ProfileEditorFragment extends Fragment implements CropperUtils.Crop
|
||||
@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);
|
||||
|
@ -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 class CropperView extends View {
|
||||
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 class CropperView extends View {
|
||||
}
|
||||
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 class CropperView extends View {
|
||||
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 @@ public class CropperView extends View {
|
||||
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 @@ public class CropperView extends View {
|
||||
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 @@ public class CropperView extends View {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import android.graphics.Matrix;
|
||||
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());
|
||||
|
@ -15,7 +15,9 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||
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 @@ public class CropperUtils {
|
||||
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 @@ public class CropperUtils {
|
||||
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<String[]> realResultLauncher =
|
||||
(ActivityResultLauncher<String[]>) resultLauncher;
|
||||
realResultLauncher.launch(new String[]{"image/*"});
|
||||
|
@ -1,30 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
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="wrap_content">
|
||||
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<net.kdt.pojavlaunch.imgcropper.CropperView
|
||||
android:id="@+id/crop_dialog_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/crop_dialog_button_layout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_marginBottom="8dp" />
|
||||
<LinearLayout
|
||||
android:id="@+id/crop_dialog_button_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
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">
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
<ToggleButton
|
||||
android:id="@+id/crop_dialog_hlock"
|
||||
android:layout_weight="1"
|
||||
@ -46,5 +41,10 @@
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/cropper_reset"/>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<ProgressBar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:id="@+id/crop_dialog_progressbar"
|
||||
android:indeterminate="true"/>
|
||||
</LinearLayout>
|
Loading…
x
Reference in New Issue
Block a user