From 762b4350b731e1c2189c871dc77627df2be87549 Mon Sep 17 00:00:00 2001 From: Likai Ding Date: Wed, 10 Dec 2014 18:09:27 +0800 Subject: SnapdragonCamera: implement refocus feature Add a scene mode for refocus. If the latest photo is taken with refocus, user can click on the preview thumbnail, then refocus it by tapping on the photo. Change-Id: I2fd69439467f5a1e33d23d8d239aa3472d88b585 --- src/com/android/camera/CameraActivity.java | 9 + src/com/android/camera/CameraSettings.java | 14 +- src/com/android/camera/PhotoMenu.java | 11 ++ src/com/android/camera/PhotoModule.java | 59 ++++-- src/com/android/camera/PhotoUI.java | 5 + src/com/android/camera/RefocusActivity.java | 249 ++++++++++++++++++++++++++ src/com/android/camera/ui/CameraControls.java | 106 ++++++++++- 7 files changed, 439 insertions(+), 14 deletions(-) create mode 100644 src/com/android/camera/RefocusActivity.java (limited to 'src/com') diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index d460867a6..849dc33e3 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -527,6 +527,15 @@ public class CameraActivity extends Activity if (img == null) return; Uri uri = img.getContentUri(); + if (mCurrentModule instanceof PhotoModule) { + if (((PhotoModule) mCurrentModule).isRefocus()) { + Intent intent = new Intent(); + intent.setClass(this, RefocusActivity.class); + intent.setData(uri); + startActivity(intent); + return; + } + } Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); } diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java index 37fc39718..63581b8ba 100644 --- a/src/com/android/camera/CameraSettings.java +++ b/src/com/android/camera/CameraSettings.java @@ -769,9 +769,19 @@ public class CameraSettings { filterUnsupportedOptions(group, whiteBalance, mParameters.getSupportedWhiteBalance()); } + if (sceneMode != null) { - filterUnsupportedOptions(group, - sceneMode, mParameters.getSupportedSceneModes()); + List supportedSceneModes = mParameters.getSupportedSceneModes(); + List supportedAdvancedFeatures = + getSupportedAdvancedFeatures(mParameters); + if (CameraUtil.isSupported( + mContext.getString(R.string + .pref_camera_advanced_feature_value_refocus_on), + supportedAdvancedFeatures)) { + supportedSceneModes.add(mContext.getString(R.string + .pref_camera_advanced_feature_value_refocus_on)); + } + filterUnsupportedOptions(group, sceneMode, supportedSceneModes); } if (flashMode != null) { filterUnsupportedOptions(group, diff --git a/src/com/android/camera/PhotoMenu.java b/src/com/android/camera/PhotoMenu.java index 1aea279a3..b7c126f36 100644 --- a/src/com/android/camera/PhotoMenu.java +++ b/src/com/android/camera/PhotoMenu.java @@ -1097,6 +1097,17 @@ public class PhotoMenu extends MenuController } } + String refocusOn = mActivity.getString(R.string + .pref_camera_advanced_feature_value_refocus_on); + if (notSame(pref, CameraSettings.KEY_SCENE_MODE, refocusOn)) { + ListPreference lp = mPreferenceGroup + .findPreference(CameraSettings.KEY_ADVANCED_FEATURES); + if (lp != null && refocusOn.equals(lp.getValue())) { + setPreference(CameraSettings.KEY_ADVANCED_FEATURES, + mActivity.getString(R.string.pref_camera_advanced_feature_default)); + } + } + if (notSame(pref, CameraSettings.KEY_SCENE_MODE, Parameters.SCENE_MODE_AUTO)) { buttonSetEnabled(mFilterModeSwitcher, false); } else { diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java index 1e238be80..0e97889f5 100644 --- a/src/com/android/camera/PhotoModule.java +++ b/src/com/android/camera/PhotoModule.java @@ -35,7 +35,9 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.location.Location; +import android.media.AudioManager; import android.media.CameraProfile; +import android.media.SoundPool; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -192,6 +194,7 @@ public class PhotoModule private boolean mTouchAfAecFlag; private boolean mLongshotSave = false; private boolean mRefocus = false; + private boolean mLastPhotoTakenWithRefocus = false; // The degrees of the device rotated clockwise from its natural orientation. private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; @@ -253,6 +256,9 @@ public class PhotoModule // when the image is ready to be saved. private NamedImages mNamedImages; + private SoundPool mSoundPool; + private int mRefocusSound; + private Runnable mDoSnapRunnable = new Runnable() { @Override public void run() { @@ -558,6 +564,8 @@ public class PhotoModule mAm.getMemoryInfo(memInfo); SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold; + mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0); + mRefocusSound = mSoundPool.load(mActivity, R.raw.camera_click_x5, 1); } private void initializeControlByIntent() { @@ -1028,6 +1036,9 @@ public class PhotoModule } }); } + if (mRefocus) { + mSoundPool.play(mRefocusSound, 1.0f, 1.0f, 0, 0, 1.0f); + } } } private final class StatsCallback @@ -1243,7 +1254,20 @@ public class PhotoModule } startFaceDetection(); } - if ((mRefocus) && (mReceivedSnapNum == 6)) { + + mLastPhotoTakenWithRefocus = mRefocus; + if (mRefocus) { + final String[] NAMES = { "00.jpg", "01.jpg", "02.jpg", "03.jpg", + "04.jpg", "DepthMapImage.y", "AllFocusImage.jpg" }; + try { + FileOutputStream out = mActivity.openFileOutput(NAMES[mReceivedSnapNum - 1], + Context.MODE_PRIVATE); + out.write(jpegData, 0, jpegData.length); + out.close(); + } catch (Exception e) { + } + } + if (mRefocus && (mReceivedSnapNum == 7)) { Size s = mParameters.getPictureSize(); mNamedImages.nameNewImage(mCaptureStartTime, mRefocus); NamedEntity name = mNamedImages.getNextNameEntity(); @@ -1259,9 +1283,9 @@ public class PhotoModule } mActivity.getMediaSaveService().addImage( jpegData, title, date, mLocation, s.width, s.height, - 0, null, mOnMediaSavedListener, mContentResolver, ".jpeg"); - - } else { + 0, null, mOnMediaSavedListener, mContentResolver, PIXEL_FORMAT_JPEG); + mUI.showRefocusToast(mRefocus); + } else if (!mRefocus) { ExifInterface exif = Exif.getExif(jpegData); int orientation = Exif.getOrientation(exif); if (!mIsImageCaptureIntent) { @@ -1604,6 +1628,7 @@ public class PhotoModule new JpegPictureCallback(loc)); } } else { + mCameraDevice.enableShutterSound(!mRefocus); mCameraDevice.takePicture(mHandler, new ShutterCallback(!animateBefore), mRawPictureCallback, mPostViewPictureCallback, @@ -1763,9 +1788,7 @@ public class PhotoModule } else { mUI.overrideSettings(CameraSettings.KEY_PICTURE_FORMAT, null); } - if ((ubiFocus != null && ubiFocus.equals(ubiFocusOn)) || - (multiTouchFocus != null && multiTouchFocus.equals(multiTouchFocusOn)) || - (reFocus != null && reFocus.equals(reFocusOn)) || + if ((multiTouchFocus != null && multiTouchFocus.equals(multiTouchFocusOn)) || (chromaFlash != null && chromaFlash.equals(chromaFlashOn)) || (optiZoom != null && optiZoom.equals(optiZoomOn)) || (fssr != null && fssr.equals(fssrOn)) || @@ -3472,6 +3495,10 @@ public class PhotoModule mActivity.getString(R.string.pref_camera_scenemode_default)); } } + + String refocusOn = mActivity.getString(R.string + .pref_camera_advanced_feature_value_refocus_on); + if (CameraUtil.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) { if (!mParameters.getSceneMode().equals(mSceneMode)) { mParameters.setSceneMode(mSceneMode); @@ -3483,9 +3510,16 @@ public class PhotoModule mParameters = mCameraDevice.getParameters(); } } else { - mSceneMode = mParameters.getSceneMode(); - if (mSceneMode == null) { - mSceneMode = Parameters.SCENE_MODE_AUTO; + if (refocusOn.equals(mSceneMode)) { + try { + mUI.setPreference(CameraSettings.KEY_ADVANCED_FEATURES, refocusOn); + } catch (NullPointerException e) { + } + } else { + mSceneMode = mParameters.getSceneMode(); + if (mSceneMode == null) { + mSceneMode = Parameters.SCENE_MODE_AUTO; + } } } @@ -4423,6 +4457,10 @@ public class PhotoModule } } } + + public boolean isRefocus() { + return mLastPhotoTakenWithRefocus; + } } /* Below is no longer needed, except to get rid of compile error @@ -4600,5 +4638,4 @@ class DrawAutoHDR extends View{ public void setPhotoModuleObject (PhotoModule photoModule) { mPhotoModule = photoModule; } - } diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java index b4e08c398..86be3165d 100644 --- a/src/com/android/camera/PhotoUI.java +++ b/src/com/android/camera/PhotoUI.java @@ -529,6 +529,10 @@ public class PhotoUI implements PieListener, task.execute(); } + public void showRefocusToast(boolean show) { + mCameraControls.showRefocusToast(show); + } + private void openMenu() { if (mPieRenderer != null) { // If autofocus is not finished, cancel autofocus so that the @@ -962,6 +966,7 @@ public class PhotoUI implements PieListener, ret = true; } onShowSwitcherPopup(); + mCameraControls.showRefocusToast(false); return ret; } diff --git a/src/com/android/camera/RefocusActivity.java b/src/com/android/camera/RefocusActivity.java new file mode 100644 index 000000000..2102d1a78 --- /dev/null +++ b/src/com/android/camera/RefocusActivity.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.android.camera; + +import java.io.File; +import java.io.FileInputStream; +import java.io.OutputStream; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Point; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Build; +import android.view.Display; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; + +import org.codeaurora.snapcam.R; + +public class RefocusActivity extends Activity { + private static final String TAG = "RefocusActivity"; + private static final String[] NAMES = { + "00", "01", "02", "03", "04", "AllFocusImage" + }; + + private Uri mUri; + + private ImageView mImageView; + private int mWidth; + private int mHeight; + + private DepthMap mDepthMap; + private int mCurrentImage = -1; + private int mRequestedImage = -1; + private LoadImageTask mLoadImageTask; + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + + new Thread(new Runnable() { + public void run() { + mDepthMap = new DepthMap(getFilesDir() + "/DepthMapImage.y"); + } + }).start(); + + mUri = getIntent().getData(); + setResult(RESULT_CANCELED, new Intent()); + + setContentView(R.layout.refocus_editor); + mImageView = (ImageView) findViewById(R.id.refocus_image); + mImageView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_UP: + float x = event.getX(); + float y = event.getY(); + int w = v.getWidth(); + int h = v.getHeight(); + if (mDepthMap != null) { + int depth = mDepthMap.getDepth(x / (float) w, y / (float) h); + setCurrentImage(depth); + } + break; + } + return true; + } + }); + + findViewById(R.id.refocus_all).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View v) { + allInFocus(); + } + }); + + findViewById(R.id.refocus_cancel).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View v) { + finish(); + } + }); + + findViewById(R.id.refocus_done).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View v) { + if (mRequestedImage != NAMES.length - 1) { + new SaveImageTask().execute(getFilesDir() + "/" + NAMES[mRequestedImage] + + ".jpg"); + } else { + finish(); + } + } + }); + + Display display = getWindowManager().getDefaultDisplay(); + Point size = new Point(); + display.getSize(size); + mWidth = size.x; + mHeight = size.y; + + allInFocus(); + } + + private void setCurrentImage(int depth) { + if (depth >= 0 && depth < NAMES.length && depth != mRequestedImage) { + mRequestedImage = depth; + if (mLoadImageTask != null) { + mLoadImageTask.cancel(true); + } + if (depth != mCurrentImage) { + mCurrentImage = depth; + mLoadImageTask = new LoadImageTask(); + mLoadImageTask.execute(getFilesDir() + "/" + NAMES[depth] + ".jpg"); + } + } + } + + private void allInFocus() { + setCurrentImage(NAMES.length - 1); + } + + private class SaveImageTask extends AsyncTask { + protected Void doInBackground(String... path) { + try { + OutputStream out = getContentResolver().openOutputStream(mUri); + FileInputStream in = new FileInputStream(path[0]); + byte[] buf = new byte[4096]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + in.close(); + out.close(); + } catch (Exception e) { + } + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mUri); + sendBroadcast(intent); + return null; + } + + protected void onPostExecute(Void v) { + finish(); + } + } + + private class LoadImageTask extends AsyncTask { + protected Bitmap doInBackground(String... path) { + final BitmapFactory.Options o = new BitmapFactory.Options(); + o.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path[0], o); + + int h = o.outHeight; + int w = o.outWidth; + int sample = 1; + + if (h > mHeight || w > mWidth) { + while (h / sample / 2 > mHeight && w / sample / 2 > mWidth) { + sample *= 2; + } + } + + o.inJustDecodeBounds = false; + o.inSampleSize = sample; + return BitmapFactory.decodeFile(path[0], o); + } + + protected void onPostExecute(Bitmap result) { + mImageView.setImageBitmap(result); + } + } + + private class DepthMap { + private byte[] mData; + private int mWidth; + private int mHeight; + private boolean mFail = true; + + public DepthMap(final String path) { + File file = new File(path); + try { + FileInputStream stream = new FileInputStream(file); + mData = new byte[(int) file.length()]; + stream.read(mData); + stream.close(); + } catch (Exception e) { + } + + int length = (int) mData.length; + if (length > 25) { + mFail = (mData[length - 25] != 0); + mWidth = readInteger(length - 24); + mHeight = readInteger(length - 20); + } + if (mWidth * mHeight + 25 > length) { + mFail = true; + } + } + + public int getDepth(float x, float y) { + if (mFail || x > 1.0f || y > 1.0f) { + return NAMES.length - 1; + } else { + return mData[(int) ((y * mHeight + x) * mWidth)]; + } + } + + private int readInteger(int offset) { + int result = mData[offset] & 0xff; + for (int i = 1; i < 4; ++i) { + result <<= 8; + result += mData[offset + i] & 0xff; + } + return result; + } + } +} diff --git a/src/com/android/camera/ui/CameraControls.java b/src/com/android/camera/ui/CameraControls.java index c2c33e785..bb3d80c8d 100644 --- a/src/com/android/camera/ui/CameraControls.java +++ b/src/com/android/camera/ui/CameraControls.java @@ -19,13 +19,18 @@ package com.android.camera.ui; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.content.Context; -import android.util.Log; +import android.graphics.Canvas; import android.graphics.drawable.AnimationDrawable; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.Paint; +import android.graphics.Path; import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; import android.view.View; +import android.view.ViewGroup; import android.view.ViewPropertyAnimator; import android.widget.FrameLayout; import android.widget.TextView; @@ -50,6 +55,8 @@ public class CameraControls extends RotatableLayout { private View mPreview; private View mSceneModeSwitcher; private View mFilterModeSwitcher; + private ArrowTextView mRefocusToast; + private int mSize; private static final int WIDTH_GRID = 5; private static final int HEIGHT_GRID = 7; @@ -152,8 +159,15 @@ public class CameraControls extends RotatableLayout { public CameraControls(Context context, AttributeSet attrs) { super(context, attrs); + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); setWillNotDraw(false); + + mRefocusToast = new ArrowTextView(context); + addView(mRefocusToast); + setClipChildren(false); + + setMeasureAllChildren(true); } public CameraControls(Context context) { @@ -291,6 +305,46 @@ public class CameraControls extends RotatableLayout { toIndex(mHdrSwitcher, w, h, rotation, 3, 0, HDR_INDEX); toIndex(mFilterModeSwitcher, w, h, rotation, 1, 0, FILTER_MODE_INDEX); toIndex(mSceneModeSwitcher, w, h, rotation, 0, 0, SCENE_MODE_INDEX); + layoutToast(mRefocusToast, w, h, rotation); + } + + private void layoutToast(final View v, int w, int h, int rotation) { + int tw = v.getMeasuredWidth(); + int th = v.getMeasuredHeight(); + int l, t, r, b, c; + switch (rotation) { + case 90: + c = (int) (h / WIDTH_GRID * (WIDTH_GRID - 0.5)); + t = c - th / 2; + b = c + th / 2; + r = (int) (w / HEIGHT_GRID * (HEIGHT_GRID - 1.25)); + l = r - tw; + mRefocusToast.setArrow(tw, th / 2, tw + th / 2, th, tw, th); + break; + case 180: + t = (int) (h / HEIGHT_GRID * 1.25); + b = t + th; + r = (int) (w / WIDTH_GRID * (WIDTH_GRID - 0.25)); + l = r - tw; + mRefocusToast.setArrow(tw - th / 2, 0, tw, 0, tw, - th / 2); + break; + case 270: + c = (int) (h / WIDTH_GRID * 0.5); + t = c - th / 2; + b = c + th / 2; + l = (int) (w / HEIGHT_GRID * 1.25); + r = l + tw; + mRefocusToast.setArrow(0, 0, 0, th / 2, - th / 2, 0); + break; + default: + l = w / WIDTH_GRID / 4; + b = (int) (h / HEIGHT_GRID * (HEIGHT_GRID - 1.25)); + r = l + tw; + t = b - th; + mRefocusToast.setArrow(0, th, th / 2, th, 0, th * 3 / 2); + break; + } + mRefocusToast.layout(l, t, r, b); } private void center(View v, int l, int t, int r, int b, int orientation, int rotation, @@ -430,6 +484,7 @@ public class CameraControls extends RotatableLayout { break; } mRemainingPhotos.setVisibility(View.INVISIBLE); + mRefocusToast.setVisibility(View.GONE); } public void showUI() { @@ -520,6 +575,7 @@ public class CameraControls extends RotatableLayout { if (mRemainingPhotos.getVisibility() == View.INVISIBLE) { mRemainingPhotos.setVisibility(View.VISIBLE); } + mRefocusToast.setVisibility(View.GONE); } private void center(View v, Rect other, int rotation) { @@ -765,4 +821,52 @@ public class CameraControls extends RotatableLayout { } invalidate(); } + + public void showRefocusToast(boolean show) { + mRefocusToast.setVisibility(show ? View.VISIBLE : View.GONE); + mRemainingPhotos.setVisibility(show ? View.GONE : View.VISIBLE); + } + + private class ArrowTextView extends TextView { + private static final int TEXT_SIZE = 14; + private static final int PADDING_SIZE = 18; + private static final int BACKGROUND = 0x80000000; + + private Paint mPaint; + private Path mPath; + + public ArrowTextView(Context context) { + super(context); + + setText(context.getString(R.string.refocus_toast)); + setBackgroundColor(BACKGROUND); + setVisibility(View.GONE); + setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + setTextSize(TEXT_SIZE); + setPadding(PADDING_SIZE, PADDING_SIZE, PADDING_SIZE, PADDING_SIZE); + + mPaint = new Paint(); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setColor(BACKGROUND); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mPath != null) { + canvas.drawPath(mPath, mPaint); + } + } + + public void setArrow(float x1, float y1, float x2, float y2, float x3, float y3) { + mPath = new Path(); + mPath.reset(); + mPath.moveTo(x1, y1); + mPath.lineTo(x2, y2); + mPath.lineTo(x3, y3); + mPath.lineTo(x1, y1); + } + } } -- cgit v1.2.3