summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/filtershow
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/filtershow')
-rw-r--r--src/com/android/gallery3d/filtershow/CenteredLinearLayout.java51
-rw-r--r--src/com/android/gallery3d/filtershow/EditorPlaceHolder.java82
-rw-r--r--src/com/android/gallery3d/filtershow/FilterShowActivity.java1121
-rw-r--r--src/com/android/gallery3d/filtershow/cache/ImageLoader.java502
-rw-r--r--src/com/android/gallery3d/filtershow/category/Action.java186
-rw-r--r--src/com/android/gallery3d/filtershow/category/CategoryAdapter.java182
-rw-r--r--src/com/android/gallery3d/filtershow/category/CategoryPanel.java108
-rw-r--r--src/com/android/gallery3d/filtershow/category/CategoryTrack.java77
-rw-r--r--src/com/android/gallery3d/filtershow/category/CategoryView.java176
-rw-r--r--src/com/android/gallery3d/filtershow/category/MainPanel.java239
-rw-r--r--src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java100
-rw-r--r--src/com/android/gallery3d/filtershow/colorpicker/ColorListener.java21
-rw-r--r--src/com/android/gallery3d/filtershow/colorpicker/ColorOpacityView.java197
-rw-r--r--src/com/android/gallery3d/filtershow/colorpicker/ColorPickerDialog.java123
-rw-r--r--src/com/android/gallery3d/filtershow/colorpicker/ColorRectView.java225
-rw-r--r--src/com/android/gallery3d/filtershow/colorpicker/ColorValueView.java180
-rw-r--r--src/com/android/gallery3d/filtershow/colorpicker/RGBListener.java21
-rw-r--r--src/com/android/gallery3d/filtershow/controller/ActionSlider.java71
-rw-r--r--src/com/android/gallery3d/filtershow/controller/BasicParameterInt.java113
-rw-r--r--src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java111
-rw-r--r--src/com/android/gallery3d/filtershow/controller/BasicSlider.java87
-rw-r--r--src/com/android/gallery3d/filtershow/controller/Control.java32
-rw-r--r--src/com/android/gallery3d/filtershow/controller/FilterView.java25
-rw-r--r--src/com/android/gallery3d/filtershow/controller/Parameter.java33
-rw-r--r--src/com/android/gallery3d/filtershow/controller/ParameterActionAndInt.java29
-rw-r--r--src/com/android/gallery3d/filtershow/controller/ParameterInteger.java31
-rw-r--r--src/com/android/gallery3d/filtershow/controller/ParameterSet.java23
-rw-r--r--src/com/android/gallery3d/filtershow/controller/ParameterStyles.java37
-rw-r--r--src/com/android/gallery3d/filtershow/controller/StyleChooser.java88
-rw-r--r--src/com/android/gallery3d/filtershow/controller/TitledSlider.java106
-rw-r--r--src/com/android/gallery3d/filtershow/crop/BoundedRect.java368
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropActivity.java697
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java168
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropExtras.java121
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropMath.java260
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropObject.java330
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropView.java378
-rw-r--r--src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java101
-rw-r--r--src/com/android/gallery3d/filtershow/data/FilterStackSource.java197
-rw-r--r--src/com/android/gallery3d/filtershow/data/UserPresetsManager.java149
-rw-r--r--src/com/android/gallery3d/filtershow/editors/BasicEditor.java138
-rw-r--r--src/com/android/gallery3d/filtershow/editors/Editor.java330
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorChanSat.java227
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorCrop.java168
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorCurves.java56
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorDraw.java158
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorGrad.java315
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorInfo.java23
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorMirror.java109
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorPanel.java143
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorRedEye.java65
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorRotate.java112
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorStraighten.java103
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java58
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorVignette.java53
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorZoom.java27
-rw-r--r--src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java50
-rw-r--r--src/com/android/gallery3d/filtershow/editors/ParametricEditor.java206
-rw-r--r--src/com/android/gallery3d/filtershow/editors/SwapButton.java111
-rw-r--r--src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java296
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ColorSpaceMatrix.java225
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java196
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterChanSatRepresentation.java211
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterColorBorderRepresentation.java113
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java179
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java170
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterDirectRepresentation.java38
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java171
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java112
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterGradRepresentation.java497
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterImageBorderRepresentation.java91
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterMirrorRepresentation.java190
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterPoint.java21
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterPointRepresentation.java88
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java67
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java262
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterRotateRepresentation.java190
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterStraightenRepresentation.java154
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java101
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterUserPresetRepresentation.java53
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java173
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java21
-rw-r--r--src/com/android/gallery3d/filtershow/filters/IconUtilities.java75
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilter.java109
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java92
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java64
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterChanSat.java161
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java58
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java112
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java83
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java278
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java53
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java56
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java111
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterGrad.java190
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java74
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java64
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java95
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java39
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java69
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java260
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java79
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java58
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java58
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java107
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java158
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java57
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java98
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java59
-rw-r--r--src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java50
-rw-r--r--src/com/android/gallery3d/filtershow/filters/SimpleImageFilter.java37
-rw-r--r--src/com/android/gallery3d/filtershow/filters/SplineMath.java166
-rw-r--r--src/com/android/gallery3d/filtershow/filters/convolve3x3.rs67
-rw-r--r--src/com/android/gallery3d/filtershow/filters/grad.rs121
-rw-r--r--src/com/android/gallery3d/filtershow/filters/grey.rs22
-rw-r--r--src/com/android/gallery3d/filtershow/filters/saturation.rs161
-rw-r--r--src/com/android/gallery3d/filtershow/history/HistoryItem.java53
-rw-r--r--src/com/android/gallery3d/filtershow/history/HistoryManager.java172
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ControlPoint.java64
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java302
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/GeometryMathUtils.java416
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/GradControl.java274
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java307
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageCurves.java445
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java139
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageGrad.java215
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageMirror.java78
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java89
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageRedEye.java137
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java81
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageShow.java578
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java260
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java174
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java165
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/Line.java26
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/MasterImage.java581
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/Oval.java29
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/Spline.java450
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/Buffer.java74
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/CacheProcessing.java193
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java469
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java178
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/HighresRenderingRequestTask.java90
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java694
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java125
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java31
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java283
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java88
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java97
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/RenderingRequest.java174
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/RenderingRequestCaller.java21
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/RenderingRequestTask.java81
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/SharedBuffer.java77
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/SharedPreset.java42
-rw-r--r--src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java79
-rw-r--r--src/com/android/gallery3d/filtershow/presets/PresetManagementDialog.java69
-rw-r--r--src/com/android/gallery3d/filtershow/presets/UserPresetsAdapter.java171
-rw-r--r--src/com/android/gallery3d/filtershow/provider/SharedImageProvider.java137
-rw-r--r--src/com/android/gallery3d/filtershow/state/DragListener.java110
-rw-r--r--src/com/android/gallery3d/filtershow/state/PanelTrack.java37
-rw-r--r--src/com/android/gallery3d/filtershow/state/State.java78
-rw-r--r--src/com/android/gallery3d/filtershow/state/StateAdapter.java115
-rw-r--r--src/com/android/gallery3d/filtershow/state/StatePanel.java44
-rw-r--r--src/com/android/gallery3d/filtershow/state/StatePanelTrack.java351
-rw-r--r--src/com/android/gallery3d/filtershow/state/StateView.java291
-rw-r--r--src/com/android/gallery3d/filtershow/tools/IconFactory.java108
-rw-r--r--src/com/android/gallery3d/filtershow/tools/MatrixFit.java200
-rw-r--r--src/com/android/gallery3d/filtershow/tools/SaveImage.java632
-rw-r--r--src/com/android/gallery3d/filtershow/tools/XmpPresets.java133
-rw-r--r--src/com/android/gallery3d/filtershow/ui/ExportDialog.java90
-rw-r--r--src/com/android/gallery3d/filtershow/ui/FramedTextButton.java137
-rw-r--r--src/com/android/gallery3d/filtershow/ui/SelectionRenderer.java48
172 files changed, 27140 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/filtershow/CenteredLinearLayout.java b/src/com/android/gallery3d/filtershow/CenteredLinearLayout.java
new file mode 100644
index 000000000..bc9342d6f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/CenteredLinearLayout.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+
+public class CenteredLinearLayout extends LinearLayout {
+ private final int mMaxWidth;
+
+ public CenteredLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CenteredLinearLayout);
+ mMaxWidth = a.getDimensionPixelSize(R.styleable.CenteredLinearLayout_max_width, 0);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
+ Resources r = getContext().getResources();
+ float value = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, parentWidth,
+ r.getDisplayMetrics());
+ if (mMaxWidth > 0 && parentWidth > mMaxWidth) {
+ int measureMode = MeasureSpec.getMode(widthMeasureSpec);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, measureMode);
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/EditorPlaceHolder.java b/src/com/android/gallery3d/filtershow/EditorPlaceHolder.java
new file mode 100644
index 000000000..95abce114
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/EditorPlaceHolder.java
@@ -0,0 +1,82 @@
+package com.android.gallery3d.filtershow;
+
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.editors.Editor;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+
+import java.util.HashMap;
+import java.util.Vector;
+
+public class EditorPlaceHolder {
+ private static final String LOGTAG = "EditorPlaceHolder";
+
+ private FilterShowActivity mActivity = null;
+ private FrameLayout mContainer = null;
+ private HashMap<Integer, Editor> mEditors = new HashMap<Integer, Editor>();
+ private Vector<ImageShow> mOldViews = new Vector<ImageShow>();
+
+ public EditorPlaceHolder(FilterShowActivity activity) {
+ mActivity = activity;
+ }
+
+ public void setContainer(FrameLayout container) {
+ mContainer = container;
+ }
+
+ public void addEditor(Editor c) {
+ mEditors.put(c.getID(), c);
+ }
+
+ public boolean contains(int type) {
+ if (mEditors.get(type) != null) {
+ return true;
+ }
+ return false;
+ }
+
+ public Editor showEditor(int type) {
+ Editor editor = mEditors.get(type);
+ if (editor == null) {
+ return null;
+ }
+
+ editor.createEditor(mActivity, mContainer);
+ editor.getImageShow().bindAsImageLoadListener();
+ mContainer.setVisibility(View.VISIBLE);
+ mContainer.removeAllViews();
+ View eview = editor.getTopLevelView();
+ ViewParent parent = eview.getParent();
+
+ if (parent != null && parent instanceof FrameLayout) {
+ ((FrameLayout) parent).removeAllViews();
+ }
+
+ mContainer.addView(eview);
+ hideOldViews();
+ editor.setVisibility(View.VISIBLE);
+ return editor;
+ }
+
+ public void setOldViews(Vector<ImageShow> views) {
+ mOldViews = views;
+ }
+
+ public void hide() {
+ mContainer.setVisibility(View.GONE);
+ }
+
+ public void hideOldViews() {
+ for (View view : mOldViews) {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ public Editor getEditor(int editorId) {
+ return mEditors.get(editorId);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
new file mode 100644
index 000000000..4700fccfe
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -0,0 +1,1121 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewPropertyAnimator;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.FrameLayout;
+import android.widget.ShareActionProvider;
+import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.PhotoPage;
+import com.android.gallery3d.data.LocalAlbum;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.category.Action;
+import com.android.gallery3d.filtershow.category.CategoryAdapter;
+import com.android.gallery3d.filtershow.category.MainPanel;
+import com.android.gallery3d.filtershow.data.UserPresetsManager;
+import com.android.gallery3d.filtershow.editors.BasicEditor;
+import com.android.gallery3d.filtershow.editors.Editor;
+import com.android.gallery3d.filtershow.editors.EditorChanSat;
+import com.android.gallery3d.filtershow.editors.EditorCrop;
+import com.android.gallery3d.filtershow.editors.EditorDraw;
+import com.android.gallery3d.filtershow.editors.EditorGrad;
+import com.android.gallery3d.filtershow.editors.EditorManager;
+import com.android.gallery3d.filtershow.editors.EditorMirror;
+import com.android.gallery3d.filtershow.editors.EditorPanel;
+import com.android.gallery3d.filtershow.editors.EditorRedEye;
+import com.android.gallery3d.filtershow.editors.EditorRotate;
+import com.android.gallery3d.filtershow.editors.EditorStraighten;
+import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.history.HistoryItem;
+import com.android.gallery3d.filtershow.history.HistoryManager;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.imageshow.Spline;
+import com.android.gallery3d.filtershow.pipeline.CachingPipeline;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+import com.android.gallery3d.filtershow.pipeline.ProcessingService;
+import com.android.gallery3d.filtershow.presets.PresetManagementDialog;
+import com.android.gallery3d.filtershow.presets.UserPresetsAdapter;
+import com.android.gallery3d.filtershow.provider.SharedImageProvider;
+import com.android.gallery3d.filtershow.state.StateAdapter;
+import com.android.gallery3d.filtershow.tools.SaveImage;
+import com.android.gallery3d.filtershow.tools.XmpPresets;
+import com.android.gallery3d.filtershow.tools.XmpPresets.XMresults;
+import com.android.gallery3d.filtershow.ui.ExportDialog;
+import com.android.gallery3d.filtershow.ui.FramedTextButton;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.UsageStatistics;
+import com.android.photos.data.GalleryBitmapPool;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Vector;
+
+public class FilterShowActivity extends FragmentActivity implements OnItemClickListener,
+ OnShareTargetSelectedListener {
+
+ private String mAction = "";
+ MasterImage mMasterImage = null;
+
+ private static final long LIMIT_SUPPORTS_HIGHRES = 134217728; // 128Mb
+
+ public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET";
+ public static final String LAUNCH_FULLSCREEN = "launch-fullscreen";
+ private ImageShow mImageShow = null;
+
+ private View mSaveButton = null;
+
+ private EditorPlaceHolder mEditorPlaceHolder = new EditorPlaceHolder(this);
+
+ private static final int SELECT_PICTURE = 1;
+ private static final String LOGTAG = "FilterShowActivity";
+
+ private boolean mShowingTinyPlanet = false;
+ private boolean mShowingImageStatePanel = false;
+
+ private final Vector<ImageShow> mImageViews = new Vector<ImageShow>();
+
+ private ShareActionProvider mShareActionProvider;
+ private File mSharedOutputFile = null;
+
+ private boolean mSharingImage = false;
+
+ private WeakReference<ProgressDialog> mSavingProgressDialog;
+
+ private LoadBitmapTask mLoadBitmapTask;
+
+ private Uri mOriginalImageUri = null;
+ private ImagePreset mOriginalPreset = null;
+
+ private Uri mSelectedImageUri = null;
+
+ private UserPresetsManager mUserPresetsManager = null;
+ private UserPresetsAdapter mUserPresetsAdapter = null;
+ private CategoryAdapter mCategoryLooksAdapter = null;
+ private CategoryAdapter mCategoryBordersAdapter = null;
+ private CategoryAdapter mCategoryGeometryAdapter = null;
+ private CategoryAdapter mCategoryFiltersAdapter = null;
+ private int mCurrentPanel = MainPanel.LOOKS;
+
+ private ProcessingService mBoundService;
+ private boolean mIsBound = false;
+
+ public ProcessingService getProcessingService() {
+ return mBoundService;
+ }
+
+ public boolean isSimpleEditAction() {
+ return !PhotoPage.ACTION_NEXTGEN_EDIT.equalsIgnoreCase(mAction);
+ }
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ /*
+ * This is called when the connection with the service has been
+ * established, giving us the service object we can use to
+ * interact with the service. Because we have bound to a explicit
+ * service that we know is running in our own process, we can
+ * cast its IBinder to a concrete class and directly access it.
+ */
+ mBoundService = ((ProcessingService.LocalBinder)service).getService();
+ mBoundService.setFiltershowActivity(FilterShowActivity.this);
+ mBoundService.onStart();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ /*
+ * This is called when the connection with the service has been
+ * unexpectedly disconnected -- that is, its process crashed.
+ * Because it is running in our same process, we should never
+ * see this happen.
+ */
+ mBoundService = null;
+ }
+ };
+
+ void doBindService() {
+ /*
+ * Establish a connection with the service. We use an explicit
+ * class name because we want a specific service implementation that
+ * we know will be running in our own process (and thus won't be
+ * supporting component replacement by other applications).
+ */
+ bindService(new Intent(FilterShowActivity.this, ProcessingService.class),
+ mConnection, Context.BIND_AUTO_CREATE);
+ mIsBound = true;
+ }
+
+ void doUnbindService() {
+ if (mIsBound) {
+ // Detach our existing connection.
+ unbindService(mConnection);
+ mIsBound = false;
+ }
+ }
+
+ private void setupPipeline() {
+ doBindService();
+ ImageFilter.setActivityForMemoryToasts(this);
+ mUserPresetsManager = new UserPresetsManager(this);
+ mUserPresetsAdapter = new UserPresetsAdapter(this);
+ mCategoryLooksAdapter = new CategoryAdapter(this);
+ }
+
+ public void updateUIAfterServiceStarted() {
+ fillCategories();
+ loadMainPanel();
+ setDefaultPreset();
+ extractXMPData();
+ processIntent();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ boolean onlyUsePortrait = getResources().getBoolean(R.bool.only_use_portrait);
+ if (onlyUsePortrait) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+ MasterImage.setMaster(mMasterImage);
+
+ clearGalleryBitmapPool();
+ setupPipeline();
+
+ setupMasterImage();
+ setDefaultValues();
+ fillEditors();
+
+ loadXML();
+ UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_EDITOR, "Main");
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+ UsageStatistics.CATEGORY_LIFECYCLE, UsageStatistics.LIFECYCLE_START);
+ }
+
+ public boolean isShowingImageStatePanel() {
+ return mShowingImageStatePanel;
+ }
+
+ public void loadMainPanel() {
+ if (findViewById(R.id.main_panel_container) == null) {
+ return;
+ }
+ MainPanel panel = new MainPanel();
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
+ transaction.commit();
+ }
+
+ public void loadEditorPanel(FilterRepresentation representation,
+ final Editor currentEditor) {
+ if (representation.getEditorId() == ImageOnlyEditor.ID) {
+ currentEditor.reflectCurrentFilter();
+ return;
+ }
+ final int currentId = currentEditor.getID();
+ Runnable showEditor = new Runnable() {
+ @Override
+ public void run() {
+ EditorPanel panel = new EditorPanel();
+ panel.setEditor(currentId);
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.remove(getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG));
+ transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
+ transaction.commit();
+ }
+ };
+ Fragment main = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
+ boolean doAnimation = false;
+ if (mShowingImageStatePanel
+ && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ doAnimation = true;
+ }
+ if (doAnimation && main != null && main instanceof MainPanel) {
+ MainPanel mainPanel = (MainPanel) main;
+ View container = mainPanel.getView().findViewById(R.id.category_panel_container);
+ View bottom = mainPanel.getView().findViewById(R.id.bottom_panel);
+ int panelHeight = container.getHeight() + bottom.getHeight();
+ ViewPropertyAnimator anim = mainPanel.getView().animate();
+ anim.translationY(panelHeight).start();
+ final Handler handler = new Handler();
+ handler.postDelayed(showEditor, anim.getDuration());
+ } else {
+ showEditor.run();
+ }
+ }
+
+ private void loadXML() {
+ setContentView(R.layout.filtershow_activity);
+
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+ actionBar.setCustomView(R.layout.filtershow_actionbar);
+
+ mSaveButton = actionBar.getCustomView();
+ mSaveButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ saveImage();
+ }
+ });
+
+ mImageShow = (ImageShow) findViewById(R.id.imageShow);
+ mImageViews.add(mImageShow);
+
+ setupEditors();
+
+ mEditorPlaceHolder.hide();
+ mImageShow.bindAsImageLoadListener();
+
+ setupStatePanel();
+ }
+
+ public void fillCategories() {
+ fillLooks();
+ loadUserPresets();
+ fillBorders();
+ fillTools();
+ fillEffects();
+ }
+
+ public void setupStatePanel() {
+ MasterImage.getImage().setHistoryManager(mMasterImage.getHistory());
+ }
+
+ private void fillEffects() {
+ FiltersManager filtersManager = FiltersManager.getManager();
+ ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getEffects();
+ mCategoryFiltersAdapter = new CategoryAdapter(this);
+ for (FilterRepresentation representation : filtersRepresentations) {
+ if (representation.getTextId() != 0) {
+ representation.setName(getString(representation.getTextId()));
+ }
+ mCategoryFiltersAdapter.add(new Action(this, representation));
+ }
+ }
+
+ private void fillTools() {
+ FiltersManager filtersManager = FiltersManager.getManager();
+ ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getTools();
+ mCategoryGeometryAdapter = new CategoryAdapter(this);
+ for (FilterRepresentation representation : filtersRepresentations) {
+ mCategoryGeometryAdapter.add(new Action(this, representation));
+ }
+ }
+
+ private void processIntent() {
+ Intent intent = getIntent();
+ if (intent.getBooleanExtra(LAUNCH_FULLSCREEN, false)) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+
+ mAction = intent.getAction();
+ mSelectedImageUri = intent.getData();
+ Uri loadUri = mSelectedImageUri;
+ if (mOriginalImageUri != null) {
+ loadUri = mOriginalImageUri;
+ }
+ if (loadUri != null) {
+ startLoadBitmap(loadUri);
+ } else {
+ pickImage();
+ }
+ }
+
+ private void setupEditors() {
+ mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer));
+ EditorManager.addEditors(mEditorPlaceHolder);
+ mEditorPlaceHolder.setOldViews(mImageViews);
+ }
+
+ private void fillEditors() {
+ mEditorPlaceHolder.addEditor(new EditorChanSat());
+ mEditorPlaceHolder.addEditor(new EditorGrad());
+ mEditorPlaceHolder.addEditor(new EditorDraw());
+ mEditorPlaceHolder.addEditor(new BasicEditor());
+ mEditorPlaceHolder.addEditor(new ImageOnlyEditor());
+ mEditorPlaceHolder.addEditor(new EditorTinyPlanet());
+ mEditorPlaceHolder.addEditor(new EditorRedEye());
+ mEditorPlaceHolder.addEditor(new EditorCrop());
+ mEditorPlaceHolder.addEditor(new EditorMirror());
+ mEditorPlaceHolder.addEditor(new EditorRotate());
+ mEditorPlaceHolder.addEditor(new EditorStraighten());
+ }
+
+ private void setDefaultValues() {
+ Resources res = getResources();
+
+ // TODO: get those values from XML.
+ FramedTextButton.setTextSize((int) getPixelsFromDip(14));
+ FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
+ FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
+
+ Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
+ int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
+ Spline.setCurveHandle(curveHandle, curveHandleSize);
+ Spline.setCurveWidth((int) getPixelsFromDip(3));
+ }
+
+ private void startLoadBitmap(Uri uri) {
+ final View loading = findViewById(R.id.loading);
+ final View imageShow = findViewById(R.id.imageShow);
+ imageShow.setVisibility(View.INVISIBLE);
+ loading.setVisibility(View.VISIBLE);
+ mShowingTinyPlanet = false;
+ mLoadBitmapTask = new LoadBitmapTask();
+ mLoadBitmapTask.execute(uri);
+ }
+
+ private void fillBorders() {
+ FiltersManager filtersManager = FiltersManager.getManager();
+ ArrayList<FilterRepresentation> borders = filtersManager.getBorders();
+
+ for (int i = 0; i < borders.size(); i++) {
+ FilterRepresentation filter = borders.get(i);
+ filter.setName(getString(R.string.borders));
+ if (i == 0) {
+ filter.setName(getString(R.string.none));
+ }
+ }
+
+ mCategoryBordersAdapter = new CategoryAdapter(this);
+ for (FilterRepresentation representation : borders) {
+ if (representation.getTextId() != 0) {
+ representation.setName(getString(representation.getTextId()));
+ }
+ mCategoryBordersAdapter.add(new Action(this, representation, Action.FULL_VIEW));
+ }
+ }
+
+ public UserPresetsAdapter getUserPresetsAdapter() {
+ return mUserPresetsAdapter;
+ }
+
+ public CategoryAdapter getCategoryLooksAdapter() {
+ return mCategoryLooksAdapter;
+ }
+
+ public CategoryAdapter getCategoryBordersAdapter() {
+ return mCategoryBordersAdapter;
+ }
+
+ public CategoryAdapter getCategoryGeometryAdapter() {
+ return mCategoryGeometryAdapter;
+ }
+
+ public CategoryAdapter getCategoryFiltersAdapter() {
+ return mCategoryFiltersAdapter;
+ }
+
+ public void removeFilterRepresentation(FilterRepresentation filterRepresentation) {
+ if (filterRepresentation == null) {
+ return;
+ }
+ ImagePreset oldPreset = MasterImage.getImage().getPreset();
+ ImagePreset copy = new ImagePreset(oldPreset);
+ copy.removeFilter(filterRepresentation);
+ MasterImage.getImage().setPreset(copy, copy.getLastRepresentation(), true);
+ if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
+ FilterRepresentation lastRepresentation = copy.getLastRepresentation();
+ MasterImage.getImage().setCurrentFilterRepresentation(lastRepresentation);
+ }
+ }
+
+ public void useFilterRepresentation(FilterRepresentation filterRepresentation) {
+ if (filterRepresentation == null) {
+ return;
+ }
+ if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
+ return;
+ }
+ ImagePreset oldPreset = MasterImage.getImage().getPreset();
+ ImagePreset copy = new ImagePreset(oldPreset);
+ FilterRepresentation representation = copy.getRepresentation(filterRepresentation);
+ if (representation == null) {
+ copy.addFilter(filterRepresentation);
+ } else if (filterRepresentation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY) {
+ filterRepresentation = representation;
+ } else {
+ if (filterRepresentation.allowsSingleInstanceOnly()) {
+ // Don't just update the filter representation. Centralize the
+ // logic in the addFilter(), such that we can keep "None" as
+ // null.
+ copy.removeFilter(representation);
+ copy.addFilter(filterRepresentation);
+ }
+ }
+ MasterImage.getImage().setPreset(copy, filterRepresentation, true);
+ MasterImage.getImage().setCurrentFilterRepresentation(filterRepresentation);
+ }
+
+ public void showRepresentation(FilterRepresentation representation) {
+ if (representation == null) {
+ return;
+ }
+
+ useFilterRepresentation(representation);
+
+ // show representation
+ Editor mCurrentEditor = mEditorPlaceHolder.showEditor(representation.getEditorId());
+ loadEditorPanel(representation, mCurrentEditor);
+ }
+
+ public Editor getEditor(int editorID) {
+ return mEditorPlaceHolder.getEditor(editorID);
+ }
+
+ public void setCurrentPanel(int currentPanel) {
+ mCurrentPanel = currentPanel;
+ }
+
+ public int getCurrentPanel() {
+ return mCurrentPanel;
+ }
+
+ public void updateCategories() {
+ ImagePreset preset = mMasterImage.getPreset();
+ mCategoryLooksAdapter.reflectImagePreset(preset);
+ mCategoryBordersAdapter.reflectImagePreset(preset);
+ }
+
+ private class LoadHighresBitmapTask extends AsyncTask<Void, Void, Boolean> {
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ MasterImage master = MasterImage.getImage();
+ Rect originalBounds = master.getOriginalBounds();
+ if (master.supportsHighRes()) {
+ int highresPreviewSize = master.getOriginalBitmapLarge().getWidth() * 2;
+ if (highresPreviewSize > originalBounds.width()) {
+ highresPreviewSize = originalBounds.width();
+ }
+ Rect bounds = new Rect();
+ Bitmap originalHires = ImageLoader.loadOrientedConstrainedBitmap(master.getUri(),
+ master.getActivity(), highresPreviewSize,
+ master.getOrientation(), bounds);
+ master.setOriginalBounds(bounds);
+ master.setOriginalBitmapHighres(originalHires);
+ mBoundService.setOriginalBitmapHighres(originalHires);
+ master.warnListeners();
+ }
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ Bitmap highresBitmap = MasterImage.getImage().getOriginalBitmapHighres();
+ if (highresBitmap != null) {
+ float highResPreviewScale = (float) highresBitmap.getWidth()
+ / (float) MasterImage.getImage().getOriginalBounds().width();
+ mBoundService.setHighresPreviewScaleFactor(highResPreviewScale);
+ }
+ }
+ }
+
+ private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> {
+ int mBitmapSize;
+
+ public LoadBitmapTask() {
+ mBitmapSize = getScreenImageSize();
+ }
+
+ @Override
+ protected Boolean doInBackground(Uri... params) {
+ if (!MasterImage.getImage().loadBitmap(params[0], mBitmapSize)) {
+ return false;
+ }
+ publishProgress(ImageLoader.queryLightCycle360(MasterImage.getImage().getActivity()));
+ return true;
+ }
+
+ @Override
+ protected void onProgressUpdate(Boolean... values) {
+ super.onProgressUpdate(values);
+ if (isCancelled()) {
+ return;
+ }
+ if (values[0]) {
+ mShowingTinyPlanet = true;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ MasterImage.setMaster(mMasterImage);
+ if (isCancelled()) {
+ return;
+ }
+
+ if (!result) {
+ cannotLoadImage();
+ }
+
+ if (null == CachingPipeline.getRenderScriptContext()){
+ Log.v(LOGTAG,"RenderScript context destroyed during load");
+ return;
+ }
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.GONE);
+ final View imageShow = findViewById(R.id.imageShow);
+ imageShow.setVisibility(View.VISIBLE);
+
+ Bitmap largeBitmap = MasterImage.getImage().getOriginalBitmapLarge();
+ mBoundService.setOriginalBitmap(largeBitmap);
+
+ float previewScale = (float) largeBitmap.getWidth()
+ / (float) MasterImage.getImage().getOriginalBounds().width();
+ mBoundService.setPreviewScaleFactor(previewScale);
+ if (!mShowingTinyPlanet) {
+ mCategoryFiltersAdapter.removeTinyPlanet();
+ }
+ mCategoryLooksAdapter.imageLoaded();
+ mCategoryBordersAdapter.imageLoaded();
+ mCategoryGeometryAdapter.imageLoaded();
+ mCategoryFiltersAdapter.imageLoaded();
+ mLoadBitmapTask = null;
+
+ if (mOriginalPreset != null) {
+ MasterImage.getImage().setLoadedPreset(mOriginalPreset);
+ MasterImage.getImage().setPreset(mOriginalPreset,
+ mOriginalPreset.getLastRepresentation(), true);
+ mOriginalPreset = null;
+ }
+
+ if (mAction == TINY_PLANET_ACTION) {
+ showRepresentation(mCategoryFiltersAdapter.getTinyPlanet());
+ }
+ LoadHighresBitmapTask highresLoad = new LoadHighresBitmapTask();
+ highresLoad.execute();
+ super.onPostExecute(result);
+ }
+
+ }
+
+ private void clearGalleryBitmapPool() {
+ (new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ // Free memory held in Gallery's Bitmap pool. May be O(n) for n bitmaps.
+ GalleryBitmapPool.getInstance().clear();
+ return null;
+ }
+ }).execute();
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mLoadBitmapTask != null) {
+ mLoadBitmapTask.cancel(false);
+ }
+ mUserPresetsManager.close();
+ doUnbindService();
+ super.onDestroy();
+ }
+
+ // TODO: find a more robust way of handling image size selection
+ // for high screen densities.
+ private int getScreenImageSize() {
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
+ return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
+ }
+
+ private void showSavingProgress(String albumName) {
+ ProgressDialog progress;
+ if (mSavingProgressDialog != null) {
+ progress = mSavingProgressDialog.get();
+ if (progress != null) {
+ progress.show();
+ return;
+ }
+ }
+ // TODO: Allow cancellation of the saving process
+ String progressText;
+ if (albumName == null) {
+ progressText = getString(R.string.saving_image);
+ } else {
+ progressText = getString(R.string.filtershow_saving_image, albumName);
+ }
+ progress = ProgressDialog.show(this, "", progressText, true, false);
+ mSavingProgressDialog = new WeakReference<ProgressDialog>(progress);
+ }
+
+ private void hideSavingProgress() {
+ if (mSavingProgressDialog != null) {
+ ProgressDialog progress = mSavingProgressDialog.get();
+ if (progress != null)
+ progress.dismiss();
+ }
+ }
+
+ public void completeSaveImage(Uri saveUri) {
+ if (mSharingImage && mSharedOutputFile != null) {
+ // Image saved, we unblock the content provider
+ Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
+ Uri.encode(mSharedOutputFile.getAbsolutePath()));
+ ContentValues values = new ContentValues();
+ values.put(SharedImageProvider.PREPARE, false);
+ getContentResolver().insert(uri, values);
+ }
+ setResult(RESULT_OK, new Intent().setData(saveUri));
+ hideSavingProgress();
+ finish();
+ }
+
+ @Override
+ public boolean onShareTargetSelected(ShareActionProvider arg0, Intent arg1) {
+ // First, let's tell the SharedImageProvider that it will need to wait
+ // for the image
+ Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
+ Uri.encode(mSharedOutputFile.getAbsolutePath()));
+ ContentValues values = new ContentValues();
+ values.put(SharedImageProvider.PREPARE, true);
+ getContentResolver().insert(uri, values);
+ mSharingImage = true;
+
+ // Process and save the image in the background.
+ showSavingProgress(null);
+ mImageShow.saveImage(this, mSharedOutputFile);
+ return true;
+ }
+
+ private Intent getDefaultShareIntent() {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setType(SharedImageProvider.MIME_TYPE);
+ mSharedOutputFile = SaveImage.getNewFile(this, MasterImage.getImage().getUri());
+ Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
+ Uri.encode(mSharedOutputFile.getAbsolutePath()));
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+ return intent;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
+ MenuItem showState = menu.findItem(R.id.showImageStateButton);
+ if (mShowingImageStatePanel) {
+ showState.setTitle(R.string.hide_imagestate_panel);
+ } else {
+ showState.setTitle(R.string.show_imagestate_panel);
+ }
+ mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share)
+ .getActionProvider();
+ mShareActionProvider.setShareIntent(getDefaultShareIntent());
+ mShareActionProvider.setOnShareTargetSelectedListener(this);
+
+ MenuItem undoItem = menu.findItem(R.id.undoButton);
+ MenuItem redoItem = menu.findItem(R.id.redoButton);
+ MenuItem resetItem = menu.findItem(R.id.resetHistoryButton);
+ mMasterImage.getHistory().setMenuItems(undoItem, redoItem, resetItem);
+ return true;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mShareActionProvider != null) {
+ mShareActionProvider.setOnShareTargetSelectedListener(null);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mShareActionProvider != null) {
+ mShareActionProvider.setOnShareTargetSelectedListener(this);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.undoButton: {
+ HistoryManager adapter = mMasterImage.getHistory();
+ int position = adapter.undo();
+ mMasterImage.onHistoryItemClick(position);
+ backToMain();
+ invalidateViews();
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+ UsageStatistics.CATEGORY_BUTTON_PRESS, "Undo");
+ return true;
+ }
+ case R.id.redoButton: {
+ HistoryManager adapter = mMasterImage.getHistory();
+ int position = adapter.redo();
+ mMasterImage.onHistoryItemClick(position);
+ invalidateViews();
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+ UsageStatistics.CATEGORY_BUTTON_PRESS, "Redo");
+ return true;
+ }
+ case R.id.resetHistoryButton: {
+ resetHistory();
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+ UsageStatistics.CATEGORY_BUTTON_PRESS, "ResetHistory");
+ return true;
+ }
+ case R.id.showImageStateButton: {
+ toggleImageStatePanel();
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+ UsageStatistics.CATEGORY_BUTTON_PRESS,
+ mShowingImageStatePanel ? "ShowPanel" : "HidePanel");
+ return true;
+ }
+ case R.id.exportFlattenButton: {
+ showExportOptionsDialog();
+ return true;
+ }
+ case android.R.id.home: {
+ saveImage();
+ return true;
+ }
+ case R.id.manageUserPresets: {
+ manageUserPresets();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void manageUserPresets() {
+ DialogFragment dialog = new PresetManagementDialog();
+ dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
+ }
+
+ private void showExportOptionsDialog() {
+ DialogFragment dialog = new ExportDialog();
+ dialog.show(getSupportFragmentManager(), "ExportDialogFragment");
+ }
+
+ public void updateUserPresetsFromAdapter(UserPresetsAdapter adapter) {
+ ArrayList<FilterUserPresetRepresentation> representations =
+ adapter.getDeletedRepresentations();
+ for (FilterUserPresetRepresentation representation : representations) {
+ deletePreset(representation.getId());
+ }
+ ArrayList<FilterUserPresetRepresentation> changedRepresentations =
+ adapter.getChangedRepresentations();
+ for (FilterUserPresetRepresentation representation : changedRepresentations) {
+ updatePreset(representation);
+ }
+ adapter.clearDeletedRepresentations();
+ adapter.clearChangedRepresentations();
+ loadUserPresets();
+ }
+
+ public void loadUserPresets() {
+ mUserPresetsManager.load();
+ }
+
+ public void updateUserPresetsFromManager() {
+ ArrayList<FilterUserPresetRepresentation> presets = mUserPresetsManager.getRepresentations();
+ if (presets == null) {
+ return;
+ }
+ if (mCategoryLooksAdapter != null) {
+ fillLooks();
+ }
+ mUserPresetsAdapter.clear();
+ for (int i = 0; i < presets.size(); i++) {
+ FilterUserPresetRepresentation representation = presets.get(i);
+ mCategoryLooksAdapter.add(
+ new Action(this, representation, Action.FULL_VIEW));
+ mUserPresetsAdapter.add(new Action(this, representation, Action.FULL_VIEW));
+ }
+ mCategoryLooksAdapter.notifyDataSetInvalidated();
+
+ }
+
+ public void saveCurrentImagePreset() {
+ mUserPresetsManager.save(MasterImage.getImage().getPreset());
+ }
+
+ private void deletePreset(int id) {
+ mUserPresetsManager.delete(id);
+ }
+
+ private void updatePreset(FilterUserPresetRepresentation representation) {
+ mUserPresetsManager.update(representation);
+ }
+
+ public void enableSave(boolean enable) {
+ if (mSaveButton != null) {
+ mSaveButton.setEnabled(enable);
+ }
+ }
+
+ private void fillLooks() {
+ FiltersManager filtersManager = FiltersManager.getManager();
+ ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getLooks();
+
+ mCategoryLooksAdapter.clear();
+ int verticalItemHeight = (int) getResources().getDimension(R.dimen.action_item_height);
+ mCategoryLooksAdapter.setItemHeight(verticalItemHeight);
+ for (FilterRepresentation representation : filtersRepresentations) {
+ mCategoryLooksAdapter.add(new Action(this, representation, Action.FULL_VIEW));
+ }
+ }
+
+ public void setDefaultPreset() {
+ // Default preset (original)
+ ImagePreset preset = new ImagePreset(); // empty
+ mMasterImage.setPreset(preset, preset.getLastRepresentation(), true);
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////
+ // Some utility functions
+ // TODO: finish the cleanup.
+
+ public void invalidateViews() {
+ for (ImageShow views : mImageViews) {
+ views.updateImage();
+ }
+ }
+
+ public void hideImageViews() {
+ for (View view : mImageViews) {
+ view.setVisibility(View.GONE);
+ }
+ mEditorPlaceHolder.hide();
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////
+ // imageState panel...
+
+ public void toggleImageStatePanel() {
+ invalidateOptionsMenu();
+ mShowingImageStatePanel = !mShowingImageStatePanel;
+ Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
+ if (panel != null) {
+ if (panel instanceof EditorPanel) {
+ EditorPanel editorPanel = (EditorPanel) panel;
+ editorPanel.showImageStatePanel(mShowingImageStatePanel);
+ } else if (panel instanceof MainPanel) {
+ MainPanel mainPanel = (MainPanel) panel;
+ mainPanel.showImageStatePanel(mShowingImageStatePanel);
+ }
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig)
+ {
+ super.onConfigurationChanged(newConfig);
+ setDefaultValues();
+ loadXML();
+ fillCategories();
+ loadMainPanel();
+
+ // mLoadBitmapTask==null implies you have looked at the intent
+ if (!mShowingTinyPlanet && (mLoadBitmapTask == null)) {
+ mCategoryFiltersAdapter.removeTinyPlanet();
+ }
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.GONE);
+ }
+
+ public void setupMasterImage() {
+
+ HistoryManager historyManager = new HistoryManager();
+ StateAdapter imageStateAdapter = new StateAdapter(this, 0);
+ MasterImage.reset();
+ mMasterImage = MasterImage.getImage();
+ mMasterImage.setHistoryManager(historyManager);
+ mMasterImage.setStateAdapter(imageStateAdapter);
+ mMasterImage.setActivity(this);
+
+ if (Runtime.getRuntime().maxMemory() > LIMIT_SUPPORTS_HIGHRES) {
+ mMasterImage.setSupportsHighRes(true);
+ } else {
+ mMasterImage.setSupportsHighRes(false);
+ }
+ }
+
+ void resetHistory() {
+ HistoryManager adapter = mMasterImage.getHistory();
+ adapter.reset();
+ HistoryItem historyItem = adapter.getItem(0);
+ ImagePreset original = new ImagePreset(historyItem.getImagePreset());
+ mMasterImage.setPreset(original, historyItem.getFilterRepresentation(), true);
+ invalidateViews();
+ backToMain();
+ }
+
+ public void showDefaultImageView() {
+ mEditorPlaceHolder.hide();
+ mImageShow.setVisibility(View.VISIBLE);
+ MasterImage.getImage().setCurrentFilter(null);
+ MasterImage.getImage().setCurrentFilterRepresentation(null);
+ }
+
+ public void backToMain() {
+ Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
+ if (currentPanel instanceof MainPanel) {
+ return;
+ }
+ loadMainPanel();
+ showDefaultImageView();
+ }
+
+ @Override
+ public void onBackPressed() {
+ Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
+ if (currentPanel instanceof MainPanel) {
+ if (!mImageShow.hasModifications()) {
+ done();
+ } else {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit);
+ builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ saveImage();
+ }
+ });
+ builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ done();
+ }
+ });
+ builder.show();
+ }
+ } else {
+ backToMain();
+ }
+ }
+
+ public void cannotLoadImage() {
+ Toast.makeText(this, R.string.cannot_load_image, Toast.LENGTH_SHORT).show();
+ finish();
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////
+
+ public float getPixelsFromDip(float value) {
+ Resources r = getResources();
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
+ r.getDisplayMetrics());
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ mMasterImage.onHistoryItemClick(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);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == RESULT_OK) {
+ if (requestCode == SELECT_PICTURE) {
+ Uri selectedImageUri = data.getData();
+ startLoadBitmap(selectedImageUri);
+ }
+ }
+ }
+
+
+ public void saveImage() {
+ if (mImageShow.hasModifications()) {
+ // Get the name of the album, to which the image will be saved
+ File saveDir = SaveImage.getFinalSaveDirectory(this, mSelectedImageUri);
+ int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
+ String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
+ showSavingProgress(albumName);
+ mImageShow.saveImage(this, null);
+ } else {
+ done();
+ }
+ }
+
+
+ public void done() {
+ hideSavingProgress();
+ if (mLoadBitmapTask != null) {
+ mLoadBitmapTask.cancel(false);
+ }
+ finish();
+ }
+
+ private void extractXMPData() {
+ XMresults res = XmpPresets.extractXMPData(
+ getBaseContext(), mMasterImage, getIntent().getData());
+ if (res == null)
+ return;
+
+ mOriginalImageUri = res.originalimage;
+ mOriginalPreset = res.preset;
+ }
+
+ public Uri getSelectedImageUri() {
+ return mSelectedImageUri;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
new file mode 100644
index 000000000..b6c72fd9d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.cache;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.util.XmpUtilHelper;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public final class ImageLoader {
+
+ private static final String LOGTAG = "ImageLoader";
+
+ public static final String JPEG_MIME_TYPE = "image/jpeg";
+ public static final int DEFAULT_COMPRESS_QUALITY = 95;
+
+ public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT;
+ public static final int ORI_ROTATE_90 = ExifInterface.Orientation.RIGHT_TOP;
+ public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT;
+ public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM;
+ public static final int ORI_FLIP_HOR = ExifInterface.Orientation.TOP_RIGHT;
+ public static final int ORI_FLIP_VERT = ExifInterface.Orientation.BOTTOM_RIGHT;
+ public static final int ORI_TRANSPOSE = ExifInterface.Orientation.LEFT_TOP;
+ public static final int ORI_TRANSVERSE = ExifInterface.Orientation.LEFT_BOTTOM;
+
+ private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
+
+ private ImageLoader() {}
+
+ /**
+ * Returns the Mime type for a Url. Safe to use with Urls that do not
+ * come from Gallery's content provider.
+ */
+ public static String getMimeType(Uri src) {
+ String postfix = MimeTypeMap.getFileExtensionFromUrl(src.toString());
+ String ret = null;
+ if (postfix != null) {
+ ret = MimeTypeMap.getSingleton().getMimeTypeFromExtension(postfix);
+ }
+ return ret;
+ }
+
+ /**
+ * Returns the image's orientation flag. Defaults to ORI_NORMAL if no valid
+ * orientation was found.
+ */
+ public static int getMetadataOrientation(Context context, Uri uri) {
+ if (uri == null || context == null) {
+ throw new IllegalArgumentException("bad argument to getOrientation");
+ }
+
+ // First try to find orientation data in Gallery's ContentProvider.
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(uri,
+ new String[] { MediaStore.Images.ImageColumns.ORIENTATION },
+ null, null, null);
+ if (cursor != null && cursor.moveToNext()) {
+ int ori = cursor.getInt(0);
+ switch (ori) {
+ case 90:
+ return ORI_ROTATE_90;
+ case 270:
+ return ORI_ROTATE_270;
+ case 180:
+ return ORI_ROTATE_180;
+ default:
+ return ORI_NORMAL;
+ }
+ }
+ } catch (SQLiteException e) {
+ // Do nothing
+ } catch (IllegalArgumentException e) {
+ // Do nothing
+ } finally {
+ Utils.closeSilently(cursor);
+ }
+
+ // Fall back to checking EXIF tags in file.
+ if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+ String mimeType = getMimeType(uri);
+ if (!JPEG_MIME_TYPE.equals(mimeType)) {
+ return ORI_NORMAL;
+ }
+ String path = uri.getPath();
+ ExifInterface exif = new ExifInterface();
+ try {
+ exif.readExif(path);
+ Integer tagval = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+ if (tagval != null) {
+ int orientation = tagval;
+ switch(orientation) {
+ case ORI_NORMAL:
+ case ORI_ROTATE_90:
+ case ORI_ROTATE_180:
+ case ORI_ROTATE_270:
+ case ORI_FLIP_HOR:
+ case ORI_FLIP_VERT:
+ case ORI_TRANSPOSE:
+ case ORI_TRANSVERSE:
+ return orientation;
+ default:
+ return ORI_NORMAL;
+ }
+ }
+ } catch (IOException e) {
+ Log.w(LOGTAG, "Failed to read EXIF orientation", e);
+ }
+ }
+ return ORI_NORMAL;
+ }
+
+ /**
+ * Returns the rotation of image at the given URI as one of 0, 90, 180,
+ * 270. Defaults to 0.
+ */
+ public static int getMetadataRotation(Context context, Uri uri) {
+ int orientation = getMetadataOrientation(context, uri);
+ switch(orientation) {
+ case ORI_ROTATE_90:
+ return 90;
+ case ORI_ROTATE_180:
+ return 180;
+ case ORI_ROTATE_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Takes an orientation and a bitmap, and returns the bitmap transformed
+ * to that orientation.
+ */
+ public static Bitmap orientBitmap(Bitmap bitmap, int ori) {
+ Matrix matrix = new Matrix();
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ if (ori == ORI_ROTATE_90 ||
+ ori == ORI_ROTATE_270 ||
+ ori == ORI_TRANSPOSE ||
+ ori == ORI_TRANSVERSE) {
+ int tmp = w;
+ w = h;
+ h = tmp;
+ }
+ switch (ori) {
+ case ORI_ROTATE_90:
+ matrix.setRotate(90, w / 2f, h / 2f);
+ break;
+ case ORI_ROTATE_180:
+ matrix.setRotate(180, w / 2f, h / 2f);
+ break;
+ case ORI_ROTATE_270:
+ matrix.setRotate(270, w / 2f, h / 2f);
+ break;
+ case ORI_FLIP_HOR:
+ matrix.preScale(-1, 1);
+ break;
+ case ORI_FLIP_VERT:
+ matrix.preScale(1, -1);
+ break;
+ case ORI_TRANSPOSE:
+ matrix.setRotate(90, w / 2f, h / 2f);
+ matrix.preScale(1, -1);
+ break;
+ case ORI_TRANSVERSE:
+ matrix.setRotate(270, w / 2f, h / 2f);
+ matrix.preScale(1, -1);
+ break;
+ case ORI_NORMAL:
+ default:
+ return bitmap;
+ }
+ return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
+ bitmap.getHeight(), matrix, true);
+ }
+
+ /**
+ * Returns the bitmap for the rectangular region given by "bounds"
+ * if it is a subset of the bitmap stored at uri. Otherwise returns
+ * null.
+ */
+ public static Bitmap loadRegionBitmap(Context context, Uri uri, BitmapFactory.Options options,
+ Rect bounds) {
+ InputStream is = null;
+ try {
+ is = context.getContentResolver().openInputStream(uri);
+ BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+ Rect r = new Rect(0, 0, decoder.getWidth(), decoder.getHeight());
+ // return null if bounds are not entirely within the bitmap
+ if (!r.contains(bounds)) {
+ return null;
+ }
+ return decoder.decodeRegion(bounds, options);
+ } catch (FileNotFoundException e) {
+ Log.e(LOGTAG, "FileNotFoundException for " + uri, e);
+ } catch (IOException e) {
+ Log.e(LOGTAG, "FileNotFoundException for " + uri, e);
+ } finally {
+ Utils.closeSilently(is);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the bounds of the bitmap stored at a given Url.
+ */
+ public static Rect loadBitmapBounds(Context context, Uri uri) {
+ BitmapFactory.Options o = new BitmapFactory.Options();
+ loadBitmap(context, uri, o);
+ return new Rect(0, 0, o.outWidth, o.outHeight);
+ }
+
+ /**
+ * Loads a bitmap that has been downsampled using sampleSize from a given url.
+ */
+ public static Bitmap loadDownsampledBitmap(Context context, Uri uri, int sampleSize) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+ options.inSampleSize = sampleSize;
+ return loadBitmap(context, uri, options);
+ }
+
+
+ /**
+ * Returns the bitmap from the given uri loaded using the given options.
+ * Returns null on failure.
+ */
+ public static Bitmap loadBitmap(Context context, Uri uri, BitmapFactory.Options o) {
+ if (uri == null || context == null) {
+ throw new IllegalArgumentException("bad argument to loadBitmap");
+ }
+ InputStream is = null;
+ try {
+ is = context.getContentResolver().openInputStream(uri);
+ return BitmapFactory.decodeStream(is, null, o);
+ } catch (FileNotFoundException e) {
+ Log.e(LOGTAG, "FileNotFoundException for " + uri, e);
+ } finally {
+ Utils.closeSilently(is);
+ }
+ return null;
+ }
+
+ /**
+ * Loads a bitmap at a given URI that is downsampled so that both sides are
+ * smaller than maxSideLength. The Bitmap's original dimensions are stored
+ * in the rect originalBounds.
+ *
+ * @param uri URI of image to open.
+ * @param context context whose ContentResolver to use.
+ * @param maxSideLength max side length of returned bitmap.
+ * @param originalBounds If not null, set to the actual bounds of the stored bitmap.
+ * @param useMin use min or max side of the original image
+ * @return downsampled bitmap or null if this operation failed.
+ */
+ public static Bitmap loadConstrainedBitmap(Uri uri, Context context, int maxSideLength,
+ Rect originalBounds, boolean useMin) {
+ if (maxSideLength <= 0 || uri == null || context == null) {
+ throw new IllegalArgumentException("bad argument to getScaledBitmap");
+ }
+ // Get width and height of stored bitmap
+ Rect storedBounds = loadBitmapBounds(context, uri);
+ if (originalBounds != null) {
+ originalBounds.set(storedBounds);
+ }
+ int w = storedBounds.width();
+ int h = storedBounds.height();
+
+ // If bitmap cannot be decoded, return null
+ if (w <= 0 || h <= 0) {
+ return null;
+ }
+
+ // Find best downsampling size
+ int imageSide = 0;
+ if (useMin) {
+ imageSide = Math.min(w, h);
+ } else {
+ imageSide = Math.max(w, h);
+ }
+ int sampleSize = 1;
+ while (imageSide > maxSideLength) {
+ imageSide >>>= 1;
+ sampleSize <<= 1;
+ }
+
+ // Make sure sample size is reasonable
+ if (sampleSize <= 0 ||
+ 0 >= (int) (Math.min(w, h) / sampleSize)) {
+ return null;
+ }
+ return loadDownsampledBitmap(context, uri, sampleSize);
+ }
+
+ /**
+ * Loads a bitmap at a given URI that is downsampled so that both sides are
+ * smaller than maxSideLength. The Bitmap's original dimensions are stored
+ * in the rect originalBounds. The output is also transformed to the given
+ * orientation.
+ *
+ * @param uri URI of image to open.
+ * @param context context whose ContentResolver to use.
+ * @param maxSideLength max side length of returned bitmap.
+ * @param orientation the orientation to transform the bitmap to.
+ * @param originalBounds set to the actual bounds of the stored bitmap.
+ * @return downsampled bitmap or null if this operation failed.
+ */
+ public static Bitmap loadOrientedConstrainedBitmap(Uri uri, Context context, int maxSideLength,
+ int orientation, Rect originalBounds) {
+ Bitmap bmap = loadConstrainedBitmap(uri, context, maxSideLength, originalBounds, false);
+ if (bmap != null) {
+ bmap = orientBitmap(bmap, orientation);
+ }
+ return bmap;
+ }
+
+ public static Bitmap getScaleOneImageForPreset(Context context, Uri uri, Rect bounds,
+ Rect destination) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+ if (destination != null) {
+ if (bounds.width() > destination.width()) {
+ int sampleSize = 1;
+ int w = bounds.width();
+ while (w > destination.width()) {
+ sampleSize *= 2;
+ w /= sampleSize;
+ }
+ options.inSampleSize = sampleSize;
+ }
+ }
+ Bitmap bmp = loadRegionBitmap(context, uri, options, bounds);
+ return bmp;
+ }
+
+ /**
+ * Loads a bitmap that is downsampled by at least the input sample size. In
+ * low-memory situations, the bitmap may be downsampled further.
+ */
+ public static Bitmap loadBitmapWithBackouts(Context context, Uri sourceUri, int sampleSize) {
+ boolean noBitmap = true;
+ int num_tries = 0;
+ if (sampleSize <= 0) {
+ sampleSize = 1;
+ }
+ Bitmap bmap = null;
+ while (noBitmap) {
+ try {
+ // Try to decode, downsample if low-memory.
+ bmap = loadDownsampledBitmap(context, sourceUri, sampleSize);
+ noBitmap = false;
+ } catch (java.lang.OutOfMemoryError e) {
+ // Try with more downsampling before failing for good.
+ if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
+ throw e;
+ }
+ bmap = null;
+ System.gc();
+ sampleSize *= 2;
+ }
+ }
+ return bmap;
+ }
+
+ /**
+ * Loads an oriented bitmap that is downsampled by at least the input sample
+ * size. In low-memory situations, the bitmap may be downsampled further.
+ */
+ public static Bitmap loadOrientedBitmapWithBackouts(Context context, Uri sourceUri,
+ int sampleSize) {
+ Bitmap bitmap = loadBitmapWithBackouts(context, sourceUri, sampleSize);
+ if (bitmap == null) {
+ return null;
+ }
+ int orientation = getMetadataOrientation(context, sourceUri);
+ bitmap = orientBitmap(bitmap, orientation);
+ return bitmap;
+ }
+
+ /**
+ * Loads bitmap from a resource that may be downsampled in low-memory situations.
+ */
+ public static Bitmap decodeResourceWithBackouts(Resources res, BitmapFactory.Options options,
+ int id) {
+ boolean noBitmap = true;
+ int num_tries = 0;
+ if (options.inSampleSize < 1) {
+ options.inSampleSize = 1;
+ }
+ // Stopgap fix for low-memory devices.
+ Bitmap bmap = null;
+ while (noBitmap) {
+ try {
+ // Try to decode, downsample if low-memory.
+ bmap = BitmapFactory.decodeResource(
+ res, id, options);
+ noBitmap = false;
+ } catch (java.lang.OutOfMemoryError e) {
+ // Retry before failing for good.
+ if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
+ throw e;
+ }
+ bmap = null;
+ System.gc();
+ options.inSampleSize *= 2;
+ }
+ }
+ return bmap;
+ }
+
+ public static XMPMeta getXmpObject(Context context) {
+ try {
+ InputStream is = context.getContentResolver().openInputStream(
+ MasterImage.getImage().getUri());
+ return XmpUtilHelper.extractXMPMeta(is);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Determine if this is a light cycle 360 image
+ *
+ * @return true if it is a light Cycle image that is full 360
+ */
+ public static boolean queryLightCycle360(Context context) {
+ InputStream is = null;
+ try {
+ is = context.getContentResolver().openInputStream(MasterImage.getImage().getUri());
+ XMPMeta meta = XmpUtilHelper.extractXMPMeta(is);
+ if (meta == null) {
+ return false;
+ }
+ String namespace = "http://ns.google.com/photos/1.0/panorama/";
+ String cropWidthName = "GPano:CroppedAreaImageWidthPixels";
+ String fullWidthName = "GPano:FullPanoWidthPixels";
+
+ if (!meta.doesPropertyExist(namespace, cropWidthName)) {
+ return false;
+ }
+ if (!meta.doesPropertyExist(namespace, fullWidthName)) {
+ return false;
+ }
+
+ Integer cropValue = meta.getPropertyInteger(namespace, cropWidthName);
+ Integer fullValue = meta.getPropertyInteger(namespace, fullWidthName);
+
+ // Definition of a 360:
+ // GFullPanoWidthPixels == CroppedAreaImageWidthPixels
+ if (cropValue != null && fullValue != null) {
+ return cropValue.equals(fullValue);
+ }
+
+ return false;
+ } catch (FileNotFoundException e) {
+ return false;
+ } catch (XMPException e) {
+ return false;
+ } finally {
+ Utils.closeSilently(is);
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/category/Action.java b/src/com/android/gallery3d/filtershow/category/Action.java
new file mode 100644
index 000000000..332ca18b0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/Action.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2013 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.category;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
+import com.android.gallery3d.filtershow.pipeline.RenderingRequest;
+import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+public class Action implements RenderingRequestCaller {
+
+ private static final String LOGTAG = "Action";
+ private FilterRepresentation mRepresentation;
+ private String mName;
+ private Rect mImageFrame;
+ private Bitmap mImage;
+ private ArrayAdapter mAdapter;
+ public static final int FULL_VIEW = 0;
+ public static final int CROP_VIEW = 1;
+ private int mType = CROP_VIEW;
+ private Bitmap mPortraitImage;
+ private Bitmap mOverlayBitmap;
+ private Context mContext;
+
+ public Action(Context context, FilterRepresentation representation, int type) {
+ mContext = context;
+ setRepresentation(representation);
+ setType(type);
+ }
+
+ public Action(Context context, FilterRepresentation representation) {
+ this(context, representation, CROP_VIEW);
+ }
+
+ public FilterRepresentation getRepresentation() {
+ return mRepresentation;
+ }
+
+ public void setRepresentation(FilterRepresentation representation) {
+ mRepresentation = representation;
+ mName = representation.getName();
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public void setImageFrame(Rect imageFrame, int orientation) {
+ if (mImageFrame != null && mImageFrame.equals(imageFrame)) {
+ return;
+ }
+ Bitmap bitmap = MasterImage.getImage().getLargeThumbnailBitmap();
+ if (bitmap != null) {
+ mImageFrame = imageFrame;
+ int w = mImageFrame.width();
+ int h = mImageFrame.height();
+ if (orientation == CategoryView.VERTICAL
+ && mType == CROP_VIEW) {
+ w /= 2;
+ }
+ Bitmap bitmapCrop = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ drawCenteredImage(bitmap, bitmapCrop, true);
+
+ postNewIconRenderRequest(bitmapCrop);
+ }
+ }
+
+ public Bitmap getImage() {
+ return mImage;
+ }
+
+ public void setImage(Bitmap image) {
+ mImage = image;
+ }
+
+ public void setAdapter(ArrayAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ public void setType(int type) {
+ mType = type;
+ }
+
+ private void postNewIconRenderRequest(Bitmap bitmap) {
+ if (bitmap != null && mRepresentation != null) {
+ ImagePreset preset = new ImagePreset();
+ preset.addFilter(mRepresentation);
+ RenderingRequest.post(mContext, bitmap,
+ preset, RenderingRequest.ICON_RENDERING, this);
+ }
+ }
+
+ private void drawCenteredImage(Bitmap source, Bitmap destination, boolean scale) {
+ RectF image = new RectF(0, 0, source.getWidth(), source.getHeight());
+ int border = 0;
+ if (!scale) {
+ border = destination.getWidth() - destination.getHeight();
+ if (border < 0) {
+ border = 0;
+ }
+ }
+ RectF frame = new RectF(border, 0,
+ destination.getWidth() - border,
+ destination.getHeight());
+ Matrix m = new Matrix();
+ m.setRectToRect(frame, image, Matrix.ScaleToFit.CENTER);
+ image.set(frame);
+ m.mapRect(image);
+ m.setRectToRect(image, frame, Matrix.ScaleToFit.FILL);
+ Canvas canvas = new Canvas(destination);
+ canvas.drawBitmap(source, m, new Paint(Paint.FILTER_BITMAP_FLAG));
+ }
+
+ @Override
+ public void available(RenderingRequest request) {
+ mImage = request.getBitmap();
+ if (mImage == null) {
+ return;
+ }
+ if (mRepresentation.getOverlayId() != 0 && mOverlayBitmap == null) {
+ mOverlayBitmap = BitmapFactory.decodeResource(
+ mContext.getResources(),
+ mRepresentation.getOverlayId());
+ }
+ if (mOverlayBitmap != null) {
+ if (getRepresentation().getFilterType() == FilterRepresentation.TYPE_BORDER) {
+ Canvas canvas = new Canvas(mImage);
+ canvas.drawBitmap(mOverlayBitmap, new Rect(0, 0, mOverlayBitmap.getWidth(), mOverlayBitmap.getHeight()),
+ new Rect(0, 0, mImage.getWidth(), mImage.getHeight()), new Paint());
+ } else {
+ Canvas canvas = new Canvas(mImage);
+ canvas.drawARGB(128, 0, 0, 0);
+ drawCenteredImage(mOverlayBitmap, mImage, false);
+ }
+ }
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ public void setPortraitImage(Bitmap portraitImage) {
+ mPortraitImage = portraitImage;
+ }
+
+ public Bitmap getPortraitImage() {
+ return mPortraitImage;
+ }
+
+ public Bitmap getOverlayBitmap() {
+ return mOverlayBitmap;
+ }
+
+ public void setOverlayBitmap(Bitmap overlayBitmap) {
+ mOverlayBitmap = overlayBitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/category/CategoryAdapter.java b/src/com/android/gallery3d/filtershow/category/CategoryAdapter.java
new file mode 100644
index 000000000..6451c39df
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/CategoryAdapter.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2013 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.category;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterTinyPlanetRepresentation;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+public class CategoryAdapter extends ArrayAdapter<Action> {
+
+ private static final String LOGTAG = "CategoryAdapter";
+ private int mItemHeight;
+ private View mContainer;
+ private int mItemWidth = ListView.LayoutParams.MATCH_PARENT;
+ private int mSelectedPosition;
+ int mCategory;
+ private int mOrientation;
+
+ public CategoryAdapter(Context context, int textViewResourceId) {
+ super(context, textViewResourceId);
+ mItemHeight = (int) (context.getResources().getDisplayMetrics().density * 100);
+ }
+
+ public CategoryAdapter(Context context) {
+ this(context, 0);
+ }
+
+ public void setItemHeight(int height) {
+ mItemHeight = height;
+ }
+
+ public void setItemWidth(int width) {
+ mItemWidth = width;
+ }
+
+ @Override
+ public void add(Action action) {
+ super.add(action);
+ action.setAdapter(this);
+ }
+
+ public void initializeSelection(int category) {
+ mCategory = category;
+ mSelectedPosition = -1;
+ if (category == MainPanel.LOOKS) {
+ mSelectedPosition = 0;
+ }
+ if (category == MainPanel.BORDERS) {
+ mSelectedPosition = 0;
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = new CategoryView(getContext());
+ }
+ CategoryView view = (CategoryView) convertView;
+ view.setOrientation(mOrientation);
+ view.setAction(getItem(position), this);
+ view.setLayoutParams(
+ new ListView.LayoutParams(mItemWidth, mItemHeight));
+ view.setTag(position);
+ view.invalidate();
+ return view;
+ }
+
+ public void setSelected(View v) {
+ int old = mSelectedPosition;
+ mSelectedPosition = (Integer) v.getTag();
+ if (old != -1) {
+ invalidateView(old);
+ }
+ invalidateView(mSelectedPosition);
+ }
+
+ public boolean isSelected(View v) {
+ return (Integer) v.getTag() == mSelectedPosition;
+ }
+
+ private void invalidateView(int position) {
+ View child = null;
+ if (mContainer instanceof ListView) {
+ ListView lv = (ListView) mContainer;
+ child = lv.getChildAt(position - lv.getFirstVisiblePosition());
+ } else {
+ CategoryTrack ct = (CategoryTrack) mContainer;
+ child = ct.getChildAt(position);
+ }
+ if (child != null) {
+ child.invalidate();
+ }
+ }
+
+ public void setContainer(View container) {
+ mContainer = container;
+ }
+
+ public void imageLoaded() {
+ notifyDataSetChanged();
+ }
+
+ public FilterRepresentation getTinyPlanet() {
+ for (int i = 0; i < getCount(); i++) {
+ Action action = getItem(i);
+ if (action.getRepresentation() != null
+ && action.getRepresentation()
+ instanceof FilterTinyPlanetRepresentation) {
+ return action.getRepresentation();
+ }
+ }
+ return null;
+ }
+
+ public void removeTinyPlanet() {
+ for (int i = 0; i < getCount(); i++) {
+ Action action = getItem(i);
+ if (action.getRepresentation() != null
+ && action.getRepresentation()
+ instanceof FilterTinyPlanetRepresentation) {
+ remove(action);
+ return;
+ }
+ }
+ }
+
+ public void setOrientation(int orientation) {
+ mOrientation = orientation;
+ }
+
+ public void reflectImagePreset(ImagePreset preset) {
+ if (preset == null) {
+ return;
+ }
+ int selected = 0; // if nothing found, select "none" (first element)
+ FilterRepresentation rep = null;
+ if (mCategory == MainPanel.LOOKS) {
+ int pos = preset.getPositionForType(FilterRepresentation.TYPE_FX);
+ if (pos != -1) {
+ rep = preset.getFilterRepresentation(pos);
+ }
+ } else if (mCategory == MainPanel.BORDERS) {
+ int pos = preset.getPositionForType(FilterRepresentation.TYPE_BORDER);
+ if (pos != -1) {
+ rep = preset.getFilterRepresentation(pos);
+ }
+ }
+ if (rep != null) {
+ for (int i = 0; i < getCount(); i++) {
+ if (rep.getName().equalsIgnoreCase(
+ getItem(i).getRepresentation().getName())) {
+ selected = i;
+ break;
+ }
+ }
+ }
+ if (mSelectedPosition != selected) {
+ mSelectedPosition = selected;
+ this.notifyDataSetChanged();
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/category/CategoryPanel.java b/src/com/android/gallery3d/filtershow/category/CategoryPanel.java
new file mode 100644
index 000000000..de2481f3f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/CategoryPanel.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2013 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.category;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+
+public class CategoryPanel extends Fragment {
+
+ public static final String FRAGMENT_TAG = "CategoryPanel";
+ private static final String PARAMETER_TAG = "currentPanel";
+
+ private int mCurrentAdapter = MainPanel.LOOKS;
+ private CategoryAdapter mAdapter;
+
+ public void setAdapter(int value) {
+ mCurrentAdapter = value;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ loadAdapter(mCurrentAdapter);
+ }
+
+ private void loadAdapter(int adapter) {
+ FilterShowActivity activity = (FilterShowActivity) getActivity();
+ switch (adapter) {
+ case MainPanel.LOOKS: {
+ mAdapter = activity.getCategoryLooksAdapter();
+ mAdapter.initializeSelection(MainPanel.LOOKS);
+ activity.updateCategories();
+ break;
+ }
+ case MainPanel.BORDERS: {
+ mAdapter = activity.getCategoryBordersAdapter();
+ mAdapter.initializeSelection(MainPanel.BORDERS);
+ activity.updateCategories();
+ break;
+ }
+ case MainPanel.GEOMETRY: {
+ mAdapter = activity.getCategoryGeometryAdapter();
+ mAdapter.initializeSelection(MainPanel.GEOMETRY);
+ break;
+ }
+ case MainPanel.FILTERS: {
+ mAdapter = activity.getCategoryFiltersAdapter();
+ mAdapter.initializeSelection(MainPanel.FILTERS);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle state) {
+ super.onSaveInstanceState(state);
+ state.putInt(PARAMETER_TAG, mCurrentAdapter);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ LinearLayout main = (LinearLayout) inflater.inflate(
+ R.layout.filtershow_category_panel_new, container,
+ false);
+
+ if (savedInstanceState != null) {
+ int selectedPanel = savedInstanceState.getInt(PARAMETER_TAG);
+ loadAdapter(selectedPanel);
+ }
+
+ View panelView = main.findViewById(R.id.listItems);
+ if (panelView instanceof CategoryTrack) {
+ CategoryTrack panel = (CategoryTrack) panelView;
+ mAdapter.setOrientation(CategoryView.HORIZONTAL);
+ panel.setAdapter(mAdapter);
+ mAdapter.setContainer(panel);
+ } else {
+ ListView panel = (ListView) main.findViewById(R.id.listItems);
+ panel.setAdapter(mAdapter);
+ mAdapter.setContainer(panel);
+ }
+ return main;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/category/CategoryTrack.java b/src/com/android/gallery3d/filtershow/category/CategoryTrack.java
new file mode 100644
index 000000000..ac8245a3b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/CategoryTrack.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 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.category;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.LinearLayout;
+import com.android.gallery3d.R;
+
+public class CategoryTrack extends LinearLayout {
+
+ private CategoryAdapter mAdapter;
+ private int mElemSize;
+ private DataSetObserver mDataSetObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ super.onChanged();
+ invalidate();
+ }
+ @Override
+ public void onInvalidated() {
+ super.onInvalidated();
+ fillContent();
+ }
+ };
+
+ public CategoryTrack(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CategoryTrack);
+ mElemSize = a.getDimensionPixelSize(R.styleable.CategoryTrack_iconSize, 0);
+ }
+
+ public void setAdapter(CategoryAdapter adapter) {
+ mAdapter = adapter;
+ mAdapter.registerDataSetObserver(mDataSetObserver);
+ fillContent();
+ }
+
+ public void fillContent() {
+ removeAllViews();
+ mAdapter.setItemWidth(mElemSize);
+ mAdapter.setItemHeight(LayoutParams.MATCH_PARENT);
+ int n = mAdapter.getCount();
+ for (int i = 0; i < n; i++) {
+ View view = mAdapter.getView(i, null, this);
+ addView(view, i);
+ }
+ requestLayout();
+ }
+
+ @Override
+ public void invalidate() {
+ for (int i = 0; i < this.getChildCount(); i++) {
+ View child = getChildAt(i);
+ child.invalidate();
+ }
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/category/CategoryView.java b/src/com/android/gallery3d/filtershow/category/CategoryView.java
new file mode 100644
index 000000000..c456dc207
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/CategoryView.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2013 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.category;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.view.View;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.ui.SelectionRenderer;
+
+public class CategoryView extends View implements View.OnClickListener {
+
+ private static final String LOGTAG = "CategoryView";
+ public static final int VERTICAL = 0;
+ public static final int HORIZONTAL = 1;
+ private Paint mPaint = new Paint();
+ private Action mAction;
+ private Rect mTextBounds = new Rect();
+ private int mMargin = 16;
+ private int mTextSize = 32;
+ private int mTextColor;
+ private int mBackgroundColor;
+ private Paint mSelectPaint;
+ CategoryAdapter mAdapter;
+ private int mSelectionStroke;
+ private Paint mBorderPaint;
+ private int mBorderStroke;
+ private int mOrientation = VERTICAL;
+
+ public CategoryView(Context context) {
+ super(context);
+ setOnClickListener(this);
+ Resources res = getResources();
+ mBackgroundColor = res.getColor(R.color.filtershow_categoryview_background);
+ mTextColor = res.getColor(R.color.filtershow_categoryview_text);
+ mSelectionStroke = res.getDimensionPixelSize(R.dimen.thumbnail_margin);
+ mTextSize = res.getDimensionPixelSize(R.dimen.category_panel_text_size);
+ mMargin = res.getDimensionPixelOffset(R.dimen.category_panel_margin);
+ mSelectPaint = new Paint();
+ mSelectPaint.setStyle(Paint.Style.FILL);
+ mSelectPaint.setColor(res.getColor(R.color.filtershow_category_selection));
+ mBorderPaint = new Paint(mSelectPaint);
+ mBorderPaint.setColor(Color.BLACK);
+ mBorderStroke = mSelectionStroke / 3;
+ }
+
+ private void computeTextPosition(String text) {
+ if (text == null) {
+ return;
+ }
+ mPaint.setTextSize(mTextSize);
+ if (mOrientation == VERTICAL) {
+ text = text.toUpperCase();
+ // TODO: set this in xml
+ mPaint.setTypeface(Typeface.DEFAULT_BOLD);
+ }
+ mPaint.getTextBounds(text, 0, text.length(), mTextBounds);
+ }
+
+ public void drawText(Canvas canvas, String text) {
+ if (text == null) {
+ return;
+ }
+ float textWidth = mPaint.measureText(text);
+ int x = (int) (canvas.getWidth() - textWidth - mMargin);
+ if (mOrientation == HORIZONTAL) {
+ x = (int) ((canvas.getWidth() - textWidth) / 2.0f);
+ }
+ if (x < 0) {
+ // If the text takes more than the view width,
+ // justify to the left.
+ x = mMargin;
+ }
+ int y = canvas.getHeight() - mMargin;
+ canvas.drawText(text, x, y, mPaint);
+ }
+
+ @Override
+ public CharSequence getContentDescription () {
+ if (mAction != null) {
+ return mAction.getName();
+ }
+ return null;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ canvas.drawColor(mBackgroundColor);
+ if (mAction != null) {
+ mPaint.reset();
+ mPaint.setAntiAlias(true);
+ computeTextPosition(mAction.getName());
+ if (mAction.getImage() == null) {
+ mAction.setImageFrame(new Rect(0, 0, getWidth(), getHeight()), mOrientation);
+ } else {
+ Bitmap bitmap = mAction.getImage();
+ canvas.save();
+ Rect clipRect = new Rect(mSelectionStroke, mSelectionStroke,
+ getWidth() - mSelectionStroke,
+ getHeight() - 2* mMargin - mTextSize);
+ int offsetx = 0;
+ int offsety = 0;
+ if (mOrientation == HORIZONTAL) {
+ canvas.clipRect(clipRect);
+ offsetx = - (bitmap.getWidth() - clipRect.width()) / 2;
+ offsety = - (bitmap.getHeight() - clipRect.height()) / 2;
+ }
+ canvas.drawBitmap(bitmap, offsetx, offsety, mPaint);
+ canvas.restore();
+ if (mAdapter.isSelected(this)) {
+ if (mOrientation == HORIZONTAL) {
+ SelectionRenderer.drawSelection(canvas, 0, 0,
+ getWidth(), getHeight() - mMargin - mTextSize,
+ mSelectionStroke, mSelectPaint, mBorderStroke, mBorderPaint);
+ } else {
+ SelectionRenderer.drawSelection(canvas, 0, 0,
+ Math.min(bitmap.getWidth(), getWidth()),
+ Math.min(bitmap.getHeight(), getHeight()),
+ mSelectionStroke, mSelectPaint, mBorderStroke, mBorderPaint);
+ }
+ }
+ }
+ mPaint.setColor(mBackgroundColor);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(3);
+ drawText(canvas, mAction.getName());
+ mPaint.setColor(mTextColor);
+ mPaint.setStyle(Paint.Style.FILL);
+ mPaint.setStrokeWidth(1);
+ drawText(canvas, mAction.getName());
+ }
+ }
+
+ public void setAction(Action action, CategoryAdapter adapter) {
+ mAction = action;
+ mAdapter = adapter;
+ invalidate();
+ }
+
+ public FilterRepresentation getRepresentation() {
+ return mAction.getRepresentation();
+ }
+
+ @Override
+ public void onClick(View view) {
+ FilterShowActivity activity = (FilterShowActivity) getContext();
+ activity.showRepresentation(mAction.getRepresentation());
+ mAdapter.setSelected(this);
+ }
+
+ public void setOrientation(int orientation) {
+ mOrientation = orientation;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/category/MainPanel.java b/src/com/android/gallery3d/filtershow/category/MainPanel.java
new file mode 100644
index 000000000..9a64ffbf3
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/MainPanel.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2013 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.category;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.state.StatePanel;
+
+public class MainPanel extends Fragment {
+
+ private static final String LOGTAG = "MainPanel";
+
+ private LinearLayout mMainView;
+ private ImageButton looksButton;
+ private ImageButton bordersButton;
+ private ImageButton geometryButton;
+ private ImageButton filtersButton;
+
+ public static final String FRAGMENT_TAG = "MainPanel";
+ public static final int LOOKS = 0;
+ public static final int BORDERS = 1;
+ public static final int GEOMETRY = 2;
+ public static final int FILTERS = 3;
+
+ private int mCurrentSelected = -1;
+
+ private void selection(int position, boolean value) {
+ if (value) {
+ FilterShowActivity activity = (FilterShowActivity) getActivity();
+ activity.setCurrentPanel(position);
+ }
+ switch (position) {
+ case LOOKS: {
+ looksButton.setSelected(value);
+ break;
+ }
+ case BORDERS: {
+ bordersButton.setSelected(value);
+ break;
+ }
+ case GEOMETRY: {
+ geometryButton.setSelected(value);
+ break;
+ }
+ case FILTERS: {
+ filtersButton.setSelected(value);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ if (mMainView != null) {
+ if (mMainView.getParent() != null) {
+ ViewGroup parent = (ViewGroup) mMainView.getParent();
+ parent.removeView(mMainView);
+ }
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ mMainView = (LinearLayout) inflater.inflate(
+ R.layout.filtershow_main_panel, null, false);
+
+ looksButton = (ImageButton) mMainView.findViewById(R.id.fxButton);
+ bordersButton = (ImageButton) mMainView.findViewById(R.id.borderButton);
+ geometryButton = (ImageButton) mMainView.findViewById(R.id.geometryButton);
+ filtersButton = (ImageButton) mMainView.findViewById(R.id.colorsButton);
+
+ looksButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showPanel(LOOKS);
+ }
+ });
+ bordersButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showPanel(BORDERS);
+ }
+ });
+ geometryButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showPanel(GEOMETRY);
+ }
+ });
+ filtersButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showPanel(FILTERS);
+ }
+ });
+
+ FilterShowActivity activity = (FilterShowActivity) getActivity();
+ showImageStatePanel(activity.isShowingImageStatePanel());
+ showPanel(activity.getCurrentPanel());
+ return mMainView;
+ }
+
+ private boolean isRightAnimation(int newPos) {
+ if (newPos < mCurrentSelected) {
+ return false;
+ }
+ return true;
+ }
+
+ private void setCategoryFragment(CategoryPanel category, boolean fromRight) {
+ FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
+ if (fromRight) {
+ transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right);
+ } else {
+ transaction.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_left);
+ }
+ transaction.replace(R.id.category_panel_container, category, CategoryPanel.FRAGMENT_TAG);
+ transaction.commit();
+ }
+
+ public void loadCategoryLookPanel() {
+ if (mCurrentSelected == LOOKS) {
+ return;
+ }
+ boolean fromRight = isRightAnimation(LOOKS);
+ selection(mCurrentSelected, false);
+ CategoryPanel categoryPanel = new CategoryPanel();
+ categoryPanel.setAdapter(LOOKS);
+ setCategoryFragment(categoryPanel, fromRight);
+ mCurrentSelected = LOOKS;
+ selection(mCurrentSelected, true);
+ }
+
+ public void loadCategoryBorderPanel() {
+ if (mCurrentSelected == BORDERS) {
+ return;
+ }
+ boolean fromRight = isRightAnimation(BORDERS);
+ selection(mCurrentSelected, false);
+ CategoryPanel categoryPanel = new CategoryPanel();
+ categoryPanel.setAdapter(BORDERS);
+ setCategoryFragment(categoryPanel, fromRight);
+ mCurrentSelected = BORDERS;
+ selection(mCurrentSelected, true);
+ }
+
+ public void loadCategoryGeometryPanel() {
+ if (mCurrentSelected == GEOMETRY) {
+ return;
+ }
+ boolean fromRight = isRightAnimation(GEOMETRY);
+ selection(mCurrentSelected, false);
+ CategoryPanel categoryPanel = new CategoryPanel();
+ categoryPanel.setAdapter(GEOMETRY);
+ setCategoryFragment(categoryPanel, fromRight);
+ mCurrentSelected = GEOMETRY;
+ selection(mCurrentSelected, true);
+ }
+
+ public void loadCategoryFiltersPanel() {
+ if (mCurrentSelected == FILTERS) {
+ return;
+ }
+ boolean fromRight = isRightAnimation(FILTERS);
+ selection(mCurrentSelected, false);
+ CategoryPanel categoryPanel = new CategoryPanel();
+ categoryPanel.setAdapter(FILTERS);
+ setCategoryFragment(categoryPanel, fromRight);
+ mCurrentSelected = FILTERS;
+ selection(mCurrentSelected, true);
+ }
+
+ public void showPanel(int currentPanel) {
+ switch (currentPanel) {
+ case LOOKS: {
+ loadCategoryLookPanel();
+ break;
+ }
+ case BORDERS: {
+ loadCategoryBorderPanel();
+ break;
+ }
+ case GEOMETRY: {
+ loadCategoryGeometryPanel();
+ break;
+ }
+ case FILTERS: {
+ loadCategoryFiltersPanel();
+ break;
+ }
+ }
+ }
+
+ public void showImageStatePanel(boolean show) {
+ if (mMainView.findViewById(R.id.state_panel_container) == null) {
+ return;
+ }
+ FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
+ final View container = mMainView.findViewById(R.id.state_panel_container);
+ if (show) {
+ container.setVisibility(View.VISIBLE);
+ StatePanel statePanel = new StatePanel();
+ transaction.replace(R.id.state_panel_container, statePanel, StatePanel.FRAGMENT_TAG);
+ } else {
+ container.setVisibility(View.GONE);
+ Fragment statePanel = getChildFragmentManager().findFragmentByTag(StatePanel.FRAGMENT_TAG);
+ if (statePanel != null) {
+ transaction.remove(statePanel);
+ }
+ }
+ transaction.commit();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java
new file mode 100644
index 000000000..dd4df7dc8
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013 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.colorpicker;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+
+public class ColorGridDialog extends Dialog {
+ RGBListener mCallback;
+ private static final String LOGTAG = "ColorGridDialog";
+
+ public ColorGridDialog(Context context, final RGBListener cl) {
+ super(context);
+ mCallback = cl;
+ setTitle(R.string.color_pick_title);
+ setContentView(R.layout.filtershow_color_gird);
+ Button sel = (Button) findViewById(R.id.filtershow_cp_custom);
+ ArrayList<Button> b = getButtons((ViewGroup) getWindow().getDecorView());
+ int k = 0;
+ float[] hsv = new float[3];
+
+ for (Button button : b) {
+ if (!button.equals(sel)){
+ hsv[0] = (k % 5) * 360 / 5;
+ hsv[1] = (k / 5) / 3.0f;
+ hsv[2] = (k < 5) ? (k / 4f) : 1;
+ final int c = (Color.HSVToColor(hsv) & 0x00FFFFFF) | 0xAA000000;
+ GradientDrawable sd = ((GradientDrawable) button.getBackground());
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mCallback.setColor(c);
+ dismiss();
+ }
+ });
+ sd.setColor(c);
+ k++;
+ }
+
+ }
+ sel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showColorPicker();
+ ColorGridDialog.this.dismiss();
+ }
+ });
+ }
+
+ private ArrayList<Button> getButtons(ViewGroup vg) {
+ ArrayList<Button> list = new ArrayList<Button>();
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ View v = vg.getChildAt(i);
+ if (v instanceof Button) {
+ list.add((Button) v);
+ } else if (v instanceof ViewGroup) {
+ list.addAll(getButtons((ViewGroup) v));
+ }
+ }
+ return list;
+ }
+
+ public void showColorPicker() {
+ ColorListener cl = new ColorListener() {
+ @Override
+ public void setColor(float[] hsvo) {
+ int c = Color.HSVToColor(hsvo) & 0xFFFFFF;
+ int alpha = (int) (hsvo[3] * 255);
+ c |= alpha << 24;
+ mCallback.setColor(c);
+ }
+ };
+ ColorPickerDialog cpd = new ColorPickerDialog(this.getContext(), cl);
+ cpd.show();
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorListener.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorListener.java
new file mode 100644
index 000000000..5127dad26
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorListener.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2013 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.colorpicker;
+
+public interface ColorListener {
+ void setColor(float[] hsvo);
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorOpacityView.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorOpacityView.java
new file mode 100644
index 000000000..2bff501f7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorOpacityView.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2013 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.colorpicker;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.Shader;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+
+public class ColorOpacityView extends View implements ColorListener {
+
+ private float mRadius;
+ private float mWidth;
+ private Paint mBarPaint1;
+ private Paint mLinePaint1;
+ private Paint mLinePaint2;
+ private Paint mCheckPaint;
+
+ private float mHeight;
+ private Paint mDotPaint;
+ private int mBgcolor = 0;
+
+ private float mDotRadius;
+ private float mBorder;
+
+ private float[] mHSVO = new float[4];
+ private int mSliderColor;
+ private float mDotX = mBorder;
+ private float mDotY = mBorder;
+ private final static float DOT_SIZE = ColorRectView.DOT_SIZE;
+ public final static float BORDER_SIZE = 20;;
+
+ public ColorOpacityView(Context ctx, AttributeSet attrs) {
+ super(ctx, attrs);
+ DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();
+ float mDpToPix = metrics.density;
+ mDotRadius = DOT_SIZE * mDpToPix;
+ mBorder = BORDER_SIZE * mDpToPix;
+ mBarPaint1 = new Paint();
+
+ mDotPaint = new Paint();
+
+ mDotPaint.setStyle(Paint.Style.FILL);
+ mDotPaint.setColor(ctx.getResources().getColor(R.color.slider_dot_color));
+ mSliderColor = ctx.getResources().getColor(R.color.slider_line_color);
+
+ mBarPaint1.setStyle(Paint.Style.FILL);
+
+ mLinePaint1 = new Paint();
+ mLinePaint1.setColor(Color.GRAY);
+ mLinePaint2 = new Paint();
+ mLinePaint2.setColor(mSliderColor);
+ mLinePaint2.setStrokeWidth(4);
+
+ int[] colors = new int[16 * 16];
+ for (int i = 0; i < colors.length; i++) {
+ int y = i / (16 * 8);
+ int x = (i / 8) % 2;
+ colors[i] = (x == y) ? 0xFFAAAAAA : 0xFF444444;
+ }
+ Bitmap bitmap = Bitmap.createBitmap(colors, 16, 16, Bitmap.Config.ARGB_8888);
+ BitmapShader bs = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
+ mCheckPaint = new Paint();
+ mCheckPaint.setShader(bs);
+ }
+
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ float ox = mDotX;
+ float oy = mDotY;
+
+ float x = event.getX();
+ float y = event.getY();
+
+ mDotX = x;
+
+ if (mDotX < mBorder) {
+ mDotX = mBorder;
+ }
+
+ if (mDotX > mWidth - mBorder) {
+ mDotX = mWidth - mBorder;
+ }
+ mHSVO[3] = (mDotX - mBorder) / (mWidth - mBorder * 2);
+ notifyColorListeners(mHSVO);
+ setupButton();
+ invalidate((int) (ox - mDotRadius), (int) (oy - mDotRadius), (int) (ox + mDotRadius),
+ (int) (oy + mDotRadius));
+ invalidate(
+ (int) (mDotX - mDotRadius), (int) (mDotY - mDotRadius), (int) (mDotX + mDotRadius),
+ (int) (mDotY + mDotRadius));
+
+ return true;
+ }
+
+ private void setupButton() {
+ float pos = mHSVO[3] * (mWidth - mBorder * 2);
+ mDotX = pos + mBorder;
+
+ int[] colors3 = new int[] {
+ mSliderColor, mSliderColor, 0x66000000, 0 };
+ RadialGradient g = new RadialGradient(mDotX, mDotY, mDotRadius, colors3, new float[] {
+ 0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+ mDotPaint.setShader(g);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+ mHeight = h;
+ mDotY = mHeight / 2;
+ updatePaint();
+ setupButton();
+ }
+
+ private void updatePaint() {
+
+ int color2 = Color.HSVToColor(mHSVO);
+ int color1 = color2 & 0xFFFFFF;
+
+ Shader sg = new LinearGradient(
+ mBorder, mBorder, mWidth - mBorder, mBorder, color1, color2, Shader.TileMode.CLAMP);
+ mBarPaint1.setShader(sg);
+
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawColor(mBgcolor);
+ canvas.drawRect(mBorder, mBorder, mWidth - mBorder, mHeight - mBorder, mCheckPaint);
+ canvas.drawRect(mBorder, mBorder, mWidth - mBorder, mHeight - mBorder, mBarPaint1);
+ canvas.drawLine(mDotX, mDotY, mWidth - mBorder, mDotY, mLinePaint1);
+ canvas.drawLine(mBorder, mDotY, mDotX, mDotY, mLinePaint2);
+ if (mDotX != Float.NaN) {
+ canvas.drawCircle(mDotX, mDotY, mDotRadius, mDotPaint);
+ }
+ }
+
+ @Override
+ public void setColor(float[] hsv) {
+ System.arraycopy(hsv, 0, mHSVO, 0, mHSVO.length);
+
+ float oy = mDotY;
+
+ updatePaint();
+ setupButton();
+ invalidate();
+ }
+
+ ArrayList<ColorListener> mColorListeners = new ArrayList<ColorListener>();
+
+ public void notifyColorListeners(float[] hsvo) {
+ for (ColorListener l : mColorListeners) {
+ l.setColor(hsvo);
+ }
+ }
+
+ public void addColorListener(ColorListener l) {
+ mColorListeners.add(l);
+ }
+
+ public void removeColorListener(ColorListener l) {
+ mColorListeners.remove(l);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorPickerDialog.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorPickerDialog.java
new file mode 100644
index 000000000..73a5c907c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorPickerDialog.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2013 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.colorpicker;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ToggleButton;
+
+import com.android.gallery3d.R;
+
+public class ColorPickerDialog extends Dialog implements ColorListener {
+ ToggleButton mSelectedButton;
+ GradientDrawable mSelectRect;
+
+ float[] mHSVO = new float[4];
+
+ public ColorPickerDialog(Context context, final ColorListener cl) {
+ super(context);
+
+ setContentView(R.layout.filtershow_color_picker);
+ ColorValueView csv = (ColorValueView) findViewById(R.id.colorValueView);
+ ColorRectView cwv = (ColorRectView) findViewById(R.id.colorRectView);
+ ColorOpacityView cvv = (ColorOpacityView) findViewById(R.id.colorOpacityView);
+ float[] hsvo = new float[] {
+ 123, .9f, 1, 1 };
+
+ mSelectRect = (GradientDrawable) getContext()
+ .getResources().getDrawable(R.drawable.filtershow_color_picker_roundrect);
+ Button selButton = (Button) findViewById(R.id.btnSelect);
+ selButton.setCompoundDrawablesWithIntrinsicBounds(null, null, mSelectRect, null);
+ Button sel = (Button) findViewById(R.id.btnSelect);
+
+ sel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ColorPickerDialog.this.dismiss();
+ if (cl != null) {
+ cl.setColor(mHSVO);
+ }
+ }
+ });
+
+ cwv.setColor(hsvo);
+ cvv.setColor(hsvo);
+ csv.setColor(hsvo);
+ csv.addColorListener(cwv);
+ cwv.addColorListener(csv);
+ csv.addColorListener(cvv);
+ cwv.addColorListener(cvv);
+ cvv.addColorListener(cwv);
+ cvv.addColorListener(csv);
+ cvv.addColorListener(this);
+ csv.addColorListener(this);
+ cwv.addColorListener(this);
+
+ }
+
+ void toggleClick(ToggleButton v, int[] buttons, boolean isChecked) {
+ int id = v.getId();
+ if (!isChecked) {
+ mSelectedButton = null;
+ return;
+ }
+ for (int i = 0; i < buttons.length; i++) {
+ if (id != buttons[i]) {
+ ToggleButton b = (ToggleButton) findViewById(buttons[i]);
+ b.setChecked(false);
+ }
+ }
+ mSelectedButton = v;
+
+ float[] hsv = (float[]) v.getTag();
+
+ ColorValueView csv = (ColorValueView) findViewById(R.id.colorValueView);
+ ColorRectView cwv = (ColorRectView) findViewById(R.id.colorRectView);
+ ColorOpacityView cvv = (ColorOpacityView) findViewById(R.id.colorOpacityView);
+ cwv.setColor(hsv);
+ cvv.setColor(hsv);
+ csv.setColor(hsv);
+ }
+
+ @Override
+ public void setColor(float[] hsvo) {
+ System.arraycopy(hsvo, 0, mHSVO, 0, mHSVO.length);
+ int color = Color.HSVToColor(hsvo);
+ mSelectRect.setColor(color);
+ setButtonColor(mSelectedButton, hsvo);
+ }
+
+ private void setButtonColor(ToggleButton button, float[] hsv) {
+ if (button == null) {
+ return;
+ }
+ int color = Color.HSVToColor(hsv);
+ button.setBackgroundColor(color);
+ float[] fg = new float[] {
+ (hsv[0] + 180) % 360,
+ hsv[1],
+ (hsv[2] > .5f) ? .1f : .9f
+ };
+ button.setTextColor(Color.HSVToColor(fg));
+ button.setTag(hsv);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorRectView.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorRectView.java
new file mode 100644
index 000000000..07d7c7126
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorRectView.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2013 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.colorpicker;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+
+public class ColorRectView extends View implements ColorListener {
+ private float mDpToPix;
+ private float mRadius = 80;
+ private float mCtrY = 100;
+ private Paint mWheelPaint1;
+ private Paint mWheelPaint2;
+ private Paint mWheelPaint3;
+ private float mCtrX = 100;
+ private Paint mDotPaint;
+ private float mDotRadus;
+ private float mBorder;
+ private int mBgcolor = 0;
+ private float mDotX = Float.NaN;
+ private float mDotY;
+ private int mSliderColor = 0xFF33B5E5;
+ private float[] mHSVO = new float[4];
+ private int[] mColors = new int[] {
+ 0xFFFF0000,// red
+ 0xFFFFFF00,// yellow
+ 0xFF00FF00,// green
+ 0xFF00FFFF,// cyan
+ 0xFF0000FF,// blue
+ 0xFFFF00FF,// magenta
+ 0xFFFF0000,// red
+ };
+ private int mWidth;
+ private int mHeight;
+ public final static float DOT_SIZE = 20;
+ public final static float BORDER_SIZE = 10;
+
+ public ColorRectView(Context ctx, AttributeSet attrs) {
+ super(ctx, attrs);
+
+ DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();
+ mDpToPix = metrics.density;
+ mDotRadus = DOT_SIZE * mDpToPix;
+ mBorder = BORDER_SIZE * mDpToPix;
+
+ mWheelPaint1 = new Paint();
+ mWheelPaint2 = new Paint();
+ mWheelPaint3 = new Paint();
+ mDotPaint = new Paint();
+
+ mDotPaint.setStyle(Paint.Style.FILL);
+ mDotPaint.setColor(ctx.getResources().getColor(R.color.slider_dot_color));
+ mSliderColor = ctx.getResources().getColor(R.color.slider_line_color);
+ mWheelPaint1.setStyle(Paint.Style.FILL);
+ mWheelPaint2.setStyle(Paint.Style.FILL);
+ mWheelPaint3.setStyle(Paint.Style.FILL);
+ }
+
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+
+ invalidate((int) (mDotX - mDotRadus), (int) (mDotY - mDotRadus), (int) (mDotX + mDotRadus),
+ (int) (mDotY + mDotRadus));
+ float x = event.getX();
+ float y = event.getY();
+
+ x = Math.max(Math.min(x, mWidth - mBorder), mBorder);
+ y = Math.max(Math.min(y, mHeight - mBorder), mBorder);
+ mDotX = x;
+ mDotY = y;
+ float sat = 1 - (mDotY - mBorder) / (mHeight - 2 * mBorder);
+ if (sat > 1) {
+ sat = 1;
+ }
+
+ double hue = Math.PI * 2 * (mDotX - mBorder) / (mHeight - 2 * mBorder);
+ mHSVO[0] = ((float) Math.toDegrees(hue) + 360) % 360;
+ mHSVO[1] = sat;
+ notifyColorListeners(mHSVO);
+ updateDotPaint();
+ invalidate((int) (mDotX - mDotRadus), (int) (mDotY - mDotRadus), (int) (mDotX + mDotRadus),
+ (int) (mDotY + mDotRadus));
+
+ return true;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+ mHeight = h;
+ mCtrY = h / 2f;
+ mCtrX = w / 2f;
+ mRadius = Math.min(mCtrY, mCtrX) - 2 * mBorder;
+ setUpColorPanel();
+ }
+
+ private void setUpColorPanel() {
+ float val = mHSVO[2];
+ int v = 0xFF000000 | 0x10101 * (int) (val * 0xFF);
+ int[] colors = new int[] {
+ 0x0000000, v };
+ int[] colors2 = new int[] {
+ 0x0000000, 0xFF000000 };
+ int[] wheelColor = new int[mColors.length];
+ float[] hsv = new float[3];
+ for (int i = 0; i < wheelColor.length; i++) {
+ Color.colorToHSV(mColors[i], hsv);
+ hsv[2] = mHSVO[2];
+ wheelColor[i] = Color.HSVToColor(hsv);
+ }
+ updateDot();
+ updateDotPaint();
+ SweepGradient sg = new SweepGradient(mCtrX, mCtrY, wheelColor, null);
+ LinearGradient lg = new LinearGradient(
+ mBorder, 0, mWidth - mBorder, 0, wheelColor, null, Shader.TileMode.CLAMP);
+
+ mWheelPaint1.setShader(lg);
+ LinearGradient rg = new LinearGradient(
+ 0, mBorder, 0, mHeight - mBorder, colors, null, Shader.TileMode.CLAMP);
+ mWheelPaint2.setShader(rg);
+ LinearGradient rg2 = new LinearGradient(
+ 0, mBorder, 0, mHeight - mBorder, colors2, null, Shader.TileMode.CLAMP);
+ mWheelPaint3.setShader(rg2);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawColor(mBgcolor);
+ RectF rect = new RectF();
+ rect.left = mBorder;
+ rect.right = mWidth - mBorder;
+ rect.top = mBorder;
+ rect.bottom = mHeight - mBorder;
+
+ canvas.drawRect(rect, mWheelPaint1);
+ canvas.drawRect(rect, mWheelPaint3);
+ canvas.drawRect(rect, mWheelPaint2);
+
+ if (mDotX != Float.NaN) {
+
+ canvas.drawCircle(mDotX, mDotY, mDotRadus, mDotPaint);
+ }
+ }
+
+ private void updateDot() {
+
+ double hue = mHSVO[0];
+ double sat = mHSVO[1];
+
+ mDotX = (float) (mBorder + (mHeight - 2 * mBorder) * Math.toRadians(hue) / (Math.PI * 2));
+ mDotY = (float) ((1 - sat) * (mHeight - 2 * mBorder) + mBorder);
+
+ }
+
+ private void updateDotPaint() {
+ int[] colors3 = new int[] {
+ mSliderColor, mSliderColor, 0x66000000, 0 };
+ RadialGradient g = new RadialGradient(mDotX, mDotY, mDotRadus, colors3, new float[] {
+ 0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+ mDotPaint.setShader(g);
+
+ }
+
+ @Override
+ public void setColor(float[] hsvo) {
+ System.arraycopy(hsvo, 0, mHSVO, 0, mHSVO.length);
+
+ setUpColorPanel();
+ invalidate();
+
+ updateDot();
+ updateDotPaint();
+
+ }
+
+ ArrayList<ColorListener> mColorListeners = new ArrayList<ColorListener>();
+
+ public void notifyColorListeners(float[] hsv) {
+ for (ColorListener l : mColorListeners) {
+ l.setColor(hsv);
+ }
+ }
+
+ public void addColorListener(ColorListener l) {
+ mColorListeners.add(l);
+ }
+
+ public void removeColorListener(ColorListener l) {
+ mColorListeners.remove(l);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorValueView.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorValueView.java
new file mode 100644
index 000000000..13cb44bad
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorValueView.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2013 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.colorpicker;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.Shader;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+
+public class ColorValueView extends View implements ColorListener {
+
+ private float mRadius;
+ private float mWidth;
+ private Paint mBarPaint1;
+ private Paint mLinePaint1;
+ private Paint mLinePaint2;
+ private float mHeight;
+ private int mBgcolor = 0;
+ private Paint mDotPaint;
+ private float dotRadus;
+ private float mBorder;
+
+ private float[] mHSVO = new float[4];
+ private int mSliderColor;
+ private float mDotX;
+ private float mDotY = mBorder;
+ private final static float DOT_SIZE = ColorRectView.DOT_SIZE;
+ private final static float BORDER_SIZE = ColorRectView.DOT_SIZE;
+
+ public ColorValueView(Context ctx, AttributeSet attrs) {
+ super(ctx, attrs);
+ DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();
+ float mDpToPix = metrics.density;
+ dotRadus = DOT_SIZE * mDpToPix;
+ mBorder = BORDER_SIZE * mDpToPix;
+
+ mBarPaint1 = new Paint();
+
+ mDotPaint = new Paint();
+
+ mDotPaint.setStyle(Paint.Style.FILL);
+ mDotPaint.setColor(ctx.getResources().getColor(R.color.slider_dot_color));
+
+ mBarPaint1.setStyle(Paint.Style.FILL);
+
+ mLinePaint1 = new Paint();
+ mLinePaint1.setColor(Color.GRAY);
+ mLinePaint2 = new Paint();
+ mSliderColor = ctx.getResources().getColor(R.color.slider_line_color);
+ mLinePaint2.setColor(mSliderColor);
+ mLinePaint2.setStrokeWidth(4);
+ }
+
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ float ox = mDotX;
+ float oy = mDotY;
+
+ float x = event.getX();
+ float y = event.getY();
+
+ mDotY = y;
+
+ if (mDotY < mBorder) {
+ mDotY = mBorder;
+ }
+
+ if (mDotY > mHeight - mBorder) {
+ mDotY = mHeight - mBorder;
+ }
+ mHSVO[2] = (mDotY - mBorder) / (mHeight - mBorder * 2);
+ notifyColorListeners(mHSVO);
+ setupButton();
+ invalidate((int) (ox - dotRadus), (int) (oy - dotRadus), (int) (ox + dotRadus),
+ (int) (oy + dotRadus));
+ invalidate((int) (mDotX - dotRadus), (int) (mDotY - dotRadus), (int) (mDotX + dotRadus),
+ (int) (mDotY + dotRadus));
+
+ return true;
+ }
+
+ private void setupButton() {
+ float pos = mHSVO[2] * (mHeight - mBorder * 2);
+ mDotY = pos + mBorder;
+
+ int[] colors3 = new int[] {
+ mSliderColor, mSliderColor, 0x66000000, 0 };
+ RadialGradient g = new RadialGradient(mDotX, mDotY, dotRadus, colors3, new float[] {
+ 0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+ mDotPaint.setShader(g);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+ mHeight = h;
+ mDotX = mWidth / 2;
+ updatePaint();
+ setupButton();
+ }
+
+ private void updatePaint() {
+ float[] hsv = new float[] {
+ mHSVO[0], mHSVO[1], 0f };
+ int color1 = Color.HSVToColor(hsv);
+ hsv[2] = 1;
+ int color2 = Color.HSVToColor(hsv);
+
+ Shader sg = new LinearGradient(mBorder, mBorder, mBorder, mHeight - mBorder, color1, color2,
+ Shader.TileMode.CLAMP);
+ mBarPaint1.setShader(sg);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawColor(mBgcolor);
+ canvas.drawRect(mBorder, mBorder, mWidth - mBorder, mHeight - mBorder, mBarPaint1);
+ canvas.drawLine(mDotX, mDotY, mDotX, mHeight - mBorder, mLinePaint2);
+ canvas.drawLine(mDotX, mBorder, mDotX, mDotY, mLinePaint1);
+ if (mDotX != Float.NaN) {
+ canvas.drawCircle(mDotX, mDotY, dotRadus, mDotPaint);
+ }
+ }
+
+ @Override
+ public void setColor(float[] hsvo) {
+ System.arraycopy(hsvo, 0, mHSVO, 0, mHSVO.length);
+
+ float oy = mDotY;
+ updatePaint();
+ setupButton();
+ invalidate();
+
+ }
+
+ ArrayList<ColorListener> mColorListeners = new ArrayList<ColorListener>();
+
+ public void notifyColorListeners(float[] hsv) {
+ for (ColorListener l : mColorListeners) {
+ l.setColor(hsv);
+ }
+ }
+
+ public void addColorListener(ColorListener l) {
+ mColorListeners.add(l);
+ }
+
+ public void removeColorListener(ColorListener l) {
+ mColorListeners.remove(l);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/RGBListener.java b/src/com/android/gallery3d/filtershow/colorpicker/RGBListener.java
new file mode 100644
index 000000000..147fb91a4
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/RGBListener.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2013 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.colorpicker;
+
+public interface RGBListener {
+ void setColor(int hsv);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/ActionSlider.java b/src/com/android/gallery3d/filtershow/controller/ActionSlider.java
new file mode 100644
index 000000000..f80a1cacb
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ActionSlider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.Editor;
+
+public class ActionSlider extends TitledSlider {
+ private static final String LOGTAG = "ActionSlider";
+ ImageButton mLeftButton;
+ ImageButton mRightButton;
+ public ActionSlider() {
+ mLayoutID = R.layout.filtershow_control_action_slider;
+ }
+
+ @Override
+ public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+ super.setUp(container, parameter, editor);
+ mLeftButton = (ImageButton) mTopView.findViewById(R.id.leftActionButton);
+ mLeftButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ ((ParameterActionAndInt) mParameter).fireLeftAction();
+ }
+ });
+
+ mRightButton = (ImageButton) mTopView.findViewById(R.id.rightActionButton);
+ mRightButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ ((ParameterActionAndInt) mParameter).fireRightAction();
+ }
+ });
+ updateUI();
+ }
+
+ @Override
+ public void updateUI() {
+ super.updateUI();
+ if (mLeftButton != null) {
+ int iconId = ((ParameterActionAndInt) mParameter).getLeftIcon();
+ mLeftButton.setImageResource(iconId);
+ }
+ if (mRightButton != null) {
+ int iconId = ((ParameterActionAndInt) mParameter).getRightIcon();
+ mRightButton.setImageResource(iconId);
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/BasicParameterInt.java b/src/com/android/gallery3d/filtershow/controller/BasicParameterInt.java
new file mode 100644
index 000000000..92145e9be
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/BasicParameterInt.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+import android.util.Log;
+
+public class BasicParameterInt implements ParameterInteger {
+ protected String mParameterName;
+ protected Control mControl;
+ protected int mMaximum = 100;
+ protected int mMinimum = 0;
+ protected int mDefaultValue;
+ protected int mValue;
+ public final int ID;
+ protected FilterView mEditor;
+ private final String LOGTAG = "BasicParameterInt";
+
+ @Override
+ public void copyFrom(Parameter src) {
+ if (!(src instanceof BasicParameterInt)) {
+ throw new IllegalArgumentException(src.getClass().getName());
+ }
+ BasicParameterInt p = (BasicParameterInt) src;
+ mMaximum = p.mMaximum;
+ mMinimum = p.mMinimum;
+ mDefaultValue = p.mDefaultValue;
+ mValue = p.mValue;
+ }
+
+ public BasicParameterInt(int id, int value) {
+ ID = id;
+ mValue = value;
+ }
+
+ public BasicParameterInt(int id, int value, int min, int max) {
+ ID = id;
+ mValue = value;
+ mMinimum = min;
+ mMaximum = max;
+ }
+
+ @Override
+ public String getParameterName() {
+ return mParameterName;
+ }
+
+ @Override
+ public String getParameterType() {
+ return sParameterType;
+ }
+
+ @Override
+ public String getValueString() {
+ return mParameterName + mValue;
+ }
+
+ @Override
+ public void setController(Control control) {
+ mControl = control;
+ }
+
+ @Override
+ public int getMaximum() {
+ return mMaximum;
+ }
+
+ @Override
+ public int getMinimum() {
+ return mMinimum;
+ }
+
+ @Override
+ public int getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ @Override
+ public int getValue() {
+ return mValue;
+ }
+
+ @Override
+ public void setValue(int value) {
+ mValue = value;
+ if (mEditor != null) {
+ mEditor.commitLocalRepresentation();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getValueString();
+ }
+
+ @Override
+ public void setFilterView(FilterView editor) {
+ mEditor = editor;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java b/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java
new file mode 100644
index 000000000..fb9f95e97
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+import android.content.Context;
+
+import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
+
+public class BasicParameterStyle implements ParameterStyles {
+ protected String mParameterName;
+ protected int mSelectedStyle;
+ protected int mNumberOfStyles;
+ protected int mDefaultStyle = 0;
+ protected Control mControl;
+ protected FilterView mEditor;
+ public final int ID;
+ private final String LOGTAG = "BasicParameterStyle";
+
+ @Override
+ public void copyFrom(Parameter src) {
+ if (!(src instanceof BasicParameterStyle)) {
+ throw new IllegalArgumentException(src.getClass().getName());
+ }
+ BasicParameterStyle p = (BasicParameterStyle) src;
+ mNumberOfStyles = p.mNumberOfStyles;
+ mSelectedStyle = p.mSelectedStyle;
+ mDefaultStyle = p.mDefaultStyle;
+ }
+
+ public BasicParameterStyle(int id, int numberOfStyles) {
+ ID = id;
+ mNumberOfStyles = numberOfStyles;
+ }
+
+ @Override
+ public String getParameterName() {
+ return mParameterName;
+ }
+
+ @Override
+ public String getParameterType() {
+ return sParameterType;
+ }
+
+ @Override
+ public String getValueString() {
+ return mParameterName + mSelectedStyle;
+ }
+
+ @Override
+ public void setController(Control control) {
+ mControl = control;
+ }
+
+ @Override
+ public int getNumberOfStyles() {
+ return mNumberOfStyles;
+ }
+
+ @Override
+ public int getDefaultSelected() {
+ return mDefaultStyle;
+ }
+
+ @Override
+ public int getSelected() {
+ return mSelectedStyle;
+ }
+
+ @Override
+ public void setSelected(int selectedStyle) {
+ mSelectedStyle = selectedStyle;
+ if (mEditor != null) {
+ mEditor.commitLocalRepresentation();
+ }
+ }
+
+ @Override
+ public void getIcon(int index, RenderingRequestCaller caller) {
+ mEditor.computeIcon(index, caller);
+ }
+
+ @Override
+ public String getStyleTitle(int index, Context context) {
+ return "";
+ }
+
+ @Override
+ public String toString() {
+ return getValueString();
+ }
+
+ @Override
+ public void setFilterView(FilterView editor) {
+ mEditor = editor;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/BasicSlider.java b/src/com/android/gallery3d/filtershow/controller/BasicSlider.java
new file mode 100644
index 000000000..9d8278d52
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/BasicSlider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.Editor;
+
+public class BasicSlider implements Control {
+ private SeekBar mSeekBar;
+ private ParameterInteger mParameter;
+ Editor mEditor;
+
+ @Override
+ public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+ container.removeAllViews();
+ mEditor = editor;
+ Context context = container.getContext();
+ mParameter = (ParameterInteger) parameter;
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ LinearLayout lp = (LinearLayout) inflater.inflate(
+ R.layout.filtershow_seekbar, container, true);
+ mSeekBar = (SeekBar) lp.findViewById(R.id.primarySeekBar);
+
+ updateUI();
+ mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mParameter != null) {
+ mParameter.setValue(progress + mParameter.getMinimum());
+ mEditor.commitLocalRepresentation();
+
+ }
+ }
+ });
+ }
+
+ @Override
+ public View getTopView() {
+ return mSeekBar;
+ }
+
+ @Override
+ public void setPrameter(Parameter parameter) {
+ mParameter = (ParameterInteger) parameter;
+ if (mSeekBar != null) {
+ updateUI();
+ }
+ }
+
+ @Override
+ public void updateUI() {
+ mSeekBar.setMax(mParameter.getMaximum() - mParameter.getMinimum());
+ mSeekBar.setProgress(mParameter.getValue() - mParameter.getMinimum());
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/Control.java b/src/com/android/gallery3d/filtershow/controller/Control.java
new file mode 100644
index 000000000..43422904c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/Control.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.gallery3d.filtershow.editors.Editor;
+
+public interface Control {
+ public void setUp(ViewGroup container, Parameter parameter, Editor editor);
+
+ public View getTopView();
+
+ public void setPrameter(Parameter parameter);
+
+ public void updateUI();
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/FilterView.java b/src/com/android/gallery3d/filtershow/controller/FilterView.java
new file mode 100644
index 000000000..9ca81dc35
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/FilterView.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
+
+public interface FilterView {
+ public void computeIcon(int index, RenderingRequestCaller caller);
+
+ public void commitLocalRepresentation();
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/Parameter.java b/src/com/android/gallery3d/filtershow/controller/Parameter.java
new file mode 100644
index 000000000..8f4d5c0a5
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/Parameter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+import com.android.gallery3d.filtershow.editors.Editor;
+
+public interface Parameter {
+ String getParameterName();
+
+ String getParameterType();
+
+ String getValueString();
+
+ public void setController(Control c);
+
+ public void setFilterView(FilterView editor);
+
+ public void copyFrom(Parameter src);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/ParameterActionAndInt.java b/src/com/android/gallery3d/filtershow/controller/ParameterActionAndInt.java
new file mode 100644
index 000000000..8a05c3aa6
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ParameterActionAndInt.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+public interface ParameterActionAndInt extends ParameterInteger {
+ static String sParameterType = "ParameterActionAndInt";
+
+ public void fireLeftAction();
+
+ public int getLeftIcon();
+
+ public void fireRightAction();
+
+ public int getRightIcon();
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/ParameterInteger.java b/src/com/android/gallery3d/filtershow/controller/ParameterInteger.java
new file mode 100644
index 000000000..0bfd20135
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ParameterInteger.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+public interface ParameterInteger extends Parameter {
+ static String sParameterType = "ParameterInteger";
+
+ int getMaximum();
+
+ int getMinimum();
+
+ int getDefaultValue();
+
+ int getValue();
+
+ void setValue(int value);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/ParameterSet.java b/src/com/android/gallery3d/filtershow/controller/ParameterSet.java
new file mode 100644
index 000000000..6b50a4d0b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ParameterSet.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+public interface ParameterSet {
+ int getNumberOfParameters();
+
+ Parameter getFilterParameter(int index);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/ParameterStyles.java b/src/com/android/gallery3d/filtershow/controller/ParameterStyles.java
new file mode 100644
index 000000000..7d250a0bf
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ParameterStyles.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+import android.content.Context;
+
+import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
+
+public interface ParameterStyles extends Parameter {
+ public static String sParameterType = "ParameterStyles";
+
+ int getNumberOfStyles();
+
+ int getDefaultSelected();
+
+ int getSelected();
+
+ void setSelected(int value);
+
+ void getIcon(int index, RenderingRequestCaller caller);
+
+ String getStyleTitle(int index, Context context);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/StyleChooser.java b/src/com/android/gallery3d/filtershow/controller/StyleChooser.java
new file mode 100644
index 000000000..fb613abc7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/StyleChooser.java
@@ -0,0 +1,88 @@
+package com.android.gallery3d.filtershow.controller;
+
+import android.app.ActionBar.LayoutParams;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.pipeline.RenderingRequest;
+import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
+import com.android.gallery3d.filtershow.editors.Editor;
+
+import java.util.Vector;
+
+public class StyleChooser implements Control {
+ private final String LOGTAG = "StyleChooser";
+ protected ParameterStyles mParameter;
+ protected LinearLayout mLinearLayout;
+ protected Editor mEditor;
+ private View mTopView;
+ private Vector<ImageButton> mIconButton = new Vector<ImageButton>();
+ protected int mLayoutID = R.layout.filtershow_control_style_chooser;
+
+ @Override
+ public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+ container.removeAllViews();
+ mEditor = editor;
+ Context context = container.getContext();
+ mParameter = (ParameterStyles) parameter;
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mTopView = inflater.inflate(mLayoutID, container, true);
+ mLinearLayout = (LinearLayout) mTopView.findViewById(R.id.listStyles);
+ mTopView.setVisibility(View.VISIBLE);
+ int n = mParameter.getNumberOfStyles();
+ mIconButton.clear();
+ LayoutParams lp = new LayoutParams(120, 120);
+ for (int i = 0; i < n; i++) {
+ final ImageButton button = new ImageButton(context);
+ button.setScaleType(ScaleType.CENTER_CROP);
+ button.setLayoutParams(lp);
+ button.setBackgroundResource(android.R.color.transparent);
+ mIconButton.add(button);
+ final int buttonNo = i;
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ mParameter.setSelected(buttonNo);
+ }
+ });
+ mLinearLayout.addView(button);
+ mParameter.getIcon(i, new RenderingRequestCaller() {
+ @Override
+ public void available(RenderingRequest request) {
+ Bitmap bmap = request.getBitmap();
+ if (bmap == null) {
+ return;
+ }
+ button.setImageBitmap(bmap);
+ }
+ });
+ }
+ }
+
+ @Override
+ public View getTopView() {
+ return mTopView;
+ }
+
+ @Override
+ public void setPrameter(Parameter parameter) {
+ mParameter = (ParameterStyles) parameter;
+ updateUI();
+ }
+
+ @Override
+ public void updateUI() {
+ if (mParameter == null) {
+ return;
+ }
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/TitledSlider.java b/src/com/android/gallery3d/filtershow/controller/TitledSlider.java
new file mode 100644
index 000000000..f29442bb9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/TitledSlider.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013 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.controller;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.Editor;
+
+public class TitledSlider implements Control {
+ private final String LOGTAG = "ParametricEditor";
+ private SeekBar mSeekBar;
+ private TextView mControlName;
+ private TextView mControlValue;
+ protected ParameterInteger mParameter;
+ Editor mEditor;
+ View mTopView;
+ protected int mLayoutID = R.layout.filtershow_control_title_slider;
+
+ @Override
+ public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+ container.removeAllViews();
+ mEditor = editor;
+ Context context = container.getContext();
+ mParameter = (ParameterInteger) parameter;
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mTopView = inflater.inflate(mLayoutID, container, true);
+ mTopView.setVisibility(View.VISIBLE);
+ mSeekBar = (SeekBar) mTopView.findViewById(R.id.controlValueSeekBar);
+ mControlName = (TextView) mTopView.findViewById(R.id.controlName);
+ mControlValue = (TextView) mTopView.findViewById(R.id.controlValue);
+ updateUI();
+ mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mParameter != null) {
+ mParameter.setValue(progress + mParameter.getMinimum());
+ if (mControlName != null) {
+ mControlName.setText(mParameter.getParameterName());
+ }
+ if (mControlValue != null) {
+ mControlValue.setText(Integer.toString(mParameter.getValue()));
+ }
+ mEditor.commitLocalRepresentation();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setPrameter(Parameter parameter) {
+ mParameter = (ParameterInteger) parameter;
+ if (mSeekBar != null)
+ updateUI();
+ }
+
+ @Override
+ public void updateUI() {
+ if (mControlName != null && mParameter.getParameterName() != null) {
+ mControlName.setText(mParameter.getParameterName().toUpperCase());
+ }
+ if (mControlValue != null) {
+ mControlValue.setText(
+ Integer.toString(mParameter.getValue()));
+ }
+ mSeekBar.setMax(mParameter.getMaximum() - mParameter.getMinimum());
+ mSeekBar.setProgress(mParameter.getValue() - mParameter.getMinimum());
+ mEditor.commitLocalRepresentation();
+ }
+
+ @Override
+ public View getTopView() {
+ return mTopView;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/BoundedRect.java b/src/com/android/gallery3d/filtershow/crop/BoundedRect.java
new file mode 100644
index 000000000..13b8d6de1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/BoundedRect.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.gallery3d.filtershow.crop;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
+
+import java.util.Arrays;
+
+/**
+ * Maintains invariant that inner rectangle is constrained to be within the
+ * outer, rotated rectangle.
+ */
+public class BoundedRect {
+ private float rot;
+ private RectF outer;
+ private RectF inner;
+ private float[] innerRotated;
+
+ public BoundedRect(float rotation, Rect outerRect, Rect innerRect) {
+ rot = rotation;
+ outer = new RectF(outerRect);
+ inner = new RectF(innerRect);
+ innerRotated = CropMath.getCornersFromRect(inner);
+ rotateInner();
+ if (!isConstrained())
+ reconstrain();
+ }
+
+ public BoundedRect(float rotation, RectF outerRect, RectF innerRect) {
+ rot = rotation;
+ outer = new RectF(outerRect);
+ inner = new RectF(innerRect);
+ innerRotated = CropMath.getCornersFromRect(inner);
+ rotateInner();
+ if (!isConstrained())
+ reconstrain();
+ }
+
+ public void resetTo(float rotation, RectF outerRect, RectF innerRect) {
+ rot = rotation;
+ outer.set(outerRect);
+ inner.set(innerRect);
+ innerRotated = CropMath.getCornersFromRect(inner);
+ rotateInner();
+ if (!isConstrained())
+ reconstrain();
+ }
+
+ /**
+ * Sets inner, and re-constrains it to fit within the rotated bounding rect.
+ */
+ public void setInner(RectF newInner) {
+ if (inner.equals(newInner))
+ return;
+ inner = newInner;
+ innerRotated = CropMath.getCornersFromRect(inner);
+ rotateInner();
+ if (!isConstrained())
+ reconstrain();
+ }
+
+ /**
+ * Sets rotation, and re-constrains inner to fit within the rotated bounding rect.
+ */
+ public void setRotation(float rotation) {
+ if (rotation == rot)
+ return;
+ rot = rotation;
+ innerRotated = CropMath.getCornersFromRect(inner);
+ rotateInner();
+ if (!isConstrained())
+ reconstrain();
+ }
+
+ public void setToInner(RectF r) {
+ r.set(inner);
+ }
+
+ public void setToOuter(RectF r) {
+ r.set(outer);
+ }
+
+ public RectF getInner() {
+ return new RectF(inner);
+ }
+
+ public RectF getOuter() {
+ return new RectF(outer);
+ }
+
+ /**
+ * Tries to move the inner rectangle by (dx, dy). If this would cause it to leave
+ * the bounding rectangle, snaps the inner rectangle to the edge of the bounding
+ * rectangle.
+ */
+ public void moveInner(float dx, float dy) {
+ Matrix m0 = getInverseRotMatrix();
+
+ RectF translatedInner = new RectF(inner);
+ translatedInner.offset(dx, dy);
+
+ float[] translatedInnerCorners = CropMath.getCornersFromRect(translatedInner);
+ float[] outerCorners = CropMath.getCornersFromRect(outer);
+
+ m0.mapPoints(translatedInnerCorners);
+ float[] correction = {
+ 0, 0
+ };
+
+ // find correction vectors for corners that have moved out of bounds
+ for (int i = 0; i < translatedInnerCorners.length; i += 2) {
+ float correctedInnerX = translatedInnerCorners[i] + correction[0];
+ float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
+ if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) {
+ float[] badCorner = {
+ correctedInnerX, correctedInnerY
+ };
+ float[] nearestSide = CropMath.closestSide(badCorner, outerCorners);
+ float[] correctionVec =
+ GeometryMathUtils.shortestVectorFromPointToLine(badCorner, nearestSide);
+ correction[0] += correctionVec[0];
+ correction[1] += correctionVec[1];
+ }
+ }
+
+ for (int i = 0; i < translatedInnerCorners.length; i += 2) {
+ float correctedInnerX = translatedInnerCorners[i] + correction[0];
+ float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
+ if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) {
+ float[] correctionVec = {
+ correctedInnerX, correctedInnerY
+ };
+ CropMath.getEdgePoints(outer, correctionVec);
+ correctionVec[0] -= correctedInnerX;
+ correctionVec[1] -= correctedInnerY;
+ correction[0] += correctionVec[0];
+ correction[1] += correctionVec[1];
+ }
+ }
+
+ // Set correction
+ for (int i = 0; i < translatedInnerCorners.length; i += 2) {
+ float correctedInnerX = translatedInnerCorners[i] + correction[0];
+ float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
+ // update translated corners with correction vectors
+ translatedInnerCorners[i] = correctedInnerX;
+ translatedInnerCorners[i + 1] = correctedInnerY;
+ }
+
+ innerRotated = translatedInnerCorners;
+ // reconstrain to update inner
+ reconstrain();
+ }
+
+ /**
+ * Attempts to resize the inner rectangle. If this would cause it to leave
+ * the bounding rect, clips the inner rectangle to fit.
+ */
+ public void resizeInner(RectF newInner) {
+ Matrix m = getRotMatrix();
+ Matrix m0 = getInverseRotMatrix();
+
+ float[] outerCorners = CropMath.getCornersFromRect(outer);
+ m.mapPoints(outerCorners);
+ float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
+ float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
+ RectF ret = new RectF(newInner);
+
+ for (int i = 0; i < newInnerCorners.length; i += 2) {
+ float[] c = {
+ newInnerCorners[i], newInnerCorners[i + 1]
+ };
+ float[] c0 = Arrays.copyOf(c, 2);
+ m0.mapPoints(c0);
+ if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
+ float[] outerSide = CropMath.closestSide(c, outerCorners);
+ float[] pathOfCorner = {
+ newInnerCorners[i], newInnerCorners[i + 1],
+ oldInnerCorners[i], oldInnerCorners[i + 1]
+ };
+ float[] p = GeometryMathUtils.lineIntersect(pathOfCorner, outerSide);
+ if (p == null) {
+ // lines are parallel or not well defined, so don't resize
+ p = new float[2];
+ p[0] = oldInnerCorners[i];
+ p[1] = oldInnerCorners[i + 1];
+ }
+ // relies on corners being in same order as method
+ // getCornersFromRect
+ switch (i) {
+ case 0:
+ case 1:
+ ret.left = (p[0] > ret.left) ? p[0] : ret.left;
+ ret.top = (p[1] > ret.top) ? p[1] : ret.top;
+ break;
+ case 2:
+ case 3:
+ ret.right = (p[0] < ret.right) ? p[0] : ret.right;
+ ret.top = (p[1] > ret.top) ? p[1] : ret.top;
+ break;
+ case 4:
+ case 5:
+ ret.right = (p[0] < ret.right) ? p[0] : ret.right;
+ ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
+ break;
+ case 6:
+ case 7:
+ ret.left = (p[0] > ret.left) ? p[0] : ret.left;
+ ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ float[] retCorners = CropMath.getCornersFromRect(ret);
+ m0.mapPoints(retCorners);
+ innerRotated = retCorners;
+ // reconstrain to update inner
+ reconstrain();
+ }
+
+ /**
+ * Attempts to resize the inner rectangle. If this would cause it to leave
+ * the bounding rect, clips the inner rectangle to fit while maintaining
+ * aspect ratio.
+ */
+ public void fixedAspectResizeInner(RectF newInner) {
+ Matrix m = getRotMatrix();
+ Matrix m0 = getInverseRotMatrix();
+
+ float aspectW = inner.width();
+ float aspectH = inner.height();
+ float aspRatio = aspectW / aspectH;
+ float[] corners = CropMath.getCornersFromRect(outer);
+
+ m.mapPoints(corners);
+ float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
+ float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
+
+ // find fixed corner
+ int fixed = -1;
+ if (inner.top == newInner.top) {
+ if (inner.left == newInner.left)
+ fixed = 0; // top left
+ else if (inner.right == newInner.right)
+ fixed = 2; // top right
+ } else if (inner.bottom == newInner.bottom) {
+ if (inner.right == newInner.right)
+ fixed = 4; // bottom right
+ else if (inner.left == newInner.left)
+ fixed = 6; // bottom left
+ }
+ // no fixed corner, return without update
+ if (fixed == -1)
+ return;
+ float widthSoFar = newInner.width();
+ int moved = -1;
+ for (int i = 0; i < newInnerCorners.length; i += 2) {
+ float[] c = {
+ newInnerCorners[i], newInnerCorners[i + 1]
+ };
+ float[] c0 = Arrays.copyOf(c, 2);
+ m0.mapPoints(c0);
+ if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
+ moved = i;
+ if (moved == fixed)
+ continue;
+ float[] l2 = CropMath.closestSide(c, corners);
+ float[] l1 = {
+ newInnerCorners[i], newInnerCorners[i + 1],
+ oldInnerCorners[i], oldInnerCorners[i + 1]
+ };
+ float[] p = GeometryMathUtils.lineIntersect(l1, l2);
+ if (p == null) {
+ // lines are parallel or not well defined, so set to old
+ // corner
+ p = new float[2];
+ p[0] = oldInnerCorners[i];
+ p[1] = oldInnerCorners[i + 1];
+ }
+ // relies on corners being in same order as method
+ // getCornersFromRect
+ float fixed_x = oldInnerCorners[fixed];
+ float fixed_y = oldInnerCorners[fixed + 1];
+ float newWidth = Math.abs(fixed_x - p[0]);
+ float newHeight = Math.abs(fixed_y - p[1]);
+ newWidth = Math.max(newWidth, aspRatio * newHeight);
+ if (newWidth < widthSoFar)
+ widthSoFar = newWidth;
+ }
+ }
+
+ float heightSoFar = widthSoFar / aspRatio;
+ RectF ret = new RectF(inner);
+ if (fixed == 0) {
+ ret.right = ret.left + widthSoFar;
+ ret.bottom = ret.top + heightSoFar;
+ } else if (fixed == 2) {
+ ret.left = ret.right - widthSoFar;
+ ret.bottom = ret.top + heightSoFar;
+ } else if (fixed == 4) {
+ ret.left = ret.right - widthSoFar;
+ ret.top = ret.bottom - heightSoFar;
+ } else if (fixed == 6) {
+ ret.right = ret.left + widthSoFar;
+ ret.top = ret.bottom - heightSoFar;
+ }
+ float[] retCorners = CropMath.getCornersFromRect(ret);
+ m0.mapPoints(retCorners);
+ innerRotated = retCorners;
+ // reconstrain to update inner
+ reconstrain();
+ }
+
+ // internal methods
+
+ private boolean isConstrained() {
+ for (int i = 0; i < 8; i += 2) {
+ if (!CropMath.inclusiveContains(outer, innerRotated[i], innerRotated[i + 1]))
+ return false;
+ }
+ return true;
+ }
+
+ private void reconstrain() {
+ // innerRotated has been changed to have incorrect values
+ CropMath.getEdgePoints(outer, innerRotated);
+ Matrix m = getRotMatrix();
+ float[] unrotated = Arrays.copyOf(innerRotated, 8);
+ m.mapPoints(unrotated);
+ inner = CropMath.trapToRect(unrotated);
+ }
+
+ private void rotateInner() {
+ Matrix m = getInverseRotMatrix();
+ m.mapPoints(innerRotated);
+ }
+
+ private Matrix getRotMatrix() {
+ Matrix m = new Matrix();
+ m.setRotate(rot, outer.centerX(), outer.centerY());
+ return m;
+ }
+
+ private Matrix getInverseRotMatrix() {
+ Matrix m = new Matrix();
+ m.setRotate(-rot, outer.centerX(), outer.centerY());
+ return m;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropActivity.java b/src/com/android/gallery3d/filtershow/crop/CropActivity.java
new file mode 100644
index 000000000..0a0c36703
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropActivity.java
@@ -0,0 +1,697 @@
+/*
+ * Copyright (C) 2013 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.crop;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.tools.SaveImage;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Activity for cropping an image.
+ */
+public class CropActivity extends Activity {
+ private static final String LOGTAG = "CropActivity";
+ public static final String CROP_ACTION = "com.android.camera.action.CROP";
+ private CropExtras mCropExtras = null;
+ private LoadBitmapTask mLoadBitmapTask = null;
+
+ private int mOutputX = 0;
+ private int mOutputY = 0;
+ private Bitmap mOriginalBitmap = null;
+ private RectF mOriginalBounds = null;
+ private int mOriginalRotation = 0;
+ private Uri mSourceUri = null;
+ private CropView mCropView = null;
+ private View mSaveButton = null;
+ private boolean finalIOGuard = false;
+
+ private static final int SELECT_PICTURE = 1; // request code for picker
+
+ private static final int DEFAULT_COMPRESS_QUALITY = 90;
+ /**
+ * The maximum bitmap size we allow to be returned through the intent.
+ * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
+ * have some overhead to hit so that we go way below the limit here to make
+ * sure the intent stays below 1MB.We should consider just returning a byte
+ * array instead of a Bitmap instance to avoid overhead.
+ */
+ public static final int MAX_BMAP_IN_INTENT = 750000;
+
+ // Flags
+ private static final int DO_SET_WALLPAPER = 1;
+ private static final int DO_RETURN_DATA = 1 << 1;
+ private static final int DO_EXTRA_OUTPUT = 1 << 2;
+
+ private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ setResult(RESULT_CANCELED, new Intent());
+ mCropExtras = getExtrasFromIntent(intent);
+ if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ }
+
+ setContentView(R.layout.crop_activity);
+ mCropView = (CropView) findViewById(R.id.cropView);
+
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+ actionBar.setCustomView(R.layout.filtershow_actionbar);
+
+ View mSaveButton = actionBar.getCustomView();
+ mSaveButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startFinishOutput();
+ }
+ });
+
+ if (intent.getData() != null) {
+ mSourceUri = intent.getData();
+ startLoadBitmap(mSourceUri);
+ } else {
+ pickImage();
+ }
+ }
+
+ private void enableSave(boolean enable) {
+ if (mSaveButton != null) {
+ mSaveButton.setEnabled(enable);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mLoadBitmapTask != null) {
+ mLoadBitmapTask.cancel(false);
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public void onConfigurationChanged (Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mCropView.configChanged();
+ }
+
+ /**
+ * Opens a selector in Gallery to chose an image for use when none was given
+ * in the CROP intent.
+ */
+ private 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);
+ }
+
+ /**
+ * Callback for pickImage().
+ */
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
+ mSourceUri = data.getData();
+ startLoadBitmap(mSourceUri);
+ }
+ }
+
+ /**
+ * Gets screen size metric.
+ */
+ private int getScreenImageSize() {
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
+ return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
+ }
+
+ /**
+ * Method that loads a bitmap in an async task.
+ */
+ private void startLoadBitmap(Uri uri) {
+ if (uri != null) {
+ enableSave(false);
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.VISIBLE);
+ mLoadBitmapTask = new LoadBitmapTask();
+ mLoadBitmapTask.execute(uri);
+ } else {
+ cannotLoadImage();
+ done();
+ }
+ }
+
+ /**
+ * Method called on UI thread with loaded bitmap.
+ */
+ private void doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation) {
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.GONE);
+ mOriginalBitmap = bitmap;
+ mOriginalBounds = bounds;
+ mOriginalRotation = orientation;
+ if (bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
+ RectF imgBounds = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ mCropView.initialize(bitmap, imgBounds, imgBounds, orientation);
+ if (mCropExtras != null) {
+ int aspectX = mCropExtras.getAspectX();
+ int aspectY = mCropExtras.getAspectY();
+ mOutputX = mCropExtras.getOutputX();
+ mOutputY = mCropExtras.getOutputY();
+ if (mOutputX > 0 && mOutputY > 0) {
+ mCropView.applyAspect(mOutputX, mOutputY);
+
+ }
+ float spotX = mCropExtras.getSpotlightX();
+ float spotY = mCropExtras.getSpotlightY();
+ if (spotX > 0 && spotY > 0) {
+ mCropView.setWallpaperSpotlight(spotX, spotY);
+ }
+ if (aspectX > 0 && aspectY > 0) {
+ mCropView.applyAspect(aspectX, aspectY);
+ }
+ }
+ enableSave(true);
+ } else {
+ Log.w(LOGTAG, "could not load image for cropping");
+ cannotLoadImage();
+ setResult(RESULT_CANCELED, new Intent());
+ done();
+ }
+ }
+
+ /**
+ * Display toast for image loading failure.
+ */
+ private void cannotLoadImage() {
+ CharSequence text = getString(R.string.cannot_load_image);
+ Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+ /**
+ * AsyncTask for loading a bitmap into memory.
+ *
+ * @see #startLoadBitmap(Uri)
+ * @see #doneLoadBitmap(Bitmap)
+ */
+ private class LoadBitmapTask extends AsyncTask<Uri, Void, Bitmap> {
+ int mBitmapSize;
+ Context mContext;
+ Rect mOriginalBounds;
+ int mOrientation;
+
+ public LoadBitmapTask() {
+ mBitmapSize = getScreenImageSize();
+ mContext = getApplicationContext();
+ mOriginalBounds = new Rect();
+ mOrientation = 0;
+ }
+
+ @Override
+ protected Bitmap doInBackground(Uri... params) {
+ Uri uri = params[0];
+ Bitmap bmap = ImageLoader.loadConstrainedBitmap(uri, mContext, mBitmapSize,
+ mOriginalBounds, false);
+ mOrientation = ImageLoader.getMetadataRotation(mContext, uri);
+ return bmap;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap result) {
+ doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation);
+ }
+ }
+
+ private void startFinishOutput() {
+ if (finalIOGuard) {
+ return;
+ } else {
+ finalIOGuard = true;
+ }
+ enableSave(false);
+ Uri destinationUri = null;
+ int flags = 0;
+ if (mOriginalBitmap != null && mCropExtras != null) {
+ if (mCropExtras.getExtraOutput() != null) {
+ destinationUri = mCropExtras.getExtraOutput();
+ if (destinationUri != null) {
+ flags |= DO_EXTRA_OUTPUT;
+ }
+ }
+ if (mCropExtras.getSetAsWallpaper()) {
+ flags |= DO_SET_WALLPAPER;
+ }
+ if (mCropExtras.getReturnData()) {
+ flags |= DO_RETURN_DATA;
+ }
+ }
+ if (flags == 0) {
+ destinationUri = SaveImage.makeAndInsertUri(this, mSourceUri);
+ if (destinationUri != null) {
+ flags |= DO_EXTRA_OUTPUT;
+ }
+ }
+ if ((flags & FLAG_CHECK) != 0 && mOriginalBitmap != null) {
+ RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight());
+ RectF crop = getBitmapCrop(photo);
+ startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop,
+ photo, mOriginalBounds,
+ (mCropExtras == null) ? null : mCropExtras.getOutputFormat(), mOriginalRotation);
+ return;
+ }
+ setResult(RESULT_CANCELED, new Intent());
+ done();
+ return;
+ }
+
+ private void startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri,
+ RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format,
+ int rotation) {
+ if (cropBounds == null || photoBounds == null || currentBitmap == null
+ || currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0
+ || cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0
+ || photoBounds.height() == 0) {
+ return; // fail fast
+ }
+ if ((flags & FLAG_CHECK) == 0) {
+ return; // no output options
+ }
+ if ((flags & DO_SET_WALLPAPER) != 0) {
+ Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
+ }
+
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.VISIBLE);
+ BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds,
+ photoBounds, currentBitmapBounds, rotation, mOutputX, mOutputY);
+ ioTask.execute(currentBitmap);
+ }
+
+ private void doneBitmapIO(boolean success, Intent intent) {
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.GONE);
+ if (success) {
+ setResult(RESULT_OK, intent);
+ } else {
+ setResult(RESULT_CANCELED, intent);
+ }
+ done();
+ }
+
+ private class BitmapIOTask extends AsyncTask<Bitmap, Void, Boolean> {
+
+ private final WallpaperManager mWPManager;
+ InputStream mInStream = null;
+ OutputStream mOutStream = null;
+ String mOutputFormat = null;
+ Uri mOutUri = null;
+ Uri mInUri = null;
+ int mFlags = 0;
+ RectF mCrop = null;
+ RectF mPhoto = null;
+ RectF mOrig = null;
+ Intent mResultIntent = null;
+ int mRotation = 0;
+
+ // Helper to setup input stream
+ private void regenerateInputStream() {
+ if (mInUri == null) {
+ Log.w(LOGTAG, "cannot read original file, no input URI given");
+ } else {
+ Utils.closeSilently(mInStream);
+ try {
+ mInStream = getContentResolver().openInputStream(mInUri);
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
+ }
+ }
+ }
+
+ public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags,
+ RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation,
+ int outputX, int outputY) {
+ mOutputFormat = outputFormat;
+ mOutStream = null;
+ mOutUri = destUri;
+ mInUri = sourceUri;
+ mFlags = flags;
+ mCrop = cropBounds;
+ mPhoto = photoBounds;
+ mOrig = originalBitmapBounds;
+ mWPManager = WallpaperManager.getInstance(getApplicationContext());
+ mResultIntent = new Intent();
+ mRotation = (rotation < 0) ? -rotation : rotation;
+ mRotation %= 360;
+ mRotation = 90 * (int) (mRotation / 90); // now mRotation is a multiple of 90
+ mOutputX = outputX;
+ mOutputY = outputY;
+
+ if ((flags & DO_EXTRA_OUTPUT) != 0) {
+ if (mOutUri == null) {
+ Log.w(LOGTAG, "cannot write file, no output URI given");
+ } else {
+ try {
+ mOutStream = getContentResolver().openOutputStream(mOutUri);
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e);
+ }
+ }
+ }
+
+ if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) {
+ regenerateInputStream();
+ }
+ }
+
+ @Override
+ protected Boolean doInBackground(Bitmap... params) {
+ boolean failure = false;
+ Bitmap img = params[0];
+
+ // Set extra for crop bounds
+ if (mCrop != null && mPhoto != null && mOrig != null) {
+ RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
+ Matrix m = new Matrix();
+ m.setRotate(mRotation);
+ m.mapRect(trueCrop);
+ if (trueCrop != null) {
+ Rect rounded = new Rect();
+ trueCrop.roundOut(rounded);
+ mResultIntent.putExtra(CropExtras.KEY_CROPPED_RECT, rounded);
+ }
+ }
+
+ // Find the small cropped bitmap that is returned in the intent
+ if ((mFlags & DO_RETURN_DATA) != 0) {
+ assert (img != null);
+ Bitmap ret = getCroppedImage(img, mCrop, mPhoto);
+ if (ret != null) {
+ ret = getDownsampledBitmap(ret, MAX_BMAP_IN_INTENT);
+ }
+ if (ret == null) {
+ Log.w(LOGTAG, "could not downsample bitmap to return in data");
+ failure = true;
+ } else {
+ if (mRotation > 0) {
+ Matrix m = new Matrix();
+ m.setRotate(mRotation);
+ Bitmap tmp = Bitmap.createBitmap(ret, 0, 0, ret.getWidth(),
+ ret.getHeight(), m, true);
+ if (tmp != null) {
+ ret = tmp;
+ }
+ }
+ mResultIntent.putExtra(CropExtras.KEY_DATA, ret);
+ }
+ }
+
+ // Do the large cropped bitmap and/or set the wallpaper
+ if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) {
+ // Find crop bounds (scaled to original image size)
+ RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
+ if (trueCrop == null) {
+ Log.w(LOGTAG, "cannot find crop for full size image");
+ failure = true;
+ return false;
+ }
+ Rect roundedTrueCrop = new Rect();
+ trueCrop.roundOut(roundedTrueCrop);
+
+ if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
+ Log.w(LOGTAG, "crop has bad values for full size image");
+ failure = true;
+ return false;
+ }
+
+ // Attempt to open a region decoder
+ BitmapRegionDecoder decoder = null;
+ try {
+ decoder = BitmapRegionDecoder.newInstance(mInStream, true);
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
+ }
+
+ Bitmap crop = null;
+ if (decoder != null) {
+ // Do region decoding to get crop bitmap
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+ crop = decoder.decodeRegion(roundedTrueCrop, options);
+ decoder.recycle();
+ }
+
+ if (crop == null) {
+ // BitmapRegionDecoder has failed, try to crop in-memory
+ regenerateInputStream();
+ Bitmap fullSize = null;
+ if (mInStream != null) {
+ fullSize = BitmapFactory.decodeStream(mInStream);
+ }
+ if (fullSize != null) {
+ crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
+ roundedTrueCrop.top, roundedTrueCrop.width(),
+ roundedTrueCrop.height());
+ }
+ }
+
+ if (crop == null) {
+ Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
+ failure = true;
+ return false;
+ }
+ if (mOutputX > 0 && mOutputY > 0) {
+ Matrix m = new Matrix();
+ RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
+ if (mRotation > 0) {
+ m.setRotate(mRotation);
+ m.mapRect(cropRect);
+ }
+ RectF returnRect = new RectF(0, 0, mOutputX, mOutputY);
+ m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+ m.preRotate(mRotation);
+ Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
+ (int) returnRect.height(), Bitmap.Config.ARGB_8888);
+ if (tmp != null) {
+ Canvas c = new Canvas(tmp);
+ c.drawBitmap(crop, m, new Paint());
+ crop = tmp;
+ }
+ } else if (mRotation > 0) {
+ Matrix m = new Matrix();
+ m.setRotate(mRotation);
+ Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(),
+ crop.getHeight(), m, true);
+ if (tmp != null) {
+ crop = tmp;
+ }
+ }
+ // Get output compression format
+ CompressFormat cf =
+ convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
+
+ // If we only need to output to a URI, compress straight to file
+ if (mFlags == DO_EXTRA_OUTPUT) {
+ if (mOutStream == null
+ || !crop.compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream)) {
+ Log.w(LOGTAG, "failed to compress bitmap to file: " + mOutUri.toString());
+ failure = true;
+ } else {
+ mResultIntent.setData(mOutUri);
+ }
+ } else {
+ // Compress to byte array
+ ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+ if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
+
+ // If we need to output to a Uri, write compressed
+ // bitmap out
+ if ((mFlags & DO_EXTRA_OUTPUT) != 0) {
+ if (mOutStream == null) {
+ Log.w(LOGTAG,
+ "failed to compress bitmap to file: " + mOutUri.toString());
+ failure = true;
+ } else {
+ try {
+ mOutStream.write(tmpOut.toByteArray());
+ mResultIntent.setData(mOutUri);
+ } catch (IOException e) {
+ Log.w(LOGTAG,
+ "failed to compress bitmap to file: "
+ + mOutUri.toString(), e);
+ failure = true;
+ }
+ }
+ }
+
+ // If we need to set to the wallpaper, set it
+ if ((mFlags & DO_SET_WALLPAPER) != 0 && mWPManager != null) {
+ if (mWPManager == null) {
+ Log.w(LOGTAG, "no wallpaper manager");
+ failure = true;
+ } else {
+ try {
+ mWPManager.setStream(new ByteArrayInputStream(tmpOut
+ .toByteArray()));
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+ failure = true;
+ }
+ }
+ }
+ } else {
+ Log.w(LOGTAG, "cannot compress bitmap");
+ failure = true;
+ }
+ }
+ }
+ return !failure; // True if any of the operations failed
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ Utils.closeSilently(mOutStream);
+ Utils.closeSilently(mInStream);
+ doneBitmapIO(result.booleanValue(), mResultIntent);
+ }
+
+ }
+
+ private void done() {
+ finish();
+ }
+
+ protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
+ RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
+ RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
+ if (crop == null) {
+ return null;
+ }
+ Rect intCrop = new Rect();
+ crop.roundOut(intCrop);
+ return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
+ intCrop.height());
+ }
+
+ protected static Bitmap getDownsampledBitmap(Bitmap image, int max_size) {
+ if (image == null || image.getWidth() == 0 || image.getHeight() == 0 || max_size < 16) {
+ throw new IllegalArgumentException("Bad argument to getDownsampledBitmap()");
+ }
+ int shifts = 0;
+ int size = CropMath.getBitmapSize(image);
+ while (size > max_size) {
+ shifts++;
+ size /= 4;
+ }
+ Bitmap ret = Bitmap.createScaledBitmap(image, image.getWidth() >> shifts,
+ image.getHeight() >> shifts, true);
+ if (ret == null) {
+ return null;
+ }
+ // Handle edge case for rounding.
+ if (CropMath.getBitmapSize(ret) > max_size) {
+ return Bitmap.createScaledBitmap(ret, ret.getWidth() >> 1, ret.getHeight() >> 1, true);
+ }
+ return ret;
+ }
+
+ /**
+ * Gets the crop extras from the intent, or null if none exist.
+ */
+ protected static CropExtras getExtrasFromIntent(Intent intent) {
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
+ extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
+ extras.getBoolean(CropExtras.KEY_SCALE, true) &&
+ extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
+ extras.getInt(CropExtras.KEY_ASPECT_X, 0),
+ extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
+ extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
+ extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
+ (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
+ extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
+ extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
+ extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
+ extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
+ }
+ return null;
+ }
+
+ protected static CompressFormat convertExtensionToCompressFormat(String extension) {
+ return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
+ }
+
+ protected static String getFileExtension(String requestFormat) {
+ String outputFormat = (requestFormat == null)
+ ? "jpg"
+ : requestFormat;
+ outputFormat = outputFormat.toLowerCase();
+ return (outputFormat.equals("png") || outputFormat.equals("gif"))
+ ? "png" // We don't support gif compression.
+ : "jpg";
+ }
+
+ private RectF getBitmapCrop(RectF imageBounds) {
+ RectF crop = mCropView.getCrop();
+ RectF photo = mCropView.getPhoto();
+ if (crop == null || photo == null) {
+ Log.w(LOGTAG, "could not get crop");
+ return null;
+ }
+ RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds);
+ return scaledCrop;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java b/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java
new file mode 100644
index 000000000..b0d324cbb
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2013 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.crop;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+
+public abstract class CropDrawingUtils {
+
+ public static void drawRuleOfThird(Canvas canvas, RectF bounds) {
+ Paint p = new Paint();
+ p.setStyle(Paint.Style.STROKE);
+ p.setColor(Color.argb(128, 255, 255, 255));
+ p.setStrokeWidth(2);
+ float stepX = bounds.width() / 3.0f;
+ float stepY = bounds.height() / 3.0f;
+ float x = bounds.left + stepX;
+ float y = bounds.top + stepY;
+ for (int i = 0; i < 2; i++) {
+ canvas.drawLine(x, bounds.top, x, bounds.bottom, p);
+ x += stepX;
+ }
+ for (int j = 0; j < 2; j++) {
+ canvas.drawLine(bounds.left, y, bounds.right, y, p);
+ y += stepY;
+ }
+ }
+
+ public static void drawCropRect(Canvas canvas, RectF bounds) {
+ Paint p = new Paint();
+ p.setStyle(Paint.Style.STROKE);
+ p.setColor(Color.WHITE);
+ p.setStrokeWidth(3);
+ canvas.drawRect(bounds, p);
+ }
+
+ public static void drawIndicator(Canvas canvas, Drawable indicator, int indicatorSize,
+ float centerX, float centerY) {
+ int left = (int) centerX - indicatorSize / 2;
+ int top = (int) centerY - indicatorSize / 2;
+ indicator.setBounds(left, top, left + indicatorSize, top + indicatorSize);
+ indicator.draw(canvas);
+ }
+
+ public static void drawIndicators(Canvas canvas, Drawable cropIndicator, int indicatorSize,
+ RectF bounds, boolean fixedAspect, int selection) {
+ boolean notMoving = (selection == CropObject.MOVE_NONE);
+ if (fixedAspect) {
+ if ((selection == CropObject.TOP_LEFT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.top);
+ }
+ if ((selection == CropObject.TOP_RIGHT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.top);
+ }
+ if ((selection == CropObject.BOTTOM_LEFT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.bottom);
+ }
+ if ((selection == CropObject.BOTTOM_RIGHT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.bottom);
+ }
+ } else {
+ if (((selection & CropObject.MOVE_TOP) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.top);
+ }
+ if (((selection & CropObject.MOVE_BOTTOM) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.bottom);
+ }
+ if (((selection & CropObject.MOVE_LEFT) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.centerY());
+ }
+ if (((selection & CropObject.MOVE_RIGHT) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.centerY());
+ }
+ }
+ }
+
+ public static void drawWallpaperSelectionFrame(Canvas canvas, RectF cropBounds, float spotX,
+ float spotY, Paint p, Paint shadowPaint) {
+ float sx = cropBounds.width() * spotX;
+ float sy = cropBounds.height() * spotY;
+ float cx = cropBounds.centerX();
+ float cy = cropBounds.centerY();
+ RectF r1 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2);
+ float temp = sx;
+ sx = sy;
+ sy = temp;
+ RectF r2 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2);
+ canvas.save();
+ canvas.clipRect(cropBounds);
+ canvas.clipRect(r1, Region.Op.DIFFERENCE);
+ canvas.clipRect(r2, Region.Op.DIFFERENCE);
+ canvas.drawPaint(shadowPaint);
+ canvas.restore();
+ Path path = new Path();
+ path.moveTo(r1.left, r1.top);
+ path.lineTo(r1.right, r1.top);
+ path.moveTo(r1.left, r1.top);
+ path.lineTo(r1.left, r1.bottom);
+ path.moveTo(r1.left, r1.bottom);
+ path.lineTo(r1.right, r1.bottom);
+ path.moveTo(r1.right, r1.top);
+ path.lineTo(r1.right, r1.bottom);
+ path.moveTo(r2.left, r2.top);
+ path.lineTo(r2.right, r2.top);
+ path.moveTo(r2.right, r2.top);
+ path.lineTo(r2.right, r2.bottom);
+ path.moveTo(r2.left, r2.bottom);
+ path.lineTo(r2.right, r2.bottom);
+ path.moveTo(r2.left, r2.top);
+ path.lineTo(r2.left, r2.bottom);
+ canvas.drawPath(path, p);
+ }
+
+ public static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds) {
+ canvas.drawRect(outerBounds.left, outerBounds.top, innerBounds.right, innerBounds.top, p);
+ canvas.drawRect(innerBounds.right, outerBounds.top, outerBounds.right, innerBounds.bottom,
+ p);
+ canvas.drawRect(innerBounds.left, innerBounds.bottom, outerBounds.right,
+ outerBounds.bottom, p);
+ canvas.drawRect(outerBounds.left, innerBounds.top, innerBounds.left, outerBounds.bottom, p);
+ }
+
+ public static Matrix getBitmapToDisplayMatrix(RectF imageBounds, RectF displayBounds) {
+ Matrix m = new Matrix();
+ CropDrawingUtils.setBitmapToDisplayMatrix(m, imageBounds, displayBounds);
+ return m;
+ }
+
+ public static boolean setBitmapToDisplayMatrix(Matrix m, RectF imageBounds,
+ RectF displayBounds) {
+ m.reset();
+ return m.setRectToRect(imageBounds, displayBounds, Matrix.ScaleToFit.CENTER);
+ }
+
+ public static boolean setImageToScreenMatrix(Matrix dst, RectF image,
+ RectF screen, int rotation) {
+ RectF rotatedImage = new RectF();
+ dst.setRotate(rotation, image.centerX(), image.centerY());
+ if (!dst.mapRect(rotatedImage, image)) {
+ return false; // fails for rotations that are not multiples of 90
+ // degrees
+ }
+ boolean rToR = dst.setRectToRect(rotatedImage, screen, Matrix.ScaleToFit.CENTER);
+ boolean rot = dst.preRotate(rotation, image.centerX(), image.centerY());
+ return rToR && rot;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropExtras.java b/src/com/android/gallery3d/filtershow/crop/CropExtras.java
new file mode 100644
index 000000000..60fe9af53
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropExtras.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.crop;
+
+import android.net.Uri;
+
+public class CropExtras {
+
+ public static final String KEY_CROPPED_RECT = "cropped-rect";
+ public static final String KEY_OUTPUT_X = "outputX";
+ public static final String KEY_OUTPUT_Y = "outputY";
+ public static final String KEY_SCALE = "scale";
+ public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded";
+ public static final String KEY_ASPECT_X = "aspectX";
+ public static final String KEY_ASPECT_Y = "aspectY";
+ public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper";
+ public static final String KEY_RETURN_DATA = "return-data";
+ public static final String KEY_DATA = "data";
+ public static final String KEY_SPOTLIGHT_X = "spotlightX";
+ public static final String KEY_SPOTLIGHT_Y = "spotlightY";
+ public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
+ public static final String KEY_OUTPUT_FORMAT = "outputFormat";
+
+ private int mOutputX = 0;
+ private int mOutputY = 0;
+ private boolean mScaleUp = true;
+ private int mAspectX = 0;
+ private int mAspectY = 0;
+ private boolean mSetAsWallpaper = false;
+ private boolean mReturnData = false;
+ private Uri mExtraOutput = null;
+ private String mOutputFormat = null;
+ private boolean mShowWhenLocked = false;
+ private float mSpotlightX = 0;
+ private float mSpotlightY = 0;
+
+ public CropExtras(int outputX, int outputY, boolean scaleUp, int aspectX, int aspectY,
+ boolean setAsWallpaper, boolean returnData, Uri extraOutput, String outputFormat,
+ boolean showWhenLocked, float spotlightX, float spotlightY) {
+ mOutputX = outputX;
+ mOutputY = outputY;
+ mScaleUp = scaleUp;
+ mAspectX = aspectX;
+ mAspectY = aspectY;
+ mSetAsWallpaper = setAsWallpaper;
+ mReturnData = returnData;
+ mExtraOutput = extraOutput;
+ mOutputFormat = outputFormat;
+ mShowWhenLocked = showWhenLocked;
+ mSpotlightX = spotlightX;
+ mSpotlightY = spotlightY;
+ }
+
+ public CropExtras(CropExtras c) {
+ this(c.mOutputX, c.mOutputY, c.mScaleUp, c.mAspectX, c.mAspectY, c.mSetAsWallpaper,
+ c.mReturnData, c.mExtraOutput, c.mOutputFormat, c.mShowWhenLocked,
+ c.mSpotlightX, c.mSpotlightY);
+ }
+
+ public int getOutputX() {
+ return mOutputX;
+ }
+
+ public int getOutputY() {
+ return mOutputY;
+ }
+
+ public boolean getScaleUp() {
+ return mScaleUp;
+ }
+
+ public int getAspectX() {
+ return mAspectX;
+ }
+
+ public int getAspectY() {
+ return mAspectY;
+ }
+
+ public boolean getSetAsWallpaper() {
+ return mSetAsWallpaper;
+ }
+
+ public boolean getReturnData() {
+ return mReturnData;
+ }
+
+ public Uri getExtraOutput() {
+ return mExtraOutput;
+ }
+
+ public String getOutputFormat() {
+ return mOutputFormat;
+ }
+
+ public boolean getShowWhenLocked() {
+ return mShowWhenLocked;
+ }
+
+ public float getSpotlightX() {
+ return mSpotlightX;
+ }
+
+ public float getSpotlightY() {
+ return mSpotlightY;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropMath.java b/src/com/android/gallery3d/filtershow/crop/CropMath.java
new file mode 100644
index 000000000..02c65310e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropMath.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.crop;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+
+import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
+
+import java.util.Arrays;
+
+public class CropMath {
+
+ /**
+ * Gets a float array of the 2D coordinates representing a rectangles
+ * corners.
+ * The order of the corners in the float array is:
+ * 0------->1
+ * ^ |
+ * | v
+ * 3<-------2
+ *
+ * @param r the rectangle to get the corners of
+ * @return the float array of corners (8 floats)
+ */
+
+ public static float[] getCornersFromRect(RectF r) {
+ float[] corners = {
+ r.left, r.top,
+ r.right, r.top,
+ r.right, r.bottom,
+ r.left, r.bottom
+ };
+ return corners;
+ }
+
+ /**
+ * Returns true iff point (x, y) is within or on the rectangle's bounds.
+ * RectF's "contains" function treats points on the bottom and right bound
+ * as not being contained.
+ *
+ * @param r the rectangle
+ * @param x the x value of the point
+ * @param y the y value of the point
+ * @return
+ */
+ public static boolean inclusiveContains(RectF r, float x, float y) {
+ return !(x > r.right || x < r.left || y > r.bottom || y < r.top);
+ }
+
+ /**
+ * Takes an array of 2D coordinates representing corners and returns the
+ * smallest rectangle containing those coordinates.
+ *
+ * @param array array of 2D coordinates
+ * @return smallest rectangle containing coordinates
+ */
+ public static RectF trapToRect(float[] array) {
+ RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
+ Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
+ for (int i = 1; i < array.length; i += 2) {
+ float x = array[i - 1];
+ float y = array[i];
+ r.left = (x < r.left) ? x : r.left;
+ r.top = (y < r.top) ? y : r.top;
+ r.right = (x > r.right) ? x : r.right;
+ r.bottom = (y > r.bottom) ? y : r.bottom;
+ }
+ r.sort();
+ return r;
+ }
+
+ /**
+ * If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the
+ * image bound rectangle, clamps it to the edge of the rectangle.
+ *
+ * @param imageBound the rectangle to clamp edge points to.
+ * @param array an array of points to clamp to the rectangle, gets set to
+ * the clamped values.
+ */
+ public static void getEdgePoints(RectF imageBound, float[] array) {
+ if (array.length < 2)
+ return;
+ for (int x = 0; x < array.length; x += 2) {
+ array[x] = GeometryMathUtils.clamp(array[x], imageBound.left, imageBound.right);
+ array[x + 1] = GeometryMathUtils.clamp(array[x + 1], imageBound.top, imageBound.bottom);
+ }
+ }
+
+ /**
+ * Takes a point and the corners of a rectangle and returns the two corners
+ * representing the side of the rectangle closest to the point.
+ *
+ * @param point the point which is being checked
+ * @param corners the corners of the rectangle
+ * @return two corners representing the side of the rectangle
+ */
+ public static float[] closestSide(float[] point, float[] corners) {
+ int len = corners.length;
+ float oldMag = Float.POSITIVE_INFINITY;
+ float[] bestLine = null;
+ for (int i = 0; i < len; i += 2) {
+ float[] line = {
+ corners[i], corners[(i + 1) % len],
+ corners[(i + 2) % len], corners[(i + 3) % len]
+ };
+ float mag = GeometryMathUtils.vectorLength(
+ GeometryMathUtils.shortestVectorFromPointToLine(point, line));
+ if (mag < oldMag) {
+ oldMag = mag;
+ bestLine = line;
+ }
+ }
+ return bestLine;
+ }
+
+ /**
+ * Checks if a given point is within a rotated rectangle.
+ *
+ * @param point 2D point to check
+ * @param bound rectangle to rotate
+ * @param rot angle of rotation about rectangle center
+ * @return true if point is within rotated rectangle
+ */
+ public static boolean pointInRotatedRect(float[] point, RectF bound, float rot) {
+ Matrix m = new Matrix();
+ float[] p = Arrays.copyOf(point, 2);
+ m.setRotate(rot, bound.centerX(), bound.centerY());
+ Matrix m0 = new Matrix();
+ if (!m.invert(m0))
+ return false;
+ m0.mapPoints(p);
+ return inclusiveContains(bound, p[0], p[1]);
+ }
+
+ /**
+ * Checks if a given point is within a rotated rectangle.
+ *
+ * @param point 2D point to check
+ * @param rotatedRect corners of a rotated rectangle
+ * @param center center of the rotated rectangle
+ * @return true if point is within rotated rectangle
+ */
+ public static boolean pointInRotatedRect(float[] point, float[] rotatedRect, float[] center) {
+ RectF unrotated = new RectF();
+ float angle = getUnrotated(rotatedRect, center, unrotated);
+ return pointInRotatedRect(point, unrotated, angle);
+ }
+
+ /**
+ * Resizes rectangle to have a certain aspect ratio (center remains
+ * stationary).
+ *
+ * @param r rectangle to resize
+ * @param w new width aspect
+ * @param h new height aspect
+ */
+ public static void fixAspectRatio(RectF r, float w, float h) {
+ float scale = Math.min(r.width() / w, r.height() / h);
+ float centX = r.centerX();
+ float centY = r.centerY();
+ float hw = scale * w / 2;
+ float hh = scale * h / 2;
+ r.set(centX - hw, centY - hh, centX + hw, centY + hh);
+ }
+
+ /**
+ * Resizes rectangle to have a certain aspect ratio (center remains
+ * stationary) while constraining it to remain within the original rect.
+ *
+ * @param r rectangle to resize
+ * @param w new width aspect
+ * @param h new height aspect
+ */
+ public static void fixAspectRatioContained(RectF r, float w, float h) {
+ float origW = r.width();
+ float origH = r.height();
+ float origA = origW / origH;
+ float a = w / h;
+ float finalW = origW;
+ float finalH = origH;
+ if (origA < a) {
+ finalH = origW / a;
+ r.top = r.centerY() - finalH / 2;
+ r.bottom = r.top + finalH;
+ } else {
+ finalW = origH * a;
+ r.left = r.centerX() - finalW / 2;
+ r.right = r.left + finalW;
+ }
+ }
+
+ /**
+ * Stretches/Scales/Translates photoBounds to match displayBounds, and
+ * and returns an equivalent stretched/scaled/translated cropBounds or null
+ * if the mapping is invalid.
+ * @param cropBounds cropBounds to transform
+ * @param photoBounds original bounds containing crop bounds
+ * @param displayBounds final bounds for crop
+ * @return the stretched/scaled/translated crop bounds that fit within displayBounds
+ */
+ public static RectF getScaledCropBounds(RectF cropBounds, RectF photoBounds,
+ RectF displayBounds) {
+ Matrix m = new Matrix();
+ m.setRectToRect(photoBounds, displayBounds, Matrix.ScaleToFit.FILL);
+ RectF trueCrop = new RectF(cropBounds);
+ if (!m.mapRect(trueCrop)) {
+ return null;
+ }
+ return trueCrop;
+ }
+
+ /**
+ * Returns the size of a bitmap in bytes.
+ * @param bmap bitmap whose size to check
+ * @return bitmap size in bytes
+ */
+ public static int getBitmapSize(Bitmap bmap) {
+ return bmap.getRowBytes() * bmap.getHeight();
+ }
+
+ /**
+ * Constrains rotation to be in [0, 90, 180, 270] rounding down.
+ * @param rotation any rotation value, in degrees
+ * @return integer rotation in [0, 90, 180, 270]
+ */
+ public static int constrainedRotation(float rotation) {
+ int r = (int) ((rotation % 360) / 90);
+ r = (r < 0) ? (r + 4) : r;
+ return r * 90;
+ }
+
+ private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) {
+ float dy = rotatedRect[1] - rotatedRect[3];
+ float dx = rotatedRect[0] - rotatedRect[2];
+ float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI);
+ Matrix m = new Matrix();
+ m.setRotate(-angle, center[0], center[1]);
+ float[] unrotatedRect = new float[rotatedRect.length];
+ m.mapPoints(unrotatedRect, rotatedRect);
+ unrotated.set(trapToRect(unrotatedRect));
+ return angle;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropObject.java b/src/com/android/gallery3d/filtershow/crop/CropObject.java
new file mode 100644
index 000000000..b98ed1bfd
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropObject.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2013 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.crop;
+
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
+
+public class CropObject {
+ private BoundedRect mBoundedRect;
+ private float mAspectWidth = 1;
+ private float mAspectHeight = 1;
+ private boolean mFixAspectRatio = false;
+ private float mRotation = 0;
+ private float mTouchTolerance = 45;
+ private float mMinSideSize = 20;
+
+ public static final int MOVE_NONE = 0;
+ // Sides
+ public static final int MOVE_LEFT = 1;
+ public static final int MOVE_TOP = 2;
+ public static final int MOVE_RIGHT = 4;
+ public static final int MOVE_BOTTOM = 8;
+ public static final int MOVE_BLOCK = 16;
+
+ // Corners
+ public static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT;
+ public static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT;
+ public static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT;
+ public static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT;
+
+ private int mMovingEdges = MOVE_NONE;
+
+ public CropObject(Rect outerBound, Rect innerBound, int outerAngle) {
+ mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
+ }
+
+ public CropObject(RectF outerBound, RectF innerBound, int outerAngle) {
+ mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
+ }
+
+ public void resetBoundsTo(RectF inner, RectF outer) {
+ mBoundedRect.resetTo(0, outer, inner);
+ }
+
+ public void getInnerBounds(RectF r) {
+ mBoundedRect.setToInner(r);
+ }
+
+ public void getOuterBounds(RectF r) {
+ mBoundedRect.setToOuter(r);
+ }
+
+ public RectF getInnerBounds() {
+ return mBoundedRect.getInner();
+ }
+
+ public RectF getOuterBounds() {
+ return mBoundedRect.getOuter();
+ }
+
+ public int getSelectState() {
+ return mMovingEdges;
+ }
+
+ public boolean isFixedAspect() {
+ return mFixAspectRatio;
+ }
+
+ public void rotateOuter(int angle) {
+ mRotation = angle % 360;
+ mBoundedRect.setRotation(mRotation);
+ clearSelectState();
+ }
+
+ public boolean setInnerAspectRatio(float width, float height) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Width and Height must be greater than zero");
+ }
+ RectF inner = mBoundedRect.getInner();
+ CropMath.fixAspectRatioContained(inner, width, height);
+ if (inner.width() < mMinSideSize || inner.height() < mMinSideSize) {
+ return false;
+ }
+ mAspectWidth = width;
+ mAspectHeight = height;
+ mFixAspectRatio = true;
+ mBoundedRect.setInner(inner);
+ clearSelectState();
+ return true;
+ }
+
+ public void setTouchTolerance(float tolerance) {
+ if (tolerance <= 0) {
+ throw new IllegalArgumentException("Tolerance must be greater than zero");
+ }
+ mTouchTolerance = tolerance;
+ }
+
+ public void setMinInnerSideSize(float minSide) {
+ if (minSide <= 0) {
+ throw new IllegalArgumentException("Min dide must be greater than zero");
+ }
+ mMinSideSize = minSide;
+ }
+
+ public void unsetAspectRatio() {
+ mFixAspectRatio = false;
+ clearSelectState();
+ }
+
+ public boolean hasSelectedEdge() {
+ return mMovingEdges != MOVE_NONE;
+ }
+
+ public static boolean checkCorner(int selected) {
+ return selected == TOP_LEFT || selected == TOP_RIGHT || selected == BOTTOM_RIGHT
+ || selected == BOTTOM_LEFT;
+ }
+
+ public static boolean checkEdge(int selected) {
+ return selected == MOVE_LEFT || selected == MOVE_TOP || selected == MOVE_RIGHT
+ || selected == MOVE_BOTTOM;
+ }
+
+ public static boolean checkBlock(int selected) {
+ return selected == MOVE_BLOCK;
+ }
+
+ public static boolean checkValid(int selected) {
+ return selected == MOVE_NONE || checkBlock(selected) || checkEdge(selected)
+ || checkCorner(selected);
+ }
+
+ public void clearSelectState() {
+ mMovingEdges = MOVE_NONE;
+ }
+
+ public int wouldSelectEdge(float x, float y) {
+ int edgeSelected = calculateSelectedEdge(x, y);
+ if (edgeSelected != MOVE_NONE && edgeSelected != MOVE_BLOCK) {
+ return edgeSelected;
+ }
+ return MOVE_NONE;
+ }
+
+ public boolean selectEdge(int edge) {
+ if (!checkValid(edge)) {
+ // temporary
+ throw new IllegalArgumentException("bad edge selected");
+ // return false;
+ }
+ if ((mFixAspectRatio && !checkCorner(edge)) && !checkBlock(edge) && edge != MOVE_NONE) {
+ // temporary
+ throw new IllegalArgumentException("bad corner selected");
+ // return false;
+ }
+ mMovingEdges = edge;
+ return true;
+ }
+
+ public boolean selectEdge(float x, float y) {
+ int edgeSelected = calculateSelectedEdge(x, y);
+ if (mFixAspectRatio) {
+ edgeSelected = fixEdgeToCorner(edgeSelected);
+ }
+ if (edgeSelected == MOVE_NONE) {
+ return false;
+ }
+ return selectEdge(edgeSelected);
+ }
+
+ public boolean moveCurrentSelection(float dX, float dY) {
+ if (mMovingEdges == MOVE_NONE) {
+ return false;
+ }
+ RectF crop = mBoundedRect.getInner();
+
+ float minWidthHeight = mMinSideSize;
+
+ int movingEdges = mMovingEdges;
+ if (movingEdges == MOVE_BLOCK) {
+ mBoundedRect.moveInner(dX, dY);
+ return true;
+ } else {
+ float dx = 0;
+ float dy = 0;
+
+ if ((movingEdges & MOVE_LEFT) != 0) {
+ dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left;
+ }
+ if ((movingEdges & MOVE_TOP) != 0) {
+ dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top;
+ }
+ if ((movingEdges & MOVE_RIGHT) != 0) {
+ dx = Math.max(crop.right + dX, crop.left + minWidthHeight)
+ - crop.right;
+ }
+ if ((movingEdges & MOVE_BOTTOM) != 0) {
+ dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight)
+ - crop.bottom;
+ }
+
+ if (mFixAspectRatio) {
+ float[] l1 = {
+ crop.left, crop.bottom
+ };
+ float[] l2 = {
+ crop.right, crop.top
+ };
+ if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) {
+ l1[1] = crop.top;
+ l2[1] = crop.bottom;
+ }
+ float[] b = {
+ l1[0] - l2[0], l1[1] - l2[1]
+ };
+ float[] disp = {
+ dx, dy
+ };
+ float[] bUnit = GeometryMathUtils.normalize(b);
+ float sp = GeometryMathUtils.scalarProjection(disp, bUnit);
+ dx = sp * bUnit[0];
+ dy = sp * bUnit[1];
+ RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy);
+
+ mBoundedRect.fixedAspectResizeInner(newCrop);
+ } else {
+ if ((movingEdges & MOVE_LEFT) != 0) {
+ crop.left += dx;
+ }
+ if ((movingEdges & MOVE_TOP) != 0) {
+ crop.top += dy;
+ }
+ if ((movingEdges & MOVE_RIGHT) != 0) {
+ crop.right += dx;
+ }
+ if ((movingEdges & MOVE_BOTTOM) != 0) {
+ crop.bottom += dy;
+ }
+ mBoundedRect.resizeInner(crop);
+ }
+ }
+ return true;
+ }
+
+ // Helper methods
+
+ private int calculateSelectedEdge(float x, float y) {
+ RectF cropped = mBoundedRect.getInner();
+
+ float left = Math.abs(x - cropped.left);
+ float right = Math.abs(x - cropped.right);
+ float top = Math.abs(y - cropped.top);
+ float bottom = Math.abs(y - cropped.bottom);
+
+ int edgeSelected = MOVE_NONE;
+ // Check left or right.
+ if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
+ && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) {
+ edgeSelected |= MOVE_LEFT;
+ }
+ else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
+ && ((y - mTouchTolerance) <= cropped.bottom)) {
+ edgeSelected |= MOVE_RIGHT;
+ }
+
+ // Check top or bottom.
+ if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
+ && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) {
+ edgeSelected |= MOVE_TOP;
+ }
+ else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
+ && ((x - mTouchTolerance) <= cropped.right)) {
+ edgeSelected |= MOVE_BOTTOM;
+ }
+ return edgeSelected;
+ }
+
+ private static RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) {
+ RectF newCrop = null;
+ // Fix opposite corner in place and move sides
+ if (moving_corner == BOTTOM_RIGHT) {
+ newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height()
+ + dy);
+ } else if (moving_corner == BOTTOM_LEFT) {
+ newCrop = new RectF(r.right - r.width() + dx, r.top, r.right, r.top + r.height()
+ + dy);
+ } else if (moving_corner == TOP_LEFT) {
+ newCrop = new RectF(r.right - r.width() + dx, r.bottom - r.height() + dy,
+ r.right, r.bottom);
+ } else if (moving_corner == TOP_RIGHT) {
+ newCrop = new RectF(r.left, r.bottom - r.height() + dy, r.left
+ + r.width() + dx, r.bottom);
+ }
+ return newCrop;
+ }
+
+ private static int fixEdgeToCorner(int moving_edges) {
+ if (moving_edges == MOVE_LEFT) {
+ moving_edges |= MOVE_TOP;
+ }
+ if (moving_edges == MOVE_TOP) {
+ moving_edges |= MOVE_LEFT;
+ }
+ if (moving_edges == MOVE_RIGHT) {
+ moving_edges |= MOVE_BOTTOM;
+ }
+ if (moving_edges == MOVE_BOTTOM) {
+ moving_edges |= MOVE_RIGHT;
+ }
+ return moving_edges;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropView.java b/src/com/android/gallery3d/filtershow/crop/CropView.java
new file mode 100644
index 000000000..bbb7cfd4c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropView.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2013 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.crop;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.DashPathEffect;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+
+public class CropView extends View {
+ private static final String LOGTAG = "CropView";
+
+ private RectF mImageBounds = new RectF();
+ private RectF mScreenBounds = new RectF();
+ private RectF mScreenImageBounds = new RectF();
+ private RectF mScreenCropBounds = new RectF();
+ private Rect mShadowBounds = new Rect();
+
+ private Bitmap mBitmap;
+ private Paint mPaint = new Paint();
+
+ private NinePatchDrawable mShadow;
+ private CropObject mCropObj = null;
+ private Drawable mCropIndicator;
+ private int mIndicatorSize;
+ private int mRotation = 0;
+ private boolean mMovingBlock = false;
+ private Matrix mDisplayMatrix = null;
+ private Matrix mDisplayMatrixInverse = null;
+ private boolean mDirty = false;
+
+ private float mPrevX = 0;
+ private float mPrevY = 0;
+ private float mSpotX = 0;
+ private float mSpotY = 0;
+ private boolean mDoSpot = false;
+
+ private int mShadowMargin = 15;
+ private int mMargin = 32;
+ private int mOverlayShadowColor = 0xCF000000;
+ private int mOverlayWPShadowColor = 0x5F000000;
+ private int mWPMarkerColor = 0x7FFFFFFF;
+ private int mMinSideSize = 90;
+ private int mTouchTolerance = 40;
+ private float mDashOnLength = 20;
+ private float mDashOffLength = 10;
+
+ private enum Mode {
+ NONE, MOVE
+ }
+
+ private Mode mState = Mode.NONE;
+
+ public CropView(Context context) {
+ super(context);
+ setup(context);
+ }
+
+ public CropView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setup(context);
+ }
+
+ public CropView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setup(context);
+ }
+
+ private void setup(Context context) {
+ Resources rsc = context.getResources();
+ mShadow = (NinePatchDrawable) rsc.getDrawable(R.drawable.geometry_shadow);
+ mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
+ mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
+ mShadowMargin = (int) rsc.getDimension(R.dimen.shadow_margin);
+ mMargin = (int) rsc.getDimension(R.dimen.preview_margin);
+ mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
+ mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
+ mOverlayShadowColor = (int) rsc.getColor(R.color.crop_shadow_color);
+ mOverlayWPShadowColor = (int) rsc.getColor(R.color.crop_shadow_wp_color);
+ mWPMarkerColor = (int) rsc.getColor(R.color.crop_wp_markers);
+ mDashOnLength = rsc.getDimension(R.dimen.wp_selector_dash_length);
+ mDashOffLength = rsc.getDimension(R.dimen.wp_selector_off_length);
+ }
+
+ public void initialize(Bitmap image, RectF newCropBounds, RectF newPhotoBounds, int rotation) {
+ mBitmap = image;
+ if (mCropObj != null) {
+ RectF crop = mCropObj.getInnerBounds();
+ RectF containing = mCropObj.getOuterBounds();
+ if (crop != newCropBounds || containing != newPhotoBounds
+ || mRotation != rotation) {
+ mRotation = rotation;
+ mCropObj.resetBoundsTo(newCropBounds, newPhotoBounds);
+ clearDisplay();
+ }
+ } else {
+ mRotation = rotation;
+ mCropObj = new CropObject(newPhotoBounds, newCropBounds, 0);
+ clearDisplay();
+ }
+ }
+
+ public RectF getCrop() {
+ return mCropObj.getInnerBounds();
+ }
+
+ public RectF getPhoto() {
+ return mCropObj.getOuterBounds();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
+ return true;
+ }
+ float[] touchPoint = {
+ x, y
+ };
+ mDisplayMatrixInverse.mapPoints(touchPoint);
+ x = touchPoint[0];
+ y = touchPoint[1];
+ switch (event.getActionMasked()) {
+ case (MotionEvent.ACTION_DOWN):
+ if (mState == Mode.NONE) {
+ if (!mCropObj.selectEdge(x, y)) {
+ mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
+ }
+ mPrevX = x;
+ mPrevY = y;
+ mState = Mode.MOVE;
+ }
+ break;
+ case (MotionEvent.ACTION_UP):
+ if (mState == Mode.MOVE) {
+ mCropObj.selectEdge(CropObject.MOVE_NONE);
+ mMovingBlock = false;
+ mPrevX = x;
+ mPrevY = y;
+ mState = Mode.NONE;
+ }
+ break;
+ case (MotionEvent.ACTION_MOVE):
+ if (mState == Mode.MOVE) {
+ float dx = x - mPrevX;
+ float dy = y - mPrevY;
+ mCropObj.moveCurrentSelection(dx, dy);
+ mPrevX = x;
+ mPrevY = y;
+ }
+ break;
+ default:
+ break;
+ }
+ invalidate();
+ return true;
+ }
+
+ private void reset() {
+ Log.w(LOGTAG, "crop reset called");
+ mState = Mode.NONE;
+ mCropObj = null;
+ mRotation = 0;
+ mMovingBlock = false;
+ clearDisplay();
+ }
+
+ private void clearDisplay() {
+ mDisplayMatrix = null;
+ mDisplayMatrixInverse = null;
+ invalidate();
+ }
+
+ protected void configChanged() {
+ mDirty = true;
+ }
+
+ public void applyFreeAspect() {
+ mCropObj.unsetAspectRatio();
+ invalidate();
+ }
+
+ public void applyOriginalAspect() {
+ RectF outer = mCropObj.getOuterBounds();
+ float w = outer.width();
+ float h = outer.height();
+ if (w > 0 && h > 0) {
+ applyAspect(w, h);
+ mCropObj.resetBoundsTo(outer, outer);
+ } else {
+ Log.w(LOGTAG, "failed to set aspect ratio original");
+ }
+ }
+
+ public void applySquareAspect() {
+ applyAspect(1, 1);
+ }
+
+ public void applyAspect(float x, float y) {
+ if (x <= 0 || y <= 0) {
+ throw new IllegalArgumentException("Bad arguments to applyAspect");
+ }
+ // If we are rotated by 90 degrees from horizontal, swap x and y
+ if (((mRotation < 0) ? -mRotation : mRotation) % 180 == 90) {
+ float tmp = x;
+ x = y;
+ y = tmp;
+ }
+ if (!mCropObj.setInnerAspectRatio(x, y)) {
+ Log.w(LOGTAG, "failed to set aspect ratio");
+ }
+ invalidate();
+ }
+
+ public void setWallpaperSpotlight(float spotlightX, float spotlightY) {
+ mSpotX = spotlightX;
+ mSpotY = spotlightY;
+ if (mSpotX > 0 && mSpotY > 0) {
+ mDoSpot = true;
+ }
+ }
+
+ public void unsetWallpaperSpotlight() {
+ mDoSpot = false;
+ }
+
+ /**
+ * Rotates first d bits in integer x to the left some number of times.
+ */
+ private int bitCycleLeft(int x, int times, int d) {
+ int mask = (1 << d) - 1;
+ int mout = x & mask;
+ times %= d;
+ int hi = mout >> (d - times);
+ int low = (mout << times) & mask;
+ int ret = x & ~mask;
+ ret |= low;
+ ret |= hi;
+ return ret;
+ }
+
+ /**
+ * Find the selected edge or corner in screen coordinates.
+ */
+ private int decode(int movingEdges, float rotation) {
+ int rot = CropMath.constrainedRotation(rotation);
+ switch (rot) {
+ case 90:
+ return bitCycleLeft(movingEdges, 1, 4);
+ case 180:
+ return bitCycleLeft(movingEdges, 2, 4);
+ case 270:
+ return bitCycleLeft(movingEdges, 3, 4);
+ default:
+ return movingEdges;
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ if (mBitmap == null) {
+ return;
+ }
+ if (mDirty) {
+ mDirty = false;
+ clearDisplay();
+ }
+
+ mImageBounds = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ mScreenBounds = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
+ mScreenBounds.inset(mMargin, mMargin);
+
+ // If crop object doesn't exist, create it and update it from master
+ // state
+ if (mCropObj == null) {
+ reset();
+ mCropObj = new CropObject(mImageBounds, mImageBounds, 0);
+ }
+
+ // If display matrix doesn't exist, create it and its dependencies
+ if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
+ mDisplayMatrix = new Matrix();
+ mDisplayMatrix.reset();
+ if (!CropDrawingUtils.setImageToScreenMatrix(mDisplayMatrix, mImageBounds, mScreenBounds,
+ mRotation)) {
+ Log.w(LOGTAG, "failed to get screen matrix");
+ mDisplayMatrix = null;
+ return;
+ }
+ mDisplayMatrixInverse = new Matrix();
+ mDisplayMatrixInverse.reset();
+ if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) {
+ Log.w(LOGTAG, "could not invert display matrix");
+ mDisplayMatrixInverse = null;
+ return;
+ }
+ // Scale min side and tolerance by display matrix scale factor
+ mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
+ mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
+ }
+
+ mScreenImageBounds.set(mImageBounds);
+
+ // Draw background shadow
+ if (mDisplayMatrix.mapRect(mScreenImageBounds)) {
+ int margin = (int) mDisplayMatrix.mapRadius(mShadowMargin);
+ mScreenImageBounds.roundOut(mShadowBounds);
+ mShadowBounds.set(mShadowBounds.left - margin, mShadowBounds.top -
+ margin, mShadowBounds.right + margin, mShadowBounds.bottom + margin);
+ mShadow.setBounds(mShadowBounds);
+ mShadow.draw(canvas);
+ }
+
+ mPaint.setAntiAlias(true);
+ mPaint.setFilterBitmap(true);
+ // Draw actual bitmap
+ canvas.drawBitmap(mBitmap, mDisplayMatrix, mPaint);
+
+ mCropObj.getInnerBounds(mScreenCropBounds);
+
+ if (mDisplayMatrix.mapRect(mScreenCropBounds)) {
+
+ // Draw overlay shadows
+ Paint p = new Paint();
+ p.setColor(mOverlayShadowColor);
+ p.setStyle(Paint.Style.FILL);
+ CropDrawingUtils.drawShadows(canvas, p, mScreenCropBounds, mScreenImageBounds);
+
+ // Draw crop rect and markers
+ CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
+ if (!mDoSpot) {
+ CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
+ } else {
+ Paint wpPaint = new Paint();
+ wpPaint.setColor(mWPMarkerColor);
+ wpPaint.setStrokeWidth(3);
+ wpPaint.setStyle(Paint.Style.STROKE);
+ wpPaint.setPathEffect(new DashPathEffect(new float[]
+ {mDashOnLength, mDashOnLength + mDashOffLength}, 0));
+ p.setColor(mOverlayWPShadowColor);
+ CropDrawingUtils.drawWallpaperSelectionFrame(canvas, mScreenCropBounds,
+ mSpotX, mSpotY, wpPaint, p);
+ }
+ CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize,
+ mScreenCropBounds, mCropObj.isFixedAspect(), decode(mCropObj.getSelectState(), mRotation));
+ }
+
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java b/src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java
new file mode 100644
index 000000000..e18d3104f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2013 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.data;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public class FilterStackDBHelper extends SQLiteOpenHelper {
+
+ public static final int DATABASE_VERSION = 1;
+ public static final String DATABASE_NAME = "filterstacks.db";
+ private static final String SQL_CREATE_TABLE = "CREATE TABLE ";
+
+ public static interface FilterStack {
+ /** The row uid */
+ public static final String _ID = "_id";
+ /** The table name */
+ public static final String TABLE = "filterstack";
+ /** The stack name */
+ public static final String STACK_ID = "stack_id";
+ /** A serialized stack of filters. */
+ public static final String FILTER_STACK= "stack";
+ }
+
+ private static final String[][] CREATE_FILTER_STACK = {
+ { FilterStack._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+ { FilterStack.STACK_ID, "TEXT" },
+ { FilterStack.FILTER_STACK, "BLOB" },
+ };
+
+ public FilterStackDBHelper(Context context, String name, int version) {
+ super(context, name, null, version);
+ }
+
+ public FilterStackDBHelper(Context context, String name) {
+ this(context, name, DATABASE_VERSION);
+ }
+
+ public FilterStackDBHelper(Context context) {
+ this(context, DATABASE_NAME);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ createTable(db, FilterStack.TABLE, CREATE_FILTER_STACK);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ dropTable(db, FilterStack.TABLE);
+ onCreate(db);
+ }
+
+ protected static void createTable(SQLiteDatabase db, String table, String[][] columns) {
+ StringBuilder create = new StringBuilder(SQL_CREATE_TABLE);
+ create.append(table).append('(');
+ boolean first = true;
+ for (String[] column : columns) {
+ if (!first) {
+ create.append(',');
+ }
+ first = false;
+ for (String val : column) {
+ create.append(val).append(' ');
+ }
+ }
+ create.append(')');
+ db.beginTransaction();
+ try {
+ db.execSQL(create.toString());
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ protected static void dropTable(SQLiteDatabase db, String table) {
+ db.beginTransaction();
+ try {
+ db.execSQL("drop table if exists " + table);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/data/FilterStackSource.java b/src/com/android/gallery3d/filtershow/data/FilterStackSource.java
new file mode 100644
index 000000000..d283771b4
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/data/FilterStackSource.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2013 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.data;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.gallery3d.filtershow.data.FilterStackDBHelper.FilterStack;
+import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FilterStackSource {
+ private static final String LOGTAG = "FilterStackSource";
+
+ private SQLiteDatabase database = null;
+ private final FilterStackDBHelper dbHelper;
+
+ public FilterStackSource(Context context) {
+ dbHelper = new FilterStackDBHelper(context);
+ }
+
+ public void open() {
+ try {
+ database = dbHelper.getWritableDatabase();
+ } catch (SQLiteException e) {
+ Log.w(LOGTAG, "could not open database", e);
+ }
+ }
+
+ public void close() {
+ database = null;
+ dbHelper.close();
+ }
+
+ public boolean insertStack(String stackName, byte[] stackBlob) {
+ boolean ret = true;
+ ContentValues val = new ContentValues();
+ val.put(FilterStack.STACK_ID, stackName);
+ val.put(FilterStack.FILTER_STACK, stackBlob);
+ database.beginTransaction();
+ try {
+ ret = (-1 != database.insert(FilterStack.TABLE, null, val));
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ return ret;
+ }
+
+ public void updateStackName(int id, String stackName) {
+ ContentValues val = new ContentValues();
+ val.put(FilterStack.STACK_ID, stackName);
+ database.beginTransaction();
+ try {
+ database.update(FilterStack.TABLE, val, FilterStack._ID + " = ?",
+ new String[] { "" + id});
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ public boolean removeStack(int id) {
+ boolean ret = true;
+ database.beginTransaction();
+ try {
+ ret = (0 != database.delete(FilterStack.TABLE, FilterStack._ID + " = ?",
+ new String[] { "" + id }));
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ return ret;
+ }
+
+ public void removeAllStacks() {
+ database.beginTransaction();
+ try {
+ database.delete(FilterStack.TABLE, null, null);
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ public byte[] getStack(String stackName) {
+ byte[] ret = null;
+ Cursor c = null;
+ database.beginTransaction();
+ try {
+ c = database.query(FilterStack.TABLE,
+ new String[] { FilterStack.FILTER_STACK },
+ FilterStack.STACK_ID + " = ?",
+ new String[] { stackName }, null, null, null, null);
+ if (c != null && c.moveToFirst() && !c.isNull(0)) {
+ ret = c.getBlob(0);
+ }
+ database.setTransactionSuccessful();
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ database.endTransaction();
+ }
+ return ret;
+ }
+
+ public ArrayList<FilterUserPresetRepresentation> getAllUserPresets() {
+ ArrayList<FilterUserPresetRepresentation> ret =
+ new ArrayList<FilterUserPresetRepresentation>();
+
+ Cursor c = null;
+ database.beginTransaction();
+ try {
+ c = database.query(FilterStack.TABLE,
+ new String[] { FilterStack._ID,
+ FilterStack.STACK_ID,
+ FilterStack.FILTER_STACK },
+ null, null, null, null, null, null);
+ if (c != null) {
+ boolean loopCheck = c.moveToFirst();
+ while (loopCheck) {
+ int id = c.getInt(0);
+ String name = (c.isNull(1)) ? null : c.getString(1);
+ byte[] b = (c.isNull(2)) ? null : c.getBlob(2);
+ String json = new String(b);
+
+ ImagePreset preset = new ImagePreset();
+ preset.readJsonFromString(json);
+ FilterUserPresetRepresentation representation =
+ new FilterUserPresetRepresentation(name, preset, id);
+ ret.add(representation);
+ loopCheck = c.moveToNext();
+ }
+ }
+ database.setTransactionSuccessful();
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ database.endTransaction();
+ }
+
+ return ret;
+ }
+
+ public List<Pair<String, byte[]>> getAllStacks() {
+ List<Pair<String, byte[]>> ret = new ArrayList<Pair<String, byte[]>>();
+ Cursor c = null;
+ database.beginTransaction();
+ try {
+ c = database.query(FilterStack.TABLE,
+ new String[] { FilterStack.STACK_ID, FilterStack.FILTER_STACK },
+ null, null, null, null, null, null);
+ if (c != null) {
+ boolean loopCheck = c.moveToFirst();
+ while (loopCheck) {
+ String name = (c.isNull(0)) ? null : c.getString(0);
+ byte[] b = (c.isNull(1)) ? null : c.getBlob(1);
+ ret.add(new Pair<String, byte[]>(name, b));
+ loopCheck = c.moveToNext();
+ }
+ }
+ database.setTransactionSuccessful();
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ database.endTransaction();
+ }
+ if (ret.size() <= 0) {
+ return null;
+ }
+ return ret;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/data/UserPresetsManager.java b/src/com/android/gallery3d/filtershow/data/UserPresetsManager.java
new file mode 100644
index 000000000..114cd3ebc
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/data/UserPresetsManager.java
@@ -0,0 +1,149 @@
+package com.android.gallery3d.filtershow.data;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+import java.util.ArrayList;
+
+public class UserPresetsManager implements Handler.Callback {
+
+ private static final String LOGTAG = "UserPresetsManager";
+
+ private FilterShowActivity mActivity;
+ private HandlerThread mHandlerThread = null;
+ private Handler mProcessingHandler = null;
+ private FilterStackSource mUserPresets;
+
+ private static final int LOAD = 1;
+ private static final int LOAD_RESULT = 2;
+ private static final int SAVE = 3;
+ private static final int DELETE = 4;
+ private static final int UPDATE = 5;
+
+ private ArrayList<FilterUserPresetRepresentation> mRepresentations;
+
+ private final Handler mResultHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case LOAD_RESULT:
+ resultLoad(msg);
+ break;
+ }
+ }
+ };
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case LOAD:
+ processLoad();
+ return true;
+ case SAVE:
+ processSave(msg);
+ return true;
+ case DELETE:
+ processDelete(msg);
+ return true;
+ case UPDATE:
+ processUpdate(msg);
+ return true;
+ }
+ return false;
+ }
+
+ public UserPresetsManager(FilterShowActivity context) {
+ mActivity = context;
+ mHandlerThread = new HandlerThread(LOGTAG,
+ android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+ mProcessingHandler = new Handler(mHandlerThread.getLooper(), this);
+ mUserPresets = new FilterStackSource(mActivity);
+ mUserPresets.open();
+ }
+
+ public ArrayList<FilterUserPresetRepresentation> getRepresentations() {
+ return mRepresentations;
+ }
+
+ public void load() {
+ Message msg = mProcessingHandler.obtainMessage(LOAD);
+ mProcessingHandler.sendMessage(msg);
+ }
+
+ public void close() {
+ mUserPresets.close();
+ mHandlerThread.quit();
+ }
+
+ static class SaveOperation {
+ String json;
+ String name;
+ }
+
+ public void save(ImagePreset preset) {
+ Message msg = mProcessingHandler.obtainMessage(SAVE);
+ SaveOperation op = new SaveOperation();
+ op.json = preset.getJsonString(mActivity.getString(R.string.saved));
+ op.name= mActivity.getString(R.string.filtershow_new_preset);
+ msg.obj = op;
+ mProcessingHandler.sendMessage(msg);
+ }
+
+ public void delete(int id) {
+ Message msg = mProcessingHandler.obtainMessage(DELETE);
+ msg.arg1 = id;
+ mProcessingHandler.sendMessage(msg);
+ }
+
+ static class UpdateOperation {
+ int id;
+ String name;
+ }
+
+ public void update(FilterUserPresetRepresentation representation) {
+ Message msg = mProcessingHandler.obtainMessage(UPDATE);
+ UpdateOperation op = new UpdateOperation();
+ op.id = representation.getId();
+ op.name = representation.getName();
+ msg.obj = op;
+ mProcessingHandler.sendMessage(msg);
+ }
+
+ private void processLoad() {
+ ArrayList<FilterUserPresetRepresentation> list = mUserPresets.getAllUserPresets();
+ Message msg = mResultHandler.obtainMessage(LOAD_RESULT);
+ msg.obj = list;
+ mResultHandler.sendMessage(msg);
+ }
+
+ private void resultLoad(Message msg) {
+ mRepresentations =
+ (ArrayList<FilterUserPresetRepresentation>) msg.obj;
+ mActivity.updateUserPresetsFromManager();
+ }
+
+ private void processSave(Message msg) {
+ SaveOperation op = (SaveOperation) msg.obj;
+ mUserPresets.insertStack(op.name, op.json.getBytes());
+ processLoad();
+ }
+
+ private void processDelete(Message msg) {
+ int id = msg.arg1;
+ mUserPresets.removeStack(id);
+ processLoad();
+ }
+
+ private void processUpdate(Message msg) {
+ UpdateOperation op = (UpdateOperation) msg.obj;
+ mUserPresets.updateStackName(op.id, op.name);
+ processLoad();
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/BasicEditor.java b/src/com/android/gallery3d/filtershow/editors/BasicEditor.java
new file mode 100644
index 000000000..af694d811
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/BasicEditor.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.content.Context;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.controller.Control;
+import com.android.gallery3d.filtershow.controller.FilterView;
+import com.android.gallery3d.filtershow.controller.Parameter;
+import com.android.gallery3d.filtershow.controller.ParameterInteger;
+import com.android.gallery3d.filtershow.filters.FilterBasicRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+
+
+/**
+ * The basic editor that all the one parameter filters
+ */
+public class BasicEditor extends ParametricEditor implements ParameterInteger {
+ public static int ID = R.id.basicEditor;
+ private final String LOGTAG = "BasicEditor";
+
+ public BasicEditor() {
+ super(ID, R.layout.filtershow_default_editor, R.id.basicEditor);
+ }
+
+ protected BasicEditor(int id) {
+ super(id, R.layout.filtershow_default_editor, R.id.basicEditor);
+ }
+
+ protected BasicEditor(int id, int layoutID, int viewID) {
+ super(id, layoutID, viewID);
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ super.reflectCurrentFilter();
+ if (getLocalRepresentation() != null && getLocalRepresentation() instanceof FilterBasicRepresentation) {
+ FilterBasicRepresentation interval = (FilterBasicRepresentation) getLocalRepresentation();
+ updateText();
+ }
+ }
+
+ private FilterBasicRepresentation getBasicRepresentation() {
+ FilterRepresentation tmpRep = getLocalRepresentation();
+ if (tmpRep != null && tmpRep instanceof FilterBasicRepresentation) {
+ return (FilterBasicRepresentation) tmpRep;
+
+ }
+ return null;
+ }
+
+ @Override
+ public int getMaximum() {
+ FilterBasicRepresentation rep = getBasicRepresentation();
+ if (rep == null) {
+ return 0;
+ }
+ return rep.getMaximum();
+ }
+
+ @Override
+ public int getMinimum() {
+ FilterBasicRepresentation rep = getBasicRepresentation();
+ if (rep == null) {
+ return 0;
+ }
+ return rep.getMinimum();
+ }
+
+ @Override
+ public int getDefaultValue() {
+ return 0;
+ }
+
+ @Override
+ public int getValue() {
+ FilterBasicRepresentation rep = getBasicRepresentation();
+ if (rep == null) {
+ return 0;
+ }
+ return rep.getValue();
+ }
+
+ @Override
+ public String getValueString() {
+ return null;
+ }
+
+ @Override
+ public void setValue(int value) {
+ FilterBasicRepresentation rep = getBasicRepresentation();
+ if (rep == null) {
+ return;
+ }
+ rep.setValue(value);
+ commitLocalRepresentation();
+ }
+
+ @Override
+ public String getParameterName() {
+ FilterBasicRepresentation rep = getBasicRepresentation();
+ return mContext.getString(rep.getTextId());
+ }
+
+ @Override
+ public String getParameterType() {
+ return sParameterType;
+ }
+
+ @Override
+ public void setController(Control c) {
+ }
+
+ @Override
+ public void setFilterView(FilterView editor) {
+
+ }
+
+ @Override
+ public void copyFrom(Parameter src) {
+
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/Editor.java b/src/com/android/gallery3d/filtershow/editors/Editor.java
new file mode 100644
index 000000000..a9e56e0c1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/Editor.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.controller.Control;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Base class for Editors Must contain a mImageShow and a top level view
+ */
+public class Editor implements OnSeekBarChangeListener, SwapButton.SwapButtonListener {
+ protected Context mContext;
+ protected View mView;
+ protected ImageShow mImageShow;
+ protected FrameLayout mFrameLayout;
+ protected SeekBar mSeekBar;
+ Button mEditTitle;
+ protected Button mFilterTitle;
+ protected int mID;
+ private final String LOGTAG = "Editor";
+ protected boolean mChangesGeometry = false;
+ protected FilterRepresentation mLocalRepresentation = null;
+ protected byte mShowParameter = SHOW_VALUE_UNDEFINED;
+ private Button mButton;
+ public static byte SHOW_VALUE_UNDEFINED = -1;
+ public static byte SHOW_VALUE_OFF = 0;
+ public static byte SHOW_VALUE_INT = 1;
+
+ public static void hackFixStrings(Menu menu) {
+ int count = menu.size();
+ for (int i = 0; i < count; i++) {
+ MenuItem item = menu.getItem(i);
+ item.setTitle(item.getTitle().toString().toUpperCase());
+ }
+ }
+
+ public String calculateUserMessage(Context context, String effectName, Object parameterValue) {
+ return effectName.toUpperCase() + " " + parameterValue;
+ }
+
+ protected Editor(int id) {
+ mID = id;
+ }
+
+ public int getID() {
+ return mID;
+ }
+
+ public byte showParameterValue() {
+ return mShowParameter;
+ }
+
+ public boolean showsSeekBar() {
+ return true;
+ }
+
+ public void setUpEditorUI(View actionButton, View editControl,
+ Button editTitle, Button stateButton) {
+ mEditTitle = editTitle;
+ mFilterTitle = stateButton;
+ mButton = editTitle;
+ setMenuIcon(true);
+ setUtilityPanelUI(actionButton, editControl);
+ }
+
+ public boolean showsPopupIndicator() {
+ return true;
+ }
+
+ /**
+ * @param actionButton the would be the area for menu etc
+ * @param editControl this is the black area for sliders etc
+ */
+ public void setUtilityPanelUI(View actionButton, View editControl) {
+
+ AttributeSet aset;
+ Context context = editControl.getContext();
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ LinearLayout lp = (LinearLayout) inflater.inflate(
+ R.layout.filtershow_seekbar, (ViewGroup) editControl, true);
+ mSeekBar = (SeekBar) lp.findViewById(R.id.primarySeekBar);
+ mSeekBar.setOnSeekBarChangeListener(this);
+
+ if (showsSeekBar()) {
+ mSeekBar.setOnSeekBarChangeListener(this);
+ mSeekBar.setVisibility(View.VISIBLE);
+ } else {
+ mSeekBar.setVisibility(View.INVISIBLE);
+ }
+
+ if (mButton != null) {
+ if (showsPopupIndicator()) {
+ mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0,
+ R.drawable.filtershow_menu_marker, 0);
+ } else {
+ mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
+ }
+ }
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar sbar, int progress, boolean arg2) {
+
+ }
+
+ public void setPanel() {
+
+ }
+
+ public void createEditor(Context context,FrameLayout frameLayout) {
+ mContext = context;
+ mFrameLayout = frameLayout;
+ mLocalRepresentation = null;
+ }
+
+ protected void unpack(int viewid, int layoutid) {
+
+ if (mView == null) {
+ mView = mFrameLayout.findViewById(viewid);
+ if (mView == null) {
+ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService
+ (Context.LAYOUT_INFLATER_SERVICE);
+ mView = inflater.inflate(layoutid, mFrameLayout, false);
+ mFrameLayout.addView(mView, mView.getLayoutParams());
+ }
+ }
+ mImageShow = findImageShow(mView);
+ }
+
+ private ImageShow findImageShow(View view) {
+ if (view instanceof ImageShow) {
+ return (ImageShow) view;
+ }
+ if (!(view instanceof ViewGroup)) {
+ return null;
+ }
+ ViewGroup vg = (ViewGroup) view;
+ int n = vg.getChildCount();
+ for (int i = 0; i < n; i++) {
+ View v = vg.getChildAt(i);
+ if (v instanceof ImageShow) {
+ return (ImageShow) v;
+ } else if (v instanceof ViewGroup) {
+ return findImageShow(v);
+ }
+ }
+ return null;
+ }
+
+ public View getTopLevelView() {
+ return mView;
+ }
+
+ public ImageShow getImageShow() {
+ return mImageShow;
+ }
+
+ public void setVisibility(int visible) {
+ mView.setVisibility(visible);
+ }
+
+ public FilterRepresentation getLocalRepresentation() {
+ if (mLocalRepresentation == null) {
+ ImagePreset preset = MasterImage.getImage().getPreset();
+ FilterRepresentation filterRepresentation = MasterImage.getImage().getCurrentFilterRepresentation();
+ mLocalRepresentation = preset.getFilterRepresentationCopyFrom(filterRepresentation);
+ if (mShowParameter == SHOW_VALUE_UNDEFINED && filterRepresentation != null) {
+ boolean show = filterRepresentation.showParameterValue();
+ mShowParameter = show ? SHOW_VALUE_INT : SHOW_VALUE_OFF;
+ }
+
+ }
+ return mLocalRepresentation;
+ }
+
+ /**
+ * Call this to update the preset in MasterImage with the current representation
+ * returned by getLocalRepresentation. This causes the preview bitmap to be
+ * regenerated.
+ */
+ public void commitLocalRepresentation() {
+ commitLocalRepresentation(getLocalRepresentation());
+ }
+
+ /**
+ * Call this to update the preset in MasterImage with a given representation.
+ * This causes the preview bitmap to be regenerated.
+ */
+ public void commitLocalRepresentation(FilterRepresentation rep) {
+ ArrayList<FilterRepresentation> filter = new ArrayList<FilterRepresentation>(1);
+ filter.add(rep);
+ commitLocalRepresentation(filter);
+ }
+
+ /**
+ * Call this to update the preset in MasterImage with a collection of FilterRepresnations.
+ * This causes the preview bitmap to be regenerated.
+ */
+ public void commitLocalRepresentation(Collection<FilterRepresentation> reps) {
+ ImagePreset preset = MasterImage.getImage().getPreset();
+ preset.updateFilterRepresentations(reps);
+ if (mButton != null) {
+ updateText();
+ }
+ if (mChangesGeometry) {
+ // Regenerate both the filtered and the geometry-only bitmaps
+ MasterImage.getImage().updatePresets(true);
+ } else {
+ // Regenerate only the filtered bitmap.
+ MasterImage.getImage().invalidateFiltersOnly();
+ }
+ preset.fillImageStateAdapter(MasterImage.getImage().getState());
+ }
+
+ /**
+ * This is called in response to a click to apply and leave the editor.
+ */
+ public void finalApplyCalled() {
+ commitLocalRepresentation();
+ }
+
+ protected void updateText() {
+ String s = "";
+ if (mLocalRepresentation != null) {
+ s = mContext.getString(mLocalRepresentation.getTextId());
+ }
+ mButton.setText(calculateUserMessage(mContext, s, ""));
+ }
+
+ /**
+ * called after the filter is set and the select is called
+ */
+ public void reflectCurrentFilter() {
+ mLocalRepresentation = null;
+ FilterRepresentation representation = getLocalRepresentation();
+ if (representation != null && mFilterTitle != null && representation.getTextId() != 0) {
+ String text = mContext.getString(representation.getTextId()).toUpperCase();
+ mFilterTitle.setText(text);
+ updateText();
+ }
+ }
+
+ public boolean useUtilityPanel() {
+ return true;
+ }
+
+ public void openUtilityPanel(LinearLayout mAccessoryViewList) {
+ setMenuIcon(false);
+ if (mImageShow != null) {
+ mImageShow.openUtilityPanel(mAccessoryViewList);
+ }
+ }
+
+ protected void setMenuIcon(boolean on) {
+ mEditTitle.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ 0, 0, on ? R.drawable.filtershow_menu_marker : 0, 0);
+ }
+
+ protected void createMenu(int[] strId, View button) {
+ PopupMenu pmenu = new PopupMenu(mContext, button);
+ Menu menu = pmenu.getMenu();
+ for (int i = 0; i < strId.length; i++) {
+ menu.add(Menu.NONE, Menu.FIRST + i, 0, mContext.getString(strId[i]));
+ }
+ setMenuIcon(true);
+
+ }
+
+ public Control[] getControls() {
+ return null;
+ }
+ @Override
+ public void onStartTrackingTouch(SeekBar arg0) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar arg0) {
+
+ }
+
+ @Override
+ public void swapLeft(MenuItem item) {
+
+ }
+
+ @Override
+ public void swapRight(MenuItem item) {
+
+ }
+
+ public void detach() {
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorChanSat.java b/src/com/android/gallery3d/filtershow/editors/EditorChanSat.java
new file mode 100644
index 000000000..7e31f09ae
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorChanSat.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.controller.BasicParameterStyle;
+import com.android.gallery3d.filtershow.controller.FilterView;
+import com.android.gallery3d.filtershow.controller.Parameter;
+import com.android.gallery3d.filtershow.filters.FilterChanSatRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+import com.android.gallery3d.filtershow.pipeline.RenderingRequest;
+import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
+
+public class EditorChanSat extends ParametricEditor implements OnSeekBarChangeListener, FilterView {
+ public static final int ID = R.id.editorChanSat;
+ private final String LOGTAG = "EditorGrunge";
+ private SwapButton mButton;
+ private final Handler mHandler = new Handler();
+
+ int[] mMenuStrings = {
+ R.string.editor_chan_sat_main,
+ R.string.editor_chan_sat_red,
+ R.string.editor_chan_sat_yellow,
+ R.string.editor_chan_sat_green,
+ R.string.editor_chan_sat_cyan,
+ R.string.editor_chan_sat_blue,
+ R.string.editor_chan_sat_magenta
+ };
+
+ String mCurrentlyEditing = null;
+
+ public EditorChanSat() {
+ super(ID, R.layout.filtershow_default_editor, R.id.basicEditor);
+ }
+
+ @Override
+ public String calculateUserMessage(Context context, String effectName, Object parameterValue) {
+ FilterRepresentation rep = getLocalRepresentation();
+ if (rep == null || !(rep instanceof FilterChanSatRepresentation)) {
+ return "";
+ }
+ FilterChanSatRepresentation csrep = (FilterChanSatRepresentation) rep;
+ int mode = csrep.getParameterMode();
+ String paramString;
+
+ paramString = mContext.getString(mMenuStrings[mode]);
+
+ int val = csrep.getCurrentParameter();
+ return paramString + ((val > 0) ? " +" : " ") + val;
+ }
+
+ @Override
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ mButton = (SwapButton) accessoryViewList.findViewById(R.id.applyEffect);
+ mButton.setText(mContext.getString(R.string.editor_chan_sat_main));
+
+ final PopupMenu popupMenu = new PopupMenu(mImageShow.getActivity(), mButton);
+
+ popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_chan_sat, popupMenu.getMenu());
+
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ selectMenuItem(item);
+ return true;
+ }
+ });
+ mButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ popupMenu.show();
+ }
+ });
+ mButton.setListener(this);
+
+ FilterChanSatRepresentation csrep = getChanSatRep();
+ String menuString = mContext.getString(mMenuStrings[0]);
+ switchToMode(csrep, FilterChanSatRepresentation.MODE_MASTER, menuString);
+
+ }
+
+ public int getParameterIndex(int id) {
+ switch (id) {
+ case R.id.editor_chan_sat_main:
+ return FilterChanSatRepresentation.MODE_MASTER;
+ case R.id.editor_chan_sat_red:
+ return FilterChanSatRepresentation.MODE_RED;
+ case R.id.editor_chan_sat_yellow:
+ return FilterChanSatRepresentation.MODE_YELLOW;
+ case R.id.editor_chan_sat_green:
+ return FilterChanSatRepresentation.MODE_GREEN;
+ case R.id.editor_chan_sat_cyan:
+ return FilterChanSatRepresentation.MODE_CYAN;
+ case R.id.editor_chan_sat_blue:
+ return FilterChanSatRepresentation.MODE_BLUE;
+ case R.id.editor_chan_sat_magenta:
+ return FilterChanSatRepresentation.MODE_MAGENTA;
+ }
+ return -1;
+ }
+
+ @Override
+ public void detach() {
+ mButton.setListener(null);
+ mButton.setOnClickListener(null);
+ }
+
+ private void updateSeekBar(FilterChanSatRepresentation rep) {
+ mControl.updateUI();
+ }
+
+ @Override
+ protected Parameter getParameterToEdit(FilterRepresentation rep) {
+ if (rep instanceof FilterChanSatRepresentation) {
+ FilterChanSatRepresentation csrep = (FilterChanSatRepresentation) rep;
+ Parameter param = csrep.getFilterParameter(csrep.getParameterMode());
+ if (param instanceof BasicParameterStyle) {
+ param.setFilterView(EditorChanSat.this);
+ }
+ return param;
+ }
+ return null;
+ }
+
+ private FilterChanSatRepresentation getChanSatRep() {
+ FilterRepresentation rep = getLocalRepresentation();
+ if (rep != null
+ && rep instanceof FilterChanSatRepresentation) {
+ FilterChanSatRepresentation csrep = (FilterChanSatRepresentation) rep;
+ return csrep;
+ }
+ return null;
+ }
+
+ @Override
+ public void computeIcon(int n, RenderingRequestCaller caller) {
+ FilterChanSatRepresentation rep = getChanSatRep();
+ if (rep == null) return;
+ rep = (FilterChanSatRepresentation) rep.copy();
+ ImagePreset preset = new ImagePreset();
+ preset.addFilter(rep);
+ Bitmap src = MasterImage.getImage().getThumbnailBitmap();
+ RenderingRequest.post(null, src, preset, RenderingRequest.STYLE_ICON_RENDERING,
+ caller);
+ }
+
+ protected void selectMenuItem(MenuItem item) {
+ if (getLocalRepresentation() != null
+ && getLocalRepresentation() instanceof FilterChanSatRepresentation) {
+ FilterChanSatRepresentation csrep =
+ (FilterChanSatRepresentation) getLocalRepresentation();
+
+ switchToMode(csrep, getParameterIndex(item.getItemId()), item.getTitle().toString());
+
+ }
+ }
+
+ protected void switchToMode(FilterChanSatRepresentation csrep, int mode, String title) {
+ csrep.setParameterMode(mode);
+ mCurrentlyEditing = title;
+ mButton.setText(mCurrentlyEditing);
+ {
+ Parameter param = getParameterToEdit(csrep);
+
+ control(param, mEditControl);
+ }
+ updateSeekBar(csrep);
+ mView.invalidate();
+ }
+
+ @Override
+ public void swapLeft(MenuItem item) {
+ super.swapLeft(item);
+ mButton.setTranslationX(0);
+ mButton.animate().translationX(mButton.getWidth()).setDuration(SwapButton.ANIM_DURATION);
+ Runnable updateButton = new Runnable() {
+ @Override
+ public void run() {
+ mButton.animate().cancel();
+ mButton.setTranslationX(0);
+ }
+ };
+ mHandler.postDelayed(updateButton, SwapButton.ANIM_DURATION);
+ selectMenuItem(item);
+ }
+
+ @Override
+ public void swapRight(MenuItem item) {
+ super.swapRight(item);
+ mButton.setTranslationX(0);
+ mButton.animate().translationX(-mButton.getWidth()).setDuration(SwapButton.ANIM_DURATION);
+ Runnable updateButton = new Runnable() {
+ @Override
+ public void run() {
+ mButton.animate().cancel();
+ mButton.setTranslationX(0);
+ }
+ };
+ mHandler.postDelayed(updateButton, SwapButton.ANIM_DURATION);
+ selectMenuItem(item);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorCrop.java b/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
new file mode 100644
index 000000000..511d4ff87
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageCrop;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorCrop extends Editor implements EditorInfo {
+ public static final String TAG = EditorCrop.class.getSimpleName();
+ public static final int ID = R.id.editorCrop;
+
+ // Holder for an aspect ratio it's string id
+ protected static final class AspectInfo {
+ int mAspectX;
+ int mAspectY;
+ int mStringId;
+ AspectInfo(int stringID, int x, int y) {
+ mStringId = stringID;
+ mAspectX = x;
+ mAspectY = y;
+ }
+ };
+
+ // Mapping from menu id to aspect ratio
+ protected static final SparseArray<AspectInfo> sAspects;
+ static {
+ sAspects = new SparseArray<AspectInfo>();
+ sAspects.put(R.id.crop_menu_1to1, new AspectInfo(R.string.aspect1to1_effect, 1, 1));
+ sAspects.put(R.id.crop_menu_4to3, new AspectInfo(R.string.aspect4to3_effect, 4, 3));
+ sAspects.put(R.id.crop_menu_3to4, new AspectInfo(R.string.aspect3to4_effect, 3, 4));
+ sAspects.put(R.id.crop_menu_5to7, new AspectInfo(R.string.aspect5to7_effect, 5, 7));
+ sAspects.put(R.id.crop_menu_7to5, new AspectInfo(R.string.aspect7to5_effect, 7, 5));
+ sAspects.put(R.id.crop_menu_none, new AspectInfo(R.string.aspectNone_effect, 0, 0));
+ sAspects.put(R.id.crop_menu_original, new AspectInfo(R.string.aspectOriginal_effect, 0, 0));
+ }
+
+ protected ImageCrop mImageCrop;
+ private String mAspectString = "";
+
+ public EditorCrop() {
+ super(ID);
+ mChangesGeometry = true;
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ if (mImageCrop == null) {
+ mImageCrop = new ImageCrop(context);
+ }
+ mView = mImageShow = mImageCrop;
+ mImageCrop.setEditor(this);
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ MasterImage master = MasterImage.getImage();
+ master.setCurrentFilterRepresentation(master.getPreset()
+ .getFilterWithSerializationName(FilterCropRepresentation.SERIALIZATION_NAME));
+ super.reflectCurrentFilter();
+ FilterRepresentation rep = getLocalRepresentation();
+ if (rep == null || rep instanceof FilterCropRepresentation) {
+ mImageCrop.setFilterCropRepresentation((FilterCropRepresentation) rep);
+ } else {
+ Log.w(TAG, "Could not reflect current filter, not of type: "
+ + FilterCropRepresentation.class.getSimpleName());
+ }
+ mImageCrop.invalidate();
+ }
+
+ @Override
+ public void finalApplyCalled() {
+ commitLocalRepresentation(mImageCrop.getFinalRepresentation());
+ }
+
+ @Override
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ view.setText(mContext.getString(R.string.crop));
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ showPopupMenu(accessoryViewList);
+ }
+ });
+ }
+
+ private void changeCropAspect(int itemId) {
+ AspectInfo info = sAspects.get(itemId);
+ if (info == null) {
+ throw new IllegalArgumentException("Invalid resource ID: " + itemId);
+ }
+ if (itemId == R.id.crop_menu_original) {
+ mImageCrop.applyOriginalAspect();
+ } else if (itemId == R.id.crop_menu_none) {
+ mImageCrop.applyFreeAspect();
+ } else {
+ mImageCrop.applyAspect(info.mAspectX, info.mAspectY);
+ }
+ setAspectString(mContext.getString(info.mStringId));
+ }
+
+ private void showPopupMenu(LinearLayout accessoryViewList) {
+ final Button button = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ final PopupMenu popupMenu = new PopupMenu(mImageShow.getActivity(), button);
+ popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_crop, popupMenu.getMenu());
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ changeCropAspect(item.getItemId());
+ return true;
+ }
+ });
+ popupMenu.show();
+ }
+
+ @Override
+ public boolean showsSeekBar() {
+ return false;
+ }
+
+ @Override
+ public int getTextId() {
+ return R.string.crop;
+ }
+
+ @Override
+ public int getOverlayId() {
+ return R.drawable.filtershow_button_geometry_crop;
+ }
+
+ @Override
+ public boolean getOverlayOnly() {
+ return true;
+ }
+
+ private void setAspectString(String s) {
+ mAspectString = s;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorCurves.java b/src/com/android/gallery3d/filtershow/editors/EditorCurves.java
new file mode 100644
index 000000000..83fbced79
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorCurves.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageCurves;
+
+public class EditorCurves extends Editor {
+ public static final int ID = R.id.imageCurves;
+ ImageCurves mImageCurves;
+
+ public EditorCurves() {
+ super(ID);
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ mView = mImageShow = mImageCurves = new ImageCurves(context);
+ mImageCurves.setEditor(this);
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ super.reflectCurrentFilter();
+ FilterRepresentation rep = getLocalRepresentation();
+ if (rep != null && getLocalRepresentation() instanceof FilterCurvesRepresentation) {
+ FilterCurvesRepresentation drawRep = (FilterCurvesRepresentation) rep;
+ mImageCurves.setFilterDrawRepresentation(drawRep);
+ }
+ }
+
+ @Override
+ public boolean showsSeekBar() {
+ return false;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorDraw.java b/src/com/android/gallery3d/filtershow/editors/EditorDraw.java
new file mode 100644
index 000000000..4b09051e2
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorDraw.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.SeekBar;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.colorpicker.ColorGridDialog;
+import com.android.gallery3d.filtershow.colorpicker.RGBListener;
+import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.ImageFilterDraw;
+import com.android.gallery3d.filtershow.imageshow.ImageDraw;
+
+public class EditorDraw extends Editor {
+ private static final String LOGTAG = "EditorDraw";
+ public static final int ID = R.id.editorDraw;
+ public ImageDraw mImageDraw;
+
+ public EditorDraw() {
+ super(ID);
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ mView = mImageShow = mImageDraw = new ImageDraw(context);
+ mImageDraw.setEditor(this);
+
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ super.reflectCurrentFilter();
+ FilterRepresentation rep = getLocalRepresentation();
+
+ if (rep != null && getLocalRepresentation() instanceof FilterDrawRepresentation) {
+ FilterDrawRepresentation drawRep = (FilterDrawRepresentation) getLocalRepresentation();
+ mImageDraw.setFilterDrawRepresentation(drawRep);
+ }
+ }
+
+ @Override
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ view.setText(mContext.getString(R.string.draw_style));
+ view.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ showPopupMenu(accessoryViewList);
+ }
+ });
+ }
+
+ @Override
+ public boolean showsSeekBar() {
+ return false;
+ }
+
+ private void showPopupMenu(LinearLayout accessoryViewList) {
+ final Button button = (Button) accessoryViewList.findViewById(
+ R.id.applyEffect);
+ if (button == null) {
+ return;
+ }
+ final PopupMenu popupMenu = new PopupMenu(mImageShow.getActivity(), button);
+ popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_draw, popupMenu.getMenu());
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ ImageFilterDraw filter = (ImageFilterDraw) mImageShow.getCurrentFilter();
+ if (item.getItemId() == R.id.draw_menu_color) {
+ showColorGrid(item);
+ } else if (item.getItemId() == R.id.draw_menu_size) {
+ showSizeDialog(item);
+ } else if (item.getItemId() == R.id.draw_menu_style_brush_marker) {
+ ImageDraw idraw = (ImageDraw) mImageShow;
+ idraw.setStyle(ImageFilterDraw.BRUSH_STYLE_MARKER);
+ } else if (item.getItemId() == R.id.draw_menu_style_brush_spatter) {
+ ImageDraw idraw = (ImageDraw) mImageShow;
+ idraw.setStyle(ImageFilterDraw.BRUSH_STYLE_SPATTER);
+ } else if (item.getItemId() == R.id.draw_menu_style_line) {
+ ImageDraw idraw = (ImageDraw) mImageShow;
+ idraw.setStyle(ImageFilterDraw.SIMPLE_STYLE);
+ } else if (item.getItemId() == R.id.draw_menu_clear) {
+ ImageDraw idraw = (ImageDraw) mImageShow;
+ idraw.resetParameter();
+ commitLocalRepresentation();
+ }
+ mView.invalidate();
+ return true;
+ }
+ });
+ popupMenu.show();
+ }
+
+ public void showSizeDialog(final MenuItem item) {
+ FilterShowActivity ctx = mImageShow.getActivity();
+ final Dialog dialog = new Dialog(ctx);
+ dialog.setTitle(R.string.draw_size_title);
+ dialog.setContentView(R.layout.filtershow_draw_size);
+ final SeekBar bar = (SeekBar) dialog.findViewById(R.id.sizeSeekBar);
+ ImageDraw idraw = (ImageDraw) mImageShow;
+ bar.setProgress(idraw.getSize());
+ Button button = (Button) dialog.findViewById(R.id.sizeAcceptButton);
+ button.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ int p = bar.getProgress();
+ ImageDraw idraw = (ImageDraw) mImageShow;
+ idraw.setSize(p + 1);
+ dialog.dismiss();
+ }
+ });
+ dialog.show();
+ }
+
+ public void showColorGrid(final MenuItem item) {
+ RGBListener cl = new RGBListener() {
+ @Override
+ public void setColor(int rgb) {
+ ImageDraw idraw = (ImageDraw) mImageShow;
+ idraw.setColor(rgb);
+ }
+ };
+ ColorGridDialog cpd = new ColorGridDialog(mImageShow.getActivity(), cl);
+ cpd.show();
+ LayoutParams params = cpd.getWindow().getAttributes();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorGrad.java b/src/com/android/gallery3d/filtershow/editors/EditorGrad.java
new file mode 100644
index 000000000..f427ccbd8
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorGrad.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.content.Context;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.ToggleButton;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.controller.Control;
+import com.android.gallery3d.filtershow.controller.FilterView;
+import com.android.gallery3d.filtershow.controller.Parameter;
+import com.android.gallery3d.filtershow.controller.ParameterActionAndInt;
+import com.android.gallery3d.filtershow.filters.FilterGradRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageGrad;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorGrad extends ParametricEditor
+ implements OnSeekBarChangeListener, ParameterActionAndInt {
+ private static final String LOGTAG = "EditorGrad";
+ public static final int ID = R.id.editorGrad;
+ PopupMenu mPopupMenu;
+ ToggleButton mAddModeButton;
+ String mEffectName = "";
+ private static final int MODE_BRIGHTNESS = FilterGradRepresentation.PARAM_BRIGHTNESS;
+ private static final int MODE_SATURATION = FilterGradRepresentation.PARAM_SATURATION;
+ private static final int MODE_CONTRAST = FilterGradRepresentation.PARAM_CONTRAST;
+ private static final int ADD_ICON = R.drawable.ic_grad_add;
+ private static final int DEL_ICON = R.drawable.ic_grad_del;
+ private int mSliderMode = MODE_BRIGHTNESS;
+ ImageGrad mImageGrad;
+
+ public EditorGrad() {
+ super(ID, R.layout.filtershow_grad_editor, R.id.gradEditor);
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ mImageGrad = (ImageGrad) mImageShow;
+ mImageGrad.setEditor(this);
+
+ }
+
+ public void clearAddMode() {
+ mAddModeButton.setChecked(false);
+ FilterRepresentation tmpRep = getLocalRepresentation();
+ if (tmpRep instanceof FilterGradRepresentation) {
+ updateMenuItems((FilterGradRepresentation) tmpRep);
+ }
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ super.reflectCurrentFilter();
+ FilterRepresentation tmpRep = getLocalRepresentation();
+ if (tmpRep instanceof FilterGradRepresentation) {
+ FilterGradRepresentation rep = (FilterGradRepresentation) tmpRep;
+ boolean f = rep.showParameterValue();
+
+ mImageGrad.setRepresentation(rep);
+ }
+ }
+
+ public void updateSeekBar(FilterGradRepresentation rep) {
+ mControl.updateUI();
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar sbar, int progress, boolean arg2) {
+ FilterRepresentation tmpRep = getLocalRepresentation();
+ if (tmpRep instanceof FilterGradRepresentation) {
+ FilterGradRepresentation rep = (FilterGradRepresentation) tmpRep;
+ int min = rep.getParameterMin(mSliderMode);
+ int value = progress + min;
+ rep.setParameter(mSliderMode, value);
+ mView.invalidate();
+ commitLocalRepresentation();
+ }
+ }
+
+ @Override
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ view.setText(mContext.getString(R.string.editor_grad_brightness));
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ showPopupMenu(accessoryViewList);
+ }
+ });
+
+ setUpPopupMenu(view);
+ setEffectName();
+ }
+
+ private void updateMenuItems(FilterGradRepresentation rep) {
+ int n = rep.getNumberOfBands();
+ }
+
+ public void setEffectName() {
+ if (mPopupMenu != null) {
+ MenuItem item = mPopupMenu.getMenu().findItem(R.id.editor_grad_brightness);
+ mEffectName = item.getTitle().toString();
+ }
+ }
+
+ private void showPopupMenu(LinearLayout accessoryViewList) {
+ Button button = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ if (button == null) {
+ return;
+ }
+
+ if (mPopupMenu == null) {
+ setUpPopupMenu(button);
+ }
+ mPopupMenu.show();
+ }
+
+ private void setUpPopupMenu(Button button) {
+ mPopupMenu = new PopupMenu(mImageShow.getActivity(), button);
+ mPopupMenu.getMenuInflater()
+ .inflate(R.menu.filtershow_menu_grad, mPopupMenu.getMenu());
+ FilterGradRepresentation rep = (FilterGradRepresentation) getLocalRepresentation();
+ if (rep == null) {
+ return;
+ }
+ updateMenuItems(rep);
+ hackFixStrings(mPopupMenu.getMenu());
+ setEffectName();
+ updateText();
+
+ mPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ FilterRepresentation tmpRep = getLocalRepresentation();
+
+ if (tmpRep instanceof FilterGradRepresentation) {
+ FilterGradRepresentation rep = (FilterGradRepresentation) tmpRep;
+ int cmdID = item.getItemId();
+ switch (cmdID) {
+ case R.id.editor_grad_brightness:
+ mSliderMode = MODE_BRIGHTNESS;
+ mEffectName = item.getTitle().toString();
+ break;
+ case R.id.editor_grad_contrast:
+ mSliderMode = MODE_CONTRAST;
+ mEffectName = item.getTitle().toString();
+ break;
+ case R.id.editor_grad_saturation:
+ mSliderMode = MODE_SATURATION;
+ mEffectName = item.getTitle().toString();
+ break;
+ }
+ updateMenuItems(rep);
+ updateSeekBar(rep);
+
+ commitLocalRepresentation();
+ mView.invalidate();
+ }
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public String calculateUserMessage(Context context, String effectName, Object parameterValue) {
+ FilterGradRepresentation rep = getGradRepresentation();
+ if (rep == null) {
+ return mEffectName;
+ }
+ int val = rep.getParameter(mSliderMode);
+ return mEffectName.toUpperCase() + ((val > 0) ? " +" : " ") + val;
+ }
+
+ private FilterGradRepresentation getGradRepresentation() {
+ FilterRepresentation tmpRep = getLocalRepresentation();
+ if (tmpRep instanceof FilterGradRepresentation) {
+ return (FilterGradRepresentation) tmpRep;
+ }
+ return null;
+ }
+
+ @Override
+ public int getMaximum() {
+ FilterGradRepresentation rep = getGradRepresentation();
+ if (rep == null) {
+ return 0;
+ }
+ return rep.getParameterMax(mSliderMode);
+ }
+
+ @Override
+ public int getMinimum() {
+ FilterGradRepresentation rep = getGradRepresentation();
+ if (rep == null) {
+ return 0;
+ }
+ return rep.getParameterMin(mSliderMode);
+ }
+
+ @Override
+ public int getDefaultValue() {
+ return 0;
+ }
+
+ @Override
+ public int getValue() {
+ FilterGradRepresentation rep = getGradRepresentation();
+ if (rep == null) {
+ return 0;
+ }
+ return rep.getParameter(mSliderMode);
+ }
+
+ @Override
+ public String getValueString() {
+ return null;
+ }
+
+ @Override
+ public void setValue(int value) {
+ FilterGradRepresentation rep = getGradRepresentation();
+ if (rep == null) {
+ return;
+ }
+ rep.setParameter(mSliderMode, value);
+ }
+
+ @Override
+ public String getParameterName() {
+ return mEffectName;
+ }
+
+ @Override
+ public String getParameterType() {
+ return sParameterType;
+ }
+
+ @Override
+ public void setController(Control c) {
+
+ }
+
+ @Override
+ public void fireLeftAction() {
+ FilterGradRepresentation rep = getGradRepresentation();
+ if (rep == null) {
+ return;
+ }
+ rep.addBand(MasterImage.getImage().getOriginalBounds());
+ updateMenuItems(rep);
+ updateSeekBar(rep);
+
+ commitLocalRepresentation();
+ mView.invalidate();
+ }
+
+ @Override
+ public int getLeftIcon() {
+ return ADD_ICON;
+ }
+
+ @Override
+ public void fireRightAction() {
+ FilterGradRepresentation rep = getGradRepresentation();
+ if (rep == null) {
+ return;
+ }
+ rep.deleteCurrentBand();
+
+ updateMenuItems(rep);
+ updateSeekBar(rep);
+ commitLocalRepresentation();
+ mView.invalidate();
+ }
+
+ @Override
+ public int getRightIcon() {
+ return DEL_ICON;
+ }
+
+ @Override
+ public void setFilterView(FilterView editor) {
+
+ }
+
+ @Override
+ public void copyFrom(Parameter src) {
+
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorInfo.java b/src/com/android/gallery3d/filtershow/editors/EditorInfo.java
new file mode 100644
index 000000000..75afe49c2
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorInfo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+public interface EditorInfo {
+ public int getTextId();
+ public int getOverlayId();
+ public boolean getOverlayOnly();
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorMirror.java b/src/com/android/gallery3d/filtershow/editors/EditorMirror.java
new file mode 100644
index 000000000..d6d9ee75d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorMirror.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageMirror;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorMirror extends Editor implements EditorInfo {
+ public static final String TAG = EditorMirror.class.getSimpleName();
+ public static final int ID = R.id.editorFlip;
+ ImageMirror mImageMirror;
+
+ public EditorMirror() {
+ super(ID);
+ mChangesGeometry = true;
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ if (mImageMirror == null) {
+ mImageMirror = new ImageMirror(context);
+ }
+ mView = mImageShow = mImageMirror;
+ mImageMirror.setEditor(this);
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ MasterImage master = MasterImage.getImage();
+ master.setCurrentFilterRepresentation(master.getPreset()
+ .getFilterWithSerializationName(FilterMirrorRepresentation.SERIALIZATION_NAME));
+ super.reflectCurrentFilter();
+ FilterRepresentation rep = getLocalRepresentation();
+ if (rep == null || rep instanceof FilterMirrorRepresentation) {
+ mImageMirror.setFilterMirrorRepresentation((FilterMirrorRepresentation) rep);
+ } else {
+ Log.w(TAG, "Could not reflect current filter, not of type: "
+ + FilterMirrorRepresentation.class.getSimpleName());
+ }
+ mImageMirror.invalidate();
+ }
+
+ @Override
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ final Button button = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ button.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ mImageMirror.flip();
+ }
+ });
+ }
+
+ @Override
+ public void finalApplyCalled() {
+ commitLocalRepresentation(mImageMirror.getFinalRepresentation());
+ }
+
+ @Override
+ public int getTextId() {
+ return R.string.mirror;
+ }
+
+ @Override
+ public int getOverlayId() {
+ return R.drawable.filtershow_button_geometry_flip;
+ }
+
+ @Override
+ public boolean getOverlayOnly() {
+ return true;
+ }
+
+ @Override
+ public boolean showsSeekBar() {
+ return false;
+ }
+
+ @Override
+ public boolean showsPopupIndicator() {
+ return false;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorPanel.java b/src/com/android/gallery3d/filtershow/editors/EditorPanel.java
new file mode 100644
index 000000000..bc4ca6ab6
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorPanel.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.history.HistoryManager;
+import com.android.gallery3d.filtershow.category.MainPanel;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.state.StatePanel;
+
+public class EditorPanel extends Fragment {
+
+ private static final String LOGTAG = "EditorPanel";
+
+ private LinearLayout mMainView;
+ private Editor mEditor;
+ private int mEditorID;
+
+ public void setEditor(int editor) {
+ mEditorID = editor;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ FilterShowActivity filterShowActivity = (FilterShowActivity) activity;
+ mEditor = filterShowActivity.getEditor(mEditorID);
+ }
+
+ public void cancelCurrentFilter() {
+ MasterImage masterImage = MasterImage.getImage();
+ HistoryManager adapter = masterImage.getHistory();
+
+ int position = adapter.undo();
+ masterImage.onHistoryItemClick(position);
+ ((FilterShowActivity)getActivity()).invalidateViews();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ FilterShowActivity activity = (FilterShowActivity) getActivity();
+ if (mMainView != null) {
+ if (mMainView.getParent() != null) {
+ ViewGroup parent = (ViewGroup) mMainView.getParent();
+ parent.removeView(mMainView);
+ }
+ showImageStatePanel(activity.isShowingImageStatePanel());
+ return mMainView;
+ }
+ mMainView = (LinearLayout) inflater.inflate(R.layout.filtershow_editor_panel, null);
+
+ View actionControl = mMainView.findViewById(R.id.panelAccessoryViewList);
+ View editControl = mMainView.findViewById(R.id.controlArea);
+ ImageButton cancelButton = (ImageButton) mMainView.findViewById(R.id.cancelFilter);
+ ImageButton applyButton = (ImageButton) mMainView.findViewById(R.id.applyFilter);
+ Button editTitle = (Button) mMainView.findViewById(R.id.applyEffect);
+ cancelButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ cancelCurrentFilter();
+ FilterShowActivity activity = (FilterShowActivity) getActivity();
+ activity.backToMain();
+ }
+ });
+
+ Button toggleState = (Button) mMainView.findViewById(R.id.toggle_state);
+ mEditor = activity.getEditor(mEditorID);
+ if (mEditor != null) {
+ mEditor.setUpEditorUI(actionControl, editControl, editTitle, toggleState);
+ mEditor.reflectCurrentFilter();
+ if (mEditor.useUtilityPanel()) {
+ mEditor.openUtilityPanel((LinearLayout) actionControl);
+ }
+ }
+ applyButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ FilterShowActivity activity = (FilterShowActivity) getActivity();
+ mEditor.finalApplyCalled();
+ activity.backToMain();
+ }
+ });
+
+ showImageStatePanel(activity.isShowingImageStatePanel());
+ return mMainView;
+ }
+
+ @Override
+ public void onDetach() {
+ if (mEditor != null) {
+ mEditor.detach();
+ }
+ super.onDetach();
+ }
+
+ public void showImageStatePanel(boolean show) {
+ if (mMainView.findViewById(R.id.state_panel_container) == null) {
+ return;
+ }
+ FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
+ Fragment panel = getActivity().getSupportFragmentManager().findFragmentByTag(
+ MainPanel.FRAGMENT_TAG);
+ if (panel == null || panel instanceof MainPanel) {
+ transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
+ }
+ if (show) {
+ StatePanel statePanel = new StatePanel();
+ transaction.replace(R.id.state_panel_container, statePanel, StatePanel.FRAGMENT_TAG);
+ } else {
+ Fragment statePanel = getChildFragmentManager().findFragmentByTag(StatePanel.FRAGMENT_TAG);
+ if (statePanel != null) {
+ transaction.remove(statePanel);
+ }
+ }
+ transaction.commit();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorRedEye.java b/src/com/android/gallery3d/filtershow/editors/EditorRedEye.java
new file mode 100644
index 000000000..b0e88dd44
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorRedEye.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterRedEyeRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageRedEye;
+
+/**
+ * The editor with no slider for filters without UI
+ */
+public class EditorRedEye extends Editor {
+ public static int ID = R.id.editorRedEye;
+ private final String LOGTAG = "EditorRedEye";
+ ImageRedEye mImageRedEyes;
+
+ public EditorRedEye() {
+ super(ID);
+ }
+
+ protected EditorRedEye(int id) {
+ super(id);
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ mView = mImageShow = mImageRedEyes= new ImageRedEye(context);
+ mImageRedEyes.setEditor(this);
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ super.reflectCurrentFilter();
+ FilterRepresentation rep = getLocalRepresentation();
+ if (rep != null && getLocalRepresentation() instanceof FilterRedEyeRepresentation) {
+ FilterRedEyeRepresentation redEyeRep = (FilterRedEyeRepresentation) rep;
+
+ mImageRedEyes.setRepresentation(redEyeRep);
+ }
+ }
+
+ @Override
+ public boolean showsSeekBar() {
+ return false;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorRotate.java b/src/com/android/gallery3d/filtershow/editors/EditorRotate.java
new file mode 100644
index 000000000..9452bf0c0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorRotate.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageRotate;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorRotate extends Editor implements EditorInfo {
+ public static final String TAG = EditorRotate.class.getSimpleName();
+ public static final int ID = R.id.editorRotate;
+ ImageRotate mImageRotate;
+
+ public EditorRotate() {
+ super(ID);
+ mChangesGeometry = true;
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ if (mImageRotate == null) {
+ mImageRotate = new ImageRotate(context);
+ }
+ mView = mImageShow = mImageRotate;
+ mImageRotate.setEditor(this);
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ MasterImage master = MasterImage.getImage();
+ master.setCurrentFilterRepresentation(master.getPreset()
+ .getFilterWithSerializationName(FilterRotateRepresentation.SERIALIZATION_NAME));
+ super.reflectCurrentFilter();
+ FilterRepresentation rep = getLocalRepresentation();
+ if (rep == null || rep instanceof FilterRotateRepresentation) {
+ mImageRotate.setFilterRotateRepresentation((FilterRotateRepresentation) rep);
+ } else {
+ Log.w(TAG, "Could not reflect current filter, not of type: "
+ + FilterRotateRepresentation.class.getSimpleName());
+ }
+ mImageRotate.invalidate();
+ }
+
+ @Override
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ final Button button = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ button.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ mImageRotate.rotate();
+ String displayVal = mContext.getString(getTextId()) + " "
+ + mImageRotate.getLocalValue();
+ button.setText(displayVal);
+ }
+ });
+ }
+
+ @Override
+ public void finalApplyCalled() {
+ commitLocalRepresentation(mImageRotate.getFinalRepresentation());
+ }
+
+ @Override
+ public int getTextId() {
+ return R.string.rotate;
+ }
+
+ @Override
+ public int getOverlayId() {
+ return R.drawable.filtershow_button_geometry_rotate;
+ }
+
+ @Override
+ public boolean getOverlayOnly() {
+ return true;
+ }
+
+ @Override
+ public boolean showsSeekBar() {
+ return false;
+ }
+
+ @Override
+ public boolean showsPopupIndicator() {
+ return false;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorStraighten.java b/src/com/android/gallery3d/filtershow/editors/EditorStraighten.java
new file mode 100644
index 000000000..ff84ba8f9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorStraighten.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.content.Context;
+import android.util.Log;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorStraighten extends Editor implements EditorInfo {
+ public static final String TAG = EditorStraighten.class.getSimpleName();
+ public static final int ID = R.id.editorStraighten;
+ ImageStraighten mImageStraighten;
+
+ public EditorStraighten() {
+ super(ID);
+ mShowParameter = SHOW_VALUE_INT;
+ mChangesGeometry = true;
+ }
+
+ @Override
+ public String calculateUserMessage(Context context, String effectName, Object parameterValue) {
+ String apply = context.getString(R.string.apply_effect);
+ apply += " " + effectName;
+ return apply.toUpperCase();
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ if (mImageStraighten == null) {
+ mImageStraighten = new ImageStraighten(context);
+ }
+ mView = mImageShow = mImageStraighten;
+ mImageStraighten.setEditor(this);
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ MasterImage master = MasterImage.getImage();
+ master.setCurrentFilterRepresentation(master.getPreset().getFilterWithSerializationName(
+ FilterStraightenRepresentation.SERIALIZATION_NAME));
+ super.reflectCurrentFilter();
+ FilterRepresentation rep = getLocalRepresentation();
+ if (rep == null || rep instanceof FilterStraightenRepresentation) {
+ mImageStraighten
+ .setFilterStraightenRepresentation((FilterStraightenRepresentation) rep);
+ } else {
+ Log.w(TAG, "Could not reflect current filter, not of type: "
+ + FilterStraightenRepresentation.class.getSimpleName());
+ }
+ mImageStraighten.invalidate();
+ }
+
+ @Override
+ public void finalApplyCalled() {
+ commitLocalRepresentation(mImageStraighten.getFinalRepresentation());
+ }
+
+ @Override
+ public int getTextId() {
+ return R.string.straighten;
+ }
+
+ @Override
+ public int getOverlayId() {
+ return R.drawable.filtershow_button_geometry_straighten;
+ }
+
+ @Override
+ public boolean getOverlayOnly() {
+ return true;
+ }
+
+ @Override
+ public boolean showsSeekBar() {
+ return false;
+ }
+
+ @Override
+ public boolean showsPopupIndicator() {
+ return false;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java b/src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java
new file mode 100644
index 000000000..9376fbef0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterTinyPlanetRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet;
+
+public class EditorTinyPlanet extends BasicEditor {
+ public static final int ID = R.id.tinyPlanetEditor;
+ private static final String LOGTAG = "EditorTinyPlanet";
+ ImageTinyPlanet mImageTinyPlanet;
+
+ public EditorTinyPlanet() {
+ super(ID, R.layout.filtershow_tiny_planet_editor, R.id.imageTinyPlanet);
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ mImageTinyPlanet = (ImageTinyPlanet) mImageShow;
+ mImageTinyPlanet.setEditor(this);
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ super.reflectCurrentFilter();
+ FilterRepresentation rep = getLocalRepresentation();
+ if (rep != null && rep instanceof FilterTinyPlanetRepresentation) {
+ FilterTinyPlanetRepresentation drawRep = (FilterTinyPlanetRepresentation) rep;
+ mImageTinyPlanet.setRepresentation(drawRep);
+ }
+ }
+
+ public void updateUI() {
+ if (mControl != null) {
+ mControl.updateUI();
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorVignette.java b/src/com/android/gallery3d/filtershow/editors/EditorVignette.java
new file mode 100644
index 000000000..7127b2188
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorVignette.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterVignetteRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageVignette;
+
+public class EditorVignette extends ParametricEditor {
+ public static final int ID = R.id.vignetteEditor;
+ private static final String LOGTAG = "EditorVignettePlanet";
+ ImageVignette mImageVignette;
+
+ public EditorVignette() {
+ super(ID, R.layout.filtershow_vignette_editor, R.id.imageVignette);
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ mImageVignette = (ImageVignette) mImageShow;
+ mImageVignette.setEditor(this);
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ super.reflectCurrentFilter();
+
+ FilterRepresentation rep = getLocalRepresentation();
+ if (rep != null && getLocalRepresentation() instanceof FilterVignetteRepresentation) {
+ FilterVignetteRepresentation drawRep = (FilterVignetteRepresentation) rep;
+ mImageVignette.setRepresentation(drawRep);
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorZoom.java b/src/com/android/gallery3d/filtershow/editors/EditorZoom.java
new file mode 100644
index 000000000..ea8e3d140
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorZoom.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.editors;
+
+import com.android.gallery3d.R;
+
+public class EditorZoom extends BasicEditor {
+ public static final int ID = R.id.imageZoom;
+
+ public EditorZoom() {
+ super(ID, R.layout.filtershow_zoom_editor,R.id.imageZoom);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java b/src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java
new file mode 100644
index 000000000..d4e66edf8
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+
+/**
+ * The editor with no slider for filters without UI
+ */
+public class ImageOnlyEditor extends Editor {
+ public final static int ID = R.id.imageOnlyEditor;
+ private final String LOGTAG = "ImageOnlyEditor";
+
+ public ImageOnlyEditor() {
+ super(ID);
+ }
+
+ protected ImageOnlyEditor(int id) {
+ super(id);
+ }
+
+ public boolean useUtilityPanel() {
+ return false;
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ mView = mImageShow = new ImageShow(context);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/ParametricEditor.java b/src/com/android/gallery3d/filtershow/editors/ParametricEditor.java
new file mode 100644
index 000000000..9ec858ca5
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/ParametricEditor.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.controller.ActionSlider;
+import com.android.gallery3d.filtershow.controller.BasicSlider;
+import com.android.gallery3d.filtershow.controller.Control;
+import com.android.gallery3d.filtershow.controller.Parameter;
+import com.android.gallery3d.filtershow.controller.ParameterActionAndInt;
+import com.android.gallery3d.filtershow.controller.ParameterInteger;
+import com.android.gallery3d.filtershow.controller.ParameterStyles;
+import com.android.gallery3d.filtershow.controller.StyleChooser;
+import com.android.gallery3d.filtershow.controller.TitledSlider;
+import com.android.gallery3d.filtershow.filters.FilterBasicRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+
+public class ParametricEditor extends Editor {
+ private int mLayoutID;
+ private int mViewID;
+ public static int ID = R.id.editorParametric;
+ private final String LOGTAG = "ParametricEditor";
+ protected Control mControl;
+ public static final int MINIMUM_WIDTH = 600;
+ public static final int MINIMUM_HEIGHT = 800;
+ View mActionButton;
+ View mEditControl;
+ static HashMap<String, Class> portraitMap = new HashMap<String, Class>();
+ static HashMap<String, Class> landscapeMap = new HashMap<String, Class>();
+ static {
+ portraitMap.put(ParameterInteger.sParameterType, BasicSlider.class);
+ landscapeMap.put(ParameterInteger.sParameterType, TitledSlider.class);
+ portraitMap.put(ParameterActionAndInt.sParameterType, ActionSlider.class);
+ landscapeMap.put(ParameterActionAndInt.sParameterType, ActionSlider.class);
+ portraitMap.put(ParameterStyles.sParameterType, StyleChooser.class);
+ landscapeMap.put(ParameterStyles.sParameterType, StyleChooser.class);
+ }
+
+ static Constructor getConstructor(Class cl) {
+ try {
+ return cl.getConstructor(Context.class, ViewGroup.class);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public ParametricEditor() {
+ super(ID);
+ }
+
+ protected ParametricEditor(int id) {
+ super(id);
+ }
+
+ protected ParametricEditor(int id, int layoutID, int viewID) {
+ super(id);
+ mLayoutID = layoutID;
+ mViewID = viewID;
+ }
+
+ @Override
+ public String calculateUserMessage(Context context, String effectName, Object parameterValue) {
+ String apply = "";
+
+ if (mShowParameter == SHOW_VALUE_INT & useCompact(context)) {
+ if (getLocalRepresentation() instanceof FilterBasicRepresentation) {
+ FilterBasicRepresentation interval = (FilterBasicRepresentation) getLocalRepresentation();
+ apply += " " + effectName.toUpperCase() + " " + interval.getStateRepresentation();
+ } else {
+ apply += " " + effectName.toUpperCase() + " " + parameterValue;
+ }
+ } else {
+ apply += " " + effectName.toUpperCase();
+ }
+ return apply;
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ unpack(mViewID, mLayoutID);
+ }
+
+ @Override
+ public void reflectCurrentFilter() {
+ super.reflectCurrentFilter();
+ if (getLocalRepresentation() != null
+ && getLocalRepresentation() instanceof FilterBasicRepresentation) {
+ FilterBasicRepresentation interval = (FilterBasicRepresentation) getLocalRepresentation();
+ mControl.setPrameter(interval);
+ }
+ }
+
+ @Override
+ public Control[] getControls() {
+ BasicSlider slider = new BasicSlider();
+ return new Control[] {
+ slider
+ };
+ }
+
+ // TODO: need a better way to decide which representation
+ static boolean useCompact(Context context) {
+ WindowManager w = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE));
+ Point size = new Point();
+ w.getDefaultDisplay().getSize(size);
+ if (size.x < size.y) { // if tall than wider
+ return true;
+ }
+ if (size.x < MINIMUM_WIDTH) {
+ return true;
+ }
+ if (size.y < MINIMUM_HEIGHT) {
+ return true;
+ }
+ return false;
+ }
+
+ protected Parameter getParameterToEdit(FilterRepresentation rep) {
+ if (this instanceof Parameter) {
+ return (Parameter) this;
+ } else if (rep instanceof Parameter) {
+ return ((Parameter) rep);
+ }
+ return null;
+ }
+
+ @Override
+ public void setUtilityPanelUI(View actionButton, View editControl) {
+ mActionButton = actionButton;
+ mEditControl = editControl;
+ FilterRepresentation rep = getLocalRepresentation();
+ Parameter param = getParameterToEdit(rep);
+ if (param != null) {
+ control(param, editControl);
+ } else {
+ mSeekBar = new SeekBar(editControl.getContext());
+ LayoutParams lp = new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ mSeekBar.setLayoutParams(lp);
+ ((LinearLayout) editControl).addView(mSeekBar);
+ mSeekBar.setOnSeekBarChangeListener(this);
+ }
+ }
+
+ protected void control(Parameter p, View editControl) {
+ String pType = p.getParameterType();
+ Context context = editControl.getContext();
+ Class c = ((useCompact(context)) ? portraitMap : landscapeMap).get(pType);
+
+ if (c != null) {
+ try {
+ mControl = (Control) c.newInstance();
+ p.setController(mControl);
+ mControl.setUp((ViewGroup) editControl, p, this);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Error in loading Control ", e);
+ }
+ } else {
+ Log.e(LOGTAG, "Unable to find class for " + pType);
+ for (String string : portraitMap.keySet()) {
+ Log.e(LOGTAG, "for " + string + " use " + portraitMap.get(string));
+ }
+ }
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar sbar, int progress, boolean arg2) {
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar arg0) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar arg0) {
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/SwapButton.java b/src/com/android/gallery3d/filtershow/editors/SwapButton.java
new file mode 100644
index 000000000..bb4432e28
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/SwapButton.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 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.editors;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.widget.Button;
+
+public class SwapButton extends Button implements GestureDetector.OnGestureListener {
+
+ public static int ANIM_DURATION = 200;
+
+ public interface SwapButtonListener {
+ public void swapLeft(MenuItem item);
+ public void swapRight(MenuItem item);
+ }
+
+ private GestureDetector mDetector;
+ private SwapButtonListener mListener;
+ private Menu mMenu;
+ private int mCurrentMenuIndex;
+
+ public SwapButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDetector = new GestureDetector(context, this);
+ }
+
+ public SwapButtonListener getListener() {
+ return mListener;
+ }
+
+ public void setListener(SwapButtonListener listener) {
+ mListener = listener;
+ }
+
+ public boolean onTouchEvent(MotionEvent me) {
+ if (!mDetector.onTouchEvent(me)) {
+ return super.onTouchEvent(me);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ callOnClick();
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ return false;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ if (mMenu == null) {
+ return false;
+ }
+ if (e1.getX() - e2.getX() > 0) {
+ // right to left
+ mCurrentMenuIndex++;
+ if (mCurrentMenuIndex == mMenu.size()) {
+ mCurrentMenuIndex = 0;
+ }
+ if (mListener != null) {
+ mListener.swapRight(mMenu.getItem(mCurrentMenuIndex));
+ }
+ } else {
+ // left to right
+ mCurrentMenuIndex--;
+ if (mCurrentMenuIndex < 0) {
+ mCurrentMenuIndex = mMenu.size() - 1;
+ }
+ if (mListener != null) {
+ mListener.swapLeft(mMenu.getItem(mCurrentMenuIndex));
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
new file mode 100644
index 000000000..3fa91916d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorCrop;
+import com.android.gallery3d.filtershow.editors.EditorMirror;
+import com.android.gallery3d.filtershow.editors.EditorRotate;
+import com.android.gallery3d.filtershow.editors.EditorStraighten;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Vector;
+
+public abstract class BaseFiltersManager implements FiltersManagerInterface {
+ protected HashMap<Class, ImageFilter> mFilters = null;
+ protected HashMap<String, FilterRepresentation> mRepresentationLookup = null;
+ private static final String LOGTAG = "BaseFiltersManager";
+
+ protected ArrayList<FilterRepresentation> mLooks = new ArrayList<FilterRepresentation>();
+ protected ArrayList<FilterRepresentation> mBorders = new ArrayList<FilterRepresentation>();
+ protected ArrayList<FilterRepresentation> mTools = new ArrayList<FilterRepresentation>();
+ protected ArrayList<FilterRepresentation> mEffects = new ArrayList<FilterRepresentation>();
+
+ protected void init() {
+ mFilters = new HashMap<Class, ImageFilter>();
+ mRepresentationLookup = new HashMap<String, FilterRepresentation>();
+ Vector<Class> filters = new Vector<Class>();
+ addFilterClasses(filters);
+ for (Class filterClass : filters) {
+ try {
+ Object filterInstance = filterClass.newInstance();
+ if (filterInstance instanceof ImageFilter) {
+ mFilters.put(filterClass, (ImageFilter) filterInstance);
+
+ FilterRepresentation rep =
+ ((ImageFilter) filterInstance).getDefaultRepresentation();
+ if (rep != null) {
+ addRepresentation(rep);
+ }
+ }
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void addRepresentation(FilterRepresentation rep) {
+ mRepresentationLookup.put(rep.getSerializationName(), rep);
+ }
+
+ public FilterRepresentation createFilterFromName(String name) {
+ try {
+ return mRepresentationLookup.get(name).copy();
+ } catch (Exception e) {
+ Log.v(LOGTAG, "unable to generate a filter representation for \"" + name + "\"");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public ImageFilter getFilter(Class c) {
+ return mFilters.get(c);
+ }
+
+ @Override
+ public ImageFilter getFilterForRepresentation(FilterRepresentation representation) {
+ return mFilters.get(representation.getFilterClass());
+ }
+
+ public FilterRepresentation getRepresentation(Class c) {
+ ImageFilter filter = mFilters.get(c);
+ if (filter != null) {
+ return filter.getDefaultRepresentation();
+ }
+ return null;
+ }
+
+ public void freeFilterResources(ImagePreset preset) {
+ if (preset == null) {
+ return;
+ }
+ Vector<ImageFilter> usedFilters = preset.getUsedFilters(this);
+ for (Class c : mFilters.keySet()) {
+ ImageFilter filter = mFilters.get(c);
+ if (!usedFilters.contains(filter)) {
+ filter.freeResources();
+ }
+ }
+ }
+
+ public void freeRSFilterScripts() {
+ for (Class c : mFilters.keySet()) {
+ ImageFilter filter = mFilters.get(c);
+ if (filter != null && filter instanceof ImageFilterRS) {
+ ((ImageFilterRS) filter).resetScripts();
+ }
+ }
+ }
+
+ protected void addFilterClasses(Vector<Class> filters) {
+ filters.add(ImageFilterTinyPlanet.class);
+ filters.add(ImageFilterRedEye.class);
+ filters.add(ImageFilterWBalance.class);
+ filters.add(ImageFilterExposure.class);
+ filters.add(ImageFilterVignette.class);
+ filters.add(ImageFilterGrad.class);
+ filters.add(ImageFilterContrast.class);
+ filters.add(ImageFilterShadows.class);
+ filters.add(ImageFilterHighlights.class);
+ filters.add(ImageFilterVibrance.class);
+ filters.add(ImageFilterSharpen.class);
+ filters.add(ImageFilterCurves.class);
+ filters.add(ImageFilterDraw.class);
+ filters.add(ImageFilterHue.class);
+ filters.add(ImageFilterChanSat.class);
+ filters.add(ImageFilterSaturated.class);
+ filters.add(ImageFilterBwFilter.class);
+ filters.add(ImageFilterNegative.class);
+ filters.add(ImageFilterEdge.class);
+ filters.add(ImageFilterKMeans.class);
+ filters.add(ImageFilterFx.class);
+ filters.add(ImageFilterBorder.class);
+ filters.add(ImageFilterParametricBorder.class);
+ }
+
+ public ArrayList<FilterRepresentation> getLooks() {
+ return mLooks;
+ }
+
+ public ArrayList<FilterRepresentation> getBorders() {
+ return mBorders;
+ }
+
+ public ArrayList<FilterRepresentation> getTools() {
+ return mTools;
+ }
+
+ public ArrayList<FilterRepresentation> getEffects() {
+ return mEffects;
+ }
+
+ public void addBorders(Context context) {
+
+ }
+
+ public void addLooks(Context context) {
+ int[] drawid = {
+ R.drawable.filtershow_fx_0005_punch,
+ R.drawable.filtershow_fx_0000_vintage,
+ R.drawable.filtershow_fx_0004_bw_contrast,
+ R.drawable.filtershow_fx_0002_bleach,
+ R.drawable.filtershow_fx_0001_instant,
+ R.drawable.filtershow_fx_0007_washout,
+ R.drawable.filtershow_fx_0003_blue_crush,
+ R.drawable.filtershow_fx_0008_washout_color,
+ R.drawable.filtershow_fx_0006_x_process
+ };
+
+ int[] fxNameid = {
+ R.string.ffx_punch,
+ R.string.ffx_vintage,
+ R.string.ffx_bw_contrast,
+ R.string.ffx_bleach,
+ R.string.ffx_instant,
+ R.string.ffx_washout,
+ R.string.ffx_blue_crush,
+ R.string.ffx_washout_color,
+ R.string.ffx_x_process
+ };
+
+ // Do not localize.
+ String[] serializationNames = {
+ "LUT3D_PUNCH",
+ "LUT3D_VINTAGE",
+ "LUT3D_BW",
+ "LUT3D_BLEACH",
+ "LUT3D_INSTANT",
+ "LUT3D_WASHOUT",
+ "LUT3D_BLUECRUSH",
+ "LUT3D_WASHOUT",
+ "LUT3D_XPROCESS"
+ };
+
+ FilterFxRepresentation nullFx =
+ new FilterFxRepresentation(context.getString(R.string.none),
+ 0, R.string.none);
+ mLooks.add(nullFx);
+
+ for (int i = 0; i < drawid.length; i++) {
+ FilterFxRepresentation fx = new FilterFxRepresentation(
+ context.getString(fxNameid[i]), drawid[i], fxNameid[i]);
+ fx.setSerializationName(serializationNames[i]);
+ ImagePreset preset = new ImagePreset();
+ preset.addFilter(fx);
+ FilterUserPresetRepresentation rep = new FilterUserPresetRepresentation(
+ context.getString(fxNameid[i]), preset, -1);
+ mLooks.add(rep);
+ addRepresentation(fx);
+ }
+ }
+
+ public void addEffects() {
+ mEffects.add(getRepresentation(ImageFilterTinyPlanet.class));
+ mEffects.add(getRepresentation(ImageFilterWBalance.class));
+ mEffects.add(getRepresentation(ImageFilterExposure.class));
+ mEffects.add(getRepresentation(ImageFilterVignette.class));
+ mEffects.add(getRepresentation(ImageFilterGrad.class));
+ mEffects.add(getRepresentation(ImageFilterContrast.class));
+ mEffects.add(getRepresentation(ImageFilterShadows.class));
+ mEffects.add(getRepresentation(ImageFilterHighlights.class));
+ mEffects.add(getRepresentation(ImageFilterVibrance.class));
+ mEffects.add(getRepresentation(ImageFilterSharpen.class));
+ mEffects.add(getRepresentation(ImageFilterCurves.class));
+ mEffects.add(getRepresentation(ImageFilterHue.class));
+ mEffects.add(getRepresentation(ImageFilterChanSat.class));
+ mEffects.add(getRepresentation(ImageFilterBwFilter.class));
+ mEffects.add(getRepresentation(ImageFilterNegative.class));
+ mEffects.add(getRepresentation(ImageFilterEdge.class));
+ mEffects.add(getRepresentation(ImageFilterKMeans.class));
+ }
+
+ public void addTools(Context context) {
+
+ int[] editorsId = {
+ EditorCrop.ID,
+ EditorStraighten.ID,
+ EditorRotate.ID,
+ EditorMirror.ID
+ };
+
+ int[] textId = {
+ R.string.crop,
+ R.string.straighten,
+ R.string.rotate,
+ R.string.mirror
+ };
+
+ int[] overlayId = {
+ R.drawable.filtershow_button_geometry_crop,
+ R.drawable.filtershow_button_geometry_straighten,
+ R.drawable.filtershow_button_geometry_rotate,
+ R.drawable.filtershow_button_geometry_flip
+ };
+
+ FilterRepresentation[] geometryFilters = {
+ new FilterCropRepresentation(),
+ new FilterStraightenRepresentation(),
+ new FilterRotateRepresentation(),
+ new FilterMirrorRepresentation()
+ };
+
+ for (int i = 0; i < editorsId.length; i++) {
+ int editorId = editorsId[i];
+ FilterRepresentation geometry = geometryFilters[i];
+ geometry.setEditorId(editorId);
+ geometry.setTextId(textId[i]);
+ geometry.setOverlayId(overlayId[i]);
+ geometry.setOverlayOnly(true);
+ if (geometry.getTextId() != 0) {
+ geometry.setName(context.getString(geometry.getTextId()));
+ }
+ mTools.add(geometry);
+ }
+
+ mTools.add(getRepresentation(ImageFilterRedEye.class));
+ mTools.add(getRepresentation(ImageFilterDraw.class));
+ }
+
+ public void setFilterResources(Resources resources) {
+ ImageFilterBorder filterBorder = (ImageFilterBorder) getFilter(ImageFilterBorder.class);
+ filterBorder.setResources(resources);
+ ImageFilterFx filterFx = (ImageFilterFx) getFilter(ImageFilterFx.class);
+ filterFx.setResources(resources);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ColorSpaceMatrix.java b/src/com/android/gallery3d/filtershow/filters/ColorSpaceMatrix.java
new file mode 100644
index 000000000..7c307a9e7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ColorSpaceMatrix.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import java.util.Arrays;
+
+public class ColorSpaceMatrix {
+ private final float[] mMatrix = new float[16];
+ private static final float RLUM = 0.3086f;
+ private static final float GLUM = 0.6094f;
+ private static final float BLUM = 0.0820f;
+
+ public ColorSpaceMatrix() {
+ identity();
+ }
+
+ /**
+ * Copy constructor
+ *
+ * @param matrix
+ */
+ public ColorSpaceMatrix(ColorSpaceMatrix matrix) {
+ System.arraycopy(matrix.mMatrix, 0, mMatrix, 0, matrix.mMatrix.length);
+ }
+
+ /**
+ * get the matrix
+ *
+ * @return the internal matrix
+ */
+ public float[] getMatrix() {
+ return mMatrix;
+ }
+
+ /**
+ * set matrix to identity
+ */
+ public void identity() {
+ Arrays.fill(mMatrix, 0);
+ mMatrix[0] = mMatrix[5] = mMatrix[10] = mMatrix[15] = 1;
+ }
+
+ public void convertToLuminance() {
+ mMatrix[0] = mMatrix[1] = mMatrix[2] = 0.3086f;
+ mMatrix[4] = mMatrix[5] = mMatrix[6] = 0.6094f;
+ mMatrix[8] = mMatrix[9] = mMatrix[10] = 0.0820f;
+ }
+
+ private void multiply(float[] a)
+ {
+ int x, y;
+ float[] temp = new float[16];
+
+ for (y = 0; y < 4; y++) {
+ int y4 = y * 4;
+ for (x = 0; x < 4; x++) {
+ temp[y4 + x] = mMatrix[y4 + 0] * a[x]
+ + mMatrix[y4 + 1] * a[4 + x]
+ + mMatrix[y4 + 2] * a[8 + x]
+ + mMatrix[y4 + 3] * a[12 + x];
+ }
+ }
+ for (int i = 0; i < 16; i++)
+ mMatrix[i] = temp[i];
+ }
+
+ private void xRotateMatrix(float rs, float rc)
+ {
+ ColorSpaceMatrix c = new ColorSpaceMatrix();
+ float[] tmp = c.mMatrix;
+
+ tmp[5] = rc;
+ tmp[6] = rs;
+ tmp[9] = -rs;
+ tmp[10] = rc;
+
+ multiply(tmp);
+ }
+
+ private void yRotateMatrix(float rs, float rc)
+ {
+ ColorSpaceMatrix c = new ColorSpaceMatrix();
+ float[] tmp = c.mMatrix;
+
+ tmp[0] = rc;
+ tmp[2] = -rs;
+ tmp[8] = rs;
+ tmp[10] = rc;
+
+ multiply(tmp);
+ }
+
+ private void zRotateMatrix(float rs, float rc)
+ {
+ ColorSpaceMatrix c = new ColorSpaceMatrix();
+ float[] tmp = c.mMatrix;
+
+ tmp[0] = rc;
+ tmp[1] = rs;
+ tmp[4] = -rs;
+ tmp[5] = rc;
+ multiply(tmp);
+ }
+
+ private void zShearMatrix(float dx, float dy)
+ {
+ ColorSpaceMatrix c = new ColorSpaceMatrix();
+ float[] tmp = c.mMatrix;
+
+ tmp[2] = dx;
+ tmp[6] = dy;
+ multiply(tmp);
+ }
+
+ /**
+ * sets the transform to a shift in Hue
+ *
+ * @param rot rotation in degrees
+ */
+ public void setHue(float rot)
+ {
+ float mag = (float) Math.sqrt(2.0);
+ float xrs = 1 / mag;
+ float xrc = 1 / mag;
+ xRotateMatrix(xrs, xrc);
+ mag = (float) Math.sqrt(3.0);
+ float yrs = -1 / mag;
+ float yrc = (float) Math.sqrt(2.0) / mag;
+ yRotateMatrix(yrs, yrc);
+
+ float lx = getRedf(RLUM, GLUM, BLUM);
+ float ly = getGreenf(RLUM, GLUM, BLUM);
+ float lz = getBluef(RLUM, GLUM, BLUM);
+ float zsx = lx / lz;
+ float zsy = ly / lz;
+ zShearMatrix(zsx, zsy);
+
+ float zrs = (float) Math.sin(rot * Math.PI / 180.0);
+ float zrc = (float) Math.cos(rot * Math.PI / 180.0);
+ zRotateMatrix(zrs, zrc);
+ zShearMatrix(-zsx, -zsy);
+ yRotateMatrix(-yrs, yrc);
+ xRotateMatrix(-xrs, xrc);
+ }
+
+ /**
+ * set it to a saturation matrix
+ *
+ * @param s
+ */
+ public void changeSaturation(float s) {
+ mMatrix[0] = (1 - s) * RLUM + s;
+ mMatrix[1] = (1 - s) * RLUM;
+ mMatrix[2] = (1 - s) * RLUM;
+ mMatrix[4] = (1 - s) * GLUM;
+ mMatrix[5] = (1 - s) * GLUM + s;
+ mMatrix[6] = (1 - s) * GLUM;
+ mMatrix[8] = (1 - s) * BLUM;
+ mMatrix[9] = (1 - s) * BLUM;
+ mMatrix[10] = (1 - s) * BLUM + s;
+ }
+
+ /**
+ * Transform RGB value
+ *
+ * @param r red pixel value
+ * @param g green pixel value
+ * @param b blue pixel value
+ * @return computed red pixel value
+ */
+ public float getRed(int r, int g, int b) {
+ return r * mMatrix[0] + g * mMatrix[4] + b * mMatrix[8] + mMatrix[12];
+ }
+
+ /**
+ * Transform RGB value
+ *
+ * @param r red pixel value
+ * @param g green pixel value
+ * @param b blue pixel value
+ * @return computed green pixel value
+ */
+ public float getGreen(int r, int g, int b) {
+ return r * mMatrix[1] + g * mMatrix[5] + b * mMatrix[9] + mMatrix[13];
+ }
+
+ /**
+ * Transform RGB value
+ *
+ * @param r red pixel value
+ * @param g green pixel value
+ * @param b blue pixel value
+ * @return computed blue pixel value
+ */
+ public float getBlue(int r, int g, int b) {
+ return r * mMatrix[2] + g * mMatrix[6] + b * mMatrix[10] + mMatrix[14];
+ }
+
+ private float getRedf(float r, float g, float b) {
+ return r * mMatrix[0] + g * mMatrix[4] + b * mMatrix[8] + mMatrix[12];
+ }
+
+ private float getGreenf(float r, float g, float b) {
+ return r * mMatrix[1] + g * mMatrix[5] + b * mMatrix[9] + mMatrix[13];
+ }
+
+ private float getBluef(float r, float g, float b) {
+ return r * mMatrix[2] + g * mMatrix[6] + b * mMatrix[10] + mMatrix[14];
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java
new file mode 100644
index 000000000..1eebdb571
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.controller.Control;
+import com.android.gallery3d.filtershow.controller.FilterView;
+import com.android.gallery3d.filtershow.controller.Parameter;
+import com.android.gallery3d.filtershow.controller.ParameterInteger;
+
+public class FilterBasicRepresentation extends FilterRepresentation implements ParameterInteger {
+ private static final String LOGTAG = "FilterBasicRep";
+ private int mMinimum;
+ private int mValue;
+ private int mMaximum;
+ private int mDefaultValue;
+ private int mPreviewValue;
+ public static final String SERIAL_NAME = "Name";
+ public static final String SERIAL_VALUE = "Value";
+ private boolean mLogVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
+
+ public FilterBasicRepresentation(String name, int minimum, int value, int maximum) {
+ super(name);
+ mMinimum = minimum;
+ mMaximum = maximum;
+ setValue(value);
+ }
+
+ @Override
+ public String toString() {
+ return getName() + " : " + mMinimum + " < " + mValue + " < " + mMaximum;
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterBasicRepresentation representation = new FilterBasicRepresentation(getName(),0,0,0);
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ if (a instanceof FilterBasicRepresentation) {
+ FilterBasicRepresentation representation = (FilterBasicRepresentation) a;
+ setMinimum(representation.getMinimum());
+ setMaximum(representation.getMaximum());
+ setValue(representation.getValue());
+ setDefaultValue(representation.getDefaultValue());
+ setPreviewValue(representation.getPreviewValue());
+ }
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation representation) {
+ if (!super.equals(representation)) {
+ return false;
+ }
+ if (representation instanceof FilterBasicRepresentation) {
+ FilterBasicRepresentation basic = (FilterBasicRepresentation) representation;
+ if (basic.mMinimum == mMinimum
+ && basic.mMaximum == mMaximum
+ && basic.mValue == mValue
+ && basic.mDefaultValue == mDefaultValue
+ && basic.mPreviewValue == mPreviewValue) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getMinimum() {
+ return mMinimum;
+ }
+
+ public void setMinimum(int minimum) {
+ mMinimum = minimum;
+ }
+
+ @Override
+ public int getValue() {
+ return mValue;
+ }
+
+ @Override
+ public void setValue(int value) {
+ mValue = value;
+ if (mValue < mMinimum) {
+ mValue = mMinimum;
+ }
+ if (mValue > mMaximum) {
+ mValue = mMaximum;
+ }
+ }
+
+ @Override
+ public int getMaximum() {
+ return mMaximum;
+ }
+
+ public void setMaximum(int maximum) {
+ mMaximum = maximum;
+ }
+
+ public void setDefaultValue(int defaultValue) {
+ mDefaultValue = defaultValue;
+ }
+
+ @Override
+ public int getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ public int getPreviewValue() {
+ return mPreviewValue;
+ }
+
+ public void setPreviewValue(int previewValue) {
+ mPreviewValue = previewValue;
+ }
+
+ @Override
+ public String getStateRepresentation() {
+ int val = getValue();
+ return ((val > 0) ? "+" : "") + val;
+ }
+
+ @Override
+ public String getParameterType(){
+ return sParameterType;
+ }
+
+ @Override
+ public void setController(Control control) {
+ }
+
+ @Override
+ public String getValueString() {
+ return getStateRepresentation();
+ }
+
+ @Override
+ public String getParameterName() {
+ return getName();
+ }
+
+ @Override
+ public void setFilterView(FilterView editor) {
+ }
+
+ @Override
+ public void copyFrom(Parameter src) {
+ useParametersFrom((FilterBasicRepresentation) src);
+ }
+
+ @Override
+ public String[][] serializeRepresentation() {
+ String[][] ret = {
+ {SERIAL_NAME , getName() },
+ {SERIAL_VALUE , Integer.toString(mValue)}};
+ return ret;
+ }
+
+ @Override
+ public void deSerializeRepresentation(String[][] rep) {
+ super.deSerializeRepresentation(rep);
+ for (int i = 0; i < rep.length; i++) {
+ if (SERIAL_VALUE.equals(rep[i][0])) {
+ mValue = Integer.parseInt(rep[i][1]);
+ break;
+ }
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterChanSatRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterChanSatRepresentation.java
new file mode 100644
index 000000000..7ce67dd96
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterChanSatRepresentation.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.controller.BasicParameterInt;
+import com.android.gallery3d.filtershow.controller.Parameter;
+import com.android.gallery3d.filtershow.controller.ParameterSet;
+import com.android.gallery3d.filtershow.editors.EditorChanSat;
+import com.android.gallery3d.filtershow.imageshow.ControlPoint;
+import com.android.gallery3d.filtershow.imageshow.Spline;
+
+import java.io.IOException;
+import java.util.Vector;
+
+/**
+ * Representation for a filter that has per channel & Master saturation
+ */
+public class FilterChanSatRepresentation extends FilterRepresentation implements ParameterSet {
+ private static final String LOGTAG = "FilterChanSatRepresentation";
+ private static final String ARGS = "ARGS";
+ private static final String SERIALIZATION_NAME = "channelsaturation";
+
+ public static final int MODE_MASTER = 0;
+ public static final int MODE_RED = 1;
+ public static final int MODE_YELLOW = 2;
+ public static final int MODE_GREEN = 3;
+ public static final int MODE_CYAN = 4;
+ public static final int MODE_BLUE = 5;
+ public static final int MODE_MAGENTA = 6;
+ private int mParameterMode = MODE_MASTER;
+
+ private static int MINSAT = -100;
+ private static int MAXSAT = 100;
+ private BasicParameterInt mParamMaster = new BasicParameterInt(MODE_MASTER, 0, MINSAT, MAXSAT);
+ private BasicParameterInt mParamRed = new BasicParameterInt(MODE_RED, 0, MINSAT, MAXSAT);
+ private BasicParameterInt mParamYellow = new BasicParameterInt(MODE_YELLOW, 0, MINSAT, MAXSAT);
+ private BasicParameterInt mParamGreen = new BasicParameterInt(MODE_GREEN, 0, MINSAT, MAXSAT);
+ private BasicParameterInt mParamCyan = new BasicParameterInt(MODE_CYAN, 0, MINSAT, MAXSAT);
+ private BasicParameterInt mParamBlue = new BasicParameterInt(MODE_BLUE, 0, MINSAT, MAXSAT);
+ private BasicParameterInt mParamMagenta = new BasicParameterInt(MODE_MAGENTA, 0, MINSAT, MAXSAT);
+
+ private BasicParameterInt[] mAllParam = {
+ mParamMaster,
+ mParamRed,
+ mParamYellow,
+ mParamGreen,
+ mParamCyan,
+ mParamBlue,
+ mParamMagenta};
+
+ public FilterChanSatRepresentation() {
+ super("ChannelSaturation");
+ setTextId(R.string.saturation);
+ setFilterType(FilterRepresentation.TYPE_NORMAL);
+ setSerializationName(SERIALIZATION_NAME);
+ setFilterClass(ImageFilterChanSat.class);
+ setEditorId(EditorChanSat.ID);
+ }
+
+ public String toString() {
+ return getName() + " : " + mParamRed + ", " + mParamCyan + ", " + mParamRed
+ + ", " + mParamGreen + ", " + mParamMaster + ", " + mParamYellow;
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterChanSatRepresentation representation = new FilterChanSatRepresentation();
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ public void useParametersFrom(FilterRepresentation a) {
+ if (a instanceof FilterChanSatRepresentation) {
+ FilterChanSatRepresentation representation = (FilterChanSatRepresentation) a;
+
+ for (int i = 0; i < mAllParam.length; i++) {
+ mAllParam[i].copyFrom(representation.mAllParam[i]);
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation representation) {
+ if (!super.equals(representation)) {
+ return false;
+ }
+ if (representation instanceof FilterChanSatRepresentation) {
+ FilterChanSatRepresentation rep = (FilterChanSatRepresentation) representation;
+ for (int i = 0; i < mAllParam.length; i++) {
+ if (rep.getValue(i) != getValue(i))
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public int getValue(int mode) {
+ return mAllParam[mode].getValue();
+ }
+
+ public void setValue(int mode, int value) {
+ mAllParam[mode].setValue(value);
+ }
+
+ public int getMinimum() {
+ return mParamMaster.getMinimum();
+ }
+
+ public int getMaximum() {
+ return mParamMaster.getMaximum();
+ }
+
+ public int getParameterMode() {
+ return mParameterMode;
+ }
+
+ public void setParameterMode(int parameterMode) {
+ mParameterMode = parameterMode;
+ }
+
+ public int getCurrentParameter() {
+ return getValue(mParameterMode);
+ }
+
+ public void setCurrentParameter(int value) {
+ setValue(mParameterMode, value);
+ }
+
+ @Override
+ public int getNumberOfParameters() {
+ return 6;
+ }
+
+ @Override
+ public Parameter getFilterParameter(int index) {
+ return mAllParam[index];
+ }
+
+ @Override
+ public void serializeRepresentation(JsonWriter writer) throws IOException {
+ writer.beginObject();
+
+ writer.name(ARGS);
+ writer.beginArray();
+ writer.value(getValue(MODE_MASTER));
+ writer.value(getValue(MODE_RED));
+ writer.value(getValue(MODE_YELLOW));
+ writer.value(getValue(MODE_GREEN));
+ writer.value(getValue(MODE_CYAN));
+ writer.value(getValue(MODE_BLUE));
+ writer.value(getValue(MODE_MAGENTA));
+ writer.endArray();
+ writer.endObject();
+ }
+
+ @Override
+ public void deSerializeRepresentation(JsonReader sreader) throws IOException {
+ sreader.beginObject();
+
+ while (sreader.hasNext()) {
+ String name = sreader.nextName();
+ if (name.startsWith(ARGS)) {
+ sreader.beginArray();
+ sreader.hasNext();
+ setValue(MODE_MASTER, sreader.nextInt());
+ sreader.hasNext();
+ setValue(MODE_RED, sreader.nextInt());
+ sreader.hasNext();
+ setValue(MODE_YELLOW, sreader.nextInt());
+ sreader.hasNext();
+ setValue(MODE_GREEN, sreader.nextInt());
+ sreader.hasNext();
+ setValue(MODE_CYAN, sreader.nextInt());
+ sreader.hasNext();
+ setValue(MODE_BLUE, sreader.nextInt());
+ sreader.hasNext();
+ setValue(MODE_MAGENTA, sreader.nextInt());
+ sreader.hasNext();
+ sreader.endArray();
+ } else {
+ sreader.skipValue();
+ }
+ }
+ sreader.endObject();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterColorBorderRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterColorBorderRepresentation.java
new file mode 100644
index 000000000..94eb20631
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterColorBorderRepresentation.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+
+public class FilterColorBorderRepresentation extends FilterRepresentation {
+ private int mColor;
+ private int mBorderSize;
+ private int mBorderRadius;
+
+ public FilterColorBorderRepresentation(int color, int size, int radius) {
+ super("ColorBorder");
+ mColor = color;
+ mBorderSize = size;
+ mBorderRadius = radius;
+ setFilterType(FilterRepresentation.TYPE_BORDER);
+ setTextId(R.string.borders);
+ setEditorId(ImageOnlyEditor.ID);
+ setShowParameterValue(false);
+ }
+
+ public String toString() {
+ return "FilterBorder: " + getName();
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterColorBorderRepresentation representation = new FilterColorBorderRepresentation(0,0,0);
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ public void useParametersFrom(FilterRepresentation a) {
+ if (a instanceof FilterColorBorderRepresentation) {
+ FilterColorBorderRepresentation representation = (FilterColorBorderRepresentation) a;
+ setName(representation.getName());
+ setColor(representation.getColor());
+ setBorderSize(representation.getBorderSize());
+ setBorderRadius(representation.getBorderRadius());
+ }
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation representation) {
+ if (!super.equals(representation)) {
+ return false;
+ }
+ if (representation instanceof FilterColorBorderRepresentation) {
+ FilterColorBorderRepresentation border = (FilterColorBorderRepresentation) representation;
+ if (border.mColor == mColor
+ && border.mBorderSize == mBorderSize
+ && border.mBorderRadius == mBorderRadius) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean allowsSingleInstanceOnly() {
+ return true;
+ }
+
+ @Override
+ public int getTextId() {
+ return R.string.borders;
+ }
+
+ public int getColor() {
+ return mColor;
+ }
+
+ public void setColor(int color) {
+ mColor = color;
+ }
+
+ public int getBorderSize() {
+ return mBorderSize;
+ }
+
+ public void setBorderSize(int borderSize) {
+ mBorderSize = borderSize;
+ }
+
+ public int getBorderRadius() {
+ return mBorderRadius;
+ }
+
+ public void setBorderRadius(int borderRadius) {
+ mBorderRadius = borderRadius;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java
new file mode 100644
index 000000000..c1bd7b3bb
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.graphics.RectF;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorCrop;
+
+import java.io.IOException;
+
+public class FilterCropRepresentation extends FilterRepresentation {
+ public static final String SERIALIZATION_NAME = "CROP";
+ public static final String[] BOUNDS = {
+ "C0", "C1", "C2", "C3"
+ };
+ private static final String TAG = FilterCropRepresentation.class.getSimpleName();
+
+ RectF mCrop = getNil();
+
+ public FilterCropRepresentation(RectF crop) {
+ super(FilterCropRepresentation.class.getSimpleName());
+ setSerializationName(SERIALIZATION_NAME);
+ setShowParameterValue(true);
+ setFilterClass(FilterCropRepresentation.class);
+ setFilterType(FilterRepresentation.TYPE_GEOMETRY);
+ setTextId(R.string.crop);
+ setEditorId(EditorCrop.ID);
+ setCrop(crop);
+ }
+
+ public FilterCropRepresentation(FilterCropRepresentation m) {
+ this(m.mCrop);
+ }
+
+ public FilterCropRepresentation() {
+ this(sNilRect);
+ }
+
+ public void set(FilterCropRepresentation r) {
+ mCrop.set(r.mCrop);
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation rep) {
+ if (!(rep instanceof FilterCropRepresentation)) {
+ return false;
+ }
+ FilterCropRepresentation crop = (FilterCropRepresentation) rep;
+ if (mCrop.bottom != crop.mCrop.bottom
+ || mCrop.left != crop.mCrop.left
+ || mCrop.right != crop.mCrop.right
+ || mCrop.top != crop.mCrop.top) {
+ return false;
+ }
+ return true;
+ }
+
+ public RectF getCrop() {
+ return new RectF(mCrop);
+ }
+
+ public void getCrop(RectF r) {
+ r.set(mCrop);
+ }
+
+ public void setCrop(RectF crop) {
+ if (crop == null) {
+ throw new IllegalArgumentException("Argument to setCrop is null");
+ }
+ mCrop.set(crop);
+ }
+
+ /**
+ * Takes a crop rect contained by [0, 0, 1, 1] and scales it by the height
+ * and width of the image rect.
+ */
+ public static void findScaledCrop(RectF crop, int bitmapWidth, int bitmapHeight) {
+ crop.left *= bitmapWidth;
+ crop.top *= bitmapHeight;
+ crop.right *= bitmapWidth;
+ crop.bottom *= bitmapHeight;
+ }
+
+ /**
+ * Takes crop rect and normalizes it by scaling down by the height and width
+ * of the image rect.
+ */
+ public static void findNormalizedCrop(RectF crop, int bitmapWidth, int bitmapHeight) {
+ crop.left /= bitmapWidth;
+ crop.top /= bitmapHeight;
+ crop.right /= bitmapWidth;
+ crop.bottom /= bitmapHeight;
+ }
+
+ @Override
+ public boolean allowsSingleInstanceOnly() {
+ return true;
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ return new FilterCropRepresentation(this);
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ if (!(representation instanceof FilterCropRepresentation)) {
+ throw new IllegalArgumentException("calling copyAllParameters with incompatible types!");
+ }
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ if (!(a instanceof FilterCropRepresentation)) {
+ throw new IllegalArgumentException("calling useParametersFrom with incompatible types!");
+ }
+ setCrop(((FilterCropRepresentation) a).mCrop);
+ }
+
+ private static final RectF sNilRect = new RectF(0, 0, 1, 1);
+
+ @Override
+ public boolean isNil() {
+ return mCrop.equals(sNilRect);
+ }
+
+ public static RectF getNil() {
+ return new RectF(sNilRect);
+ }
+
+ @Override
+ public void serializeRepresentation(JsonWriter writer) throws IOException {
+ writer.beginObject();
+ writer.name(BOUNDS[0]).value(mCrop.left);
+ writer.name(BOUNDS[1]).value(mCrop.top);
+ writer.name(BOUNDS[2]).value(mCrop.right);
+ writer.name(BOUNDS[3]).value(mCrop.bottom);
+ writer.endObject();
+ }
+
+ @Override
+ public void deSerializeRepresentation(JsonReader reader) throws IOException {
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ if (BOUNDS[0].equals(name)) {
+ mCrop.left = (float) reader.nextDouble();
+ } else if (BOUNDS[1].equals(name)) {
+ mCrop.top = (float) reader.nextDouble();
+ } else if (BOUNDS[2].equals(name)) {
+ mCrop.right = (float) reader.nextDouble();
+ } else if (BOUNDS[3].equals(name)) {
+ mCrop.bottom = (float) reader.nextDouble();
+ } else {
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java
new file mode 100644
index 000000000..edab2a08d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java
@@ -0,0 +1,170 @@
+package com.android.gallery3d.filtershow.filters;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.ControlPoint;
+import com.android.gallery3d.filtershow.imageshow.Spline;
+
+import java.io.IOException;
+
+/**
+ * TODO: Insert description here. (generated by hoford)
+ */
+public class FilterCurvesRepresentation extends FilterRepresentation {
+ private static final String LOGTAG = "FilterCurvesRepresentation";
+ public static final String SERIALIZATION_NAME = "Curve";
+ private static final int MAX_SPLINE_NUMBER = 4;
+
+ private Spline[] mSplines = new Spline[MAX_SPLINE_NUMBER];
+
+ public FilterCurvesRepresentation() {
+ super("Curves");
+ setSerializationName("CURVES");
+ setFilterClass(ImageFilterCurves.class);
+ setTextId(R.string.curvesRGB);
+ setOverlayId(R.drawable.filtershow_button_colors_curve);
+ setEditorId(R.id.imageCurves);
+ setShowParameterValue(false);
+ setSupportsPartialRendering(true);
+ reset();
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterCurvesRepresentation representation = new FilterCurvesRepresentation();
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ if (!(a instanceof FilterCurvesRepresentation)) {
+ Log.v(LOGTAG, "cannot use parameters from " + a);
+ return;
+ }
+ FilterCurvesRepresentation representation = (FilterCurvesRepresentation) a;
+ Spline[] spline = new Spline[MAX_SPLINE_NUMBER];
+ for (int i = 0; i < spline.length; i++) {
+ Spline sp = representation.mSplines[i];
+ if (sp != null) {
+ spline[i] = new Spline(sp);
+ } else {
+ spline[i] = new Spline();
+ }
+ }
+ mSplines = spline;
+ }
+
+ @Override
+ public boolean isNil() {
+ for (int i = 0; i < MAX_SPLINE_NUMBER; i++) {
+ if (getSpline(i) != null && !getSpline(i).isOriginal()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation representation) {
+ if (!super.equals(representation)) {
+ return false;
+ }
+
+ if (!(representation instanceof FilterCurvesRepresentation)) {
+ return false;
+ } else {
+ FilterCurvesRepresentation curve =
+ (FilterCurvesRepresentation) representation;
+ for (int i = 0; i < MAX_SPLINE_NUMBER; i++) {
+ if (!getSpline(i).sameValues(curve.getSpline(i))) {
+ return false;
+ }
+ }
+ }
+ // Every spline matches, therefore they are the same.
+ return true;
+ }
+
+ public void reset() {
+ Spline spline = new Spline();
+
+ spline.addPoint(0.0f, 1.0f);
+ spline.addPoint(1.0f, 0.0f);
+
+ for (int i = 0; i < MAX_SPLINE_NUMBER; i++) {
+ mSplines[i] = new Spline(spline);
+ }
+ }
+
+ public void setSpline(int splineIndex, Spline s) {
+ mSplines[splineIndex] = s;
+ }
+
+ public Spline getSpline(int splineIndex) {
+ return mSplines[splineIndex];
+ }
+
+ @Override
+ public void serializeRepresentation(JsonWriter writer) throws IOException {
+ writer.beginObject();
+ {
+ writer.name(NAME_TAG);
+ writer.value(getName());
+ for (int i = 0; i < mSplines.length; i++) {
+ writer.name(SERIALIZATION_NAME + i);
+ writer.beginArray();
+ int nop = mSplines[i].getNbPoints();
+ for (int j = 0; j < nop; j++) {
+ ControlPoint p = mSplines[i].getPoint(j);
+ writer.beginArray();
+ writer.value(p.x);
+ writer.value(p.y);
+ writer.endArray();
+ }
+ writer.endArray();
+ }
+
+ }
+ writer.endObject();
+ }
+
+ @Override
+ public void deSerializeRepresentation(JsonReader sreader) throws IOException {
+ sreader.beginObject();
+ Spline[] spline = new Spline[MAX_SPLINE_NUMBER];
+ while (sreader.hasNext()) {
+ String name = sreader.nextName();
+ if (NAME_TAG.equals(name)) {
+ setName(sreader.nextString());
+ } else if (name.startsWith(SERIALIZATION_NAME)) {
+ int curveNo = Integer.parseInt(name.substring(SERIALIZATION_NAME.length()));
+ spline[curveNo] = new Spline();
+ sreader.beginArray();
+ while (sreader.hasNext()) {
+ sreader.beginArray();
+ sreader.hasNext();
+ float x = (float) sreader.nextDouble();
+ sreader.hasNext();
+ float y = (float) sreader.nextDouble();
+ sreader.endArray();
+ spline[curveNo].addPoint(x, y);
+ }
+ sreader.endArray();
+
+ }
+ }
+ mSplines = spline;
+ sreader.endObject();
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterDirectRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterDirectRepresentation.java
new file mode 100644
index 000000000..ac0cb7492
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterDirectRepresentation.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+public class FilterDirectRepresentation extends FilterRepresentation {
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterDirectRepresentation representation = new FilterDirectRepresentation(getName());
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ public FilterDirectRepresentation(String name) {
+ super(name);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
new file mode 100644
index 000000000..977dbeac5
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.graphics.Path;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorDraw;
+
+import java.util.Vector;
+
+public class FilterDrawRepresentation extends FilterRepresentation {
+ private static final String LOGTAG = "FilterDrawRepresentation";
+
+ public static class StrokeData implements Cloneable {
+ public byte mType;
+ public Path mPath;
+ public float mRadius;
+ public int mColor;
+ public int noPoints = 0;
+ @Override
+ public String toString() {
+ return "stroke(" + mType + ", path(" + (mPath) + "), " + mRadius + " , "
+ + Integer.toHexString(mColor) + ")";
+ }
+ @Override
+ public StrokeData clone() throws CloneNotSupportedException {
+ return (StrokeData) super.clone();
+ }
+ }
+
+ private Vector<StrokeData> mDrawing = new Vector<StrokeData>();
+ private StrokeData mCurrent; // used in the currently drawing style
+
+ public FilterDrawRepresentation() {
+ super("Draw");
+ setFilterClass(ImageFilterDraw.class);
+ setSerializationName("DRAW");
+ setFilterType(FilterRepresentation.TYPE_VIGNETTE);
+ setTextId(R.string.imageDraw);
+ setEditorId(EditorDraw.ID);
+ setOverlayId(R.drawable.filtershow_drawing);
+ setOverlayOnly(true);
+ }
+
+ @Override
+ public String toString() {
+ return getName() + " : strokes=" + mDrawing.size()
+ + ((mCurrent == null) ? " no current "
+ : ("draw=" + mCurrent.mType + " " + mCurrent.noPoints));
+ }
+
+ public Vector<StrokeData> getDrawing() {
+ return mDrawing;
+ }
+
+ public StrokeData getCurrentDrawing() {
+ return mCurrent;
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterDrawRepresentation representation = new FilterDrawRepresentation();
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public boolean isNil() {
+ return getDrawing().isEmpty();
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ if (a instanceof FilterDrawRepresentation) {
+ FilterDrawRepresentation representation = (FilterDrawRepresentation) a;
+ try {
+ if (representation.mCurrent != null) {
+ mCurrent = (StrokeData) representation.mCurrent.clone();
+ } else {
+ mCurrent = null;
+ }
+ if (representation.mDrawing != null) {
+ mDrawing = (Vector<StrokeData>) representation.mDrawing.clone();
+ } else {
+ mDrawing = null;
+ }
+
+ } catch (CloneNotSupportedException e) {
+ e.printStackTrace();
+ }
+ } else {
+ Log.v(LOGTAG, "cannot use parameters from " + a);
+ }
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation representation) {
+ if (!super.equals(representation)) {
+ return false;
+ }
+ if (representation instanceof FilterDrawRepresentation) {
+ FilterDrawRepresentation fdRep = (FilterDrawRepresentation) representation;
+ if (fdRep.mDrawing.size() != mDrawing.size())
+ return false;
+ if (fdRep.mCurrent == null && mCurrent.mPath == null) {
+ return true;
+ }
+ if (fdRep.mCurrent != null && mCurrent.mPath != null) {
+ if (fdRep.mCurrent.noPoints == mCurrent.noPoints) {
+ return true;
+ }
+ return false;
+ }
+ }
+ return false;
+ }
+
+ public void startNewSection(byte type, int color, float size, float x, float y) {
+ mCurrent = new StrokeData();
+ mCurrent.mColor = color;
+ mCurrent.mRadius = size;
+ mCurrent.mType = type;
+ mCurrent.mPath = new Path();
+ mCurrent.mPath.moveTo(x, y);
+ mCurrent.noPoints = 0;
+ }
+
+ public void addPoint(float x, float y) {
+ mCurrent.noPoints++;
+ mCurrent.mPath.lineTo(x, y);
+ }
+
+ public void endSection(float x, float y) {
+ mCurrent.mPath.lineTo(x, y);
+ mCurrent.noPoints++;
+ mDrawing.add(mCurrent);
+ mCurrent = null;
+ }
+
+ public void clearCurrentSection() {
+ mCurrent = null;
+ }
+
+ public void clear() {
+ mCurrent = null;
+ mDrawing.clear();
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
new file mode 100644
index 000000000..e5a6fdd23
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+
+public class FilterFxRepresentation extends FilterRepresentation {
+ private static final String LOGTAG = "FilterFxRepresentation";
+ // TODO: When implementing serialization, we should find a unique way of
+ // specifying bitmaps / names (the resource IDs being random)
+ private int mBitmapResource = 0;
+ private int mNameResource = 0;
+
+ public FilterFxRepresentation(String name, int bitmapResource, int nameResource) {
+ super(name);
+ setFilterClass(ImageFilterFx.class);
+ mBitmapResource = bitmapResource;
+ mNameResource = nameResource;
+ setFilterType(FilterRepresentation.TYPE_FX);
+ setTextId(nameResource);
+ setEditorId(ImageOnlyEditor.ID);
+ setShowParameterValue(false);
+ setSupportsPartialRendering(true);
+ }
+
+ @Override
+ public String toString() {
+ return "FilterFx: " + hashCode() + " : " + getName() + " bitmap rsc: " + mBitmapResource;
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterFxRepresentation representation = new FilterFxRepresentation(getName(),0,0);
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public synchronized void useParametersFrom(FilterRepresentation a) {
+ if (a instanceof FilterFxRepresentation) {
+ FilterFxRepresentation representation = (FilterFxRepresentation) a;
+ setName(representation.getName());
+ setSerializationName(representation.getSerializationName());
+ setBitmapResource(representation.getBitmapResource());
+ setNameResource(representation.getNameResource());
+ }
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation representation) {
+ if (!super.equals(representation)) {
+ return false;
+ }
+ if (representation instanceof FilterFxRepresentation) {
+ FilterFxRepresentation fx = (FilterFxRepresentation) representation;
+ if (fx.mNameResource == mNameResource
+ && fx.mBitmapResource == mBitmapResource) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean same(FilterRepresentation representation) {
+ if (!super.same(representation)) {
+ return false;
+ }
+ return equals(representation);
+ }
+
+ @Override
+ public boolean allowsSingleInstanceOnly() {
+ return true;
+ }
+
+ public int getNameResource() {
+ return mNameResource;
+ }
+
+ public void setNameResource(int nameResource) {
+ mNameResource = nameResource;
+ }
+
+ public int getBitmapResource() {
+ return mBitmapResource;
+ }
+
+ public void setBitmapResource(int bitmapResource) {
+ mBitmapResource = bitmapResource;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterGradRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterGradRepresentation.java
new file mode 100644
index 000000000..0c272d48a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterGradRepresentation.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.graphics.Rect;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorGrad;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.imageshow.Line;
+
+import java.io.IOException;
+import java.util.Vector;
+
+public class FilterGradRepresentation extends FilterRepresentation
+ implements Line {
+ private static final String LOGTAG = "FilterGradRepresentation";
+ public static final int MAX_POINTS = 16;
+ public static final int PARAM_BRIGHTNESS = 0;
+ public static final int PARAM_SATURATION = 1;
+ public static final int PARAM_CONTRAST = 2;
+ private static final double ADD_MIN_DIST = .05;
+ private static String LINE_NAME = "Point";
+ private static final String SERIALIZATION_NAME = "grad";
+
+ public FilterGradRepresentation() {
+ super("Grad");
+ setSerializationName(SERIALIZATION_NAME);
+ creatExample();
+ setOverlayId(R.drawable.filtershow_button_grad);
+ setFilterClass(ImageFilterGrad.class);
+ setTextId(R.string.grad);
+ setEditorId(EditorGrad.ID);
+ }
+
+ public void trimVector(){
+ int n = mBands.size();
+ for (int i = n; i < MAX_POINTS; i++) {
+ mBands.add(new Band());
+ }
+ for (int i = MAX_POINTS; i < n; i++) {
+ mBands.remove(i);
+ }
+ }
+
+ Vector<Band> mBands = new Vector<Band>();
+ Band mCurrentBand;
+
+ static class Band {
+ private boolean mask = true;
+
+ private int xPos1 = -1;
+ private int yPos1 = 100;
+ private int xPos2 = -1;
+ private int yPos2 = 100;
+ private int brightness = 40;
+ private int contrast = 0;
+ private int saturation = 0;
+
+
+ public Band() {
+ }
+
+ public Band(int x, int y) {
+ xPos1 = x;
+ yPos1 = y+30;
+ xPos2 = x;
+ yPos2 = y-30;
+ }
+
+ public Band(Band copy) {
+ mask = copy.mask;
+ xPos1 = copy.xPos1;
+ yPos1 = copy.yPos1;
+ xPos2 = copy.xPos2;
+ yPos2 = copy.yPos2;
+ brightness = copy.brightness;
+ contrast = copy.contrast;
+ saturation = copy.saturation;
+ }
+
+ }
+
+ @Override
+ public String toString() {
+ int count = 0;
+ for (Band point : mBands) {
+ if (!point.mask) {
+ count++;
+ }
+ }
+ return "c=" + mBands.indexOf(mBands) + "[" + mBands.size() + "]" + count;
+ }
+
+ private void creatExample() {
+ Band p = new Band();
+ p.mask = false;
+ p.xPos1 = -1;
+ p.yPos1 = 100;
+ p.xPos2 = -1;
+ p.yPos2 = 100;
+ p.brightness = 40;
+ p.contrast = 0;
+ p.saturation = 0;
+ mBands.add(0, p);
+ mCurrentBand = p;
+ trimVector();
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ FilterGradRepresentation rep = (FilterGradRepresentation) a;
+ Vector<Band> tmpBands = new Vector<Band>();
+ int n = (rep.mCurrentBand == null) ? 0 : rep.mBands.indexOf(rep.mCurrentBand);
+ for (Band band : rep.mBands) {
+ tmpBands.add(new Band(band));
+ }
+ mCurrentBand = null;
+ mBands = tmpBands;
+ mCurrentBand = mBands.elementAt(n);
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterGradRepresentation representation = new FilterGradRepresentation();
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation representation) {
+ if (representation instanceof FilterGradRepresentation) {
+ FilterGradRepresentation rep = (FilterGradRepresentation) representation;
+ int n = getNumberOfBands();
+ if (rep.getNumberOfBands() != n) {
+ return false;
+ }
+ for (int i = 0; i < mBands.size(); i++) {
+ Band b1 = mBands.get(i);
+ Band b2 = rep.mBands.get(i);
+ if (b1.mask != b2.mask
+ || b1.brightness != b2.brightness
+ || b1.contrast != b2.contrast
+ || b1.saturation != b2.saturation
+ || b1.xPos1 != b2.xPos1
+ || b1.xPos2 != b2.xPos2
+ || b1.yPos1 != b2.yPos1
+ || b1.yPos2 != b2.yPos2) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public int getNumberOfBands() {
+ int count = 0;
+ for (Band point : mBands) {
+ if (!point.mask) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public int addBand(Rect rect) {
+ mBands.add(0, mCurrentBand = new Band(rect.centerX(), rect.centerY()));
+ mCurrentBand.mask = false;
+ int x = (mCurrentBand.xPos1 + mCurrentBand.xPos2)/2;
+ int y = (mCurrentBand.yPos1 + mCurrentBand.yPos2)/2;
+ double addDelta = ADD_MIN_DIST * Math.max(rect.width(), rect.height());
+ boolean moved = true;
+ int count = 0;
+ int toMove = mBands.indexOf(mCurrentBand);
+
+ while (moved) {
+ moved = false;
+ count++;
+ if (count > 14) {
+ break;
+ }
+
+ for (Band point : mBands) {
+ if (point.mask) {
+ break;
+ }
+ }
+
+ for (Band point : mBands) {
+ if (point.mask) {
+ break;
+ }
+ int index = mBands.indexOf(point);
+
+ if (toMove != index) {
+ double dist = Math.hypot(point.xPos1 - x, point.yPos1 - y);
+ if (dist < addDelta) {
+ moved = true;
+ mCurrentBand.xPos1 += addDelta;
+ mCurrentBand.yPos1 += addDelta;
+ mCurrentBand.xPos2 += addDelta;
+ mCurrentBand.yPos2 += addDelta;
+ x = (mCurrentBand.xPos1 + mCurrentBand.xPos2)/2;
+ y = (mCurrentBand.yPos1 + mCurrentBand.yPos2)/2;
+
+ if (mCurrentBand.yPos1 > rect.bottom) {
+ mCurrentBand.yPos1 = (int) (rect.top + addDelta);
+ }
+ if (mCurrentBand.xPos1 > rect.right) {
+ mCurrentBand.xPos1 = (int) (rect.left + addDelta);
+ }
+ }
+ }
+ }
+ }
+ trimVector();
+ return 0;
+ }
+
+ public void deleteCurrentBand() {
+ int index = mBands.indexOf(mCurrentBand);
+ mBands.remove(mCurrentBand);
+ trimVector();
+ if (getNumberOfBands() == 0) {
+ addBand(MasterImage.getImage().getOriginalBounds());
+ }
+ mCurrentBand = mBands.get(0);
+ }
+
+ public void nextPoint(){
+ int index = mBands.indexOf(mCurrentBand);
+ int tmp = index;
+ Band point;
+ int k = 0;
+ do {
+ index = (index+1)% mBands.size();
+ point = mBands.get(index);
+ if (k++ >= mBands.size()) {
+ break;
+ }
+ }
+ while (point.mask == true);
+ mCurrentBand = mBands.get(index);
+ }
+
+ public void setSelectedPoint(int pos) {
+ mCurrentBand = mBands.get(pos);
+ }
+
+ public int getSelectedPoint() {
+ return mBands.indexOf(mCurrentBand);
+ }
+
+ public boolean[] getMask() {
+ boolean[] ret = new boolean[mBands.size()];
+ int i = 0;
+ for (Band point : mBands) {
+ ret[i++] = !point.mask;
+ }
+ return ret;
+ }
+
+ public int[] getXPos1() {
+ int[] ret = new int[mBands.size()];
+ int i = 0;
+ for (Band point : mBands) {
+ ret[i++] = point.xPos1;
+ }
+ return ret;
+ }
+
+ public int[] getYPos1() {
+ int[] ret = new int[mBands.size()];
+ int i = 0;
+ for (Band point : mBands) {
+ ret[i++] = point.yPos1;
+ }
+ return ret;
+ }
+
+ public int[] getXPos2() {
+ int[] ret = new int[mBands.size()];
+ int i = 0;
+ for (Band point : mBands) {
+ ret[i++] = point.xPos2;
+ }
+ return ret;
+ }
+
+ public int[] getYPos2() {
+ int[] ret = new int[mBands.size()];
+ int i = 0;
+ for (Band point : mBands) {
+ ret[i++] = point.yPos2;
+ }
+ return ret;
+ }
+
+ public int[] getBrightness() {
+ int[] ret = new int[mBands.size()];
+ int i = 0;
+ for (Band point : mBands) {
+ ret[i++] = point.brightness;
+ }
+ return ret;
+ }
+
+ public int[] getContrast() {
+ int[] ret = new int[mBands.size()];
+ int i = 0;
+ for (Band point : mBands) {
+ ret[i++] = point.contrast;
+ }
+ return ret;
+ }
+
+ public int[] getSaturation() {
+ int[] ret = new int[mBands.size()];
+ int i = 0;
+ for (Band point : mBands) {
+ ret[i++] = point.saturation;
+ }
+ return ret;
+ }
+
+ public int getParameter(int type) {
+ switch (type){
+ case PARAM_BRIGHTNESS:
+ return mCurrentBand.brightness;
+ case PARAM_SATURATION:
+ return mCurrentBand.saturation;
+ case PARAM_CONTRAST:
+ return mCurrentBand.contrast;
+ }
+ throw new IllegalArgumentException("no such type " + type);
+ }
+
+ public int getParameterMax(int type) {
+ switch (type) {
+ case PARAM_BRIGHTNESS:
+ return 100;
+ case PARAM_SATURATION:
+ return 100;
+ case PARAM_CONTRAST:
+ return 100;
+ }
+ throw new IllegalArgumentException("no such type " + type);
+ }
+
+ public int getParameterMin(int type) {
+ switch (type) {
+ case PARAM_BRIGHTNESS:
+ return -100;
+ case PARAM_SATURATION:
+ return -100;
+ case PARAM_CONTRAST:
+ return -100;
+ }
+ throw new IllegalArgumentException("no such type " + type);
+ }
+
+ public void setParameter(int type, int value) {
+ mCurrentBand.mask = false;
+ switch (type) {
+ case PARAM_BRIGHTNESS:
+ mCurrentBand.brightness = value;
+ break;
+ case PARAM_SATURATION:
+ mCurrentBand.saturation = value;
+ break;
+ case PARAM_CONTRAST:
+ mCurrentBand.contrast = value;
+ break;
+ default:
+ throw new IllegalArgumentException("no such type " + type);
+ }
+ }
+
+ @Override
+ public void setPoint1(float x, float y) {
+ mCurrentBand.xPos1 = (int)x;
+ mCurrentBand.yPos1 = (int)y;
+ }
+
+ @Override
+ public void setPoint2(float x, float y) {
+ mCurrentBand.xPos2 = (int)x;
+ mCurrentBand.yPos2 = (int)y;
+ }
+
+ @Override
+ public float getPoint1X() {
+ return mCurrentBand.xPos1;
+ }
+
+ @Override
+ public float getPoint1Y() {
+ return mCurrentBand.yPos1;
+ }
+ @Override
+ public float getPoint2X() {
+ return mCurrentBand.xPos2;
+ }
+
+ @Override
+ public float getPoint2Y() {
+ return mCurrentBand.yPos2;
+ }
+
+ @Override
+ public void serializeRepresentation(JsonWriter writer) throws IOException {
+ writer.beginObject();
+ int len = mBands.size();
+ int count = 0;
+
+ for (int i = 0; i < len; i++) {
+ Band point = mBands.get(i);
+ if (point.mask) {
+ continue;
+ }
+ writer.name(LINE_NAME + count);
+ count++;
+ writer.beginArray();
+ writer.value(point.xPos1);
+ writer.value(point.yPos1);
+ writer.value(point.xPos2);
+ writer.value(point.yPos2);
+ writer.value(point.brightness);
+ writer.value(point.contrast);
+ writer.value(point.saturation);
+ writer.endArray();
+ }
+ writer.endObject();
+ }
+
+ @Override
+ public void deSerializeRepresentation(JsonReader sreader) throws IOException {
+ sreader.beginObject();
+ Vector<Band> points = new Vector<Band>();
+
+ while (sreader.hasNext()) {
+ String name = sreader.nextName();
+ if (name.startsWith(LINE_NAME)) {
+ int pointNo = Integer.parseInt(name.substring(LINE_NAME.length()));
+ sreader.beginArray();
+ Band p = new Band();
+ p.mask = false;
+ sreader.hasNext();
+ p.xPos1 = sreader.nextInt();
+ sreader.hasNext();
+ p.yPos1 = sreader.nextInt();
+ sreader.hasNext();
+ p.xPos2 = sreader.nextInt();
+ sreader.hasNext();
+ p.yPos2 = sreader.nextInt();
+ sreader.hasNext();
+ p.brightness = sreader.nextInt();
+ sreader.hasNext();
+ p.contrast = sreader.nextInt();
+ sreader.hasNext();
+ p.saturation = sreader.nextInt();
+ sreader.hasNext();
+ sreader.endArray();
+ points.add(p);
+
+ } else {
+ sreader.skipValue();
+ }
+ }
+ mBands = points;
+ trimVector();
+ mCurrentBand = mBands.get(0);
+ sreader.endObject();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterImageBorderRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterImageBorderRepresentation.java
new file mode 100644
index 000000000..f310a2be1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterImageBorderRepresentation.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+
+public class FilterImageBorderRepresentation extends FilterRepresentation {
+ private int mDrawableResource = 0;
+
+ public FilterImageBorderRepresentation(int drawableResource) {
+ super("ImageBorder");
+ setFilterClass(ImageFilterBorder.class);
+ mDrawableResource = drawableResource;
+ setFilterType(FilterRepresentation.TYPE_BORDER);
+ setTextId(R.string.borders);
+ setEditorId(ImageOnlyEditor.ID);
+ setShowParameterValue(false);
+ }
+
+ public String toString() {
+ return "FilterBorder: " + getName();
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterImageBorderRepresentation representation =
+ new FilterImageBorderRepresentation(mDrawableResource);
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ public void useParametersFrom(FilterRepresentation a) {
+ if (a instanceof FilterImageBorderRepresentation) {
+ FilterImageBorderRepresentation representation = (FilterImageBorderRepresentation) a;
+ setName(representation.getName());
+ setDrawableResource(representation.getDrawableResource());
+ }
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation representation) {
+ if (!super.equals(representation)) {
+ return false;
+ }
+ if (representation instanceof FilterImageBorderRepresentation) {
+ FilterImageBorderRepresentation border = (FilterImageBorderRepresentation) representation;
+ if (border.mDrawableResource == mDrawableResource) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getTextId() {
+ return R.string.none;
+ }
+
+ public boolean allowsSingleInstanceOnly() {
+ return true;
+ }
+
+ public int getDrawableResource() {
+ return mDrawableResource;
+ }
+
+ public void setDrawableResource(int drawableResource) {
+ mDrawableResource = drawableResource;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterMirrorRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterMirrorRepresentation.java
new file mode 100644
index 000000000..8dcff0d16
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterMirrorRepresentation.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorMirror;
+
+import java.io.IOException;
+
+public class FilterMirrorRepresentation extends FilterRepresentation {
+ public static final String SERIALIZATION_NAME = "MIRROR";
+ private static final String SERIALIZATION_MIRROR_VALUE = "value";
+ private static final String TAG = FilterMirrorRepresentation.class.getSimpleName();
+
+ Mirror mMirror;
+
+ public enum Mirror {
+ NONE('N'), VERTICAL('V'), HORIZONTAL('H'), BOTH('B');
+ char mValue;
+
+ private Mirror(char value) {
+ mValue = value;
+ }
+
+ public char value() {
+ return mValue;
+ }
+
+ public static Mirror fromValue(char value) {
+ switch (value) {
+ case 'N':
+ return NONE;
+ case 'V':
+ return VERTICAL;
+ case 'H':
+ return HORIZONTAL;
+ case 'B':
+ return BOTH;
+ default:
+ return null;
+ }
+ }
+ }
+
+ public FilterMirrorRepresentation(Mirror mirror) {
+ super(FilterMirrorRepresentation.class.getSimpleName());
+ setSerializationName(SERIALIZATION_NAME);
+ setShowParameterValue(true);
+ setFilterClass(FilterMirrorRepresentation.class);
+ setFilterType(FilterRepresentation.TYPE_GEOMETRY);
+ setTextId(R.string.mirror);
+ setEditorId(EditorMirror.ID);
+ setMirror(mirror);
+ }
+
+ public FilterMirrorRepresentation(FilterMirrorRepresentation m) {
+ this(m.getMirror());
+ }
+
+ public FilterMirrorRepresentation() {
+ this(getNil());
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation rep) {
+ if (!(rep instanceof FilterMirrorRepresentation)) {
+ return false;
+ }
+ FilterMirrorRepresentation mirror = (FilterMirrorRepresentation) rep;
+ if (mMirror != mirror.mMirror) {
+ return false;
+ }
+ return true;
+ }
+
+ public Mirror getMirror() {
+ return mMirror;
+ }
+
+ public void set(FilterMirrorRepresentation r) {
+ mMirror = r.mMirror;
+ }
+
+ public void setMirror(Mirror mirror) {
+ if (mirror == null) {
+ throw new IllegalArgumentException("Argument to setMirror is null");
+ }
+ mMirror = mirror;
+ }
+
+ public void cycle() {
+ switch (mMirror) {
+ case NONE:
+ mMirror = Mirror.HORIZONTAL;
+ break;
+ case HORIZONTAL:
+ mMirror = Mirror.VERTICAL;
+ break;
+ case VERTICAL:
+ mMirror = Mirror.BOTH;
+ break;
+ case BOTH:
+ mMirror = Mirror.NONE;
+ break;
+ }
+ }
+
+ @Override
+ public boolean allowsSingleInstanceOnly() {
+ return true;
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ return new FilterMirrorRepresentation(this);
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ if (!(representation instanceof FilterMirrorRepresentation)) {
+ throw new IllegalArgumentException("calling copyAllParameters with incompatible types!");
+ }
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ if (!(a instanceof FilterMirrorRepresentation)) {
+ throw new IllegalArgumentException("calling useParametersFrom with incompatible types!");
+ }
+ setMirror(((FilterMirrorRepresentation) a).getMirror());
+ }
+
+ @Override
+ public boolean isNil() {
+ return mMirror == getNil();
+ }
+
+ public static Mirror getNil() {
+ return Mirror.NONE;
+ }
+
+ @Override
+ public void serializeRepresentation(JsonWriter writer) throws IOException {
+ writer.beginObject();
+ writer.name(SERIALIZATION_MIRROR_VALUE).value(mMirror.value());
+ writer.endObject();
+ }
+
+ @Override
+ public void deSerializeRepresentation(JsonReader reader) throws IOException {
+ boolean unset = true;
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ if (SERIALIZATION_MIRROR_VALUE.equals(name)) {
+ Mirror r = Mirror.fromValue((char) reader.nextInt());
+ if (r != null) {
+ setMirror(r);
+ unset = false;
+ }
+ } else {
+ reader.skipValue();
+ }
+ }
+ if (unset) {
+ Log.w(TAG, "WARNING: bad value when deserializing " + SERIALIZATION_NAME);
+ }
+ reader.endObject();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterPoint.java b/src/com/android/gallery3d/filtershow/filters/FilterPoint.java
new file mode 100644
index 000000000..4520717a1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterPoint.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+public interface FilterPoint {
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterPointRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterPointRepresentation.java
new file mode 100644
index 000000000..9bd1699d9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterPointRepresentation.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import java.util.Vector;
+
+public abstract class FilterPointRepresentation extends FilterRepresentation {
+ private static final String LOGTAG = "FilterPointRepresentation";
+ private Vector<FilterPoint> mCandidates = new Vector<FilterPoint>();
+
+ public FilterPointRepresentation(String type, int textid, int editorID) {
+ super(type);
+ setFilterClass(ImageFilterRedEye.class);
+ setFilterType(FilterRepresentation.TYPE_NORMAL);
+ setTextId(textid);
+ setEditorId(editorID);
+ }
+
+ @Override
+ public abstract FilterRepresentation copy();
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ public boolean hasCandidates() {
+ return mCandidates != null;
+ }
+
+ public Vector<FilterPoint> getCandidates() {
+ return mCandidates;
+ }
+
+ @Override
+ public boolean isNil() {
+ if (getCandidates() != null && getCandidates().size() > 0) {
+ return false;
+ }
+ return true;
+ }
+
+ public Object getCandidate(int index) {
+ return this.mCandidates.get(index);
+ }
+
+ public void addCandidate(FilterPoint c) {
+ this.mCandidates.add(c);
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ if (a instanceof FilterPointRepresentation) {
+ FilterPointRepresentation representation = (FilterPointRepresentation) a;
+ mCandidates.clear();
+ for (FilterPoint redEyeCandidate : representation.mCandidates) {
+ mCandidates.add(redEyeCandidate);
+ }
+ }
+ }
+
+ public void removeCandidate(RedEyeCandidate c) {
+ this.mCandidates.remove(c);
+ }
+
+ public void clearCandidates() {
+ this.mCandidates.clear();
+ }
+
+ public int getNumberOfCandidates() {
+ return mCandidates.size();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java
new file mode 100644
index 000000000..dd06a9760
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.graphics.RectF;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorRedEye;
+
+import java.util.Vector;
+
+public class FilterRedEyeRepresentation extends FilterPointRepresentation {
+ private static final String LOGTAG = "FilterRedEyeRepresentation";
+
+ public FilterRedEyeRepresentation() {
+ super("RedEye",R.string.redeye,EditorRedEye.ID);
+ setSerializationName("REDEYE");
+ setFilterClass(ImageFilterRedEye.class);
+ setOverlayId(R.drawable.photoeditor_effect_redeye);
+ setOverlayOnly(true);
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterRedEyeRepresentation representation = new FilterRedEyeRepresentation();
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ public void addRect(RectF rect, RectF bounds) {
+ Vector<RedEyeCandidate> intersects = new Vector<RedEyeCandidate>();
+ for (int i = 0; i < getCandidates().size(); i++) {
+ RedEyeCandidate r = (RedEyeCandidate) getCandidate(i);
+ if (r.intersect(rect)) {
+ intersects.add(r);
+ }
+ }
+ for (int i = 0; i < intersects.size(); i++) {
+ RedEyeCandidate r = intersects.elementAt(i);
+ rect.union(r.mRect);
+ bounds.union(r.mBounds);
+ removeCandidate(r);
+ }
+ addCandidate(new RedEyeCandidate(rect, bounds));
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
new file mode 100644
index 000000000..5b33ffba5
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.editors.BasicEditor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class FilterRepresentation {
+ private static final String LOGTAG = "FilterRepresentation";
+ private static final boolean DEBUG = false;
+ private String mName;
+ private int mPriority = TYPE_NORMAL;
+ private Class<?> mFilterClass;
+ private boolean mSupportsPartialRendering = false;
+ private int mTextId = 0;
+ private int mEditorId = BasicEditor.ID;
+ private int mButtonId = 0;
+ private int mOverlayId = 0;
+ private boolean mOverlayOnly = false;
+ private boolean mShowParameterValue = true;
+ private String mSerializationName;
+ public static final byte TYPE_BORDER = 1;
+ public static final byte TYPE_FX = 2;
+ public static final byte TYPE_WBALANCE = 3;
+ public static final byte TYPE_VIGNETTE = 4;
+ public static final byte TYPE_NORMAL = 5;
+ public static final byte TYPE_TINYPLANET = 6;
+ public static final byte TYPE_GEOMETRY = 7;
+ protected static final String NAME_TAG = "Name";
+
+ public FilterRepresentation(String name) {
+ mName = name;
+ }
+
+ public FilterRepresentation copy(){
+ FilterRepresentation representation = new FilterRepresentation(mName);
+ representation.useParametersFrom(this);
+ return representation;
+ }
+
+ protected void copyAllParameters(FilterRepresentation representation) {
+ representation.setName(getName());
+ representation.setFilterClass(getFilterClass());
+ representation.setFilterType(getFilterType());
+ representation.setSupportsPartialRendering(supportsPartialRendering());
+ representation.setTextId(getTextId());
+ representation.setEditorId(getEditorId());
+ representation.setOverlayId(getOverlayId());
+ representation.setOverlayOnly(getOverlayOnly());
+ representation.setShowParameterValue(showParameterValue());
+ representation.mSerializationName = mSerializationName;
+
+ }
+
+ public boolean equals(FilterRepresentation representation) {
+ if (representation == null) {
+ return false;
+ }
+ if (representation.mFilterClass == mFilterClass
+ && representation.mName.equalsIgnoreCase(mName)
+ && representation.mPriority == mPriority
+ // TODO: After we enable partial rendering, we can switch back
+ // to use member variable here.
+ && representation.supportsPartialRendering() == supportsPartialRendering()
+ && representation.mTextId == mTextId
+ && representation.mEditorId == mEditorId
+ && representation.mButtonId == mButtonId
+ && representation.mOverlayId == mOverlayId
+ && representation.mOverlayOnly == mOverlayOnly
+ && representation.mShowParameterValue == mShowParameterValue) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setSerializationName(String sname) {
+ mSerializationName = sname;
+ }
+
+ public String getSerializationName() {
+ return mSerializationName;
+ }
+
+ public void setFilterType(int priority) {
+ mPriority = priority;
+ }
+
+ public int getFilterType() {
+ return mPriority;
+ }
+
+ public boolean isNil() {
+ return false;
+ }
+
+ public boolean supportsPartialRendering() {
+ return false && mSupportsPartialRendering; // disable for now
+ }
+
+ public void setSupportsPartialRendering(boolean value) {
+ mSupportsPartialRendering = value;
+ }
+
+ public void useParametersFrom(FilterRepresentation a) {
+ }
+
+ public boolean allowsSingleInstanceOnly() {
+ return false;
+ }
+
+ public Class<?> getFilterClass() {
+ return mFilterClass;
+ }
+
+ public void setFilterClass(Class<?> filterClass) {
+ mFilterClass = filterClass;
+ }
+
+ // This same() function is different from equals(), basically it checks
+ // whether 2 FilterRepresentations are the same type. It doesn't care about
+ // the values.
+ public boolean same(FilterRepresentation b) {
+ if (b == null) {
+ return false;
+ }
+ return getFilterClass() == b.getFilterClass();
+ }
+
+ public int getTextId() {
+ return mTextId;
+ }
+
+ public void setTextId(int textId) {
+ mTextId = textId;
+ }
+
+ public int getOverlayId() {
+ return mOverlayId;
+ }
+
+ public void setOverlayId(int overlayId) {
+ mOverlayId = overlayId;
+ }
+
+ public boolean getOverlayOnly() {
+ return mOverlayOnly;
+ }
+
+ public void setOverlayOnly(boolean value) {
+ mOverlayOnly = value;
+ }
+
+ final public int getEditorId() {
+ return mEditorId;
+ }
+
+ public int[] getEditorIds() {
+ return new int[] {
+ mEditorId };
+ }
+
+ public void setEditorId(int editorId) {
+ mEditorId = editorId;
+ }
+
+ public boolean showParameterValue() {
+ return mShowParameterValue;
+ }
+
+ public void setShowParameterValue(boolean showParameterValue) {
+ mShowParameterValue = showParameterValue;
+ }
+
+ public String getStateRepresentation() {
+ return "";
+ }
+
+ /**
+ * Method must "beginObject()" add its info and "endObject()"
+ * @param writer
+ * @throws IOException
+ */
+ public void serializeRepresentation(JsonWriter writer) throws IOException {
+ writer.beginObject();
+ {
+ String[][] rep = serializeRepresentation();
+ for (int k = 0; k < rep.length; k++) {
+ writer.name(rep[k][0]);
+ writer.value(rep[k][1]);
+ }
+ }
+ writer.endObject();
+ }
+
+ // this is the old way of doing this and will be removed soon
+ public String[][] serializeRepresentation() {
+ String[][] ret = {{NAME_TAG, getName()}};
+ return ret;
+ }
+
+ public void deSerializeRepresentation(JsonReader reader) throws IOException {
+ ArrayList<String[]> al = new ArrayList<String[]>();
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String[] kv = {reader.nextName(), reader.nextString()};
+ al.add(kv);
+
+ }
+ reader.endObject();
+ String[][] oldFormat = al.toArray(new String[al.size()][]);
+
+ deSerializeRepresentation(oldFormat);
+ }
+
+ // this is the old way of doing this and will be removed soon
+ public void deSerializeRepresentation(String[][] rep) {
+ for (int i = 0; i < rep.length; i++) {
+ if (NAME_TAG.equals(rep[i][0])) {
+ mName = rep[i][1];
+ break;
+ }
+ }
+ }
+
+ // Override this in subclasses
+ public int getStyle() {
+ return -1;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRotateRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRotateRepresentation.java
new file mode 100644
index 000000000..eb89de036
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRotateRepresentation.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorRotate;
+
+import java.io.IOException;
+
+public class FilterRotateRepresentation extends FilterRepresentation {
+ public static final String SERIALIZATION_NAME = "ROTATION";
+ public static final String SERIALIZATION_ROTATE_VALUE = "value";
+ private static final String TAG = FilterRotateRepresentation.class.getSimpleName();
+
+ Rotation mRotation;
+
+ public enum Rotation {
+ ZERO(0), NINETY(90), ONE_EIGHTY(180), TWO_SEVENTY(270);
+ private final int mValue;
+
+ private Rotation(int value) {
+ mValue = value;
+ }
+
+ public int value() {
+ return mValue;
+ }
+
+ public static Rotation fromValue(int value) {
+ switch (value) {
+ case 0:
+ return ZERO;
+ case 90:
+ return NINETY;
+ case 180:
+ return ONE_EIGHTY;
+ case 270:
+ return TWO_SEVENTY;
+ default:
+ return null;
+ }
+ }
+ }
+
+ public FilterRotateRepresentation(Rotation rotation) {
+ super(FilterRotateRepresentation.class.getSimpleName());
+ setSerializationName(SERIALIZATION_NAME);
+ setShowParameterValue(true);
+ setFilterClass(FilterRotateRepresentation.class);
+ setFilterType(FilterRepresentation.TYPE_GEOMETRY);
+ setTextId(R.string.rotate);
+ setEditorId(EditorRotate.ID);
+ setRotation(rotation);
+ }
+
+ public FilterRotateRepresentation(FilterRotateRepresentation r) {
+ this(r.getRotation());
+ }
+
+ public FilterRotateRepresentation() {
+ this(getNil());
+ }
+
+ public Rotation getRotation() {
+ return mRotation;
+ }
+
+ public void rotateCW() {
+ switch(mRotation) {
+ case ZERO:
+ mRotation = Rotation.NINETY;
+ break;
+ case NINETY:
+ mRotation = Rotation.ONE_EIGHTY;
+ break;
+ case ONE_EIGHTY:
+ mRotation = Rotation.TWO_SEVENTY;
+ break;
+ case TWO_SEVENTY:
+ mRotation = Rotation.ZERO;
+ break;
+ }
+ }
+
+ public void set(FilterRotateRepresentation r) {
+ mRotation = r.mRotation;
+ }
+
+ public void setRotation(Rotation rotation) {
+ if (rotation == null) {
+ throw new IllegalArgumentException("Argument to setRotation is null");
+ }
+ mRotation = rotation;
+ }
+
+ @Override
+ public boolean allowsSingleInstanceOnly() {
+ return true;
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ return new FilterRotateRepresentation(this);
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ if (!(representation instanceof FilterRotateRepresentation)) {
+ throw new IllegalArgumentException("calling copyAllParameters with incompatible types!");
+ }
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ if (!(a instanceof FilterRotateRepresentation)) {
+ throw new IllegalArgumentException("calling useParametersFrom with incompatible types!");
+ }
+ setRotation(((FilterRotateRepresentation) a).getRotation());
+ }
+
+ @Override
+ public boolean isNil() {
+ return mRotation == getNil();
+ }
+
+ public static Rotation getNil() {
+ return Rotation.ZERO;
+ }
+
+ @Override
+ public void serializeRepresentation(JsonWriter writer) throws IOException {
+ writer.beginObject();
+ writer.name(SERIALIZATION_ROTATE_VALUE).value(mRotation.value());
+ writer.endObject();
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation rep) {
+ if (!(rep instanceof FilterRotateRepresentation)) {
+ return false;
+ }
+ FilterRotateRepresentation rotate = (FilterRotateRepresentation) rep;
+ if (rotate.mRotation.value() != mRotation.value()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void deSerializeRepresentation(JsonReader reader) throws IOException {
+ boolean unset = true;
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ if (SERIALIZATION_ROTATE_VALUE.equals(name)) {
+ Rotation r = Rotation.fromValue(reader.nextInt());
+ if (r != null) {
+ setRotation(r);
+ unset = false;
+ }
+ } else {
+ reader.skipValue();
+ }
+ }
+ if (unset) {
+ Log.w(TAG, "WARNING: bad value when deserializing " + SERIALIZATION_NAME);
+ }
+ reader.endObject();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterStraightenRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterStraightenRepresentation.java
new file mode 100644
index 000000000..94c9497fc
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterStraightenRepresentation.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorStraighten;
+
+import java.io.IOException;
+
+public class FilterStraightenRepresentation extends FilterRepresentation {
+ public static final String SERIALIZATION_NAME = "STRAIGHTEN";
+ public static final String SERIALIZATION_STRAIGHTEN_VALUE = "value";
+ private static final String TAG = FilterStraightenRepresentation.class.getSimpleName();
+ public static final int MAX_STRAIGHTEN_ANGLE = 45;
+ public static final int MIN_STRAIGHTEN_ANGLE = -45;
+
+ float mStraighten;
+
+ public FilterStraightenRepresentation(float straighten) {
+ super(FilterStraightenRepresentation.class.getSimpleName());
+ setSerializationName(SERIALIZATION_NAME);
+ setShowParameterValue(true);
+ setFilterClass(FilterStraightenRepresentation.class);
+ setFilterType(FilterRepresentation.TYPE_GEOMETRY);
+ setTextId(R.string.straighten);
+ setEditorId(EditorStraighten.ID);
+ setStraighten(straighten);
+ }
+
+ public FilterStraightenRepresentation(FilterStraightenRepresentation s) {
+ this(s.getStraighten());
+ }
+
+ public FilterStraightenRepresentation() {
+ this(getNil());
+ }
+
+ public void set(FilterStraightenRepresentation r) {
+ mStraighten = r.mStraighten;
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation rep) {
+ if (!(rep instanceof FilterStraightenRepresentation)) {
+ return false;
+ }
+ FilterStraightenRepresentation straighten = (FilterStraightenRepresentation) rep;
+ if (straighten.mStraighten != mStraighten) {
+ return false;
+ }
+ return true;
+ }
+
+ public float getStraighten() {
+ return mStraighten;
+ }
+
+ public void setStraighten(float straighten) {
+ if (!rangeCheck(straighten)) {
+ straighten = Math.min(Math.max(straighten, MIN_STRAIGHTEN_ANGLE), MAX_STRAIGHTEN_ANGLE);
+ }
+ mStraighten = straighten;
+ }
+
+ @Override
+ public boolean allowsSingleInstanceOnly() {
+ return true;
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ return new FilterStraightenRepresentation(this);
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ if (!(representation instanceof FilterStraightenRepresentation)) {
+ throw new IllegalArgumentException("calling copyAllParameters with incompatible types!");
+ }
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ if (!(a instanceof FilterStraightenRepresentation)) {
+ throw new IllegalArgumentException("calling useParametersFrom with incompatible types!");
+ }
+ setStraighten(((FilterStraightenRepresentation) a).getStraighten());
+ }
+
+ @Override
+ public boolean isNil() {
+ return mStraighten == getNil();
+ }
+
+ public static float getNil() {
+ return 0;
+ }
+
+ @Override
+ public void serializeRepresentation(JsonWriter writer) throws IOException {
+ writer.beginObject();
+ writer.name(SERIALIZATION_STRAIGHTEN_VALUE).value(mStraighten);
+ writer.endObject();
+ }
+
+ @Override
+ public void deSerializeRepresentation(JsonReader reader) throws IOException {
+ boolean unset = true;
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ if (SERIALIZATION_STRAIGHTEN_VALUE.equals(name)) {
+ float s = (float) reader.nextDouble();
+ if (rangeCheck(s)) {
+ setStraighten(s);
+ unset = false;
+ }
+ } else {
+ reader.skipValue();
+ }
+ }
+ if (unset) {
+ Log.w(TAG, "WARNING: bad value when deserializing " + SERIALIZATION_NAME);
+ }
+ reader.endObject();
+ }
+
+ private boolean rangeCheck(double s) {
+ if (s < -45 || s > 45) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java
new file mode 100644
index 000000000..be1812957
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
+
+public class FilterTinyPlanetRepresentation extends FilterBasicRepresentation {
+ private static final String SERIALIZATION_NAME = "TINYPLANET";
+ private static final String LOGTAG = "FilterTinyPlanetRepresentation";
+ private static final String SERIAL_ANGLE = "Angle";
+ private float mAngle = 0;
+
+ public FilterTinyPlanetRepresentation() {
+ super("TinyPlanet", 0, 50, 100);
+ setSerializationName(SERIALIZATION_NAME);
+ setShowParameterValue(true);
+ setFilterClass(ImageFilterTinyPlanet.class);
+ setFilterType(FilterRepresentation.TYPE_TINYPLANET);
+ setTextId(R.string.tinyplanet);
+ setEditorId(EditorTinyPlanet.ID);
+ setMinimum(1);
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterTinyPlanetRepresentation representation = new FilterTinyPlanetRepresentation();
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ FilterTinyPlanetRepresentation representation = (FilterTinyPlanetRepresentation) a;
+ super.useParametersFrom(a);
+ mAngle = representation.mAngle;
+ setZoom(representation.getZoom());
+ }
+
+ public void setAngle(float angle) {
+ mAngle = angle;
+ }
+
+ public float getAngle() {
+ return mAngle;
+ }
+
+ public int getZoom() {
+ return getValue();
+ }
+
+ public void setZoom(int zoom) {
+ setValue(zoom);
+ }
+
+ public boolean isNil() {
+ // TinyPlanet always has an effect
+ return false;
+ }
+
+ @Override
+ public String[][] serializeRepresentation() {
+ String[][] ret = {
+ {SERIAL_NAME , getName() },
+ {SERIAL_VALUE , Integer.toString(getValue())},
+ {SERIAL_ANGLE , Float.toString(mAngle)}};
+ return ret;
+ }
+
+ @Override
+ public void deSerializeRepresentation(String[][] rep) {
+ super.deSerializeRepresentation(rep);
+ for (int i = 0; i < rep.length; i++) {
+ if (SERIAL_VALUE.equals(rep[i][0])) {
+ setValue(Integer.parseInt(rep[i][1]));
+ } else if (SERIAL_ANGLE.equals(rep[i][0])) {
+ setAngle(Float.parseFloat(rep[i][1]));
+ }
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterUserPresetRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterUserPresetRepresentation.java
new file mode 100644
index 000000000..dfdb6fcf0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterUserPresetRepresentation.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+public class FilterUserPresetRepresentation extends FilterRepresentation {
+
+ private ImagePreset mPreset;
+ private int mId;
+
+ public FilterUserPresetRepresentation(String name, ImagePreset preset, int id) {
+ super(name);
+ setEditorId(ImageOnlyEditor.ID);
+ setFilterType(FilterRepresentation.TYPE_FX);
+ mPreset = preset;
+ mId = id;
+ }
+
+ public ImagePreset getImagePreset() {
+ return mPreset;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public FilterRepresentation copy(){
+ FilterRepresentation representation = new FilterUserPresetRepresentation(getName(),
+ new ImagePreset(mPreset), mId);
+ return representation;
+ }
+
+ @Override
+ public boolean allowsSingleInstanceOnly() {
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java
new file mode 100644
index 000000000..42a7406bc
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorVignette;
+import com.android.gallery3d.filtershow.imageshow.Oval;
+
+public class FilterVignetteRepresentation extends FilterBasicRepresentation implements Oval {
+ private static final String LOGTAG = "FilterVignetteRepresentation";
+ private float mCenterX = Float.NaN;
+ private float mCenterY;
+ private float mRadiusX = Float.NaN;
+ private float mRadiusY;
+
+ public FilterVignetteRepresentation() {
+ super("Vignette", -100, 50, 100);
+ setSerializationName("VIGNETTE");
+ setShowParameterValue(true);
+ setFilterType(FilterRepresentation.TYPE_VIGNETTE);
+ setTextId(R.string.vignette);
+ setEditorId(EditorVignette.ID);
+ setName("Vignette");
+ setFilterClass(ImageFilterVignette.class);
+ setMinimum(-100);
+ setMaximum(100);
+ setDefaultValue(0);
+ }
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ super.useParametersFrom(a);
+ mCenterX = ((FilterVignetteRepresentation) a).mCenterX;
+ mCenterY = ((FilterVignetteRepresentation) a).mCenterY;
+ mRadiusX = ((FilterVignetteRepresentation) a).mRadiusX;
+ mRadiusY = ((FilterVignetteRepresentation) a).mRadiusY;
+ }
+
+ @Override
+ public FilterRepresentation copy() {
+ FilterVignetteRepresentation representation = new FilterVignetteRepresentation();
+ copyAllParameters(representation);
+ return representation;
+ }
+
+ @Override
+ protected void copyAllParameters(FilterRepresentation representation) {
+ super.copyAllParameters(representation);
+ representation.useParametersFrom(this);
+ }
+
+ @Override
+ public void setCenter(float centerX, float centerY) {
+ mCenterX = centerX;
+ mCenterY = centerY;
+ }
+
+ @Override
+ public float getCenterX() {
+ return mCenterX;
+ }
+
+ @Override
+ public float getCenterY() {
+ return mCenterY;
+ }
+
+ @Override
+ public void setRadius(float radiusX, float radiusY) {
+ mRadiusX = radiusX;
+ mRadiusY = radiusY;
+ }
+
+ @Override
+ public void setRadiusX(float radiusX) {
+ mRadiusX = radiusX;
+ }
+
+ @Override
+ public void setRadiusY(float radiusY) {
+ mRadiusY = radiusY;
+ }
+
+ @Override
+ public float getRadiusX() {
+ return mRadiusX;
+ }
+
+ @Override
+ public float getRadiusY() {
+ return mRadiusY;
+ }
+
+ public boolean isCenterSet() {
+ return mCenterX != Float.NaN;
+ }
+
+ @Override
+ public boolean isNil() {
+ return getValue() == 0;
+ }
+
+ @Override
+ public boolean equals(FilterRepresentation representation) {
+ if (!super.equals(representation)) {
+ return false;
+ }
+ if (representation instanceof FilterVignetteRepresentation) {
+ FilterVignetteRepresentation rep = (FilterVignetteRepresentation) representation;
+ if (rep.getCenterX() == getCenterX()
+ && rep.getCenterY() == getCenterY()
+ && rep.getRadiusX() == getRadiusX()
+ && rep.getRadiusY() == getRadiusY()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static final String[] sParams = {
+ "Name", "value", "mCenterX", "mCenterY", "mRadiusX",
+ "mRadiusY"
+ };
+
+ @Override
+ public String[][] serializeRepresentation() {
+ String[][] ret = {
+ { sParams[0], getName() },
+ { sParams[1], Integer.toString(getValue()) },
+ { sParams[2], Float.toString(mCenterX) },
+ { sParams[3], Float.toString(mCenterY) },
+ { sParams[4], Float.toString(mRadiusX) },
+ { sParams[5], Float.toString(mRadiusY) }
+ };
+ return ret;
+ }
+
+ @Override
+ public void deSerializeRepresentation(String[][] rep) {
+ super.deSerializeRepresentation(rep);
+ for (int i = 0; i < rep.length; i++) {
+ String key = rep[i][0];
+ String value = rep[i][1];
+ if (sParams[0].equals(key)) {
+ setName(value);
+ } else if (sParams[1].equals(key)) {
+ setValue(Integer.parseInt(value));
+ } else if (sParams[2].equals(key)) {
+ mCenterX = Float.parseFloat(value);
+ } else if (sParams[3].equals(key)) {
+ mCenterY = Float.parseFloat(value);
+ } else if (sParams[4].equals(key)) {
+ mRadiusX = Float.parseFloat(value);
+ } else if (sParams[5].equals(key)) {
+ mRadiusY = Float.parseFloat(value);
+ }
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java b/src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java
new file mode 100644
index 000000000..710128f99
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+public interface FiltersManagerInterface {
+ ImageFilter getFilterForRepresentation(FilterRepresentation representation);
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/IconUtilities.java b/src/com/android/gallery3d/filtershow/filters/IconUtilities.java
new file mode 100644
index 000000000..e2a01472d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/IconUtilities.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import com.android.gallery3d.R;
+
+public class IconUtilities {
+ public static final int PUNCH = R.drawable.filtershow_fx_0005_punch;
+ public static final int VINTAGE = R.drawable.filtershow_fx_0000_vintage;
+ public static final int BW_CONTRAST = R.drawable.filtershow_fx_0004_bw_contrast;
+ public static final int BLEACH = R.drawable.filtershow_fx_0002_bleach;
+ public static final int INSTANT = R.drawable.filtershow_fx_0001_instant;
+ public static final int WASHOUT = R.drawable.filtershow_fx_0007_washout;
+ public static final int BLUECRUSH = R.drawable.filtershow_fx_0003_blue_crush;
+ public static final int WASHOUT_COLOR = R.drawable.filtershow_fx_0008_washout_color;
+ public static final int X_PROCESS = R.drawable.filtershow_fx_0006_x_process;
+
+ public static Bitmap getFXBitmap(Resources res, int id) {
+ Bitmap ret;
+ BitmapFactory.Options o = new BitmapFactory.Options();
+ o.inScaled = false;
+
+ if (id != 0) {
+ return BitmapFactory.decodeResource(res, id, o);
+ }
+ return null;
+ }
+
+ public static Bitmap loadBitmap(Resources res, int resource) {
+
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ Bitmap bitmap = BitmapFactory.decodeResource(
+ res,
+ resource, options);
+
+ return bitmap;
+ }
+
+ public static Bitmap applyFX(Bitmap bitmap, final Bitmap fxBitmap) {
+ ImageFilterFx fx = new ImageFilterFx() {
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ int fxw = fxBitmap.getWidth();
+ int fxh = fxBitmap.getHeight();
+ int start = 0;
+ int end = w * h * 4;
+ nativeApplyFilter(bitmap, w, h, fxBitmap, fxw, fxh, start, end);
+ return bitmap;
+ }
+ };
+ return fx.apply(bitmap, 0, 0);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
new file mode 100644
index 000000000..437137416
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.support.v8.renderscript.Allocation;
+import android.widget.Toast;
+
+import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.pipeline.FilterEnvironment;
+
+public abstract class ImageFilter implements Cloneable {
+ private FilterEnvironment mEnvironment = null;
+
+ protected String mName = "Original";
+ private final String LOGTAG = "ImageFilter";
+ protected static final boolean SIMPLE_ICONS = true;
+ // TODO: Temporary, for dogfood note memory issues with toasts for better
+ // feedback. Remove this when filters actually work in low memory
+ // situations.
+ private static Activity sActivity = null;
+
+ public static void setActivityForMemoryToasts(Activity activity) {
+ sActivity = activity;
+ }
+
+ public static void resetStatics() {
+ sActivity = null;
+ }
+
+ public void freeResources() {}
+
+ public void displayLowMemoryToast() {
+ if (sActivity != null) {
+ sActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Toast.makeText(sActivity, "Memory too low for filter " + getName() +
+ ", please file a bug report", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public boolean supportsAllocationInput() { return false; }
+
+ public void apply(Allocation in, Allocation out) {
+ setGeneralParameters();
+ }
+
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ // do nothing here, subclasses will implement filtering here
+ setGeneralParameters();
+ return bitmap;
+ }
+
+ public abstract void useRepresentation(FilterRepresentation representation);
+
+ native protected void nativeApplyGradientFilter(Bitmap bitmap, int w, int h,
+ int[] redGradient, int[] greenGradient, int[] blueGradient);
+
+ public FilterRepresentation getDefaultRepresentation() {
+ return null;
+ }
+
+ protected Matrix getOriginalToScreenMatrix(int w, int h) {
+ return GeometryMathUtils.getImageToScreenMatrix(getEnvironment().getImagePreset()
+ .getGeometryFilters(), true, MasterImage.getImage().getOriginalBounds(), w, h);
+ }
+
+ public void setEnvironment(FilterEnvironment environment) {
+ mEnvironment = environment;
+ }
+
+ public FilterEnvironment getEnvironment() {
+ return mEnvironment;
+ }
+
+ public void setGeneralParameters() {
+ // should implement in subclass which like to transport
+ // some information to other filters. (like the style setting from RetroLux
+ // and Film to FixedFrame)
+ mEnvironment.clearGeneralParameters();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
new file mode 100644
index 000000000..a7286f0fa
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import java.util.HashMap;
+
+public class ImageFilterBorder extends ImageFilter {
+ private static final float NINEPATCH_ICON_SCALING = 10;
+ private static final float BITMAP_ICON_SCALING = 1 / 3.0f;
+ private FilterImageBorderRepresentation mParameters = null;
+ private Resources mResources = null;
+
+ private HashMap<Integer, Drawable> mDrawables = new HashMap<Integer, Drawable>();
+
+ public ImageFilterBorder() {
+ mName = "Border";
+ }
+
+ public void useRepresentation(FilterRepresentation representation) {
+ FilterImageBorderRepresentation parameters = (FilterImageBorderRepresentation) representation;
+ mParameters = parameters;
+ }
+
+ public FilterImageBorderRepresentation getParameters() {
+ return mParameters;
+ }
+
+ public void freeResources() {
+ mDrawables.clear();
+ }
+
+ public Bitmap applyHelper(Bitmap bitmap, float scale1, float scale2 ) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ Rect bounds = new Rect(0, 0, (int) (w * scale1), (int) (h * scale1));
+ Canvas canvas = new Canvas(bitmap);
+ canvas.scale(scale2, scale2);
+ Drawable drawable = getDrawable(getParameters().getDrawableResource());
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
+ return bitmap;
+ }
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null || getParameters().getDrawableResource() == 0) {
+ return bitmap;
+ }
+ float scale2 = scaleFactor * 2.0f;
+ float scale1 = 1 / scale2;
+ return applyHelper(bitmap, scale1, scale2);
+ }
+
+ public void setResources(Resources resources) {
+ if (mResources != resources) {
+ mResources = resources;
+ mDrawables.clear();
+ }
+ }
+
+ public Drawable getDrawable(int rsc) {
+ Drawable drawable = mDrawables.get(rsc);
+ if (drawable == null && mResources != null && rsc != 0) {
+ drawable = new BitmapDrawable(mResources, BitmapFactory.decodeResource(mResources, rsc));
+ mDrawables.put(rsc, drawable);
+ }
+ return drawable;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
new file mode 100644
index 000000000..50837ca2f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+
+
+public class ImageFilterBwFilter extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "BWFILTER";
+
+ public ImageFilterBwFilter() {
+ mName = "BW Filter";
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation();
+ representation.setName("BW Filter");
+ representation.setSerializationName(SERIALIZATION_NAME);
+
+ representation.setFilterClass(ImageFilterBwFilter.class);
+ representation.setMaximum(180);
+ representation.setMinimum(-180);
+ representation.setTextId(R.string.bwfilter);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, int r, int g, int b);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ float[] hsv = new float[] {
+ 180 + getParameters().getValue(), 1, 1
+ };
+ int rgb = Color.HSVToColor(hsv);
+ int r = 0xFF & (rgb >> 16);
+ int g = 0xFF & (rgb >> 8);
+ int b = 0xFF & (rgb >> 0);
+ nativeApplyFilter(bitmap, w, h, r, g, b);
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterChanSat.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterChanSat.java
new file mode 100644
index 000000000..1ea8edfb8
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterChanSat.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.support.v8.renderscript.Allocation;
+import android.support.v8.renderscript.Element;
+import android.support.v8.renderscript.RenderScript;
+import android.support.v8.renderscript.Script.LaunchOptions;
+import android.support.v8.renderscript.Type;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.pipeline.FilterEnvironment;
+
+public class ImageFilterChanSat extends ImageFilterRS {
+ private static final String LOGTAG = "ImageFilterChanSat";
+ private ScriptC_saturation mScript;
+ private Bitmap mSourceBitmap;
+
+ private static final int STRIP_SIZE = 64;
+
+ FilterChanSatRepresentation mParameters = new FilterChanSatRepresentation();
+ private Bitmap mOverlayBitmap;
+
+ public ImageFilterChanSat() {
+ mName = "ChannelSat";
+ }
+
+ @Override
+ public FilterRepresentation getDefaultRepresentation() {
+ return new FilterChanSatRepresentation();
+ }
+
+ @Override
+ public void useRepresentation(FilterRepresentation representation) {
+ mParameters = (FilterChanSatRepresentation) representation;
+ }
+
+ @Override
+ protected void resetAllocations() {
+
+ }
+
+ @Override
+ public void resetScripts() {
+ if (mScript != null) {
+ mScript.destroy();
+ mScript = null;
+ }
+ }
+ @Override
+ protected void createFilter(android.content.res.Resources res, float scaleFactor,
+ int quality) {
+ createFilter(res, scaleFactor, quality, getInPixelsAllocation());
+ }
+
+ @Override
+ protected void createFilter(android.content.res.Resources res, float scaleFactor,
+ int quality, Allocation in) {
+ RenderScript rsCtx = getRenderScriptContext();
+
+ Type.Builder tb_float = new Type.Builder(rsCtx, Element.F32_4(rsCtx));
+ tb_float.setX(in.getType().getX());
+ tb_float.setY(in.getType().getY());
+ mScript = new ScriptC_saturation(rsCtx, res, R.raw.saturation);
+ }
+
+
+ private Bitmap getSourceBitmap() {
+ assert (mSourceBitmap != null);
+ return mSourceBitmap;
+ }
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (SIMPLE_ICONS && FilterEnvironment.QUALITY_ICON == quality) {
+ return bitmap;
+ }
+
+ mSourceBitmap = bitmap;
+ Bitmap ret = super.apply(bitmap, scaleFactor, quality);
+ mSourceBitmap = null;
+
+ return ret;
+ }
+
+ @Override
+ protected void bindScriptValues() {
+ int width = getInPixelsAllocation().getType().getX();
+ int height = getInPixelsAllocation().getType().getY();
+ }
+
+
+
+ @Override
+ protected void runFilter() {
+ int []sat = new int[7];
+ for(int i = 0;i<sat.length ;i ++){
+ sat[i] = mParameters.getValue(i);
+ }
+
+
+ int width = getInPixelsAllocation().getType().getX();
+ int height = getInPixelsAllocation().getType().getY();
+ Matrix m = getOriginalToScreenMatrix(width, height);
+
+
+ mScript.set_saturation(sat);
+
+ mScript.invoke_setupGradParams();
+ runSelectiveAdjust(
+ getInPixelsAllocation(), getOutPixelsAllocation());
+
+ }
+
+ private void runSelectiveAdjust(Allocation in, Allocation out) {
+ int width = in.getType().getX();
+ int height = in.getType().getY();
+
+ LaunchOptions options = new LaunchOptions();
+ int ty;
+ options.setX(0, width);
+
+ for (ty = 0; ty < height; ty += STRIP_SIZE) {
+ int endy = ty + STRIP_SIZE;
+ if (endy > height) {
+ endy = height;
+ }
+ options.setY(ty, endy);
+ mScript.forEach_selectiveAdjust(in, out, options);
+ if (checkStop()) {
+ return;
+ }
+ }
+ }
+
+ private boolean checkStop() {
+ RenderScript rsCtx = getRenderScriptContext();
+ rsCtx.finish();
+ if (getEnvironment().needsStop()) {
+ return true;
+ }
+ return false;
+ }
+}
+
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
new file mode 100644
index 000000000..27c0e0877
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterContrast extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "CONTRAST";
+
+ public ImageFilterContrast() {
+ mName = "Contrast";
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterBasicRepresentation representation =
+ (FilterBasicRepresentation) super.getDefaultRepresentation();
+ representation.setName("Contrast");
+ representation.setSerializationName(SERIALIZATION_NAME);
+
+ representation.setFilterClass(ImageFilterContrast.class);
+ representation.setTextId(R.string.contrast);
+ representation.setMinimum(-100);
+ representation.setMaximum(100);
+ representation.setDefaultValue(0);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ float value = getParameters().getValue();
+ nativeApplyFilter(bitmap, w, h, value);
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
new file mode 100644
index 000000000..61b60d2e3
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.filtershow.imageshow.Spline;
+
+public class ImageFilterCurves extends ImageFilter {
+
+ private static final String LOGTAG = "ImageFilterCurves";
+ FilterCurvesRepresentation mParameters = new FilterCurvesRepresentation();
+
+ @Override
+ public FilterRepresentation getDefaultRepresentation() {
+ return new FilterCurvesRepresentation();
+ }
+
+ @Override
+ public void useRepresentation(FilterRepresentation representation) {
+ FilterCurvesRepresentation parameters = (FilterCurvesRepresentation) representation;
+ mParameters = parameters;
+ }
+
+ public ImageFilterCurves() {
+ mName = "Curves";
+ reset();
+ }
+
+ public void populateArray(int[] array, int curveIndex) {
+ Spline spline = mParameters.getSpline(curveIndex);
+ if (spline == null) {
+ return;
+ }
+ float[] curve = spline.getAppliedCurve();
+ for (int i = 0; i < 256; i++) {
+ array[i] = (int) (curve[i] * 255);
+ }
+ }
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (!mParameters.getSpline(Spline.RGB).isOriginal()) {
+ int[] rgbGradient = new int[256];
+ populateArray(rgbGradient, Spline.RGB);
+ nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
+ rgbGradient, rgbGradient, rgbGradient);
+ }
+
+ int[] redGradient = null;
+ if (!mParameters.getSpline(Spline.RED).isOriginal()) {
+ redGradient = new int[256];
+ populateArray(redGradient, Spline.RED);
+ }
+ int[] greenGradient = null;
+ if (!mParameters.getSpline(Spline.GREEN).isOriginal()) {
+ greenGradient = new int[256];
+ populateArray(greenGradient, Spline.GREEN);
+ }
+ int[] blueGradient = null;
+ if (!mParameters.getSpline(Spline.BLUE).isOriginal()) {
+ blueGradient = new int[256];
+ populateArray(blueGradient, Spline.BLUE);
+ }
+
+ nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
+ redGradient, greenGradient, blueGradient);
+ return bitmap;
+ }
+
+ public void setSpline(Spline spline, int splineIndex) {
+ mParameters.setSpline(splineIndex, new Spline(spline));
+ }
+
+ public Spline getSpline(int splineIndex) {
+ return mParameters.getSpline(splineIndex);
+ }
+
+ public void reset() {
+ Spline spline = new Spline();
+
+ spline.addPoint(0.0f, 1.0f);
+ spline.addPoint(1.0f, 0.0f);
+
+ for (int i = 0; i < 4; i++) {
+ mParameters.setSpline(i, new Spline(spline));
+ }
+ }
+
+ public void useFilter(ImageFilter a) {
+ ImageFilterCurves c = (ImageFilterCurves) a;
+ for (int i = 0; i < 4; i++) {
+ if (c.mParameters.getSpline(i) != null) {
+ setSpline(c.mParameters.getSpline(i), i);
+ }
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java
new file mode 100644
index 000000000..efb9cde71
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class ImageFilterDownsample extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "DOWNSAMPLE";
+ private static final int ICON_DOWNSAMPLE_FRACTION = 8;
+ private ImageLoader mImageLoader;
+
+ public ImageFilterDownsample(ImageLoader loader) {
+ mName = "Downsample";
+ mImageLoader = loader;
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation();
+ representation.setName("Downsample");
+ representation.setSerializationName(SERIALIZATION_NAME);
+
+ representation.setFilterClass(ImageFilterDownsample.class);
+ representation.setMaximum(100);
+ representation.setMinimum(1);
+ representation.setValue(50);
+ representation.setDefaultValue(50);
+ representation.setPreviewValue(3);
+ representation.setTextId(R.string.downsample);
+ return representation;
+ }
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ int p = getParameters().getValue();
+
+ // size of original precached image
+ Rect size = MasterImage.getImage().getOriginalBounds();
+ int orig_w = size.width();
+ int orig_h = size.height();
+
+ if (p > 0 && p < 100) {
+ // scale preview to same size as the resulting bitmap from a "save"
+ int newWidth = orig_w * p / 100;
+ int newHeight = orig_h * p / 100;
+
+ // only scale preview if preview isn't already scaled enough
+ if (newWidth <= 0 || newHeight <= 0 || newWidth >= w || newHeight >= h) {
+ return bitmap;
+ }
+ Bitmap ret = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
+ if (ret != bitmap) {
+ bitmap.recycle();
+ }
+ return ret;
+ }
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
new file mode 100644
index 000000000..7df5ffb64
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation.StrokeData;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.pipeline.FilterEnvironment;
+
+import java.util.Vector;
+
+public class ImageFilterDraw extends ImageFilter {
+ private static final String LOGTAG = "ImageFilterDraw";
+ public final static byte SIMPLE_STYLE = 0;
+ public final static byte BRUSH_STYLE_SPATTER = 1;
+ public final static byte BRUSH_STYLE_MARKER = 2;
+ public final static int NUMBER_OF_STYLES = 3;
+ Bitmap mOverlayBitmap; // this accelerates interaction
+ int mCachedStrokes = -1;
+ int mCurrentStyle = 0;
+
+ FilterDrawRepresentation mParameters = new FilterDrawRepresentation();
+
+ public ImageFilterDraw() {
+ mName = "Image Draw";
+ }
+
+ DrawStyle[] mDrawingsTypes = new DrawStyle[] {
+ new SimpleDraw(),
+ new Brush(R.drawable.brush_marker),
+ new Brush(R.drawable.brush_spatter)
+ };
+ {
+ for (int i = 0; i < mDrawingsTypes.length; i++) {
+ mDrawingsTypes[i].setType((byte) i);
+ }
+
+ }
+
+ @Override
+ public FilterRepresentation getDefaultRepresentation() {
+ return new FilterDrawRepresentation();
+ }
+
+ @Override
+ public void useRepresentation(FilterRepresentation representation) {
+ FilterDrawRepresentation parameters = (FilterDrawRepresentation) representation;
+ mParameters = parameters;
+ }
+
+ public void setStyle(byte style) {
+ mCurrentStyle = style % mDrawingsTypes.length;
+ }
+
+ public int getStyle() {
+ return mCurrentStyle;
+ }
+
+ public static interface DrawStyle {
+ public void setType(byte type);
+ public void paint(FilterDrawRepresentation.StrokeData sd, Canvas canvas, Matrix toScrMatrix,
+ int quality);
+ }
+
+ class SimpleDraw implements DrawStyle {
+ byte mType;
+
+ @Override
+ public void setType(byte type) {
+ mType = type;
+ }
+
+ @Override
+ public void paint(FilterDrawRepresentation.StrokeData sd, Canvas canvas, Matrix toScrMatrix,
+ int quality) {
+ if (sd == null) {
+ return;
+ }
+ if (sd.mPath == null) {
+ return;
+ }
+ Paint paint = new Paint();
+
+ paint.setStyle(Style.STROKE);
+ paint.setColor(sd.mColor);
+ paint.setStrokeWidth(toScrMatrix.mapRadius(sd.mRadius));
+
+ // done this way because of a bug in path.transform(matrix)
+ Path mCacheTransPath = new Path();
+ mCacheTransPath.addPath(sd.mPath, toScrMatrix);
+
+ canvas.drawPath(mCacheTransPath, paint);
+ }
+ }
+
+ class Brush implements DrawStyle {
+ int mBrushID;
+ Bitmap mBrush;
+ byte mType;
+
+ public Brush(int brushID) {
+ mBrushID = brushID;
+ }
+
+ public Bitmap getBrush() {
+ if (mBrush == null) {
+ BitmapFactory.Options opt = new BitmapFactory.Options();
+ opt.inPreferredConfig = Bitmap.Config.ALPHA_8;
+ mBrush = BitmapFactory.decodeResource(MasterImage.getImage().getActivity()
+ .getResources(), mBrushID, opt);
+ mBrush = mBrush.extractAlpha();
+ }
+ return mBrush;
+ }
+
+ @Override
+ public void paint(FilterDrawRepresentation.StrokeData sd, Canvas canvas,
+ Matrix toScrMatrix,
+ int quality) {
+ if (sd == null || sd.mPath == null) {
+ return;
+ }
+ Paint paint = new Paint();
+ paint.setStyle(Style.STROKE);
+ paint.setAntiAlias(true);
+ Path mCacheTransPath = new Path();
+ mCacheTransPath.addPath(sd.mPath, toScrMatrix);
+ draw(canvas, paint, sd.mColor, toScrMatrix.mapRadius(sd.mRadius) * 2,
+ mCacheTransPath);
+ }
+
+ public Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)
+ {
+ Matrix m = new Matrix();
+ m.setScale(dstWidth / (float) src.getWidth(), dstHeight / (float) src.getHeight());
+ Bitmap result = Bitmap.createBitmap(dstWidth, dstHeight, src.getConfig());
+ Canvas canvas = new Canvas(result);
+
+ Paint paint = new Paint();
+ paint.setFilterBitmap(filter);
+ canvas.drawBitmap(src, m, paint);
+
+ return result;
+
+ }
+ void draw(Canvas canvas, Paint paint, int color, float size, Path path) {
+ PathMeasure mPathMeasure = new PathMeasure();
+ float[] mPosition = new float[2];
+ float[] mTan = new float[2];
+
+ mPathMeasure.setPath(path, false);
+
+ paint.setAntiAlias(true);
+ paint.setColor(color);
+
+ paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
+ Bitmap brush;
+ // done this way because of a bug in
+ // Bitmap.createScaledBitmap(getBrush(),(int) size,(int) size,true);
+ brush = createScaledBitmap(getBrush(), (int) size, (int) size, true);
+ float len = mPathMeasure.getLength();
+ float s2 = size / 2;
+ float step = s2 / 8;
+ for (float i = 0; i < len; i += step) {
+ mPathMeasure.getPosTan(i, mPosition, mTan);
+ // canvas.drawCircle(pos[0], pos[1], size, paint);
+ canvas.drawBitmap(brush, mPosition[0] - s2, mPosition[1] - s2, paint);
+ }
+ }
+
+ @Override
+ public void setType(byte type) {
+ mType = type;
+ }
+ }
+
+ void paint(FilterDrawRepresentation.StrokeData sd, Canvas canvas, Matrix toScrMatrix,
+ int quality) {
+ mDrawingsTypes[sd.mType].paint(sd, canvas, toScrMatrix, quality);
+ }
+
+ public void drawData(Canvas canvas, Matrix originalRotateToScreen, int quality) {
+ Paint paint = new Paint();
+ if (quality == FilterEnvironment.QUALITY_FINAL) {
+ paint.setAntiAlias(true);
+ }
+ paint.setStyle(Style.STROKE);
+ paint.setColor(Color.RED);
+ paint.setStrokeWidth(40);
+
+ if (mParameters.getDrawing().isEmpty() && mParameters.getCurrentDrawing() == null) {
+ return;
+ }
+ if (quality == FilterEnvironment.QUALITY_FINAL) {
+ for (FilterDrawRepresentation.StrokeData strokeData : mParameters.getDrawing()) {
+ paint(strokeData, canvas, originalRotateToScreen, quality);
+ }
+ return;
+ }
+
+ if (mOverlayBitmap == null ||
+ mOverlayBitmap.getWidth() != canvas.getWidth() ||
+ mOverlayBitmap.getHeight() != canvas.getHeight() ||
+ mParameters.getDrawing().size() < mCachedStrokes) {
+
+ mOverlayBitmap = Bitmap.createBitmap(
+ canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
+ mCachedStrokes = 0;
+ }
+
+ if (mCachedStrokes < mParameters.getDrawing().size()) {
+ fillBuffer(originalRotateToScreen);
+ }
+ canvas.drawBitmap(mOverlayBitmap, 0, 0, paint);
+
+ StrokeData stroke = mParameters.getCurrentDrawing();
+ if (stroke != null) {
+ paint(stroke, canvas, originalRotateToScreen, quality);
+ }
+ }
+
+ public void fillBuffer(Matrix originalRotateToScreen) {
+ Canvas drawCache = new Canvas(mOverlayBitmap);
+ Vector<FilterDrawRepresentation.StrokeData> v = mParameters.getDrawing();
+ int n = v.size();
+
+ for (int i = mCachedStrokes; i < n; i++) {
+ paint(v.get(i), drawCache, originalRotateToScreen, FilterEnvironment.QUALITY_PREVIEW);
+ }
+ mCachedStrokes = n;
+ }
+
+ public void draw(Canvas canvas, Matrix originalRotateToScreen) {
+ for (FilterDrawRepresentation.StrokeData strokeData : mParameters.getDrawing()) {
+ paint(strokeData, canvas, originalRotateToScreen, FilterEnvironment.QUALITY_PREVIEW);
+ }
+ mDrawingsTypes[mCurrentStyle].paint(
+ null, canvas, originalRotateToScreen, FilterEnvironment.QUALITY_PREVIEW);
+ }
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+
+ Matrix m = getOriginalToScreenMatrix(w, h);
+ drawData(new Canvas(bitmap), m, quality);
+ return bitmap;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java
new file mode 100644
index 000000000..2d0d7653d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.R;
+
+public class ImageFilterEdge extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "EDGE";
+ public ImageFilterEdge() {
+ mName = "Edge";
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterRepresentation representation = super.getDefaultRepresentation();
+ representation.setName("Edge");
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setFilterClass(ImageFilterEdge.class);
+ representation.setTextId(R.string.edge);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float p);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ float p = getParameters().getValue() + 101;
+ p = (float) p / 100;
+ nativeApplyFilter(bitmap, w, h, p);
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
new file mode 100644
index 000000000..69eab7330
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterExposure extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "EXPOSURE";
+ public ImageFilterExposure() {
+ mName = "Exposure";
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterBasicRepresentation representation =
+ (FilterBasicRepresentation) super.getDefaultRepresentation();
+ representation.setName("Exposure");
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setFilterClass(ImageFilterExposure.class);
+ representation.setTextId(R.string.exposure);
+ representation.setMinimum(-100);
+ representation.setMaximum(100);
+ representation.setDefaultValue(0);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ float value = getParameters().getValue();
+ nativeApplyFilter(bitmap, w, h, value);
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
new file mode 100644
index 000000000..19bea593b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import com.android.gallery3d.app.Log;
+
+public class ImageFilterFx extends ImageFilter {
+ private static final String LOGTAG = "ImageFilterFx";
+ private FilterFxRepresentation mParameters = null;
+ private Bitmap mFxBitmap = null;
+ private Resources mResources = null;
+ private int mFxBitmapId = 0;
+
+ public ImageFilterFx() {
+ }
+
+ @Override
+ public void freeResources() {
+ if (mFxBitmap != null) mFxBitmap.recycle();
+ mFxBitmap = null;
+ }
+
+ @Override
+ public FilterRepresentation getDefaultRepresentation() {
+ return null;
+ }
+
+ public void useRepresentation(FilterRepresentation representation) {
+ FilterFxRepresentation parameters = (FilterFxRepresentation) representation;
+ mParameters = parameters;
+ }
+
+ public FilterFxRepresentation getParameters() {
+ return mParameters;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h,
+ Bitmap fxBitmap, int fxw, int fxh,
+ int start, int end);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null || mResources == null) {
+ return bitmap;
+ }
+
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+
+ int bitmapResourceId = getParameters().getBitmapResource();
+ if (bitmapResourceId == 0) { // null filter fx
+ return bitmap;
+ }
+
+ if (mFxBitmap == null || mFxBitmapId != bitmapResourceId) {
+ BitmapFactory.Options o = new BitmapFactory.Options();
+ o.inScaled = false;
+ mFxBitmapId = bitmapResourceId;
+ if (mFxBitmapId != 0) {
+ mFxBitmap = BitmapFactory.decodeResource(mResources, mFxBitmapId, o);
+ } else {
+ Log.w(LOGTAG, "bad resource for filter: " + mName);
+ }
+ }
+
+ if (mFxBitmap == null) {
+ return bitmap;
+ }
+
+ int fxw = mFxBitmap.getWidth();
+ int fxh = mFxBitmap.getHeight();
+
+ int stride = w * 4;
+ int max = stride * h;
+ int increment = stride * 256; // 256 lines
+ for (int i = 0; i < max; i += increment) {
+ int start = i;
+ int end = i + increment;
+ if (end > max) {
+ end = max;
+ }
+ if (!getEnvironment().needsStop()) {
+ nativeApplyFilter(bitmap, w, h, mFxBitmap, fxw, fxh, start, end);
+ }
+ }
+
+ return bitmap;
+ }
+
+ public void setResources(Resources resources) {
+ mResources = resources;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGrad.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGrad.java
new file mode 100644
index 000000000..cbdfaa623
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGrad.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Matrix;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.pipeline.FilterEnvironment;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.support.v8.renderscript.Allocation;
+import android.support.v8.renderscript.Element;
+import android.support.v8.renderscript.RenderScript;
+import android.support.v8.renderscript.Script.LaunchOptions;
+import android.support.v8.renderscript.Type;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.pipeline.FilterEnvironment;
+
+public class ImageFilterGrad extends ImageFilterRS {
+ private static final String LOGTAG = "ImageFilterGrad";
+ private ScriptC_grad mScript;
+ private Bitmap mSourceBitmap;
+ private static final int RADIUS_SCALE_FACTOR = 160;
+
+ private static final int STRIP_SIZE = 64;
+
+ FilterGradRepresentation mParameters = new FilterGradRepresentation();
+ private Bitmap mOverlayBitmap;
+
+ public ImageFilterGrad() {
+ mName = "grad";
+ }
+
+ @Override
+ public FilterRepresentation getDefaultRepresentation() {
+ return new FilterGradRepresentation();
+ }
+
+ @Override
+ public void useRepresentation(FilterRepresentation representation) {
+ mParameters = (FilterGradRepresentation) representation;
+ }
+
+ @Override
+ protected void resetAllocations() {
+
+ }
+
+ @Override
+ public void resetScripts() {
+ if (mScript != null) {
+ mScript.destroy();
+ mScript = null;
+ }
+ }
+ @Override
+ protected void createFilter(android.content.res.Resources res, float scaleFactor,
+ int quality) {
+ createFilter(res, scaleFactor, quality, getInPixelsAllocation());
+ }
+
+ @Override
+ protected void createFilter(android.content.res.Resources res, float scaleFactor,
+ int quality, Allocation in) {
+ RenderScript rsCtx = getRenderScriptContext();
+
+ Type.Builder tb_float = new Type.Builder(rsCtx, Element.F32_4(rsCtx));
+ tb_float.setX(in.getType().getX());
+ tb_float.setY(in.getType().getY());
+ mScript = new ScriptC_grad(rsCtx, res, R.raw.grad);
+ }
+
+
+ private Bitmap getSourceBitmap() {
+ assert (mSourceBitmap != null);
+ return mSourceBitmap;
+ }
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (SIMPLE_ICONS && FilterEnvironment.QUALITY_ICON == quality) {
+ return bitmap;
+ }
+
+ mSourceBitmap = bitmap;
+ Bitmap ret = super.apply(bitmap, scaleFactor, quality);
+ mSourceBitmap = null;
+
+ return ret;
+ }
+
+ @Override
+ protected void bindScriptValues() {
+ int width = getInPixelsAllocation().getType().getX();
+ int height = getInPixelsAllocation().getType().getY();
+ mScript.set_inputWidth(width);
+ mScript.set_inputHeight(height);
+ }
+
+ @Override
+ protected void runFilter() {
+ int[] x1 = mParameters.getXPos1();
+ int[] y1 = mParameters.getYPos1();
+ int[] x2 = mParameters.getXPos2();
+ int[] y2 = mParameters.getYPos2();
+
+ int width = getInPixelsAllocation().getType().getX();
+ int height = getInPixelsAllocation().getType().getY();
+ Matrix m = getOriginalToScreenMatrix(width, height);
+ float[] coord = new float[2];
+ for (int i = 0; i < x1.length; i++) {
+ coord[0] = x1[i];
+ coord[1] = y1[i];
+ m.mapPoints(coord);
+ x1[i] = (int) coord[0];
+ y1[i] = (int) coord[1];
+ coord[0] = x2[i];
+ coord[1] = y2[i];
+ m.mapPoints(coord);
+ x2[i] = (int) coord[0];
+ y2[i] = (int) coord[1];
+ }
+
+ mScript.set_mask(mParameters.getMask());
+ mScript.set_xPos1(x1);
+ mScript.set_yPos1(y1);
+ mScript.set_xPos2(x2);
+ mScript.set_yPos2(y2);
+
+ mScript.set_brightness(mParameters.getBrightness());
+ mScript.set_contrast(mParameters.getContrast());
+ mScript.set_saturation(mParameters.getSaturation());
+
+ mScript.invoke_setupGradParams();
+ runSelectiveAdjust(
+ getInPixelsAllocation(), getOutPixelsAllocation());
+
+ }
+
+ private void runSelectiveAdjust(Allocation in, Allocation out) {
+ int width = in.getType().getX();
+ int height = in.getType().getY();
+
+ LaunchOptions options = new LaunchOptions();
+ int ty;
+ options.setX(0, width);
+
+ for (ty = 0; ty < height; ty += STRIP_SIZE) {
+ int endy = ty + STRIP_SIZE;
+ if (endy > height) {
+ endy = height;
+ }
+ options.setY(ty, endy);
+ mScript.forEach_selectiveAdjust(in, out, options);
+ if (checkStop()) {
+ return;
+ }
+ }
+ }
+
+ private boolean checkStop() {
+ RenderScript rsCtx = getRenderScriptContext();
+ rsCtx.finish();
+ if (getEnvironment().needsStop()) {
+ return true;
+ }
+ return false;
+ }
+}
+
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
new file mode 100644
index 000000000..4c837e0bf
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.R;
+
+public class ImageFilterHighlights extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "HIGHLIGHTS";
+ private static final String LOGTAG = "ImageFilterVignette";
+
+ public ImageFilterHighlights() {
+ mName = "Highlights";
+ }
+
+ SplineMath mSpline = new SplineMath(5);
+ double[] mHighlightCurve = { 0.0, 0.32, 0.418, 0.476, 0.642 };
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterBasicRepresentation representation =
+ (FilterBasicRepresentation) super.getDefaultRepresentation();
+ representation.setName("Highlights");
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setFilterClass(ImageFilterHighlights.class);
+ representation.setTextId(R.string.highlight_recovery);
+ representation.setMinimum(-100);
+ representation.setMaximum(100);
+ representation.setDefaultValue(0);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float[] luminanceMap);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ float p = getParameters().getValue();
+ double t = p/100.;
+ for (int i = 0; i < 5; i++) {
+ double x = i / 4.;
+ double y = mHighlightCurve[i] *t+x*(1-t);
+ mSpline.setPoint(i, x, y);
+ }
+
+ float[][] curve = mSpline.calculatetCurve(256);
+ float[] luminanceMap = new float[curve.length];
+ for (int i = 0; i < luminanceMap.length; i++) {
+ luminanceMap[i] = curve[i][1];
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+
+ nativeApplyFilter(bitmap, w, h, luminanceMap);
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
new file mode 100644
index 000000000..b87c25490
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.BasicEditor;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterHue extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "HUE";
+ private ColorSpaceMatrix cmatrix = null;
+
+ public ImageFilterHue() {
+ mName = "Hue";
+ cmatrix = new ColorSpaceMatrix();
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterBasicRepresentation representation =
+ (FilterBasicRepresentation) super.getDefaultRepresentation();
+ representation.setName("Hue");
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setFilterClass(ImageFilterHue.class);
+ representation.setMinimum(-180);
+ representation.setMaximum(180);
+ representation.setTextId(R.string.hue);
+ representation.setEditorId(BasicEditor.ID);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float []matrix);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ float value = getParameters().getValue();
+ cmatrix.identity();
+ cmatrix.setHue(value);
+
+ nativeApplyFilter(bitmap, w, h, cmatrix.getMatrix());
+
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
new file mode 100644
index 000000000..77cdf47b3
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.text.format.Time;
+
+import com.android.gallery3d.R;
+
+public class ImageFilterKMeans extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "KMEANS";
+ private int mSeed = 0;
+
+ public ImageFilterKMeans() {
+ mName = "KMeans";
+
+ // set random seed for session
+ Time t = new Time();
+ t.setToNow();
+ mSeed = (int) t.toMillis(false);
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation();
+ representation.setName("KMeans");
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setFilterClass(ImageFilterKMeans.class);
+ representation.setMaximum(20);
+ representation.setMinimum(2);
+ representation.setValue(4);
+ representation.setDefaultValue(4);
+ representation.setPreviewValue(4);
+ representation.setTextId(R.string.kmeans);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int width, int height,
+ Bitmap large_ds_bm, int lwidth, int lheight, Bitmap small_ds_bm,
+ int swidth, int sheight, int p, int seed);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+
+ Bitmap large_bm_ds = bitmap;
+ Bitmap small_bm_ds = bitmap;
+
+ // find width/height for larger downsampled bitmap
+ int lw = w;
+ int lh = h;
+ while (lw > 256 && lh > 256) {
+ lw /= 2;
+ lh /= 2;
+ }
+ if (lw != w) {
+ large_bm_ds = Bitmap.createScaledBitmap(bitmap, lw, lh, true);
+ }
+
+ // find width/height for smaller downsampled bitmap
+ int sw = lw;
+ int sh = lh;
+ while (sw > 64 && sh > 64) {
+ sw /= 2;
+ sh /= 2;
+ }
+ if (sw != lw) {
+ small_bm_ds = Bitmap.createScaledBitmap(large_bm_ds, sw, sh, true);
+ }
+
+ if (getParameters() != null) {
+ int p = Math.max(getParameters().getValue(), getParameters().getMinimum()) % (getParameters().getMaximum() + 1);
+ nativeApplyFilter(bitmap, w, h, large_bm_ds, lw, lh, small_bm_ds, sw, sh, p, mSeed);
+ }
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java
new file mode 100644
index 000000000..98497596b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java
@@ -0,0 +1,39 @@
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+
+public class ImageFilterNegative extends ImageFilter {
+ private static final String SERIALIZATION_NAME = "NEGATIVE";
+ public ImageFilterNegative() {
+ mName = "Negative";
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterRepresentation representation = new FilterDirectRepresentation("Negative");
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setFilterClass(ImageFilterNegative.class);
+ representation.setTextId(R.string.negative);
+ representation.setShowParameterValue(false);
+ representation.setEditorId(ImageOnlyEditor.ID);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
+
+ @Override
+ public void useRepresentation(FilterRepresentation representation) {
+
+ }
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ nativeApplyFilter(bitmap, w, h);
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java
new file mode 100644
index 000000000..25e5d1476
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+
+public class ImageFilterParametricBorder extends ImageFilter {
+ private FilterColorBorderRepresentation mParameters = null;
+
+ public ImageFilterParametricBorder() {
+ mName = "Border";
+ }
+
+ public void useRepresentation(FilterRepresentation representation) {
+ FilterColorBorderRepresentation parameters = (FilterColorBorderRepresentation) representation;
+ mParameters = parameters;
+ }
+
+ public FilterColorBorderRepresentation getParameters() {
+ return mParameters;
+ }
+
+ private void applyHelper(Canvas canvas, int w, int h) {
+ if (getParameters() == null) {
+ return;
+ }
+ Path border = new Path();
+ border.moveTo(0, 0);
+ float bs = getParameters().getBorderSize() / 100.0f * w;
+ float r = getParameters().getBorderRadius() / 100.0f * w;
+ border.lineTo(0, h);
+ border.lineTo(w, h);
+ border.lineTo(w, 0);
+ border.lineTo(0, 0);
+ border.addRoundRect(new RectF(bs, bs, w - bs, h - bs),
+ r, r, Path.Direction.CW);
+
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setColor(getParameters().getColor());
+ canvas.drawPath(border, paint);
+ }
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ Canvas canvas = new Canvas(bitmap);
+ applyHelper(canvas, bitmap.getWidth(), bitmap.getHeight());
+ return bitmap;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
new file mode 100644
index 000000000..5695ef53e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.support.v8.renderscript.*;
+import android.util.Log;
+import android.content.res.Resources;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.pipeline.PipelineInterface;
+
+public abstract class ImageFilterRS extends ImageFilter {
+ private static final String LOGTAG = "ImageFilterRS";
+ private boolean DEBUG = false;
+ private int mLastInputWidth = 0;
+ private int mLastInputHeight = 0;
+ private long mLastTimeCalled;
+
+ public static boolean PERF_LOGGING = false;
+
+ private static ScriptC_grey mGreyConvert = null;
+ private static RenderScript mRScache = null;
+
+ private volatile boolean mResourcesLoaded = false;
+
+ protected abstract void createFilter(android.content.res.Resources res,
+ float scaleFactor, int quality);
+
+ protected void createFilter(android.content.res.Resources res,
+ float scaleFactor, int quality, Allocation in) {}
+ protected void bindScriptValues(Allocation in) {}
+
+ protected abstract void runFilter();
+
+ protected void update(Bitmap bitmap) {
+ getOutPixelsAllocation().copyTo(bitmap);
+ }
+
+ protected RenderScript getRenderScriptContext() {
+ PipelineInterface pipeline = getEnvironment().getPipeline();
+ return pipeline.getRSContext();
+ }
+
+ protected Allocation getInPixelsAllocation() {
+ PipelineInterface pipeline = getEnvironment().getPipeline();
+ return pipeline.getInPixelsAllocation();
+ }
+
+ protected Allocation getOutPixelsAllocation() {
+ PipelineInterface pipeline = getEnvironment().getPipeline();
+ return pipeline.getOutPixelsAllocation();
+ }
+
+ @Override
+ public void apply(Allocation in, Allocation out) {
+ long startOverAll = System.nanoTime();
+ if (PERF_LOGGING) {
+ long delay = (startOverAll - mLastTimeCalled) / 1000;
+ String msg = String.format("%s; image size %dx%d; ", getName(),
+ in.getType().getX(), in.getType().getY());
+ msg += String.format("called after %.2f ms (%.2f FPS); ",
+ delay / 1000.f, 1000000.f / delay);
+ Log.i(LOGTAG, msg);
+ }
+ mLastTimeCalled = startOverAll;
+ long startFilter = 0;
+ long endFilter = 0;
+ if (!mResourcesLoaded) {
+ PipelineInterface pipeline = getEnvironment().getPipeline();
+ createFilter(pipeline.getResources(), getEnvironment().getScaleFactor(),
+ getEnvironment().getQuality(), in);
+ mResourcesLoaded = true;
+ }
+ startFilter = System.nanoTime();
+ bindScriptValues(in);
+ run(in, out);
+ if (PERF_LOGGING) {
+ getRenderScriptContext().finish();
+ endFilter = System.nanoTime();
+ long endOverAll = System.nanoTime();
+ String msg = String.format("%s; image size %dx%d; ", getName(),
+ in.getType().getX(), in.getType().getY());
+ long timeOverAll = (endOverAll - startOverAll) / 1000;
+ long timeFilter = (endFilter - startFilter) / 1000;
+ msg += String.format("over all %.2f ms (%.2f FPS); ",
+ timeOverAll / 1000.f, 1000000.f / timeOverAll);
+ msg += String.format("run filter %.2f ms (%.2f FPS)",
+ timeFilter / 1000.f, 1000000.f / timeFilter);
+ Log.i(LOGTAG, msg);
+ }
+ }
+
+ protected void run(Allocation in, Allocation out) {}
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) {
+ return bitmap;
+ }
+ try {
+ PipelineInterface pipeline = getEnvironment().getPipeline();
+ if (DEBUG) {
+ Log.v(LOGTAG, "apply filter " + getName() + " in pipeline " + pipeline.getName());
+ }
+ Resources rsc = pipeline.getResources();
+ boolean sizeChanged = false;
+ if (getInPixelsAllocation() != null
+ && ((getInPixelsAllocation().getType().getX() != mLastInputWidth)
+ || (getInPixelsAllocation().getType().getY() != mLastInputHeight))) {
+ sizeChanged = true;
+ }
+ if (pipeline.prepareRenderscriptAllocations(bitmap)
+ || !isResourcesLoaded() || sizeChanged) {
+ freeResources();
+ createFilter(rsc, scaleFactor, quality);
+ setResourcesLoaded(true);
+ mLastInputWidth = getInPixelsAllocation().getType().getX();
+ mLastInputHeight = getInPixelsAllocation().getType().getY();
+ }
+ bindScriptValues();
+ runFilter();
+ update(bitmap);
+ if (DEBUG) {
+ Log.v(LOGTAG, "DONE apply filter " + getName() + " in pipeline " + pipeline.getName());
+ }
+ } catch (android.renderscript.RSIllegalArgumentException e) {
+ Log.e(LOGTAG, "Illegal argument? " + e);
+ } catch (android.renderscript.RSRuntimeException e) {
+ Log.e(LOGTAG, "RS runtime exception ? " + e);
+ } catch (java.lang.OutOfMemoryError e) {
+ // Many of the renderscript filters allocated large (>16Mb resources) in order to apply.
+ System.gc();
+ displayLowMemoryToast();
+ Log.e(LOGTAG, "not enough memory for filter " + getName(), e);
+ }
+ return bitmap;
+ }
+
+ protected static Allocation convertBitmap(RenderScript RS, Bitmap bitmap) {
+ return Allocation.createFromBitmap(RS, bitmap,
+ Allocation.MipmapControl.MIPMAP_NONE,
+ Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_TEXTURE);
+ }
+
+ private static Allocation convertRGBAtoA(RenderScript RS, Bitmap bitmap) {
+ if (RS != mRScache || mGreyConvert == null) {
+ mGreyConvert = new ScriptC_grey(RS, RS.getApplicationContext().getResources(),
+ R.raw.grey);
+ mRScache = RS;
+ }
+
+ Type.Builder tb_a8 = new Type.Builder(RS, Element.A_8(RS));
+
+ Allocation bitmapTemp = convertBitmap(RS, bitmap);
+ if (bitmapTemp.getType().getElement().isCompatible(Element.A_8(RS))) {
+ return bitmapTemp;
+ }
+
+ tb_a8.setX(bitmapTemp.getType().getX());
+ tb_a8.setY(bitmapTemp.getType().getY());
+ Allocation bitmapAlloc = Allocation.createTyped(RS, tb_a8.create(),
+ Allocation.MipmapControl.MIPMAP_NONE,
+ Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_TEXTURE);
+ mGreyConvert.forEach_RGBAtoA(bitmapTemp, bitmapAlloc);
+ bitmapTemp.destroy();
+ return bitmapAlloc;
+ }
+
+ public Allocation loadScaledResourceAlpha(int resource, int inSampleSize) {
+ Resources res = getEnvironment().getPipeline().getResources();
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ALPHA_8;
+ options.inSampleSize = inSampleSize;
+ Bitmap bitmap = BitmapFactory.decodeResource(
+ res,
+ resource, options);
+ Allocation ret = convertRGBAtoA(getRenderScriptContext(), bitmap);
+ bitmap.recycle();
+ return ret;
+ }
+
+ public Allocation loadScaledResourceAlpha(int resource, int w, int h, int inSampleSize) {
+ Resources res = getEnvironment().getPipeline().getResources();
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ALPHA_8;
+ options.inSampleSize = inSampleSize;
+ Bitmap bitmap = BitmapFactory.decodeResource(
+ res,
+ resource, options);
+ Bitmap resizeBitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
+ Allocation ret = convertRGBAtoA(getRenderScriptContext(), resizeBitmap);
+ resizeBitmap.recycle();
+ bitmap.recycle();
+ return ret;
+ }
+
+ public Allocation loadResourceAlpha(int resource) {
+ return loadScaledResourceAlpha(resource, 1);
+ }
+
+ public Allocation loadResource(int resource) {
+ Resources res = getEnvironment().getPipeline().getResources();
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ Bitmap bitmap = BitmapFactory.decodeResource(
+ res,
+ resource, options);
+ Allocation ret = convertBitmap(getRenderScriptContext(), bitmap);
+ bitmap.recycle();
+ return ret;
+ }
+
+ private boolean isResourcesLoaded() {
+ return mResourcesLoaded;
+ }
+
+ private void setResourcesLoaded(boolean resourcesLoaded) {
+ mResourcesLoaded = resourcesLoaded;
+ }
+
+ /**
+ * Bitmaps and RS Allocations should be cleared here
+ */
+ abstract protected void resetAllocations();
+
+ /**
+ * RS Script objects (and all other RS objects) should be cleared here
+ */
+ public abstract void resetScripts();
+
+ /**
+ * Scripts values should be bound here
+ */
+ abstract protected void bindScriptValues();
+
+ public void freeResources() {
+ if (!isResourcesLoaded()) {
+ return;
+ }
+ resetAllocations();
+ mLastInputWidth = 0;
+ mLastInputHeight = 0;
+ setResourcesLoaded(false);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java
new file mode 100644
index 000000000..511f9e90f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+
+import java.util.Vector;
+
+public class ImageFilterRedEye extends ImageFilter {
+ private static final String LOGTAG = "ImageFilterRedEye";
+ FilterRedEyeRepresentation mParameters = new FilterRedEyeRepresentation();
+
+ public ImageFilterRedEye() {
+ mName = "Red Eye";
+ }
+
+ @Override
+ public FilterRepresentation getDefaultRepresentation() {
+ return new FilterRedEyeRepresentation();
+ }
+
+ public boolean isNil() {
+ return mParameters.isNil();
+ }
+
+ public Vector<FilterPoint> getCandidates() {
+ return mParameters.getCandidates();
+ }
+
+ public void clear() {
+ mParameters.clearCandidates();
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, short[] matrix);
+
+ @Override
+ public void useRepresentation(FilterRepresentation representation) {
+ FilterRedEyeRepresentation parameters = (FilterRedEyeRepresentation) representation;
+ mParameters = parameters;
+ }
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ short[] rect = new short[4];
+
+ int size = mParameters.getNumberOfCandidates();
+ Matrix originalToScreen = getOriginalToScreenMatrix(w, h);
+ for (int i = 0; i < size; i++) {
+ RectF r = new RectF(((RedEyeCandidate) (mParameters.getCandidate(i))).mRect);
+ originalToScreen.mapRect(r);
+ if (r.intersect(0, 0, w, h)) {
+ rect[0] = (short) r.left;
+ rect[1] = (short) r.top;
+ rect[2] = (short) r.width();
+ rect[3] = (short) r.height();
+ nativeApplyFilter(bitmap, w, h, rect);
+ }
+ }
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
new file mode 100644
index 000000000..c3124ff77
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterSaturated extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "SATURATED";
+ public ImageFilterSaturated() {
+ mName = "Saturated";
+ }
+
+ @Override
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterBasicRepresentation representation =
+ (FilterBasicRepresentation) super.getDefaultRepresentation();
+ representation.setName("Saturated");
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setFilterClass(ImageFilterSaturated.class);
+ representation.setTextId(R.string.saturation);
+ representation.setMinimum(-100);
+ representation.setMaximum(100);
+ representation.setDefaultValue(0);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float saturation);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ int p = getParameters().getValue();
+ float value = 1 + p / 100.0f;
+ nativeApplyFilter(bitmap, w, h, value);
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
new file mode 100644
index 000000000..bd119bbc9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterShadows extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "SHADOWS";
+ public ImageFilterShadows() {
+ mName = "Shadows";
+
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterBasicRepresentation representation =
+ (FilterBasicRepresentation) super.getDefaultRepresentation();
+ representation.setName("Shadows");
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setFilterClass(ImageFilterShadows.class);
+ representation.setTextId(R.string.shadow_recovery);
+ representation.setMinimum(-100);
+ representation.setMaximum(100);
+ representation.setDefaultValue(0);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float factor);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ float p = getParameters().getValue();
+
+ nativeApplyFilter(bitmap, w, h, p);
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
new file mode 100644
index 000000000..3bd794464
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+
+public class ImageFilterSharpen extends ImageFilterRS {
+ private static final String SERIALIZATION_NAME = "SHARPEN";
+ private static final String LOGTAG = "ImageFilterSharpen";
+ private ScriptC_convolve3x3 mScript;
+
+ private FilterBasicRepresentation mParameters;
+
+ public ImageFilterSharpen() {
+ mName = "Sharpen";
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterRepresentation representation = new FilterBasicRepresentation("Sharpen", 0, 0, 100);
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setShowParameterValue(true);
+ representation.setFilterClass(ImageFilterSharpen.class);
+ representation.setTextId(R.string.sharpness);
+ representation.setOverlayId(R.drawable.filtershow_button_colors_sharpen);
+ representation.setEditorId(R.id.imageShow);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ public void useRepresentation(FilterRepresentation representation) {
+ FilterBasicRepresentation parameters = (FilterBasicRepresentation) representation;
+ mParameters = parameters;
+ }
+
+ @Override
+ protected void resetAllocations() {
+ // nothing to do
+ }
+
+ @Override
+ public void resetScripts() {
+ if (mScript != null) {
+ mScript.destroy();
+ mScript = null;
+ }
+ }
+
+ @Override
+ protected void createFilter(android.content.res.Resources res, float scaleFactor,
+ int quality) {
+ if (mScript == null) {
+ mScript = new ScriptC_convolve3x3(getRenderScriptContext(), res, R.raw.convolve3x3);
+ }
+ }
+
+ private void computeKernel() {
+ float scaleFactor = getEnvironment().getScaleFactor();
+ float p1 = mParameters.getValue() * scaleFactor;
+ float value = p1 / 100.0f;
+ float f[] = new float[9];
+ float p = value;
+ f[0] = -p;
+ f[1] = -p;
+ f[2] = -p;
+ f[3] = -p;
+ f[4] = 8 * p + 1;
+ f[5] = -p;
+ f[6] = -p;
+ f[7] = -p;
+ f[8] = -p;
+ mScript.set_gCoeffs(f);
+ }
+
+ @Override
+ protected void bindScriptValues() {
+ int w = getInPixelsAllocation().getType().getX();
+ int h = getInPixelsAllocation().getType().getY();
+ mScript.set_gWidth(w);
+ mScript.set_gHeight(h);
+ }
+
+ @Override
+ protected void runFilter() {
+ if (mParameters == null) {
+ return;
+ }
+ computeKernel();
+ mScript.set_gIn(getInPixelsAllocation());
+ mScript.bind_gPixels(getInPixelsAllocation());
+ mScript.forEach_root(getInPixelsAllocation(), getOutPixelsAllocation());
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
new file mode 100644
index 000000000..77250bd7a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.android.gallery3d.app.Log;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+/**
+ * An image filter which creates a tiny planet projection.
+ */
+public class ImageFilterTinyPlanet extends SimpleImageFilter {
+
+
+ private static final String LOGTAG = ImageFilterTinyPlanet.class.getSimpleName();
+ public static final String GOOGLE_PANO_NAMESPACE = "http://ns.google.com/photos/1.0/panorama/";
+ FilterTinyPlanetRepresentation mParameters = new FilterTinyPlanetRepresentation();
+
+ public static final String CROPPED_AREA_IMAGE_WIDTH_PIXELS =
+ "CroppedAreaImageWidthPixels";
+ public static final String CROPPED_AREA_IMAGE_HEIGHT_PIXELS =
+ "CroppedAreaImageHeightPixels";
+ public static final String CROPPED_AREA_FULL_PANO_WIDTH_PIXELS =
+ "FullPanoWidthPixels";
+ public static final String CROPPED_AREA_FULL_PANO_HEIGHT_PIXELS =
+ "FullPanoHeightPixels";
+ public static final String CROPPED_AREA_LEFT =
+ "CroppedAreaLeftPixels";
+ public static final String CROPPED_AREA_TOP =
+ "CroppedAreaTopPixels";
+
+ public ImageFilterTinyPlanet() {
+ mName = "TinyPlanet";
+ }
+
+ @Override
+ public void useRepresentation(FilterRepresentation representation) {
+ FilterTinyPlanetRepresentation parameters = (FilterTinyPlanetRepresentation) representation;
+ mParameters = parameters;
+ }
+
+ @Override
+ public FilterRepresentation getDefaultRepresentation() {
+ return new FilterTinyPlanetRepresentation();
+ }
+
+
+ native protected void nativeApplyFilter(
+ Bitmap bitmapIn, int width, int height, Bitmap bitmapOut, int outSize, float scale,
+ float angle);
+
+
+ @Override
+ public Bitmap apply(Bitmap bitmapIn, float scaleFactor, int quality) {
+ int w = bitmapIn.getWidth();
+ int h = bitmapIn.getHeight();
+ int outputSize = (int) (w / 2f);
+ ImagePreset preset = getEnvironment().getImagePreset();
+ Bitmap mBitmapOut = null;
+ if (preset != null) {
+ XMPMeta xmp = ImageLoader.getXmpObject(MasterImage.getImage().getActivity());
+ // Do nothing, just use bitmapIn as is if we don't have XMP.
+ if(xmp != null) {
+ bitmapIn = applyXmp(bitmapIn, xmp, w);
+ }
+ }
+ if (mBitmapOut != null) {
+ if (outputSize != mBitmapOut.getHeight()) {
+ mBitmapOut = null;
+ }
+ }
+ while (mBitmapOut == null) {
+ try {
+ mBitmapOut = getEnvironment().getBitmap(outputSize, outputSize);
+ } catch (java.lang.OutOfMemoryError e) {
+ System.gc();
+ outputSize /= 2;
+ Log.v(LOGTAG, "No memory to create Full Tiny Planet create half");
+ }
+ }
+ nativeApplyFilter(bitmapIn, bitmapIn.getWidth(), bitmapIn.getHeight(), mBitmapOut,
+ outputSize, mParameters.getZoom() / 100f, mParameters.getAngle());
+
+ return mBitmapOut;
+ }
+
+ private Bitmap applyXmp(Bitmap bitmapIn, XMPMeta xmp, int intermediateWidth) {
+ try {
+ int croppedAreaWidth =
+ getInt(xmp, CROPPED_AREA_IMAGE_WIDTH_PIXELS);
+ int croppedAreaHeight =
+ getInt(xmp, CROPPED_AREA_IMAGE_HEIGHT_PIXELS);
+ int fullPanoWidth =
+ getInt(xmp, CROPPED_AREA_FULL_PANO_WIDTH_PIXELS);
+ int fullPanoHeight =
+ getInt(xmp, CROPPED_AREA_FULL_PANO_HEIGHT_PIXELS);
+ int left = getInt(xmp, CROPPED_AREA_LEFT);
+ int top = getInt(xmp, CROPPED_AREA_TOP);
+
+ if (fullPanoWidth == 0 || fullPanoHeight == 0) {
+ return bitmapIn;
+ }
+ // Make sure the intermediate image has the similar size to the
+ // input.
+ Bitmap paddedBitmap = null;
+ float scale = intermediateWidth / (float) fullPanoWidth;
+ while (paddedBitmap == null) {
+ try {
+ paddedBitmap = Bitmap.createBitmap(
+ (int) (fullPanoWidth * scale), (int) (fullPanoHeight * scale),
+ Bitmap.Config.ARGB_8888);
+ } catch (java.lang.OutOfMemoryError e) {
+ System.gc();
+ scale /= 2;
+ }
+ }
+ Canvas paddedCanvas = new Canvas(paddedBitmap);
+
+ int right = left + croppedAreaWidth;
+ int bottom = top + croppedAreaHeight;
+ RectF destRect = new RectF(left * scale, top * scale, right * scale, bottom * scale);
+ paddedCanvas.drawBitmap(bitmapIn, null, destRect, null);
+ bitmapIn = paddedBitmap;
+ } catch (XMPException ex) {
+ // Do nothing, just use bitmapIn as is.
+ }
+ return bitmapIn;
+ }
+
+ private static int getInt(XMPMeta xmp, String key) throws XMPException {
+ if (xmp.doesPropertyExist(GOOGLE_PANO_NAMESPACE, key)) {
+ return xmp.getPropertyInteger(GOOGLE_PANO_NAMESPACE, key);
+ } else {
+ return 0;
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
new file mode 100644
index 000000000..86be9a155
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterVibrance extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "VIBRANCE";
+ public ImageFilterVibrance() {
+ mName = "Vibrance";
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterBasicRepresentation representation =
+ (FilterBasicRepresentation) super.getDefaultRepresentation();
+ representation.setName("Vibrance");
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setFilterClass(ImageFilterVibrance.class);
+ representation.setTextId(R.string.vibrance);
+ representation.setMinimum(-100);
+ representation.setMaximum(100);
+ representation.setDefaultValue(0);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (getParameters() == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ float value = getParameters().getValue();
+ nativeApplyFilter(bitmap, w, h, value);
+
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
new file mode 100644
index 000000000..7e0a452bf
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.pipeline.FilterEnvironment;
+
+public class ImageFilterVignette extends SimpleImageFilter {
+ private static final String LOGTAG = "ImageFilterVignette";
+ private Bitmap mOverlayBitmap;
+
+ public ImageFilterVignette() {
+ mName = "Vignette";
+ }
+
+ @Override
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterVignetteRepresentation representation = new FilterVignetteRepresentation();
+ return representation;
+ }
+
+ native protected void nativeApplyFilter(
+ Bitmap bitmap, int w, int h, int cx, int cy, float radx, float rady, float strength);
+
+ private float calcRadius(float cx, float cy, int w, int h) {
+ float d = cx;
+ if (d < (w - cx)) {
+ d = w - cx;
+ }
+ if (d < cy) {
+ d = cy;
+ }
+ if (d < (h - cy)) {
+ d = h - cy;
+ }
+ return d * d * 2.0f;
+ }
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ if (SIMPLE_ICONS && FilterEnvironment.QUALITY_ICON == quality) {
+ if (mOverlayBitmap == null) {
+ Resources res = getEnvironment().getPipeline().getResources();
+ mOverlayBitmap = IconUtilities.getFXBitmap(res,
+ R.drawable.filtershow_icon_vignette);
+ }
+ Canvas c = new Canvas(bitmap);
+ int dim = Math.max(bitmap.getWidth(), bitmap.getHeight());
+ Rect r = new Rect(0, 0, dim, dim);
+ c.drawBitmap(mOverlayBitmap, null, r, null);
+ return bitmap;
+ }
+ FilterVignetteRepresentation rep = (FilterVignetteRepresentation) getParameters();
+ if (rep == null) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ float value = rep.getValue() / 100.0f;
+ float cx = w / 2;
+ float cy = h / 2;
+ float r = calcRadius(cx, cy, w, h);
+ float rx = r;
+ float ry = r;
+ if (rep.isCenterSet()) {
+ Matrix m = getOriginalToScreenMatrix(w, h);
+ cx = rep.getCenterX();
+ cy = rep.getCenterY();
+ float[] center = new float[] { cx, cy };
+ m.mapPoints(center);
+ cx = center[0];
+ cy = center[1];
+ rx = m.mapRadius(rep.getRadiusX());
+ ry = m.mapRadius(rep.getRadiusY());
+ }
+ nativeApplyFilter(bitmap, w, h, (int) cx, (int) cy, rx, ry, value);
+ return bitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
new file mode 100644
index 000000000..6bb88ec21
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+
+import android.graphics.Bitmap;
+
+public class ImageFilterWBalance extends ImageFilter {
+ private static final String SERIALIZATION_NAME = "WBALANCE";
+ private static final String TAG = "ImageFilterWBalance";
+
+ public ImageFilterWBalance() {
+ mName = "WBalance";
+ }
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterRepresentation representation = new FilterDirectRepresentation("WBalance");
+ representation.setSerializationName(SERIALIZATION_NAME);
+ representation.setFilterClass(ImageFilterWBalance.class);
+ representation.setFilterType(FilterRepresentation.TYPE_WBALANCE);
+ representation.setTextId(R.string.wbalance);
+ representation.setShowParameterValue(false);
+ representation.setEditorId(ImageOnlyEditor.ID);
+ representation.setSupportsPartialRendering(true);
+ return representation;
+ }
+
+ @Override
+ public void useRepresentation(FilterRepresentation representation) {
+
+ }
+
+ native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, int locX, int locY);
+
+ @Override
+ public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ nativeApplyFilter(bitmap, w, h, -1, -1);
+ return bitmap;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java b/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java
new file mode 100644
index 000000000..a40d4fa3b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+import android.graphics.RectF;
+
+public class RedEyeCandidate implements FilterPoint {
+ RectF mRect = new RectF();
+ RectF mBounds = new RectF();
+
+ public RedEyeCandidate(RedEyeCandidate candidate) {
+ mRect.set(candidate.mRect);
+ mBounds.set(candidate.mBounds);
+ }
+
+ public RedEyeCandidate(RectF rect, RectF bounds) {
+ mRect.set(rect);
+ mBounds.set(bounds);
+ }
+
+ public boolean equals(RedEyeCandidate candidate) {
+ if (candidate.mRect.equals(mRect)
+ && candidate.mBounds.equals(mBounds)) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean intersect(RectF rect) {
+ return mRect.intersect(rect);
+ }
+
+ public RectF getRect() {
+ return mRect;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/SimpleImageFilter.java b/src/com/android/gallery3d/filtershow/filters/SimpleImageFilter.java
new file mode 100644
index 000000000..c891d20f3
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/SimpleImageFilter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.filters;
+
+public class SimpleImageFilter extends ImageFilter {
+
+ private FilterBasicRepresentation mParameters;
+
+ public FilterRepresentation getDefaultRepresentation() {
+ FilterRepresentation representation = new FilterBasicRepresentation("Default", 0, 50, 100);
+ representation.setShowParameterValue(true);
+ return representation;
+ }
+
+ public void useRepresentation(FilterRepresentation representation) {
+ FilterBasicRepresentation parameters = (FilterBasicRepresentation) representation;
+ mParameters = parameters;
+ }
+
+ public FilterBasicRepresentation getParameters() {
+ return mParameters;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/SplineMath.java b/src/com/android/gallery3d/filtershow/filters/SplineMath.java
new file mode 100644
index 000000000..5b12d0a61
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/SplineMath.java
@@ -0,0 +1,166 @@
+package com.android.gallery3d.filtershow.filters;
+
+
+public class SplineMath {
+ double[][] mPoints = new double[6][2];
+ double[] mDerivatives;
+ SplineMath(int n) {
+ mPoints = new double[n][2];
+ }
+
+ public void setPoint(int index, double x, double y) {
+ mPoints[index][0] = x;
+ mPoints[index][1] = y;
+ mDerivatives = null;
+ }
+
+ public float[][] calculatetCurve(int n) {
+ float[][] curve = new float[n][2];
+ double[][] points = new double[mPoints.length][2];
+ for (int i = 0; i < mPoints.length; i++) {
+
+ points[i][0] = mPoints[i][0];
+ points[i][1] = mPoints[i][1];
+
+ }
+ double[] derivatives = solveSystem(points);
+ float start = (float) points[0][0];
+ float end = (float) (points[points.length - 1][0]);
+
+ curve[0][0] = (float) (points[0][0]);
+ curve[0][1] = (float) (points[0][1]);
+ int last = curve.length - 1;
+ curve[last][0] = (float) (points[points.length - 1][0]);
+ curve[last][1] = (float) (points[points.length - 1][1]);
+
+ for (int i = 0; i < curve.length; i++) {
+
+ double[] cur = null;
+ double[] next = null;
+ double x = start + i * (end - start) / (curve.length - 1);
+ int pivot = 0;
+ for (int j = 0; j < points.length - 1; j++) {
+ if (x >= points[j][0] && x <= points[j + 1][0]) {
+ pivot = j;
+ }
+ }
+ cur = points[pivot];
+ next = points[pivot + 1];
+ if (x <= next[0]) {
+ double x1 = cur[0];
+ double x2 = next[0];
+ double y1 = cur[1];
+ double y2 = next[1];
+
+ // Use the second derivatives to apply the cubic spline
+ // equation:
+ double delta = (x2 - x1);
+ double delta2 = delta * delta;
+ double b = (x - x1) / delta;
+ double a = 1 - b;
+ double ta = a * y1;
+ double tb = b * y2;
+ double tc = (a * a * a - a) * derivatives[pivot];
+ double td = (b * b * b - b) * derivatives[pivot + 1];
+ double y = ta + tb + (delta2 / 6) * (tc + td);
+
+ curve[i][0] = (float) (x);
+ curve[i][1] = (float) (y);
+ } else {
+ curve[i][0] = (float) (next[0]);
+ curve[i][1] = (float) (next[1]);
+ }
+ }
+ return curve;
+ }
+
+ public double getValue(double x) {
+ double[] cur = null;
+ double[] next = null;
+ if (mDerivatives == null)
+ mDerivatives = solveSystem(mPoints);
+ int pivot = 0;
+ for (int j = 0; j < mPoints.length - 1; j++) {
+ pivot = j;
+ if (x <= mPoints[j][0]) {
+ break;
+ }
+ }
+ cur = mPoints[pivot];
+ next = mPoints[pivot + 1];
+ double x1 = cur[0];
+ double x2 = next[0];
+ double y1 = cur[1];
+ double y2 = next[1];
+
+ // Use the second derivatives to apply the cubic spline
+ // equation:
+ double delta = (x2 - x1);
+ double delta2 = delta * delta;
+ double b = (x - x1) / delta;
+ double a = 1 - b;
+ double ta = a * y1;
+ double tb = b * y2;
+ double tc = (a * a * a - a) * mDerivatives[pivot];
+ double td = (b * b * b - b) * mDerivatives[pivot + 1];
+ double y = ta + tb + (delta2 / 6) * (tc + td);
+
+ return y;
+
+ }
+
+ double[] solveSystem(double[][] points) {
+ int n = points.length;
+ double[][] system = new double[n][3];
+ double[] result = new double[n]; // d
+ double[] solution = new double[n]; // returned coefficients
+ system[0][1] = 1;
+ system[n - 1][1] = 1;
+ double d6 = 1.0 / 6.0;
+ double d3 = 1.0 / 3.0;
+
+ // let's create a tridiagonal matrix representing the
+ // system, and apply the TDMA algorithm to solve it
+ // (see http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
+ for (int i = 1; i < n - 1; i++) {
+ double deltaPrevX = points[i][0] - points[i - 1][0];
+ double deltaX = points[i + 1][0] - points[i - 1][0];
+ double deltaNextX = points[i + 1][0] - points[i][0];
+ double deltaNextY = points[i + 1][1] - points[i][1];
+ double deltaPrevY = points[i][1] - points[i - 1][1];
+ system[i][0] = d6 * deltaPrevX; // a_i
+ system[i][1] = d3 * deltaX; // b_i
+ system[i][2] = d6 * deltaNextX; // c_i
+ result[i] = (deltaNextY / deltaNextX) - (deltaPrevY / deltaPrevX); // d_i
+ }
+
+ // Forward sweep
+ for (int i = 1; i < n; i++) {
+ // m = a_i/b_i-1
+ double m = system[i][0] / system[i - 1][1];
+ // b_i = b_i - m(c_i-1)
+ system[i][1] = system[i][1] - m * system[i - 1][2];
+ // d_i = d_i - m(d_i-1)
+ result[i] = result[i] - m * result[i - 1];
+ }
+
+ // Back substitution
+ solution[n - 1] = result[n - 1] / system[n - 1][1];
+ for (int i = n - 2; i >= 0; --i) {
+ solution[i] = (result[i] - system[i][2] * solution[i + 1]) / system[i][1];
+ }
+ return solution;
+ }
+
+ public static void main(String[] args) {
+ SplineMath s = new SplineMath(10);
+ for (int i = 0; i < 10; i++) {
+ s.setPoint(i, i, i);
+ }
+ float[][] curve = s.calculatetCurve(40);
+
+ for (int j = 0; j < curve.length; j++) {
+ System.out.println(curve[j][0] + "," + curve[j][1]);
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/convolve3x3.rs b/src/com/android/gallery3d/filtershow/filters/convolve3x3.rs
new file mode 100644
index 000000000..2acffab06
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/convolve3x3.rs
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.gallery3d.filtershow.filters)
+#pragma rs_fp_relaxed
+
+int32_t gWidth;
+int32_t gHeight;
+const uchar4 *gPixels;
+rs_allocation gIn;
+
+float gCoeffs[9];
+
+void root(const uchar4 *in, uchar4 *out, const void *usrData, uint32_t x, uint32_t y) {
+ uint32_t x1 = min((int32_t)x+1, gWidth-1);
+ uint32_t x2 = max((int32_t)x-1, 0);
+ uint32_t y1 = min((int32_t)y+1, gHeight-1);
+ uint32_t y2 = max((int32_t)y-1, 0);
+
+ float4 p00 = rsUnpackColor8888(gPixels[x1 + gWidth * y1]);
+ float4 p01 = rsUnpackColor8888(gPixels[x + gWidth * y1]);
+ float4 p02 = rsUnpackColor8888(gPixels[x2 + gWidth * y1]);
+ float4 p10 = rsUnpackColor8888(gPixels[x1 + gWidth * y]);
+ float4 p11 = rsUnpackColor8888(gPixels[x + gWidth * y]);
+ float4 p12 = rsUnpackColor8888(gPixels[x2 + gWidth * y]);
+ float4 p20 = rsUnpackColor8888(gPixels[x1 + gWidth * y2]);
+ float4 p21 = rsUnpackColor8888(gPixels[x + gWidth * y2]);
+ float4 p22 = rsUnpackColor8888(gPixels[x2 + gWidth * y2]);
+
+ p00 *= gCoeffs[0];
+ p01 *= gCoeffs[1];
+ p02 *= gCoeffs[2];
+ p10 *= gCoeffs[3];
+ p11 *= gCoeffs[4];
+ p12 *= gCoeffs[5];
+ p20 *= gCoeffs[6];
+ p21 *= gCoeffs[7];
+ p22 *= gCoeffs[8];
+
+ p00 += p01;
+ p02 += p10;
+ p11 += p12;
+ p20 += p21;
+
+ p22 += p00;
+ p02 += p11;
+
+ p20 += p22;
+ p20 += p02;
+
+ p20 = clamp(p20, 0.f, 1.f);
+ *out = rsPackColorTo8888(p20.r, p20.g, p20.b);
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/grad.rs b/src/com/android/gallery3d/filtershow/filters/grad.rs
new file mode 100644
index 000000000..ddbafd349
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/grad.rs
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2012 Unknown
+ *
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.gallery3d.filtershow.filters)
+
+#define MAX_POINTS 16
+
+uint32_t inputWidth;
+uint32_t inputHeight;
+static const float Rf = 0.2999f;
+static const float Gf = 0.587f;
+static const float Bf = 0.114f;
+//static const float size_scale = 0.01f;
+
+typedef struct {
+ rs_matrix3x3 colorMatrix;
+ float rgbOff;
+ float dx;
+ float dy;
+ float off;
+} UPointData;
+int mNumberOfLines;
+// input data
+bool mask[MAX_POINTS];
+int xPos1[MAX_POINTS];
+int yPos1[MAX_POINTS];
+int xPos2[MAX_POINTS];
+int yPos2[MAX_POINTS];
+int size[MAX_POINTS];
+int brightness[MAX_POINTS];
+int contrast[MAX_POINTS];
+int saturation[MAX_POINTS];
+
+// generated data
+static UPointData grads[MAX_POINTS];
+
+void setupGradParams() {
+ int k = 0;
+ for (int i = 0; i < MAX_POINTS; i++) {
+ if (!mask[i]) {
+ continue;
+ }
+ float x1 = xPos1[i];
+ float y1 = yPos1[i];
+ float x2 = xPos2[i];
+ float y2 = yPos2[i];
+
+ float denom = (y2 * y2 - 2 * y1 * y2 + x2 * x2 - 2 * x1 * x2 + y1 * y1 + x1 * x1);
+ if (denom == 0) {
+ continue;
+ }
+ grads[k].dy = (y1 - y2) / denom;
+ grads[k].dx = (x1 - x2) / denom;
+ grads[k].off = (y2 * y2 + x2 * x2 - x1 * x2 - y1 * y2) / denom;
+
+ float S = 1+saturation[i]/100.f;
+ float MS = 1-S;
+ float Rt = Rf * MS;
+ float Gt = Gf * MS;
+ float Bt = Bf * MS;
+
+ float b = 1+brightness[i]/100.f;
+ float c = 1+contrast[i]/100.f;
+ b *= c;
+ grads[k].rgbOff = .5f - c/2.f;
+ rsMatrixSet(&grads[i].colorMatrix, 0, 0, b * (Rt + S));
+ rsMatrixSet(&grads[i].colorMatrix, 1, 0, b * Gt);
+ rsMatrixSet(&grads[i].colorMatrix, 2, 0, b * Bt);
+ rsMatrixSet(&grads[i].colorMatrix, 0, 1, b * Rt);
+ rsMatrixSet(&grads[i].colorMatrix, 1, 1, b * (Gt + S));
+ rsMatrixSet(&grads[i].colorMatrix, 2, 1, b * Bt);
+ rsMatrixSet(&grads[i].colorMatrix, 0, 2, b * Rt);
+ rsMatrixSet(&grads[i].colorMatrix, 1, 2, b * Gt);
+ rsMatrixSet(&grads[i].colorMatrix, 2, 2, b * (Bt + S));
+
+ k++;
+ }
+ mNumberOfLines = k;
+}
+
+void init() {
+
+}
+
+uchar4 __attribute__((kernel)) selectiveAdjust(const uchar4 in, uint32_t x,
+ uint32_t y) {
+ float4 pixel = rsUnpackColor8888(in);
+
+ float4 wsum = pixel;
+ wsum.a = 0.f;
+ for (int i = 0; i < mNumberOfLines; i++) {
+ UPointData* grad = &grads[i];
+ float t = clamp(x*grad->dx+y*grad->dy+grad->off,0.f,1.0f);
+ wsum.xyz = wsum.xyz*(1-t)+
+ t*(rsMatrixMultiply(&grad->colorMatrix ,wsum.xyz)+grad->rgbOff);
+
+ }
+
+ pixel.rgb = wsum.rgb;
+ pixel.a = 1.0f;
+
+ uchar4 out = rsPackColorTo8888(clamp(pixel, 0.f, 1.0f));
+ return out;
+}
+
+
+
diff --git a/src/com/android/gallery3d/filtershow/filters/grey.rs b/src/com/android/gallery3d/filtershow/filters/grey.rs
new file mode 100644
index 000000000..e01880360
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/grey.rs
@@ -0,0 +1,22 @@
+ /*
+ * Copyright (C) 2013 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.gallery3d.filtershow.filters)
+
+uchar __attribute__((kernel)) RGBAtoA(uchar4 in) {
+ return in.r;
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/saturation.rs b/src/com/android/gallery3d/filtershow/filters/saturation.rs
new file mode 100644
index 000000000..5210e34a3
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/saturation.rs
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2012 Unknown
+ *
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.gallery3d.filtershow.filters)
+
+#define MAX_CHANELS 7
+#define MAX_HUE 4096
+static const int ABITS = 4;
+static const int HSCALE = 256;
+static const int k1=255 << ABITS;
+static const int k2=HSCALE << ABITS;
+
+static const float Rf = 0.2999f;
+static const float Gf = 0.587f;
+static const float Bf = 0.114f;
+
+rs_matrix3x3 colorMatrix_min;
+rs_matrix3x3 colorMatrix_max;
+
+int mNumberOfLines;
+// input data
+int saturation[MAX_CHANELS];
+float sat[MAX_CHANELS];
+
+float satLut[MAX_HUE];
+// generated data
+
+
+void setupGradParams() {
+
+ int master = saturation[0];
+ int max = master+saturation[1];
+ int min = max;
+
+ // calculate the minimum and maximum saturation
+ for (int i = 1; i < MAX_CHANELS; i++) {
+ int v = master+saturation[i];
+ if (max < v) {
+ max = v;
+ }
+ else if (min > v) {
+ min = v;
+ }
+ }
+ // generate a lookup table for all hue 0 to 4K which goes from 0 to 1 0=min sat 1 = max sat
+ min = min - 1;
+ for(int i = 0; i < MAX_HUE ; i++) {
+ float p = i * 6 / (float)MAX_HUE;
+ int ip = ((int)(p + .5f)) % 6;
+ int v = master + saturation[ip + 1];
+ satLut[i] = (v - min)/(float)(max - min);
+ }
+
+ float S = 1 + max / 100.f;
+ float MS = 1 - S;
+ float Rt = Rf * MS;
+ float Gt = Gf * MS;
+ float Bt = Bf * MS;
+ float b = 1.f;
+
+ // Generate 2 color matrix one at min sat and one at max
+ rsMatrixSet(&colorMatrix_max, 0, 0, b * (Rt + S));
+ rsMatrixSet(&colorMatrix_max, 1, 0, b * Gt);
+ rsMatrixSet(&colorMatrix_max, 2, 0, b * Bt);
+ rsMatrixSet(&colorMatrix_max, 0, 1, b * Rt);
+ rsMatrixSet(&colorMatrix_max, 1, 1, b * (Gt + S));
+ rsMatrixSet(&colorMatrix_max, 2, 1, b * Bt);
+ rsMatrixSet(&colorMatrix_max, 0, 2, b * Rt);
+ rsMatrixSet(&colorMatrix_max, 1, 2, b * Gt);
+ rsMatrixSet(&colorMatrix_max, 2, 2, b * (Bt + S));
+
+ S = 1 + min / 100.f;
+ MS = 1-S;
+ Rt = Rf * MS;
+ Gt = Gf * MS;
+ Bt = Bf * MS;
+ b = 1;
+
+ rsMatrixSet(&colorMatrix_min, 0, 0, b * (Rt + S));
+ rsMatrixSet(&colorMatrix_min, 1, 0, b * Gt);
+ rsMatrixSet(&colorMatrix_min, 2, 0, b * Bt);
+ rsMatrixSet(&colorMatrix_min, 0, 1, b * Rt);
+ rsMatrixSet(&colorMatrix_min, 1, 1, b * (Gt + S));
+ rsMatrixSet(&colorMatrix_min, 2, 1, b * Bt);
+ rsMatrixSet(&colorMatrix_min, 0, 2, b * Rt);
+ rsMatrixSet(&colorMatrix_min, 1, 2, b * Gt);
+ rsMatrixSet(&colorMatrix_min, 2, 2, b * (Bt + S));
+}
+
+static ushort rgb2hue( uchar4 rgb)
+{
+ int iMin,iMax,chroma;
+
+ int ri = rgb.r;
+ int gi = rgb.g;
+ int bi = rgb.b;
+ short rv,rs,rh;
+
+ if (ri > gi) {
+ iMax = max (ri, bi);
+ iMin = min (gi, bi);
+ } else {
+ iMax = max (gi, bi);
+ iMin = min (ri, bi);
+ }
+
+ rv = (short) (iMax << ABITS);
+
+ if (rv == 0) {
+ return 0;
+ }
+
+ chroma = iMax - iMin;
+ rs = (short) ((k1 * chroma) / iMax);
+ if (rs == 0) {
+ return 0;
+ }
+
+ if ( ri == iMax ) {
+ rh = (short) ((k2 * (6 * chroma + gi - bi))/(6 * chroma));
+ if (rh >= k2) {
+ rh -= k2;
+ }
+ return rh;
+ }
+
+ if (gi == iMax) {
+ return(short) ((k2 * (2 * chroma + bi - ri)) / (6 * chroma));
+ }
+
+ return (short) ((k2 * (4 * chroma + ri - gi)) / (6 * chroma));
+}
+
+uchar4 __attribute__((kernel)) selectiveAdjust(const uchar4 in, uint32_t x,
+ uint32_t y) {
+ float4 pixel = rsUnpackColor8888(in);
+
+ float4 wsum = pixel;
+ int hue = rgb2hue(in);
+
+ float t = satLut[hue];
+ pixel.xyz = rsMatrixMultiply(&colorMatrix_min ,pixel.xyz) * (1 - t) +
+ t * (rsMatrixMultiply(&colorMatrix_max ,pixel.xyz));
+
+ pixel.a = 1.0f;
+ return rsPackColorTo8888(clamp(pixel, 0.f, 1.0f));
+} \ No newline at end of file
diff --git a/src/com/android/gallery3d/filtershow/history/HistoryItem.java b/src/com/android/gallery3d/filtershow/history/HistoryItem.java
new file mode 100644
index 000000000..2baaac327
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/history/HistoryItem.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 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.history;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+public class HistoryItem {
+ private static final String LOGTAG = "HistoryItem";
+ private ImagePreset mImagePreset;
+ private FilterRepresentation mFilterRepresentation;
+ private Bitmap mPreviewImage;
+
+ public HistoryItem(ImagePreset preset, FilterRepresentation representation) {
+ mImagePreset = new ImagePreset(preset);
+ if (representation != null) {
+ mFilterRepresentation = representation.copy();
+ }
+ }
+
+ public ImagePreset getImagePreset() {
+ return mImagePreset;
+ }
+
+ public FilterRepresentation getFilterRepresentation() {
+ return mFilterRepresentation;
+ }
+
+ public Bitmap getPreviewImage() {
+ return mPreviewImage;
+ }
+
+ public void setPreviewImage(Bitmap previewImage) {
+ mPreviewImage = previewImage;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/history/HistoryManager.java b/src/com/android/gallery3d/filtershow/history/HistoryManager.java
new file mode 100644
index 000000000..755e2ea58
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/history/HistoryManager.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2013 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.history;
+
+import android.graphics.drawable.Drawable;
+import android.view.MenuItem;
+
+import java.util.Vector;
+
+public class HistoryManager {
+ private static final String LOGTAG = "HistoryManager";
+
+ private Vector<HistoryItem> mHistoryItems = new Vector<HistoryItem>();
+ private int mCurrentPresetPosition = 0;
+ private MenuItem mUndoMenuItem = null;
+ private MenuItem mRedoMenuItem = null;
+ private MenuItem mResetMenuItem = null;
+
+ public void setMenuItems(MenuItem undoItem, MenuItem redoItem, MenuItem resetItem) {
+ mUndoMenuItem = undoItem;
+ mRedoMenuItem = redoItem;
+ mResetMenuItem = resetItem;
+ updateMenuItems();
+ }
+
+ private int getCount() {
+ return mHistoryItems.size();
+ }
+
+ public HistoryItem getItem(int position) {
+ return mHistoryItems.elementAt(position);
+ }
+
+ private void clear() {
+ mHistoryItems.clear();
+ }
+
+ private void add(HistoryItem item) {
+ mHistoryItems.add(item);
+ }
+
+ private void notifyDataSetChanged() {
+ // TODO
+ }
+
+ public boolean canReset() {
+ if (getCount() <= 1) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean canUndo() {
+ if (mCurrentPresetPosition == getCount() - 1) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean canRedo() {
+ if (mCurrentPresetPosition == 0) {
+ return false;
+ }
+ return true;
+ }
+
+ public void updateMenuItems() {
+ if (mUndoMenuItem != null) {
+ setEnabled(mUndoMenuItem, canUndo());
+ }
+ if (mRedoMenuItem != null) {
+ setEnabled(mRedoMenuItem, canRedo());
+ }
+ if (mResetMenuItem != null) {
+ setEnabled(mResetMenuItem, canReset());
+ }
+ }
+
+ private void setEnabled(MenuItem item, boolean enabled) {
+ item.setEnabled(enabled);
+ Drawable drawable = item.getIcon();
+ if (drawable != null) {
+ drawable.setAlpha(enabled ? 255 : 80);
+ }
+ }
+
+ public void setCurrentPreset(int n) {
+ mCurrentPresetPosition = n;
+ updateMenuItems();
+ notifyDataSetChanged();
+ }
+
+ public void reset() {
+ if (getCount() == 0) {
+ return;
+ }
+ HistoryItem first = getItem(getCount() - 1);
+ clear();
+ addHistoryItem(first);
+ updateMenuItems();
+ }
+
+ public HistoryItem getLast() {
+ if (getCount() == 0) {
+ return null;
+ }
+ return getItem(0);
+ }
+
+ public HistoryItem getCurrent() {
+ return getItem(mCurrentPresetPosition);
+ }
+
+ public void addHistoryItem(HistoryItem preset) {
+ insert(preset, 0);
+ updateMenuItems();
+ }
+
+ private void insert(HistoryItem preset, int position) {
+ if (mCurrentPresetPosition != 0) {
+ // in this case, let's discount the presets before the current one
+ Vector<HistoryItem> oldItems = new Vector<HistoryItem>();
+ 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;
+ notifyDataSetChanged();
+ }
+ mHistoryItems.insertElementAt(preset, position);
+ mCurrentPresetPosition = position;
+ notifyDataSetChanged();
+ }
+
+ public int redo() {
+ mCurrentPresetPosition--;
+ if (mCurrentPresetPosition < 0) {
+ mCurrentPresetPosition = 0;
+ }
+ notifyDataSetChanged();
+ updateMenuItems();
+ return mCurrentPresetPosition;
+ }
+
+ public int undo() {
+ mCurrentPresetPosition++;
+ if (mCurrentPresetPosition >= getCount()) {
+ mCurrentPresetPosition = getCount() - 1;
+ }
+ notifyDataSetChanged();
+ updateMenuItems();
+ return mCurrentPresetPosition;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ControlPoint.java b/src/com/android/gallery3d/filtershow/imageshow/ControlPoint.java
new file mode 100644
index 000000000..aaec728a6
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ControlPoint.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+public class ControlPoint implements Comparable {
+ public float x;
+ public float y;
+
+ public ControlPoint(float px, float py) {
+ x = px;
+ y = py;
+ }
+
+ public ControlPoint(ControlPoint point) {
+ x = point.x;
+ y = point.y;
+ }
+
+ public boolean sameValues(ControlPoint other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null) {
+ return false;
+ }
+
+ if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) {
+ return false;
+ }
+ if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y)) {
+ return false;
+ }
+ return true;
+ }
+
+ public ControlPoint copy() {
+ return new ControlPoint(x, y);
+ }
+
+ @Override
+ public int compareTo(Object another) {
+ ControlPoint p = (ControlPoint) another;
+ if (p.x < x) {
+ return 1;
+ } else if (p.x > x) {
+ return -1;
+ }
+ return 0;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java b/src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java
new file mode 100644
index 000000000..8ceb37599
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.RectF;
+import android.graphics.Shader;
+
+import com.android.gallery3d.R;
+
+public class EclipseControl {
+ private float mCenterX = Float.NaN;
+ private float mCenterY = 0;
+ private float mRadiusX = 200;
+ private float mRadiusY = 300;
+ private static int MIN_TOUCH_DIST = 80;// should be a resource & in dips
+
+ private float[] handlex = new float[9];
+ private float[] handley = new float[9];
+ private int mSliderColor;
+ private int mCenterDotSize = 40;
+ private float mDownX;
+ private float mDownY;
+ private float mDownCenterX;
+ private float mDownCenterY;
+ private float mDownRadiusX;
+ private float mDownRadiusY;
+ private Matrix mScrToImg;
+
+ private boolean mShowReshapeHandles = true;
+ public final static int HAN_CENTER = 0;
+ public final static int HAN_NORTH = 7;
+ public final static int HAN_NE = 8;
+ public final static int HAN_EAST = 1;
+ public final static int HAN_SE = 2;
+ public final static int HAN_SOUTH = 3;
+ public final static int HAN_SW = 4;
+ public final static int HAN_WEST = 5;
+ public final static int HAN_NW = 6;
+
+ public EclipseControl(Context context) {
+ mSliderColor = Color.WHITE;
+ }
+
+ public void setRadius(float x, float y) {
+ mRadiusX = x;
+ mRadiusY = y;
+ }
+
+ public void setCenter(float x, float y) {
+ mCenterX = x;
+ mCenterY = y;
+ }
+
+ public int getCloseHandle(float x, float y) {
+ float min = Float.MAX_VALUE;
+ int handle = -1;
+ for (int i = 0; i < handlex.length; i++) {
+ float dx = handlex[i] - x;
+ float dy = handley[i] - y;
+ float dist = dx * dx + dy * dy;
+ if (dist < min) {
+ min = dist;
+ handle = i;
+ }
+ }
+
+ if (min < MIN_TOUCH_DIST * MIN_TOUCH_DIST) {
+ return handle;
+ }
+ for (int i = 0; i < handlex.length; i++) {
+ float dx = handlex[i] - x;
+ float dy = handley[i] - y;
+ float dist = (float) Math.sqrt(dx * dx + dy * dy);
+ }
+
+ return -1;
+ }
+
+ public void setScrToImageMatrix(Matrix scrToImg) {
+ mScrToImg = scrToImg;
+ }
+
+ public void actionDown(float x, float y, Oval oval) {
+ float[] point = new float[] {
+ x, y };
+ mScrToImg.mapPoints(point);
+ mDownX = point[0];
+ mDownY = point[1];
+ mDownCenterX = oval.getCenterX();
+ mDownCenterY = oval.getCenterY();
+ mDownRadiusX = oval.getRadiusX();
+ mDownRadiusY = oval.getRadiusY();
+ }
+
+ public void actionMove(int handle, float x, float y, Oval oval) {
+ float[] point = new float[] {
+ x, y };
+ mScrToImg.mapPoints(point);
+ x = point[0];
+ y = point[1];
+
+ // Test if the matrix is swapping x and y
+ point[0] = 0;
+ point[1] = 1;
+ mScrToImg.mapVectors(point);
+ boolean swapxy = (point[0] > 0.0f);
+
+ int sign = 1;
+ switch (handle) {
+ case HAN_CENTER:
+ float ctrdx = mDownX - mDownCenterX;
+ float ctrdy = mDownY - mDownCenterY;
+ oval.setCenter(x - ctrdx, y - ctrdy);
+ // setRepresentation(mVignetteRep);
+ break;
+ case HAN_NORTH:
+ sign = -1;
+ case HAN_SOUTH:
+ if (swapxy) {
+ float raddx = mDownRadiusY - Math.abs(mDownX - mDownCenterY);
+ oval.setRadiusY(Math.abs(x - oval.getCenterY() + sign * raddx));
+ } else {
+ float raddy = mDownRadiusY - Math.abs(mDownY - mDownCenterY);
+ oval.setRadiusY(Math.abs(y - oval.getCenterY() + sign * raddy));
+ }
+ break;
+ case HAN_EAST:
+ sign = -1;
+ case HAN_WEST:
+ if (swapxy) {
+ float raddy = mDownRadiusX - Math.abs(mDownY - mDownCenterX);
+ oval.setRadiusX(Math.abs(y - oval.getCenterX() + sign * raddy));
+ } else {
+ float raddx = mDownRadiusX - Math.abs(mDownX - mDownCenterX);
+ oval.setRadiusX(Math.abs(x - oval.getCenterX() - sign * raddx));
+ }
+ break;
+ case HAN_SE:
+ case HAN_NE:
+ case HAN_SW:
+ case HAN_NW:
+ float sin45 = (float) Math.sin(45);
+ float dr = (mDownRadiusX + mDownRadiusY) * sin45;
+ float ctr_dx = mDownX - mDownCenterX;
+ float ctr_dy = mDownY - mDownCenterY;
+ float downRad = Math.abs(ctr_dx) + Math.abs(ctr_dy) - dr;
+ float rx = oval.getRadiusX();
+ float ry = oval.getRadiusY();
+ float r = (Math.abs(rx) + Math.abs(ry)) * sin45;
+ float dx = x - oval.getCenterX();
+ float dy = y - oval.getCenterY();
+ float nr = Math.abs(Math.abs(dx) + Math.abs(dy) - downRad);
+ oval.setRadius(rx * nr / r, ry * nr / r);
+
+ break;
+ }
+ }
+
+ public void paintGrayPoint(Canvas canvas, float x, float y) {
+ if (x == Float.NaN) {
+ return;
+ }
+
+ Paint paint = new Paint();
+
+ paint.setStyle(Paint.Style.FILL);
+ paint.setColor(Color.BLUE);
+ int[] colors3 = new int[] {
+ Color.GRAY, Color.LTGRAY, 0x66000000, 0 };
+ RadialGradient g = new RadialGradient(x, y, mCenterDotSize, colors3, new float[] {
+ 0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+ paint.setShader(g);
+ canvas.drawCircle(x, y, mCenterDotSize, paint);
+ }
+
+ public void paintPoint(Canvas canvas, float x, float y) {
+ if (x == Float.NaN) {
+ return;
+ }
+
+ Paint paint = new Paint();
+
+ paint.setStyle(Paint.Style.FILL);
+ paint.setColor(Color.BLUE);
+ int[] colors3 = new int[] {
+ mSliderColor, mSliderColor, 0x66000000, 0 };
+ RadialGradient g = new RadialGradient(x, y, mCenterDotSize, colors3, new float[] {
+ 0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+ paint.setShader(g);
+ canvas.drawCircle(x, y, mCenterDotSize, paint);
+ }
+
+ void paintRadius(Canvas canvas, float cx, float cy, float rx, float ry) {
+ if (cx == Float.NaN) {
+ return;
+ }
+ int mSliderColor = 0xFF33B5E5;
+ Paint paint = new Paint();
+ RectF rect = new RectF(cx - rx, cy - ry, cx + rx, cy + ry);
+ paint.setAntiAlias(true);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeWidth(6);
+ paint.setColor(Color.BLACK);
+ paintOvallines(canvas, rect, paint, cx, cy, rx, ry);
+
+ paint.setStrokeWidth(3);
+ paint.setColor(Color.WHITE);
+ paintOvallines(canvas, rect, paint, cx, cy, rx, ry);
+ }
+
+ public void paintOvallines(
+ Canvas canvas, RectF rect, Paint paint, float cx, float cy, float rx, float ry) {
+ canvas.drawOval(rect, paint);
+ float da = 4;
+ float arclen = da + da;
+ if (mShowReshapeHandles) {
+ paint.setStyle(Paint.Style.STROKE);
+
+ for (int i = 0; i < 361; i += 90) {
+ float dx = rx + 10;
+ float dy = ry + 10;
+ rect.left = cx - dx;
+ rect.top = cy - dy;
+ rect.right = cx + dx;
+ rect.bottom = cy + dy;
+ canvas.drawArc(rect, i - da, arclen, false, paint);
+ dx = rx - 10;
+ dy = ry - 10;
+ rect.left = cx - dx;
+ rect.top = cy - dy;
+ rect.right = cx + dx;
+ rect.bottom = cy + dy;
+ canvas.drawArc(rect, i - da, arclen, false, paint);
+ }
+ }
+ da *= 2;
+ paint.setStyle(Paint.Style.FILL);
+
+ for (int i = 45; i < 361; i += 90) {
+ double angle = Math.PI * i / 180.;
+ float x = cx + (float) (rx * Math.cos(angle));
+ float y = cy + (float) (ry * Math.sin(angle));
+ canvas.drawRect(x - da, y - da, x + da, y + da, paint);
+ }
+ paint.setStyle(Paint.Style.STROKE);
+ rect.left = cx - rx;
+ rect.top = cy - ry;
+ rect.right = cx + rx;
+ rect.bottom = cy + ry;
+ }
+
+ public void fillHandles(Canvas canvas, float cx, float cy, float rx, float ry) {
+ handlex[0] = cx;
+ handley[0] = cy;
+ int k = 1;
+
+ for (int i = 0; i < 360; i += 45) {
+ double angle = Math.PI * i / 180.;
+
+ float x = cx + (float) (rx * Math.cos(angle));
+ float y = cy + (float) (ry * Math.sin(angle));
+ handlex[k] = x;
+ handley[k] = y;
+
+ k++;
+ }
+ }
+
+ public void draw(Canvas canvas) {
+ paintRadius(canvas, mCenterX, mCenterY, mRadiusX, mRadiusY);
+ fillHandles(canvas, mCenterX, mCenterY, mRadiusX, mRadiusY);
+ paintPoint(canvas, mCenterX, mCenterY);
+ }
+
+ public boolean isUndefined() {
+ return Float.isNaN(mCenterX);
+ }
+
+ public void setShowReshapeHandles(boolean showReshapeHandles) {
+ this.mShowReshapeHandles = showReshapeHandles;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMathUtils.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMathUtils.java
new file mode 100644
index 000000000..81394f142
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMathUtils.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation.Mirror;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation.Rotation;
+import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+public final class GeometryMathUtils {
+ private GeometryMathUtils() {};
+
+ // Holder class for Geometry data.
+ public static final class GeometryHolder {
+ public Rotation rotation = FilterRotateRepresentation.getNil();
+ public float straighten = FilterStraightenRepresentation.getNil();
+ public RectF crop = FilterCropRepresentation.getNil();
+ public Mirror mirror = FilterMirrorRepresentation.getNil();
+
+ public void set(GeometryHolder h) {
+ rotation = h.rotation;
+ straighten = h.straighten;
+ crop.set(h.crop);
+ mirror = h.mirror;
+ }
+
+ public void wipe() {
+ rotation = FilterRotateRepresentation.getNil();
+ straighten = FilterStraightenRepresentation.getNil();
+ crop = FilterCropRepresentation.getNil();
+ mirror = FilterMirrorRepresentation.getNil();
+ }
+
+ public boolean isNil() {
+ return rotation == FilterRotateRepresentation.getNil() &&
+ straighten == FilterStraightenRepresentation.getNil() &&
+ crop.equals(FilterCropRepresentation.getNil()) &&
+ mirror == FilterMirrorRepresentation.getNil();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof GeometryHolder)) {
+ return false;
+ }
+ GeometryHolder h = (GeometryHolder) o;
+ return rotation == h.rotation && straighten == h.straighten &&
+ ((crop == null && h.crop == null) || (crop != null && crop.equals(h.crop))) &&
+ mirror == h.mirror;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[" + "rotation:" + rotation.value()
+ + ",straighten:" + straighten + ",crop:" + crop.toString()
+ + ",mirror:" + mirror.value() + "]";
+ }
+ }
+
+ // Math operations for 2d vectors
+ public static float clamp(float i, float low, float high) {
+ return Math.max(Math.min(i, high), low);
+ }
+
+ public static float[] lineIntersect(float[] line1, float[] line2) {
+ float a0 = line1[0];
+ float a1 = line1[1];
+ float b0 = line1[2];
+ float b1 = line1[3];
+ float c0 = line2[0];
+ float c1 = line2[1];
+ float d0 = line2[2];
+ float d1 = line2[3];
+ float t0 = a0 - b0;
+ float t1 = a1 - b1;
+ float t2 = b0 - d0;
+ float t3 = d1 - b1;
+ float t4 = c0 - d0;
+ float t5 = c1 - d1;
+
+ float denom = t1 * t4 - t0 * t5;
+ if (denom == 0)
+ return null;
+ float u = (t3 * t4 + t5 * t2) / denom;
+ float[] intersect = {
+ b0 + u * t0, b1 + u * t1
+ };
+ return intersect;
+ }
+
+ public static float[] shortestVectorFromPointToLine(float[] point, float[] line) {
+ float x1 = line[0];
+ float x2 = line[2];
+ float y1 = line[1];
+ float y2 = line[3];
+ float xdelt = x2 - x1;
+ float ydelt = y2 - y1;
+ if (xdelt == 0 && ydelt == 0)
+ return null;
+ float u = ((point[0] - x1) * xdelt + (point[1] - y1) * ydelt)
+ / (xdelt * xdelt + ydelt * ydelt);
+ float[] ret = {
+ (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1))
+ };
+ float[] vec = {
+ ret[0] - point[0], ret[1] - point[1]
+ };
+ return vec;
+ }
+
+ // A . B
+ public static float dotProduct(float[] a, float[] b) {
+ return a[0] * b[0] + a[1] * b[1];
+ }
+
+ public static float[] normalize(float[] a) {
+ float length = (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]);
+ float[] b = {
+ a[0] / length, a[1] / length
+ };
+ return b;
+ }
+
+ // A onto B
+ public static float scalarProjection(float[] a, float[] b) {
+ float length = (float) Math.sqrt(b[0] * b[0] + b[1] * b[1]);
+ return dotProduct(a, b) / length;
+ }
+
+ public static float[] getVectorFromPoints(float[] point1, float[] point2) {
+ float[] p = {
+ point2[0] - point1[0], point2[1] - point1[1]
+ };
+ return p;
+ }
+
+ public static float[] getUnitVectorFromPoints(float[] point1, float[] point2) {
+ float[] p = {
+ point2[0] - point1[0], point2[1] - point1[1]
+ };
+ float length = (float) Math.sqrt(p[0] * p[0] + p[1] * p[1]);
+ p[0] = p[0] / length;
+ p[1] = p[1] / length;
+ return p;
+ }
+
+ public static void scaleRect(RectF r, float scale) {
+ r.set(r.left * scale, r.top * scale, r.right * scale, r.bottom * scale);
+ }
+
+ // A - B
+ public static float[] vectorSubtract(float[] a, float[] b) {
+ int len = a.length;
+ if (len != b.length)
+ return null;
+ float[] ret = new float[len];
+ for (int i = 0; i < len; i++) {
+ ret[i] = a[i] - b[i];
+ }
+ return ret;
+ }
+
+ public static float vectorLength(float[] a) {
+ return (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]);
+ }
+
+ public static float scale(float oldWidth, float oldHeight, float newWidth, float newHeight) {
+ if (oldHeight == 0 || oldWidth == 0 || (oldWidth == newWidth && oldHeight == newHeight)) {
+ return 1;
+ }
+ return Math.min(newWidth / oldWidth, newHeight / oldHeight);
+ }
+
+ public static Rect roundNearest(RectF r) {
+ Rect q = new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right),
+ Math.round(r.bottom));
+ return q;
+ }
+
+ private static void concatMirrorMatrix(Matrix m, Mirror type) {
+ if (type == Mirror.HORIZONTAL) {
+ m.postScale(-1, 1);
+ } else if (type == Mirror.VERTICAL) {
+ m.postScale(1, -1);
+ } else if (type == Mirror.BOTH) {
+ m.postScale(1, -1);
+ m.postScale(-1, 1);
+ }
+ }
+
+ private static int getRotationForOrientation(int orientation) {
+ switch (orientation) {
+ case ImageLoader.ORI_ROTATE_90:
+ return 90;
+ case ImageLoader.ORI_ROTATE_180:
+ return 180;
+ case ImageLoader.ORI_ROTATE_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ public static GeometryHolder unpackGeometry(Collection<FilterRepresentation> geometry) {
+ GeometryHolder holder = new GeometryHolder();
+ unpackGeometry(holder, geometry);
+ return holder;
+ }
+
+ public static void unpackGeometry(GeometryHolder out,
+ Collection<FilterRepresentation> geometry) {
+ out.wipe();
+ // Get geometry data from filters
+ for (FilterRepresentation r : geometry) {
+ if (r.isNil()) {
+ continue;
+ }
+ if (r.getSerializationName() == FilterRotateRepresentation.SERIALIZATION_NAME) {
+ out.rotation = ((FilterRotateRepresentation) r).getRotation();
+ } else if (r.getSerializationName() ==
+ FilterStraightenRepresentation.SERIALIZATION_NAME) {
+ out.straighten = ((FilterStraightenRepresentation) r).getStraighten();
+ } else if (r.getSerializationName() == FilterCropRepresentation.SERIALIZATION_NAME) {
+ ((FilterCropRepresentation) r).getCrop(out.crop);
+ } else if (r.getSerializationName() == FilterMirrorRepresentation.SERIALIZATION_NAME) {
+ out.mirror = ((FilterMirrorRepresentation) r).getMirror();
+ }
+ }
+ }
+
+ public static void replaceInstances(Collection<FilterRepresentation> geometry,
+ FilterRepresentation rep) {
+ Iterator<FilterRepresentation> iter = geometry.iterator();
+ while (iter.hasNext()) {
+ FilterRepresentation r = iter.next();
+ if (ImagePreset.sameSerializationName(rep, r)) {
+ iter.remove();
+ }
+ }
+ if (!rep.isNil()) {
+ geometry.add(rep);
+ }
+ }
+
+ public static void initializeHolder(GeometryHolder outHolder,
+ FilterRepresentation currentLocal) {
+ Collection<FilterRepresentation> geometry = MasterImage.getImage().getPreset()
+ .getGeometryFilters();
+ replaceInstances(geometry, currentLocal);
+ unpackGeometry(outHolder, geometry);
+ }
+
+ private static Bitmap applyFullGeometryMatrix(Bitmap image, GeometryHolder holder) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+ RectF crop = getTrueCropRect(holder, width, height);
+ Rect frame = new Rect();
+ crop.roundOut(frame);
+ Matrix m = getCropSelectionToScreenMatrix(null, holder, width, height, frame.width(),
+ frame.height());
+ Bitmap temp = Bitmap.createBitmap(frame.width(), frame.height(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(temp);
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setFilterBitmap(true);
+ paint.setDither(true);
+ canvas.drawBitmap(image, m, paint);
+ return temp;
+ }
+
+ public static Matrix getImageToScreenMatrix(Collection<FilterRepresentation> geometry,
+ boolean reflectRotation, Rect bmapDimens, float viewWidth, float viewHeight) {
+ GeometryHolder h = unpackGeometry(geometry);
+ return GeometryMathUtils.getOriginalToScreen(h, reflectRotation, bmapDimens.width(),
+ bmapDimens.height(), viewWidth, viewHeight);
+ }
+
+ public static Matrix getOriginalToScreen(GeometryHolder holder, boolean rotate,
+ float originalWidth,
+ float originalHeight, float viewWidth, float viewHeight) {
+ int orientation = MasterImage.getImage().getZoomOrientation();
+ int rotation = getRotationForOrientation(orientation);
+ Rotation prev = holder.rotation;
+ rotation = (rotation + prev.value()) % 360;
+ holder.rotation = Rotation.fromValue(rotation);
+ Matrix m = getCropSelectionToScreenMatrix(null, holder, (int) originalWidth,
+ (int) originalHeight, (int) viewWidth, (int) viewHeight);
+ holder.rotation = prev;
+ return m;
+ }
+
+ public static Bitmap applyGeometryRepresentations(Collection<FilterRepresentation> res,
+ Bitmap image) {
+ GeometryHolder holder = unpackGeometry(res);
+ Bitmap bmap = image;
+ // If there are geometry changes, apply them to the image
+ if (!holder.isNil()) {
+ bmap = applyFullGeometryMatrix(bmap, holder);
+ }
+ return bmap;
+ }
+
+ public static RectF drawTransformedCropped(GeometryHolder holder, Canvas canvas,
+ Bitmap photo, int viewWidth, int viewHeight) {
+ if (photo == null) {
+ return null;
+ }
+ RectF crop = new RectF();
+ Matrix m = getCropSelectionToScreenMatrix(crop, holder, photo.getWidth(), photo.getHeight(),
+ viewWidth, viewHeight);
+ canvas.save();
+ canvas.clipRect(crop);
+ Paint p = new Paint();
+ p.setAntiAlias(true);
+ canvas.drawBitmap(photo, m, p);
+ canvas.restore();
+ return crop;
+ }
+
+ public static boolean needsDimensionSwap(Rotation rotation) {
+ switch (rotation) {
+ case NINETY:
+ case TWO_SEVENTY:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ // Gives matrix for rotated, straightened, mirrored bitmap centered at 0,0.
+ private static Matrix getFullGeometryMatrix(GeometryHolder holder, int bitmapWidth,
+ int bitmapHeight) {
+ float centerX = bitmapWidth / 2f;
+ float centerY = bitmapHeight / 2f;
+ Matrix m = new Matrix();
+ m.setTranslate(-centerX, -centerY);
+ m.postRotate(holder.straighten + holder.rotation.value());
+ concatMirrorMatrix(m, holder.mirror);
+ return m;
+ }
+
+ public static Matrix getFullGeometryToScreenMatrix(GeometryHolder holder, int bitmapWidth,
+ int bitmapHeight, int viewWidth, int viewHeight) {
+ float scale = GeometryMathUtils.scale(bitmapWidth, bitmapHeight, viewWidth, viewHeight);
+ Matrix m = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight);
+ m.postScale(scale, scale);
+ m.postTranslate(viewWidth / 2f, viewHeight / 2f);
+ return m;
+ }
+
+ public static RectF getTrueCropRect(GeometryHolder holder, int bitmapWidth, int bitmapHeight) {
+ RectF r = new RectF(holder.crop);
+ FilterCropRepresentation.findScaledCrop(r, bitmapWidth, bitmapHeight);
+ float s = holder.straighten;
+ holder.straighten = 0;
+ Matrix m1 = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight);
+ holder.straighten = s;
+ m1.mapRect(r);
+ return r;
+ }
+
+ public static Matrix getCropSelectionToScreenMatrix(RectF outCrop, GeometryHolder holder,
+ int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) {
+ Matrix m = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight);
+ RectF crop = getTrueCropRect(holder, bitmapWidth, bitmapHeight);
+ float scale = GeometryMathUtils.scale(crop.width(), crop.height(), viewWidth, viewHeight);
+ m.postScale(scale, scale);
+ GeometryMathUtils.scaleRect(crop, scale);
+ m.postTranslate(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY());
+ if (outCrop != null) {
+ crop.offset(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY());
+ outCrop.set(crop);
+ }
+ return m;
+ }
+
+ public static Matrix getCropSelectionToScreenMatrix(RectF outCrop,
+ Collection<FilterRepresentation> res, int bitmapWidth, int bitmapHeight, int viewWidth,
+ int viewHeight) {
+ GeometryHolder holder = unpackGeometry(res);
+ return getCropSelectionToScreenMatrix(outCrop, holder, bitmapWidth, bitmapHeight,
+ viewWidth, viewHeight);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GradControl.java b/src/com/android/gallery3d/filtershow/imageshow/GradControl.java
new file mode 100644
index 000000000..964da99e9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/GradControl.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.Shader;
+
+import com.android.gallery3d.R;
+
+public class GradControl {
+ private float mPoint1X = Float.NaN; // used to flag parameters have not been set
+ private float mPoint1Y = 0;
+ private float mPoint2X = 200;
+ private float mPoint2Y = 300;
+ private int mMinTouchDist = 80;// should be a resource & in dips
+
+ private float[] handlex = new float[3];
+ private float[] handley = new float[3];
+ private int mSliderColor;
+ private int mCenterDotSize;
+ private float mDownX;
+ private float mDownY;
+ private float mDownPoint1X;
+ private float mDownPoint1Y;
+ private float mDownPoint2X;
+ private float mDownPoint2Y;
+ Rect mImageBounds;
+ int mImageHeight;
+ private Matrix mScrToImg;
+ Paint mPaint = new Paint();
+ DashPathEffect mDash = new DashPathEffect(new float[]{30, 30}, 0);
+ private boolean mShowReshapeHandles = true;
+ public final static int HAN_CENTER = 0;
+ public final static int HAN_NORTH = 2;
+ public final static int HAN_SOUTH = 1;
+ private int[] mPointColorPatern;
+ private int[] mGrayPointColorPatern;
+ private float[] mPointRadialPos = new float[]{0, .3f, .31f, 1};
+ private int mLineColor;
+ private int mlineShadowColor;
+
+ public GradControl(Context context) {
+
+ Resources res = context.getResources();
+ mCenterDotSize = (int) res.getDimension(R.dimen.gradcontrol_dot_size);
+ mMinTouchDist = (int) res.getDimension(R.dimen.gradcontrol_min_touch_dist);
+ int grayPointCenterColor = res.getColor(R.color.gradcontrol_graypoint_center);
+ int grayPointEdgeColor = res.getColor(R.color.gradcontrol_graypoint_edge);
+ int pointCenterColor = res.getColor(R.color.gradcontrol_point_center);
+ int pointEdgeColor = res.getColor(R.color.gradcontrol_point_edge);
+ int pointShadowStartColor = res.getColor(R.color.gradcontrol_point_shadow_start);
+ int pointShadowEndColor = res.getColor(R.color.gradcontrol_point_shadow_end);
+ mPointColorPatern = new int[]{
+ pointCenterColor, pointEdgeColor, pointShadowStartColor, pointShadowEndColor};
+ mGrayPointColorPatern = new int[]{
+ grayPointCenterColor, grayPointEdgeColor, pointShadowStartColor, pointShadowEndColor};
+ mSliderColor = Color.WHITE;
+ mLineColor = res.getColor(R.color.gradcontrol_line_color);
+ mlineShadowColor = res.getColor(R.color.gradcontrol_line_shadow);
+ }
+
+ public void setPoint2(float x, float y) {
+ mPoint2X = x;
+ mPoint2Y = y;
+ }
+
+ public void setPoint1(float x, float y) {
+ mPoint1X = x;
+ mPoint1Y = y;
+ }
+
+ public int getCloseHandle(float x, float y) {
+ float min = Float.MAX_VALUE;
+ int handle = -1;
+ for (int i = 0; i < handlex.length; i++) {
+ float dx = handlex[i] - x;
+ float dy = handley[i] - y;
+ float dist = dx * dx + dy * dy;
+ if (dist < min) {
+ min = dist;
+ handle = i;
+ }
+ }
+
+ if (min < mMinTouchDist * mMinTouchDist) {
+ return handle;
+ }
+ for (int i = 0; i < handlex.length; i++) {
+ float dx = handlex[i] - x;
+ float dy = handley[i] - y;
+ float dist = (float) Math.sqrt(dx * dx + dy * dy);
+ }
+
+ return -1;
+ }
+
+ public void setScrImageInfo(Matrix scrToImg, Rect imageBounds) {
+ mScrToImg = scrToImg;
+ mImageBounds = new Rect(imageBounds);
+ }
+
+ private boolean centerIsOutside(float x1, float y1, float x2, float y2) {
+ return (!mImageBounds.contains((int) ((x1 + x2) / 2), (int) ((y1 + y2) / 2)));
+ }
+
+ public void actionDown(float x, float y, Line line) {
+ float[] point = new float[]{
+ x, y};
+ mScrToImg.mapPoints(point);
+ mDownX = point[0];
+ mDownY = point[1];
+ mDownPoint1X = line.getPoint1X();
+ mDownPoint1Y = line.getPoint1Y();
+ mDownPoint2X = line.getPoint2X();
+ mDownPoint2Y = line.getPoint2Y();
+ }
+
+ public void actionMove(int handle, float x, float y, Line line) {
+ float[] point = new float[]{
+ x, y};
+ mScrToImg.mapPoints(point);
+ x = point[0];
+ y = point[1];
+
+ // Test if the matrix is swapping x and y
+ point[0] = 0;
+ point[1] = 1;
+ mScrToImg.mapVectors(point);
+ boolean swapxy = (point[0] > 0.0f);
+
+ int sign = 1;
+
+ float dx = x - mDownX;
+ float dy = y - mDownY;
+ switch (handle) {
+ case HAN_CENTER:
+ if (centerIsOutside(mDownPoint1X + dx, mDownPoint1Y + dy,
+ mDownPoint2X + dx, mDownPoint2Y + dy)) {
+ break;
+ }
+ line.setPoint1(mDownPoint1X + dx, mDownPoint1Y + dy);
+ line.setPoint2(mDownPoint2X + dx, mDownPoint2Y + dy);
+ break;
+ case HAN_SOUTH:
+ if (centerIsOutside(mDownPoint1X + dx, mDownPoint1Y + dy,
+ mDownPoint2X, mDownPoint2Y)) {
+ break;
+ }
+ line.setPoint1(mDownPoint1X + dx, mDownPoint1Y + dy);
+ break;
+ case HAN_NORTH:
+ if (centerIsOutside(mDownPoint1X, mDownPoint1Y,
+ mDownPoint2X + dx, mDownPoint2Y + dy)) {
+ break;
+ }
+ line.setPoint2(mDownPoint2X + dx, mDownPoint2Y + dy);
+ break;
+ }
+ }
+
+ public void paintGrayPoint(Canvas canvas, float x, float y) {
+ if (isUndefined()) {
+ return;
+ }
+
+ Paint paint = new Paint();
+ paint.setStyle(Paint.Style.FILL);
+ RadialGradient g = new RadialGradient(x, y, mCenterDotSize, mGrayPointColorPatern,
+ mPointRadialPos, Shader.TileMode.CLAMP);
+ paint.setShader(g);
+ canvas.drawCircle(x, y, mCenterDotSize, paint);
+ }
+
+ public void paintPoint(Canvas canvas, float x, float y) {
+ if (isUndefined()) {
+ return;
+ }
+
+ Paint paint = new Paint();
+ paint.setStyle(Paint.Style.FILL);
+ RadialGradient g = new RadialGradient(x, y, mCenterDotSize, mPointColorPatern,
+ mPointRadialPos, Shader.TileMode.CLAMP);
+ paint.setShader(g);
+ canvas.drawCircle(x, y, mCenterDotSize, paint);
+ }
+
+ void paintLines(Canvas canvas, float p1x, float p1y, float p2x, float p2y) {
+ if (isUndefined()) {
+ return;
+ }
+
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Paint.Style.STROKE);
+
+ mPaint.setStrokeWidth(6);
+ mPaint.setColor(mlineShadowColor);
+ mPaint.setPathEffect(mDash);
+ paintOvallines(canvas, mPaint, p1x, p1y, p2x, p2y);
+
+ mPaint.setStrokeWidth(3);
+ mPaint.setColor(mLineColor);
+ mPaint.setPathEffect(mDash);
+ paintOvallines(canvas, mPaint, p1x, p1y, p2x, p2y);
+ }
+
+ public void paintOvallines(
+ Canvas canvas, Paint paint, float p1x, float p1y, float p2x, float p2y) {
+
+
+
+ canvas.drawLine(p1x, p1y, p2x, p2y, paint);
+
+ float cx = (p1x + p2x) / 2;
+ float cy = (p1y + p2y) / 2;
+ float dx = p1x - p2x;
+ float dy = p1y - p2y;
+ float len = (float) Math.sqrt(dx * dx + dy * dy);
+ dx *= 2048 / len;
+ dy *= 2048 / len;
+
+ canvas.drawLine(p1x + dy, p1y - dx, p1x - dy, p1y + dx, paint);
+ canvas.drawLine(p2x + dy, p2y - dx, p2x - dy, p2y + dx, paint);
+ }
+
+ public void fillHandles(Canvas canvas, float p1x, float p1y, float p2x, float p2y) {
+ float cx = (p1x + p2x) / 2;
+ float cy = (p1y + p2y) / 2;
+ handlex[0] = cx;
+ handley[0] = cy;
+ handlex[1] = p1x;
+ handley[1] = p1y;
+ handlex[2] = p2x;
+ handley[2] = p2y;
+
+ }
+
+ public void draw(Canvas canvas) {
+ paintLines(canvas, mPoint1X, mPoint1Y, mPoint2X, mPoint2Y);
+ fillHandles(canvas, mPoint1X, mPoint1Y, mPoint2X, mPoint2Y);
+ paintPoint(canvas, mPoint2X, mPoint2Y);
+ paintPoint(canvas, mPoint1X, mPoint1Y);
+ paintPoint(canvas, (mPoint1X + mPoint2X) / 2, (mPoint1Y + mPoint2Y) / 2);
+ }
+
+ public boolean isUndefined() {
+ return Float.isNaN(mPoint1X);
+ }
+
+ public void setShowReshapeHandles(boolean showReshapeHandles) {
+ this.mShowReshapeHandles = showReshapeHandles;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
new file mode 100644
index 000000000..7fee03188
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
+import com.android.gallery3d.filtershow.crop.CropMath;
+import com.android.gallery3d.filtershow.crop.CropObject;
+import com.android.gallery3d.filtershow.editors.EditorCrop;
+import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
+import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;
+
+public class ImageCrop extends ImageShow {
+ private static final String TAG = ImageCrop.class.getSimpleName();
+ private RectF mImageBounds = new RectF();
+ private RectF mScreenCropBounds = new RectF();
+ private Paint mPaint = new Paint();
+ private CropObject mCropObj = null;
+ private GeometryHolder mGeometry = new GeometryHolder();
+ private GeometryHolder mUpdateHolder = new GeometryHolder();
+ private Drawable mCropIndicator;
+ private int mIndicatorSize;
+ private boolean mMovingBlock = false;
+ private Matrix mDisplayMatrix = null;
+ private Matrix mDisplayCropMatrix = null;
+ private Matrix mDisplayMatrixInverse = null;
+ private float mPrevX = 0;
+ private float mPrevY = 0;
+ private int mMinSideSize = 90;
+ private int mTouchTolerance = 40;
+ private enum Mode {
+ NONE, MOVE
+ }
+ private Mode mState = Mode.NONE;
+ private boolean mValidDraw = false;
+ FilterCropRepresentation mLocalRep = new FilterCropRepresentation();
+ EditorCrop mEditorCrop;
+
+ public ImageCrop(Context context) {
+ super(context);
+ setup(context);
+ }
+
+ public ImageCrop(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setup(context);
+ }
+
+ public ImageCrop(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setup(context);
+ }
+
+ private void setup(Context context) {
+ Resources rsc = context.getResources();
+ mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
+ mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
+ mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
+ mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
+ }
+
+ public void setFilterCropRepresentation(FilterCropRepresentation crop) {
+ mLocalRep = (crop == null) ? new FilterCropRepresentation() : crop;
+ GeometryMathUtils.initializeHolder(mUpdateHolder, mLocalRep);
+ mValidDraw = true;
+ }
+
+ public FilterCropRepresentation getFinalRepresentation() {
+ return mLocalRep;
+ }
+
+ private void internallyUpdateLocalRep(RectF crop, RectF image) {
+ FilterCropRepresentation
+ .findNormalizedCrop(crop, (int) image.width(), (int) image.height());
+ mGeometry.crop.set(crop);
+ mUpdateHolder.set(mGeometry);
+ mLocalRep.setCrop(crop);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
+ return true;
+ }
+ float[] touchPoint = {
+ x, y
+ };
+ mDisplayMatrixInverse.mapPoints(touchPoint);
+ x = touchPoint[0];
+ y = touchPoint[1];
+ switch (event.getActionMasked()) {
+ case (MotionEvent.ACTION_DOWN):
+ if (mState == Mode.NONE) {
+ if (!mCropObj.selectEdge(x, y)) {
+ mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
+ }
+ mPrevX = x;
+ mPrevY = y;
+ mState = Mode.MOVE;
+ }
+ break;
+ case (MotionEvent.ACTION_UP):
+ if (mState == Mode.MOVE) {
+ mCropObj.selectEdge(CropObject.MOVE_NONE);
+ mMovingBlock = false;
+ mPrevX = x;
+ mPrevY = y;
+ mState = Mode.NONE;
+ internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
+ }
+ break;
+ case (MotionEvent.ACTION_MOVE):
+ if (mState == Mode.MOVE) {
+ float dx = x - mPrevX;
+ float dy = y - mPrevY;
+ mCropObj.moveCurrentSelection(dx, dy);
+ mPrevX = x;
+ mPrevY = y;
+ }
+ break;
+ default:
+ break;
+ }
+ invalidate();
+ return true;
+ }
+
+ private void clearDisplay() {
+ mDisplayMatrix = null;
+ mDisplayMatrixInverse = null;
+ invalidate();
+ }
+
+ public void applyFreeAspect() {
+ mCropObj.unsetAspectRatio();
+ invalidate();
+ }
+
+ public void applyOriginalAspect() {
+ RectF outer = mCropObj.getOuterBounds();
+ float w = outer.width();
+ float h = outer.height();
+ if (w > 0 && h > 0) {
+ applyAspect(w, h);
+ mCropObj.resetBoundsTo(outer, outer);
+ internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
+ } else {
+ Log.w(TAG, "failed to set aspect ratio original");
+ }
+ invalidate();
+ }
+
+ public void applyAspect(float x, float y) {
+ if (x <= 0 || y <= 0) {
+ throw new IllegalArgumentException("Bad arguments to applyAspect");
+ }
+ // If we are rotated by 90 degrees from horizontal, swap x and y
+ if (GeometryMathUtils.needsDimensionSwap(mGeometry.rotation)) {
+ float tmp = x;
+ x = y;
+ y = tmp;
+ }
+ if (!mCropObj.setInnerAspectRatio(x, y)) {
+ Log.w(TAG, "failed to set aspect ratio");
+ }
+ internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
+ invalidate();
+ }
+
+ /**
+ * Rotates first d bits in integer x to the left some number of times.
+ */
+ private int bitCycleLeft(int x, int times, int d) {
+ int mask = (1 << d) - 1;
+ int mout = x & mask;
+ times %= d;
+ int hi = mout >> (d - times);
+ int low = (mout << times) & mask;
+ int ret = x & ~mask;
+ ret |= low;
+ ret |= hi;
+ return ret;
+ }
+
+ /**
+ * Find the selected edge or corner in screen coordinates.
+ */
+ private int decode(int movingEdges, float rotation) {
+ int rot = CropMath.constrainedRotation(rotation);
+ switch (rot) {
+ case 90:
+ return bitCycleLeft(movingEdges, 1, 4);
+ case 180:
+ return bitCycleLeft(movingEdges, 2, 4);
+ case 270:
+ return bitCycleLeft(movingEdges, 3, 4);
+ default:
+ return movingEdges;
+ }
+ }
+
+ private void forceStateConsistency() {
+ MasterImage master = MasterImage.getImage();
+ Bitmap image = master.getFiltersOnlyImage();
+ int width = image.getWidth();
+ int height = image.getHeight();
+ if (mCropObj == null || !mUpdateHolder.equals(mGeometry)
+ || mImageBounds.width() != width || mImageBounds.height() != height
+ || !mLocalRep.getCrop().equals(mUpdateHolder.crop)) {
+ mImageBounds.set(0, 0, width, height);
+ mGeometry.set(mUpdateHolder);
+ mLocalRep.setCrop(mUpdateHolder.crop);
+ RectF scaledCrop = new RectF(mUpdateHolder.crop);
+ FilterCropRepresentation.findScaledCrop(scaledCrop, width, height);
+ mCropObj = new CropObject(mImageBounds, scaledCrop, (int) mUpdateHolder.straighten);
+ mState = Mode.NONE;
+ clearDisplay();
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ clearDisplay();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ Bitmap bitmap = MasterImage.getImage().getFiltersOnlyImage();
+ if (!mValidDraw || bitmap == null) {
+ return;
+ }
+ forceStateConsistency();
+ mImageBounds.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ // If display matrix doesn't exist, create it and its dependencies
+ if (mDisplayCropMatrix == null || mDisplayMatrix == null || mDisplayMatrixInverse == null) {
+ mDisplayMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry,
+ bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
+ float straighten = mGeometry.straighten;
+ mGeometry.straighten = 0;
+ mDisplayCropMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry,
+ bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
+ mGeometry.straighten = straighten;
+ mDisplayMatrixInverse = new Matrix();
+ mDisplayMatrixInverse.reset();
+ if (!mDisplayCropMatrix.invert(mDisplayMatrixInverse)) {
+ Log.w(TAG, "could not invert display matrix");
+ mDisplayMatrixInverse = null;
+ return;
+ }
+ // Scale min side and tolerance by display matrix scale factor
+ mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
+ mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
+ }
+ // Draw actual bitmap
+ mPaint.reset();
+ mPaint.setAntiAlias(true);
+ mPaint.setFilterBitmap(true);
+ canvas.drawBitmap(bitmap, mDisplayMatrix, mPaint);
+ mCropObj.getInnerBounds(mScreenCropBounds);
+ RectF outer = mCropObj.getOuterBounds();
+ FilterCropRepresentation.findNormalizedCrop(mScreenCropBounds, (int) outer.width(),
+ (int) outer.height());
+ FilterCropRepresentation.findScaledCrop(mScreenCropBounds, bitmap.getWidth(),
+ bitmap.getHeight());
+ if (mDisplayCropMatrix.mapRect(mScreenCropBounds)) {
+ // Draw crop rect and markers
+ CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
+ CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
+ CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize,
+ mScreenCropBounds, mCropObj.isFixedAspect(),
+ decode(mCropObj.getSelectState(), mGeometry.rotation.value()));
+ }
+ }
+
+ public void setEditor(EditorCrop editorCrop) {
+ mEditorCrop = editorCrop;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCurves.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCurves.java
new file mode 100644
index 000000000..82c4b2fc7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCurves.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.Editor;
+import com.android.gallery3d.filtershow.editors.EditorCurves;
+import com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+
+import java.util.HashMap;
+
+public class ImageCurves extends ImageShow {
+
+ private static final String LOGTAG = "ImageCurves";
+ Paint gPaint = new Paint();
+ Path gPathSpline = new Path();
+ HashMap<Integer, String> mIdStrLut;
+
+ private int mCurrentCurveIndex = Spline.RGB;
+ private boolean mDidAddPoint = false;
+ private boolean mDidDelete = false;
+ private ControlPoint mCurrentControlPoint = null;
+ private int mCurrentPick = -1;
+ private ImagePreset mLastPreset = null;
+ int[] redHistogram = new int[256];
+ int[] greenHistogram = new int[256];
+ int[] blueHistogram = new int[256];
+ Path gHistoPath = new Path();
+
+ boolean mDoingTouchMove = false;
+ private EditorCurves mEditorCurves;
+ private FilterCurvesRepresentation mFilterCurvesRepresentation;
+
+ public ImageCurves(Context context) {
+ super(context);
+ setLayerType(LAYER_TYPE_SOFTWARE, gPaint);
+ resetCurve();
+ }
+
+ public ImageCurves(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setLayerType(LAYER_TYPE_SOFTWARE, gPaint);
+ resetCurve();
+ }
+
+ @Override
+ protected boolean enableComparison() {
+ return false;
+ }
+
+ @Override
+ public boolean useUtilityPanel() {
+ return true;
+ }
+
+ private void showPopupMenu(LinearLayout accessoryViewList) {
+ final Button button = (Button) accessoryViewList.findViewById(
+ R.id.applyEffect);
+ if (button == null) {
+ return;
+ }
+ if (mIdStrLut == null){
+ mIdStrLut = new HashMap<Integer, String>();
+ mIdStrLut.put(R.id.curve_menu_rgb,
+ getContext().getString(R.string.curves_channel_rgb));
+ mIdStrLut.put(R.id.curve_menu_red,
+ getContext().getString(R.string.curves_channel_red));
+ mIdStrLut.put(R.id.curve_menu_green,
+ getContext().getString(R.string.curves_channel_green));
+ mIdStrLut.put(R.id.curve_menu_blue,
+ getContext().getString(R.string.curves_channel_blue));
+ }
+ PopupMenu popupMenu = new PopupMenu(getActivity(), button);
+ popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_curves, popupMenu.getMenu());
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ setChannel(item.getItemId());
+ button.setText(mIdStrLut.get(item.getItemId()));
+ return true;
+ }
+ });
+ Editor.hackFixStrings(popupMenu.getMenu());
+ popupMenu.show();
+ }
+
+ @Override
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ Context context = accessoryViewList.getContext();
+ Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+ view.setText(context.getString(R.string.curves_channel_rgb));
+ view.setVisibility(View.VISIBLE);
+
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ showPopupMenu(accessoryViewList);
+ }
+ });
+
+ if (view != null) {
+ view.setVisibility(View.VISIBLE);
+ }
+ }
+
+ public void nextChannel() {
+ mCurrentCurveIndex = ((mCurrentCurveIndex + 1) % 4);
+ invalidate();
+ }
+
+ private ImageFilterCurves curves() {
+ String filterName = getFilterName();
+ ImagePreset p = getImagePreset();
+ if (p != null) {
+ return (ImageFilterCurves) FiltersManager.getManager().getFilter(ImageFilterCurves.class);
+ }
+ return null;
+ }
+
+ private Spline getSpline(int index) {
+ return mFilterCurvesRepresentation.getSpline(index);
+ }
+
+ @Override
+ public void resetParameter() {
+ super.resetParameter();
+ resetCurve();
+ mLastPreset = null;
+ invalidate();
+ }
+
+ public void resetCurve() {
+ if (mFilterCurvesRepresentation != null) {
+ mFilterCurvesRepresentation.reset();
+ updateCachedImage();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mFilterCurvesRepresentation == null) {
+ return;
+ }
+
+ gPaint.setAntiAlias(true);
+
+ if (getImagePreset() != mLastPreset && getFilteredImage() != null) {
+ new ComputeHistogramTask().execute(getFilteredImage());
+ mLastPreset = getImagePreset();
+ }
+
+ if (curves() == null) {
+ return;
+ }
+
+ if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.RED) {
+ drawHistogram(canvas, redHistogram, Color.RED, PorterDuff.Mode.SCREEN);
+ }
+ if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.GREEN) {
+ drawHistogram(canvas, greenHistogram, Color.GREEN, PorterDuff.Mode.SCREEN);
+ }
+ if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.BLUE) {
+ drawHistogram(canvas, blueHistogram, Color.BLUE, PorterDuff.Mode.SCREEN);
+ }
+ // We only display the other channels curves when showing the RGB curve
+ if (mCurrentCurveIndex == Spline.RGB) {
+ for (int i = 0; i < 4; i++) {
+ Spline spline = getSpline(i);
+ if (i != mCurrentCurveIndex && !spline.isOriginal()) {
+ // And we only display a curve if it has more than two
+ // points
+ spline.draw(canvas, Spline.colorForCurve(i), getWidth(),
+ getHeight(), false, mDoingTouchMove);
+ }
+ }
+ }
+ // ...but we always display the current curve.
+ getSpline(mCurrentCurveIndex)
+ .draw(canvas, Spline.colorForCurve(mCurrentCurveIndex), getWidth(), getHeight(),
+ true, mDoingTouchMove);
+
+ }
+
+ private int pickControlPoint(float x, float y) {
+ int pick = 0;
+ Spline spline = getSpline(mCurrentCurveIndex);
+ float px = spline.getPoint(0).x;
+ float py = spline.getPoint(0).y;
+ double delta = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y));
+ for (int i = 1; i < spline.getNbPoints(); i++) {
+ px = spline.getPoint(i).x;
+ py = spline.getPoint(i).y;
+ double currentDelta = Math.sqrt((px - x) * (px - x) + (py - y)
+ * (py - y));
+ if (currentDelta < delta) {
+ delta = currentDelta;
+ pick = i;
+ }
+ }
+
+ if (!mDidAddPoint && (delta * getWidth() > 100)
+ && (spline.getNbPoints() < 10)) {
+ return -1;
+ }
+
+ return pick;
+ }
+
+ private String getFilterName() {
+ return "Curves";
+ }
+
+ @Override
+ public synchronized boolean onTouchEvent(MotionEvent e) {
+ if (e.getPointerCount() != 1) {
+ return true;
+ }
+
+ if (didFinishScalingOperation()) {
+ return true;
+ }
+
+ float margin = Spline.curveHandleSize() / 2;
+ float posX = e.getX();
+ if (posX < margin) {
+ posX = margin;
+ }
+ float posY = e.getY();
+ if (posY < margin) {
+ posY = margin;
+ }
+ if (posX > getWidth() - margin) {
+ posX = getWidth() - margin;
+ }
+ if (posY > getHeight() - margin) {
+ posY = getHeight() - margin;
+ }
+ posX = (posX - margin) / (getWidth() - 2 * margin);
+ posY = (posY - margin) / (getHeight() - 2 * margin);
+
+ if (e.getActionMasked() == MotionEvent.ACTION_UP) {
+ mCurrentControlPoint = null;
+ mCurrentPick = -1;
+ updateCachedImage();
+ mDidAddPoint = false;
+ if (mDidDelete) {
+ mDidDelete = false;
+ }
+ mDoingTouchMove = false;
+ return true;
+ }
+
+ if (mDidDelete) {
+ return true;
+ }
+
+ if (curves() == null) {
+ return true;
+ }
+
+ if (e.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ mDoingTouchMove = true;
+ Spline spline = getSpline(mCurrentCurveIndex);
+ int pick = mCurrentPick;
+ if (mCurrentControlPoint == null) {
+ pick = pickControlPoint(posX, posY);
+ if (pick == -1) {
+ mCurrentControlPoint = new ControlPoint(posX, posY);
+ pick = spline.addPoint(mCurrentControlPoint);
+ mDidAddPoint = true;
+ } else {
+ mCurrentControlPoint = spline.getPoint(pick);
+ }
+ mCurrentPick = pick;
+ }
+
+ if (spline.isPointContained(posX, pick)) {
+ spline.movePoint(pick, posX, posY);
+ } else if (pick != -1 && spline.getNbPoints() > 2) {
+ spline.deletePoint(pick);
+ mDidDelete = true;
+ }
+ updateCachedImage();
+ invalidate();
+ }
+ return true;
+ }
+
+ public synchronized void updateCachedImage() {
+ if (getImagePreset() != null) {
+ resetImageCaches(this);
+ if (mEditorCurves != null) {
+ mEditorCurves.commitLocalRepresentation();
+ }
+ invalidate();
+ }
+ }
+
+ class ComputeHistogramTask extends AsyncTask<Bitmap, Void, int[]> {
+ @Override
+ protected int[] doInBackground(Bitmap... params) {
+ int[] histo = new int[256 * 3];
+ Bitmap bitmap = params[0];
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ int[] pixels = new int[w * h];
+ bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
+ for (int i = 0; i < w; i++) {
+ for (int j = 0; j < h; j++) {
+ int index = j * w + i;
+ int r = Color.red(pixels[index]);
+ int g = Color.green(pixels[index]);
+ int b = Color.blue(pixels[index]);
+ histo[r]++;
+ histo[256 + g]++;
+ histo[512 + b]++;
+ }
+ }
+ return histo;
+ }
+
+ @Override
+ protected void onPostExecute(int[] result) {
+ System.arraycopy(result, 0, redHistogram, 0, 256);
+ System.arraycopy(result, 256, greenHistogram, 0, 256);
+ System.arraycopy(result, 512, blueHistogram, 0, 256);
+ invalidate();
+ }
+ }
+
+ private void drawHistogram(Canvas canvas, int[] histogram, int color, PorterDuff.Mode mode) {
+ int max = 0;
+ for (int i = 0; i < histogram.length; i++) {
+ if (histogram[i] > max) {
+ max = histogram[i];
+ }
+ }
+ float w = getWidth() - Spline.curveHandleSize();
+ float h = getHeight() - Spline.curveHandleSize() / 2.0f;
+ float dx = Spline.curveHandleSize() / 2.0f;
+ float wl = w / histogram.length;
+ float wh = (0.3f * h) / max;
+ Paint paint = new Paint();
+ paint.setARGB(100, 255, 255, 255);
+ paint.setStrokeWidth((int) Math.ceil(wl));
+
+ Paint paint2 = new Paint();
+ paint2.setColor(color);
+ paint2.setStrokeWidth(6);
+ paint2.setXfermode(new PorterDuffXfermode(mode));
+ gHistoPath.reset();
+ gHistoPath.moveTo(dx, h);
+ boolean firstPointEncountered = false;
+ float prev = 0;
+ float last = 0;
+ for (int i = 0; i < histogram.length; i++) {
+ float x = i * wl + dx;
+ float l = histogram[i] * wh;
+ if (l != 0) {
+ float v = h - (l + prev) / 2.0f;
+ if (!firstPointEncountered) {
+ gHistoPath.lineTo(x, h);
+ firstPointEncountered = true;
+ }
+ gHistoPath.lineTo(x, v);
+ prev = l;
+ last = x;
+ }
+ }
+ gHistoPath.lineTo(last, h);
+ gHistoPath.lineTo(w, h);
+ gHistoPath.close();
+ canvas.drawPath(gHistoPath, paint2);
+ paint2.setStrokeWidth(2);
+ paint2.setStyle(Paint.Style.STROKE);
+ paint2.setARGB(255, 200, 200, 200);
+ canvas.drawPath(gHistoPath, paint2);
+ }
+
+ public void setChannel(int itemId) {
+ switch (itemId) {
+ case R.id.curve_menu_rgb: {
+ mCurrentCurveIndex = Spline.RGB;
+ break;
+ }
+ case R.id.curve_menu_red: {
+ mCurrentCurveIndex = Spline.RED;
+ break;
+ }
+ case R.id.curve_menu_green: {
+ mCurrentCurveIndex = Spline.GREEN;
+ break;
+ }
+ case R.id.curve_menu_blue: {
+ mCurrentCurveIndex = Spline.BLUE;
+ break;
+ }
+ }
+ mEditorCurves.commitLocalRepresentation();
+ invalidate();
+ }
+
+ public void setEditor(EditorCurves editorCurves) {
+ mEditorCurves = editorCurves;
+ }
+
+ public void setFilterDrawRepresentation(FilterCurvesRepresentation drawRep) {
+ mFilterCurvesRepresentation = drawRep;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java b/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
new file mode 100644
index 000000000..9722034e0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
@@ -0,0 +1,139 @@
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.filtershow.editors.EditorDraw;
+import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation;
+import com.android.gallery3d.filtershow.filters.ImageFilterDraw;
+
+public class ImageDraw extends ImageShow {
+
+ private static final String LOGTAG = "ImageDraw";
+ private int mCurrentColor = Color.RED;
+ final static float INITAL_STROKE_RADIUS = 40;
+ private float mCurrentSize = INITAL_STROKE_RADIUS;
+ private byte mType = 0;
+ private FilterDrawRepresentation mFRep;
+ private EditorDraw mEditorDraw;
+
+ public ImageDraw(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ resetParameter();
+ }
+
+ public ImageDraw(Context context) {
+ super(context);
+ resetParameter();
+ }
+
+ public void setEditor(EditorDraw editorDraw) {
+ mEditorDraw = editorDraw;
+ }
+ public void setFilterDrawRepresentation(FilterDrawRepresentation fr) {
+ mFRep = fr;
+ }
+
+ public Drawable getIcon(Context context) {
+
+ return null;
+ }
+
+ @Override
+ public void resetParameter() {
+ if (mFRep != null) {
+ mFRep.clear();
+ }
+ }
+
+ public void setColor(int color) {
+ mCurrentColor = color;
+ }
+
+ public void setSize(int size) {
+ mCurrentSize = size;
+ }
+
+ public void setStyle(byte style) {
+ mType = (byte) (style % ImageFilterDraw.NUMBER_OF_STYLES);
+ }
+
+ public int getStyle() {
+ return mType;
+ }
+
+ public int getSize() {
+ return (int) mCurrentSize;
+ }
+
+ float[] mTmpPoint = new float[2]; // so we do not malloc
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getPointerCount() > 1) {
+ boolean ret = super.onTouchEvent(event);
+ if (mFRep.getCurrentDrawing() != null) {
+ mFRep.clearCurrentSection();
+ mEditorDraw.commitLocalRepresentation();
+ }
+ return ret;
+ }
+ if (event.getAction() != MotionEvent.ACTION_DOWN) {
+ if (mFRep.getCurrentDrawing() == null) {
+ return super.onTouchEvent(event);
+ }
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ calcScreenMapping();
+ mTmpPoint[0] = event.getX();
+ mTmpPoint[1] = event.getY();
+ mToOrig.mapPoints(mTmpPoint);
+ mFRep.startNewSection(mType, mCurrentColor, mCurrentSize, mTmpPoint[0], mTmpPoint[1]);
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_MOVE) {
+
+ int historySize = event.getHistorySize();
+ for (int h = 0; h < historySize; h++) {
+ int p = 0;
+ {
+ mTmpPoint[0] = event.getHistoricalX(p, h);
+ mTmpPoint[1] = event.getHistoricalY(p, h);
+ mToOrig.mapPoints(mTmpPoint);
+ mFRep.addPoint(mTmpPoint[0], mTmpPoint[1]);
+ }
+ }
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ mTmpPoint[0] = event.getX();
+ mTmpPoint[1] = event.getY();
+ mToOrig.mapPoints(mTmpPoint);
+ mFRep.endSection(mTmpPoint[0], mTmpPoint[1]);
+ }
+ mEditorDraw.commitLocalRepresentation();
+ invalidate();
+ return true;
+ }
+
+ Matrix mRotateToScreen = new Matrix();
+ Matrix mToOrig;
+ private void calcScreenMapping() {
+ mToOrig = getScreenToImageMatrix(true);
+ mToOrig.invert(mRotateToScreen);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ calcScreenMapping();
+
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageGrad.java b/src/com/android/gallery3d/filtershow/imageshow/ImageGrad.java
new file mode 100644
index 000000000..b55cc2bc4
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageGrad.java
@@ -0,0 +1,215 @@
+package com.android.gallery3d.filtershow.imageshow;
+/*
+ * Copyright (C) 2013 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.
+ */
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorGrad;
+import com.android.gallery3d.filtershow.filters.FilterGradRepresentation;
+
+public class ImageGrad extends ImageShow {
+ private static final String LOGTAG = "ImageGrad";
+ private FilterGradRepresentation mGradRep;
+ private EditorGrad mEditorGrad;
+ private float mMinTouchDist;
+ private int mActiveHandle = -1;
+ private GradControl mEllipse;
+
+ Matrix mToScr = new Matrix();
+ float[] mPointsX = new float[FilterGradRepresentation.MAX_POINTS];
+ float[] mPointsY = new float[FilterGradRepresentation.MAX_POINTS];
+
+ public ImageGrad(Context context) {
+ super(context);
+ Resources res = context.getResources();
+ mMinTouchDist = res.getDimensionPixelSize(R.dimen.gradcontrol_min_touch_dist);
+ mEllipse = new GradControl(context);
+ mEllipse.setShowReshapeHandles(false);
+ }
+
+ public ImageGrad(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ Resources res = context.getResources();
+ mMinTouchDist = res.getDimensionPixelSize(R.dimen.gradcontrol_min_touch_dist);
+ mEllipse = new GradControl(context);
+ mEllipse.setShowReshapeHandles(false);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int mask = event.getActionMasked();
+
+ if (mActiveHandle == -1) {
+ if (MotionEvent.ACTION_DOWN != mask) {
+ return super.onTouchEvent(event);
+ }
+ if (event.getPointerCount() == 1) {
+ mActiveHandle = mEllipse.getCloseHandle(event.getX(), event.getY());
+ if (mActiveHandle == -1) {
+ float x = event.getX();
+ float y = event.getY();
+ float min_d = Float.MAX_VALUE;
+ int pos = -1;
+ for (int i = 0; i < mPointsX.length; i++) {
+ if (mPointsX[i] == -1) {
+ continue;
+ }
+ float d = (float) Math.hypot(x - mPointsX[i], y - mPointsY[i]);
+ if ( min_d > d) {
+ min_d = d;
+ pos = i;
+ }
+ }
+ if (min_d > mMinTouchDist){
+ pos = -1;
+ }
+
+ if (pos != -1) {
+ mGradRep.setSelectedPoint(pos);
+ resetImageCaches(this);
+ mEditorGrad.updateSeekBar(mGradRep);
+ mEditorGrad.commitLocalRepresentation();
+ invalidate();
+ }
+ }
+ }
+ if (mActiveHandle == -1) {
+ return super.onTouchEvent(event);
+ }
+ } else {
+ switch (mask) {
+ case MotionEvent.ACTION_UP: {
+
+ mActiveHandle = -1;
+ break;
+ }
+ case MotionEvent.ACTION_DOWN: {
+ break;
+ }
+ }
+ }
+ float x = event.getX();
+ float y = event.getY();
+
+ mEllipse.setScrImageInfo(getScreenToImageMatrix(true),
+ MasterImage.getImage().getOriginalBounds());
+
+ switch (mask) {
+ case (MotionEvent.ACTION_DOWN): {
+ mEllipse.actionDown(x, y, mGradRep);
+ break;
+ }
+ case (MotionEvent.ACTION_UP):
+ case (MotionEvent.ACTION_MOVE): {
+ mEllipse.actionMove(mActiveHandle, x, y, mGradRep);
+ setRepresentation(mGradRep);
+ break;
+ }
+ }
+ invalidate();
+ mEditorGrad.commitLocalRepresentation();
+ return true;
+ }
+
+ public void setRepresentation(FilterGradRepresentation pointRep) {
+ mGradRep = pointRep;
+ Matrix toImg = getScreenToImageMatrix(false);
+
+ toImg.invert(mToScr);
+
+ float[] c1 = new float[] { mGradRep.getPoint1X(), mGradRep.getPoint1Y() };
+ float[] c2 = new float[] { mGradRep.getPoint2X(), mGradRep.getPoint2Y() };
+
+ if (c1[0] == -1) {
+ float cx = MasterImage.getImage().getOriginalBounds().width() / 2;
+ float cy = MasterImage.getImage().getOriginalBounds().height() / 2;
+ float rx = Math.min(cx, cy) * .4f;
+
+ mGradRep.setPoint1(cx, cy-rx);
+ mGradRep.setPoint2(cx, cy+rx);
+ c1[0] = cx;
+ c1[1] = cy-rx;
+ mToScr.mapPoints(c1);
+ if (getWidth() != 0) {
+ mEllipse.setPoint1(c1[0], c1[1]);
+ c2[0] = cx;
+ c2[1] = cy+rx;
+ mToScr.mapPoints(c2);
+ mEllipse.setPoint2(c2[0], c2[1]);
+ }
+ mEditorGrad.commitLocalRepresentation();
+ } else {
+ mToScr.mapPoints(c1);
+ mToScr.mapPoints(c2);
+ mEllipse.setPoint1(c1[0], c1[1]);
+ mEllipse.setPoint2(c2[0], c2[1]);
+ }
+ }
+
+ public void drawOtherPoints(Canvas canvas) {
+ computCenterLocations();
+ for (int i = 0; i < mPointsX.length; i++) {
+ if (mPointsX[i] != -1) {
+ mEllipse.paintGrayPoint(canvas, mPointsX[i], mPointsY[i]);
+ }
+ }
+ }
+
+ public void computCenterLocations() {
+ int x1[] = mGradRep.getXPos1();
+ int y1[] = mGradRep.getYPos1();
+ int x2[] = mGradRep.getXPos2();
+ int y2[] = mGradRep.getYPos2();
+ int selected = mGradRep.getSelectedPoint();
+ boolean m[] = mGradRep.getMask();
+ float[] c = new float[2];
+ for (int i = 0; i < m.length; i++) {
+ if (selected == i || !m[i]) {
+ mPointsX[i] = -1;
+ continue;
+ }
+
+ c[0] = (x1[i]+x2[i])/2;
+ c[1] = (y1[i]+y2[i])/2;
+ mToScr.mapPoints(c);
+
+ mPointsX[i] = c[0];
+ mPointsY[i] = c[1];
+ }
+ }
+
+ public void setEditor(EditorGrad editorGrad) {
+ mEditorGrad = editorGrad;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mGradRep == null) {
+ return;
+ }
+ setRepresentation(mGradRep);
+ mEllipse.draw(canvas);
+ drawOtherPoints(canvas);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageMirror.java b/src/com/android/gallery3d/filtershow/imageshow/ImageMirror.java
new file mode 100644
index 000000000..26c49b1a8
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageMirror.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.filtershow.editors.EditorMirror;
+import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
+import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;
+
+public class ImageMirror extends ImageShow {
+ private static final String TAG = ImageMirror.class.getSimpleName();
+ private EditorMirror mEditorMirror;
+ private FilterMirrorRepresentation mLocalRep = new FilterMirrorRepresentation();
+ private GeometryHolder mDrawHolder = new GeometryHolder();
+
+ public ImageMirror(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ImageMirror(Context context) {
+ super(context);
+ }
+
+ public void setFilterMirrorRepresentation(FilterMirrorRepresentation rep) {
+ mLocalRep = (rep == null) ? new FilterMirrorRepresentation() : rep;
+ }
+
+ public void flip() {
+ mLocalRep.cycle();
+ invalidate();
+ }
+
+ public FilterMirrorRepresentation getFinalRepresentation() {
+ return mLocalRep;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Treat event as handled.
+ return true;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ MasterImage master = MasterImage.getImage();
+ Bitmap image = master.getFiltersOnlyImage();
+ if (image == null) {
+ return;
+ }
+ GeometryMathUtils.initializeHolder(mDrawHolder, mLocalRep);
+ GeometryMathUtils.drawTransformedCropped(mDrawHolder, canvas, image, getWidth(),
+ getHeight());
+ }
+
+ public void setEditor(EditorMirror editorFlip) {
+ mEditorMirror = editorFlip;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java b/src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java
new file mode 100644
index 000000000..fd5714139
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.filtershow.editors.EditorRedEye;
+import com.android.gallery3d.filtershow.filters.FilterPoint;
+import com.android.gallery3d.filtershow.filters.FilterRedEyeRepresentation;
+import com.android.gallery3d.filtershow.filters.ImageFilterRedEye;
+
+public abstract class ImagePoint extends ImageShow {
+
+ private static final String LOGTAG = "ImageRedEyes";
+ protected EditorRedEye mEditorRedEye;
+ protected FilterRedEyeRepresentation mRedEyeRep;
+ protected static float mTouchPadding = 80;
+
+ public static void setTouchPadding(float padding) {
+ mTouchPadding = padding;
+ }
+
+ public ImagePoint(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ImagePoint(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void resetParameter() {
+ ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter();
+ if (filter != null) {
+ filter.clear();
+ }
+ invalidate();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ Paint paint = new Paint();
+ paint.setStyle(Style.STROKE);
+ paint.setColor(Color.RED);
+ paint.setStrokeWidth(2);
+
+ Matrix originalToScreen = getImageToScreenMatrix(false);
+ Matrix originalRotateToScreen = getImageToScreenMatrix(true);
+
+ if (mRedEyeRep != null) {
+ for (FilterPoint candidate : mRedEyeRep.getCandidates()) {
+ drawPoint(candidate, canvas, originalToScreen, originalRotateToScreen, paint);
+ }
+ }
+ }
+
+ protected abstract void drawPoint(
+ FilterPoint candidate, Canvas canvas, Matrix originalToScreen,
+ Matrix originalRotateToScreen, Paint paint);
+
+ public void setEditor(EditorRedEye editorRedEye) {
+ mEditorRedEye = editorRedEye;
+ }
+
+ public void setRepresentation(FilterRedEyeRepresentation redEyeRep) {
+ mRedEyeRep = redEyeRep;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRedEye.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRedEye.java
new file mode 100644
index 000000000..40433a02e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRedEye.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.filtershow.filters.FilterPoint;
+import com.android.gallery3d.filtershow.filters.RedEyeCandidate;
+
+public class ImageRedEye extends ImagePoint {
+ private static final String LOGTAG = "ImageRedEyes";
+ private RectF mCurrentRect = null;
+
+ public ImageRedEye(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void resetParameter() {
+ super.resetParameter();
+ invalidate();
+ }
+
+ @Override
+
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+
+ if (event.getPointerCount() > 1) {
+ return true;
+ }
+
+ if (didFinishScalingOperation()) {
+ return true;
+ }
+
+ float ex = event.getX();
+ float ey = event.getY();
+
+ // let's transform (ex, ey) to displayed image coordinates
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ mCurrentRect = new RectF();
+ mCurrentRect.left = ex - mTouchPadding;
+ mCurrentRect.top = ey - mTouchPadding;
+ }
+ if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ mCurrentRect.right = ex + mTouchPadding;
+ mCurrentRect.bottom = ey + mTouchPadding;
+ }
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (mCurrentRect != null) {
+ // transform to original coordinates
+ Matrix originalNoRotateToScreen = getImageToScreenMatrix(false);
+ Matrix originalToScreen = getImageToScreenMatrix(true);
+ Matrix invert = new Matrix();
+ originalToScreen.invert(invert);
+ RectF r = new RectF(mCurrentRect);
+ invert.mapRect(r);
+ RectF r2 = new RectF(mCurrentRect);
+ invert.reset();
+ originalNoRotateToScreen.invert(invert);
+ invert.mapRect(r2);
+ mRedEyeRep.addRect(r, r2);
+ this.resetImageCaches(this);
+ }
+ mCurrentRect = null;
+ }
+ mEditorRedEye.commitLocalRepresentation();
+ invalidate();
+ return true;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ Paint paint = new Paint();
+ paint.setStyle(Style.STROKE);
+ paint.setColor(Color.RED);
+ paint.setStrokeWidth(2);
+ if (mCurrentRect != null) {
+ paint.setColor(Color.RED);
+ RectF drawRect = new RectF(mCurrentRect);
+ canvas.drawRect(drawRect, paint);
+ }
+ }
+
+ @Override
+ protected void drawPoint(FilterPoint point, Canvas canvas, Matrix originalToScreen,
+ Matrix originalRotateToScreen, Paint paint) {
+ RedEyeCandidate candidate = (RedEyeCandidate) point;
+ RectF rect = candidate.getRect();
+ RectF drawRect = new RectF();
+ originalToScreen.mapRect(drawRect, rect);
+ RectF fullRect = new RectF();
+ originalRotateToScreen.mapRect(fullRect, rect);
+ paint.setColor(Color.BLUE);
+ canvas.drawRect(fullRect, paint);
+ canvas.drawLine(fullRect.centerX(), fullRect.top,
+ fullRect.centerX(), fullRect.bottom, paint);
+ canvas.drawLine(fullRect.left, fullRect.centerY(),
+ fullRect.right, fullRect.centerY(), paint);
+ paint.setColor(Color.GREEN);
+ float dw = drawRect.width();
+ float dh = drawRect.height();
+ float dx = fullRect.centerX() - dw / 2;
+ float dy = fullRect.centerY() - dh / 2;
+ drawRect.set(dx, dy, dx + dw, dy + dh);
+ canvas.drawRect(drawRect, paint);
+ canvas.drawLine(drawRect.centerX(), drawRect.top,
+ drawRect.centerX(), drawRect.bottom, paint);
+ canvas.drawLine(drawRect.left, drawRect.centerY(),
+ drawRect.right, drawRect.centerY(), paint);
+ canvas.drawCircle(drawRect.centerX(), drawRect.centerY(),
+ mTouchPadding, paint);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
new file mode 100644
index 000000000..5186c09d7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.filtershow.editors.EditorRotate;
+import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
+import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;
+
+public class ImageRotate extends ImageShow {
+ private EditorRotate mEditorRotate;
+ private static final String TAG = ImageRotate.class.getSimpleName();
+ private FilterRotateRepresentation mLocalRep = new FilterRotateRepresentation();
+ private GeometryHolder mDrawHolder = new GeometryHolder();
+
+ public ImageRotate(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ImageRotate(Context context) {
+ super(context);
+ }
+
+ public void setFilterRotateRepresentation(FilterRotateRepresentation rep) {
+ mLocalRep = (rep == null) ? new FilterRotateRepresentation() : rep;
+ }
+
+ public void rotate() {
+ mLocalRep.rotateCW();
+ invalidate();
+ }
+
+ public FilterRotateRepresentation getFinalRepresentation() {
+ return mLocalRep;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Treat event as handled.
+ return true;
+ }
+
+ public int getLocalValue() {
+ return mLocalRep.getRotation().value();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ MasterImage master = MasterImage.getImage();
+ Bitmap image = master.getFiltersOnlyImage();
+ if (image == null) {
+ return;
+ }
+ GeometryMathUtils.initializeHolder(mDrawHolder, mLocalRep);
+ GeometryMathUtils.drawTransformedCropped(mDrawHolder, canvas, image, canvas.getWidth(),
+ canvas.getHeight());
+ }
+
+ public void setEditor(EditorRotate editorRotate) {
+ mEditorRotate = editorRotate;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
new file mode 100644
index 000000000..6278b2ad4
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.GestureDetector.OnDoubleTapListener;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+import com.android.gallery3d.filtershow.tools.SaveImage;
+
+import java.io.File;
+
+public class ImageShow extends View implements OnGestureListener,
+ ScaleGestureDetector.OnScaleGestureListener,
+ OnDoubleTapListener {
+
+ private static final String LOGTAG = "ImageShow";
+ private static final boolean ENABLE_ZOOMED_COMPARISON = false;
+
+ protected Paint mPaint = new Paint();
+ protected int mTextSize;
+ protected int mTextPadding;
+
+ protected int mBackgroundColor;
+
+ private GestureDetector mGestureDetector = null;
+ private ScaleGestureDetector mScaleGestureDetector = null;
+
+ protected Rect mImageBounds = new Rect();
+ private boolean mOriginalDisabled = false;
+ private boolean mTouchShowOriginal = false;
+ private long mTouchShowOriginalDate = 0;
+ private final long mTouchShowOriginalDelayMin = 200; // 200ms
+ private int mShowOriginalDirection = 0;
+ private static int UNVEIL_HORIZONTAL = 1;
+ private static int UNVEIL_VERTICAL = 2;
+
+ private Point mTouchDown = new Point();
+ private Point mTouch = new Point();
+ private boolean mFinishedScalingOperation = false;
+
+ private int mOriginalTextMargin;
+ private int mOriginalTextSize;
+ private String mOriginalText;
+ private boolean mZoomIn = false;
+ Point mOriginalTranslation = new Point();
+ float mOriginalScale;
+ float mStartFocusX, mStartFocusY;
+ private enum InteractionMode {
+ NONE,
+ SCALE,
+ MOVE
+ }
+ InteractionMode mInteractionMode = InteractionMode.NONE;
+
+ private FilterShowActivity mActivity = null;
+
+ public FilterShowActivity getActivity() {
+ return mActivity;
+ }
+
+ public boolean hasModifications() {
+ return MasterImage.getImage().hasModifications();
+ }
+
+ public void resetParameter() {
+ // TODO: implement reset
+ }
+
+ public void onNewValue(int parameter) {
+ invalidate();
+ }
+
+ public ImageShow(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setupImageShow(context);
+ }
+
+ public ImageShow(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setupImageShow(context);
+
+ }
+
+ public ImageShow(Context context) {
+ super(context);
+ setupImageShow(context);
+ }
+
+ private void setupImageShow(Context context) {
+ Resources res = context.getResources();
+ mTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_text_size);
+ mTextPadding = res.getDimensionPixelSize(R.dimen.photoeditor_text_padding);
+ mOriginalTextMargin = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_margin);
+ mOriginalTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_size);
+ mBackgroundColor = res.getColor(R.color.background_screen);
+ mOriginalText = res.getString(R.string.original_picture_text);
+ setupGestureDetector(context);
+ mActivity = (FilterShowActivity) context;
+ MasterImage.getImage().addObserver(this);
+ }
+
+ public void setupGestureDetector(Context context) {
+ mGestureDetector = new GestureDetector(context, this);
+ mScaleGestureDetector = new ScaleGestureDetector(context, this);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(parentWidth, parentHeight);
+ }
+
+ public ImageFilter getCurrentFilter() {
+ return MasterImage.getImage().getCurrentFilter();
+ }
+
+ /* consider moving the following 2 methods into a subclass */
+ /**
+ * This function calculates a Image to Screen Transformation matrix
+ *
+ * @param reflectRotation set true if you want the rotation encoded
+ * @return Image to Screen transformation matrix
+ */
+ protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
+ MasterImage master = MasterImage.getImage();
+ if (master.getOriginalBounds() == null) {
+ return new Matrix();
+ }
+ Matrix m = GeometryMathUtils.getImageToScreenMatrix(master.getPreset().getGeometryFilters(),
+ reflectRotation, master.getOriginalBounds(), getWidth(), getHeight());
+ Point translate = master.getTranslation();
+ float scaleFactor = master.getScaleFactor();
+ m.postTranslate(translate.x, translate.y);
+ m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
+ return m;
+ }
+
+ /**
+ * This function calculates a to Screen Image Transformation matrix
+ *
+ * @param reflectRotation set true if you want the rotation encoded
+ * @return Screen to Image transformation matrix
+ */
+ protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
+ Matrix m = getImageToScreenMatrix(reflectRotation);
+ Matrix invert = new Matrix();
+ m.invert(invert);
+ return invert;
+ }
+
+ public ImagePreset getImagePreset() {
+ return MasterImage.getImage().getPreset();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ MasterImage.getImage().setImageShowSize(getWidth(), getHeight());
+
+ float cx = canvas.getWidth()/2.0f;
+ float cy = canvas.getHeight()/2.0f;
+ float scaleFactor = MasterImage.getImage().getScaleFactor();
+ Point translation = MasterImage.getImage().getTranslation();
+
+ Matrix scalingMatrix = new Matrix();
+ scalingMatrix.postScale(scaleFactor, scaleFactor, cx, cy);
+ scalingMatrix.preTranslate(translation.x, translation.y);
+
+ RectF unscaledClipRect = new RectF(mImageBounds);
+ scalingMatrix.mapRect(unscaledClipRect, unscaledClipRect);
+
+ canvas.save();
+
+ boolean enablePartialRendering = false;
+
+ // For now, partial rendering is disabled for all filters,
+ // so no need to clip.
+ if (enablePartialRendering && !unscaledClipRect.isEmpty()) {
+ canvas.clipRect(unscaledClipRect);
+ }
+
+ canvas.save();
+ // TODO: center scale on gesture
+ canvas.scale(scaleFactor, scaleFactor, cx, cy);
+ canvas.translate(translation.x, translation.y);
+ drawImage(canvas, getFilteredImage(), true);
+ Bitmap highresPreview = MasterImage.getImage().getHighresImage();
+ if (highresPreview != null) {
+ drawImage(canvas, highresPreview, true);
+ }
+ canvas.restore();
+
+ Bitmap partialPreview = MasterImage.getImage().getPartialImage();
+ if (partialPreview != null) {
+ Rect src = new Rect(0, 0, partialPreview.getWidth(), partialPreview.getHeight());
+ Rect dest = new Rect(0, 0, getWidth(), getHeight());
+ canvas.drawBitmap(partialPreview, src, dest, mPaint);
+ }
+
+ canvas.save();
+ canvas.scale(scaleFactor, scaleFactor, cx, cy);
+ canvas.translate(translation.x, translation.y);
+ drawPartialImage(canvas, getGeometryOnlyImage());
+ canvas.restore();
+
+ canvas.restore();
+ }
+
+ public void resetImageCaches(ImageShow caller) {
+ MasterImage.getImage().updatePresets(true);
+ }
+
+ public Bitmap getFiltersOnlyImage() {
+ return MasterImage.getImage().getFiltersOnlyImage();
+ }
+
+ public Bitmap getGeometryOnlyImage() {
+ return MasterImage.getImage().getGeometryOnlyImage();
+ }
+
+ public Bitmap getFilteredImage() {
+ return MasterImage.getImage().getFilteredImage();
+ }
+
+ public void drawImage(Canvas canvas, Bitmap image, boolean updateBounds) {
+ if (image != null) {
+ Rect s = new Rect(0, 0, image.getWidth(),
+ image.getHeight());
+
+ float scale = GeometryMathUtils.scale(image.getWidth(), image.getHeight(), getWidth(),
+ getHeight());
+
+ float w = image.getWidth() * scale;
+ float h = image.getHeight() * scale;
+ float ty = (getHeight() - h) / 2.0f;
+ float tx = (getWidth() - w) / 2.0f;
+
+ Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
+ (int) (h + ty));
+ if (updateBounds) {
+ mImageBounds = d;
+ }
+ canvas.drawBitmap(image, s, d, mPaint);
+ }
+ }
+
+ public void drawPartialImage(Canvas canvas, Bitmap image) {
+ boolean showsOriginal = MasterImage.getImage().showsOriginal();
+ if (!showsOriginal && !mTouchShowOriginal)
+ return;
+ canvas.save();
+ if (image != null) {
+ if (mShowOriginalDirection == 0) {
+ if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
+ mShowOriginalDirection = UNVEIL_VERTICAL;
+ } else {
+ mShowOriginalDirection = UNVEIL_HORIZONTAL;
+ }
+ }
+
+ int px = 0;
+ int py = 0;
+ if (mShowOriginalDirection == UNVEIL_VERTICAL) {
+ px = mImageBounds.width();
+ py = mTouch.y - mImageBounds.top;
+ } else {
+ px = mTouch.x - mImageBounds.left;
+ py = mImageBounds.height();
+ if (showsOriginal) {
+ px = mImageBounds.width();
+ }
+ }
+
+ Rect d = new Rect(mImageBounds.left, mImageBounds.top,
+ mImageBounds.left + px, mImageBounds.top + py);
+ canvas.clipRect(d);
+ drawImage(canvas, image, false);
+ Paint paint = new Paint();
+ paint.setColor(Color.BLACK);
+ paint.setStrokeWidth(3);
+
+ if (mShowOriginalDirection == UNVEIL_VERTICAL) {
+ canvas.drawLine(mImageBounds.left, mTouch.y,
+ mImageBounds.right, mTouch.y, paint);
+ } else {
+ canvas.drawLine(mTouch.x, mImageBounds.top,
+ mTouch.x, mImageBounds.bottom, paint);
+ }
+
+ Rect bounds = new Rect();
+ paint.setAntiAlias(true);
+ paint.setTextSize(mOriginalTextSize);
+ paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
+ paint.setColor(Color.BLACK);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeWidth(3);
+ canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
+ mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
+ paint.setStyle(Paint.Style.FILL);
+ paint.setStrokeWidth(1);
+ paint.setColor(Color.WHITE);
+ canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
+ mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
+ }
+ canvas.restore();
+ }
+
+ public void bindAsImageLoadListener() {
+ MasterImage.getImage().addListener(this);
+ }
+
+ public void updateImage() {
+ invalidate();
+ }
+
+ public void imageLoaded() {
+ updateImage();
+ }
+
+ public void saveImage(FilterShowActivity filterShowActivity, File file) {
+ SaveImage.saveImage(getImagePreset(), filterShowActivity, file);
+ }
+
+
+ public boolean scaleInProgress() {
+ return mScaleGestureDetector.isInProgress();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+ int action = event.getAction();
+ action = action & MotionEvent.ACTION_MASK;
+
+ mGestureDetector.onTouchEvent(event);
+ boolean scaleInProgress = scaleInProgress();
+ mScaleGestureDetector.onTouchEvent(event);
+ if (mInteractionMode == InteractionMode.SCALE) {
+ return true;
+ }
+ if (!scaleInProgress() && scaleInProgress) {
+ // If we were scaling, the scale will stop but we will
+ // still issue an ACTION_UP. Let the subclasses know.
+ mFinishedScalingOperation = true;
+ }
+
+ int ex = (int) event.getX();
+ int ey = (int) event.getY();
+ if (action == MotionEvent.ACTION_DOWN) {
+ mInteractionMode = InteractionMode.MOVE;
+ mTouchDown.x = ex;
+ mTouchDown.y = ey;
+ mTouchShowOriginalDate = System.currentTimeMillis();
+ mShowOriginalDirection = 0;
+ MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
+ }
+
+ if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
+ mTouch.x = ex;
+ mTouch.y = ey;
+
+ float scaleFactor = MasterImage.getImage().getScaleFactor();
+ if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) {
+ float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
+ float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
+ Point originalTranslation = MasterImage.getImage().getOriginalTranslation();
+ Point translation = MasterImage.getImage().getTranslation();
+ translation.x = (int) (originalTranslation.x + translateX);
+ translation.y = (int) (originalTranslation.y + translateY);
+ constrainTranslation(translation, scaleFactor);
+ MasterImage.getImage().setTranslation(translation);
+ mTouchShowOriginal = false;
+ } else if (enableComparison() && !mOriginalDisabled
+ && (System.currentTimeMillis() - mTouchShowOriginalDate
+ > mTouchShowOriginalDelayMin)
+ && event.getPointerCount() == 1) {
+ mTouchShowOriginal = true;
+ }
+ }
+
+ if (action == MotionEvent.ACTION_UP) {
+ mInteractionMode = InteractionMode.NONE;
+ mTouchShowOriginal = false;
+ mTouchDown.x = 0;
+ mTouchDown.y = 0;
+ mTouch.x = 0;
+ mTouch.y = 0;
+ if (MasterImage.getImage().getScaleFactor() <= 1) {
+ MasterImage.getImage().setScaleFactor(1);
+ MasterImage.getImage().resetTranslation();
+ }
+ }
+ invalidate();
+ return true;
+ }
+
+ protected boolean enableComparison() {
+ return true;
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent arg0) {
+ mZoomIn = !mZoomIn;
+ float scale = 1.0f;
+ if (mZoomIn) {
+ scale = MasterImage.getImage().getMaxScaleFactor();
+ }
+ if (scale != MasterImage.getImage().getScaleFactor()) {
+ MasterImage.getImage().setScaleFactor(scale);
+ float translateX = (getWidth() / 2 - arg0.getX());
+ float translateY = (getHeight() / 2 - arg0.getY());
+ Point translation = MasterImage.getImage().getTranslation();
+ translation.x = (int) (mOriginalTranslation.x + translateX);
+ translation.y = (int) (mOriginalTranslation.y + translateY);
+ constrainTranslation(translation, scale);
+ MasterImage.getImage().setTranslation(translation);
+ invalidate();
+ }
+ return true;
+ }
+
+ private void constrainTranslation(Point translation, float scale) {
+ float maxTranslationX = getWidth() / scale;
+ float maxTranslationY = getHeight() / scale;
+ if (Math.abs(translation.x) > maxTranslationX) {
+ translation.x = (int) (Math.signum(translation.x) *
+ maxTranslationX);
+ if (Math.abs(translation.y) > maxTranslationY) {
+ translation.y = (int) (Math.signum(translation.y) *
+ maxTranslationY);
+ }
+
+ }
+ }
+
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent arg0) {
+ return false;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent arg0) {
+ return false;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent arg0) {
+ return false;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
+ if (mActivity == null) {
+ return false;
+ }
+ if (endEvent.getPointerCount() == 2) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent arg0) {
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
+ return false;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent arg0) {
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent arg0) {
+ return false;
+ }
+
+ public boolean useUtilityPanel() {
+ return false;
+ }
+
+ public void openUtilityPanel(final LinearLayout accessoryViewList) {
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ MasterImage img = MasterImage.getImage();
+ float scaleFactor = img.getScaleFactor();
+
+ scaleFactor = scaleFactor * detector.getScaleFactor();
+ if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
+ scaleFactor = MasterImage.getImage().getMaxScaleFactor();
+ }
+ if (scaleFactor < 0.5) {
+ scaleFactor = 0.5f;
+ }
+ MasterImage.getImage().setScaleFactor(scaleFactor);
+ scaleFactor = img.getScaleFactor();
+ float focusx = detector.getFocusX();
+ float focusy = detector.getFocusY();
+ float translateX = (focusx - mStartFocusX) / scaleFactor;
+ float translateY = (focusy - mStartFocusY) / scaleFactor;
+ Point translation = MasterImage.getImage().getTranslation();
+ translation.x = (int) (mOriginalTranslation.x + translateX);
+ translation.y = (int) (mOriginalTranslation.y + translateY);
+ constrainTranslation(translation, scaleFactor);
+ MasterImage.getImage().setTranslation(translation);
+
+ invalidate();
+ return true;
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ Point pos = MasterImage.getImage().getTranslation();
+ mOriginalTranslation.x = pos.x;
+ mOriginalTranslation.y = pos.y;
+ mOriginalScale = MasterImage.getImage().getScaleFactor();
+ mStartFocusX = detector.getFocusX();
+ mStartFocusY = detector.getFocusY();
+ mInteractionMode = InteractionMode.SCALE;
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mInteractionMode = InteractionMode.NONE;
+ if (MasterImage.getImage().getScaleFactor() < 1) {
+ MasterImage.getImage().setScaleFactor(1);
+ invalidate();
+ }
+ }
+
+ public boolean didFinishScalingOperation() {
+ if (mFinishedScalingOperation) {
+ mFinishedScalingOperation = false;
+ return true;
+ }
+ 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
index 000000000..ff75dcc09
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.filtershow.editors.EditorStraighten;
+import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
+import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+
+public class ImageStraighten extends ImageShow {
+ private static final String TAG = ImageStraighten.class.getSimpleName();
+ private float mBaseAngle = 0;
+ private float mAngle = 0;
+ private float mInitialAngle = 0;
+ private boolean mFirstDrawSinceUp = false;
+ private EditorStraighten mEditorStraighten;
+ private FilterStraightenRepresentation mLocalRep = new FilterStraightenRepresentation();
+ private RectF mPriorCropAtUp = new RectF();
+ private RectF mDrawRect = new RectF();
+ private Path mDrawPath = new Path();
+ private GeometryHolder mDrawHolder = new GeometryHolder();
+ private enum MODES {
+ NONE, MOVE
+ }
+ private MODES mState = MODES.NONE;
+ private static final float MAX_STRAIGHTEN_ANGLE
+ = FilterStraightenRepresentation.MAX_STRAIGHTEN_ANGLE;
+ private static final float MIN_STRAIGHTEN_ANGLE
+ = FilterStraightenRepresentation.MIN_STRAIGHTEN_ANGLE;
+ private float mCurrentX;
+ private float mCurrentY;
+ private float mTouchCenterX;
+ private float mTouchCenterY;
+ private RectF mCrop = new RectF();
+ private final Paint mPaint = new Paint();
+
+ public ImageStraighten(Context context) {
+ super(context);
+ }
+
+ public ImageStraighten(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setFilterStraightenRepresentation(FilterStraightenRepresentation rep) {
+ mLocalRep = (rep == null) ? new FilterStraightenRepresentation() : rep;
+ mInitialAngle = mBaseAngle = mAngle = mLocalRep.getStraighten();
+ }
+
+ public Collection<FilterRepresentation> getFinalRepresentation() {
+ ArrayList<FilterRepresentation> reps = new ArrayList<FilterRepresentation>(2);
+ reps.add(mLocalRep);
+ if (mInitialAngle != mLocalRep.getStraighten()) {
+ reps.add(new FilterCropRepresentation(mCrop));
+ }
+ return reps;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+
+ switch (event.getActionMasked()) {
+ case (MotionEvent.ACTION_DOWN):
+ if (mState == MODES.NONE) {
+ mTouchCenterX = x;
+ mTouchCenterY = y;
+ mCurrentX = x;
+ mCurrentY = y;
+ mState = MODES.MOVE;
+ mBaseAngle = mAngle;
+ }
+ break;
+ case (MotionEvent.ACTION_UP):
+ if (mState == MODES.MOVE) {
+ mState = MODES.NONE;
+ mCurrentX = x;
+ mCurrentY = y;
+ computeValue();
+ mFirstDrawSinceUp = true;
+ }
+ break;
+ case (MotionEvent.ACTION_MOVE):
+ if (mState == MODES.MOVE) {
+ mCurrentX = x;
+ mCurrentY = y;
+ computeValue();
+ }
+ break;
+ default:
+ break;
+ }
+ invalidate();
+ return true;
+ }
+
+ private static float angleFor(float dx, float dy) {
+ return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
+ }
+
+ private float getCurrentTouchAngle() {
+ float centerX = getWidth() / 2f;
+ float centerY = getHeight() / 2f;
+ if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
+ return 0;
+ }
+ float dX1 = mTouchCenterX - centerX;
+ float dY1 = mTouchCenterY - centerY;
+ float dX2 = mCurrentX - centerX;
+ float dY2 = mCurrentY - centerY;
+ float angleA = angleFor(dX1, dY1);
+ float angleB = angleFor(dX2, dY2);
+ return (angleB - angleA) % 360;
+ }
+
+ private void computeValue() {
+ float angle = getCurrentTouchAngle();
+ mAngle = (mBaseAngle - angle) % 360;
+ mAngle = Math.max(MIN_STRAIGHTEN_ANGLE, mAngle);
+ mAngle = Math.min(MAX_STRAIGHTEN_ANGLE, mAngle);
+ }
+
+ private static void getUntranslatedStraightenCropBounds(RectF outRect, float straightenAngle) {
+ float deg = straightenAngle;
+ if (deg < 0) {
+ deg = -deg;
+ }
+ double a = Math.toRadians(deg);
+ double sina = Math.sin(a);
+ double cosa = Math.cos(a);
+ double rw = outRect.width();
+ double rh = outRect.height();
+ 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);
+ outRect.set(left, top, right, bottom);
+ }
+
+ private void updateCurrentCrop(Matrix m, GeometryHolder h, RectF tmp, int imageWidth,
+ int imageHeight, int viewWidth, int viewHeight) {
+ if (GeometryMathUtils.needsDimensionSwap(h.rotation)) {
+ tmp.set(0, 0, imageHeight, imageWidth);
+ } else {
+ tmp.set(0, 0, imageWidth, imageHeight);
+ }
+ float scale = GeometryMathUtils.scale(imageWidth, imageHeight, viewWidth, viewHeight);
+ GeometryMathUtils.scaleRect(tmp, scale);
+ getUntranslatedStraightenCropBounds(tmp, mAngle);
+ tmp.offset(viewWidth / 2f - tmp.centerX(), viewHeight / 2f - tmp.centerY());
+ h.straighten = 0;
+ Matrix m1 = GeometryMathUtils.getFullGeometryToScreenMatrix(h, imageWidth,
+ imageHeight, viewWidth, viewHeight);
+ m.reset();
+ m1.invert(m);
+ mCrop.set(tmp);
+ m.mapRect(mCrop);
+ FilterCropRepresentation.findNormalizedCrop(mCrop, imageWidth, imageHeight);
+ }
+
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ MasterImage master = MasterImage.getImage();
+ Bitmap image = master.getFiltersOnlyImage();
+ if (image == null) {
+ return;
+ }
+ GeometryMathUtils.initializeHolder(mDrawHolder, mLocalRep);
+ mDrawHolder.straighten = mAngle;
+ int imageWidth = image.getWidth();
+ int imageHeight = image.getHeight();
+ int viewWidth = canvas.getWidth();
+ int viewHeight = canvas.getHeight();
+
+ // Get matrix for drawing bitmap
+ Matrix m = GeometryMathUtils.getFullGeometryToScreenMatrix(mDrawHolder, imageWidth,
+ imageHeight, viewWidth, viewHeight);
+ mPaint.reset();
+ mPaint.setAntiAlias(true);
+ mPaint.setFilterBitmap(true);
+ canvas.drawBitmap(image, m, mPaint);
+
+ mPaint.setFilterBitmap(false);
+ mPaint.setColor(Color.WHITE);
+ mPaint.setStrokeWidth(2);
+ mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ updateCurrentCrop(m, mDrawHolder, mDrawRect, imageWidth,
+ imageHeight, viewWidth, viewHeight);
+ if (mFirstDrawSinceUp) {
+ mPriorCropAtUp.set(mCrop);
+ mLocalRep.setStraighten(mAngle);
+ mFirstDrawSinceUp = false;
+ }
+
+ // Draw the grid
+ if (mState == MODES.MOVE) {
+ canvas.save();
+ canvas.clipRect(mDrawRect);
+ int n = 16;
+ float step = viewWidth / n;
+ float p = 0;
+ for (int i = 1; i < n; i++) {
+ p = i * step;
+ mPaint.setAlpha(60);
+ canvas.drawLine(p, 0, p, viewHeight, mPaint);
+ canvas.drawLine(0, p, viewHeight, p, mPaint);
+ }
+ canvas.restore();
+ }
+ mPaint.reset();
+ mPaint.setColor(Color.WHITE);
+ mPaint.setStyle(Style.STROKE);
+ mPaint.setStrokeWidth(3);
+ mDrawPath.reset();
+ mDrawPath.addRect(mDrawRect, Path.Direction.CW);
+ canvas.drawPath(mDrawPath, mPaint);
+ }
+
+ public void setEditor(EditorStraighten editorStraighten) {
+ mEditorStraighten = editorStraighten;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java b/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java
new file mode 100644
index 000000000..25a0a9073
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+
+import com.android.gallery3d.filtershow.editors.BasicEditor;
+import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
+import com.android.gallery3d.filtershow.filters.FilterTinyPlanetRepresentation;
+
+public class ImageTinyPlanet extends ImageShow {
+ private static final String LOGTAG = "ImageTinyPlanet";
+
+ private float mTouchCenterX = 0;
+ private float mTouchCenterY = 0;
+ private float mCurrentX = 0;
+ private float mCurrentY = 0;
+ private float mCenterX = 0;
+ private float mCenterY = 0;
+ private float mStartAngle = 0;
+ private FilterTinyPlanetRepresentation mTinyPlanetRep;
+ private EditorTinyPlanet mEditorTinyPlanet;
+ private ScaleGestureDetector mScaleGestureDetector = null;
+ boolean mInScale = false;
+ RectF mDestRect = new RectF();
+
+ OnScaleGestureListener mScaleGestureListener = new OnScaleGestureListener() {
+ private float mScale = 100;
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mInScale = false;
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ mInScale = true;
+ mScale = mTinyPlanetRep.getValue();
+ return true;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ int value = mTinyPlanetRep.getValue();
+ mScale *= detector.getScaleFactor();
+ value = (int) (mScale);
+ value = Math.min(mTinyPlanetRep.getMaximum(), value);
+ value = Math.max(mTinyPlanetRep.getMinimum(), value);
+ mTinyPlanetRep.setValue(value);
+ invalidate();
+ mEditorTinyPlanet.commitLocalRepresentation();
+ mEditorTinyPlanet.updateUI();
+ return true;
+ }
+ };
+
+ public ImageTinyPlanet(Context context) {
+ super(context);
+ mScaleGestureDetector = new ScaleGestureDetector(context, mScaleGestureListener);
+ }
+
+ public ImageTinyPlanet(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mScaleGestureDetector = new ScaleGestureDetector(context,mScaleGestureListener );
+ }
+
+ protected static float angleFor(float dx, float dy) {
+ return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
+ }
+
+ protected float getCurrentTouchAngle() {
+ if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
+ return 0;
+ }
+ 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);
+ return (float) (((angleB - angleA) % 360) * Math.PI / 180);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ mCurrentX = x;
+ mCurrentY = y;
+ mCenterX = getWidth() / 2;
+ mCenterY = getHeight() / 2;
+ mScaleGestureDetector.onTouchEvent(event);
+ if (mInScale) {
+ return true;
+ }
+ switch (event.getActionMasked()) {
+ case (MotionEvent.ACTION_DOWN):
+ mTouchCenterX = x;
+ mTouchCenterY = y;
+ mStartAngle = mTinyPlanetRep.getAngle();
+ break;
+
+ case (MotionEvent.ACTION_MOVE):
+ mTinyPlanetRep.setAngle(mStartAngle + getCurrentTouchAngle());
+ break;
+ }
+ invalidate();
+ mEditorTinyPlanet.commitLocalRepresentation();
+ return true;
+ }
+
+ public void setRepresentation(FilterTinyPlanetRepresentation tinyPlanetRep) {
+ mTinyPlanetRep = tinyPlanetRep;
+ }
+
+ public void setEditor(BasicEditor editorTinyPlanet) {
+ mEditorTinyPlanet = (EditorTinyPlanet) editorTinyPlanet;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ Bitmap bitmap = MasterImage.getImage().getHighresImage();
+ if (bitmap == null) {
+ bitmap = MasterImage.getImage().getFilteredImage();
+ }
+
+ if (bitmap != null) {
+ display(canvas, bitmap);
+ }
+ }
+
+ private void display(Canvas canvas, Bitmap bitmap) {
+ float sw = canvas.getWidth();
+ float sh = canvas.getHeight();
+ float iw = bitmap.getWidth();
+ float ih = bitmap.getHeight();
+ float nsw = sw;
+ float nsh = sh;
+
+ if (sw * ih > sh * iw) {
+ nsw = sh * iw / ih;
+ } else {
+ nsh = sw * ih / iw;
+ }
+
+ mDestRect.left = (sw - nsw) / 2;
+ mDestRect.top = (sh - nsh) / 2;
+ mDestRect.right = sw - mDestRect.left;
+ mDestRect.bottom = sh - mDestRect.top;
+
+ canvas.drawBitmap(bitmap, null, mDestRect, mPaint);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
new file mode 100644
index 000000000..518969ee1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.filtershow.editors.EditorVignette;
+import com.android.gallery3d.filtershow.filters.FilterVignetteRepresentation;
+
+public class ImageVignette extends ImageShow {
+ private static final String LOGTAG = "ImageVignette";
+
+ private FilterVignetteRepresentation mVignetteRep;
+ private EditorVignette mEditorVignette;
+
+ private int mActiveHandle = -1;
+
+ EclipseControl mElipse;
+
+ public ImageVignette(Context context) {
+ super(context);
+ mElipse = new EclipseControl(context);
+ }
+
+ public ImageVignette(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mElipse = new EclipseControl(context);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int mask = event.getActionMasked();
+ if (mActiveHandle == -1) {
+ if (MotionEvent.ACTION_DOWN != mask) {
+ return super.onTouchEvent(event);
+ }
+ if (event.getPointerCount() == 1) {
+ mActiveHandle = mElipse.getCloseHandle(event.getX(), event.getY());
+ }
+ if (mActiveHandle == -1) {
+ return super.onTouchEvent(event);
+ }
+ } else {
+ switch (mask) {
+ case MotionEvent.ACTION_UP:
+ mActiveHandle = -1;
+ break;
+ case MotionEvent.ACTION_DOWN:
+ break;
+ }
+ }
+ float x = event.getX();
+ float y = event.getY();
+
+ mElipse.setScrToImageMatrix(getScreenToImageMatrix(true));
+
+ boolean didComputeEllipses = false;
+ switch (mask) {
+ case (MotionEvent.ACTION_DOWN):
+ mElipse.actionDown(x, y, mVignetteRep);
+ break;
+ case (MotionEvent.ACTION_UP):
+ case (MotionEvent.ACTION_MOVE):
+ mElipse.actionMove(mActiveHandle, x, y, mVignetteRep);
+ setRepresentation(mVignetteRep);
+ didComputeEllipses = true;
+ break;
+ }
+ if (!didComputeEllipses) {
+ computeEllipses();
+ }
+ invalidate();
+ return true;
+ }
+
+ public void setRepresentation(FilterVignetteRepresentation vignetteRep) {
+ mVignetteRep = vignetteRep;
+ computeEllipses();
+ }
+
+ public void computeEllipses() {
+ if (mVignetteRep == null) {
+ return;
+ }
+ Matrix toImg = getScreenToImageMatrix(false);
+ Matrix toScr = new Matrix();
+ toImg.invert(toScr);
+
+ float[] c = new float[] {
+ mVignetteRep.getCenterX(), mVignetteRep.getCenterY() };
+ if (Float.isNaN(c[0])) {
+ float cx = MasterImage.getImage().getOriginalBounds().width() / 2;
+ float cy = MasterImage.getImage().getOriginalBounds().height() / 2;
+ float rx = Math.min(cx, cy) * .8f;
+ float ry = rx;
+ mVignetteRep.setCenter(cx, cy);
+ mVignetteRep.setRadius(rx, ry);
+
+ c[0] = cx;
+ c[1] = cy;
+ toScr.mapPoints(c);
+ if (getWidth() != 0) {
+ mElipse.setCenter(c[0], c[1]);
+ mElipse.setRadius(c[0] * 0.8f, c[1] * 0.8f);
+ }
+ } else {
+
+ toScr.mapPoints(c);
+
+ mElipse.setCenter(c[0], c[1]);
+ mElipse.setRadius(toScr.mapRadius(mVignetteRep.getRadiusX()),
+ toScr.mapRadius(mVignetteRep.getRadiusY()));
+ }
+ mEditorVignette.commitLocalRepresentation();
+ }
+
+ public void setEditor(EditorVignette editorVignette) {
+ mEditorVignette = editorVignette;
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ computeEllipses();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mVignetteRep == null) {
+ return;
+ }
+ Matrix toImg = getScreenToImageMatrix(false);
+ Matrix toScr = new Matrix();
+ toImg.invert(toScr);
+ float[] c = new float[] {
+ mVignetteRep.getCenterX(), mVignetteRep.getCenterY() };
+ toScr.mapPoints(c);
+ mElipse.setCenter(c[0], c[1]);
+ mElipse.setRadius(toScr.mapRadius(mVignetteRep.getRadiusX()),
+ toScr.mapRadius(mVignetteRep.getRadiusY()));
+
+ mElipse.draw(canvas);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/Line.java b/src/com/android/gallery3d/filtershow/imageshow/Line.java
new file mode 100644
index 000000000..a767bd809
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/Line.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+public interface Line {
+ void setPoint1(float x, float y);
+ void setPoint2(float x, float y);
+ float getPoint1X();
+ float getPoint1Y();
+ float getPoint2X();
+ float getPoint2Y();
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
new file mode 100644
index 000000000..92e57bfc1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
@@ -0,0 +1,581 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.history.HistoryItem;
+import com.android.gallery3d.filtershow.history.HistoryManager;
+import com.android.gallery3d.filtershow.pipeline.Buffer;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+import com.android.gallery3d.filtershow.pipeline.RenderingRequest;
+import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
+import com.android.gallery3d.filtershow.pipeline.SharedBuffer;
+import com.android.gallery3d.filtershow.pipeline.SharedPreset;
+import com.android.gallery3d.filtershow.state.StateAdapter;
+
+import java.util.Vector;
+
+public class MasterImage implements RenderingRequestCaller {
+
+ private static final String LOGTAG = "MasterImage";
+ private boolean DEBUG = false;
+ private static final boolean DISABLEZOOM = false;
+ public static final int SMALL_BITMAP_DIM = 160;
+ public static final int MAX_BITMAP_DIM = 900;
+ private static MasterImage sMasterImage = null;
+
+ private boolean mSupportsHighRes = false;
+
+ private ImageFilter mCurrentFilter = null;
+ private ImagePreset mPreset = null;
+ private ImagePreset mLoadedPreset = null;
+ private ImagePreset mGeometryOnlyPreset = null;
+ private ImagePreset mFiltersOnlyPreset = null;
+
+ private SharedBuffer mPreviewBuffer = new SharedBuffer();
+ private SharedPreset mPreviewPreset = new SharedPreset();
+
+ private Bitmap mOriginalBitmapSmall = null;
+ private Bitmap mOriginalBitmapLarge = null;
+ private Bitmap mOriginalBitmapHighres = null;
+ private int mOrientation;
+ private Rect mOriginalBounds;
+ private final Vector<ImageShow> mLoadListeners = new Vector<ImageShow>();
+ private Uri mUri = null;
+ private int mZoomOrientation = ImageLoader.ORI_NORMAL;
+
+ private Bitmap mGeometryOnlyBitmap = null;
+ private Bitmap mFiltersOnlyBitmap = null;
+ private Bitmap mPartialBitmap = null;
+ private Bitmap mHighresBitmap = null;
+
+ private HistoryManager mHistory = null;
+ private StateAdapter mState = null;
+
+ private FilterShowActivity mActivity = null;
+
+ private Vector<ImageShow> mObservers = new Vector<ImageShow>();
+ private FilterRepresentation mCurrentFilterRepresentation;
+
+ private float mScaleFactor = 1.0f;
+ private float mMaxScaleFactor = 3.0f; // TODO: base this on the current view / image
+ private Point mTranslation = new Point();
+ private Point mOriginalTranslation = new Point();
+
+ private Point mImageShowSize = new Point();
+
+ private boolean mShowsOriginal;
+
+ private MasterImage() {
+ }
+
+ // TODO: remove singleton
+ public static void setMaster(MasterImage master) {
+ sMasterImage = master;
+ }
+
+ public static MasterImage getImage() {
+ if (sMasterImage == null) {
+ sMasterImage = new MasterImage();
+ }
+ return sMasterImage;
+ }
+
+ public Bitmap getOriginalBitmapSmall() {
+ return mOriginalBitmapSmall;
+ }
+
+ public Bitmap getOriginalBitmapLarge() {
+ return mOriginalBitmapLarge;
+ }
+
+ public Bitmap getOriginalBitmapHighres() {
+ return mOriginalBitmapHighres;
+ }
+
+ public void setOriginalBitmapHighres(Bitmap mOriginalBitmapHighres) {
+ this.mOriginalBitmapHighres = mOriginalBitmapHighres;
+ }
+
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ public Rect getOriginalBounds() {
+ return mOriginalBounds;
+ }
+
+ public void setOriginalBounds(Rect r) {
+ mOriginalBounds = r;
+ }
+
+ public Uri getUri() {
+ return mUri;
+ }
+
+ public void setUri(Uri uri) {
+ mUri = uri;
+ }
+
+ public int getZoomOrientation() {
+ return mZoomOrientation;
+ }
+
+ public void addListener(ImageShow imageShow) {
+ if (!mLoadListeners.contains(imageShow)) {
+ mLoadListeners.add(imageShow);
+ }
+ }
+
+ public void warnListeners() {
+ mActivity.runOnUiThread(mWarnListenersRunnable);
+ }
+
+ private Runnable mWarnListenersRunnable = new Runnable() {
+ @Override
+ public void run() {
+ for (int i = 0; i < mLoadListeners.size(); i++) {
+ ImageShow imageShow = mLoadListeners.elementAt(i);
+ imageShow.imageLoaded();
+ }
+ invalidatePreview();
+ }
+ };
+
+ public boolean loadBitmap(Uri uri, int size) {
+ setUri(uri);
+ mOrientation = ImageLoader.getMetadataOrientation(mActivity, uri);
+ Rect originalBounds = new Rect();
+ mOriginalBitmapLarge = ImageLoader.loadOrientedConstrainedBitmap(uri, mActivity,
+ Math.min(MAX_BITMAP_DIM, size),
+ mOrientation, originalBounds);
+ setOriginalBounds(originalBounds);
+ if (mOriginalBitmapLarge == null) {
+ return false;
+ }
+ int sw = SMALL_BITMAP_DIM;
+ int sh = (int) (sw * (float) mOriginalBitmapLarge.getHeight() / mOriginalBitmapLarge
+ .getWidth());
+ mOriginalBitmapSmall = Bitmap.createScaledBitmap(mOriginalBitmapLarge, sw, sh, true);
+ mZoomOrientation = mOrientation;
+ warnListeners();
+ return true;
+ }
+
+ public void setSupportsHighRes(boolean value) {
+ mSupportsHighRes = value;
+ }
+
+ public void addObserver(ImageShow observer) {
+ if (mObservers.contains(observer)) {
+ return;
+ }
+ mObservers.add(observer);
+ }
+
+ public void setActivity(FilterShowActivity activity) {
+ mActivity = activity;
+ }
+
+ public FilterShowActivity getActivity() {
+ return mActivity;
+ }
+
+ public synchronized ImagePreset getPreset() {
+ return mPreset;
+ }
+
+ public synchronized ImagePreset getGeometryPreset() {
+ return mGeometryOnlyPreset;
+ }
+
+ public synchronized ImagePreset getFiltersOnlyPreset() {
+ return mFiltersOnlyPreset;
+ }
+
+ public synchronized void setPreset(ImagePreset preset,
+ FilterRepresentation change,
+ boolean addToHistory) {
+ if (DEBUG) {
+ preset.showFilters();
+ }
+ mPreset = preset;
+ mPreset.fillImageStateAdapter(mState);
+ if (addToHistory) {
+ HistoryItem historyItem = new HistoryItem(mPreset, change);
+ mHistory.addHistoryItem(historyItem);
+ }
+ updatePresets(true);
+ mActivity.updateCategories();
+ }
+
+ public void onHistoryItemClick(int position) {
+ HistoryItem historyItem = mHistory.getItem(position);
+ // We need a copy from the history
+ ImagePreset newPreset = new ImagePreset(historyItem.getImagePreset());
+ // don't need to add it to the history
+ setPreset(newPreset, historyItem.getFilterRepresentation(), false);
+ mHistory.setCurrentPreset(position);
+ }
+
+ public HistoryManager getHistory() {
+ return mHistory;
+ }
+
+ public StateAdapter getState() {
+ return mState;
+ }
+
+ public void setHistoryManager(HistoryManager adapter) {
+ mHistory = adapter;
+ }
+
+ public void setStateAdapter(StateAdapter adapter) {
+ mState = adapter;
+ }
+
+ public void setCurrentFilter(ImageFilter filter) {
+ mCurrentFilter = filter;
+ }
+
+ public ImageFilter getCurrentFilter() {
+ return mCurrentFilter;
+ }
+
+ public synchronized boolean hasModifications() {
+ // TODO: We need to have a better same effects check to see if two
+ // presets are functionally the same. Right now, we are relying on a
+ // stricter check as equals().
+ ImagePreset loadedPreset = getLoadedPreset();
+ if (mPreset == null) {
+ if (loadedPreset == null) {
+ return false;
+ } else {
+ return loadedPreset.hasModifications();
+ }
+ } else {
+ if (loadedPreset == null) {
+ return mPreset.hasModifications();
+ } else {
+ return !mPreset.equals(loadedPreset);
+ }
+ }
+ }
+
+ public SharedBuffer getPreviewBuffer() {
+ return mPreviewBuffer;
+ }
+
+ public SharedPreset getPreviewPreset() {
+ return mPreviewPreset;
+ }
+
+ public Bitmap getFilteredImage() {
+ mPreviewBuffer.swapConsumerIfNeeded(); // get latest bitmap
+ Buffer consumer = mPreviewBuffer.getConsumer();
+ if (consumer != null) {
+ return consumer.getBitmap();
+ }
+ return null;
+ }
+
+ public Bitmap getFiltersOnlyImage() {
+ return mFiltersOnlyBitmap;
+ }
+
+ public Bitmap getGeometryOnlyImage() {
+ return mGeometryOnlyBitmap;
+ }
+
+ public Bitmap getPartialImage() {
+ return mPartialBitmap;
+ }
+
+ public Bitmap getHighresImage() {
+ return mHighresBitmap;
+ }
+
+ public void notifyObservers() {
+ for (ImageShow observer : mObservers) {
+ observer.invalidate();
+ }
+ }
+
+ public void updatePresets(boolean force) {
+ if (force || mGeometryOnlyPreset == null) {
+ ImagePreset newPreset = new ImagePreset(mPreset);
+ newPreset.setDoApplyFilters(false);
+ newPreset.setDoApplyGeometry(true);
+ if (force || mGeometryOnlyPreset == null
+ || !newPreset.same(mGeometryOnlyPreset)) {
+ mGeometryOnlyPreset = newPreset;
+ RenderingRequest.post(mActivity, getOriginalBitmapLarge(),
+ mGeometryOnlyPreset, RenderingRequest.GEOMETRY_RENDERING, this);
+ }
+ }
+ if (force || mFiltersOnlyPreset == null) {
+ ImagePreset newPreset = new ImagePreset(mPreset);
+ newPreset.setDoApplyFilters(true);
+ newPreset.setDoApplyGeometry(false);
+ if (force || mFiltersOnlyPreset == null
+ || !newPreset.same(mFiltersOnlyPreset)) {
+ mFiltersOnlyPreset = newPreset;
+ RenderingRequest.post(mActivity, MasterImage.getImage().getOriginalBitmapLarge(),
+ mFiltersOnlyPreset, RenderingRequest.FILTERS_RENDERING, this);
+ }
+ }
+ invalidatePreview();
+ }
+
+ public FilterRepresentation getCurrentFilterRepresentation() {
+ return mCurrentFilterRepresentation;
+ }
+
+ public void setCurrentFilterRepresentation(FilterRepresentation currentFilterRepresentation) {
+ mCurrentFilterRepresentation = currentFilterRepresentation;
+ }
+
+ public void invalidateFiltersOnly() {
+ mFiltersOnlyPreset = null;
+ updatePresets(false);
+ }
+
+ public void invalidatePartialPreview() {
+ if (mPartialBitmap != null) {
+ mPartialBitmap = null;
+ notifyObservers();
+ }
+ }
+
+ public void invalidateHighresPreview() {
+ if (mHighresBitmap != null) {
+ mHighresBitmap = null;
+ notifyObservers();
+ }
+ }
+
+ public void invalidatePreview() {
+ mPreviewPreset.enqueuePreset(mPreset);
+ mPreviewBuffer.invalidate();
+ invalidatePartialPreview();
+ invalidateHighresPreview();
+ needsUpdatePartialPreview();
+ needsUpdateHighResPreview();
+ mActivity.getProcessingService().updatePreviewBuffer();
+ }
+
+ public void setImageShowSize(int w, int h) {
+ if (mImageShowSize.x != w || mImageShowSize.y != h) {
+ mImageShowSize.set(w, h);
+ needsUpdatePartialPreview();
+ needsUpdateHighResPreview();
+ }
+ }
+
+ private Matrix getImageToScreenMatrix(boolean reflectRotation) {
+ if (getOriginalBounds() == null || mImageShowSize.x == 0 || mImageShowSize.y == 0) {
+ return new Matrix();
+ }
+ Matrix m = GeometryMathUtils.getImageToScreenMatrix(mPreset.getGeometryFilters(),
+ reflectRotation, getOriginalBounds(), mImageShowSize.x, mImageShowSize.y);
+ if (m == null) {
+ m = new Matrix();
+ m.reset();
+ return m;
+ }
+ Point translate = getTranslation();
+ float scaleFactor = getScaleFactor();
+ m.postTranslate(translate.x, translate.y);
+ m.postScale(scaleFactor, scaleFactor, mImageShowSize.x / 2.0f, mImageShowSize.y / 2.0f);
+ return m;
+ }
+
+ private Matrix getScreenToImageMatrix(boolean reflectRotation) {
+ Matrix m = getImageToScreenMatrix(reflectRotation);
+ Matrix invert = new Matrix();
+ m.invert(invert);
+ return invert;
+ }
+
+ public void needsUpdateHighResPreview() {
+ if (!mSupportsHighRes) {
+ return;
+ }
+ if (mActivity.getProcessingService() == null) {
+ return;
+ }
+ mActivity.getProcessingService().postHighresRenderingRequest(mPreset,
+ getScaleFactor(), this);
+ invalidateHighresPreview();
+ }
+
+ public void needsUpdatePartialPreview() {
+ if (mPreset == null) {
+ return;
+ }
+ if (!mPreset.canDoPartialRendering()) {
+ invalidatePartialPreview();
+ return;
+ }
+ Matrix m = getScreenToImageMatrix(true);
+ RectF r = new RectF(0, 0, mImageShowSize.x, mImageShowSize.y);
+ RectF dest = new RectF();
+ m.mapRect(dest, r);
+ Rect bounds = new Rect();
+ dest.roundOut(bounds);
+ RenderingRequest.post(mActivity, null, mPreset, RenderingRequest.PARTIAL_RENDERING,
+ this, bounds, new Rect(0, 0, mImageShowSize.x, mImageShowSize.y));
+ invalidatePartialPreview();
+ }
+
+ @Override
+ public void available(RenderingRequest request) {
+ if (request.getBitmap() == null) {
+ return;
+ }
+
+ boolean needsCheckModification = false;
+ if (request.getType() == RenderingRequest.GEOMETRY_RENDERING) {
+ mGeometryOnlyBitmap = request.getBitmap();
+ needsCheckModification = true;
+ }
+ if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
+ mFiltersOnlyBitmap = request.getBitmap();
+ notifyObservers();
+ needsCheckModification = true;
+ }
+ if (request.getType() == RenderingRequest.PARTIAL_RENDERING
+ && request.getScaleFactor() == getScaleFactor()) {
+ mPartialBitmap = request.getBitmap();
+ notifyObservers();
+ needsCheckModification = true;
+ }
+ if (request.getType() == RenderingRequest.HIGHRES_RENDERING) {
+ mHighresBitmap = request.getBitmap();
+ notifyObservers();
+ needsCheckModification = true;
+ }
+ if (needsCheckModification) {
+ mActivity.enableSave(hasModifications());
+ }
+ }
+
+ public static void reset() {
+ sMasterImage = null;
+ }
+
+ public float getScaleFactor() {
+ return mScaleFactor;
+ }
+
+ public void setScaleFactor(float scaleFactor) {
+ if (DISABLEZOOM) {
+ return;
+ }
+ if (scaleFactor == mScaleFactor) {
+ return;
+ }
+ mScaleFactor = scaleFactor;
+ invalidatePartialPreview();
+ }
+
+ public Point getTranslation() {
+ return mTranslation;
+ }
+
+ public void setTranslation(Point translation) {
+ if (DISABLEZOOM) {
+ mTranslation.x = 0;
+ mTranslation.y = 0;
+ return;
+ }
+ mTranslation.x = translation.x;
+ mTranslation.y = translation.y;
+ needsUpdatePartialPreview();
+ }
+
+ public Point getOriginalTranslation() {
+ return mOriginalTranslation;
+ }
+
+ public void setOriginalTranslation(Point originalTranslation) {
+ if (DISABLEZOOM) {
+ return;
+ }
+ mOriginalTranslation.x = originalTranslation.x;
+ mOriginalTranslation.y = originalTranslation.y;
+ }
+
+ public void resetTranslation() {
+ mTranslation.x = 0;
+ mTranslation.y = 0;
+ needsUpdatePartialPreview();
+ }
+
+ public Bitmap getThumbnailBitmap() {
+ return getOriginalBitmapSmall();
+ }
+
+ public Bitmap getLargeThumbnailBitmap() {
+ return getOriginalBitmapLarge();
+ }
+
+ public float getMaxScaleFactor() {
+ if (DISABLEZOOM) {
+ return 1;
+ }
+ return mMaxScaleFactor;
+ }
+
+ public void setMaxScaleFactor(float maxScaleFactor) {
+ mMaxScaleFactor = maxScaleFactor;
+ }
+
+ public boolean supportsHighRes() {
+ return mSupportsHighRes;
+ }
+
+ public void setShowsOriginal(boolean value) {
+ mShowsOriginal = value;
+ notifyObservers();
+ }
+
+ public boolean showsOriginal() {
+ return mShowsOriginal;
+ }
+
+ public void setLoadedPreset(ImagePreset preset) {
+ mLoadedPreset = preset;
+ }
+
+ public ImagePreset getLoadedPreset() {
+ return mLoadedPreset;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/Oval.java b/src/com/android/gallery3d/filtershow/imageshow/Oval.java
new file mode 100644
index 000000000..28f278f1c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/Oval.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+public interface Oval {
+ void setCenter(float x, float y);
+ void setRadius(float w, float h);
+ float getCenterX();
+ float getCenterY();
+ float getRadiusX();
+ float getRadiusY();
+ void setRadiusY(float y);
+ void setRadiusX(float x);
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/Spline.java b/src/com/android/gallery3d/filtershow/imageshow/Spline.java
new file mode 100644
index 000000000..3c27a4d0f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/Spline.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.Vector;
+
+public class Spline {
+ private final Vector<ControlPoint> mPoints;
+ private static Drawable mCurveHandle;
+ private static int mCurveHandleSize;
+ private static int mCurveWidth;
+
+ public static final int RGB = 0;
+ public static final int RED = 1;
+ public static final int GREEN = 2;
+ public static final int BLUE = 3;
+ private static final String LOGTAG = "Spline";
+
+ private final Paint gPaint = new Paint();
+ private ControlPoint mCurrentControlPoint = null;
+
+ public Spline() {
+ mPoints = new Vector<ControlPoint>();
+ }
+
+ public Spline(Spline spline) {
+ mPoints = new Vector<ControlPoint>();
+ for (int i = 0; i < spline.mPoints.size(); i++) {
+ ControlPoint p = spline.mPoints.elementAt(i);
+ ControlPoint newPoint = new ControlPoint(p);
+ mPoints.add(newPoint);
+ if (spline.mCurrentControlPoint == p) {
+ mCurrentControlPoint = newPoint;
+ }
+ }
+ Collections.sort(mPoints);
+ }
+
+ public static void setCurveHandle(Drawable drawable, int size) {
+ mCurveHandle = drawable;
+ mCurveHandleSize = size;
+ }
+
+ public static void setCurveWidth(int width) {
+ mCurveWidth = width;
+ }
+
+ public static int curveHandleSize() {
+ return mCurveHandleSize;
+ }
+
+ public static int colorForCurve(int curveIndex) {
+ switch (curveIndex) {
+ case Spline.RED:
+ return Color.RED;
+ case GREEN:
+ return Color.GREEN;
+ case BLUE:
+ return Color.BLUE;
+ }
+ return Color.WHITE;
+ }
+
+ public boolean sameValues(Spline other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null) {
+ return false;
+ }
+
+ if (getNbPoints() != other.getNbPoints()) {
+ return false;
+ }
+
+ for (int i = 0; i < getNbPoints(); i++) {
+ ControlPoint p = mPoints.elementAt(i);
+ ControlPoint otherPoint = other.mPoints.elementAt(i);
+ if (!p.sameValues(otherPoint)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void didMovePoint(ControlPoint point) {
+ mCurrentControlPoint = point;
+ }
+
+ public void movePoint(int pick, float x, float y) {
+ if (pick < 0 || pick > mPoints.size() - 1) {
+ return;
+ }
+ ControlPoint point = mPoints.elementAt(pick);
+ point.x = x;
+ point.y = y;
+ didMovePoint(point);
+ }
+
+ public boolean isOriginal() {
+ if (this.getNbPoints() != 2) {
+ return false;
+ }
+ if (mPoints.elementAt(0).x != 0 || mPoints.elementAt(0).y != 1) {
+ return false;
+ }
+ if (mPoints.elementAt(1).x != 1 || mPoints.elementAt(1).y != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ public void reset() {
+ mPoints.clear();
+ addPoint(0.0f, 1.0f);
+ addPoint(1.0f, 0.0f);
+ }
+
+ private void drawHandles(Canvas canvas, Drawable indicator, float centerX, float centerY) {
+ int left = (int) centerX - mCurveHandleSize / 2;
+ int top = (int) centerY - mCurveHandleSize / 2;
+ indicator.setBounds(left, top, left + mCurveHandleSize, top + mCurveHandleSize);
+ indicator.draw(canvas);
+ }
+
+ public float[] getAppliedCurve() {
+ float[] curve = new float[256];
+ ControlPoint[] points = new ControlPoint[mPoints.size()];
+ for (int i = 0; i < mPoints.size(); i++) {
+ ControlPoint p = mPoints.get(i);
+ points[i] = new ControlPoint(p.x, p.y);
+ }
+ double[] derivatives = solveSystem(points);
+ int start = 0;
+ int end = 256;
+ if (points[0].x != 0) {
+ start = (int) (points[0].x * 256);
+ }
+ if (points[points.length - 1].x != 1) {
+ end = (int) (points[points.length - 1].x * 256);
+ }
+ for (int i = 0; i < start; i++) {
+ curve[i] = 1.0f - points[0].y;
+ }
+ for (int i = end; i < 256; i++) {
+ curve[i] = 1.0f - points[points.length - 1].y;
+ }
+ for (int i = start; i < end; i++) {
+ ControlPoint cur = null;
+ ControlPoint next = null;
+ double x = i / 256.0;
+ int pivot = 0;
+ for (int j = 0; j < points.length - 1; j++) {
+ if (x >= points[j].x && x <= points[j + 1].x) {
+ pivot = j;
+ }
+ }
+ cur = points[pivot];
+ next = points[pivot + 1];
+ if (x <= next.x) {
+ double x1 = cur.x;
+ double x2 = next.x;
+ double y1 = cur.y;
+ double y2 = next.y;
+
+ // Use the second derivatives to apply the cubic spline
+ // equation:
+ double delta = (x2 - x1);
+ double delta2 = delta * delta;
+ double b = (x - x1) / delta;
+ double a = 1 - b;
+ double ta = a * y1;
+ double tb = b * y2;
+ double tc = (a * a * a - a) * derivatives[pivot];
+ double td = (b * b * b - b) * derivatives[pivot + 1];
+ double y = ta + tb + (delta2 / 6) * (tc + td);
+ if (y > 1.0f) {
+ y = 1.0f;
+ }
+ if (y < 0) {
+ y = 0;
+ }
+ curve[i] = (float) (1.0f - y);
+ } else {
+ curve[i] = 1.0f - next.y;
+ }
+ }
+ return curve;
+ }
+
+ private void drawGrid(Canvas canvas, float w, float h) {
+ // Grid
+ gPaint.setARGB(128, 150, 150, 150);
+ gPaint.setStrokeWidth(1);
+
+ float stepH = h / 9;
+ float stepW = w / 9;
+
+ // central diagonal
+ gPaint.setARGB(255, 100, 100, 100);
+ gPaint.setStrokeWidth(2);
+ canvas.drawLine(0, h, w, 0, gPaint);
+
+ gPaint.setARGB(128, 200, 200, 200);
+ gPaint.setStrokeWidth(4);
+ stepH = h / 3;
+ stepW = w / 3;
+ for (int j = 1; j < 3; j++) {
+ canvas.drawLine(0, j * stepH, w, j * stepH, gPaint);
+ canvas.drawLine(j * stepW, 0, j * stepW, h, gPaint);
+ }
+ canvas.drawLine(0, 0, 0, h, gPaint);
+ canvas.drawLine(w, 0, w, h, gPaint);
+ canvas.drawLine(0, 0, w, 0, gPaint);
+ canvas.drawLine(0, h, w, h, gPaint);
+ }
+
+ public void draw(Canvas canvas, int color, int canvasWidth, int canvasHeight,
+ boolean showHandles, boolean moving) {
+ float w = canvasWidth - mCurveHandleSize;
+ float h = canvasHeight - mCurveHandleSize;
+ float dx = mCurveHandleSize / 2;
+ float dy = mCurveHandleSize / 2;
+
+ // The cubic spline equation is (from numerical recipes in C):
+ // y = a(y_i) + b(y_i+1) + c(y"_i) + d(y"_i+1)
+ //
+ // with c(y"_i) and d(y"_i+1):
+ // c(y"_i) = 1/6 (a^3 - a) delta^2 (y"_i)
+ // d(y"_i_+1) = 1/6 (b^3 - b) delta^2 (y"_i+1)
+ //
+ // and delta:
+ // delta = x_i+1 - x_i
+ //
+ // To find the second derivatives y", we can rearrange the equation as:
+ // A(y"_i-1) + B(y"_i) + C(y"_i+1) = D
+ //
+ // With the coefficients A, B, C, D:
+ // A = 1/6 (x_i - x_i-1)
+ // B = 1/3 (x_i+1 - x_i-1)
+ // C = 1/6 (x_i+1 - x_i)
+ // D = (y_i+1 - y_i)/(x_i+1 - x_i) - (y_i - y_i-1)/(x_i - x_i-1)
+ //
+ // We can now easily solve the equation to find the second derivatives:
+ ControlPoint[] points = new ControlPoint[mPoints.size()];
+ for (int i = 0; i < mPoints.size(); i++) {
+ ControlPoint p = mPoints.get(i);
+ points[i] = new ControlPoint(p.x * w, p.y * h);
+ }
+ double[] derivatives = solveSystem(points);
+
+ Path path = new Path();
+ path.moveTo(0, points[0].y);
+ for (int i = 0; i < points.length - 1; i++) {
+ double x1 = points[i].x;
+ double x2 = points[i + 1].x;
+ double y1 = points[i].y;
+ double y2 = points[i + 1].y;
+
+ for (double x = x1; x < x2; x += 20) {
+ // Use the second derivatives to apply the cubic spline
+ // equation:
+ double delta = (x2 - x1);
+ double delta2 = delta * delta;
+ double b = (x - x1) / delta;
+ double a = 1 - b;
+ double ta = a * y1;
+ double tb = b * y2;
+ double tc = (a * a * a - a) * derivatives[i];
+ double td = (b * b * b - b) * derivatives[i + 1];
+ double y = ta + tb + (delta2 / 6) * (tc + td);
+ if (y > h) {
+ y = h;
+ }
+ if (y < 0) {
+ y = 0;
+ }
+ path.lineTo((float) x, (float) y);
+ }
+ }
+ canvas.save();
+ canvas.translate(dx, dy);
+ drawGrid(canvas, w, h);
+ ControlPoint lastPoint = points[points.length - 1];
+ path.lineTo(lastPoint.x, lastPoint.y);
+ path.lineTo(w, lastPoint.y);
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setFilterBitmap(true);
+ paint.setDither(true);
+ paint.setStyle(Paint.Style.STROKE);
+ int curveWidth = mCurveWidth;
+ if (showHandles) {
+ curveWidth *= 1.5;
+ }
+ paint.setStrokeWidth(curveWidth + 2);
+ paint.setColor(Color.BLACK);
+ canvas.drawPath(path, paint);
+
+ if (moving && mCurrentControlPoint != null) {
+ float px = mCurrentControlPoint.x * w;
+ float py = mCurrentControlPoint.y * h;
+ paint.setStrokeWidth(3);
+ paint.setColor(Color.BLACK);
+ canvas.drawLine(px, py, px, h, paint);
+ canvas.drawLine(0, py, px, py, paint);
+ paint.setStrokeWidth(1);
+ paint.setColor(color);
+ canvas.drawLine(px, py, px, h, paint);
+ canvas.drawLine(0, py, px, py, paint);
+ }
+
+ paint.setStrokeWidth(curveWidth);
+ paint.setColor(color);
+ canvas.drawPath(path, paint);
+ if (showHandles) {
+ for (int i = 0; i < points.length; i++) {
+ float x = points[i].x;
+ float y = points[i].y;
+ drawHandles(canvas, mCurveHandle, x, y);
+ }
+ }
+ canvas.restore();
+ }
+
+ double[] solveSystem(ControlPoint[] points) {
+ int n = points.length;
+ double[][] system = new double[n][3];
+ double[] result = new double[n]; // d
+ double[] solution = new double[n]; // returned coefficients
+ system[0][1] = 1;
+ system[n - 1][1] = 1;
+ double d6 = 1.0 / 6.0;
+ double d3 = 1.0 / 3.0;
+
+ // let's create a tridiagonal matrix representing the
+ // system, and apply the TDMA algorithm to solve it
+ // (see http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
+ for (int i = 1; i < n - 1; i++) {
+ double deltaPrevX = points[i].x - points[i - 1].x;
+ double deltaX = points[i + 1].x - points[i - 1].x;
+ double deltaNextX = points[i + 1].x - points[i].x;
+ double deltaNextY = points[i + 1].y - points[i].y;
+ double deltaPrevY = points[i].y - points[i - 1].y;
+ system[i][0] = d6 * deltaPrevX; // a_i
+ system[i][1] = d3 * deltaX; // b_i
+ system[i][2] = d6 * deltaNextX; // c_i
+ result[i] = (deltaNextY / deltaNextX) - (deltaPrevY / deltaPrevX); // d_i
+ }
+
+ // Forward sweep
+ for (int i = 1; i < n; i++) {
+ // m = a_i/b_i-1
+ double m = system[i][0] / system[i - 1][1];
+ // b_i = b_i - m(c_i-1)
+ system[i][1] = system[i][1] - m * system[i - 1][2];
+ // d_i = d_i - m(d_i-1)
+ result[i] = result[i] - m * result[i - 1];
+ }
+
+ // Back substitution
+ solution[n - 1] = result[n - 1] / system[n - 1][1];
+ for (int i = n - 2; i >= 0; --i) {
+ solution[i] = (result[i] - system[i][2] * solution[i + 1]) / system[i][1];
+ }
+ return solution;
+ }
+
+ public int addPoint(float x, float y) {
+ return addPoint(new ControlPoint(x, y));
+ }
+
+ public int addPoint(ControlPoint v) {
+ mPoints.add(v);
+ Collections.sort(mPoints);
+ return mPoints.indexOf(v);
+ }
+
+ public void deletePoint(int n) {
+ mPoints.remove(n);
+ if (mPoints.size() < 2) {
+ reset();
+ }
+ Collections.sort(mPoints);
+ }
+
+ public int getNbPoints() {
+ return mPoints.size();
+ }
+
+ public ControlPoint getPoint(int n) {
+ return mPoints.elementAt(n);
+ }
+
+ public boolean isPointContained(float x, int n) {
+ for (int i = 0; i < n; i++) {
+ ControlPoint point = mPoints.elementAt(i);
+ if (point.x > x) {
+ return false;
+ }
+ }
+ for (int i = n + 1; i < mPoints.size(); i++) {
+ ControlPoint point = mPoints.elementAt(i);
+ if (point.x < x) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public Spline copy() {
+ Spline spline = new Spline();
+ for (int i = 0; i < mPoints.size(); i++) {
+ ControlPoint point = mPoints.elementAt(i);
+ spline.addPoint(point.copy());
+ }
+ return spline;
+ }
+
+ public void show() {
+ Log.v(LOGTAG, "show curve " + this);
+ for (int i = 0; i < mPoints.size(); i++) {
+ ControlPoint point = mPoints.elementAt(i);
+ Log.v(LOGTAG, "point " + i + " is (" + point.x + ", " + point.y + ")");
+ }
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/Buffer.java b/src/com/android/gallery3d/filtershow/pipeline/Buffer.java
new file mode 100644
index 000000000..744451229
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/Buffer.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.graphics.Bitmap;
+import android.support.v8.renderscript.Allocation;
+import android.support.v8.renderscript.RenderScript;
+
+public class Buffer {
+ private static final String LOGTAG = "Buffer";
+ private Bitmap mBitmap;
+ private Allocation mAllocation;
+ private boolean mUseAllocation = false;
+ private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
+ private ImagePreset mPreset;
+
+ public Buffer(Bitmap bitmap) {
+ RenderScript rs = CachingPipeline.getRenderScriptContext();
+ if (bitmap != null) {
+ mBitmap = bitmap.copy(BITMAP_CONFIG, true);
+ }
+ if (mUseAllocation) {
+ // TODO: recreate the allocation when the RS context changes
+ mAllocation = Allocation.createFromBitmap(rs, mBitmap,
+ Allocation.MipmapControl.MIPMAP_NONE,
+ Allocation.USAGE_SHARED | Allocation.USAGE_SCRIPT);
+ }
+ }
+
+ public void setBitmap(Bitmap bitmap) {
+ mBitmap = bitmap.copy(BITMAP_CONFIG, true);
+ }
+
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ public Allocation getAllocation() {
+ return mAllocation;
+ }
+
+ public void sync() {
+ if (mUseAllocation) {
+ mAllocation.copyTo(mBitmap);
+ }
+ }
+
+ public ImagePreset getPreset() {
+ return mPreset;
+ }
+
+ public void setPreset(ImagePreset preset) {
+ if ((mPreset == null) || (!mPreset.same(preset))) {
+ mPreset = new ImagePreset(preset);
+ } else {
+ mPreset.updateWith(preset);
+ }
+ }
+}
+
diff --git a/src/com/android/gallery3d/filtershow/pipeline/CacheProcessing.java b/src/com/android/gallery3d/filtershow/pipeline/CacheProcessing.java
new file mode 100644
index 000000000..e0269e9bb
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/CacheProcessing.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+
+import java.util.Vector;
+
+public class CacheProcessing {
+ private static final String LOGTAG = "CacheProcessing";
+ private static final boolean DEBUG = false;
+ private Vector<CacheStep> mSteps = new Vector<CacheStep>();
+
+ static class CacheStep {
+ FilterRepresentation representation;
+ Bitmap cache;
+ }
+
+ public Bitmap process(Bitmap originalBitmap,
+ Vector<FilterRepresentation> filters,
+ FilterEnvironment environment) {
+
+ if (filters.size() == 0) {
+ return originalBitmap;
+ }
+
+ // New set of filters, let's clear the cache and rebuild it.
+ if (filters.size() != mSteps.size()) {
+ mSteps.clear();
+ for (int i = 0; i < filters.size(); i++) {
+ FilterRepresentation representation = filters.elementAt(i);
+ CacheStep step = new CacheStep();
+ step.representation = representation.copy();
+ mSteps.add(step);
+ }
+ }
+
+ if (DEBUG) {
+ displayFilters(filters);
+ }
+
+ // First, let's find how similar we are in our cache
+ // compared to the current list of filters
+ int similarUpToIndex = -1;
+ for (int i = 0; i < filters.size(); i++) {
+ FilterRepresentation representation = filters.elementAt(i);
+ CacheStep step = mSteps.elementAt(i);
+ boolean similar = step.representation.equals(representation);
+ if (similar) {
+ similarUpToIndex = i;
+ } else {
+ break;
+ }
+ }
+ if (DEBUG) {
+ Log.v(LOGTAG, "similar up to index " + similarUpToIndex);
+ }
+
+ // Now, let's get the earliest cached result in our pipeline
+ Bitmap cacheBitmap = null;
+ int findBaseImageIndex = similarUpToIndex;
+ if (findBaseImageIndex > -1) {
+ while (findBaseImageIndex > 0
+ && mSteps.elementAt(findBaseImageIndex).cache == null) {
+ findBaseImageIndex--;
+ }
+ cacheBitmap = mSteps.elementAt(findBaseImageIndex).cache;
+ }
+ boolean emptyStack = false;
+ if (cacheBitmap == null) {
+ emptyStack = true;
+ // Damn, it's an empty stack, we have to start from scratch
+ // TODO: use a bitmap cache + RS allocation instead of Bitmap.copy()
+ cacheBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true);
+ if (findBaseImageIndex > -1) {
+ FilterRepresentation representation = filters.elementAt(findBaseImageIndex);
+ if (representation.getFilterType() != FilterRepresentation.TYPE_GEOMETRY) {
+ cacheBitmap = environment.applyRepresentation(representation, cacheBitmap);
+ }
+ mSteps.elementAt(findBaseImageIndex).representation = representation.copy();
+ mSteps.elementAt(findBaseImageIndex).cache = cacheBitmap;
+ }
+ if (DEBUG) {
+ Log.v(LOGTAG, "empty stack");
+ }
+ }
+
+ // Ok, so sadly the earliest cached result is before the index we want.
+ // We have to rebuild a new result for this position, and then cache it.
+ if (findBaseImageIndex != similarUpToIndex) {
+ if (DEBUG) {
+ Log.v(LOGTAG, "rebuild cacheBitmap from " + findBaseImageIndex
+ + " to " + similarUpToIndex);
+ }
+ // rebuild the cache image for this step
+ if (!emptyStack) {
+ cacheBitmap = cacheBitmap.copy(Bitmap.Config.ARGB_8888, true);
+ } else {
+ // if it was an empty stack, we already applied it
+ findBaseImageIndex ++;
+ }
+ for (int i = findBaseImageIndex; i <= similarUpToIndex; i++) {
+ FilterRepresentation representation = filters.elementAt(i);
+ if (representation.getFilterType() != FilterRepresentation.TYPE_GEOMETRY) {
+ cacheBitmap = environment.applyRepresentation(representation, cacheBitmap);
+ }
+ if (DEBUG) {
+ Log.v(LOGTAG, " - " + i + " => apply " + representation.getName());
+ }
+ }
+ // Let's cache it!
+ mSteps.elementAt(similarUpToIndex).cache = cacheBitmap;
+ }
+
+ if (DEBUG) {
+ Log.v(LOGTAG, "process pipeline from " + similarUpToIndex
+ + " to " + (filters.size() - 1));
+ }
+
+ // Now we are good to go, let's use the cacheBitmap as a starting point
+ for (int i = similarUpToIndex + 1; i < filters.size(); i++) {
+ FilterRepresentation representation = filters.elementAt(i);
+ CacheStep currentStep = mSteps.elementAt(i);
+ cacheBitmap = cacheBitmap.copy(Bitmap.Config.ARGB_8888, true);
+ if (representation.getFilterType() != FilterRepresentation.TYPE_GEOMETRY) {
+ cacheBitmap = environment.applyRepresentation(representation, cacheBitmap);
+ }
+ currentStep.representation = representation.copy();
+ currentStep.cache = cacheBitmap;
+ if (DEBUG) {
+ Log.v(LOGTAG, " - " + i + " => apply " + representation.getName());
+ }
+ }
+
+ if (DEBUG) {
+ Log.v(LOGTAG, "now let's cleanup the cache...");
+ displayNbBitmapsInCache();
+ }
+
+ // Let's see if we can cleanup the cache for unused bitmaps
+ for (int i = 0; i < similarUpToIndex; i++) {
+ CacheStep currentStep = mSteps.elementAt(i);
+ currentStep.cache = null;
+ }
+
+ if (DEBUG) {
+ Log.v(LOGTAG, "cleanup done...");
+ displayNbBitmapsInCache();
+ }
+ return cacheBitmap;
+ }
+
+ private void displayFilters(Vector<FilterRepresentation> filters) {
+ Log.v(LOGTAG, "------>>>");
+ for (int i = 0; i < filters.size(); i++) {
+ FilterRepresentation representation = filters.elementAt(i);
+ CacheStep step = mSteps.elementAt(i);
+ boolean similar = step.representation.equals(representation);
+ Log.v(LOGTAG, "[" + i + "] - " + representation.getName()
+ + " similar rep ? " + (similar ? "YES" : "NO")
+ + " -- bitmap: " + step.cache);
+ }
+ Log.v(LOGTAG, "<<<------");
+ }
+
+ private void displayNbBitmapsInCache() {
+ int nbBitmapsCached = 0;
+ for (int i = 0; i < mSteps.size(); i++) {
+ CacheStep step = mSteps.elementAt(i);
+ if (step.cache != null) {
+ nbBitmapsCached++;
+ }
+ }
+ Log.v(LOGTAG, "nb bitmaps in cache: " + nbBitmapsCached + " / " + mSteps.size());
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java b/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java
new file mode 100644
index 000000000..fc0d6ce49
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.support.v8.renderscript.Allocation;
+import android.support.v8.renderscript.RenderScript;
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+import java.util.Vector;
+
+public class CachingPipeline implements PipelineInterface {
+ private static final String LOGTAG = "CachingPipeline";
+ private boolean DEBUG = false;
+
+ private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
+
+ private static volatile RenderScript sRS = null;
+
+ private FiltersManager mFiltersManager = null;
+ private volatile Bitmap mOriginalBitmap = null;
+ private volatile Bitmap mResizedOriginalBitmap = null;
+
+ private FilterEnvironment mEnvironment = new FilterEnvironment();
+ private CacheProcessing mCachedProcessing = new CacheProcessing();
+
+
+ private volatile Allocation mOriginalAllocation = null;
+ private volatile Allocation mFiltersOnlyOriginalAllocation = null;
+
+ protected volatile Allocation mInPixelsAllocation;
+ protected volatile Allocation mOutPixelsAllocation;
+ private volatile int mWidth = 0;
+ private volatile int mHeight = 0;
+
+ private volatile float mPreviewScaleFactor = 1.0f;
+ private volatile float mHighResPreviewScaleFactor = 1.0f;
+ private volatile String mName = "";
+
+ public CachingPipeline(FiltersManager filtersManager, String name) {
+ mFiltersManager = filtersManager;
+ mName = name;
+ }
+
+ public static synchronized RenderScript getRenderScriptContext() {
+ return sRS;
+ }
+
+ public static synchronized void createRenderscriptContext(Context context) {
+ if (sRS != null) {
+ Log.w(LOGTAG, "A prior RS context exists when calling setRenderScriptContext");
+ destroyRenderScriptContext();
+ }
+ sRS = RenderScript.create(context);
+ }
+
+ public static synchronized void destroyRenderScriptContext() {
+ if (sRS != null) {
+ sRS.destroy();
+ }
+ sRS = null;
+ }
+
+ public void stop() {
+ mEnvironment.setStop(true);
+ }
+
+ public synchronized void reset() {
+ synchronized (CachingPipeline.class) {
+ if (getRenderScriptContext() == null) {
+ return;
+ }
+ mOriginalBitmap = null; // just a reference to the bitmap in ImageLoader
+ if (mResizedOriginalBitmap != null) {
+ mResizedOriginalBitmap.recycle();
+ mResizedOriginalBitmap = null;
+ }
+ if (mOriginalAllocation != null) {
+ mOriginalAllocation.destroy();
+ mOriginalAllocation = null;
+ }
+ if (mFiltersOnlyOriginalAllocation != null) {
+ mFiltersOnlyOriginalAllocation.destroy();
+ mFiltersOnlyOriginalAllocation = null;
+ }
+ mPreviewScaleFactor = 1.0f;
+ mHighResPreviewScaleFactor = 1.0f;
+
+ destroyPixelAllocations();
+ }
+ }
+
+ public Resources getResources() {
+ return sRS.getApplicationContext().getResources();
+ }
+
+ private synchronized void destroyPixelAllocations() {
+ if (DEBUG) {
+ Log.v(LOGTAG, "destroyPixelAllocations in " + getName());
+ }
+ if (mInPixelsAllocation != null) {
+ mInPixelsAllocation.destroy();
+ mInPixelsAllocation = null;
+ }
+ if (mOutPixelsAllocation != null) {
+ mOutPixelsAllocation.destroy();
+ mOutPixelsAllocation = null;
+ }
+ mWidth = 0;
+ mHeight = 0;
+ }
+
+ private String getType(RenderingRequest request) {
+ if (request.getType() == RenderingRequest.ICON_RENDERING) {
+ return "ICON_RENDERING";
+ }
+ if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
+ return "FILTERS_RENDERING";
+ }
+ if (request.getType() == RenderingRequest.FULL_RENDERING) {
+ return "FULL_RENDERING";
+ }
+ if (request.getType() == RenderingRequest.GEOMETRY_RENDERING) {
+ return "GEOMETRY_RENDERING";
+ }
+ if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
+ return "PARTIAL_RENDERING";
+ }
+ if (request.getType() == RenderingRequest.HIGHRES_RENDERING) {
+ return "HIGHRES_RENDERING";
+ }
+ return "UNKNOWN TYPE!";
+ }
+
+ private void setupEnvironment(ImagePreset preset, boolean highResPreview) {
+ mEnvironment.setPipeline(this);
+ mEnvironment.setFiltersManager(mFiltersManager);
+ if (highResPreview) {
+ mEnvironment.setScaleFactor(mHighResPreviewScaleFactor);
+ } else {
+ mEnvironment.setScaleFactor(mPreviewScaleFactor);
+ }
+ mEnvironment.setQuality(FilterEnvironment.QUALITY_PREVIEW);
+ mEnvironment.setImagePreset(preset);
+ mEnvironment.setStop(false);
+ }
+
+ public void setOriginal(Bitmap bitmap) {
+ mOriginalBitmap = bitmap;
+ Log.v(LOGTAG,"setOriginal, size " + bitmap.getWidth() + " x " + bitmap.getHeight());
+ ImagePreset preset = MasterImage.getImage().getPreset();
+ setupEnvironment(preset, false);
+ updateOriginalAllocation(preset);
+ }
+
+ private synchronized boolean updateOriginalAllocation(ImagePreset preset) {
+ Bitmap originalBitmap = mOriginalBitmap;
+
+ if (originalBitmap == null) {
+ return false;
+ }
+
+ RenderScript RS = getRenderScriptContext();
+
+ Allocation filtersOnlyOriginalAllocation = mFiltersOnlyOriginalAllocation;
+ mFiltersOnlyOriginalAllocation = Allocation.createFromBitmap(RS, originalBitmap,
+ Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
+ if (filtersOnlyOriginalAllocation != null) {
+ filtersOnlyOriginalAllocation.destroy();
+ }
+
+ Allocation originalAllocation = mOriginalAllocation;
+ mResizedOriginalBitmap = preset.applyGeometry(originalBitmap, mEnvironment);
+ mOriginalAllocation = Allocation.createFromBitmap(RS, mResizedOriginalBitmap,
+ Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
+ if (originalAllocation != null) {
+ originalAllocation.destroy();
+ }
+
+ return true;
+ }
+
+ public void renderHighres(RenderingRequest request) {
+ synchronized (CachingPipeline.class) {
+ if (getRenderScriptContext() == null) {
+ return;
+ }
+ ImagePreset preset = request.getImagePreset();
+ setupEnvironment(preset, false);
+ Bitmap bitmap = MasterImage.getImage().getOriginalBitmapHighres();
+ if (bitmap == null) {
+ return;
+ }
+ // TODO: use a cache of bitmaps
+ bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
+ bitmap = preset.applyGeometry(bitmap, mEnvironment);
+
+ mEnvironment.setQuality(FilterEnvironment.QUALITY_PREVIEW);
+ Bitmap bmp = preset.apply(bitmap, mEnvironment);
+ if (!mEnvironment.needsStop()) {
+ request.setBitmap(bmp);
+ }
+ mFiltersManager.freeFilterResources(preset);
+ }
+ }
+
+ public synchronized void render(RenderingRequest request) {
+ synchronized (CachingPipeline.class) {
+ if (getRenderScriptContext() == null) {
+ return;
+ }
+ if (((request.getType() != RenderingRequest.PARTIAL_RENDERING
+ && request.getType() != RenderingRequest.HIGHRES_RENDERING)
+ && request.getBitmap() == null)
+ || request.getImagePreset() == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.v(LOGTAG, "render image of type " + getType(request));
+ }
+
+ Bitmap bitmap = request.getBitmap();
+ ImagePreset preset = request.getImagePreset();
+ setupEnvironment(preset,
+ request.getType() != RenderingRequest.HIGHRES_RENDERING);
+ mFiltersManager.freeFilterResources(preset);
+
+ if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
+ MasterImage master = MasterImage.getImage();
+ bitmap = ImageLoader.getScaleOneImageForPreset(master.getActivity(),
+ master.getUri(), request.getBounds(),
+ request.getDestination());
+ if (bitmap == null) {
+ Log.w(LOGTAG, "could not get bitmap for: " + getType(request));
+ return;
+ }
+ }
+
+ if (request.getType() == RenderingRequest.HIGHRES_RENDERING) {
+ bitmap = MasterImage.getImage().getOriginalBitmapHighres();
+ if (bitmap != null) {
+ bitmap = preset.applyGeometry(bitmap, mEnvironment);
+ }
+ }
+
+ if (request.getType() == RenderingRequest.FULL_RENDERING
+ || request.getType() == RenderingRequest.GEOMETRY_RENDERING
+ || request.getType() == RenderingRequest.FILTERS_RENDERING) {
+ updateOriginalAllocation(preset);
+ }
+
+ if (DEBUG) {
+ Log.v(LOGTAG, "after update, req bitmap (" + bitmap.getWidth() + "x" + bitmap.getHeight()
+ + " ? resizeOriginal (" + mResizedOriginalBitmap.getWidth() + "x"
+ + mResizedOriginalBitmap.getHeight());
+ }
+
+ if (request.getType() == RenderingRequest.FULL_RENDERING
+ || request.getType() == RenderingRequest.GEOMETRY_RENDERING) {
+ mOriginalAllocation.copyTo(bitmap);
+ } else if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
+ mFiltersOnlyOriginalAllocation.copyTo(bitmap);
+ }
+
+ if (request.getType() == RenderingRequest.FULL_RENDERING
+ || request.getType() == RenderingRequest.FILTERS_RENDERING
+ || request.getType() == RenderingRequest.ICON_RENDERING
+ || request.getType() == RenderingRequest.PARTIAL_RENDERING
+ || request.getType() == RenderingRequest.HIGHRES_RENDERING
+ || request.getType() == RenderingRequest.STYLE_ICON_RENDERING) {
+
+ if (request.getType() == RenderingRequest.ICON_RENDERING) {
+ mEnvironment.setQuality(FilterEnvironment.QUALITY_ICON);
+ } else {
+ mEnvironment.setQuality(FilterEnvironment.QUALITY_PREVIEW);
+ }
+
+ Bitmap bmp = preset.apply(bitmap, mEnvironment);
+ if (!mEnvironment.needsStop()) {
+ request.setBitmap(bmp);
+ }
+ mFiltersManager.freeFilterResources(preset);
+ }
+ }
+ }
+
+ public synchronized void renderImage(ImagePreset preset, Allocation in, Allocation out) {
+ synchronized (CachingPipeline.class) {
+ if (getRenderScriptContext() == null) {
+ return;
+ }
+ setupEnvironment(preset, false);
+ mFiltersManager.freeFilterResources(preset);
+ preset.applyFilters(-1, -1, in, out, mEnvironment);
+ boolean copyOut = false;
+ if (preset.nbFilters() > 0) {
+ copyOut = true;
+ }
+ preset.applyBorder(in, out, copyOut, mEnvironment);
+ }
+ }
+
+ public synchronized Bitmap renderFinalImage(Bitmap bitmap, ImagePreset preset) {
+ synchronized (CachingPipeline.class) {
+ if (getRenderScriptContext() == null) {
+ return bitmap;
+ }
+ setupEnvironment(preset, false);
+ mEnvironment.setQuality(FilterEnvironment.QUALITY_FINAL);
+ mEnvironment.setScaleFactor(1.0f);
+ mFiltersManager.freeFilterResources(preset);
+ bitmap = preset.applyGeometry(bitmap, mEnvironment);
+ bitmap = preset.apply(bitmap, mEnvironment);
+ return bitmap;
+ }
+ }
+
+ public Bitmap renderGeometryIcon(Bitmap bitmap, ImagePreset preset) {
+ return GeometryMathUtils.applyGeometryRepresentations(preset.getGeometryFilters(), bitmap);
+ }
+
+ public void compute(SharedBuffer buffer, ImagePreset preset, int type) {
+ if (getRenderScriptContext() == null) {
+ return;
+ }
+ setupEnvironment(preset, false);
+ Vector<FilterRepresentation> filters = preset.getFilters();
+ Bitmap result = mCachedProcessing.process(mOriginalBitmap, filters, mEnvironment);
+ buffer.setProducer(result);
+ }
+
+ public synchronized void computeOld(SharedBuffer buffer, ImagePreset preset, int type) {
+ synchronized (CachingPipeline.class) {
+ if (getRenderScriptContext() == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(LOGTAG, "compute preset " + preset);
+ preset.showFilters();
+ }
+
+ String thread = Thread.currentThread().getName();
+ long time = System.currentTimeMillis();
+ setupEnvironment(preset, false);
+ mFiltersManager.freeFilterResources(preset);
+
+ Bitmap resizedOriginalBitmap = mResizedOriginalBitmap;
+ if (updateOriginalAllocation(preset) || buffer.getProducer() == null) {
+ resizedOriginalBitmap = mResizedOriginalBitmap;
+ buffer.setProducer(resizedOriginalBitmap);
+ mEnvironment.cache(buffer.getProducer());
+ }
+
+ Bitmap bitmap = buffer.getProducer().getBitmap();
+ long time2 = System.currentTimeMillis();
+
+ if (bitmap == null || (bitmap.getWidth() != resizedOriginalBitmap.getWidth())
+ || (bitmap.getHeight() != resizedOriginalBitmap.getHeight())) {
+ mEnvironment.cache(buffer.getProducer());
+ buffer.setProducer(resizedOriginalBitmap);
+ bitmap = buffer.getProducer().getBitmap();
+ }
+ mOriginalAllocation.copyTo(bitmap);
+
+ Bitmap tmpbitmap = preset.apply(bitmap, mEnvironment);
+ if (tmpbitmap != bitmap) {
+ mEnvironment.cache(buffer.getProducer());
+ buffer.setProducer(tmpbitmap);
+ }
+
+ mFiltersManager.freeFilterResources(preset);
+
+ time = System.currentTimeMillis() - time;
+ time2 = System.currentTimeMillis() - time2;
+ if (DEBUG) {
+ Log.v(LOGTAG, "Applying type " + type + " filters to bitmap "
+ + bitmap + " (" + bitmap.getWidth() + " x " + bitmap.getHeight()
+ + ") took " + time + " ms, " + time2 + " ms for the filter, on thread " + thread);
+ }
+ }
+ }
+
+ public boolean needsRepaint() {
+ SharedBuffer buffer = MasterImage.getImage().getPreviewBuffer();
+ return buffer.checkRepaintNeeded();
+ }
+
+ public void setPreviewScaleFactor(float previewScaleFactor) {
+ mPreviewScaleFactor = previewScaleFactor;
+ }
+
+ public void setHighResPreviewScaleFactor(float highResPreviewScaleFactor) {
+ mHighResPreviewScaleFactor = highResPreviewScaleFactor;
+ }
+
+ public synchronized boolean isInitialized() {
+ return getRenderScriptContext() != null && mOriginalBitmap != null;
+ }
+
+ public boolean prepareRenderscriptAllocations(Bitmap bitmap) {
+ RenderScript RS = getRenderScriptContext();
+ boolean needsUpdate = false;
+ if (mOutPixelsAllocation == null || mInPixelsAllocation == null ||
+ bitmap.getWidth() != mWidth || bitmap.getHeight() != mHeight) {
+ destroyPixelAllocations();
+ Bitmap bitmapBuffer = bitmap;
+ if (bitmap.getConfig() == null || bitmap.getConfig() != BITMAP_CONFIG) {
+ bitmapBuffer = bitmap.copy(BITMAP_CONFIG, true);
+ }
+ mOutPixelsAllocation = Allocation.createFromBitmap(RS, bitmapBuffer,
+ Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
+ mInPixelsAllocation = Allocation.createTyped(RS,
+ mOutPixelsAllocation.getType());
+ needsUpdate = true;
+ }
+ if (RS != null) {
+ mInPixelsAllocation.copyFrom(bitmap);
+ }
+ if (bitmap.getWidth() != mWidth
+ || bitmap.getHeight() != mHeight) {
+ mWidth = bitmap.getWidth();
+ mHeight = bitmap.getHeight();
+ needsUpdate = true;
+ }
+ if (DEBUG) {
+ Log.v(LOGTAG, "prepareRenderscriptAllocations: " + needsUpdate + " in " + getName());
+ }
+ return needsUpdate;
+ }
+
+ public synchronized Allocation getInPixelsAllocation() {
+ return mInPixelsAllocation;
+ }
+
+ public synchronized Allocation getOutPixelsAllocation() {
+ return mOutPixelsAllocation;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public RenderScript getRSContext() {
+ return CachingPipeline.getRenderScriptContext();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java b/src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java
new file mode 100644
index 000000000..4fac956be
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.support.v8.renderscript.Allocation;
+
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation.Rotation;
+import com.android.gallery3d.filtershow.filters.FiltersManagerInterface;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+
+public class FilterEnvironment {
+ private static final String LOGTAG = "FilterEnvironment";
+ private ImagePreset mImagePreset;
+ private float mScaleFactor;
+ private int mQuality;
+ private FiltersManagerInterface mFiltersManager;
+ private PipelineInterface mPipeline;
+ private volatile boolean mStop = false;
+
+ public static final int QUALITY_ICON = 0;
+ public static final int QUALITY_PREVIEW = 1;
+ public static final int QUALITY_FINAL = 2;
+
+ public synchronized boolean needsStop() {
+ return mStop;
+ }
+
+ public synchronized void setStop(boolean stop) {
+ this.mStop = stop;
+ }
+
+ private HashMap<Long, WeakReference<Bitmap>>
+ bitmapCach = new HashMap<Long, WeakReference<Bitmap>>();
+
+ private HashMap<Integer, Integer>
+ generalParameters = new HashMap<Integer, Integer>();
+
+ public void cache(Buffer buffer) {
+ if (buffer == null) {
+ return;
+ }
+ Bitmap bitmap = buffer.getBitmap();
+ if (bitmap == null) {
+ return;
+ }
+ Long key = calcKey(bitmap.getWidth(), bitmap.getHeight());
+ bitmapCach.put(key, new WeakReference<Bitmap>(bitmap));
+ }
+
+ public Bitmap getBitmap(int w, int h) {
+ Long key = calcKey(w, h);
+ WeakReference<Bitmap> ref = bitmapCach.remove(key);
+ Bitmap bitmap = null;
+ if (ref != null) {
+ bitmap = ref.get();
+ }
+ if (bitmap == null) {
+ bitmap = Bitmap.createBitmap(
+ w, h, Bitmap.Config.ARGB_8888);
+ }
+ return bitmap;
+ }
+
+ private Long calcKey(long w, long h) {
+ return (w << 32) | (h << 32);
+ }
+
+ public void setImagePreset(ImagePreset imagePreset) {
+ mImagePreset = imagePreset;
+ }
+
+ public ImagePreset getImagePreset() {
+ return mImagePreset;
+ }
+
+ public void setScaleFactor(float scaleFactor) {
+ mScaleFactor = scaleFactor;
+ }
+
+ public float getScaleFactor() {
+ return mScaleFactor;
+ }
+
+ public void setQuality(int quality) {
+ mQuality = quality;
+ }
+
+ public int getQuality() {
+ return mQuality;
+ }
+
+ public void setFiltersManager(FiltersManagerInterface filtersManager) {
+ mFiltersManager = filtersManager;
+ }
+
+ public FiltersManagerInterface getFiltersManager() {
+ return mFiltersManager;
+ }
+
+ public void applyRepresentation(FilterRepresentation representation,
+ Allocation in, Allocation out) {
+ ImageFilter filter = mFiltersManager.getFilterForRepresentation(representation);
+ filter.useRepresentation(representation);
+ filter.setEnvironment(this);
+ if (filter.supportsAllocationInput()) {
+ filter.apply(in, out);
+ }
+ filter.setGeneralParameters();
+ filter.setEnvironment(null);
+ }
+
+ public Bitmap applyRepresentation(FilterRepresentation representation, Bitmap bitmap) {
+ if (representation instanceof FilterUserPresetRepresentation) {
+ // we allow instances of FilterUserPresetRepresentation in a preset only to know if one
+ // has been applied (so we can show this in the UI). But as all the filters in them are
+ // applied directly they do not themselves need to do any kind of filtering.
+ return bitmap;
+ }
+ ImageFilter filter = mFiltersManager.getFilterForRepresentation(representation);
+ filter.useRepresentation(representation);
+ filter.setEnvironment(this);
+ Bitmap ret = filter.apply(bitmap, mScaleFactor, mQuality);
+ filter.setGeneralParameters();
+ filter.setEnvironment(null);
+ return ret;
+ }
+
+ public PipelineInterface getPipeline() {
+ return mPipeline;
+ }
+
+ public void setPipeline(PipelineInterface cachingPipeline) {
+ mPipeline = cachingPipeline;
+ }
+
+ public synchronized void clearGeneralParameters() {
+ generalParameters = null;
+ }
+
+ public synchronized Integer getGeneralParameter(int id) {
+ if (generalParameters == null || !generalParameters.containsKey(id)) {
+ return null;
+ }
+ return generalParameters.get(id);
+ }
+
+ public synchronized void setGeneralParameter(int id, int value) {
+ if (generalParameters == null) {
+ generalParameters = new HashMap<Integer, Integer>();
+ }
+
+ generalParameters.put(id, value);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/HighresRenderingRequestTask.java b/src/com/android/gallery3d/filtershow/pipeline/HighresRenderingRequestTask.java
new file mode 100644
index 000000000..5a0eb4d45
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/HighresRenderingRequestTask.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.graphics.Bitmap;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+
+public class HighresRenderingRequestTask extends ProcessingTask {
+
+ private CachingPipeline mHighresPreviewPipeline = null;
+ private boolean mPipelineIsOn = false;
+
+ public void setHighresPreviewScaleFactor(float highResPreviewScale) {
+ mHighresPreviewPipeline.setHighResPreviewScaleFactor(highResPreviewScale);
+ }
+
+ public void setPreviewScaleFactor(float previewScale) {
+ mHighresPreviewPipeline.setPreviewScaleFactor(previewScale);
+ }
+
+ static class Render implements Request {
+ RenderingRequest request;
+ }
+
+ static class RenderResult implements Result {
+ RenderingRequest request;
+ }
+
+ public HighresRenderingRequestTask() {
+ mHighresPreviewPipeline = new CachingPipeline(
+ FiltersManager.getHighresManager(), "Highres");
+ }
+
+ public void setOriginal(Bitmap bitmap) {
+ mHighresPreviewPipeline.setOriginal(bitmap);
+ }
+
+ public void setOriginalBitmapHighres(Bitmap originalHires) {
+ mPipelineIsOn = true;
+ }
+
+ public void stop() {
+ mHighresPreviewPipeline.stop();
+ }
+
+ public void postRenderingRequest(RenderingRequest request) {
+ if (!mPipelineIsOn) {
+ return;
+ }
+ Render render = new Render();
+ render.request = request;
+ postRequest(render);
+ }
+
+ @Override
+ public Result doInBackground(Request message) {
+ RenderingRequest request = ((Render) message).request;
+ RenderResult result = null;
+ mHighresPreviewPipeline.renderHighres(request);
+ result = new RenderResult();
+ result.request = request;
+ return result;
+ }
+
+ @Override
+ public void onResult(Result message) {
+ if (message == null) {
+ return;
+ }
+ RenderingRequest request = ((RenderResult) message).request;
+ request.markAvailable();
+ }
+
+ @Override
+ public boolean isDelayedTask() { return true; }
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java b/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java
new file mode 100644
index 000000000..d34216ad6
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java
@@ -0,0 +1,694 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.pipeline;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.support.v8.renderscript.Allocation;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.BaseFiltersManager;
+import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterFxRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterImageBorderRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.state.State;
+import com.android.gallery3d.filtershow.state.StateAdapter;
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Vector;
+
+public class ImagePreset {
+
+ private static final String LOGTAG = "ImagePreset";
+
+ private Vector<FilterRepresentation> mFilters = new Vector<FilterRepresentation>();
+
+ private boolean mDoApplyGeometry = true;
+ private boolean mDoApplyFilters = true;
+
+ private boolean mPartialRendering = false;
+ private Rect mPartialRenderingBounds;
+ private static final boolean DEBUG = false;
+
+ public ImagePreset() {
+ }
+
+ public ImagePreset(ImagePreset source) {
+ for (int i = 0; i < source.mFilters.size(); i++) {
+ FilterRepresentation sourceRepresentation = source.mFilters.elementAt(i);
+ mFilters.add(sourceRepresentation.copy());
+ }
+ }
+
+ public Vector<FilterRepresentation> getFilters() {
+ return mFilters;
+ }
+
+ public FilterRepresentation getFilterRepresentation(int position) {
+ FilterRepresentation representation = null;
+
+ representation = mFilters.elementAt(position).copy();
+
+ return representation;
+ }
+
+ private static boolean sameSerializationName(String a, String b) {
+ if (a != null && b != null) {
+ return a.equals(b);
+ } else {
+ return a == null && b == null;
+ }
+ }
+
+ public static boolean sameSerializationName(FilterRepresentation a, FilterRepresentation b) {
+ if (a == null || b == null) {
+ return false;
+ }
+ return sameSerializationName(a.getSerializationName(), b.getSerializationName());
+ }
+
+ public int getPositionForRepresentation(FilterRepresentation representation) {
+ for (int i = 0; i < mFilters.size(); i++) {
+ if (sameSerializationName(mFilters.elementAt(i), representation)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private FilterRepresentation getFilterRepresentationForType(int type) {
+ for (int i = 0; i < mFilters.size(); i++) {
+ if (mFilters.elementAt(i).getFilterType() == type) {
+ return mFilters.elementAt(i);
+ }
+ }
+ return null;
+ }
+
+ public int getPositionForType(int type) {
+ for (int i = 0; i < mFilters.size(); i++) {
+ if (mFilters.elementAt(i).getFilterType() == type) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public FilterRepresentation getFilterRepresentationCopyFrom(
+ FilterRepresentation filterRepresentation) {
+ // TODO: add concept of position in the filters (to allow multiple instances)
+ if (filterRepresentation == null) {
+ return null;
+ }
+ int position = getPositionForRepresentation(filterRepresentation);
+ if (position == -1) {
+ return null;
+ }
+ FilterRepresentation representation = mFilters.elementAt(position);
+ if (representation != null) {
+ representation = representation.copy();
+ }
+ return representation;
+ }
+
+ public void updateFilterRepresentations(Collection<FilterRepresentation> reps) {
+ for (FilterRepresentation r : reps) {
+ updateOrAddFilterRepresentation(r);
+ }
+ }
+
+ public void updateOrAddFilterRepresentation(FilterRepresentation rep) {
+ int pos = getPositionForRepresentation(rep);
+ if (pos != -1) {
+ mFilters.elementAt(pos).useParametersFrom(rep);
+ } else {
+ addFilter(rep.copy());
+ }
+ }
+
+ public void setDoApplyGeometry(boolean value) {
+ mDoApplyGeometry = value;
+ }
+
+ public void setDoApplyFilters(boolean value) {
+ mDoApplyFilters = value;
+ }
+
+ public boolean getDoApplyFilters() {
+ return mDoApplyFilters;
+ }
+
+ public boolean hasModifications() {
+ for (int i = 0; i < mFilters.size(); i++) {
+ FilterRepresentation filter = mFilters.elementAt(i);
+ if (!filter.isNil()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isPanoramaSafe() {
+ for (FilterRepresentation representation : mFilters) {
+ if (representation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY
+ && !representation.isNil()) {
+ return false;
+ }
+ if (representation.getFilterType() == FilterRepresentation.TYPE_BORDER
+ && !representation.isNil()) {
+ return false;
+ }
+ if (representation.getFilterType() == FilterRepresentation.TYPE_VIGNETTE
+ && !representation.isNil()) {
+ return false;
+ }
+ if (representation.getFilterType() == FilterRepresentation.TYPE_TINYPLANET
+ && !representation.isNil()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean same(ImagePreset preset) {
+ if (preset == null) {
+ return false;
+ }
+
+ if (preset.mFilters.size() != mFilters.size()) {
+ return false;
+ }
+
+ if (mDoApplyGeometry != preset.mDoApplyGeometry) {
+ return false;
+ }
+
+ if (mDoApplyFilters != preset.mDoApplyFilters) {
+ if (mFilters.size() > 0 || preset.mFilters.size() > 0) {
+ return false;
+ }
+ }
+
+ if (mDoApplyFilters && preset.mDoApplyFilters) {
+ for (int i = 0; i < preset.mFilters.size(); i++) {
+ FilterRepresentation a = preset.mFilters.elementAt(i);
+ FilterRepresentation b = mFilters.elementAt(i);
+
+ if (!a.same(b)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public int similarUpTo(ImagePreset preset) {
+ for (int i = 0; i < preset.mFilters.size(); i++) {
+ FilterRepresentation a = preset.mFilters.elementAt(i);
+ if (i < mFilters.size()) {
+ FilterRepresentation b = mFilters.elementAt(i);
+ if (!a.same(b)) {
+ return i;
+ }
+ if (!a.equals(b)) {
+ return i;
+ }
+ } else {
+ return i;
+ }
+ }
+ return preset.mFilters.size();
+ }
+
+ public void showFilters() {
+ Log.v(LOGTAG, "\\\\\\ showFilters -- " + mFilters.size() + " filters");
+ int n = 0;
+ for (FilterRepresentation representation : mFilters) {
+ Log.v(LOGTAG, " filter " + n + " : " + representation.toString());
+ n++;
+ }
+ Log.v(LOGTAG, "/// showFilters -- " + mFilters.size() + " filters");
+ }
+
+ public FilterRepresentation getLastRepresentation() {
+ if (mFilters.size() > 0) {
+ return mFilters.lastElement();
+ }
+ return null;
+ }
+
+ public void removeFilter(FilterRepresentation filterRepresentation) {
+ if (filterRepresentation.getFilterType() == FilterRepresentation.TYPE_BORDER) {
+ for (int i = 0; i < mFilters.size(); i++) {
+ if (mFilters.elementAt(i).getFilterType()
+ == filterRepresentation.getFilterType()) {
+ mFilters.remove(i);
+ break;
+ }
+ }
+ } else {
+ for (int i = 0; i < mFilters.size(); i++) {
+ if (sameSerializationName(mFilters.elementAt(i), filterRepresentation)) {
+ mFilters.remove(i);
+ break;
+ }
+ }
+ }
+ }
+
+ // If the filter is an "None" effect or border, then just don't add this filter.
+ public void addFilter(FilterRepresentation representation) {
+ if (representation instanceof FilterUserPresetRepresentation) {
+ ImagePreset preset = ((FilterUserPresetRepresentation) representation).getImagePreset();
+ // user preset replace everything but geometry
+ mFilters.clear();
+ for (int i = 0; i < preset.nbFilters(); i++) {
+ addFilter(preset.getFilterRepresentation(i));
+ }
+ mFilters.add(representation);
+ } else if (representation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY) {
+ // Add geometry filter, removing duplicates and do-nothing operations.
+ for (int i = 0; i < mFilters.size(); i++) {
+ if (sameSerializationName(representation, mFilters.elementAt(i))) {
+ mFilters.remove(i);
+ }
+ }
+ if (!representation.isNil()) {
+ mFilters.add(representation);
+ }
+ } else if (representation.getFilterType() == FilterRepresentation.TYPE_BORDER) {
+ removeFilter(representation);
+ if (!isNoneBorderFilter(representation)) {
+ mFilters.add(representation);
+ }
+ } else if (representation.getFilterType() == FilterRepresentation.TYPE_FX) {
+ boolean found = false;
+ for (int i = 0; i < mFilters.size(); i++) {
+ FilterRepresentation current = mFilters.elementAt(i);
+ int type = current.getFilterType();
+ if (found) {
+ if (type != FilterRepresentation.TYPE_VIGNETTE) {
+ mFilters.remove(i);
+ continue;
+ }
+ }
+ if (type == FilterRepresentation.TYPE_FX) {
+ if (current instanceof FilterUserPresetRepresentation) {
+ ImagePreset preset = ((FilterUserPresetRepresentation) current)
+ .getImagePreset();
+ // If we had an existing user preset, let's remove all the presets that
+ // were added by it
+ for (int j = 0; j < preset.nbFilters(); j++) {
+ FilterRepresentation rep = preset.getFilterRepresentation(j);
+ int pos = getPositionForRepresentation(rep);
+ if (pos != -1) {
+ mFilters.remove(pos);
+ }
+ }
+ int pos = getPositionForRepresentation(current);
+ if (pos != -1) {
+ mFilters.remove(pos);
+ } else {
+ pos = 0;
+ }
+ if (!isNoneFxFilter(representation)) {
+ mFilters.add(pos, representation);
+ }
+
+ } else {
+ mFilters.remove(i);
+ if (!isNoneFxFilter(representation)) {
+ mFilters.add(i, representation);
+ }
+ }
+ found = true;
+ }
+ }
+ if (!found) {
+ if (!isNoneFxFilter(representation)) {
+ mFilters.add(representation);
+ }
+ }
+ } else {
+ mFilters.add(representation);
+ }
+ }
+
+ private boolean isNoneBorderFilter(FilterRepresentation representation) {
+ return representation instanceof FilterImageBorderRepresentation &&
+ ((FilterImageBorderRepresentation) representation).getDrawableResource() == 0;
+ }
+
+ private boolean isNoneFxFilter(FilterRepresentation representation) {
+ return representation instanceof FilterFxRepresentation &&
+ ((FilterFxRepresentation) representation).getNameResource() == R.string.none;
+ }
+
+ public FilterRepresentation getRepresentation(FilterRepresentation filterRepresentation) {
+ for (int i = 0; i < mFilters.size(); i++) {
+ FilterRepresentation representation = mFilters.elementAt(i);
+ if (sameSerializationName(representation, filterRepresentation)) {
+ return representation;
+ }
+ }
+ return null;
+ }
+
+ public Bitmap apply(Bitmap original, FilterEnvironment environment) {
+ Bitmap bitmap = original;
+ bitmap = applyFilters(bitmap, -1, -1, environment);
+ return applyBorder(bitmap, environment);
+ }
+
+ public Collection<FilterRepresentation> getGeometryFilters() {
+ ArrayList<FilterRepresentation> geometry = new ArrayList<FilterRepresentation>();
+ for (FilterRepresentation r : mFilters) {
+ if (r.getFilterType() == FilterRepresentation.TYPE_GEOMETRY) {
+ geometry.add(r);
+ }
+ }
+ return geometry;
+ }
+
+ public FilterRepresentation getFilterWithSerializationName(String serializationName) {
+ for (FilterRepresentation r : mFilters) {
+ if (r != null) {
+ if (sameSerializationName(r.getSerializationName(), serializationName)) {
+ return r.copy();
+ }
+ }
+ }
+ return null;
+ }
+
+ public Bitmap applyGeometry(Bitmap bitmap, FilterEnvironment environment) {
+ // Apply any transform -- 90 rotate, flip, straighten, crop
+ // Returns a new bitmap.
+ if (mDoApplyGeometry) {
+ bitmap = GeometryMathUtils.applyGeometryRepresentations(getGeometryFilters(), bitmap);
+ }
+ return bitmap;
+ }
+
+ public Bitmap applyBorder(Bitmap bitmap, FilterEnvironment environment) {
+ // get the border from the list of filters.
+ FilterRepresentation border = getFilterRepresentationForType(
+ FilterRepresentation.TYPE_BORDER);
+ if (border != null && mDoApplyGeometry) {
+ bitmap = environment.applyRepresentation(border, bitmap);
+ if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) {
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+ "SaveBorder", border.getSerializationName(), 1);
+ }
+ }
+ return bitmap;
+ }
+
+ public int nbFilters() {
+ return mFilters.size();
+ }
+
+ public Bitmap applyFilters(Bitmap bitmap, int from, int to, FilterEnvironment environment) {
+ if (mDoApplyFilters) {
+ if (from < 0) {
+ from = 0;
+ }
+ if (to == -1) {
+ to = mFilters.size();
+ }
+ if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) {
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+ "SaveFilters", "Total", to - from + 1);
+ }
+ for (int i = from; i < to; i++) {
+ FilterRepresentation representation = mFilters.elementAt(i);
+ if (representation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY) {
+ // skip the geometry as it's already applied.
+ continue;
+ }
+ if (representation.getFilterType() == FilterRepresentation.TYPE_BORDER) {
+ // for now, let's skip the border as it will be applied in
+ // applyBorder()
+ // TODO: might be worth getting rid of applyBorder.
+ continue;
+ }
+ bitmap = environment.applyRepresentation(representation, bitmap);
+ if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) {
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+ "SaveFilter", representation.getSerializationName(), 1);
+ }
+ if (environment.needsStop()) {
+ return bitmap;
+ }
+ }
+ }
+
+ return bitmap;
+ }
+
+ public void applyBorder(Allocation in, Allocation out,
+ boolean copyOut, FilterEnvironment environment) {
+ FilterRepresentation border = getFilterRepresentationForType(
+ FilterRepresentation.TYPE_BORDER);
+ if (border != null && mDoApplyGeometry) {
+ // TODO: should keep the bitmap around
+ Allocation bitmapIn = in;
+ if (copyOut) {
+ bitmapIn = Allocation.createTyped(
+ CachingPipeline.getRenderScriptContext(), in.getType());
+ bitmapIn.copyFrom(out);
+ }
+ environment.applyRepresentation(border, bitmapIn, out);
+ }
+ }
+
+ public void applyFilters(int from, int to, Allocation in, Allocation out,
+ FilterEnvironment environment) {
+ if (mDoApplyFilters) {
+ if (from < 0) {
+ from = 0;
+ }
+ if (to == -1) {
+ to = mFilters.size();
+ }
+ for (int i = from; i < to; i++) {
+ FilterRepresentation representation = mFilters.elementAt(i);
+ if (representation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY
+ || representation.getFilterType() == FilterRepresentation.TYPE_BORDER) {
+ continue;
+ }
+ if (i > from) {
+ in.copyFrom(out);
+ }
+ environment.applyRepresentation(representation, in, out);
+ }
+ }
+ }
+
+ public boolean canDoPartialRendering() {
+ if (MasterImage.getImage().getZoomOrientation() != ImageLoader.ORI_NORMAL) {
+ return false;
+ }
+ for (int i = 0; i < mFilters.size(); i++) {
+ FilterRepresentation representation = mFilters.elementAt(i);
+ if (representation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY
+ && !representation.isNil()) {
+ return false;
+ }
+ if (!representation.supportsPartialRendering()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void fillImageStateAdapter(StateAdapter imageStateAdapter) {
+ if (imageStateAdapter == null) {
+ return;
+ }
+ Vector<State> states = new Vector<State>();
+ for (FilterRepresentation filter : mFilters) {
+ if (filter.getFilterType() == FilterRepresentation.TYPE_GEOMETRY) {
+ // TODO: supports Geometry representations in the state panel.
+ continue;
+ }
+ if (filter instanceof FilterUserPresetRepresentation) {
+ // do not show the user preset itself in the state panel
+ continue;
+ }
+ State state = new State(filter.getName());
+ state.setFilterRepresentation(filter);
+ states.add(state);
+ }
+ imageStateAdapter.fill(states);
+ }
+
+ public void setPartialRendering(boolean partialRendering, Rect bounds) {
+ mPartialRendering = partialRendering;
+ mPartialRenderingBounds = bounds;
+ }
+
+ public boolean isPartialRendering() {
+ return mPartialRendering;
+ }
+
+ public Rect getPartialRenderingBounds() {
+ return mPartialRenderingBounds;
+ }
+
+ public Vector<ImageFilter> getUsedFilters(BaseFiltersManager filtersManager) {
+ Vector<ImageFilter> usedFilters = new Vector<ImageFilter>();
+ for (int i = 0; i < mFilters.size(); i++) {
+ FilterRepresentation representation = mFilters.elementAt(i);
+ ImageFilter filter = filtersManager.getFilterForRepresentation(representation);
+ usedFilters.add(filter);
+ }
+ return usedFilters;
+ }
+
+ public String getJsonString(String name) {
+ StringWriter swriter = new StringWriter();
+ try {
+ JsonWriter writer = new JsonWriter(swriter);
+ writeJson(writer, name);
+ writer.close();
+ } catch (IOException e) {
+ return null;
+ }
+ return swriter.toString();
+ }
+
+ public void writeJson(JsonWriter writer, String name) {
+ int numFilters = mFilters.size();
+ try {
+ writer.beginObject();
+ for (int i = 0; i < numFilters; i++) {
+ FilterRepresentation filter = mFilters.get(i);
+ if (filter instanceof FilterUserPresetRepresentation) {
+ continue;
+ }
+ String sname = filter.getSerializationName();
+ if (DEBUG) {
+ Log.v(LOGTAG, "Serialization: " + sname);
+ if (sname == null) {
+ Log.v(LOGTAG, "Serialization name null for filter: " + filter);
+ }
+ }
+ writer.name(sname);
+ filter.serializeRepresentation(writer);
+ }
+ writer.endObject();
+
+ } catch (IOException e) {
+ Log.e(LOGTAG,"Error encoding JASON",e);
+ }
+ }
+
+ /**
+ * populates preset from JSON string
+ *
+ * @param filterString a JSON string
+ * @return true on success if false ImagePreset is undefined
+ */
+ public boolean readJsonFromString(String filterString) {
+ if (DEBUG) {
+ Log.v(LOGTAG, "reading preset: \"" + filterString + "\"");
+ }
+ StringReader sreader = new StringReader(filterString);
+ try {
+ JsonReader reader = new JsonReader(sreader);
+ boolean ok = readJson(reader);
+ if (!ok) {
+ reader.close();
+ return false;
+ }
+ reader.close();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "parsing the filter parameters:", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * populates preset from JSON stream
+ *
+ * @param sreader a JSON string
+ * @return true on success if false ImagePreset is undefined
+ */
+ public boolean readJson(JsonReader sreader) throws IOException {
+ sreader.beginObject();
+
+ while (sreader.hasNext()) {
+ String name = sreader.nextName();
+ FilterRepresentation filter = creatFilterFromName(name);
+ if (filter == null) {
+ Log.w(LOGTAG, "UNKNOWN FILTER! " + name);
+ return false;
+ }
+ filter.deSerializeRepresentation(sreader);
+ addFilter(filter);
+ }
+ sreader.endObject();
+ return true;
+ }
+
+ FilterRepresentation creatFilterFromName(String name) {
+ if (FilterRotateRepresentation.SERIALIZATION_NAME.equals(name)) {
+ return new FilterRotateRepresentation();
+ } else if (FilterMirrorRepresentation.SERIALIZATION_NAME.equals(name)) {
+ return new FilterMirrorRepresentation();
+ } else if (FilterStraightenRepresentation.SERIALIZATION_NAME.equals(name)) {
+ return new FilterStraightenRepresentation();
+ } else if (FilterCropRepresentation.SERIALIZATION_NAME.equals(name)) {
+ return new FilterCropRepresentation();
+ }
+ FiltersManager filtersManager = FiltersManager.getManager();
+ return filtersManager.createFilterFromName(name);
+ }
+
+ public void updateWith(ImagePreset preset) {
+ if (preset.mFilters.size() != mFilters.size()) {
+ Log.e(LOGTAG, "Updating a preset with an incompatible one");
+ return;
+ }
+ for (int i = 0; i < mFilters.size(); i++) {
+ FilterRepresentation destRepresentation = mFilters.elementAt(i);
+ FilterRepresentation sourceRepresentation = preset.mFilters.elementAt(i);
+ destRepresentation.useParametersFrom(sourceRepresentation);
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java b/src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java
new file mode 100644
index 000000000..b760edd5a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.tools.SaveImage;
+
+import java.io.File;
+
+public class ImageSavingTask extends ProcessingTask {
+ private ProcessingService mProcessingService;
+
+ static class SaveRequest implements Request {
+ Uri sourceUri;
+ Uri selectedUri;
+ File destinationFile;
+ ImagePreset preset;
+ boolean flatten;
+ int quality;
+ }
+
+ static class UpdateBitmap implements Update {
+ Bitmap bitmap;
+ }
+
+ static class UpdateProgress implements Update {
+ int max;
+ int current;
+ }
+
+ static class URIResult implements Result {
+ Uri uri;
+ }
+
+ public ImageSavingTask(ProcessingService service) {
+ mProcessingService = service;
+ }
+
+ public void saveImage(Uri sourceUri, Uri selectedUri,
+ File destinationFile, ImagePreset preset, boolean flatten, int quality) {
+ SaveRequest request = new SaveRequest();
+ request.sourceUri = sourceUri;
+ request.selectedUri = selectedUri;
+ request.destinationFile = destinationFile;
+ request.preset = preset;
+ request.flatten = flatten;
+ request.quality = quality;
+ postRequest(request);
+ }
+
+ public Result doInBackground(Request message) {
+ SaveRequest request = (SaveRequest) message;
+ Uri sourceUri = request.sourceUri;
+ Uri selectedUri = request.selectedUri;
+ File destinationFile = request.destinationFile;
+ ImagePreset preset = request.preset;
+ boolean flatten = request.flatten;
+ // We create a small bitmap showing the result that we can
+ // give to the notification
+ UpdateBitmap updateBitmap = new UpdateBitmap();
+ updateBitmap.bitmap = createNotificationBitmap(sourceUri, preset);
+ postUpdate(updateBitmap);
+ SaveImage saveImage = new SaveImage(mProcessingService, sourceUri,
+ selectedUri, destinationFile,
+ new SaveImage.Callback() {
+ @Override
+ public void onProgress(int max, int current) {
+ UpdateProgress updateProgress = new UpdateProgress();
+ updateProgress.max = max;
+ updateProgress.current = current;
+ postUpdate(updateProgress);
+ }
+ });
+ Uri uri = saveImage.processAndSaveImage(preset, !flatten, request.quality);
+ URIResult result = new URIResult();
+ result.uri = uri;
+ return result;
+ }
+
+ @Override
+ public void onResult(Result message) {
+ URIResult result = (URIResult) message;
+ mProcessingService.completeSaveImage(result.uri);
+ }
+
+ @Override
+ public void onUpdate(Update message) {
+ if (message instanceof UpdateBitmap) {
+ Bitmap bitmap = ((UpdateBitmap) message).bitmap;
+ mProcessingService.updateNotificationWithBitmap(bitmap);
+ }
+ if (message instanceof UpdateProgress) {
+ UpdateProgress progress = (UpdateProgress) message;
+ mProcessingService.updateProgress(progress.max, progress.current);
+ }
+ }
+
+ private Bitmap createNotificationBitmap(Uri sourceUri, ImagePreset preset) {
+ int notificationBitmapSize = Resources.getSystem().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_width);
+ Bitmap bitmap = ImageLoader.loadConstrainedBitmap(sourceUri, getContext(),
+ notificationBitmapSize, null, true);
+ CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(), "Thumb");
+ return pipeline.renderFinalImage(bitmap, preset);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java b/src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java
new file mode 100644
index 000000000..d53768c95
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.support.v8.renderscript.Allocation;
+import android.support.v8.renderscript.RenderScript;
+
+public interface PipelineInterface {
+ public String getName();
+ public Resources getResources();
+ public Allocation getInPixelsAllocation();
+ public Allocation getOutPixelsAllocation();
+ public boolean prepareRenderscriptAllocations(Bitmap bitmap);
+ public RenderScript getRSContext();
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java b/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java
new file mode 100644
index 000000000..d0504d11f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.tools.SaveImage;
+
+import java.io.File;
+
+public class ProcessingService extends Service {
+ private static final String LOGTAG = "ProcessingService";
+ private static final boolean SHOW_IMAGE = false;
+ private int mNotificationId;
+ private NotificationManager mNotifyMgr = null;
+ private Notification.Builder mBuilder = null;
+
+ private static final String PRESET = "preset";
+ private static final String QUALITY = "quality";
+ private static final String SOURCE_URI = "sourceUri";
+ private static final String SELECTED_URI = "selectedUri";
+ private static final String DESTINATION_FILE = "destinationFile";
+ private static final String SAVING = "saving";
+ private static final String FLATTEN = "flatten";
+
+ private ProcessingTaskController mProcessingTaskController;
+ private ImageSavingTask mImageSavingTask;
+ private UpdatePreviewTask mUpdatePreviewTask;
+ private HighresRenderingRequestTask mHighresRenderingRequestTask;
+ private RenderingRequestTask mRenderingRequestTask;
+
+ private final IBinder mBinder = new LocalBinder();
+ private FilterShowActivity mFiltershowActivity;
+
+ private boolean mSaving = false;
+ private boolean mNeedsAlive = false;
+
+ public void setFiltershowActivity(FilterShowActivity filtershowActivity) {
+ mFiltershowActivity = filtershowActivity;
+ }
+
+ public void setOriginalBitmap(Bitmap originalBitmap) {
+ if (mUpdatePreviewTask == null) {
+ return;
+ }
+ mUpdatePreviewTask.setOriginal(originalBitmap);
+ mHighresRenderingRequestTask.setOriginal(originalBitmap);
+ mRenderingRequestTask.setOriginal(originalBitmap);
+ }
+
+ public void updatePreviewBuffer() {
+ mHighresRenderingRequestTask.stop();
+ mUpdatePreviewTask.updatePreview();
+ }
+
+ public void postRenderingRequest(RenderingRequest request) {
+ mRenderingRequestTask.postRenderingRequest(request);
+ }
+
+ public void postHighresRenderingRequest(ImagePreset preset, float scaleFactor,
+ RenderingRequestCaller caller) {
+ RenderingRequest request = new RenderingRequest();
+ // TODO: use the triple buffer preset as UpdatePreviewTask does instead of creating a copy
+ ImagePreset passedPreset = new ImagePreset(preset);
+ request.setOriginalImagePreset(preset);
+ request.setScaleFactor(scaleFactor);
+ request.setImagePreset(passedPreset);
+ request.setType(RenderingRequest.HIGHRES_RENDERING);
+ request.setCaller(caller);
+ mHighresRenderingRequestTask.postRenderingRequest(request);
+ }
+
+ public void setHighresPreviewScaleFactor(float highResPreviewScale) {
+ mHighresRenderingRequestTask.setHighresPreviewScaleFactor(highResPreviewScale);
+ }
+
+ public void setPreviewScaleFactor(float previewScale) {
+ mHighresRenderingRequestTask.setPreviewScaleFactor(previewScale);
+ mRenderingRequestTask.setPreviewScaleFactor(previewScale);
+ }
+
+ public void setOriginalBitmapHighres(Bitmap originalHires) {
+ mHighresRenderingRequestTask.setOriginalBitmapHighres(originalHires);
+ }
+
+ public class LocalBinder extends Binder {
+ public ProcessingService getService() {
+ return ProcessingService.this;
+ }
+ }
+
+ public static Intent getSaveIntent(Context context, ImagePreset preset, File destination,
+ Uri selectedImageUri, Uri sourceImageUri, boolean doFlatten, int quality) {
+ Intent processIntent = new Intent(context, ProcessingService.class);
+ processIntent.putExtra(ProcessingService.SOURCE_URI,
+ sourceImageUri.toString());
+ processIntent.putExtra(ProcessingService.SELECTED_URI,
+ selectedImageUri.toString());
+ processIntent.putExtra(ProcessingService.QUALITY, quality);
+ if (destination != null) {
+ processIntent.putExtra(ProcessingService.DESTINATION_FILE, destination.toString());
+ }
+ processIntent.putExtra(ProcessingService.PRESET,
+ preset.getJsonString(context.getString(R.string.saved)));
+ processIntent.putExtra(ProcessingService.SAVING, true);
+ if (doFlatten) {
+ processIntent.putExtra(ProcessingService.FLATTEN, true);
+ }
+ return processIntent;
+ }
+
+
+ @Override
+ public void onCreate() {
+ mProcessingTaskController = new ProcessingTaskController(this);
+ mImageSavingTask = new ImageSavingTask(this);
+ mUpdatePreviewTask = new UpdatePreviewTask();
+ mHighresRenderingRequestTask = new HighresRenderingRequestTask();
+ mRenderingRequestTask = new RenderingRequestTask();
+ mProcessingTaskController.add(mImageSavingTask);
+ mProcessingTaskController.add(mUpdatePreviewTask);
+ mProcessingTaskController.add(mHighresRenderingRequestTask);
+ mProcessingTaskController.add(mRenderingRequestTask);
+ setupPipeline();
+ }
+
+ @Override
+ public void onDestroy() {
+ tearDownPipeline();
+ mProcessingTaskController.quit();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ mNeedsAlive = true;
+ if (intent != null && intent.getBooleanExtra(SAVING, false)) {
+ // we save using an intent to keep the service around after the
+ // activity has been destroyed.
+ String presetJson = intent.getStringExtra(PRESET);
+ String source = intent.getStringExtra(SOURCE_URI);
+ String selected = intent.getStringExtra(SELECTED_URI);
+ String destination = intent.getStringExtra(DESTINATION_FILE);
+ int quality = intent.getIntExtra(QUALITY, 100);
+ boolean flatten = intent.getBooleanExtra(FLATTEN, false);
+ Uri sourceUri = Uri.parse(source);
+ Uri selectedUri = null;
+ if (selected != null) {
+ selectedUri = Uri.parse(selected);
+ }
+ File destinationFile = null;
+ if (destination != null) {
+ destinationFile = new File(destination);
+ }
+ ImagePreset preset = new ImagePreset();
+ preset.readJsonFromString(presetJson);
+ mNeedsAlive = false;
+ mSaving = true;
+ handleSaveRequest(sourceUri, selectedUri, destinationFile, preset, flatten, quality);
+ }
+ return START_REDELIVER_INTENT;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ public void onStart() {
+ mNeedsAlive = true;
+ if (!mSaving && mFiltershowActivity != null) {
+ mFiltershowActivity.updateUIAfterServiceStarted();
+ }
+ }
+
+ public void handleSaveRequest(Uri sourceUri, Uri selectedUri,
+ File destinationFile, ImagePreset preset, boolean flatten, int quality) {
+ mNotifyMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+
+ mNotificationId++;
+
+ mBuilder =
+ new Notification.Builder(this)
+ .setSmallIcon(R.drawable.filtershow_button_fx)
+ .setContentTitle(getString(R.string.filtershow_notification_label))
+ .setContentText(getString(R.string.filtershow_notification_message));
+
+ startForeground(mNotificationId, mBuilder.build());
+
+ updateProgress(SaveImage.MAX_PROCESSING_STEPS, 0);
+
+ // Process the image
+
+ mImageSavingTask.saveImage(sourceUri, selectedUri, destinationFile,
+ preset, flatten, quality);
+ }
+
+ public void updateNotificationWithBitmap(Bitmap bitmap) {
+ mBuilder.setLargeIcon(bitmap);
+ mNotifyMgr.notify(mNotificationId, mBuilder.build());
+ }
+
+ public void updateProgress(int max, int current) {
+ mBuilder.setProgress(max, current, false);
+ mNotifyMgr.notify(mNotificationId, mBuilder.build());
+ }
+
+ public void completeSaveImage(Uri result) {
+ if (SHOW_IMAGE) {
+ // TODO: we should update the existing image in Gallery instead
+ Intent viewImage = new Intent(Intent.ACTION_VIEW, result);
+ viewImage.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(viewImage);
+ }
+ stopForeground(true);
+ stopSelf();
+ if (mNeedsAlive) {
+ // If the app has been restarted while we were saving...
+ mFiltershowActivity.updateUIAfterServiceStarted();
+ } else if (mFiltershowActivity.isSimpleEditAction()) {
+ // terminate now
+ mFiltershowActivity.completeSaveImage(result);
+ }
+ }
+
+ private void setupPipeline() {
+ Resources res = getResources();
+ FiltersManager.setResources(res);
+ CachingPipeline.createRenderscriptContext(this);
+
+ FiltersManager filtersManager = FiltersManager.getManager();
+ filtersManager.addLooks(this);
+ filtersManager.addBorders(this);
+ filtersManager.addTools(this);
+ filtersManager.addEffects();
+
+ FiltersManager highresFiltersManager = FiltersManager.getHighresManager();
+ highresFiltersManager.addLooks(this);
+ highresFiltersManager.addBorders(this);
+ highresFiltersManager.addTools(this);
+ highresFiltersManager.addEffects();
+ }
+
+ private void tearDownPipeline() {
+ ImageFilter.resetStatics();
+ FiltersManager.getPreviewManager().freeRSFilterScripts();
+ FiltersManager.getManager().freeRSFilterScripts();
+ FiltersManager.getHighresManager().freeRSFilterScripts();
+ FiltersManager.reset();
+ CachingPipeline.destroyRenderScriptContext();
+ }
+
+ static {
+ System.loadLibrary("jni_filtershow_filters");
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java
new file mode 100644
index 000000000..8d3e8110f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+public abstract class ProcessingTask {
+ private ProcessingTaskController mTaskController;
+ private Handler mProcessingHandler;
+ private Handler mResultHandler;
+ private int mType;
+ private static final int DELAY = 300;
+
+ static interface Request {}
+ static interface Update {}
+ static interface Result {}
+
+ public boolean postRequest(Request message) {
+ Message msg = mProcessingHandler.obtainMessage(mType);
+ msg.obj = message;
+ if (isPriorityTask()) {
+ if (mProcessingHandler.hasMessages(getType())) {
+ return false;
+ }
+ mProcessingHandler.sendMessageAtFrontOfQueue(msg);
+ } else if (isDelayedTask()) {
+ if (mProcessingHandler.hasMessages(getType())) {
+ mProcessingHandler.removeMessages(getType());
+ }
+ mProcessingHandler.sendMessageDelayed(msg, DELAY);
+ } else {
+ mProcessingHandler.sendMessage(msg);
+ }
+ return true;
+ }
+
+ public void postUpdate(Update message) {
+ Message msg = mResultHandler.obtainMessage(mType);
+ msg.obj = message;
+ msg.arg1 = ProcessingTaskController.UPDATE;
+ mResultHandler.sendMessage(msg);
+ }
+
+ public void processRequest(Request message) {
+ Object result = doInBackground(message);
+ Message msg = mResultHandler.obtainMessage(mType);
+ msg.obj = result;
+ msg.arg1 = ProcessingTaskController.RESULT;
+ mResultHandler.sendMessage(msg);
+ }
+
+ public void added(ProcessingTaskController taskController) {
+ mTaskController = taskController;
+ mResultHandler = taskController.getResultHandler();
+ mProcessingHandler = taskController.getProcessingHandler();
+ mType = taskController.getReservedType();
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ public Context getContext() {
+ return mTaskController.getContext();
+ }
+
+ public abstract Result doInBackground(Request message);
+ public abstract void onResult(Result message);
+ public void onUpdate(Update message) {}
+ public boolean isPriorityTask() { return false; }
+ public boolean isDelayedTask() { return false; }
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java
new file mode 100644
index 000000000..b54bbb044
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.HashMap;
+
+public class ProcessingTaskController implements Handler.Callback {
+ private static final String LOGTAG = "ProcessingTaskController";
+
+ private Context mContext;
+ private HandlerThread mHandlerThread = null;
+ private Handler mProcessingHandler = null;
+ private int mCurrentType;
+ private HashMap<Integer, ProcessingTask> mTasks = new HashMap<Integer, ProcessingTask>();
+
+ public final static int RESULT = 1;
+ public final static int UPDATE = 2;
+
+ private final Handler mResultHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ ProcessingTask task = mTasks.get(msg.what);
+ if (task != null) {
+ if (msg.arg1 == RESULT) {
+ task.onResult((ProcessingTask.Result) msg.obj);
+ } else if (msg.arg1 == UPDATE) {
+ task.onUpdate((ProcessingTask.Update) msg.obj);
+ } else {
+ Log.w(LOGTAG, "received unknown message! " + msg.arg1);
+ }
+ }
+ }
+ };
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ ProcessingTask task = mTasks.get(msg.what);
+ if (task != null) {
+ task.processRequest((ProcessingTask.Request) msg.obj);
+ return true;
+ }
+ return false;
+ }
+
+ public ProcessingTaskController(Context context) {
+ mContext = context;
+ mHandlerThread = new HandlerThread("ProcessingTaskController",
+ android.os.Process.THREAD_PRIORITY_FOREGROUND);
+ mHandlerThread.start();
+ mProcessingHandler = new Handler(mHandlerThread.getLooper(), this);
+ }
+
+ public Handler getProcessingHandler() {
+ return mProcessingHandler;
+ }
+
+ public Handler getResultHandler() {
+ return mResultHandler;
+ }
+
+ public int getReservedType() {
+ return mCurrentType++;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public void add(ProcessingTask task) {
+ task.added(this);
+ mTasks.put(task.getType(), task);
+ }
+
+ public void quit() {
+ mHandlerThread.quit();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/RenderingRequest.java b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequest.java
new file mode 100644
index 000000000..ef4bb9bc0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import com.android.gallery3d.app.Log;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class RenderingRequest {
+ private static final String LOGTAG = "RenderingRequest";
+ private boolean mIsDirect = false;
+ private Bitmap mBitmap = null;
+ private ImagePreset mImagePreset = null;
+ private ImagePreset mOriginalImagePreset = null;
+ private RenderingRequestCaller mCaller = null;
+ private float mScaleFactor = 1.0f;
+ private Rect mBounds = null;
+ private Rect mDestination = null;
+ private int mType = FULL_RENDERING;
+ public static final int FULL_RENDERING = 0;
+ public static final int FILTERS_RENDERING = 1;
+ public static final int GEOMETRY_RENDERING = 2;
+ public static final int ICON_RENDERING = 3;
+ public static final int PARTIAL_RENDERING = 4;
+ public static final int HIGHRES_RENDERING = 5;
+ public static final int STYLE_ICON_RENDERING = 6;
+
+ private static final Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
+
+ public static void post(Context context, Bitmap source, ImagePreset preset,
+ int type, RenderingRequestCaller caller) {
+ RenderingRequest.post(context, source, preset, type, caller, null, null);
+ }
+
+ public static void post(Context context, Bitmap source, ImagePreset preset, int type,
+ RenderingRequestCaller caller, Rect bounds, Rect destination) {
+ if (((type != PARTIAL_RENDERING && type != HIGHRES_RENDERING) && source == null)
+ || preset == null || caller == null) {
+ Log.v(LOGTAG, "something null: source: " + source
+ + " or preset: " + preset + " or caller: " + caller);
+ return;
+ }
+ RenderingRequest request = new RenderingRequest();
+ Bitmap bitmap = null;
+ if (type == FULL_RENDERING
+ || type == GEOMETRY_RENDERING
+ || type == ICON_RENDERING
+ || type == STYLE_ICON_RENDERING) {
+ CachingPipeline pipeline = new CachingPipeline(
+ FiltersManager.getManager(), "Icon");
+ bitmap = pipeline.renderGeometryIcon(source, preset);
+ } else if (type != PARTIAL_RENDERING && type != HIGHRES_RENDERING) {
+ bitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(), mConfig);
+ }
+
+ request.setBitmap(bitmap);
+ ImagePreset passedPreset = new ImagePreset(preset);
+ request.setOriginalImagePreset(preset);
+ request.setScaleFactor(MasterImage.getImage().getScaleFactor());
+
+ if (type == PARTIAL_RENDERING) {
+ request.setBounds(bounds);
+ request.setDestination(destination);
+ passedPreset.setPartialRendering(true, bounds);
+ }
+
+ request.setImagePreset(passedPreset);
+ request.setType(type);
+ request.setCaller(caller);
+ request.post(context);
+ }
+
+ public void post(Context context) {
+ if (context instanceof FilterShowActivity) {
+ FilterShowActivity activity = (FilterShowActivity) context;
+ ProcessingService service = activity.getProcessingService();
+ service.postRenderingRequest(this);
+ }
+ }
+
+ public void markAvailable() {
+ if (mBitmap == null || mImagePreset == null
+ || mCaller == null) {
+ return;
+ }
+ mCaller.available(this);
+ }
+
+ public boolean isDirect() {
+ return mIsDirect;
+ }
+
+ public void setDirect(boolean isDirect) {
+ mIsDirect = isDirect;
+ }
+
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ public void setBitmap(Bitmap bitmap) {
+ mBitmap = bitmap;
+ }
+
+ public ImagePreset getImagePreset() {
+ return mImagePreset;
+ }
+
+ public void setImagePreset(ImagePreset imagePreset) {
+ mImagePreset = imagePreset;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ public void setType(int type) {
+ mType = type;
+ }
+
+ public void setCaller(RenderingRequestCaller caller) {
+ mCaller = caller;
+ }
+
+ public Rect getBounds() {
+ return mBounds;
+ }
+
+ public void setBounds(Rect bounds) {
+ mBounds = bounds;
+ }
+
+ public void setScaleFactor(float scaleFactor) {
+ mScaleFactor = scaleFactor;
+ }
+
+ public float getScaleFactor() {
+ return mScaleFactor;
+ }
+
+ public Rect getDestination() {
+ return mDestination;
+ }
+
+ public void setDestination(Rect destination) {
+ mDestination = destination;
+ }
+
+ public ImagePreset getOriginalImagePreset() {
+ return mOriginalImagePreset;
+ }
+
+ public void setOriginalImagePreset(ImagePreset originalImagePreset) {
+ mOriginalImagePreset = originalImagePreset;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestCaller.java b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestCaller.java
new file mode 100644
index 000000000..b978e7040
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestCaller.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+public interface RenderingRequestCaller {
+ public void available(RenderingRequest request);
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestTask.java b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestTask.java
new file mode 100644
index 000000000..7a83f7072
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestTask.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.graphics.Bitmap;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+
+public class RenderingRequestTask extends ProcessingTask {
+
+ private CachingPipeline mPreviewPipeline = null;
+ private boolean mPipelineIsOn = false;
+
+ public void setPreviewScaleFactor(float previewScale) {
+ mPreviewPipeline.setPreviewScaleFactor(previewScale);
+ }
+
+ static class Render implements Request {
+ RenderingRequest request;
+ }
+
+ static class RenderResult implements Result {
+ RenderingRequest request;
+ }
+
+ public RenderingRequestTask() {
+ mPreviewPipeline = new CachingPipeline(
+ FiltersManager.getManager(), "Normal");
+ }
+
+ public void setOriginal(Bitmap bitmap) {
+ mPreviewPipeline.setOriginal(bitmap);
+ mPipelineIsOn = true;
+ }
+
+ public void stop() {
+ mPreviewPipeline.stop();
+ }
+
+ public void postRenderingRequest(RenderingRequest request) {
+ if (!mPipelineIsOn) {
+ return;
+ }
+ Render render = new Render();
+ render.request = request;
+ postRequest(render);
+ }
+
+ @Override
+ public Result doInBackground(Request message) {
+ RenderingRequest request = ((Render) message).request;
+ RenderResult result = null;
+ mPreviewPipeline.render(request);
+ result = new RenderResult();
+ result.request = request;
+ return result;
+ }
+
+ @Override
+ public void onResult(Result message) {
+ if (message == null) {
+ return;
+ }
+ RenderingRequest request = ((RenderResult) message).request;
+ request.markAvailable();
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/SharedBuffer.java b/src/com/android/gallery3d/filtershow/pipeline/SharedBuffer.java
new file mode 100644
index 000000000..98e69f60e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/SharedBuffer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.graphics.Bitmap;
+
+public class SharedBuffer {
+
+ private static final String LOGTAG = "SharedBuffer";
+
+ private volatile Buffer mProducer = null;
+ private volatile Buffer mConsumer = null;
+ private volatile Buffer mIntermediate = null;
+
+ private volatile boolean mNeedsSwap = false;
+ private volatile boolean mNeedsRepaint = true;
+
+ public void setProducer(Bitmap producer) {
+ Buffer buffer = new Buffer(producer);
+ synchronized (this) {
+ mProducer = buffer;
+ }
+ }
+
+ public synchronized Buffer getProducer() {
+ return mProducer;
+ }
+
+ public synchronized Buffer getConsumer() {
+ return mConsumer;
+ }
+
+ public synchronized void swapProducer() {
+ Buffer intermediate = mIntermediate;
+ mIntermediate = mProducer;
+ mProducer = intermediate;
+ mNeedsSwap = true;
+ }
+
+ public synchronized void swapConsumerIfNeeded() {
+ if (!mNeedsSwap) {
+ return;
+ }
+ Buffer intermediate = mIntermediate;
+ mIntermediate = mConsumer;
+ mConsumer = intermediate;
+ mNeedsSwap = false;
+ }
+
+ public synchronized void invalidate() {
+ mNeedsRepaint = true;
+ }
+
+ public synchronized boolean checkRepaintNeeded() {
+ if (mNeedsRepaint) {
+ mNeedsRepaint = false;
+ return true;
+ }
+ return false;
+ }
+
+}
+
diff --git a/src/com/android/gallery3d/filtershow/pipeline/SharedPreset.java b/src/com/android/gallery3d/filtershow/pipeline/SharedPreset.java
new file mode 100644
index 000000000..3f850fed2
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/SharedPreset.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+public class SharedPreset {
+
+ private volatile ImagePreset mProducerPreset = null;
+ private volatile ImagePreset mConsumerPreset = null;
+ private volatile ImagePreset mIntermediatePreset = null;
+
+ public synchronized void enqueuePreset(ImagePreset preset) {
+ if (mProducerPreset == null || (!mProducerPreset.same(preset))) {
+ mProducerPreset = new ImagePreset(preset);
+ } else {
+ mProducerPreset.updateWith(preset);
+ }
+ ImagePreset temp = mIntermediatePreset;
+ mIntermediatePreset = mProducerPreset;
+ mProducerPreset = temp;
+ }
+
+ public synchronized ImagePreset dequeuePreset() {
+ ImagePreset temp = mConsumerPreset;
+ mConsumerPreset = mIntermediatePreset;
+ mIntermediatePreset = temp;
+ return mConsumerPreset;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java b/src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java
new file mode 100644
index 000000000..406cc9bf5
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013 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.pipeline;
+
+import android.graphics.Bitmap;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class UpdatePreviewTask extends ProcessingTask {
+ private CachingPipeline mPreviewPipeline = null;
+ private boolean mHasUnhandledPreviewRequest = false;
+ private boolean mPipelineIsOn = false;
+
+ public UpdatePreviewTask() {
+ mPreviewPipeline = new CachingPipeline(
+ FiltersManager.getPreviewManager(), "Preview");
+ }
+
+ public void setOriginal(Bitmap bitmap) {
+ mPreviewPipeline.setOriginal(bitmap);
+ mPipelineIsOn = true;
+ }
+
+ public void updatePreview() {
+ if (!mPipelineIsOn) {
+ return;
+ }
+ mHasUnhandledPreviewRequest = true;
+ if (postRequest(null)) {
+ mHasUnhandledPreviewRequest = false;
+ }
+ }
+
+ @Override
+ public boolean isPriorityTask() {
+ return true;
+ }
+
+ @Override
+ public Result doInBackground(Request message) {
+ SharedBuffer buffer = MasterImage.getImage().getPreviewBuffer();
+ SharedPreset preset = MasterImage.getImage().getPreviewPreset();
+ ImagePreset renderingPreset = preset.dequeuePreset();
+ if (renderingPreset != null) {
+ mPreviewPipeline.compute(buffer, renderingPreset, 0);
+ // set the preset we used in the buffer for later inspection UI-side
+ buffer.getProducer().setPreset(renderingPreset);
+ buffer.getProducer().sync();
+ buffer.swapProducer(); // push back the result
+ }
+ return null;
+ }
+
+ @Override
+ public void onResult(Result message) {
+ MasterImage.getImage().notifyObservers();
+ if (mHasUnhandledPreviewRequest) {
+ updatePreview();
+ }
+ }
+
+ public void setPipelineIsOn(boolean pipelineIsOn) {
+ mPipelineIsOn = pipelineIsOn;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/PresetManagementDialog.java b/src/com/android/gallery3d/filtershow/presets/PresetManagementDialog.java
new file mode 100644
index 000000000..7ab61fcc9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/PresetManagementDialog.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 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.presets;
+
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+
+public class PresetManagementDialog extends DialogFragment implements View.OnClickListener {
+ private UserPresetsAdapter mAdapter;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.filtershow_presets_management_dialog, container);
+
+ FilterShowActivity activity = (FilterShowActivity) getActivity();
+ mAdapter = activity.getUserPresetsAdapter();
+ ListView panel = (ListView) view.findViewById(R.id.listItems);
+ panel.setAdapter(mAdapter);
+
+ view.findViewById(R.id.cancel).setOnClickListener(this);
+ view.findViewById(R.id.addpreset).setOnClickListener(this);
+ view.findViewById(R.id.ok).setOnClickListener(this);
+ getDialog().setTitle(getString(R.string.filtershow_manage_preset));
+ return view;
+ }
+
+ @Override
+ public void onClick(View v) {
+ FilterShowActivity activity = (FilterShowActivity) getActivity();
+ switch (v.getId()) {
+ case R.id.cancel:
+ mAdapter.clearChangedRepresentations();
+ mAdapter.clearDeletedRepresentations();
+ activity.updateUserPresetsFromAdapter(mAdapter);
+ dismiss();
+ break;
+ case R.id.addpreset:
+ activity.saveCurrentImagePreset();
+ dismiss();
+ break;
+ case R.id.ok:
+ mAdapter.updateCurrent();
+ activity.updateUserPresetsFromAdapter(mAdapter);
+ dismiss();
+ break;
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/UserPresetsAdapter.java b/src/com/android/gallery3d/filtershow/presets/UserPresetsAdapter.java
new file mode 100644
index 000000000..dab9ea454
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/UserPresetsAdapter.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2013 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.presets;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.category.Action;
+import com.android.gallery3d.filtershow.category.CategoryView;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
+
+import java.util.ArrayList;
+
+public class UserPresetsAdapter extends ArrayAdapter<Action>
+ implements View.OnClickListener, View.OnFocusChangeListener {
+ private static final String LOGTAG = "UserPresetsAdapter";
+ private LayoutInflater mInflater;
+ private int mIconSize = 160;
+ private ArrayList<FilterUserPresetRepresentation> mDeletedRepresentations =
+ new ArrayList<FilterUserPresetRepresentation>();
+ private ArrayList<FilterUserPresetRepresentation> mChangedRepresentations =
+ new ArrayList<FilterUserPresetRepresentation>();
+ private EditText mCurrentEditText;
+
+ public UserPresetsAdapter(Context context, int textViewResourceId) {
+ super(context, textViewResourceId);
+ mInflater = LayoutInflater.from(context);
+ mIconSize = context.getResources().getDimensionPixelSize(R.dimen.category_panel_icon_size);
+ }
+
+ public UserPresetsAdapter(Context context) {
+ this(context, 0);
+ }
+
+ @Override
+ public void add(Action action) {
+ super.add(action);
+ action.setAdapter(this);
+ }
+
+ private void deletePreset(Action action) {
+ FilterRepresentation rep = action.getRepresentation();
+ if (rep instanceof FilterUserPresetRepresentation) {
+ mDeletedRepresentations.add((FilterUserPresetRepresentation) rep);
+ }
+ remove(action);
+ notifyDataSetChanged();
+ }
+
+ private void changePreset(Action action) {
+ FilterRepresentation rep = action.getRepresentation();
+ rep.setName(action.getName());
+ if (rep instanceof FilterUserPresetRepresentation) {
+ mChangedRepresentations.add((FilterUserPresetRepresentation) rep);
+ }
+ }
+
+ public void updateCurrent() {
+ if (mCurrentEditText != null) {
+ updateActionFromEditText(mCurrentEditText);
+ }
+ }
+
+ static class UserPresetViewHolder {
+ ImageView imageView;
+ EditText editText;
+ ImageButton deleteButton;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ UserPresetViewHolder viewHolder;
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.filtershow_presets_management_row, null);
+ viewHolder = new UserPresetViewHolder();
+ viewHolder.imageView = (ImageView) convertView.findViewById(R.id.imageView);
+ viewHolder.editText = (EditText) convertView.findViewById(R.id.editView);
+ viewHolder.deleteButton = (ImageButton) convertView.findViewById(R.id.deleteUserPreset);
+ viewHolder.editText.setOnClickListener(this);
+ viewHolder.editText.setOnFocusChangeListener(this);
+ viewHolder.deleteButton.setOnClickListener(this);
+ convertView.setTag(viewHolder);
+ } else {
+ viewHolder = (UserPresetViewHolder) convertView.getTag();
+ }
+ Action action = getItem(position);
+ viewHolder.imageView.setImageBitmap(action.getImage());
+ if (action.getImage() == null) {
+ // queue image rendering for this action
+ action.setImageFrame(new Rect(0, 0, mIconSize, mIconSize), CategoryView.VERTICAL);
+ }
+ viewHolder.deleteButton.setTag(action);
+ viewHolder.editText.setTag(action);
+ viewHolder.editText.setHint(action.getName());
+
+ return convertView;
+ }
+
+ public ArrayList<FilterUserPresetRepresentation> getDeletedRepresentations() {
+ return mDeletedRepresentations;
+ }
+
+ public void clearDeletedRepresentations() {
+ mDeletedRepresentations.clear();
+ }
+
+ public ArrayList<FilterUserPresetRepresentation> getChangedRepresentations() {
+ return mChangedRepresentations;
+ }
+
+ public void clearChangedRepresentations() {
+ mChangedRepresentations.clear();
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.editView:
+ v.requestFocus();
+ break;
+ case R.id.deleteUserPreset:
+ Action action = (Action) v.getTag();
+ deletePreset(action);
+ break;
+ }
+ }
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (v.getId() != R.id.editView) {
+ return;
+ }
+ EditText editText = (EditText) v;
+ if (!hasFocus) {
+ updateActionFromEditText(editText);
+ } else {
+ mCurrentEditText = editText;
+ }
+ }
+
+ private void updateActionFromEditText(EditText editText) {
+ Action action = (Action) editText.getTag();
+ String newName = editText.getText().toString();
+ if (newName.length() > 0) {
+ action.setName(editText.getText().toString());
+ changePreset(action);
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/provider/SharedImageProvider.java b/src/com/android/gallery3d/filtershow/provider/SharedImageProvider.java
new file mode 100644
index 000000000..bc17a6e03
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/provider/SharedImageProvider.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.provider;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.ConditionVariable;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+public class SharedImageProvider extends ContentProvider {
+
+ private static final String LOGTAG = "SharedImageProvider";
+
+ public static final String MIME_TYPE = "image/jpeg";
+ public static final String AUTHORITY = "com.android.gallery3d.filtershow.provider.SharedImageProvider";
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/image");
+ public static final String PREPARE = "prepare";
+
+ private final String[] mMimeStreamType = {
+ MIME_TYPE
+ };
+
+ private static ConditionVariable mImageReadyCond = new ConditionVariable(false);
+
+ @Override
+ public int delete(Uri arg0, String arg1, String[] arg2) {
+ return 0;
+ }
+
+ @Override
+ public String getType(Uri arg0) {
+ return MIME_TYPE;
+ }
+
+ @Override
+ public String[] getStreamTypes(Uri arg0, String mimeTypeFilter) {
+ return mMimeStreamType;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ if (values.containsKey(PREPARE)) {
+ if (values.getAsBoolean(PREPARE)) {
+ mImageReadyCond.close();
+ } else {
+ mImageReadyCond.open();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
+ return 0;
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ String uriPath = uri.getLastPathSegment();
+ if (uriPath == null) {
+ return null;
+ }
+ if (projection == null) {
+ projection = new String[] {
+ BaseColumns._ID,
+ MediaStore.MediaColumns.DATA,
+ OpenableColumns.DISPLAY_NAME,
+ OpenableColumns.SIZE
+ };
+ }
+ // If we receive a query on display name or size,
+ // we should block until the image is ready
+ mImageReadyCond.block();
+
+ File path = new File(uriPath);
+
+ MatrixCursor cursor = new MatrixCursor(projection);
+ Object[] columns = new Object[projection.length];
+ for (int i = 0; i < projection.length; i++) {
+ if (projection[i].equalsIgnoreCase(BaseColumns._ID)) {
+ columns[i] = 0;
+ } else if (projection[i].equalsIgnoreCase(MediaStore.MediaColumns.DATA)) {
+ columns[i] = uri;
+ } else if (projection[i].equalsIgnoreCase(OpenableColumns.DISPLAY_NAME)) {
+ columns[i] = path.getName();
+ } else if (projection[i].equalsIgnoreCase(OpenableColumns.SIZE)) {
+ columns[i] = path.length();
+ }
+ }
+ cursor.addRow(columns);
+
+ return cursor;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ String uriPath = uri.getLastPathSegment();
+ if (uriPath == null) {
+ return null;
+ }
+ // Here we need to block until the image is ready
+ mImageReadyCond.block();
+ File path = new File(uriPath);
+ int imode = 0;
+ imode |= ParcelFileDescriptor.MODE_READ_ONLY;
+ return ParcelFileDescriptor.open(path, imode);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/DragListener.java b/src/com/android/gallery3d/filtershow/state/DragListener.java
new file mode 100644
index 000000000..1aa81ed69
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/DragListener.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 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.state;
+
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+
+class DragListener implements View.OnDragListener {
+
+ private static final String LOGTAG = "DragListener";
+ private PanelTrack mStatePanelTrack;
+ private static float sSlope = 0.2f;
+
+ public DragListener(PanelTrack statePanelTrack) {
+ mStatePanelTrack = statePanelTrack;
+ }
+
+ private void setState(DragEvent event) {
+ float translation = event.getY() - mStatePanelTrack.getTouchPoint().y;
+ float alpha = 1.0f - (Math.abs(translation)
+ / mStatePanelTrack.getCurrentView().getHeight());
+ if (mStatePanelTrack.getOrientation() == LinearLayout.VERTICAL) {
+ translation = event.getX() - mStatePanelTrack.getTouchPoint().x;
+ alpha = 1.0f - (Math.abs(translation)
+ / mStatePanelTrack.getCurrentView().getWidth());
+ mStatePanelTrack.getCurrentView().setTranslationX(translation);
+ } else {
+ mStatePanelTrack.getCurrentView().setTranslationY(translation);
+ }
+ mStatePanelTrack.getCurrentView().setBackgroundAlpha(alpha);
+ }
+
+ @Override
+ public boolean onDrag(View v, DragEvent event) {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED: {
+ break;
+ }
+ case DragEvent.ACTION_DRAG_LOCATION: {
+ if (mStatePanelTrack.getCurrentView() != null) {
+ setState(event);
+ View over = mStatePanelTrack.findChildAt((int) event.getX(),
+ (int) event.getY());
+ if (over != null && over != mStatePanelTrack.getCurrentView()) {
+ StateView stateView = (StateView) over;
+ if (stateView != mStatePanelTrack.getCurrentView()) {
+ int pos = mStatePanelTrack.findChild(over);
+ int origin = mStatePanelTrack.findChild(
+ mStatePanelTrack.getCurrentView());
+ ArrayAdapter array = (ArrayAdapter) mStatePanelTrack.getAdapter();
+ if (origin != -1 && pos != -1) {
+ State current = (State) array.getItem(origin);
+ array.remove(current);
+ array.insert(current, pos);
+ mStatePanelTrack.fillContent(false);
+ mStatePanelTrack.setCurrentView(mStatePanelTrack.getChildAt(pos));
+ }
+ }
+ }
+ }
+ break;
+ }
+ case DragEvent.ACTION_DRAG_ENTERED: {
+ mStatePanelTrack.setExited(false);
+ if (mStatePanelTrack.getCurrentView() != null) {
+ mStatePanelTrack.getCurrentView().setVisibility(View.VISIBLE);
+ }
+ return true;
+ }
+ case DragEvent.ACTION_DRAG_EXITED: {
+ if (mStatePanelTrack.getCurrentView() != null) {
+ setState(event);
+ mStatePanelTrack.getCurrentView().setVisibility(View.INVISIBLE);
+ }
+ mStatePanelTrack.setExited(true);
+ break;
+ }
+ case DragEvent.ACTION_DROP: {
+ break;
+ }
+ case DragEvent.ACTION_DRAG_ENDED: {
+ if (mStatePanelTrack.getCurrentView() != null
+ && mStatePanelTrack.getCurrentView().getAlpha() > sSlope) {
+ setState(event);
+ }
+ mStatePanelTrack.checkEndState();
+ break;
+ }
+ default:
+ break;
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/PanelTrack.java b/src/com/android/gallery3d/filtershow/state/PanelTrack.java
new file mode 100644
index 000000000..d02207d9b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/PanelTrack.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.state;
+
+import android.graphics.Point;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Adapter;
+
+public interface PanelTrack {
+ public int getOrientation();
+ public void onTouch(MotionEvent event, StateView view);
+ public StateView getCurrentView();
+ public void setCurrentView(View view);
+ public Point getTouchPoint();
+ public View findChildAt(int x, int y);
+ public int findChild(View view);
+ public Adapter getAdapter();
+ public void fillContent(boolean value);
+ public View getChildAt(int pos);
+ public void setExited(boolean value);
+ public void checkEndState();
+}
diff --git a/src/com/android/gallery3d/filtershow/state/State.java b/src/com/android/gallery3d/filtershow/state/State.java
new file mode 100644
index 000000000..e7dedd6a2
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/State.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 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.state;
+
+import com.android.gallery3d.filtershow.filters.FilterFxRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+
+public class State {
+ private String mText;
+ private int mType;
+ private FilterRepresentation mFilterRepresentation;
+
+ public State(State state) {
+ this(state.getText(), state.getType());
+ }
+
+ public State(String text) {
+ this(text, StateView.DEFAULT);
+ }
+
+ public State(String text, int type) {
+ mText = text;
+ mType = type;
+ }
+
+ public boolean equals(State state) {
+ if (mFilterRepresentation.getFilterClass()
+ != state.mFilterRepresentation.getFilterClass()) {
+ return false;
+ }
+ if (mFilterRepresentation instanceof FilterFxRepresentation) {
+ return mFilterRepresentation.equals(state.getFilterRepresentation());
+ }
+ return true;
+ }
+
+ public boolean isDraggable() {
+ return mFilterRepresentation != null;
+ }
+
+ String getText() {
+ return mText;
+ }
+
+ void setText(String text) {
+ mText = text;
+ }
+
+ int getType() {
+ return mType;
+ }
+
+ void setType(int type) {
+ mType = type;
+ }
+
+ public FilterRepresentation getFilterRepresentation() {
+ return mFilterRepresentation;
+ }
+
+ public void setFilterRepresentation(FilterRepresentation filterRepresentation) {
+ mFilterRepresentation = filterRepresentation;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/StateAdapter.java b/src/com/android/gallery3d/filtershow/state/StateAdapter.java
new file mode 100644
index 000000000..522585280
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/StateAdapter.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2013 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.state;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+import java.util.Vector;
+
+public class StateAdapter extends ArrayAdapter<State> {
+
+ private static final String LOGTAG = "StateAdapter";
+ private int mOrientation;
+ private String mOriginalText;
+ private String mResultText;
+
+ public StateAdapter(Context context, int textViewResourceId) {
+ super(context, textViewResourceId);
+ mOriginalText = context.getString(R.string.state_panel_original);
+ mResultText = context.getString(R.string.state_panel_result);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ StateView view = null;
+ if (convertView == null) {
+ convertView = new StateView(getContext());
+ }
+ view = (StateView) convertView;
+ State state = getItem(position);
+ view.setState(state);
+ view.setOrientation(mOrientation);
+ FilterRepresentation currentRep = MasterImage.getImage().getCurrentFilterRepresentation();
+ FilterRepresentation stateRep = state.getFilterRepresentation();
+ if (currentRep != null && stateRep != null
+ && currentRep.getFilterClass() == stateRep.getFilterClass()
+ && currentRep.getEditorId() != ImageOnlyEditor.ID) {
+ view.setSelected(true);
+ } else {
+ view.setSelected(false);
+ }
+ return view;
+ }
+
+ public boolean contains(State state) {
+ for (int i = 0; i < getCount(); i++) {
+ if (state == getItem(i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void setOrientation(int orientation) {
+ mOrientation = orientation;
+ }
+
+ public void addOriginal() {
+ add(new State(mOriginalText));
+ }
+
+ public boolean same(Vector<State> states) {
+ // we have the original state in addition
+ if (states.size() + 1 != getCount()) {
+ return false;
+ }
+ for (int i = 1; i < getCount(); i++) {
+ State state = getItem(i);
+ if (!state.equals(states.elementAt(i-1))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void fill(Vector<State> states) {
+ if (same(states)) {
+ return;
+ }
+ clear();
+ addOriginal();
+ addAll(states);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void remove(State state) {
+ super.remove(state);
+ FilterRepresentation filterRepresentation = state.getFilterRepresentation();
+ FilterShowActivity activity = (FilterShowActivity) getContext();
+ activity.removeFilterRepresentation(filterRepresentation);
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/StatePanel.java b/src/com/android/gallery3d/filtershow/state/StatePanel.java
new file mode 100644
index 000000000..df470f23e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/StatePanel.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 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.state;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class StatePanel extends Fragment {
+ private static final String LOGTAG = "StatePanel";
+ private StatePanelTrack track;
+ private LinearLayout mMainView;
+ public static final String FRAGMENT_TAG = "StatePanel";
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mMainView = (LinearLayout) inflater.inflate(R.layout.filtershow_state_panel_new, null);
+ View panel = mMainView.findViewById(R.id.listStates);
+ track = (StatePanelTrack) panel;
+ track.setAdapter(MasterImage.getImage().getState());
+ return mMainView;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/StatePanelTrack.java b/src/com/android/gallery3d/filtershow/state/StatePanelTrack.java
new file mode 100644
index 000000000..fff7e7f5f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/StatePanelTrack.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2013 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.state;
+
+import android.animation.LayoutTransition;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Adapter;
+import android.widget.LinearLayout;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class StatePanelTrack extends LinearLayout implements PanelTrack {
+
+ private static final String LOGTAG = "StatePanelTrack";
+ private Point mTouchPoint;
+ private StateView mCurrentView;
+ private StateView mCurrentSelectedView;
+ private boolean mExited = false;
+ private boolean mStartedDrag = false;
+ private StateAdapter mAdapter;
+ private DragListener mDragListener = new DragListener(this);
+ private float mDeleteSlope = 0.2f;
+ private GestureDetector mGestureDetector;
+ private int mElemWidth;
+ private int mElemHeight;
+ private int mElemSize;
+ private int mElemEndSize;
+ private int mEndElemWidth;
+ private int mEndElemHeight;
+ private long mTouchTime;
+ private int mMaxTouchDelay = 300; // 300ms delay for touch
+ private static final boolean ALLOWS_DRAG = false;
+ private DataSetObserver mObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ super.onChanged();
+ fillContent(false);
+ }
+
+ @Override
+ public void onInvalidated() {
+ super.onInvalidated();
+ fillContent(false);
+ }
+ };
+
+ public StatePanelTrack(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StatePanelTrack);
+ mElemSize = a.getDimensionPixelSize(R.styleable.StatePanelTrack_elemSize, 0);
+ mElemEndSize = a.getDimensionPixelSize(R.styleable.StatePanelTrack_elemEndSize, 0);
+ if (getOrientation() == LinearLayout.HORIZONTAL) {
+ mElemWidth = mElemSize;
+ mElemHeight = LayoutParams.MATCH_PARENT;
+ mEndElemWidth = mElemEndSize;
+ mEndElemHeight = LayoutParams.MATCH_PARENT;
+ } else {
+ mElemWidth = LayoutParams.MATCH_PARENT;
+ mElemHeight = mElemSize;
+ mEndElemWidth = LayoutParams.MATCH_PARENT;
+ mEndElemHeight = mElemEndSize;
+ }
+ GestureDetector.SimpleOnGestureListener simpleOnGestureListener
+ = new GestureDetector.SimpleOnGestureListener(){
+ @Override
+ public void onLongPress(MotionEvent e) {
+ longPress(e);
+ }
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ addDuplicate(e);
+ return true;
+ }
+ };
+ mGestureDetector = new GestureDetector(context, simpleOnGestureListener);
+ }
+
+ private void addDuplicate(MotionEvent e) {
+ if (mCurrentSelectedView == null) {
+ return;
+ }
+ int pos = findChild(mCurrentSelectedView);
+ if (pos != -1) {
+ mAdapter.insert(new State(mCurrentSelectedView.getState()), pos);
+ fillContent(true);
+ }
+ }
+
+ private void longPress(MotionEvent e) {
+ View view = findChildAt((int) e.getX(), (int) e.getY());
+ if (view == null) {
+ return;
+ }
+ if (view instanceof StateView) {
+ StateView stateView = (StateView) view;
+ stateView.setDuplicateButton(true);
+ }
+ }
+
+ public void setAdapter(StateAdapter adapter) {
+ mAdapter = adapter;
+ mAdapter.registerDataSetObserver(mObserver);
+ mAdapter.setOrientation(getOrientation());
+ fillContent(false);
+ requestLayout();
+ }
+
+ public StateView findChildWithState(State state) {
+ for (int i = 0; i < getChildCount(); i++) {
+ StateView view = (StateView) getChildAt(i);
+ if (view.getState() == state) {
+ return view;
+ }
+ }
+ return null;
+ }
+
+ public void fillContent(boolean animate) {
+ if (!animate) {
+ this.setLayoutTransition(null);
+ }
+ int n = mAdapter.getCount();
+ for (int i = 0; i < getChildCount(); i++) {
+ StateView child = (StateView) getChildAt(i);
+ child.resetPosition();
+ if (!mAdapter.contains(child.getState())) {
+ removeView(child);
+ }
+ }
+ LayoutParams params = new LayoutParams(mElemWidth, mElemHeight);
+ for (int i = 0; i < n; i++) {
+ State s = mAdapter.getItem(i);
+ if (findChildWithState(s) == null) {
+ View view = mAdapter.getView(i, null, this);
+ addView(view, i, params);
+ }
+ }
+
+ for (int i = 0; i < n; i++) {
+ State state = mAdapter.getItem(i);
+ StateView view = (StateView) getChildAt(i);
+ view.setState(state);
+ if (i == 0) {
+ view.setType(StateView.BEGIN);
+ } else if (i == n - 1) {
+ view.setType(StateView.END);
+ } else {
+ view.setType(StateView.DEFAULT);
+ }
+ view.resetPosition();
+ }
+
+ if (!animate) {
+ this.setLayoutTransition(new LayoutTransition());
+ }
+ }
+
+ public void onTouch(MotionEvent event, StateView view) {
+ if (!view.isDraggable()) {
+ return;
+ }
+ mCurrentView = view;
+ if (mCurrentSelectedView == mCurrentView) {
+ return;
+ }
+ if (mCurrentSelectedView != null) {
+ mCurrentSelectedView.setSelected(false);
+ }
+ // We changed the current view -- let's reset the
+ // gesture detector.
+ MotionEvent cancelEvent = MotionEvent.obtain(event);
+ cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
+ mGestureDetector.onTouchEvent(cancelEvent);
+ mCurrentSelectedView = mCurrentView;
+ // We have to send the event to the gesture detector
+ mGestureDetector.onTouchEvent(event);
+ mTouchTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (mCurrentView != null) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mCurrentView == null) {
+ return false;
+ }
+ if (mTouchTime == 0) {
+ mTouchTime = System.currentTimeMillis();
+ }
+ mGestureDetector.onTouchEvent(event);
+ if (mTouchPoint == null) {
+ mTouchPoint = new Point();
+ mTouchPoint.x = (int) event.getX();
+ mTouchPoint.y = (int) event.getY();
+ }
+
+ if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ float translation = event.getY() - mTouchPoint.y;
+ float alpha = 1.0f - (Math.abs(translation) / mCurrentView.getHeight());
+ if (getOrientation() == LinearLayout.VERTICAL) {
+ translation = event.getX() - mTouchPoint.x;
+ alpha = 1.0f - (Math.abs(translation) / mCurrentView.getWidth());
+ mCurrentView.setTranslationX(translation);
+ } else {
+ mCurrentView.setTranslationY(translation);
+ }
+ mCurrentView.setBackgroundAlpha(alpha);
+ if (ALLOWS_DRAG && alpha < 0.7) {
+ setOnDragListener(mDragListener);
+ DragShadowBuilder shadowBuilder = new DragShadowBuilder(mCurrentView);
+ mCurrentView.startDrag(null, shadowBuilder, mCurrentView, 0);
+ mStartedDrag = true;
+ }
+ }
+ if (!mExited && mCurrentView != null
+ && mCurrentView.getBackgroundAlpha() > mDeleteSlope
+ && event.getActionMasked() == MotionEvent.ACTION_UP
+ && System.currentTimeMillis() - mTouchTime < mMaxTouchDelay) {
+ FilterRepresentation representation = mCurrentView.getState().getFilterRepresentation();
+ mCurrentView.setSelected(true);
+ if (representation != MasterImage.getImage().getCurrentFilterRepresentation()) {
+ FilterShowActivity activity = (FilterShowActivity) getContext();
+ activity.showRepresentation(representation);
+ mCurrentView.setSelected(false);
+ }
+ }
+ if (event.getActionMasked() == MotionEvent.ACTION_UP
+ || (!mStartedDrag && event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
+ checkEndState();
+ if (mCurrentView != null) {
+ FilterRepresentation representation = mCurrentView.getState().getFilterRepresentation();
+ if (representation.getEditorId() == ImageOnlyEditor.ID) {
+ mCurrentView.setSelected(false);
+ }
+ }
+ }
+ return true;
+ }
+
+ public void checkEndState() {
+ mTouchPoint = null;
+ mTouchTime = 0;
+ if (mExited || mCurrentView.getBackgroundAlpha() < mDeleteSlope) {
+ int origin = findChild(mCurrentView);
+ if (origin != -1) {
+ State current = mAdapter.getItem(origin);
+ FilterRepresentation currentRep = MasterImage.getImage().getCurrentFilterRepresentation();
+ FilterRepresentation removedRep = current.getFilterRepresentation();
+ mAdapter.remove(current);
+ fillContent(true);
+ if (currentRep != null && removedRep != null
+ && currentRep.getFilterClass() == removedRep.getFilterClass()) {
+ FilterShowActivity activity = (FilterShowActivity) getContext();
+ activity.backToMain();
+ return;
+ }
+ }
+ } else {
+ mCurrentView.setBackgroundAlpha(1.0f);
+ mCurrentView.setTranslationX(0);
+ mCurrentView.setTranslationY(0);
+ }
+ if (mCurrentSelectedView != null) {
+ mCurrentSelectedView.invalidate();
+ }
+ if (mCurrentView != null) {
+ mCurrentView.invalidate();
+ }
+ mCurrentView = null;
+ mExited = false;
+ mStartedDrag = false;
+ }
+
+ public View findChildAt(int x, int y) {
+ Rect frame = new Rect();
+ int scrolledXInt = getScrollX() + x;
+ int scrolledYInt = getScrollY() + y;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ child.getHitRect(frame);
+ if (frame.contains(scrolledXInt, scrolledYInt)) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ public int findChild(View view) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child == view) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public StateView getCurrentView() {
+ return mCurrentView;
+ }
+
+ public void setCurrentView(View currentView) {
+ mCurrentView = (StateView) currentView;
+ }
+
+ public void setExited(boolean value) {
+ mExited = value;
+ }
+
+ public Point getTouchPoint() {
+ return mTouchPoint;
+ }
+
+ public Adapter getAdapter() {
+ return mAdapter;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/StateView.java b/src/com/android/gallery3d/filtershow/state/StateView.java
new file mode 100644
index 000000000..73d57846a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/StateView.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2013 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.state;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.*;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.LinearLayout;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class StateView extends View {
+
+ private static final String LOGTAG = "StateView";
+ private Path mPath = new Path();
+ private Paint mPaint = new Paint();
+
+ public static int DEFAULT = 0;
+ public static int BEGIN = 1;
+ public static int END = 2;
+
+ public static int UP = 1;
+ public static int DOWN = 2;
+ public static int LEFT = 3;
+ public static int RIGHT = 4;
+
+ private int mType = DEFAULT;
+ private float mAlpha = 1.0f;
+ private String mText = "Default";
+ private float mTextSize = 32;
+ private static int sMargin = 16;
+ private static int sArrowHeight = 16;
+ private static int sArrowWidth = 8;
+ private int mOrientation = LinearLayout.VERTICAL;
+ private int mDirection = DOWN;
+ private boolean mDuplicateButton;
+ private State mState;
+
+ private int mEndsBackgroundColor;
+ private int mEndsTextColor;
+ private int mBackgroundColor;
+ private int mTextColor;
+ private int mSelectedBackgroundColor;
+ private int mSelectedTextColor;
+ private Rect mTextBounds = new Rect();
+
+ public StateView(Context context) {
+ this(context, DEFAULT);
+ }
+
+ public StateView(Context context, int type) {
+ super(context);
+ mType = type;
+ Resources res = getResources();
+ mEndsBackgroundColor = res.getColor(R.color.filtershow_stateview_end_background);
+ mEndsTextColor = res.getColor(R.color.filtershow_stateview_end_text);
+ mBackgroundColor = res.getColor(R.color.filtershow_stateview_background);
+ mTextColor = res.getColor(R.color.filtershow_stateview_text);
+ mSelectedBackgroundColor = res.getColor(R.color.filtershow_stateview_selected_background);
+ mSelectedTextColor = res.getColor(R.color.filtershow_stateview_selected_text);
+ mTextSize = res.getDimensionPixelSize(R.dimen.state_panel_text_size);
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ public void setText(String text) {
+ mText = text;
+ invalidate();
+ }
+
+ public void setType(int type) {
+ mType = type;
+ invalidate();
+ }
+
+ @Override
+ public void setSelected(boolean value) {
+ super.setSelected(value);
+ if (!value) {
+ mDuplicateButton = false;
+ }
+ invalidate();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ ViewParent parent = getParent();
+ if (parent instanceof PanelTrack) {
+ ((PanelTrack) getParent()).onTouch(event, this);
+ }
+ if (mType == BEGIN) {
+ MasterImage.getImage().setShowsOriginal(true);
+ }
+ }
+ if (event.getActionMasked() == MotionEvent.ACTION_UP
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+ MasterImage.getImage().setShowsOriginal(false);
+ }
+ return true;
+ }
+
+ public void drawText(Canvas canvas) {
+ if (mText == null) {
+ return;
+ }
+ mPaint.reset();
+ if (isSelected()) {
+ mPaint.setColor(mSelectedTextColor);
+ } else {
+ mPaint.setColor(mTextColor);
+ }
+ if (mType == BEGIN) {
+ mPaint.setColor(mEndsTextColor);
+ }
+ mPaint.setTypeface(Typeface.DEFAULT_BOLD);
+ mPaint.setAntiAlias(true);
+ mPaint.setTextSize(mTextSize);
+ mPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
+ int x = (canvas.getWidth() - mTextBounds.width()) / 2;
+ int y = mTextBounds.height() + (canvas.getHeight() - mTextBounds.height()) / 2;
+ canvas.drawText(mText, x, y, mPaint);
+ }
+
+ public void onDraw(Canvas canvas) {
+ canvas.drawARGB(0, 0, 0, 0);
+ mPaint.reset();
+ mPath.reset();
+
+ float w = canvas.getWidth();
+ float h = canvas.getHeight();
+ float r = sArrowHeight;
+ float d = sArrowWidth;
+
+ if (mOrientation == LinearLayout.HORIZONTAL) {
+ drawHorizontalPath(w, h, r, d);
+ } else {
+ if (mDirection == DOWN) {
+ drawVerticalDownPath(w, h, r, d);
+ } else {
+ drawVerticalPath(w, h, r, d);
+ }
+ }
+
+ if (mType == DEFAULT || mType == END) {
+ if (mDuplicateButton) {
+ mPaint.setARGB(255, 200, 0, 0);
+ } else if (isSelected()) {
+ mPaint.setColor(mSelectedBackgroundColor);
+ } else {
+ mPaint.setColor(mBackgroundColor);
+ }
+ } else {
+ mPaint.setColor(mEndsBackgroundColor);
+ }
+ canvas.drawPath(mPath, mPaint);
+ drawText(canvas);
+ }
+
+ private void drawHorizontalPath(float w, float h, float r, float d) {
+ mPath.moveTo(0, 0);
+ if (mType == END) {
+ mPath.lineTo(w, 0);
+ mPath.lineTo(w, h);
+ } else {
+ mPath.lineTo(w - d, 0);
+ mPath.lineTo(w - d, r);
+ mPath.lineTo(w, r + d);
+ mPath.lineTo(w - d, r + d + r);
+ mPath.lineTo(w - d, h);
+ }
+ mPath.lineTo(0, h);
+ if (mType != BEGIN) {
+ mPath.lineTo(0, r + d + r);
+ mPath.lineTo(d, r + d);
+ mPath.lineTo(0, r);
+ }
+ mPath.close();
+ }
+
+ private void drawVerticalPath(float w, float h, float r, float d) {
+ if (mType == BEGIN) {
+ mPath.moveTo(0, 0);
+ mPath.lineTo(w, 0);
+ } else {
+ mPath.moveTo(0, d);
+ mPath.lineTo(r, d);
+ mPath.lineTo(r + d, 0);
+ mPath.lineTo(r + d + r, d);
+ mPath.lineTo(w, d);
+ }
+ mPath.lineTo(w, h);
+ if (mType != END) {
+ mPath.lineTo(r + d + r, h);
+ mPath.lineTo(r + d, h - d);
+ mPath.lineTo(r, h);
+ }
+ mPath.lineTo(0, h);
+ mPath.close();
+ }
+
+ private void drawVerticalDownPath(float w, float h, float r, float d) {
+ mPath.moveTo(0, 0);
+ if (mType != BEGIN) {
+ mPath.lineTo(r, 0);
+ mPath.lineTo(r + d, d);
+ mPath.lineTo(r + d + r, 0);
+ }
+ mPath.lineTo(w, 0);
+
+ if (mType != END) {
+ mPath.lineTo(w, h - d);
+
+ mPath.lineTo(r + d + r, h - d);
+ mPath.lineTo(r + d, h);
+ mPath.lineTo(r, h - d);
+
+ mPath.lineTo(0, h - d);
+ } else {
+ mPath.lineTo(w, h);
+ mPath.lineTo(0, h);
+ }
+
+ mPath.close();
+ }
+
+ public void setBackgroundAlpha(float alpha) {
+ if (mType == BEGIN) {
+ return;
+ }
+ mAlpha = alpha;
+ setAlpha(alpha);
+ invalidate();
+ }
+
+ public float getBackgroundAlpha() {
+ return mAlpha;
+ }
+
+ public void setOrientation(int orientation) {
+ mOrientation = orientation;
+ }
+
+ public void setDuplicateButton(boolean b) {
+ mDuplicateButton = b;
+ invalidate();
+ }
+
+ public State getState() {
+ return mState;
+ }
+
+ public void setState(State state) {
+ mState = state;
+ mText = mState.getText().toUpperCase();
+ mType = mState.getType();
+ invalidate();
+ }
+
+ public void resetPosition() {
+ setTranslationX(0);
+ setTranslationY(0);
+ setBackgroundAlpha(1.0f);
+ }
+
+ public boolean isDraggable() {
+ return mState.isDraggable();
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/IconFactory.java b/src/com/android/gallery3d/filtershow/tools/IconFactory.java
new file mode 100644
index 000000000..9e39f27fc
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/tools/IconFactory.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2013 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.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * A factory class for producing bitmaps to use as UI icons.
+ */
+public class IconFactory {
+
+ /**
+ * Builds an icon with the dimensions iconWidth:iconHeight. If scale is set
+ * the source image is stretched to fit within the given dimensions;
+ * otherwise, the source image is cropped to the proper aspect ratio.
+ *
+ * @param sourceImage image to create an icon from.
+ * @param iconWidth width of the icon bitmap.
+ * @param iconHeight height of the icon bitmap.
+ * @param scale if true, stretch sourceImage to fit the icon dimensions.
+ * @return an icon bitmap with the dimensions iconWidth:iconHeight.
+ */
+ public static Bitmap createIcon(Bitmap sourceImage, int iconWidth, int iconHeight,
+ boolean scale) {
+ if (sourceImage == null) {
+ throw new IllegalArgumentException("Null argument to buildIcon");
+ }
+
+ int sourceWidth = sourceImage.getWidth();
+ int sourceHeight = sourceImage.getHeight();
+
+ if (sourceWidth == 0 || sourceHeight == 0 || iconWidth == 0 || iconHeight == 0) {
+ throw new IllegalArgumentException("Bitmap with dimension 0 used as input");
+ }
+
+ Bitmap icon = Bitmap.createBitmap(iconWidth, iconHeight,
+ Bitmap.Config.ARGB_8888);
+ drawIcon(icon, sourceImage, scale);
+ return icon;
+ }
+
+ /**
+ * Draws an icon in the destination bitmap. If scale is set the source image
+ * is stretched to fit within the destination dimensions; otherwise, the
+ * source image is cropped to the proper aspect ratio.
+ *
+ * @param dest bitmap into which to draw the icon.
+ * @param sourceImage image to create an icon from.
+ * @param scale if true, stretch sourceImage to fit the destination.
+ */
+ public static void drawIcon(Bitmap dest, Bitmap sourceImage, boolean scale) {
+ if (dest == null || sourceImage == null) {
+ throw new IllegalArgumentException("Null argument to buildIcon");
+ }
+
+ int sourceWidth = sourceImage.getWidth();
+ int sourceHeight = sourceImage.getHeight();
+ int iconWidth = dest.getWidth();
+ int iconHeight = dest.getHeight();
+
+ if (sourceWidth == 0 || sourceHeight == 0 || iconWidth == 0 || iconHeight == 0) {
+ throw new IllegalArgumentException("Bitmap with dimension 0 used as input");
+ }
+
+ Rect destRect = new Rect(0, 0, iconWidth, iconHeight);
+ Canvas canvas = new Canvas(dest);
+
+ Rect srcRect = null;
+ if (scale) {
+ // scale image to fit in icon (stretches if aspect isn't the same)
+ srcRect = new Rect(0, 0, sourceWidth, sourceHeight);
+ } else {
+ // crop image to aspect ratio iconWidth:iconHeight
+ float wScale = sourceWidth / (float) iconWidth;
+ float hScale = sourceHeight / (float) iconHeight;
+ float s = Math.min(hScale, wScale);
+
+ float iw = iconWidth * s;
+ float ih = iconHeight * s;
+
+ float borderW = (sourceWidth - iw) / 2.0f;
+ float borderH = (sourceHeight - ih) / 2.0f;
+ RectF rec = new RectF(borderW, borderH, borderW + iw, borderH + ih);
+ srcRect = new Rect();
+ rec.roundOut(srcRect);
+ }
+
+ canvas.drawBitmap(sourceImage, srcRect, destRect, new Paint(Paint.FILTER_BITMAP_FLAG));
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/MatrixFit.java b/src/com/android/gallery3d/filtershow/tools/MatrixFit.java
new file mode 100644
index 000000000..3b815673c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/tools/MatrixFit.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2013 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.util.Log;
+
+public class MatrixFit {
+ // Simple implementation of a matrix fit in N dimensions.
+
+ private static final String LOGTAG = "MatrixFit";
+
+ private double[][] mMatrix;
+ private int mDimension;
+ private boolean mValid = false;
+ private static double sEPS = 1.0f/10000000000.0f;
+
+ public MatrixFit(double[][] from, double[][] to) {
+ mValid = fit(from, to);
+ }
+
+ public int getDimension() {
+ return mDimension;
+ }
+
+ public boolean isValid() {
+ return mValid;
+ }
+
+ public double[][] getMatrix() {
+ return mMatrix;
+ }
+
+ public boolean fit(double[][] from, double[][] to) {
+ if ((from.length != to.length) || (from.length < 1)) {
+ Log.e(LOGTAG, "from and to must be of same size");
+ return false;
+ }
+
+ mDimension = from[0].length;
+ mMatrix = new double[mDimension +1][mDimension + mDimension +1];
+
+ if (from.length < mDimension) {
+ Log.e(LOGTAG, "Too few points => under-determined system");
+ return false;
+ }
+
+ double[][] q = new double[from.length][mDimension];
+ for (int i = 0; i < from.length; i++) {
+ for (int j = 0; j < mDimension; j++) {
+ q[i][j] = from[i][j];
+ }
+ }
+
+ double[][] p = new double[to.length][mDimension];
+ for (int i = 0; i < to.length; i++) {
+ for (int j = 0; j < mDimension; j++) {
+ p[i][j] = to[i][j];
+ }
+ }
+
+ // Make an empty (dim) x (dim + 1) matrix and fill it
+ double[][] c = new double[mDimension+1][mDimension];
+ for (int j = 0; j < mDimension; j++) {
+ for (int k = 0; k < mDimension + 1; k++) {
+ for (int i = 0; i < q.length; i++) {
+ double qt = 1;
+ if (k < mDimension) {
+ qt = q[i][k];
+ }
+ c[k][j] += qt * p[i][j];
+ }
+ }
+ }
+
+ // Make an empty (dim+1) x (dim+1) matrix and fill it
+ double[][] Q = new double[mDimension+1][mDimension+1];
+ for (int qi = 0; qi < q.length; qi++) {
+ double[] qt = new double[mDimension + 1];
+ for (int i = 0; i < mDimension; i++) {
+ qt[i] = q[qi][i];
+ }
+ qt[mDimension] = 1;
+ for (int i = 0; i < mDimension + 1; i++) {
+ for (int j = 0; j < mDimension + 1; j++) {
+ Q[i][j] += qt[i] * qt[j];
+ }
+ }
+ }
+
+ // Use a gaussian elimination to solve the linear system
+ for (int i = 0; i < mDimension + 1; i++) {
+ for (int j = 0; j < mDimension + 1; j++) {
+ mMatrix[i][j] = Q[i][j];
+ }
+ for (int j = 0; j < mDimension; j++) {
+ mMatrix[i][mDimension + 1 + j] = c[i][j];
+ }
+ }
+ if (!gaussianElimination(mMatrix)) {
+ return false;
+ }
+ return true;
+ }
+
+ public double[] apply(double[] point) {
+ if (mDimension != point.length) {
+ return null;
+ }
+ double[] res = new double[mDimension];
+ for (int j = 0; j < mDimension; j++) {
+ for (int i = 0; i < mDimension; i++) {
+ res[j] += point[i] * mMatrix[i][j+ mDimension +1];
+ }
+ res[j] += mMatrix[mDimension][j+ mDimension +1];
+ }
+ return res;
+ }
+
+ public void printEquation() {
+ for (int j = 0; j < mDimension; j++) {
+ String str = "x" + j + "' = ";
+ for (int i = 0; i < mDimension; i++) {
+ str += "x" + i + " * " + mMatrix[i][j+mDimension+1] + " + ";
+ }
+ str += mMatrix[mDimension][j+mDimension+1];
+ Log.v(LOGTAG, str);
+ }
+ }
+
+ private void printMatrix(String name, double[][] matrix) {
+ Log.v(LOGTAG, "name: " + name);
+ for (int i = 0; i < matrix.length; i++) {
+ String str = "";
+ for (int j = 0; j < matrix[0].length; j++) {
+ str += "" + matrix[i][j] + " ";
+ }
+ Log.v(LOGTAG, str);
+ }
+ }
+
+ /*
+ * Transforms the given matrix into a row echelon matrix
+ */
+ private boolean gaussianElimination(double[][] m) {
+ int h = m.length;
+ int w = m[0].length;
+
+ for (int y = 0; y < h; y++) {
+ int maxrow = y;
+ for (int y2 = y + 1; y2 < h; y2++) { // Find max pivot
+ if (Math.abs(m[y2][y]) > Math.abs(m[maxrow][y])) {
+ maxrow = y2;
+ }
+ }
+ // swap
+ for (int i = 0; i < mDimension; i++) {
+ double t = m[y][i];
+ m[y][i] = m[maxrow][i];
+ m[maxrow][i] = t;
+ }
+
+ if (Math.abs(m[y][y]) <= sEPS) { // Singular Matrix
+ return false;
+ }
+ for (int y2 = y + 1; y2 < h; y2++) { // Eliminate column y
+ double c = m[y2][y] / m[y][y];
+ for (int x = y; x < w; x++) {
+ m[y2][x] -= m[y][x] * c;
+ }
+ }
+ }
+ for (int y = h -1; y > -1; y--) { // Back substitution
+ double c = m[y][y];
+ for (int y2 = 0; y2 < y; y2++) {
+ for (int x = w - 1; x > y - 1; x--) {
+ m[y2][x] -= m[y][x] * m[y2][y] / c;
+ }
+ }
+ m[y][y] /= c;
+ for (int x = h; x < w; x++) { // Normalize row y
+ m[y][x] /= c;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/SaveImage.java b/src/com/android/gallery3d/filtershow/tools/SaveImage.java
new file mode 100644
index 000000000..83cbd0136
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/tools/SaveImage.java
@@ -0,0 +1,632 @@
+/*
+ * 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.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.pipeline.CachingPipeline;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+import com.android.gallery3d.filtershow.pipeline.ProcessingService;
+import com.android.gallery3d.util.UsageStatistics;
+import com.android.gallery3d.util.XmpUtilHelper;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
+
+/**
+ * Handles saving edited photo
+ */
+public class SaveImage {
+ private static final String LOGTAG = "SaveImage";
+
+ /**
+ * Callback for updates
+ */
+ public interface Callback {
+ void onProgress(int max, int current);
+ }
+
+ public interface ContentResolverQueryCallback {
+ void onCursorResult(Cursor cursor);
+ }
+
+ private static final String TIME_STAMP_NAME = "_yyyyMMdd_HHmmss";
+ private static final String PREFIX_PANO = "PANO";
+ private static final String PREFIX_IMG = "IMG";
+ private static final String POSTFIX_JPG = ".jpg";
+ private static final String AUX_DIR_NAME = ".aux";
+
+ private final Context mContext;
+ private final Uri mSourceUri;
+ private final Callback mCallback;
+ private final File mDestinationFile;
+ private final Uri mSelectedImageUri;
+
+ private int mCurrentProcessingStep = 1;
+
+ public static final int MAX_PROCESSING_STEPS = 6;
+ public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
+
+ // In order to support the new edit-save behavior such that user won't see
+ // the edited image together with the original image, we are adding a new
+ // auxiliary directory for the edited image. Basically, the original image
+ // will be hidden in that directory after edit and user will see the edited
+ // image only.
+ // Note that deletion on the edited image will also cause the deletion of
+ // the original image under auxiliary directory.
+ //
+ // There are several situations we need to consider:
+ // 1. User edit local image local01.jpg. A local02.jpg will be created in the
+ // same directory, and original image will be moved to auxiliary directory as
+ // ./.aux/local02.jpg.
+ // If user edit the local02.jpg, local03.jpg will be created in the local
+ // directory and ./.aux/local02.jpg will be renamed to ./.aux/local03.jpg
+ //
+ // 2. User edit remote image remote01.jpg from picassa or other server.
+ // remoteSavedLocal01.jpg will be saved under proper local directory.
+ // In remoteSavedLocal01.jpg, there will be a reference pointing to the
+ // remote01.jpg. There will be no local copy of remote01.jpg.
+ // If user edit remoteSavedLocal01.jpg, then a new remoteSavedLocal02.jpg
+ // will be generated and still pointing to the remote01.jpg
+ //
+ // 3. User delete any local image local.jpg.
+ // Since the filenames are kept consistent in auxiliary directory, every
+ // time a local.jpg get deleted, the files in auxiliary directory whose
+ // names starting with "local." will be deleted.
+ // This pattern will facilitate the multiple images deletion in the auxiliary
+ // directory.
+
+ /**
+ * @param context
+ * @param sourceUri The Uri for the original image, which can be the hidden
+ * image under the auxiliary directory or the same as selectedImageUri.
+ * @param selectedImageUri The Uri for the image selected by the user.
+ * In most cases, it is a content Uri for local image or remote image.
+ * @param destination Destinaton File, if this is null, a new file will be
+ * created under the same directory as selectedImageUri.
+ * @param callback Let the caller know the saving has completed.
+ * @return the newSourceUri
+ */
+ public SaveImage(Context context, Uri sourceUri, Uri selectedImageUri,
+ File destination, Callback callback) {
+ mContext = context;
+ mSourceUri = sourceUri;
+ mCallback = callback;
+ if (destination == null) {
+ mDestinationFile = getNewFile(context, selectedImageUri);
+ } else {
+ mDestinationFile = destination;
+ }
+
+ mSelectedImageUri = selectedImageUri;
+ }
+
+ public static File getFinalSaveDirectory(Context context, Uri sourceUri) {
+ File saveDirectory = SaveImage.getSaveDirectory(context, sourceUri);
+ if ((saveDirectory == null) || !saveDirectory.canWrite()) {
+ saveDirectory = new File(Environment.getExternalStorageDirectory(),
+ SaveImage.DEFAULT_SAVE_DIRECTORY);
+ }
+ // Create the directory if it doesn't exist
+ if (!saveDirectory.exists())
+ saveDirectory.mkdirs();
+ return saveDirectory;
+ }
+
+ public static File getNewFile(Context context, Uri sourceUri) {
+ File saveDirectory = getFinalSaveDirectory(context, sourceUri);
+ String filename = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(
+ System.currentTimeMillis()));
+ if (hasPanoPrefix(context, sourceUri)) {
+ return new File(saveDirectory, PREFIX_PANO + filename + POSTFIX_JPG);
+ }
+ return new File(saveDirectory, PREFIX_IMG + filename + POSTFIX_JPG);
+ }
+
+ /**
+ * Remove the files in the auxiliary directory whose names are the same as
+ * the source image.
+ * @param contentResolver The application's contentResolver
+ * @param srcContentUri The content Uri for the source image.
+ */
+ public static void deleteAuxFiles(ContentResolver contentResolver,
+ Uri srcContentUri) {
+ final String[] fullPath = new String[1];
+ String[] queryProjection = new String[] { ImageColumns.DATA };
+ querySourceFromContentResolver(contentResolver,
+ srcContentUri, queryProjection,
+ new ContentResolverQueryCallback() {
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ fullPath[0] = cursor.getString(0);
+ }
+ }
+ );
+ if (fullPath[0] != null) {
+ // Construct the auxiliary directory given the source file's path.
+ // Then select and delete all the files starting with the same name
+ // under the auxiliary directory.
+ File currentFile = new File(fullPath[0]);
+
+ String filename = currentFile.getName();
+ int firstDotPos = filename.indexOf(".");
+ final String filenameNoExt = (firstDotPos == -1) ? filename :
+ filename.substring(0, firstDotPos);
+ File auxDir = getLocalAuxDirectory(currentFile);
+ if (auxDir.exists()) {
+ FilenameFilter filter = new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ if (name.startsWith(filenameNoExt + ".")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+
+ // Delete all auxiliary files whose name is matching the
+ // current local image.
+ File[] auxFiles = auxDir.listFiles(filter);
+ for (File file : auxFiles) {
+ file.delete();
+ }
+ }
+ }
+ }
+
+ public Object getPanoramaXMPData(Uri source, ImagePreset preset) {
+ Object xmp = null;
+ if (preset.isPanoramaSafe()) {
+ InputStream is = null;
+ try {
+ is = mContext.getContentResolver().openInputStream(source);
+ xmp = XmpUtilHelper.extractXMPMeta(is);
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "Failed to get XMP data from image: ", e);
+ } finally {
+ Utils.closeSilently(is);
+ }
+ }
+ return xmp;
+ }
+
+ public boolean putPanoramaXMPData(File file, Object xmp) {
+ if (xmp != null) {
+ return XmpUtilHelper.writeXMPMeta(file.getAbsolutePath(), xmp);
+ }
+ return false;
+ }
+
+ public ExifInterface getExifData(Uri source) {
+ ExifInterface exif = new ExifInterface();
+ String mimeType = mContext.getContentResolver().getType(mSelectedImageUri);
+ if (mimeType == null) {
+ mimeType = ImageLoader.getMimeType(mSelectedImageUri);
+ }
+ if (mimeType.equals(ImageLoader.JPEG_MIME_TYPE)) {
+ InputStream inStream = null;
+ try {
+ inStream = mContext.getContentResolver().openInputStream(source);
+ exif.readExif(inStream);
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "Cannot find file: " + source, e);
+ } catch (IOException e) {
+ Log.w(LOGTAG, "Cannot read exif for: " + source, e);
+ } finally {
+ Utils.closeSilently(inStream);
+ }
+ }
+ return exif;
+ }
+
+ public boolean putExifData(File file, ExifInterface exif, Bitmap image,
+ int jpegCompressQuality) {
+ boolean ret = false;
+ OutputStream s = null;
+ try {
+ s = exif.getExifWriterStream(file.getAbsolutePath());
+ image.compress(Bitmap.CompressFormat.JPEG,
+ (jpegCompressQuality > 0) ? jpegCompressQuality : 1, s);
+ s.flush();
+ s.close();
+ s = null;
+ ret = true;
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "File not found: " + file.getAbsolutePath(), e);
+ } catch (IOException e) {
+ Log.w(LOGTAG, "Could not write exif: ", e);
+ } finally {
+ Utils.closeSilently(s);
+ }
+ return ret;
+ }
+
+ private Uri resetToOriginalImageIfNeeded(ImagePreset preset, boolean doAuxBackup) {
+ Uri uri = null;
+ if (!preset.hasModifications()) {
+ // This can happen only when preset has no modification but save
+ // button is enabled, it means the file is loaded with filters in
+ // the XMP, then all the filters are removed or restore to default.
+ // In this case, when mSourceUri exists, rename it to the
+ // destination file.
+ File srcFile = getLocalFileFromUri(mContext, mSourceUri);
+ // If the source is not a local file, then skip this renaming and
+ // create a local copy as usual.
+ if (srcFile != null) {
+ srcFile.renameTo(mDestinationFile);
+ uri = SaveImage.linkNewFileToUri(mContext, mSelectedImageUri,
+ mDestinationFile, System.currentTimeMillis(), doAuxBackup);
+ }
+ }
+ return uri;
+ }
+
+ private void resetProgress() {
+ mCurrentProcessingStep = 0;
+ }
+
+ private void updateProgress() {
+ if (mCallback != null) {
+ mCallback.onProgress(MAX_PROCESSING_STEPS, ++mCurrentProcessingStep);
+ }
+ }
+
+ public Uri processAndSaveImage(ImagePreset preset, boolean doAuxBackup, int quality) {
+
+ Uri uri = resetToOriginalImageIfNeeded(preset, doAuxBackup);
+ if (uri != null) {
+ return null;
+ }
+
+ resetProgress();
+
+ boolean noBitmap = true;
+ int num_tries = 0;
+ int sampleSize = 1;
+
+ // If necessary, move the source file into the auxiliary directory,
+ // newSourceUri is then pointing to the new location.
+ // If no file is moved, newSourceUri will be the same as mSourceUri.
+ Uri newSourceUri = mSourceUri;
+ if (doAuxBackup) {
+ newSourceUri = moveSrcToAuxIfNeeded(mSourceUri, mDestinationFile);
+ }
+
+ // Stopgap fix for low-memory devices.
+ while (noBitmap) {
+ try {
+ updateProgress();
+ // Try to do bitmap operations, downsample if low-memory
+ Bitmap bitmap = ImageLoader.loadOrientedBitmapWithBackouts(mContext, newSourceUri,
+ sampleSize);
+ if (bitmap == null) {
+ return null;
+ }
+ updateProgress();
+ CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(),
+ "Saving");
+
+ bitmap = pipeline.renderFinalImage(bitmap, preset);
+ updateProgress();
+
+ Object xmp = getPanoramaXMPData(newSourceUri, preset);
+ ExifInterface exif = getExifData(newSourceUri);
+
+ updateProgress();
+ // Set tags
+ long time = System.currentTimeMillis();
+ exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, time,
+ TimeZone.getDefault());
+ exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,
+ ExifInterface.Orientation.TOP_LEFT));
+ // Remove old thumbnail
+ exif.removeCompressedThumbnail();
+
+ updateProgress();
+
+ // If we succeed in writing the bitmap as a jpeg, return a uri.
+ if (putExifData(mDestinationFile, exif, bitmap, quality)) {
+ putPanoramaXMPData(mDestinationFile, xmp);
+ // mDestinationFile will save the newSourceUri info in the XMP.
+ XmpPresets.writeFilterXMP(mContext, newSourceUri,
+ mDestinationFile, preset);
+
+ // After this call, mSelectedImageUri will be actually
+ // pointing at the new file mDestinationFile.
+ uri = SaveImage.linkNewFileToUri(mContext, mSelectedImageUri,
+ mDestinationFile, time, doAuxBackup);
+ }
+ updateProgress();
+
+ noBitmap = false;
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+ "SaveComplete", null);
+ } catch (OutOfMemoryError e) {
+ // Try 5 times before failing for good.
+ if (++num_tries >= 5) {
+ throw e;
+ }
+ System.gc();
+ sampleSize *= 2;
+ resetProgress();
+ }
+ }
+ return uri;
+ }
+
+ /**
+ * Move the source file to auxiliary directory if needed and return the Uri
+ * pointing to this new source file.
+ * @param srcUri Uri to the source image.
+ * @param dstFile Providing the destination file info to help to build the
+ * auxiliary directory and new source file's name.
+ * @return the newSourceUri pointing to the new source image.
+ */
+ private Uri moveSrcToAuxIfNeeded(Uri srcUri, File dstFile) {
+ File srcFile = getLocalFileFromUri(mContext, srcUri);
+ if (srcFile == null) {
+ Log.d(LOGTAG, "Source file is not a local file, no update.");
+ return srcUri;
+ }
+
+ // Get the destination directory and create the auxilliary directory
+ // if necessary.
+ File auxDiretory = getLocalAuxDirectory(dstFile);
+ if (!auxDiretory.exists()) {
+ auxDiretory.mkdirs();
+ }
+
+ // Make sure there is a .nomedia file in the auxiliary directory, such
+ // that MediaScanner will not report those files under this directory.
+ File noMedia = new File(auxDiretory, ".nomedia");
+ if (!noMedia.exists()) {
+ try {
+ noMedia.createNewFile();
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Can't create the nomedia");
+ return srcUri;
+ }
+ }
+ // We are using the destination file name such that photos sitting in
+ // the auxiliary directory are matching the parent directory.
+ File newSrcFile = new File(auxDiretory, dstFile.getName());
+
+ if (!newSrcFile.exists()) {
+ srcFile.renameTo(newSrcFile);
+ }
+
+ return Uri.fromFile(newSrcFile);
+
+ }
+
+ private static File getLocalAuxDirectory(File dstFile) {
+ File dstDirectory = dstFile.getParentFile();
+ File auxDiretory = new File(dstDirectory + "/" + AUX_DIR_NAME);
+ return auxDiretory;
+ }
+
+ public static Uri makeAndInsertUri(Context context, Uri sourceUri) {
+ long time = System.currentTimeMillis();
+ String filename = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(time));
+ File saveDirectory = getFinalSaveDirectory(context, sourceUri);
+ File file = new File(saveDirectory, filename + ".JPG");
+ return linkNewFileToUri(context, sourceUri, file, time, false);
+ }
+
+ public static void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
+ File destination) {
+ Uri selectedImageUri = filterShowActivity.getSelectedImageUri();
+ Uri sourceImageUri = MasterImage.getImage().getUri();
+
+ Intent processIntent = ProcessingService.getSaveIntent(filterShowActivity, preset,
+ destination, selectedImageUri, sourceImageUri, false, 90);
+
+ filterShowActivity.startService(processIntent);
+
+ if (!filterShowActivity.isSimpleEditAction()) {
+ // terminate for now
+ filterShowActivity.completeSaveImage(selectedImageUri);
+ }
+ }
+
+ public static void querySource(Context context, Uri sourceUri, String[] projection,
+ ContentResolverQueryCallback callback) {
+ ContentResolver contentResolver = context.getContentResolver();
+ querySourceFromContentResolver(contentResolver, sourceUri, projection, callback);
+ }
+
+ private static void querySourceFromContentResolver(
+ ContentResolver contentResolver, Uri sourceUri, String[] projection,
+ ContentResolverQueryCallback callback) {
+ 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 static File getSaveDirectory(Context context, Uri sourceUri) {
+ File file = getLocalFileFromUri(context, sourceUri);
+ if (file != null) {
+ return file.getParentFile();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Construct a File object based on the srcUri.
+ * @return The file object. Return null if srcUri is invalid or not a local
+ * file.
+ */
+ private static File getLocalFileFromUri(Context context, Uri srcUri) {
+ if (srcUri == null) {
+ Log.e(LOGTAG, "srcUri is null.");
+ return null;
+ }
+
+ String scheme = srcUri.getScheme();
+ if (scheme == null) {
+ Log.e(LOGTAG, "scheme is null.");
+ return null;
+ }
+
+ final File[] file = new File[1];
+ // sourceUri can be a file path or a content Uri, it need to be handled
+ // differently.
+ if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
+ if (srcUri.getAuthority().equals(MediaStore.AUTHORITY)) {
+ querySource(context, srcUri, new String[] {
+ ImageColumns.DATA
+ },
+ new ContentResolverQueryCallback() {
+
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ file[0] = new File(cursor.getString(0));
+ }
+ });
+ }
+ } else if (scheme.equals(ContentResolver.SCHEME_FILE)) {
+ file[0] = new File(srcUri.getPath());
+ }
+ return file[0];
+ }
+
+ /**
+ * Gets the actual filename for a Uri from Gallery's ContentProvider.
+ */
+ private static String getTrueFilename(Context context, Uri src) {
+ if (context == null || src == null) {
+ return null;
+ }
+ final String[] trueName = new String[1];
+ querySource(context, src, new String[] {
+ ImageColumns.DATA
+ }, new ContentResolverQueryCallback() {
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ trueName[0] = new File(cursor.getString(0)).getName();
+ }
+ });
+ return trueName[0];
+ }
+
+ /**
+ * Checks whether the true filename has the panorama image prefix.
+ */
+ private static boolean hasPanoPrefix(Context context, Uri src) {
+ String name = getTrueFilename(context, src);
+ return name != null && name.startsWith(PREFIX_PANO);
+ }
+
+ /**
+ * If the <code>sourceUri</code> is a local content Uri, update the
+ * <code>sourceUri</code> to point to the <code>file</code>.
+ * At the same time, the old file <code>sourceUri</code> used to point to
+ * will be removed if it is local.
+ * If the <code>sourceUri</code> is not a local content Uri, then the
+ * <code>file</code> will be inserted as a new content Uri.
+ * @return the final Uri referring to the <code>file</code>.
+ */
+ public static Uri linkNewFileToUri(Context context, Uri sourceUri,
+ File file, long time, boolean deleteOriginal) {
+ File oldSelectedFile = getLocalFileFromUri(context, sourceUri);
+ final ContentValues values = new ContentValues();
+
+ time /= 1000;
+ values.put(Images.Media.TITLE, file.getName());
+ values.put(Images.Media.DISPLAY_NAME, file.getName());
+ values.put(Images.Media.MIME_TYPE, "image/jpeg");
+ values.put(Images.Media.DATE_TAKEN, time);
+ values.put(Images.Media.DATE_MODIFIED, time);
+ values.put(Images.Media.DATE_ADDED, time);
+ values.put(Images.Media.ORIENTATION, 0);
+ values.put(Images.Media.DATA, file.getAbsolutePath());
+ values.put(Images.Media.SIZE, file.length());
+
+ final String[] projection = new String[] {
+ ImageColumns.DATE_TAKEN,
+ ImageColumns.LATITUDE, ImageColumns.LONGITUDE,
+ };
+ SaveImage.querySource(context, sourceUri, projection,
+ new SaveImage.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);
+ }
+ }
+ });
+
+ Uri result = sourceUri;
+ if (oldSelectedFile == null || !deleteOriginal) {
+ result = context.getContentResolver().insert(
+ Images.Media.EXTERNAL_CONTENT_URI, values);
+ } else {
+ context.getContentResolver().update(sourceUri, values, null, null);
+ if (oldSelectedFile.exists()) {
+ oldSelectedFile.delete();
+ }
+ }
+
+ return result;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/XmpPresets.java b/src/com/android/gallery3d/filtershow/tools/XmpPresets.java
new file mode 100644
index 000000000..3995eeb85
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/tools/XmpPresets.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2013 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.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+import com.android.gallery3d.util.XmpUtilHelper;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+public class XmpPresets {
+ public static final String
+ XMP_GOOGLE_FILTER_NAMESPACE = "http://ns.google.com/photos/1.0/filter/";
+ public static final String XMP_GOOGLE_FILTER_PREFIX = "AFltr";
+ public static final String XMP_SRC_FILE_URI = "SourceFileUri";
+ public static final String XMP_FILTERSTACK = "filterstack";
+ private static final String LOGTAG = "XmpPresets";
+
+ public static class XMresults {
+ public String presetString;
+ public ImagePreset preset;
+ public Uri originalimage;
+ }
+
+ static {
+ try {
+ XMPMetaFactory.getSchemaRegistry().registerNamespace(
+ XMP_GOOGLE_FILTER_NAMESPACE, XMP_GOOGLE_FILTER_PREFIX);
+ } catch (XMPException e) {
+ Log.e(LOGTAG, "Register XMP name space failed", e);
+ }
+ }
+
+ public static void writeFilterXMP(
+ Context context, Uri srcUri, File dstFile, ImagePreset preset) {
+ InputStream is = null;
+ XMPMeta xmpMeta = null;
+ try {
+ is = context.getContentResolver().openInputStream(srcUri);
+ xmpMeta = XmpUtilHelper.extractXMPMeta(is);
+ } catch (FileNotFoundException e) {
+
+ } finally {
+ Utils.closeSilently(is);
+ }
+
+ if (xmpMeta == null) {
+ xmpMeta = XMPMetaFactory.create();
+ }
+ try {
+ xmpMeta.setProperty(XMP_GOOGLE_FILTER_NAMESPACE,
+ XMP_SRC_FILE_URI, srcUri.toString());
+ xmpMeta.setProperty(XMP_GOOGLE_FILTER_NAMESPACE,
+ XMP_FILTERSTACK, preset.getJsonString(context.getString(R.string.saved)));
+ } catch (XMPException e) {
+ Log.v(LOGTAG, "Write XMP meta to file failed:" + dstFile.getAbsolutePath());
+ return;
+ }
+
+ if (!XmpUtilHelper.writeXMPMeta(dstFile.getAbsolutePath(), xmpMeta)) {
+ Log.v(LOGTAG, "Write XMP meta to file failed:" + dstFile.getAbsolutePath());
+ }
+ }
+
+ public static XMresults extractXMPData(
+ Context context, MasterImage mMasterImage, Uri uriToEdit) {
+ XMresults ret = new XMresults();
+
+ InputStream is = null;
+ XMPMeta xmpMeta = null;
+ try {
+ is = context.getContentResolver().openInputStream(uriToEdit);
+ xmpMeta = XmpUtilHelper.extractXMPMeta(is);
+ } catch (FileNotFoundException e) {
+ } finally {
+ Utils.closeSilently(is);
+ }
+
+ if (xmpMeta == null) {
+ return null;
+ }
+
+ try {
+ String strSrcUri = xmpMeta.getPropertyString(XMP_GOOGLE_FILTER_NAMESPACE,
+ XMP_SRC_FILE_URI);
+
+ if (strSrcUri != null) {
+ String filterString = xmpMeta.getPropertyString(XMP_GOOGLE_FILTER_NAMESPACE,
+ XMP_FILTERSTACK);
+
+ Uri srcUri = Uri.parse(strSrcUri);
+ ret.originalimage = srcUri;
+
+ ret.preset = new ImagePreset(mMasterImage.getPreset());
+ ret.presetString = filterString;
+ boolean ok = ret.preset.readJsonFromString(filterString);
+ if (!ok) {
+ return null;
+ }
+ return ret;
+ }
+ } catch (XMPException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/ExportDialog.java b/src/com/android/gallery3d/filtershow/ui/ExportDialog.java
new file mode 100644
index 000000000..4b30e7b18
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/ExportDialog.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 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.ui;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.pipeline.ProcessingService;
+import com.android.gallery3d.filtershow.tools.SaveImage;
+
+import java.io.File;
+
+public class ExportDialog extends DialogFragment implements View.OnClickListener, SeekBar.OnSeekBarChangeListener{
+ SeekBar mSeekBar;
+ TextView mSeekVal;
+ String mSliderLabel;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.filtershow_export_dialog, container);
+ mSeekBar = (SeekBar) view.findViewById(R.id.qualitySeekBar);
+ mSeekVal = (TextView) view.findViewById(R.id.qualityTextView);
+ mSliderLabel = getString(R.string.quality) + ": ";
+ mSeekVal.setText(mSliderLabel + mSeekBar.getProgress());
+ mSeekBar.setOnSeekBarChangeListener(this);
+ view.findViewById(R.id.cancel).setOnClickListener(this);
+ view.findViewById(R.id.done).setOnClickListener(this);
+ getDialog().setTitle(R.string.export_flattened);
+ return view;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar arg0) {
+ // Do nothing
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar arg0) {
+ // Do nothing
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
+ mSeekVal.setText(mSliderLabel + arg1);
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.cancel:
+ dismiss();
+ break;
+ case R.id.done:
+ FilterShowActivity activity = (FilterShowActivity) getActivity();
+ Uri sourceUri = MasterImage.getImage().getUri();
+ File dest = SaveImage.getNewFile(activity, sourceUri);
+ Intent processIntent = ProcessingService.getSaveIntent(activity, MasterImage
+ .getImage().getPreset(), dest, activity.getSelectedImageUri(), sourceUri,
+ true, mSeekBar.getProgress());
+ activity.startService(processIntent);
+ dismiss();
+ break;
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java b/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java
new file mode 100644
index 000000000..c1e4109d2
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.ui;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+import com.android.gallery3d.R;
+
+public class FramedTextButton extends ImageButton {
+ private static final String LOGTAG = "FramedTextButton";
+ private String mText = null;
+ private static int mTextSize = 24;
+ private static int mTextPadding = 20;
+ private static Paint gPaint = new Paint();
+ private static Path gPath = new Path();
+ private static int mTrianglePadding = 2;
+ private static int mTriangleSize = 30;
+
+ public static void setTextSize(int value) {
+ mTextSize = value;
+ }
+
+ public static void setTextPadding(int value) {
+ mTextPadding = value;
+ }
+
+ public static void setTrianglePadding(int value) {
+ mTrianglePadding = value;
+ }
+
+ public static void setTriangleSize(int value) {
+ mTriangleSize = value;
+ }
+
+ public void setText(String text) {
+ mText = text;
+ invalidate();
+ }
+
+ public void setTextFrom(int itemId) {
+ switch (itemId) {
+ case R.id.curve_menu_rgb: {
+ setText(getContext().getString(R.string.curves_channel_rgb));
+ break;
+ }
+ case R.id.curve_menu_red: {
+ setText(getContext().getString(R.string.curves_channel_red));
+ break;
+ }
+ case R.id.curve_menu_green: {
+ setText(getContext().getString(R.string.curves_channel_green));
+ break;
+ }
+ case R.id.curve_menu_blue: {
+ setText(getContext().getString(R.string.curves_channel_blue));
+ break;
+ }
+ }
+ invalidate();
+ }
+
+ public FramedTextButton(Context context) {
+ this(context, null);
+ }
+
+ public FramedTextButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ if (attrs == null) {
+ return;
+ }
+ TypedArray a = getContext().obtainStyledAttributes(
+ attrs, R.styleable.ImageButtonTitle);
+
+ mText = a.getString(R.styleable.ImageButtonTitle_android_text);
+ }
+
+ public String getText(){
+ return mText;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ gPaint.setARGB(96, 255, 255, 255);
+ gPaint.setStrokeWidth(2);
+ gPaint.setStyle(Paint.Style.STROKE);
+ int w = getWidth();
+ int h = getHeight();
+ canvas.drawRect(mTextPadding, mTextPadding, w - mTextPadding,
+ h - mTextPadding, gPaint);
+ gPath.reset();
+ gPath.moveTo(w - mTextPadding - mTrianglePadding - mTriangleSize,
+ h - mTextPadding - mTrianglePadding);
+ gPath.lineTo(w - mTextPadding - mTrianglePadding,
+ h - mTextPadding - mTrianglePadding - mTriangleSize);
+ gPath.lineTo(w - mTextPadding - mTrianglePadding,
+ h - mTextPadding - mTrianglePadding);
+ gPath.close();
+ gPaint.setARGB(128, 255, 255, 255);
+ gPaint.setStrokeWidth(1);
+ gPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ canvas.drawPath(gPath, gPaint);
+ if (mText != null) {
+ gPaint.reset();
+ gPaint.setARGB(255, 255, 255, 255);
+ gPaint.setTextSize(mTextSize);
+ float textWidth = gPaint.measureText(mText);
+ Rect bounds = new Rect();
+ gPaint.getTextBounds(mText, 0, mText.length(), bounds);
+ int x = (int) ((w - textWidth) / 2);
+ int y = (h + bounds.height()) / 2;
+
+ canvas.drawText(mText, x, y, gPaint);
+ }
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/SelectionRenderer.java b/src/com/android/gallery3d/filtershow/ui/SelectionRenderer.java
new file mode 100644
index 000000000..ef40c5e44
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/SelectionRenderer.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 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.ui;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+public class SelectionRenderer {
+
+ public static void drawSelection(Canvas canvas, int left, int top, int right, int bottom,
+ int stroke, Paint paint) {
+ canvas.drawRect(left, top, right, top + stroke, paint);
+ canvas.drawRect(left, bottom - stroke, right, bottom, paint);
+ canvas.drawRect(left, top, left + stroke, bottom, paint);
+ canvas.drawRect(right - stroke, top, right, bottom, paint);
+ }
+
+ public static void drawSelection(Canvas canvas, int left, int top, int right, int bottom,
+ int stroke, Paint selectPaint, int border, Paint borderPaint) {
+ canvas.drawRect(left, top, right, top + stroke, selectPaint);
+ canvas.drawRect(left, bottom - stroke, right, bottom, selectPaint);
+ canvas.drawRect(left, top, left + stroke, bottom, selectPaint);
+ canvas.drawRect(right - stroke, top, right, bottom, selectPaint);
+ canvas.drawRect(left + stroke, top + stroke, right - stroke,
+ top + stroke + border, borderPaint);
+ canvas.drawRect(left + stroke, bottom - stroke - border, right - stroke,
+ bottom - stroke, borderPaint);
+ canvas.drawRect(left + stroke, top + stroke, left + stroke + border,
+ bottom - stroke, borderPaint);
+ canvas.drawRect(right - stroke - border, top + stroke, right - stroke,
+ bottom - stroke, borderPaint);
+ }
+
+}