diff options
author | Jack Yoo <jyoo@codeaurora.org> | 2016-02-10 17:22:09 -0800 |
---|---|---|
committer | Jay Wang <jaywang@codeaurora.org> | 2016-09-27 15:54:51 -0700 |
commit | 7dd609bbc372c2bbeb9e3c10fe567e085da4f15d (patch) | |
tree | c947d9f35a0bfded03cea094ab81f222afde7258 /src | |
parent | 13f5f043eca7b0b6487f2809a06a010c268648b6 (diff) | |
download | android_packages_apps_Snap-7dd609bbc372c2bbeb9e3c10fe567e085da4f15d.tar.gz android_packages_apps_Snap-7dd609bbc372c2bbeb9e3c10fe567e085da4f15d.tar.bz2 android_packages_apps_Snap-7dd609bbc372c2bbeb9e3c10fe567e085da4f15d.zip |
SnapdragonCamera: Panorama Module
Introducing Panorama capture module for Camera2
Change-Id: I98306c6c88c34c58b99adf9d472418392015f6c5
CRs-Fixed: 1067848
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/camera/CameraActivity.java | 24 | ||||
-rw-r--r-- | src/com/android/camera/CaptureModule.java | 18 | ||||
-rw-r--r-- | src/com/android/camera/PanoCaptureFrameProcessor.java | 208 | ||||
-rw-r--r-- | src/com/android/camera/PanoCaptureModule.java | 752 | ||||
-rw-r--r-- | src/com/android/camera/PanoCaptureUI.java | 383 | ||||
-rw-r--r-- | src/com/android/camera/SettingsManager.java | 3 | ||||
-rw-r--r-- | src/com/android/camera/ui/CameraControls.java | 36 | ||||
-rw-r--r-- | src/com/android/camera/ui/ModuleSwitcher.java | 3 | ||||
-rw-r--r-- | src/com/android/camera/ui/PanoCaptureProcessView.java | 1097 |
9 files changed, 2518 insertions, 6 deletions
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index f5fe903da..749716254 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -85,6 +85,7 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.ShareActionProvider; +import android.widget.Toast; import com.android.camera.app.AppManagerFactory; import com.android.camera.app.PlaceholderManager; @@ -106,6 +107,8 @@ import com.android.camera.ui.ModuleSwitcher; import com.android.camera.ui.DetailsDialog; import com.android.camera.ui.FilmStripView; import com.android.camera.ui.FilmStripView.ImageData; +import com.android.camera.ui.PanoCaptureProcessView; +import com.android.camera.ui.RotateTextToast; import com.android.camera.util.ApiHelper; import com.android.camera.util.CameraUtil; import com.android.camera.util.GcamHelper; @@ -199,6 +202,7 @@ public class CameraActivity extends Activity private VideoModule mVideoModule; private WideAnglePanoramaModule mPanoModule; private CaptureModule mCaptureModule; + private PanoCaptureModule mPano2Module; private FrameLayout mAboveFilmstripControlLayout; private FrameLayout mCameraRootFrame; private View mCameraPhotoModuleRootView; @@ -2095,6 +2099,26 @@ public class CameraActivity extends Activity mCurrentModule = mCaptureModule; mCameraCaptureModuleRootView.setVisibility(View.VISIBLE); break; + + case ModuleSwitcher.PANOCAPTURE_MODULE_INDEX: + final Activity activity = this; + if(!PanoCaptureProcessView.isSupportedStatic()) { + this.runOnUiThread(new Runnable() { + public void run() { + RotateTextToast.makeText(activity, "Panocapture library is missing", Toast.LENGTH_SHORT).show(); + } + }); + mCurrentModuleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX; + //Let it fall through to photo module + } else { + if (mPano2Module == null) { + mPano2Module = new PanoCaptureModule(); + mPano2Module.init(this, mCameraPanoModuleRootView); + } + mCurrentModule = mPano2Module; + mCameraPanoModuleRootView.setVisibility(View.VISIBLE); + break; + } case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX: //Unused module for now case ModuleSwitcher.GCAM_MODULE_INDEX: //Unused module for now default: diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java index 64d111678..69cdecfc5 100644 --- a/src/com/android/camera/CaptureModule.java +++ b/src/com/android/camera/CaptureModule.java @@ -1880,10 +1880,10 @@ public class CaptureModule implements CameraModule, PhotoController, mSound = new MediaActionSound(); } + String scene = mSettingsManager.getValue(SettingsManager.KEY_SCENE_MODE); if(mPostProcessor != null) { String longshot = mSettingsManager.getValue(SettingsManager.KEY_LONGSHOT); String flashMode = mSettingsManager.getValue(SettingsManager.KEY_FLASH_MODE); - 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)); @@ -1937,6 +1937,11 @@ public class CaptureModule implements CameraModule, PhotoController, } }); mUI.enableShutter(true); + + if(isPanoSetting(scene)) { + mActivity.onModuleSelected(ModuleSwitcher.PANOCAPTURE_MODULE_INDEX); + mSettingsManager.setValue(SettingsManager.KEY_SCENE_MODE, SettingsManager.SCENE_MODE_AUTO_INT+""); + } } @Override @@ -3389,6 +3394,17 @@ public class CaptureModule implements CameraModule, PhotoController, } } + private boolean isPanoSetting(String value) { + try { + int mode = Integer.parseInt(value); + if(mode == SettingsManager.SCENE_MODE_PANORAMA_INT) { + return true; + } + } catch(Exception e) { + } + return false; + } + private void updateFaceDetection() { final String value = mSettingsManager.getValue(SettingsManager.KEY_FACE_DETECTION); mActivity.runOnUiThread(new Runnable() { diff --git a/src/com/android/camera/PanoCaptureFrameProcessor.java b/src/com/android/camera/PanoCaptureFrameProcessor.java new file mode 100644 index 000000000..5aae1e2ca --- /dev/null +++ b/src/com/android/camera/PanoCaptureFrameProcessor.java @@ -0,0 +1,208 @@ +/* + * 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; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.ImageFormat; +import android.graphics.Rect; +import android.graphics.YuvImage; +import android.os.Handler; +import android.os.HandlerThread; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicYuvToRGB; +import android.renderscript.Type; +import android.util.Log; +import android.util.Size; +import android.view.Surface; + +import com.android.camera.ui.PanoCaptureProcessView; + +import java.io.ByteArrayOutputStream; + +public class PanoCaptureFrameProcessor { + + private Allocation mInputAllocation; + private Allocation mARGBOutputAllocation; + + private Surface mSurface; + private HandlerThread mProcessingThread; + private Handler mProcessingHandler; + + public ProcessingTask mTask; + private Size mSize; + private RenderScript mRs; + private PanoCaptureUI mUI; + private Activity mActivity; + private PanoCaptureModule mController; + ScriptIntrinsicYuvToRGB mRsYuvToRGB; + private Bitmap mBitmap; + private boolean mIsPanoActive = false; + private Object mPanoSwitchLock = new Object(); + private Object mAllocationLock = new Object(); + private boolean mIsAllocationEverUsed; + + public PanoCaptureFrameProcessor(Size dimensions, Activity activity, PanoCaptureUI ui, PanoCaptureModule controller) { + mUI = ui; + mSize = dimensions; + mActivity = activity; + mController = controller; + synchronized (mAllocationLock) { + mRs = RenderScript.create(mActivity); + mRsYuvToRGB = ScriptIntrinsicYuvToRGB.create(mRs, Element.RGBA_8888(mRs)); + + Type.Builder yuvTypeBuilder = new Type.Builder(mRs, Element.YUV(mRs)); + yuvTypeBuilder.setX(dimensions.getWidth()); + yuvTypeBuilder.setY(dimensions.getHeight()); + yuvTypeBuilder.setYuvFormat(ImageFormat.YUV_420_888); + mInputAllocation = Allocation.createTyped(mRs, yuvTypeBuilder.create(), + Allocation.USAGE_IO_INPUT | Allocation.USAGE_SCRIPT); + + Type.Builder rgbTypeBuilder = new Type.Builder(mRs, Element.RGBA_8888(mRs)); + rgbTypeBuilder.setX(dimensions.getWidth()); + rgbTypeBuilder.setY(dimensions.getHeight()); + mARGBOutputAllocation = Allocation.createTyped(mRs, rgbTypeBuilder.create(), Allocation.USAGE_SCRIPT); + + if (mProcessingThread == null) { + mProcessingThread = new HandlerThread("PanoCapture_FrameProcessor"); + mProcessingThread.start(); + mProcessingHandler = new Handler(mProcessingThread.getLooper()); + } + mTask = new ProcessingTask(); + mInputAllocation.setOnBufferAvailableListener(mTask); + mIsAllocationEverUsed = false; + } + } + + public void clear() { + if(mIsPanoActive) { + changePanoStatus(false, true); + } + synchronized (mAllocationLock) { + mInputAllocation.setOnBufferAvailableListener(null); + if(mIsAllocationEverUsed) { + mRs.destroy(); + mInputAllocation.destroy(); + mARGBOutputAllocation.destroy(); + } + mRs = null; + mInputAllocation = null; + mARGBOutputAllocation = null; + } + mProcessingThread.quitSafely(); + try { + mProcessingThread.join(); + mProcessingThread = null; + mProcessingHandler = null; + } catch (InterruptedException e) { + } + } + + public Surface getInputSurface() { + synchronized (mAllocationLock) { + if (mInputAllocation == null) + return null; + return mInputAllocation.getSurface(); + } + } + + public void changePanoStatus(boolean newStatus, boolean isCancelling) { + if(newStatus == mIsPanoActive) { + return; + } + synchronized (mPanoSwitchLock) { + if(mUI.isPanoCompleting()) { + return; + } + mIsPanoActive = newStatus; + if (!mIsPanoActive) { + mUI.onFrameAvailable(null, isCancelling); + } + } + if(!mIsPanoActive) { + mController.unlockFocus(); + } + } + + public boolean isPanoActive() { + return mIsPanoActive; + } + + class ProcessingTask implements Runnable, Allocation.OnBufferAvailableListener { + private int mPendingFrames = 0; + private int mFrameCounter = 0; + + public ProcessingTask() { + mBitmap = Bitmap.createBitmap(mSize.getWidth(), mSize.getHeight(), Bitmap.Config.ARGB_8888); + } + + @Override + public void onBufferAvailable(Allocation a) { + if(mProcessingHandler == null) + return; + synchronized(this) { + mPendingFrames++; + mProcessingHandler.post(this); + } + } + + @Override + public void run() { + int pendingFrames; + synchronized(this) { + pendingFrames = mPendingFrames; + mPendingFrames = 0; + mProcessingHandler.removeCallbacks(this); + } + + synchronized (mAllocationLock) { + if(mInputAllocation == null || mARGBOutputAllocation == null) + return; + mIsAllocationEverUsed = true; + for (int i = 0; i < pendingFrames; i++) { + mInputAllocation.ioReceive(); + } + synchronized (mPanoSwitchLock) { + if (mIsPanoActive && !mUI.isFrameProcessing()) { + mRsYuvToRGB.setInput(mInputAllocation); + mRsYuvToRGB.forEach(mARGBOutputAllocation); + mARGBOutputAllocation.copyTo(mBitmap); + mUI.onFrameAvailable(mBitmap, false); + } + } + } + } + } + +} + diff --git a/src/com/android/camera/PanoCaptureModule.java b/src/com/android/camera/PanoCaptureModule.java new file mode 100644 index 000000000..7a9f7a705 --- /dev/null +++ b/src/com/android/camera/PanoCaptureModule.java @@ -0,0 +1,752 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a Contribution. + * + * 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.camera; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.ImageFormat; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.location.Location; +import android.media.Image; +import android.media.ImageReader; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Log; +import android.util.Size; +import android.util.SparseIntArray; +import android.view.Display; +import android.view.KeyEvent; +import android.view.OrientationEventListener; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.View; +import android.widget.Toast; + +import com.android.camera.PhotoModule.NamedImages; +import com.android.camera.PhotoModule.NamedImages.NamedEntity; +import com.android.camera.data.LocalData; +import com.android.camera.exif.ExifInterface; +import com.android.camera.ui.RotateTextToast; +import com.android.camera.util.CameraUtil; + +import org.codeaurora.snapcam.R; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.TimeZone; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +public class PanoCaptureModule implements CameraModule, PhotoController { + /** + * Camera state: Showing camera preview. + */ + private static final int STATE_PREVIEW = 0; + + private static final String TAG = "SnapCam_PanoCaptureModule"; + + private static final int BAYER_CAMERA_ID = 0; + private CaptureRequest.Builder mPreviewRequestBuilder; + private CaptureRequest mPreviewRequest; + private int mState = STATE_PREVIEW; + private Semaphore mCameraOpenCloseLock = new Semaphore(1); + + private boolean mSurfaceReady = false; + private boolean mCameraOpened = false; + private CameraDevice mCameraDevice; + private String mCameraId; + private PanoCaptureUI mUI; + private CameraActivity mActivity; + + private CameraCaptureSession mCaptureSession; + + private HandlerThread mCameraThread; + + private Handler mCameraHandler; + + private ContentResolver mContentResolver; + private Size mOutputSize; + private PanoCaptureFrameProcessor mFrameProcessor; + private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; + private LocationManager mLocationManager; + private Object mSessionLock = new Object(); + public static final float TARGET_RATIO = 4f/3f; + private static final int STATE_WAITING_LOCK = 1; + private Semaphore mFocusLockSemaphore = new Semaphore(1); + private boolean mIsLockFocusAttempted = false; + + private CameraCaptureSession.CaptureCallback mCaptureCallback + = new CameraCaptureSession.CaptureCallback() { + + private void process(CaptureResult result) { + switch (mState) { + case STATE_PREVIEW: { + break; + } + case STATE_WAITING_LOCK: { + Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + Log.d(TAG, "STATE_WAITING_LOCK afState:" + afState + " aeState:" + aeState); + if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || + CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { + changePanoStatus(true, false); + mState = STATE_PREVIEW; + } + break; + } + } + } + + @Override + public void onCaptureProgressed(CameraCaptureSession session, + CaptureRequest request, + CaptureResult partialResult) { + } + + @Override + public void onCaptureCompleted(CameraCaptureSession session, + CaptureRequest request, + TotalCaptureResult result) { + process(result); + } + + }; + private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { + + @Override + public void onOpened(CameraDevice cameraDevice) { + mCameraOpenCloseLock.release(); + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mUI.onCameraOpened(); + } + + + }); + mCameraDevice = cameraDevice; + mCameraOpened = true; + createSession(); + } + + @Override + public void onDisconnected(CameraDevice cameraDevice) { + mCameraOpenCloseLock.release(); + cameraDevice.close(); + mCameraDevice = null; + } + + @Override + public void onError(CameraDevice cameraDevice, int error) { + int id = Integer.parseInt(cameraDevice.getId()); + mCameraOpenCloseLock.release(); + cameraDevice.close(); + mCameraDevice = null; + if (null != mActivity) { + mActivity.finish(); + } + } + + }; + + private void closeSession() { + synchronized (mSessionLock) { + if (mFrameProcessor != null) { + mFrameProcessor.clear(); + mFrameProcessor = null; + } + } + } + + private void createSession() { + if (!mCameraOpened || !mSurfaceReady) return; + synchronized (mSessionLock) { + List<Surface> list = new LinkedList<Surface>(); + try { + Surface surface = null; + SurfaceHolder sh = mUI.getSurfaceHolder(); + if (sh != null) { + surface = sh.getSurface(); + } + if (surface == null) + return; + + if(mFrameProcessor == null) { + mFrameProcessor = new PanoCaptureFrameProcessor(mOutputSize, mActivity, mUI, this); + } + + mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice + .TEMPLATE_PREVIEW); + mPreviewRequestBuilder.addTarget(mFrameProcessor.getInputSurface()); + mPreviewRequestBuilder.addTarget(surface); + + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + mPreviewRequest = mPreviewRequestBuilder.build(); + list.add(surface); + list.add(mFrameProcessor.getInputSurface()); + mCameraDevice.createCaptureSession(list, + new CameraCaptureSession.StateCallback() { + + @Override + public void onConfigured(CameraCaptureSession cameraCaptureSession) { + if (null == mCameraDevice) { + Log.e(TAG, "The camera is already closed."); + return; + } + // When the session is ready, we start displaying the preview. + mCaptureSession = cameraCaptureSession; + try { + mCaptureSession.setRepeatingRequest(mPreviewRequest, + mCaptureCallback, mCameraHandler); + } catch (CameraAccessException e) { + Log.e(TAG, "createCaptureSession: " + e.toString()); + } + } + + @Override + public void onConfigureFailed( + CameraCaptureSession cameraCaptureSession) { + Log.e(TAG, "Capture session configuration is failed"); + } + }, null + ); + } catch (CameraAccessException e) { + Log.e(TAG, "createSession: " + e.toString()); + mActivity.finish(); + } catch (SecurityException e) { + Log.e(TAG, "createSession: " + e.toString()); + mActivity.finish(); + } + } + } + + @Override + public void init(CameraActivity activity, View parent) { + mCameraOpened = false; + mSurfaceReady = false; + mActivity = activity; + + mUI = new PanoCaptureUI(activity, this, parent); + mContentResolver = mActivity.getContentResolver(); + mLocationManager = new LocationManager(mActivity, null); + } + + public void changePanoStatus(boolean newStatus, boolean isCancelling) { + if(mFrameProcessor != null) { + mFrameProcessor.changePanoStatus(newStatus, isCancelling); + } + } + + public boolean isPanoActive() { + if(mFrameProcessor != null) { + return mFrameProcessor.isPanoActive(); + } + return false; + } + + private void setUpCameraOutputs() { + Activity activity = mActivity; + CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); + try { + String[] cameraIdList = manager.getCameraIdList(); + String cameraId = cameraIdList[BAYER_CAMERA_ID]; + CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); + StreamConfigurationMap map = characteristics.get( + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + if (map == null) { + return; + } + + Display display = mActivity.getWindowManager().getDefaultDisplay(); + Point ds = new Point(); + display.getSize(ds); + mOutputSize = getOutputSize(TARGET_RATIO, map.getOutputSizes(ImageFormat.YUV_420_888), ds.x, ds.y); + mCameraId = cameraId; + } catch (CameraAccessException e) { + Log.e(TAG, "setUpCameraOutputs: " + e.toString()); + } + } + + private Size getOutputSize(float ratio, Size[] prevSizes, int screenW, int + screenH) { + Size optimal = prevSizes[0]; + for (Size prevSize: prevSizes) { + float prevRatio = (float) prevSize.getWidth() / prevSize.getHeight(); + if (Math.abs(prevRatio - ratio) < 0.01) { + if (prevSize.getWidth() <= screenH && prevSize.getHeight() <= screenW) { + return prevSize; + } else { + optimal = prevSize; + } + } + } + return optimal; + } + + public Size getPictureOutputSize() { + return mOutputSize; + } + + /** + * Closes the current {@link CameraDevice}. + */ + private void closeCamera() { + boolean wasPreviousCameraOpenFailed = false; + try { + if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { + Log.d(TAG, "Time out waiting to lock camera closing."); + wasPreviousCameraOpenFailed = true; + } + if (null != mCaptureSession) { + mCaptureSession.close(); + mCaptureSession = null; + } + if (null != mCameraDevice) { + mCameraDevice.close(); + mCameraDevice = null; + mCameraOpened = false; + } + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted while trying to lock camera closing.", e); + } finally { + mCameraOpenCloseLock.release(); + if(wasPreviousCameraOpenFailed) { + mActivity.finish(); + } + } + } + + /** + * Starts a background thread and its {@link Handler}. + */ + private void startBackgroundThread() { + mCameraThread = new HandlerThread("CameraBackground"); + mCameraThread.start(); + mCameraHandler = new Handler(mCameraThread.getLooper()); + } + + /** + * Stops the background thread and its {@link Handler}. + */ + private void stopBackgroundThread() { + mCameraThread.quitSafely(); + try { + mCameraThread.join(); + mCameraThread = null; + mCameraHandler = null; + } catch (InterruptedException e) { + //Ignore this + } + } + + private void openCamera() { + CameraManager manager; + try { + manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE); + mCameraId = manager.getCameraIdList()[BAYER_CAMERA_ID]; + if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { + Log.d(TAG, "Time out waiting to lock camera opening."); + } + manager.openCamera(mCameraId, mStateCallback, mCameraHandler); + } catch (SecurityException e) { + Log.e(TAG, "openCamera: " + e.toString()); + Toast.makeText(mActivity, "Can't open camera, please restart it", Toast.LENGTH_LONG).show(); + mActivity.finish(); + } catch (CameraAccessException e) { + Log.e(TAG, "openCamera: " + e.toString()); + Toast.makeText(mActivity, "Can't open camera, please restart it", Toast.LENGTH_LONG).show(); + mActivity.finish(); + } catch (InterruptedException e) { + Log.e(TAG, "openCamera: " + e.toString()); + } + } + + @Override + public void onPreviewFocusChanged(boolean previewFocused) { + + } + + @Override + public void onPauseBeforeSuper() { + mUI.applySurfaceChange(0, false); + } + + @Override + public void onPauseAfterSuper() { + stopBackgroundThread(); + closeCamera(); + mUI.onPause(); + } + + @Override + public void onResumeBeforeSuper() { + + } + + @Override + public void onResumeAfterSuper() { + mUI.onResume(); + openCamera(); + setUpCameraOutputs(); + mUI.applySurfaceChange(2, false); + mUI.setLayout(mOutputSize); + startBackgroundThread(); + mUI.enableShutter(true); + mUI.setSwitcherIndex(); + mUI.initializeShutterButton(); + } + + @Override + public void onConfigurationChanged(Configuration config) { + + } + + @Override + public void onStop() { + + } + + public Uri savePanorama(byte[] jpegData, int width, int height, int orientation) { + long timeTaken = System.currentTimeMillis(); + + if (jpegData != null) { + String filename = PanoUtil.createName( + mActivity.getResources().getString(R.string.pano_file_name_format), timeTaken); + String filepath = Storage.generateFilepath(filename, + PhotoModule.PIXEL_FORMAT_JPEG); + + Location loc = mLocationManager.getCurrentLocation(); + ExifInterface exif = new ExifInterface(); + try { + exif.readExif(jpegData); + exif.addGpsDateTimeStampTag(timeTaken); + exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, timeTaken, + TimeZone.getDefault()); + exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,orientation)); + writeLocation(loc, exif); + exif.writeExif(jpegData, filepath); + } catch (IOException e) { + Log.e(TAG, "Cannot set exif for " + filepath, e); + Storage.writeFile(filepath, jpegData); + } + int jpegLength = (int) (new File(filepath).length()); + return Storage.addImage(mContentResolver, filename, timeTaken, loc, orientation, + jpegLength, filepath, width, height, LocalData.MIME_TYPE_JPEG); + } + return null; + } + + private static void writeLocation(Location location, ExifInterface exif) { + if (location == null) { + return; + } + exif.addGpsTags(location.getLatitude(), location.getLongitude()); + exif.setTag(exif.buildTag(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider())); + } + + @Override + public void installIntentFilter() { + + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return false; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return false; + } + + @Override + public int onZoomChanged(int requestedZoom) { + return 0; + } + + @Override + public void onZoomChanged(float requestedZoom) { + + } + + @Override + public boolean isImageCaptureIntent() { + return false; + } + + @Override + public boolean isCameraIdle() { + return false; + } + + @Override + public void onCaptureDone() { + + } + + @Override + public void onCaptureCancelled() { + + } + + @Override + public void onCaptureRetake() { + + } + + @Override + public void cancelAutoFocus() { + + } + + @Override + public void stopPreview() { + + } + + @Override + public int getCameraState() { + return 0; + } + + @Override + public void onSingleTapUp(View view, int x, int y) { + + } + + @Override + public void onCountDownFinished() { + + } + + @Override + public void onScreenSizeChanged(int width, int height) { + + } + + @Override + public void onPreviewRectChanged(Rect previewRect) { + + } + + @Override + public void updateCameraOrientation() { + + } + + @Override + public void enableRecordingLocation(boolean enable) { + + } + + @Override + public void setPreferenceForTest(String key, String value) { + } + + @Override + public void onPreviewUIReady() { + mSurfaceReady = true; + createSession(); + } + + @Override + public void onPreviewUIDestroyed() { + closeSession(); + } + + @Override + public void onPreviewTextureCopied() { + + } + + @Override + public void onCaptureTextureCopied() { + + } + + @Override + public void onUserInteraction() { + + } + + @Override + public boolean updateStorageHintOnResume() { + return false; + } + + @Override + public void onOrientationChanged(int orientation) { + if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return; + int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation); + + if (mOrientation != newOrientation) { + mOrientation = newOrientation; + mUI.setOrientation(newOrientation, true); + } + } + + @Override + public void onShowSwitcherPopup() { + + } + + @Override + public void onMediaSaveServiceConnected(MediaSaveService s) { + + } + + @Override + public boolean arePreviewControlsVisible() { + return false; + } + + @Override + public void resizeForPreviewAspectRatio() { + + } + + @Override + public void onSwitchSavePath() { + + } + + @Override + public void waitingLocationPermissionResult(boolean waiting) { + + } + + @Override + public void onShutterButtonFocus(boolean pressed) { + + } + + @Override + public void onShutterButtonClick() { + if(!mFocusLockSemaphore.tryAcquire()) + return; + mFocusLockSemaphore.release(); + if (mState == STATE_WAITING_LOCK) { + return; + } else { + if (isPanoActive()) { + changePanoStatus(false, false); + } else { + lockFocus(); + } + } + } + + private void lockFocus() { + Log.d(TAG, "lockFocus"); + mIsLockFocusAttempted = true; + try { + mFocusLockSemaphore.acquire(); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, mCameraHandler); + mState = STATE_WAITING_LOCK; + mFocusLockSemaphore.release(); + } catch (CameraAccessException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + } + } + + public void unlockFocus() { + if(!mIsLockFocusAttempted) { + return; + } + Log.d(TAG, "unlockFocus "); + try { + mFocusLockSemaphore.acquire(); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, mCameraHandler); + mState = STATE_PREVIEW; + mFocusLockSemaphore.release(); + } catch (CameraAccessException e) { + e.printStackTrace(); + } catch (Exception e) { + } + mIsLockFocusAttempted = false; + } + + @Override + public void onShutterButtonLongClick() { + } + + /** + * Compares two {@code Size}s based on their areas. + */ + static class CompareSizesByArea implements Comparator<Size> { + + @Override + public int compare(Size lhs, Size rhs) { + // We cast here to ensure the multiplications won't overflow + return Long.signum((long) lhs.getWidth() * lhs.getHeight() - + (long) rhs.getWidth() * rhs.getHeight()); + } + + } +} diff --git a/src/com/android/camera/PanoCaptureUI.java b/src/com/android/camera/PanoCaptureUI.java new file mode 100644 index 000000000..256647ad0 --- /dev/null +++ b/src/com/android/camera/PanoCaptureUI.java @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a Contribution. + * + * 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.camera; + +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.RectF; +import android.hardware.Camera.Face; +import android.util.Log; +import android.util.Size; +import android.view.Gravity; +import android.view.SurfaceHolder; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLayoutChangeListener; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.camera.ui.AutoFitSurfaceView; +import com.android.camera.ui.CameraControls; +import com.android.camera.ui.CameraRootView; +import com.android.camera.ui.FocusIndicator; +import com.android.camera.ui.ModuleSwitcher; +import com.android.camera.ui.PanoCaptureProcessView; +import com.android.camera.util.CameraUtil; + +import org.codeaurora.snapcam.R; + +public class PanoCaptureUI implements + SurfaceHolder.Callback, + LocationManager.Listener, + CameraRootView.MyDisplayListener, + CameraManager.CameraFaceDetectionCallback { + + private static final String TAG = "SnapCam_PanoCaptureUI"; + private CameraActivity mActivity; + private PanoCaptureModule mController; + + private View mRootView; + private SurfaceHolder mSurfaceHolder; + private ShutterButton mShutterButton; + private ModuleSwitcher mSwitcher; + private CameraControls mCameraControls; + // Small indicators which show the camera settings in the viewfinder. + private OnScreenIndicators mOnScreenIndicators; + + private AutoFitSurfaceView mSurfaceView = null; + private Matrix mMatrix = null; + private boolean mUIhidden = false; + + private int mTopMargin = 0; + private int mBottomMargin = 0; + private int mSurfaceMode = 0; //0: INIT 1: TextureView 2: SurfaceView + private PanoCaptureProcessView mPreviewProcessView; + private ImageView mThumbnail; + + private int mOrientation; + + public void clearSurfaces() { + mSurfaceHolder = null; + } + + public boolean isPanoCompleting() { + return mPreviewProcessView.isPanoCompleting(); + } + + public boolean isFrameProcessing() { + return mPreviewProcessView.isFrameProcessing(); + } + + public void onFrameAvailable(Bitmap bitmap, boolean isCancelling) { + mPreviewProcessView.onFrameAvailable(bitmap, isCancelling); + } + + public void setSwitcherIndex() { + mSwitcher.setCurrentIndex(ModuleSwitcher.PANOCAPTURE_MODULE_INDEX); + } + + /* + * mode + * 0: Hiding and closing + * 1: TextureView + * 2: SurfaceView + */ + public synchronized void applySurfaceChange(int mode, boolean isForcing) { + if(mode == 0) { + clearSurfaces(); + mSurfaceView.setVisibility(View.GONE); + mSurfaceMode = 0; + return; + } + if(!isForcing && + ((mode == 1 && mSurfaceMode == 1) || (mode == 2 && mSurfaceMode == 2))) + return; + if(mode == 1) { + mSurfaceView.setVisibility(View.GONE); + mSurfaceMode = 1; + } else { + mSurfaceView.setVisibility(View.VISIBLE); + mSurfaceMode = 2; + } + } + + private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + int width = right - left; + int height = bottom - top; + Size size = mController.getPictureOutputSize(); + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(width, height, Gravity.CENTER); + mPreviewProcessView.setLayoutParams(lp); + mPreviewProcessView.setPanoPreviewSize(lp.width, + lp.height, + size.getWidth(), + size.getHeight()); + } + }; + + + public void setLayout(Size size) { + mSurfaceView.setAspectRatio(size.getHeight(), size.getWidth()); + } + + public PanoCaptureUI(CameraActivity activity, PanoCaptureModule controller, View parent) { + mActivity = activity; + mController = controller; + mRootView = parent; + mActivity.getLayoutInflater().inflate(R.layout.pano_capture_module, + (ViewGroup) mRootView, true); + + mPreviewProcessView = (PanoCaptureProcessView)mRootView.findViewById(R.id.preview_process_view); + mPreviewProcessView.setContext(activity, mController); + mSurfaceView = (AutoFitSurfaceView) mRootView.findViewById(R.id.mdp_preview_content); + mSurfaceView.setVisibility(View.VISIBLE); + mSurfaceView.addOnLayoutChangeListener(mLayoutListener); + mSurfaceHolder = mSurfaceView.getHolder(); + mSurfaceHolder.addCallback(this); + mRootView.findViewById(R.id.mute_button).setVisibility(View.GONE); + mRootView.findViewById(R.id.menu).setVisibility(View.GONE); + applySurfaceChange(2, false); + + mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button); + mShutterButton.setLongClickable(false); + mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher); + mSwitcher.setCurrentIndex(ModuleSwitcher.PHOTO_MODULE_INDEX); + mSwitcher.setSwitchListener(mActivity); + mSwitcher.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mController.getCameraState() == PhotoController.LONGSHOT) { + return; + } + mSwitcher.showPopup(); + mSwitcher.setOrientation(mOrientation, false); + } + }); + mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls); + + mThumbnail = (ImageView) mRootView.findViewById(R.id.preview_thumb); + mThumbnail.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (!CameraControls.isAnimating()) + mActivity.gotoGallery(); + } + }); + + initIndicators(); + + Point size = new Point(); + mActivity.getWindowManager().getDefaultDisplay().getSize(size); + calculateMargins(size); + mCameraControls.setMargins(mTopMargin, mBottomMargin); + } + + private void calculateMargins(Point size) { + int l = size.x > size.y ? size.x : size.y; + int tm = mActivity.getResources().getDimensionPixelSize(R.dimen.preview_top_margin); + int bm = mActivity.getResources().getDimensionPixelSize(R.dimen.preview_bottom_margin); + mTopMargin = l / 4 * tm / (tm + bm); + mBottomMargin = l / 4 - mTopMargin; + } + + private void setTransformMatrix(int width, int height) { + mMatrix = mSurfaceView.getMatrix(); + + // Calculate the new preview rectangle. + RectF previewRect = new RectF(0, 0, width, height); + mMatrix.mapRect(previewRect); + mController.onPreviewRectChanged(CameraUtil.rectFToRect(previewRect)); + } + + // SurfaceHolder callbacks + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.v(TAG, "surfaceChanged: width =" + width + ", height = " + height); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.v(TAG, "surfaceCreated"); + mSurfaceHolder = holder; + mController.onPreviewUIReady(); + mActivity.updateThumbnail(mThumbnail); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.v(TAG, "surfaceDestroyed"); + mController.onPreviewUIDestroyed(); + mSurfaceHolder = null; + } + + public View getRootView() { + return mRootView; + } + + private void initIndicators() { + mOnScreenIndicators = new OnScreenIndicators(mActivity, + mRootView.findViewById(R.id.on_screen_indicators)); + } + + public void onCameraOpened() { + + } + + public void hideUI() { + mSwitcher.closePopup(); + if (mUIhidden) + return; + mUIhidden = true; + mCameraControls.hideUI(); + } + + public void showUI() { + mUIhidden = false; + mCameraControls.showUI(); + } + + public boolean arePreviewControlsVisible() { + return !mUIhidden; + } + + public void initializeShutterButton() { + // Initialize shutter button. + mShutterButton.setImageResource(R.drawable.btn_new_shutter_panorama); + mShutterButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + //TODO: Any animation is needed? + } + }); + mShutterButton.setOnShutterButtonListener(mController); + mShutterButton.setVisibility(View.VISIBLE); + } + + /** + * Enables or disables the shutter button. + */ + public void enableShutter(boolean enabled) { + if (mShutterButton != null) { + mShutterButton.setEnabled(enabled); + } + } + + public void overrideSettings(final String... keyvalues) { + } + + public boolean onBackPressed() { + // In image capture mode, back button should: + // 1) if there is any popup, dismiss them, 2) otherwise, get out of + // image capture + if (mController.isImageCaptureIntent()) { + mController.onCaptureCancelled(); + return true; + } else if (!mController.isCameraIdle()) { + // ignore backs while we're taking a picture + return true; + } + if (mSwitcher != null && mSwitcher.showsPopup()) { + mSwitcher.closePopup(); + return true; + } else { + return false; + } + } + + public void onPreviewFocusChanged(boolean previewFocused) { + if (previewFocused) { + showUI(); + } else { + hideUI(); + } + setShowMenu(previewFocused); + } + + private void setShowMenu(boolean show) { + if (mOnScreenIndicators != null) { + mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE); + } + } + + public boolean collapseCameraControls() { + // TODO: Mode switcher should behave like a popup and should hide itself when there + // is a touch outside of it. + mSwitcher.closePopup(); + return true; + } + + public SurfaceHolder getSurfaceHolder() { + return mSurfaceHolder; + } + + public void onResume() { + mPreviewProcessView.onResume(); + mCameraControls.getPanoramaExitButton().setVisibility(View.VISIBLE); + mCameraControls.getPanoramaExitButton().setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + SettingsManager.getInstance().setValueIndex(SettingsManager.KEY_SCENE_MODE, SettingsManager.SCENE_MODE_AUTO_INT); + mActivity.onModuleSelected(ModuleSwitcher.CAPTURE_MODULE_INDEX); + } + }); + } + + public void onPause() { + collapseCameraControls(); + mPreviewProcessView.onPause(); + mCameraControls.getPanoramaExitButton().setVisibility(View.GONE); + mCameraControls.getPanoramaExitButton().setOnClickListener(null); + } + + // focus UI implementation + private FocusIndicator getFocusIndicator() { + return null; + } + + @Override + public void onFaceDetection(Face[] faces, CameraManager.CameraProxy camera) { + } + + @Override + public void onDisplayChanged() { + Log.d(TAG, "Device flip detected."); + mCameraControls.checkLayoutFlip(); + mController.updateCameraOrientation(); + } + + public void setOrientation(int orientation, boolean animation) { + mOrientation = orientation; + mCameraControls.setOrientation(orientation, animation); + mPreviewProcessView.setOrientation(orientation); + } + + public int getOrientation() { + return mOrientation; + } + + @Override + public void onErrorListener(int error) { + + } +} diff --git a/src/com/android/camera/SettingsManager.java b/src/com/android/camera/SettingsManager.java index 6e87ae7ad..20fbc2173 100644 --- a/src/com/android/camera/SettingsManager.java +++ b/src/com/android/camera/SettingsManager.java @@ -51,6 +51,7 @@ import com.android.camera.imageprocessor.filter.OptizoomFilter; import com.android.camera.imageprocessor.filter.TrackingFocusFrameListener; import com.android.camera.imageprocessor.filter.UbifocusFilter; import com.android.camera.ui.ListMenu; +import com.android.camera.ui.PanoCaptureProcessView; import com.android.camera.util.SettingTranslation; import org.codeaurora.snapcam.R; @@ -80,6 +81,7 @@ public class SettingsManager implements ListMenu.SettingsListener { public static final int SCENE_MODE_OPTIZOOM_INT = 101; public static final int SCENE_MODE_UBIFOCUS_INT = 102; public static final int SCENE_MODE_BESTPICTURE_INT = 103; + public static final int SCENE_MODE_PANORAMA_INT = 104; public static final String SCENE_MODE_DUAL_STRING = "100"; public static final String KEY_CAMERA_SAVEPATH = "pref_camera2_savepath_key"; public static final String KEY_RECORD_LOCATION = "pref_camera2_recordlocation_key"; @@ -1019,6 +1021,7 @@ public class SettingsManager implements ListMenu.SettingsListener { if (OptizoomFilter.isSupportedStatic()) modes.add(SCENE_MODE_OPTIZOOM_INT + ""); if (UbifocusFilter.isSupportedStatic() && cameraId == CaptureModule.BAYER_ID) modes.add(SCENE_MODE_UBIFOCUS_INT + ""); if (BestpictureFilter.isSupportedStatic() && cameraId == CaptureModule.BAYER_ID) modes.add(SCENE_MODE_BESTPICTURE_INT + ""); + if (PanoCaptureProcessView.isSupportedStatic() && cameraId == CaptureModule.BAYER_ID) modes.add(SCENE_MODE_PANORAMA_INT + ""); for (int mode : sceneModes) { modes.add("" + mode); } diff --git a/src/com/android/camera/ui/CameraControls.java b/src/com/android/camera/ui/CameraControls.java index 48a5492e3..9039a9dce 100644 --- a/src/com/android/camera/ui/CameraControls.java +++ b/src/com/android/camera/ui/CameraControls.java @@ -53,6 +53,7 @@ public class CameraControls extends RotatableLayout { private View mShutter; private View mVideoShutter; private View mSwitcher; + private View mExitPanorama; private View mMenu; private View mMute; private View mFrontBackSwitcher; @@ -84,9 +85,11 @@ public class CameraControls extends RotatableLayout { private static final int INDICATOR_INDEX = 8; private static final int MUTE_INDEX = 9; private static final int VIDEO_SHUTTER_INDEX = 10; + private static final int EXIT_PANORAMA_INDEX = 11; + private static final int MAX_INDEX= 12; private static final int ANIME_DURATION = 300; - private float[][] mLocX = new float[4][11]; - private float[][] mLocY = new float[4][11]; + private float[][] mLocX = new float[4][MAX_INDEX]; + private float[][] mLocY = new float[4][MAX_INDEX]; private boolean mLocSet = false; private boolean mHideRemainingPhoto = false; private LinearLayout mRemainingPhotos; @@ -131,6 +134,7 @@ public class CameraControls extends RotatableLayout { mVideoShutter.setVisibility(View.INVISIBLE); mMenu.setVisibility(View.INVISIBLE); mMute.setVisibility(View.INVISIBLE); + mExitPanorama.setVisibility(View.INVISIBLE); mIndicators.setVisibility(View.INVISIBLE); mPreview.setVisibility(View.INVISIBLE); isAnimating = false; @@ -155,6 +159,7 @@ public class CameraControls extends RotatableLayout { mVideoShutter.setVisibility(View.INVISIBLE); mMenu.setVisibility(View.INVISIBLE); mMute.setVisibility(View.INVISIBLE); + mExitPanorama.setVisibility(View.INVISIBLE); mIndicators.setVisibility(View.INVISIBLE); mPreview.setVisibility(View.INVISIBLE); isAnimating = false; @@ -162,6 +167,10 @@ public class CameraControls extends RotatableLayout { } }; + public View getPanoramaExitButton() { + return mExitPanorama; + } + AnimatorListener inlistener = new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { @@ -215,6 +224,7 @@ public class CameraControls extends RotatableLayout { mSwitcher.setPressed(false); mMenu.setPressed(false); mMute.setPressed(false); + mExitPanorama.setPressed(false); mFrontBackSwitcher.setPressed(false); if(TsMakeupManager.HAS_TS_MAKEUP) { mTsMakeupSwitcher.setPressed(false); @@ -230,6 +240,7 @@ public class CameraControls extends RotatableLayout { ((ModuleSwitcher) mSwitcher).enableTouch(enable); mMenu.setEnabled(enable); mMute.setEnabled(enable); + mExitPanorama.setEnabled(enable); mFrontBackSwitcher.setEnabled(enable); if(TsMakeupManager.HAS_TS_MAKEUP) { mTsMakeupSwitcher.setEnabled(enable); @@ -264,10 +275,10 @@ public class CameraControls extends RotatableLayout { mViewList.add(mMenu); if (mMute.getVisibility() == View.VISIBLE) mViewList.add(mMute); + if (mExitPanorama.getVisibility() == View.VISIBLE) + mViewList.add(mExitPanorama); if (mIndicators.getVisibility() == View.VISIBLE) mViewList.add(mIndicators); - if (mMute.getVisibility() == View.VISIBLE) - mViewList.add(mMute); } public void removeFromViewList(View view) { @@ -291,6 +302,8 @@ public class CameraControls extends RotatableLayout { } mMenu = findViewById(R.id.menu); mMute = findViewById(R.id.mute_button); + mExitPanorama = findViewById(R.id.exit_panorama); + mExitPanorama.setVisibility(View.GONE); mIndicators = findViewById(R.id.on_screen_indicators); mPreview = findViewById(R.id.preview_thumb); mSceneModeSwitcher = findViewById(R.id.scene_mode_switcher); @@ -371,6 +384,7 @@ public class CameraControls extends RotatableLayout { toIndex(mVideoShutter, w, h, rotation, 3, 6, VIDEO_SHUTTER_INDEX); toIndex(mMenu, w, h, rotation, 4, 0, MENU_INDEX); toIndex(mMute, w, h, rotation, 3, 0, MUTE_INDEX); + toIndex(mExitPanorama, w, h, rotation, 0, 0, EXIT_PANORAMA_INDEX); toIndex(mIndicators, w, h, rotation, 0, 6, INDICATOR_INDEX); toIndex(mFrontBackSwitcher, w, h, rotation, 2, 0, FRONT_BACK_INDEX); toIndex(mPreview, w, h, rotation, 0, 6, PREVIEW_INDEX); @@ -481,6 +495,7 @@ public class CameraControls extends RotatableLayout { mFilterModeSwitcher.setX(mLocX[idx1][FILTER_MODE_INDEX] + x); mMenu.setX(mLocX[idx1][MENU_INDEX] + x); mMute.setX(mLocX[idx1][MUTE_INDEX] + x); + mExitPanorama.setX(mLocX[idx1][EXIT_PANORAMA_INDEX] + x); mSwitcher.setX(mLocX[idx1][SWITCHER_INDEX] - x); mShutter.setX(mLocX[idx1][SHUTTER_INDEX] - x); mVideoShutter.setX(mLocX[idx1][VIDEO_SHUTTER_INDEX] - x); @@ -497,6 +512,7 @@ public class CameraControls extends RotatableLayout { mFilterModeSwitcher.setY(mLocY[idx1][FILTER_MODE_INDEX] + y); mMenu.setY(mLocY[idx1][MENU_INDEX] + y); mMute.setY(mLocY[idx1][MUTE_INDEX] + y); + mExitPanorama.setY(mLocY[idx1][EXIT_PANORAMA_INDEX] + y); mSwitcher.setY(mLocY[idx1][SWITCHER_INDEX] - y); mShutter.setY(mLocY[idx1][SHUTTER_INDEX] - y); mVideoShutter.setY(mLocY[idx1][VIDEO_SHUTTER_INDEX] - y); @@ -534,6 +550,7 @@ public class CameraControls extends RotatableLayout { mVideoShutter.animate().cancel(); mMenu.animate().cancel(); mMute.animate().cancel(); + mExitPanorama.animate().cancel(); mIndicators.animate().cancel(); mPreview.animate().cancel(); mFrontBackSwitcher.animate().setListener(outlistener); @@ -552,6 +569,7 @@ public class CameraControls extends RotatableLayout { mFilterModeSwitcher.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); mMenu.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); mMute.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); + mExitPanorama.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); mSwitcher.animate().translationYBy(mSize).setDuration(ANIME_DURATION); mShutter.animate().translationYBy(mSize).setDuration(ANIME_DURATION); @@ -570,6 +588,7 @@ public class CameraControls extends RotatableLayout { mFilterModeSwitcher.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); mMenu.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); mMute.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); + mExitPanorama.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); mSwitcher.animate().translationXBy(mSize).setDuration(ANIME_DURATION); mShutter.animate().translationXBy(mSize).setDuration(ANIME_DURATION); @@ -588,6 +607,7 @@ public class CameraControls extends RotatableLayout { mFilterModeSwitcher.animate().translationYBy(mSize).setDuration(ANIME_DURATION); mMenu.animate().translationYBy(mSize).setDuration(ANIME_DURATION); mMute.animate().translationYBy(mSize).setDuration(ANIME_DURATION); + mExitPanorama.animate().translationYBy(mSize).setDuration(ANIME_DURATION); mSwitcher.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); mShutter.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); @@ -606,6 +626,7 @@ public class CameraControls extends RotatableLayout { mFilterModeSwitcher.animate().translationXBy(mSize).setDuration(ANIME_DURATION); mMenu.animate().translationXBy(mSize).setDuration(ANIME_DURATION); mMute.animate().translationXBy(mSize).setDuration(ANIME_DURATION); + mExitPanorama.animate().translationXBy(mSize).setDuration(ANIME_DURATION); mSwitcher.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); mShutter.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); @@ -636,6 +657,7 @@ public class CameraControls extends RotatableLayout { mVideoShutter.animate().cancel(); mMenu.animate().cancel(); mMute.animate().cancel(); + mExitPanorama.animate().cancel(); mIndicators.animate().cancel(); mPreview.animate().cancel(); if (mViewList != null) @@ -666,6 +688,7 @@ public class CameraControls extends RotatableLayout { mFilterModeSwitcher.animate().translationYBy(mSize).setDuration(ANIME_DURATION); mMenu.animate().translationYBy(mSize).setDuration(ANIME_DURATION); mMute.animate().translationYBy(mSize).setDuration(ANIME_DURATION); + mExitPanorama.animate().translationYBy(mSize).setDuration(ANIME_DURATION); mSwitcher.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); mShutter.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); @@ -686,6 +709,7 @@ public class CameraControls extends RotatableLayout { mFilterModeSwitcher.animate().translationXBy(mSize).setDuration(ANIME_DURATION); mMenu.animate().translationXBy(mSize).setDuration(ANIME_DURATION); mMute.animate().translationXBy(mSize).setDuration(ANIME_DURATION); + mExitPanorama.animate().translationXBy(mSize).setDuration(ANIME_DURATION); mSwitcher.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); mShutter.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); @@ -706,6 +730,7 @@ public class CameraControls extends RotatableLayout { mFilterModeSwitcher.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); mMenu.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); mMute.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); + mExitPanorama.animate().translationYBy(-mSize).setDuration(ANIME_DURATION); mSwitcher.animate().translationYBy(mSize).setDuration(ANIME_DURATION); mShutter.animate().translationYBy(mSize).setDuration(ANIME_DURATION); @@ -726,6 +751,7 @@ public class CameraControls extends RotatableLayout { mFilterModeSwitcher.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); mMenu.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); mMute.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); + mExitPanorama.animate().translationXBy(-mSize).setDuration(ANIME_DURATION); mSwitcher.animate().translationXBy(mSize).setDuration(ANIME_DURATION); mShutter.animate().translationXBy(mSize).setDuration(ANIME_DURATION); @@ -1001,7 +1027,7 @@ public class CameraControls extends RotatableLayout { mSceneModeSwitcher, mFilterModeSwitcher, mFrontBackSwitcher, TsMakeupManager.HAS_TS_MAKEUP ? mTsMakeupSwitcher : mHdrSwitcher, mMenu, mShutter, mPreview, mSwitcher, mMute, mReviewRetakeButton, - mReviewCancelButton, mReviewDoneButton + mReviewCancelButton, mReviewDoneButton, mExitPanorama }; for (View v : views) { if (v != null) { diff --git a/src/com/android/camera/ui/ModuleSwitcher.java b/src/com/android/camera/ui/ModuleSwitcher.java index 603f81d4d..d96775d14 100644 --- a/src/com/android/camera/ui/ModuleSwitcher.java +++ b/src/com/android/camera/ui/ModuleSwitcher.java @@ -54,6 +54,7 @@ public class ModuleSwitcher extends RotateImageView public static final int LIGHTCYCLE_MODULE_INDEX = 3; public static final int GCAM_MODULE_INDEX = 4; public static final int CAPTURE_MODULE_INDEX = 5; + public static final int PANOCAPTURE_MODULE_INDEX = 6; private boolean mTouchEnabled = true; private boolean mIsVisible = true; @@ -140,6 +141,8 @@ public class ModuleSwitcher extends RotateImageView mCurrentIndex = i; if (i == GCAM_MODULE_INDEX) { setImageResource(R.drawable.ic_switch_camera); + } if (i== PANOCAPTURE_MODULE_INDEX) { + return; } else { setImageResource(mDrawIds[i]); } diff --git a/src/com/android/camera/ui/PanoCaptureProcessView.java b/src/com/android/camera/ui/PanoCaptureProcessView.java new file mode 100644 index 000000000..724703b10 --- /dev/null +++ b/src/com/android/camera/ui/PanoCaptureProcessView.java @@ -0,0 +1,1097 @@ +/* + * 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.ui; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.net.Uri; +import android.os.Handler; +import android.text.Layout; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.OrientationEventListener; +import android.view.SurfaceView; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.Toast; + +import com.android.camera.CameraActivity; +import com.android.camera.PanoCaptureModule; +import com.android.camera.exif.ExifInterface; +import com.android.camera.util.CameraUtil; + +import org.codeaurora.snapcam.R; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Objects; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public class PanoCaptureProcessView extends View implements SensorEventListener { + private static final int DEG_INIT_VALUE = 365; + private float mCurrDegX = DEG_INIT_VALUE; + private float mCurrDegY = DEG_INIT_VALUE; + private final static int OBJ_DEPTH = 800; + private RectF rectF = new RectF(); + private CameraActivity mActivity; + private PanoCaptureModule mController; + private Matrix matrix = new Matrix(); + private SensorManager mSensorManager; + private Sensor mRotationSensor; + private float[] mRots = new float[5]; + private float[] mOldRots = new float[5]; + private float[] mR = new float[9]; + private float[] mRR = new float[9]; + private float[] mOrients = new float[3]; + private int mOrientation; + private int mPendingOrientation; + private Bitmap tmpBitmap; + private Paint mCenterRectPaint = new Paint(); + + public static int mPreviewThumbWidth; + public static int mPreviewThumbHeight; + private static float mPanoPreviewRatioToCamera; + public static int mFinalPictureWidth; + public static int mFinalPictureHeight; + private static float mFinalPictureRatioToCamera; + private Picture mPreviewPicture; + private int[] mAargbBuffer; + private byte[] mDataBuffer; + public static int MAX_PANO_FRAME = 6; + private static String TAG = "PanoramaCapture"; + private Bitmap mTempBitmap; + private int mTempOrietnation; + private Picture mGuidePicture; + private Handler mHandler; + private final static int DIRECTION_GOT_LOST = -1; + private final static int DIRECTION_LEFTRIGHT = 0; + private final static int DIRECTION_UPDOWN = 1; + private int mDir = DIRECTION_LEFTRIGHT; + private boolean mShouldFinish = false; + private static final boolean DEBUG = false; //TODO: This has to be false before release + private BitmapArrayOutputStream mBitmapStream; + private static boolean mIsSupported = false; + + private boolean mIsFrameProcessing = false; + enum PANO_STATUS { + INACTIVE, + ACTIVE_UNKNOWN, + ACTIVE_LEFT, + ACTIVE_RIGHT, + ACTIVE_UP, + ACTIVE_DOWN, + + /* These two statuses below are for showing the background thread is running + while subsequent frame input will be ignored and passed. + */ + COMPLETING, + OPENING + }; + private PANO_STATUS mPanoStatus = PANO_STATUS.INACTIVE; + private Object mPreviewBitmapLock = new Object(); + private static int DECISION_MARGIN; + private boolean mIsFirstBlend; + private PanoQueueProcessor mQueueProcessor; + private ProgressDialog mProgressDialog; + private String mCompleteSentence = ""; + private String mProgressSentence = ""; + private Paint mCompleteSentencePaint = new Paint(); + private int mFinalDoneLength; + + class Picture { + Bitmap bitmap; + Bitmap bitmapIn; + float xDeg; + float yDeg; + int xPos; + int yPos; + int leftIn; + int topIn; + int width; + int height; + Matrix mat; + RectF rF; + float[] pts; + Paint paintInAir = new Paint(); + Paint paintFrameEdge = new Paint(); + + public Picture(Bitmap bm, float xdeg, float ydeg, int x, int y) { + init(bm, xdeg, ydeg, x, y); + } + + public Picture(Bitmap bm, float xdeg, float ydeg, int x, int y, int w, int h) { + init(bm, xdeg, ydeg, x, y); + width = w; + height = h; + } + + private void init(Bitmap bm, float xdeg, float ydeg, int x, int y) { + bitmap = bm; + xDeg = xdeg; + yDeg = ydeg; + xPos = x; + yPos = y; + mat = new Matrix(); + rF = new RectF(); + pts = new float[8]; + paintInAir.setAlpha(124); + if(bm != null) { + width = bm.getWidth(); + height = bm.getHeight(); + } + paintFrameEdge.setColor(Color.WHITE); + paintFrameEdge.setStrokeWidth(2f); + paintFrameEdge.setStyle(Paint.Style.STROKE); + } + + public void drawPictureInAir(Canvas canvas) { + float setha, x, y; + + setha = ((xDeg - mCurrDegX) + 360) % 360; + if(90 <= setha && setha <= 270) + return; + x = (OBJ_DEPTH * (float) Math.sin(Math.toRadians(setha))); + setha = ((yDeg - mCurrDegY) + 360) % 360; + if(90 <= setha && setha <= 270) + return; + y = (OBJ_DEPTH * (float) Math.sin(Math.toRadians(setha))); + + rF.left = canvas.getWidth()/2 + x - bitmap.getWidth()/2; + rF.right = canvas.getWidth()/2 + x + bitmap.getWidth()/2; + rF.top = canvas.getHeight()/2 + y - bitmap.getHeight()/2; + rF.bottom = canvas.getHeight()/2 + y + bitmap.getHeight()/2; + skew(rF, pts, x, y, canvas.getWidth() / 2, canvas.getHeight() / 2); + mat.reset(); + mat.setPolyToPoly(new float[]{rF.left, rF.top, + rF.right, rF.top, + rF.right, rF.bottom, + rF.left, rF.bottom}, + 0, + pts, + 0, + 4 + ); + canvas.translate(rF.left, rF.top); + canvas.drawBitmap(bitmap, mat, paintInAir); + } + + public void drawGuideInAir(Canvas canvas) { + float setha, x, y; + + setha = ((xDeg - mCurrDegX) + 360) % 360; + if(90 <= setha && setha <= 270) + return; + x = (OBJ_DEPTH * (float) Math.sin(Math.toRadians(setha))); + setha = ((yDeg - mCurrDegY) + 360) % 360; + if(90 <= setha && setha <= 270) + return; + y = (OBJ_DEPTH * (float) Math.sin(Math.toRadians(setha))); + + rF.left = canvas.getWidth()/2 + x - width; + rF.right = canvas.getWidth()/2 + x + width; + rF.top = canvas.getHeight()/2 + y - height; + rF.bottom = canvas.getHeight()/2 + y + height; + skew(rF, pts, x, y, canvas.getWidth() / 2, canvas.getHeight() / 2); + for(int i=1; i < 4; i++) { + canvas.drawLine(pts[2*(i-1)], pts[2*(i-1)+1], pts[2*i], pts[2*i+1], paintFrameEdge); + } + canvas.drawLine(pts[0], pts[1], pts[6], pts[7], paintFrameEdge); + } + + public void drawMasterPanoPreview(Canvas canvas) { + int bitmapWidth; + int bitmapHeight; + if(mPanoStatus == PANO_STATUS.ACTIVE_LEFT || mPanoStatus == PANO_STATUS.ACTIVE_RIGHT) { + bitmapWidth = mPreviewPicture.bitmap.getWidth(); + bitmapHeight = mPreviewPicture.bitmap.getHeight(); + rectF.left = canvas.getWidth() / 2 - bitmapWidth / 2; + rectF.right = canvas.getWidth() / 2 + bitmapWidth / 2; + rectF.top = canvas.getHeight() * 4 / 5 - bitmapHeight; + rectF.bottom = canvas.getHeight() * 4 / 5; + canvas.drawBitmap(mPreviewPicture.bitmap, null, rectF, null); + canvas.drawRect(rectF, paintFrameEdge); + + } else if(mPanoStatus == PANO_STATUS.ACTIVE_UP || mPanoStatus == PANO_STATUS.ACTIVE_DOWN) { + bitmapWidth = mPreviewPicture.bitmap.getWidth(); + bitmapHeight = mPreviewPicture.bitmap.getHeight(); + rectF.left = canvas.getWidth() / 4 - bitmapWidth / 2; + rectF.right = canvas.getWidth() / 4 + bitmapWidth / 2; + rectF.top = canvas.getHeight() / 2 - bitmapHeight / 2; + rectF.bottom = canvas.getHeight() / 2 + bitmapHeight / 2; + canvas.drawBitmap(mPreviewPicture.bitmap, null, rectF, null); + canvas.drawRect(rectF, paintFrameEdge); + } + if(mOrientation == 0 || mOrientation == 180) { + rectF.left += leftIn; + rectF.right = rectF.left + mPreviewThumbWidth; + rectF.top += topIn; + rectF.bottom = rectF.top + mPreviewThumbHeight; + } else { + rectF.left += leftIn; + rectF.right = rectF.left + mPreviewThumbHeight; + rectF.top += topIn; + rectF.bottom = rectF.top + mPreviewThumbWidth; + } + canvas.drawBitmap(mPreviewPicture.bitmapIn, null, rectF, null); + canvas.drawRect(rectF, paintFrameEdge); + } + + private void skew(RectF src, float[] pts, float x, float y, float Wh, float Hh) { + float lh = src.height(); //Left height + float tw = src.width(); //Top width + float rh = lh; //Right height + float bw = tw; //Bottom width + + if(x < 0) { + lh = lh * ((-x/Wh)/2f + 1); + } else { + rh = rh * ((x/Wh)/2f + 1); + } + + if(y < 0) { + tw = tw * ((-y/Hh)/2f + 1); + } else { + bw = bw * ((y/Hh)/2f + 1); + } + + //Left Top + pts[0] = src.centerX() - tw/2; + pts[1] = src.centerY() - lh/2; + //Right Top + pts[2] = src.centerX() + tw/2; + pts[3] = src.centerY() - rh/2; + //Right Bottom + pts[4] = src.centerX() + bw/2; + pts[5] = src.centerY() + rh/2; + //Left Bottom + pts[6] = src.centerX() - bw/2; + pts[7] = src.centerY() + lh/2; + } + } + + public void setContext(CameraActivity activity, PanoCaptureModule contoller) { + mActivity = activity; + mController = contoller; + mSensorManager = (SensorManager) mActivity.getSystemService(Context.SENSOR_SERVICE); + mRotationSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); + mCenterRectPaint.setColor(Color.CYAN); + mCenterRectPaint.setStrokeWidth(2f); + mCenterRectPaint.setStyle(Paint.Style.STROKE); + mCompleteSentencePaint.setColor(Color.WHITE); + mCompleteSentencePaint.setTextSize(60f); + mQueueProcessor = new PanoQueueProcessor(); + mQueueProcessor.start(); + mHandler = new Handler(); + } + + public void onPause() { + mSensorManager.unregisterListener(this, mRotationSensor); + if(mBitmapStream != null) { + try { + mBitmapStream.close(); + } catch (IOException e) { + //Ignore + } + mBitmapStream = null; + } + } + + public void onResume() { + mSensorManager.registerListener(this, mRotationSensor, SensorManager.SENSOR_DELAY_NORMAL); + } + + public void setPanoPreviewSize(int width, int height, int cameraWidth, int cameraHeight) { + mPreviewThumbWidth = width / (PanoCaptureProcessView.MAX_PANO_FRAME+2) / 2 * 2; + mPreviewThumbHeight = height / (PanoCaptureProcessView.MAX_PANO_FRAME+2) / 2 * 2; + mFinalPictureWidth = width / 2 * 2; + mFinalPictureHeight = height / 2 * 2; + mAargbBuffer = new int[mPreviewThumbWidth * mPreviewThumbHeight]; + mDataBuffer = new byte[mPreviewThumbWidth * mPreviewThumbHeight * 3 / 2]; + + DECISION_MARGIN = (int)(0.2 * mPreviewThumbHeight); + + mPanoPreviewRatioToCamera = (float)Math.min(mPreviewThumbWidth, mPreviewThumbHeight) / + (float)Math.min(cameraWidth, cameraHeight); + mFinalPictureRatioToCamera = (float)Math.min(mFinalPictureWidth, mFinalPictureHeight) / + (float)Math.min(cameraWidth, cameraHeight); + } + + public PanoCaptureProcessView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onDraw(Canvas canvas) { + if(mPanoStatus != PANO_STATUS.INACTIVE) { + canvas.rotate(-mOrientation, canvas.getWidth() / 2, canvas.getHeight() / 2); + + if (mOrientation == 0 || mOrientation == 180) { + rectF.left = canvas.getWidth() / 2 - mPreviewThumbWidth; + rectF.right = canvas.getWidth() / 2 + mPreviewThumbWidth; + rectF.top = canvas.getHeight() / 2 - mPreviewThumbHeight; + rectF.bottom = canvas.getHeight() / 2 + mPreviewThumbHeight; + } else { + rectF.left = canvas.getWidth() / 2 - mPreviewThumbHeight; + rectF.right = canvas.getWidth() / 2 + mPreviewThumbHeight; + rectF.top = canvas.getHeight() / 2 - mPreviewThumbWidth; + rectF.bottom = canvas.getHeight() / 2 + mPreviewThumbWidth; + } + + if(!mProgressSentence.equals("")) { + int textWidth = (int) mCompleteSentencePaint.measureText(mProgressSentence); + canvas.drawText(mProgressSentence, rectF.centerX() - textWidth / 2, canvas.getHeight()/4, mCompleteSentencePaint); + } + + if(mPanoStatus == PANO_STATUS.COMPLETING) { + int textWidth = (int) mCompleteSentencePaint.measureText(mCompleteSentence); + canvas.drawText(mCompleteSentence, rectF.centerX() - textWidth / 2, rectF.centerY(), mCompleteSentencePaint); + } else { + //Draw Aiming rectangle at the center + canvas.drawRect(rectF, mCenterRectPaint); + + //Draw the guide frames + if(mGuidePicture != null) { + canvas.save(); + mGuidePicture.drawGuideInAir(canvas); + canvas.restore(); + } + + //Blended pano preview + synchronized (mPreviewBitmapLock) { + if (mPreviewPicture != null) { + mPreviewPicture.drawMasterPanoPreview(canvas); + } + } + } + } + } + + @Override + public boolean onTouchEvent(MotionEvent e){ + return true; + } + + private void bitmapToDataNV21(Bitmap bitmap) { + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + int y = 0; + int u = w*h; + int a, R, G, B, Y, U, V; + int index = 0; + + bitmap.getPixels(mAargbBuffer, 0, w, 0, 0, w, h); + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + + a = (mAargbBuffer[index] & 0xff000000) >> 24; + R = (mAargbBuffer[index] & 0xff0000) >> 16; + G = (mAargbBuffer[index] & 0xff00) >> 8; + B = (mAargbBuffer[index] & 0xff) >> 0; + + Y = ( ( 66 * R + 129 * G + 25 * B + 128) >> 8) + 16; + U = ( ( -38 * R - 74 * G + 112 * B + 128) >> 8) + 128; + V = ( ( 112 * R - 94 * G - 18 * B + 128) >> 8) + 128; + + mDataBuffer[y++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y)); + if (j % 2 == 0 && index % 2 == 0) { + mDataBuffer[u++] = (byte)((V<0) ? 0 : ((V > 255) ? 255 : V)); + mDataBuffer[u++] = (byte)((U<0) ? 0 : ((U > 255) ? 255 : U)); + } + index ++; + } + } + } + + class PanoQueueProcessor extends Thread { + private ArrayBlockingQueue<BitmapTask> queue; + private Object lock = new Object(); + public PanoQueueProcessor() { + queue = new ArrayBlockingQueue<BitmapTask>(MAX_PANO_FRAME); + } + + private void waitTillNotFull() { + while(true) { + if (queue.size() < MAX_PANO_FRAME) { + return; + } + } + } + + @Override + public void run() { + while(true) { + try { + BitmapTask bt = queue.take(); + if(mShouldFinish) + continue; + synchronized (lock) { + doTask(bt); + } + } catch (InterruptedException e) { + //Ignore + } + } + } + + public boolean isEmpty() { + synchronized (lock) { + if(!queue.isEmpty()) { + return false; + } + return true; + } + } + + public void queueClear() { + this.interrupt(); + queue.clear(); + } + + //This function is the only one running on UI thread. + public void addTask(Bitmap bitmap, int x, int y, int dir) { + waitTillNotFull(); + BitmapTask bt = new BitmapTask(bitmap, x, y, dir); + queue.add(bt); + } + + private void doTask(BitmapTask bitmapTask) { + if(mBitmapStream == null) { + mBitmapStream = new BitmapArrayOutputStream(1024*1204); + } + mBitmapStream.reset(); + bitmapTask.bitmap.compress(Bitmap.CompressFormat.JPEG, 100, mBitmapStream); + int rtv = callNativeProcessKeyFrame(mBitmapStream.toByteArray(), mBitmapStream.size(), + bitmapTask.x, bitmapTask.y, 0, bitmapTask.dir); + if(rtv < 0) { + mShouldFinish = true; + stopPano(false, "The direction is changed. Stopping."); + Log.w(TAG, "Keyframe return value: "+rtv); + } + bitmapTask.clear(); + } + } + + class BitmapArrayOutputStream extends ByteArrayOutputStream { + + public BitmapArrayOutputStream(int size) { + super(size); + } + + @Override + public synchronized byte[] toByteArray() { + return buf; + } + + @Override + public void close() throws IOException{ + super.close(); + buf = null; + } + } + + class BitmapTask { + Bitmap bitmap; + int x; + int y; + int dir; + public BitmapTask(Bitmap orgBitmap, int x, int y, int dir) { + Bitmap newBitmap; + if (mOrientation == 0 || mOrientation == 180) { + newBitmap = Bitmap.createBitmap(mFinalPictureWidth, mFinalPictureHeight, Bitmap.Config.ARGB_8888); + } else {//if(mOrientation == 90 || mOrientation == 270) + newBitmap = Bitmap.createBitmap(mFinalPictureHeight, mFinalPictureWidth, Bitmap.Config.ARGB_8888); + } + rotateAndScale(orgBitmap, newBitmap, mFinalPictureRatioToCamera); + this.bitmap = newBitmap; + this.x = x; + this.y = y; + this.dir = dir; + } + public void clear() { + this.bitmap.recycle(); + } + } + + private void waitForQueueDone() { + while(true) { + if(mQueueProcessor.isEmpty()) return; + try { + Thread.sleep(10); + } catch(InterruptedException e) { + //Ignore + } + } + } + + private void processPreviewFrame(boolean[] isKey, int[] framePos, int[] moveSpeed) { + if (callNativeProcessPreviewFrame(mDataBuffer, isKey, framePos, moveSpeed) < 0) { + Log.e(TAG, "Preview processing is failed."); + } + } + + public boolean isPanoCompleting() { + return (mPanoStatus == PANO_STATUS.COMPLETING); + } + + public boolean isFrameProcessing() { + return mIsFrameProcessing; + } + + /* + * bitmap will be kept to use further. + */ + public void onFrameAvailable(final Bitmap bitmap, final boolean isCancelling) { + if(mPanoStatus == PANO_STATUS.COMPLETING || mPanoStatus == PANO_STATUS.OPENING) { + return; + } + if(bitmap == null) { + if(isCancelling) { + mCompleteSentence = "Cancelling..."; + } else { + mCompleteSentence = "Processing..."; + } + mPanoStatus = PANO_STATUS.COMPLETING; + invalidate(); + mHandler.post(new Runnable() { + public void run() { + if(mPreviewPicture != null) { + waitForQueueDone(); + if(!isCancelling) { + int size = callNativeGetResultSize(); + if (size <= 0) { + callNativeCancelPanorama(); + } else { + byte[] jpegData = new byte[size]; + callNativeCompletePanorama(jpegData, size); + int orient = 270; + if(mDir == DIRECTION_UPDOWN) { + orient = 0; + } + final Uri uri = mController.savePanorama(jpegData, mFinalPictureWidth*8, mFinalPictureHeight, orient); + Bitmap bm = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); + final Bitmap thumbBitmap = CameraUtil.rotate(bm, orient); + if(uri != null) { + mActivity.runOnUiThread(new Runnable() { + public void run() { + mActivity.updateThumbnail(thumbBitmap); + mActivity.notifyNewMedia(uri); + } + }); + } else { + Log.d(TAG, "Image uri is null, size : "+size+" jpegData: "+jpegData); + } + } + } else { + callNativeCancelPanorama(); + mQueueProcessor.queueClear(); + } + } else { + callNativeCancelPanorama(); + } + synchronized (mPreviewBitmapLock) { + if(mPreviewPicture != null) { + mPreviewPicture.bitmap.recycle(); + mPreviewPicture.bitmapIn.recycle(); + } + mPreviewPicture = null; + mGuidePicture = null; + } + callNativeInstanceRelease(); + mPanoStatus = PANO_STATUS.INACTIVE; + mShouldFinish = false; + mOrientation = mPendingOrientation; + } + }); + return; + } + if(mPanoStatus == PANO_STATUS.INACTIVE) { + mPanoStatus = PANO_STATUS.OPENING; + mHandler.post(new Runnable() { + public void run() { + int width; + int height; + if (mOrientation == 0 || mOrientation == 180) { + width = mPreviewThumbWidth; + height = mPreviewThumbHeight; + } else { + width = mPreviewThumbHeight; + height = mPreviewThumbWidth; + } + if(callNativeInstanceInit(width, height, width, 0, 1) < 0) { + Log.e(TAG,"Failed to create panorama native instance"); + mPanoStatus = PANO_STATUS.INACTIVE; + mOrientation = mPendingOrientation; + return; + } + mPanoStatus = PANO_STATUS.ACTIVE_UNKNOWN; + } + }); + return; + } + + if(mPanoStatus != PANO_STATUS.INACTIVE) + { + if(mIsFrameProcessing) { + return; + } + mIsFrameProcessing = true; + mHandler.post(new Runnable() { + public void run() { + Picture picture; + if (mTempBitmap == null || mTempOrietnation != mOrientation) { + if (mOrientation == 0 || mOrientation == 180) { + mTempBitmap = Bitmap.createBitmap(mPreviewThumbWidth, mPreviewThumbHeight, Bitmap.Config.ARGB_8888); + } else {//if(mOrientation == 90 || mOrientation == 270) + mTempBitmap = Bitmap.createBitmap(mPreviewThumbHeight, mPreviewThumbWidth, Bitmap.Config.ARGB_8888); + } + mTempOrietnation = mOrientation; + } + rotateAndScale(bitmap, mTempBitmap, mPanoPreviewRatioToCamera); + bitmapToDataNV21(mTempBitmap); + boolean[] isKey = new boolean[1]; + int[] framePos = new int[3]; + int[] moveSpeed = new int[1]; + processPreviewFrame(isKey, framePos, moveSpeed); + if (framePos[2] == DIRECTION_GOT_LOST) { + mProgressSentence = mActivity.getResources().getString(R.string.panocapture_direction_is_not_determined); + } else { + mProgressSentence = ""; + mDir = framePos[2]; + } + + if (isKey[0]) { + mQueueProcessor.addTask(bitmap, framePos[0], framePos[1], framePos[2]); + } + picture = new Picture(mTempBitmap, mCurrDegX, mCurrDegY, framePos[0], framePos[1]); + if (mPanoStatus == PANO_STATUS.ACTIVE_UNKNOWN) { + if (framePos[0] < -DECISION_MARGIN) { + mPanoStatus = PANO_STATUS.ACTIVE_RIGHT; + } else if (framePos[0] > DECISION_MARGIN) { + mPanoStatus = PANO_STATUS.ACTIVE_LEFT; + } else if (framePos[1] < -DECISION_MARGIN) { + mPanoStatus = PANO_STATUS.ACTIVE_DOWN; + } else if (framePos[1] > DECISION_MARGIN) { + mPanoStatus = PANO_STATUS.ACTIVE_UP; + } + } + if (mPreviewPicture == null && mPanoStatus != PANO_STATUS.ACTIVE_UNKNOWN) { + Picture masterPicture; + Bitmap masterBitmap; + Bitmap liveBitmap; + if (mPanoStatus == PANO_STATUS.ACTIVE_RIGHT || mPanoStatus == PANO_STATUS.ACTIVE_LEFT) { + if (mOrientation == 0 || mOrientation == 180) { + mFinalDoneLength = mPreviewThumbWidth * MAX_PANO_FRAME; + masterBitmap = Bitmap.createBitmap(mFinalDoneLength, mPreviewThumbHeight, Bitmap.Config.ARGB_8888); + liveBitmap = Bitmap.createBitmap(mPreviewThumbWidth, mPreviewThumbHeight, Bitmap.Config.ARGB_8888); + } else {//if(mOrientation == 90 || mOrientation == 270) + mFinalDoneLength = mPreviewThumbHeight * MAX_PANO_FRAME; + masterBitmap = Bitmap.createBitmap(mFinalDoneLength, mPreviewThumbWidth, Bitmap.Config.ARGB_8888); + liveBitmap = Bitmap.createBitmap(mPreviewThumbHeight, mPreviewThumbWidth, Bitmap.Config.ARGB_8888); + } + } else { //UP or DOWN + if (mOrientation == 0 || mOrientation == 180) { + mFinalDoneLength = mPreviewThumbHeight * MAX_PANO_FRAME; + masterBitmap = Bitmap.createBitmap(mPreviewThumbWidth, mFinalDoneLength, Bitmap.Config.ARGB_8888); + liveBitmap = Bitmap.createBitmap(mPreviewThumbWidth, mPreviewThumbHeight, Bitmap.Config.ARGB_8888); + } else {//if(mOrientation == 90 || mOrientation == 270) + mFinalDoneLength = mPreviewThumbWidth * MAX_PANO_FRAME; + masterBitmap = Bitmap.createBitmap(mPreviewThumbHeight, mFinalDoneLength, Bitmap.Config.ARGB_8888); + liveBitmap = Bitmap.createBitmap(mPreviewThumbHeight, mPreviewThumbWidth, Bitmap.Config.ARGB_8888); + } + } + mGuidePicture = new Picture(null, mCurrDegX, mCurrDegY, 0, 0, liveBitmap.getWidth(), liveBitmap.getHeight()); + masterPicture = new Picture(masterBitmap, mCurrDegX, mCurrDegY, 0, 0, 0, 0); + synchronized (mPreviewBitmapLock) { + mPreviewPicture = masterPicture; + mPreviewPicture.bitmapIn = liveBitmap; + } + mIsFirstBlend = true; + } + if (mPreviewPicture != null) { + blendToPreviewPicture(picture, isKey[0], mIsFirstBlend); + if (isAllTaken()) { + stopPano(false, null); + } + mIsFirstBlend = false; + } + mIsFrameProcessing = false; + } + }); + } + } + + private void stopPano(final boolean isCancelling, final String message) { + if(message != null) { + mProgressSentence = message; + Log.w(TAG, message); + } + mHandler.post(new Runnable() { + public void run() { + mController.changePanoStatus(false, isCancelling); + } + }); + } + + private boolean isAllTaken() { + if(mFinalDoneLength == 0) { + return false; + } + if(mPanoStatus == PANO_STATUS.ACTIVE_LEFT || mPanoStatus == PANO_STATUS.ACTIVE_RIGHT) { + if(mPreviewPicture.width >= mFinalDoneLength) { + return true; + } + } else if(mPanoStatus == PANO_STATUS.ACTIVE_UP || mPanoStatus == PANO_STATUS.ACTIVE_DOWN) { + if(mPreviewPicture.height >= mFinalDoneLength) { + return true; + } + } + return false; + } + + private void blendToPreviewPicture(Picture pic2, boolean isKey, boolean isFirst) { + Canvas canvas; + canvas = new Canvas(mPreviewPicture.bitmapIn); + canvas.drawBitmap(pic2.bitmap, 0, 0, null); + Picture pic1 = mPreviewPicture; + if(mPanoStatus == PANO_STATUS.ACTIVE_RIGHT || mPanoStatus == PANO_STATUS.ACTIVE_LEFT) { + int gap = pic2.xPos - pic1.xPos; + pic1.topIn = -pic2.yPos; + if((gap > 0 && mPanoStatus == PANO_STATUS.ACTIVE_RIGHT) || + gap < 0 && mPanoStatus == PANO_STATUS.ACTIVE_LEFT) { + return; + } + gap = pic2.width - Math.abs(gap); + if(isFirst) { + gap = 0; + } + int newWidth = pic1.width + pic2.width - gap; + + if(mPanoStatus == PANO_STATUS.ACTIVE_RIGHT) { + pic1.leftIn = newWidth - pic2.width; + } else { + pic1.leftIn = pic1.bitmap.getWidth() - newWidth; + } + if(pic1.leftIn < 0) { + pic1.leftIn = 0; + } + if(pic1.leftIn > pic1.bitmap.getWidth() - pic2.bitmap.getWidth()) { + pic1.leftIn = pic1.bitmap.getWidth() - pic2.bitmap.getWidth(); + } + + if(isKey || isFirst) { + canvas = new Canvas(pic1.bitmap); + canvas.drawBitmap(pic2.bitmap, pic1.leftIn, 0, null); + int overlapS, overlapE; + if (mPanoStatus == PANO_STATUS.ACTIVE_RIGHT) { + overlapS = newWidth - pic2.width; + overlapE = newWidth - pic2.width + gap; + } else { + overlapS = pic1.bitmap.getWidth() - newWidth + pic2.width - gap; + overlapE = pic1.bitmap.getWidth() - newWidth + pic2.width; + } + for (int i = overlapS; i < overlapE; i++) { + if (i >= pic1.bitmap.getWidth() || i - overlapS >= pic2.bitmap.getWidth()) + break; + for (int j = 0; j < pic1.height; j++) { + if (j >= pic1.bitmap.getHeight() || j >= pic2.bitmap.getHeight()) + break; + int iC1 = pic1.bitmap.getPixel(i, j); + int iC2 = pic2.bitmap.getPixel(i - overlapS, j); + int blendAlpha = (overlapE - i) / gap; + int or = blendAlpha * Color.red(iC1) + (1 - blendAlpha) * Color.red(iC2); + int og = blendAlpha * Color.green(iC1) + (1 - blendAlpha) * Color.green(iC2); + int ob = blendAlpha * Color.blue(iC1) + (1 - blendAlpha) * Color.blue(iC2); + int pixel = Color.argb(255, or, og, ob); + pic1.bitmap.setPixel(i, j, pixel); + } + } + } + pic1.width = newWidth; + } else { //UP or DOWN + int gap = pic2.yPos - pic1.yPos; + pic1.leftIn = -pic2.xPos; + if((gap > 0 && mPanoStatus == PANO_STATUS.ACTIVE_DOWN) || + gap < 0 && mPanoStatus == PANO_STATUS.ACTIVE_UP) { + return; + } + gap = pic2.height - Math.abs(gap); + if(isFirst) { + gap = 0; + } + int newHeight = pic1.height + pic2.height - gap; + + if(mPanoStatus == PANO_STATUS.ACTIVE_DOWN) { + pic1.topIn = newHeight - pic2.height; + } else { + pic1.topIn = pic1.bitmap.getHeight() - newHeight; + } + if(pic1.topIn < 0) { + pic1.topIn = 0; + } + if(pic1.topIn > pic1.bitmap.getHeight() - pic2.bitmap.getHeight()) { + pic1.topIn = pic1.bitmap.getHeight() - pic2.bitmap.getHeight(); + } + if(isKey || isFirst) { + canvas = new Canvas(pic1.bitmap); + canvas.drawBitmap(pic2.bitmap, 0, pic1.topIn, null); + int overlapS, overlapE; + if (mPanoStatus == PANO_STATUS.ACTIVE_DOWN) { + overlapS = newHeight - pic2.height; + overlapE = newHeight - pic2.height + gap; + } else { + overlapS = pic1.bitmap.getHeight() - newHeight + pic2.height - gap; + overlapE = pic1.bitmap.getHeight() - newHeight + pic2.height; + } + for (int i = overlapS; i < overlapE; i++) { + if (i >= pic1.bitmap.getHeight() || i - overlapS >= pic2.bitmap.getHeight()) + break; + for (int j = 0; j < pic1.width; j++) { + if (j >= pic1.bitmap.getWidth() || j >= pic2.bitmap.getWidth()) + break; + int iC1 = pic1.bitmap.getPixel(j, i); + int iC2 = pic2.bitmap.getPixel(j, i - overlapS); + int blendAlpha = (overlapE - i) / gap; + int or = blendAlpha * Color.red(iC1) + (1 - blendAlpha) * Color.red(iC2); + int og = blendAlpha * Color.green(iC1) + (1 - blendAlpha) * Color.green(iC2); + int ob = blendAlpha * Color.blue(iC1) + (1 - blendAlpha) * Color.blue(iC2); + int pixel = Color.argb(255, or, og, ob); + pic1.bitmap.setPixel(j, i, pixel); + } + } + } + pic1.height = newHeight; + } + pic1.xPos = pic2.xPos; + pic1.yPos = pic2.yPos; + } + + private void rotateAndScale(Bitmap srcBitmap, Bitmap dstBitmap, float ratio) { + Canvas canvas = new Canvas(dstBitmap); + matrix.reset(); + if(mOrientation == 0 || mOrientation == 270) { + matrix.postRotate((90 + mOrientation + 360) % 360, srcBitmap.getHeight() / 2, srcBitmap.getHeight() / 2); + } else if (mOrientation == 180){ + matrix.postRotate((90 + mOrientation + 180 + 360) % 360, srcBitmap.getHeight() / 2, srcBitmap.getHeight() / 2); + matrix.postRotate(180, srcBitmap.getHeight() / 2, srcBitmap.getWidth() / 2); + } else if(mOrientation == 90) { + matrix.postRotate((90 + mOrientation + 180 + 360) % 360, srcBitmap.getHeight() / 2, srcBitmap.getHeight() / 2); + matrix.postRotate(180, srcBitmap.getWidth() / 2, srcBitmap.getHeight() / 2); + } + matrix.postScale(ratio, ratio); + canvas.drawBitmap(srcBitmap, matrix, null); + } + + public void setOrientation(int orientation) { + if(mPanoStatus != PANO_STATUS.INACTIVE) { + mPendingOrientation = orientation; + return; + } + mOrientation = mPendingOrientation = orientation; + } + + private boolean isPortrait() { + if(mOrientation == 0 || mOrientation == 180) { + return true; + } + return false; + } + + @Override + public void onSensorChanged(SensorEvent event) { + switch (event.sensor.getType()) { + case Sensor.TYPE_ROTATION_VECTOR: + System.arraycopy(event.values, 0, mOldRots, 0, event.values.length); + SensorManager.getRotationMatrixFromVector(mR, mOldRots); + if(isPortrait()) { + SensorManager.remapCoordinateSystem(mR, + SensorManager.AXIS_X, SensorManager.AXIS_Z, + mRR); + } else { + SensorManager.remapCoordinateSystem(mR, + SensorManager.AXIS_Z, SensorManager.AXIS_X, + mRR); + } + SensorManager.getOrientation(mRR, mOrients); + mCurrDegX = (((float) Math.toDegrees(mOrients[0]) + 360) % 360); + mCurrDegY = (((float) Math.toDegrees(mOrients[1]) + 360) % 360); + if(!isPortrait()) { + mCurrDegX = (mCurrDegX + 180) % 360; + mCurrDegY = (-mCurrDegY + 360) % 360; + } + invalidate(); + break; + default: + return; + } + } + + private void lowPassFilteredCopy( float[] a, float[] b) { + for ( int i=0; i < 3; i++ ) { + b[i] = b[i] + 0.45f * (a[i] - b[i]); + } + } + + private void highPassFilteredCopy( float[] a, float[] b, float[] c, boolean isValid ) { + if(!isValid) { + System.arraycopy(a, 0, b, 0, a.length); + return; + } + + for ( int i=0; i < a.length; i++ ) { + b[i] = 1.2f*(b[i] + a[i] - c[i]); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + + } + + private int callNativeInstanceInit(int width, int height, int stride, int orientation, int colorFormat) { + if(DEBUG) { + Log.d(TAG, "native instance init"); + } + int rtv = nativeInstanceInit(width, height, stride, orientation, colorFormat); + if(DEBUG) { + Log.d(TAG, "native instance init done"); + } + return rtv; + } + + private int callNativeInstanceRelease() { + if(DEBUG) { + Log.d(TAG, "native instance release"); + } + int rtv = nativeInstanceRelease(); + if(DEBUG) { + Log.d(TAG, "native instance release done"); + } + return rtv; + } + + private int callNativeProcessPreviewFrame(byte[] frameData ,boolean[] isKey , int[] framePosition , int[] moveSpeed) { + if(DEBUG) { + Log.d(TAG, "native process preview frame"); + } + int rtv = nativeProcessPreviewFrame(frameData, isKey, framePosition, moveSpeed); + if(DEBUG) { + Log.d(TAG, "native process preview frame done"); + } + return rtv; + } + + private int callNativeProcessKeyFrame(byte[] jpegInData, int dataSize, int x, int y, int orientation, int direction) { + if(DEBUG) { + Log.d(TAG, "native process key frame"); + } + int rtv = nativeProcessKeyFrame(jpegInData, dataSize, x, y, orientation, direction); + if(DEBUG) { + Log.d(TAG, "native process key frame done"); + } + return rtv; + } + + private int callNativeCancelPanorama() { + if(DEBUG) { + Log.d(TAG, "native cancel panorama"); + } + int rtv = nativeCancelPanorama(); + if(DEBUG) { + Log.d(TAG, "native cancel panorama done"); + } + return rtv; + } + + private int callNativeGetResultSize() { + if(DEBUG) { + Log.d(TAG, "native getResultSize"); + } + int rtv = nativeGetResultSize(); + if(DEBUG) { + Log.d(TAG, "native getResultSize done"); + } + return rtv; + } + + private int callNativeCompletePanorama(byte[] jpegOutData, int size) { + if(DEBUG) { + Log.d(TAG, "native complete panorama"); + } + int rtv = nativeCompletePanorama(jpegOutData, size); + if(DEBUG) { + Log.d(TAG, "native complete panorama done"); + } + return rtv; + } + + public static boolean isSupportedStatic() { + return mIsSupported; + } + + private native int nativeInstanceInit(int width, int height, int stride, int orientation, int colorFormat); + private native int nativeInstanceRelease(); + private native int nativeProcessPreviewFrame(byte[] frameData ,boolean[] isKey , int[] framePosition , int[] moveSpeed); + private native int nativeProcessKeyFrame(byte[] jpegInData, int dataSize, int x, int y, int orientation, int direction); + private native int nativeCancelPanorama(); + private native int nativeGetResultSize(); + private native int nativeCompletePanorama(byte[] jpegOutData, int size); + + static { + try { + mIsSupported = true; + System.loadLibrary("jni_panorama"); + } catch(UnsatisfiedLinkError e) { + Log.e(TAG, e.toString()); + mIsSupported = false; + } + } +} |