1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera;
18
19import android.util.Log;
20
21/**
22 * Class to handle the processing of each frame by Mosaicer.
23 */
24public class MosaicFrameProcessor {
25    private static final String TAG = "MosaicFrameProcessor";
26    private static final int NUM_FRAMES_IN_BUFFER = 2;
27    private static final int MAX_NUMBER_OF_FRAMES = 100;
28    private static final int MOSAIC_RET_CODE_INDEX = 10;
29    private static final int FRAME_COUNT_INDEX = 9;
30    private static final int X_COORD_INDEX = 2;
31    private static final int Y_COORD_INDEX = 5;
32    private static final int HR_TO_LR_DOWNSAMPLE_FACTOR = 4;
33    private static final int WINDOW_SIZE = 3;
34
35    private Mosaic mMosaicer;
36    private boolean mIsMosaicMemoryAllocated = false;
37    private float mTranslationLastX;
38    private float mTranslationLastY;
39
40    private int mFillIn = 0;
41    private int mTotalFrameCount = 0;
42    private int mLastProcessFrameIdx = -1;
43    private int mCurrProcessFrameIdx = -1;
44    private boolean mFirstRun;
45
46    // Panning rate is in unit of percentage of image content translation per
47    // frame. Use moving average to calculate the panning rate.
48    private float mPanningRateX;
49    private float mPanningRateY;
50
51    private float[] mDeltaX = new float[WINDOW_SIZE];
52    private float[] mDeltaY = new float[WINDOW_SIZE];
53    private int mOldestIdx = 0;
54    private float mTotalTranslationX = 0f;
55    private float mTotalTranslationY = 0f;
56
57    private ProgressListener mProgressListener;
58
59    private int mPreviewWidth;
60    private int mPreviewHeight;
61    private int mPreviewBufferSize;
62
63    private static MosaicFrameProcessor sMosaicFrameProcessor; // singleton
64
65    public interface ProgressListener {
66        public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
67                float progressX, float progressY);
68    }
69
70    public static MosaicFrameProcessor getInstance() {
71        if (sMosaicFrameProcessor == null) {
72            sMosaicFrameProcessor = new MosaicFrameProcessor();
73        }
74        return sMosaicFrameProcessor;
75    }
76
77    private MosaicFrameProcessor() {
78        mMosaicer = new Mosaic();
79    }
80
81    public void setProgressListener(ProgressListener listener) {
82        mProgressListener = listener;
83    }
84
85    public int reportProgress(boolean hires, boolean cancel) {
86        return mMosaicer.reportProgress(hires, cancel);
87    }
88
89    public void initialize(int previewWidth, int previewHeight, int bufSize) {
90        mPreviewWidth = previewWidth;
91        mPreviewHeight = previewHeight;
92        mPreviewBufferSize = bufSize;
93        setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize);
94        setStripType(Mosaic.STRIPTYPE_WIDE);
95        // no need to call reset() here. reset() should be called by the client
96        // after this initialization before calling other methods of this object.
97    }
98
99    public void clear() {
100        if (mIsMosaicMemoryAllocated) {
101            mMosaicer.freeMosaicMemory();
102            mIsMosaicMemoryAllocated = false;
103        }
104        synchronized (this) {
105            notify();
106        }
107    }
108
109    public boolean isMosaicMemoryAllocated() {
110        return mIsMosaicMemoryAllocated;
111    }
112
113    public void setStripType(int type) {
114        mMosaicer.setStripType(type);
115    }
116
117    private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) {
118        Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize);
119
120        if (mIsMosaicMemoryAllocated) throw new RuntimeException("MosaicFrameProcessor in use!");
121        mIsMosaicMemoryAllocated = true;
122        mMosaicer.allocateMosaicMemory(previewWidth, previewHeight);
123    }
124
125    public void reset() {
126        // reset() can be called even if MosaicFrameProcessor is not initialized.
127        // Only counters will be changed.
128        mFirstRun = true;
129        mTotalFrameCount = 0;
130        mFillIn = 0;
131        mTotalTranslationX = 0;
132        mTranslationLastX = 0;
133        mTotalTranslationY = 0;
134        mTranslationLastY = 0;
135        mPanningRateX = 0;
136        mPanningRateY = 0;
137        mLastProcessFrameIdx = -1;
138        mCurrProcessFrameIdx = -1;
139        for (int i = 0; i < WINDOW_SIZE; ++i) {
140            mDeltaX[i] = 0f;
141            mDeltaY[i] = 0f;
142        }
143        mMosaicer.reset();
144    }
145
146    public int createMosaic(boolean highRes) {
147        return mMosaicer.createMosaic(highRes);
148    }
149
150    public byte[] getFinalMosaicNV21() {
151        return mMosaicer.getFinalMosaicNV21();
152    }
153
154    // Processes the last filled image frame through the mosaicer and
155    // updates the UI to show progress.
156    // When done, processes and displays the final mosaic.
157    public void processFrame() {
158        if (!mIsMosaicMemoryAllocated) {
159            // clear() is called and buffers are cleared, stop computation.
160            // This can happen when the onPause() is called in the activity, but still some frames
161            // are not processed yet and thus the callback may be invoked.
162            return;
163        }
164
165        mCurrProcessFrameIdx = mFillIn;
166        mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER);
167
168        // Check that we are trying to process a frame different from the
169        // last one processed (useful if this class was running asynchronously)
170        if (mCurrProcessFrameIdx != mLastProcessFrameIdx) {
171            mLastProcessFrameIdx = mCurrProcessFrameIdx;
172
173            // TODO: make the termination condition regarding reaching
174            // MAX_NUMBER_OF_FRAMES solely determined in the library.
175            if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) {
176                // If we are still collecting new frames for the current mosaic,
177                // process the new frame.
178                calculateTranslationRate();
179
180                // Publish progress of the ongoing processing
181                if (mProgressListener != null) {
182                    mProgressListener.onProgress(false, mPanningRateX, mPanningRateY,
183                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
184                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
185                }
186            } else {
187                if (mProgressListener != null) {
188                    mProgressListener.onProgress(true, mPanningRateX, mPanningRateY,
189                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
190                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
191                }
192            }
193        }
194    }
195
196    public void calculateTranslationRate() {
197        float[] frameData = mMosaicer.setSourceImageFromGPU();
198        int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX];
199        mTotalFrameCount  = (int) frameData[FRAME_COUNT_INDEX];
200        float translationCurrX = frameData[X_COORD_INDEX];
201        float translationCurrY = frameData[Y_COORD_INDEX];
202
203        if (mFirstRun) {
204            // First time: no need to update delta values.
205            mTranslationLastX = translationCurrX;
206            mTranslationLastY = translationCurrY;
207            mFirstRun = false;
208            return;
209        }
210
211        // Moving average: remove the oldest translation/deltaTime and
212        // add the newest translation/deltaTime in
213        int idx = mOldestIdx;
214        mTotalTranslationX -= mDeltaX[idx];
215        mTotalTranslationY -= mDeltaY[idx];
216        mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX);
217        mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY);
218        mTotalTranslationX += mDeltaX[idx];
219        mTotalTranslationY += mDeltaY[idx];
220
221        // The panning rate is measured as the rate of the translation percentage in
222        // image width/height. Take the horizontal panning rate for example, the image width
223        // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR).
224        // To get the horizontal translation percentage, the horizontal translation,
225        // (translationCurrX - mTranslationLastX), is divided by the
226        // image width. We then get the rate by dividing the translation percentage with the
227        // number of frames.
228        mPanningRateX = mTotalTranslationX /
229                (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE;
230        mPanningRateY = mTotalTranslationY /
231                (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE;
232
233        mTranslationLastX = translationCurrX;
234        mTranslationLastY = translationCurrY;
235        mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE;
236    }
237}
238