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        reset();
96    }
97
98    public void clear() {
99        if (mIsMosaicMemoryAllocated) {
100            mMosaicer.freeMosaicMemory();
101            mIsMosaicMemoryAllocated = false;
102        }
103        synchronized (this) {
104            notify();
105        }
106    }
107
108    public boolean isMosaicMemoryAllocated() {
109        return mIsMosaicMemoryAllocated;
110    }
111
112    public void setStripType(int type) {
113        mMosaicer.setStripType(type);
114    }
115
116    private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) {
117        Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize);
118
119        if (mIsMosaicMemoryAllocated) throw new RuntimeException("MosaicFrameProcessor in use!");
120        mIsMosaicMemoryAllocated = true;
121        mMosaicer.allocateMosaicMemory(previewWidth, previewHeight);
122    }
123
124    public void reset() {
125        // reset() can be called even if MosaicFrameProcessor is not initialized.
126        // Only counters will be changed.
127        mFirstRun = true;
128        mTotalFrameCount = 0;
129        mFillIn = 0;
130        mTotalTranslationX = 0;
131        mTranslationLastX = 0;
132        mTotalTranslationY = 0;
133        mTranslationLastY = 0;
134        mPanningRateX = 0;
135        mPanningRateY = 0;
136        mLastProcessFrameIdx = -1;
137        mCurrProcessFrameIdx = -1;
138        for (int i = 0; i < WINDOW_SIZE; ++i) {
139            mDeltaX[i] = 0f;
140            mDeltaY[i] = 0f;
141        }
142        mMosaicer.reset();
143    }
144
145    public int createMosaic(boolean highRes) {
146        return mMosaicer.createMosaic(highRes);
147    }
148
149    public byte[] getFinalMosaicNV21() {
150        return mMosaicer.getFinalMosaicNV21();
151    }
152
153    // Processes the last filled image frame through the mosaicer and
154    // updates the UI to show progress.
155    // When done, processes and displays the final mosaic.
156    public void processFrame() {
157        if (!mIsMosaicMemoryAllocated) {
158            // clear() is called and buffers are cleared, stop computation.
159            // This can happen when the onPause() is called in the activity, but still some frames
160            // are not processed yet and thus the callback may be invoked.
161            return;
162        }
163
164        mCurrProcessFrameIdx = mFillIn;
165        mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER);
166
167        // Check that we are trying to process a frame different from the
168        // last one processed (useful if this class was running asynchronously)
169        if (mCurrProcessFrameIdx != mLastProcessFrameIdx) {
170            mLastProcessFrameIdx = mCurrProcessFrameIdx;
171
172            // TODO: make the termination condition regarding reaching
173            // MAX_NUMBER_OF_FRAMES solely determined in the library.
174            if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) {
175                // If we are still collecting new frames for the current mosaic,
176                // process the new frame.
177                calculateTranslationRate();
178
179                // Publish progress of the ongoing processing
180                if (mProgressListener != null) {
181                    mProgressListener.onProgress(false, mPanningRateX, mPanningRateY,
182                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
183                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
184                }
185            } else {
186                if (mProgressListener != null) {
187                    mProgressListener.onProgress(true, mPanningRateX, mPanningRateY,
188                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
189                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
190                }
191            }
192        }
193    }
194
195    public void calculateTranslationRate() {
196        float[] frameData = mMosaicer.setSourceImageFromGPU();
197        int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX];
198        mTotalFrameCount  = (int) frameData[FRAME_COUNT_INDEX];
199        float translationCurrX = frameData[X_COORD_INDEX];
200        float translationCurrY = frameData[Y_COORD_INDEX];
201
202        if (mFirstRun) {
203            // First time: no need to update delta values.
204            mTranslationLastX = translationCurrX;
205            mTranslationLastY = translationCurrY;
206            mFirstRun = false;
207            return;
208        }
209
210        // Moving average: remove the oldest translation/deltaTime and
211        // add the newest translation/deltaTime in
212        int idx = mOldestIdx;
213        mTotalTranslationX -= mDeltaX[idx];
214        mTotalTranslationY -= mDeltaY[idx];
215        mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX);
216        mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY);
217        mTotalTranslationX += mDeltaX[idx];
218        mTotalTranslationY += mDeltaY[idx];
219
220        // The panning rate is measured as the rate of the translation percentage in
221        // image width/height. Take the horizontal panning rate for example, the image width
222        // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR).
223        // To get the horizontal translation percentage, the horizontal translation,
224        // (translationCurrX - mTranslationLastX), is divided by the
225        // image width. We then get the rate by dividing the translation percentage with the
226        // number of frames.
227        mPanningRateX = mTotalTranslationX /
228                (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE;
229        mPanningRateY = mTotalTranslationY /
230                (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE;
231
232        mTranslationLastX = translationCurrX;
233        mTranslationLastY = translationCurrY;
234        mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE;
235    }
236}
237