1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.gallery3d.filtershow.ui; 18 19 import android.graphics.Canvas; 20 import android.graphics.Color; 21 import android.graphics.Paint; 22 import android.graphics.Path; 23 import android.graphics.drawable.Drawable; 24 import android.util.Log; 25 26 import java.util.Collections; 27 import java.util.Vector; 28 29 public class Spline { 30 private final Vector<ControlPoint> mPoints; 31 private static Drawable mCurveHandle; 32 private static int mCurveHandleSize; 33 private static int mCurveWidth; 34 35 public static final int RGB = 0; 36 public static final int RED = 1; 37 public static final int GREEN = 2; 38 public static final int BLUE = 3; 39 private static final String LOGTAG = "Spline"; 40 41 private final Paint gPaint = new Paint(); 42 private ControlPoint mCurrentControlPoint = null; 43 44 public Spline() { 45 mPoints = new Vector<ControlPoint>(); 46 } 47 48 public Spline(Spline spline) { 49 mPoints = new Vector<ControlPoint>(); 50 for (int i = 0; i < spline.mPoints.size(); i++) { 51 ControlPoint p = spline.mPoints.elementAt(i); 52 ControlPoint newPoint = new ControlPoint(p); 53 mPoints.add(newPoint); 54 if (spline.mCurrentControlPoint == p) { 55 mCurrentControlPoint = newPoint; 56 } 57 } 58 Collections.sort(mPoints); 59 } 60 61 public static void setCurveHandle(Drawable drawable, int size) { 62 mCurveHandle = drawable; 63 mCurveHandleSize = size; 64 } 65 66 public static void setCurveWidth(int width) { 67 mCurveWidth = width; 68 } 69 70 public static int curveHandleSize() { 71 return mCurveHandleSize; 72 } 73 74 public static int colorForCurve(int curveIndex) { 75 switch (curveIndex) { 76 case Spline.RED: 77 return Color.RED; 78 case GREEN: 79 return Color.GREEN; 80 case BLUE: 81 return Color.BLUE; 82 } 83 return Color.WHITE; 84 } 85 86 private void didMovePoint(ControlPoint point) { 87 mCurrentControlPoint = point; 88 } 89 90 public void movePoint(int pick, float x, float y) { 91 if (pick < 0 || pick > mPoints.size() - 1) { 92 return; 93 } 94 ControlPoint point = mPoints.elementAt(pick); 95 point.x = x; 96 point.y = y; 97 didMovePoint(point); 98 } 99 100 public boolean isOriginal() { 101 if (this.getNbPoints() != 2) { 102 return false; 103 } 104 if (mPoints.elementAt(0).x != 0 || mPoints.elementAt(0).y != 1) { 105 return false; 106 } 107 if (mPoints.elementAt(1).x != 1 || mPoints.elementAt(1).y != 0) { 108 return false; 109 } 110 return true; 111 } 112 113 public void reset() { 114 mPoints.clear(); 115 addPoint(0.0f, 1.0f); 116 addPoint(1.0f, 0.0f); 117 } 118 119 private void drawHandles(Canvas canvas, Drawable indicator, float centerX, float centerY) { 120 int left = (int) centerX - mCurveHandleSize / 2; 121 int top = (int) centerY - mCurveHandleSize / 2; 122 indicator.setBounds(left, top, left + mCurveHandleSize, top + mCurveHandleSize); 123 indicator.draw(canvas); 124 } 125 126 public float[] getAppliedCurve() { 127 float[] curve = new float[256]; 128 ControlPoint[] points = new ControlPoint[mPoints.size()]; 129 for (int i = 0; i < mPoints.size(); i++) { 130 ControlPoint p = mPoints.get(i); 131 points[i] = new ControlPoint(p.x, p.y); 132 } 133 double[] derivatives = solveSystem(points); 134 int start = 0; 135 int end = 256; 136 if (points[0].x != 0) { 137 start = (int) (points[0].x * 256); 138 } 139 if (points[points.length - 1].x != 1) { 140 end = (int) (points[points.length - 1].x * 256); 141 } 142 for (int i = 0; i < start; i++) { 143 curve[i] = 1.0f - points[0].y; 144 } 145 for (int i = end; i < 256; i++) { 146 curve[i] = 1.0f - points[points.length - 1].y; 147 } 148 for (int i = start; i < end; i++) { 149 ControlPoint cur = null; 150 ControlPoint next = null; 151 double x = i / 256.0; 152 int pivot = 0; 153 for (int j = 0; j < points.length - 1; j++) { 154 if (x >= points[j].x && x <= points[j + 1].x) { 155 pivot = j; 156 } 157 } 158 cur = points[pivot]; 159 next = points[pivot + 1]; 160 if (x <= next.x) { 161 double x1 = cur.x; 162 double x2 = next.x; 163 double y1 = cur.y; 164 double y2 = next.y; 165 166 // Use the second derivatives to apply the cubic spline 167 // equation: 168 double delta = (x2 - x1); 169 double delta2 = delta * delta; 170 double b = (x - x1) / delta; 171 double a = 1 - b; 172 double ta = a * y1; 173 double tb = b * y2; 174 double tc = (a * a * a - a) * derivatives[pivot]; 175 double td = (b * b * b - b) * derivatives[pivot + 1]; 176 double y = ta + tb + (delta2 / 6) * (tc + td); 177 if (y > 1.0f) { 178 y = 1.0f; 179 } 180 if (y < 0) { 181 y = 0; 182 } 183 curve[i] = (float) (1.0f - y); 184 } else { 185 curve[i] = 1.0f - next.y; 186 } 187 } 188 return curve; 189 } 190 191 private void drawGrid(Canvas canvas, float w, float h) { 192 // Grid 193 gPaint.setARGB(128, 150, 150, 150); 194 gPaint.setStrokeWidth(1); 195 196 float stepH = h / 9; 197 float stepW = w / 9; 198 199 // central diagonal 200 gPaint.setARGB(255, 100, 100, 100); 201 gPaint.setStrokeWidth(2); 202 canvas.drawLine(0, h, w, 0, gPaint); 203 204 gPaint.setARGB(128, 200, 200, 200); 205 gPaint.setStrokeWidth(4); 206 stepH = h / 3; 207 stepW = w / 3; 208 for (int j = 1; j < 3; j++) { 209 canvas.drawLine(0, j * stepH, w, j * stepH, gPaint); 210 canvas.drawLine(j * stepW, 0, j * stepW, h, gPaint); 211 } 212 canvas.drawLine(0, 0, 0, h, gPaint); 213 canvas.drawLine(w, 0, w, h, gPaint); 214 canvas.drawLine(0, 0, w, 0, gPaint); 215 canvas.drawLine(0, h, w, h, gPaint); 216 } 217 218 public void draw(Canvas canvas, int color, int canvasWidth, int canvasHeight, 219 boolean showHandles, boolean moving) { 220 float w = canvasWidth - mCurveHandleSize; 221 float h = canvasHeight - mCurveHandleSize; 222 float dx = mCurveHandleSize / 2; 223 float dy = mCurveHandleSize / 2; 224 225 // The cubic spline equation is (from numerical recipes in C): 226 // y = a(y_i) + b(y_i+1) + c(y"_i) + d(y"_i+1) 227 // 228 // with c(y"_i) and d(y"_i+1): 229 // c(y"_i) = 1/6 (a^3 - a) delta^2 (y"_i) 230 // d(y"_i_+1) = 1/6 (b^3 - b) delta^2 (y"_i+1) 231 // 232 // and delta: 233 // delta = x_i+1 - x_i 234 // 235 // To find the second derivatives y", we can rearrange the equation as: 236 // A(y"_i-1) + B(y"_i) + C(y"_i+1) = D 237 // 238 // With the coefficients A, B, C, D: 239 // A = 1/6 (x_i - x_i-1) 240 // B = 1/3 (x_i+1 - x_i-1) 241 // C = 1/6 (x_i+1 - x_i) 242 // D = (y_i+1 - y_i)/(x_i+1 - x_i) - (y_i - y_i-1)/(x_i - x_i-1) 243 // 244 // We can now easily solve the equation to find the second derivatives: 245 ControlPoint[] points = new ControlPoint[mPoints.size()]; 246 for (int i = 0; i < mPoints.size(); i++) { 247 ControlPoint p = mPoints.get(i); 248 points[i] = new ControlPoint(p.x * w, p.y * h); 249 } 250 double[] derivatives = solveSystem(points); 251 252 Path path = new Path(); 253 path.moveTo(0, points[0].y); 254 for (int i = 0; i < points.length - 1; i++) { 255 double x1 = points[i].x; 256 double x2 = points[i + 1].x; 257 double y1 = points[i].y; 258 double y2 = points[i + 1].y; 259 260 for (double x = x1; x < x2; x += 20) { 261 // Use the second derivatives to apply the cubic spline 262 // equation: 263 double delta = (x2 - x1); 264 double delta2 = delta * delta; 265 double b = (x - x1) / delta; 266 double a = 1 - b; 267 double ta = a * y1; 268 double tb = b * y2; 269 double tc = (a * a * a - a) * derivatives[i]; 270 double td = (b * b * b - b) * derivatives[i + 1]; 271 double y = ta + tb + (delta2 / 6) * (tc + td); 272 if (y > h) { 273 y = h; 274 } 275 if (y < 0) { 276 y = 0; 277 } 278 path.lineTo((float) x, (float) y); 279 } 280 } 281 canvas.save(); 282 canvas.translate(dx, dy); 283 drawGrid(canvas, w, h); 284 ControlPoint lastPoint = points[points.length - 1]; 285 path.lineTo(lastPoint.x, lastPoint.y); 286 path.lineTo(w, lastPoint.y); 287 Paint paint = new Paint(); 288 paint.setAntiAlias(true); 289 paint.setFilterBitmap(true); 290 paint.setDither(true); 291 paint.setStyle(Paint.Style.STROKE); 292 int curveWidth = mCurveWidth; 293 if (showHandles) { 294 curveWidth *= 1.5; 295 } 296 paint.setStrokeWidth(curveWidth + 2); 297 paint.setColor(Color.BLACK); 298 canvas.drawPath(path, paint); 299 300 if (moving && mCurrentControlPoint != null) { 301 float px = mCurrentControlPoint.x * w; 302 float py = mCurrentControlPoint.y * h; 303 paint.setStrokeWidth(3); 304 paint.setColor(Color.BLACK); 305 canvas.drawLine(px, py, px, h, paint); 306 canvas.drawLine(0, py, px, py, paint); 307 paint.setStrokeWidth(1); 308 paint.setColor(color); 309 canvas.drawLine(px, py, px, h, paint); 310 canvas.drawLine(0, py, px, py, paint); 311 } 312 313 paint.setStrokeWidth(curveWidth); 314 paint.setColor(color); 315 canvas.drawPath(path, paint); 316 if (showHandles) { 317 for (int i = 0; i < points.length; i++) { 318 float x = points[i].x; 319 float y = points[i].y; 320 drawHandles(canvas, mCurveHandle, x, y); 321 } 322 } 323 canvas.restore(); 324 } 325 326 double[] solveSystem(ControlPoint[] points) { 327 int n = points.length; 328 double[][] system = new double[n][3]; 329 double[] result = new double[n]; // d 330 double[] solution = new double[n]; // returned coefficients 331 system[0][1] = 1; 332 system[n - 1][1] = 1; 333 double d6 = 1.0 / 6.0; 334 double d3 = 1.0 / 3.0; 335 336 // let's create a tridiagonal matrix representing the 337 // system, and apply the TDMA algorithm to solve it 338 // (see http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm) 339 for (int i = 1; i < n - 1; i++) { 340 double deltaPrevX = points[i].x - points[i - 1].x; 341 double deltaX = points[i + 1].x - points[i - 1].x; 342 double deltaNextX = points[i + 1].x - points[i].x; 343 double deltaNextY = points[i + 1].y - points[i].y; 344 double deltaPrevY = points[i].y - points[i - 1].y; 345 system[i][0] = d6 * deltaPrevX; // a_i 346 system[i][1] = d3 * deltaX; // b_i 347 system[i][2] = d6 * deltaNextX; // c_i 348 result[i] = (deltaNextY / deltaNextX) - (deltaPrevY / deltaPrevX); // d_i 349 } 350 351 // Forward sweep 352 for (int i = 1; i < n; i++) { 353 // m = a_i/b_i-1 354 double m = system[i][0] / system[i - 1][1]; 355 // b_i = b_i - m(c_i-1) 356 system[i][1] = system[i][1] - m * system[i - 1][2]; 357 // d_i = d_i - m(d_i-1) 358 result[i] = result[i] - m * result[i - 1]; 359 } 360 361 // Back substitution 362 solution[n - 1] = result[n - 1] / system[n - 1][1]; 363 for (int i = n - 2; i >= 0; --i) { 364 solution[i] = (result[i] - system[i][2] * solution[i + 1]) / system[i][1]; 365 } 366 return solution; 367 } 368 369 public int addPoint(float x, float y) { 370 return addPoint(new ControlPoint(x, y)); 371 } 372 373 public int addPoint(ControlPoint v) { 374 mPoints.add(v); 375 Collections.sort(mPoints); 376 return mPoints.indexOf(v); 377 } 378 379 public void deletePoint(int n) { 380 mPoints.remove(n); 381 if (mPoints.size() < 2) { 382 reset(); 383 } 384 Collections.sort(mPoints); 385 } 386 387 public int getNbPoints() { 388 return mPoints.size(); 389 } 390 391 public ControlPoint getPoint(int n) { 392 return mPoints.elementAt(n); 393 } 394 395 public boolean isPointContained(float x, int n) { 396 for (int i = 0; i < n; i++) { 397 ControlPoint point = mPoints.elementAt(i); 398 if (point.x > x) { 399 return false; 400 } 401 } 402 for (int i = n + 1; i < mPoints.size(); i++) { 403 ControlPoint point = mPoints.elementAt(i); 404 if (point.x < x) { 405 return false; 406 } 407 } 408 return true; 409 } 410 411 public Spline copy() { 412 Spline spline = new Spline(); 413 for (int i = 0; i < mPoints.size(); i++) { 414 ControlPoint point = mPoints.elementAt(i); 415 spline.addPoint(point.copy()); 416 } 417 return spline; 418 } 419 420 public void show() { 421 Log.v(LOGTAG, "show curve " + this); 422 for (int i = 0; i < mPoints.size(); i++) { 423 ControlPoint point = mPoints.elementAt(i); 424 Log.v(LOGTAG, "point " + i + " is (" + point.x + ", " + point.y + ")"); 425 } 426 } 427 428 } 429