diff options
author | Byunghun Jeon <bjeon@codeaurora.org> | 2015-05-18 11:35:38 -0700 |
---|---|---|
committer | Gerrit - the friendly Code Review server <code-review@localhost> | 2015-07-30 12:29:07 -0700 |
commit | f61915f04cf9c47e1c23688a9eeffbf552617b82 (patch) | |
tree | c78e9f505aadd87d37ed91d74a75c3199bffc575 /src/com | |
parent | a2a57a538c401b564070f10dfac34cae12312928 (diff) | |
download | android_packages_apps_Snap-f61915f04cf9c47e1c23688a9eeffbf552617b82.tar.gz android_packages_apps_Snap-f61915f04cf9c47e1c23688a9eeffbf552617b82.tar.bz2 android_packages_apps_Snap-f61915f04cf9c47e1c23688a9eeffbf552617b82.zip |
SnapdragonCamera: Improved panorama
Improved panorama with faster performance and low memory
consumption. Also displays progress while taking panorama shot.
Change-Id: I88ae1205f056ebe59129e65d69fcc44f46a5ab92
CRs-Fixed: 859274
Diffstat (limited to 'src/com')
-rw-r--r-- | src/com/android/camera/Mosaic.java | 206 | ||||
-rw-r--r-- | src/com/android/camera/MosaicFrameProcessor.java | 268 | ||||
-rw-r--r-- | src/com/android/camera/MosaicPreviewRenderer.java | 207 | ||||
-rw-r--r-- | src/com/android/camera/MosaicRenderer.java | 101 | ||||
-rw-r--r-- | src/com/android/camera/WideAnglePanoramaModule.java | 786 | ||||
-rw-r--r-- | src/com/android/camera/WideAnglePanoramaUI.java | 397 | ||||
-rw-r--r-- | src/com/android/camera/ui/FilmStripView.java | 1 |
7 files changed, 577 insertions, 1389 deletions
diff --git a/src/com/android/camera/Mosaic.java b/src/com/android/camera/Mosaic.java deleted file mode 100644 index a9bc32e12..000000000 --- a/src/com/android/camera/Mosaic.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2013 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; - -/** - * The Java interface to JNI calls regarding mosaic stitching. - * - * A high-level usage is: - * - * Mosaic mosaic = new Mosaic(); - * mosaic.setSourceImageDimensions(width, height); - * mosaic.reset(blendType); - * - * while ((pixels = hasNextImage()) != null) { - * mosaic.setSourceImage(pixels); - * } - * - * mosaic.createMosaic(highRes); - * byte[] result = mosaic.getFinalMosaic(); - * - */ -public class Mosaic { - /** - * In this mode, the images are stitched together in the same spatial arrangement as acquired - * i.e. if the user follows a curvy trajectory, the image boundary of the resulting mosaic will - * be curved in the same manner. This mode is useful if the user wants to capture a mosaic as - * if "painting" the scene using the smart-phone device and does not want any corrective warps - * to distort the captured images. - */ - public static final int BLENDTYPE_FULL = 0; - - /** - * This mode is the same as BLENDTYPE_FULL except that the resulting mosaic is rotated - * to balance the first and last images to be approximately at the same vertical offset in the - * output mosaic. This is useful when acquiring a mosaic by a typical panning-like motion to - * remove a one-sided curve in the mosaic (typically due to the camera not staying horizontal - * during the video capture) and convert it to a more symmetrical "smiley-face" like output. - */ - public static final int BLENDTYPE_PAN = 1; - - /** - * This mode compensates for typical "smiley-face" like output in longer mosaics and creates - * a rectangular mosaic with minimal black borders (by unwrapping the mosaic onto an imaginary - * cylinder). If the user follows a curved trajectory (instead of a perfect panning trajectory), - * the resulting mosaic here may suffer from some image distortions in trying to map the - * trajectory to a cylinder. - */ - public static final int BLENDTYPE_CYLINDERPAN = 2; - - /** - * This mode is basically BLENDTYPE_CYLINDERPAN plus doing a rectangle cropping before returning - * the mosaic. The mode is useful for making the resulting mosaic have a rectangle shape. - */ - public static final int BLENDTYPE_HORIZONTAL =3; - - /** - * This strip type will use the default thin strips where the strips are - * spaced according to the image capture rate. - */ - public static final int STRIPTYPE_THIN = 0; - - /** - * This strip type will use wider strips for blending. The strip separation - * is controlled by a threshold on the native side. Since the strips are - * wider, there is an additional cross-fade blending step to make the seam - * boundaries smoother. Since this mode uses lesser image frames, it is - * computationally more efficient than the thin strip mode. - */ - public static final int STRIPTYPE_WIDE = 1; - - /** - * Return flags returned by createMosaic() are one of the following. - */ - public static final int MOSAIC_RET_OK = 1; - public static final int MOSAIC_RET_ERROR = -1; - public static final int MOSAIC_RET_CANCELLED = -2; - public static final int MOSAIC_RET_LOW_TEXTURE = -3; - public static final int MOSAIC_RET_FEW_INLIERS = 2; - - - static { - System.loadLibrary("jni_snapcammosaic"); - } - - /** - * Allocate memory for the image frames at the given resolution. - * - * @param width width of the input frames in pixels - * @param height height of the input frames in pixels - */ - public native void allocateMosaicMemory(int width, int height); - - /** - * Free memory allocated by allocateMosaicMemory. - * - */ - public native void freeMosaicMemory(); - - /** - * Pass the input image frame to the native layer. Each time the a new - * source image t is set, the transformation matrix from the first source - * image to t is computed and returned. - * - * @param pixels source image of NV21 format. - * @return Float array of length 11; first 9 entries correspond to the 3x3 - * transformation matrix between the first frame and the passed frame; - * the 10th entry is the number of the passed frame, where the counting - * starts from 1; and the 11th entry is the returning code, whose value - * is one of those MOSAIC_RET_* returning flags defined above. - */ - public native float[] setSourceImage(byte[] pixels); - - /** - * This is an alternative to the setSourceImage function above. This should - * be called when the image data is already on the native side in a fixed - * byte array. In implementation, this array is filled by the GL thread - * using glReadPixels directly from GPU memory (where it is accessed by - * an associated SurfaceTexture). - * - * @return Float array of length 11; first 9 entries correspond to the 3x3 - * transformation matrix between the first frame and the passed frame; - * the 10th entry is the number of the passed frame, where the counting - * starts from 1; and the 11th entry is the returning code, whose value - * is one of those MOSAIC_RET_* returning flags defined above. - */ - public native float[] setSourceImageFromGPU(); - - /** - * Set the type of blending. - * - * @param type the blending type defined in the class. {BLENDTYPE_FULL, - * BLENDTYPE_PAN, BLENDTYPE_CYLINDERPAN, BLENDTYPE_HORIZONTAL} - */ - public native void setBlendingType(int type); - - /** - * Set the type of strips to use for blending. - * @param type the blending strip type to use {STRIPTYPE_THIN, - * STRIPTYPE_WIDE}. - */ - public native void setStripType(int type); - - /** - * Tell the native layer to create the final mosaic after all the input frame - * data have been collected. - * The case of generating high-resolution mosaic may take dozens of seconds to finish. - * - * @param value True means generating a high-resolution mosaic - - * which is based on the original images set in setSourceImage(). - * False means generating a low-resolution version - - * which is based on 1/4 downscaled images from the original images. - * @return Returns a status code suggesting if the mosaic building was - * successful, in error, or was cancelled by the user. - */ - public native int createMosaic(boolean value); - - /** - * Get the data for the created mosaic. - * - * @return Returns an integer array which contains the final mosaic in the ARGB_8888 format. - * The first MosaicWidth*MosaicHeight values contain the image data, followed by 2 - * integers corresponding to the values MosaicWidth and MosaicHeight respectively. - */ - public native int[] getFinalMosaic(); - - /** - * Get the data for the created mosaic. - * - * @return Returns a byte array which contains the final mosaic in the NV21 format. - * The first MosaicWidth*MosaicHeight*1.5 values contain the image data, followed by - * 8 bytes which pack the MosaicWidth and MosaicHeight integers into 4 bytes each - * respectively. - */ - public native byte[] getFinalMosaicNV21(); - - /** - * Reset the state of the frame arrays which maintain the captured frame data. - * Also re-initializes the native mosaic object to make it ready for capturing a new mosaic. - */ - public native void reset(); - - /** - * Get the progress status of the mosaic computation process. - * @param hires Boolean flag to select whether to report progress of the - * low-res or high-res mosaicer. - * @param cancelComputation Boolean flag to allow cancelling the - * mosaic computation when needed from the GUI end. - * @return Returns a number from 0-100 where 50 denotes that the mosaic - * computation is 50% done. - */ - public native int reportProgress(boolean hires, boolean cancelComputation); -} diff --git a/src/com/android/camera/MosaicFrameProcessor.java b/src/com/android/camera/MosaicFrameProcessor.java index cb305344d..de92f347f 100644 --- a/src/com/android/camera/MosaicFrameProcessor.java +++ b/src/com/android/camera/MosaicFrameProcessor.java @@ -16,55 +16,48 @@ package com.android.camera; +import android.content.Context; +import android.os.Handler; +import android.os.Message; import android.util.Log; +import com.android.camera.WideAnglePanoramaModule.INotifier; + /** * A singleton to handle the processing of each frame by {@link Mosaic}. */ public class MosaicFrameProcessor { private static final String TAG = "MosaicFrameProcessor"; - private static final int NUM_FRAMES_IN_BUFFER = 2; - private static final int MAX_NUMBER_OF_FRAMES = 100; - private static final int MOSAIC_RET_CODE_INDEX = 10; - private static final int FRAME_COUNT_INDEX = 9; - private static final int X_COORD_INDEX = 2; - private static final int Y_COORD_INDEX = 5; - private static final int HR_TO_LR_DOWNSAMPLE_FACTOR = 4; - private static final int WINDOW_SIZE = 3; - - private Mosaic mMosaicer; - private boolean mIsMosaicMemoryAllocated = false; - private float mTranslationLastX; - private float mTranslationLastY; - - private int mFillIn = 0; - private int mTotalFrameCount = 0; - private int mLastProcessFrameIdx = -1; - private int mCurrProcessFrameIdx = -1; - private boolean mFirstRun; - - // Panning rate is in unit of percentage of image content translation per - // frame. Use moving average to calculate the panning rate. - private float mPanningRateX; - private float mPanningRateY; - - private float[] mDeltaX = new float[WINDOW_SIZE]; - private float[] mDeltaY = new float[WINDOW_SIZE]; - private int mOldestIdx = 0; - private float mTotalTranslationX = 0f; - private float mTotalTranslationY = 0f; - - private ProgressListener mProgressListener; + + public static final int MAX_HORIZONAL_ANGLE = 180; + public static final int MAX_VERTICAL_ANGLE = 180; + public static final int MSG_PANORAMA_TIP = 0x0001; + public static final int MSG_CAPTURE_SUCCESS = 0x0002; + public static final int MSG_CAPTURE_FAILED = 0x0003; + public static final int MSG_UPDATE_UI = 0x0004; + /** Panorama direction unknown. */ + public final static int DIRECTION_UNKNOW = 0x00000000; + /** Panorama direction left to right. */ + public final static int DIRECTION_LEFTTORIGHT = 0x00000001; + /** Panorama direction right to left. */ + public final static int DIRECTION_RIGHTTOLEFT = 0x00000002; + /** Panorama direction up to down. */ + public final static int DIRECTION_UPTODOWN = 0x00000004; + /** Panorama direction down to up. */ + public final static int DIRECTION_DOWMTOUP = 0x00000008; private int mPreviewWidth; private int mPreviewHeight; - private int mPreviewBufferSize; + private Handler mThreadHandler = null; + private long mHandler = 0; + private boolean mIsActive = false; + + private INotifier mPanoNotifier; private static MosaicFrameProcessor sMosaicFrameProcessor; // singleton - public interface ProgressListener { - public void onProgress(boolean isFinished, float panningRateX, float panningRateY, - float progressX, float progressY); + static { + System.loadLibrary("jni_snapcammosaic"); } public static MosaicFrameProcessor getInstance() { @@ -74,164 +67,87 @@ public class MosaicFrameProcessor { return sMosaicFrameProcessor; } - private MosaicFrameProcessor() { - mMosaicer = new Mosaic(); + public synchronized boolean IsInited() { + return (mHandler != 0 && mIsActive); } - public void setProgressListener(ProgressListener listener) { - mProgressListener = listener; - } + public synchronized int Init(Context context, int maxFrameCount, int width, int height, + INotifier notifier) { + Log.v(TAG, "Init <----"); + Uninit(); + mPanoNotifier = notifier; + mThreadHandler = new Handler(context.getMainLooper(), new Handler.Callback() { + + @Override + public boolean handleMessage(Message msg) { + if (mPanoNotifier != null) { + mPanoNotifier.onNotify(msg.what, msg.obj); + } + return true; + } - public int reportProgress(boolean hires, boolean cancel) { - return mMosaicer.reportProgress(hires, cancel); + }); + mPreviewWidth = width; + mPreviewHeight = height; + mHandler = _InitMosaic(context, this, maxFrameCount, width, height); + mIsActive = (mHandler != 0); + Log.v(TAG, "Init mHandler: " + mHandler + " ---->"); + return mIsActive ? 0 : -1; } - public void initialize(int previewWidth, int previewHeight, int bufSize) { - mPreviewWidth = previewWidth; - mPreviewHeight = previewHeight; - mPreviewBufferSize = bufSize; - setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize); - setStripType(Mosaic.STRIPTYPE_WIDE); - // no need to call reset() here. reset() should be called by the client - // after this initialization before calling other methods of this object. + public synchronized void Uninit() { + if (mHandler != 0) { + Log.v(TAG, "Unint <----"); + mIsActive = false; + mThreadHandler = null; + _UninitMosaic(mHandler); + mHandler = 0; + Log.v(TAG, "Unint ---->"); + } } - public void clear() { - if (mIsMosaicMemoryAllocated) { - mMosaicer.freeMosaicMemory(); - mIsMosaicMemoryAllocated = false; + public synchronized int Process(byte[] data, int width, int height) { + int res = -1; + if (IsInited() && data != null && mPreviewWidth == width && mPreviewHeight == height) { + Log.v(TAG, "Process <----"); + res = _ProcessMosaic(mHandler, data, width, height); + Log.v(TAG, "Process res " + res + " ---->"); } - synchronized (this) { - notify(); + else { + Log.v(TAG, "Process Error " + " mWidth " + mPreviewWidth + " mHeight " + mPreviewHeight + + " width " + width + " height " + height + " data " + (data != null)); } + return res; } - public boolean isMosaicMemoryAllocated() { - return mIsMosaicMemoryAllocated; - } - - public void setStripType(int type) { - mMosaicer.setStripType(type); - } - - private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) { - Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize); - - if (mIsMosaicMemoryAllocated) throw new RuntimeException("MosaicFrameProcessor in use!"); - mIsMosaicMemoryAllocated = true; - mMosaicer.allocateMosaicMemory(previewWidth, previewHeight); - } - - public void reset() { - // reset() can be called even if MosaicFrameProcessor is not initialized. - // Only counters will be changed. - mFirstRun = true; - mTotalFrameCount = 0; - mFillIn = 0; - mTotalTranslationX = 0; - mTranslationLastX = 0; - mTotalTranslationY = 0; - mTranslationLastY = 0; - mPanningRateX = 0; - mPanningRateY = 0; - mLastProcessFrameIdx = -1; - mCurrProcessFrameIdx = -1; - for (int i = 0; i < WINDOW_SIZE; ++i) { - mDeltaX[i] = 0f; - mDeltaY[i] = 0f; + public synchronized int StopProcessing() { + int res = -1; + if (IsInited()) { + Log.v(TAG, "StopProcessing <----"); + mIsActive = false; + res = _StopProcessMosaic(mHandler); + Log.v(TAG, "StopProcessing res " + res + " ---->"); } - mMosaicer.reset(); + return res; } - public int createMosaic(boolean highRes) { - return mMosaicer.createMosaic(highRes); + public int onNotify(int key, Object obj) { + if (mThreadHandler != null && mHandler != 0) { + Message msg = new Message(); + msg.what = key; + msg.obj = obj; + mThreadHandler.sendMessage(msg); + } + return 0; } - public byte[] getFinalMosaicNV21() { - return mMosaicer.getFinalMosaicNV21(); - } + public native long _InitMosaic(Context context, Object thiz, int maxFrameCount, int width, + int height); - // Processes the last filled image frame through the mosaicer and - // updates the UI to show progress. - // When done, processes and displays the final mosaic. - public void processFrame() { - if (!mIsMosaicMemoryAllocated) { - // clear() is called and buffers are cleared, stop computation. - // This can happen when the onPause() is called in the activity, but still some frames - // are not processed yet and thus the callback may be invoked. - return; - } + public native int _UninitMosaic(long handler); - mCurrProcessFrameIdx = mFillIn; - mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER); - - // Check that we are trying to process a frame different from the - // last one processed (useful if this class was running asynchronously) - if (mCurrProcessFrameIdx != mLastProcessFrameIdx) { - mLastProcessFrameIdx = mCurrProcessFrameIdx; - - // TODO: make the termination condition regarding reaching - // MAX_NUMBER_OF_FRAMES solely determined in the library. - if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) { - // If we are still collecting new frames for the current mosaic, - // process the new frame. - calculateTranslationRate(); - - // Publish progress of the ongoing processing - if (mProgressListener != null) { - mProgressListener.onProgress(false, mPanningRateX, mPanningRateY, - mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth, - mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight); - } - } else { - if (mProgressListener != null) { - mProgressListener.onProgress(true, mPanningRateX, mPanningRateY, - mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth, - mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight); - } - } - } - } + public native int _StopProcessMosaic(long handler); - public void calculateTranslationRate() { - float[] frameData = mMosaicer.setSourceImageFromGPU(); - int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX]; - mTotalFrameCount = (int) frameData[FRAME_COUNT_INDEX]; - float translationCurrX = frameData[X_COORD_INDEX]; - float translationCurrY = frameData[Y_COORD_INDEX]; - - if (mFirstRun) { - // First time: no need to update delta values. - mTranslationLastX = translationCurrX; - mTranslationLastY = translationCurrY; - mFirstRun = false; - return; - } + public native int _ProcessMosaic(long handler, byte[] data, int width, int height); - // Moving average: remove the oldest translation/deltaTime and - // add the newest translation/deltaTime in - int idx = mOldestIdx; - mTotalTranslationX -= mDeltaX[idx]; - mTotalTranslationY -= mDeltaY[idx]; - mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX); - mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY); - mTotalTranslationX += mDeltaX[idx]; - mTotalTranslationY += mDeltaY[idx]; - - // The panning rate is measured as the rate of the translation percentage in - // image width/height. Take the horizontal panning rate for example, the image width - // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR). - // To get the horizontal translation percentage, the horizontal translation, - // (translationCurrX - mTranslationLastX), is divided by the - // image width. We then get the rate by dividing the translation percentage with the - // number of frames. - mPanningRateX = mTotalTranslationX / - (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE; - mPanningRateY = mTotalTranslationY / - (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE; - - mTranslationLastX = translationCurrX; - mTranslationLastY = translationCurrY; - mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE; - } } diff --git a/src/com/android/camera/MosaicPreviewRenderer.java b/src/com/android/camera/MosaicPreviewRenderer.java deleted file mode 100644 index ba35b6f5a..000000000 --- a/src/com/android/camera/MosaicPreviewRenderer.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2013 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.SurfaceTexture; -import android.os.ConditionVariable; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; - -import javax.microedition.khronos.opengles.GL10; - -public class MosaicPreviewRenderer { - - @SuppressWarnings("unused") - private static final String TAG = "CAM_MosaicPreviewRenderer"; - - private int mWidth; // width of the view in UI - private int mHeight; // height of the view in UI - - private boolean mIsLandscape = true; - private int mOrientation = 0; - private final float[] mTransformMatrix = new float[16]; - - private ConditionVariable mEglThreadBlockVar = new ConditionVariable(); - private HandlerThread mEglThread; - private MyHandler mHandler; - private SurfaceTextureRenderer mSTRenderer; - - private SurfaceTexture mInputSurfaceTexture; - - private boolean mEnableWarpedPanoPreview = false; - - private class MyHandler extends Handler { - public static final int MSG_INIT_SYNC = 0; - public static final int MSG_SHOW_PREVIEW_FRAME_SYNC = 1; - public static final int MSG_SHOW_PREVIEW_FRAME = 2; - public static final int MSG_ALIGN_FRAME_SYNC = 3; - public static final int MSG_RELEASE = 4; - public static final int MSG_DO_PREVIEW_RESET = 5; - public MyHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_INIT_SYNC: - doInit(); - mEglThreadBlockVar.open(); - break; - case MSG_SHOW_PREVIEW_FRAME_SYNC: - doShowPreviewFrame(); - mEglThreadBlockVar.open(); - break; - case MSG_DO_PREVIEW_RESET: - doPreviewReset(); - break; - case MSG_SHOW_PREVIEW_FRAME: - doShowPreviewFrame(); - break; - case MSG_ALIGN_FRAME_SYNC: - doAlignFrame(); - mEglThreadBlockVar.open(); - break; - case MSG_RELEASE: - doRelease(); - mEglThreadBlockVar.open(); - break; - } - } - - private void doAlignFrame() { - mInputSurfaceTexture.updateTexImage(); - mInputSurfaceTexture.getTransformMatrix(mTransformMatrix); - - // Call setPreviewBackground to render high-res RGB textures to full screen. - MosaicRenderer.setPreviewBackground(true); - MosaicRenderer.preprocess(mTransformMatrix); - MosaicRenderer.step(); - MosaicRenderer.setPreviewBackground(false); - - MosaicRenderer.setWarping(true); - MosaicRenderer.transferGPUtoCPU(); - MosaicRenderer.updateMatrix(); - MosaicRenderer.step(); - } - - private void doShowPreviewFrame() { - mInputSurfaceTexture.updateTexImage(); - mInputSurfaceTexture.getTransformMatrix(mTransformMatrix); - - MosaicRenderer.setWarping(false); - // Call preprocess to render it to low-res and high-res RGB textures. - MosaicRenderer.preprocess(mTransformMatrix); - MosaicRenderer.updateMatrix(); - MosaicRenderer.step(); - } - - private void doInit() { - mInputSurfaceTexture = new SurfaceTexture(MosaicRenderer.init(mEnableWarpedPanoPreview)); - MosaicRenderer.reset(mWidth, mHeight, mIsLandscape, mOrientation); - } - - private void doPreviewReset() { - MosaicRenderer.reset(mWidth, mHeight, mIsLandscape, mOrientation); - } - - private void doRelease() { - releaseSurfaceTexture(mInputSurfaceTexture); - mEglThread.quit(); - } - - private void releaseSurfaceTexture(SurfaceTexture st) { - st.release(); - } - - // Should be called from other thread. - public void sendMessageSync(int msg) { - mEglThreadBlockVar.close(); - sendEmptyMessage(msg); - mEglThreadBlockVar.block(); - } - } - - /** - * Constructor. - * - * @param tex The {@link SurfaceTexture} for the final UI output. - * @param w The width of the UI view. - * @param h The height of the UI view. - * @param isLandscape The UI orientation. {@code true} if in landscape, - * false if in portrait. - */ - public MosaicPreviewRenderer(SurfaceTexture tex, int w, int h, boolean isLandscape, - int orientation, boolean enableWarpedPanoPreview) { - mIsLandscape = isLandscape; - mOrientation = orientation; - mEnableWarpedPanoPreview = enableWarpedPanoPreview; - mEglThread = new HandlerThread("PanoramaRealtimeRenderer"); - mEglThread.start(); - mHandler = new MyHandler(mEglThread.getLooper()); - mWidth = w; - mHeight = h; - - SurfaceTextureRenderer.FrameDrawer dummy = new SurfaceTextureRenderer.FrameDrawer() { - @Override - public void onDrawFrame(GL10 gl) { - // nothing, we have our draw functions. - } - }; - mSTRenderer = new SurfaceTextureRenderer(tex, mHandler, dummy); - - // We need to sync this because the generation of surface texture for input is - // done here and the client will continue with the assumption that the - // generation is completed. - mHandler.sendMessageSync(MyHandler.MSG_INIT_SYNC); - } - - public void previewReset(int w, int h, boolean isLandscape, int orientation) { - mWidth = w; - mHeight = h; - mIsLandscape = isLandscape; - mOrientation = orientation; - mHandler.sendEmptyMessage(MyHandler.MSG_DO_PREVIEW_RESET); - mSTRenderer.draw(false); - } - - public void release() { - mSTRenderer.release(); - mHandler.sendMessageSync(MyHandler.MSG_RELEASE); - } - - public void showPreviewFrameSync() { - mHandler.sendMessageSync(MyHandler.MSG_SHOW_PREVIEW_FRAME_SYNC); - mSTRenderer.draw(true); - } - - public void showPreviewFrame() { - mHandler.sendEmptyMessage(MyHandler.MSG_SHOW_PREVIEW_FRAME); - mSTRenderer.draw(false); - } - - public void alignFrameSync() { - mHandler.sendMessageSync(MyHandler.MSG_ALIGN_FRAME_SYNC); - mSTRenderer.draw(true); - } - - public SurfaceTexture getInputSurfaceTexture() { - return mInputSurfaceTexture; - } -} diff --git a/src/com/android/camera/MosaicRenderer.java b/src/com/android/camera/MosaicRenderer.java deleted file mode 100644 index 5006b364d..000000000 --- a/src/com/android/camera/MosaicRenderer.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2011 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; - -/** - * The Java interface to JNI calls regarding mosaic preview rendering. - * - */ -public class MosaicRenderer -{ - static - { - System.loadLibrary("jni_snapcammosaic"); - } - - /** - * Function to be called in onSurfaceCreated() to initialize - * the GL context, load and link the shaders and create the - * program. Returns a texture ID to be used for SurfaceTexture. - * - * @return textureID the texture ID of the newly generated texture to - * be assigned to the SurfaceTexture object. - */ - public static native int init(boolean mEnableWarpedPanoPreview); - - /** - * Pass the drawing surface's width and height to initialize the - * renderer viewports and FBO dimensions. - * - * @param width width of the drawing surface in pixels. - * @param height height of the drawing surface in pixels. - * @param isLandscapeOrientation is the orientation of the activity layout in landscape. - * @param oientation is the orientation value in integer. - */ - public static native void reset(int width, int height, - boolean isLandscapeOrientation, int orientation); - - /** - * Calling this function will render the SurfaceTexture to a new 2D texture - * using the provided STMatrix. - * - * @param stMatrix texture coordinate transform matrix obtained from the - * Surface texture - */ - public static native void preprocess(float[] stMatrix); - - /** - * This function calls glReadPixels to transfer both the low-res and high-res - * data from the GPU memory to the CPU memory for further processing by the - * mosaicing library. - */ - public static native void transferGPUtoCPU(); - - /** - * Function to be called in onDrawFrame() to update the screen with - * the new frame data. - */ - public static native void step(); - - /** - * Call this function when a new low-res frame has been processed by - * the mosaicing library. This will tell the renderer library to - * update its texture and warping transformation. Any calls to step() - * after this call will use the new image frame and transformation data. - */ - public static native void updateMatrix(); - - /** - * This function allows toggling between showing the input image data - * (without applying any warp) and the warped image data. For running - * the renderer as a viewfinder, we set the flag to false. To see the - * preview mosaic, we set the flag to true. - * - * @param flag boolean flag to set the warping to true or false. - */ - public static native void setWarping(boolean flag); - /** - * This function allows toggling between drawing the background full - * screen preview image data to screen and drawing the warped smaller - * preview on top of it. To render the full screen background preview, - * we set the falsg to true and to render the warped image on top of this - * we set the flag to false and flag in setWarping to true. - * - * @param flag boolean flag to set the warping to true or false. - */ - public static native void setPreviewBackground(boolean flag); -} diff --git a/src/com/android/camera/WideAnglePanoramaModule.java b/src/com/android/camera/WideAnglePanoramaModule.java index e7285fa6a..eb02bb50f 100644 --- a/src/com/android/camera/WideAnglePanoramaModule.java +++ b/src/com/android/camera/WideAnglePanoramaModule.java @@ -25,28 +25,28 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.ImageFormat; +import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.SurfaceTexture; import android.graphics.YuvImage; import android.hardware.Camera.Parameters; import android.hardware.Camera.Size; import android.location.Location; import android.net.Uri; -import android.os.AsyncTask; import android.os.Handler; import android.os.Message; -import android.os.PowerManager; import android.util.Log; import android.view.KeyEvent; import android.view.OrientationEventListener; +import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.Toast; import com.android.camera.PhotoModule; import com.android.camera.CameraManager.CameraProxy; +import com.android.camera.CameraManager.CameraPreviewDataCallback; import com.android.camera.app.OrientationManager; import com.android.camera.data.LocalData; import com.android.camera.exif.ExifInterface; @@ -64,12 +64,9 @@ import java.util.TimeZone; /** * Activity to handle panorama capturing. */ -public class WideAnglePanoramaModule - implements CameraModule, WideAnglePanoramaController, - SurfaceTexture.OnFrameAvailableListener { - - public static final int DEFAULT_SWEEP_ANGLE = 160; - public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL; +public class WideAnglePanoramaModule implements + CameraModule, WideAnglePanoramaController, + CameraPreviewDataCallback { public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720; private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1; @@ -87,43 +84,23 @@ public class WideAnglePanoramaModule public static final int CAPTURE_STATE_VIEWFINDER = 0; public static final int CAPTURE_STATE_MOSAIC = 1; - // The unit of speed is degrees per frame. - private static final float PANNING_SPEED_THRESHOLD = 2.5f; private static final boolean DEBUG = false; private ContentResolver mContentResolver; private WideAnglePanoramaUI mUI; - private MosaicPreviewRenderer mMosaicPreviewRenderer; - private Object mRendererLock = new Object(); - private Object mWaitObject = new Object(); private final CameraErrorCallback mErrorCallback = new CameraErrorCallback(); - private String mPreparePreviewString; - private String mDialogTitle; - private String mDialogOkString; - private String mDialogPanoramaFailedString; - private String mDialogWaitingPreviousString; - - private int mPreviewUIWidth; - private int mPreviewUIHeight; private boolean mUsingFrontCamera; private int mCameraPreviewWidth; private int mCameraPreviewHeight; private int mCameraState; private int mCaptureState; - private PowerManager.WakeLock mPartialWakeLock; private MosaicFrameProcessor mMosaicFrameProcessor; - private boolean mMosaicFrameProcessorInitialized; - private AsyncTask <Void, Void, Void> mWaitProcessorTask; private long mTimeTaken; private Handler mMainHandler; - private SurfaceTexture mCameraTexture; - private boolean mThreadRunning; - private boolean mCancelComputation; - private float mHorizontalViewAngle; - private float mVerticalViewAngle; - + private SurfaceHolder mCameraSurfaceHolder; + private boolean isCapturing = false; // Prefer FOCUS_MODE_INFINITY to FOCUS_MODE_CONTINUOUS_VIDEO because of // getting a better image quality by the former. private String mTargetFocusMode = Parameters.FOCUS_MODE_INFINITY; @@ -139,8 +116,6 @@ public class WideAnglePanoramaModule private SoundClips.Player mSoundPlayer; - private Runnable mOnFrameAvailableRunnable; - private CameraActivity mActivity; private View mRootView; private CameraProxy mCameraDevice; @@ -153,11 +128,25 @@ public class WideAnglePanoramaModule private boolean mPreviewFocused = true; private boolean mPreviewLayoutChanged = false; - private boolean mDirectionChanged = false; + // The display rotation in degrees. This is only valid when mCameraState is + // not PREVIEW_STOPPED. + private int mDisplayRotation; + // The value for android.hardware.Camera.setDisplayOrientation. + private int mCameraDisplayOrientation; + // The value for UI components like indicators. + private int mDisplayOrientation; + + private int mCaptureOrientation = MosaicFrameProcessor.DIRECTION_UNKNOW; + private int mPanoState = STATUS_PREVIEW; + public static final int STATUS_PREVIEW = 0x0001; + public static final int STATUS_PREPARE = 0x0002; + public static final int STATUS_CAPTURING = 0x0003; + public static final int STATUS_SUCCESS = 0x0004; + public static final int STATUS_FAILED = 0x0006; @Override public void onPreviewUIReady() { - configMosaicPreview(); + resetToPreviewIfPossible(); } @Override @@ -223,72 +212,14 @@ public class WideAnglePanoramaModule mOrientationManager = new OrientationManager(activity); mCaptureState = CAPTURE_STATE_VIEWFINDER; mUI = new WideAnglePanoramaUI(mActivity, this, (ViewGroup) mRootView); - mUI.setCaptureProgressOnDirectionChangeListener( - new PanoProgressBar.OnDirectionChangeListener() { - @Override - public void onDirectionChange(int direction) { - if (mDirectionChanged) { - stopCapture(false); - return; - } - if (mCaptureState == CAPTURE_STATE_MOSAIC) { - mUI.showDirectionIndicators(direction); - } - if (direction != PanoProgressBar.DIRECTION_NONE) { - mDirectionChanged = true; - } - } - }); mContentResolver = mActivity.getContentResolver(); - // This runs in UI thread. - mOnFrameAvailableRunnable = new Runnable() { - @Override - public void run() { - // Frames might still be available after the activity is paused. - // If we call onFrameAvailable after pausing, the GL thread will crash. - if (mPaused) return; - - MosaicPreviewRenderer renderer = null; - synchronized (mRendererLock) { - if (mMosaicPreviewRenderer == null) { - return; - } - renderer = mMosaicPreviewRenderer; - } - if (mRootView.getVisibility() != View.VISIBLE) { - renderer.showPreviewFrameSync(); - mRootView.setVisibility(View.VISIBLE); - } else { - if (mCaptureState == CAPTURE_STATE_VIEWFINDER) { - if (mPreviewLayoutChanged) { - boolean isLandscape = (mDeviceOrientation / 90) % 2 == 1; - renderer.previewReset(mPreviewUIWidth, mPreviewUIHeight, - isLandscape, mDeviceOrientation); - mPreviewLayoutChanged = false; - } - renderer.showPreviewFrame(); - } else { - renderer.alignFrameSync(); - mMosaicFrameProcessor.processFrame(); - } - } - } - }; - - PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE); - mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Panorama"); mOrientationEventListener = new PanoOrientationEventListener(mActivity); mMosaicFrameProcessor = MosaicFrameProcessor.getInstance(); Resources appRes = mActivity.getResources(); - mPreparePreviewString = appRes.getString(R.string.pano_dialog_prepare_preview); - mDialogTitle = appRes.getString(R.string.pano_dialog_title); - mDialogOkString = appRes.getString(R.string.dialog_ok); - mDialogPanoramaFailedString = appRes.getString(R.string.pano_dialog_panorama_failed); - mDialogWaitingPreviousString = appRes.getString(R.string.pano_dialog_waiting_previous); mPreferences = new ComboPreferences(mActivity); CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal(), activity); @@ -298,31 +229,8 @@ public class WideAnglePanoramaModule @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_LOW_RES_FINAL_MOSAIC_READY: - onBackgroundThreadFinished(); - showFinalMosaic((Bitmap) msg.obj); - saveHighResMosaic(); - break; - case MSG_GENERATE_FINAL_MOSAIC_ERROR: - onBackgroundThreadFinished(); - if (mPaused) { - resetToPreviewIfPossible(); - } else { - mUI.showAlertDialog( - mDialogTitle, mDialogPanoramaFailedString, - mDialogOkString, new Runnable() { - @Override - public void run() { - resetToPreviewIfPossible(); - } - }); - } - clearMosaicFrameProcessorIfNeeded(); - break; case MSG_END_DIALOG_RESET_TO_PREVIEW: - onBackgroundThreadFinished(); resetToPreviewIfPossible(); - clearMosaicFrameProcessorIfNeeded(); break; case MSG_CLEAR_SCREEN_DELAY: mActivity.getWindow().clearFlags(WindowManager.LayoutParams. @@ -450,9 +358,6 @@ public class WideAnglePanoramaModule } parameters.set(CameraUtil.RECORDING_HINT, CameraUtil.FALSE); - - mHorizontalViewAngle = parameters.getHorizontalViewAngle(); - mVerticalViewAngle = parameters.getVerticalViewAngle(); } public int getPreviewBufSize() { @@ -467,44 +372,6 @@ public class WideAnglePanoramaModule } /** - * Configures the preview renderer according to the dimension defined by - * {@code mPreviewUIWidth} and {@code mPreviewUIHeight}. - * Will stop the camera preview first. - */ - private void configMosaicPreview() { - if (mPreviewUIWidth == 0 || mPreviewUIHeight == 0 - || mUI.getSurfaceTexture() == null) { - return; - } - - stopCameraPreview(); - synchronized (mRendererLock) { - if (mMosaicPreviewRenderer != null) { - mMosaicPreviewRenderer.release(); - } - mMosaicPreviewRenderer = null; - } - final boolean isLandscape = (mDeviceOrientation / 90) % 2 == 1; - final boolean enableWarpedPanoPreview = - mActivity.getResources().getBoolean(R.bool.enable_warped_pano_preview); - mUI.flipPreviewIfNeeded(); - MosaicPreviewRenderer renderer = new MosaicPreviewRenderer( - mUI.getSurfaceTexture(), mPreviewUIWidth, mPreviewUIHeight, - isLandscape, mDeviceOrientation, enableWarpedPanoPreview); - synchronized (mRendererLock) { - mMosaicPreviewRenderer = renderer; - mCameraTexture = mMosaicPreviewRenderer.getInputSurfaceTexture(); - - if (!mPaused && !mThreadRunning && mWaitProcessorTask == null) { - mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW); - } - mRendererLock.notifyAll(); - } - mMosaicPreviewConfigured = true; - resetToPreviewIfPossible(); - } - - /** * Receives the layout change event from the preview area. So we can * initialize the mosaic preview renderer. */ @@ -515,9 +382,6 @@ public class WideAnglePanoramaModule if (mCaptureState == CAPTURE_STATE_MOSAIC){ capturePending = true; } - mPreviewUIWidth = r - l; - mPreviewUIHeight = b - t; - configMosaicPreview(); if (capturePending == true){ mMainHandler.post(new Runnable() { @Override @@ -531,68 +395,34 @@ public class WideAnglePanoramaModule } } - @Override - public void onFrameAvailable(SurfaceTexture surface) { - /* This function may be called by some random thread, - * so let's be safe and jump back to ui thread. - * No OpenGL calls can be done here. */ - mActivity.runOnUiThread(mOnFrameAvailableRunnable); - } - public void startCapture() { // Reset values so we can do this again. - mCancelComputation = false; mTimeTaken = System.currentTimeMillis(); mActivity.setSwipingEnabled(false); mCaptureState = CAPTURE_STATE_MOSAIC; mUI.onStartCapture(); + mCaptureOrientation = MosaicFrameProcessor.DIRECTION_UNKNOW; Parameters parameters = mCameraDevice.getParameters(); parameters.setAutoExposureLock(true); parameters.setAutoWhiteBalanceLock(true); configureCamera(parameters); + updateState(STATUS_PREPARE); + createPanoramaEngine(); - mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() { - @Override - public void onProgress(boolean isFinished, float panningRateX, float panningRateY, - float progressX, float progressY) { - float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle; - float accumulatedVerticalAngle = progressY * mVerticalViewAngle; - boolean isRotated = !(mDeviceOrientationAtCapture == mDeviceOrientation); - if (isFinished - || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE) - || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE) - || isRotated) { - stopCapture(false); - } else { - float panningRateXInDegree = panningRateX * mHorizontalViewAngle; - float panningRateYInDegree = panningRateY * mVerticalViewAngle; - if (mDeviceOrientation == 180 || mDeviceOrientation == 90) { - accumulatedHorizontalAngle = -accumulatedHorizontalAngle; - accumulatedVerticalAngle = -accumulatedVerticalAngle; - } - mUI.updateCaptureProgress(panningRateXInDegree, panningRateYInDegree, - accumulatedHorizontalAngle, accumulatedVerticalAngle, - PANNING_SPEED_THRESHOLD); - } - } - }); - - mUI.resetCaptureProgress(); - // TODO: calculate the indicator width according to different devices to reflect the actual - // angle of view of the camera device. - mUI.setMaxCaptureProgress(DEFAULT_SWEEP_ANGLE); - mUI.showCaptureProgress(); mDeviceOrientationAtCapture = mDeviceOrientation; keepScreenOn(); mOrientationLocked = true; - int degrees = CameraUtil.getDisplayRotation(mActivity); - int cameraId = CameraHolder.instance().getBackCameraId(); - int orientation = CameraUtil.getDisplayOrientation(degrees, cameraId); - mUI.setProgressOrientation(orientation); + } + + private void createPanoramaEngine() { + if (mMosaicFrameProcessor == null) { + mMosaicFrameProcessor = MosaicFrameProcessor.getInstance(); + } + mMosaicFrameProcessor.Init(mActivity, 30, mCameraPreviewWidth, mCameraPreviewHeight, + mPanoNotifier); } private void stopCapture(boolean aborted) { - mDirectionChanged = false; mCaptureState = CAPTURE_STATE_VIEWFINDER; mUI.onStopCapture(); Parameters parameters = mCameraDevice.getParameters(); @@ -600,41 +430,20 @@ public class WideAnglePanoramaModule parameters.setAutoWhiteBalanceLock(false); configureCamera(parameters); - mMosaicFrameProcessor.setProgressListener(null); - stopCameraPreview(); + if (mMosaicFrameProcessor != null) { + mMosaicFrameProcessor.StopProcessing(); + } - mCameraTexture.setOnFrameAvailableListener(null); + stopCameraPreview(); + mMainHandler.sendMessage(mMainHandler.obtainMessage( + MSG_END_DIALOG_RESET_TO_PREVIEW)); - if (!aborted && !mThreadRunning) { - mUI.showWaitingDialog(mPreparePreviewString); - // Hide shutter button, shutter icon, etc when waiting for - // panorama to stitch - mUI.hideUI(); - runBackgroundThread(new Thread() { - @Override - public void run() { - MosaicJpeg jpeg = generateFinalMosaic(false); - - if (jpeg != null && jpeg.isValid) { - Bitmap bitmap = null; - bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length); - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap)); - } else { - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_END_DIALOG_RESET_TO_PREVIEW)); - } - } - }); - } keepScreenOnAwhile(); } @Override public void onShutterButtonClick() { - // If mCameraTexture == null then GL setup is not finished yet. - // No buttons can be pressed. - if (mPaused || mThreadRunning || mCameraTexture == null) { + if (mPaused || mCameraSurfaceHolder == null) { return; } // Since this button will stay on the screen when capturing, we need to check the state @@ -659,35 +468,6 @@ public class WideAnglePanoramaModule } } - public void reportProgress() { - mUI.resetSavingProgress(); - Thread t = new Thread() { - @Override - public void run() { - while (mThreadRunning) { - final int progress = mMosaicFrameProcessor.reportProgress( - true, mCancelComputation); - - try { - synchronized (mWaitObject) { - mWaitObject.wait(50); - } - } catch (InterruptedException e) { - throw new RuntimeException("Panorama reportProgress failed", e); - } - // Update the progress bar - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mUI.updateSavingProgress(progress); - } - }); - } - } - }; - t.start(); - } - private int getCaptureOrientation() { // The panorama image returned from the library is oriented based on the // natural orientation of a camera. We need to set an orientation for the image @@ -713,62 +493,9 @@ public class WideAnglePanoramaModule return mCameraOrientation; } - public void saveHighResMosaic() { - runBackgroundThread(new Thread() { - @Override - public void run() { - mPartialWakeLock.acquire(); - MosaicJpeg jpeg; - try { - jpeg = generateFinalMosaic(true); - } finally { - mPartialWakeLock.release(); - } - - if (jpeg == null) { // Cancelled by user. - mMainHandler.sendEmptyMessage(MSG_END_DIALOG_RESET_TO_PREVIEW); - } else if (!jpeg.isValid) { // Error when generating mosaic. - mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR); - } else { - int orientation = getCaptureOrientation(); - final Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation); - if (uri != null) { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mActivity.notifyNewMedia(uri); - } - }); - } - mMainHandler.sendMessage( - mMainHandler.obtainMessage(MSG_END_DIALOG_RESET_TO_PREVIEW)); - } - } - }); - reportProgress(); - } - - private void runBackgroundThread(Thread thread) { - mThreadRunning = true; - thread.start(); - } - - private void onBackgroundThreadFinished() { - mThreadRunning = false; - mUI.dismissAllDialogs(); - } - - private void cancelHighResComputation() { - mCancelComputation = true; - synchronized (mWaitObject) { - mWaitObject.notify(); - } - } - // This function will be called upon the first camera frame is available. private void reset() { mCaptureState = CAPTURE_STATE_VIEWFINDER; - mDirectionChanged = false; mOrientationLocked = false; mUI.setOrientation(mDeviceOrientation, true); @@ -779,14 +506,11 @@ public class WideAnglePanoramaModule if (mPreviewFocused) { mUI.showPreviewUI(); } - mMosaicFrameProcessor.reset(); } private void resetToPreviewIfPossible() { reset(); - if (!mMosaicFrameProcessorInitialized - || mUI.getSurfaceTexture() == null - || !mMosaicPreviewConfigured) { + if (mUI.getSurfaceHolder() == null) { return; } if (!mPaused) { @@ -839,38 +563,6 @@ public class WideAnglePanoramaModule exif.setTag(exif.buildTag(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider())); } - private void clearMosaicFrameProcessorIfNeeded() { - if (!mPaused || mThreadRunning) return; - // Only clear the processor if it is initialized by this activity - // instance. Other activity instances may be using it. - if (mMosaicFrameProcessorInitialized) { - mMosaicFrameProcessor.clear(); - mMosaicFrameProcessorInitialized = false; - } - } - - private void initMosaicFrameProcessorIfNeeded() { - if (mPaused || mThreadRunning) { - return; - } - - int perct = 100; - final ActivityManager am = (ActivityManager) - mActivity.getSystemService(Context.ACTIVITY_SERVICE); - if (am.isLowRamDevice()) { - perct = mActivity.getResources().getInteger(R.integer.panorama_frame_size_reduction); - } - - int width = (mCameraPreviewWidth * perct) / 100; - int height = (mCameraPreviewHeight * perct) / 100; - if ((0 < width) && (0 < height)) { - mMosaicFrameProcessor.initialize(width, height, getPreviewBufSize()); - mMosaicFrameProcessorInitialized = true; - } else { - throw new RuntimeException("Invalid preview dimension"); - } - } - @Override public void onPauseBeforeSuper() { mPaused = true; @@ -892,22 +584,8 @@ public class WideAnglePanoramaModule } mUI.showPreviewCover(); releaseCamera(); - synchronized (mRendererLock) { - mCameraTexture = null; - - // The preview renderer might not have a chance to be initialized - // before onPause(). - if (mMosaicPreviewRenderer != null) { - mMosaicPreviewRenderer.release(); - mMosaicPreviewRenderer = null; - } - } + mCameraSurfaceHolder = null; - clearMosaicFrameProcessorIfNeeded(); - if (mWaitProcessorTask != null) { - mWaitProcessorTask.cancel(true); - mWaitProcessorTask = null; - } resetScreenOn(); mUI.removeDisplayChangeListener(); if (mSoundPlayer != null) { @@ -919,7 +597,7 @@ public class WideAnglePanoramaModule @Override public void onConfigurationChanged(Configuration newConfig) { - mUI.onConfigurationChanged(newConfig, mThreadRunning); + mUI.onConfigurationChanged(newConfig, false); } @Override @@ -952,33 +630,20 @@ public class WideAnglePanoramaModule Log.e(TAG, "Failed to open camera, aborting"); return; } + resetToPreviewIfPossible(); // Set up sound playback for shutter button mSoundPlayer = SoundClips.getPlayer(mActivity); // Check if another panorama instance is using the mosaic frame processor. mUI.dismissAllDialogs(); - if (mThreadRunning && mMosaicFrameProcessor.isMosaicMemoryAllocated()) { - mUI.showWaitingDialog(mDialogWaitingPreviousString); - // If stitching is still going on, make sure switcher and shutter button - // are not showing - mUI.hideUI(); - mWaitProcessorTask = new WaitProcessorTask().execute(); - } else { - // Camera must be initialized before MosaicFrameProcessor is - // initialized. The preview size has to be decided by camera device. - initMosaicFrameProcessorIfNeeded(); - Point size = mUI.getPreviewAreaSize(); - mPreviewUIWidth = size.x; - mPreviewUIHeight = size.y; - configMosaicPreview(); - mMainHandler.post(new Runnable(){ - @Override - public void run(){ - mActivity.updateStorageSpaceAndHint(); - } - }); - } + + mMainHandler.post(new Runnable(){ + @Override + public void run(){ + mActivity.updateStorageSpaceAndHint(); + } + }); keepScreenOnAwhile(); mOrientationManager.resume(); @@ -989,54 +654,19 @@ public class WideAnglePanoramaModule mUI.initDisplayChangeListener(); UsageStatistics.onContentViewChanged( UsageStatistics.COMPONENT_CAMERA, "PanoramaModule"); + mUI.hidePreviewCover(); } - /** - * Generate the final mosaic image. - * - * @param highRes flag to indicate whether we want to get a high-res version. - * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation - * process is cancelled; and a MosaicJpeg with its isValid flag set to false if there - * is an error in generating the final mosaic. - */ - public MosaicJpeg generateFinalMosaic(boolean highRes) { - int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes); - if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) { - return null; - } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) { - return new MosaicJpeg(); - } + private void setDisplayOrientation() { + mDisplayRotation = CameraUtil.getDisplayRotation(mActivity); + mDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, CameraHolder + .instance().getBackCameraId()); + mCameraDisplayOrientation = mDisplayOrientation; - byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21(); - if (imageData == null) { - Log.e(TAG, "getFinalMosaicNV21() returned null."); - return new MosaicJpeg(); - } - - int len = imageData.length - 8; - int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16) - + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF); - int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16) - + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF); - Log.d(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height); - - if (width <= 0 || height <= 0) { - // TODO: pop up an error message indicating that the final result is not generated. - Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " + - height); - return new MosaicJpeg(); - } - - YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out); - try { - out.close(); - } catch (Exception e) { - Log.e(TAG, "Exception in storing final mosaic", e); - return new MosaicJpeg(); + // Change the camera display orientation + if (mCameraDevice != null) { + mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation); } - return new MosaicJpeg(out.toByteArray(), width, height); } private void startCameraPreview() { @@ -1045,38 +675,25 @@ public class WideAnglePanoramaModule return; } - if (mUI.getSurfaceTexture() == null) { + if (mUI.getSurfaceHolder() == null) { // UI is not ready. return; } mErrorCallback.setActivity(mActivity); mCameraDevice.setErrorCallback(mErrorCallback); - // This works around a driver issue. startPreview may fail if - // stopPreview/setPreviewTexture/startPreview are called several times - // in a row. mCameraTexture can be null after pressing home during - // mosaic generation and coming back. Preview will be started later in - // onLayoutChange->configMosaicPreview. This also reduces the latency. - synchronized (mRendererLock) { - if (mCameraTexture == null) return; - - // If we're previewing already, stop the preview first (this will - // blank the screen). - if (mCameraState != PREVIEW_STOPPED) stopCameraPreview(); - - // Set the display orientation to 0, so that the underlying mosaic - // library can always get undistorted mCameraPreviewWidth x mCameraPreviewHeight - // image data from SurfaceTexture. - // as Panoroma will add 90 degree rotation compensation during - // postprocessing, we need to consider both camera mount angle and - // this compensation angle - mCameraDevice.setDisplayOrientation(0); - - if (mCameraTexture != null) - mCameraTexture.setOnFrameAvailableListener(this); - mCameraDevice.setPreviewTexture(mCameraTexture); - } + if (mUI.getSurfaceHolder() == null) return; + mCameraSurfaceHolder = mUI.getSurfaceHolder(); + + // If we're previewing already, stop the preview first (this will + // blank the screen). + if (mCameraState != PREVIEW_STOPPED) stopCameraPreview(); + + mCameraDevice.setPreviewDataCallback(mMainHandler, this); + mCameraDevice.setPreviewDisplay(mCameraSurfaceHolder); + mCameraDevice.startPreview(); + setDisplayOrientation(); mCameraState = PREVIEW_ACTIVE; } @@ -1096,8 +713,6 @@ public class WideAnglePanoramaModule public boolean onBackPressed() { // If panorama is generating low res or high res mosaic, ignore back // key. So the activity will not be destroyed. - if (mThreadRunning) return true; - if (mUI.hideSwitcherPopup()) return true; @@ -1120,41 +735,8 @@ public class WideAnglePanoramaModule mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } - private class WaitProcessorTask extends AsyncTask<Void, Void, Void> { - @Override - protected Void doInBackground(Void... params) { - synchronized (mMosaicFrameProcessor) { - while (!isCancelled() && mMosaicFrameProcessor.isMosaicMemoryAllocated()) { - try { - mMosaicFrameProcessor.wait(); - } catch (Exception e) { - // ignore - } - } - } - mActivity.updateStorageSpace(); - return null; - } - - @Override - protected void onPostExecute(Void result) { - mWaitProcessorTask = null; - mUI.dismissAllDialogs(); - // TODO (shkong): mGLRootView.setVisibility(View.VISIBLE); - initMosaicFrameProcessorIfNeeded(); - Point size = mUI.getPreviewAreaSize(); - mPreviewUIWidth = size.x; - mPreviewUIHeight = size.y; - configMosaicPreview(); - resetToPreviewIfPossible(); - mActivity.updateStorageHint(mActivity.getStorageSpaceBytes()); - } - } - @Override public void cancelHighResStitching() { - if (mPaused || mCameraTexture == null) return; - cancelHighResComputation(); } @Override @@ -1169,6 +751,226 @@ public class WideAnglePanoramaModule public void onActivityResult(int requestCode, int resultCode, Intent data) { } + @Override + public void onPreviewFrame(byte[] data, CameraProxy camera) { + if (mMosaicFrameProcessor != null && mMosaicFrameProcessor.IsInited()) { + mMosaicFrameProcessor.Process(data, mCameraPreviewWidth, mCameraPreviewHeight); + } + } + + /** + * This class defines panorama stitching information. + **/ + static class PanoramaInfo { + /** The angle for the panorama information. */ + public int angle; + /** The offset of the next preview frame to be acquired. */ + public Point offset; + /** The direction of capturing. */ + public int direction; + /** + * Indicate whether current frame is properly positioned to be added for + * stitching: 0 for not properly positioned, 1 for ready to stitch. + */ + public int selected; + + /** + * This is the default constructor of the panorama information object. + */ + public PanoramaInfo() { + offset = new Point(0, 0); + } + + public PanoramaInfo(int ang, Point ptOffset, int direct, int sel) { + angle = ang; + offset = new Point(ptOffset.x, ptOffset.y); + direction = direct; + selected = sel; + } + + public PanoramaInfo(int ang, int offsetX, int offsetY, int direct, int sel) { + angle = ang; + offset = new Point(offsetX, offsetY); + direction = direct; + selected = sel; + } + } + + public interface INotifier { + /** + * Messages are notified to the application through this function. + * + * @param key The message key. + * @param obj The message object with this message key. + * @return The user defined result of processing message. + */ + int onNotify(int key, Object obj); + } + + private final INotifier mPanoNotifier = new INotifier() { + @Override + public int onNotify(int key, Object obj) { + byte[] imageData; + int len; + int width; + int height; + YuvImage yuvimage; + ByteArrayOutputStream out; + switch (key) { + case MosaicFrameProcessor.MSG_PANORAMA_TIP: + if (mCaptureState == CAPTURE_STATE_MOSAIC) { + PanoramaInfo panoInfo = (PanoramaInfo) obj; + if (null == panoInfo) + break; + if (!isCapturing) { + isCapturing = true; + + /** + * Device Orientation and Capture Orientation Note + * that: Pano's left is always device's top; Pano's + * right is always device's bottom Pano's top is + * always device's right; Pano's bottom is always + * device's left + */ + mCaptureOrientation = panoInfo.direction; + updateState(STATUS_CAPTURING); + } + + boolean bIsHorizonal = MosaicFrameProcessor.DIRECTION_LEFTTORIGHT == panoInfo.direction + || MosaicFrameProcessor.DIRECTION_RIGHTTOLEFT == panoInfo.direction; + int iAbsAngle = Math.abs(panoInfo.angle); + if ((bIsHorizonal && iAbsAngle >= MosaicFrameProcessor.MAX_HORIZONAL_ANGLE) + || (!bIsHorizonal && iAbsAngle >= MosaicFrameProcessor.MAX_VERTICAL_ANGLE)) { + Log.v(TAG, TAG + "capture end !"); + break; + } + } + break; + + case MosaicFrameProcessor.MSG_CAPTURE_FAILED: + if (mCaptureState == CAPTURE_STATE_MOSAIC) { + mMosaicFrameProcessor.Uninit(); + } + break; + + case MosaicFrameProcessor.MSG_CAPTURE_SUCCESS: + imageData = (byte[]) obj; + + len = imageData.length - 8; + width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16) + + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF); + height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16) + + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF); + + if (width == 0 || height == 0) { + onNotify(MosaicFrameProcessor.MSG_CAPTURE_FAILED, null); + return 0; + } + + yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null); + out = new ByteArrayOutputStream(); + yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out); + final MosaicJpeg jpeg = new MosaicJpeg(out.toByteArray(), width, height); + try { + out.close(); + } catch (Exception e) { + mMosaicFrameProcessor.Uninit(); + Log.e(TAG, "Exception in storing final mosaic", e); + return 0; + } + + mMainHandler.postDelayed(new Runnable() { + @Override + public void run() { + saveFinalMosaic(jpeg); + } + }, 200); + break; + case MosaicFrameProcessor.MSG_UPDATE_UI: + if (mCaptureState == CAPTURE_STATE_MOSAIC) { + if (mCaptureOrientation == 0) + break; + imageData = (byte[]) obj; + len = imageData.length - 8; + width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16) + + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF); + height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16) + + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF); + + yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null); + out = new ByteArrayOutputStream(); + yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out); + byte[] jpeg2 = out.toByteArray(); + Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg2, 0, jpeg2.length); + Matrix rotateMatrix = new Matrix(); + rotateMatrix.setRotate(getCaptureOrientation()); + bitmap = Bitmap.createBitmap( + bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), + rotateMatrix, false); + try { + out.close(); + } catch (Exception e) { + } + + boolean horiz = true; + switch (mDeviceOrientationAtCapture) { + case 0: + case 180: + horiz = (mCaptureOrientation == 4 || mCaptureOrientation == 8); + break; + case 90: + case 270: + horiz = (mCaptureOrientation == 1 || mCaptureOrientation == 2); + break; + + } + mUI.drawCapturePreview(bitmap, mDeviceOrientationAtCapture, horiz); + } + break; + default: + Log.v(TAG, "on Notify unknown MSG: " + key); + break; + } + return 0; + } + }; + + void saveFinalMosaic(MosaicJpeg jpeg) { + mMosaicFrameProcessor.Uninit(); + if (mCaptureState == CAPTURE_STATE_MOSAIC) + stopCapture(false); + Bitmap bitmap = null; + bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length); + int orientation = getCaptureOrientation(); + showFinalMosaic(bitmap); + + final Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation); + if (uri != null) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mActivity.notifyNewMedia(uri); + } + }); + } + updateState(STATUS_SUCCESS); + } + + public void updateState(int status) { + mPanoState = status; + switch (status) { + case STATUS_PREVIEW: + case STATUS_SUCCESS: + case STATUS_PREPARE: + case STATUS_FAILED: + isCapturing = false; + break; + + case STATUS_CAPTURING: + isCapturing = true; + break; + } + } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { diff --git a/src/com/android/camera/WideAnglePanoramaUI.java b/src/com/android/camera/WideAnglePanoramaUI.java index 862e4d2ba..ba42fc969 100644 --- a/src/com/android/camera/WideAnglePanoramaUI.java +++ b/src/com/android/camera/WideAnglePanoramaUI.java @@ -29,21 +29,26 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.SurfaceTexture; +import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.Log; +import android.util.TypedValue; import android.view.Gravity; import android.view.Display; import android.view.LayoutInflater; -import android.view.TextureView; +import android.view.SurfaceView; +import android.view.SurfaceHolder; +import android.view.SurfaceHolder.Callback; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; import android.widget.ImageView; +import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.TextView; @@ -59,7 +64,7 @@ import org.codeaurora.snapcam.R; * The UI of {@link WideAnglePanoramaModule}. */ public class WideAnglePanoramaUI implements - TextureView.SurfaceTextureListener, + SurfaceHolder.Callback, ShutterButton.OnShutterButtonListener, CameraRootView.MyDisplayListener, View.OnLayoutChangeListener { @@ -76,29 +81,23 @@ public class WideAnglePanoramaUI implements private View mReviewLayout; private ImageView mReview; private View mPreviewBorder; - private View mLeftIndicator; - private View mRightIndicator; private View mCaptureIndicator; - private PanoProgressBar mCaptureProgressBar; private PanoProgressBar mSavingProgressBar; private TextView mTooFastPrompt; private View mPreviewLayout; private ViewGroup mReviewControl; - private TextureView mTextureView; + private SurfaceView mSurfaceView; private ShutterButton mShutterButton; private CameraControls mCameraControls; private ImageView mThumbnail; - - private Matrix mProgressDirectionMatrix = new Matrix(); - private float[] mProgressAngle = new float[2]; + private ImageView mCapturePreview; + private ViewGroup mCapturePreviewLayout; private DialogHelper mDialogHelper; // Color definitions. - private int mIndicatorColor; - private int mIndicatorColorFast; private int mReviewBackground; - private SurfaceTexture mSurfaceTexture; + private SurfaceHolder mSurfaceHolder; private View mPreviewCover; private int mOrientation; @@ -107,6 +106,34 @@ public class WideAnglePanoramaUI implements private RotateLayout mPanoFailedDialog; private Button mPanoFailedButton; + private int mPreviewWidth = 0; + private int mPreviewHeight = 0; + private int mOriginalPreviewWidth = 0; + private int mOriginalPreviewHeight = 0; + + private boolean mOrientationResize; + private boolean mPrevOrientationResize; + + private Matrix mMatrix = null; + + private int mScreenRatio = CameraUtil.RATIO_UNKNOWN; + private int mTopMargin = 0; + private int mBottomMargin = 0; + private boolean mCapturePreviewSet = false; + + private final int capturePreviewW = 80; + private final int capturePreviewH = 80; + private final int sidePadding = 10; + private final int botPadding = 130; + + 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); + } + /** Constructor. */ public WideAnglePanoramaUI( CameraActivity activity, @@ -136,13 +163,27 @@ public class WideAnglePanoramaUI implements mSwitcher.setOrientation(mOrientation, false); } }); + + mOrientationResize = false; + mPrevOrientationResize = false; + + Point size = new Point(); + mActivity.getWindowManager().getDefaultDisplay().getSize(size); + mScreenRatio = CameraUtil.determineRatio(size.x, size.y); + if (mScreenRatio == CameraUtil.RATIO_16_9) { + 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; + } + mCameraControls.setMargins(mTopMargin, mBottomMargin); } public void onStartCapture() { hideSwitcher(); mShutterButton.setImageResource(R.drawable.shutter_button_stop); mCaptureIndicator.setVisibility(View.VISIBLE); - showDirectionIndicators(PanoProgressBar.DIRECTION_NONE); } public void showPreviewUI() { @@ -152,8 +193,8 @@ public class WideAnglePanoramaUI implements public void onStopCapture() { mCaptureIndicator.setVisibility(View.INVISIBLE); - hideTooFastIndication(); - hideDirectionIndicators(); + mCapturePreview.setImageBitmap(null); + mCapturePreviewSet = false; } public void hideSwitcher() { @@ -187,116 +228,56 @@ public class WideAnglePanoramaUI implements mSwitcher.setVisibility(View.VISIBLE); } - public void setCaptureProgressOnDirectionChangeListener( - PanoProgressBar.OnDirectionChangeListener listener) { - mCaptureProgressBar.setOnDirectionChangeListener(listener); - } - - public void resetCaptureProgress() { - mCaptureProgressBar.reset(); - } - - public void setMaxCaptureProgress(int max) { - mCaptureProgressBar.setMaxProgress(max); - } - - public void showCaptureProgress() { - mCaptureProgressBar.setVisibility(View.VISIBLE); - } + public void drawCapturePreview(Bitmap bitmap, int mDeviceOrientationAtCapture, boolean horiz) { + if (!mCapturePreviewSet) { + int w = horiz ? LayoutParams.MATCH_PARENT : convertDpToPix(capturePreviewW); + int h = horiz ? convertDpToPix(capturePreviewH) : LayoutParams.MATCH_PARENT; + FrameLayout.LayoutParams param = (FrameLayout.LayoutParams) mCapturePreview + .getLayoutParams(); + param.height = h; + param.width = w; + param.gravity = Gravity.CENTER; + mCapturePreview.setLayoutParams(param); - public void updateCaptureProgress( - float panningRateXInDegree, float panningRateYInDegree, - float progressHorizontalAngle, float progressVerticalAngle, - float maxPanningSpeed) { + mCapturePreviewSet = true; + setPreviewOrientation(mDeviceOrientationAtCapture, horiz); - if ((Math.abs(panningRateXInDegree) > maxPanningSpeed) - || (Math.abs(panningRateYInDegree) > maxPanningSpeed)) { - showTooFastIndication(); - } else { - hideTooFastIndication(); } - - // progressHorizontalAngle and progressVerticalAngle are relative to the - // camera. Convert them to UI direction. - mProgressAngle[0] = progressHorizontalAngle; - mProgressAngle[1] = progressVerticalAngle; - mProgressDirectionMatrix.mapPoints(mProgressAngle); - - int angleInMajorDirection = - (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1])) - ? (int) mProgressAngle[0] - : (int) mProgressAngle[1]; - mCaptureProgressBar.setProgress((angleInMajorDirection)); + mCapturePreview.setImageBitmap(bitmap); } - public void setProgressOrientation(int orientation) { - mProgressDirectionMatrix.reset(); - mProgressDirectionMatrix.postRotate(orientation); + public SurfaceHolder getSurfaceHolder() { + return mSurfaceHolder; } - public void showDirectionIndicators(int direction) { - switch (direction) { - case PanoProgressBar.DIRECTION_NONE: - mLeftIndicator.setVisibility(View.VISIBLE); - mRightIndicator.setVisibility(View.VISIBLE); - break; - case PanoProgressBar.DIRECTION_LEFT: - mLeftIndicator.setVisibility(View.VISIBLE); - mRightIndicator.setVisibility(View.INVISIBLE); - break; - case PanoProgressBar.DIRECTION_RIGHT: - mLeftIndicator.setVisibility(View.INVISIBLE); - mRightIndicator.setVisibility(View.VISIBLE); - break; - } - } - - public SurfaceTexture getSurfaceTexture() { - return mSurfaceTexture; + // SurfaceHolder callbacks + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + hidePreviewCover(); } @Override - public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) { - mSurfaceTexture = surfaceTexture; + public void surfaceCreated(SurfaceHolder holder) { + mSurfaceHolder = holder; mController.onPreviewUIReady(); - mActivity.updateThumbnail(mThumbnail); - } - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) { + if (mPreviewWidth != 0 && mPreviewHeight != 0) { + // Re-apply transform matrix for new surface texture + setTransformMatrix(mPreviewWidth, mPreviewHeight); + } + mActivity.updateThumbnail(mThumbnail); } @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + public void surfaceDestroyed(SurfaceHolder holder) { + mSurfaceHolder = null; mController.onPreviewUIDestroyed(); - mSurfaceTexture = null; - Log.d(TAG, "surfaceTexture is destroyed"); - return true; - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { - // Make sure preview cover is hidden if preview data is available. - if (mPreviewCover.getVisibility() != View.GONE) { - mPreviewCover.setVisibility(View.GONE); - } - } - - private void hideDirectionIndicators() { - mLeftIndicator.setVisibility(View.INVISIBLE); - mRightIndicator.setVisibility(View.INVISIBLE); - } - - public Point getPreviewAreaSize() { - return new Point( - mTextureView.getWidth(), mTextureView.getHeight()); } public void reset() { mShutterButton.setImageResource(R.drawable.btn_new_shutter_panorama); mReviewLayout.setVisibility(View.GONE); - mCaptureProgressBar.setVisibility(View.INVISIBLE); } public void showFinalMosaic(Bitmap bitmap, int orientation) { @@ -308,14 +289,6 @@ public class WideAnglePanoramaUI implements rotateMatrix, false); } - mReview.setImageBitmap(bitmap); - mCaptureLayout.setVisibility(View.GONE); - mReviewLayout.setVisibility(View.VISIBLE); - // If capture is stopped by device rotation, the rendering progress bar - // is sometimes not shown due to wrong layout result. It's likely to be - // a framework bug. Call requestLayout() as a workaround. - mSavingProgressBar.requestLayout(); - mActivity.updateThumbnail(bitmap); } @@ -333,7 +306,6 @@ public class WideAnglePanoramaUI implements inflater.inflate(R.layout.pano_review_control, mReviewControl, true); mRootView.bringChildToFront(mCameraControls); - setViews(mActivity.getResources()); if (threadRunning) { mReview.setImageDrawable(lowResReview); mCaptureLayout.setVisibility(View.GONE); @@ -358,31 +330,18 @@ public class WideAnglePanoramaUI implements yOffset = (height - h) / 2; FrameLayout.LayoutParams param = new FrameLayout.LayoutParams(w, h); - mTextureView.setLayoutParams(param); - mTextureView.setX(xOffset); - mTextureView.setY(yOffset); + + mSurfaceView.setLayoutParams(param); + mSurfaceView.setX(xOffset); + mSurfaceView.setY(yOffset); mPreviewBorder.setLayoutParams(param); mPreviewBorder.setX(xOffset); mPreviewBorder.setY(yOffset); mPreviewYOffset = yOffset; - int t = mPreviewYOffset; - int b1 = mTextureView.getBottom() - mPreviewYOffset; - int r = mTextureView.getRight(); - int b2 = mTextureView.getBottom(); - mCameraControls.setPreviewRatio(1.0f, true); } - public void resetSavingProgress() { - mSavingProgressBar.reset(); - mSavingProgressBar.setRightIncreasing(true); - } - - public void updateSavingProgress(int progress) { - mSavingProgressBar.setProgress(progress); - } - @Override public void onShutterButtonFocus(boolean pressed) { // Do nothing. @@ -422,29 +381,18 @@ public class WideAnglePanoramaUI implements .getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflator.inflate(R.layout.panorama_module, mRootView, true); - Resources appRes = mActivity.getResources(); - mIndicatorColor = appRes.getColor(R.color.pano_progress_indication); - mReviewBackground = appRes.getColor(R.color.review_background); - mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast); - mPreviewCover = mRootView.findViewById(R.id.preview_cover); mPreviewLayout = mRootView.findViewById(R.id.pano_preview_layout); mReviewControl = (ViewGroup) mRootView.findViewById(R.id.pano_review_control); mReviewLayout = mRootView.findViewById(R.id.pano_review_layout); mReview = (ImageView) mRootView.findViewById(R.id.pano_reviewarea); mCaptureLayout = (FrameLayout) mRootView.findViewById(R.id.panorama_capture_layout); - mCaptureProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_pan_progress_bar); - mCaptureProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); - mCaptureProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done)); - mCaptureProgressBar.setIndicatorColor(mIndicatorColor); - mCaptureProgressBar.setIndicatorWidth(20); - + mCapturePreviewLayout = (FrameLayout) mRootView + .findViewById(R.id.pano_capture_preview_layout); + mCapturePreview = (ImageView) mRootView.findViewById(R.id.pano_capture_preview); + mCapturePreview.setScaleType(ScaleType.FIT_CENTER); mPreviewBorder = mCaptureLayout.findViewById(R.id.pano_preview_area_border); - mLeftIndicator = mRootView.findViewById(R.id.pano_pan_left_indicator); - mRightIndicator = mRootView.findViewById(R.id.pano_pan_right_indicator); - mLeftIndicator.setEnabled(false); - mRightIndicator.setEnabled(false); mTooFastPrompt = (TextView) mRootView.findViewById(R.id.pano_capture_too_fast_textview); mCaptureIndicator = mRootView.findViewById(R.id.pano_capture_indicator); @@ -458,9 +406,11 @@ public class WideAnglePanoramaUI implements // TODO: set display change listener properly. ((CameraRootView) mRootView).setDisplayChangeListener(null); - mTextureView = (TextureView) mRootView.findViewById(R.id.pano_preview_textureview); - mTextureView.setSurfaceTextureListener(this); - mTextureView.addOnLayoutChangeListener(this); + mSurfaceView = (SurfaceView) mRootView.findViewById(R.id.pano_preview_surfaceview); + mSurfaceHolder = mSurfaceView.getHolder(); + mSurfaceHolder.addCallback(this); + mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + mSurfaceView.addOnLayoutChangeListener(this); mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls); setPanoramaPreviewView(); @@ -468,64 +418,11 @@ public class WideAnglePanoramaUI implements mPanoFailedDialog = (RotateLayout) mRootView.findViewById(R.id.pano_dialog_layout); mPanoFailedButton = (Button) mRootView.findViewById(R.id.pano_dialog_button1); mDialogHelper = new DialogHelper(); - setViews(appRes); - } - - private void setViews(Resources appRes) { - int weight = appRes.getInteger(R.integer.SRI_pano_layout_weight); - - mSavingProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_saving_progress_bar); - mSavingProgressBar.setIndicatorWidth(0); - mSavingProgressBar.setMaxProgress(100); - mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); - mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication)); - - View cancelButton = mRootView.findViewById(R.id.pano_review_cancel_button); - cancelButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View arg0) { - mController.cancelHighResStitching(); - } - }); - - - } - - private void showTooFastIndication() { - mTooFastPrompt.setVisibility(View.VISIBLE); - // The PreviewArea also contains the border for "too fast" indication. - mPreviewBorder.setVisibility(View.VISIBLE); - mCaptureProgressBar.setIndicatorColor(mIndicatorColorFast); - mLeftIndicator.setEnabled(true); - mRightIndicator.setEnabled(true); - } - - private void hideTooFastIndication() { - mTooFastPrompt.setVisibility(View.GONE); - mPreviewBorder.setVisibility(View.INVISIBLE); - mCaptureProgressBar.setIndicatorColor(mIndicatorColor); - mLeftIndicator.setEnabled(false); - mRightIndicator.setEnabled(false); - } - - public void flipPreviewIfNeeded() { - // Rotation needed to display image correctly clockwise - int cameraOrientation = mController.getCameraOrientation(); - // Display rotated counter-clockwise - int displayRotation = CameraUtil.getDisplayRotation(mActivity); - // Rotation needed to display image correctly on current display - int rotation = (cameraOrientation - displayRotation + 360) % 360; - if (rotation >= 180) { - mTextureView.setRotation(180); - } else { - mTextureView.setRotation(0); - } } @Override public void onDisplayChanged() { mCameraControls.checkLayoutFlip(); - flipPreviewIfNeeded(); } public void initDisplayChangeListener() { @@ -540,6 +437,12 @@ public class WideAnglePanoramaUI implements mPreviewCover.setVisibility(View.VISIBLE); } + public void hidePreviewCover() { + if (mPreviewCover.getVisibility() != View.GONE) { + mPreviewCover.setVisibility(View.GONE); + } + } + private class DialogHelper { DialogHelper() { @@ -598,7 +501,91 @@ public class WideAnglePanoramaUI implements return true; } return false; - } + } + + private int convertDpToPix(int dp) { + Resources r = mActivity.getResources(); + return ((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, + r.getDisplayMetrics())); + } + + + public void setPreviewOrientation(int orientation, boolean horiz) { + + Point size = new Point(); + mActivity.getWindowManager().getDefaultDisplay().getSize(size); + int sW = size.x; + int sH = size.y; + ViewGroup v = mCapturePreviewLayout; + int w = v.getWidth(); + int h = v.getHeight(); + int idx1; + int idx2; + int botPadding_pix = convertDpToPix(botPadding); + int sidePadding_pix = convertDpToPix(sidePadding); + + final View dummy = mRootView.findViewById(R.id.pano_dummy_layout); + int t = dummy.getTop(); + int b1 = dummy.getBottom(); + int r = dummy.getRight(); + int b2 = dummy.getBottom(); + int yc = (t + b1) / 2; + int xc = r / 2; + int x = xc; + int y = yc; + int vH = convertDpToPix(capturePreviewH); + int vW = convertDpToPix(capturePreviewW); + if (horiz) { + v.setPivotX(w / 2); + v.setPivotY(h / 2); + switch (orientation) { + case 90: + x = sW - vH / 2 - sidePadding_pix; + y = yc; + break; + case 180: + x = xc; + y = t / 2; + break; + case 270: + x = vH / 2 + sidePadding_pix; + y = yc; + break; + default: + x = xc; + y = sH - botPadding_pix; + break; + } + v.setTranslationX(x - xc); + v.setTranslationY(y - yc); + v.setRotation(-orientation); + } else { + v.setPivotX(w / 2); + v.setPivotY(h / 2); + switch (orientation) { + case 90: + x = xc; + y = sH - botPadding_pix; + break; + case 180: + x = sW - vW / 2 - sidePadding_pix; + y = yc; + break; + case 270: + x = xc; + y = t / 2; + + break; + default: + x = vW / 2 + sidePadding_pix; + y = yc; + break; + } + v.setTranslationX(x - xc); + v.setTranslationY(y - yc); + v.setRotation(-orientation); + } + } public void setOrientation(int orientation, boolean animation) { mOrientation = orientation; @@ -619,9 +606,7 @@ public class WideAnglePanoramaUI implements int b2 = dummy.getBottom(); final FrameLayout progressLayout = (FrameLayout) mRootView.findViewById(R.id.pano_progress_layout); - int pivotY = ((ViewGroup) progressLayout).getPaddingTop() - + progressLayout.getChildAt(0).getHeight() / 2; - + int pivotY = ((ViewGroup) progressLayout).getPaddingTop(); int[] x = { r / 2, r / 10, r * 9 / 10, r / 2 }; int[] y = { t / 2, (t + b1) / 2, (t + b1) / 2, b1 + pivotY }; @@ -670,7 +655,7 @@ public class WideAnglePanoramaUI implements v.setRotation(-orientation); } - final View[] views2 = { progressLayout, mReviewControl }; + final View[] views2 = { progressLayout, mReviewControl}; for (final View v : views2) { v.setPivotX(r / 2); v.setPivotY(pivotY); diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java index 01469baeb..b1b3133db 100644 --- a/src/com/android/camera/ui/FilmStripView.java +++ b/src/com/android/camera/ui/FilmStripView.java @@ -1819,7 +1819,6 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { @Override public boolean onTouchEvent(MotionEvent ev) { - mGestureRecognizer.onTouchEvent(ev); return true; } |