for (int i = 0; i < observed.length; i++) {
            sumExpected += expected[i];
            sumObserved += observed[i];
        }

package org.apache.commons.math.stat.inference;

import org.apache.commons.math.MathException;
import org.apache.commons.math.exception.NotPositiveException;
import org.apache.commons.math.exception.NotStrictlyPositiveException;
import org.apache.commons.math.exception.NumberIsTooSmallException;
import org.apache.commons.math.exception.OutOfRangeException;
import org.apache.commons.math.exception.DimensionMismatchException;
import org.apache.commons.math.exception.MathIllegalArgumentException;
import org.apache.commons.math.distribution.ChiSquaredDistribution;
import org.apache.commons.math.distribution.ChiSquaredDistributionImpl;
import org.apache.commons.math.exception.util.LocalizedFormats;
import org.apache.commons.math.util.FastMath;

/**
 * Implements Chi-Square test statistics defined in the
 * {@link UnknownDistributionChiSquareTest} interface.
 *
 * @version $Revision$ $Date$
 */
public class ChiSquareTestImpl implements UnknownDistributionChiSquareTest {

    /**
     * Distribution used to compute inference statistics.
     */
    private ChiSquaredDistribution distribution;

    /**
     * Construct a ChiSquareTestImpl
     */
    public ChiSquareTestImpl() {
        this(new ChiSquaredDistributionImpl(1.0));
    }

    /**
     * Create a test instance using the given distribution for computing
     * inference statistics.
     * @param x distribution used to compute inference statistics.
     * @since 1.2
     */
    public ChiSquareTestImpl(ChiSquaredDistribution x) {
        super();
        setDistribution(x);
    }

    /**
     * {@inheritDoc}
     *
Note: This implementation rescales the
     * expected array if necessary to ensure that the sum of the
     * expected and observed counts are equal.

     *
     * @param observed array of observed frequency counts
     * @param expected array of expected frequency counts
     * @return chi-square test statistic
     * @throws DimensionMismatchException if the arrays length is less than 2.
     */
    public double chiSquare(double[] expected, long[] observed) {
        if (expected.length < 2) {
            throw new DimensionMismatchException(expected.length, 2);
        }
        if (expected.length != observed.length) {
            throw new DimensionMismatchException(expected.length, observed.length);
        }
        checkPositive(expected);
        checkNonNegative(observed);
        double sumExpected = 0d;
        double sumObserved = 0d;

double ratio = 1.0d;
        boolean rescale = false;
        if (FastMath.abs(sumExpected - sumObserved) > 10E-6) {
            ratio = sumObserved / sumExpected;
            rescale = true;
        }
        double sumSq = 0.0d;
        for (int i = 0; i < observed.length; i++) {
            if (rescale) {
                final double dev = observed[i] - ratio * expected[i];
                sumSq += dev * dev / (ratio * expected[i]);
            } else {
                final double dev = observed[i] - expected[i];
                sumSq += dev * dev / expected[i];
            }
        }
        return sumSq;
    }

    /**
     * {@inheritDoc}
     *
Note: This implementation rescales the
     * expected array if necessary to ensure that the sum of the
     * expected and observed counts are equal.

     *
     * @param observed array of observed frequency counts
     * @param expected array of expected frequency counts
     * @return p-value
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs computing the p-value
     */
    public double chiSquareTest(double[] expected, long[] observed) throws MathException {
        distribution = new ChiSquaredDistributionImpl(expected.length - 1.0);
        return 1.0 - distribution.cumulativeProbability(chiSquare(expected, observed));
    }

    /**
     * {@inheritDoc}
     *
Note: This implementation rescales the
     * expected array if necessary to ensure that the sum of the
     * expected and observed counts are equal.

     *
     * @param observed array of observed frequency counts
     * @param expected array of expected frequency counts
     * @param alpha significance level of the test
     * @return true iff null hypothesis can be rejected with confidence
     * 1 - alpha
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs performing the test
     */
    public boolean chiSquareTest(double[] expected, long[] observed, double alpha) throws MathException {
        if ((alpha <= 0) || (alpha > 0.5)) {
            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, alpha, 0, 0.5);
        }
        return chiSquareTest(expected, observed) < alpha;
    }

    /**
     * @param counts array representation of 2-way table
     * @return chi-square test statistic
     * @throws MathIllegalArgumentException if preconditions are not met.
     */
    public double chiSquare(long[][] counts) {
        checkArray(counts);
        int nRows = counts.length;
        int nCols = counts[0].length;
        // compute row, column and total sums
        double[] rowSum = new double[nRows];
        double[] colSum = new double[nCols];
        double total = 0.0d;
        for (int row = 0; row < nRows; row++) {
            for (int col = 0; col < nCols; col++) {
                rowSum[row] += counts[row][col];
                colSum[col] += counts[row][col];
                total += counts[row][col];
            }
        }
        // compute expected counts and chi-square
        double sumSq = 0.0d;
        double expected = 0.0d;
        for (int row = 0; row < nRows; row++) {
            for (int col = 0; col < nCols; col++) {
                expected = (rowSum[row] * colSum[col]) / total;
                sumSq += ((counts[row][col] - expected) * (counts[row][col] - expected)) / expected;
            }
        }
        return sumSq;
    }

    /**
     * @param counts array representation of 2-way table
     * @return p-value
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs computing the p-value
     */
    public double chiSquareTest(long[][] counts) throws MathException {
        checkArray(counts);
        double df = ((double) counts.length - 1) * ((double) counts[0].length - 1);
        distribution = new ChiSquaredDistributionImpl(df);
        return 1 - distribution.cumulativeProbability(chiSquare(counts));
    }

    /**
     * @param counts array representation of 2-way table
     * @param alpha significance level of the test
     * @return true iff null hypothesis can be rejected with confidence
     * 1 - alpha
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs performing the test
     */
    public boolean chiSquareTest(long[][] counts, double alpha) throws MathException {
        if ((alpha <= 0) || (alpha > 0.5)) {
            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, alpha, 0, 0.5);
        }
        return chiSquareTest(counts) < alpha;
    }

    /**
     * @param observed1 array of observed frequency counts of the first data set
     * @param observed2 array of observed frequency counts of the second data set
     * @return chi-square test statistic
     * @throws MathIllegalArgumentException if preconditions are not met
     * @since 1.2
     */
    public double chiSquareDataSetsComparison(long[] observed1, long[] observed2) {
        // Make sure lengths are same
        if (observed1.length < 2) {
            throw new DimensionMismatchException(observed1.length, 2);
        }
        if (observed1.length != observed2.length) {
            throw new DimensionMismatchException(observed1.length, observed2.length);
        }
        // Ensure non-negative counts
        checkNonNegative(observed1);
        checkNonNegative(observed2);
        // Compute and compare count sums
        long countSum1 = 0;
        long countSum2 = 0;
        boolean unequalCounts = false;
        double weight = 0.0;
        for (int i = 0; i < observed1.length; i++) {
            countSum1 += observed1[i];
            countSum2 += observed2[i];
        }
        // Ensure neither sample is uniformly 0
        if (countSum1 == 0) {
            throw new MathIllegalArgumentException(LocalizedFormats.OBSERVED_COUNTS_ALL_ZERO, 1);
        }
        if (countSum2 == 0) {
            throw new MathIllegalArgumentException(LocalizedFormats.OBSERVED_COUNTS_ALL_ZERO, 2);
        }
        // Compare and compute weight only if different
        unequalCounts = countSum1 != countSum2;
        if (unequalCounts) {
            weight = FastMath.sqrt((double) countSum1 / (double) countSum2);
        }
        // Compute ChiSquare statistic
        double sumSq = 0.0d;
        double dev = 0.0d;
        double obs1 = 0.0d;
        double obs2 = 0.0d;
        for (int i = 0; i < observed1.length; i++) {
            if (observed1[i] == 0 && observed2[i] == 0) {
                throw new MathIllegalArgumentException(LocalizedFormats.OBSERVED_COUNTS_BOTTH_ZERO_FOR_ENTRY, i);
            } else {
                obs1 = observed1[i];
                obs2 = observed2[i];
                if (unequalCounts) {
                    // apply weights
                    dev = obs1 / weight - obs2 * weight;
                } else {
                    dev = obs1 - obs2;
                }
                sumSq += (dev * dev) / (obs1 + obs2);
            }
        }
        return sumSq;
    }

    /**
     * @param observed1 array of observed frequency counts of the first data set
     * @param observed2 array of observed frequency counts of the second data set
     * @return p-value
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs computing the p-value
     * @since 1.2
     */
    public double chiSquareTestDataSetsComparison(long[] observed1, long[] observed2) throws MathException {
        distribution = new ChiSquaredDistributionImpl((double) observed1.length - 1);
        return 1 - distribution.cumulativeProbability(chiSquareDataSetsComparison(observed1, observed2));
    }

    /**
     * @param observed1 array of observed frequency counts of the first data set
     * @param observed2 array of observed frequency counts of the second data set
     * @param alpha significance level of the test
     * @return true iff null hypothesis can be rejected with confidence
     * 1 - alpha
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs performing the test
     * @since 1.2
     */
    public boolean chiSquareTestDataSetsComparison(long[] observed1, long[] observed2, double alpha) throws MathException {
        if (alpha <= 0 || alpha > 0.5) {
            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, alpha, 0, 0.5);
        }
        return chiSquareTestDataSetsComparison(observed1, observed2) < alpha;
    }

    /**
     * Checks to make sure that the input long[][] array is rectangular,
     * has at least 2 rows and 2 columns, and has all non-negative entries,
     * throwing MathIllegalArgumentException if any of these checks fail.
     *
     * @param in input 2-way table to check
     * @throws MathIllegalArgumentException if the array is not valid
     */
    private void checkArray(long[][] in) {
        if (in.length < 2) {
            throw new NumberIsTooSmallException(in.length, 2, true);
        }
        if (in[0].length < 2) {
            throw new NumberIsTooSmallException(in[0].length, 2, true);
        }
        checkRectangular(in);
        checkNonNegative(in);
    }

    // ---------------------  Private array methods -- should find a utility home for these
    /**
     * Throws MathIllegalArgumentException if the input array is not rectangular.
     *
     * @param in array to be tested
     * @throws NullPointerException if input array is null
     * @throws MathIllegalArgumentException if input array is not rectangular
     */
    private void checkRectangular(long[][] in) {
        for (int i = 1; i < in.length; i++) {
            if (in[i].length != in[0].length) {
                throw new DimensionMismatchException(LocalizedFormats.DIFFERENT_ROWS_LENGTHS, in[i].length, in[0].length);
            }
        }
    }

    /**
     * Check all entries of the input array are strictly postive.
     *
     * @param in Array to be tested.
     * @exception NotStrictlyPositiveException if one entry is not positive.
     */
    private void checkPositive(double[] in) {
        for (int i = 0; i < in.length; i++) {
            if (in[i] <= 0) {
                throw new NotStrictlyPositiveException(in[i]);
            }
        }
    }

    /**
     * Check all entries of the input array are >= 0.
     *
     * @param in Array to be tested.
     * @exception NotPositiveException if one entry is negative.
     */
    private void checkNonNegative(long[] in) {
        for (int i = 0; i < in.length; i++) {
            if (in[i] < 0) {
                throw new NotPositiveException(in[i]);
            }
        }
    }

    /**
     * Check all entries of the input array are >= 0.
     *
     * @param in Array to be tested.
     * @exception NotPositiveException if one entry is negative.
     */
    private void checkNonNegative(long[][] in) {
        for (int i = 0; i < in.length; i++) {
            for (int j = 0; j < in[i].length; j++) {
                if (in[i][j] < 0) {
                    throw new NotPositiveException(in[i][j]);
                }
            }
        }
    }

    /**
     * Modify the distribution used to compute inference statistics.
     *
     * @param value
     *            the new distribution
     * @since 1.2
     */
    public void setDistribution(ChiSquaredDistribution value) {
        distribution = value;
    }
}


