diff options
Diffstat (limited to 'src/com/android/gallery3d/filtershow')
33 files changed, 2232 insertions, 976 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 c1e4f6add..356a0a074 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,6 +60,7 @@ 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.ImageFilterExposure; import com.android.gallery3d.filtershow.filters.ImageFilterFx; import com.android.gallery3d.filtershow.filters.ImageFilterHue; @@ -65,6 +68,7 @@ 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 +76,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 +94,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 +102,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; @@ -117,6 +127,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, private View mListGeometry = null; private View mListColors = null; private View mListFilterButtons = null; + private View mSaveButton = null; private ImageButton mFxButton = null; private ImageButton mBorderButton = null; @@ -155,7 +166,8 @@ 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)); @@ -180,7 +192,8 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); actionBar.setCustomView(R.layout.filtershow_actionbar); - actionBar.getCustomView().setOnClickListener(new OnClickListener() { + mSaveButton = actionBar.getCustomView(); + mSaveButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { saveImage(); @@ -202,9 +215,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); @@ -214,6 +229,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); @@ -248,6 +267,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); @@ -260,6 +281,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); @@ -269,86 +291,40 @@ 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 ImageFilterSaturated(), - new ImageFilterBwFilter(), - new ImageFilterWBalance(), + new ImageFilterShadows(), + new ImageFilterVibrance(), + new ImageFilterSharpen(), + new ImageFilterCurves(), new ImageFilterHue(), - new ImageFilterExposure(), - new ImageFilterShadows() + new ImageFilterSaturated(), + new ImageFilterBwFilter() }; 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); + listColors.addView(fView); } mPanelController.addView(findViewById(R.id.applyEffect)); @@ -390,8 +366,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)); @@ -411,7 +416,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; @@ -422,19 +427,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 @@ -442,9 +454,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); } @@ -640,6 +653,11 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, return false; } + public void enableSave(boolean enable) { + if (mSaveButton != null) + mSaveButton.setEnabled(enable); + } + private void fillListImages(LinearLayout listFilters) { // TODO: use listview // TODO: load the filters straight from the filesystem @@ -990,17 +1008,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 52bf98aa7..1d9cc22b5 100644 --- a/src/com/android/gallery3d/filtershow/PanelController.java +++ b/src/com/android/gallery3d/filtershow/PanelController.java @@ -42,6 +42,7 @@ 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.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; @@ -56,6 +57,11 @@ 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 mFixedAspect = false; + + public void setFixedAspect(boolean t) { + mFixedAspect = t; + } class Panel { private final View mView; @@ -587,6 +593,27 @@ 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.setShowParameter(false); + mUtilityPanel.showCurvesButtons(); + } + ensureFilter(ename); + mCurrentImage.select(); + } + return; + } + switch (view.getId()) { case R.id.tinyplanetButton: { mCurrentImage = showImageView(R.id.imageTinyPlanet).setShowControls(true); @@ -606,11 +633,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).applyClear(); + if (mCurrentImage instanceof ImageCrop && mUtilityPanel.firstTimeCropDisplayed) { + ((ImageCrop) mCurrentImage).clear(); mUtilityPanel.firstTimeCropDisplayed = false; } - mUtilityPanel.showAspectButtons(); + if (!mFixedAspect) { + mUtilityPanel.showAspectButtons(); + } break; } case R.id.rotateButton: { @@ -626,89 +655,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 afef58aad..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; @@ -36,19 +38,27 @@ import com.adobe.xmp.XMPMeta; import com.android.gallery3d.R; 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; import java.io.File; +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; @@ -69,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; @@ -101,18 +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() { @@ -135,21 +154,25 @@ 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; @@ -160,12 +183,27 @@ public class ImageLoader { static int getOrientationFromPath(String path) { int orientation = -1; + InputStream is = null; try { - ExifInterface EXIF = new ExifInterface(path); - orientation = EXIF.getAttributeInt(ExifInterface.TAG_ORIENTATION, - 1); + is = new FileInputStream(path); + ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0); + int event = parser.next(); + while (event != ExifParser.EVENT_END) { + if (event == ExifParser.EVENT_NEW_TAG) { + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifTag.TAG_ORIENTATION) { + orientation = (int) tag.getValueAt(0); + break; + } + } + event = parser.next(); + } } catch (IOException e) { e.printStackTrace(); + } catch (ExifInvalidFormatException e) { + e.printStackTrace(); + } finally { + Utils.closeSilently(is); } return orientation; } @@ -181,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); @@ -253,6 +291,7 @@ public class ImageLoader { } static final int MAX_BITMAP_DIM = 2048; + private Bitmap loadScaledBitmap(Uri uri, int size) { InputStream is = null; try { @@ -338,7 +377,8 @@ 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) { @@ -354,6 +394,7 @@ public class ImageLoader { bmp2 = imagePreset.apply(bmp2); imagePreset.setScaleFactor(scaleFactor); mZoomCache.setImage(imagePreset, bounds, bmp2); + mLoadingLock.unlock(); return bmp2; } } @@ -366,9 +407,11 @@ public class ImageLoader { boolean hiRes) { mLoadingLock.lock(); if (mOriginalBitmapSmall == null) { + mLoadingLock.unlock(); return null; } if (mOriginalBitmapLarge == null) { + mLoadingLock.unlock(); return null; } @@ -415,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; } @@ -475,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 7f4d5ed2a..5c24fbf65 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.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.presets.ImagePreset; public class ImageFilter implements Cloneable { @@ -31,22 +32,42 @@ public class ImageFilter implements Cloneable { 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; + } + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilter filter = (ImageFilter) super.clone(); @@ -93,7 +114,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 +122,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 +130,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 +138,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; } 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..46a00f47a 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,26 @@ 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 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/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 d74a6faab..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) cropBounds = GeometryMath.roundNearest(crop); - Bitmap temp = null; - if (mGeometry.hasSwitchedWidthHeight()) { - temp = Bitmap.createBitmap(cropBounds.height(), cropBounds.width(), mConfig); - } else { - temp = Bitmap.createBitmap(cropBounds.width(), cropBounds.height(), mConfig); + + 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/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 effd89ebe..36bd62630 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java @@ -22,6 +22,7 @@ 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; @@ -59,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; } 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 55f791820..568dadfc3 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java @@ -26,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) @@ -40,67 +66,75 @@ 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){ + 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 dffdc2449..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() { } @@ -86,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() { @@ -184,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) { @@ -331,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 a352a16e7..594e008a2 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,23 +56,30 @@ 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){ + public void setAspectTextSize(int textSize) { mAspectTextSize = textSize; } - public void setAspectString(String a){ + public void setAspectString(String a) { mAspect = a; } @@ -80,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) { @@ -91,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 @@ -102,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(); } @@ -194,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)) { @@ -398,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; } @@ -414,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); @@ -434,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) { @@ -560,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 @@ -571,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); + } } } @@ -592,7 +518,7 @@ public class ImageCrop extends ImageGeometry { public void imageLoaded() { super.imageLoaded(); syncLocalToMasterGeometry(); - applyClear(); + clear(); invalidate(); } @@ -600,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(); @@ -610,7 +536,6 @@ public class ImageCrop extends ImageGeometry { @Override public void resetParameter() { super.resetParameter(); - cropSetup(); } @Override @@ -635,7 +560,6 @@ 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); @@ -645,91 +569,102 @@ public class ImageCrop extends ImageGeometry { 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); - - if (mFixAspectRatio){ - 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; + + 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); + gPaint.setTextAlign(Paint.Align.CENTER); + gPaint.setTextSize(mAspectTextSize); + canvas.drawText(mAspect, centX, centY, 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()); - } - 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/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/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java index 0145c24dc..d9df7b7fc 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; @@ -83,7 +84,7 @@ public class ImageShow extends View implements OnGestureListener, 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; @@ -230,6 +231,7 @@ public class ImageShow extends View implements OnGestureListener, } updateSeekBar(parameter, minp, maxp); invalidate(); + mActivity.enableSave(hasModifications()); } @Override @@ -323,6 +325,10 @@ public class ImageShow extends View implements OnGestureListener, return dst; } + public Rect getImageCropBounds() { + return GeometryMath.roundNearest(getImagePreset().mGeoData.getPreviewCropBounds()); + } + public Rect getDisplayedImageBounds() { return mImageBounds; } @@ -396,6 +402,7 @@ public class ImageShow extends View implements OnGestureListener, public void updateImagePresets(boolean force) { ImagePreset preset = getImagePreset(); if (preset == null) { + mActivity.enableSave(false); return; } if (force) { @@ -419,6 +426,7 @@ public class ImageShow extends View implements OnGestureListener, mFiltersOnlyImage = null; } } + mActivity.enableSave(hasModifications()); } public void requestFilteredImages() { @@ -643,13 +651,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(); } } @@ -666,6 +674,14 @@ 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); 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/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; |