import com.android.gallery3d.filtershow.tools.SaveCopyTask;
import com.android.gallery3d.filtershow.ui.ImageButtonTitle;
import com.android.gallery3d.filtershow.ui.ImageCurves;
+import com.android.gallery3d.filtershow.ui.Spline;
import java.io.File;
import java.lang.ref.WeakReference;
ImageSmallFilter.setMargin((int) getPixelsFromDip(6));
ImageSmallFilter.setTextMargin((int) getPixelsFromDip(4));
mImageBorderSize = (int) getPixelsFromDip(20);
+ Drawable curveHandle = getResources().getDrawable(R.drawable.camera_crop_holo);
+ int curveHandleSize = (int) getResources().getDimension(R.dimen.crop_indicator_size);
+ Spline.setCurveHandle(curveHandle, curveHandleSize);
+ Spline.setCurveWidth((int) getPixelsFromDip(3));
setContentView(R.layout.filtershow_activity);
ActionBar actionBar = getActionBar();
mPanelController.addComponent(mColorsButton, findViewById(R.id.shadowRecoveryButton));
mPanelController.addView(findViewById(R.id.applyEffect));
-
+ mPanelController.addView(findViewById(R.id.pickCurvesChannel));
findViewById(R.id.resetOperationsButton).setOnClickListener(
createOnClickResetOperationsButton());
adapter.reset();
ImagePreset original = new ImagePreset(adapter.getItem(0));
mImageShow.setImagePreset(original);
+ mPanelController.resetParameters();
invalidateViews();
}
imageShow.setPanelController(this);
}
+ public void resetParameters() {
+ mCurrentImage.resetParameter();
+ showPanel(mCurrentPanel);
+ mCurrentImage.select();
+ }
+
public boolean onBackPressed() {
if (mUtilityPanel == null || !mUtilityPanel.selected()) {
return true;
}
- mCurrentImage.resetParameter();
- showPanel(mCurrentPanel);
- mCurrentImage.select();
+ resetParameters();
return false;
}
}
}
+ if (view.getId() == R.id.pickCurvesChannel) {
+ ImageCurves curves = (ImageCurves) showImageView(R.id.imageCurves);
+ curves.nextChannel();
+ return;
+ }
+
if (mCurrentImage != null) {
mCurrentImage.unselect();
}
String ename = curves.getContext().getString(R.string.curvesRGB);
mUtilityPanel.setEffectName(ename);
mUtilityPanel.setShowParameter(false);
- curves.setUseRed(true);
- curves.setUseGreen(true);
- curves.setUseBlue(true);
curves.reloadCurve();
mCurrentImage = curves;
break;
private static final String LOGTAG = "ImageFilterCurves";
- private final float[] mCurve = new float[256];
-
- private boolean mUseRed = true;
- private boolean mUseGreen = true;
- private boolean mUseBlue = true;
- private Spline mSpline = null;
+ private final Spline[] mSplines = new Spline[4];
public ImageFilterCurves() {
mName = "Curves";
@Override
public ImageFilter clone() throws CloneNotSupportedException {
ImageFilterCurves filter = (ImageFilterCurves) super.clone();
- filter.setCurve(mCurve);
- filter.setSpline(new Spline(mSpline));
- return filter;
- }
-
- public void setUseRed(boolean value) {
- mUseRed = value;
- }
-
- public void setUseGreen(boolean value) {
- mUseGreen = value;
- }
-
- public void setUseBlue(boolean value) {
- mUseBlue = value;
- }
-
- public void setCurve(float[] curve) {
- for (int i = 0; i < curve.length; i++) {
- if (i < 256) {
- mCurve[i] = curve[i];
+ for (int i = 0; i < 4; i++) {
+ if (mSplines[i] != null) {
+ filter.setSpline(new Spline(mSplines[i]), i);
}
}
+ return filter;
}
@Override
return false;
}
ImageFilterCurves curve = (ImageFilterCurves) filter;
- for (int i = 0; i < 256; i++) {
- if (curve.mCurve[i] != mCurve[i]) {
+ for (int i = 0; i < 4; i++) {
+ if (mSplines[i] != curve.mSplines[i]) {
return false;
}
}
return true;
}
- public void populateArray(int[] array) {
+ public void populateArray(int[] array, int curveIndex) {
+ Spline spline = mSplines[curveIndex];
+ if (spline == null) {
+ return;
+ }
+ float[] curve = spline.getAppliedCurve();
for (int i = 0; i < 256; i++) {
- array[i] = (int) (mCurve[i]);
+ array[i] = (int) (curve[i] * 255);
}
}
@Override
public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+ if (!mSplines[Spline.RGB].isOriginal()) {
+ int[] rgbGradient = new int[256];
+ populateArray(rgbGradient, Spline.RGB);
+ nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
+ rgbGradient, rgbGradient, rgbGradient);
+ }
+
int[] redGradient = null;
- if (mUseRed) {
+ if (!mSplines[Spline.RED].isOriginal()) {
redGradient = new int[256];
- populateArray(redGradient);
+ populateArray(redGradient, Spline.RED);
}
int[] greenGradient = null;
- if (mUseGreen) {
+ if (!mSplines[Spline.GREEN].isOriginal()) {
greenGradient = new int[256];
- populateArray(greenGradient);
+ populateArray(greenGradient, Spline.GREEN);
}
int[] blueGradient = null;
- if (mUseBlue) {
+ if (!mSplines[Spline.BLUE].isOriginal()) {
blueGradient = new int[256];
- populateArray(blueGradient);
+ populateArray(blueGradient, Spline.BLUE);
}
nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
return bitmap;
}
- public void setSpline(Spline spline) {
- mSpline = spline;
+ public void setSpline(Spline spline, int splineIndex) {
+ mSplines[splineIndex] = spline;
}
- public Spline getSpline() {
- return mSpline;
+ public Spline getSpline(int splineIndex) {
+ return mSplines[splineIndex];
}
}
package com.android.gallery3d.filtershow.ui;
public class ControlPoint implements Comparable {
+ public float x;
+ public float y;
+
public ControlPoint(float px, float py) {
x = px;
y = py;
}
- public ControlPoint multiply(float m) {
- return new ControlPoint(x * m, y * m);
- }
-
- public ControlPoint add(ControlPoint v) {
- return new ControlPoint(x + v.x, y + v.y);
+ public ControlPoint(ControlPoint point) {
+ x = point.x;
+ y = point.y;
}
- public ControlPoint sub(ControlPoint v) {
- return new ControlPoint(x - v.x, y - v.y);
- }
-
- public float x;
- public float y;
-
public ControlPoint copy() {
return new ControlPoint(x, y);
}
package com.android.gallery3d.filtershow.ui;
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.PopupMenu;
-import android.widget.Toast;
-import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
import com.android.gallery3d.filtershow.imageshow.ImageSlave;
import com.android.gallery3d.filtershow.presets.ImagePreset;
private static final String LOGTAG = "ImageCurves";
Paint gPaint = new Paint();
- Spline mSpline = null;
+ Spline[] mSplines = new Spline[4];
Path gPathSpline = new Path();
- float[] mAppliedCurve = new float[256];
+
+ private int mCurrentCurveIndex = 0;
private boolean mDidAddPoint = false;
private boolean mDidDelete = false;
private ControlPoint mCurrentControlPoint = null;
- private boolean mUseRed = true;
- private boolean mUseGreen = true;
- private boolean mUseBlue = true;
+ private ImagePreset mLastPreset = null;
+ int[] redHistogram = new int[256];
+ int[] greenHistogram = new int[256];
+ int[] blueHistogram = new int[256];
+ Path gHistoPath = new Path();
public ImageCurves(Context context) {
super(context);
resetCurve();
}
+ public void nextChannel() {
+ mCurrentCurveIndex = ((mCurrentCurveIndex + 1) % 4);
+ invalidate();
+ }
+
@Override
public boolean showTitle() {
return false;
}
- public void setUseRed(boolean value) {
- mUseRed = value;
- }
-
- public void setUseGreen(boolean value) {
- mUseGreen = value;
- }
-
- public void setUseBlue(boolean value) {
- mUseBlue = value;
- }
-
public void reloadCurve() {
if (getMaster() != null) {
String filterName = getFilterName();
resetCurve();
return;
}
- mSpline = new Spline(filter.getSpline());
+ for (int i = 0; i < 4; i++) {
+ Spline spline = filter.getSpline(i);
+ if (spline != null) {
+ mSplines[i] = new Spline(spline);
+ }
+ }
applyNewCurve();
}
}
public void resetParameter() {
super.resetParameter();
resetCurve();
+ mLastPreset = null;
+ invalidate();
}
public void resetCurve() {
- mSpline = new Spline();
+ Spline spline = new Spline();
- mSpline.addPoint(0.0f, 1.0f);
- mSpline.addPoint(1.0f, 0.0f);
+ spline.addPoint(0.0f, 1.0f);
+ spline.addPoint(1.0f, 0.0f);
+
+ for (int i = 0; i < 4; i++) {
+ mSplines[i] = new Spline(spline);
+ }
if (getMaster() != null) {
applyNewCurve();
}
super.onDraw(canvas);
gPaint.setAntiAlias(true);
- gPaint.setFilterBitmap(true);
- gPaint.setDither(true);
-
- drawGrid(canvas);
- drawSpline(canvas);
-
- drawToast(canvas);
- }
-
- private void drawGrid(Canvas canvas) {
- float w = getWidth();
- float h = getHeight();
- // 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);
+ if (getImagePreset() != mLastPreset) {
+ new ComputeHistogramTask().execute(mFilteredImage);
+ mLastPreset = getImagePreset();
}
- }
-
- private void drawSpline(Canvas canvas) {
- float w = getWidth();
- float h = getHeight();
- gPathSpline.reset();
- for (int x = 0; x < w; x += 11) {
- float fx = x / w;
- ControlPoint drawPoint = mSpline.getPoint(fx);
- float newX = drawPoint.x * w;
- float newY = drawPoint.y * h;
- if (x == 0) {
- gPathSpline.moveTo(newX, newY);
- } else {
- gPathSpline.lineTo(newX, newY);
+ 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 = mSplines[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);
+ }
}
}
+ // ...but we always display the current curve.
+ mSplines[mCurrentCurveIndex]
+ .draw(canvas, Spline.colorForCurve(mCurrentCurveIndex), getWidth(), getHeight(),
+ true);
+ drawToast(canvas);
- gPaint.setStrokeWidth(10);
- gPaint.setStyle(Paint.Style.STROKE);
- gPaint.setARGB(255, 50, 50, 50);
- canvas.drawPath(gPathSpline, gPaint);
- gPaint.setStrokeWidth(5);
- gPaint.setARGB(255, 150, 150, 150);
- canvas.drawPath(gPathSpline, gPaint);
-
- gPaint.setARGB(255, 150, 150, 150);
- for (int j = 1; j < mSpline.getNbPoints() - 1; j++) {
- ControlPoint point = mSpline.getPoint(j);
- gPaint.setStrokeWidth(10);
- gPaint.setARGB(255, 50, 50, 100);
- canvas.drawCircle(point.x * w, point.y * h, 30, gPaint);
- gPaint.setStrokeWidth(5);
- gPaint.setARGB(255, 150, 150, 200);
- canvas.drawCircle(point.x * w, point.y * h, 30, gPaint);
- }
}
private int pickControlPoint(float x, float y) {
int pick = 0;
- float px = mSpline.getPoint(0).x;
- float py = mSpline.getPoint(0).y;
+ float px = mSplines[mCurrentCurveIndex].getPoint(0).x;
+ float py = mSplines[mCurrentCurveIndex].getPoint(0).y;
double delta = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y));
- for (int i = 1; i < mSpline.getNbPoints(); i++) {
- px = mSpline.getPoint(i).x;
- py = mSpline.getPoint(i).y;
+ for (int i = 1; i < mSplines[mCurrentCurveIndex].getNbPoints(); i++) {
+ px = mSplines[mCurrentCurveIndex].getPoint(i).x;
+ py = mSplines[mCurrentCurveIndex].getPoint(i).y;
double currentDelta = Math.sqrt((px - x) * (px - x) + (py - y)
* (py - y));
if (currentDelta < delta) {
}
if (!mDidAddPoint && (delta * getWidth() > 100)
- && (mSpline.getNbPoints() < 10)) {
+ && (mSplines[mCurrentCurveIndex].getNbPoints() < 10)) {
return -1;
}
- return pick;// mSpline.getPoint(pick);
- }
-
- public void showPopupMenu(View v) {
- // TODO: sort out the popup menu UI for curves
- final Context context = v.getContext();
- PopupMenu popupMenu = new PopupMenu(v.getContext(), v);
- popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_curves,
- popupMenu.getMenu());
-
- popupMenu
- .setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- Toast.makeText(context, item.toString(),
- Toast.LENGTH_LONG).show();
- return true;
- }
- });
-
- popupMenu.show();
+ return pick;
}
private String getFilterName() {
- String filterName = "Curves";
- if (mUseRed && !mUseGreen && !mUseBlue) {
- filterName = "CurvesRed";
- } else if (!mUseRed && mUseGreen && !mUseBlue) {
- filterName = "CurvesGreen";
- } else if (!mUseRed && !mUseGreen && mUseBlue) {
- filterName = "CurvesBlue";
- }
- return filterName;
+ return "Curves";
}
@Override
public synchronized boolean onTouchEvent(MotionEvent e) {
float posX = e.getX() / getWidth();
- float posY = e.getY() / getHeight();
-
- /*
- * if (true) { showPopupMenu(this); return true; }
- */
-
- // ControlPoint point = null;
-
- // Log.v(LOGTAG, "onTouchEvent - " + e + " action masked : " +
- // e.getActionMasked());
+ float posY = e.getY();
+ float margin = Spline.curveHandleSize() / 2;
+ if (posY < margin) {
+ posY = margin;
+ }
+ if (posY > getHeight() - margin) {
+ posY = getHeight() - margin;
+ }
+ posY = (posY - margin) / (getHeight() - 2 * margin);
if (e.getActionMasked() == MotionEvent.ACTION_UP) {
applyNewCurve();
- // Log.v(LOGTAG, "ACTION UP, mCurrentControlPoint set to null!");
mCurrentControlPoint = null;
- String name = null;
- if (mUseRed && mUseGreen && mUseBlue) {
- name = "Curves (RGB)";
- } else if (mUseRed) {
- name = "Curves (Red)";
- } else if (mUseGreen) {
- name = "Curves (Green)";
- } else if (mUseBlue) {
- name = "Curves (Blue)";
- }
-
-
- ImagePreset copy = new ImagePreset(getImagePreset(),name);
+ String name = "Curves";
+ ImagePreset copy = new ImagePreset(getImagePreset(), name);
copy.setIsFx(false);
mImageLoader.getHistory().insert(copy, 0);
if (mDidDelete) {
return true;
}
- // Log.v(LOGTAG, "ACTION DOWN, mCurrentControlPoint is " +
- // mCurrentControlPoint);
int pick = pickControlPoint(posX, posY);
- // Log.v(LOGTAG, "ACTION DOWN, pick is " + pick);
if (mCurrentControlPoint == null) {
if (pick == -1) {
mCurrentControlPoint = new ControlPoint(posX, posY);
- mSpline.addPoint(mCurrentControlPoint);
+ mSplines[mCurrentCurveIndex].addPoint(mCurrentControlPoint);
mDidAddPoint = true;
- // Log.v(LOGTAG, "ACTION DOWN - 2, added a new control point! "
- // + mCurrentControlPoint);
-
} else {
- mCurrentControlPoint = mSpline.getPoint(pick);
- // Log.v(LOGTAG, "ACTION DOWN - 2, picking up control point " +
- // mCurrentControlPoint + " at pick " + pick);
+ mCurrentControlPoint = mSplines[mCurrentCurveIndex].getPoint(pick);
}
}
- // Log.v(LOGTAG, "ACTION DOWN - 3, pick is " + pick);
-
- if (!((mCurrentControlPoint.x == 0 && mCurrentControlPoint.y == 1) || (mCurrentControlPoint.x == 1 && mCurrentControlPoint.y == 0))) {
- if (mSpline.isPointContained(posX, pick)) {
- mCurrentControlPoint.x = posX;
- mCurrentControlPoint.y = posY;
- // Log.v(LOGTAG, "ACTION DOWN - 4, move control point " +
- // mCurrentControlPoint);
- } else if (pick != -1) {
- // Log.v(LOGTAG, "ACTION DOWN - 4, delete pick " + pick);
- mSpline.deletePoint(pick);
- mDidDelete = true;
- }
+
+ if (mSplines[mCurrentCurveIndex].isPointContained(posX, pick)) {
+ mCurrentControlPoint.x = posX;
+ mCurrentControlPoint.y = posY;
+ } else if (pick != -1) {
+ mSplines[mCurrentCurveIndex].deletePoint(pick);
+ mDidDelete = true;
}
- // Log.v(LOGTAG, "ACTION DOWN - 5, DONE");
applyNewCurve();
invalidate();
return true;
}
public synchronized void applyNewCurve() {
- ControlPoint[] points = new ControlPoint[256];
- for (int i = 0; i < 256; i++) {
- float v = i / 255.0f;
- ControlPoint p = mSpline.getPoint(v);
- points[i] = p;
- }
- for (int i = 0; i < 256; i++) {
- mAppliedCurve[i] = -1;
- }
- for (int i = 0; i < 256; i++) {
- int index = (int) (points[i].x * 255);
- if (index >= 0 && index <= 255) {
- float v = 1.0f - points[i].y;
- if (v < 0) {
- v = 0;
- }
- if (v > 1.0f) {
- v = 1.0f;
- }
- mAppliedCurve[index] = v;
- }
- }
- float prev = 0;
- for (int i = 0; i < 256; i++) {
- if (mAppliedCurve[i] == -1) {
- // need to interpolate...
- int j = i + 1;
- if (j > 255) {
- j = 255;
- }
- for (; j < 256; j++) {
- if (mAppliedCurve[j] != -1) {
- break;
- }
- }
- if (j > 255) {
- j = 255;
- }
- // interpolate linearly between i and j - 1
- float start = prev;
- float end = mAppliedCurve[j];
- float delta = (end - start) / (j - i + 1);
- for (int k = i; k < j; k++) {
- start = start + delta;
- mAppliedCurve[k] = start;
- }
- i = j;
- }
- prev = mAppliedCurve[i];
- }
- for (int i = 0; i < 256; i++) {
- mAppliedCurve[i] = mAppliedCurve[i] * 255;
- }
- float[] appliedCurve = new float[256];
- for (int i = 0; i < 256; i++) {
- appliedCurve[i] = mAppliedCurve[i];
- }
// update image
if (getImagePreset() != null) {
String filterName = getFilterName();
}
if (filter != null) {
- filter.setSpline(new Spline(mSpline));
- filter.setCurve(appliedCurve);
- filter.setUseRed(mUseRed);
- filter.setUseGreen(mUseGreen);
- filter.setUseBlue(mUseBlue);
+ for (int i = 0; i < 4; i++) {
+ filter.setSpline(new Spline(mSplines[i]), i);
+ }
}
mImageLoader.resetImageForPreset(getImagePreset(), this);
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();
+ float h = getHeight();
+ 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(0, h);
+ boolean firstPointEncountered = false;
+ float prev = 0;
+ float last = 0;
+ for (int i = 0; i < histogram.length; i++) {
+ float x = i * wl;
+ 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);
+ }
}
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 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 final Paint gPaint = new Paint();
+
public Spline() {
mPoints = new Vector<ControlPoint>();
}
mPoints = new Vector<ControlPoint>();
for (int i = 0; i < spline.mPoints.size(); i++) {
ControlPoint p = spline.mPoints.elementAt(i);
- mPoints.add(p);
+ mPoints.add(new ControlPoint(p));
}
Collections.sort(mPoints);
- delta_t = 1.0f / mPoints.size();
}
- public ControlPoint interpolate(float t, ControlPoint p1,
- ControlPoint p2, ControlPoint p3, ControlPoint p4) {
+ public static void setCurveHandle(Drawable drawable, int size) {
+ mCurveHandle = drawable;
+ mCurveHandleSize = size;
+ }
- float t3 = t * t * t;
- float t2 = t * t;
- float b1 = 0.5f * (-t3 + 2 * t2 - t);
- float b2 = 0.5f * (3 * t3 - 5 * t2 + 2);
- float b3 = 0.5f * (-3 * t3 + 4 * t2 + t);
- float b4 = 0.5f * (t3 - t2);
+ public static void setCurveWidth(int width) {
+ mCurveWidth = width;
+ }
- ControlPoint b1p1 = p1.multiply(b1);
- ControlPoint b2p2 = p2.multiply(b2);
- ControlPoint b3p3 = p3.multiply(b3);
- ControlPoint b4p4 = p4.multiply(b4);
+ public static int curveHandleSize() {
+ return mCurveHandleSize;
+ }
- return b1p1.add(b2p2.add(b3p3.add(b4p4)));
+ 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 void addPoint(float x, float y) {
- addPoint(new ControlPoint(x, y));
+ 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 addPoint(ControlPoint v) {
- mPoints.add(v);
- Collections.sort(mPoints);
- delta_t = 1.0f / mPoints.size();
+ 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;
+ if (points[0].x != 0) {
+ start = (int) (points[0].x * 256);
+ }
+ for (int i = 0; i < start; i++) {
+ curve[i] = 1.0f - points[0].y;
+ }
+ for (int i = start; i < 256; 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 ControlPoint getPoint(float t) {
- int p = (int) (t / delta_t);
- int p0 = p - 1;
- int max = mPoints.size() - 1;
+ public void draw(Canvas canvas, int color, int canvasWidth, int canvasHeight,
+ boolean showHandles) {
+ float w = canvasWidth;
+ float h = canvasHeight - mCurveHandleSize;
+ float dx = 0;
+ float dy = mCurveHandleSize / 2;
- if (p0 < 0) {
- p0 = 0;
- } else if (p0 >= max) {
- p0 = max;
+ // 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);
}
- int p1 = p;
- if (p1 < 0) {
- p1 = 0;
- } else if (p1 >= max) {
- p1 = max;
+ 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);
+ }
}
- int p2 = p + 1;
- if (p2 < 0) {
- p2 = 0;
- } else if (p2 >= max) {
- p2 = max;
+ 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;
}
- int p3 = p + 2;
- if (p3 < 0) {
- p3 = 0;
- } else if (p3 >= max) {
- p3 = max;
+ paint.setStrokeWidth(curveWidth + 2);
+ paint.setColor(Color.BLACK);
+ canvas.drawPath(path, 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
}
- float lt = (t - delta_t * (float) p) / delta_t;
- return interpolate(lt, mPoints.elementAt(p0),
- mPoints.elementAt(p1), mPoints.elementAt(p2),
- mPoints.elementAt(p3));
+ // 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 void addPoint(float x, float y) {
+ addPoint(new ControlPoint(x, y));
+ }
+
+ public void addPoint(ControlPoint v) {
+ mPoints.add(v);
+ Collections.sort(mPoints);
+ }
+
+ public void deletePoint(int n) {
+ mPoints.remove(n);
+ Collections.sort(mPoints);
}
public int getNbPoints() {
return true;
}
- public void deletePoint(int n) {
- mPoints.remove(n);
- Collections.sort(mPoints);
- delta_t = 1.0f / (mPoints.size() - 1f);
- }
-
- private Vector<ControlPoint> mPoints;
- private float delta_t;
-
public Spline copy() {
Spline spline = new Spline();
for (int i = 0; i < mPoints.size(); i++) {
}
return spline;
}
+
}