package org.apache.commons.math.stat.inference;

import org.apache.commons.math.MathException;
import org.apache.commons.math.exception.NotPositiveException;
import org.apache.commons.math.exception.NotStrictlyPositiveException;
import org.apache.commons.math.exception.NumberIsTooSmallException;
import org.apache.commons.math.exception.OutOfRangeException;
import org.apache.commons.math.exception.DimensionMismatchException;
import org.apache.commons.math.exception.MathIllegalArgumentException;
import org.apache.commons.math.distribution.ChiSquaredDistribution;
import org.apache.commons.math.distribution.ChiSquaredDistributionImpl;
import org.apache.commons.math.exception.util.LocalizedFormats;
import org.apache.commons.math.util.FastMath;

/**
 * Implements Chi-Square test statistics defined in the
 * {@link UnknownDistributionChiSquareTest} interface.
 *
 * @version $Revision$ $Date$
 */
public class ChiSquareTestImpl implements UnknownDistributionChiSquareTest {

    /**
     * Distribution used to compute inference statistics.
     */
    private ChiSquaredDistribution distribution;

    /**
     * Construct a ChiSquareTestImpl
     */
    public ChiSquareTestImpl() {
        this(new ChiSquaredDistributionImpl(1.0));
    }

    /**
     * Create a test instance using the given distribution for computing
     * inference statistics.
     * @param x distribution used to compute inference statistics.
     * @since 1.2
     */
    public ChiSquareTestImpl(ChiSquaredDistribution x) {
        super();
        setDistribution(x);
    }

    /**
     * {@inheritDoc}
     *
Note: This implementation rescales the
     * expected array if necessary to ensure that the sum of the
     * expected and observed counts are equal.

     *
     * @param observed array of observed frequency counts
     * @param expected array of expected frequency counts
     * @return chi-square test statistic
     * @throws DimensionMismatchException if the arrays length is less than 2.
     */
    public double chiSquare(double[] expected, long[] observed) {
        if (expected.length < 2) {
            throw new DimensionMismatchException(expected.length, 2);
        }
        if (expected.length != observed.length) {
            throw new DimensionMismatchException(expected.length, observed.length);
        }
        checkPositive(expected);
        checkNonNegative(observed);
        double sumExpected = 0d;
        double sumObserved = 0d;
        for (int i = 0; i < observed.length; i++) {
            sumExpected += expected[i];
            sumObserved += observed[i];
        }
        double ratio = 1.0d;
        boolean rescale = false;
        if (FastMath.abs(sumExpected - sumObserved) > 10E-6) {
            ratio = sumObserved / sumExpected;
            rescale = true;
        }
        double sumSq = 0.0d;
        for (int i = 0; i < observed.length; i++) {
            if (rescale) {
                final double dev = observed[i] - ratio * expected[i];
                sumSq += dev * dev / (ratio * expected[i]);
            } else {
                final double dev = observed[i] - expected[i];
                sumSq += dev * dev / expected[i];
            }
        }
        return sumSq;
    }

    /**
     * {@inheritDoc}
     *
Note: This implementation rescales the
     * expected array if necessary to ensure that the sum of the
     * expected and observed counts are equal.

     *
     * @param observed array of observed frequency counts
     * @param expected array of expected frequency counts
     * @return p-value
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs computing the p-value
     */
    public double chiSquareTest(double[] expected, long[] observed) throws MathException {
        distribution = new ChiSquaredDistributionImpl(expected.length - 1.0);
        return 1.0 - distribution.cumulativeProbability(chiSquare(expected, observed));
    }

