summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/filtershow/imageshow
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/filtershow/imageshow')
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ControlPoint.java64
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageCurves.java445
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/Spline.java450
3 files changed, 959 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ControlPoint.java b/src/com/android/gallery3d/filtershow/imageshow/ControlPoint.java
new file mode 100644
index 000000000..aaec728a6
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ControlPoint.java
@@ -0,0 +1,64 @@
+/*
+ * 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.imageshow;
+
+public class ControlPoint implements Comparable {
+ public float x;
+ public float y;
+
+ public ControlPoint(float px, float py) {
+ x = px;
+ y = py;
+ }
+
+ public ControlPoint(ControlPoint point) {
+ x = point.x;
+ y = point.y;
+ }
+
+ public boolean sameValues(ControlPoint other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null) {
+ return false;
+ }
+
+ if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) {
+ return false;
+ }
+ if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y)) {
+ return false;
+ }
+ return true;
+ }
+
+ public ControlPoint copy() {
+ return new ControlPoint(x, y);
+ }
+
+ @Override
+ public int compareTo(Object another) {
+ ControlPoint p = (ControlPoint) another;
+ if (p.x < x) {
+ return 1;
+ } else if (p.x > x) {
+ return -1;
+ }
+ return 0;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCurves.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCurves.java
new file mode 100644
index 000000000..82c4b2fc7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCurves.java
@@ -0,0 +1,445 @@
+/*
+ * 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.imageshow;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.Editor;
+import com.android.gallery3d.filtershow.editors.EditorCurves;
+import com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+import java.util.HashMap;
+
+public class ImageCurves extends ImageShow {
+
+ private static final String LOGTAG = "ImageCurves";
+ Paint gPaint = new Paint();
+ Path gPathSpline = new Path();
+ HashMap<Integer, String> mIdStrLut;
+
+ private int mCurrentCurveIndex = Spline.RGB;
+ private boolean mDidAddPoint = false;
+ private boolean mDidDelete = false;
+ private ControlPoint mCurrentControlPoint = null;
+ private int mCurrentPick = -1;
+ private ImagePreset mLastPreset = null;
+ int[] redHistogram = new int[256];
+ int[] greenHistogram = new int[256];
+ int[] blueHistogram = new int[256];
+ Path gHistoPath = new Path();
+
+ boolean mDoingTouchMove = false;
+ private EditorCurves mEditorCurves;
+ private FilterCurvesRepresentation mFilterCurvesRepresentation;
+
+ public ImageCurves(Context context) {
+ super(context);
+ setLayerType(LAYER_TYPE_SOFTWARE, gPaint);
+ resetCurve();
+ }
+
+ public ImageCurves(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setLayerType(LAYER_TYPE_SOFTWARE, gPaint);
+ resetCurve();
+ }
+
+ @Override
+ protected boolean enableComparison() {
+ return false;
+ }
+
+ @Override
+ public boolean useUtilityPanel() {
+ return true;
+ }
+
+ private void showPopupMenu(LinearLayout accessoryViewList) {
+ final Button button = (Button) accessoryViewList.findViewById(
+ R.id.applyEffect);
+ if (button == null) {
+ return;
+ }
+ if (mIdStrLut == null){
+ mIdStrLut = new HashMap<Integer, String>();
+ mIdStrLut.put(R.id.curve_menu_rgb,
+ getContext().getString(R.string.curves_channel_rgb));
+ mIdStrLut.put(R.id.curve_menu_red,
+ getContext().getString(R.string.curves_channel_red));
+ mIdStrLut.put(R.id.curve_menu_green,
+ getContext().getString(R.string.curves_channel_green));
+ mIdStrLut.put(R.id.curve_menu_blue,
+ getContext().getString(R.string.curves_channel_blue));
+ }
+ PopupMenu popupMenu = new PopupMenu(getActivity(), button);
+ popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_curves, popupMenu.getMenu());
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ setChannel(item.getItemId());
+ button.setText(mIdStrLut.get(item.getItemId()));
+ return true;
+ }
+ });
+ Editor.hackFixStrings(popupMenu.getMenu());
+ popupMenu.show();
+ }
+
+ @Override
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ Context context = accessoryViewList.getContext();
+ Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ view.setText(context.getString(R.string.curves_channel_rgb));
+ view.setVisibility(View.VISIBLE);
+
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ showPopupMenu(accessoryViewList);
+ }
+ });
+
+ if (view != null) {
+ view.setVisibility(View.VISIBLE);
+ }
+ }
+
+ public void nextChannel() {
+ mCurrentCurveIndex = ((mCurrentCurveIndex + 1) % 4);
+ invalidate();
+ }
+
+ private ImageFilterCurves curves() {
+ String filterName = getFilterName();
+ ImagePreset p = getImagePreset();
+ if (p != null) {
+ return (ImageFilterCurves) FiltersManager.getManager().getFilter(ImageFilterCurves.class);
+ }
+ return null;
+ }
+
+ private Spline getSpline(int index) {
+ return mFilterCurvesRepresentation.getSpline(index);
+ }
+
+ @Override
+ public void resetParameter() {
+ super.resetParameter();
+ resetCurve();
+ mLastPreset = null;
+ invalidate();
+ }
+
+ public void resetCurve() {
+ if (mFilterCurvesRepresentation != null) {
+ mFilterCurvesRepresentation.reset();
+ updateCachedImage();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mFilterCurvesRepresentation == null) {
+ return;
+ }
+
+ gPaint.setAntiAlias(true);
+
+ if (getImagePreset() != mLastPreset && getFilteredImage() != null) {
+ new ComputeHistogramTask().execute(getFilteredImage());
+ mLastPreset = getImagePreset();
+ }
+
+ if (curves() == null) {
+ return;
+ }
+
+ if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.RED) {
+ drawHistogram(canvas, redHistogram, Color.RED, PorterDuff.Mode.SCREEN);
+ }
+ if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.GREEN) {
+ drawHistogram(canvas, greenHistogram, Color.GREEN, PorterDuff.Mode.SCREEN);
+ }
+ if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.BLUE) {
+ drawHistogram(canvas, blueHistogram, Color.BLUE, PorterDuff.Mode.SCREEN);
+ }
+ // We only display the other channels curves when showing the RGB curve
+ if (mCurrentCurveIndex == Spline.RGB) {
+ for (int i = 0; i < 4; i++) {
+ Spline spline = getSpline(i);
+ if (i != mCurrentCurveIndex && !spline.isOriginal()) {
+ // And we only display a curve if it has more than two
+ // points
+ spline.draw(canvas, Spline.colorForCurve(i), getWidth(),
+ getHeight(), false, mDoingTouchMove);
+ }
+ }
+ }
+ // ...but we always display the current curve.
+ getSpline(mCurrentCurveIndex)
+ .draw(canvas, Spline.colorForCurve(mCurrentCurveIndex), getWidth(), getHeight(),
+ true, mDoingTouchMove);
+
+ }
+
+ private int pickControlPoint(float x, float y) {
+ int pick = 0;
+ Spline spline = getSpline(mCurrentCurveIndex);
+ float px = spline.getPoint(0).x;
+ float py = spline.getPoint(0).y;
+ double delta = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y));
+ for (int i = 1; i < spline.getNbPoints(); i++) {
+ px = spline.getPoint(i).x;
+ py = spline.getPoint(i).y;
+ double currentDelta = Math.sqrt((px - x) * (px - x) + (py - y)
+ * (py - y));
+ if (currentDelta < delta) {
+ delta = currentDelta;
+ pick = i;
+ }
+ }
+
+ if (!mDidAddPoint && (delta * getWidth() > 100)
+ && (spline.getNbPoints() < 10)) {
+ return -1;
+ }
+
+ return pick;
+ }
+
+ private String getFilterName() {
+ return "Curves";
+ }
+
+ @Override
+ public synchronized boolean onTouchEvent(MotionEvent e) {
+ if (e.getPointerCount() != 1) {
+ return true;
+ }
+
+ if (didFinishScalingOperation()) {
+ return true;
+ }
+
+ float margin = Spline.curveHandleSize() / 2;
+ float posX = e.getX();
+ if (posX < margin) {
+ posX = margin;
+ }
+ float posY = e.getY();
+ if (posY < margin) {
+ posY = margin;
+ }
+ if (posX > getWidth() - margin) {
+ posX = getWidth() - margin;
+ }
+ if (posY > getHeight() - margin) {
+ posY = getHeight() - margin;
+ }
+ posX = (posX - margin) / (getWidth() - 2 * margin);
+ posY = (posY - margin) / (getHeight() - 2 * margin);
+
+ if (e.getActionMasked() == MotionEvent.ACTION_UP) {
+ mCurrentControlPoint = null;
+ mCurrentPick = -1;
+ updateCachedImage();
+ mDidAddPoint = false;
+ if (mDidDelete) {
+ mDidDelete = false;
+ }
+ mDoingTouchMove = false;
+ return true;
+ }
+
+ if (mDidDelete) {
+ return true;
+ }
+
+ if (curves() == null) {
+ return true;
+ }
+
+ if (e.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ mDoingTouchMove = true;
+ Spline spline = getSpline(mCurrentCurveIndex);
+ int pick = mCurrentPick;
+ if (mCurrentControlPoint == null) {
+ pick = pickControlPoint(posX, posY);
+ if (pick == -1) {
+ mCurrentControlPoint = new ControlPoint(posX, posY);
+ pick = spline.addPoint(mCurrentControlPoint);
+ mDidAddPoint = true;
+ } else {
+ mCurrentControlPoint = spline.getPoint(pick);
+ }
+ mCurrentPick = pick;
+ }
+
+ if (spline.isPointContained(posX, pick)) {
+ spline.movePoint(pick, posX, posY);
+ } else if (pick != -1 && spline.getNbPoints() > 2) {
+ spline.deletePoint(pick);
+ mDidDelete = true;
+ }
+ updateCachedImage();
+ invalidate();
+ }
+ return true;
+ }
+
+ public synchronized void updateCachedImage() {
+ if (getImagePreset() != null) {
+ resetImageCaches(this);
+ if (mEditorCurves != null) {
+ mEditorCurves.commitLocalRepresentation();
+ }
+ invalidate();
+ }
+ }
+
+ class ComputeHistogramTask extends AsyncTask<Bitmap, Void, int[]> {
+ @Override
+ protected int[] doInBackground(Bitmap... params) {
+ int[] histo = new int[256 * 3];
+ Bitmap bitmap = params[0];
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ int[] pixels = new int[w * h];
+ bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
+ for (int i = 0; i < w; i++) {
+ for (int j = 0; j < h; j++) {
+ int index = j * w + i;
+ int r = Color.red(pixels[index]);
+ int g = Color.green(pixels[index]);
+ int b = Color.blue(pixels[index]);
+ histo[r]++;
+ histo[256 + g]++;
+ histo[512 + b]++;
+ }
+ }
+ return histo;
+ }
+
+ @Override
+ protected void onPostExecute(int[] result) {
+ System.arraycopy(result, 0, redHistogram, 0, 256);
+ System.arraycopy(result, 256, greenHistogram, 0, 256);
+ System.arraycopy(result, 512, blueHistogram, 0, 256);
+ invalidate();
+ }
+ }
+
+ private void drawHistogram(Canvas canvas, int[] histogram, int color, PorterDuff.Mode mode) {
+ int max = 0;
+ for (int i = 0; i < histogram.length; i++) {
+ if (histogram[i] > max) {
+ max = histogram[i];
+ }
+ }
+ float w = getWidth() - Spline.curveHandleSize();
+ float h = getHeight() - Spline.curveHandleSize() / 2.0f;
+ float dx = Spline.curveHandleSize() / 2.0f;
+ float wl = w / histogram.length;
+ float wh = (0.3f * h) / max;
+ Paint paint = new Paint();
+ paint.setARGB(100, 255, 255, 255);
+ paint.setStrokeWidth((int) Math.ceil(wl));
+
+ Paint paint2 = new Paint();
+ paint2.setColor(color);
+ paint2.setStrokeWidth(6);
+ paint2.setXfermode(new PorterDuffXfermode(mode));
+ gHistoPath.reset();
+ gHistoPath.moveTo(dx, h);
+ boolean firstPointEncountered = false;
+ float prev = 0;
+ float last = 0;
+ for (int i = 0; i < histogram.length; i++) {
+ float x = i * wl + dx;
+ float l = histogram[i] * wh;
+ if (l != 0) {
+ float v = h - (l + prev) / 2.0f;
+ if (!firstPointEncountered) {
+ gHistoPath.lineTo(x, h);
+ firstPointEncountered = true;
+ }
+ gHistoPath.lineTo(x, v);
+ prev = l;
+ last = x;
+ }
+ }
+ gHistoPath.lineTo(last, h);
+ gHistoPath.lineTo(w, h);
+ gHistoPath.close();
+ canvas.drawPath(gHistoPath, paint2);
+ paint2.setStrokeWidth(2);
+ paint2.setStyle(Paint.Style.STROKE);
+ paint2.setARGB(255, 200, 200, 200);
+ canvas.drawPath(gHistoPath, paint2);
+ }
+
+ public void setChannel(int itemId) {
+ switch (itemId) {
+ case R.id.curve_menu_rgb: {
+ mCurrentCurveIndex = Spline.RGB;
+ break;
+ }
+ case R.id.curve_menu_red: {
+ mCurrentCurveIndex = Spline.RED;
+ break;
+ }
+ case R.id.curve_menu_green: {
+ mCurrentCurveIndex = Spline.GREEN;
+ break;
+ }
+ case R.id.curve_menu_blue: {
+ mCurrentCurveIndex = Spline.BLUE;
+ break;
+ }
+ }
+ mEditorCurves.commitLocalRepresentation();
+ invalidate();
+ }
+
+ public void setEditor(EditorCurves editorCurves) {
+ mEditorCurves = editorCurves;
+ }
+
+ public void setFilterDrawRepresentation(FilterCurvesRepresentation drawRep) {
+ mFilterCurvesRepresentation = drawRep;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/Spline.java b/src/com/android/gallery3d/filtershow/imageshow/Spline.java
new file mode 100644
index 000000000..3c27a4d0f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/Spline.java
@@ -0,0 +1,450 @@
+/*
+ * 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.imageshow;
+
+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<ControlPoint> 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<ControlPoint>();
+ }
+
+ public Spline(Spline spline) {
+ mPoints = new Vector<ControlPoint>();
+ 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;
+ }
+
+ public boolean sameValues(Spline other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null) {
+ return false;
+ }
+
+ if (getNbPoints() != other.getNbPoints()) {
+ return false;
+ }
+
+ for (int i = 0; i < getNbPoints(); i++) {
+ ControlPoint p = mPoints.elementAt(i);
+ ControlPoint otherPoint = other.mPoints.elementAt(i);
+ if (!p.sameValues(otherPoint)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ 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 + ")");
+ }
+ }
+
+}