summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorAngus Kong <shkong@google.com>2011-07-28 08:33:39 +0800
committerAngus Kong <shkong@google.com>2011-07-29 06:48:54 +0800
commit8a2c41754655a3733175fce81fb7506ff7022959 (patch)
treeacbd5afa93b3e1a0f2e75da86ff705c32cab1a6a /src/com
parenta72d73cbac59db43d413291e4db66763be08143a (diff)
downloadLegacyCamera-8a2c41754655a3733175fce81fb7506ff7022959.zip
LegacyCamera-8a2c41754655a3733175fce81fb7506ff7022959.tar.gz
LegacyCamera-8a2c41754655a3733175fce81fb7506ff7022959.tar.bz2
Refactoring Panorama UI codes.
Mosaic native interface is moved out from Preview.java. Program logics and UI are now decoupled. bug:5031609 Change-Id: If46a21a6be05deba7fd1f94f37d14c4afa460d78
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/camera/panorama/MosaicFrameProcessor.java195
-rw-r--r--src/com/android/camera/panorama/PanoramaActivity.java203
-rw-r--r--src/com/android/camera/panorama/Preview.java416
3 files changed, 390 insertions, 424 deletions
diff --git a/src/com/android/camera/panorama/MosaicFrameProcessor.java b/src/com/android/camera/panorama/MosaicFrameProcessor.java
new file mode 100644
index 0000000..97f5c6f
--- /dev/null
+++ b/src/com/android/camera/panorama/MosaicFrameProcessor.java
@@ -0,0 +1,195 @@
+/*
+ * 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 android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Matrix;
+
+import android.util.Log;
+
+public class MosaicFrameProcessor {
+ private static final boolean LOGV = true;
+ 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 DOWN_SAMPLE_FACTOR = 4;
+ 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 Mosaic mMosaicer;
+ private final byte[][] mFrames = new byte[NUM_FRAMES_IN_BUFFER][]; // Space for N frames
+ private final long [] mFrameTimestamp = new long[NUM_FRAMES_IN_BUFFER];
+ private Bitmap mLRBitmapAlpha = null;
+ private Matrix mTransformationMatrix = null;
+ private float mTranslationLastX;
+ private float mTranslationLastY;
+
+ private int mFillIn = 0;
+ private int mTotalFrameCount = 0;
+ private int[] mColors = null;
+ private long mLastProcessedFrameTimestamp = 0;
+ private int mLastProcessFrameIdx = -1;
+ private int mCurrProcessFrameIdx = -1;
+
+ private ProgressListener mProgressListener;
+
+ private float mCompassValueX;
+ private float mCompassValueY;
+ private float mCompassValueXStart;
+ private float mCompassValueYStart;
+ private int mCompassThreshold;
+ private int mTraversedAngleX;
+ private int mTraversedAngleY;
+ private float mTranslationRate;
+
+
+ public interface ProgressListener {
+ public void onProgress(boolean isFinished, float translationRate,
+ int traversedAngleX, int traversedAngleY,
+ Bitmap lowResBitmapAlpha, Matrix transformaMatrix);
+ }
+
+ public MosaicFrameProcessor(int sweepAngle, int previewWidth, int previewHeight, int bufSize) {
+ mMosaicer = new Mosaic();
+ setupMosaicer(previewWidth, previewHeight, bufSize);
+ reset();
+
+ mCompassThreshold = sweepAngle;
+
+ int downSizedW = previewWidth / DOWN_SAMPLE_FACTOR;
+ int downSizedH = previewHeight / DOWN_SAMPLE_FACTOR;
+
+ mColors = new int[downSizedW * downSizedH];
+ mLRBitmapAlpha = Bitmap.createBitmap(downSizedW, downSizedH, Config.ARGB_8888);
+ }
+
+ public void setProgressListener(ProgressListener listener) {
+ mProgressListener = listener;
+ }
+
+ private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) {
+ Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize);
+ mMosaicer.setSourceImageDimensions(previewWidth, previewHeight);
+
+ mTransformationMatrix = new Matrix();
+
+ for (int i = 0; i < NUM_FRAMES_IN_BUFFER; i++) {
+ mFrames[i] = new byte[bufSize];
+ }
+ }
+
+ public void createMosaic(boolean highRes) {
+ mMosaicer.createMosaic(highRes);
+ }
+
+ public byte[] getFinalMosaicNV21() {
+ return mMosaicer.getFinalMosaicNV21();
+ }
+
+ public void reset() {
+ mFillIn = 0;
+ if (mMosaicer != null) {
+ mMosaicer.reset();
+ }
+ }
+
+ // 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(byte[] data, int width, int height) {
+ long t1 = System.currentTimeMillis();
+ mFrameTimestamp[mFillIn] = t1;
+ System.arraycopy(data, 0, mFrames[mFillIn], 0, data.length);
+ 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;
+
+ if (LOGV) Log.v(TAG, "Processing: [" + mCurrProcessFrameIdx + "]");
+
+ // Access the image data and the timestamp associated with it...
+ byte[] currentFrame = mFrames[mCurrProcessFrameIdx];
+ long timestamp = mFrameTimestamp[mCurrProcessFrameIdx];
+
+ // Keep track of what compass bearing we started at...
+ if (mTotalFrameCount == 0) { // First frame
+ mCompassValueXStart = mCompassValueX;
+ mCompassValueYStart = mCompassValueY;
+ }
+
+ // By what angle has the camera moved since start of capture?
+ mTraversedAngleX = (int) PanoUtil.calculateDifferenceBetweenAngles(
+ mCompassValueX, mCompassValueXStart);
+ mTraversedAngleY = (int) PanoUtil.calculateDifferenceBetweenAngles(
+ mCompassValueY, mCompassValueYStart);
+
+ if (mTotalFrameCount <= MAX_NUMBER_OF_FRAMES
+ && mTraversedAngleX < mCompassThreshold
+ && mTraversedAngleY < mCompassThreshold) {
+ // If we are still collecting new frames for the current mosaic,
+ // process the new frame.
+ translateFrame(currentFrame, width, height, timestamp);
+
+ // Publish progress of the ongoing processing
+ if (mProgressListener != null) {
+ mProgressListener.onProgress(
+ false, mTranslationRate, mTraversedAngleX, mTraversedAngleY,
+ mLRBitmapAlpha, mTransformationMatrix);
+ }
+ } else {
+ if (mProgressListener != null) {
+ mProgressListener.onProgress(
+ true, mTranslationRate, mTraversedAngleX, mTraversedAngleY,
+ mLRBitmapAlpha, mTransformationMatrix);
+ }
+ }
+ }
+ }
+
+ public void translateFrame(final byte[] data, int width, int height, long now) {
+ float deltaTime = (float) (now - mLastProcessedFrameTimestamp) / 1000.0f;
+ mLastProcessedFrameTimestamp = now;
+
+ float[] frameData = mMosaicer.setSourceImage(data);
+ mTotalFrameCount = (int) frameData[FRAME_COUNT_INDEX];
+ float translationCurrX = frameData[X_COORD_INDEX];
+ float translationCurrY = frameData[Y_COORD_INDEX];
+ mTransformationMatrix.setValues(frameData);
+
+ int outw = width / DOWN_SAMPLE_FACTOR;
+ int outh = height / DOWN_SAMPLE_FACTOR;
+
+ PanoUtil.decodeYUV420SPQuarterRes(mColors, data, width, height);
+ mLRBitmapAlpha.setPixels(mColors, 0, outw, 0, 0, outw, outh);
+
+ mTranslationRate = Math.max(Math.abs(translationCurrX - mTranslationLastX),
+ Math.abs(translationCurrY - mTranslationLastY)) / deltaTime;
+ mTranslationLastX = translationCurrX;
+ mTranslationLastY = translationCurrY;
+ }
+
+ public void updateCompassValue(float valueX, float valueY) {
+ mCompassValueX = valueX;
+ mCompassValueY = valueY;
+ }
+}
diff --git a/src/com/android/camera/panorama/PanoramaActivity.java b/src/com/android/camera/panorama/PanoramaActivity.java
index 27c90e2..336533f 100644
--- a/src/com/android/camera/panorama/PanoramaActivity.java
+++ b/src/com/android/camera/panorama/PanoramaActivity.java
@@ -18,15 +18,24 @@ package com.android.camera.panorama;
import android.app.Activity;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
@@ -39,8 +48,12 @@ import com.android.camera.MenuHelper;
import com.android.camera.ModePicker;
import com.android.camera.R;
import com.android.camera.ShutterButton;
+import com.android.camera.Storage;
import com.android.camera.Util;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
import java.util.List;
public class PanoramaActivity extends Activity implements
@@ -58,11 +71,64 @@ public class PanoramaActivity extends Activity implements
private ShutterButton mShutterButton;
private int mPreviewWidth;
private int mPreviewHeight;
- private android.hardware.Camera mCameraDevice;
+
+ private Camera mCameraDevice;
private SensorManager mSensorManager;
private Sensor mSensor;
private ModePicker mModePicker;
+ private MosaicFrameProcessor mMosaicFrameProcessor;
+ private ScannerClient mScannerClient;
+
+ private String mCurrentImagePath = null;
+ private long mTimeTaken;
+
+ // Need handler for callbacks to the UI thread
+ private final Handler mHandler = new Handler();
+
+ /**
+ * Inner class to tell the gallery app to scan the newly created mosaic images.
+ * TODO: insert the image to media store.
+ */
+ private static final class ScannerClient implements MediaScannerConnectionClient {
+ ArrayList<String> mPaths = new ArrayList<String>();
+ MediaScannerConnection mScannerConnection;
+ boolean mConnected;
+ Object mLock = new Object();
+
+ public ScannerClient(Context context) {
+ mScannerConnection = new MediaScannerConnection(context, this);
+ }
+
+ public void scanPath(String path) {
+ synchronized (mLock) {
+ if (mConnected) {
+ mScannerConnection.scanFile(path, null);
+ } else {
+ mPaths.add(path);
+ mScannerConnection.connect();
+ }
+ }
+ }
+
+ @Override
+ public void onMediaScannerConnected() {
+ synchronized (mLock) {
+ mConnected = true;
+ if (!mPaths.isEmpty()) {
+ for (String path : mPaths) {
+ mScannerConnection.scanFile(path, null);
+ }
+ mPaths.clear();
+ }
+ }
+ }
+
+ @Override
+ public void onScanCompleted(String path, Uri uri) {
+ }
+ }
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -78,8 +144,18 @@ public class PanoramaActivity extends Activity implements
if (mSensor == null) {
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
}
+ mScannerClient = new ScannerClient(getApplicationContext());
+
}
+ // Create runnable for posting
+ private final Runnable mUpdateResults = new Runnable() {
+ public void run() {
+ showResultingMosaic("file://" + mCurrentImagePath);
+ mScannerClient.scanPath(mCurrentImagePath);
+ }
+ };
+
private void setupCamera() {
openCamera();
Parameters parameters = mCameraDevice.getParameters();
@@ -103,7 +179,7 @@ public class PanoramaActivity extends Activity implements
boolean needSmaller) {
int pixelsDiff = DEFAULT_CAPTURE_PIXELS;
boolean hasFound = false;
- for (Size size: supportedSizes) {
+ for (Size size : supportedSizes) {
int h = size.height;
int w = size.width;
// we only want 4:3 format.
@@ -186,6 +262,74 @@ public class PanoramaActivity extends Activity implements
}
}
+ public void setCaptureStarted(int sweepAngle, int blendType) {
+ // Reset values so we can do this again.
+ mTimeTaken = System.currentTimeMillis();
+ mMosaicFrameProcessor = new MosaicFrameProcessor(sweepAngle - 5, mPreviewWidth,
+ mPreviewHeight, getPreviewBufSize());
+
+ mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() {
+ @Override
+ public void onProgress(boolean isFinished, float translationRate, int traversedAngleX,
+ int traversedAngleY, Bitmap lowResBitmapAlpha, Matrix transformaMatrix) {
+ if (isFinished) {
+ onMosaicFinished();
+ } else {
+ updateProgress(translationRate, traversedAngleX, traversedAngleY,
+ lowResBitmapAlpha, transformaMatrix);
+ }
+ }
+ });
+
+ // Preview callback used whenever new viewfinder frame is available
+ mCameraDevice.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
+ @Override
+ public void onPreviewFrame(final byte[] data, Camera camera) {
+ mMosaicFrameProcessor.processFrame(data, mPreviewWidth, mPreviewHeight);
+ // The returned buffer needs be added back to callback buffer again.
+ camera.addCallbackBuffer(data);
+ }
+ });
+
+ mCaptureView.setVisibility(View.VISIBLE);
+ }
+
+ private void onMosaicFinished() {
+ mMosaicFrameProcessor.setProgressListener(null);
+ mPreview.setVisibility(View.INVISIBLE);
+ mCaptureView.setVisibility(View.INVISIBLE);
+ mCaptureView.setBitmap(null);
+ mCaptureView.setStatusText("");
+ mCaptureView.setSweepAngle(0);
+ mCaptureView.invalidate();
+ // Background-process the final blending of the mosaic so
+ // that the UI is not blocked.
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ generateAndStoreFinalMosaic(false);
+ }
+ };
+ t.start();
+ }
+
+ private void updateProgress(float translationRate, int traversedAngleX, int traversedAngleY,
+ Bitmap lowResBitmapAlpha, Matrix transformationMatrix) {
+ mCaptureView.setBitmap(lowResBitmapAlpha, transformationMatrix);
+ if (translationRate > 150) {
+ // TODO: remove the text and draw implications according to the UI spec.
+ mCaptureView.setStatusText("S L O W D O W N");
+ mCaptureView.setSweepAngle(
+ Math.max(traversedAngleX, traversedAngleY) + 1);
+ mCaptureView.invalidate();
+ } else {
+ mCaptureView.setStatusText("");
+ mCaptureView.setSweepAngle(
+ Math.max(traversedAngleX, traversedAngleY) + 1);
+ mCaptureView.invalidate();
+ }
+ }
+
private void createContentView() {
setContentView(R.layout.panorama);
@@ -201,7 +345,7 @@ public class PanoramaActivity extends Activity implements
mShutterButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- mPreview.setCaptureStarted(DEFAULT_SWEEP_ANGLE, DEFAULT_BLEND_MODE);
+ setCaptureStarted(DEFAULT_SWEEP_ANGLE, DEFAULT_BLEND_MODE);
}
});
mModePicker = (ModePicker) findViewById(R.id.mode_picker);
@@ -221,7 +365,6 @@ public class PanoramaActivity extends Activity implements
@Override
protected void onPause() {
super.onPause();
- mPreview.onPause();
mSensorManager.unregisterListener(mListener);
releaseCamera();
}
@@ -271,8 +414,8 @@ public class PanoramaActivity extends Activity implements
mCompassCurrY = event.values[1];
}
- if (mPreview != null) {
- mPreview.updateCompassValue(mCompassCurrX, mCompassCurrY);
+ if (mMosaicFrameProcessor != null) {
+ mMosaicFrameProcessor.updateCompassValue(mCompassCurrX, mCompassCurrY);
}
}
@@ -281,15 +424,47 @@ public class PanoramaActivity extends Activity implements
}
};
- public int getPreviewFrameWidth() {
- return mPreviewWidth;
- }
+ public void generateAndStoreFinalMosaic(boolean highRes) {
+ mMosaicFrameProcessor.createMosaic(highRes);
- public int getPreviewFrameHeight() {
- return mPreviewHeight;
- }
+ mCurrentImagePath = Storage.DIRECTORY + "/" + PanoUtil.createName(
+ getResources().getString(R.string.pano_file_name_format), mTimeTaken);
- public CaptureView getCaptureView() {
- return mCaptureView;
+ if (highRes) {
+ mCurrentImagePath += "_HR.jpg";
+ } else {
+ mCurrentImagePath += "_LR.jpg";
+ }
+
+ try {
+ File mosDirectory = new File(Storage.DIRECTORY);
+ // have the object build the directory structure, if needed.
+ mosDirectory.mkdirs();
+
+ byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21();
+ 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.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height);
+
+ YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
+ FileOutputStream out = new FileOutputStream(mCurrentImagePath);
+ yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
+ out.close();
+
+ // 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.
+ System.runFinalization();
+ System.gc();
+
+ mHandler.post(mUpdateResults);
+ } catch (Exception e) {
+ Log.e(TAG, "exception in storing final mosaic", e);
+ }
}
+
}
diff --git a/src/com/android/camera/panorama/Preview.java b/src/com/android/camera/panorama/Preview.java
index 2d5ddd2..5a6cf9f 100644
--- a/src/com/android/camera/panorama/Preview.java
+++ b/src/com/android/camera/panorama/Preview.java
@@ -17,225 +17,22 @@
package com.android.camera.panorama;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.ImageFormat;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.YuvImage;
import android.hardware.Camera;
-import android.media.MediaScannerConnection;
-import android.media.MediaScannerConnection.MediaScannerConnectionClient;
-import android.net.Uri;
-import android.os.Handler;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
-import android.view.View;
-import com.android.camera.R;
-import com.android.camera.Storage;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.util.ArrayList;
-
-class Preview extends SurfaceView implements SurfaceHolder.Callback,
- Camera.PreviewCallback {
+class Preview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "Preview";
- private static final boolean LOGV = true;
- private static final int NUM_FRAMES_IN_BUFFER = 2;
- private static final int MAX_NUMBER_OF_FRAMES = 100;
- private static final int DOWN_SAMPLE_SIZE = 4;
-
- private final Object mLockFillIn = new Object(); // Object used for synchronization of mFillIn
- private final byte[][] mFrames = new byte[NUM_FRAMES_IN_BUFFER][]; // Space for N frames
- private final long [] mFrameTimestamp = new long[NUM_FRAMES_IN_BUFFER];
-
- private PanoramaActivity mActivity;
- private Mosaic mMosaicer;
- private LowResFrameProcessor mLowResProcessor = null;
- private SurfaceHolder mHolder;
private android.hardware.Camera mCameraDevice;
- private Bitmap mLRBitmapAlpha = null;
- private Matrix mTransformationMatrix = null;
-
- private int mFillIn = 0;
- private long mLastProcessedFrameTimestamp = 0;
- private int mTotalFrameCount = 0;
- private int[] mColors = null;
-
- private int mPreviewWidth;
- private int mPreviewHeight;
-
- private float mTranslationLastX;
- private float mTranslationLastY;
- private float mTranslationRate;
-
- private ScannerClient mScannerClient;
-
- private String mCurrentImagePath = null;
- private long mTimeTaken;
-
- // Need handler for callbacks to the UI thread
- private final Handler mHandler = new Handler();
-
- // Create runnable for posting
- private final Runnable mUpdateResults = new Runnable() {
- public void run() {
- mActivity.showResultingMosaic("file://" + mCurrentImagePath);
- mScannerClient.scanPath(mCurrentImagePath);
- }
- };
-
public Preview(Context context, AttributeSet attrs) {
super(context, attrs);
- mActivity = (PanoramaActivity) getContext();
-
- mMosaicer = new Mosaic();
- mScannerClient = new ScannerClient(getContext());
-
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
- mHolder = getHolder();
- mHolder.addCallback(this);
- mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- }
-
- private class LowResFrameProcessor {
- private CaptureView mCaptureView;
- int mLastProcessFrameIdx = -1;
- int mCurrProcessFrameIdx = -1;
- boolean mRun = true;
-
- private float mCompassValueX;
- private float mCompassValueY;
- private float mCompassValueXStart;
- private float mCompassValueYStart;
- private int mCompassThreshold;
- private int mTraversedAngleX;
- private int mTraversedAngleY;
-
-
- public LowResFrameProcessor(int sweepAngle, CaptureView overlayer) {
- mCompassThreshold = sweepAngle;
- mCaptureView = overlayer;
- }
-
- // 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 runEachFrame() {
- mCurrProcessFrameIdx = getLastFilledIn();
-
- // 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;
-
- if (LOGV) Log.v(TAG, "Processing: [" + mCurrProcessFrameIdx + "]");
-
- // Access the image data and the timestamp associated with it...
- byte[] data = mFrames[mCurrProcessFrameIdx];
- long timestamp = mFrameTimestamp[mCurrProcessFrameIdx];
-
- // Keep track of what compass bearing we started at...
- if (mTotalFrameCount == 0) { // First frame
- mCompassValueXStart = mCompassValueX;
- mCompassValueYStart = mCompassValueY;
- }
-
- // By what angle has the camera moved since start of capture?
- mTraversedAngleX = (int) PanoUtil.calculateDifferenceBetweenAngles(
- mCompassValueX, mCompassValueXStart);
- mTraversedAngleY = (int) PanoUtil.calculateDifferenceBetweenAngles(
- mCompassValueY, mCompassValueYStart);
-
- if (mTotalFrameCount <= MAX_NUMBER_OF_FRAMES
- && mTraversedAngleX < mCompassThreshold
- && mTraversedAngleY < mCompassThreshold) {
- // If we are still collecting new frames for the current mosaic,
- // process the new frame.
- processFrame(data, timestamp);
-
- // Publish progress of the ongoing processing
- publishProgress(0);
- } else {
- // Publish progress that we are done with capture
- publishProgress(1);
-
- // Background-process the final blending of the mosaic so
- // that the UI is not blocked.
- Thread t = new Thread() {
- @Override
- public void run() {
- generateAndStoreFinalMosaic(false);
- }
- };
- t.start();
-
- mRun = false;
- }
- }
- }
-
- // Sets the screen layout before starting each fresh capture.
- protected void onPreExecute() {
- if (mTotalFrameCount == 0) {
- mCaptureView.setVisibility(View.VISIBLE);
- }
- }
-
- // Updates the GUI with ongoing updates if values[0]==0 and
- // with the constructed mosaic for values[0]==1.
- public void publishProgress(Integer... values) {
- long t1 = System.currentTimeMillis();
-
- if (values[0] == 0) { // Ongoing
- // This updates the real-time mosaic display with the current image frame and the
- // transformation matrix to warp it by.
- mCaptureView.setBitmap(mLRBitmapAlpha, mTransformationMatrix);
-
- // Update the sweep-angle sector display and show "SLOW DOWN" message if the user
- // is moving the camera too fast
- if (mTranslationRate > 150) {
- // TODO: remove the text and draw implications according to the UI spec.
- mCaptureView.setStatusText("S L O W D O W N");
- mCaptureView.setSweepAngle(
- Math.max(mTraversedAngleX, mTraversedAngleY) + 1);
- mCaptureView.invalidate();
- } else {
- mCaptureView.setStatusText("");
- mCaptureView.setSweepAngle(
- Math.max(mTraversedAngleX, mTraversedAngleY) + 1);
- mCaptureView.invalidate();
- }
- } else { // Done
- setVisibility(View.INVISIBLE);
- mCaptureView.setVisibility(View.INVISIBLE);
- mCaptureView.setBitmap(null);
- mCaptureView.setStatusText("");
- mCaptureView.setSweepAngle(0);
- mCaptureView.invalidate();
- }
-
- long t2 = System.currentTimeMillis();
- }
-
- public void updateCompassValue(float valueX, float valueY) {
- mCompassValueX = valueX;
- mCompassValueY = valueY;
- }
- }
-
- public void updateCompassValue(float valueX, float valueY) {
- if (mLowResProcessor != null) {
- mLowResProcessor.updateCompassValue(valueX, valueY);
- }
+ getHolder().addCallback(this);
}
@Override
@@ -246,219 +43,18 @@ class Preview extends SurfaceView implements SurfaceHolder.Callback,
public void surfaceDestroyed(SurfaceHolder holder) {
}
- private void setupMosaicer() {
- mPreviewWidth = mActivity.getPreviewFrameWidth();
- mPreviewHeight = mActivity.getPreviewFrameHeight();
-
- mMosaicer.setSourceImageDimensions(mPreviewWidth, mPreviewHeight);
-
- mColors = new int[(mActivity.getPreviewFrameWidth() / DOWN_SAMPLE_SIZE)
- * (mActivity.getPreviewFrameHeight() / DOWN_SAMPLE_SIZE)];
- mLRBitmapAlpha = Bitmap.createBitmap((mPreviewWidth / DOWN_SAMPLE_SIZE),
- (mPreviewHeight / DOWN_SAMPLE_SIZE), Config.ARGB_8888);
- mTransformationMatrix = new Matrix();
-
- int bufSize = mActivity.getPreviewBufSize();
- for (int i = 0; i < NUM_FRAMES_IN_BUFFER; i++) {
- mFrames[i] = new byte[bufSize];
- }
- }
-
public void setCameraDevice(android.hardware.Camera camera) {
- setupMosaicer();
-
mCameraDevice = camera;
- // Preview callback used whenever new viewfinder frame is available
- mCameraDevice.setPreviewCallbackWithBuffer(this);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
- try {
- mCameraDevice.setPreviewDisplay(holder);
- } catch (Throwable ex) {
- throw new RuntimeException("setPreviewDisplay failed", ex);
- }
- }
-
- public void onPreviewFrame(final byte[] data, Camera camera) {
- long t1 = System.currentTimeMillis();
- synchronized (mLockFillIn) {
- mFrameTimestamp[mFillIn] = t1;
- System.arraycopy(data, 0, mFrames[mFillIn], 0, data.length);
- }
- incrementFillIn();
-
- if (mLowResProcessor != null && mLowResProcessor.mRun) {
- mLowResProcessor.runEachFrame();
- }
-
- // The returned buffer needs be added back to callback buffer again.
if (mCameraDevice != null) {
- mCameraDevice.addCallbackBuffer(data);
- }
- }
-
- public void processFrame(final byte[] data, long now) {
- float deltaTime = (float) (now - mLastProcessedFrameTimestamp) / 1000.0f;
- mLastProcessedFrameTimestamp = now;
-
- long t1 = System.currentTimeMillis();
-
- float[] frameData = mMosaicer.setSourceImage(data);
-
- mTotalFrameCount = (int) frameData[9];
- float translationCurrX = frameData[2];
- float translationCurrY = frameData[5];
-
- long t2 = System.currentTimeMillis();
-
- Log.v(TAG, "[ " + deltaTime + " ] AddFrame: " + (t2 - t1));
-
- t1 = System.currentTimeMillis();
- mTransformationMatrix.setValues(frameData);
-
- int outw = mPreviewWidth / DOWN_SAMPLE_SIZE;
- int outh = mPreviewHeight / DOWN_SAMPLE_SIZE;
-
- PanoUtil.decodeYUV420SPQuarterRes(mColors, data, mPreviewWidth, mPreviewHeight);
-
- mLRBitmapAlpha.setPixels(mColors, 0, outw, 0, 0, outw, outh);
-
- t2 = System.currentTimeMillis();
- Log.v(TAG, "GenerateLowResBitmap: " + (t2 - t1));
-
- mTranslationRate = Math.max(Math.abs(translationCurrX - mTranslationLastX),
- Math.abs(translationCurrY - mTranslationLastY)) / deltaTime;
- mTranslationLastX = translationCurrX;
- mTranslationLastY = translationCurrY;
- }
-
- public void generateAndStoreFinalMosaic(boolean highRes) {
- long t1 = System.currentTimeMillis();
-
- mMosaicer.createMosaic(highRes);
-
- mCurrentImagePath = Storage.DIRECTORY + "/" + PanoUtil.createName(
- mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken);
-
- if (highRes) {
- mCurrentImagePath += "_HR.jpg";
- } else {
- mCurrentImagePath += "_LR.jpg";
- }
-
- long t2 = System.currentTimeMillis();
- long dur = (t2 - t1) / 1000;
-
- try {
- File mosDirectory = new File(Storage.DIRECTORY);
- // have the object build the directory structure, if needed.
- mosDirectory.mkdirs();
-
- byte[] imageData = mMosaicer.getFinalMosaicNV21();
- 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.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height);
-
- YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
- FileOutputStream out = new FileOutputStream(mCurrentImagePath);
- yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
- out.close();
-
- // 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.
- System.runFinalization();
- System.gc();
-
- mHandler.post(mUpdateResults);
- } catch (Exception e) {
- Log.e(TAG, "exception in storing final mosaic", e);
- }
- }
-
- public void setCaptureStarted(int sweepAngle, int blendType) {
- // Reset values so we can do this again.
- mMosaicer.reset();
- mTotalFrameCount = 0;
- mLastProcessedFrameTimestamp = 0;
-
- mTimeTaken = System.currentTimeMillis();
- CaptureView captureView = mActivity.getCaptureView();
- captureView.setVisibility(View.VISIBLE);
- mLowResProcessor = new LowResFrameProcessor(sweepAngle - 5, captureView);
-
- mLowResProcessor.onPreExecute();
- }
-
- /**
- * This must be called when the activity pauses (in Activity.onPause).
- */
- public void onPause() {
- mMosaicer.reset();
- }
-
- private int getLastFilledIn() {
- synchronized (mLockFillIn) {
- if (mFillIn > 0) {
- return mFillIn - 1;
- } else {
- return NUM_FRAMES_IN_BUFFER - 1;
- }
- }
- }
-
- private void incrementFillIn() {
- synchronized (mLockFillIn) {
- mFillIn = ((mFillIn + 1) >= NUM_FRAMES_IN_BUFFER) ? 0 : (mFillIn + 1);
- }
- }
-
- /**
- * Inner class to tell the gallery app to scan the newly created mosaic images.
- * TODO: insert the image to media store.
- */
- private static final class ScannerClient implements MediaScannerConnectionClient {
- ArrayList<String> mPaths = new ArrayList<String>();
- MediaScannerConnection mScannerConnection;
- boolean mConnected;
- Object mLock = new Object();
-
- public ScannerClient(Context context) {
- mScannerConnection = new MediaScannerConnection(context, this);
- }
-
- public void scanPath(String path) {
- synchronized (mLock) {
- if (mConnected) {
- mScannerConnection.scanFile(path, null);
- } else {
- mPaths.add(path);
- mScannerConnection.connect();
- }
+ try {
+ mCameraDevice.setPreviewDisplay(holder);
+ } catch (Throwable ex) {
+ throw new RuntimeException("setPreviewDisplay failed", ex);
}
}
-
- @Override
- public void onMediaScannerConnected() {
- synchronized (mLock) {
- mConnected = true;
- if (!mPaths.isEmpty()) {
- for (String path : mPaths) {
- mScannerConnection.scanFile(path, null);
- }
- mPaths.clear();
- }
- }
- }
-
- @Override
- public void onScanCompleted(String path, Uri uri) {
- }
}
}