    /**
     * {@inheritDoc}
     *
Note: This implementation rescales the
     * expected array if necessary to ensure that the sum of the
     * expected and observed counts are equal.

     *
     * @param observed array of observed frequency counts
     * @param expected array of expected frequency counts
     * @param alpha significance level of the test
     * @return true iff null hypothesis can be rejected with confidence
     * 1 - alpha
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs performing the test
     */
    public boolean chiSquareTest(double[] expected, long[] observed, double alpha) throws MathException {
        if ((alpha <= 0) || (alpha > 0.5)) {
            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, alpha, 0, 0.5);
        }
        return chiSquareTest(expected, observed) < alpha;
    }

    /**
     * @param counts array representation of 2-way table
     * @return chi-square test statistic
     * @throws MathIllegalArgumentException if preconditions are not met.
     */
    public double chiSquare(long[][] counts) {
        checkArray(counts);
        int nRows = counts.length;
        int nCols = counts[0].length;
        // compute row, column and total sums
        double[] rowSum = new double[nRows];
        double[] colSum = new double[nCols];
        double total = 0.0d;
        for (int row = 0; row < nRows; row++) {
            for (int col = 0; col < nCols; col++) {
                rowSum[row] += counts[row][col];
                colSum[col] += counts[row][col];
                total += counts[row][col];
            }
        }
        // compute expected counts and chi-square
        double sumSq = 0.0d;
        double expected = 0.0d;
        for (int row = 0; row < nRows; row++) {
            for (int col = 0; col < nCols; col++) {
                expected = (rowSum[row] * colSum[col]) / total;
                sumSq += ((counts[row][col] - expected) * (counts[row][col] - expected)) / expected;
            }
        }
        return sumSq;
    }

    /**
     * @param counts array representation of 2-way table
     * @return p-value
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs computing the p-value
     */
    public double chiSquareTest(long[][] counts) throws MathException {
        checkArray(counts);
        double df = ((double) counts.length - 1) * ((double) counts[0].length - 1);
        distribution = new ChiSquaredDistributionImpl(df);
        return 1 - distribution.cumulativeProbability(chiSquare(counts));
    }

    /**
     * @param counts array representation of 2-way table
     * @param alpha significance level of the test
     * @return true iff null hypothesis can be rejected with confidence
     * 1 - alpha
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs performing the test
     */
    public boolean chiSquareTest(long[][] counts, double alpha) throws MathException {
        if ((alpha <= 0) || (alpha > 0.5)) {
            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, alpha, 0, 0.5);
        }
        return chiSquareTest(counts) < alpha;
    }

    /**
     * @param observed1 array of observed frequency counts of the first data set
     * @param observed2 array of observed frequency counts of the second data set
     * @return chi-square test statistic
     * @throws MathIllegalArgumentException if preconditions are not met
     * @since 1.2
     */
    public double chiSquareDataSetsComparison(long[] observed1, long[] observed2) {
        // Make sure lengths are same
        if (observed1.length < 2) {
            throw new DimensionMismatchException(observed1.length, 2);
        }
        if (observed1.length != observed2.length) {
            throw new DimensionMismatchException(observed1.length, observed2.length);
        }
        // Ensure non-negative counts
        checkNonNegative(observed1);
        checkNonNegative(observed2);
        // Compute and compare count sums
        long countSum1 = 0;
        long countSum2 = 0;
        boolean unequalCounts = false;
        double weight = 0.0;
        for (int i = 0; i < observed1.length; i++) {
            countSum1 += observed1[i];
            countSum2 += observed2[i];
        }
        // Ensure neither sample is uniformly 0
        if (countSum1 == 0) {
            throw new MathIllegalArgumentException(LocalizedFormats.OBSERVED_COUNTS_ALL_ZERO, 1);
        }
        if (countSum2 == 0) {
            throw new MathIllegalArgumentException(LocalizedFormats.OBSERVED_COUNTS_ALL_ZERO, 2);
        }
        // Compare and compute weight only if different
        unequalCounts = countSum1 != countSum2;
        if (unequalCounts) {
            weight = FastMath.sqrt((double) countSum1 / (double) countSum2);
        }
        // Compute ChiSquare statistic
        double sumSq = 0.0d;
        double dev = 0.0d;
        double obs1 = 0.0d;
        double obs2 = 0.0d;
        for (int i = 0; i < observed1.length; i++) {
            if (observed1[i] == 0 && observed2[i] == 0) {
                throw new MathIllegalArgumentException(LocalizedFormats.OBSERVED_COUNT

S_BOTTH_ZERO_FOR_ENTRY, i);
            } else {
                obs1 = observed1[i];
                obs2 = observed2[i];
                if (unequalCounts) {
                    // apply weights
                    dev = obs1 / weight - obs2 * weight;
                } else {
                    dev = obs1 - obs2;
                }
                sumSq += (dev * dev) / (obs1 + obs2);
            }
        }
        return sumSq;
    }

    /**
     * @param observed1 array of observed frequency counts of the first data set
     * @param observed2 array of observed frequency counts of the second data set
     * @return p-value
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs computing the p-value
     * @since 1.2
     */
    public double chiSquareTestDataSetsComparison(long[] observed1, long[] observed2) throws MathException {
        distribution = new ChiSquaredDistributionImpl((double) observed1.length - 1);
        return 1 - distribution.cumulativeProbability(chiSquareDataSetsComparison(observed1, observed2));
    }

    /**
     * @param observed1 array of observed frequency counts of the first data set
     * @param observed2 array of observed frequency counts of the second data set
     * @param alpha significance level of the test
     * @return true iff null hypothesis can be rejected with confidence
     * 1 - alpha
     * @throws MathIllegalArgumentException if preconditions are not met
     * @throws MathException if an error occurs performing the test
     * @since 1.2
     */
    public boolean chiSquareTestDataSetsComparison(long[] observed1, long[] observed2, double alpha) throws MathException {
        if (alpha <= 0 || alpha > 0.5) {
            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, alpha, 0, 0.5);
        }
        return chiSquareTestDataSetsComparison(observed1, observed2) < alpha;
    }

    /**
     * Checks to make sure that the input long[][] array is rectangular,
     * has at least 2 rows and 2 columns, and has all non-negative entries,
     * throwing MathIllegalArgumentException if any of these checks fail.
     *
     * @param in input 2-way table to check
     * @throws MathIllegalArgumentException if the array is not valid
     */
    private void checkArray(long[][] in) {
        if (in.length < 2) {
            throw new NumberIsTooSmallException(in.length, 2, true);
        }
        if (in[0].length < 2) {
            throw new NumberIsTooSmallException(in[0].length, 2, true);
        }
        checkRectangular(in);
        checkNonNegative(in);
    }

    // ---------------------  Private array methods -- should find a utility home for these
    /**
     * Throws MathIllegalArgumentException if the input array is not rectangular.
     *
     * @param in array to be tested
     * @throws NullPointerException if input array is null
     * @throws MathIllegalArgumentException if input array is not rectangular
     */
    private void checkRectangular(long[][] in) {
        for (int i = 1; i < in.length; i++) {
            if (in[i].length != in[0].length) {
                throw new DimensionMismatchException(LocalizedFormats.DIFFERENT_ROWS_LENGTHS, in[i].length, in[0].length);
            }
        }
    }

    /**
     * Check all entries of the input array are strictly postive.
     *
     * @param in Array to be tested.
     * @exception NotStrictlyPositiveException if one entry is not positive.
     */
    private void checkPositive(double[] in) {
        for (int i = 0; i < in.length; i++) {
            if (in[i] <= 0) {
                throw new NotStrictlyPositiveException(in[i]);
            }
        }
    }

    /**
     * Check all entries of the input array are >= 0.
     *
     * @param in Array to be tested.
     * @exception NotPositiveException if one entry is negative.
     */
    private void checkNonNegative(long[] in) {
        for (int i = 0; i < in.length; i++) {
            if (in[i] < 0) {
                throw new NotPositiveException(in[i]);
            }
        }
    }

    /**
     * Check all entries of the input array are >= 0.
     *
     * @param in Array to be tested.
     * @exception NotPositiveException if one entry is negative.
     */
    private void checkNonNegative(long[][] in) {
        for (int i = 0; i < in.length; i++) {
            for (int j = 0; j < in[i].length; j++) {
                if (in[i][j] < 0) {
                    throw new NotPositiveException(in[i][j]);
                }
            }
        }
    }

    /**
     * Modify the distribution used to compute inference statistics.
     *
     * @param value
     *            the new distribution
     * @since 1.2
     */
    public void setDistribution(ChiSquaredDistribution value) {
        distribution = value;
    }
}


package com.yangdb.fuse.model.resourceInfo;

/**
 * Created by lior.perry on 09/03/2017.
 */
public abstract class ResourceInfoBase {

    // region Constructor
    public ResourceInfoBase() {
    }

    public ResourceInfoBase(String resourceUrl, String resourceId) {
        this.resourceUrl = resourceUrl;
        this.resourceId = resourceId;
    }

    // endregion
    // region properties

{
        return this.resourceUrl;
    }

    public String getResourceId() {
        return resourceId;
    }

    public void setResourceUrl(String resourceUrl) {
        this.resourceUrl = resourceUrl;
    }

    public void setResourceId(String resourceId) {
        this.resourceId = resourceId;
    }

    // endregion
    // region Fields
    private String resourceUrl;

    private String resourceId;
    // endregion
}


{
        this.resourceUrl = resourceUrl;
    }

package com.yangdb.fuse.model.resourceInfo;

/**
 * Created by lior.perry on 09/03/2017.
 */
public abstract class ResourceInfoBase {

    // region Constructor
    public ResourceInfoBase() {
    }

    public ResourceInfoBase(String resourceUrl, String resourceId) {
        this.resourceUrl = resourceUrl;
        this.resourceId = resourceId;
    }

    // endregion
    // region properties
    public String getResourceUrl() {
        return this.resourceUrl;
    }

    public String getResourceId() {
        return resourceId;
    }

    public void setResourceUrl(String resourceUrl)

    public void setResourceId(String resourceId) {
        this.resourceId = resourceId;
    }

    // endregion
    // region Fields
    private String resourceUrl;

    private String resourceId;
    // endregion
}


package com.yangdb.fuse.model.resourceInfo;

/**
 * Created by lior.perry on 09/03/2017.
 */
public abstract class ResourceInfoBase {

    // region Constructor
    public ResourceInfoBase() {
    }

    public ResourceInfoBase(String resourceUrl, String resourceId) {
        this.resourceUrl = resourceUrl;
        this.resourceId = resourceId;
    }

    // endregion
    // region properties
    public String getResourceUrl() {
        return this.resourceUrl;
    }

    public String getResourceId() {
        return resourceId;
    }

    public void setResourceUrl(String resourceUrl) {
        this.resourceUrl = resourceUrl

    }

    public void setResourceId(String resourceId) {
        this.resourceId = resourceId;
    }

    // endregion
    // region Fields
    private String resourceUrl;

    private String resourceId;
    // endregion
}


package com.yangdb.fuse.model.resourceInfo;

/**
 * Created by lior.perry on 09/03/2017.
 */
public abstract class ResourceInfoBase {

    // region Constructor
    public ResourceInfoBase() {
    }

    public ResourceInfoBase(String resourceUrl, String resourceId) {
        this.resourceUrl = resourceUrl;
        this.resourceId = resourceId;
    }

    // endregion
    // region properties
    public String getResourceUrl() {
        return this.resourceUrl;
    }

    public String getResourceId() {
        return resourceId;
    }

    public void setResourceUrl(String resourceUrl) {
        this.resourceUrl = resourceUrl;
    }

    public void setResourceId(String resourceId) {
        this.resourceId = resourceId;
    }

    // endregion
    // region Fields
    private String resourceUrl;

    private String resourceId;
    // endregion
}


@Override
    public void onDraw(Canvas canvas)

package com.android.gallery3d.filtershow.imageshow;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
import com.android.gallery3d.filtershow.crop.CropMath;
import com.android.gallery3d.filtershow.crop.CropObject;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;

