diff options
-rw-r--r-- | proguard.flags | 4 | ||||
-rw-r--r-- | res/layout/pano_capture.xml | 1 | ||||
-rw-r--r-- | res/layout/pano_control.xml | 1 | ||||
-rw-r--r-- | res/layout/pano_review.xml | 2 | ||||
-rw-r--r-- | src/com/android/camera/panorama/MosaicFrameProcessor.java | 37 | ||||
-rw-r--r-- | src/com/android/camera/panorama/OnClickAttr.java | 31 | ||||
-rw-r--r-- | src/com/android/camera/panorama/PanoramaActivity.java | 155 |
7 files changed, 176 insertions, 55 deletions
diff --git a/proguard.flags b/proguard.flags index 5dcd2b9..a3dc864 100644 --- a/proguard.flags +++ b/proguard.flags @@ -11,3 +11,7 @@ -keep class com.android.camera.VideoCamera { public boolean isRecording(); } + +-keep class * extends android.app.Activity { + @com.android.camera.panorama.OnClickAttr <methods>; +} diff --git a/res/layout/pano_capture.xml b/res/layout/pano_capture.xml index a12e6e6..0ce34b9 100644 --- a/res/layout/pano_capture.xml +++ b/res/layout/pano_capture.xml @@ -41,6 +41,7 @@ <Button android:id="@+id/pano_capture_stop_button" android:text="@string/pano_capture_stop" + android:onClick="onStopButtonClicked" android:textSize="24dp" android:layout_width="180dp" android:layout_height="180dp" /> diff --git a/res/layout/pano_control.xml b/res/layout/pano_control.xml index e3ab1d4..4a1175a 100644 --- a/res/layout/pano_control.xml +++ b/res/layout/pano_control.xml @@ -29,6 +29,7 @@ android:layout_centerInParent="true" android:clickable="true" android:focusable="true" + android:onClick="onShutterButtonClicked" android:background="@drawable/btn_shutter"/> <include layout="@layout/mode_picker"/> diff --git a/res/layout/pano_review.xml b/res/layout/pano_review.xml index b778daa..40dee1e 100644 --- a/res/layout/pano_review.xml +++ b/res/layout/pano_review.xml @@ -39,11 +39,13 @@ <Button android:id="@+id/pano_review_retake_button" android:text="@string/review_retake" + android:onClick="onRetakeButtonClicked" android:textSize="24dp" android:layout_width="180dp" android:layout_height="180dp" /> <Button android:id="@+id/pano_review_ok_button" android:text="@string/review_ok" + android:onClick="onOkButtonClicked" android:textSize="24dp" android:layout_width="180dp" android:layout_height="180dp" /> diff --git a/src/com/android/camera/panorama/MosaicFrameProcessor.java b/src/com/android/camera/panorama/MosaicFrameProcessor.java index c5eeac5..6496f11 100644 --- a/src/com/android/camera/panorama/MosaicFrameProcessor.java +++ b/src/com/android/camera/panorama/MosaicFrameProcessor.java @@ -18,6 +18,9 @@ package com.android.camera.panorama; import android.util.Log; +/** + * Class to handle the processing of each frame by Mosaicer. + */ public class MosaicFrameProcessor { private static final boolean LOGV = true; private static final String TAG = "MosaicFrameProcessor"; @@ -54,7 +57,6 @@ public class MosaicFrameProcessor { private int mPreviewHeight; private int mPreviewBufferSize; - public interface ProgressListener { public void onProgress(boolean isFinished, float translationRate, int traversedAngleX, int traversedAngleY); @@ -72,14 +74,20 @@ public class MosaicFrameProcessor { mProgressListener = listener; } - public void onResume() { + public void initialize() { setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize); + reset(); } - public void onPause() { - releaseMosaicer(); + public void clear() { + mMosaicer.freeMosaicMemory(); + + for (int i = 0; i < NUM_FRAMES_IN_BUFFER; i++) { + mFrames[i] = null; + } } + private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) { Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize); mMosaicer.allocateMosaicMemory(previewWidth, previewHeight); @@ -94,12 +102,15 @@ public class MosaicFrameProcessor { } } - private void releaseMosaicer() { - mMosaicer.freeMosaicMemory(); - - for (int i = 0; i < NUM_FRAMES_IN_BUFFER; i++) { - mFrames[i] = null; - } + public void reset() { + // reset() can be called even if MosaicFrameProcessor is not initialized. + // Only counters will be changed. + mTotalFrameCount = 0; + mFillIn = 0; + mLastProcessedFrameTimestamp = 0; + mLastProcessFrameIdx = -1; + mCurrProcessFrameIdx = -1; + mMosaicer.reset(); } public void createMosaic(boolean highRes) { @@ -114,6 +125,12 @@ public class MosaicFrameProcessor { // updates the UI to show progress. // When done, processes and displays the final mosaic. public void processFrame(byte[] data) { + if (mFrames[mFillIn] == null) { + // 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; + } long t1 = System.currentTimeMillis(); mFrameTimestamp[mFillIn] = t1; System.arraycopy(data, 0, mFrames[mFillIn], 0, data.length); diff --git a/src/com/android/camera/panorama/OnClickAttr.java b/src/com/android/camera/panorama/OnClickAttr.java new file mode 100644 index 0000000..bfc7c4e --- /dev/null +++ b/src/com/android/camera/panorama/OnClickAttr.java @@ -0,0 +1,31 @@ +/* + * 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.panorama; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Interface for OnClickAttr annotation. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface OnClickAttr { +} diff --git a/src/com/android/camera/panorama/PanoramaActivity.java b/src/com/android/camera/panorama/PanoramaActivity.java index 11fb6d1..0b1ec60 100644 --- a/src/com/android/camera/panorama/PanoramaActivity.java +++ b/src/com/android/camera/panorama/PanoramaActivity.java @@ -28,6 +28,8 @@ import com.android.camera.Util; import android.app.Activity; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.ImageFormat; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -39,7 +41,6 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -48,7 +49,6 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.WindowManager; -import android.widget.Button; import android.widget.ImageView; import java.io.ByteArrayOutputStream; @@ -64,13 +64,16 @@ public class PanoramaActivity extends Activity implements public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720; private static final int MSG_FINAL_MOSAIC_READY = 1; + private static final int MSG_RESET_TO_PREVIEW = 2; private static final String TAG = "PanoramaActivity"; private static final int PREVIEW_STOPPED = 0; private static final int PREVIEW_ACTIVE = 1; - // Ratio of nanosecond to second private static final float NS2S = 1.0f / 1000000000.0f; + + private boolean mPausing; + private View mPanoControlLayout; private View mCaptureLayout; private View mReviewLayout; @@ -78,9 +81,10 @@ public class PanoramaActivity extends Activity implements private ImageView mReview; private CaptureView mCaptureView; private MosaicRendererSurfaceView mRealTimeMosaicView; - private ShutterButton mShutterButton; - private Button mStopButton; + + private byte[] mFinalJpegData; + private int mPreviewWidth; private int mPreviewHeight; private Camera mCameraDevice; @@ -94,6 +98,8 @@ public class PanoramaActivity extends Activity implements private Handler mMainHandler; private SurfaceHolder mSurfaceHolder; + private boolean mThreadRunning; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -114,9 +120,15 @@ public class PanoramaActivity extends Activity implements public void handleMessage(Message msg) { switch (msg.what) { case MSG_FINAL_MOSAIC_READY: - Uri uri = (Uri) msg.obj; - showFinalMosaic(uri); + mThreadRunning = false; + showFinalMosaic((Bitmap) msg.obj); + break; + case MSG_RESET_TO_PREVIEW: + mThreadRunning = false; + resetToPreview(); + break; } + clearMosaicFrameProcessorIfNeeded(); } }; } @@ -130,6 +142,7 @@ public class PanoramaActivity extends Activity implements private void releaseCamera() { if (mCameraDevice != null) { + mCameraDevice.setPreviewCallbackWithBuffer(null); CameraHolder.instance().release(); mCameraDevice = null; mCameraState = PREVIEW_STOPPED; @@ -267,23 +280,25 @@ public class PanoramaActivity extends Activity implements }); mCaptureLayout.setVisibility(View.VISIBLE); - mPreview.setVisibility(View.GONE); + mPreview.setVisibility(View.INVISIBLE); // will be re-used, invisible is better than gone. mRealTimeMosaicView.setVisibility(View.VISIBLE); mPanoControlLayout.setVisibility(View.GONE); } private void stopCapture() { mMosaicFrameProcessor.setProgressListener(null); - mCameraDevice.stopPreview(); - mCameraDevice.setPreviewCallbackWithBuffer(null); + stopPreview(); // TODO: show some dialog for long computation. - Thread t = new Thread() { - @Override - public void run() { - generateAndStoreFinalMosaic(false); - } - }; - t.start(); + if (!mThreadRunning) { + mThreadRunning = true; + Thread t = new Thread() { + @Override + public void run() { + generateAndStoreFinalMosaic(false); + } + }; + t.start(); + } } private void updateProgress(float translationRate, int traversedAngleX, int traversedAngleY) { @@ -323,16 +338,10 @@ public class PanoramaActivity extends Activity implements mShutterButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + if (mPausing || mThreadRunning) return; startCapture(); } }); - mStopButton = (Button) findViewById(R.id.pano_capture_stop_button); - mStopButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - stopCapture(); - } - }); mPanoControlLayout = (View) findViewById(R.id.pano_control_layout); @@ -342,23 +351,85 @@ public class PanoramaActivity extends Activity implements mModePicker.setCurrentMode(ModePicker.MODE_PANORAMA); } - private void showFinalMosaic(Uri uri) { - mReview.setImageURI(uri); - mCaptureLayout.setVisibility(View.INVISIBLE); - mPreview.setVisibility(View.INVISIBLE); - mReviewLayout.setVisibility(View.VISIBLE); - mCaptureView.setStatusText(""); - mCaptureView.setSweepAngle(0); + @OnClickAttr + public void onStopButtonClicked(View v) { + if (mPausing || mThreadRunning) return; + stopCapture(); + } + + @OnClickAttr + public void onOkButtonClicked(View v) { + if (mPausing || mThreadRunning) return; + mThreadRunning = true; + Thread t = new Thread() { + @Override + public void run() { + saveFinalMosaic(); + } + }; + t.start(); + } + + @OnClickAttr + public void onRetakeButtonClicked(View v) { + if (mPausing || mThreadRunning) return; + resetToPreview(); + } + + private void resetToPreview() { + mPreview.setVisibility(View.VISIBLE); + mPanoControlLayout.setVisibility(View.VISIBLE); + mRealTimeMosaicView.setVisibility(View.GONE); + mCaptureLayout.setVisibility(View.GONE); + mReviewLayout.setVisibility(View.GONE); + mMosaicFrameProcessor.reset(); + if (!mPausing) startPreview(); + } + + private void showFinalMosaic(Bitmap bitmap) { + if (bitmap != null) { + mReview.setImageBitmap(bitmap); + mCaptureLayout.setVisibility(View.GONE); + mPreview.setVisibility(View.INVISIBLE); + mReviewLayout.setVisibility(View.VISIBLE); + mCaptureView.setStatusText(""); + mCaptureView.setSweepAngle(0); + } + } + + private void saveFinalMosaic() { + if (mFinalJpegData != null) { + Storage.addImage(getContentResolver(), mCurrentImagePath, mTimeTaken, null, 0, + mFinalJpegData); + mFinalJpegData = null; + } + mMainHandler.sendMessage(mMainHandler.obtainMessage(MSG_RESET_TO_PREVIEW)); + } + + private void clearMosaicFrameProcessorIfNeeded() { + if (!mPausing || mThreadRunning) return; + mMosaicFrameProcessor.clear(); + } + + private void initMosaicFrameProcessorIfNeeded() { + if (mPausing || mThreadRunning) return; + if (mMosaicFrameProcessor == null) { + // Start the activity for the first time. + mMosaicFrameProcessor = new MosaicFrameProcessor(DEFAULT_SWEEP_ANGLE - 5, + mPreviewWidth, mPreviewHeight, getPreviewBufSize()); + } + mMosaicFrameProcessor.initialize(); } @Override protected void onPause() { super.onPause(); releaseCamera(); - mMosaicFrameProcessor.onPause(); + mPausing = true; mCaptureView.onPause(); mRealTimeMosaicView.onPause(); mSensorManager.unregisterListener(mListener); + clearMosaicFrameProcessorIfNeeded(); System.gc(); } @@ -366,6 +437,7 @@ public class PanoramaActivity extends Activity implements protected void onResume() { super.onResume(); + mPausing = false; /* * It is not necessary to get accelerometer events at a very high rate, * by using a slower rate (SENSOR_DELAY_UI), we get an automatic @@ -376,16 +448,10 @@ public class PanoramaActivity extends Activity implements mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI); setupCamera(); + // Camera must be initialized before MosaicFrameProcessor is initialized. The preview size + // has to be decided by camera device. + initMosaicFrameProcessorIfNeeded(); startPreview(); - - if (mMosaicFrameProcessor == null) { - // Start the activity for the first time. - mMosaicFrameProcessor = new MosaicFrameProcessor(DEFAULT_SWEEP_ANGLE - 5, - mPreviewWidth, mPreviewHeight, getPreviewBufSize()); - mMosaicFrameProcessor.onResume(); - } else { - mMosaicFrameProcessor.onResume(); - } mCaptureView.onResume(); mRealTimeMosaicView.onResume(); } @@ -449,11 +515,10 @@ public class PanoramaActivity extends Activity implements Log.e(TAG, "Exception in storing final mosaic", e); return; } - Uri uri = Storage.addImage( - getContentResolver(), mCurrentImagePath, mTimeTaken, null, 0, - out.toByteArray()); - mMainHandler.sendMessage(mMainHandler.obtainMessage(MSG_FINAL_MOSAIC_READY, uri)); + mFinalJpegData = out.toByteArray(); + Bitmap bitmap = BitmapFactory.decodeByteArray(mFinalJpegData, 0, mFinalJpegData.length); + mMainHandler.sendMessage(mMainHandler.obtainMessage(MSG_FINAL_MOSAIC_READY, bitmap)); // Now's a good time to run the GC. Since we won't do any explicit // allocation during the test, the GC should stay dormant and not // influence our results. |