diff options
Diffstat (limited to 'src/com/android/gallery3d/filtershow')
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); + } + +} |