public class ImageCrop extends ImageShow {

    private static final String TAG = ImageCrop.class.getSimpleName();

    private RectF mImageBounds = new RectF();

    private RectF mScreenCropBounds = new RectF();

    private Paint mPaint = new Paint();

    private CropObject mCropObj = null;

    private GeometryHolder mGeometry = new GeometryHolder();

    private GeometryHolder mUpdateHolder = new GeometryHolder();

    private Drawable mCropIndicator;

    private int mIndicatorSize;

    private boolean mMovingBlock = false;

    private Matrix mDisplayMatrix = null;

    private Matrix mDisplayCropMatrix = null;

    private Matrix mDisplayMatrixInverse = null;

    private float mPrevX = 0;

    private float mPrevY = 0;

    private int mMinSideSize = 90;

    private int mTouchTolerance = 40;

    private enum Mode {

        NONE, MOVE
    }

    private Mode mState = Mode.NONE;

    private boolean mValidDraw = false;

    FilterCropRepresentation mLocalRep = new FilterCropRepresentation();

    EditorCrop mEditorCrop;

    public ImageCrop(Context context) {
        super(context);
        setup(context);
    }

    public ImageCrop(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup(context);
    }

    public ImageCrop(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup(context);
    }

    private void setup(Context context) {
        Resources rsc = context.getResources();
        mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
        mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
        mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
        mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
    }

    public void setFilterCropRepresentation(FilterCropRepresentation crop) {
        mLocalRep = (crop == null) ? new FilterCropRepresentation() : crop;
        GeometryMathUtils.initializeHolder(mUpdateHolder, mLocalRep);
        mValidDraw = true;
    }

    public FilterCropRepresentation getFinalRepresentation() {
        return mLocalRep;
    }

    private void internallyUpdateLocalRep(RectF crop, RectF image) {
        FilterCropRepresentation.findNormalizedCrop(crop, (int) image.width(), (int) image.height());
        mGeometry.crop.set(crop);
        mUpdateHolder.set(mGeometry);
        mLocalRep.setCrop(crop);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
            return true;
        }
        float[] touchPoint = { x, y };
        mDisplayMatrixInverse.mapPoints(touchPoint);
        x = touchPoint[0];
        y = touchPoint[1];
        switch(event.getActionMasked()) {
            case (MotionEvent.ACTION_DOWN):
                if (mState == Mode.NONE) {
                    if (!mCropObj.selectEdge(x, y)) {
                        mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
                    }
                    mPrevX = x;
                    mPrevY = y;
                    mState = Mode.MOVE;
                }
                break;
            case (MotionEvent.ACTION_UP):
                if (mState == Mode.MOVE) {
                    mCropObj.selectEdge(CropObject.MOVE_NONE);
                    mMovingBlock = false;
                    mPrevX = x;
                    mPrevY = y;
                    mState = Mode.NONE;
                    internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
                }
                break;
            case (MotionEvent.ACTION_MOVE):
                if (mState == Mode.MOVE) {
                    float dx = x - mPrevX;
                    float dy = y - mPrevY;
                    mCropObj.moveCurrentSelection(dx, dy);
                    mPrevX = x;
                    mPrevY = y;
                }
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

    private void clearDisplay() {
        mDisplayMatrix = null;
        mDisplayMatrixInverse = null;
        invalidate();
    }

    public void applyFreeAspect() {
        mCropObj.unsetAspectRatio();
        invalidate();
    }

    public void applyOriginalAspect() {
        RectF outer = mCropObj.getOuterBounds();
        float w = outer.width();
        float h = outer.height();
        if (w > 0 && h > 0) {
            applyAspect(w, h);
            mCropObj.resetBoundsTo(outer, outer);
            internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
        } else {
            Log.w(TAG, "failed to set aspect ratio original");
        }
        invalidate();
    }

    public void applyAspect(float x, float y) {
        if (x <= 0 || y <= 0) {
            throw new IllegalArgumentException("Bad arguments to applyAspect");
        }
        // If we are rotated by 90 degrees from horizontal, swap x and y
        if (GeometryMathUtils.needsDimensionSwap(mGeometry.rotation)) {
            float tmp = x;
            x = y;
            y = tmp;
        }
        if (!mCropObj.setInnerAspectRatio(x, y)) {
            Log.w(TAG, "failed to set aspect ratio");
        }
        internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
        invalidate();
    }

    /**
     * Rotates first d bits in integer x to the left some number of times.
     */
    private int bitCycleLeft(int x, int times, int d) {
        int mask = (1 << d) - 1;
        int mout = x & mask;
        times %= d;
        int hi = mout >> (d - times);
        int low = (mout << times) & mask;
        int ret = x & ~mask;
        ret |= low;
        ret |= hi;
        return ret;
    }

    /**
     * Find the selected edge or corner in screen coordinates.
     */
    private int decode(int movingEdges, float rotation) {
        int rot = CropMath.constrainedRotation(rotation);
        switch(rot) {
            case 90:
                return bitCycleLeft(movingEdges, 1, 4);
            case 180:
                return bitCycleLeft(movingEdges, 2, 4);
            case 270:
                return bitCycleLeft(movingEdges, 3, 4);
            default:
                return movingEdges;
        }
    }

    private void forceStateConsistency() {
        MasterImage master = MasterImage.getImage();
        Bitmap image = master.getFiltersOnlyImage();
        int width = image.getWidth();
        int height = image.getHeight();
        if (mCropObj == null || !mUpdateHolder.equals(mGeometry) || mImageBounds.width() != width || mImageBounds.height() != height || !mLocalRep.getCrop().equals(mUpdateHolder.crop)) {
            mImageBounds.set(0, 0, width, height);
            mGeometry.set(mUpdateHolder);
            mLocalRep.setCrop(mUpdateHolder.crop);
            RectF scaledCrop = new RectF(mUpdateHolder.crop);
            FilterCropRepresentation.findScaledCrop(scaledCrop, width, height);
            mCropObj = new CropObject(mImageBounds, scaledCrop, (int) mUpdateHolder.straighten);
            mState = Mode.NONE;
            clearDisplay();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        clearDisplay();
    }

{
        Bitmap bitmap = MasterImage.getImage().getFiltersOnlyImage();
        if (bitmap == null) {
            MasterImage.getImage().invalidateFiltersOnly();
        }
        if (!mValidDraw || bitmap == null) {
            return;
        }
        forceStateConsistency();
        mImageBounds.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
        // If display matrix doesn't exist, create it and its dependencies
        if (mDisplayCropMatrix == null || mDisplayMatrix == null || mDisplayMatrixInverse == null) {
            mCropObj.unsetAspectRatio();
            mDisplayMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry, bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
            float straighten = mGeometry.straighten;
            mGeometry.straighten = 0;
            mDisplayCropMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry, bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
            mGeometry.straighten = straighten;
            mDisplayMatrixInverse = new Matrix();
            mDisplayMatrixInverse.reset();
            if (!mDisplayCropMatrix.invert(mDisplayMatrixInverse)) {
                Log.w(TAG, "could not invert display matrix");
                mDisplayMatrixInverse = null;
                return;
            }
            // Scale min side and tolerance by display matrix scale factor
            mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
            mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
            // drive Crop engine to clamp to crop bounds
            int[] sides = { CropObject.MOVE_TOP, CropObject.MOVE_BOTTOM, CropObject.MOVE_LEFT, CropObject.MOVE_RIGHT };
            int delta = Math.min(canvas.getWidth(), canvas.getHeight()) / 4;
            int[] dx = { delta, -delta, 0, 0 };
            int[] dy = { 0, 0, delta, -delta };
            for (int i = 0; i < sides.length; i++) {
                mCropObj.selectEdge(sides[i]);
                mCropObj.moveCurrentSelection(dx[i], dy[i]);
                mCropObj.moveCurrentSelection(-dx[i], -dy[i]);
            }
            mCropObj.selectEdge(CropObject.MOVE_NONE);
        }
        // Draw actual bitmap
        mPaint.reset();
        mPaint.setAntiAlias(true);
        mPaint.setFilterBitmap(true);
        canvas.drawBitmap(bitmap, mDisplayMatrix, mPaint);
        mCropObj.getInnerBounds(mScreenCropBounds);
        RectF outer = mCropObj.getOuterBounds();
        FilterCropRepresentation.findNormalizedCrop(mScreenCropBounds, (int) outer.width(), (int) outer.height());
        FilterCropRepresentation.findScaledCrop(mScreenCropBounds, bitmap.getWidth(), bitmap.getHeight());
        if (mDisplayCropMatrix.mapRect(mScreenCropBounds)) {
            // Draw crop rect and markers
            CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
            CropDrawingUtils.drawShade(canvas, mScreenCropBounds);
            CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
            CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize, mScreenCropBounds, mCropObj.isFixedAspect(), decode(mCropObj.getSelectState(), mGeometry.rotation.value()));
        }
    }

    public void setEditor(EditorCrop editorCrop) {
        mEditorCrop = editorCrop;
    }
}


{
        Resources rsc = context.getResources();
        mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
        mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
        mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
        mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
    }

package com.android.gallery3d.filtershow.imageshow;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
import com.android.gallery3d.filtershow.crop.CropMath;
import com.android.gallery3d.filtershow.crop.CropObject;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;

public class ImageCrop extends ImageShow {

