CameraScreenNail.java revision 6342be380a34976c828d21c2a3c172630123846a
1/*
2 * Copyright (C) 2012 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.annotation.TargetApi;
20import android.graphics.SurfaceTexture;
21import android.util.Log;
22
23import com.android.gallery3d.common.ApiHelper;
24import com.android.gallery3d.ui.GLCanvas;
25import com.android.gallery3d.ui.RawTexture;
26import com.android.gallery3d.ui.SurfaceTextureScreenNail;
27
28/*
29 * This is a ScreenNail which can display camera's preview.
30 */
31@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
32public class CameraScreenNail extends SurfaceTextureScreenNail {
33    private static final String TAG = "CAM_ScreenNail";
34    private static final int ANIM_NONE = 0;
35    // Capture animation is about to start.
36    private static final int ANIM_CAPTURE_START = 1;
37    // Capture animation is running.
38    private static final int ANIM_CAPTURE_RUNNING = 2;
39    // Switch camera animation needs to copy texture.
40    private static final int ANIM_SWITCH_COPY_TEXTURE = 3;
41    // Switch camera animation shows the initial feedback by darkening the
42    // preview.
43    private static final int ANIM_SWITCH_DARK_PREVIEW = 4;
44    // Switch camera animation is waiting for the first frame.
45    private static final int ANIM_SWITCH_WAITING_FIRST_FRAME = 5;
46    // Switch camera animation is about to start.
47    private static final int ANIM_SWITCH_START = 6;
48    // Switch camera animation is running.
49    private static final int ANIM_SWITCH_RUNNING = 7;
50
51    private boolean mVisible;
52    // True if first onFrameAvailable has been called. If screen nail is drawn
53    // too early, it will be all white.
54    private boolean mFirstFrameArrived;
55    private Listener mListener;
56    private final float[] mTextureTransformMatrix = new float[16];
57
58    // Animation.
59    private CaptureAnimManager mCaptureAnimManager = new CaptureAnimManager();
60    private SwitchAnimManager mSwitchAnimManager = new SwitchAnimManager();
61    private int mAnimState = ANIM_NONE;
62    private RawTexture mAnimTexture;
63    // Some methods are called by GL thread and some are called by main thread.
64    // This protects mAnimState, mVisible, and surface texture. This also makes
65    // sure some code are atomic. For example, requestRender and setting
66    // mAnimState.
67    private Object mLock = new Object();
68
69    private OnFrameDrawnListener mOneTimeFrameDrawnListener;
70    private float mAspectRatio;
71    private int mRenderWidth;
72    private int mRenderHeight;
73    private int mPreviewWidth;
74    private int mPreviewHeight;
75    private boolean mFullScreen;
76    private int mRotation;
77
78    public interface Listener {
79        void requestRender();
80        // Preview has been copied to a texture.
81        void onPreviewTextureCopied();
82
83        void onCaptureTextureCopied();
84    }
85
86    public interface OnFrameDrawnListener {
87        void onFrameDrawn(CameraScreenNail c);
88    }
89
90    public CameraScreenNail(Listener listener) {
91        mListener = listener;
92    }
93
94    public void setFullScreen(boolean full) {
95        mFullScreen = full;
96    }
97
98    @Override
99    public void setSize(int w, int h) {
100        super.setSize(w,  h);
101        if (w > h) {
102            mAspectRatio  = (float) w / h;
103        } else {
104            mAspectRatio = (float) h / w;
105        }
106        updateRenderSize();
107    }
108
109    private void setPreviewLayoutSize(int w, int h) {
110        mPreviewWidth = w;
111        mPreviewHeight = h;
112        updateRenderSize();
113    }
114
115    private void updateRenderSize() {
116        // these callbacks above come at different times,
117        // so make sure we have all the data
118        if (mPreviewWidth != 0 && mAspectRatio > 0) {
119            if (mPreviewWidth > mPreviewHeight) {
120                mRenderWidth = Math.max(mPreviewWidth,
121                        (int) (mPreviewHeight * mAspectRatio));
122                mRenderHeight = Math.max(mPreviewHeight,
123                        (int)(mPreviewWidth / mAspectRatio));
124            } else {
125                mRenderWidth = Math.max(mPreviewWidth,
126                        (int) (mPreviewHeight / mAspectRatio));
127                mRenderHeight = Math.max(mPreviewHeight,
128                        (int) (mPreviewWidth * mAspectRatio));
129            }
130        }
131    }
132
133    @Override
134    public void acquireSurfaceTexture() {
135        synchronized (mLock) {
136            mFirstFrameArrived = false;
137            super.acquireSurfaceTexture();
138            mAnimTexture = new RawTexture(getWidth(), getHeight(), true);
139        }
140    }
141
142    @Override
143    public void releaseSurfaceTexture() {
144        synchronized (mLock) {
145            super.releaseSurfaceTexture();
146            mAnimState = ANIM_NONE; // stop the animation
147            mRotation = 0;
148        }
149    }
150
151    public void copyTexture() {
152        synchronized (mLock) {
153            mListener.requestRender();
154            mAnimState = ANIM_SWITCH_COPY_TEXTURE;
155        }
156    }
157
158    public void animateSwitchCamera() {
159        Log.v(TAG, "animateSwitchCamera");
160        synchronized (mLock) {
161            if (mAnimState == ANIM_SWITCH_DARK_PREVIEW) {
162                // Do not request render here because camera has been just
163                // started. We do not want to draw black frames.
164                mAnimState = ANIM_SWITCH_WAITING_FIRST_FRAME;
165            }
166        }
167    }
168
169    public void animateCapture(int animOrientation) {
170        synchronized (mLock) {
171            mCaptureAnimManager.setOrientation(animOrientation);
172            mListener.requestRender();
173            mAnimState = ANIM_CAPTURE_START;
174        }
175    }
176
177    public void setInitialOrientation(int initialOrientation) {
178        mRotation = initialOrientation;
179    }
180
181    private void callbackIfNeeded() {
182        if (mOneTimeFrameDrawnListener != null) {
183            mOneTimeFrameDrawnListener.onFrameDrawn(this);
184            mOneTimeFrameDrawnListener = null;
185        }
186    }
187
188    public void directDraw(GLCanvas canvas, int x, int y, int width, int height) {
189        if (mRotation != 0) {
190            canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
191            canvas.rotate(mRotation, 0f, 0f, 1.0f);
192        }
193        super.draw(canvas, x, y, width, height);
194        if (mRotation != 0) {
195            canvas.restore();
196        }
197    }
198
199    @Override
200    public void draw(GLCanvas canvas, int x, int y, int width, int height) {
201        synchronized (mLock) {
202            if (!mVisible) mVisible = true;
203            SurfaceTexture surfaceTexture = getSurfaceTexture();
204            if (surfaceTexture == null || !mFirstFrameArrived) return;
205
206            switch (mAnimState) {
207                case ANIM_NONE:
208                    if (mFullScreen && (mRenderWidth != 0)) {
209                        // overscale image to make it fullscreen
210                        x = (x + width / 2) - mRenderWidth / 2;
211                        y = (y + height / 2) - mRenderHeight / 2;
212                        width = mRenderWidth;
213                        height = mRenderHeight;
214                    }
215                    super.draw(canvas, x, y, width, height);
216                    break;
217                case ANIM_SWITCH_COPY_TEXTURE:
218                    copyPreviewTexture(canvas);
219                    mSwitchAnimManager.setReviewDrawingSize(width, height);
220                    mListener.onPreviewTextureCopied();
221                    mAnimState = ANIM_SWITCH_DARK_PREVIEW;
222                    // The texture is ready. Fall through to draw darkened
223                    // preview.
224                case ANIM_SWITCH_DARK_PREVIEW:
225                case ANIM_SWITCH_WAITING_FIRST_FRAME:
226                    // Consume the frame. If the buffers are full,
227                    // onFrameAvailable will not be called. Animation state
228                    // relies on onFrameAvailable.
229                    surfaceTexture.updateTexImage();
230                    if (mRenderWidth != 0) {
231                        // overscale image to make it fullscreen
232                        x = (x + width / 2) - mRenderWidth / 2;
233                        y = (y + height / 2) - mRenderHeight / 2;
234                        width = mRenderWidth;
235                        height = mRenderHeight;
236                    }
237                    mSwitchAnimManager.drawDarkPreview(canvas, x, y, width,
238                            height, mAnimTexture);
239                    break;
240                case ANIM_SWITCH_START:
241                    mSwitchAnimManager.startAnimation();
242                    mAnimState = ANIM_SWITCH_RUNNING;
243                    break;
244                case ANIM_CAPTURE_START:
245                    copyPreviewTexture(canvas);
246                    mListener.onCaptureTextureCopied();
247                    if (mRenderWidth > 0) {
248                        // overscale image to make it fullscreen
249                        x = (x + width / 2) - mRenderWidth / 2;
250                        y = (y + height / 2) - mRenderHeight / 2;
251                        width = mRenderWidth;
252                        height = mRenderHeight;
253                    }
254                    mCaptureAnimManager.startAnimation(x, y, width, height);
255                    mAnimState = ANIM_CAPTURE_RUNNING;
256                    break;
257            }
258
259            if (mAnimState == ANIM_CAPTURE_RUNNING || mAnimState == ANIM_SWITCH_RUNNING) {
260                boolean drawn;
261                if (mAnimState == ANIM_CAPTURE_RUNNING) {
262                    drawn = mCaptureAnimManager.drawAnimation(canvas, this, mAnimTexture);
263                } else {
264                    if (mRenderWidth != 0) {
265                        // overscale image to make it fullscreen
266                        x = (x + width / 2) - mRenderWidth / 2;
267                        y = (y + height / 2) - mRenderHeight / 2;
268                        width = mRenderWidth;
269                        height = mRenderHeight;
270                    }
271                    drawn = mSwitchAnimManager.drawAnimation(canvas, x, y,
272                            width, height, this, mAnimTexture);
273                }
274                if (drawn) {
275                    mListener.requestRender();
276                } else {
277                    // Continue to the normal draw procedure if the animation is
278                    // not drawn.
279                    mAnimState = ANIM_NONE;
280                    if (mRenderWidth != 0) {
281                        // overscale image to make it fullscreen
282                        x = (x + width / 2) - mRenderWidth / 2;
283                        y = (y + height / 2) - mRenderHeight / 2;
284                        width = mRenderWidth;
285                        height = mRenderHeight;
286                    }
287                    super.draw(canvas, x, y, width, height);
288                }
289            }
290            callbackIfNeeded();
291        } // mLock
292    }
293
294    private void copyPreviewTexture(GLCanvas canvas) {
295        int width = mAnimTexture.getWidth();
296        int height = mAnimTexture.getHeight();
297        canvas.beginRenderTarget(mAnimTexture);
298        // Flip preview texture vertically. OpenGL uses bottom left point
299        // as the origin (0, 0).
300        canvas.translate(0, height);
301        canvas.scale(1, -1, 1);
302        getSurfaceTexture().getTransformMatrix(mTextureTransformMatrix);
303        canvas.drawTexture(mExtTexture,
304                mTextureTransformMatrix, 0, 0, width, height);
305        canvas.endRenderTarget();
306    }
307
308    @Override
309    public void noDraw() {
310        synchronized (mLock) {
311            mVisible = false;
312        }
313    }
314
315    @Override
316    public void recycle() {
317        synchronized (mLock) {
318            mVisible = false;
319        }
320    }
321
322    @Override
323    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
324        synchronized (mLock) {
325            if (getSurfaceTexture() != surfaceTexture) {
326                return;
327            }
328            mFirstFrameArrived = true;
329            if (mVisible) {
330                if (mAnimState == ANIM_SWITCH_WAITING_FIRST_FRAME) {
331                    mAnimState = ANIM_SWITCH_START;
332                }
333                // We need to ask for re-render if the SurfaceTexture receives a new
334                // frame.
335                mListener.requestRender();
336            }
337        }
338    }
339
340    // We need to keep track of the size of preview frame on the screen because
341    // it's needed when we do switch-camera animation. See comments in
342    // SwitchAnimManager.java. This is based on the natural orientation, not the
343    // view system orientation.
344    public void setPreviewFrameLayoutSize(int width, int height) {
345        synchronized (mLock) {
346            mSwitchAnimManager.setPreviewFrameLayoutSize(width, height);
347            setPreviewLayoutSize(width, height);
348        }
349    }
350
351    public void setOneTimeOnFrameDrawnListener(OnFrameDrawnListener l) {
352        synchronized (mLock) {
353            mFirstFrameArrived = false;
354            mOneTimeFrameDrawnListener = l;
355        }
356    }
357}
358