diff options
-rw-r--r-- | rs/YuvToRgb.rs | 6 | ||||
-rw-r--r-- | rs/rotator.rs | 24 | ||||
-rw-r--r-- | src/com/android/camera/CameraActivity.java | 2 | ||||
-rw-r--r-- | src/com/android/camera/CameraSettings.java | 7 | ||||
-rw-r--r-- | src/com/android/camera/CaptureModule.java | 111 | ||||
-rw-r--r-- | src/com/android/camera/CaptureUI.java | 30 | ||||
-rw-r--r-- | src/com/android/camera/ComboPreferences.java | 1 | ||||
-rw-r--r-- | src/com/android/camera/SettingsManager.java | 36 | ||||
-rw-r--r-- | src/com/android/camera/exif/ExifInterface.java | 17 | ||||
-rw-r--r-- | src/com/android/camera/imageprocessor/FrameProcessor.java | 8 | ||||
-rw-r--r-- | src/com/android/camera/imageprocessor/PostProcessor.java | 46 | ||||
-rw-r--r-- | src/com/android/camera/imageprocessor/filter/OptizoomFilter.java | 2 | ||||
-rw-r--r-- | src/com/android/camera/imageprocessor/filter/SharpshooterFilter.java | 172 | ||||
-rw-r--r-- | src/com/android/camera/mpo/MpoOutputStream.java | 98 |
14 files changed, 488 insertions, 72 deletions
diff --git a/rs/YuvToRgb.rs b/rs/YuvToRgb.rs index 25771c5c7..57076e979 100644 --- a/rs/YuvToRgb.rs +++ b/rs/YuvToRgb.rs @@ -41,9 +41,9 @@ uchar4 __attribute__((kernel)) nv21ToRgb(uint32_t x, uint32_t y) { int vV = (int)(rsGetElementAt_uchar(gIn, index) & 0xFF ) -128; int uV = (int)(rsGetElementAt_uchar(gIn, index+1) & 0xFF ) -128; - int r = (int) (1.164f * yV + 1.596f * vV ); - int g = (int) (1.164f * yV - 0.813f * vV - 0.391f * uV); - int b = (int) (1.164f * yV + 2.018f * uV ); + int r = (int) (yV + 1.370705f * vV ); + int g = (int) (yV - 0.698001f * vV - 0.337633f* uV); + int b = (int) (yV + 1.732446 * uV ); r = r>255? 255 : r<0 ? 0 : r; g = g>255? 255 : g<0 ? 0 : g; diff --git a/rs/rotator.rs b/rs/rotator.rs index cd9da4396..5a27e00f7 100644 --- a/rs/rotator.rs +++ b/rs/rotator.rs @@ -34,18 +34,34 @@ rs_allocation gOut; rs_allocation gIn; uint32_t width; uint32_t height; +uint32_t pad; +bool gFlip; uchar __attribute__((kernel)) rotate90andMerge(uint32_t x, uint32_t y) { uchar yValue = rsGetElementAt_uchar(gIn, x + y*width); - rsSetElementAt_uchar(gOut, yValue, x*height + height - 1 - y); - if(x%2 == 0 && y%2==1) { + if(gFlip) { + if(x >= width - pad) + return (uchar)0; + rsSetElementAt_uchar(gOut, yValue, (width-1-x-pad)*height + height - 1 - y); + } else { + rsSetElementAt_uchar(gOut, yValue, x*height + height - 1 - y); + } + + if(x%2 == 0 && y%2 == 0) { uint32_t ySize = width*height; uint32_t index = ySize + x + ((y/2) * width); uchar vValue = rsGetElementAt_uchar(gIn, index); uchar uValue = rsGetElementAt_uchar(gIn, index + 1); - rsSetElementAt_uchar(gOut, vValue, ySize + x/2*height + height - 1 - y); - rsSetElementAt_uchar(gOut, uValue, ySize + x/2*height + height - 1 - y - 1); + if(gFlip) { + if(x >= width - pad) + return (uchar)0; + rsSetElementAt_uchar(gOut, uValue, ySize + (width-2-x-pad)/2*height + height - 1 - y); + rsSetElementAt_uchar(gOut, vValue, ySize + (width-2-x-pad)/2*height + height - 1 - y - 1); + } else { + rsSetElementAt_uchar(gOut, uValue, ySize + x/2*height + height - 1 - y); + rsSetElementAt_uchar(gOut, vValue, ySize + x/2*height + height - 1 - y - 1); + } } return (uchar)0; }
\ No newline at end of file diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index f10423163..1b6031b53 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -708,7 +708,7 @@ public class CameraActivity extends Activity public void updateThumbnail(final byte[] jpegData) { if (mUpdateThumbnailTask != null) mUpdateThumbnailTask.cancel(true); - mUpdateThumbnailTask = new UpdateThumbnailTask(jpegData, false); + mUpdateThumbnailTask = new UpdateThumbnailTask(jpegData, true); mUpdateThumbnailTask.execute(); } diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java index 7a1cf8d74..f700d2187 100644 --- a/src/com/android/camera/CameraSettings.java +++ b/src/com/android/camera/CameraSettings.java @@ -1058,7 +1058,7 @@ public class CameraSettings { resetIfInvalid(pref); } - private void filterSimilarPictureSize(PreferenceGroup group, + public static void filterSimilarPictureSize(PreferenceGroup group, ListPreference pref) { pref.filterDuplicated(); if (pref.getEntries().length <= 1) { @@ -1175,11 +1175,6 @@ public class CameraSettings { return Integer.parseInt(pref.getString(KEY_CAMERA_ID, rearCameraId)); } - public static int getInitialCameraId(SharedPreferences pref) { - String value = pref.getString(SettingsManager.KEY_INITIAL_CAMERA, "0"); - return Integer.parseInt(value); - } - public static void writePreferredCameraId(SharedPreferences pref, int cameraId) { Editor editor = pref.edit(); diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java index 0f65f21c3..8ac48afcb 100644 --- a/src/com/android/camera/CaptureModule.java +++ b/src/com/android/camera/CaptureModule.java @@ -68,6 +68,7 @@ import com.android.camera.imageprocessor.PostProcessor; import com.android.camera.imageprocessor.FrameProcessor; import com.android.camera.PhotoModule.NamedImages; import com.android.camera.PhotoModule.NamedImages.NamedEntity; +import com.android.camera.imageprocessor.filter.SharpshooterFilter; import com.android.camera.ui.CountDownView; import com.android.camera.ui.ModuleSwitcher; import com.android.camera.ui.RotateTextToast; @@ -187,6 +188,7 @@ public class CaptureModule implements CameraModule, PhotoController, private SettingsManager mSettingsManager; private long SECONDARY_SERVER_MEM; private boolean mLongshotActive = false; + private CameraCharacteristics mMainCameraCharacteristics; /** * A {@link CameraCaptureSession } for camera preview. @@ -205,6 +207,7 @@ public class CaptureModule implements CameraModule, PhotoController, private PostProcessor mPostProcessor; private FrameProcessor mFrameProcessor; private Size mFrameProcPreviewOutputSize; + private CaptureResult mPreviewCaptureResult; private Face[] mPreviewFaces = null; private Face[] mStickyFaces = null; private Rect mBayerCameraRegion; @@ -223,6 +226,7 @@ public class CaptureModule implements CameraModule, PhotoController, private boolean mFirstPreviewLoaded; private int[] mPrecaptureRequestHashCode = new int[MAX_NUM_CAM]; private int[] mLockRequestHashCode = new int[MAX_NUM_CAM]; + private final Handler mHandler = new MainHandler(); private class MediaSaveNotifyThread extends Thread { private Uri uri; @@ -323,6 +327,10 @@ public class CaptureModule implements CameraModule, PhotoController, return mStickyFaces; } + public CaptureResult getPreviewCaptureResult() { + return mPreviewCaptureResult; + } + public Rect getCameraRegion() { return mBayerCameraRegion; } @@ -333,7 +341,7 @@ public class CaptureModule implements CameraModule, PhotoController, private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() { - private void process(CaptureResult result) { + private void updateState(CaptureResult result) { int id = (int) result.getRequest().getTag(); if (!mFirstPreviewLoaded) { @@ -351,6 +359,7 @@ public class CaptureModule implements CameraModule, PhotoController, if(faces != null && faces.length != 0) { mStickyFaces = faces; } + mPreviewCaptureResult = result; switch (mState[id]) { case STATE_PREVIEW: { @@ -412,7 +421,6 @@ public class CaptureModule implements CameraModule, PhotoController, CaptureResult partialResult) { int id = (int) partialResult.getRequest().getTag(); if (id == getMainCameraId()) updateFocusStateChange(partialResult); - process(partialResult); } @Override @@ -421,7 +429,7 @@ public class CaptureModule implements CameraModule, PhotoController, TotalCaptureResult result) { int id = (int) result.getRequest().getTag(); if (id == getMainCameraId()) updateFocusStateChange(result); - process(result); + updateState(result); } }; @@ -492,7 +500,7 @@ public class CaptureModule implements CameraModule, PhotoController, else return false; } - private boolean isBackCamera() { + public boolean isBackCamera() { String value = mSettingsManager.getValue(SettingsManager.KEY_CAMERA_ID); if (value == null) return true; if (Integer.parseInt(value) == BAYER_ID) return true; @@ -556,6 +564,9 @@ public class CaptureModule implements CameraModule, PhotoController, // are initialized. if (s != null) { s.setListener(this); + if (isClearSightOn()) { + ClearSightImageProcessor.getInstance().setMediaSaveService(s); + } } mNamedImages = new NamedImages(); @@ -569,6 +580,9 @@ public class CaptureModule implements CameraModule, PhotoController, MediaSaveService s = mActivity.getMediaSaveService(); if (s != null) { s.setListener(this); + if (isClearSightOn()) { + ClearSightImageProcessor.getInstance().setMediaSaveService(s); + } } mNamedImages = new NamedImages(); } @@ -855,9 +869,7 @@ public class CaptureModule implements CameraModule, PhotoController, captureBuilder = mCameraDevice[id].createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); } - // Orientation - int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, CameraUtil.getJpegRotation(id, rotation)); + captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, CameraUtil.getJpegRotation(id, mOrientation)); captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); captureBuilder.addTarget(getPreviewSurface(id)); captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, mControlAFMode); @@ -1027,6 +1039,11 @@ public class CaptureModule implements CameraModule, PhotoController, mFrameProcPreviewOutputSize = sizeList.get(i); } } + + public CameraCharacteristics getMainCameraCharacteristics() { + return mMainCameraCharacteristics; + } + /** * Sets up member variables related to camera. * @@ -1047,6 +1064,7 @@ public class CaptureModule implements CameraModule, PhotoController, if(i == getMainCameraId()) { mBayerCameraRegion = characteristics.get(CameraCharacteristics .SENSOR_INFO_ACTIVE_ARRAY_SIZE); + mMainCameraCharacteristics = characteristics; } StreamConfigurationMap map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); @@ -1069,9 +1087,11 @@ public class CaptureModule implements CameraModule, PhotoController, mUI.setPreviewSize(mFrameProcPreviewOutputSize.getWidth(), mFrameProcPreviewOutputSize.getHeight()); } if (isClearSightOn()) { - ClearSightImageProcessor.getInstance().init(size.getWidth(), size.getHeight(), - mActivity, mOnMediaSavedListener); - ClearSightImageProcessor.getInstance().setCallback(this); + if(i == getMainCameraId()) { + ClearSightImageProcessor.getInstance().init(size.getWidth(), size.getHeight(), + mActivity, mOnMediaSavedListener); + ClearSightImageProcessor.getInstance().setCallback(this); + } } else { // No Clearsight mImageReader[i] = ImageReader.newInstance(size.getWidth(), size.getHeight(), imageFormat, MAX_IMAGE_NUM); @@ -1364,23 +1384,15 @@ public class CaptureModule implements CameraModule, PhotoController, return filters; } - private int getPostProcFilterId() { - String scene = mSettingsManager.getValue(SettingsManager.KEY_SCENE_MODE); - if (scene != null) { - int mode = Integer.parseInt(scene); - if (mode == SettingsManager.SCENE_MODE_OPTIZOOM_INT) - return PostProcessor.FILTER_OPTIZOOM; + private int getPostProcFilterId(int mode) { + if (mode == SettingsManager.SCENE_MODE_OPTIZOOM_INT) { + return PostProcessor.FILTER_OPTIZOOM; + } else if (mode == SettingsManager.SCENE_MODE_NIGHT_INT && SharpshooterFilter.isSupportedStatic()) { + return PostProcessor.FILTER_SHARPSHOOTER; } return PostProcessor.FILTER_NONE; } - private boolean isPostProcFilter(String value) { - if(value.equalsIgnoreCase(SettingsManager.SCENE_MODE_OPTIZOOM_INT+"")) { - return true; - } - return false; - } - @Override public void onResumeAfterSuper() { Log.d(TAG, "onResume " + getCameraMode()); @@ -1388,12 +1400,19 @@ public class CaptureModule implements CameraModule, PhotoController, mUI.setSwitcherIndex(); mCameraIdList = new ArrayList<>(); if(mPostProcessor != null) { - Log.d(TAG, "Chosen postproc filter id : "+getPostProcFilterId()); - mPostProcessor.onOpen(getPostProcFilterId()); + String scene = mSettingsManager.getValue(SettingsManager.KEY_SCENE_MODE); + if (scene != null) { + int mode = Integer.parseInt(scene); + Log.d(TAG, "Chosen postproc filter id : " + getPostProcFilterId(mode)); + mPostProcessor.onOpen(getPostProcFilterId(mode)); + } else { + mPostProcessor.onOpen(PostProcessor.FILTER_NONE); + } } if(mFrameProcessor != null) { mFrameProcessor.onOpen(getFrameProcFilterId()); } + if(mPostProcessor.isFilterOn()) { setUpCameraOutputs(ImageFormat.YUV_420_888); } else { @@ -1425,7 +1444,12 @@ public class CaptureModule implements CameraModule, PhotoController, initializeSecondTime(); } mUI.reInitUI(); - mActivity.updateStorageSpaceAndHint(); + mHandler.post(new Runnable() { + @Override + public void run() { + mActivity.updateStorageSpaceAndHint(); + } + }); estimateJpegFileSize(); mUI.enableShutter(true); } @@ -1575,7 +1599,7 @@ public class CaptureModule implements CameraModule, PhotoController, } } - private int getMainCameraId() { + public int getMainCameraId() { if (isBackCamera()) { switch (getCameraMode()) { case DUAL_MODE: @@ -1664,6 +1688,7 @@ public class CaptureModule implements CameraModule, PhotoController, @Override public void onPreviewUIDestroyed() { + mSurfaceReady = false; } @Override @@ -1700,6 +1725,10 @@ public class CaptureModule implements CameraModule, PhotoController, } } + public int getDisplayOrientation() { + return mOrientation; + } + @Override public void onShowSwitcherPopup() { @@ -1741,6 +1770,12 @@ public class CaptureModule implements CameraModule, PhotoController, @Override public void onShutterButtonClick() { + if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { + Log.i(TAG, "Not enough space or storage not ready. remaining=" + + mActivity.getStorageSpaceBytes()); + return; + } + String timer = mSettingsManager.getValue(SettingsManager.KEY_TIMER); int seconds = Integer.parseInt(timer); @@ -1959,6 +1994,10 @@ public class CaptureModule implements CameraModule, PhotoController, String value = mSettingsManager.getValue(SettingsManager.KEY_SCENE_MODE); if (value == null) return; int mode = Integer.parseInt(value); + if(getPostProcFilterId(mode) != PostProcessor.FILTER_NONE) { + request.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + return; + } if (mode != CaptureRequest.CONTROL_SCENE_MODE_DISABLED && mode != SettingsManager.SCENE_MODE_DUAL_INT) { request.set(CaptureRequest.CONTROL_SCENE_MODE, mode); @@ -2011,7 +2050,7 @@ public class CaptureModule implements CameraModule, PhotoController, .FLASH_MODE_OFF); break; case CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH: - if (redeye.equals("disable")) { + if (redeye != null && redeye.equals("disable")) { request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest .CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE); } @@ -2095,7 +2134,8 @@ public class CaptureModule implements CameraModule, PhotoController, } private void updateFocusStateChange(CaptureResult result) { - final int resultAFState = result.get(CaptureResult.CONTROL_AF_STATE); + final Integer resultAFState = result.get(CaptureResult.CONTROL_AF_STATE); + if (resultAFState == null) return; // Report state change when AF state has changed. if (resultAFState != mLastResultAFState && mFocusStateListener != null) { @@ -2203,7 +2243,8 @@ public class CaptureModule implements CameraModule, PhotoController, private boolean checkNeedToRestart(String value) { mPostProcessor.setFilter(PostProcessor.FILTER_NONE); - if (isPostProcFilter(value)) + int mode = Integer.parseInt(value); + if (getPostProcFilterId(mode) != PostProcessor.FILTER_NONE) return true; if (value.equals(SettingsManager.SCENE_MODE_DUAL_STRING) && mCurrentMode != DUAL_MODE) return true; @@ -2312,4 +2353,14 @@ public class CaptureModule implements CameraModule, PhotoController, unlockFocus(BAYER_ID); unlockFocus(MONO_ID); } + + /** + * This Handler is used to post message back onto the main thread of the + * application + */ + private class MainHandler extends Handler { + public MainHandler() { + super(Looper.getMainLooper()); + } + } } diff --git a/src/com/android/camera/CaptureUI.java b/src/com/android/camera/CaptureUI.java index 5b8ecad6e..71d39e067 100644 --- a/src/com/android/camera/CaptureUI.java +++ b/src/com/android/camera/CaptureUI.java @@ -96,6 +96,7 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, SettingsManager.KEY_MAKEUP }; String[] mDeveloperKeys = new String[]{ + SettingsManager.KEY_REDEYE_REDUCTION, SettingsManager.KEY_MONO_ONLY, SettingsManager.KEY_CLEARSIGHT, SettingsManager.KEY_MONO_PREVIEW @@ -135,15 +136,14 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, public void surfaceCreated(SurfaceHolder holder) { Log.v(TAG, "surfaceCreated"); mSurfaceHolder = holder; - mModule.onPreviewUIReady(); - mActivity.updateThumbnail(mThumbnail); + previewUIReady(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.v(TAG, "surfaceDestroyed"); mSurfaceHolder = null; - mModule.onPreviewUIDestroyed(); + previewUIDestroyed(); } }; @@ -171,19 +171,39 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, // SurfaceHolder callbacks @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.v(TAG, "surfaceChanged2"); } @Override public void surfaceCreated(SurfaceHolder holder) { + Log.v(TAG, "surfaceCreated2"); mSurfaceHolder2 = holder; + previewUIReady(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { + Log.v(TAG, "surfaceDestroyed2"); mSurfaceHolder2 = null; + previewUIDestroyed(); } }; + private void previewUIReady() { + if((mSurfaceHolder != null && mSurfaceHolder.getSurface().isValid()) && + (mSurfaceView2.getVisibility() != View.VISIBLE || + (mSurfaceView2.getVisibility() == View.VISIBLE && + mSurfaceHolder2 != null && + mSurfaceHolder2.getSurface().isValid()))) { + mModule.onPreviewUIReady(); + mActivity.updateThumbnail(mThumbnail); + } + } + + private void previewUIDestroyed() { + mModule.onPreviewUIDestroyed(); + } + public CaptureUI(CameraActivity activity, CaptureModule module, View parent) { mActivity = activity; mModule = module; @@ -348,7 +368,9 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, int index = mSettingsManager.getValueIndex(SettingsManager.KEY_CAMERA_ID); CharSequence[] entries = mSettingsManager.getEntries(SettingsManager.KEY_CAMERA_ID); - index = (index + 1) % entries.length; + do { + index = (index + 1) % entries.length; + } while (entries[index] == null); mSettingsManager.setValueIndex(SettingsManager.KEY_CAMERA_ID, index); int[] largeIcons = mSettingsManager.getResource(SettingsManager.KEY_CAMERA_ID, SettingsManager.RESOURCE_TYPE_LARGEICON); diff --git a/src/com/android/camera/ComboPreferences.java b/src/com/android/camera/ComboPreferences.java index 7d4d92087..24a5612c7 100644 --- a/src/com/android/camera/ComboPreferences.java +++ b/src/com/android/camera/ComboPreferences.java @@ -154,7 +154,6 @@ public class ComboPreferences implements || key.equals(CameraSettings.KEY_PHOTOSPHERE_PICTURESIZE) || key.equals(CameraSettings.KEY_CAMERA_SAVEPATH) || key.equals(SettingsManager.KEY_CAMERA2) - || key.equals(SettingsManager.KEY_INITIAL_CAMERA) || key.equals(SettingsManager.KEY_CAMERA_ID) || key.equals(SettingsManager.KEY_MONO_ONLY) || key.equals(SettingsManager.KEY_MONO_PREVIEW) diff --git a/src/com/android/camera/SettingsManager.java b/src/com/android/camera/SettingsManager.java index af26b27fe..cc7523265 100644 --- a/src/com/android/camera/SettingsManager.java +++ b/src/com/android/camera/SettingsManager.java @@ -30,11 +30,13 @@ package com.android.camera; import android.content.Context; +import android.content.SharedPreferences; import android.graphics.ImageFormat; import android.graphics.Rect; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.params.StreamConfigurationMap; import android.util.Log; import android.util.Range; @@ -58,6 +60,8 @@ import java.util.Set; public class SettingsManager implements ListMenu.SettingsListener { public static final int RESOURCE_TYPE_THUMBNAIL = 0; public static final int RESOURCE_TYPE_LARGEICON = 1; + public static final int SCENE_MODE_NIGHT_INT = 5; + // Custom-Scenemodes start from 100 public static final int SCENE_MODE_DUAL_INT = 100; public static final int SCENE_MODE_OPTIZOOM_INT = 101; @@ -83,7 +87,6 @@ public class SettingsManager implements ListMenu.SettingsListener { public static final String KEY_EXPOSURE = "pref_camera2_exposure_key"; public static final String KEY_TIMER = "pref_camera2_timer_key"; public static final String KEY_LONGSHOT = "pref_camera2_longshot_key"; - public static final String KEY_INITIAL_CAMERA = "pref_camera2_initial_camera_key"; private static final String TAG = "SnapCam_SettingsManager"; private static SettingsManager sInstance; @@ -171,7 +174,7 @@ public class SettingsManager implements ListMenu.SettingsListener { public void init() { Log.d(TAG, "SettingsManager init"); - int cameraId = CameraSettings.getInitialCameraId(mPreferences); + int cameraId = getInitialCameraId(mPreferences); setLocalIdAndInitialize(cameraId); } @@ -473,6 +476,15 @@ public class SettingsManager implements ListMenu.SettingsListener { return null; } + public int getInitialCameraId(SharedPreferences pref) { + String value = pref.getString(SettingsManager.KEY_CAMERA_ID, "0"); + int frontBackId = Integer.parseInt(value); + if (frontBackId == CaptureModule.FRONT_ID) return frontBackId; + String monoOnly = pref.getString(SettingsManager.KEY_MONO_ONLY, "off"); + if (monoOnly.equals("off")) return frontBackId; + else return CaptureModule.MONO_ID; + } + private void filterPreferences(int cameraId) { // filter unsupported preferences ListPreference whiteBalance = mPreferenceGroup.findPreference(KEY_WHITE_BALANCE); @@ -486,6 +498,7 @@ public class SettingsManager implements ListMenu.SettingsListener { ListPreference clearsight = mPreferenceGroup.findPreference(KEY_CLEARSIGHT); ListPreference monoPreview = mPreferenceGroup.findPreference(KEY_MONO_PREVIEW); ListPreference monoOnly = mPreferenceGroup.findPreference(KEY_MONO_ONLY); + ListPreference redeyeReduction = mPreferenceGroup.findPreference(KEY_REDEYE_REDUCTION); if (whiteBalance != null) { CameraSettings.filterUnsupportedOptions(mPreferenceGroup, @@ -511,6 +524,7 @@ public class SettingsManager implements ListMenu.SettingsListener { if (pictureSize != null) { CameraSettings.filterUnsupportedOptions(mPreferenceGroup, pictureSize, getSupportedPictureSize(cameraId)); + CameraSettings.filterSimilarPictureSize(mPreferenceGroup, pictureSize); } if (exposure != null) buildExposureCompensation(cameraId); @@ -524,7 +538,11 @@ public class SettingsManager implements ListMenu.SettingsListener { if (clearsight != null) removePreference(mPreferenceGroup, KEY_CLEARSIGHT); if (monoPreview != null) removePreference(mPreferenceGroup, KEY_MONO_PREVIEW); if (monoOnly != null) removePreference(mPreferenceGroup, KEY_MONO_ONLY); + } + if (redeyeReduction != null) { + CameraSettings.filterUnsupportedOptions(mPreferenceGroup, + redeyeReduction, getSupportedRedeyeReduction(cameraId)); } } @@ -687,6 +705,20 @@ public class SettingsManager implements ListMenu.SettingsListener { return res; } + private List<String> getSupportedRedeyeReduction(int cameraId) { + int[] flashModes = mCharacteristics.get(cameraId).get(CameraCharacteristics + .CONTROL_AE_AVAILABLE_MODES); + List<String> modes = new ArrayList<>(); + for (int i = 0; i < flashModes.length; i++) { + if (flashModes[i] == CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE) { + modes.add("disable"); + modes.add("enable"); + break; + } + } + return modes; + } + private List<String> getSupportedWhiteBalanceModes(int cameraId) { int[] whiteBalanceModes = mCharacteristics.get(cameraId).get(CameraCharacteristics .CONTROL_AWB_AVAILABLE_MODES); diff --git a/src/com/android/camera/exif/ExifInterface.java b/src/com/android/camera/exif/ExifInterface.java index 2fec1bf4f..773518821 100644 --- a/src/com/android/camera/exif/ExifInterface.java +++ b/src/com/android/camera/exif/ExifInterface.java @@ -1978,6 +1978,23 @@ public class ExifInterface { return true; } + public boolean addOrientationTag(int orientation) { + int value = Orientation.TOP_LEFT; + if(orientation == 90) { + value = Orientation.RIGHT_TOP; + } else if(orientation == 180) { + value = Orientation.BOTTOM_LEFT; + } else if(orientation == 270) { + value = Orientation.RIGHT_BOTTOM; + } + ExifTag t = buildTag(TAG_ORIENTATION, value); + if (t == null) { + return false; + } + setTag(t); + return true; + } + /** * Creates and sets all to the GPS tags for a give latitude and longitude. * diff --git a/src/com/android/camera/imageprocessor/FrameProcessor.java b/src/com/android/camera/imageprocessor/FrameProcessor.java index 951479de9..6a2091158 100644 --- a/src/com/android/camera/imageprocessor/FrameProcessor.java +++ b/src/com/android/camera/imageprocessor/FrameProcessor.java @@ -128,7 +128,7 @@ public class FrameProcessor { } } - private void createAllocation(int width, int height) { + private void createAllocation(int width, int height, int stridePad) { Type.Builder yuvTypeBuilder = new Type.Builder(mRs, Element.YUV(mRs)); yuvTypeBuilder.setX(width); yuvTypeBuilder.setY(height); @@ -141,6 +141,8 @@ public class FrameProcessor { mRsRotator.set_gOut(mProcessAllocation); mRsRotator.set_width(width); mRsRotator.set_height(height); + mRsRotator.set_pad(stridePad); + mRsRotator.set_gFlip(!mModule.isBackCamera()); mRsYuvToRGB.set_gIn(mProcessAllocation); mRsYuvToRGB.set_width(height); mRsYuvToRGB.set_height(width); @@ -266,6 +268,7 @@ public class FrameProcessor { int ySize; int stride; int height; + int width; public ProcessingTask() { } @@ -288,6 +291,7 @@ public class FrameProcessor { ByteBuffer bVU = image.getPlanes()[2].getBuffer(); if(yvuBytes == null) { stride = image.getPlanes()[0].getRowStride(); + width = mSize.getWidth(); height = mSize.getHeight(); ySize = stride * mSize.getHeight(); yvuBytes = new byte[ySize*3/2]; @@ -314,7 +318,7 @@ public class FrameProcessor { return; } if(mInputAllocation == null) { - createAllocation(stride, height); + createAllocation(stride, height, stride-width); } mInputAllocation.copyFrom(yvuBytes); mRsRotator.forEach_rotate90andMerge(mInputAllocation); diff --git a/src/com/android/camera/imageprocessor/PostProcessor.java b/src/com/android/camera/imageprocessor/PostProcessor.java index a126e8817..f39845346 100644 --- a/src/com/android/camera/imageprocessor/PostProcessor.java +++ b/src/com/android/camera/imageprocessor/PostProcessor.java @@ -43,17 +43,24 @@ import android.widget.Toast; import com.android.camera.CameraActivity; import com.android.camera.CaptureModule; +import com.android.camera.Exif; import com.android.camera.MediaSaveService; import com.android.camera.PhotoModule; import com.android.camera.SettingsManager; +import com.android.camera.exif.ExifInterface; import com.android.camera.imageprocessor.filter.OptizoomFilter; +import com.android.camera.imageprocessor.filter.SharpshooterFilter; import com.android.camera.ui.RotateTextToast; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.TimeZone; + import com.android.camera.imageprocessor.filter.ImageFilter; +import com.android.camera.util.CameraUtil; public class PostProcessor implements ImageReader.OnImageAvailableListener{ @@ -62,7 +69,8 @@ public class PostProcessor implements ImageReader.OnImageAvailableListener{ private static final String TAG = "PostProcessor"; public static final int FILTER_NONE = 0; public static final int FILTER_OPTIZOOM = 1; - public static final int FILTER_MAX = 2; + public static final int FILTER_SHARPSHOOTER = 2; + public static final int FILTER_MAX = 3; private int mCurrentNumImage = 0; private ImageFilter mFilter; @@ -80,7 +88,7 @@ public class PostProcessor implements ImageReader.OnImageAvailableListener{ private WatchdogThread mWatchdog; //This is for the debug feature. - private static boolean DEBUG_FILTER = true; //TODO: This has to be false before releasing. + private static boolean DEBUG_FILTER = false; private ImageFilter.ResultImage mDebugResultImage; @Override @@ -258,6 +266,9 @@ public class PostProcessor implements ImageReader.OnImageAvailableListener{ case FILTER_OPTIZOOM: mFilter = new OptizoomFilter(mController); break; + case FILTER_SHARPSHOOTER: + mFilter = new SharpshooterFilter(mController); + break; } } @@ -363,6 +374,20 @@ public class PostProcessor implements ImageReader.OnImageAvailableListener{ }); } + private byte[] addExifTags(byte[] jpeg, int orientationInDegree) { + ExifInterface exif = new ExifInterface(); + exif.addOrientationTag(orientationInDegree); + exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, System.currentTimeMillis(), + TimeZone.getDefault()); + ByteArrayOutputStream jpegOut = new ByteArrayOutputStream(); + try { + exif.writeExif(jpeg, jpegOut); + } catch (IOException e) { + Log.e(TAG, "Could not write EXIF", e); + } + return jpegOut.toByteArray(); + } + private void clear() { mCurrentNumImage = 0; } @@ -409,29 +434,32 @@ public class PostProcessor implements ImageReader.OnImageAvailableListener{ ) { Log.e(TAG, "Processed outRoi is not within picture range"); } else { + int orientation = CameraUtil.getJpegRotation(mController.getMainCameraId(), mController.getDisplayOrientation()); if(mFilter != null && DEBUG_FILTER) { - bytes = nv21ToJpeg(mDebugResultImage); + bytes = nv21ToJpeg(mDebugResultImage, orientation); mActivity.getMediaSaveService().addImage( bytes, title + "_beforeApplyingFilter", date, null, mDebugResultImage.outRoi.width(), mDebugResultImage.outRoi.height(), - 0, null, mediaSavedListener, contentResolver, "jpeg"); + orientation, null, mediaSavedListener, contentResolver, "jpeg"); } - bytes = nv21ToJpeg(resultImage); - mController.updateThumbnailJpegData(bytes); + bytes = nv21ToJpeg(resultImage, orientation); mActivity.getMediaSaveService().addImage( bytes, title, date, null, resultImage.outRoi.width(), resultImage.outRoi.height(), - 0, null, mediaSavedListener, contentResolver, "jpeg"); + orientation, null, mediaSavedListener, contentResolver, "jpeg"); + mController.updateThumbnailJpegData(bytes); } } } }); } - private byte[] nv21ToJpeg(ImageFilter.ResultImage resultImage) { + private byte[] nv21ToJpeg(ImageFilter.ResultImage resultImage, int orientation) { BitmapOutputStream bos = new BitmapOutputStream(1024); YuvImage im = new YuvImage(resultImage.outBuffer.array(), ImageFormat.NV21, resultImage.width, resultImage.height, new int[]{resultImage.stride, resultImage.stride}); im.compressToJpeg(resultImage.outRoi, 50, bos); - return bos.getArray(); + byte[] bytes = bos.getArray(); + bytes = addExifTags(bytes, orientation); + return bytes; } private class BitmapOutputStream extends ByteArrayOutputStream { diff --git a/src/com/android/camera/imageprocessor/filter/OptizoomFilter.java b/src/com/android/camera/imageprocessor/filter/OptizoomFilter.java index 4773418de..9b5af29dc 100644 --- a/src/com/android/camera/imageprocessor/filter/OptizoomFilter.java +++ b/src/com/android/camera/imageprocessor/filter/OptizoomFilter.java @@ -45,7 +45,7 @@ public class OptizoomFilter implements ImageFilter{ private int mStrideY; private int mStrideVU; private static String TAG = "OptizoomFilter"; - private static final boolean DEBUG = true; //TODO: Have to be false before releasing. + private static final boolean DEBUG = false; private int temp; private static boolean mIsSupported = true; private ByteBuffer mOutBuf; diff --git a/src/com/android/camera/imageprocessor/filter/SharpshooterFilter.java b/src/com/android/camera/imageprocessor/filter/SharpshooterFilter.java new file mode 100644 index 000000000..74469afc3 --- /dev/null +++ b/src/com/android/camera/imageprocessor/filter/SharpshooterFilter.java @@ -0,0 +1,172 @@ +/* +Copyright (c) 2016, 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.imageprocessor.filter; + +import android.graphics.Rect; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.util.Log; +import android.util.Range; +import android.util.Rational; + +import com.android.camera.CaptureModule; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class SharpshooterFilter implements ImageFilter{ + public static final int NUM_REQUIRED_IMAGE = 5; + private int mWidth; + private int mHeight; + private int mStrideY; + private int mStrideVU; + private static String TAG = "SharpshooterFilter"; + private static final boolean DEBUG = false; + private int temp; + private static boolean mIsSupported = true; + private ByteBuffer mOutBuf; + private CaptureModule mModule; + private int mSenseValue = 0; + private long mExpoTime; + + private static void Log(String msg) { + if(DEBUG) { + Log.d(TAG, msg); + } + } + + public SharpshooterFilter(CaptureModule module) { + mModule = module; + } + + private void getSenseUpperValue() { + if(mSenseValue == 0) { + Range<Integer> sensRange = mModule.getMainCameraCharacteristics().get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE); + mSenseValue = sensRange.getUpper(); + } + } + @Override + public List<CaptureRequest> setRequiredImages(CaptureRequest.Builder builder) { + getSenseUpperValue(); + mExpoTime = (mModule.getPreviewCaptureResult().get(CaptureResult.SENSOR_EXPOSURE_TIME)/2); + int isoValue = (mModule.getPreviewCaptureResult().get(CaptureResult.SENSOR_SENSITIVITY)).intValue()*2; + if(isoValue < mSenseValue) { + mSenseValue = isoValue; + } + + List<CaptureRequest> list = new ArrayList<CaptureRequest>(); + for(int i=0; i < NUM_REQUIRED_IMAGE; i++) { + builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF); + builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, new Long(mExpoTime)); + builder.set(CaptureRequest.SENSOR_SENSITIVITY, mSenseValue); + list.add(builder.build()); + } + return list; + } + + @Override + public String getStringName() { + return "SharpshooterFilter"; + } + + @Override + public int getNumRequiredImage() { + return NUM_REQUIRED_IMAGE; + } + + @Override + public void init(int width, int height, int strideY, int strideVU) { + Log("init"); + mWidth = width/2*2; + mHeight = height/2*2; + mStrideY = strideY/2*2; + mStrideVU = strideVU/2*2; + mOutBuf = ByteBuffer.allocate(mStrideY*mHeight*3/2); + Log("width: "+mWidth+" height: "+mHeight+" strideY: "+mStrideY+" strideVU: "+mStrideVU); + nativeInit(mWidth, mHeight, mStrideY, mStrideVU, + 0, 0, mWidth, mHeight, NUM_REQUIRED_IMAGE); + } + + @Override + public void deinit() { + Log("deinit"); + mOutBuf = null; + nativeDeinit(); + } + + @Override + public void addImage(ByteBuffer bY, ByteBuffer bVU, int imageNum, Object param) { + Log("addImage"); + int yActualSize = bY.remaining(); + int vuActualSize = bVU.remaining(); + int status = nativeAddImage(bY, bVU, yActualSize, vuActualSize, imageNum); + if(status != 0) { + Log.e(TAG, "Fail to add image"); + } + } + + @Override + public ResultImage processImage() { + Log("processImage "); + int[] roi = new int[4]; + int status = nativeProcessImage(mOutBuf.array(), (int) (mExpoTime / 1000000), mSenseValue, roi); + Log("processImage done"); + if(status < 0) { //In failure case, library will return the first image as it is. + Log.w(TAG, "Fail to process the image."); + } + return new ResultImage(mOutBuf, new Rect(roi[0], roi[1], roi[0]+roi[2], roi[1] + roi[3]), mWidth, mHeight, mStrideY); + } + + @Override + public boolean isSupported() { + return mIsSupported; + } + + public static boolean isSupportedStatic() { + return mIsSupported; + } + + private native int nativeInit(int width, int height, int yStride, int vuStride, + int roiX, int roiY, int roiW, int roiH, int numImages); + private native int nativeDeinit(); + private native int nativeAddImage(ByteBuffer yB, ByteBuffer vuB, int ySize, int vuSize, int imageNum); + private native int nativeProcessImage(byte[] buffer, int expoTime, int isoValue, int[] roi); + + static { + try { + System.loadLibrary("jni_sharpshooter"); + mIsSupported = true; + }catch(UnsatisfiedLinkError e) { + Log.d(TAG, e.toString()); + mIsSupported = false; + } + } +} diff --git a/src/com/android/camera/mpo/MpoOutputStream.java b/src/com/android/camera/mpo/MpoOutputStream.java index 6e8e72fd9..5d01d269f 100644 --- a/src/com/android/camera/mpo/MpoOutputStream.java +++ b/src/com/android/camera/mpo/MpoOutputStream.java @@ -40,6 +40,7 @@ class MpoOutputStream extends FilterOutputStream { private static final int STATE_SOI = 0; private static final int STATE_FRAME_HEADER = 1; + private static final int STATE_SKIP_CROP = 2; private static final int STATE_JPEG_DATA = 3; private static final short TIFF_HEADER = 0x002A; @@ -47,6 +48,9 @@ class MpoOutputStream extends FilterOutputStream { private static final short TIFF_LITTLE_ENDIAN = 0x4949; private static final int MAX_EXIF_SIZE = 65535; + private static final String DC_CROP_INFO = "Qualcomm Dual Camera Attributes"; + private static final int DC_CROP_INFO_BYTE_SIZE = DC_CROP_INFO.length(); + private MpoData mMpoData; private MpoImageData mCurrentImageData; private int mState = STATE_SOI; @@ -54,8 +58,10 @@ class MpoOutputStream extends FilterOutputStream { private int mByteToCopy; private byte[] mSingleByteArray = new byte[1]; private ByteBuffer mBuffer = ByteBuffer.allocate(4); + private ByteBuffer mCropInfo = ByteBuffer.allocate(DC_CROP_INFO_BYTE_SIZE); private int mMpoOffsetStart = -1; private int mSize = 0; + private boolean mSkipCropData = false; protected MpoOutputStream(OutputStream ou) { super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE)); @@ -77,19 +83,41 @@ class MpoOutputStream extends FilterOutputStream { mBuffer.rewind(); } - private int requestByteToBuffer(int requestByteCount, byte[] buffer, int offset, int length) { - int byteNeeded = requestByteCount - mBuffer.position(); + private int requestByteToBuffer(ByteBuffer buffer, int requestByteCount, byte[] data, int offset, int length) { + int byteNeeded = requestByteCount - buffer.position(); int byteToRead = length > byteNeeded ? byteNeeded : length; - mBuffer.put(buffer, offset, byteToRead); + buffer.put(data, offset, byteToRead); return byteToRead; } + private boolean isDualCamCropInfo() { + // first check length + if(mCropInfo.position() != DC_CROP_INFO_BYTE_SIZE) { + return false; + } + + mCropInfo.rewind(); + for(int i = 0; i < DC_CROP_INFO.length(); i++) { + char c = (char)mCropInfo.get(i); + //Log.d(TAG, "mCropInfo char @ " + (i) + ": " + c); + if(DC_CROP_INFO.charAt(i) != c) + return false; + } + + return true; + } + void writeMpoFile() throws IOException { // check and write primary image mCurrentImageData = mMpoData.getPrimaryMpoImage(); + // don't skip if primary == bayer + if(mMpoData.getAuxiliaryImageCount() > 1) { + mSkipCropData = true; + } write(mCurrentImageData.getJpegData()); flush(); + mSkipCropData = false; // check and write auxiliary images for (MpoImageData image : mMpoData.getAuxiliaryMpoImages()) { resetStates(); @@ -125,7 +153,7 @@ class MpoOutputStream extends FilterOutputStream { } switch (mState) { case STATE_SOI: - int byteRead = requestByteToBuffer(2, buffer, offset, length); + int byteRead = requestByteToBuffer(mBuffer, 2, buffer, offset, length); offset += byteRead; length -= byteRead; if (mBuffer.position() < 2) { @@ -141,12 +169,9 @@ class MpoOutputStream extends FilterOutputStream { mBuffer.rewind(); break; case STATE_FRAME_HEADER: - // Copy APP1 if it exists + // Copy APP0 and APP1 if it exists // Insert MPO data - // Copy remainder of image - byteRead = requestByteToBuffer(4, buffer, offset, length); - offset += byteRead; - length -= byteRead; + byteRead = requestByteToBuffer(mBuffer, 4, buffer, offset, length); // Check if this image data doesn't contain SOF. if (mBuffer.position() == 2) { short tag = mBuffer.getShort(); @@ -165,8 +190,63 @@ class MpoOutputStream extends FilterOutputStream { out.write(mBuffer.array(), 0, 4); mSize += 4; mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2; + offset += byteRead; + length -= byteRead; } else { writeMpoData(); + if(mSkipCropData) + mState = STATE_SKIP_CROP; + else + mState = STATE_JPEG_DATA; + } + mBuffer.rewind(); + break; + case STATE_SKIP_CROP: + byteRead = requestByteToBuffer(mBuffer, 4, buffer, offset, length); + // Check if this image data doesn't contain SOF. + if (mBuffer.position() == 2) { + short tag = mBuffer.getShort(); + if (tag == JpegHeader.EOI) { + out.write(mBuffer.array(), 0, 2); + mSize += 2; + mBuffer.rewind(); + } + } + if (mBuffer.position() < 4) { + return; + } + + offset += byteRead; + length -= byteRead; + mBuffer.rewind(); + + marker = mBuffer.getShort(); + if (!JpegHeader.isSofMarker(marker)) { + // if not SOF, read first 31 bytes + // try to match dual cam crop magic string + byteRead = requestByteToBuffer(mCropInfo, DC_CROP_INFO_BYTE_SIZE, buffer, offset, length); + if(isDualCamCropInfo()) { + // if crop info, clear with 0 + out.write(mBuffer.array(), 0, 4); + mSize += 4; + + int sizeToClear = mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2; + while(sizeToClear > 0) { + out.write(0); + mSize++; + sizeToClear--; + } + mState = STATE_JPEG_DATA; + } else { + // else copy this block + // and move on to next header + out.write(mBuffer.array(), 0, 4); + mSize += 4; + mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2; + } + mCropInfo.rewind(); + } else { + // SOF is reached, no crop info detected, skip out.write(mBuffer.array(), 0, 4); mSize += 4; mState = STATE_JPEG_DATA; |