    private static final String TAG = ImageCrop.class.getSimpleName();

    private RectF mImageBounds = new RectF();

    private RectF mScreenCropBounds = new RectF();

    private Paint mPaint = new Paint();

    private CropObject mCropObj = null;

    private GeometryHolder mGeometry = new GeometryHolder();

    private GeometryHolder mUpdateHolder = new GeometryHolder();

    private Drawable mCropIndicator;

    private int mIndicatorSize;

    private boolean mMovingBlock = false;

    private Matrix mDisplayMatrix = null;

    private Matrix mDisplayCropMatrix = null;

    private Matrix mDisplayMatrixInverse = null;

    private float mPrevX = 0;

    private float mPrevY = 0;

    private int mMinSideSize = 90;

    private int mTouchTolerance = 40;

    private enum Mode {

        NONE, MOVE
    }

    private Mode mState = Mode.NONE;

    private boolean mValidDraw = false;

    FilterCropRepresentation mLocalRep = new FilterCropRepresentation();

    EditorCrop mEditorCrop;

    public ImageCrop(Context context) {
        super(context);
        setup(context);
    }

    public ImageCrop(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup(context);
    }

    public ImageCrop(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup(context);
    }

    private void setup(Context context)

    public void setFilterCropRepresentation(FilterCropRepresentation crop) {
        mLocalRep = (crop == null) ? new FilterCropRepresentation() : crop;
        GeometryMathUtils.initializeHolder(mUpdateHolder, mLocalRep);
        mValidDraw = true;
    }

    public FilterCropRepresentation getFinalRepresentation() {
        return mLocalRep;
    }

    private void internallyUpdateLocalRep(RectF crop, RectF image) {
        FilterCropRepresentation.findNormalizedCrop(crop, (int) image.width(), (int) image.height());
        mGeometry.crop.set(crop);
        mUpdateHolder.set(mGeometry);
        mLocalRep.setCrop(crop);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
            return true;
        }
        float[] touchPoint = { x, y };
        mDisplayMatrixInverse.mapPoints(touchPoint);
        x = touchPoint[0];
        y = touchPoint[1];
        switch(event.getActionMasked()) {
            case (MotionEvent.ACTION_DOWN):
                if (mState == Mode.NONE) {
                    if (!mCropObj.selectEdge(x, y)) {
                        mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
                    }
                    mPrevX = x;
                    mPrevY = y;
                    mState = Mode.MOVE;
                }
                break;
            case (MotionEvent.ACTION_UP):
                if (mState == Mode.MOVE) {
                    mCropObj.selectEdge(CropObject.MOVE_NONE);
                    mMovingBlock = false;
                    mPrevX = x;
                    mPrevY = y;
                    mState = Mode.NONE;
                    internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
                }
                break;
            case (MotionEvent.ACTION_MOVE):
                if (mState == Mode.MOVE) {
                    float dx = x - mPrevX;
                    float dy = y - mPrevY;
                    mCropObj.moveCurrentSelection(dx, dy);
                    mPrevX = x;
                    mPrevY = y;
                }
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

    private void clearDisplay() {
        mDisplayMatrix = null;
        mDisplayMatrixInverse = null;
        invalidate();
    }

    public void applyFreeAspect() {
        mCropObj.unsetAspectRatio();
        invalidate();
    }

    public void applyOriginalAspect() {
        RectF outer = mCropObj.getOuterBounds();
        float w = outer.width();
        float h = outer.height();
        if (w > 0 && h > 0) {
            applyAspect(w, h);
            mCropObj.resetBoundsTo(outer, outer);
            internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
        } else {
            Log.w(TAG, "failed to set aspect ratio original");
        }
        invalidate();
    }

    public void applyAspect(float x, float y) {
        if (x <= 0 || y <= 0) {
            throw new IllegalArgumentException("Bad arguments to applyAspect");
        }
        // If we are rotated by 90 degrees from horizontal, swap x and y
        if (GeometryMathUtils.needsDimensionSwap(mGeometry.rotation)) {
            float tmp = x;
            x = y;
            y = tmp;
        }
        if (!mCropObj.setInnerAspectRatio(x, y)) {
            Log.w(TAG, "failed to set aspect ratio");
        }
        internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
        invalidate();
    }

    /**
     * Rotates first d bits in integer x to the left some number of times.
     */
    private int bitCycleLeft(int x, int times, int d) {
        int mask = (1 << d) - 1;
        int mout = x & mask;
        times %= d;
        int hi = mout >> (d - times);
        int low = (mout << times) & mask;
        int ret = x & ~mask;
        ret |= low;
        ret |= hi;
        return ret;
    }

    /**
     * Find the selected edge or corner in screen coordinates.
     */
    private int decode(int movingEdges, float rotation) {
        int rot = CropMath.constrainedRotation(rotation);
        switch(rot) {
            case 90:
                return bitCycleLeft(movingEdges, 1, 4);
            case 180:
                return bitCycleLeft(movingEdges, 2, 4);
            case 270:
                return bitCycleLeft(movingEdges, 3, 4);
            default:
                return movingEdges;
        }
    }

    private void forceStateConsistency() {
        MasterImage master = MasterImage.getImage();
        Bitmap image = master.getFiltersOnlyImage();
        int width = image.getWidth();
        int height = image.getHeight();
        if (mCropObj == null || !mUpdateHolder.equals(mGeometry) || mImageBounds.width() != width || mImageBounds.height() != height || !mLocalRep.getCrop().equals(mUpdateHolder.crop)) {
            mImageBounds.set(0, 0, width, height);
            mGeometry.set(mUpdateHolder);
            mLocalRep.setCrop(mUpdateHolder.crop);
            RectF scaledCrop = new RectF(mUpdateHolder.crop);
            FilterCropRepresentation.findScaledCrop(scaledCrop, width, height);
            mCropObj = new CropObject(mImageBounds, scaledCrop, (int) mUpdateHolder.straighten);
            mState = Mode.NONE;
            clearDisplay();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        clearDisplay();
    }

    @Override
    public void onDraw(Canvas canvas) {
        Bitmap bitmap = MasterImage.getImage().getFiltersOnlyImage();
        if (bitmap == null) {
            MasterImage.getImage().invalidateFiltersOnly();
        }
        if (!mValidDraw || bitmap == null) {
            return;
        }
        forceStateConsistency();
        mImageBounds.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
        // If display matrix doesn't exist, create it and its dependencies
        if (mDisplayCropMatrix == null || mDisplayMatrix == null || mDisplayMatrixInverse == null) {
            mCropObj.unsetAspectRatio();
            mDisplayMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry, bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
            float straighten = mGeometry.straighten;
            mGeometry.straighten = 0;
            mDisplayCropMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry, bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
            mGeometry.straighten = straighten;
            mDisplayMatrixInverse = new Matrix();
            mDisplayMatrixInverse.reset();
            if (!mDisplayCropMatrix.invert(mDisplayMatrixInverse)) {
                Log.w(TAG, "could not invert display matrix");
                mDisplayMatrixInverse = null;
                return;
            }
            // Scale min side and tolerance by display matrix scale factor
            mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
            mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
            // drive Crop engine to clamp to crop bounds
            int[] sides = { CropObject.MOVE_TOP, CropObject.MOVE_BOTTOM, CropObject.MOVE_LEFT, CropObject.MOVE_RIGHT };
            int delta = Math.min(canvas.getWidth(), canvas.getHeight()) / 4;
            int[] dx = { delta, -delta, 0, 0 };
            int[] dy = { 0, 0, delta, -delta };
            for (int i = 0; i < sides.length; i++) {
                mCropObj.selectEdge(sides[i]);
                mCropObj.moveCurrentSelection(dx[i], dy[i]);
                mCropObj.moveCurrentSelection(-dx[i], -dy[i]);
            }
            mCropObj.selectEdge(CropObject.MOVE_NONE);
        }
        // Draw actual bitmap
        mPaint.reset();
        mPaint.setAntiAlias(true);
        mPaint.setFilterBitmap(true);
        canvas.drawBitmap(bitmap, mDisplayMatrix, mPaint);
        mCropObj.getInnerBounds(mScreenCropBounds);
        RectF outer = mCropObj.getOuterBounds();
        FilterCropRepresentation.findNormalizedCrop(mScreenCropBounds, (int) outer.width(), (int) outer.height());
        FilterCropRepresentation.findScaledCrop(mScreenCropBounds, bitmap.getWidth(), bitmap.getHeight());
        if (mDisplayCropMatrix.mapRect(mScreenCropBounds)) {
            // Draw crop rect and markers
            CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
            CropDrawingUtils.drawShade(canvas, mScreenCropBounds);
            CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
            CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize, mScreenCropBounds, mCropObj.isFixedAspect(), decode(mCropObj.getSelectState(), mGeometry.rotation.value()));
        }
    }

    public void setEditor(EditorCrop editorCrop) {
        mEditorCrop = editorCrop;
    }
}


