package com.zomato.photofilters.geometry; import android.graphics.Path; import android.graphics.PathMeasure; import android.os.Build; import android.view.animation.PathInterpolator; /** * @author Varun on 29/06/15. */ public final class BezierSpline { private BezierSpline() { } /** * Generates Curve {in a plane ranging from 0-255} using the knots provided */ public static int[] curveGenerator(Point[] knots) { if (knots == null) { throw new NullPointerException("Knots cannot be null"); } int n = knots.length - 1; if (n < 1) { throw new IllegalArgumentException("Atleast two points are required"); } if (Build.VERSION.SDK_INT >= 21) { return getOutputPointsForNewerDevices(knots); } else { return getOutputPointsForOlderDevices(knots); } } // This is for lollipop and newer devices private static int[] getOutputPointsForNewerDevices(Point[] knots) { Point[] controlPoints = calculateControlPoints(knots); Path path = new Path(); path.moveTo(0, 0); path.lineTo(knots[0].x / 255.0f, knots[0].y / 255.0f); path.moveTo(knots[0].x / 255.0f, knots[0].y / 255.0f); for (int index = 1; index < knots.length; index++) { path.quadTo( controlPoints[index - 1].x / 255.0f, controlPoints[index - 1].y / 255.0f, knots[index].x / 255.0f, knots[index].y / 255.0f ); path.moveTo(knots[index].x / 255.0f, knots[index].y / 255.0f); } path.lineTo(1, 1); path.moveTo(1, 1); float[] allPoints = new float[256]; for (int x = 0; x < 256; x++) { PathInterpolator pathInterpolator = new PathInterpolator(path); allPoints[x] = 255.0f * pathInterpolator.getInterpolation((float) x / 255.0f); } allPoints[0] = knots[0].y; allPoints[255] = knots[knots.length - 1].y; return validateCurve(allPoints); } //This is for devices older than lollipop private static int[] getOutputPointsForOlderDevices(Point[] knots) { Point[] controlPoints = calculateControlPoints(knots); Path path = new Path(); path.moveTo(0, 0); path.lineTo(knots[0].x, knots[0].y); path.moveTo(knots[0].x, knots[0].y); for (int index = 1; index < knots.length; index++) { path.quadTo(controlPoints[index - 1].x, controlPoints[index - 1].y, knots[index].x, knots[index].y); path.moveTo(knots[index].x, knots[index].y); } path.lineTo(255, 255); path.moveTo(255, 255); float[] allPoints = new float[256]; PathMeasure pm = new PathMeasure(path, false); for (int i = 0; i < 256; i++) { allPoints[i] = -1; } int x = 0; float[] acoordinates = {0, 0}; do { float pathLength = pm.getLength(); for (float i = 0; i <= pathLength; i += 0.08f) { pm.getPosTan(i, acoordinates, null); if ((int) (acoordinates[0]) > x && x < 256) { allPoints[x] = acoordinates[1]; x++; } if (x > 255) { break; } } } while (pm.nextContour()); allPoints[0] = knots[0].y; for (int i = 0; i < 256; i++) { if (allPoints[i] == -1) { allPoints[i] = allPoints[i - 1]; } } allPoints[255] = knots[knots.length - 1].y; return validateCurve(allPoints); } private static int[] validateCurve(float[] allPoints) { int[] curvedPath = new int[256]; for (int x = 0; x < 256; x++) { if (allPoints[x] > 255.0f) { curvedPath[x] = 255; } else if (allPoints[x] < 0.0f) { curvedPath[x] = 0; } else { curvedPath[x] = Math.round(allPoints[x]); } } return curvedPath; } // Calculates the control points for the specified knots private static Point[] calculateControlPoints(Point[] knots) { int n = knots.length - 1; Point[] controlPoints = new Point[n]; if (n == 1) { // Special case: Bezier curve should be a straight line. // 3P1 = 2P0 + P3 controlPoints[0] = new Point((2 * knots[0].x + knots[1].x) / 3, (2 * knots[0].y + knots[1].y) / 3); // P2 = 2P1 – P0 //controlPoints[1][0] = new Point(2*controlPoints[0][0].x - knots[0].x, 2*controlPoints[0][0].y-knots[0].y); } else { // Calculate first Bezier control points // Right hand side vector float[] rhs = new float[n]; // Set right hand side x values for (int i = 1; i < n - 1; ++i) { rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x; } rhs[0] = knots[0].x + 2 * knots[1].x; rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0f; // Get first control points x-values float[] x = getFirstControlPoints(rhs); // Set right hand side y values for (int i = 1; i < n - 1; ++i) { rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y; } rhs[0] = knots[0].y + 2 * knots[1].y; rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0f; // Get first control points y-values float[] y = getFirstControlPoints(rhs); for (int i = 0; i < n; ++i) { controlPoints[i] = new Point(x[i], y[i]); } } return controlPoints; } private static float[] getFirstControlPoints(float[] rhs) { int n = rhs.length; float[] x = new float[n]; // Solution vector. float[] tmp = new float[n]; // Temp workspace. float b = 1.0f; // Control Point Factor x[0] = rhs[0] / b; for (int i = 1; i < n; i++) // Decomposition and forward substitution. { tmp[i] = 1 / b; b = (i < n - 1 ? 4.0f : 3.5f) - tmp[i]; x[i] = (rhs[i] - x[i - 1]) / b; } for (int i = 1; i < n; i++) { x[n - i - 1] -= tmp[n - i] * x[n - i]; // Backsubstitution. } return x; } }