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() {
947d9eadd0c6c38b3761b7e8d3fa3658d194810d60Jim Miller        if (mIsMosaicMemoryAllocated) {
957d9eadd0c6c38b3761b7e8d3fa3658d194810d60Jim Miller            mIsMosaicMemoryAllocated = false;
967d9eadd0c6c38b3761b7e8d3fa3658d194810d60Jim Miller            mMosaicer.freeMosaicMemory();
977d9eadd0c6c38b3761b7e8d3fa3658d194810d60Jim Miller        }
9894f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen    }
9994f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen
100b28b9c0fa991bc97e8aa11da83d27f71fdfef6dambansal    public void setStripType(int type) {
101b28b9c0fa991bc97e8aa11da83d27f71fdfef6dambansal        mMosaicer.setStripType(type);
102b28b9c0fa991bc97e8aa11da83d27f71fdfef6dambansal    }
103b28b9c0fa991bc97e8aa11da83d27f71fdfef6dambansal
1040adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen    private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) {
1058a2c41754655a3733175fce81fb7506ff7022959Angus Kong        Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize);
10694f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        mMosaicer.allocateMosaicMemory(previewWidth, previewHeight);
1070adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen        mIsMosaicMemoryAllocated = true;
10894f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen
10994f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        mFillIn = 0;
11094f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        if  (mMosaicer != null) {
11194f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen            mMosaicer.reset();
11294f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen        }
11394f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen    }
11494f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen
115142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong    public void reset() {
116142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        // reset() can be called even if MosaicFrameProcessor is not initialized.
117142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        // Only counters will be changed.
118142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mTotalFrameCount = 0;
119142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mFillIn = 0;
120142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mLastProcessedFrameTimestamp = 0;
121e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationX = 0;
122e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTranslationLastX = 0;
123e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationY = 0;
124e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTranslationLastY = 0;
125e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalDeltaTime = 0;
126e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mPanningRateX = 0;
127e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mPanningRateY = 0;
128142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mLastProcessFrameIdx = -1;
129142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mCurrProcessFrameIdx = -1;
130e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        for (int i = 0; i < WINDOW_SIZE; ++i) {
131e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mDeltaX[i] = 0f;
132e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mDeltaY[i] = 0f;
133e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mDeltaTime[i] = 0f;
134e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        }
135142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        mMosaicer.reset();
13694f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen    }
13794f592fc405ca45b8794007cd9083c3250924b50Wei-Ta Chen
138e1178a73fd5756771d25d0b8375452450f509e99mbansal    public int createMosaic(boolean highRes) {
139e1178a73fd5756771d25d0b8375452450f509e99mbansal        return mMosaicer.createMosaic(highRes);
1408a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
1418a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1428a2c41754655a3733175fce81fb7506ff7022959Angus Kong    public byte[] getFinalMosaicNV21() {
1438a2c41754655a3733175fce81fb7506ff7022959Angus Kong        return mMosaicer.getFinalMosaicNV21();
1448a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
1458a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1468a2c41754655a3733175fce81fb7506ff7022959Angus Kong    // Processes the last filled image frame through the mosaicer and
1478a2c41754655a3733175fce81fb7506ff7022959Angus Kong    // updates the UI to show progress.
1488a2c41754655a3733175fce81fb7506ff7022959Angus Kong    // When done, processes and displays the final mosaic.
1490adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen    public void processFrame() {
1500adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen        if (!mIsMosaicMemoryAllocated) {
151142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong            // clear() is called and buffers are cleared, stop computation.
152142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong            // This can happen when the onPause() is called in the activity, but still some frames
153142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong            // are not processed yet and thus the callback may be invoked.
154142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong            return;
155142402d57c1689c1342d096c976b9b0826f8ce1aAngus Kong        }
1568a2c41754655a3733175fce81fb7506ff7022959Angus Kong        long t1 = System.currentTimeMillis();
1578a2c41754655a3733175fce81fb7506ff7022959Angus Kong        mFrameTimestamp[mFillIn] = t1;
15841a2e9735136f372de95652d0828600282c8e967mbansal
1598a2c41754655a3733175fce81fb7506ff7022959Angus Kong        mCurrProcessFrameIdx = mFillIn;
1608a2c41754655a3733175fce81fb7506ff7022959Angus Kong        mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER);
1618a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1628a2c41754655a3733175fce81fb7506ff7022959Angus Kong        // Check that we are trying to process a frame different from the
1638a2c41754655a3733175fce81fb7506ff7022959Angus Kong        // last one processed (useful if this class was running asynchronously)
1648a2c41754655a3733175fce81fb7506ff7022959Angus Kong        if (mCurrProcessFrameIdx != mLastProcessFrameIdx) {
1658a2c41754655a3733175fce81fb7506ff7022959Angus Kong            mLastProcessFrameIdx = mCurrProcessFrameIdx;
1668a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1670adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen            // Access the timestamp associated with it...
1688a2c41754655a3733175fce81fb7506ff7022959Angus Kong            long timestamp = mFrameTimestamp[mCurrProcessFrameIdx];
1698a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1701f227d20aa000d0798581f451b89b1d3b2bcd349Wei-Ta Chen            // TODO: make the termination condition regarding reaching
1711f227d20aa000d0798581f451b89b1d3b2bcd349Wei-Ta Chen            // MAX_NUMBER_OF_FRAMES solely determined in the library.
172c89a0eb7b1f0cd5cd45de8fd08ff051f6f74f382Angus Kong            if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) {
1738a2c41754655a3733175fce81fb7506ff7022959Angus Kong                // If we are still collecting new frames for the current mosaic,
1748a2c41754655a3733175fce81fb7506ff7022959Angus Kong                // process the new frame.
1750adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen                calculateTranslationRate(timestamp);
1768a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1778a2c41754655a3733175fce81fb7506ff7022959Angus Kong                // Publish progress of the ongoing processing
1788a2c41754655a3733175fce81fb7506ff7022959Angus Kong                if (mProgressListener != null) {
179d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                    mProgressListener.onProgress(false, mPanningRateX, mPanningRateY,
180d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
181d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
1828a2c41754655a3733175fce81fb7506ff7022959Angus Kong                }
1838a2c41754655a3733175fce81fb7506ff7022959Angus Kong            } else {
1848a2c41754655a3733175fce81fb7506ff7022959Angus Kong                if (mProgressListener != null) {
185d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                    mProgressListener.onProgress(true, mPanningRateX, mPanningRateY,
186d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
187d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
1888a2c41754655a3733175fce81fb7506ff7022959Angus Kong                }
1898a2c41754655a3733175fce81fb7506ff7022959Angus Kong            }
1908a2c41754655a3733175fce81fb7506ff7022959Angus Kong        }
1918a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
1928a2c41754655a3733175fce81fb7506ff7022959Angus Kong
1930adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen    public void calculateTranslationRate(long now) {
1940adf2489520d3a98a56e081aeacb1ab9336a012fWei-Ta Chen        float[] frameData = mMosaicer.setSourceImageFromGPU();
195dd28e1cc00373c02adf88dff878dbbe5d8be9e59mbansal        int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX];
1968a2c41754655a3733175fce81fb7506ff7022959Angus Kong        mTotalFrameCount  = (int) frameData[FRAME_COUNT_INDEX];
1978a2c41754655a3733175fce81fb7506ff7022959Angus Kong        float translationCurrX = frameData[X_COORD_INDEX];
1988a2c41754655a3733175fce81fb7506ff7022959Angus Kong        float translationCurrY = frameData[Y_COORD_INDEX];
19943b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen
200e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        if (mLastProcessedFrameTimestamp == 0f) {
201e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            // First time: no need to update delta values.
202e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mTranslationLastX = translationCurrX;
203e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mTranslationLastY = translationCurrY;
204e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            mLastProcessedFrameTimestamp = now;
205e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen            return;
206e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        }
207e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen
208e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        // Moving average: remove the oldest translation/deltaTime and
209e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        // add the newest translation/deltaTime in
210e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        int idx = mOldestIdx;
211e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationX -= mDeltaX[idx];
212e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationY -= mDeltaY[idx];
213e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalDeltaTime -= mDeltaTime[idx];
214e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX);
215e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY);
216e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mDeltaTime[idx] = (now - mLastProcessedFrameTimestamp) / 1000.0f;
217e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationX += mDeltaX[idx];
218e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalTranslationY += mDeltaY[idx];
219e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen        mTotalDeltaTime += mDeltaTime[idx];
220e47b5624a1aa3cc7bd9763852bed4ac3215a77b2Wei-Ta Chen
22143b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // The panning rate is measured as the rate of the translation percentage in
22243b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // image width/height. Take the horizontal panning rate for example, the image width
22343b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR).
22443b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // To get the horizontal translation percentage, the horizontal translation,
22543b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // (translationCurrX - mTranslationLastX), is divided by the
22643b0b2ccd5949ee8fc377c37e7e2b82a26c88ca8Wei-Ta Chen        // image width. We then get the rate by dividing the translation percentage with deltaTime.
227d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mPanningRateX = mTotalTranslationX /
228d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / mTotalDeltaTime;
229d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mPanningRateY = mTotalTranslationY /
230d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong                (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / mTotalDeltaTime;
231d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong
232d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mTranslationLastX = translationCurrX;
233d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mTranslationLastY = translationCurrY;
234d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mLastProcessedFrameTimestamp = now;
235d991766dc3bcc03a02c6648e2cfd54ee4f8cbd9eAngus Kong        mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE;
2368a2c41754655a3733175fce81fb7506ff7022959Angus Kong    }
2378a2c41754655a3733175fce81fb7506ff7022959Angus Kong}
238