        RectF outer = mCropObj.getOuterBounds();
        float w = outer.width();

package com.android.gallery3d.filtershow.imageshow;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
import com.android.gallery3d.filtershow.crop.CropMath;
import com.android.gallery3d.filtershow.crop.CropObject;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;

public class ImageCrop extends ImageShow {

    private static final String TAG = ImageCrop.class.getSimpleName();

    private RectF mImageBounds = new RectF();

    private RectF mScreenCropBounds = new RectF();

    private Paint mPaint = new Paint();

    private CropObject mCropObj = null;

    private GeometryHolder mGeometry = new GeometryHolder();

    private GeometryHolder mUpdateHolder = new GeometryHolder();

    private Drawable mCropIndicator;

    private int mIndicatorSize;

    private boolean mMovingBlock = false;

    private Matrix mDisplayMatrix = null;

    private Matrix mDisplayCropMatrix = null;

    private Matrix mDisplayMatrixInverse = null;

    private float mPrevX = 0;

    private float mPrevY = 0;

    private int mMinSideSize = 90;

    private int mTouchTolerance = 40;

    private enum Mode {

        NONE, MOVE
    }

    private Mode mState = Mode.NONE;

    private boolean mValidDraw = false;

    FilterCropRepresentation mLocalRep = new FilterCropRepresentation();

    EditorCrop mEditorCrop;

    public ImageCrop(Context context) {
        super(context);
        setup(context);
    }

    public ImageCrop(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup(context);
    }

    public ImageCrop(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup(context);
    }

    private void setup(Context context) {
        Resources rsc = context.getResources();
        mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
        mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
        mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
        mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
    }

    public void setFilterCropRepresentation(FilterCropRepresentation crop) {
        mLocalRep = (crop == null) ? new FilterCropRepresentation() : crop;
        GeometryMathUtils.initializeHolder(mUpdateHolder, mLocalRep);
        mValidDraw = true;
    }

    public FilterCropRepresentation getFinalRepresentation() {
        return mLocalRep;
    }

    private void internallyUpdateLocalRep(RectF crop, RectF image) {
        FilterCropRepresentation.findNormalizedCrop(crop, (int) image.width(), (int) image.height());
        mGeometry.crop.set(crop);
        mUpdateHolder.set(mGeometry);
        mLocalRep.setCrop(crop);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
            return true;
        }
        float[] touchPoint = { x, y };
        mDisplayMatrixInverse.mapPoints(touchPoint);
        x = touchPoint[0];
        y = touchPoint[1];
        switch(event.getActionMasked()) {
            case (MotionEvent.ACTION_DOWN):
                if (mState == Mode.NONE) {
                    if (!mCropObj.selectEdge(x, y)) {
                        mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
                    }
                    mPrevX = x;
                    mPrevY = y;
                    mState = Mode.MOVE;
                }
                break;
            case (MotionEvent.ACTION_UP):
                if (mState == Mode.MOVE) {
                    mCropObj.selectEdge(CropObject.MOVE_NONE);
                    mMovingBlock = false;
                    mPrevX = x;
                    mPrevY = y;
                    mState = Mode.NONE;
                    internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
                }
                break;
            case (MotionEvent.ACTION_MOVE):
                if (mState == Mode.MOVE) {
                    float dx = x - mPrevX;
                    float dy = y - mPrevY;
                    mCropObj.moveCurrentSelection(dx, dy);
                    mPrevX = x;
                    mPrevY = y;
                }
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

    private void clearDisplay() {
        mDisplayMatrix = null;
        mDisplayMatrixInverse = null;
        invalidate();
    }

    public void applyFreeAspect() {
        mCropObj.unsetAspectRatio();
        invalidate();
    }

    public void applyOriginalAspect() {

        float h = outer.height();
        if (w > 0 && h > 0) {
            applyAspect(w, h);
            mCropObj.resetBoundsTo(outer, outer);
            internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
        } else {
            Log.w(TAG, "failed to set aspect ratio original");
        }
        invalidate();
    }

    public void applyAspect(float x, float y) {
        if (x <= 0 || y <= 0) {
            throw new IllegalArgumentException("Bad arguments to applyAspect");
        }
        // If we are rotated by 90 degrees from horizontal, swap x and y
        if (GeometryMathUtils.needsDimensionSwap(mGeometry.rotation)) {
            float tmp = x;
            x = y;
            y = tmp;
        }
        if (!mCropObj.setInnerAspectRatio(x, y)) {
            Log.w(TAG, "failed to set aspect ratio");
        }
        internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
        invalidate();
    }

    /**
     * Rotates first d bits in integer x to the left some number of times.
     */
    private int bitCycleLeft(int x, int times, int d) {
        int mask = (1 << d) - 1;
        int mout = x & mask;
        times %= d;
        int hi = mout >> (d - times);
        int low = (mout << times) & mask;
        int ret = x & ~mask;
        ret |= low;
        ret |= hi;
        return ret;
    }

    /**
     * Find the selected edge or corner in screen coordinates.
     */
    private int decode(int movingEdges, float rotation) {
        int rot = CropMath.constrainedRotation(rotation);
        switch(rot) {
            case 90:
                return bitCycleLeft(movingEdges, 1, 4);
            case 180:
                return bitCycleLeft(movingEdges, 2, 4);
            case 270:
                return bitCycleLeft(movingEdges, 3, 4);
            default:
                return movingEdges;
        }
    }

    private void forceStateConsistency() {
        MasterImage master = MasterImage.getImage();
        Bitmap image = master.getFiltersOnlyImage();
        int width = image.getWidth();
        int height = image.getHeight();
        if (mCropObj == null || !mUpdateHolder.equals(mGeometry) || mImageBounds.width() != width || mImageBounds.height() != height || !mLocalRep.getCrop().equals(mUpdateHolder.crop)) {
            mImageBounds.set(0, 0, width, height);
            mGeometry.set(mUpdateHolder);
            mLocalRep.setCrop(mUpdateHolder.crop);
            RectF scaledCrop = new RectF(mUpdateHolder.crop);
            FilterCropRepresentation.findScaledCrop(scaledCrop, width, height);
            mCropObj = new CropObject(mImageBounds, scaledCrop, (int) mUpdateHolder.straighten);
            mState = Mode.NONE;
            clearDisplay();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        clearDisplay();
    }

    @Override
    public void onDraw(Canvas canvas) {
        Bitmap bitmap = MasterImage.getImage().getFiltersOnlyImage();
        if (bitmap == null) {
            MasterImage.getImage().invalidateFiltersOnly();
        }
        if (!mValidDraw || bitmap == null) {
            return;
        }
        forceStateConsistency();
        mImageBounds.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
        // If display matrix doesn't exist, create it and its dependencies
        if (mDisplayCropMatrix == null || mDisplayMatrix == null || mDisplayMatrixInverse == null) {
            mCropObj.unsetAspectRatio();
            mDisplayMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry, bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
            float straighten = mGeometry.straighten;
            mGeometry.straighten = 0;
            mDisplayCropMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry, bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
            mGeometry.straighten = straighten;
            mDisplayMatrixInverse = new Matrix();
            mDisplayMatrixInverse.reset();
            if (!mDisplayCropMatrix.invert(mDisplayMatrixInverse)) {
                Log.w(TAG, "could not invert display matrix");
                mDisplayMatrixInverse = null;
                return;
            }
            // Scale min side and tolerance by display matrix scale factor
            mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
            mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
            // drive Crop engine to clamp to crop bounds
            int[] sides = { CropObject.MOVE_TOP, CropObject.MOVE_BOTTOM, CropObject.MOVE_LEFT, CropObject.MOVE_RIGHT };
            int delta = Math.min(canvas.getWidth(), canvas.getHeight()) / 4;
            int[] dx = { delta, -delta, 0, 0 };
            int[] dy = { 0, 0, delta, -delta };
            for (int i = 0; i < sides.length; i++) {
                mCropObj.selectEdge(sides[i]);
                mCropObj.moveCurrentSelection(dx[i], dy[i]);
                mCropObj.moveCurrentSelection(-dx[i], -dy[i]);
            }
            mCropObj.selectEdge(CropObject.MOVE_NONE);
        }
        // Draw actual bitmap
        mPaint.reset();
        mPaint.setAntiAlias(true);
        mPaint.setFilterBitmap(true);
        canvas.drawBitmap(bitmap, mDisplayMatrix, mPaint);
        mCropObj.getInnerBounds(mScreenCropBounds);
        RectF outer = mCropObj.getOuterBounds();
        FilterCropRepresentation.findNormalizedCrop(mScreenCropBounds, (int) outer.width(), (int) outer.height());
        FilterCropRepresentation.findScaledCrop(mScreenCropBounds, bitmap.getWidth(), bitmap.getHeight());
        if (mDisplayCropMatrix.mapRect(mScreenCropBounds)) {
            // Draw crop rect and markers
            CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
            CropDrawingUtils.drawShade(canvas, mScreenCropBounds);
            CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
            CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize, mScreenCropBounds, mCropObj.isFixedAspect(), decode(mCropObj.getSelectState(), mGeometry.rotation.value()));
        }
    }

    public void setEditor(EditorCrop editorCrop) {
        mEditorCrop = editorCrop;
    }
}


float h = outer.height();
        if (w > 0 && h > 0) {
            applyAspect(w, h);
            mCropObj.resetBoundsTo(outer, outer);
            internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
        } else {
            Log.w(TAG, "failed to set aspect ratio original");
        }

package com.android.gallery3d.filtershow.imageshow;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
import com.android.gallery3d.filtershow.crop.CropMath;
import com.android.gallery3d.filtershow.crop.CropObject;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;

public class ImageCrop extends ImageShow {

