diff options
Diffstat (limited to 'src/com/android/camera')
-rw-r--r-- | src/com/android/camera/panorama/MosaicFrameProcessor.java | 195 | ||||
-rw-r--r-- | src/com/android/camera/panorama/PanoramaActivity.java | 203 | ||||
-rw-r--r-- | src/com/android/camera/panorama/Preview.java | 416 |
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) { - } } } |