mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2025-09-15 07:39:00 -04:00
Feat[utils]: Implement MatrixUtils, with methods to easily work with transform matrices
This commit is contained in:
parent
83fdf15379
commit
2cef7ccd49
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user