    private static final String TAG = ImageCrop.class.getSimpleName();

    private RectF mImageBounds = new RectF();

    private RectF mScreenCropBounds = new RectF();

    private Paint mPaint = new Paint();

    private CropObject mCropObj = null;

    private GeometryHolder mGeometry = new GeometryHolder();

    private GeometryHolder mUpdateHolder = new GeometryHolder();

    private Drawable mCropIndicator;

    private int mIndicatorSize;

    private boolean mMovingBlock = false;

    private Matrix mDisplayMatrix = null;

    private Matrix mDisplayCropMatrix = null;

    private Matrix mDisplayMatrixInverse = null;

    private float mPrevX = 0;

    private float mPrevY = 0;

    private int mMinSideSize = 90;

    private int mTouchTolerance = 40;

    private enum Mode {

        NONE, MOVE
    }

    private Mode mState = Mode.NONE;

    private boolean mValidDraw = false;

    FilterCropRepresentation mLocalRep = new FilterCropRepresentation();

    EditorCrop mEditorCrop;

    public ImageCrop(Context context) {
        super(context);
        setup(context);
    }

    public ImageCrop(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup(context);
    }

    public ImageCrop(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup(context);
    }

    private void setup(Context context) {
        Resources rsc = context.getResources();
        mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
        mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
        mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
        mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
    }

    public void setFilterCropRepresentation(FilterCropRepresentation crop) {
        mLocalRep = (crop == null) ? new FilterCropRepresentation() : crop;
        GeometryMathUtils.initializeHolder(mUpdateHolder, mLocalRep);
        mValidDraw = true;
    }

    public FilterCropRepresentation getFinalRepresentation() {
        return mLocalRep;
    }

    private void internallyUpdateLocalRep(RectF crop, RectF image) {
        FilterCropRepresentation.findNormalizedCrop(crop, (int) image.width(), (int) image.height());
        mGeometry.crop.set(crop);
        mUpdateHolder.set(mGeometry);
        mLocalRep.setCrop(crop);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
            return true;
        }
        float[] touchPoint = { x, y };
        mDisplayMatrixInverse.mapPoints(touchPoint);
        x = touchPoint[0];
        y = touchPoint[1];
        switch(event.getActionMasked()) {
            case (MotionEvent.ACTION_DOWN):
                if (mState == Mode.NONE) {
                    if (!mCropObj.selectEdge(x, y)) {
                        mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
                    }
                    mPrevX = x;
                    mPrevY = y;
                    mState = Mode.MOVE;
                }
                break;
            case (MotionEvent.ACTION_UP):
                if (mState == Mode.MOVE) {
                    mCropObj.selectEdge(CropObject.MOVE_NONE);
                    mMovingBlock = false;
                    mPrevX = x;
                    mPrevY = y;
                    mState = Mode.NONE;
                    internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
                }
                break;
            case (MotionEvent.ACTION_MOVE):
                if (mState == Mode.MOVE) {
                    float dx = x - mPrevX;
                    float dy = y - mPrevY;
                    mCropObj.moveCurrentSelection(dx, dy);
                    mPrevX = x;
                    mPrevY = y;
                }
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

    private void clearDisplay() {
        mDisplayMatrix = null;
        mDisplayMatrixInverse = null;
        invalidate();
    }

    public void applyFreeAspect() {
        mCropObj.unsetAspectRatio();
        invalidate();
    }

    public void applyOriginalAspect() {
        RectF outer = mCropObj.getOuterBounds();
        float w = outer.width();

        invalidate();
    }

    public void applyAspect(float x, float y) {
        if (x <= 0 || y <= 0) {
            throw new IllegalArgumentException("Bad arguments to applyAspect");
        }
        // If we are rotated by 90 degrees from horizontal, swap x and y
        if (GeometryMathUtils.needsDimensionSwap(mGeometry.rotation)) {
            float tmp = x;
            x = y;
            y = tmp;
        }
        if (!mCropObj.setInnerAspectRatio(x, y)) {
            Log.w(TAG, "failed to set aspect ratio");
        }
        internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
        invalidate();
    }

    /**
     * Rotates first d bits in integer x to the left some number of times.
     */
    private int bitCycleLeft(int x, int times, int d) {
        int mask = (1 << d) - 1;
        int mout = x & mask;
        times %= d;
        int hi = mout >> (d - times);
        int low = (mout << times) & mask;
        int ret = x & ~mask;
        ret |= low;
        ret |= hi;
        return ret;
    }

    /**
     * Find the selected edge or corner in screen coordinates.
     */
    private int decode(int movingEdges, float rotation) {
        int rot = CropMath.constrainedRotation(rotation);
        switch(rot) {
            case 90:
                return bitCycleLeft(movingEdges, 1, 4);
            case 180:
                return bitCycleLeft(movingEdges, 2, 4);
            case 270:
                return bitCycleLeft(movingEdges, 3, 4);
            default:
                return movingEdges;
        }
    }

    private void forceStateConsistency() {
        MasterImage master = MasterImage.getImage();
        Bitmap image = master.getFiltersOnlyImage();
        int width = image.getWidth();
        int height = image.getHeight();
        if (mCropObj == null || !mUpdateHolder.equals(mGeometry) || mImageBounds.width() != width || mImageBounds.height() != height || !mLocalRep.getCrop().equals(mUpdateHolder.crop)) {
            mImageBounds.set(0, 0, width, height);
            mGeometry.set(mUpdateHolder);
            mLocalRep.setCrop(mUpdateHolder.crop);
            RectF scaledCrop = new RectF(mUpdateHolder.crop);
            FilterCropRepresentation.findScaledCrop(scaledCrop, width, height);
            mCropObj = new CropObject(mImageBounds, scaledCrop, (int) mUpdateHolder.straighten);
            mState = Mode.NONE;
            clearDisplay();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        clearDisplay();
    }

    @Override
    public void onDraw(Canvas canvas) {
        Bitmap bitmap = MasterImage.getImage().getFiltersOnlyImage();
        if (bitmap == null) {
            MasterImage.getImage().invalidateFiltersOnly();
        }
        if (!mValidDraw || bitmap == null) {
            return;
        }
        forceStateConsistency();
        mImageBounds.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
        // If display matrix doesn't exist, create it and its dependencies
        if (mDisplayCropMatrix == null || mDisplayMatrix == null || mDisplayMatrixInverse == null) {
            mCropObj.unsetAspectRatio();
            mDisplayMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry, bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
            float straighten = mGeometry.straighten;
            mGeometry.straighten = 0;
            mDisplayCropMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry, bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
            mGeometry.straighten = straighten;
            mDisplayMatrixInverse = new Matrix();
            mDisplayMatrixInverse.reset();
            if (!mDisplayCropMatrix.invert(mDisplayMatrixInverse)) {
                Log.w(TAG, "could not invert display matrix");
                mDisplayMatrixInverse = null;
                return;
            }
            // Scale min side and tolerance by display matrix scale factor
            mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
            mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
            // drive Crop engine to clamp to crop bounds
            int[] sides = { CropObject.MOVE_TOP, CropObject.MOVE_BOTTOM, CropObject.MOVE_LEFT, CropObject.MOVE_RIGHT };
            int delta = Math.min(canvas.getWidth(), canvas.getHeight()) / 4;
            int[] dx = { delta, -delta, 0, 0 };
            int[] dy = { 0, 0, delta, -delta };
            for (int i = 0; i < sides.length; i++) {
                mCropObj.selectEdge(sides[i]);
                mCropObj.moveCurrentSelection(dx[i], dy[i]);
                mCropObj.moveCurrentSelection(-dx[i], -dy[i]);
            }
            mCropObj.selectEdge(CropObject.MOVE_NONE);
        }
        // Draw actual bitmap
        mPaint.reset();
        mPaint.setAntiAlias(true);
        mPaint.setFilterBitmap(true);
        canvas.drawBitmap(bitmap, mDisplayMatrix, mPaint);
        mCropObj.getInnerBounds(mScreenCropBounds);
        RectF outer = mCropObj.getOuterBounds();
        FilterCropRepresentation.findNormalizedCrop(mScreenCropBounds, (int) outer.width(), (int) outer.height());
        FilterCropRepresentation.findScaledCrop(mScreenCropBounds, bitmap.getWidth(), bitmap.getHeight());
        if (mDisplayCropMatrix.mapRect(mScreenCropBounds)) {
            // Draw crop rect and markers
            CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
            CropDrawingUtils.drawShade(canvas, mScreenCropBounds);
            CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
            CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize, mScreenCropBounds, mCropObj.isFixedAspect(), decode(mCropObj.getSelectState(), mGeometry.rotation.value()));
        }
    }

    public void setEditor(EditorCrop editorCrop) {
        mEditorCrop = editorCrop;
    }
}


package com.android.gallery3d.filtershow.imageshow;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
import com.android.gallery3d.filtershow.crop.CropMath;
import com.android.gallery3d.filtershow.crop.CropObject;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;

public class ImageCrop extends ImageShow {

