Image Editor - Further crop improvements.
* Thumb accuracy improved. * When out of bounds from drag, try to fix by adjusting translation. * Update undo state when listener changes.pull/9/head
parent
5a4c2fc7b0
commit
7f0c998b24
@ -0,0 +1,113 @@
|
|||||||
|
package org.thoughtcrime.securesms.imageeditor.model;
|
||||||
|
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
final class Bisect {
|
||||||
|
|
||||||
|
static final float ACCURACY = 0.001f;
|
||||||
|
|
||||||
|
private static final int MAX_ITERATIONS = 16;
|
||||||
|
|
||||||
|
interface Predicate {
|
||||||
|
boolean test();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModifyElement {
|
||||||
|
void applyFactor(@NonNull Matrix matrix, float factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a predicate function, attempts to finds the boundary between predicate true and predicate false.
|
||||||
|
* If it returns true, it will animate the element to the closest true value found to that boundary.
|
||||||
|
*
|
||||||
|
* @param element The element to modify.
|
||||||
|
* @param outOfBoundsValue The current value, known to be out of bounds. 1 for a scale and 0 for a translate.
|
||||||
|
* @param atMost A value believed to be in bounds.
|
||||||
|
* @param predicate The out of bounds predicate.
|
||||||
|
* @param modifyElement Apply the latest value to the element local matrix.
|
||||||
|
* @param invalidate For animation if finds a result.
|
||||||
|
* @return true iff finds a result.
|
||||||
|
*/
|
||||||
|
static boolean bisectToTest(@NonNull EditorElement element,
|
||||||
|
float outOfBoundsValue,
|
||||||
|
float atMost,
|
||||||
|
@NonNull Predicate predicate,
|
||||||
|
@NonNull ModifyElement modifyElement,
|
||||||
|
@NonNull Runnable invalidate)
|
||||||
|
{
|
||||||
|
Matrix closestSuccesful = bisectToTest(element, outOfBoundsValue, atMost, predicate, modifyElement);
|
||||||
|
|
||||||
|
if (closestSuccesful != null) {
|
||||||
|
element.animateLocalTo(closestSuccesful, invalidate);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a predicate function, attempts to finds the boundary between predicate true and predicate false.
|
||||||
|
* Returns new local matrix for the element if a solution is found.
|
||||||
|
*
|
||||||
|
* @param element The element to modify.
|
||||||
|
* @param outOfBoundsValue The current value, known to be out of bounds. 1 for a scale and 0 for a translate.
|
||||||
|
* @param atMost A value believed to be in bounds.
|
||||||
|
* @param predicate The out of bounds predicate.
|
||||||
|
* @param modifyElement Apply the latest value to the element local matrix.
|
||||||
|
* @return matrix to replace local matrix iff finds a result, null otherwise.
|
||||||
|
*/
|
||||||
|
static @Nullable Matrix bisectToTest(@NonNull EditorElement element,
|
||||||
|
float outOfBoundsValue,
|
||||||
|
float atMost,
|
||||||
|
@NonNull Predicate predicate,
|
||||||
|
@NonNull ModifyElement modifyElement)
|
||||||
|
{
|
||||||
|
Matrix elementMatrix = element.getLocalMatrix();
|
||||||
|
Matrix original = new Matrix(elementMatrix);
|
||||||
|
Matrix closestSuccessful = new Matrix();
|
||||||
|
boolean haveResult = false;
|
||||||
|
int attempt = 0;
|
||||||
|
float successValue = 0;
|
||||||
|
float inBoundsValue = atMost;
|
||||||
|
float nextValueToTry = inBoundsValue;
|
||||||
|
|
||||||
|
do {
|
||||||
|
attempt++;
|
||||||
|
|
||||||
|
modifyElement.applyFactor(elementMatrix, nextValueToTry);
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (predicate.test()) {
|
||||||
|
inBoundsValue = nextValueToTry;
|
||||||
|
|
||||||
|
// if first success or closer to out of bounds than the current closest
|
||||||
|
if (!haveResult || Math.abs(nextValueToTry) < Math.abs(successValue)) {
|
||||||
|
haveResult = true;
|
||||||
|
successValue = nextValueToTry;
|
||||||
|
closestSuccessful.set(elementMatrix);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (attempt == 1) {
|
||||||
|
// failure on first attempt means inBoundsValue is actually out of bounds and so no solution
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
outOfBoundsValue = nextValueToTry;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// reset
|
||||||
|
elementMatrix.set(original);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextValueToTry = (inBoundsValue + outOfBoundsValue) / 2f;
|
||||||
|
|
||||||
|
} while (attempt < MAX_ITERATIONS && Math.abs(inBoundsValue - outOfBoundsValue) > ACCURACY);
|
||||||
|
|
||||||
|
if (haveResult) {
|
||||||
|
return closestSuccessful;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue