/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.gallery3d.filtershow.ui; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.drawable.Drawable; import android.util.Log; import java.util.Collections; import java.util.Vector; public class Spline { private final Vector mPoints; private static Drawable mCurveHandle; private static int mCurveHandleSize; private static int mCurveWidth; public static final int RGB = 0; public static final int RED = 1; public static final int GREEN = 2; public static final int BLUE = 3; private static final String LOGTAG = "Spline"; private final Paint gPaint = new Paint(); private ControlPoint mCurrentControlPoint = null; public Spline() { mPoints = new Vector(); } public Spline(Spline spline) { mPoints = new Vector(); for (int i = 0; i < spline.mPoints.size(); i++) { ControlPoint p = spline.mPoints.elementAt(i); ControlPoint newPoint = new ControlPoint(p); mPoints.add(newPoint); if (spline.mCurrentControlPoint == p) { mCurrentControlPoint = newPoint; } } Collections.sort(mPoints); } public static void setCurveHandle(Drawable drawable, int size) { mCurveHandle = drawable; mCurveHandleSize = size; } public static void setCurveWidth(int width) { mCurveWidth = width; } public static int curveHandleSize() { return mCurveHandleSize; } public static int colorForCurve(int curveIndex) { switch (curveIndex) { case Spline.RED: return Color.RED; case GREEN: return Color.GREEN; case BLUE: return Color.BLUE; } return Color.WHITE; } private void didMovePoint(ControlPoint point) { mCurrentControlPoint = point; } public void movePoint(int pick, float x, float y) { if (pick < 0 || pick > mPoints.size() - 1) { return; } ControlPoint point = mPoints.elementAt(pick); point.x = x; point.y = y; didMovePoint(point); } public boolean isOriginal() { if (this.getNbPoints() != 2) { return false; } if (mPoints.elementAt(0).x != 0 || mPoints.elementAt(0).y != 1) { return false; } if (mPoints.elementAt(1).x != 1 || mPoints.elementAt(1).y != 0) { return false; } return true; } public void reset() { mPoints.clear(); addPoint(0.0f, 1.0f); addPoint(1.0f, 0.0f); } private void drawHandles(Canvas canvas, Drawable indicator, float centerX, float centerY) { int left = (int) centerX - mCurveHandleSize / 2; int top = (int) centerY - mCurveHandleSize / 2; indicator.setBounds(left, top, left + mCurveHandleSize, top + mCurveHandleSize); indicator.draw(canvas); } public float[] getAppliedCurve() { float[] curve = new float[256]; ControlPoint[] points = new ControlPoint[mPoints.size()]; for (int i = 0; i < mPoints.size(); i++) { ControlPoint p = mPoints.get(i); points[i] = new ControlPoint(p.x, p.y); } double[] derivatives = solveSystem(points); int start = 0; int end = 256; if (points[0].x != 0) { start = (int) (points[0].x * 256); } if (points[points.length - 1].x != 1) { end = (int) (points[points.length - 1].x * 256); } for (int i = 0; i < start; i++) { curve[i] = 1.0f - points[0].y; } for (int i = end; i < 256; i++) { curve[i] = 1.0f - points[points.length - 1].y; } for (int i = start; i < end; i++) { ControlPoint cur = null; ControlPoint next = null; double x = i / 256.0; int pivot = 0; for (int j = 0; j < points.length - 1; j++) { if (x >= points[j].x && x <= points[j + 1].x) { pivot = j; } } cur = points[pivot]; next = points[pivot + 1]; if (x <= next.x) { double x1 = cur.x; double x2 = next.x; double y1 = cur.y; double y2 = next.y; // Use the second derivatives to apply the cubic spline // equation: double delta = (x2 - x1); double delta2 = delta * delta; double b = (x - x1) / delta; double a = 1 - b; double ta = a * y1; double tb = b * y2; double tc = (a * a * a - a) * derivatives[pivot]; double td = (b * b * b - b) * derivatives[pivot + 1]; double y = ta + tb + (delta2 / 6) * (tc + td); if (y > 1.0f) { y = 1.0f; } if (y < 0) { y = 0; } curve[i] = (float) (1.0f - y); } else { curve[i] = 1.0f - next.y; } } return curve; } private void drawGrid(Canvas canvas, float w, float h) { // Grid gPaint.setARGB(128, 150, 150, 150); gPaint.setStrokeWidth(1); float stepH = h / 9; float stepW = w / 9; // central diagonal gPaint.setARGB(255, 100, 100, 100); gPaint.setStrokeWidth(2); canvas.drawLine(0, h, w, 0, gPaint); gPaint.setARGB(128, 200, 200, 200); gPaint.setStrokeWidth(4); stepH = h / 3; stepW = w / 3; for (int j = 1; j < 3; j++) { canvas.drawLine(0, j * stepH, w, j * stepH, gPaint); canvas.drawLine(j * stepW, 0, j * stepW, h, gPaint); } canvas.drawLine(0, 0, 0, h, gPaint); canvas.drawLine(w, 0, w, h, gPaint); canvas.drawLine(0, 0, w, 0, gPaint); canvas.drawLine(0, h, w, h, gPaint); } public void draw(Canvas canvas, int color, int canvasWidth, int canvasHeight, boolean showHandles, boolean moving) { float w = canvasWidth - mCurveHandleSize; float h = canvasHeight - mCurveHandleSize; float dx = mCurveHandleSize / 2; float dy = mCurveHandleSize / 2; // The cubic spline equation is (from numerical recipes in C): // y = a(y_i) + b(y_i+1) + c(y"_i) + d(y"_i+1) // // with c(y"_i) and d(y"_i+1): // c(y"_i) = 1/6 (a^3 - a) delta^2 (y"_i) // d(y"_i_+1) = 1/6 (b^3 - b) delta^2 (y"_i+1) // // and delta: // delta = x_i+1 - x_i // // To find the second derivatives y", we can rearrange the equation as: // A(y"_i-1) + B(y"_i) + C(y"_i+1) = D // // With the coefficients A, B, C, D: // A = 1/6 (x_i - x_i-1) // B = 1/3 (x_i+1 - x_i-1) // C = 1/6 (x_i+1 - x_i) // D = (y_i+1 - y_i)/(x_i+1 - x_i) - (y_i - y_i-1)/(x_i - x_i-1) // // We can now easily solve the equation to find the second derivatives: ControlPoint[] points = new ControlPoint[mPoints.size()]; for (int i = 0; i < mPoints.size(); i++) { ControlPoint p = mPoints.get(i); points[i] = new ControlPoint(p.x * w, p.y * h); } double[] derivatives = solveSystem(points); Path path = new Path(); path.moveTo(0, points[0].y); for (int i = 0; i < points.length - 1; i++) { double x1 = points[i].x; double x2 = points[i + 1].x; double y1 = points[i].y; double y2 = points[i + 1].y; for (double x = x1; x < x2; x += 20) { // Use the second derivatives to apply the cubic spline // equation: double delta = (x2 - x1); double delta2 = delta * delta; double b = (x - x1) / delta; double a = 1 - b; double ta = a * y1; double tb = b * y2; double tc = (a * a * a - a) * derivatives[i]; double td = (b * b * b - b) * derivatives[i + 1]; double y = ta + tb + (delta2 / 6) * (tc + td); if (y > h) { y = h; } if (y < 0) { y = 0; } path.lineTo((float) x, (float) y); } } canvas.save(); canvas.translate(dx, dy); drawGrid(canvas, w, h); ControlPoint lastPoint = points[points.length - 1]; path.lineTo(lastPoint.x, lastPoint.y); path.lineTo(w, lastPoint.y); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); paint.setStyle(Paint.Style.STROKE); int curveWidth = mCurveWidth; if (showHandles) { curveWidth *= 1.5; } paint.setStrokeWidth(curveWidth + 2); paint.setColor(Color.BLACK); canvas.drawPath(path, paint); if (moving && mCurrentControlPoint != null) { float px = mCurrentControlPoint.x * w; float py = mCurrentControlPoint.y * h; paint.setStrokeWidth(3); paint.setColor(Color.BLACK); canvas.drawLine(px, py, px, h, paint); canvas.drawLine(0, py, px, py, paint); paint.setStrokeWidth(1); paint.setColor(color); canvas.drawLine(px, py, px, h, paint); canvas.drawLine(0, py, px, py, paint); } paint.setStrokeWidth(curveWidth); paint.setColor(color); canvas.drawPath(path, paint); if (showHandles) { for (int i = 0; i < points.length; i++) { float x = points[i].x; float y = points[i].y; drawHandles(canvas, mCurveHandle, x, y); } } canvas.restore(); } double[] solveSystem(ControlPoint[] points) { int n = points.length; double[][] system = new double[n][3]; double[] result = new double[n]; // d double[] solution = new double[n]; // returned coefficients system[0][1] = 1; system[n - 1][1] = 1; double d6 = 1.0 / 6.0; double d3 = 1.0 / 3.0; // let's create a tridiagonal matrix representing the // system, and apply the TDMA algorithm to solve it // (see http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm) for (int i = 1; i < n - 1; i++) { double deltaPrevX = points[i].x - points[i - 1].x; double deltaX = points[i + 1].x - points[i - 1].x; double deltaNextX = points[i + 1].x - points[i].x; double deltaNextY = points[i + 1].y - points[i].y; double deltaPrevY = points[i].y - points[i - 1].y; system[i][0] = d6 * deltaPrevX; // a_i system[i][1] = d3 * deltaX; // b_i system[i][2] = d6 * deltaNextX; // c_i result[i] = (deltaNextY / deltaNextX) - (deltaPrevY / deltaPrevX); // d_i } // Forward sweep for (int i = 1; i < n; i++) { // m = a_i/b_i-1 double m = system[i][0] / system[i - 1][1]; // b_i = b_i - m(c_i-1) system[i][1] = system[i][1] - m * system[i - 1][2]; // d_i = d_i - m(d_i-1) result[i] = result[i] - m * result[i - 1]; } // Back substitution solution[n - 1] = result[n - 1] / system[n - 1][1]; for (int i = n - 2; i >= 0; --i) { solution[i] = (result[i] - system[i][2] * solution[i + 1]) / system[i][1]; } return solution; } public int addPoint(float x, float y) { return addPoint(new ControlPoint(x, y)); } public int addPoint(ControlPoint v) { mPoints.add(v); Collections.sort(mPoints); return mPoints.indexOf(v); } public void deletePoint(int n) { mPoints.remove(n); if (mPoints.size() < 2) { reset(); } Collections.sort(mPoints); } public int getNbPoints() { return mPoints.size(); } public ControlPoint getPoint(int n) { return mPoints.elementAt(n); } public boolean isPointContained(float x, int n) { for (int i = 0; i < n; i++) { ControlPoint point = mPoints.elementAt(i); if (point.x > x) { return false; } } for (int i = n + 1; i < mPoints.size(); i++) { ControlPoint point = mPoints.elementAt(i); if (point.x < x) { return false; } } return true; } public Spline copy() { Spline spline = new Spline(); for (int i = 0; i < mPoints.size(); i++) { ControlPoint point = mPoints.elementAt(i); spline.addPoint(point.copy()); } return spline; } public void show() { Log.v(LOGTAG, "show curve " + this); for (int i = 0; i < mPoints.size(); i++) { ControlPoint point = mPoints.elementAt(i); Log.v(LOGTAG, "point " + i + " is (" + point.x + ", " + point.y + ")"); } } }