OSDN Git Service

Fix curves tool
authornicolasroard <nicolasroard@google.com>
Tue, 16 Oct 2012 01:03:37 +0000 (18:03 -0700)
committernicolasroard <nicolasroard@google.com>
Tue, 16 Oct 2012 20:09:34 +0000 (13:09 -0700)
bug:7344301
bug:7328680
bug:7289525
bug:7234239
Change-Id: I78d09dc2b38b11ccb2aac38ceef3acd8041f245e

src/com/android/gallery3d/filtershow/FilterShowActivity.java
src/com/android/gallery3d/filtershow/PanelController.java
src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
src/com/android/gallery3d/filtershow/ui/ControlPoint.java
src/com/android/gallery3d/filtershow/ui/ImageCurves.java
src/com/android/gallery3d/filtershow/ui/Spline.java

index fbec411..656d29a 100644 (file)
@@ -62,6 +62,7 @@ import com.android.gallery3d.filtershow.provider.SharedImageProvider;
 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;
@@ -127,6 +128,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
         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();
@@ -304,7 +309,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
         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());
 
@@ -705,6 +710,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
         adapter.reset();
         ImagePreset original = new ImagePreset(adapter.getItem(0));
         mImageShow.setImagePreset(original);
+        mPanelController.resetParameters();
         invalidateViews();
     }
 
index 963d17a..a21bb4f 100644 (file)
@@ -229,13 +229,17 @@ public class PanelController implements OnClickListener {
         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;
     }
 
@@ -389,6 +393,12 @@ public class PanelController implements OnClickListener {
             }
         }
 
+        if (view.getId() == R.id.pickCurvesChannel) {
+            ImageCurves curves = (ImageCurves) showImageView(R.id.imageCurves);
+            curves.nextChannel();
+            return;
+        }
+
         if (mCurrentImage != null) {
             mCurrentImage.unselect();
         }
@@ -432,9 +442,6 @@ public class PanelController implements OnClickListener {
                 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;
index 01b280b..3e8d298 100644 (file)
@@ -9,12 +9,7 @@ public class ImageFilterCurves extends ImageFilter {
 
     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";
@@ -23,29 +18,12 @@ public class ImageFilterCurves extends ImageFilter {
     @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
@@ -55,36 +33,48 @@ public class ImageFilterCurves extends ImageFilter {
             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(),
@@ -92,11 +82,11 @@ public class ImageFilterCurves extends ImageFilter {
         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];
     }
 }
index 68b799a..0c08e76 100644 (file)
@@ -2,26 +2,19 @@
 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);
     }
index 660a4fa..a8445b8 100644 (file)
@@ -2,17 +2,17 @@
 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;
@@ -21,15 +21,18 @@ public class ImageCurves extends ImageSlave {
 
     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);
@@ -41,23 +44,16 @@ public class ImageCurves extends ImageSlave {
         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();
@@ -67,7 +63,12 @@ public class ImageCurves extends ImageSlave {
                 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();
         }
     }
@@ -76,13 +77,19 @@ public class ImageCurves extends ImageSlave {
     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();
         }
@@ -93,86 +100,48 @@ public class ImageCurves extends ImageSlave {
         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) {
@@ -182,77 +151,35 @@ public class ImageCurves extends ImageSlave {
         }
 
         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);
@@ -268,103 +195,31 @@ public class ImageCurves extends ImageSlave {
         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();
@@ -379,15 +234,92 @@ public class ImageCurves extends ImageSlave {
             }
 
             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);
+    }
 }
index a272d28..b5c7974 100644 (file)
@@ -1,10 +1,28 @@
 
 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>();
     }
@@ -13,73 +31,290 @@ public class Spline {
         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() {
@@ -106,15 +341,6 @@ public class Spline {
         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++) {
@@ -123,4 +349,5 @@ public class Spline {
         }
         return spline;
     }
+
 }