diff options
Diffstat (limited to 'src/com/android/gallery3d/filtershow')
57 files changed, 2688 insertions, 1483 deletions
diff --git a/src/com/android/gallery3d/filtershow/CropExtras.java b/src/com/android/gallery3d/filtershow/CropExtras.java new file mode 100644 index 000000000..7ed8f1eb5 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/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; + +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/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index 94d318558..4198da020 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -20,6 +20,7 @@ import android.annotation.TargetApi; import android.app.ActionBar; import android.app.Activity; import android.app.ProgressDialog; +import android.app.WallpaperManager; import android.content.ContentValues; import android.content.Intent; import android.content.res.Configuration; @@ -32,6 +33,7 @@ import android.graphics.drawable.Drawable; 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.util.TypedValue; @@ -58,13 +60,19 @@ import com.android.gallery3d.filtershow.filters.ImageFilter; import com.android.gallery3d.filtershow.filters.ImageFilterBorder; import com.android.gallery3d.filtershow.filters.ImageFilterBwFilter; import com.android.gallery3d.filtershow.filters.ImageFilterContrast; +import com.android.gallery3d.filtershow.filters.ImageFilterCurves; +import com.android.gallery3d.filtershow.filters.ImageFilterDownsample; +import com.android.gallery3d.filtershow.filters.ImageFilterEdge; import com.android.gallery3d.filtershow.filters.ImageFilterExposure; import com.android.gallery3d.filtershow.filters.ImageFilterFx; import com.android.gallery3d.filtershow.filters.ImageFilterHue; +import com.android.gallery3d.filtershow.filters.ImageFilterKMeans; +import com.android.gallery3d.filtershow.filters.ImageFilterNegative; import com.android.gallery3d.filtershow.filters.ImageFilterParametricBorder; import com.android.gallery3d.filtershow.filters.ImageFilterRS; import com.android.gallery3d.filtershow.filters.ImageFilterSaturated; import com.android.gallery3d.filtershow.filters.ImageFilterShadows; +import com.android.gallery3d.filtershow.filters.ImageFilterSharpen; import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet; import com.android.gallery3d.filtershow.filters.ImageFilterVibrance; import com.android.gallery3d.filtershow.filters.ImageFilterVignette; @@ -72,13 +80,13 @@ import com.android.gallery3d.filtershow.filters.ImageFilterWBalance; import com.android.gallery3d.filtershow.imageshow.ImageBorder; import com.android.gallery3d.filtershow.imageshow.ImageCrop; import com.android.gallery3d.filtershow.imageshow.ImageFlip; +import com.android.gallery3d.filtershow.imageshow.ImageRedEyes; import com.android.gallery3d.filtershow.imageshow.ImageRotate; import com.android.gallery3d.filtershow.imageshow.ImageShow; import com.android.gallery3d.filtershow.imageshow.ImageSmallBorder; import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter; import com.android.gallery3d.filtershow.imageshow.ImageStraighten; import com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet; -import com.android.gallery3d.filtershow.imageshow.ImageWithIcon; import com.android.gallery3d.filtershow.imageshow.ImageZoom; import com.android.gallery3d.filtershow.presets.ImagePreset; import com.android.gallery3d.filtershow.provider.SharedImageProvider; @@ -90,6 +98,7 @@ import com.android.gallery3d.filtershow.ui.Spline; import com.android.gallery3d.util.GalleryUtils; import java.io.File; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.Vector; @@ -97,14 +106,19 @@ import java.util.Vector; public class FilterShowActivity extends Activity implements OnItemClickListener, OnShareTargetSelectedListener { - public static final String CROP_ACTION = "com.android.camera.action.EDITOR_CROP"; + // fields for supporting crop action + public static final String CROP_ACTION = "com.android.camera.action.CROP"; + private CropExtras mCropExtras = null; + public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET"; public static final String LAUNCH_FULLSCREEN = "launch-fullscreen"; + public static final int MAX_BMAP_IN_INTENT = 990000; private final PanelController mPanelController = new PanelController(); private ImageLoader mImageLoader = null; private ImageShow mImageShow = null; private ImageCurves mImageCurves = null; private ImageBorder mImageBorders = null; + private ImageRedEyes mImageRedEyes = null; private ImageStraighten mImageStraighten = null; private ImageZoom mImageZoom = null; private ImageCrop mImageCrop = null; @@ -156,10 +170,13 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, ImageFilterRS.setRenderScriptContext(this); ImageShow.setDefaultBackgroundColor(getResources().getColor(R.color.background_screen)); - ImageSmallFilter.setDefaultBackgroundColor(getResources().getColor(R.color.background_main_toolbar)); + ImageSmallFilter.setDefaultBackgroundColor(getResources().getColor( + R.color.background_main_toolbar)); // TODO: get those values from XML. ImageZoom.setZoomedSize(getPixelsFromDip(256)); FramedTextButton.setTextSize((int) getPixelsFromDip(14)); + FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4)); + FramedTextButton.setTriangleSize((int) getPixelsFromDip(10)); ImageShow.setTextSize((int) getPixelsFromDip(12)); ImageShow.setTextPadding((int) getPixelsFromDip(10)); ImageShow.setOriginalTextMargin((int) getPixelsFromDip(4)); @@ -202,8 +219,11 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageRotate = (ImageRotate) findViewById(R.id.imageRotate); mImageFlip = (ImageFlip) findViewById(R.id.imageFlip); mImageTinyPlanet = (ImageTinyPlanet) findViewById(R.id.imageTinyPlanet); + mImageRedEyes = (ImageRedEyes) findViewById(R.id.imageRedEyes); + mImageCrop.setAspectTextSize((int) getPixelsFromDip(18)); ImageCrop.setTouchTolerance((int) getPixelsFromDip(25)); + ImageCrop.setMinCropSize((int) getPixelsFromDip(55)); mImageViews.add(mImageShow); mImageViews.add(mImageCurves); mImageViews.add(mImageBorders); @@ -213,6 +233,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageViews.add(mImageRotate); mImageViews.add(mImageFlip); mImageViews.add(mImageTinyPlanet); + mImageViews.add(mImageRedEyes); + for (ImageShow imageShow : mImageViews) { + mImageLoader.addCacheListener(imageShow); + } mListFx = findViewById(R.id.fxList); mListBorders = findViewById(R.id.bordersList); @@ -230,6 +254,11 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mGeometryButton = (ImageButton) findViewById(R.id.geometryButton); mColorsButton = (ImageButton) findViewById(R.id.colorsButton); + mBottomPanelButtons.add(mFxButton); + mBottomPanelButtons.add(mBorderButton); + mBottomPanelButtons.add(mGeometryButton); + mBottomPanelButtons.add(mColorsButton); + mImageShow.setImageLoader(mImageLoader); mImageCurves.setImageLoader(mImageLoader); mImageCurves.setMaster(mImageShow); @@ -247,6 +276,8 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageFlip.setMaster(mImageShow); mImageTinyPlanet.setImageLoader(mImageLoader); mImageTinyPlanet.setMaster(mImageShow); + mImageRedEyes.setImageLoader(mImageLoader); + mImageRedEyes.setMaster(mImageShow); mPanelController.setActivity(this); @@ -259,6 +290,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mPanelController.addImageView(findViewById(R.id.imageFlip)); mPanelController.addImageView(findViewById(R.id.imageZoom)); mPanelController.addImageView(findViewById(R.id.imageTinyPlanet)); + mPanelController.addImageView(findViewById(R.id.imageRedEyes)); mPanelController.addPanel(mFxButton, mListFx, 0); mPanelController.addPanel(mBorderButton, mListBorders, 1); @@ -268,86 +300,45 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mPanelController.addComponent(mGeometryButton, findViewById(R.id.cropButton)); mPanelController.addComponent(mGeometryButton, findViewById(R.id.rotateButton)); mPanelController.addComponent(mGeometryButton, findViewById(R.id.flipButton)); + mPanelController.addComponent(mGeometryButton, findViewById(R.id.redEyeButton)); mPanelController.addPanel(mColorsButton, mListColors, 3); - int[] recastIDs = { - R.id.tinyplanetButton, - R.id.vignetteButton, - R.id.vibranceButton, - R.id.contrastButton, - R.id.saturationButton, - R.id.bwfilterButton, - R.id.wbalanceButton, - R.id.hueButton, - R.id.exposureButton, - R.id.shadowRecoveryButton - }; ImageFilter[] filters = { new ImageFilterTinyPlanet(), + new ImageFilterWBalance(), + new ImageFilterExposure(), new ImageFilterVignette(), - new ImageFilterVibrance(), new ImageFilterContrast(), + new ImageFilterShadows(), + new ImageFilterVibrance(), + new ImageFilterSharpen(), + new ImageFilterCurves(), + new ImageFilterHue(), new ImageFilterSaturated(), new ImageFilterBwFilter(), - new ImageFilterWBalance(), - new ImageFilterHue(), - new ImageFilterExposure(), - new ImageFilterShadows() + new ImageFilterNegative(), + new ImageFilterEdge(), + new ImageFilterKMeans(), + new ImageFilterDownsample() }; for (int i = 0; i < filters.length; i++) { ImageSmallFilter fView = new ImageSmallFilter(this); - View v = listColors.findViewById(recastIDs[i]); - int pos = listColors.indexOfChild(v); - listColors.removeView(v); - filters[i].setParameter(filters[i].getPreviewParameter()); - if (v instanceof ImageButtonTitle) - filters[i].setName(((ImageButtonTitle) v).getText()); + filters[i].setName(getString(filters[i].getTextId())); fView.setImageFilter(filters[i]); fView.setController(this); fView.setImageLoader(mImageLoader); - fView.setId(recastIDs[i]); - mPanelController.addComponent(mColorsButton, fView); - listColors.addView(fView, pos); - } - - int[] overlayIDs = { - R.id.sharpenButton, - R.id.curvesButtonRGB - }; - int[] overlayBitmaps = { - R.drawable.filtershow_button_colors_sharpen, - R.drawable.filtershow_button_colors_curve - }; - int[] overlayNames = { - R.string.sharpness, - R.string.curvesRGB - }; - - for (int i = 0; i < overlayIDs.length; i++) { - ImageWithIcon fView = new ImageWithIcon(this); - View v = listColors.findViewById(overlayIDs[i]); - int pos = listColors.indexOfChild(v); - listColors.removeView(v); - final int sid = overlayNames[i]; - ImageFilterExposure efilter = new ImageFilterExposure() { - { - mName = getString(sid); - } - }; - efilter.setParameter(-300); - Bitmap bitmap = BitmapFactory.decodeResource(getResources(), - overlayBitmaps[i]); - - fView.setIcon(bitmap); - fView.setImageFilter(efilter); - fView.setController(this); - fView.setImageLoader(mImageLoader); - fView.setId(overlayIDs[i]); + fView.setId(filters[i].getButtonId()); + if (filters[i].getOverlayBitmaps() != 0) { + Bitmap bitmap = BitmapFactory.decodeResource(getResources(), + filters[i].getOverlayBitmaps()); + fView.setOverlayBitmap(bitmap); + } mPanelController.addComponent(mColorsButton, fView); - listColors.addView(fView, pos); + mPanelController.addFilter(filters[i]); + listColors.addView(fView); } mPanelController.addView(findViewById(R.id.applyEffect)); @@ -389,8 +380,37 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, pickImage(); } + // Handle behavior for various actions String action = intent.getAction(); if (action.equalsIgnoreCase(CROP_ACTION)) { + Bundle extras = intent.getExtras(); + if (extras != null) { + mCropExtras = 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)); + + if (mCropExtras.getShowWhenLocked()) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + mImageShow.getImagePreset().mGeoData.setCropExtras(mCropExtras); + + mImageCrop.setExtras(mCropExtras); + String s = getString(R.string.Fixed); + mImageCrop.setAspectString(s); + mImageCrop.setCropActionFlag(true); + mPanelController.setFixedAspect(mCropExtras.getAspectX() > 0 + && mCropExtras.getAspectY() > 0); + } mPanelController.showComponent(findViewById(R.id.cropButton)); } else if (action.equalsIgnoreCase(TINY_PLANET_ACTION)) { mPanelController.showComponent(findViewById(R.id.tinyplanetButton)); @@ -410,7 +430,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mLoadBitmapTask.execute(uri); } - private class LoadBitmapTask extends AsyncTask<Uri, Void, Boolean> { + private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> { View mTinyPlanetButton; int mBitmapSize; @@ -421,19 +441,26 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, @Override protected Boolean doInBackground(Uri... params) { - mImageLoader.loadBitmap(params[0], mBitmapSize); - publishProgress(); - return mImageLoader.queryLightCycle360(); + if (!mImageLoader.loadBitmap(params[0], mBitmapSize)) { + return false; + } + publishProgress(mImageLoader.queryLightCycle360()); + return true; } @Override - protected void onProgressUpdate(Void... values) { + protected void onProgressUpdate(Boolean... values) { super.onProgressUpdate(values); - if (isCancelled()) return; + if (isCancelled()) { + return; + } final View filters = findViewById(R.id.filtersPanel); final View loading = findViewById(R.id.loading); loading.setVisibility(View.GONE); filters.setVisibility(View.VISIBLE); + if (values[0]) { + mTinyPlanetButton.setVisibility(View.VISIBLE); + } } @Override @@ -441,9 +468,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, if (isCancelled()) { return; } - if (result) { - mTinyPlanetButton.setVisibility(View.VISIBLE); + if (!result) { + cannotLoadImage(); } + mLoadBitmapTask = null; super.onPostExecute(result); } @@ -465,6 +493,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, if (mainPanelWidth == 0) { mainPanelWidth = mainViewWidth; } + int filtersPanelWidth = findViewById(R.id.filtersPanel).getWidth(); + if (mainPanelWidth < filtersPanelWidth) { + mainPanelWidth = filtersPanelWidth; + } int leftOver = mainViewWidth - mainPanelWidth - accessoryPanelWidth; if (leftOver < 0) { return -accessoryPanelWidth; @@ -711,19 +743,29 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, // TODO: use listview // TODO: load the borders straight from the filesystem int p = 0; - ImageFilter[] borders = new ImageFilter[7]; + ImageFilter[] borders = new ImageFilter[12]; borders[p++] = new ImageFilterBorder(null); Drawable npd1 = getResources().getDrawable(R.drawable.filtershow_border_4x5); borders[p++] = new ImageFilterBorder(npd1); Drawable npd2 = getResources().getDrawable(R.drawable.filtershow_border_brush); borders[p++] = new ImageFilterBorder(npd2); + Drawable npd3 = getResources().getDrawable(R.drawable.filtershow_border_grunge); + borders[p++] = new ImageFilterBorder(npd3); + Drawable npd4 = getResources().getDrawable(R.drawable.filtershow_border_sumi_e); + borders[p++] = new ImageFilterBorder(npd4); + Drawable npd5 = getResources().getDrawable(R.drawable.filtershow_border_tape); + borders[p++] = new ImageFilterBorder(npd5); borders[p++] = new ImageFilterParametricBorder(Color.BLACK, mImageBorderSize, 0); borders[p++] = new ImageFilterParametricBorder(Color.BLACK, mImageBorderSize, mImageBorderSize); borders[p++] = new ImageFilterParametricBorder(Color.WHITE, mImageBorderSize, 0); borders[p++] = new ImageFilterParametricBorder(Color.WHITE, mImageBorderSize, mImageBorderSize); + int creamColor = Color.argb(255, 237, 237, 227); + borders[p++] = new ImageFilterParametricBorder(creamColor, mImageBorderSize, 0); + borders[p++] = new ImageFilterParametricBorder(creamColor, mImageBorderSize, + mImageBorderSize); ImageSmallFilter previousFilter = null; for (int i = 0; i < p; i++) { @@ -786,6 +828,22 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, } } + public void disableFilterButtons() { + for (ImageButton b : mBottomPanelButtons) { + b.setEnabled(false); + b.setClickable(false); + b.setAlpha(0.4f); + } + } + + public void enableFilterButtons() { + for (ImageButton b : mBottomPanelButtons) { + b.setEnabled(true); + b.setClickable(true); + b.setAlpha(1.0f); + } + } + // ////////////////////////////////////////////////////////////////////////////// // imageState panel... @@ -867,7 +925,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, invalidateOptionsMenu(); } - private void resetHistory() { + void resetHistory() { mNullFxFilter.onClick(mNullFxFilter); mNullBorderFilter.onClick(mNullBorderFilter); @@ -980,17 +1038,88 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, } } + private boolean mSaveToExtraUri = false; + private boolean mSaveAsWallpaper = false; + private boolean mReturnAsExtra = false; + private boolean outputted = false; + public void saveImage() { - if (mImageShow.hasModifications()) { - // Get the name of the album, to which the image will be saved - File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri()); - int bucketId = GalleryUtils.getBucketId(saveDir.getPath()); - String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null); - showSavingProgress(albumName); - mImageShow.saveImage(this, null); - } else { - finish(); + // boolean outputted = false; + if (mCropExtras != null) { + if (mCropExtras.getExtraOutput() != null) { + mSaveToExtraUri = true; + outputted = true; + } + if (mCropExtras.getSetAsWallpaper()) { + mSaveAsWallpaper = true; + outputted = true; + } + if (mCropExtras.getReturnData()) { + + mReturnAsExtra = true; + outputted = true; + } + + if (outputted) { + mImageShow.getImagePreset().mGeoData.setUseCropExtrasFlag(true); + showSavingProgress(null); + mImageShow.returnFilteredResult(this); + } } + if (!outputted) { + if (mImageShow.hasModifications()) { + // Get the name of the album, to which the image will be saved + File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri()); + int bucketId = GalleryUtils.getBucketId(saveDir.getPath()); + String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null); + showSavingProgress(albumName); + mImageShow.saveImage(this, null); + } else { + done(); + } + } + } + + public void onFilteredResult(Bitmap filtered) { + Intent intent = new Intent(); + intent.putExtra(CropExtras.KEY_CROPPED_RECT, mImageShow.getImageCropBounds()); + if (mSaveToExtraUri) { + mImageShow.saveToUri(filtered, mCropExtras.getExtraOutput(), + mCropExtras.getOutputFormat(), this); + } + if (mSaveAsWallpaper) { + try { + WallpaperManager.getInstance(this).setBitmap(filtered); + } catch (IOException e) { + Log.w(LOGTAG, "fail to set wall paper", e); + } + } + if (mReturnAsExtra) { + if (filtered != null) { + int bmapSize = filtered.getRowBytes() * filtered.getHeight(); + /* + * Max size of Binder transaction buffer is 1Mb, so constrain + * Bitmap to be somewhat less than this, otherwise we get + * TransactionTooLargeExceptions. + */ + if (bmapSize > MAX_BMAP_IN_INTENT) { + Log.w(LOGTAG, "Bitmap too large to be returned via intent"); + } else { + intent.putExtra(CropExtras.KEY_DATA, filtered); + } + } + } + setResult(RESULT_OK, intent); + if (!mSaveToExtraUri) { + done(); + } + } + + public void done() { + if (outputted) { + hideSavingProgress(); + } + finish(); } static { diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java index 03a9d8bd5..94259e91d 100644 --- a/src/com/android/gallery3d/filtershow/PanelController.java +++ b/src/com/android/gallery3d/filtershow/PanelController.java @@ -32,6 +32,7 @@ import com.android.gallery3d.filtershow.filters.ImageFilterContrast; import com.android.gallery3d.filtershow.filters.ImageFilterCurves; import com.android.gallery3d.filtershow.filters.ImageFilterExposure; import com.android.gallery3d.filtershow.filters.ImageFilterHue; +import com.android.gallery3d.filtershow.filters.ImageFilterNegative; import com.android.gallery3d.filtershow.filters.ImageFilterRedEye; import com.android.gallery3d.filtershow.filters.ImageFilterSaturated; import com.android.gallery3d.filtershow.filters.ImageFilterShadows; @@ -41,8 +42,8 @@ import com.android.gallery3d.filtershow.filters.ImageFilterVibrance; import com.android.gallery3d.filtershow.filters.ImageFilterVignette; import com.android.gallery3d.filtershow.filters.ImageFilterWBalance; import com.android.gallery3d.filtershow.imageshow.ImageCrop; -import com.android.gallery3d.filtershow.imageshow.ImageGeometry; import com.android.gallery3d.filtershow.imageshow.ImageShow; +import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter; import com.android.gallery3d.filtershow.presets.ImagePreset; import com.android.gallery3d.filtershow.ui.FramedTextButton; import com.android.gallery3d.filtershow.ui.ImageCurves; @@ -57,6 +58,12 @@ public class PanelController implements OnClickListener { private static int HORIZONTAL_MOVE = 1; private static final int ANIM_DURATION = 200; private static final String LOGTAG = "PanelController"; + private boolean mDisableFilterButtons = false; + private boolean mFixedAspect = false; + + public void setFixedAspect(boolean t) { + mFixedAspect = t; + } class Panel { private final View mView; @@ -158,38 +165,52 @@ public class PanelController implements OnClickListener { ImageCrop imageCrop = (ImageCrop) mCurrentImage; switch (itemId) { case R.id.crop_menu_1to1: { - button.setText(mContext.getString(R.string.aspect1to1_effect)); + String t = mContext.getString(R.string.aspect1to1_effect); + button.setText(t); imageCrop.apply(1, 1); + imageCrop.setAspectString(t); break; } case R.id.crop_menu_4to3: { - button.setText(mContext.getString(R.string.aspect4to3_effect)); + String t = mContext.getString(R.string.aspect4to3_effect); + button.setText(t); imageCrop.apply(4, 3); + imageCrop.setAspectString(t); break; } case R.id.crop_menu_3to4: { - button.setText(mContext.getString(R.string.aspect3to4_effect)); + String t = mContext.getString(R.string.aspect3to4_effect); + button.setText(t); imageCrop.apply(3, 4); + imageCrop.setAspectString(t); break; } case R.id.crop_menu_5to7: { - button.setText(mContext.getString(R.string.aspect5to7_effect)); + String t = mContext.getString(R.string.aspect5to7_effect); + button.setText(t); imageCrop.apply(5, 7); + imageCrop.setAspectString(t); break; } case R.id.crop_menu_7to5: { - button.setText(mContext.getString(R.string.aspect7to5_effect)); + String t = mContext.getString(R.string.aspect7to5_effect); + button.setText(t); imageCrop.apply(7, 5); + imageCrop.setAspectString(t); break; } case R.id.crop_menu_none: { - button.setText(mContext.getString(R.string.aspectNone_effect)); + String t = mContext.getString(R.string.aspectNone_effect); + button.setText(t); imageCrop.applyClear(); + imageCrop.setAspectString(t); break; } case R.id.crop_menu_original: { - button.setText(mContext.getString(R.string.aspectOriginal_effect)); + String t = mContext.getString(R.string.aspectOriginal_effect); + button.setText(t); imageCrop.applyOriginal(); + imageCrop.setAspectString(t); break; } } @@ -287,6 +308,7 @@ public class PanelController implements OnClickListener { private final HashMap<View, Panel> mPanels = new HashMap<View, Panel>(); private final HashMap<View, ViewType> mViews = new HashMap<View, ViewType>(); + private final HashMap<String, ImageFilter> mFilters = new HashMap<String, ImageFilter>(); private final Vector<View> mImageViews = new Vector<View>(); private View mCurrentPanel = null; private View mRowPanel = null; @@ -320,6 +342,10 @@ public class PanelController implements OnClickListener { mViews.put(component, new ViewType(component, COMPONENT)); } + public void addFilter(ImageFilter filter) { + mFilters.put(filter.getName(), filter); + } + public void addImageView(View view) { mImageViews.add(view); ImageShow imageShow = (ImageShow) view; @@ -332,6 +358,10 @@ public class PanelController implements OnClickListener { mCurrentImage.resetParameter(); mCurrentImage.select(); } + if (mDisableFilterButtons) { + mActivity.enableFilterButtons(); + mDisableFilterButtons = false; + } } public boolean onBackPressed() { @@ -343,6 +373,11 @@ public class PanelController implements OnClickListener { mMasterImage.onItemClick(position); showPanel(mCurrentPanel); mCurrentImage.select(); + if (mDisableFilterButtons) { + mActivity.enableFilterButtons(); + mActivity.resetHistory(); + mDisableFilterButtons = false; + } return false; } @@ -464,60 +499,21 @@ public class PanelController implements OnClickListener { filter = copy.getFilter(name); } - if (filter == null && name.equalsIgnoreCase( - mCurrentImage.getContext().getString(R.string.curvesRGB))) { - filter = setImagePreset(new ImageFilterCurves(), name); - } - if (filter == null && name.equalsIgnoreCase( - mCurrentImage.getContext().getString(R.string.tinyplanet))) { - filter = setImagePreset(new ImageFilterTinyPlanet(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.vignette))) { - filter = setImagePreset(new ImageFilterVignette(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.sharpness))) { - filter = setImagePreset(new ImageFilterSharpen(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.contrast))) { - filter = setImagePreset(new ImageFilterContrast(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.saturation))) { - filter = setImagePreset(new ImageFilterSaturated(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.bwfilter))) { - filter = setImagePreset(new ImageFilterBwFilter(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.hue))) { - filter = setImagePreset(new ImageFilterHue(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.exposure))) { - filter = setImagePreset(new ImageFilterExposure(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.vibrance))) { - filter = setImagePreset(new ImageFilterVibrance(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString( - R.string.shadow_recovery))) { - filter = setImagePreset(new ImageFilterShadows(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.redeye))) { - filter = setImagePreset(new ImageFilterRedEye(), name); + if (filter == null) { + ImageFilter filterInstance = mFilters.get(name); + if (filterInstance != null) { + try { + ImageFilter newFilter = filterInstance.clone(); + newFilter.reset(); + filter = setImagePreset(newFilter, name); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + } } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.wbalance))) { - filter = setImagePreset(new ImageFilterWBalance(), name); + if (filter != null) { + mMasterImage.setCurrentFilter(filter); } - mMasterImage.setCurrentFilter(filter); } private void showCurvesPopupMenu(final ImageCurves curves, final FramedTextButton anchor) { @@ -574,12 +570,37 @@ public class PanelController implements OnClickListener { } mUtilityPanel.hideAspectButtons(); mUtilityPanel.hideCurvesButtons(); + + if (view instanceof ImageSmallFilter) { + ImageSmallFilter component = (ImageSmallFilter) view; + ImageFilter filter = component.getImageFilter(); + if (filter.getEditingViewId() != 0) { + mCurrentImage = showImageView(filter.getEditingViewId()); + mCurrentImage.setShowControls(filter.showEditingControls()); + String ename = mCurrentImage.getContext().getString(filter.getTextId()); + mUtilityPanel.setEffectName(ename); + if (view.getId() == R.id.curvesButtonRGB) { + // TODO: delegate to the filter / editing view the management of the + // panel accessory view + mUtilityPanel.showCurvesButtons(); + } + mUtilityPanel.setShowParameter(filter.showParameterValue()); + ensureFilter(ename); + mCurrentImage.select(); + } + return; + } + switch (view.getId()) { case R.id.tinyplanetButton: { mCurrentImage = showImageView(R.id.imageTinyPlanet).setShowControls(true); String ename = mCurrentImage.getContext().getString(R.string.tinyplanet); mUtilityPanel.setEffectName(ename); ensureFilter(ename); + if (!mDisableFilterButtons) { + mActivity.disableFilterButtons(); + mDisableFilterButtons = true; + } break; } case R.id.straightenButton: { @@ -593,11 +614,13 @@ public class PanelController implements OnClickListener { String ename = mCurrentImage.getContext().getString(R.string.crop); mUtilityPanel.setEffectName(ename); mUtilityPanel.setShowParameter(false); - if (mCurrentImage instanceof ImageCrop && mUtilityPanel.firstTimeCropDisplayed){ - ((ImageCrop) mCurrentImage).applyOriginal(); + if (mCurrentImage instanceof ImageCrop && mUtilityPanel.firstTimeCropDisplayed) { + ((ImageCrop) mCurrentImage).clear(); mUtilityPanel.firstTimeCropDisplayed = false; } - mUtilityPanel.showAspectButtons(); + if (!mFixedAspect) { + mUtilityPanel.showAspectButtons(); + } break; } case R.id.rotateButton: { @@ -613,89 +636,8 @@ public class PanelController implements OnClickListener { mUtilityPanel.setShowParameter(false); break; } - case R.id.vignetteButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.vignette); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.curvesButtonRGB: { - ImageCurves curves = (ImageCurves) showImageView(R.id.imageCurves); - String ename = curves.getContext().getString(R.string.curvesRGB); - mUtilityPanel.setEffectName(ename); - mUtilityPanel.setShowParameter(false); - mUtilityPanel.showCurvesButtons(); - mCurrentImage = curves; - ensureFilter(ename); - break; - } - case R.id.sharpenButton: { - mCurrentImage = showImageView(R.id.imageZoom).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.sharpness); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.contrastButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.contrast); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.saturationButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.saturation); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.bwfilterButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.bwfilter); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.wbalanceButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(false); - String ename = mCurrentImage.getContext().getString(R.string.wbalance); - mUtilityPanel.setEffectName(ename); - mUtilityPanel.setShowParameter(false); - ensureFilter(ename); - break; - } - case R.id.hueButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.hue); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.exposureButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.exposure); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.vibranceButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.vibrance); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.shadowRecoveryButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.shadow_recovery); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } case R.id.redEyeButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); + mCurrentImage = showImageView(R.id.imageRedEyes).setShowControls(true); String ename = mCurrentImage.getContext().getString(R.string.redeye); mUtilityPanel.setEffectName(ename); ensureFilter(ename); diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java index 7874881fa..a1a1ba186 100644 --- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java +++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java @@ -18,6 +18,7 @@ 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; @@ -26,6 +27,7 @@ import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Matrix; import android.graphics.Rect; +import android.graphics.Bitmap.CompressFormat; import android.media.ExifInterface; import android.net.Uri; import android.provider.MediaStore; @@ -39,12 +41,15 @@ import com.android.gallery3d.common.Utils; import com.android.gallery3d.exif.ExifInvalidFormatException; import com.android.gallery3d.exif.ExifParser; import com.android.gallery3d.exif.ExifTag; +import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.FilterShowActivity; import com.android.gallery3d.filtershow.HistoryAdapter; import com.android.gallery3d.filtershow.imageshow.ImageCrop; import com.android.gallery3d.filtershow.imageshow.ImageShow; import com.android.gallery3d.filtershow.presets.ImagePreset; +import com.android.gallery3d.filtershow.tools.BitmapTask; import com.android.gallery3d.filtershow.tools.SaveCopyTask; +import com.android.gallery3d.util.InterruptableOutputStream; import com.android.gallery3d.util.XmpUtilHelper; import java.io.Closeable; @@ -53,7 +58,9 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.Vector; +import java.util.concurrent.locks.ReentrantLock; public class ImageLoader { @@ -72,13 +79,16 @@ public class ImageLoader { private FilterShowActivity mActivity = null; - public static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL; - public static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90; + public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; + public static final int DEFAULT_COMPRESS_QUALITY = 95; + + public static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL; + public static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90; public static final int ORI_ROTATE_180 = ExifInterface.ORIENTATION_ROTATE_180; public static final int ORI_ROTATE_270 = ExifInterface.ORIENTATION_ROTATE_270; - public static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL; - public static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL; - public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE; + public static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL; + public static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL; + public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE; public static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE; private Context mContext = null; @@ -87,6 +97,8 @@ public class ImageLoader { private Rect mOriginalBounds = null; private static int mZoomOrientation = ORI_NORMAL; + private ReentrantLock mLoadingLock = new ReentrantLock(); + public ImageLoader(FilterShowActivity activity, Context context) { mActivity = activity; mContext = context; @@ -102,16 +114,24 @@ public class ImageLoader { return mActivity; } - public void loadBitmap(Uri uri,int size) { + public boolean loadBitmap(Uri uri, int size) { + mLoadingLock.lock(); mUri = uri; mOrientation = getOrientation(mContext, uri); mOriginalBitmapSmall = loadScaledBitmap(uri, 160); if (mOriginalBitmapSmall == null) { // Couldn't read the bitmap, let's exit - mActivity.cannotLoadImage(); + mLoadingLock.unlock(); + return false; } mOriginalBitmapLarge = loadScaledBitmap(uri, size); + if (mOriginalBitmapLarge == null) { + mLoadingLock.unlock(); + return false; + } updateBitmaps(); + mLoadingLock.unlock(); + return true; } public Uri getUri() { @@ -134,21 +154,27 @@ public class ImageLoader { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null); - if (cursor.moveToNext()){ - int ori = cursor.getInt(0); - - switch (ori){ - case 0: return ORI_NORMAL; - case 90: return ORI_ROTATE_90; - case 270: return ORI_ROTATE_270; - case 180: return ORI_ROTATE_180; - default: - return -1; - } - } else{ + if (cursor.moveToNext()) { + int ori = cursor.getInt(0); + + switch (ori) { + case 0: + return ORI_NORMAL; + case 90: + return ORI_ROTATE_90; + case 270: + return ORI_ROTATE_270; + case 180: + return ORI_ROTATE_180; + default: + return -1; + } + } else { return -1; } - } catch (SQLiteException e){ + } catch (SQLiteException e) { + return ExifInterface.ORIENTATION_UNDEFINED; + } catch (IllegalArgumentException e) { return ExifInterface.ORIENTATION_UNDEFINED; } finally { Utils.closeSilently(cursor); @@ -193,46 +219,46 @@ public class ImageLoader { warnListeners(); } - public static Bitmap rotateToPortrait(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; - } + public static Bitmap rotateToPortrait(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); @@ -265,6 +291,7 @@ public class ImageLoader { } static final int MAX_BITMAP_DIM = 2048; + private Bitmap loadScaledBitmap(Uri uri, int size) { InputStream is = null; try { @@ -327,10 +354,12 @@ public class ImageLoader { } public void addListener(ImageShow imageShow) { + mLoadingLock.lock(); if (!mListeners.contains(imageShow)) { mListeners.add(imageShow); } mHiresCache.addObserver(imageShow); + mLoadingLock.unlock(); } private void warnListeners() { @@ -348,10 +377,12 @@ public class ImageLoader { } }; - // TODO: this currently does the loading + filtering on the UI thread -- need to + // TODO: this currently does the loading + filtering on the UI thread -- + // need to // move this to a background thread. public Bitmap getScaleOneImageForPreset(ImageShow caller, ImagePreset imagePreset, Rect bounds, boolean force) { + mLoadingLock.lock(); Bitmap bmp = mZoomCache.getImage(imagePreset, bounds); if (force || bmp == null) { bmp = loadRegionBitmap(mUri, bounds); @@ -363,19 +394,24 @@ public class ImageLoader { bmp2 = imagePreset.apply(bmp2); imagePreset.setScaleFactor(scaleFactor); mZoomCache.setImage(imagePreset, bounds, bmp2); + mLoadingLock.unlock(); return bmp2; } } + mLoadingLock.unlock(); return bmp; } // Caching method public Bitmap getImageForPreset(ImageShow caller, ImagePreset imagePreset, boolean hiRes) { + mLoadingLock.lock(); if (mOriginalBitmapSmall == null) { + mLoadingLock.unlock(); return null; } if (mOriginalBitmapLarge == null) { + mLoadingLock.unlock(); return null; } @@ -396,13 +432,16 @@ public class ImageLoader { mCache.addObserver(caller); } } + mLoadingLock.unlock(); return filteredImage; } public void resetImageForPreset(ImagePreset imagePreset, ImageShow caller) { + mLoadingLock.lock(); mHiresCache.reset(imagePreset); mCache.reset(imagePreset); mZoomCache.reset(imagePreset); + mLoadingLock.unlock(); } public void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity, @@ -419,6 +458,109 @@ public class ImageLoader { }).execute(preset); } + public static Bitmap loadMutableBitmap(Context context, Uri sourceUri) + throws FileNotFoundException { + BitmapFactory.Options options = new BitmapFactory.Options(); + // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't + // exist) + options.inMutable = true; + + InputStream is = context.getContentResolver().openInputStream(sourceUri); + Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); + int orientation = ImageLoader.getOrientation(context, sourceUri); + bitmap = ImageLoader.rotateToPortrait(bitmap, orientation); + return bitmap; + } + + public void returnFilteredResult(ImagePreset preset, + final FilterShowActivity filterShowActivity) { + preset.setIsHighQuality(true); + preset.setScaleFactor(1.0f); + + BitmapTask.Callbacks<ImagePreset> cb = new BitmapTask.Callbacks<ImagePreset>() { + + @Override + public void onComplete(Bitmap result) { + filterShowActivity.onFilteredResult(result); + } + + @Override + public void onCancel() { + } + + @Override + public Bitmap onExecute(ImagePreset param) { + if (param == null) { + return null; + } + try { + Bitmap bitmap = param.apply(loadMutableBitmap(mContext, mUri)); + return bitmap; + } catch (FileNotFoundException ex) { + Log.w(LOGTAG, "Failed to save image!", ex); + return null; + } + } + }; + + (new BitmapTask<ImagePreset>(cb)).execute(preset); + } + + private 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 CompressFormat convertExtensionToCompressFormat(String extension) { + return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; + } + + public void saveToUri(Bitmap bmap, Uri uri, final String outputFormat, + final FilterShowActivity filterShowActivity) { + + OutputStream out = null; + try { + out = filterShowActivity.getContentResolver().openOutputStream(uri); + } catch (FileNotFoundException e) { + Log.w(LOGTAG, "cannot write output", e); + out = null; + } finally { + if (bmap == null || out == null) { + return; + } + } + + final InterruptableOutputStream ios = new InterruptableOutputStream(out); + + BitmapTask.Callbacks<Bitmap> cb = new BitmapTask.Callbacks<Bitmap>() { + + @Override + public void onComplete(Bitmap result) { + filterShowActivity.done(); + } + + @Override + public void onCancel() { + ios.interrupt(); + } + + @Override + public Bitmap onExecute(Bitmap param) { + CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(outputFormat)); + param.compress(cf, DEFAULT_COMPRESS_QUALITY, ios); + Utils.closeSilently(ios); + return null; + } + }; + + (new BitmapTask<Bitmap>(cb)).execute(bmap); + } + public void setAdapter(HistoryAdapter adapter) { mAdapter = adapter; } @@ -479,4 +621,9 @@ public class ImageLoader { } } + public void addCacheListener(ImageShow imageShow) { + mHiresCache.addObserver(imageShow); + mCache.addObserver(imageShow); + } + } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java index d00c7e31b..b8f0cf84f 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java @@ -18,35 +18,64 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; +import com.android.gallery3d.R; import com.android.gallery3d.filtershow.presets.ImagePreset; public class ImageFilter implements Cloneable { - protected int mMaxParameter = 100; - protected int mMinParameter = -100; + public static int DEFAULT_MAX_PARAMETER = 100; + public static int DEFAULT_MIN_PARAMETER = -100; + public static int DEFAULT_INITIAL_PARAMETER = 0; + + protected int mMaxParameter = DEFAULT_MAX_PARAMETER; + protected int mMinParameter = DEFAULT_MIN_PARAMETER; protected int mPreviewParameter = mMaxParameter; - protected int mDefaultParameter = 0; - protected int mParameter = 0; + protected int mDefaultParameter = DEFAULT_INITIAL_PARAMETER; + protected int mParameter = DEFAULT_INITIAL_PARAMETER; private ImagePreset mImagePreset; protected String mName = "Original"; private final String LOGTAG = "ImageFilter"; - public static final byte TYPE_BORDER =1; - public static final byte TYPE_FX = 2; + 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; private byte filterType = TYPE_NORMAL; - public byte getFilterType(){ + public byte getFilterType() { return filterType; } - protected void setFilterType(byte type){ + protected void setFilterType(byte type) { filterType = type; } + public int getButtonId() { + return 0; + } + + public int getTextId() { + return 0; + } + + public int getOverlayBitmaps() { + return 0; + } + + public int getEditingViewId() { + return R.id.imageShow; + } + + public boolean showEditingControls() { + return true; + } + + public boolean showParameterValue() { + return true; + } + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilter filter = (ImageFilter) super.clone(); @@ -61,6 +90,10 @@ public class ImageFilter implements Cloneable { return filter; } + public void reset() { + setParameter(mDefaultParameter); + } + public boolean isNil() { if (mParameter == mDefaultParameter) { return true; @@ -93,7 +126,7 @@ public class ImageFilter implements Cloneable { * The maximum allowed value (inclusive) * @return maximum value allowed as input to this filter */ - public int getMaxParameter(){ + public int getMaxParameter() { return mMaxParameter; } @@ -101,7 +134,7 @@ public class ImageFilter implements Cloneable { * The parameter value to be used in previews. * @return parameter value to be used to preview the filter */ - public int getPreviewParameter(){ + public int getPreviewParameter() { return mPreviewParameter; } @@ -109,7 +142,7 @@ public class ImageFilter implements Cloneable { * The minimum allowed value (inclusive) * @return minimum value allowed as input to this filter */ - public int getMinParameter(){ + public int getMinParameter() { return mMinParameter; } @@ -117,7 +150,7 @@ public class ImageFilter implements Cloneable { * Returns the default value returned by this filter. * @return default value */ - public int getDefaultParameter(){ + public int getDefaultParameter() { return mDefaultParameter; } @@ -130,6 +163,9 @@ public class ImageFilter implements Cloneable { } public boolean same(ImageFilter filter) { + if (filter == null) { + return false; + } if (!filter.getName().equalsIgnoreCase(getName())) { return false; } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java deleted file mode 100644 index 45f49164b..000000000 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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; - -public class ImageFilterBWBlue extends ImageFilter { - - public ImageFilterBWBlue() { - mName = "B&W - Blue"; - } - - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h); - - @Override - public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h); - return bitmap; - } - -} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java deleted file mode 100644 index 8f91c3c82..000000000 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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; - -public class ImageFilterBWGreen extends ImageFilter { - - public ImageFilterBWGreen() { - mName = "B&W - Green"; - } - - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h); - - @Override - public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h); - return bitmap; - } - -} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java deleted file mode 100644 index f0c65d71e..000000000 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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; - -public class ImageFilterBWRed extends ImageFilter { - - public ImageFilterBWRed() { - mName = "B&W - Red"; - } - - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h); - - @Override - public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h); - return bitmap; - } - -} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java index a310358ce..1d198e45c 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java @@ -51,6 +51,9 @@ public class ImageFilterBorder extends ImageFilter { if (!isBorderFilter) { return false; } + if (!(filter instanceof ImageFilterBorder)) { + return false; + } ImageFilterBorder borderFilter = (ImageFilterBorder) filter; if (mNinePatch != borderFilter.mNinePatch) { return false; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java index 558abe3c3..1bb5c76ac 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; import android.graphics.Color; @@ -29,6 +31,16 @@ public class ImageFilterBwFilter extends ImageFilter { } @Override + public int getButtonId() { + return R.id.bwfilterButton; + } + + @Override + public int getTextId() { + return R.string.bwfilter; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterBwFilter filter = (ImageFilterBwFilter) super.clone(); return filter; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java index 0c3bb37ca..70e3d8589 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterContrast extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterContrast extends ImageFilter { mName = "Contrast"; } + @Override + public int getButtonId() { + return R.id.contrastButton; + } + + @Override + public int getTextId() { + return R.string.contrast; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java index 89641d103..ba49a8fcb 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java @@ -18,6 +18,7 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; +import com.android.gallery3d.R; import com.android.gallery3d.filtershow.ui.Spline; public class ImageFilterCurves extends ImageFilter { @@ -31,6 +32,31 @@ public class ImageFilterCurves extends ImageFilter { } @Override + public int getButtonId() { + return R.id.curvesButtonRGB; + } + + @Override + public int getTextId() { + return R.string.curvesRGB; + } + + @Override + public int getOverlayBitmaps() { + return R.drawable.filtershow_button_colors_curve; + } + + @Override + public int getEditingViewId() { + return R.id.imageCurves; + } + + @Override + public boolean showParameterValue() { + return false; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterCurves filter = (ImageFilterCurves) super.clone(); for (int i = 0; i < 4; 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..fa2293ca7 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java @@ -0,0 +1,68 @@ +/* + * 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 ImageFilterDownsample extends ImageFilter { + + public ImageFilterDownsample() { + mName = "Downsample"; + mMaxParameter = 100; + mMinParameter = 5; + mPreviewParameter = 10; + mDefaultParameter = 50; + mParameter = 50; + } + + @Override + public int getButtonId() { + return R.id.downsampleButton; + } + + @Override + public int getTextId() { + return R.string.downsample; + } + + @Override + public boolean isNil() { + return false; + } + + @Override + public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + int p = mParameter; + if (p > 0 && p < 100) { + int newWidth = w * p / 100; + int newHeight = h * p / 100; + if (newWidth <= 0 || newHeight <= 0) { + 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/ImageFilterBW.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java index f4a7717f9..9eda64874 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java @@ -18,20 +18,39 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; -public class ImageFilterBW extends ImageFilter { +import com.android.gallery3d.R; - public ImageFilterBW() { - mName = "Black & White"; +public class ImageFilterEdge extends ImageFilter { + + public ImageFilterEdge() { + mName = "Edge"; + mPreviewParameter = 0; } - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h); + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float p); + + @Override + public int getButtonId() { + return R.id.edgeButton; + } + + @Override + public int getTextId() { + return R.string.edge; + } + + @Override + public boolean isNil() { + return false; + } @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h); + float p = mParameter + 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 index e38dc8eb5..63f860171 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterExposure extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterExposure extends ImageFilter { mName = "Exposure"; } + @Override + public int getButtonId() { + return R.id.exposureButton; + } + + @Override + public int getTextId() { + return R.string.exposure; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java index bdcb0ea4e..33ecc8ab9 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java @@ -23,6 +23,7 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; +import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.imageshow.GeometryMath; import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; @@ -69,22 +70,56 @@ public class ImageFilterGeometry extends ImageFilter { // TODO: implement bilinear or bicubic here... for now, just use // canvas to do a simple implementation... // TODO: and be more memory efficient! (do it in native?) + + CropExtras extras = mGeometry.getCropExtras(); + boolean useExtras = mGeometry.getUseCropExtrasFlag(); + int outputX = 0; + int outputY = 0; + boolean s = false; + if (extras != null && useExtras){ + outputX = extras.getOutputX(); + outputY = extras.getOutputY(); + s = extras.getScaleUp(); + } + + Rect cropBounds = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); RectF crop = mGeometry.getCropBounds(bitmap); if (crop.width() > 0 && crop.height() > 0) - crop.roundOut(cropBounds); - Bitmap temp = null; - if (mGeometry.hasSwitchedWidthHeight()) { - temp = Bitmap.createBitmap(cropBounds.height(), cropBounds.width(), mConfig); - } else { - temp = Bitmap.createBitmap(cropBounds.width(), cropBounds.height(), mConfig); + cropBounds = GeometryMath.roundNearest(crop); + + int width = cropBounds.width(); + int height = cropBounds.height(); + + if (mGeometry.hasSwitchedWidthHeight()){ + int temp = width; + width = height; + height = temp; } + + if(outputX <= 0 || outputY <= 0){ + outputX = width; + outputY = height; + } + + float scaleX = 1; + float scaleY = 1; + if (s){ + scaleX = (float) outputX / width; + scaleY = (float) outputY / height; + } + + Bitmap temp = null; + temp = Bitmap.createBitmap(outputX, outputY, mConfig); + float[] displayCenter = { temp.getWidth() / 2f, temp.getHeight() / 2f }; Matrix m1 = mGeometry.buildTotalXform(bitmap.getWidth(), bitmap.getHeight(), displayCenter); + m1.postScale(scaleX, scaleY, displayCenter[0], displayCenter[1]); + Canvas canvas = new Canvas(temp); Paint paint = new Paint(); paint.setAntiAlias(true); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java index 279718edb..e2ea388dc 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterHue extends ImageFilter { @@ -29,6 +31,16 @@ public class ImageFilterHue extends ImageFilter { } @Override + public int getButtonId() { + return R.id.hueButton; + } + + @Override + public int getTextId() { + return R.string.hue; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterHue filter = (ImageFilterHue) super.clone(); filter.cmatrix = new ColorSpaceMatrix(cmatrix); 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..f03baca39 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java @@ -0,0 +1,94 @@ +/* + * 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 ImageFilter { + private int mSeed = 0; + + public ImageFilterKMeans() { + mName = "KMeans"; + mMaxParameter = 20; + mMinParameter = 2; + mPreviewParameter = 4; + mDefaultParameter = 4; + mParameter = 4; + + // set random seed for session + Time t = new Time(); + t.setToNow(); + mSeed = (int) t.toMillis(false); + } + + 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 int getButtonId() { + return R.id.kmeansButton; + } + + @Override + public int getTextId() { + return R.string.kmeans; + } + + @Override + public boolean isNil() { + return false; + } + + @Override + public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { + 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); + } + + int p = Math.max(mParameter, mMinParameter) % (mMaxParameter + 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..04fd1e42e --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java @@ -0,0 +1,47 @@ +package com.android.gallery3d.filtershow.filters; + +import android.graphics.Bitmap; + +import com.android.gallery3d.R; + +public class ImageFilterNegative extends ImageFilter { + + public ImageFilterNegative() { + mName = "Negative"; + } + + @Override + public int getButtonId() { + return R.id.negativeButton; + } + + @Override + public int getTextId() { + return R.string.negative; + } + + @Override + public boolean isNil() { + return false; + } + + @Override + public boolean showEditingControls() { + return false; + } + + @Override + public boolean showParameterValue() { + return false; + } + + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h); + + @Override + public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { + 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 index 3d6954691..ade3cb26b 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java @@ -56,6 +56,9 @@ public class ImageFilterParametricBorder extends ImageFilter { if (!isBorderFilter) { return false; } + if (!(filter instanceof ImageFilterParametricBorder)) { + return false; + } ImageFilterParametricBorder borderFilter = (ImageFilterParametricBorder) filter; if (borderFilter.mBorderColor != mBorderColor) { return false; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java index c77de330f..9ae6f511e 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java @@ -17,40 +17,167 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.RectF; -public class ImageFilterRedEye extends ImageFilter { - private static final String TAG = "ImageFilterRedEye"; +import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; + +import java.util.Vector; +public class ImageFilterRedEye extends ImageFilter { + private static final String LOGTAG = "ImageFilterRedEye"; + private Vector<RedEyeCandidate> mCandidates = null; public ImageFilterRedEye() { - mName = "Redeye"; + mName = "Red Eye"; + } + @Override + public int getButtonId() { + return R.id.redEyeButton; + } + + @Override + public int getTextId() { + return R.string.redeye; + } + + @Override + public int getEditingViewId() { + return R.id.imageRedEyes; } @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterRedEye filter = (ImageFilterRedEye) super.clone(); - + if (mCandidates != null) { + int size = mCandidates.size(); + filter.mCandidates = new Vector<RedEyeCandidate>(); + for (int i = 0; i < size; i++) { + filter.mCandidates.add(new RedEyeCandidate(mCandidates.elementAt(i))); + } + } return filter; } - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, short []matrix); + @Override + public boolean isNil() { + if (mCandidates != null && mCandidates.size() > 0) { + return false; + } + return true; + } + + @Override + public boolean same(ImageFilter filter) { + boolean isRedEyeFilter = super.same(filter); + if (!isRedEyeFilter) { + return false; + } + ImageFilterRedEye redEyeFilter = (ImageFilterRedEye) filter; + if (redEyeFilter.mCandidates == null && mCandidates == null) { + return true; + } + if (redEyeFilter.mCandidates == null || mCandidates == null) { + return false; + } + if (redEyeFilter.mCandidates.size() != mCandidates.size()) { + return false; + } + int size = mCandidates.size(); + for (int i = 0; i < size; i++) { + RedEyeCandidate c1 = mCandidates.elementAt(i); + RedEyeCandidate c2 = redEyeFilter.mCandidates.elementAt(i); + if (!c1.equals(c2)) { + return false; + } + } + return true; + } + + public Vector<RedEyeCandidate> getCandidates() { + if (mCandidates == null) { + mCandidates = new Vector<RedEyeCandidate>(); + } + return mCandidates; + } + + public void addRect(RectF rect, RectF bounds) { + if (mCandidates == null) { + mCandidates = new Vector<RedEyeCandidate>(); + } + Vector<RedEyeCandidate> intersects = new Vector<RedEyeCandidate>(); + for (int i = 0; i < mCandidates.size(); i++) { + RedEyeCandidate r = mCandidates.elementAt(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); + mCandidates.remove(r); + } + mCandidates.add(new RedEyeCandidate(rect, bounds)); + } + + public void clear() { + if (mCandidates == null) { + mCandidates = new Vector<RedEyeCandidate>(); + } + mCandidates.clear(); + } + + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, short[] matrix); @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); - float p = mParameter; - float value = p; - int box = Math.min(w, h); - int sizex = Math.min((int)((p+100)*box/400),w/2); - int sizey = Math.min((int)((p+100)*box/800),h/2); - - short [] rect = new short[]{ - (short) (w/2-sizex),(short) (w/2-sizey), - (short) (2*sizex),(short) (2*sizey)}; + short[] rect = new short[4]; - nativeApplyFilter(bitmap, w, h, rect); + if (mCandidates != null && mCandidates.size() > 0) { + for (int i = 0; i < mCandidates.size(); i++) { + RectF r = new RectF(mCandidates.elementAt(i).mRect); + GeometryMetadata geo = getImagePreset().mGeoData; + Matrix originalToScreen = geo.getOriginalToScreen(true, + getImagePreset().getImageLoader().getOriginalBounds().width(), + getImagePreset().getImageLoader().getOriginalBounds().height(), + w, h); + originalToScreen.mapRect(r); + if (r.left < 0) { + r.left = 0; + } + if (r.left > w) { + r.left = w; + } + if (r.top < 0) { + r.top = 0; + } + if (r.top > h) { + r.top = h; + } + if (r.right < 0) { + r.right = 0; + } + if (r.right > w) { + r.right = w; + } + if (r.bottom < 0) { + r.bottom = 0; + } + if (r.bottom > h) { + r.bottom = 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 index 1d3459195..129165b3e 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterSaturated extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterSaturated extends ImageFilter { mName = "Saturated"; } + @Override + public int getButtonId() { + return R.id.saturationButton; + } + + @Override + public int getTextId() { + return R.string.saturation; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float saturation); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java index 4e6b848ae..de8fcd5ea 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterShadows extends ImageFilter { @@ -26,6 +28,16 @@ public class ImageFilterShadows extends ImageFilter { } @Override + public int getButtonId() { + return R.id.shadowRecoveryButton; + } + + @Override + public int getTextId() { + return R.string.shadow_recovery; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterShadows filter = (ImageFilterShadows) super.clone(); return filter; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java index a355539c2..1951b9b9e 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java @@ -28,6 +28,26 @@ public class ImageFilterSharpen extends ImageFilterRS { } @Override + public int getButtonId() { + return R.id.sharpenButton; + } + + @Override + public int getTextId() { + return R.string.sharpness; + } + + @Override + public int getOverlayBitmaps() { + return R.drawable.filtershow_button_colors_sharpen; + } + + @Override + public int getEditingViewId() { + return R.id.imageZoom; + } + + @Override public void createFilter(android.content.res.Resources res, float scaleFactor, boolean highQuality) { int w = mInPixelsAllocation.getType().getX(); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java index 423e55828..36bd62630 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java @@ -22,6 +22,8 @@ import android.graphics.RectF; import com.adobe.xmp.XMPException; import com.adobe.xmp.XMPMeta; +import com.android.gallery3d.R; +import com.android.gallery3d.app.Log; import com.android.gallery3d.filtershow.presets.ImagePreset; /** @@ -58,6 +60,16 @@ public class ImageFilterTinyPlanet extends ImageFilter { mAngle = 0; } + @Override + public int getButtonId() { + return R.id.tinyplanetButton; + } + + @Override + public int getTextId() { + return R.string.tinyplanet; + } + public void setAngle(float angle) { mAngle = angle; } @@ -90,8 +102,17 @@ public class ImageFilterTinyPlanet extends ImageFilter { } } - Bitmap mBitmapOut = Bitmap.createBitmap( - outputSize, outputSize, Bitmap.Config.ARGB_8888); + Bitmap mBitmapOut = null; + while (mBitmapOut == null) { + try { + mBitmapOut = Bitmap.createBitmap( + outputSize, outputSize, Bitmap.Config.ARGB_8888); + } catch (java.lang.OutOfMemoryError e) { + System.gc(); + outputSize /= 2; + Log.v(TAG, "No memory to create Full Tiny Planet create half"); + } + } nativeApplyFilter(bitmapIn, bitmapIn.getWidth(), bitmapIn.getHeight(), mBitmapOut, outputSize, mParameter / 100f, mAngle); return mBitmapOut; @@ -112,10 +133,19 @@ public class ImageFilterTinyPlanet extends ImageFilter { // Make sure the intermediate image has the similar size to the // input. + Bitmap paddedBitmap = null; float scale = intermediateWidth / (float) fullPanoWidth; - Bitmap paddedBitmap = Bitmap.createBitmap( + 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; + Log.v(TAG, "No memory to create Full Tiny Planet create half"); + } + } Canvas paddedCanvas = new Canvas(paddedBitmap); int right = left + croppedAreaWidth; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java index 34f8b245e..7720d0490 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterVibrance extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterVibrance extends ImageFilter { mName = "Vibrance"; } + @Override + public int getButtonId() { + return R.id.vibranceButton; + } + + @Override + public int getTextId() { + return R.string.vibrance; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java index 7a471e5b9..3c904fa6c 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterVignette extends ImageFilter { @@ -25,6 +27,16 @@ public class ImageFilterVignette extends ImageFilter { mName = "Vignette"; } + @Override + public int getButtonId() { + return R.id.vignetteButton; + } + + @Override + public int getTextId() { + return R.string.vignette; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java index b00b867b3..8665dc54c 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterWBalance extends ImageFilter { @@ -26,13 +28,32 @@ public class ImageFilterWBalance extends ImageFilter { mName = "WBalance"; } + @Override + public int getButtonId() { + return R.id.wbalanceButton; + } + + @Override + public int getTextId() { + return R.string.wbalance; + } + + public boolean showEditingControls() { + return false; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, int locX, int locY); @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h, -1,-1); + nativeApplyFilter(bitmap, w, h, -1, -1); return bitmap; } + + @Override + public boolean isNil() { + return false; + } } 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..58d3afa3b --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.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.filters; + +import android.graphics.RectF; + +public class RedEyeCandidate { + 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/imageshow/BoundedRect.java b/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java new file mode 100644 index 000000000..e94d1ed9e --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java @@ -0,0 +1,340 @@ +/* + * 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.Matrix; +import android.graphics.RectF; + +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() { + rot = 0; + outer = new RectF(); + inner = new RectF(); + innerRotated = new float[8]; + } + + 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(); + } + + /** + * 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 RectF getInner() { + return new RectF(inner); + } + + /** + * 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 = + GeometryMath.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 = GeometryMath.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 = GeometryMath.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/imageshow/CropMath.java b/src/com/android/gallery3d/filtershow/imageshow/CropMath.java new file mode 100644 index 000000000..9037ca043 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/CropMath.java @@ -0,0 +1,191 @@ +/* + * 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.Matrix; +import android.graphics.RectF; + +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] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right); + array[x + 1] = GeometryMath.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 = GeometryMath.vectorLength( + GeometryMath.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); + } + + 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/imageshow/GeometryMath.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java index 33a3f73cb..568dadfc3 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java @@ -16,6 +16,7 @@ package com.android.gallery3d.filtershow.imageshow; +import android.graphics.Rect; import android.graphics.RectF; public class GeometryMath { @@ -25,11 +26,37 @@ public class GeometryMath { return Math.max(Math.min(i, high), low); } - protected static float[] shortestVectorFromPointToLine(float[] point, float[] l1, float[] l2) { - float x1 = l1[0]; - float x2 = l2[0]; - float y1 = l1[1]; - float y2 = l2[1]; + 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) @@ -39,64 +66,78 @@ public class GeometryMath { float[] ret = { (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1)) }; - float [] vec = {ret[0] - point[0], ret[1] - point[1] }; + float[] vec = { + ret[0] - point[0], ret[1] - point[1] + }; return vec; } // A . B - public static float dotProduct(float[] a, float[] b){ + public static float dotProduct(float[] a, float[] b) { return a[0] * b[0] + a[1] * b[1]; } - public static float[] normalize(float[] a){ + 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 }; + float[] b = { + a[0] / length, a[1] / length + }; return b; } // A onto B - public static float scalarProjection(float[] a, float[] 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] }; + 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] }; + 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 RectF scaleRect(RectF r, float scale){ + public static RectF scaleRect(RectF r, float scale) { return new RectF(r.left * scale, r.top * scale, r.right * scale, r.bottom * scale); } // A - B - public static float[] vectorSubtract(float [] a, float [] 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++){ + 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){ + 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) return 1; - return Math.min(newWidth / oldWidth , newHeight / oldHeight); + 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; } } diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java index cf52b15ca..b53284061 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java @@ -21,12 +21,11 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; +import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.filters.ImageFilterGeometry; public class GeometryMetadata { - // Applied in order: rotate, crop, scale. - // Do not scale saved image (presumably?). private static final ImageFilterGeometry mImageFilter = new ImageFilterGeometry(); private static final String LOGTAG = "GeometryMetadata"; private float mScaleFactor = 1.0f; @@ -36,12 +35,29 @@ public class GeometryMetadata { private final RectF mPhotoBounds = new RectF(); private FLIP mFlip = FLIP.NONE; - private RectF mBounds = new RectF(); - public enum FLIP { NONE, VERTICAL, HORIZONTAL, BOTH } + // Output format data from intent extras + private boolean mUseCropExtras = false; + private CropExtras mCropExtras = null; + public void setUseCropExtrasFlag(boolean f){ + mUseCropExtras = f; + } + + public boolean getUseCropExtrasFlag(){ + return mUseCropExtras; + } + + public void setCropExtras(CropExtras e){ + mCropExtras = e; + } + + public CropExtras getCropExtras(){ + return mCropExtras; + } + public GeometryMetadata() { } @@ -59,10 +75,8 @@ public class GeometryMetadata { if (mStraightenRotation != 0) { return true; } - Rect cropBounds = new Rect(); - mCropBounds.roundOut(cropBounds); - Rect photoBounds = new Rect(); - mPhotoBounds.roundOut(photoBounds); + Rect cropBounds = GeometryMath.roundNearest(mCropBounds); + Rect photoBounds = GeometryMath.roundNearest(mPhotoBounds); if (!cropBounds.equals(photoBounds)) { return true; } @@ -88,7 +102,11 @@ public class GeometryMetadata { mCropBounds.set(g.mCropBounds); mPhotoBounds.set(g.mPhotoBounds); mFlip = g.mFlip; - mBounds = g.mBounds; + + mUseCropExtras = g.mUseCropExtras; + if (g.mCropExtras != null){ + mCropExtras = new CropExtras(g.mCropExtras); + } } public float getScaleFactor() { @@ -186,48 +204,16 @@ public class GeometryMetadata { + ",photoRect=" + mPhotoBounds.toShortString() + "]"; } - // TODO: refactor away - protected static Matrix getHorizontalMatrix(float width) { - Matrix flipHorizontalMatrix = new Matrix(); - flipHorizontalMatrix.setScale(-1, 1); - flipHorizontalMatrix.postTranslate(width, 0); - return flipHorizontalMatrix; - } - protected static void concatHorizontalMatrix(Matrix m, float width) { m.postScale(-1, 1); m.postTranslate(width, 0); } - // TODO: refactor away - protected static Matrix getVerticalMatrix(float height) { - Matrix flipVerticalMatrix = new Matrix(); - flipVerticalMatrix.setScale(1, -1); - flipVerticalMatrix.postTranslate(0, height); - return flipVerticalMatrix; - } - protected static void concatVerticalMatrix(Matrix m, float height) { m.postScale(1, -1); m.postTranslate(0, height); } - // TODO: refactor away - public static Matrix getFlipMatrix(float width, float height, FLIP type) { - if (type == FLIP.HORIZONTAL) { - return getHorizontalMatrix(width); - } else if (type == FLIP.VERTICAL) { - return getVerticalMatrix(height); - } else if (type == FLIP.BOTH) { - Matrix flipper = getVerticalMatrix(height); - flipper.postConcat(getHorizontalMatrix(width)); - return flipper; - } else { - Matrix m = new Matrix(); - m.reset(); // identity - return m; - } - } public static void concatMirrorMatrix(Matrix m, float width, float height, FLIP type) { if (type == FLIP.HORIZONTAL) { @@ -333,46 +319,10 @@ public class GeometryMetadata { return m1; } - // TODO: refactor away - public Matrix getFlipMatrix(float width, float height) { - FLIP type = getFlipType(); - return getFlipMatrix(width, height, type); - } - public boolean hasSwitchedWidthHeight() { return (((int) (mRotation / 90)) % 2) != 0; } - // TODO: refactor away - public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy, - float rotation) { - float dx0 = width / 2; - float dy0 = height / 2; - Matrix m = getFlipMatrix(width, height); - m.postTranslate(-dx0, -dy0); - m.postRotate(rotation); - m.postScale(scaling, scaling); - m.postTranslate(dx, dy); - return m; - } - - // TODO: refactor away - public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy, - boolean onlyRotate) { - float rot = mRotation; - if (!onlyRotate) { - rot += mStraightenRotation; - } - return buildGeometryMatrix(width, height, scaling, dx, dy, rot); - } - - // TODO: refactor away - public Matrix buildGeometryUIMatrix(float scaling, float dx, float dy) { - float w = mPhotoBounds.width(); - float h = mPhotoBounds.height(); - return buildGeometryMatrix(w, h, scaling, dx, dy, false); - } - public static Matrix buildPhotoMatrix(RectF photo, RectF crop, float rotation, float straighten, FLIP type) { Matrix m = new Matrix(); diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java index 35e8ad4b7..cd1ad5178 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java @@ -29,22 +29,25 @@ import android.util.AttributeSet; import android.util.Log; import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.CropExtras; public class ImageCrop extends ImageGeometry { private static final boolean LOGV = false; + + // Sides private static final int MOVE_LEFT = 1; private static final int MOVE_TOP = 2; private static final int MOVE_RIGHT = 4; private static final int MOVE_BOTTOM = 8; private static final int MOVE_BLOCK = 16; - //Corners + // Corners private static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT; private static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT; private static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT; private static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT; - private static final float MIN_CROP_WIDTH_HEIGHT = 0.1f; + private static int mMinSideSize = 100; private static int mTouchTolerance = 45; private boolean mFirstDraw = true; @@ -53,15 +56,33 @@ public class ImageCrop extends ImageGeometry { private boolean mFixAspectRatio = false; private float mLastRot = 0; - private final Paint borderPaint; + private BoundedRect mBounded = null; private int movingEdges; private final Drawable cropIndicator; private final int indicatorSize; private final int mBorderColor = Color.argb(128, 255, 255, 255); + // Offset between crop center and photo center + private float[] mOffset = { + 0, 0 + }; + private CropExtras mCropExtras = null; + private boolean mDoingCropIntentAction = false; + private static final String LOGTAG = "ImageCrop"; + private String mAspect = ""; + private int mAspectTextSize = 24; + + public void setAspectTextSize(int textSize) { + mAspectTextSize = textSize; + } + + public void setAspectString(String a) { + mAspect = a; + } + private static final Paint gPaint = new Paint(); public ImageCrop(Context context) { @@ -69,10 +90,6 @@ public class ImageCrop extends ImageGeometry { Resources resources = context.getResources(); cropIndicator = resources.getDrawable(R.drawable.camera_crop); indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); - borderPaint = new Paint(); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setColor(mBorderColor); - borderPaint.setStrokeWidth(2f); } public ImageCrop(Context context, AttributeSet attrs) { @@ -80,10 +97,6 @@ public class ImageCrop extends ImageGeometry { Resources resources = context.getResources(); cropIndicator = resources.getDrawable(R.drawable.camera_crop); indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); - borderPaint = new Paint(); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setColor(mBorderColor); - borderPaint.setStrokeWidth(2f); } @Override @@ -91,84 +104,46 @@ public class ImageCrop extends ImageGeometry { return getContext().getString(R.string.crop); } - private void swapAspect(){ + private void swapAspect() { + if (mDoingCropIntentAction) { + return; + } float temp = mAspectWidth; mAspectWidth = mAspectHeight; mAspectHeight = temp; } - public static void setTouchTolerance(int tolerance){ + /** + * Set tolerance for crop marker selection (in pixels) + */ + public static void setTouchTolerance(int tolerance) { mTouchTolerance = tolerance; } - private boolean switchCropBounds(int moving_corner, RectF dst) { - RectF crop = getCropBoundsDisplayed(); - float dx1 = 0; - float dy1 = 0; - float dx2 = 0; - float dy2 = 0; - if ((moving_corner & MOVE_RIGHT) != 0) { - dx1 = mCurrentX - crop.right; - } else if ((moving_corner & MOVE_LEFT) != 0) { - dx1 = mCurrentX - crop.left; - } - if ((moving_corner & MOVE_BOTTOM) != 0) { - dy1 = mCurrentY - crop.bottom; - } else if ((moving_corner & MOVE_TOP) != 0) { - dy1 = mCurrentY - crop.top; - } - RectF newCrop = null; - //Fix opposite corner in place and move sides - if (moving_corner == BOTTOM_RIGHT) { - newCrop = new RectF(crop.left, crop.top, crop.left + crop.height(), crop.top - + crop.width()); - } else if (moving_corner == BOTTOM_LEFT) { - newCrop = new RectF(crop.right - crop.height(), crop.top, crop.right, crop.top - + crop.width()); - } else if (moving_corner == TOP_LEFT) { - newCrop = new RectF(crop.right - crop.height(), crop.bottom - crop.width(), - crop.right, crop.bottom); - } else if (moving_corner == TOP_RIGHT) { - newCrop = new RectF(crop.left, crop.bottom - crop.width(), crop.left - + crop.height(), crop.bottom); - } - if ((moving_corner & MOVE_RIGHT) != 0) { - dx2 = mCurrentX - newCrop.right; - } else if ((moving_corner & MOVE_LEFT) != 0) { - dx2 = mCurrentX - newCrop.left; - } - if ((moving_corner & MOVE_BOTTOM) != 0) { - dy2 = mCurrentY - newCrop.bottom; - } else if ((moving_corner & MOVE_TOP) != 0) { - dy2 = mCurrentY - newCrop.top; - } - if (Math.sqrt(dx1*dx1 + dy1*dy1) > Math.sqrt(dx2*dx2 + dy2*dy2)){ - Matrix m = getCropBoundDisplayMatrix(); - Matrix m0 = new Matrix(); - if (!m.invert(m0)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT CROP MATRIX"); - return false; - } - if (!m0.mapRect(newCrop)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP RECTANGLE TO RECTANGLE"); - return false; - } - swapAspect(); - dst.set(newCrop); - return true; - } - return false; + /** + * Set minimum side length for crop box (in pixels) + */ + public static void setMinCropSize(int minHeightWidth) { + mMinSideSize = minHeightWidth; + } + + public void setExtras(CropExtras e) { + mCropExtras = e; + } + + public void setCropActionFlag(boolean f) { + mDoingCropIntentAction = f; } - public void apply(float w, float h){ + public void apply(float w, float h) { mFixAspectRatio = true; mAspectWidth = w; mAspectHeight = h; setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } @@ -183,202 +158,159 @@ public class ImageCrop extends ImageGeometry { mAspectHeight = h / scale; setLocalCropBounds(getUntranslatedStraightenCropBounds(photobounds, getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } public void applyClear() { mFixAspectRatio = false; + mAspectWidth = 1; + mAspectHeight = 1; setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } - private float getScaledMinWidthHeight() { - RectF disp = new RectF(0, 0, getWidth(), getHeight()); - float scaled = Math.min(disp.width(), disp.height()) * MIN_CROP_WIDTH_HEIGHT - / computeScale(getWidth(), getHeight()); - return scaled; - } - - protected Matrix getCropRotationMatrix(float rotation, RectF localImage) { - Matrix m = getLocalGeoFlipMatrix(localImage.width(), localImage.height()); - m.postRotate(rotation, localImage.centerX(), localImage.centerY()); - if (!m.rectStaysRect()) { - return null; - } - return m; - } - - protected Matrix getCropBoundDisplayMatrix(){ - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - m = new Matrix(); - } - float zoom = computeScale(getWidth(), getHeight()); - m.postTranslate(mXOffset, mYOffset); - m.postScale(zoom, zoom, mCenterX, mCenterY); - return m; - } - - protected RectF getCropBoundsDisplayed() { - RectF bounds = getLocalCropBounds(); - RectF crop = new RectF(bounds); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - m = new Matrix(); + public void clear() { + if (mCropExtras != null) { + int x = mCropExtras.getAspectX(); + int y = mCropExtras.getAspectY(); + if (mDoingCropIntentAction && x > 0 && y > 0) { + apply(x, y); + } } else { - m.mapRect(crop); + applyClear(); } - m = new Matrix(); - float zoom = computeScale(getWidth(), getHeight()); - m.setScale(zoom, zoom, mCenterX, mCenterY); - m.preTranslate(mXOffset, mYOffset); - m.mapRect(crop); - return crop; } - private RectF getRotatedCropBounds() { - RectF bounds = getLocalCropBounds(); - RectF crop = new RectF(bounds); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - return null; - } else { - m.mapRect(crop); - } - return crop; + private Matrix getPhotoBoundDisplayedMatrix() { + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); + Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop, + getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); + m.preScale(scale, scale); + return m; } - private RectF getUnrotatedCropBounds(RectF cropBounds) { - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO GET ROTATION MATRIX"); - return null; - } - Matrix m0 = new Matrix(); - if (!m.invert(m0)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX"); - return null; - } - RectF crop = new RectF(cropBounds); - if (!m0.mapRect(crop)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS"); - return null; - } - return crop; + private Matrix getCropBoundDisplayedMatrix() { + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); + Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop, + getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); + m1.preScale(scale, scale); + return m1; } - private RectF getRotatedStraightenBounds() { - RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), - getLocalStraighten()); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP STRAIGHTEN BOUNDS TO RECTANGLE"); - return null; - } else { - m.mapRect(straightenBounds); - } - return straightenBounds; + /** + * Takes the rotated corners of a rectangle and returns the angle; sets + * unrotated to be the unrotated version of the rectangle. + */ + 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(CropMath.trapToRect(unrotatedRect)); + return angle; } /** * Sets cropped bounds; modifies the bounds if it's smaller than the allowed * dimensions. */ - public void setCropBounds(RectF bounds) { - // Avoid cropping smaller than minimum width or height. + public boolean setCropBounds(RectF bounds) { RectF cbounds = new RectF(bounds); - float minWidthHeight = getScaledMinWidthHeight(); - float aw = mAspectWidth; - float ah = mAspectHeight; - if (mFixAspectRatio) { - minWidthHeight /= aw * ah; - int r = (int) (getLocalRotation() / 90); - if (r % 2 != 0) { - float temp = aw; - aw = ah; - ah = temp; - } - } - + Matrix mc = getCropBoundDisplayedMatrix(); + Matrix mcInv = new Matrix(); + mc.invert(mcInv); + mcInv.mapRect(cbounds); + // Avoid cropping smaller than minimum float newWidth = cbounds.width(); float newHeight = cbounds.height(); - if (mFixAspectRatio) { - if (newWidth < (minWidthHeight * aw) || newHeight < (minWidthHeight * ah)) { - newWidth = minWidthHeight * aw; - newHeight = minWidthHeight * ah; - } - } else { - if (newWidth < minWidthHeight) { - newWidth = minWidthHeight; - } - if (newHeight < minWidthHeight) { - newHeight = minWidthHeight; - } - } + float scale = getTransformState(null, null, null); + float minWidthHeight = mMinSideSize / scale; RectF pbounds = getLocalPhotoBounds(); - if (pbounds.width() < minWidthHeight) { - newWidth = pbounds.width(); + + // if photo is smaller than minimum, refuse to set crop bounds + if (pbounds.width() < minWidthHeight || pbounds.height() < minWidthHeight) { + return false; } - if (pbounds.height() < minWidthHeight) { - newHeight = pbounds.height(); + + // if incoming crop is smaller than minimum, refuse to set crop bounds + if (newWidth < minWidthHeight || newHeight < minWidthHeight) { + return false; } - cbounds.set(cbounds.left, cbounds.top, cbounds.left + newWidth, cbounds.top + newHeight); - RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), - getLocalStraighten()); - cbounds.intersect(straightenBounds); + float newX = bounds.centerX() - (getWidth() / 2f); + float newY = bounds.centerY() - (getHeight() / 2f); + mOffset[0] = newX; + mOffset[1] = newY; - if (mFixAspectRatio) { - fixAspectRatio(cbounds, aw, ah); - } setLocalCropBounds(cbounds); invalidate(); + return true; + } + + private BoundedRect getBoundedCrop(RectF crop) { + RectF photo = getLocalPhotoBounds(); + Matrix mp = getPhotoBoundDisplayedMatrix(); + float[] photoCorners = CropMath.getCornersFromRect(photo); + float[] photoCenter = { + photo.centerX(), photo.centerY() + }; + mp.mapPoints(photoCorners); + mp.mapPoints(photoCenter); + RectF scaledPhoto = new RectF(); + float angle = getUnrotated(photoCorners, photoCenter, scaledPhoto); + return new BoundedRect(angle, scaledPhoto, crop); } private void detectMovingEdges(float x, float y) { - RectF cropped = getCropBoundsDisplayed(); + Matrix m = getCropBoundDisplayedMatrix(); + RectF cropped = getLocalCropBounds(); + m.mapRect(cropped); + mBounded = getBoundedCrop(cropped); movingEdges = 0; - // Check left or right. float left = Math.abs(x - cropped.left); float right = Math.abs(x - cropped.right); - if ((left <= mTouchTolerance) && (left < right)) { + float top = Math.abs(y - cropped.top); + float bottom = Math.abs(y - cropped.bottom); + + // Check left or right. + if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) + && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) { movingEdges |= MOVE_LEFT; } - else if (right <= mTouchTolerance) { + else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) + && ((y - mTouchTolerance) <= cropped.bottom)) { movingEdges |= MOVE_RIGHT; } // Check top or bottom. - float top = Math.abs(y - cropped.top); - float bottom = Math.abs(y - cropped.bottom); - if ((top <= mTouchTolerance) & (top < bottom)) { + if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) + && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) { movingEdges |= MOVE_TOP; } - else if (bottom <= mTouchTolerance) { + else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) + && ((x - mTouchTolerance) <= cropped.right)) { movingEdges |= MOVE_BOTTOM; } - // Check inside block. - if (cropped.contains(x, y) && (movingEdges == 0)) { + if (movingEdges == 0) { movingEdges = MOVE_BLOCK; } if (mFixAspectRatio && (movingEdges != MOVE_BLOCK)) { @@ -387,7 +319,7 @@ public class ImageCrop extends ImageGeometry { invalidate(); } - private int fixEdgeToCorner(int moving_edges){ + private int fixEdgeToCorner(int moving_edges) { if (moving_edges == MOVE_LEFT) { moving_edges |= MOVE_TOP; } @@ -403,9 +335,9 @@ public class ImageCrop extends ImageGeometry { return moving_edges; } - private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy){ + private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) { RectF newCrop = null; - //Fix opposite corner in place and move sides + // 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); @@ -423,120 +355,90 @@ public class ImageCrop extends ImageGeometry { } private void moveEdges(float dX, float dY) { - RectF cropped = getRotatedCropBounds(); - float minWidthHeight = getScaledMinWidthHeight(); - float scale = computeScale(getWidth(), getHeight()); - float deltaX = dX / scale; - float deltaY = dY / scale; - int select = movingEdges; - if (mFixAspectRatio && (select != MOVE_BLOCK)) { - - // TODO: add in orientation change for fixed aspect - /*if (select == TOP_LEFT || select == TOP_RIGHT || - select == BOTTOM_LEFT || select == BOTTOM_RIGHT){ - RectF blank = new RectF(); - if(switchCropBounds(select, blank)){ - setCropBounds(blank); - return; - } - }*/ - if (select == MOVE_LEFT) { - select |= MOVE_TOP; - } - if (select == MOVE_TOP) { - select |= MOVE_LEFT; - } - if (select == MOVE_RIGHT) { - select |= MOVE_BOTTOM; - } - if (select == MOVE_BOTTOM) { - select |= MOVE_RIGHT; - } - } - - if (select == MOVE_BLOCK) { - RectF straight = getRotatedStraightenBounds(); - // Move the whole cropped bounds within the photo display bounds. - deltaX = (deltaX > 0) ? Math.min(straight.right - cropped.right, deltaX) - : Math.max(straight.left - cropped.left, deltaX); - deltaY = (deltaY > 0) ? Math.min(straight.bottom - cropped.bottom, deltaY) - : Math.max(straight.top - cropped.top, deltaY); - cropped.offset(deltaX, deltaY); + RectF crop = mBounded.getInner(); + + Matrix mc = getCropBoundDisplayedMatrix(); + + RectF photo = getLocalPhotoBounds(); + Matrix mp = getPhotoBoundDisplayedMatrix(); + float[] photoCorners = CropMath.getCornersFromRect(photo); + float[] photoCenter = { + photo.centerX(), photo.centerY() + }; + mp.mapPoints(photoCorners); + mp.mapPoints(photoCenter); + + float minWidthHeight = mMinSideSize; + + if (movingEdges == MOVE_BLOCK) { + mBounded.moveInner(-dX, -dY); + RectF r = mBounded.getInner(); + setCropBounds(r); + return; } else { float dx = 0; float dy = 0; - if ((select & MOVE_LEFT) != 0) { - dx = Math.min(cropped.left + deltaX, cropped.right - minWidthHeight) - cropped.left; + if ((movingEdges & MOVE_LEFT) != 0) { + dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left; } - if ((select & MOVE_TOP) != 0) { - dy = Math.min(cropped.top + deltaY, cropped.bottom - minWidthHeight) - cropped.top; + if ((movingEdges & MOVE_TOP) != 0) { + dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top; } - if ((select & MOVE_RIGHT) != 0) { - dx = Math.max(cropped.right + deltaX, cropped.left + minWidthHeight) - - cropped.right; + if ((movingEdges & MOVE_RIGHT) != 0) { + dx = Math.max(crop.right + dX, crop.left + minWidthHeight) + - crop.right; } - if ((select & MOVE_BOTTOM) != 0) { - dy = Math.max(cropped.bottom + deltaY, cropped.top + minWidthHeight) - - cropped.bottom; + if ((movingEdges & MOVE_BOTTOM) != 0) { + dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight) + - crop.bottom; } if (mFixAspectRatio) { - RectF crop = getCropBoundsDisplayed(); - float [] l1 = {crop.left, crop.bottom}; - float [] l2 = {crop.right, crop.top}; - if(movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT){ + 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[] b = { + l1[0] - l2[0], l1[1] - l2[1] + }; + float[] disp = { + dx, dy + }; float[] bUnit = GeometryMath.normalize(b); float sp = GeometryMath.scalarProjection(disp, bUnit); dx = sp * bUnit[0]; dy = sp * bUnit[1]; - RectF newCrop = fixedCornerResize(crop, select, dx * scale, dy * scale); - Matrix m = getCropBoundDisplayMatrix(); - Matrix m0 = new Matrix(); - if (!m.invert(m0)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT CROP MATRIX"); - return; - } - if (!m0.mapRect(newCrop)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP RECTANGLE TO RECTANGLE"); - return; - } + RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy); + + mBounded.fixedAspectResizeInner(newCrop); + newCrop = mBounded.getInner(); setCropBounds(newCrop); return; } else { - if ((select & MOVE_LEFT) != 0) { - cropped.left += dx; + if ((movingEdges & MOVE_LEFT) != 0) { + crop.left += dx; } - if ((select & MOVE_TOP) != 0) { - cropped.top += dy; + if ((movingEdges & MOVE_TOP) != 0) { + crop.top += dy; } - if ((select & MOVE_RIGHT) != 0) { - cropped.right += dx; + if ((movingEdges & MOVE_RIGHT) != 0) { + crop.right += dx; } - if ((select & MOVE_BOTTOM) != 0) { - cropped.bottom += dy; + if ((movingEdges & MOVE_BOTTOM) != 0) { + crop.bottom += dy; } } } - movingEdges = select; - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - Matrix m0 = new Matrix(); - if (!m.invert(m0)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX"); - } - if (!m0.mapRect(cropped)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS"); - } - setCropBounds(cropped); + mBounded.resizeInner(crop); + crop = mBounded.getInner(); + setCropBounds(crop); } private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) { @@ -549,7 +451,8 @@ public class ImageCrop extends ImageGeometry { @Override protected void setActionDown(float x, float y) { super.setActionDown(x, y); - detectMovingEdges(x, y); + detectMovingEdges(x + mOffset[0], y + mOffset[1]); + } @Override @@ -560,20 +463,54 @@ public class ImageCrop extends ImageGeometry { @Override protected void setActionMove(float x, float y) { - if (movingEdges != 0){ + + if (movingEdges != 0) { moveEdges(x - mCurrentX, y - mCurrentY); } super.setActionMove(x, y); + + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + setActionUp(); + cropSetup(); + invalidate(); } private void cropSetup() { + RectF crop = getLocalCropBounds(); + Matrix m = getCropBoundDisplayedMatrix(); + m.mapRect(crop); if (mFixAspectRatio) { - RectF cb = getRotatedCropBounds(); - fixAspectRatio(cb, mAspectWidth, mAspectHeight); - RectF cb0 = getUnrotatedCropBounds(cb); - setCropBounds(cb0); - } else { - setCropBounds(getLocalCropBounds()); + CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight); + } + float dCentX = getWidth() / 2; + float dCentY = getHeight() / 2; + + BoundedRect r = getBoundedCrop(crop); + crop = r.getInner(); + if (!setCropBounds(crop)) { + float h = mMinSideSize / 2; + float wScale = 1; + float hScale = mAspectHeight / mAspectWidth; + if (hScale < 1) { + wScale = mAspectWidth / mAspectHeight; + hScale = 1; + } + crop.set(dCentX - h * wScale, dCentY - h * hScale, dCentX + h * wScale, dCentY + h + * hScale); + if (mFixAspectRatio) { + CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight); + } + r.setInner(crop); + crop = r.getInner(); + if (!setCropBounds(crop)) { + crop.set(dCentX - h, dCentY - h, dCentX + h, dCentY + h); + r.setInner(crop); + crop = r.getInner(); + setCropBounds(crop); + } } } @@ -581,7 +518,7 @@ public class ImageCrop extends ImageGeometry { public void imageLoaded() { super.imageLoaded(); syncLocalToMasterGeometry(); - applyOriginal(); + clear(); invalidate(); } @@ -589,7 +526,7 @@ public class ImageCrop extends ImageGeometry { protected void gainedVisibility() { float rot = getLocalRotation(); // if has changed orientation via rotate - if( ((int) ((rot - mLastRot) / 90)) % 2 != 0 ){ + if (((int) ((rot - mLastRot) / 90)) % 2 != 0) { swapAspect(); } cropSetup(); @@ -599,7 +536,6 @@ public class ImageCrop extends ImageGeometry { @Override public void resetParameter() { super.resetParameter(); - cropSetup(); } @Override @@ -624,72 +560,109 @@ public class ImageCrop extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { - // TODO: move style to xml gPaint.setAntiAlias(true); - gPaint.setFilterBitmap(true); - gPaint.setDither(true); gPaint.setARGB(255, 255, 255, 255); if (mFirstDraw) { cropSetup(); mFirstDraw = false; } - float rotation = getLocalRotation(); - RectF crop = drawTransformed(canvas, image, gPaint); + RectF crop = drawTransformed(canvas, image, gPaint, mOffset); gPaint.setColor(mBorderColor); gPaint.setStrokeWidth(3); gPaint.setStyle(Paint.Style.STROKE); - drawRuleOfThird(canvas, crop, gPaint); - gPaint.setColor(mBorderColor); - gPaint.setStrokeWidth(3); - gPaint.setStyle(Paint.Style.STROKE); - drawStraighten(canvas, gPaint); - - int decoded_moving = decoder(movingEdges, rotation); - canvas.save(); - canvas.rotate(rotation, mCenterX, mCenterY); - RectF scaledCrop = unrotatedCropBounds(); - boolean notMoving = decoded_moving == 0; - if (((decoded_moving & MOVE_TOP) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top); - } - if (((decoded_moving & MOVE_BOTTOM) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom); - } - if (((decoded_moving & MOVE_LEFT) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY()); + boolean doThirds = true; + + if (mFixAspectRatio) { + float spotlightX = 0; + float spotlightY = 0; + if (mCropExtras != null) { + spotlightX = mCropExtras.getSpotlightX(); + spotlightY = mCropExtras.getSpotlightY(); + } + if (mDoingCropIntentAction && spotlightX > 0 && spotlightY > 0) { + float sx = crop.width() * spotlightX; + float sy = crop.height() * spotlightY; + float cx = crop.centerX(); + float cy = crop.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.drawRect(r1, gPaint); + canvas.drawRect(r2, gPaint); + doThirds = false; + } else { + float w = crop.width(); + float h = crop.height(); + float diag = (float) Math.sqrt(w * w + h * h); + + float dash_len = 20; + int num_intervals = (int) (diag / dash_len); + float[] tl = { + crop.left, crop.top + }; + float centX = tl[0] + w / 2; + float centY = tl[1] + h / 2 + 5; + float[] br = { + crop.right, crop.bottom + }; + float[] vec = GeometryMath.getUnitVectorFromPoints(tl, br); + + float[] counter = tl; + for (int x = 0; x < num_intervals; x++) { + float tempX = counter[0] + vec[0] * dash_len; + float tempY = counter[1] + vec[1] * dash_len; + if ((x % 2) == 0 && Math.abs(x - num_intervals / 2) > 2) { + canvas.drawLine(counter[0], counter[1], tempX, tempY, gPaint); + } + counter[0] = tempX; + counter[1] = tempY; + } + + gPaint.setTextAlign(Paint.Align.CENTER); + gPaint.setTextSize(mAspectTextSize); + canvas.drawText(mAspect, centX, centY, gPaint); + } } - if (((decoded_moving & MOVE_RIGHT) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY()); + + if (doThirds) { + drawRuleOfThird(canvas, crop, gPaint); + } - canvas.restore(); - } - - 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; - } - - protected int decoder(int movingEdges, float rotation) { - int rot = constrainedRotation(rotation); - switch (rot) { - case 90: - return bitCycleLeft(movingEdges, 3, 4); - case 180: - return bitCycleLeft(movingEdges, 2, 4); - case 270: - return bitCycleLeft(movingEdges, 1, 4); - default: - return movingEdges; + + RectF scaledCrop = crop; + boolean notMoving = (movingEdges == 0); + if (mFixAspectRatio) { + if ((movingEdges == TOP_LEFT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.top); + } + if ((movingEdges == TOP_RIGHT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.top); + } + if ((movingEdges == BOTTOM_LEFT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.bottom); + } + if ((movingEdges == BOTTOM_RIGHT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.bottom); + } + } else { + if (((movingEdges & MOVE_TOP) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top); + } + if (((movingEdges & MOVE_BOTTOM) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom); + } + if (((movingEdges & MOVE_LEFT) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY()); + } + if (((movingEdges & MOVE_RIGHT) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY()); + } } } + } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java index 5d6fe502f..6bfba1b2c 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java @@ -136,8 +136,6 @@ public class ImageFlip extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { gPaint.setAntiAlias(true); - gPaint.setFilterBitmap(true); - gPaint.setDither(true); gPaint.setARGB(255, 255, 255, 255); drawTransformedCropped(canvas, image, gPaint); } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java index 42dd139bc..c8ae444da 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java @@ -33,7 +33,7 @@ import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP; import com.android.gallery3d.filtershow.presets.ImagePreset; public abstract class ImageGeometry extends ImageSlave { - private boolean mVisibilityGained = false; + protected boolean mVisibilityGained = false; private boolean mHasDrawn = false; protected static final float MAX_STRAIGHTEN_ANGLE = 45; @@ -191,8 +191,8 @@ public abstract class ImageGeometry extends ImageSlave { return r * 90; } - protected Matrix getLocalGeoFlipMatrix(float width, float height) { - return mLocalGeometry.getFlipMatrix(width, height); + protected boolean isHeightWidthSwapped() { + return ((int) (getLocalRotation() / 90)) % 2 != 0; } protected void setLocalStraighten(float r) { @@ -217,32 +217,6 @@ public abstract class ImageGeometry extends ImageSlave { return getLocalRotation() + getLocalStraighten(); } - protected static float[] getCornersFromRect(RectF r) { - // Order is: - // 0------->1 - // ^ | - // | v - // 3<-------2 - float[] corners = { - r.left, r.top, // 0 - r.right, r.top, // 1 - r.right, r.bottom,// 2 - r.left, r.bottom // 3 - }; - return corners; - } - - // 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. - protected static void getEdgePoints(RectF imageBound, float[] array) { - if (array.length < 2) - return; - for (int x = 0; x < array.length; x += 2) { - array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right); - array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom); - } - } - protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) { Path crop = new Path(); crop.moveTo(points[0], points[1]); @@ -254,16 +228,6 @@ public abstract class ImageGeometry extends ImageSlave { return crop; } - protected 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); - - } - protected static float getNewHeightForWidthAspect(float width, float w, float h) { return width * h / w; } @@ -290,11 +254,11 @@ public abstract class ImageGeometry extends ImageSlave { } protected void gainedVisibility() { - // TODO: Override this stub. + // Override this stub. } protected void lostVisibility() { - // TODO: Override this stub. + // Override this stub. } @Override @@ -327,7 +291,7 @@ public abstract class ImageGeometry extends ImageSlave { } protected int getLocalValue() { - return 0; // TODO: Override this + return 0; // Override this } protected void setActionDown(float x, float y) { @@ -402,110 +366,19 @@ public abstract class ImageGeometry extends ImageSlave { return new RectF(left, top, right, bottom); } - protected Matrix getGeoMatrix(RectF r, boolean onlyRotate) { - RectF pbounds = getLocalPhotoBounds(); - float scale = GeometryMath - .scale(pbounds.width(), pbounds.height(), getWidth(), getHeight()); - if (((int) (getLocalRotation() / 90)) % 2 != 0) { - scale = GeometryMath.scale(pbounds.width(), pbounds.height(), getHeight(), getWidth()); - } - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - float w = r.left * 2 + r.width(); - float h = r.top * 2 + r.height(); - return mLocalGeometry.buildGeometryMatrix(w, h, scale, xoff, yoff, onlyRotate); - } - - protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint, Matrix m) { - canvas.save(); - canvas.drawBitmap(bitmap, m, paint); - canvas.restore(); - } - - protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint) { - float scale = computeScale(getWidth(), getHeight()); - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - Matrix m = mLocalGeometry.buildGeometryUIMatrix(scale, xoff, yoff); - drawImageBitmap(canvas, bitmap, paint, m); - } - protected RectF straightenBounds() { RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten()); - Matrix m = getGeoMatrix(bounds, true); - m.mapRect(bounds); - return bounds; - } - - protected void drawStraighten(Canvas canvas, Paint paint) { - RectF bounds = straightenBounds(); - canvas.save(); - canvas.drawRect(bounds, paint); - canvas.restore(); - } - - protected RectF unrotatedCropBounds() { - RectF bounds = getLocalCropBounds(); - RectF pbounds = getLocalPhotoBounds(); float scale = computeScale(getWidth(), getHeight()); - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - Matrix m = mLocalGeometry.buildGeometryMatrix(pbounds.width(), pbounds.height(), scale, - xoff, yoff, 0); - m.mapRect(bounds); - return bounds; - } - - protected RectF cropBounds() { - RectF bounds = getLocalCropBounds(); - Matrix m = getGeoMatrix(getLocalPhotoBounds(), true); - m.mapRect(bounds); + bounds = GeometryMath.scaleRect(bounds, scale); + float dx = (getWidth() / 2) - bounds.centerX(); + float dy = (getHeight() / 2) - bounds.centerY(); + bounds.offset(dx, dy); return bounds; } - // Fails for non-90 degree - protected void drawCrop(Canvas canvas, Paint paint) { - RectF bounds = cropBounds(); - canvas.save(); - canvas.drawRect(bounds, paint); - canvas.restore(); - } - - protected void drawCropSafe(Canvas canvas, Paint paint) { - Matrix m = getGeoMatrix(getLocalPhotoBounds(), true); - RectF crop = getLocalCropBounds(); - if (!m.rectStaysRect()) { - float[] corners = getCornersFromRect(crop); - m.mapPoints(corners); - drawClosedPath(canvas, paint, corners); - } else { - m.mapRect(crop); - Path path = new Path(); - path.addRect(crop, Path.Direction.CCW); - canvas.drawPath(path, paint); - } - } - - protected void drawTransformedBitmap(Canvas canvas, Bitmap bitmap, Paint paint, boolean clip) { - paint.setARGB(255, 0, 0, 0); - drawImageBitmap(canvas, bitmap, paint); - paint.setColor(Color.WHITE); - paint.setStyle(Style.STROKE); - paint.setStrokeWidth(2); - drawCropSafe(canvas, paint); - paint.setColor(getDefaultBackgroundColor()); - paint.setStyle(Paint.Style.FILL); - drawShadows(canvas, paint, unrotatedCropBounds()); - } - - protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) { - RectF display = new RectF(0, 0, getWidth(), getHeight()); - drawShadows(canvas, p, innerBounds, display, getLocalRotation(), getWidth() / 2, - getHeight() / 2); - } - - protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds, + protected static void drawRotatedShadows(Canvas canvas, Paint p, RectF innerBounds, + RectF outerBounds, float rotation, float centerX, float centerY) { canvas.save(); canvas.rotate(rotation, centerX, centerY); @@ -527,6 +400,15 @@ public abstract class ImageGeometry extends ImageSlave { canvas.restore(); } + protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) { + float w = getWidth(); + float h = getHeight(); + canvas.drawRect(0f, 0f, w, innerBounds.top, p); + canvas.drawRect(0f, innerBounds.top, innerBounds.left, innerBounds.bottom, p); + canvas.drawRect(innerBounds.right, innerBounds.top, w, innerBounds.bottom, p); + canvas.drawRect(0f, innerBounds.bottom, w, h, p); + } + @Override public void onDraw(Canvas canvas) { if (getDirtyGeometryFlag()) { @@ -547,21 +429,38 @@ public abstract class ImageGeometry extends ImageSlave { // TODO: Override this stub. } - protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p) { - p.setARGB(255, 0, 0, 0); + /** + * Sets up inputs for buildCenteredPhotoMatrix and buildWanderingCropMatrix + * and returns the scale factor. + */ + protected float getTransformState(RectF photo, RectF crop, float[] displayCenter) { RectF photoBounds = getLocalPhotoBounds(); RectF cropBounds = getLocalCropBounds(); float scale = computeScale(getWidth(), getHeight()); // checks if local rotation is an odd multiple of 90. - if (((int) (getLocalRotation() / 90)) % 2 != 0) { + if (isHeightWidthSwapped()) { scale = computeScale(getHeight(), getWidth()); } // put in screen coordinates - RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale); - RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale); - float[] displayCenter = { - getWidth() / 2f, getHeight() / 2f - }; + if (crop != null) { + crop.set(GeometryMath.scaleRect(cropBounds, scale)); + } + if (photo != null) { + photo.set(GeometryMath.scaleRect(photoBounds, scale)); + } + if (displayCenter != null && displayCenter.length >= 2) { + displayCenter[0] = getWidth() / 2f; + displayCenter[1] = getHeight() / 2f; + } + return scale; + } + + protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p, float[] offset) { + p.setARGB(255, 0, 0, 0); + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop, getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); @@ -569,9 +468,11 @@ public abstract class ImageGeometry extends ImageSlave { getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); m1.mapRect(scaledCrop); Path path = new Path(); + scaledCrop.offset(-offset[0], -offset[1]); path.addRect(scaledCrop, Path.Direction.CCW); m.preScale(scale, scale); + m.postTranslate(-offset[0], -offset[1]); canvas.save(); canvas.drawBitmap(photo, m, p); canvas.restore(); @@ -580,6 +481,11 @@ public abstract class ImageGeometry extends ImageSlave { p.setStyle(Style.STROKE); p.setStrokeWidth(2); canvas.drawPath(path, p); + + p.setColor(getDefaultBackgroundColor()); + p.setAlpha(128); + p.setStyle(Paint.Style.FILL); + drawShadows(canvas, p, scaledCrop); return scaledCrop; } @@ -590,7 +496,7 @@ public abstract class ImageGeometry extends ImageSlave { float imageHeight = cropBounds.height(); float scale = GeometryMath.scale(imageWidth, imageHeight, getWidth(), getHeight()); // checks if local rotation is an odd multiple of 90. - if (((int) (getLocalRotation() / 90)) % 2 != 0) { + if (isHeightWidthSwapped()) { scale = GeometryMath.scale(imageWidth, imageHeight, getHeight(), getWidth()); } // put in screen coordinates @@ -618,6 +524,8 @@ public abstract class ImageGeometry extends ImageSlave { p.setStyle(Paint.Style.FILL); scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1] - scaledCrop.centerY()); - drawShadows(canvas, p, scaledCrop); + RectF display = new RectF(0, 0, getWidth(), getHeight()); + drawRotatedShadows(canvas, p, scaledCrop, display, getLocalRotation(), getWidth() / 2, + getHeight() / 2); } } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRedEyes.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRedEyes.java new file mode 100644 index 000000000..5119dff3c --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRedEyes.java @@ -0,0 +1,148 @@ + +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.util.AttributeSet; +import android.view.MotionEvent; + +import com.android.gallery3d.filtershow.filters.ImageFilterRedEye; +import com.android.gallery3d.filtershow.filters.RedEyeCandidate; + +public class ImageRedEyes extends ImageSlave { + + private static final String LOGTAG = "ImageRedEyes"; + private RectF mCurrentRect = null; + private static float mTouchPadding = 80; + + public static void setTouchPadding(float padding) { + mTouchPadding = padding; + } + + public ImageRedEyes(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ImageRedEyes(Context context) { + super(context); + } + + @Override + public void resetParameter() { + ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter(); + if (filter != null) { + filter.clear(); + } + mCurrentRect = null; + invalidate(); + } + + @Override + public void updateImage() { + super.updateImage(); + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + float ex = event.getX(); + float ey = event.getY(); + + ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter(); + + // 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 + GeometryMetadata geo = getImagePreset().mGeoData; + Matrix originalToScreen = geo.getOriginalToScreen(true, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), + getWidth(), getHeight()); + Matrix originalNoRotateToScreen = geo.getOriginalToScreen(false, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), + getWidth(), getHeight()); + + 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); + filter.addRect(r, r2); + this.resetImageCaches(this); + } + mCurrentRect = null; + } + 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); + } + + GeometryMetadata geo = getImagePreset().mGeoData; + Matrix originalToScreen = geo.getOriginalToScreen(false, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), getWidth(), getHeight()); + Matrix originalRotateToScreen = geo.getOriginalToScreen(true, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), getWidth(), getHeight()); + + ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter(); + for (RedEyeCandidate candidate : filter.getCandidates()) { + 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 index a4131ff80..30cc9e2f3 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java @@ -81,8 +81,6 @@ public class ImageRotate extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { gPaint.setAntiAlias(true); - gPaint.setFilterBitmap(true); - gPaint.setDither(true); gPaint.setARGB(255, 255, 255, 255); drawTransformedCropped(canvas, image, gPaint); } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java index 358d5b795..4c74b16ca 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java @@ -23,6 +23,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; +import android.net.Uri; import android.os.Handler; import android.util.AttributeSet; import android.view.GestureDetector; @@ -74,16 +75,12 @@ public class ImageShow extends View implements OnGestureListener, private Bitmap mFiltersOnlyImage = null; private Bitmap mFilteredImage = null; - private final boolean USE_SLIDER_GESTURE = false; // set to true to have - // slider gesture - protected SliderController mSliderController = new SliderController(); - private GestureDetector mGestureDetector = null; private HistoryAdapter mHistoryAdapter = null; private ImageStateAdapter mImageStateAdapter = null; - private Rect mImageBounds = new Rect(); + protected Rect mImageBounds = new Rect(); private boolean mTouchShowOriginal = false; private long mTouchShowOriginalDate = 0; @@ -152,15 +149,15 @@ public class ImageShow extends View implements OnGestureListener, private final Handler mHandler = new Handler(); public void select() { + if (mSeekBar != null) { + mSeekBar.setOnSeekBarChangeListener(this); + } if (getCurrentFilter() != null) { int parameter = getCurrentFilter().getParameter(); int maxp = getCurrentFilter().getMaxParameter(); int minp = getCurrentFilter().getMinParameter(); updateSeekBar(parameter, minp, maxp); } - if (mSeekBar != null) { - mSeekBar.setOnSeekBarChangeListener(this); - } } private int parameterToUI(int parameter, int minp, int maxp, int uimax) { @@ -178,9 +175,6 @@ public class ImageShow extends View implements OnGestureListener, int seekMax = mSeekBar.getMax(); int progress = parameterToUI(parameter, minp, maxp, seekMax); mSeekBar.setProgress(progress); - if (getPanelController() != null) { - getPanelController().onNewValue(parameter); - } } public void unselect() { @@ -197,10 +191,8 @@ public class ImageShow extends View implements OnGestureListener, public void resetParameter() { ImageFilter currentFilter = getCurrentFilter(); if (currentFilter != null) { - onNewValue(currentFilter.getDefaultParameter()); - } - if (USE_SLIDER_GESTURE) { - mSliderController.reset(); + updateSeekBar(currentFilter.getDefaultParameter(), + getCurrentFilter().getMinParameter(), getCurrentFilter().getMaxParameter()); } } @@ -214,8 +206,8 @@ public class ImageShow extends View implements OnGestureListener, @Override public void onNewValue(int parameter) { - int maxp = 100; - int minp = -100; + int maxp = ImageFilter.DEFAULT_MAX_PARAMETER; + int minp = ImageFilter.DEFAULT_MIN_PARAMETER; if (getCurrentFilter() != null) { getCurrentFilter().setParameter(parameter); maxp = getCurrentFilter().getMaxParameter(); @@ -228,7 +220,6 @@ public class ImageShow extends View implements OnGestureListener, if (getPanelController() != null) { getPanelController().onNewValue(parameter); } - updateSeekBar(parameter, minp, maxp); invalidate(); mActivity.enableSave(hasModifications()); } @@ -246,9 +237,6 @@ public class ImageShow extends View implements OnGestureListener, public ImageShow(Context context, AttributeSet attrs) { super(context, attrs); - if (USE_SLIDER_GESTURE) { - mSliderController.setListener(this); - } mHistoryAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row, R.id.rowTextView); mImageStateAdapter = new ImageStateAdapter(context, @@ -259,9 +247,6 @@ public class ImageShow extends View implements OnGestureListener, public ImageShow(Context context) { super(context); - if (USE_SLIDER_GESTURE) { - mSliderController.setListener(this); - } mHistoryAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row, R.id.rowTextView); setupGestureDetector(context); @@ -277,10 +262,6 @@ public class ImageShow extends View implements OnGestureListener, int parentWidth = MeasureSpec.getSize(widthMeasureSpec); int parentHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(parentWidth, parentHeight); - if (USE_SLIDER_GESTURE) { - mSliderController.setWidth(parentWidth); - mSliderController.setHeight(parentHeight); - } } public void setSeekBar(SeekBar seekBar) { @@ -324,6 +305,10 @@ public class ImageShow extends View implements OnGestureListener, return dst; } + public Rect getImageCropBounds() { + return GeometryMath.roundNearest(getImagePreset().mGeoData.getPreviewCropBounds()); + } + public Rect getDisplayedImageBounds() { return mImageBounds; } @@ -378,12 +363,6 @@ public class ImageShow extends View implements OnGestureListener, 1.5f * mTextPadding, mPaint); } - if (showControls()) { - if (USE_SLIDER_GESTURE) { - mSliderController.onDraw(canvas); - } - } - drawToast(canvas); } @@ -646,13 +625,13 @@ public class ImageShow extends View implements OnGestureListener, } public void updateImage() { + invalidate(); if (!updateGeometryFlags()) { return; } Bitmap bitmap = mImageLoader.getOriginalBitmapLarge(); if (bitmap != null) { imageSizeChanged(bitmap); - invalidate(); } } @@ -669,12 +648,17 @@ public class ImageShow extends View implements OnGestureListener, mImageLoader.saveImage(getImagePreset(), filterShowActivity, file); } + public void saveToUri(Bitmap f, Uri u, String m, FilterShowActivity filterShowActivity) { + mImageLoader.saveToUri(f, u, m, filterShowActivity); + } + + public void returnFilteredResult(FilterShowActivity filterShowActivity) { + mImageLoader.returnFilteredResult(getImagePreset(), filterShowActivity); + } + @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); - if (USE_SLIDER_GESTURE) { - mSliderController.onTouchEvent(event); - } if (mGestureDetector != null) { mGestureDetector.onTouchEvent(event); } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java index 6a79e18a1..2a3ee2856 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java @@ -47,6 +47,13 @@ public class ImageSmallFilter extends ImageShow implements View.OnClickListener protected final int mTextColor = Color.WHITE; private ImageSmallFilter mNullFilter; + private Bitmap mOverlayBitmap = null; + private final int mOverlayTint = Color.argb(100, 0, 0, 0); + + public void setOverlayBitmap(Bitmap bitmap){ + mOverlayBitmap = bitmap; + } + public static void setMargin(int value) { mMargin = value; } @@ -77,6 +84,10 @@ public class ImageSmallFilter extends ImageShow implements View.OnClickListener mImagePreset.add(mImageFilter); } + public ImageFilter getImageFilter() { + return mImageFilter; + } + @Override public void setSelected(boolean value) { if (mIsSelected != value) { @@ -188,6 +199,13 @@ public class ImageSmallFilter extends ImageShow implements View.OnClickListener mPaint.setTextSize(mTextSize); mPaint.setColor(mTextColor); canvas.drawText(mImageFilter.getName(), x, y - mTextMargin, mPaint); + if (mOverlayBitmap != null) { + mPaint.setColor(mOverlayTint); + canvas.drawRect(0, mMargin, getWidth(), getWidth() + mMargin, mPaint); + Rect d = new Rect(0, mMargin, getWidth() - mMargin, getWidth()); + mPaint.setColor(Color.BLACK); + drawImage(canvas, mOverlayBitmap, d); + } } public void drawImage(Canvas canvas, Bitmap image, Rect destination) { diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java index 57a22aab3..7a539da8f 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java @@ -20,6 +20,7 @@ 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.Path; import android.graphics.RectF; @@ -105,10 +106,10 @@ public class ImageStraighten extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { - drawTransformed(canvas, image, gPaint); + float [] o = {0, 0}; + RectF bounds = drawTransformed(canvas, image, gPaint, o); // Draw the grid - RectF bounds = straightenBounds(); Path path = new Path(); path.addRect(bounds, Path.Direction.CCW); gPaint.setARGB(255, 255, 255, 255); diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java b/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java deleted file mode 100644 index a332fa72a..000000000 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.Rect; - -/** - * TODO: Insert description here. (generated by hoford) - */ -public class ImageWithIcon extends ImageSmallFilter { - /** - * @param context - */ - public ImageWithIcon(Context context) { - super(context); - // TODO(hoford): Auto-generated constructor stub - } - - private Bitmap bitmap; - - public void setIcon(Bitmap bitmap){ - this.bitmap = bitmap; - } - - @Override - public void onDraw(Canvas canvas) { - super.onDraw(canvas); - if (bitmap != null) { - Rect d = new Rect(0, mMargin, getWidth() - mMargin, getWidth()); - drawImage(canvas, bitmap, d); - } - } -} diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java b/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java index c7586fe9b..b66da0128 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java @@ -128,10 +128,6 @@ public class ImageZoom extends ImageSlave { drawImage(canvas, filteredImage); canvas.restore(); - if (showControls()) { - mSliderController.onDraw(canvas); - } - drawToast(canvas); } diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java index 8943f30de..2522c8966 100644 --- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java +++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java @@ -181,7 +181,7 @@ public class ImagePreset { return false; } - if (mImageBorder != preset.mImageBorder) { + if (mDoApplyGeometry && mImageBorder != preset.mImageBorder) { return false; } @@ -292,7 +292,7 @@ public class ImagePreset { } } - if (mImageBorder != null) { + if (mImageBorder != null && mDoApplyGeometry) { bitmap = mImageBorder.apply(bitmap, mScaleFactor, mIsHighQuality); } diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java deleted file mode 100644 index bfa6dacc7..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.presets; - -import com.android.gallery3d.filtershow.filters.ImageFilterBW; - -public class ImagePresetBW extends ImagePreset { - - @Override - public String name() { - return "B&W"; - } - - @Override - public void setup() { - mFilters.add(new ImageFilterBW()); - } - -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java deleted file mode 100644 index 5d56aa1cd..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.presets; - -import com.android.gallery3d.filtershow.filters.ImageFilterBWBlue; - -public class ImagePresetBWBlue extends ImagePreset { - - @Override - public String name() { - return "B&W - Blue"; - } - - @Override - public void setup() { - mFilters.add(new ImageFilterBWBlue()); - } - -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java deleted file mode 100644 index d1b4e5d78..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.presets; - -import com.android.gallery3d.filtershow.filters.ImageFilterBWGreen; - -public class ImagePresetBWGreen extends ImagePreset { - - @Override - public String name() { - return "B&W - Green"; - } - - @Override - public void setup() { - mFilters.add(new ImageFilterBWGreen()); - } - -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java deleted file mode 100644 index 9653bed57..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.presets; - -import com.android.gallery3d.filtershow.filters.ImageFilterBWRed; - -public class ImagePresetBWRed extends ImagePreset { - - @Override - public String name() { - return "B&W - Red"; - } - - @Override - public void setup() { - mFilters.add(new ImageFilterBWRed()); - } - -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetFX.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetFX.java deleted file mode 100644 index 95edc5d15..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetFX.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.presets; - -import android.graphics.Bitmap; - -import com.android.gallery3d.filtershow.filters.ImageFilterFx; - -public class ImagePresetFX extends ImagePreset { - String name; - Bitmap fxBitmap; - - @Override - public String name() { - return name; - } - - public ImagePresetFX(Bitmap bitmap, String name) { - fxBitmap = bitmap; - this.name = name; - setup(); - } - - @Override - public void setup() { - if (fxBitmap != null) { - mFilters.add(new ImageFilterFx(fxBitmap,name)); - } - } - -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java deleted file mode 100644 index 5e1db8336..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.presets; - -import android.graphics.Color; - -import com.android.gallery3d.filtershow.filters.ImageFilterGradient; - -public class ImagePresetOld extends ImagePreset { - - @Override - public String name() { - return "Old"; - } - - @Override - public void setup() { - ImageFilterGradient filter = new ImageFilterGradient(); - filter.addColor(Color.BLACK, 0.0f); - filter.addColor(Color.argb(255, 228, 231, 193), 1.0f); - mFilters.add(filter); - } - -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java deleted file mode 100644 index ddfca7508..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.presets; - -import com.android.gallery3d.filtershow.filters.ImageFilterSaturated; - -public class ImagePresetSaturated extends ImagePreset { - - @Override - public String name() { - return "Saturated"; - } - - @Override - public void setup() { - ImageFilterSaturated filter = new ImageFilterSaturated(); - filter.setParameter(50); - mFilters.add(filter); - } - -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java deleted file mode 100644 index 7957b5e7c..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.presets; - -import android.graphics.Color; - -import com.android.gallery3d.filtershow.filters.ImageFilterGradient; - -public class ImagePresetXProcessing extends ImagePreset { - - @Override - public String name() { - return "X-Process"; - } - - @Override - public void setup() { - ImageFilterGradient filter = new ImageFilterGradient(); - filter.addColor(Color.BLACK, 0.0f); - filter.addColor(Color.argb(255, 29, 82, 83), 0.4f); - filter.addColor(Color.argb(255, 211, 217, 186), 1.0f); - mFilters.add(filter); - } - -} diff --git a/src/com/android/gallery3d/filtershow/tools/BitmapTask.java b/src/com/android/gallery3d/filtershow/tools/BitmapTask.java new file mode 100644 index 000000000..62801c1f2 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/tools/BitmapTask.java @@ -0,0 +1,68 @@ +/* + * 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.tools; + +import android.graphics.Bitmap; +import android.os.AsyncTask; + +/** + * Asynchronous task filtering or doign I/O with bitmaps. + */ +public class BitmapTask <T> extends AsyncTask<T, Void, Bitmap> { + + private Callbacks<T> mCallbacks; + private static final String LOGTAG = "BitmapTask"; + + public BitmapTask(Callbacks<T> callbacks) { + mCallbacks = callbacks; + } + + @Override + protected Bitmap doInBackground(T... params) { + if (params == null || mCallbacks == null) { + return null; + } + return mCallbacks.onExecute(params[0]); + } + + @Override + protected void onPostExecute(Bitmap result) { + if (mCallbacks == null) { + return; + } + mCallbacks.onComplete(result); + } + + @Override + protected void onCancelled() { + if (mCallbacks == null) { + return; + } + mCallbacks.onCancel(); + } + + /** + * Callbacks for the asynchronous task. + */ + public interface Callbacks<P> { + void onComplete(Bitmap result); + + void onCancel(); + + Bitmap onExecute(P param); + } +} diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java index 9c55623d1..30659e677 100644 --- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java +++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java @@ -52,9 +52,6 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { private static final String LOGTAG = "SaveCopyTask"; - private static final int DEFAULT_COMPRESS_QUALITY = 95; - private static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; - /** * Saves the bitmap in the final destination */ @@ -62,7 +59,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { OutputStream os = null; try { os = new FileOutputStream(destination); - bitmap.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, os); + bitmap.compress(CompressFormat.JPEG, ImageLoader.DEFAULT_COMPRESS_QUALITY, os); } catch (FileNotFoundException e) { Log.v(LOGTAG,"Error in writing "+destination.getAbsolutePath()); } finally { @@ -123,7 +120,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { File saveDirectory = getSaveDirectory(context, sourceUri); if ((saveDirectory == null) || !saveDirectory.canWrite()) { saveDirectory = new File(Environment.getExternalStorageDirectory(), - DEFAULT_SAVE_DIRECTORY); + ImageLoader.DEFAULT_SAVE_DIRECTORY); } // Create the directory if it doesn't exist if (!saveDirectory.exists()) saveDirectory.mkdirs(); @@ -137,19 +134,6 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { return new File(saveDirectory, filename + ".JPG"); } - private Bitmap loadMutableBitmap() throws FileNotFoundException { - BitmapFactory.Options options = new BitmapFactory.Options(); - // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't - // exist) - options.inMutable = true; - - InputStream is = context.getContentResolver().openInputStream(sourceUri); - Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); - int orientation = ImageLoader.getOrientation(context, sourceUri); - bitmap = ImageLoader.rotateToPortrait(bitmap, orientation); - return bitmap; - } - private static final String[] COPY_EXIF_ATTRIBUTES = new String[] { ExifInterface.TAG_APERTURE, ExifInterface.TAG_DATETIME, @@ -228,7 +212,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { ImagePreset preset = params[0]; try { - Bitmap bitmap = preset.apply(loadMutableBitmap()); + Bitmap bitmap = preset.apply(ImageLoader.loadMutableBitmap(context, sourceUri)); Object xmp = null; InputStream is = null; diff --git a/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java b/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java index 17453d070..c717b6e61 100644 --- a/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java +++ b/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java @@ -20,6 +20,7 @@ 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; @@ -32,6 +33,10 @@ public class FramedTextButton extends ImageButton { 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; + private Context mContext = null; public static void setTextSize(int value) { @@ -42,6 +47,14 @@ public class FramedTextButton extends ImageButton { 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(); @@ -84,11 +97,25 @@ public class FramedTextButton extends ImageButton { @Override public void onDraw(Canvas canvas) { - gPaint.setARGB(255, 255, 255, 255); + gPaint.setARGB(96, 255, 255, 255); gPaint.setStrokeWidth(2); gPaint.setStyle(Paint.Style.STROKE); - canvas.drawRect(mTextPadding, mTextPadding, getWidth() - mTextPadding, - getHeight() - mTextPadding, gPaint); + 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); @@ -96,8 +123,8 @@ public class FramedTextButton extends ImageButton { float textWidth = gPaint.measureText(mText); Rect bounds = new Rect(); gPaint.getTextBounds(mText, 0, mText.length(), bounds); - int x = (int) ((getWidth() - textWidth) / 2); - int y = (getHeight() + bounds.height()) / 2; + 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/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java index c4ca900e1..7b04133ce 100644 --- a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java +++ b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java @@ -43,6 +43,7 @@ public class ImageCurves extends ImageSlave { 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]; @@ -186,6 +187,7 @@ public class ImageCurves extends ImageSlave { if (e.getActionMasked() == MotionEvent.ACTION_UP) { mCurrentControlPoint = null; + mCurrentPick = -1; updateCachedImage(); mDidAddPoint = false; if (mDidDelete) { @@ -205,8 +207,9 @@ public class ImageCurves extends ImageSlave { } Spline spline = getSpline(mCurrentCurveIndex); - int pick = pickControlPoint(posX, posY); + int pick = mCurrentPick; if (mCurrentControlPoint == null) { + pick = pickControlPoint(posX, posY); if (pick == -1) { mCurrentControlPoint = new ControlPoint(posX, posY); pick = spline.addPoint(mCurrentControlPoint); @@ -214,10 +217,10 @@ public class ImageCurves extends ImageSlave { } else { mCurrentControlPoint = spline.getPoint(pick); } + mCurrentPick = pick; } if (spline.isPointContained(posX, pick)) { - spline.didMovePoint(mCurrentControlPoint); spline.movePoint(pick, posX, posY); } else if (pick != -1 && spline.getNbPoints() > 2) { spline.deletePoint(pick); @@ -275,8 +278,9 @@ public class ImageCurves extends ImageSlave { max = histogram[i]; } } - float w = getWidth(); - float h = getHeight(); + 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(); @@ -288,12 +292,12 @@ public class ImageCurves extends ImageSlave { paint2.setStrokeWidth(6); paint2.setXfermode(new PorterDuffXfermode(mode)); gHistoPath.reset(); - gHistoPath.moveTo(0, h); + 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; + float x = i * wl + dx; float l = histogram[i] * wh; if (l != 0) { float v = h - (l + prev) / 2.0f; diff --git a/src/com/android/gallery3d/filtershow/ui/Spline.java b/src/com/android/gallery3d/filtershow/ui/Spline.java index dd9aac1be..83341772b 100644 --- a/src/com/android/gallery3d/filtershow/ui/Spline.java +++ b/src/com/android/gallery3d/filtershow/ui/Spline.java @@ -49,7 +49,11 @@ public class Spline { mPoints = new Vector<ControlPoint>(); for (int i = 0; i < spline.mPoints.size(); i++) { ControlPoint p = spline.mPoints.elementAt(i); - mPoints.add(new ControlPoint(p)); + ControlPoint newPoint = new ControlPoint(p); + mPoints.add(newPoint); + if (spline.mCurrentControlPoint == p) { + mCurrentControlPoint = newPoint; + } } Collections.sort(mPoints); } @@ -79,7 +83,7 @@ public class Spline { return Color.WHITE; } - public void didMovePoint(ControlPoint point) { + private void didMovePoint(ControlPoint point) { mCurrentControlPoint = point; } @@ -90,6 +94,7 @@ public class Spline { ControlPoint point = mPoints.elementAt(pick); point.x = x; point.y = y; + didMovePoint(point); } public boolean isOriginal() { @@ -121,13 +126,20 @@ public class Spline { } 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 = start; i < 256; i++) { + 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; |