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.imageshow; 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 Spline()44 public Spline() { 45 mPoints = new Vector<ControlPoint>(); 46 } 47 Spline(Spline spline)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 setCurveHandle(Drawable drawable, int size)61 public static void setCurveHandle(Drawable drawable, int size) { 62 mCurveHandle = drawable; 63 mCurveHandleSize = size; 64 } 65 setCurveWidth(int width)66 public static void setCurveWidth(int width) { 67 mCurveWidth = width; 68 } 69 curveHandleSize()70 public static int curveHandleSize() { 71 return mCurveHandleSize; 72 } 73 colorForCurve(int curveIndex)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 sameValues(Spline other)86 public boolean sameValues(Spline other) { 87 if (this == other) { 88 return true; 89 } 90 if (other == null) { 91 return false; 92 } 93 94 if (getNbPoints() != other.getNbPoints()) { 95 return false; 96 } 97 98 for (int i = 0; i < getNbPoints(); i++) { 99 ControlPoint p = mPoints.elementAt(i); 100 ControlPoint otherPoint = other.mPoints.elementAt(i); 101 if (!p.sameValues(otherPoint)) { 102 return false; 103 } 104 } 105 return true; 106 } 107 didMovePoint(ControlPoint point)108 private void didMovePoint(ControlPoint point) { 109 mCurrentControlPoint = point; 110 } 111 movePoint(int pick, float x, float y)112 public void movePoint(int pick, float x, float y) { 113 if (pick < 0 || pick > mPoints.size() - 1) { 114 return; 115 } 116 ControlPoint point = mPoints.elementAt(pick); 117 point.x = x; 118 point.y = y; 119 didMovePoint(point); 120 } 121 isOriginal()122 public boolean isOriginal() { 123 if (this.getNbPoints() != 2) { 124 return false; 125 } 126 if (mPoints.elementAt(0).x != 0 || mPoints.elementAt(0).y != 1) { 127 return false; 128 } 129 if (mPoints.elementAt(1).x != 1 || mPoints.elementAt(1).y != 0) { 130 return false; 131 } 132 return true; 133 } 134 reset()135 public void reset() { 136 mPoints.clear(); 137 addPoint(0.0f, 1.0f); 138 addPoint(1.0f, 0.0f); 139 } 140 drawHandles(Canvas canvas, Drawable indicator, float centerX, float centerY)141 private void drawHandles(Canvas canvas, Drawable indicator, float centerX, float centerY) { 142 int left = (int) centerX - mCurveHandleSize / 2; 143 int top = (int) centerY - mCurveHandleSize / 2; 144 indicator.setBounds(left, top, left + mCurveHandleSize, top + mCurveHandleSize); 145 indicator.draw(canvas); 146 } 147 getAppliedCurve()148 public float[] getAppliedCurve() { 149 float[] curve = new float[256]; 150 ControlPoint[] points = new ControlPoint[mPoints.size()]; 151 for (int i = 0; i < mPoints.size(); i++) { 152 ControlPoint p = mPoints.get(i); 153 points[i] = new ControlPoint(p.x, p.y); 154 } 155 double[] derivatives = solveSystem(points); 156 int start = 0; 157 int end = 256; 158 if (points[0].x != 0) { 159 start = (int) (points[0].x * 256); 160 } 161 if (points[points.length - 1].x != 1) { 162 end = (int) (points[points.length - 1].x * 256); 163 } 164 for (int i = 0; i < start; i++) { 165 curve[i] = 1.0f - points[0].y; 166 } 167 for (int i = end; i < 256; i++) { 168 curve[i] = 1.0f - points[points.length - 1].y; 169 } 170 for (int i = start; i < end; i++) { 171 ControlPoint cur = null; 172 ControlPoint next = null; 173 double x = i / 256.0; 174 int pivot = 0; 175 for (int j = 0; j < points.length - 1; j++) { 176 if (x >= points[j].x && x <= points[j + 1].x) { 177 pivot = j; 178 } 179 } 180 cur = points[pivot]; 181 next = points[pivot + 1]; 182 if (x <= next.x) { 183 double x1 = cur.x; 184 double x2 = next.x; 185 double y1 = cur.y; 186 double y2 = next.y; 187 188 // Use the second derivatives to apply the cubic spline 189 // equation: 190 double delta = (x2 - x1); 191 double delta2 = delta * delta; 192 double b = (x - x1) / delta; 193 double a = 1 - b; 194 double ta = a * y1; 195 double tb = b * y2; 196 double tc = (a * a * a - a) * derivatives[pivot]; 197 double td = (b * b * b - b) * derivatives[pivot + 1]; 198 double y = ta + tb + (delta2 / 6) * (tc + td); 199 if (y > 1.0f) { 200 y = 1.0f; 201 } 202 if (y < 0) { 203 y = 0; 204 } 205 curve[i] = (float) (1.0f - y); 206 } else { 207 curve[i] = 1.0f - next.y; 208 } 209 } 210 return curve; 211 } 212 drawGrid(Canvas canvas, float w, float h)213 private void drawGrid(Canvas canvas, float w, float h) { 214 // Grid 215 gPaint.setARGB(128, 150, 150, 150); 216 gPaint.setStrokeWidth(1); 217 218 float stepH = h / 9; 219 float stepW = w / 9; 220 221 // central diagonal 222 gPaint.setARGB(255, 100, 100, 100); 223 gPaint.setStrokeWidth(2); 224 canvas.drawLine(0, h, w, 0, gPaint); 225 226 gPaint.setARGB(128, 200, 200, 200); 227 gPaint.setStrokeWidth(4); 228 stepH = h / 3; 229 stepW = w / 3; 230 for (int j = 1; j < 3; j++) { 231 canvas.drawLine(0, j * stepH, w, j * stepH, gPaint); 232 canvas.drawLine(j * stepW, 0, j * stepW, h, gPaint); 233 } 234 canvas.drawLine(0, 0, 0, h, gPaint); 235 canvas.drawLine(w, 0, w, h, gPaint); 236 canvas.drawLine(0, 0, w, 0, gPaint); 237 canvas.drawLine(0, h, w, h, gPaint); 238 } 239 draw(Canvas canvas, int color, int canvasWidth, int canvasHeight, boolean showHandles, boolean moving)240 public void draw(Canvas canvas, int color, int canvasWidth, int canvasHeight, 241 boolean showHandles, boolean moving) { 242 float w = canvasWidth - mCurveHandleSize; 243 float h = canvasHeight - mCurveHandleSize; 244 float dx = mCurveHandleSize / 2; 245 float dy = mCurveHandleSize / 2; 246 247 // The cubic spline equation is (from numerical recipes in C): 248 // y = a(y_i) + b(y_i+1) + c(y"_i) + d(y"_i+1) 249 // 250 // with c(y"_i) and d(y"_i+1): 251 // c(y"_i) = 1/6 (a^3 - a) delta^2 (y"_i) 252 // d(y"_i_+1) = 1/6 (b^3 - b) delta^2 (y"_i+1) 253 // 254 // and delta: 255 // delta = x_i+1 - x_i 256 // 257 // To find the second derivatives y", we can rearrange the equation as: 258 // A(y"_i-1) + B(y"_i) + C(y"_i+1) = D 259 // 260 // With the coefficients A, B, C, D: 261 // A = 1/6 (x_i - x_i-1) 262 // B = 1/3 (x_i+1 - x_i-1) 263 // C = 1/6 (x_i+1 - x_i) 264 // D = (y_i+1 - y_i)/(x_i+1 - x_i) - (y_i - y_i-1)/(x_i - x_i-1) 265 // 266 // We can now easily solve the equation to find the second derivatives: 267 ControlPoint[] points = new ControlPoint[mPoints.size()]; 268 for (int i = 0; i < mPoints.size(); i++) { 269 ControlPoint p = mPoints.get(i); 270 points[i] = new ControlPoint(p.x * w, p.y * h); 271 } 272 double[] derivatives = solveSystem(points); 273 274 Path path = new Path(); 275 path.moveTo(0, points[0].y); 276 for (int i = 0; i < points.length - 1; i++) { 277 double x1 = points[i].x; 278 double x2 = points[i + 1].x; 279 double y1 = points[i].y; 280 double y2 = points[i + 1].y; 281 282 for (double x = x1; x < x2; x += 20) { 283 // Use the second derivatives to apply the cubic spline 284 // equation: 285 double delta = (x2 - x1); 286 double delta2 = delta * delta; 287 double b = (x - x1) / delta; 288 double a = 1 - b; 289 double ta = a * y1; 290 double tb = b * y2; 291 double tc = (a * a * a - a) * derivatives[i]; 292 double td = (b * b * b - b) * derivatives[i + 1]; 293 double y = ta + tb + (delta2 / 6) * (tc + td); 294 if (y > h) { 295 y = h; 296 } 297 if (y < 0) { 298 y = 0; 299 } 300 path.lineTo((float) x, (float) y); 301 } 302 } 303 canvas.save(); 304 canvas.translate(dx, dy); 305 drawGrid(canvas, w, h); 306 ControlPoint lastPoint = points[points.length - 1]; 307 path.lineTo(lastPoint.x, lastPoint.y); 308 path.lineTo(w, lastPoint.y); 309 Paint paint = new Paint(); 310 paint.setAntiAlias(true); 311 paint.setFilterBitmap(true); 312 paint.setDither(true); 313 paint.setStyle(Paint.Style.STROKE); 314 int curveWidth = mCurveWidth; 315 if (showHandles) { 316 curveWidth *= 1.5; 317 } 318 paint.setStrokeWidth(curveWidth + 2); 319 paint.setColor(Color.BLACK); 320 canvas.drawPath(path, paint); 321 322 if (moving && mCurrentControlPoint != null) { 323 float px = mCurrentControlPoint.x * w; 324 float py = mCurrentControlPoint.y * h; 325 paint.setStrokeWidth(3); 326 paint.setColor(Color.BLACK); 327 canvas.drawLine(px, py, px, h, paint); 328 canvas.drawLine(0, py, px, py, paint); 329 paint.setStrokeWidth(1); 330 paint.setColor(color); 331 canvas.drawLine(px, py, px, h, paint); 332 canvas.drawLine(0, py, px, py, paint); 333 } 334 335 paint.setStrokeWidth(curveWidth); 336 paint.setColor(color); 337 canvas.drawPath(path, paint); 338 if (showHandles) { 339 for (int i = 0; i < points.length; i++) { 340 float x = points[i].x; 341 float y = points[i].y; 342 drawHandles(canvas, mCurveHandle, x, y); 343 } 344 } 345 canvas.restore(); 346 } 347 solveSystem(ControlPoint[] points)348 double[] solveSystem(ControlPoint[] points) { 349 int n = points.length; 350 double[][] system = new double[n][3]; 351 double[] result = new double[n]; // d 352 double[] solution = new double[n]; // returned coefficients 353 system[0][1] = 1; 354 system[n - 1][1] = 1; 355 double d6 = 1.0 / 6.0; 356 double d3 = 1.0 / 3.0; 357 358 // let's create a tridiagonal matrix representing the 359 // system, and apply the TDMA algorithm to solve it 360 // (see http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm) 361 for (int i = 1; i < n - 1; i++) { 362 double deltaPrevX = points[i].x - points[i - 1].x; 363 double deltaX = points[i + 1].x - points[i - 1].x; 364 double deltaNextX = points[i + 1].x - points[i].x; 365 double deltaNextY = points[i + 1].y - points[i].y; 366 double deltaPrevY = points[i].y - points[i - 1].y; 367 system[i][0] = d6 * deltaPrevX; // a_i 368 system[i][1] = d3 * deltaX; // b_i 369 system[i][2] = d6 * deltaNextX; // c_i 370 result[i] = (deltaNextY / deltaNextX) - (deltaPrevY / deltaPrevX); // d_i 371 } 372 373 // Forward sweep 374 for (int i = 1; i < n; i++) { 375 // m = a_i/b_i-1 376 double m = system[i][0] / system[i - 1][1]; 377 // b_i = b_i - m(c_i-1) 378 system[i][1] = system[i][1] - m * system[i - 1][2]; 379 // d_i = d_i - m(d_i-1) 380 result[i] = result[i] - m * result[i - 1]; 381 } 382 383 // Back substitution 384 solution[n - 1] = result[n - 1] / system[n - 1][1]; 385 for (int i = n - 2; i >= 0; --i) { 386 solution[i] = (result[i] - system[i][2] * solution[i + 1]) / system[i][1]; 387 } 388 return solution; 389 } 390 addPoint(float x, float y)391 public int addPoint(float x, float y) { 392 return addPoint(new ControlPoint(x, y)); 393 } 394 addPoint(ControlPoint v)395 public int addPoint(ControlPoint v) { 396 mPoints.add(v); 397 Collections.sort(mPoints); 398 return mPoints.indexOf(v); 399 } 400 deletePoint(int n)401 public void deletePoint(int n) { 402 mPoints.remove(n); 403 if (mPoints.size() < 2) { 404 reset(); 405 } 406 Collections.sort(mPoints); 407 } 408 getNbPoints()409 public int getNbPoints() { 410 return mPoints.size(); 411 } 412 getPoint(int n)413 public ControlPoint getPoint(int n) { 414 return mPoints.elementAt(n); 415 } 416 isPointContained(float x, int n)417 public boolean isPointContained(float x, int n) { 418 for (int i = 0; i < n; i++) { 419 ControlPoint point = mPoints.elementAt(i); 420 if (point.x > x) { 421 return false; 422 } 423 } 424 for (int i = n + 1; i < mPoints.size(); i++) { 425 ControlPoint point = mPoints.elementAt(i); 426 if (point.x < x) { 427 return false; 428 } 429 } 430 return true; 431 } 432 copy()433 public Spline copy() { 434 Spline spline = new Spline(); 435 for (int i = 0; i < mPoints.size(); i++) { 436 ControlPoint point = mPoints.elementAt(i); 437 spline.addPoint(point.copy()); 438 } 439 return spline; 440 } 441 show()442 public void show() { 443 Log.v(LOGTAG, "show curve " + this); 444 for (int i = 0; i < mPoints.size(); i++) { 445 ControlPoint point = mPoints.elementAt(i); 446 Log.v(LOGTAG, "point " + i + " is (" + point.x + ", " + point.y + ")"); 447 } 448 } 449 450 } 451