mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2025-09-10 13:16:04 -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.os.Bundle;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Base64OutputStream;
|
import android.util.Base64OutputStream;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -53,7 +54,7 @@ public class ProfileEditorFragment extends Fragment implements CropperUtils.Crop
|
|||||||
private EditText mDefaultName, mDefaultJvmArgument;
|
private EditText mDefaultName, mDefaultJvmArgument;
|
||||||
private TextView mDefaultPath, mDefaultVersion, mDefaultControl;
|
private TextView mDefaultPath, mDefaultVersion, mDefaultControl;
|
||||||
private ImageView mProfileIcon;
|
private ImageView mProfileIcon;
|
||||||
private ActivityResultLauncher<?> mCropperLauncher = CropperUtils.registerCropper(this, this);
|
private final ActivityResultLauncher<?> mCropperLauncher = CropperUtils.registerCropper(this, this);
|
||||||
|
|
||||||
private List<String> mRenderNames;
|
private List<String> mRenderNames;
|
||||||
|
|
||||||
@ -131,9 +132,7 @@ public class ProfileEditorFragment extends Fragment implements CropperUtils.Crop
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Set up the icon change click listener
|
// Set up the icon change click listener
|
||||||
mProfileIcon.setOnClickListener(v ->{
|
mProfileIcon.setOnClickListener(v -> CropperUtils.startCropper(mCropperLauncher));
|
||||||
CropperUtils.startCropper(mCropperLauncher, v.getContext());
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -235,6 +234,7 @@ public class ProfileEditorFragment extends Fragment implements CropperUtils.Crop
|
|||||||
@Override
|
@Override
|
||||||
public void onCropped(Bitmap contentBitmap) {
|
public void onCropped(Bitmap contentBitmap) {
|
||||||
mProfileIcon.setImageBitmap(contentBitmap);
|
mProfileIcon.setImageBitmap(contentBitmap);
|
||||||
|
Log.i("bitmap", "w="+contentBitmap.getWidth() +" h="+contentBitmap.getHeight());
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
try (Base64OutputStream base64OutputStream = new Base64OutputStream(byteArrayOutputStream, Base64.NO_WRAP)) {
|
try (Base64OutputStream base64OutputStream = new Base64OutputStream(byteArrayOutputStream, Base64.NO_WRAP)) {
|
||||||
contentBitmap.compress(Bitmap.CompressFormat.PNG, 60, base64OutputStream);
|
contentBitmap.compress(Bitmap.CompressFormat.PNG, 60, base64OutputStream);
|
||||||
|
@ -28,7 +28,7 @@ public class CropperView extends View {
|
|||||||
private float mSelectionPadding;
|
private float mSelectionPadding;
|
||||||
private int mLastTrackedPointer;
|
private int mLastTrackedPointer;
|
||||||
private Paint mSelectionPaint;
|
private Paint mSelectionPaint;
|
||||||
public CropperBehaviour cropperBehaviour = CropperBehaviour.DUMMY;
|
private CropperBehaviour mCropperBehaviour = CropperBehaviour.DUMMY;
|
||||||
|
|
||||||
public CropperView(Context context) {
|
public CropperView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -74,7 +74,7 @@ public class CropperView extends View {
|
|||||||
float multiplier = 0.005f;
|
float multiplier = 0.005f;
|
||||||
float midpointX = (x1 + x2) / 2;
|
float midpointX = (x1 + x2) / 2;
|
||||||
float midpointY = (y1 + y2) / 2;
|
float midpointY = (y1 + y2) / 2;
|
||||||
cropperBehaviour.zoom(1 + distanceDelta * multiplier, midpointX, midpointY);
|
mCropperBehaviour.zoom(1 + distanceDelta * multiplier, midpointX, midpointY);
|
||||||
}
|
}
|
||||||
mLastDistance = distance;
|
mLastDistance = distance;
|
||||||
return true;
|
return true;
|
||||||
@ -106,7 +106,7 @@ public class CropperView extends View {
|
|||||||
}
|
}
|
||||||
if(trackedIndex != -1) {
|
if(trackedIndex != -1) {
|
||||||
// If we still track out current pointer, pan the image by the movement delta
|
// 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 {
|
} else {
|
||||||
// Otherwise, mark the new tracked pointer without panning.
|
// Otherwise, mark the new tracked pointer without panning.
|
||||||
mLastTrackedPointer = event.getPointerId(0);
|
mLastTrackedPointer = event.getPointerId(0);
|
||||||
@ -121,7 +121,7 @@ public class CropperView extends View {
|
|||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
super.onDraw(canvas);
|
super.onDraw(canvas);
|
||||||
canvas.save();
|
canvas.save();
|
||||||
cropperBehaviour.drawPreHighlight(canvas);
|
mCropperBehaviour.drawPreHighlight(canvas);
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
canvas.drawRect(mSelectionHighlight, mSelectionPaint);
|
canvas.drawRect(mSelectionHighlight, mSelectionPaint);
|
||||||
}
|
}
|
||||||
@ -150,7 +150,7 @@ public class CropperView extends View {
|
|||||||
mSelectionRect.top = centerShiftY;
|
mSelectionRect.top = centerShiftY;
|
||||||
mSelectionRect.right = centerShiftX + lesserDimension;
|
mSelectionRect.right = centerShiftX + lesserDimension;
|
||||||
mSelectionRect.bottom = centerShiftY + lesserDimension;
|
mSelectionRect.bottom = centerShiftY + lesserDimension;
|
||||||
cropperBehaviour.onSelectionRectUpdated();
|
mCropperBehaviour.onSelectionRectUpdated();
|
||||||
// Adjust the selection highlight rectangle to be bigger than the selection area
|
// 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
|
// by the highlight thickness, to make sure that the entire inside of the selection highlight
|
||||||
// will fit into the image
|
// will fit into the image
|
||||||
@ -169,7 +169,7 @@ public class CropperView extends View {
|
|||||||
setMeasuredDimension(widthSize, heightSize);
|
setMeasuredDimension(widthSize, heightSize);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int biggestAllowedDimension = cropperBehaviour.getLargestImageSide();
|
int biggestAllowedDimension = mCropperBehaviour.getLargestImageSide();
|
||||||
if(widthMode == MeasureSpec.EXACTLY) biggestAllowedDimension = widthSize;
|
if(widthMode == MeasureSpec.EXACTLY) biggestAllowedDimension = widthSize;
|
||||||
if(heightMode == MeasureSpec.EXACTLY) biggestAllowedDimension = heightSize;
|
if(heightMode == MeasureSpec.EXACTLY) biggestAllowedDimension = heightSize;
|
||||||
setMeasuredDimension(
|
setMeasuredDimension(
|
||||||
@ -191,12 +191,21 @@ public class CropperView extends View {
|
|||||||
return desired;
|
return desired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCropperBehaviour(CropperBehaviour cropperBehaviour) {
|
||||||
|
this.mCropperBehaviour = cropperBehaviour;
|
||||||
|
cropperBehaviour.onSelectionRectUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetTransforms() {
|
||||||
|
mCropperBehaviour.resetTransforms();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
protected void reset() {
|
protected void reset() {
|
||||||
mLastDistance = -1;
|
mLastDistance = -1;
|
||||||
}
|
}
|
||||||
public Bitmap crop(int targetMaxSide) {
|
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.Rect;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
import net.kdt.pojavlaunch.PojavApplication;
|
import net.kdt.pojavlaunch.PojavApplication;
|
||||||
import net.kdt.pojavlaunch.modloaders.modpacks.SelfReferencingFuture;
|
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 final RectF mOverlayDst = new RectF(0, 0, 0, 0);
|
||||||
private boolean mRequiresOverlayBitmap;
|
private boolean mRequiresOverlayBitmap;
|
||||||
private final Matrix mDecoderPrescaleMatrix = new Matrix();
|
private final Matrix mDecoderPrescaleMatrix = new Matrix();
|
||||||
private final Handler mHiresLoadHandler = new Handler();
|
private final Handler mHiresLoadHandler = new Handler(Looper.getMainLooper());
|
||||||
private Future<?> mDecodeFuture;
|
private Future<?> mDecodeFuture;
|
||||||
private final Runnable mHiresLoadRunnable = ()->{
|
private final Runnable mHiresLoadRunnable = ()->{
|
||||||
RectF subsectionRect = new RectF(0,0, mHostView.getWidth(), mHostView.getHeight());
|
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.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import net.kdt.pojavlaunch.PojavApplication;
|
||||||
import net.kdt.pojavlaunch.R;
|
import net.kdt.pojavlaunch.R;
|
||||||
|
import net.kdt.pojavlaunch.Tools;
|
||||||
import net.kdt.pojavlaunch.imgcropper.BitmapCropBehaviour;
|
import net.kdt.pojavlaunch.imgcropper.BitmapCropBehaviour;
|
||||||
import net.kdt.pojavlaunch.imgcropper.CropperBehaviour;
|
import net.kdt.pojavlaunch.imgcropper.CropperBehaviour;
|
||||||
import net.kdt.pojavlaunch.imgcropper.CropperView;
|
import net.kdt.pojavlaunch.imgcropper.CropperView;
|
||||||
@ -43,15 +45,39 @@ public class CropperUtils {
|
|||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
AlertDialog dialog = builder.show();
|
AlertDialog dialog = builder.show();
|
||||||
CropperView cropImageView = dialog.findViewById(R.id.crop_dialog_view);
|
CropperView cropImageView = dialog.findViewById(R.id.crop_dialog_view);
|
||||||
|
View finishProgressBar = dialog.findViewById(R.id.crop_dialog_progressbar);
|
||||||
assert cropImageView != null;
|
assert cropImageView != null;
|
||||||
|
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 {
|
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)) {
|
try (InputStream inputStream = contentResolver.openInputStream(selectedUri)) {
|
||||||
if(inputStream == null) return; // The provider has crashed, there is no point in trying again.
|
if(inputStream == null) return; // The provider has crashed, there is no point in trying again.
|
||||||
try {
|
try {
|
||||||
BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
|
BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
|
||||||
RegionDecoderCropBehaviour cropBehaviour = new RegionDecoderCropBehaviour(cropImageView);
|
RegionDecoderCropBehaviour cropBehaviour = new RegionDecoderCropBehaviour(cropImageView);
|
||||||
cropBehaviour.loadRegionDecoder(regionDecoder);
|
cropBehaviour.loadRegionDecoder(regionDecoder);
|
||||||
finishViewSetup(dialog, cropImageView, cropBehaviour, cropperListener);
|
finishViewSetup(cropImageView, cropBehaviour);
|
||||||
return;
|
return;
|
||||||
}catch (IOException e) {
|
}catch (IOException e) {
|
||||||
// Catch IOE here to detect the case when BitmapRegionDecoder does not support this image format.
|
// Catch IOE here to detect the case when BitmapRegionDecoder does not support this image format.
|
||||||
@ -66,25 +92,15 @@ public class CropperUtils {
|
|||||||
Bitmap originalBitmap = BitmapFactory.decodeStream(inputStream);
|
Bitmap originalBitmap = BitmapFactory.decodeStream(inputStream);
|
||||||
BitmapCropBehaviour cropBehaviour = new BitmapCropBehaviour(cropImageView);
|
BitmapCropBehaviour cropBehaviour = new BitmapCropBehaviour(cropImageView);
|
||||||
cropBehaviour.loadBitmap(originalBitmap);
|
cropBehaviour.loadBitmap(originalBitmap);
|
||||||
finishViewSetup(dialog, cropImageView, cropBehaviour, cropperListener);
|
finishViewSetup(cropImageView,cropBehaviour);
|
||||||
}
|
|
||||||
}catch (Exception e){
|
|
||||||
cropperListener.onFailed(e);
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void finishViewSetup(AlertDialog dialog,
|
private static void finishViewSetup(CropperView cropImageView, CropperBehaviour cropBehaviour) {
|
||||||
CropperView cropImageView,
|
Tools.runOnUiThread(()->{
|
||||||
CropperBehaviour cropBehaviour,
|
cropImageView.setCropperBehaviour(cropBehaviour);
|
||||||
CropperListener cropperListener) {
|
|
||||||
cropImageView.cropperBehaviour = cropBehaviour;
|
|
||||||
cropImageView.requestLayout();
|
cropImageView.requestLayout();
|
||||||
bindViews(dialog, cropImageView);
|
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v->{
|
|
||||||
dialog.dismiss();
|
|
||||||
cropperListener.onCropped(cropImageView.crop(256));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,12 +118,12 @@ public class CropperUtils {
|
|||||||
imageCropperView.verticalLock = verticalLock.isChecked()
|
imageCropperView.verticalLock = verticalLock.isChecked()
|
||||||
);
|
);
|
||||||
reset.setOnClickListener(v->
|
reset.setOnClickListener(v->
|
||||||
imageCropperView.cropperBehaviour.resetTransforms()
|
imageCropperView.resetTransforms()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static void startCropper(ActivityResultLauncher<?> resultLauncher, Context context) {
|
public static void startCropper(ActivityResultLauncher<?> resultLauncher) {
|
||||||
ActivityResultLauncher<String[]> realResultLauncher =
|
ActivityResultLauncher<String[]> realResultLauncher =
|
||||||
(ActivityResultLauncher<String[]>) resultLauncher;
|
(ActivityResultLauncher<String[]>) resultLauncher;
|
||||||
realResultLauncher.launch(new String[]{"image/*"});
|
realResultLauncher.launch(new String[]{"image/*"});
|
||||||
|
@ -1,30 +1,25 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
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_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
<net.kdt.pojavlaunch.imgcropper.CropperView
|
<net.kdt.pojavlaunch.imgcropper.CropperView
|
||||||
android:id="@+id/crop_dialog_view"
|
android:id="@+id/crop_dialog_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_marginBottom="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" />
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/crop_dialog_button_layout"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/crop_dialog_view"
|
android:layout_marginStart="8dp"
|
||||||
app:layout_constraintStart_toStartOf="@+id/crop_dialog_view"
|
android:layout_marginEnd="8dp"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/crop_dialog_view">
|
android:layout_marginBottom="8dp">
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
android:id="@+id/crop_dialog_hlock"
|
android:id="@+id/crop_dialog_hlock"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
@ -46,5 +41,10 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:text="@string/cropper_reset"/>
|
android:text="@string/cropper_reset"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
<ProgressBar
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
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