From: nicolasroard Date: Tue, 16 Oct 2012 01:03:37 +0000 (-0700) Subject: Fix curves tool X-Git-Tag: android-x86-6.0-r3~2044^2~665^2~155^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=52186ac38ef5f10babf0f933066294958a28ae26;p=android-x86%2Fpackages-apps-Camera2.git Fix curves tool bug:7344301 bug:7328680 bug:7289525 bug:7234239 Change-Id: I78d09dc2b38b11ccb2aac38ceef3acd8041f245e --- diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index fbec411e3..656d29aaf 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -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(); } diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java index 963d17a6f..a21bb4fe1 100644 --- a/src/com/android/gallery3d/filtershow/PanelController.java +++ b/src/com/android/gallery3d/filtershow/PanelController.java @@ -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; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java index 01b280b6e..3e8d298a3 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java @@ -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]; } } diff --git a/src/com/android/gallery3d/filtershow/ui/ControlPoint.java b/src/com/android/gallery3d/filtershow/ui/ControlPoint.java index 68b799a9e..0c08e76fd 100644 --- a/src/com/android/gallery3d/filtershow/ui/ControlPoint.java +++ b/src/com/android/gallery3d/filtershow/ui/ControlPoint.java @@ -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); } diff --git a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java index 660a4fadd..a8445b830 100644 --- a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java +++ b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java @@ -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 { + @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); + } } diff --git a/src/com/android/gallery3d/filtershow/ui/Spline.java b/src/com/android/gallery3d/filtershow/ui/Spline.java index a272d288f..b5c79747d 100644 --- a/src/com/android/gallery3d/filtershow/ui/Spline.java +++ b/src/com/android/gallery3d/filtershow/ui/Spline.java @@ -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 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(); } @@ -13,73 +31,290 @@ public class Spline { mPoints = new Vector(); 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 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; } + }