diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/BitmapCropBehaviour.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/BitmapCropBehaviour.java index f982743eb..33da834a5 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/BitmapCropBehaviour.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/imgcropper/BitmapCropBehaviour.java @@ -5,6 +5,8 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; +import net.kdt.pojavlaunch.utils.MatrixUtils; + public class BitmapCropBehaviour implements CropperBehaviour{ private final Matrix mTranslateInverse = new Matrix(); protected final Matrix mTranslateMatrix = new Matrix(); @@ -32,7 +34,7 @@ public class BitmapCropBehaviour implements CropperBehaviour{ public void zoom(float zoomLevel, float midpointX, float midpointY) { // Do this to avoid constantly inverting the same matrix on each touch event. if(mTranslateInverseOutdated) { - inverse(mTranslateMatrix, mTranslateInverse); + MatrixUtils.inverse(mTranslateMatrix, mTranslateInverse); mTranslateInverseOutdated = false; } float[] zoomCenter = new float[] { @@ -81,42 +83,32 @@ public class BitmapCropBehaviour implements CropperBehaviour{ public Bitmap crop(int targetMaxSide) { Matrix imageInverse = new Matrix(); - inverse(mImageMatrix, imageInverse); + MatrixUtils.inverse(mImageMatrix, imageInverse); // By inverting the matrix we will effectively "divide" our rectangle by it, thus getting - // its two points on the surface of the bitmap. Math be cool indeed. - float[] src = new float[] { - mHostView.mSelectionRect.left, - mHostView.mSelectionRect.top, - mHostView.mSelectionRect.right, - mHostView.mSelectionRect.bottom - }; - float[] dst = new float[4]; - imageInverse.mapPoints(dst, 0, src, 0, 2); - Rect originalBitmapRect = new Rect( - (int)dst[0], (int)dst[1], - (int)dst[2], (int)dst[3] - ); + // its two points on the bitmap's surface. Math be cool indeed. + Rect targetRect = new Rect(); + MatrixUtils.transformRect(mHostView.mSelectionRect, targetRect, imageInverse); // Pick the best dimensions for the crop result, shrinking the target if necessary. int targetWidth, targetHeight; - int targetMinDimension = Math.min(originalBitmapRect.width(), originalBitmapRect.height()); + int targetMinDimension = Math.min(targetRect.width(), targetRect.height()); if(targetMaxSide < targetMinDimension) { float ratio = (float) targetMaxSide / targetMinDimension; - targetWidth = (int) (originalBitmapRect.width() * ratio); - targetHeight = (int) (originalBitmapRect.height() * ratio); + targetWidth = (int) (targetRect.width() * ratio); + targetHeight = (int) (targetRect.height() * ratio); }else { - targetWidth = originalBitmapRect.width(); - targetHeight = originalBitmapRect.height(); + targetWidth = targetRect.width(); + targetHeight = targetRect.height(); } Bitmap croppedBitmap = Bitmap.createBitmap( targetWidth, targetHeight, mOriginalBitmap.getConfig() ); // Draw the bitmap on the target. Doing this allows us to not bother with making sure - // that originalBitmapRect is fully contained within image bounds. + // that targetRect is fully contained within image bounds. Canvas drawCanvas = new Canvas(croppedBitmap); drawCanvas.drawBitmap( mOriginalBitmap, - originalBitmapRect, + targetRect, new Rect(0, 0, targetWidth, targetHeight), null ); @@ -166,50 +158,4 @@ public class BitmapCropBehaviour implements CropperBehaviour{ mZoomMatrix.reset(); refresh(); } - - /** - * Android's conditions for matrix inversion are wacky, and sometimes it just stops working out - * of the blue. So, when Android's accelerated matrix inverse dies, just invert by hand. - * @param source Source matrix - * @param destination The inverse of the source matrix - */ - protected void inverse(Matrix source, Matrix destination) { - if(source.invert(destination)) return; - float[] matrix = new float[9]; - source.getValues(matrix); - inverseMatrix(matrix); - destination.setValues(matrix); - } - - // This was made by ChatGPT and i have no clue what's happening here, but it works so eh - private static void inverseMatrix(float[] matrix) { - float determinant = matrix[0] * (matrix[4] * matrix[8] - matrix[5] * matrix[7]) - - matrix[1] * (matrix[3] * matrix[8] - matrix[5] * matrix[6]) - + matrix[2] * (matrix[3] * matrix[7] - matrix[4] * matrix[6]); - - if (determinant == 0) { - throw new IllegalArgumentException("Matrix is not invertible"); - } - - float invDet = 1 / determinant; - - float temp0 = (matrix[4] * matrix[8] - matrix[5] * matrix[7]); - float temp1 = (matrix[2] * matrix[7] - matrix[1] * matrix[8]); - float temp2 = (matrix[1] * matrix[5] - matrix[2] * matrix[4]); - float temp3 = (matrix[5] * matrix[6] - matrix[3] * matrix[8]); - float temp4 = (matrix[0] * matrix[8] - matrix[2] * matrix[6]); - float temp5 = (matrix[2] * matrix[3] - matrix[0] * matrix[5]); - float temp6 = (matrix[3] * matrix[7] - matrix[4] * matrix[6]); - float temp7 = (matrix[1] * matrix[6] - matrix[0] * matrix[7]); - float temp8 = (matrix[0] * matrix[4] - matrix[1] * matrix[3]); - matrix[0] = temp0 * invDet; - matrix[1] = temp1 * invDet; - matrix[2] = temp2 * invDet; - matrix[3] = temp3 * invDet; - matrix[4] = temp4 * invDet; - matrix[5] = temp5 * invDet; - matrix[6] = temp6 * invDet; - matrix[7] = temp7 * invDet; - matrix[8] = temp8 * invDet; - } } 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 5ee65d071..89361854a 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 @@ -11,6 +11,7 @@ import android.os.Handler; import net.kdt.pojavlaunch.PojavApplication; import net.kdt.pojavlaunch.modloaders.modpacks.SelfReferencingFuture; +import net.kdt.pojavlaunch.utils.MatrixUtils; import java.util.concurrent.Future; @@ -38,7 +39,7 @@ public class RegionDecoderCropBehaviour extends BitmapCropBehaviour { }; /** - * Decode a region from this Bitmap based on a subsection in the View coordinate space. + * Decoade a region from this Bitmap based on a subsection in the View coordinate space. * @param targetDrawRect an output Rect. This Rect is the position at which the region must * be rendered within subsectionRect. * @param subsectionRect the subsection in View coordinate space. Note that this Rect is modified @@ -46,15 +47,15 @@ public class RegionDecoderCropBehaviour extends BitmapCropBehaviour { * @return null if the resulting region is bigger than the original image * null if the resulting region is completely out of the original image bounds * null if the resulting region is smaller than 16x16 pixels - * null if a region decoding error has occurred + * null if a region decoding error has occured * the resulting Bitmap region otherwise. */ private Bitmap decodeRegionBitmap(RectF targetDrawRect, RectF subsectionRect) { RectF decoderRect = new RectF(0, 0, mBitmapDecoder.getWidth(), mBitmapDecoder.getHeight()); Matrix matrix = createDecoderImageMatrix(); Matrix inverse = new Matrix(); - inverse(matrix, inverse); - transformRect(subsectionRect, inverse); + MatrixUtils.inverse(matrix, inverse); + MatrixUtils.transformRect(subsectionRect, inverse); // If our current sub-section is bigger than the decoder rect, skip. // We do this to avoid unnecessarily loading the image at full resolution. if(subsectionRect.width() > decoderRect.width() @@ -187,25 +188,6 @@ public class RegionDecoderCropBehaviour extends BitmapCropBehaviour { return decoderImageMatrix; } - /** - * Transform the coordinates of the Rect using the supplied Matrix. - * @param rect the input/output Rect for this operation - * @param regionImageInverse the Matrix for transforming the Rect. - */ - private void transformRect(RectF rect, Matrix regionImageInverse) { - if(regionImageInverse.isIdentity()) return; - float[] inOutDecodeRect = new float[8]; - inOutDecodeRect[0] = rect.left; - inOutDecodeRect[1] = rect.top; - inOutDecodeRect[2] = rect.right; - inOutDecodeRect[3] = rect.bottom; - regionImageInverse.mapPoints(inOutDecodeRect, 4, inOutDecodeRect, 0, 2); - rect.left = inOutDecodeRect[4]; - rect.top = inOutDecodeRect[5]; - rect.right = inOutDecodeRect[6]; - rect.bottom = inOutDecodeRect[7]; - } - @Override public Bitmap crop(int targetMaxSide) { RectF drawRect = new RectF(); @@ -227,7 +209,7 @@ public class RegionDecoderCropBehaviour extends BitmapCropBehaviour { float scaleRatio = (float)targetDimension / mHostView.mSelectionRect.width(); Matrix drawRectScaleMatrix = new Matrix(); drawRectScaleMatrix.setScale(scaleRatio, scaleRatio); - transformRect(drawRect, drawRectScaleMatrix); + MatrixUtils.transformRect(drawRect, drawRectScaleMatrix); Bitmap returnBitmap = Bitmap.createBitmap(targetDimension, targetDimension, regionBitmap.getConfig()); Canvas canvas = new Canvas(returnBitmap); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MatrixUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MatrixUtils.java new file mode 100644 index 000000000..556f27421 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MatrixUtils.java @@ -0,0 +1,178 @@ +package net.kdt.pojavlaunch.utils; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; + +@SuppressWarnings("unused") +public class MatrixUtils { + + /** + * Transform the coordinates of the RectF using the supplied Matrix, and write the result back into + * the RectF + * @param inOutRect the RectF for this operation + * @param transformMatrix the Matrix for transforming the Rect. + */ + public static void transformRect(Rect inOutRect, Matrix transformMatrix) { + transformRect(inOutRect, inOutRect, transformMatrix); + } + + /** + * Transform the coordinates of the RectF using the supplied Matrix, and write the result back into + * the RectF + * @param inOutRect the RectF for this operation + * @param transformMatrix the Matrix for transforming the Rect. + */ + public static void transformRect(RectF inOutRect, Matrix transformMatrix) { + transformRect(inOutRect, inOutRect, transformMatrix); + } + + /** + * Transform the coordinates of the input RectF using the supplied Matrix, and write the result + * into the output Rect + * @param inRect the input RectF for this operation + * @param outRect the output Rect for this operation + * @param transformMatrix the Matrix for transforming the Rect. + */ + public static void transformRect(RectF inRect, Rect outRect, Matrix transformMatrix) { + float[] inOutDecodeRect = createInOutDecodeRect(transformMatrix); + if(inOutDecodeRect == null) return; + writeInputRect(inOutDecodeRect, inRect); + transformPoints(inOutDecodeRect, transformMatrix); + readOutputRect(inOutDecodeRect, outRect); + } + + /** + * Transform the coordinates of the input Rect using the supplied Matrix, and write the result + * into the output RectF + * @param inRect the input Rect for this operation + * @param outRect the output RectF for this operation + * @param transformMatrix the Matrix for transforming the Rect. + */ + public static void transformRect(Rect inRect, RectF outRect, Matrix transformMatrix) { + float[] inOutDecodeRect = createInOutDecodeRect(transformMatrix); + if(inOutDecodeRect == null) return; + writeInputRect(inOutDecodeRect, inRect); + transformPoints(inOutDecodeRect, transformMatrix); + readOutputRect(inOutDecodeRect, outRect); + } + + /** + * Transform the coordinates of the input Rect using the supplied Matrix, and write the result + * into the output Rect + * @param inRect the input Rect for this operation + * @param outRect the output Rect for this operation + * @param transformMatrix the Matrix for transforming the Rect. + */ + public static void transformRect(Rect inRect, Rect outRect, Matrix transformMatrix) { + float[] inOutDecodeRect = createInOutDecodeRect(transformMatrix); + if(inOutDecodeRect == null) return; + writeInputRect(inOutDecodeRect, inRect); + transformPoints(inOutDecodeRect, transformMatrix); + readOutputRect(inOutDecodeRect, outRect); + } + + /** + * Transform the coordinates of the input RectF using the supplied Matrix, and write the result + * into the output RectF + * @param inRect the input RectF for this operation + * @param outRect the output RectF for this operation + * @param transformMatrix the Matrix for transforming the Rect. + */ + public static void transformRect(RectF inRect, RectF outRect, Matrix transformMatrix) { + float[] inOutDecodeRect = createInOutDecodeRect(transformMatrix); + if(inOutDecodeRect == null) return; + writeInputRect(inOutDecodeRect, inRect); + transformPoints(inOutDecodeRect, transformMatrix); + readOutputRect(inOutDecodeRect, outRect); + } + + // The group of functions below are used as building blocks of the transformRect() functions + // in order to not repeat the same exact code a lot of times. + private static void writeInputRect(float[] inOutDecodeRect, RectF inRect) { + inOutDecodeRect[0] = inRect.left; + inOutDecodeRect[1] = inRect.top; + inOutDecodeRect[2] = inRect.right; + inOutDecodeRect[3] = inRect.bottom; + } + + private static void writeInputRect(float[] inOutDecodeRect, Rect inRect) { + inOutDecodeRect[0] = inRect.left; + inOutDecodeRect[1] = inRect.top; + inOutDecodeRect[2] = inRect.right; + inOutDecodeRect[3] = inRect.bottom; + } + + private static void readOutputRect(float[] inOutDecodeRect, RectF outRect) { + outRect.left = inOutDecodeRect[4]; + outRect.top = inOutDecodeRect[5]; + outRect.right = inOutDecodeRect[6]; + outRect.bottom = inOutDecodeRect[7]; + } + + private static void readOutputRect(float[] inOutDecodeRect, Rect outRect) { + outRect.left = (int)inOutDecodeRect[4]; + outRect.top = (int)inOutDecodeRect[5]; + outRect.right = (int)inOutDecodeRect[6]; + outRect.bottom = (int)inOutDecodeRect[7]; + } + + private static float[] createInOutDecodeRect(Matrix transformMatrix) { + if(transformMatrix.isIdentity()) return null; + // We need an array of 8 floats because each point is two floats, + // we need to transform two points and we need to have a separated input and output + return new float[8]; + } + + private static void transformPoints(float[] inOutDecodeRect, Matrix transformMatrix) { + transformMatrix.mapPoints(inOutDecodeRect, 4, inOutDecodeRect, 0, 2); + } + + /** + * Invert the source matrix, and write the result into the destination matrix. + * Android's integrated Matrix.invert() has some unexpected conditions when the matrix + * can't be inverted, and in that case the method inverts the matrix by hand. + * @param source Source matrix + * @param destination The inverse of the source matrix + * @throws IllegalArgumentException when the matrix is not invertible + */ + public static void inverse(Matrix source, Matrix destination) throws IllegalArgumentException { + if(source.invert(destination)) return; + float[] matrix = new float[9]; + source.getValues(matrix); + inverseMatrix(matrix); + destination.setValues(matrix); + } + + // This was made by ChatGPT and i have no clue what's happening here, but it works so eh + private static void inverseMatrix(float[] matrix) { + float determinant = matrix[0] * (matrix[4] * matrix[8] - matrix[5] * matrix[7]) + - matrix[1] * (matrix[3] * matrix[8] - matrix[5] * matrix[6]) + + matrix[2] * (matrix[3] * matrix[7] - matrix[4] * matrix[6]); + + if (determinant == 0) { + throw new IllegalArgumentException("Matrix is not invertible"); + } + + float invDet = 1 / determinant; + + float temp0 = (matrix[4] * matrix[8] - matrix[5] * matrix[7]); + float temp1 = (matrix[2] * matrix[7] - matrix[1] * matrix[8]); + float temp2 = (matrix[1] * matrix[5] - matrix[2] * matrix[4]); + float temp3 = (matrix[5] * matrix[6] - matrix[3] * matrix[8]); + float temp4 = (matrix[0] * matrix[8] - matrix[2] * matrix[6]); + float temp5 = (matrix[2] * matrix[3] - matrix[0] * matrix[5]); + float temp6 = (matrix[3] * matrix[7] - matrix[4] * matrix[6]); + float temp7 = (matrix[1] * matrix[6] - matrix[0] * matrix[7]); + float temp8 = (matrix[0] * matrix[4] - matrix[1] * matrix[3]); + matrix[0] = temp0 * invDet; + matrix[1] = temp1 * invDet; + matrix[2] = temp2 * invDet; + matrix[3] = temp3 * invDet; + matrix[4] = temp4 * invDet; + matrix[5] = temp5 * invDet; + matrix[6] = temp6 * invDet; + matrix[7] = temp7 * invDet; + matrix[8] = temp8 * invDet; + } +}