OSDN Git Service

Initial import of the new image editor
authornicolasroard <nicolasroard@google.com>
Tue, 25 Sep 2012 21:27:56 +0000 (14:27 -0700)
committernicolasroard <nicolasroard@google.com>
Wed, 26 Sep 2012 23:13:45 +0000 (16:13 -0700)
bug:7165910
Change-Id: I756d6594f5bddd233772c979410362ca22e232a3

38 files changed:
src/com/android/gallery3d/filtershow/FilterShowActivity.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/HistoryAdapter.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/cache/BitmapCache.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/cache/Cache.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/cache/ImageLoader.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilter.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/imageshow/ImageShow.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/presets/ImagePreset.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/ui/ControlPoint.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/ui/ImageCurves.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/ui/PieSlider.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/ui/PieSliderListener.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/ui/Spline.java [new file with mode: 0644]

diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
new file mode 100644 (file)
index 0000000..bc6015b
--- /dev/null
@@ -0,0 +1,725 @@
+
+package com.android.gallery3d.filtershow;
+
+import java.util.Vector;
+
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.*;
+import com.android.gallery3d.filtershow.imageshow.ImageBorder;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter;
+import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
+import com.android.gallery3d.filtershow.presets.*;
+import com.android.gallery3d.filtershow.ui.ImageCurves;
+import com.android.gallery3d.R;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+import android.widget.AbsoluteLayout;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.Toast;
+
+@TargetApi(16)
+public class FilterShowActivity extends Activity implements OnItemClickListener {
+
+    private ImageLoader mImageLoader = null;
+    private ImageShow mImageShow = null;
+    private ImageCurves mImageCurves = null;
+    private ImageBorder mImageBorders = null;
+    private ImageStraighten mImageStraighten = null;
+
+    private View mListFx = null;
+    private View mListBorders = null;
+    private View mListGeometry = null;
+    private View mListColors = null;
+
+    private ImageButton mFxButton = null;
+    private ImageButton mBorderButton = null;
+    private ImageButton mGeometryButton = null;
+    private ImageButton mColorsButton = null;
+
+    private ImageButton mVignetteButton = null;
+    private ImageButton mCurvesButtonRGB = null;
+    private ImageButton mCurvesButtonRed = null;
+    private ImageButton mCurvesButtonGreen = null;
+    private ImageButton mCurvesButtonBlue = null;
+    private ImageButton mSharpenButton = null;
+    private ImageButton mContrastButton = null;
+
+    private static final int SELECT_PICTURE = 1;
+    private static final String LOGTAG = "FilterShowActivity";
+    protected static final boolean ANIMATE_PANELS = false;
+
+    private boolean mShowingHistoryPanel = false;
+    private Vector<ImageShow> mImageViews = new Vector<ImageShow>();
+    private Vector<View> mListViews = new Vector<View>();
+    private Vector<ImageButton> mBottomPanelButtons = new Vector<ImageButton>();
+    private Vector<ImageButton> mColorsPanelButtons = new Vector<ImageButton>();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.filtershow_activity);
+
+        mImageLoader = new ImageLoader(getApplicationContext());
+
+        LinearLayout listFilters = (LinearLayout) findViewById(R.id.listFilters);
+        LinearLayout listBorders = (LinearLayout) findViewById(R.id.listBorders);
+
+        mImageShow = (ImageShow) findViewById(R.id.imageShow);
+        mImageCurves = (ImageCurves) findViewById(R.id.imageCurves);
+        mImageBorders = (ImageBorder) findViewById(R.id.imageBorder);
+        mImageStraighten = (ImageStraighten) findViewById(R.id.imageStraighten);
+
+        mImageViews.add(mImageShow);
+        mImageViews.add(mImageCurves);
+        mImageViews.add(mImageBorders);
+        mImageViews.add(mImageStraighten);
+
+        mListFx = findViewById(R.id.fxList);
+        mListBorders = findViewById(R.id.bordersList);
+        mListGeometry = findViewById(R.id.gemoetryList);
+        mListColors = findViewById(R.id.colorsFxList);
+        mListViews.add(mListFx);
+        mListViews.add(mListBorders);
+        mListViews.add(mListGeometry);
+        mListViews.add(mListColors);
+
+        mFxButton = (ImageButton) findViewById(R.id.fxButton);
+        mBorderButton = (ImageButton) findViewById(R.id.borderButton);
+        mGeometryButton = (ImageButton) findViewById(R.id.geometryButton);
+        mColorsButton = (ImageButton) findViewById(R.id.colorsButton);
+        mBottomPanelButtons.add(mFxButton);
+        mBottomPanelButtons.add(mBorderButton);
+        mBottomPanelButtons.add(mGeometryButton);
+        mBottomPanelButtons.add(mColorsButton);
+        mFxButton.setSelected(true);
+
+        mVignetteButton = (ImageButton) findViewById(R.id.vignetteButton);
+        mCurvesButtonRGB = (ImageButton) findViewById(R.id.curvesButtonRGB);
+        mCurvesButtonRed = (ImageButton) findViewById(R.id.curvesButtonRed);
+        mCurvesButtonGreen = (ImageButton) findViewById(R.id.curvesButtonGreen);
+        mCurvesButtonBlue = (ImageButton) findViewById(R.id.curvesButtonBlue);
+        mSharpenButton = (ImageButton) findViewById(R.id.sharpenButton);
+        mContrastButton = (ImageButton) findViewById(R.id.contrastButton);
+        mColorsPanelButtons.add(mVignetteButton);
+        mColorsPanelButtons.add(mCurvesButtonRGB);
+        mColorsPanelButtons.add(mCurvesButtonRed);
+        mColorsPanelButtons.add(mCurvesButtonGreen);
+        mColorsPanelButtons.add(mCurvesButtonBlue);
+        mColorsPanelButtons.add(mSharpenButton);
+        mColorsPanelButtons.add(mContrastButton);
+
+        mCurvesButtonRGB.setSelected(true);
+
+        // TODO: instead of click listeners, make the activity the single listener and
+        // do a dispatch in the listener callback method.
+        findViewById(R.id.saveButton).setOnClickListener(createOnClickSaveButton());
+        findViewById(R.id.showOriginalButton).setOnTouchListener(createOnTouchShowOriginalButton());
+        findViewById(R.id.straightenButton).setOnClickListener(createOnClickStraightenButton());
+        findViewById(R.id.cropButton).setOnClickListener(createOnClickCropButton());
+        findViewById(R.id.rotateButton).setOnClickListener(createOnClickRotateButton());
+        findViewById(R.id.flipButton).setOnClickListener(createOnClickFlipButton());
+
+        mVignetteButton.setOnClickListener(createOnClickVignetteButton());
+        mCurvesButtonRGB.setOnClickListener(createOnClickCurvesRGBButton());
+        mCurvesButtonRed.setOnClickListener(createOnClickCurvesRedButton());
+        mCurvesButtonGreen.setOnClickListener(createOnClickCurvesGreenButton());
+        mCurvesButtonBlue.setOnClickListener(createOnClickCurvesBlueButton());
+
+        mSharpenButton.setOnClickListener(createOnClickSharpenButton());
+        mContrastButton.setOnClickListener(createOnClickContrastButton());
+        findViewById(R.id.undoButton).setOnClickListener(createOnClickUndoButton());
+        findViewById(R.id.redoButton).setOnClickListener(createOnClickRedoButton());
+
+        mFxButton.setOnClickListener(createOnClickFxButton());
+        mBorderButton.setOnClickListener(createOnClickBorderButton());
+        mGeometryButton.setOnClickListener(createOnClickGeometryButton());
+        mColorsButton.setOnClickListener(createOnClickColorsButton());
+
+        findViewById(R.id.operationsButton).setOnClickListener(createOnClickOperationsButton());
+        findViewById(R.id.resetOperationsButton).setOnClickListener(
+                createOnClickResetOperationsButton());
+
+        ListView operationsList = (ListView) findViewById(R.id.operationsList);
+        operationsList.setAdapter(mImageShow.getListAdapter());
+        operationsList.setOnItemClickListener(this);
+        mImageLoader.setAdapter((HistoryAdapter) mImageShow.getListAdapter());
+
+        fillListImages(listFilters);
+        fillListBorders(listBorders);
+
+        mImageShow.setImageLoader(mImageLoader);
+        mImageCurves.setImageLoader(mImageLoader);
+        mImageCurves.setMaster(mImageShow);
+        mImageBorders.setImageLoader(mImageLoader);
+        mImageBorders.setMaster(mImageShow);
+        mImageStraighten.setImageLoader(mImageLoader);
+        mImageStraighten.setMaster(mImageShow);
+
+        Intent intent = getIntent();
+        String data = intent.getDataString();
+        if (data != null) {
+            Uri uri = Uri.parse(data);
+            mImageLoader.loadBitmap(uri);
+        } else {
+            pickImage();
+        }
+    }
+
+    private void fillListImages(LinearLayout listFilters) {
+        // TODO: use listview
+        // TODO: load the filters straight from the filesystem
+        ImagePreset[] preset = new ImagePreset[9];
+        int p = 0;
+        preset[p++] = new ImagePreset();
+        preset[p++] = new ImagePresetSaturated();
+        preset[p++] = new ImagePresetOld();
+        preset[p++] = new ImagePresetXProcessing();
+        preset[p++] = new ImagePresetBW();
+        preset[p++] = new ImagePresetBWRed();
+        preset[p++] = new ImagePresetBWGreen();
+        preset[p++] = new ImagePresetBWBlue();
+
+        for (int i = 0; i < p; i++) {
+            ImageSmallFilter filter = new ImageSmallFilter(getBaseContext());
+            preset[i].setIsFx(true);
+            filter.setImagePreset(preset[i]);
+            filter.setController(this);
+            filter.setImageLoader(mImageLoader);
+            listFilters.addView(filter);
+        }
+
+        // Default preset (original)
+        mImageShow.setImagePreset(preset[0]);
+    }
+
+    private void fillListBorders(LinearLayout listBorders) {
+        // TODO: use listview
+        // TODO: load the borders straight from the filesystem
+        int p = 0;
+        ImageFilter[] borders = new ImageFilter[8];
+        borders[p++] = new ImageFilterBorder(null);
+
+        Drawable npd3 = getResources().getDrawable(R.drawable.filtershow_border_film3);
+        borders[p++] = new ImageFilterBorder(npd3);
+        Drawable npd = getResources().getDrawable(
+                R.drawable.filtershow_border_scratch3);
+        borders[p++] = new ImageFilterBorder(npd);
+        Drawable npd2 = getResources().getDrawable(R.drawable.filtershow_border_black);
+        borders[p++] = new ImageFilterBorder(npd2);
+        Drawable npd6 = getResources().getDrawable(
+                R.drawable.filtershow_border_rounded_black);
+        borders[p++] = new ImageFilterBorder(npd6);
+        Drawable npd4 = getResources().getDrawable(R.drawable.filtershow_border_white);
+        borders[p++] = new ImageFilterBorder(npd4);
+        Drawable npd5 = getResources().getDrawable(
+                R.drawable.filtershow_border_rounded_white);
+        borders[p++] = new ImageFilterBorder(npd5);
+
+        for (int i = 0; i < p; i++) {
+            ImageSmallFilter filter = new ImageSmallFilter(getBaseContext());
+            filter.setImageFilter(borders[i]);
+            filter.setController(this);
+            filter.setImageLoader(mImageLoader);
+            listBorders.addView(filter);
+        }
+    }
+
+    // //////////////////////////////////////////////////////////////////////////////
+    // Some utility functions
+
+    public void showOriginalViews(boolean value) {
+        for (ImageShow views : mImageViews) {
+            views.showOriginal(value);
+        }
+    }
+
+    public void invalidateViews() {
+        for (ImageShow views : mImageViews) {
+            views.invalidate();
+        }
+    }
+
+    public void hideListViews() {
+        for (View view : mListViews) {
+            view.setVisibility(View.GONE);
+        }
+    }
+
+    public void hideImageViews() {
+        mImageShow.setShowControls(false); // reset
+        for (View view : mImageViews) {
+            view.setVisibility(View.GONE);
+        }
+    }
+
+    public void unselectBottomPanelButtons() {
+        for (ImageButton button : mBottomPanelButtons) {
+            button.setSelected(false);
+        }
+    }
+
+    public void unselectPanelButtons(Vector<ImageButton> buttons) {
+        for (ImageButton button : buttons) {
+            button.setSelected(false);
+        }
+    }
+
+    // //////////////////////////////////////////////////////////////////////////////
+    // Click handlers for the top row buttons
+
+    private OnClickListener createOnClickSaveButton() {
+        return new View.OnClickListener() {
+            public void onClick(View v) {
+                saveImage();
+            }
+        };
+    }
+
+    private OnTouchListener createOnTouchShowOriginalButton() {
+        return new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                boolean show = false;
+                if ((event.getActionMasked() != MotionEvent.ACTION_UP)
+                        || (event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
+                    show = true;
+                }
+                showOriginalViews(show);
+                return true;
+            }
+        };
+    }
+
+    private OnClickListener createOnClickUndoButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                HistoryAdapter adapter = (HistoryAdapter) mImageShow
+                        .getListAdapter();
+                int position = adapter.undo();
+                mImageShow.onItemClick(position);
+                mImageShow.showToast("Undo");
+                invalidateViews();
+            }
+        };
+    }
+
+    private OnClickListener createOnClickRedoButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                HistoryAdapter adapter = (HistoryAdapter) mImageShow
+                        .getListAdapter();
+                int position = adapter.redo();
+                mImageShow.onItemClick(position);
+                mImageShow.showToast("Redo");
+                invalidateViews();
+            }
+        };
+    }
+
+    // //////////////////////////////////////////////////////////////////////////////
+    // history panel...
+
+    private OnClickListener createOnClickOperationsButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                final View view = findViewById(R.id.mainPanel);
+                final View viewList = findViewById(R.id.historyPanel);
+                View rootView = viewList.getRootView();
+
+                // TODO: use a custom layout instead of absolutelayout...
+                final AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) view
+                        .getLayoutParams();
+                final AbsoluteLayout.LayoutParams lph = (AbsoluteLayout.LayoutParams) viewList
+                        .getLayoutParams();
+                final int positionHistoryPanel = (int) (rootView.getWidth() - viewList
+                        .getWidth());
+                if (!mShowingHistoryPanel) {
+                    mShowingHistoryPanel = true;
+                    view.animate().setDuration(200).x(-viewList.getWidth())
+                            .withLayer().withEndAction(new Runnable() {
+                                public void run() {
+                                    view.setLayoutParams(lp);
+                                    lph.x = positionHistoryPanel;
+                                    viewList.setLayoutParams(lph);
+                                    viewList.setAlpha(0);
+                                    viewList.setVisibility(View.VISIBLE);
+                                    viewList.animate().setDuration(100)
+                                            .alpha(1.0f).start();
+                                }
+                            }).start();
+                } else {
+                    mShowingHistoryPanel = false;
+                    viewList.setVisibility(View.INVISIBLE);
+                    view.animate().setDuration(200).x(0).withLayer()
+                            .withEndAction(new Runnable() {
+                                public void run() {
+                                    lp.x = 0;
+                                    view.setLayoutParams(lp);
+                                }
+                            }).start();
+
+                }
+            }
+        };
+    }
+
+    // reset button in the history panel.
+    private OnClickListener createOnClickResetOperationsButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                HistoryAdapter adapter = (HistoryAdapter) mImageShow
+                        .getListAdapter();
+                adapter.reset();
+                ImagePreset original = new ImagePreset(adapter.getItem(0));
+                mImageShow.setImagePreset(original);
+                invalidateViews();
+            }
+        };
+    }
+
+    // //////////////////////////////////////////////////////////////////////////////
+    // Now, let's deal with the bottom panel.
+
+    private OnClickListener createOnClickFxButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                hideListViews();
+                unselectBottomPanelButtons();
+                mImageShow.setVisibility(View.VISIBLE);
+                mListFx.setVisibility(View.VISIBLE);
+                mFxButton.setSelected(true);
+            }
+        };
+    }
+
+    private OnClickListener createOnClickBorderButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                hideListViews();
+                unselectBottomPanelButtons();
+                mImageBorders.setVisibility(View.VISIBLE);
+                mListBorders.setVisibility(View.VISIBLE);
+                mBorderButton.setSelected(true);
+            }
+        };
+    }
+
+    private OnClickListener createOnClickGeometryButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                hideListViews();
+                unselectBottomPanelButtons();
+                mImageStraighten.setVisibility(View.VISIBLE);
+                mListGeometry.setVisibility(View.VISIBLE);
+                mGeometryButton.setSelected(true);
+
+                if (ANIMATE_PANELS) {
+                    mListGeometry.setX(mListGeometry.getWidth());
+                    mListGeometry.animate().setDuration(200).x(0).withLayer().start();
+                }
+            }
+        };
+    }
+
+    private OnClickListener createOnClickColorsButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                hideListViews();
+                unselectBottomPanelButtons();
+                mListColors.setVisibility(View.VISIBLE);
+                mImageCurves.setVisibility(View.VISIBLE);
+                mColorsButton.setSelected(true);
+
+                if (ANIMATE_PANELS) {
+                    View view = findViewById(R.id.listColorsFx);
+                    view.setX(mListColors.getWidth());
+                    view.animate().setDuration(200).x(0).withLayer().start();
+                }
+            }
+        };
+    }
+
+    // //////////////////////////////////////////////////////////////////////////////
+    // Geometry sub-panel
+
+    private OnClickListener createOnClickStraightenButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageStraighten.setVisibility(View.VISIBLE);
+                mImageStraighten.showToast("Straighten", true);
+            }
+        };
+    }
+
+    private OnClickListener createOnClickCropButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageShow.setVisibility(View.VISIBLE);
+                mImageShow.showToast("Crop", true);
+            }
+        };
+    }
+
+    private OnClickListener createOnClickRotateButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageShow.setVisibility(View.VISIBLE);
+                mImageShow.showToast("Rotate", true);
+            }
+        };
+    }
+
+    private OnClickListener createOnClickFlipButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageShow.setVisibility(View.VISIBLE);
+                mImageShow.showToast("Flip", true);
+            }
+        };
+    }
+
+    // //////////////////////////////////////////////////////////////////////////////
+    // Filters sub-panel
+
+    private OnClickListener createOnClickVignetteButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageShow.setVisibility(View.VISIBLE);
+                mImageShow.setShowControls(true);
+                ImagePreset preset = mImageShow.getImagePreset();
+                ImageFilter filter = preset.getFilter("Vignette");
+                if (filter == null) {
+                    ImageFilterVignette vignette = new ImageFilterVignette();
+                    ImagePreset copy = new ImagePreset(preset);
+                    copy.add(vignette);
+                    copy.setHistoryName(vignette.name());
+                    copy.setIsFx(false);
+                    mImageShow.setImagePreset(copy);
+                }
+                unselectPanelButtons(mColorsPanelButtons);
+                mVignetteButton.setSelected(true);
+                invalidateViews();
+            }
+        };
+    }
+
+    private OnClickListener createOnClickCurvesRGBButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageCurves.setVisibility(View.VISIBLE);
+                unselectPanelButtons(mColorsPanelButtons);
+                mCurvesButtonRGB.setSelected(true);
+                mImageCurves.setUseRed(true);
+                mImageCurves.setUseGreen(true);
+                mImageCurves.setUseBlue(true);
+                mImageCurves.reloadCurve();
+            }
+        };
+    }
+
+    private OnClickListener createOnClickCurvesRedButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageCurves.setVisibility(View.VISIBLE);
+                unselectPanelButtons(mColorsPanelButtons);
+                mCurvesButtonRed.setSelected(true);
+                mImageCurves.setUseRed(true);
+                mImageCurves.setUseGreen(false);
+                mImageCurves.setUseBlue(false);
+                mImageCurves.reloadCurve();
+            }
+        };
+    }
+
+    private OnClickListener createOnClickCurvesGreenButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageCurves.setVisibility(View.VISIBLE);
+                unselectPanelButtons(mColorsPanelButtons);
+                mCurvesButtonGreen.setSelected(true);
+                mImageCurves.setUseRed(false);
+                mImageCurves.setUseGreen(true);
+                mImageCurves.setUseBlue(false);
+                mImageCurves.reloadCurve();
+            }
+        };
+    }
+
+    private OnClickListener createOnClickCurvesBlueButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageCurves.setVisibility(View.VISIBLE);
+                unselectPanelButtons(mColorsPanelButtons);
+                mCurvesButtonBlue.setSelected(true);
+                mImageCurves.setUseRed(false);
+                mImageCurves.setUseGreen(false);
+                mImageCurves.setUseBlue(true);
+                mImageCurves.reloadCurve();
+            }
+        };
+    }
+
+    private OnClickListener createOnClickSharpenButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageShow.setVisibility(View.VISIBLE);
+                unselectPanelButtons(mColorsPanelButtons);
+                mSharpenButton.setSelected(true);
+                mImageShow.showToast("Sharpen", true);
+            }
+        };
+    }
+
+    private OnClickListener createOnClickContrastButton() {
+        return new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                hideImageViews();
+                mImageShow.setVisibility(View.VISIBLE);
+                unselectPanelButtons(mColorsPanelButtons);
+                mContrastButton.setSelected(true);
+                mImageShow.showToast("Contrast", true);
+            }
+        };
+    }
+
+    // //////////////////////////////////////////////////////////////////////////////
+
+    public float getPixelsFromDip(float value) {
+        Resources r = getResources();
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
+                r.getDisplayMetrics());
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
+        return true;
+    }
+
+    public void useImagePreset(ImagePreset preset) {
+        if (preset == null) {
+            return;
+        }
+        ImagePreset copy = new ImagePreset(preset);
+        mImageShow.setImagePreset(copy);
+        if (preset.isFx()) {
+            // if it's an FX we rest the curve adjustment too
+            mImageCurves.resetCurve();
+        }
+        invalidateViews();
+    }
+
+    public void useImageFilter(ImageFilter imageFilter) {
+        if (imageFilter == null) {
+            return;
+        }
+        ImagePreset oldPreset = mImageShow.getImagePreset();
+        ImagePreset copy = new ImagePreset(oldPreset);
+        // TODO: use a numerical constant instead.
+        if (imageFilter.name().equalsIgnoreCase("Border")) {
+            copy.remove("Border");
+            copy.setHistoryName("Border");
+        }
+        copy.add(imageFilter);
+        invalidateViews();
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position,
+            long id) {
+        mImageShow.onItemClick(position);
+        invalidateViews();
+    }
+
+    public void pickImage() {
+        Intent intent = new Intent();
+        intent.setType("image/*");
+        intent.setAction(Intent.ACTION_GET_CONTENT);
+        startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
+                SELECT_PICTURE);
+    }
+
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        Log.v(LOGTAG, "onActivityResult");
+        if (resultCode == RESULT_OK) {
+            if (requestCode == SELECT_PICTURE) {
+                Uri selectedImageUri = data.getData();
+                mImageLoader.loadBitmap(selectedImageUri);
+            }
+        }
+    }
+
+    public void saveImage() {
+        Toast toast = Toast.makeText(getBaseContext(), getString(R.string.saving_image),
+                Toast.LENGTH_SHORT);
+        toast.setGravity(Gravity.CENTER, 0, 0);
+        toast.show();
+
+        mImageShow.saveImage(this);
+    }
+
+    public void completeSaveImage(Uri saveUri) {
+        setResult(RESULT_OK, new Intent().setData(saveUri));
+        finish();
+    }
+
+    static {
+        System.loadLibrary("filters");
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/HistoryAdapter.java b/src/com/android/gallery3d/filtershow/HistoryAdapter.java
new file mode 100644 (file)
index 0000000..ed5b26b
--- /dev/null
@@ -0,0 +1,119 @@
+
+package com.android.gallery3d.filtershow;
+
+import java.util.Vector;
+
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.R;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class HistoryAdapter extends ArrayAdapter<ImagePreset> {
+    private static final String LOGTAG = "HistoryAdapter";
+    private int mCurrentPresetPosition = 0;
+
+    public void setCurrentPreset(int n) {
+        mCurrentPresetPosition = n;
+        this.notifyDataSetChanged();
+    }
+
+    public HistoryAdapter(Context context, int resource, int textViewResourceId) {
+        super(context, resource, textViewResourceId);
+    }
+
+    public void reset() {
+        if (getCount() == 0) {
+            return;
+        }
+        ImagePreset first = getItem(getCount() - 1);
+        clear();
+        insert(first, 0);
+    }
+
+    public void insert(ImagePreset preset, int position) {
+        if (getCount() > position && getItem(position).same(preset)) {
+            return;
+        }
+        if (mCurrentPresetPosition != 0) {
+            // in this case, let's discount the presets before the current one
+            Vector<ImagePreset> oldItems = new Vector<ImagePreset>();
+            for (int i = mCurrentPresetPosition; i < getCount(); i++) {
+                oldItems.add(getItem(i));
+            }
+            clear();
+            for (int i = 0; i < oldItems.size(); i++) {
+                add(oldItems.elementAt(i));
+            }
+            mCurrentPresetPosition = position;
+            this.notifyDataSetChanged();
+        }
+        if (getCount() > position && getItem(position).same(preset)) {
+            return;
+        }
+        super.insert(preset, position);
+        mCurrentPresetPosition = position;
+        this.notifyDataSetChanged();
+    }
+
+    public int redo() {
+        mCurrentPresetPosition--;
+        if (mCurrentPresetPosition < 0) {
+            mCurrentPresetPosition = 0;
+        }
+        this.notifyDataSetChanged();
+        return mCurrentPresetPosition;
+    }
+
+    public int undo() {
+        mCurrentPresetPosition++;
+        if (mCurrentPresetPosition >= getCount()) {
+            mCurrentPresetPosition = getCount() - 1;
+        }
+        this.notifyDataSetChanged();
+        return mCurrentPresetPosition;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View view = convertView;
+        if (view == null) {
+            LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            view = inflater.inflate(R.layout.filtershow_history_operation_row, null);
+        }
+
+        ImagePreset item = getItem(position);
+        if (item != null) {
+            TextView itemView = (TextView) view.findViewById(R.id.rowTextView);
+            if (itemView != null) {
+                // do whatever you want with your string and long
+                itemView.setText(item.historyName());
+            }
+            ImageView markView = (ImageView) view.findViewById(R.id.selectedMark);
+            if (position == mCurrentPresetPosition) {
+                markView.setVisibility(View.VISIBLE);
+            } else {
+                markView.setVisibility(View.INVISIBLE);
+            }
+            ImageView typeView = (ImageView) view.findViewById(R.id.typeMark);
+            if (position == getCount() - 1) {
+                typeView.setImageResource(R.drawable.filtershow_button_origin);
+            } else if (item.historyName().equalsIgnoreCase("Border")) {
+                typeView.setImageResource(R.drawable.filtershow_button_border);
+            } else if (item.isFx()) {
+                typeView.setImageResource(R.drawable.filtershow_button_fx);
+            } else {
+                typeView.setImageResource(R.drawable.filtershow_button_settings);
+            }
+        }
+
+        return view;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/BitmapCache.java b/src/com/android/gallery3d/filtershow/cache/BitmapCache.java
new file mode 100644 (file)
index 0000000..93d57ed
--- /dev/null
@@ -0,0 +1,115 @@
+
+package com.android.gallery3d.filtershow.cache;
+
+import java.nio.ByteBuffer;
+
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+public class BitmapCache {
+
+    private static final String LOGTAG = "BitmapCache";
+    static int mNbItems = 20;
+    private Bitmap[] mBitmaps = new Bitmap[mNbItems];
+    private Object[] mKeys = new Object[mNbItems];
+    private long[] mIndices = new long[mNbItems];
+    private boolean[] mBusyStatus = new boolean[mNbItems];
+
+    private Bitmap mOriginalBitmap = null;
+    private ByteBuffer mBuffer = null;
+    private Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
+    private long mIndex = 0;
+
+    public void setOriginalBitmap(Bitmap original) {
+        if (original == null) {
+            return;
+        }
+        mOriginalBitmap = original.copy(mConfig, true);
+        int size = 4 * original.getWidth() * original.getHeight();
+        mBuffer = ByteBuffer.allocate(size);
+        mOriginalBitmap.copyPixelsToBuffer(mBuffer);
+        mBuffer.rewind();
+        mOriginalBitmap.copyPixelsFromBuffer(mBuffer);
+        for (int i = 0; i < mNbItems; i++) {
+            mBitmaps[i] = mOriginalBitmap.copy(mConfig, true);
+        }
+    }
+
+    private int getOldestPosition() {
+        long minIndex = mIndices[0];
+        int current = 0;
+        for (int i = 1; i < mNbItems; i++) {
+            if (!mBusyStatus[i] && minIndex > mIndices[i]) {
+                minIndex = mIndices[i];
+                current = i;
+            }
+        }
+        return current;
+    }
+
+    public Bitmap put(ImagePreset preset) {
+        int pos = getOldestPosition();
+        return put(preset, pos);
+    }
+
+    public Bitmap put(ImagePreset preset, int pos) {
+        mBitmaps[pos] = mOriginalBitmap.copy(mConfig, true);
+        Bitmap bitmap = mBitmaps[pos];
+
+        preset.apply(bitmap);
+        mKeys[pos] = preset;
+        mIndices[pos] = mIndex++;
+        return bitmap;
+    }
+
+    public int reservePosition(ImagePreset preset) {
+        for (int i = 1; i < mNbItems; i++) {
+            if (mKeys[i] == preset && mBusyStatus[i]) {
+                return -1;
+            }
+        }
+        int pos = getOldestPosition();
+        mBusyStatus[pos] = true;
+        mKeys[pos] = preset;
+        return pos;
+    }
+
+    public void processPosition(int pos) {
+        ImagePreset preset = (ImagePreset) mKeys[pos];
+        mBitmaps[pos] = mOriginalBitmap.copy(mConfig, true);
+        Bitmap bitmap = mBitmaps[pos];
+        preset.apply(bitmap);
+        mIndices[pos] = mIndex++;
+    }
+
+    public void unlockPosition(int pos) {
+        mBusyStatus[pos] = false;
+    }
+
+    public Bitmap get(ImagePreset preset) {
+        int foundPosition = -1;
+        int currentIndice = 0;
+        for (int i = 0; i < mNbItems; i++) {
+            if (mKeys[i] == preset && mBitmaps[i] != null) {
+                if (mIndices[i] > currentIndice) {
+                    foundPosition = i;
+                }
+            }
+        }
+        if (foundPosition != -1) {
+            mIndices[foundPosition] = mIndex++;
+            return mBitmaps[foundPosition];
+        }
+        return null;
+    }
+
+    public void reset(ImagePreset preset) {
+        for (int i = 0; i < mNbItems; i++) {
+            if (mKeys[i] == preset && !mBusyStatus[i]) {
+                mBitmaps[i] = null;
+            }
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/Cache.java b/src/com/android/gallery3d/filtershow/cache/Cache.java
new file mode 100644 (file)
index 0000000..364c9c5
--- /dev/null
@@ -0,0 +1,19 @@
+
+package com.android.gallery3d.filtershow.cache;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+public interface Cache {
+    public void setOriginalBitmap(Bitmap bitmap);
+
+    public void reset(ImagePreset preset);
+
+    public void prepare(ImagePreset preset);
+
+    public Bitmap get(ImagePreset preset);
+
+    public void addObserver(ImageShow observer);
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java b/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java
new file mode 100644 (file)
index 0000000..8acb539
--- /dev/null
@@ -0,0 +1,59 @@
+
+package com.android.gallery3d.filtershow.cache;
+
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+
+public class DelayedPresetCache extends DirectPresetCache implements Callback {
+    private HandlerThread mHandlerThread = null;
+
+    private final static int NEW_PRESET = 0;
+    private final static int COMPUTE_PRESET = 1;
+
+    private Handler mProcessingHandler = null;
+    private Handler mUIHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case NEW_PRESET: {
+                    CachedPreset cache = (CachedPreset) msg.obj;
+                    didCompute(cache);
+                    break;
+                }
+            }
+        }
+    };
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case COMPUTE_PRESET: {
+                CachedPreset cache = (CachedPreset) msg.obj;
+                compute(cache);
+                Message uimsg = mUIHandler.obtainMessage(NEW_PRESET, cache);
+                mUIHandler.sendMessage(uimsg);
+                break;
+            }
+        }
+        return false;
+    }
+
+    public DelayedPresetCache(int size) {
+        super(size);
+        mHandlerThread = new HandlerThread("ImageProcessing", Process.THREAD_PRIORITY_BACKGROUND);
+        mHandlerThread.start();
+        mProcessingHandler = new Handler(mHandlerThread.getLooper(), this);
+    }
+
+    protected void willCompute(CachedPreset cache) {
+        if (cache == null) {
+            return;
+        }
+        cache.setBusy(true);
+        Message msg = mProcessingHandler.obtainMessage(COMPUTE_PRESET, cache);
+        mProcessingHandler.sendMessage(msg);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java b/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java
new file mode 100644 (file)
index 0000000..1ba8356
--- /dev/null
@@ -0,0 +1,144 @@
+
+package com.android.gallery3d.filtershow.cache;
+
+import java.util.Vector;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+public class DirectPresetCache implements Cache {
+
+    private static final String LOGTAG = "DirectPresetCache";
+    private Bitmap mOriginalBitmap = null;
+    private Vector<ImageShow> mObservers = new Vector<ImageShow>();
+    private Vector<CachedPreset> mCache = new Vector<CachedPreset>();
+    private int mCacheSize = 1;
+    private Bitmap.Config mBitmapConfig = Bitmap.Config.ARGB_8888;
+    private long mGlobalAge = 0;
+
+    protected class CachedPreset {
+        private Bitmap mBitmap = null;
+        private ImagePreset mPreset = null;
+        private long mAge = 0;
+        private boolean mBusy = false;
+
+        public void setBusy(boolean value) {
+            mBusy = value;
+        }
+
+        public boolean busy() {
+            return mBusy;
+        }
+    }
+
+    public DirectPresetCache(int size) {
+        mCacheSize = size;
+    }
+
+    public void setOriginalBitmap(Bitmap bitmap) {
+        mOriginalBitmap = bitmap;
+        notifyObservers();
+    }
+
+    public void notifyObservers() {
+        for (int i = 0; i < mObservers.size(); i++) {
+            ImageShow imageShow = mObservers.elementAt(i);
+            imageShow.invalidate();
+        }
+    }
+
+    public void addObserver(ImageShow observer) {
+        if (!mObservers.contains(observer)) {
+            mObservers.add(observer);
+        }
+    }
+
+    private CachedPreset getCachedPreset(ImagePreset preset) {
+        for (int i = 0; i < mCache.size(); i++) {
+            CachedPreset cache = mCache.elementAt(i);
+            if (cache.mPreset == preset && !cache.mBusy) {
+                return cache;
+            }
+        }
+        return null;
+    }
+
+    public Bitmap get(ImagePreset preset) {
+        // Log.v(LOGTAG, "get preset " + preset.name() + " : " + preset);
+        CachedPreset cache = getCachedPreset(preset);
+        if (cache != null) {
+            return cache.mBitmap;
+        }
+        // Log.v(LOGTAG, "didn't find preset " + preset.name() + " : " + preset
+        // + " we have " + mCache.size() + " elts / " + mCacheSize);
+        return null;
+    }
+
+    public void reset(ImagePreset preset) {
+        CachedPreset cache = getCachedPreset(preset);
+        if (cache != null && !cache.mBusy) {
+            cache.mBitmap = null;
+            willCompute(cache);
+        }
+    }
+
+    private CachedPreset getOldestCachedPreset() {
+        CachedPreset found = null;
+        for (int i = 0; i < mCache.size(); i++) {
+            CachedPreset cache = mCache.elementAt(i);
+            if (cache.mBusy) {
+                continue;
+            }
+            if (found == null) {
+                found = cache;
+            } else {
+                if (found.mAge > cache.mAge) {
+                    found = cache;
+                }
+            }
+        }
+        return found;
+    }
+
+    protected void willCompute(CachedPreset cache) {
+        if (cache == null) {
+            return;
+        }
+        cache.mBusy = true;
+        compute(cache);
+        didCompute(cache);
+    }
+
+    protected void didCompute(CachedPreset cache) {
+        cache.mBusy = false;
+        notifyObservers();
+    }
+
+    protected void compute(CachedPreset cache) {
+        cache.mBitmap = null;
+        cache.mBitmap = mOriginalBitmap.copy(mBitmapConfig, true);
+        cache.mPreset.apply(cache.mBitmap);
+        cache.mAge = mGlobalAge++;
+    }
+
+    public void prepare(ImagePreset preset) {
+        // Log.v(LOGTAG, "prepare preset " + preset.name() + " : " + preset);
+        CachedPreset cache = getCachedPreset(preset);
+        if (cache == null) {
+            if (mCache.size() < mCacheSize) {
+                cache = new CachedPreset();
+                mCache.add(cache);
+            } else {
+                cache = getOldestCachedPreset();
+            }
+            if (cache != null) {
+                cache.mPreset = preset;
+            }
+        }
+        willCompute(cache);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
new file mode 100644 (file)
index 0000000..19a841a
--- /dev/null
@@ -0,0 +1,260 @@
+
+package com.android.gallery3d.filtershow.cache;
+
+import java.io.Closeable;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.HistoryAdapter;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.tools.SaveCopyTask;
+import com.android.gallery3d.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.util.Log;
+
+public class ImageLoader {
+
+    private static final String LOGTAG = "ImageLoader";
+    private Vector<ImageShow> mListeners = new Vector<ImageShow>();
+    private Bitmap mOriginalBitmapSmall = null;
+    private Bitmap mOriginalBitmapLarge = null;
+    private Bitmap mBackgroundBitmap = null;
+    private Bitmap mFullOriginalBitmap = null;
+    private Bitmap mSaveCopy = null;
+
+    private Cache mCache = new DelayedPresetCache(30);
+    private Cache mHiresCache = new DelayedPresetCache(2);
+
+    private int mOrientation = 0;
+    private HistoryAdapter mAdapter = null;
+    private static int PORTRAIT_ORIENTATION = 6;
+
+    private Context mContext = null;
+    private Uri mUri = null;
+
+    public ImageLoader(Context context) {
+        mContext = context;
+    }
+
+    public void loadBitmap(Uri uri) {
+        mUri = uri;
+        mOrientation = getOrientation(uri);
+        mOriginalBitmapSmall = loadScaledBitmap(uri, 160);
+        mOriginalBitmapLarge = loadScaledBitmap(uri, 320);
+        updateBitmaps();
+    }
+
+    private int getOrientation(Uri uri) {
+        Cursor cursor = mContext.getContentResolver().query(uri,
+                new String[] {
+                    MediaStore.Images.ImageColumns.ORIENTATION
+                },
+                null, null, null);
+
+        if (cursor.getCount() != 1) {
+            return -1;
+        }
+
+        cursor.moveToFirst();
+        return cursor.getInt(0);
+    }
+
+    private int getOrientationFromPath(String path) {
+        int orientation = -1;
+        try {
+            ExifInterface EXIF = new ExifInterface(path);
+            orientation = EXIF.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+                    1);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return orientation;
+    }
+
+    private void updateBitmaps() {
+        mCache.setOriginalBitmap(mOriginalBitmapSmall);
+        mHiresCache.setOriginalBitmap(mOriginalBitmapLarge);
+        if (mOrientation == PORTRAIT_ORIENTATION) {
+            mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall);
+            mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge);
+        }
+        warnListeners();
+    }
+
+    private Bitmap rotateToPortrait(Bitmap bitmap) {
+        Matrix matrix = new Matrix();
+        matrix.postRotate(90);
+        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
+                bitmap.getHeight(), matrix, true);
+    }
+
+    private void closeStream(Closeable stream) {
+        if (stream != null) {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private Bitmap loadScaledBitmap(Uri uri, int size) {
+        InputStream is = null;
+        try {
+            is = mContext.getContentResolver().openInputStream(uri);
+            Log.v(LOGTAG, "loading uri " + uri.getPath() + " input stream: "
+                    + is);
+            BitmapFactory.Options o = new BitmapFactory.Options();
+            o.inJustDecodeBounds = true;
+            BitmapFactory.decodeStream(is, null, o);
+
+            int width_tmp = o.outWidth;
+            int height_tmp = o.outHeight;
+            int scale = 1;
+            while (true) {
+                if (width_tmp / 2 < size || height_tmp / 2 < size)
+                    break;
+                width_tmp /= 2;
+                height_tmp /= 2;
+                scale *= 2;
+            }
+
+            // decode with inSampleSize
+            BitmapFactory.Options o2 = new BitmapFactory.Options();
+            o2.inSampleSize = scale;
+            closeStream(is);
+            is = mContext.getContentResolver().openInputStream(uri);
+            return BitmapFactory.decodeStream(is, null, o2);
+        } catch (FileNotFoundException e) {
+            Log.e(LOGTAG, "FileNotFoundException: " + uri);
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            closeStream(is);
+        }
+        return null;
+    }
+
+    public Bitmap getBackgroundBitmap(Resources resources) {
+        if (mBackgroundBitmap == null) {
+            mBackgroundBitmap = BitmapFactory.decodeResource(resources,
+                    R.drawable.filtershow_background);
+        }
+        return mBackgroundBitmap;
+
+    }
+
+    public Bitmap getOriginalBitmapSmall() {
+        return mOriginalBitmapSmall;
+    }
+
+    public Bitmap getOriginalBitmapLarge() {
+        return mOriginalBitmapLarge;
+    }
+
+    public void addListener(ImageShow imageShow) {
+        if (!mListeners.contains(imageShow)) {
+            mListeners.add(imageShow);
+        }
+    }
+
+    public void warnListeners() {
+        for (int i = 0; i < mListeners.size(); i++) {
+            ImageShow imageShow = mListeners.elementAt(i);
+            imageShow.updateImage();
+        }
+    }
+
+    // Caching method
+    public Bitmap getImageForPreset(ImageShow caller, ImagePreset imagePreset,
+            boolean hiRes) {
+        if (mOriginalBitmapSmall == null) {
+            return null;
+        }
+        if (mOriginalBitmapLarge == null) {
+            return null;
+        }
+
+        Bitmap filteredImage = null;
+
+        if (hiRes) {
+            filteredImage = mHiresCache.get(imagePreset);
+        } else {
+            filteredImage = mCache.get(imagePreset);
+        }
+
+        if (filteredImage == null) {
+            if (hiRes) {
+                cachePreset(imagePreset, mHiresCache, caller);
+            } else {
+                cachePreset(imagePreset, mCache, caller);
+            }
+        }
+        return filteredImage;
+    }
+
+    public void resetImageForPreset(ImagePreset imagePreset, ImageShow caller) {
+        mHiresCache.reset(imagePreset);
+        mCache.reset(imagePreset);
+    }
+
+    public Uri saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inMutable = true;
+
+        if (mFullOriginalBitmap != null) {
+            mFullOriginalBitmap.recycle();
+        }
+
+        InputStream is = null;
+        Uri saveUri = null;
+        try {
+            is = mContext.getContentResolver().openInputStream(mUri);
+            mFullOriginalBitmap = BitmapFactory.decodeStream(is, null, options);
+            // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't
+            // exist)
+            mSaveCopy = mFullOriginalBitmap;
+            preset.apply(mSaveCopy);
+            new SaveCopyTask(mContext, mUri, new SaveCopyTask.Callback() {
+
+                @Override
+                public void onComplete(Uri result) {
+                    filterShowActivity.completeSaveImage(result);
+                }
+
+            }).execute(mSaveCopy);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } finally {
+            closeStream(is);
+        }
+
+        return saveUri;
+    }
+
+    public void setAdapter(HistoryAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    public HistoryAdapter getHistory() {
+        return mAdapter;
+    }
+
+    private void cachePreset(ImagePreset preset, Cache cache, ImageShow caller) {
+        cache.prepare(preset);
+        cache.addObserver(caller);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
new file mode 100644 (file)
index 0000000..6d504b8
--- /dev/null
@@ -0,0 +1,39 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilter {
+
+    protected int mParameter = 50;
+
+    public String name() {
+        return "Original";
+    }
+
+    // TODO: maybe use clone instead?
+    public ImageFilter copy() {
+        ImageFilter filter = new ImageFilter();
+        filter.setParameter(mParameter);
+        return filter;
+    }
+
+    public void apply(Bitmap bitmap) {
+        // do nothing here, subclasses will implement filtering here
+    }
+
+    public void setParameter(int value) {
+        mParameter = value;
+    }
+
+    public boolean same(ImageFilter filter) {
+        if (!filter.name().equalsIgnoreCase(name())) {
+            return false;
+        }
+        return true;
+    }
+
+    native protected void nativeApplyGradientFilter(Bitmap bitmap, int w, int h,
+            int[] redGradient, int[] greenGradient, int[] blueGradient);
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java
new file mode 100644 (file)
index 0000000..e997d3e
--- /dev/null
@@ -0,0 +1,24 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterBW extends ImageFilter {
+
+    public String name() {
+        return "Black & White";
+    }
+
+    public ImageFilter copy() {
+        return new ImageFilterBW();
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
+
+    public void apply(Bitmap bitmap) {
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        nativeApplyFilter(bitmap, w, h);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java
new file mode 100644 (file)
index 0000000..685b8ff
--- /dev/null
@@ -0,0 +1,24 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterBWBlue extends ImageFilter {
+
+    public String name() {
+        return "Black & White (Blue)";
+    }
+
+    public ImageFilter copy() {
+        return new ImageFilterBWBlue();
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
+
+    public void apply(Bitmap bitmap) {
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        nativeApplyFilter(bitmap, w, h);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java
new file mode 100644 (file)
index 0000000..9fa7386
--- /dev/null
@@ -0,0 +1,24 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterBWGreen extends ImageFilter {
+
+    public String name() {
+        return "Black & White (Green)";
+    }
+
+    public ImageFilter copy() {
+        return new ImageFilterBWGreen();
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
+
+    public void apply(Bitmap bitmap) {
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        nativeApplyFilter(bitmap, w, h);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java
new file mode 100644 (file)
index 0000000..c5e444f
--- /dev/null
@@ -0,0 +1,24 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterBWRed extends ImageFilter {
+
+    public String name() {
+        return "Black & White (Red)";
+    }
+
+    public ImageFilter copy() {
+        return new ImageFilterBWRed();
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
+
+    public void apply(Bitmap bitmap) {
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        nativeApplyFilter(bitmap, w, h);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
new file mode 100644 (file)
index 0000000..6a71417
--- /dev/null
@@ -0,0 +1,57 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+
+public class ImageFilterBorder extends ImageFilter {
+    Drawable mNinePatch = null;
+
+    public ImageFilterBorder(Drawable ninePatch) {
+        mNinePatch = ninePatch;
+    }
+
+    public String name() {
+        return "Border";
+    }
+
+    public ImageFilter copy() {
+        return new ImageFilterBorder(mNinePatch);
+    }
+
+    public boolean same(ImageFilter filter) {
+        boolean isBorderFilter = super.same(filter);
+        if (!isBorderFilter) {
+            return false;
+        }
+        ImageFilterBorder borderFilter = (ImageFilterBorder) filter;
+        if (mNinePatch != borderFilter.mNinePatch) {
+            return false;
+        }
+        return true;
+    }
+
+    public void setDrawable(Drawable ninePatch) {
+        // TODO: for now we only use nine patch
+        mNinePatch = ninePatch;
+    }
+
+    public void apply(Bitmap bitmap) {
+        if (mNinePatch == null) {
+            return;
+        }
+
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+
+        Rect bounds = new Rect(0, 0, w, h);
+        Canvas canvas = new Canvas(bitmap);
+        mNinePatch.setBounds(bounds);
+        mNinePatch.draw(canvas);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
new file mode 100644 (file)
index 0000000..c21ae1f
--- /dev/null
@@ -0,0 +1,106 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.filtershow.ui.Spline;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+public class ImageFilterCurves extends ImageFilter {
+
+    private static final String LOGTAG = "ImageFilterCurves";
+
+    private float[] mCurve = new float[256];
+
+    private boolean mUseRed = true;
+    private boolean mUseGreen = true;
+    private boolean mUseBlue = true;
+    private String mName = "Curves";
+    private Spline mSpline = null;
+
+    public String name() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    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];
+            }
+        }
+    }
+
+    public boolean same(ImageFilter filter) {
+        boolean isCurveFilter = super.same(filter);
+        if (!isCurveFilter) {
+            return false;
+        }
+        ImageFilterCurves curve = (ImageFilterCurves) filter;
+        for (int i = 0; i < 256; i++) {
+            if (curve.mCurve[i] != mCurve[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public ImageFilter copy() {
+        ImageFilterCurves curves = new ImageFilterCurves();
+        curves.setCurve(mCurve);
+        curves.setName(mName);
+        curves.setSpline(new Spline(mSpline));
+        return curves;
+    }
+
+    public void populateArray(int[] array) {
+        for (int i = 0; i < 256; i++) {
+            array[i] = (int) (mCurve[i]);
+        }
+    }
+
+    public void apply(Bitmap bitmap) {
+
+        int[] redGradient = null;
+        if (mUseRed) {
+            redGradient = new int[256];
+            populateArray(redGradient);
+        }
+        int[] greenGradient = null;
+        if (mUseGreen) {
+            greenGradient = new int[256];
+            populateArray(greenGradient);
+        }
+        int[] blueGradient = null;
+        if (mUseBlue) {
+            blueGradient = new int[256];
+            populateArray(blueGradient);
+        }
+
+        nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
+                redGradient, greenGradient, blueGradient);
+    }
+
+    public void setSpline(Spline spline) {
+        mSpline = spline;
+    }
+
+    public Spline getSpline() {
+        return mSpline;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java
new file mode 100644 (file)
index 0000000..baa85e1
--- /dev/null
@@ -0,0 +1,98 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Shader.TileMode;
+
+public class ImageFilterGradient extends ImageFilter {
+
+    private Bitmap mGradientBitmap = null;
+    private int[] mColors = null;
+    private float[] mPositions = null;
+
+    public String name() {
+        return "Gradient";
+    }
+
+    public ImageFilter copy() {
+        ImageFilterGradient gradient = new ImageFilterGradient();
+        for (int i = 0; i < mColors.length; i++) {
+            gradient.addColor(mColors[i], mPositions[i]);
+        }
+        return gradient;
+    }
+
+    public void addColor(int color, float position) {
+        int length = 0;
+        if (mColors != null) {
+            length = mColors.length;
+        }
+        int[] colors = new int[length + 1];
+        float[] positions = new float[length + 1];
+
+        for (int i = 0; i < length; i++) {
+            colors[i] = mColors[i];
+            positions[i] = mPositions[i];
+        }
+
+        colors[length] = color;
+        positions[length] = position;
+
+        mColors = colors;
+        mPositions = positions;
+    }
+
+    public void apply(Bitmap bitmap) {
+
+        createGradient();
+        int[] gradient = new int[256];
+        int[] redGradient = new int[256];
+        int[] greenGradient = new int[256];
+        int[] blueGradient = new int[256];
+        mGradientBitmap.getPixels(gradient, 0, 256, 0, 0, 256, 1);
+
+        for (int i = 0; i < 256; i++) {
+            redGradient[i] = Color.red(gradient[i]);
+            greenGradient[i] = Color.green(gradient[i]);
+            blueGradient[i] = Color.blue(gradient[i]);
+        }
+        nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
+                redGradient, greenGradient, blueGradient);
+    }
+
+    public void createGradient() {
+        if (mGradientBitmap != null) {
+            return;
+        }
+
+        /* Create a 200 x 200 bitmap and fill it with black. */
+        Bitmap b = Bitmap.createBitmap(256, 1, Config.ARGB_8888);
+        Canvas c = new Canvas(b);
+        c.drawColor(Color.BLACK);
+
+        /* Create your gradient. */
+
+        /*
+         * int[] colors = new int[2]; colors[0] = Color.argb(255, 20, 20, 10);
+         * colors[0] = Color.BLACK; colors[1] = Color.argb(255, 228, 231, 193);
+         * float[] positions = new float[2]; positions[0] = 0; positions[1] = 1;
+         */
+
+        LinearGradient grad = new LinearGradient(0, 0, 255, 1, mColors,
+                mPositions, TileMode.CLAMP);
+
+        /* Draw your gradient to the top of your bitmap. */
+        Paint p = new Paint();
+        p.setStyle(Style.FILL);
+        p.setShader(grad);
+        c.drawRect(0, 0, 256, 1, p);
+        mGradientBitmap = b;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
new file mode 100644 (file)
index 0000000..7c03be6
--- /dev/null
@@ -0,0 +1,25 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterSaturated extends ImageFilter {
+
+    public String name() {
+        return "Saturated";
+    }
+
+    public ImageFilter copy() {
+        return new ImageFilterSaturated();
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float saturation);
+
+    public void apply(Bitmap bitmap) {
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        int p = 100;
+        float value = 2 * p / 100.0f;
+        nativeApplyFilter(bitmap, w, h, value);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java
new file mode 100644 (file)
index 0000000..09a3f22
--- /dev/null
@@ -0,0 +1,65 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import java.nio.ByteBuffer;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+public class ImageFilterStraighten extends ImageFilter {
+    private Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
+    private float mRotation;
+    private float mZoomFactor;
+
+    public String name() {
+        return "Straighten";
+    }
+
+    public void setRotation(float rotation) {
+        mRotation = rotation;
+    }
+
+    public void setRotationZoomFactor(float zoomFactor) {
+        mZoomFactor = zoomFactor;
+    }
+
+    public ImageFilterStraighten(float rotation, float zoomFactor) {
+        mRotation = rotation;
+        mZoomFactor = zoomFactor;
+    }
+
+    public ImageFilter copy() {
+        return new ImageFilterStraighten(mRotation, mZoomFactor);
+    }
+
+    public void apply(Bitmap bitmap) {
+        // TODO: implement bilinear or bicubic here... for now, just use
+        // canvas to do a simple implementation...
+        // TODO: and be more memory efficient! (do it in native?)
+
+        Bitmap temp = bitmap.copy(mConfig, true);
+        Canvas canvas = new Canvas(temp);
+        canvas.save();
+        Rect bounds = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        float w = temp.getWidth();
+        float h = temp.getHeight();
+        float mw = temp.getWidth() / 2.0f;
+        float mh = temp.getHeight() / 2.0f;
+
+        canvas.scale(mZoomFactor, mZoomFactor, mw, mh);
+        canvas.rotate(mRotation, mw, mh);
+        canvas.drawBitmap(bitmap, bounds, bounds, new Paint());
+        canvas.restore();
+
+        int[] pixels = new int[(int) (w * h)];
+        temp.getPixels(pixels, 0, (int) w, 0, 0, (int) w, (int) h);
+        bitmap.setPixels(pixels, 0, (int) w, 0, 0, (int) w, (int) h);
+        temp.recycle();
+        temp = null;
+        pixels = null;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
new file mode 100644 (file)
index 0000000..74d5527
--- /dev/null
@@ -0,0 +1,25 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterVignette extends ImageFilter {
+
+    public String name() {
+        return "Vignette";
+    }
+
+    public ImageFilter copy() {
+        return new ImageFilterVignette();
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength);
+
+    public void apply(Bitmap bitmap) {
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        float p = mParameter;
+        float value = p / 100.0f;
+        nativeApplyFilter(bitmap, w, h, value);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java b/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java
new file mode 100644 (file)
index 0000000..00be826
--- /dev/null
@@ -0,0 +1,58 @@
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.NinePatchDrawable;
+import android.util.AttributeSet;
+
+public class ImageBorder extends ImageShow {
+    Paint gPaint = new Paint();
+    private ImageShow mMasterImageShow = null;
+
+    public ImageBorder(Context context) {
+        super(context);
+    }
+
+    public ImageBorder(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setMaster(ImageShow master) {
+        mMasterImageShow = master;
+    }
+
+    public ImagePreset getImagePreset() {
+        return mMasterImageShow.getImagePreset();
+    }
+
+    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
+        mMasterImageShow.setImagePreset(preset, addToHistory);
+    }
+
+    public boolean showTitle() {
+        return false;
+    }
+
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        /*
+         * gPaint.setAntiAlias(true); gPaint.setFilterBitmap(true);
+         * gPaint.setDither(true); // Bitmap bmp =
+         * BitmapFactory.decodeResource(getResources(), //
+         * R.drawable.border_scratch); // canvas.drawBitmap(bmp, new Rect(0, 0,
+         * bmp.getWidth(), // bmp.getHeight()), mImageBounds, new Paint()); if
+         * (mImageBounds != null) { NinePatchDrawable npd = (NinePatchDrawable)
+         * getContext() .getResources().getDrawable(R.drawable.border_scratch2);
+         * npd.setBounds(mImageBounds); npd.draw(canvas); }
+         */
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
new file mode 100644 (file)
index 0000000..c22339d
--- /dev/null
@@ -0,0 +1,323 @@
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.HistoryAdapter;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.ui.PieSlider;
+import com.android.gallery3d.filtershow.ui.PieSliderListener;
+import com.android.gallery3d.R;
+import com.android.gallery3d.R.id;
+import com.android.gallery3d.R.layout;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ArrayAdapter;
+
+public class ImageShow extends View implements PieSliderListener {
+
+    private static final String LOGTAG = "ImageShow";
+
+    protected Paint mPaint = new Paint();
+    private static int mTextSize = 24;
+    private static int mTextPadding = 20;
+
+    protected ImagePreset mImagePreset = null;
+    protected ImageLoader mImageLoader = null;
+
+    private Bitmap mBackgroundImage = null;
+    protected Bitmap mForegroundImage = null;
+    protected Bitmap mFilteredImage = null;
+
+    protected PieSlider mPieSlider = new PieSlider();
+
+    private HistoryAdapter mAdapter = null;
+
+    protected Rect mImageBounds = null;
+    protected float mImageRotation = 0;
+    protected float mImageRotationZoomFactor = 0;
+
+    private boolean mShowControls = false;
+    private boolean mShowOriginal = false;
+    private String mToast = null;
+    private boolean mShowToast = false;
+    private boolean mImportantToast = false;
+
+    private Handler mHandler = new Handler();
+
+    public void onNewValue(int value) {
+        getImagePreset().setParameter(value);
+        mImageLoader.resetImageForPreset(getImagePreset(), this);
+        invalidate();
+    }
+
+    public ImageShow(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mPieSlider.setListener(this);
+        mAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row,
+                R.id.rowTextView);
+    }
+
+    public ImageShow(Context context) {
+        super(context);
+        mPieSlider.setListener(this);
+        mAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row,
+                R.id.rowTextView);
+    }
+
+    public void setAdapter(HistoryAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    public void showToast(String text) {
+        showToast(text, false);
+    }
+
+    public void showToast(String text, boolean important) {
+        mToast = text;
+        mShowToast = true;
+        mImportantToast = important;
+        invalidate();
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                mShowToast = false;
+                invalidate();
+            }
+        }, 400);
+    }
+
+    public ImagePreset getImagePreset() {
+        return mImagePreset;
+    }
+
+    public Bitmap getOriginalFrontBitmap() {
+        if (mImageLoader != null) {
+            return mImageLoader.getOriginalBitmapLarge();
+        }
+        return null;
+    }
+
+    public void drawToast(Canvas canvas) {
+        if (mShowToast && mToast != null) {
+            Paint paint = new Paint();
+            paint.setTextSize(128);
+            float textWidth = paint.measureText(mToast);
+            int toastX = (int) ((getWidth() - textWidth) / 2.0f);
+            int toastY = (int) (getHeight() / 2.0f);
+
+            paint.setARGB(255, 0, 0, 0);
+            canvas.drawText(mToast, toastX - 2, toastY - 2, paint);
+            canvas.drawText(mToast, toastX - 2, toastY, paint);
+            canvas.drawText(mToast, toastX, toastY - 2, paint);
+            canvas.drawText(mToast, toastX + 2, toastY + 2, paint);
+            canvas.drawText(mToast, toastX + 2, toastY, paint);
+            canvas.drawText(mToast, toastX, toastY + 2, paint);
+            if (mImportantToast) {
+                paint.setARGB(255, 200, 0, 0);
+            } else {
+                paint.setARGB(255, 255, 255, 255);
+            }
+            canvas.drawText(mToast, toastX, toastY, paint);
+        }
+    }
+
+    public void onDraw(Canvas canvas) {
+        if (mBackgroundImage == null) {
+            mBackgroundImage = mImageLoader.getBackgroundBitmap(getResources());
+        }
+        if (mBackgroundImage != null) {
+            Rect s = new Rect(0, 0, mBackgroundImage.getWidth(),
+                    mBackgroundImage.getHeight());
+            Rect d = new Rect(0, 0, getWidth(), getHeight());
+            canvas.drawBitmap(mBackgroundImage, s, d, mPaint);
+        }
+
+        Bitmap filteredImage = null;
+        if (mImageLoader != null) {
+            filteredImage = mImageLoader.getImageForPreset(this,
+                    getImagePreset(), showHires());
+//            Log.v(LOGTAG, "getImageForPreset " + getImagePreset() + " is: " + filteredImage);
+        }
+
+        if (filteredImage == null) {
+            // if no image for the current preset, use the previous one
+            filteredImage = mFilteredImage;
+        } else {
+            mFilteredImage = filteredImage;
+        }
+
+        if (mShowOriginal || mFilteredImage == null) {
+            mFilteredImage = mForegroundImage;
+        }
+
+        if (mFilteredImage != null) {
+            Rect s = new Rect(0, 0, mFilteredImage.getWidth(),
+                    mFilteredImage.getHeight());
+            float ratio = mFilteredImage.getWidth()
+                    / (float) mFilteredImage.getHeight();
+            float w = getWidth();
+            float h = w / ratio;
+            float ty = (getHeight() - h) / 2.0f;
+            float tx = 0;
+            // t = 0;
+            if (ratio < 1.0f) { // portrait image
+                h = getHeight();
+                w = h * ratio;
+                tx = (getWidth() - w) / 2.0f;
+                ty = 0;
+            }
+            Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
+                    (int) (h + ty));
+            mImageBounds = d;
+
+            canvas.drawBitmap(mFilteredImage, s, d, mPaint);
+        }
+
+        if (showTitle() && getImagePreset() != null) {
+            mPaint.setARGB(200, 0, 0, 0);
+            mPaint.setTextSize(mTextSize);
+
+            Rect textRect = new Rect(0, 0, getWidth(), mTextSize + mTextPadding);
+            canvas.drawRect(textRect, mPaint);
+            mPaint.setARGB(255, 200, 200, 200);
+            canvas.drawText(getImagePreset().name(), mTextPadding,
+                    10 + mTextPadding, mPaint);
+        }
+        mPaint.setARGB(255, 150, 150, 150);
+        mPaint.setStrokeWidth(4);
+        canvas.drawLine(0, 0, getWidth(), 0, mPaint);
+
+        if (showControls()) {
+            mPieSlider.onDraw(canvas);
+        }
+
+        drawToast(canvas);
+    }
+
+    public void setShowControls(boolean value) {
+        mShowControls = value;
+    }
+
+    public boolean showControls() {
+        return mShowControls;
+    }
+
+    public boolean showHires() {
+        return true;
+    }
+
+    public boolean showTitle() {
+        return true;
+    }
+
+    public void setImagePreset(ImagePreset preset) {
+        setImagePreset(preset, true);
+    }
+
+    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
+        mImagePreset = preset;
+        if (getImagePreset() != null) {
+//            Log.v(LOGTAG, "add " + getImagePreset().name() + " " + getImagePreset());
+            if (addToHistory) {
+                mAdapter.insert(getImagePreset(), 0);
+            }
+            getImagePreset().setEndpoint(this);
+            updateImage();
+        }
+//        Log.v(LOGTAG, "invalidate from setImagePreset");
+        invalidate();
+    }
+
+    public void setImageLoader(ImageLoader loader) {
+        mImageLoader = loader;
+        if (mImageLoader != null) {
+            mImageLoader.addListener(this);
+        }
+    }
+
+    public void updateImage() {
+        mForegroundImage = getOriginalFrontBitmap();
+        /*
+         * if (mImageLoader != null) {
+         * mImageLoader.resetImageForPreset(getImagePreset(), this); }
+         */
+
+        /*
+         * if (mForegroundImage != null) { Bitmap filteredImage =
+         * mForegroundImage.copy(mConfig, true);
+         * getImagePreset().apply(filteredImage); invalidate(); }
+         */
+    }
+
+    public void updateFilteredImage(Bitmap bitmap) {
+        mFilteredImage = bitmap;
+        // Log.v(LOGTAG, "invalidate from updateFilteredImage");
+        // invalidate();
+    }
+
+    public void saveImage(FilterShowActivity filterShowActivity) {
+        mImageLoader.saveImage(getImagePreset(), filterShowActivity);
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+        mPieSlider.onTouchEvent(event);
+        // Log.v(LOGTAG, "invalidate from onTouchEvent");
+        invalidate();
+        return true;
+    }
+
+    // listview stuff
+
+    public ArrayAdapter getListAdapter() {
+        return mAdapter;
+    }
+
+    public void onItemClick(int position) {
+        Log.v(LOGTAG, "Click on item " + position);
+        Log.v(LOGTAG, "item " + position + " is " + mAdapter.getItem(position));
+        setImagePreset(new ImagePreset(mAdapter.getItem(position)), false); // we
+                                                                            // need
+                                                                            // a
+                                                                            // copy
+                                                                            // from
+                                                                            // the
+                                                                            // history
+        mAdapter.setCurrentPreset(position);
+    }
+
+    public void showOriginal(boolean show) {
+        mShowOriginal = show;
+        invalidate();
+    }
+
+    public float getImageRotation() {
+        return mImageRotation;
+    }
+
+    public float getImageRotationZoomFactor() {
+        return mImageRotationZoomFactor;
+    }
+
+    public void setImageRotation(float imageRotation,
+            float imageRotationZoomFactor) {
+        if (imageRotation != mImageRotation) {
+            invalidate();
+        }
+        mImageRotation = imageRotation;
+        mImageRotationZoomFactor = imageRotationZoomFactor;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java
new file mode 100644 (file)
index 0000000..ed8b178
--- /dev/null
@@ -0,0 +1,76 @@
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.MeasureSpec;
+
+public class ImageSmallFilter extends ImageShow implements View.OnClickListener {
+
+    private FilterShowActivity mController = null;
+    private ImageFilter mImageFilter = null;
+
+    public ImageSmallFilter(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setOnClickListener(this);
+    }
+
+    public ImageSmallFilter(Context context) {
+        super(context);
+        setOnClickListener(this);
+    }
+
+    public void setImageFilter(ImageFilter filter) {
+        mImageFilter = filter;
+        mImagePreset = new ImagePreset();
+        mImagePreset.add(mImageFilter);
+    }
+
+    public void setController(FilterShowActivity activity) {
+        mController = activity;
+    }
+
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
+        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
+        setMeasuredDimension((int) (parentHeight * 1.5), parentHeight);
+    }
+
+    /*
+     * protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+     * setMeasuredDimension(256, 256); }
+     */
+
+    public void onClick(View v) {
+        if (mController != null) {
+            if (mImageFilter != null) {
+                mController.useImageFilter(mImageFilter);
+            } else if (mImagePreset != null) {
+                mController.useImagePreset(mImagePreset);
+            }
+        }
+    }
+
+    public Bitmap getOriginalFrontBitmap() {
+        if (mImageLoader == null) {
+            return null;
+        }
+        return mImageLoader.getOriginalBitmapSmall();
+    }
+
+    public boolean showControls() {
+        return false;
+    }
+
+    public boolean showHires() {
+        return false;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
new file mode 100644 (file)
index 0000000..fbc4666
--- /dev/null
@@ -0,0 +1,258 @@
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+
+public class ImageStraighten extends ImageShow {
+    private ImageShow mMasterImageShow = null;
+    private float mImageRotation = 0;
+    private float mImageRotationZoomFactor = 0;
+
+    private float mMinAngle = -45;
+    private float mMaxAngle = 45;
+    private float mBaseAngle = 0;
+    private float mAngle = 0;
+    private float mCenterX;
+    private float mCenterY;
+    private float mTouchCenterX;
+    private float mTouchCenterY;
+    private float mCurrentX;
+    private float mCurrentY;
+
+    private enum MODES {
+        NONE, DOWN, UP, MOVE
+    }
+
+    private MODES mMode = MODES.NONE;
+
+    private static final String LOGTAG = "ImageStraighten";
+    private static final Paint gPaint = new Paint();
+
+    public ImageStraighten(Context context) {
+        super(context);
+    }
+
+    public ImageStraighten(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void updateAngle() {
+        mMasterImageShow.setImageRotation(mImageRotation, mImageRotationZoomFactor);
+    }
+
+    public void setMaster(ImageShow master) {
+        mMasterImageShow = master;
+    }
+
+    public boolean showTitle() {
+        return false;
+    }
+
+    public ImagePreset getImagePreset() {
+        return mMasterImageShow.getImagePreset();
+    }
+
+    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
+        mMasterImageShow.setImagePreset(preset, addToHistory);
+    }
+
+    // ///////////////////////////////////////////////////////////////////////////
+    // touch event handler
+
+    public void setActionDown(float x, float y) {
+        mTouchCenterX = x;
+        mTouchCenterY = y;
+        mCurrentX = x;
+        mCurrentY = y;
+        mBaseAngle = mAngle;
+        mMode = MODES.DOWN;
+    }
+
+    public void setActionMove(float x, float y) {
+        mCurrentX = x;
+        mCurrentY = y;
+        mMode = MODES.MOVE;
+        computeValue();
+    }
+
+    public void setActionUp() {
+        mMode = MODES.UP;
+    }
+
+    public void setNoAction() {
+        mMode = MODES.NONE;
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case (MotionEvent.ACTION_DOWN):
+                setActionDown(event.getX(), event.getY());
+                break;
+            case (MotionEvent.ACTION_UP):
+                setActionUp();
+                break;
+            case (MotionEvent.ACTION_MOVE):
+                setActionMove(event.getX(), event.getY());
+                break;
+            default:
+                setNoAction();
+        }
+        mImageRotation = mAngle;
+        updateAngle();
+        invalidate();
+        return true;
+    }
+
+    private float angleFor(float dx, float dy) {
+        return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
+    }
+
+    private void computeValue() {
+        if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
+            return;
+        }
+        float dX1 = mTouchCenterX - mCenterX;
+        float dY1 = mTouchCenterY - mCenterY;
+        float dX2 = mCurrentX - mCenterX;
+        float dY2 = mCurrentY - mCenterY;
+
+        float angleA = angleFor(dX1, dY1);
+        float angleB = angleFor(dX2, dY2);
+        float angle = (angleB - angleA) % 360;
+        mAngle = (mBaseAngle - angle) % 360;
+        mAngle = Math.max(mMinAngle, mAngle);
+        mAngle = Math.min(mMaxAngle, mAngle);
+    }
+
+    // ///////////////////////////////////////////////////////////////////////////
+
+    public void onNewValue(int value) {
+        mImageRotation = value;
+        invalidate();
+    }
+
+    public void onDraw(Canvas canvas) {
+        mCenterX = getWidth() / 2;
+        mCenterY = getHeight() / 2;
+        drawStraighten(canvas);
+    }
+
+    public void drawStraighten(Canvas canvas) {
+        gPaint.setAntiAlias(true);
+        gPaint.setFilterBitmap(true);
+        gPaint.setDither(true);
+        gPaint.setARGB(255, 255, 255, 255);
+
+        // canvas.drawARGB(255, 255, 0, 0);
+
+        // TODO: have the concept of multiple image passes (geometry, filter)
+        // so that we can fake the rotation, etc.
+        Bitmap image = null; // mMasterImageShow.mFilteredImage;
+        if (image == null) {
+            image = mMasterImageShow.mForegroundImage;
+        }
+        if (image == null) {
+            return;
+        }
+
+        double iw = image.getWidth();
+        float zoom = (float) (getWidth() / iw);
+        // iw = getWidth(); // we will apply the zoom
+        double ih = image.getHeight();
+        if (ih > iw) {
+            zoom = (float) (getHeight() / ih);
+        }
+        float offset = (float) ((getHeight() - ih) / 2.0f);
+
+        canvas.save();
+        float dx = (float) ((getWidth() - iw) / 2.0f);
+        float dy = offset;
+
+        canvas.rotate(mImageRotation, mCenterX, mCenterY);
+        canvas.scale(zoom, zoom, mCenterX, mCenterY);
+        canvas.translate(dx, dy);
+        canvas.drawBitmap(image, 0, 0, gPaint);
+
+        canvas.restore();
+
+        double deg = mImageRotation;
+        if (deg < 0) {
+            deg = -deg;
+        }
+        double a = Math.toRadians(deg);
+        double sina = Math.sin(a);
+        double cosa = Math.cos(a);
+
+        double rw = image.getWidth();
+        double rh = image.getHeight();
+        double h1 = rh * rh / (rw * sina + rh * cosa);
+        double h2 = rh * rw / (rw * cosa + rh * sina);
+        double hh = Math.min(h1, h2);
+        double ww = hh * rw / rh;
+
+        float left = (float) ((rw - ww) * 0.5f);
+        float top = (float) ((rh - hh) * 0.5f);
+        float right = (float) (left + ww);
+        float bottom = (float) (top + hh);
+
+        RectF boundsRect = new RectF(left, top, right, bottom);
+        Matrix m = new Matrix();
+        m.setScale(zoom, zoom, mCenterX, mCenterY);
+        m.preTranslate(dx, dy);
+        m.mapRect(boundsRect);
+
+        gPaint.setARGB(128, 0, 0, 0);
+        gPaint.setStyle(Paint.Style.FILL);
+        canvas.drawRect(0, 0, getWidth(), boundsRect.top, gPaint);
+        canvas.drawRect(0, boundsRect.bottom, getWidth(), getHeight(), gPaint);
+        canvas.drawRect(0, boundsRect.top, boundsRect.left, boundsRect.bottom,
+                gPaint);
+        canvas.drawRect(boundsRect.right, boundsRect.top, getWidth(),
+                boundsRect.bottom, gPaint);
+
+        Path path = new Path();
+        path.addRect(boundsRect, Path.Direction.CCW);
+        gPaint.setARGB(255, 255, 255, 255);
+        gPaint.setStrokeWidth(3);
+        gPaint.setStyle(Paint.Style.STROKE);
+        canvas.drawPath(path, gPaint);
+        gPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+        mImageRotationZoomFactor = (float) (rw / boundsRect.width());
+
+        ImagePreset copy = new ImagePreset(getImagePreset());
+        Log.v(LOGTAG, "creating a new image preset with rotation " + mImageRotation
+                + " and zoom factor: " + mImageRotationZoomFactor);
+
+        copy.setStraightenRotation(mImageRotation, mImageRotationZoomFactor);
+        setImagePreset(copy);
+
+        if (mMode == MODES.MOVE) {
+            canvas.save();
+            canvas.clipPath(path);
+            int n = 16;
+            float step = getWidth() / n;
+            float p = 0;
+            for (int i = 1; i < n; i++) {
+                p = i * step;
+                gPaint.setARGB(60, 255, 255, 255);
+                canvas.drawLine(p, 0, p, getHeight(), gPaint);
+                canvas.drawLine(0, p, getWidth(), p, gPaint);
+            }
+            canvas.restore();
+        }
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
new file mode 100644 (file)
index 0000000..8c0e227
--- /dev/null
@@ -0,0 +1,5 @@
+package com.android.gallery3d.filtershow.imageshow;
+
+public class ImageVignette {
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
new file mode 100644 (file)
index 0000000..302b94b
--- /dev/null
@@ -0,0 +1,193 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import java.util.Vector;
+
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.filters.ImageFilterStraighten;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+
+public class ImagePreset {
+
+    private static final String LOGTAG = "ImagePreset";
+    ImageShow mEndPoint = null;
+    protected int mParameter = 50;
+    protected Vector<ImageFilter> mFilters = new Vector<ImageFilter>();
+    protected String mName = "Original";
+    protected String mHistoryName = "Original";
+    protected boolean mIsFxPreset = false;
+
+    enum FullRotate {
+        ZERO, NINETY, HUNDRED_HEIGHTY, TWO_HUNDRED_SEVENTY
+    }
+
+    protected FullRotate mFullRotate = FullRotate.ZERO;
+    protected float mStraightenRotate = 0;
+    protected float mStraightenZoom = 0;
+    protected boolean mHorizontalFlip = false;
+    protected boolean mVerticalFlip = false;
+    protected RectF mCrop = null;
+
+    public ImagePreset() {
+        setup();
+    }
+
+    public ImagePreset(ImagePreset source) {
+        for (int i = 0; i < source.mFilters.size(); i++) {
+            add(source.mFilters.elementAt(i).copy());
+        }
+        mName = source.name();
+        mHistoryName = source.name();
+        mIsFxPreset = source.isFx();
+
+        mStraightenRotate = source.mStraightenRotate;
+        mStraightenZoom = source.mStraightenZoom;
+    }
+
+    public void setStraightenRotation(float rotate, float zoom) {
+        mStraightenRotate = rotate;
+        mStraightenZoom = zoom;
+    }
+
+    private Bitmap applyGeometry(Bitmap original) {
+        Bitmap bitmap = original;
+
+        if (mFullRotate != FullRotate.ZERO) {
+            // TODO
+        }
+
+//        Log.v(LOGTAG, "applyGeometry with rotate " + mStraightenRotate + " and zoom "
+ //               + mStraightenZoom);
+
+        if (mStraightenRotate != 0) {
+            // TODO: keep the instances around
+            ImageFilter straighten = new ImageFilterStraighten(mStraightenRotate, mStraightenZoom);
+            straighten.apply(bitmap);
+            straighten = null;
+        }
+
+        return bitmap;
+    }
+
+    public boolean isFx() {
+        return mIsFxPreset;
+    }
+
+    public void setIsFx(boolean value) {
+        mIsFxPreset = value;
+    }
+
+    public void setName(String name) {
+        mName = name;
+        mHistoryName = name;
+    }
+
+    public void setHistoryName(String name) {
+        mHistoryName = name;
+    }
+
+    public boolean same(ImagePreset preset) {
+        if (preset.mFilters.size() != mFilters.size()) {
+            return false;
+        }
+        if (!mName.equalsIgnoreCase(preset.name())) {
+            return false;
+        }
+        if (mStraightenRotate != preset.mStraightenRotate) {
+            return false;
+        }
+        for (int i = 0; i < preset.mFilters.size(); i++) {
+            ImageFilter a = preset.mFilters.elementAt(i);
+            ImageFilter b = mFilters.elementAt(i);
+            if (!a.same(b)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public String name() {
+        return mName;
+    }
+
+    public String historyName() {
+        return mHistoryName;
+    }
+
+    public void add(ImageFilter preset) {
+        mFilters.add(preset);
+    }
+
+    public void remove(String filterName) {
+        ImageFilter filter = getFilter(filterName);
+        if (filter != null) {
+            mFilters.remove(filter);
+        }
+    }
+
+    public int getCount() {
+        return mFilters.size();
+    }
+
+    public ImageFilter getFilter(String name) {
+        for (int i = 0; i < mFilters.size(); i++) {
+            ImageFilter filter = mFilters.elementAt(i);
+            if (filter.name().equalsIgnoreCase(name)) {
+                return filter;
+            }
+        }
+        return null;
+    }
+
+    public void setup() {
+        // do nothing here
+    }
+
+    public void setEndpoint(ImageShow image) {
+        mEndPoint = image;
+    }
+
+    public Bitmap apply(Bitmap original) {
+        // First we apply any transform -- 90 rotate, flip, straighten, crop
+        Bitmap bitmap = applyGeometry(original);
+
+        // TODO -- apply borders separately
+        ImageFilter borderFilter = null;
+        for (int i = 0; i < mFilters.size(); i++) {
+            ImageFilter filter = mFilters.elementAt(i);
+            if (filter.name().equalsIgnoreCase("Border")) {
+                // TODO don't use the name as an id
+                borderFilter = filter;
+            } else {
+                filter.apply(bitmap);
+            }
+        }
+        if (borderFilter != null) {
+            borderFilter.apply(bitmap);
+        }
+        if (mEndPoint != null) {
+            mEndPoint.updateFilteredImage(bitmap);
+        }
+        return bitmap;
+    }
+
+    /*
+     * public void applyFilter(Bitmap bitmap) { // do nothing here, subclasses
+     * will implement filtering here } native protected void
+     * nativeApplyGradientFilter(Bitmap bitmap, int w, int h, int[] redGradient,
+     * int[] greenGradient, int[] blueGradient);
+     */
+
+    public void setParameter(int value) {
+        mParameter = value;
+        for (int i = 0; i < mFilters.size(); i++) {
+            ImageFilter filter = mFilters.elementAt(i);
+            filter.setParameter(value);
+        }
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java
new file mode 100644 (file)
index 0000000..a270080
--- /dev/null
@@ -0,0 +1,16 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterBW;
+
+public class ImagePresetBW extends ImagePreset {
+
+    public String name() {
+        return "Black & White";
+    }
+
+    public void setup() {
+        mFilters.add(new ImageFilterBW());
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java
new file mode 100644 (file)
index 0000000..1783b48
--- /dev/null
@@ -0,0 +1,16 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterBWBlue;
+
+public class ImagePresetBWBlue extends ImagePreset {
+
+    public String name() {
+        return "Black & White (Blue)";
+    }
+
+    public void setup() {
+        mFilters.add(new ImageFilterBWBlue());
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java
new file mode 100644 (file)
index 0000000..5b317a1
--- /dev/null
@@ -0,0 +1,16 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterBWGreen;
+
+public class ImagePresetBWGreen extends ImagePreset {
+
+    public String name() {
+        return "Black & White (Green)";
+    }
+
+    public void setup() {
+        mFilters.add(new ImageFilterBWGreen());
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java
new file mode 100644 (file)
index 0000000..7b9f0e1
--- /dev/null
@@ -0,0 +1,16 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterBWRed;
+
+public class ImagePresetBWRed extends ImagePreset {
+
+    public String name() {
+        return "Black & White (Red)";
+    }
+
+    public void setup() {
+        mFilters.add(new ImageFilterBWRed());
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java
new file mode 100644 (file)
index 0000000..56e1dab
--- /dev/null
@@ -0,0 +1,21 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterGradient;
+
+import android.graphics.Color;
+
+public class ImagePresetOld extends ImagePreset {
+
+    public String name() {
+        return "Old";
+    }
+
+    public void setup() {
+        ImageFilterGradient filter = new ImageFilterGradient();
+        filter.addColor(Color.BLACK, 0.0f);
+        filter.addColor(Color.argb(255, 228, 231, 193), 1.0f);
+        mFilters.add(filter);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java
new file mode 100644 (file)
index 0000000..cf280ee
--- /dev/null
@@ -0,0 +1,16 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterSaturated;
+
+public class ImagePresetSaturated extends ImagePreset {
+
+    public String name() {
+        return "Saturated";
+    }
+
+    public void setup() {
+        mFilters.add(new ImageFilterSaturated());
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java
new file mode 100644 (file)
index 0000000..3e744be
--- /dev/null
@@ -0,0 +1,22 @@
+
+package com.android.gallery3d.filtershow.presets;
+
+import com.android.gallery3d.filtershow.filters.ImageFilterGradient;
+
+import android.graphics.Color;
+
+public class ImagePresetXProcessing extends ImagePreset {
+
+    public String name() {
+        return "X-Process";
+    }
+
+    public void setup() {
+        ImageFilterGradient filter = new ImageFilterGradient();
+        filter.addColor(Color.BLACK, 0.0f);
+        filter.addColor(Color.argb(255, 29, 82, 83), 0.4f);
+        filter.addColor(Color.argb(255, 211, 217, 186), 1.0f);
+        mFilters.add(filter);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
new file mode 100644 (file)
index 0000000..8ca21e2
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.tools;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.view.Gravity;
+import android.widget.Toast;
+
+//import com.android.gallery3d.R;
+//import com.android.gallery3d.util.BucketNames;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+
+/**
+ * Asynchronous task for saving edited photo as a new copy.
+ */
+public class SaveCopyTask extends AsyncTask<Bitmap, Void, Uri> {
+
+    public static final String DOWNLOAD = "download";
+    public static final String DEFAULT_SAVE_DIRECTORY = "Download";
+    private static final int DEFAULT_COMPRESS_QUALITY = 95;
+
+    /**
+     * Saves the bitmap by given directory, filename, and format; if the
+     * directory is given null, then saves it under the cache directory.
+     */
+    public File saveBitmap(Bitmap bitmap, File directory, String filename,
+            CompressFormat format) {
+
+        if (directory == null) {
+            directory = context.getCacheDir();
+        } else {
+            // Check if the given directory exists or try to create it.
+            if (!directory.isDirectory() && !directory.mkdirs()) {
+                return null;
+            }
+        }
+
+        File file = null;
+        OutputStream os = null;
+
+        try {
+            filename = (format == CompressFormat.PNG) ? filename + ".png"
+                    : filename + ".jpg";
+            file = new File(directory, filename);
+            os = new FileOutputStream(file);
+            bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, os);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } finally {
+            closeStream(os);
+        }
+        return file;
+    }
+
+    private void closeStream(Closeable stream) {
+        if (stream != null) {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Callback for the completed asynchronous task.
+     */
+    public interface Callback {
+
+        void onComplete(Uri result);
+    }
+
+    private interface ContentResolverQueryCallback {
+
+        void onCursorResult(Cursor cursor);
+    }
+
+    private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
+
+    private final Context context;
+    private final Uri sourceUri;
+    private final Callback callback;
+    private final String saveFileName;
+    private String saveFolderName;
+
+    public SaveCopyTask(Context context, Uri sourceUri, Callback callback) {
+        this.context = context;
+        this.sourceUri = sourceUri;
+        this.callback = callback;
+
+        saveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(
+                System.currentTimeMillis()));
+    }
+
+    /**
+     * The task should be executed with one given bitmap to be saved.
+     */
+    @Override
+    protected Uri doInBackground(Bitmap... params) {
+        // TODO: Support larger dimensions for photo saving.
+        if (params[0] == null) {
+            return null;
+        }
+        // Use the default save directory if the source directory cannot be
+        // saved.
+        File saveDirectory = getSaveDirectory();
+        if ((saveDirectory == null) || !saveDirectory.canWrite()) {
+            saveDirectory = new File(Environment.getExternalStorageDirectory(),
+                    DOWNLOAD);
+            saveFolderName = DEFAULT_SAVE_DIRECTORY;
+        } else {
+            saveFolderName = saveDirectory.getName();
+        }
+
+        Bitmap bitmap = params[0];
+
+        File file = saveBitmap(bitmap, saveDirectory, saveFileName,
+                Bitmap.CompressFormat.JPEG);
+
+        Uri uri = (file != null) ? insertContent(file) : null;
+        bitmap.recycle();
+        return uri;
+    }
+
+    @Override
+    protected void onPostExecute(Uri result) {
+        /*
+         * String message = (result == null) ?
+         * context.getString(R.string.saving_failure) :
+         * context.getString(R.string.photo_saved, saveFolderName); Toast toast
+         * = Toast.makeText(context, message, Toast.LENGTH_SHORT);
+         * toast.setGravity(Gravity.CENTER, 0, 0); toast.show();
+         */
+        if (callback != null) {
+            callback.onComplete(result);
+        }
+    }
+
+    private void querySource(String[] projection,
+            ContentResolverQueryCallback callback) {
+        ContentResolver contentResolver = context.getContentResolver();
+        Cursor cursor = null;
+        try {
+            cursor = contentResolver.query(sourceUri, projection, null, null,
+                    null);
+            if ((cursor != null) && cursor.moveToNext()) {
+                callback.onCursorResult(cursor);
+            }
+        } catch (Exception e) {
+            // Ignore error for lacking the data column from the source.
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    private File getSaveDirectory() {
+        final File[] dir = new File[1];
+        querySource(new String[] {
+            ImageColumns.DATA
+        },
+                new ContentResolverQueryCallback() {
+
+                    @Override
+                    public void onCursorResult(Cursor cursor) {
+                        dir[0] = new File(cursor.getString(0)).getParentFile();
+                    }
+                });
+        return dir[0];
+    }
+
+    /**
+     * Insert the content (saved file) with proper source photo properties.
+     */
+    private Uri insertContent(File file) {
+        long now = System.currentTimeMillis() / 1000;
+
+        final ContentValues values = new ContentValues();
+        values.put(Images.Media.TITLE, saveFileName);
+        values.put(Images.Media.DISPLAY_NAME, file.getName());
+        values.put(Images.Media.MIME_TYPE, "image/jpeg");
+        values.put(Images.Media.DATE_TAKEN, now);
+        values.put(Images.Media.DATE_MODIFIED, now);
+        values.put(Images.Media.DATE_ADDED, now);
+        values.put(Images.Media.ORIENTATION, 0);
+        values.put(Images.Media.DATA, file.getAbsolutePath());
+        values.put(Images.Media.SIZE, file.length());
+
+        String[] projection = new String[] {
+                ImageColumns.DATE_TAKEN,
+                ImageColumns.LATITUDE, ImageColumns.LONGITUDE,
+        };
+        querySource(projection, new ContentResolverQueryCallback() {
+
+            @Override
+            public void onCursorResult(Cursor cursor) {
+                values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
+
+                double latitude = cursor.getDouble(1);
+                double longitude = cursor.getDouble(2);
+                // TODO: Change || to && after the default location issue is
+                // fixed.
+                if ((latitude != 0f) || (longitude != 0f)) {
+                    values.put(Images.Media.LATITUDE, latitude);
+                    values.put(Images.Media.LONGITUDE, longitude);
+                }
+            }
+        });
+
+        return context.getContentResolver().insert(
+                Images.Media.EXTERNAL_CONTENT_URI, values);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/ControlPoint.java b/src/com/android/gallery3d/filtershow/ui/ControlPoint.java
new file mode 100644 (file)
index 0000000..6daa961
--- /dev/null
@@ -0,0 +1,39 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+class ControlPoint implements Comparable {
+    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 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);
+    }
+
+    @Override
+    public int compareTo(Object another) {
+        ControlPoint p = (ControlPoint) another;
+        if (p.x < x) {
+            return 1;
+        } else if (p.x > x) {
+            return -1;
+        }
+        return 0;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java b/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java
new file mode 100644 (file)
index 0000000..7f0b043
--- /dev/null
@@ -0,0 +1,42 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+import com.android.gallery3d.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.ImageButton;
+
+public class ImageButtonTitle extends ImageButton {
+    private static final String LOGTAG = "ImageButtonTitle";
+    private String mText = null;
+    private static int mTextSize = 24;
+    private static int mTextPadding = 20;
+    private static Paint gPaint = new Paint();
+
+    public ImageButtonTitle(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = getContext().obtainStyledAttributes(
+                attrs, R.styleable.ImageButtonTitle);
+
+        mText = a.getString(R.styleable.ImageButtonTitle_android_text);
+    }
+
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mText != null) {
+            gPaint.setARGB(255, 255, 255, 255);
+            gPaint.setTextSize(mTextSize);
+            float textWidth = gPaint.measureText(mText);
+            int x = (int) ((getWidth() - textWidth) / 2);
+            int y = getHeight() - mTextPadding;
+
+            canvas.drawText(mText, x, y, gPaint);
+        }
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
new file mode 100644 (file)
index 0000000..8091c1e
--- /dev/null
@@ -0,0 +1,408 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.ui.ControlPoint;
+import com.android.gallery3d.filtershow.ui.Spline;
+import com.android.gallery3d.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.PopupMenu;
+import android.widget.Toast;
+
+public class ImageCurves extends ImageShow {
+
+    private static final String LOGTAG = "ImageCurves";
+    Paint gPaint = new Paint();
+    Spline mSpline = null;
+    Path gPathSpline = new Path();
+    float[] mAppliedCurve = new float[256];
+    private boolean mDidAddPoint = false;
+    private boolean mDidDelete = false;
+    private ImageShow mMasterImageShow = null;
+    private ControlPoint mCurrentControlPoint = null;
+    private boolean mUseRed = true;
+    private boolean mUseGreen = true;
+    private boolean mUseBlue = true;
+
+    public ImageCurves(Context context) {
+        super(context);
+        resetCurve();
+    }
+
+    public ImageCurves(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        resetCurve();
+    }
+
+    public void setMaster(ImageShow master) {
+        mMasterImageShow = master;
+    }
+
+    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 (mMasterImageShow != null) {
+            String filterName = getFilterName();
+            ImageFilterCurves filter = (ImageFilterCurves) getImagePreset()
+                    .getFilter(filterName);
+            if (filter == null) {
+                resetCurve();
+                return;
+            }
+            mSpline = new Spline(filter.getSpline());
+            applyNewCurve();
+        }
+    }
+
+    public void resetCurve() {
+        mSpline = new Spline();
+
+        mSpline.addPoint(0.0f, 1.0f);
+        mSpline.addPoint(1.0f, 0.0f);
+        if (mMasterImageShow != null) {
+            applyNewCurve();
+        }
+    }
+
+    public ImagePreset getImagePreset() {
+        return mMasterImageShow.getImagePreset();
+    }
+
+    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
+        mMasterImageShow.setImagePreset(preset, addToHistory);
+    }
+
+    public float getImageRotation() {
+        return mMasterImageShow.getImageRotation();
+    }
+
+    public float getImageRotationZoomFactor() {
+        return mMasterImageShow.getImageRotationZoomFactor();
+    }
+
+    public void onDraw(Canvas canvas) {
+        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);
+        }
+    }
+
+    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);
+            }
+        }
+
+        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;
+        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;
+            double currentDelta = Math.sqrt((px - x) * (px - x) + (py - y)
+                    * (py - y));
+            if (currentDelta < delta) {
+                delta = currentDelta;
+                pick = i;
+            }
+        }
+
+        if (!mDidAddPoint && (delta * getWidth() > 100)
+                && (mSpline.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();
+    }
+
+    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;
+    }
+
+    @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());
+
+        if (e.getActionMasked() == MotionEvent.ACTION_UP) {
+            applyNewCurve();
+            // Log.v(LOGTAG, "ACTION UP, mCurrentControlPoint set to null!");
+            mCurrentControlPoint = null;
+            ImagePreset copy = new ImagePreset(getImagePreset());
+
+            if (mUseRed && mUseGreen && mUseBlue) {
+                copy.setHistoryName("Curves (RGB)");
+            } else if (mUseRed) {
+                copy.setHistoryName("Curves (Red)");
+            } else if (mUseGreen) {
+                copy.setHistoryName("Curves (Green)");
+            } else if (mUseBlue) {
+                copy.setHistoryName("Curves (Blue)");
+            }
+            copy.setIsFx(false);
+            mImageLoader.getHistory().insert(copy, 0);
+
+            invalidate();
+            mDidAddPoint = false;
+            if (mDidDelete) {
+                mDidDelete = false;
+            }
+            return true;
+        }
+
+        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);
+                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);
+            }
+        }
+        // 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;
+            }
+        }
+        // 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();
+            ImageFilterCurves filter = (ImageFilterCurves) getImagePreset()
+                    .getFilter(filterName);
+            if (filter == null) {
+                filter = new ImageFilterCurves();
+                filter.setName(filterName);
+                ImagePreset copy = new ImagePreset(getImagePreset());
+                copy.add(filter);
+                setImagePreset(copy, false);
+            }
+
+            if (filter != null) {
+                filter.setSpline(new Spline(mSpline));
+                filter.setCurve(appliedCurve);
+                filter.setUseRed(mUseRed);
+                filter.setUseGreen(mUseGreen);
+                filter.setUseBlue(mUseBlue);
+            }
+            mImageLoader.resetImageForPreset(getImagePreset(), this);
+            invalidate();
+        }
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/PieSlider.java b/src/com/android/gallery3d/filtershow/ui/PieSlider.java
new file mode 100644 (file)
index 0000000..0f3702d
--- /dev/null
@@ -0,0 +1,181 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Path.FillType;
+import android.view.MotionEvent;
+
+public class PieSlider {
+    private float mCenterX;
+    private float mCenterY;
+    private float mCurrentX;
+    private float mCurrentY;
+    private int mStartAngle = 210;
+    private int mEndAngle = 120;
+    private int mValue = 100;
+
+    private Paint mPaint = new Paint();
+    private Path mBasePath = new Path();
+    private Path mCirclePath = new Path();
+    private RectF mOvalRect = new RectF();
+    private Rect mTextBounds = new Rect();
+
+    private PieSliderListener mListener = null;
+
+    private MODES mMode = MODES.NONE;
+    private static int mMenuRadius = 140;
+    private static int mTextSize = 32;
+    private static int mFanDistance = 2 * mMenuRadius;
+    private static int mTextDistance = (int) (2.2f * mMenuRadius);
+    private static int mLineDistance = (int) (2.5f * mMenuRadius);
+
+    private enum MODES {
+        NONE, DOWN, UP, MOVE
+    }
+
+    public void onDraw(Canvas canvas) {
+        if (mMode == MODES.NONE || mMode == MODES.UP) {
+            return;
+        }
+        drawFan(canvas);
+    }
+
+    public void setActionDown(float x, float y) {
+        mCenterX = x;
+        mCenterY = y;
+        mCurrentX = x;
+        mCurrentY = y;
+        mMode = MODES.DOWN;
+    }
+
+    public void setActionMove(float x, float y) {
+        mCurrentX = x;
+        mCurrentY = y;
+        mMode = MODES.MOVE;
+        computeValue();
+        if (mListener != null) {
+            mListener.onNewValue(mValue);
+        }
+    }
+
+    public void setActionUp() {
+        mMode = MODES.UP;
+    }
+
+    public void setNoAction() {
+        mMode = MODES.NONE;
+    }
+
+    private void drawFan(Canvas canvas) {
+        mPaint.setARGB(200, 200, 200, 200);
+        mPaint.setStrokeWidth(4);
+        mBasePath.reset();
+        mCirclePath.reset();
+
+        int mf = 2;
+
+        mOvalRect.left = mCenterX - mFanDistance;
+        mOvalRect.top = mCenterY - mFanDistance;
+        mOvalRect.right = mOvalRect.left + mf * mFanDistance;
+        mOvalRect.bottom = mOvalRect.top + mf * mFanDistance;
+
+        canvas.save();
+        mBasePath.moveTo(mCenterX, mCenterY);
+        mBasePath.arcTo(mOvalRect, mStartAngle, mEndAngle);
+
+        mCirclePath.moveTo(mCenterX, mCenterY);
+        mOvalRect.left = mCenterX - mMenuRadius;
+        mOvalRect.top = mCenterY - mMenuRadius;
+        mOvalRect.right = mOvalRect.left + mf * mMenuRadius;
+        mOvalRect.bottom = mOvalRect.top + mf * mMenuRadius;
+        mCirclePath.arcTo(mOvalRect, mStartAngle, mEndAngle);
+
+        mBasePath.setFillType(FillType.EVEN_ODD);
+        mBasePath.addPath(mCirclePath);
+        mBasePath.close();
+
+        canvas.drawPath(mBasePath, mPaint);
+        canvas.restore();
+
+        canvas.save();
+        canvas.rotate(-60, mCenterX, mCenterY);
+        canvas.drawLine(mCenterX, mCenterY - mMenuRadius, mCenterX, mCenterY - mLineDistance,
+                mPaint);
+        canvas.restore();
+        canvas.save();
+        canvas.rotate(mEndAngle - 60, mCenterX, mCenterY);
+        canvas.drawLine(mCenterX, mCenterY - mMenuRadius, mCenterX, mCenterY - mLineDistance,
+                mPaint);
+        canvas.restore();
+
+        canvas.save();
+        canvas.rotate(mEndAngle / 2 - 60, mCenterX, mCenterY);
+        String txt = "" + mValue;
+        mPaint.setTextSize(mTextSize);
+        mPaint.getTextBounds(txt, 0, txt.length(), mTextBounds);
+        mPaint.setARGB(255, 20, 20, 20);
+        canvas.drawText(txt, mCenterX - mTextBounds.width() / 2 - 2, mCenterY - mTextDistance - 2,
+                mPaint);
+        mPaint.setARGB(200, 200, 200, 200);
+        canvas.drawText(txt, mCenterX - mTextBounds.width() / 2, mCenterY - mTextDistance, mPaint);
+        canvas.restore();
+    }
+
+    private void computeValue() {
+        float dX = mCurrentX - mCenterX;
+        float dY = mCurrentY - mCenterY;
+        float distance = (float) Math.sqrt(dX * dX + dY * dY);
+
+        if (mCenterY > mCurrentY && distance > mMenuRadius) {
+            float angle = (float) (Math.atan2(dX, dY) * 180 / Math.PI);
+            if (angle < 0) {
+                // from -90 to -180
+                angle = -angle;
+                angle = Math.max(90, angle);
+                angle = Math.min(180, angle);
+                angle -= 90;
+            } else {
+                angle = Math.max(90, angle);
+                angle = Math.min(180, angle);
+                angle -= 90;
+                angle = 90 + (90 - angle);
+            }
+            angle /= 180.0f;
+            angle = Math.max(0, angle);
+            angle = Math.min(1, angle);
+            mEndAngle = (int) (120 * angle);
+            mValue = (int) (100 * (mEndAngle / 120.0f));
+        }
+    }
+
+    public void setListener(PieSliderListener listener) {
+        mListener = listener;
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        setNoAction();
+        switch (event.getActionMasked()) {
+            case (MotionEvent.ACTION_DOWN): {
+                setActionDown(event.getX(), event.getY());
+                break;
+            }
+            case (MotionEvent.ACTION_UP): {
+                setActionUp();
+                break;
+            }
+            case (MotionEvent.ACTION_CANCEL): {
+                setActionUp();
+                break;
+            }
+            case (MotionEvent.ACTION_MOVE): {
+                setActionMove(event.getX(), event.getY());
+                break;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/PieSliderListener.java b/src/com/android/gallery3d/filtershow/ui/PieSliderListener.java
new file mode 100644 (file)
index 0000000..6020ad8
--- /dev/null
@@ -0,0 +1,6 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+public interface PieSliderListener {
+    public void onNewValue(int value);
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/Spline.java b/src/com/android/gallery3d/filtershow/ui/Spline.java
new file mode 100644 (file)
index 0000000..a272d28
--- /dev/null
@@ -0,0 +1,126 @@
+
+package com.android.gallery3d.filtershow.ui;
+
+import java.util.Collections;
+import java.util.Vector;
+
+public class Spline {
+    public Spline() {
+        mPoints = new Vector<ControlPoint>();
+    }
+
+    public Spline(Spline spline) {
+        mPoints = new Vector<ControlPoint>();
+        for (int i = 0; i < spline.mPoints.size(); i++) {
+            ControlPoint p = spline.mPoints.elementAt(i);
+            mPoints.add(p);
+        }
+        Collections.sort(mPoints);
+        delta_t = 1.0f / mPoints.size();
+    }
+
+    public ControlPoint interpolate(float t, ControlPoint p1,
+            ControlPoint p2, ControlPoint p3, ControlPoint p4) {
+
+        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);
+
+        ControlPoint b1p1 = p1.multiply(b1);
+        ControlPoint b2p2 = p2.multiply(b2);
+        ControlPoint b3p3 = p3.multiply(b3);
+        ControlPoint b4p4 = p4.multiply(b4);
+
+        return b1p1.add(b2p2.add(b3p3.add(b4p4)));
+    }
+
+    public void addPoint(float x, float y) {
+        addPoint(new ControlPoint(x, y));
+    }
+
+    public void addPoint(ControlPoint v) {
+        mPoints.add(v);
+        Collections.sort(mPoints);
+        delta_t = 1.0f / mPoints.size();
+    }
+
+    public ControlPoint getPoint(float t) {
+        int p = (int) (t / delta_t);
+        int p0 = p - 1;
+        int max = mPoints.size() - 1;
+
+        if (p0 < 0) {
+            p0 = 0;
+        } else if (p0 >= max) {
+            p0 = max;
+        }
+        int p1 = p;
+        if (p1 < 0) {
+            p1 = 0;
+        } else if (p1 >= max) {
+            p1 = max;
+        }
+        int p2 = p + 1;
+        if (p2 < 0) {
+            p2 = 0;
+        } else if (p2 >= max) {
+            p2 = max;
+        }
+        int p3 = p + 2;
+        if (p3 < 0) {
+            p3 = 0;
+        } else if (p3 >= max) {
+            p3 = max;
+        }
+        float lt = (t - delta_t * (float) p) / delta_t;
+        return interpolate(lt, mPoints.elementAt(p0),
+                mPoints.elementAt(p1), mPoints.elementAt(p2),
+                mPoints.elementAt(p3));
+
+    }
+
+    public int getNbPoints() {
+        return mPoints.size();
+    }
+
+    public ControlPoint getPoint(int n) {
+        return mPoints.elementAt(n);
+    }
+
+    public boolean isPointContained(float x, int n) {
+        for (int i = 0; i < n; i++) {
+            ControlPoint point = mPoints.elementAt(i);
+            if (point.x > x) {
+                return false;
+            }
+        }
+        for (int i = n + 1; i < mPoints.size(); i++) {
+            ControlPoint point = mPoints.elementAt(i);
+            if (point.x < x) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public 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++) {
+            ControlPoint point = mPoints.elementAt(i);
+            spline.addPoint(point.copy());
+        }
+        return spline;
+    }
+}