MosaicFrameProcessor.java revision d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9e
18a2c41754655a3733175fce81fb7506ff7022959Angus Kong/*
28a2c41754655a3733175fce81fb7506ff7022959Angus Kong * Copyright (C) 2011 The Android Open Source Project
38a2c41754655a3733175fce81fb7506ff7022959Angus Kong *
48a2c41754655a3733175fce81fb7506ff7022959Angus Kong * Licensed under the Apache License, Version 2.0 (the "License");
58a2c41754655a3733175fce81fb7506ff7022959Angus Kong * you may not use this file except in compliance with the License.
68a2c41754655a3733175fce81fb7506ff7022959Angus Kong * You may obtain a copy of the License at
78a2c41754655a3733175fce81fb7506ff7022959Angus Kong *
88a2c41754655a3733175fce81fb7506ff7022959Angus Kong *      http://www.apache.org/licenses/LICENSE-2.0
98a2c41754655a3733175fce81fb7506ff7022959Angus Kong *
108a2c41754655a3733175fce81fb7506ff7022959Angus Kong * Unless required by applicable law or agreed to in writing, software
118a2c41754655a3733175fce81fb7506ff7022959Angus Kong * distributed under the License is distributed on an "AS IS" BASIS,
128a2c41754655a3733175fce81fb7506ff7022959Angus Kong * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138a2c41754655a3733175fce81fb7506ff7022959Angus Kong * See the License for the specific language governing permissions and
148a2c41754655a3733175fce81fb7506ff7022959Angus Kong * limitations under the License.
158a2c41754655a3733175fce81fb7506ff7022959Angus Kong */
168a2c41754655a3733175fce81fb7506ff7022959Angus Kong
178a2c41754655a3733175fce81fb7506ff7022959Angus Kongpackage com.android.camera.panorama;
188a2c41754655a3733175fce81fb7506ff7022959Angus Kong
198a2c41754655a3733175fce81fb7506ff7022959Angus Kongimport android.util.Log;
208a2c41754655a3733175fce81fb7506ff7022959Angus Kong
21142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong/**
22142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong * Class to handle the processing of each frame by Mosaicer.
23142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong */
248a2c41754655a3733175fce81fb7506ff7022959Angus Kongpublic class MosaicFrameProcessor {
258a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private static final boolean LOGV = true;
268a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private static final String TAG = "MosaicFrameProcessor";
278a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private static final int NUM_FRAMES_IN_BUFFER = 2;
288a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private static final int MAX_NUMBER_OF_FRAMES = 100;
29dd28e1cc00373c02adf88dff878dbbe5d8be9e59mbansal    private static final int MOSAIC_RET_CODE_INDEX = 10;
308a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private static final int FRAME_COUNT_INDEX = 9;
318a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private static final int X_COORD_INDEX = 2;
328a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private static final int Y_COORD_INDEX = 5;
3343b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen    private static final int HR_TO_LR_DOWNSAMPLE_FACTOR = 4;
34e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen    private static final int WINDOW_SIZE = 3;
358a2c41754655a3733175fce81fb7506ff7022959Angus Kong
368a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private Mosaic mMosaicer;
370adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen    private boolean mIsMosaicMemoryAllocated = false;
388a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private final long [] mFrameTimestamp = new long[NUM_FRAMES_IN_BUFFER];
398a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private float mTranslationLastX;
408a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private float mTranslationLastY;
418a2c41754655a3733175fce81fb7506ff7022959Angus Kong
428a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private int mFillIn = 0;
438a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private int mTotalFrameCount = 0;
448a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private long mLastProcessedFrameTimestamp = 0;
458a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private int mLastProcessFrameIdx = -1;
468a2c41754655a3733175fce81fb7506ff7022959Angus Kong    private int mCurrProcessFrameIdx = -1;
478a2c41754655a3733175fce81fb7506ff7022959Angus Kong
4843b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen    // Panning rate is in unit of percentage of image content translation / second.
49e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen    // Use the moving average to calculate the panning rate.
5043b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen    private float mPanningRateX;
5143b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen    private float mPanningRateY;
528a2c41754655a3733175fce81fb7506ff7022959Angus Kong
53e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen    private float[] mDeltaX = new float[WINDOW_SIZE];
54e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen    private float[] mDeltaY = new float[WINDOW_SIZE];
55e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen    private float[] mDeltaTime = new float[WINDOW_SIZE];
56e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen    private int mOldestIdx = 0;
57e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen    private float mTotalTranslationX = 0f;
58e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen    private float mTotalTranslationY = 0f;
59e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen    private float mTotalDeltaTime = 0f;
60e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen
61e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen    private ProgressListener mProgressListener;
62e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen
6394f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen    private int mPreviewWidth;
6494f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen    private int mPreviewHeight;
6594f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen    private int mPreviewBufferSize;
6694f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen
678a2c41754655a3733175fce81fb7506ff7022959Angus Kong    public interface ProgressListener {
68d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
69d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                float progressX, float progressY);
708a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
718a2c41754655a3733175fce81fb7506ff7022959Angus Kong
72c89a0eb7b1f0cd5cd45de8fd08ff051f6f74f382Angus Kong    public MosaicFrameProcessor(int previewWidth, int previewHeight, int bufSize) {
738a2c41754655a3733175fce81fb7506ff7022959Angus Kong        mMosaicer = new Mosaic();
7494f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        mPreviewWidth = previewWidth;
7594f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        mPreviewHeight = previewHeight;
7694f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        mPreviewBufferSize = bufSize;
778a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
788a2c41754655a3733175fce81fb7506ff7022959Angus Kong
798a2c41754655a3733175fce81fb7506ff7022959Angus Kong    public void setProgressListener(ProgressListener listener) {
808a2c41754655a3733175fce81fb7506ff7022959Angus Kong        mProgressListener = listener;
818a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
828a2c41754655a3733175fce81fb7506ff7022959Angus Kong
83e1178a73fd5756771d25d0b8375452450f509e99mbansal    public int reportProgress(boolean hires, boolean cancel) {
84e1178a73fd5756771d25d0b8375452450f509e99mbansal        return mMosaicer.reportProgress(hires, cancel);
8550b3c890986aadb3780b4da8c0b8dbb0f1422ebambansal    }
8650b3c890986aadb3780b4da8c0b8dbb0f1422ebambansal
87142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong    public void initialize() {
880adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen        setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize);
89b28b9c0fa991bc97e8aa11da83d27f71fdfef6dambansal        setStripType(Mosaic.STRIPTYPE_WIDE);
90142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        reset();
9194f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen    }
9294f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen
93142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong    public void clear() {
940adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen        mIsMosaicMemoryAllocated = false;
95142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mMosaicer.freeMosaicMemory();
9694f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen    }
9794f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen
98b28b9c0fa991bc97e8aa11da83d27f71fdfef6dambansal    public void setStripType(int type) {
99b28b9c0fa991bc97e8aa11da83d27f71fdfef6dambansal        mMosaicer.setStripType(type);
100b28b9c0fa991bc97e8aa11da83d27f71fdfef6dambansal    }
101b28b9c0fa991bc97e8aa11da83d27f71fdfef6dambansal
1020adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen    private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) {
1038a2c41754655a3733175fce81fb7506ff7022959Angus Kong        Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize);
10494f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        mMosaicer.allocateMosaicMemory(previewWidth, previewHeight);
1050adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen        mIsMosaicMemoryAllocated = true;
10694f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen
10794f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        mFillIn = 0;
10894f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        if  (mMosaicer != null) {
10994f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen            mMosaicer.reset();
11094f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        }
11194f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen    }
11294f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen
113142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong    public void reset() {
114142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        // reset() can be called even if MosaicFrameProcessor is not initialized.
115142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        // Only counters will be changed.
116142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mTotalFrameCount = 0;
117142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mFillIn = 0;
118142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mLastProcessedFrameTimestamp = 0;
119e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationX = 0;
120e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTranslationLastX = 0;
121e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationY = 0;
122e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTranslationLastY = 0;
123e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalDeltaTime = 0;
124e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mPanningRateX = 0;
125e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mPanningRateY = 0;
126142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mLastProcessFrameIdx = -1;
127142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mCurrProcessFrameIdx = -1;
128e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        for (int i = 0; i < WINDOW_SIZE; ++i) {
129e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mDeltaX[i] = 0f;
130e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mDeltaY[i] = 0f;
131e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mDeltaTime[i] = 0f;
132e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        }
133142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mMosaicer.reset();
13494f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen    }
13594f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen
136e1178a73fd5756771d25d0b8375452450f509e99mbansal    public int createMosaic(boolean highRes) {
137e1178a73fd5756771d25d0b8375452450f509e99mbansal        return mMosaicer.createMosaic(highRes);
1388a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
1398a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1408a2c41754655a3733175fce81fb7506ff7022959Angus Kong    public byte[] getFinalMosaicNV21() {
1418a2c41754655a3733175fce81fb7506ff7022959Angus Kong        return mMosaicer.getFinalMosaicNV21();
1428a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
1438a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1448a2c41754655a3733175fce81fb7506ff7022959Angus Kong    // Processes the last filled image frame through the mosaicer and
1458a2c41754655a3733175fce81fb7506ff7022959Angus Kong    // updates the UI to show progress.
1468a2c41754655a3733175fce81fb7506ff7022959Angus Kong    // When done, processes and displays the final mosaic.
1470adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen    public void processFrame() {
1480adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen        if (!mIsMosaicMemoryAllocated) {
149142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong            // clear() is called and buffers are cleared, stop computation.
150142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong            // This can happen when the onPause() is called in the activity, but still some frames
151142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong            // are not processed yet and thus the callback may be invoked.
152142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong            return;
153142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        }
1548a2c41754655a3733175fce81fb7506ff7022959Angus Kong        long t1 = System.currentTimeMillis();
1558a2c41754655a3733175fce81fb7506ff7022959Angus Kong        mFrameTimestamp[mFillIn] = t1;
15641a2e9735136f372de95652d0828600282c8e967mbansal
1578a2c41754655a3733175fce81fb7506ff7022959Angus Kong        mCurrProcessFrameIdx = mFillIn;
1588a2c41754655a3733175fce81fb7506ff7022959Angus Kong        mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER);
1598a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1608a2c41754655a3733175fce81fb7506ff7022959Angus Kong        // Check that we are trying to process a frame different from the
1618a2c41754655a3733175fce81fb7506ff7022959Angus Kong        // last one processed (useful if this class was running asynchronously)
1628a2c41754655a3733175fce81fb7506ff7022959Angus Kong        if (mCurrProcessFrameIdx != mLastProcessFrameIdx) {
1638a2c41754655a3733175fce81fb7506ff7022959Angus Kong            mLastProcessFrameIdx = mCurrProcessFrameIdx;
1648a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1650adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen            // Access the timestamp associated with it...
1668a2c41754655a3733175fce81fb7506ff7022959Angus Kong            long timestamp = mFrameTimestamp[mCurrProcessFrameIdx];
1678a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1681f227d20aa000d0798581f451b89b1d3b2bcd349Wei-Ta Chen            // TODO: make the termination condition regarding reaching
1691f227d20aa000d0798581f451b89b1d3b2bcd349Wei-Ta Chen            // MAX_NUMBER_OF_FRAMES solely determined in the library.
170c89a0eb7b1f0cd5cd45de8fd08ff051f6f74f382Angus Kong            if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) {
1718a2c41754655a3733175fce81fb7506ff7022959Angus Kong                // If we are still collecting new frames for the current mosaic,
1728a2c41754655a3733175fce81fb7506ff7022959Angus Kong                // process the new frame.
1730adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen                calculateTranslationRate(timestamp);
1748a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1758a2c41754655a3733175fce81fb7506ff7022959Angus Kong                // Publish progress of the ongoing processing
1768a2c41754655a3733175fce81fb7506ff7022959Angus Kong                if (mProgressListener != null) {
177d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                    mProgressListener.onProgress(false, mPanningRateX, mPanningRateY,
178d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
179d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
1808a2c41754655a3733175fce81fb7506ff7022959Angus Kong                }
1818a2c41754655a3733175fce81fb7506ff7022959Angus Kong            } else {
1828a2c41754655a3733175fce81fb7506ff7022959Angus Kong                if (mProgressListener != null) {
183d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                    mProgressListener.onProgress(true, mPanningRateX, mPanningRateY,
184d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
185d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
1868a2c41754655a3733175fce81fb7506ff7022959Angus Kong                }
1878a2c41754655a3733175fce81fb7506ff7022959Angus Kong            }
1888a2c41754655a3733175fce81fb7506ff7022959Angus Kong        }
1898a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
1908a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1910adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen    public void calculateTranslationRate(long now) {
1920adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen        float[] frameData = mMosaicer.setSourceImageFromGPU();
193dd28e1cc00373c02adf88dff878dbbe5d8be9e59mbansal        int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX];
1948a2c41754655a3733175fce81fb7506ff7022959Angus Kong        mTotalFrameCount  = (int) frameData[FRAME_COUNT_INDEX];
1958a2c41754655a3733175fce81fb7506ff7022959Angus Kong        float translationCurrX = frameData[X_COORD_INDEX];
1968a2c41754655a3733175fce81fb7506ff7022959Angus Kong        float translationCurrY = frameData[Y_COORD_INDEX];
19743b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen
198e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        if (mLastProcessedFrameTimestamp == 0f) {
199e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            // First time: no need to update delta values.
200e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mTranslationLastX = translationCurrX;
201e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mTranslationLastY = translationCurrY;
202e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mLastProcessedFrameTimestamp = now;
203e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            return;
204e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        }
205e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen
206e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        // Moving average: remove the oldest translation/deltaTime and
207e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        // add the newest translation/deltaTime in
208e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        int idx = mOldestIdx;
209e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationX -= mDeltaX[idx];
210e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationY -= mDeltaY[idx];
211e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalDeltaTime -= mDeltaTime[idx];
212e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX);
213e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY);
214e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mDeltaTime[idx] = (now - mLastProcessedFrameTimestamp) / 1000.0f;
215e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationX += mDeltaX[idx];
216e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationY += mDeltaY[idx];
217e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalDeltaTime += mDeltaTime[idx];
218e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen
21943b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // The panning rate is measured as the rate of the translation percentage in
22043b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // image width/height. Take the horizontal panning rate for example, the image width
22143b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR).
22243b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // To get the horizontal translation percentage, the horizontal translation,
22343b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // (translationCurrX - mTranslationLastX), is divided by the
22443b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // image width. We then get the rate by dividing the translation percentage with deltaTime.
225d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mPanningRateX = mTotalTranslationX /
226d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / mTotalDeltaTime;
227d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mPanningRateY = mTotalTranslationY /
228d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / mTotalDeltaTime;
229d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong
230d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mTranslationLastX = translationCurrX;
231d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mTranslationLastY = translationCurrY;
232d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mLastProcessedFrameTimestamp = now;
233d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE;
2348a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
2358a2c41754655a3733175fce81fb7506ff7022959Angus Kong}
236