summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorByunghun Jeon <bjeon@codeaurora.org>2015-05-18 11:35:38 -0700
committerGerrit - the friendly Code Review server <code-review@localhost>2015-07-30 12:29:07 -0700
commitf61915f04cf9c47e1c23688a9eeffbf552617b82 (patch)
treec78e9f505aadd87d37ed91d74a75c3199bffc575 /src/com
parenta2a57a538c401b564070f10dfac34cae12312928 (diff)
downloadandroid_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.java206
-rw-r--r--src/com/android/camera/MosaicFrameProcessor.java268
-rw-r--r--src/com/android/camera/MosaicPreviewRenderer.java207
-rw-r--r--src/com/android/camera/MosaicRenderer.java101
-rw-r--r--src/com/android/camera/WideAnglePanoramaModule.java786
-rw-r--r--src/com/android/camera/WideAnglePanoramaUI.java397
-rw-r--r--src/com/android/camera/ui/FilmStripView.java1
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;
}