diff options
Diffstat (limited to 'src/com/android/gallery3d/filtershow/imageshow')
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 + ")"); + } + } + +} |