    private static final String TAG = ImageCrop.class.getSimpleName();

    private RectF mImageBounds = new RectF();

    private RectF mScreenCropBounds = new RectF();

    private Paint mPaint = new Paint();

    private CropObject mCropObj = null;

    private GeometryHolder mGeometry = new GeometryHolder();

    private GeometryHolder mUpdateHolder = new GeometryHolder();

    private Drawable mCropIndicator;

    private int mIndicatorSize;

    private boolean mMovingBlock = false;

    private Matrix mDisplayMatrix = null;

    private Matrix mDisplayCropMatrix = null;

    private Matrix mDisplayMatrixInverse = null;

    private float mPrevX = 0;

    private float mPrevY = 0;

    private int mMinSideSize = 90;

    private int mTouchTolerance = 40;

    private enum Mode {

        NONE, MOVE
    }

    private Mode mState = Mode.NONE;

    private boolean mValidDraw = false;

    FilterCropRepresentation mLocalRep = new FilterCropRepresentation();

    EditorCrop mEditorCrop;

    public ImageCrop(Context context) {
        super(context);
        setup(context);
    }

    public ImageCrop(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup(context);
    }

    public ImageCrop(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup(context);
    }

    private void setup(Context context) {
        Resources rsc = context.getResources();
        mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
        mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
        mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
        mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
    }

    public void setFilterCropRepresentation(FilterCropRepresentation crop) {
        mLocalRep = (crop == null) ? new FilterCropRepresentation() : crop;
        GeometryMathUtils.initializeHolder(mUpdateHolder, mLocalRep);
        mValidDraw = true;
    }

    public FilterCropRepresentation getFinalRepresentation() {
        return mLocalRep;
    }

    private void internallyUpdateLocalRep(RectF crop, RectF image) {
        FilterCropRepresentation.findNormalizedCrop(crop, (int) image.width(), (int) image.height());
        mGeometry.crop.set(crop);
        mUpdateHolder.set(mGeometry);
        mLocalRep.setCrop(crop);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
            return true;
        }
        float[] touchPoint = { x, y };
        mDisplayMatrixInverse.mapPoints(touchPoint);
        x = touchPoint[0];
        y = touchPoint[1];
        switch(event.getActionMasked()) {
            case (MotionEvent.ACTION_DOWN):
                if (mState == Mode.NONE) {
                    if (!mCropObj.selectEdge(x, y)) {
                        mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
                    }
                    mPrevX = x;
                    mPrevY = y;
                    mState = Mode.MOVE;
                }
                break;
            case (MotionEvent.ACTION_UP):
                if (mState == Mode.MOVE) {
                    mCropObj.selectEdge(CropObject.MOVE_NONE);
                    mMovingBlock = false;
                    mPrevX = x;
                    mPrevY = y;
                    mState = Mode.NONE;
                    internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
                }
                break;
            case (MotionEvent.ACTION_MOVE):
                if (mState == Mode.MOVE) {
                    float dx = x - mPrevX;
                    float dy = y - mPrevY;
                    mCropObj.moveCurrentSelection(dx, dy);
                    mPrevX = x;
                    mPrevY = y;
                }
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

    private void clearDisplay() {
        mDisplayMatrix = null;
        mDisplayMatrixInverse = null;
        invalidate();
    }

    public void applyFreeAspect() {
        mCropObj.unsetAspectRatio();
        invalidate();
    }

    public void applyOriginalAspect() {
        RectF outer = mCropObj.getOuterBounds();
        float w = outer.width();
        float h = outer.height();
        if (w > 0 && h > 0) {
            applyAspect(w, h);
            mCropObj.resetBoundsTo(outer, outer);
            internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
        } else {
            Log.w(TAG, "failed to set aspect ratio original");
        }

    public void applyAspect(float x, float y) {
        if (x <= 0 || y <= 0) {
            throw new IllegalArgumentException("Bad arguments to applyAspect");
        }
        // If we are rotated by 90 degrees from horizontal, swap x and y
        if (GeometryMathUtils.needsDimensionSwap(mGeometry.rotation)) {
            float tmp = x;
            x = y;
            y = tmp;
        }
        if (!mCropObj.setInnerAspectRatio(x, y)) {
            Log.w(TAG, "failed to set aspect ratio");
        }
        internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
        invalidate();
    }

    /**
     * Rotates first d bits in integer x to the left some number of times.
     */
    private int bitCycleLeft(int x, int times, int d) {
        int mask = (1 << d) - 1;
        int mout = x & mask;
        times %= d;
        int hi = mout >> (d - times);
        int low = (mout << times) & mask;
        int ret = x & ~mask;
        ret |= low;
        ret |= hi;
        return ret;
    }

    /**
     * Find the selected edge or corner in screen coordinates.
     */
    private int decode(int movingEdges, float rotation) {
        int rot = CropMath.constrainedRotation(rotation);
        switch(rot) {
            case 90:
                return bitCycleLeft(movingEdges, 1, 4);
            case 180:
                return bitCycleLeft(movingEdges, 2, 4);
            case 270:
                return bitCycleLeft(movingEdges, 3, 4);
            default:
                return movingEdges;
        }
    }

    private void forceStateConsistency() {
        MasterImage master = MasterImage.getImage();
        Bitmap image = master.getFiltersOnlyImage();
        int width = image.getWidth();
        int height = image.getHeight();
        if (mCropObj == null || !mUpdateHolder.equals(mGeometry) || mImageBounds.width() != width || mImageBounds.height() != height || !mLocalRep.getCrop().equals(mUpdateHolder.crop)) {
            mImageBounds.set(0, 0, width, height);
            mGeometry.set(mUpdateHolder);
            mLocalRep.setCrop(mUpdateHolder.crop);
            RectF scaledCrop = new RectF(mUpdateHolder.crop);
            FilterCropRepresentation.findScaledCrop(scaledCrop, width, height);
            mCropObj = new CropObject(mImageBounds, scaledCrop, (int) mUpdateHolder.straighten);
            mState = Mode.NONE;
            clearDisplay();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        clearDisplay();
    }

    @Override
    public void onDraw(Canvas canvas) {
        Bitmap bitmap = MasterImage.getImage().getFiltersOnlyImage();
        if (bitmap == null) {
            MasterImage.getImage().invalidateFiltersOnly();
        }
        if (!mValidDraw || bitmap == null) {
            return;
        }
        forceStateConsistency();
        mImageBounds.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
        // If display matrix doesn't exist, create it and its dependencies
        if (mDisplayCropMatrix == null || mDisplayMatrix == null || mDisplayMatrixInverse == null) {
            mCropObj.unsetAspectRatio();
            mDisplayMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry, bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
            float straighten = mGeometry.straighten;
            mGeometry.straighten = 0;
            mDisplayCropMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry, bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
            mGeometry.straighten = straighten;
            mDisplayMatrixInverse = new Matrix();
            mDisplayMatrixInverse.reset();
            if (!mDisplayCropMatrix.invert(mDisplayMatrixInverse)) {
                Log.w(TAG, "could not invert display matrix");
                mDisplayMatrixInverse = null;
                return;
            }
            // Scale min side and tolerance by display matrix scale factor
            mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
            mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
            // drive Crop engine to clamp to crop bounds
            int[] sides = { CropObject.MOVE_TOP, CropObject.MOVE_BOTTOM, CropObject.MOVE_LEFT, CropObject.MOVE_RIGHT };
            int delta = Math.min(canvas.getWidth(), canvas.getHeight()) / 4;
            int[] dx = { delta, -delta, 0, 0 };
            int[] dy = { 0, 0, delta, -delta };
            for (int i = 0; i < sides.length; i++) {
                mCropObj.selectEdge(sides[i]);
                mCropObj.moveCurrentSelection(dx[i], dy[i]);
                mCropObj.moveCurrentSelection(-dx[i], -dy[i]);
            }
            mCropObj.selectEdge(CropObject.MOVE_NONE);
        }
        // Draw actual bitmap
        mPaint.reset();
        mPaint.setAntiAlias(true);
        mPaint.setFilterBitmap(true);
        canvas.drawBitmap(bitmap, mDisplayMatrix, mPaint);
        mCropObj.getInnerBounds(mScreenCropBounds);
        RectF outer = mCropObj.getOuterBounds();
        FilterCropRepresentation.findNormalizedCrop(mScreenCropBounds, (int) outer.width(), (int) outer.height());
        FilterCropRepresentation.findScaledCrop(mScreenCropBounds, bitmap.getWidth(), bitmap.getHeight());
        if (mDisplayCropMatrix.mapRect(mScreenCropBounds)) {
            // Draw crop rect and markers
            CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
            CropDrawingUtils.drawShade(canvas, mScreenCropBounds);
            CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
            CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize, mScreenCropBounds, mCropObj.isFixedAspect(), decode(mCropObj.getSelectState(), mGeometry.rotation.value()));
        }
    }

    public void setEditor(EditorCrop editorCrop) {
        mEditorCrop = editorCrop;
    }
}