• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.camera;
18 
19 import android.annotation.TargetApi;
20 import android.graphics.SurfaceTexture;
21 import android.opengl.Matrix;
22 import android.util.Log;
23 
24 import com.android.gallery3d.common.ApiHelper;
25 import com.android.gallery3d.ui.GLCanvas;
26 import com.android.gallery3d.ui.RawTexture;
27 import com.android.gallery3d.ui.SurfaceTextureScreenNail;
28 
29 /*
30  * This is a ScreenNail which can display camera's preview.
31  */
32 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
33 public class CameraScreenNail extends SurfaceTextureScreenNail {
34     private static final String TAG = "CAM_ScreenNail";
35     private static final int ANIM_NONE = 0;
36     // Capture animation is about to start.
37     private static final int ANIM_CAPTURE_START = 1;
38     // Capture animation is running.
39     private static final int ANIM_CAPTURE_RUNNING = 2;
40     // Switch camera animation needs to copy texture.
41     private static final int ANIM_SWITCH_COPY_TEXTURE = 3;
42     // Switch camera animation shows the initial feedback by darkening the
43     // preview.
44     private static final int ANIM_SWITCH_DARK_PREVIEW = 4;
45     // Switch camera animation is waiting for the first frame.
46     private static final int ANIM_SWITCH_WAITING_FIRST_FRAME = 5;
47     // Switch camera animation is about to start.
48     private static final int ANIM_SWITCH_START = 6;
49     // Switch camera animation is running.
50     private static final int ANIM_SWITCH_RUNNING = 7;
51 
52     private boolean mVisible;
53     // True if first onFrameAvailable has been called. If screen nail is drawn
54     // too early, it will be all white.
55     private boolean mFirstFrameArrived;
56     private Listener mListener;
57     private final float[] mTextureTransformMatrix = new float[16];
58 
59     // Animation.
60     private CaptureAnimManager mCaptureAnimManager = new CaptureAnimManager();
61     private SwitchAnimManager mSwitchAnimManager = new SwitchAnimManager();
62     private int mAnimState = ANIM_NONE;
63     private RawTexture mAnimTexture;
64     // Some methods are called by GL thread and some are called by main thread.
65     // This protects mAnimState, mVisible, and surface texture. This also makes
66     // sure some code are atomic. For example, requestRender and setting
67     // mAnimState.
68     private Object mLock = new Object();
69 
70     private OnFrameDrawnListener mOneTimeFrameDrawnListener;
71     private int mRenderWidth;
72     private int mRenderHeight;
73     // This represents the scaled, uncropped size of the texture
74     // Needed for FaceView
75     private int mUncroppedRenderWidth;
76     private int mUncroppedRenderHeight;
77     private float mScaleX = 1f, mScaleY = 1f;
78     private boolean mFullScreen;
79     private boolean mEnableAspectRatioClamping = false;
80     private float mAlpha = 1f;
81     private Runnable mOnFrameDrawnListener;
82 
83     public interface Listener {
requestRender()84         void requestRender();
85         // Preview has been copied to a texture.
onPreviewTextureCopied()86         void onPreviewTextureCopied();
87 
onCaptureTextureCopied()88         void onCaptureTextureCopied();
89     }
90 
91     public interface OnFrameDrawnListener {
onFrameDrawn(CameraScreenNail c)92         void onFrameDrawn(CameraScreenNail c);
93     }
94 
CameraScreenNail(Listener listener)95     public CameraScreenNail(Listener listener) {
96         mListener = listener;
97     }
98 
setFullScreen(boolean full)99     public void setFullScreen(boolean full) {
100         synchronized (mLock) {
101             mFullScreen = full;
102         }
103     }
104 
105     /**
106      * returns the uncropped, but scaled, width of the rendered texture
107      */
getUncroppedRenderWidth()108     public int getUncroppedRenderWidth() {
109         return mUncroppedRenderWidth;
110     }
111 
112     /**
113      * returns the uncropped, but scaled, width of the rendered texture
114      */
getUncroppedRenderHeight()115     public int getUncroppedRenderHeight() {
116         return mUncroppedRenderHeight;
117     }
118 
119     @Override
getWidth()120     public int getWidth() {
121         return mEnableAspectRatioClamping ? mRenderWidth : getTextureWidth();
122     }
123 
124     @Override
getHeight()125     public int getHeight() {
126         return mEnableAspectRatioClamping ? mRenderHeight : getTextureHeight();
127     }
128 
getTextureWidth()129     private int getTextureWidth() {
130         return super.getWidth();
131     }
132 
getTextureHeight()133     private int getTextureHeight() {
134         return super.getHeight();
135     }
136 
137     @Override
setSize(int w, int h)138     public void setSize(int w, int h) {
139         super.setSize(w,  h);
140         mEnableAspectRatioClamping = false;
141         if (mRenderWidth == 0) {
142             mRenderWidth = w;
143             mRenderHeight = h;
144         }
145         updateRenderSize();
146     }
147 
148     /**
149      * Tells the ScreenNail to override the default aspect ratio scaling
150      * and instead perform custom scaling to basically do a centerCrop instead
151      * of the default centerInside
152      *
153      * Note that calls to setSize will disable this
154      */
enableAspectRatioClamping()155     public void enableAspectRatioClamping() {
156         mEnableAspectRatioClamping = true;
157         updateRenderSize();
158     }
159 
setPreviewLayoutSize(int w, int h)160     private void setPreviewLayoutSize(int w, int h) {
161         Log.i(TAG, "preview layout size: "+w+"/"+h);
162         mRenderWidth = w;
163         mRenderHeight = h;
164         updateRenderSize();
165     }
166 
updateRenderSize()167     private void updateRenderSize() {
168         if (!mEnableAspectRatioClamping) {
169             mScaleX = mScaleY = 1f;
170             mUncroppedRenderWidth = getTextureWidth();
171             mUncroppedRenderHeight = getTextureHeight();
172             Log.i(TAG, "aspect ratio clamping disabled");
173             return;
174         }
175 
176         float aspectRatio;
177         if (getTextureWidth() > getTextureHeight()) {
178             aspectRatio = (float) getTextureWidth() / (float) getTextureHeight();
179         } else {
180             aspectRatio = (float) getTextureHeight() / (float) getTextureWidth();
181         }
182         float scaledTextureWidth, scaledTextureHeight;
183         if (mRenderWidth > mRenderHeight) {
184             scaledTextureWidth = Math.max(mRenderWidth,
185                     (int) (mRenderHeight * aspectRatio));
186             scaledTextureHeight = Math.max(mRenderHeight,
187                     (int)(mRenderWidth / aspectRatio));
188         } else {
189             scaledTextureWidth = Math.max(mRenderWidth,
190                     (int) (mRenderHeight / aspectRatio));
191             scaledTextureHeight = Math.max(mRenderHeight,
192                     (int) (mRenderWidth * aspectRatio));
193         }
194         mScaleX = mRenderWidth / scaledTextureWidth;
195         mScaleY = mRenderHeight / scaledTextureHeight;
196         mUncroppedRenderWidth = Math.round(scaledTextureWidth);
197         mUncroppedRenderHeight = Math.round(scaledTextureHeight);
198         Log.i(TAG, "aspect ratio clamping enabled, surfaceTexture scale: " + mScaleX + ", " + mScaleY);
199     }
200 
201     @Override
acquireSurfaceTexture()202     public void acquireSurfaceTexture() {
203         synchronized (mLock) {
204             mFirstFrameArrived = false;
205             super.acquireSurfaceTexture();
206             mAnimTexture = new RawTexture(getTextureWidth(), getTextureHeight(), true);
207         }
208     }
209 
210     @Override
releaseSurfaceTexture()211     public void releaseSurfaceTexture() {
212         synchronized (mLock) {
213             super.releaseSurfaceTexture();
214             mAnimState = ANIM_NONE; // stop the animation
215         }
216     }
217 
copyTexture()218     public void copyTexture() {
219         synchronized (mLock) {
220             mListener.requestRender();
221             mAnimState = ANIM_SWITCH_COPY_TEXTURE;
222         }
223     }
224 
animateSwitchCamera()225     public void animateSwitchCamera() {
226         Log.v(TAG, "animateSwitchCamera");
227         synchronized (mLock) {
228             if (mAnimState == ANIM_SWITCH_DARK_PREVIEW) {
229                 // Do not request render here because camera has been just
230                 // started. We do not want to draw black frames.
231                 mAnimState = ANIM_SWITCH_WAITING_FIRST_FRAME;
232             }
233         }
234     }
235 
animateCapture(int displayRotation)236     public void animateCapture(int displayRotation) {
237         synchronized (mLock) {
238             mCaptureAnimManager.setOrientation(displayRotation);
239             mCaptureAnimManager.animateFlashAndSlide();
240             mListener.requestRender();
241             mAnimState = ANIM_CAPTURE_START;
242         }
243     }
244 
animateFlash(int displayRotation)245     public void animateFlash(int displayRotation) {
246         synchronized (mLock) {
247             mCaptureAnimManager.setOrientation(displayRotation);
248             mCaptureAnimManager.animateFlash();
249             mListener.requestRender();
250             mAnimState = ANIM_CAPTURE_START;
251         }
252     }
253 
animateSlide()254     public void animateSlide() {
255         synchronized (mLock) {
256             // Ignore the case where animateFlash is skipped but animateSlide is called
257             // e.g. Double tap shutter and immediately swipe to gallery, and quickly swipe back
258             // to camera. This case only happens in monkey tests, not applicable to normal
259             // human beings.
260             if (mAnimState != ANIM_CAPTURE_RUNNING) {
261                 Log.v(TAG, "Cannot animateSlide outside of animateCapture!"
262                         + " Animation state = " + mAnimState);
263                 return;
264             }
265             mCaptureAnimManager.animateSlide();
266             mListener.requestRender();
267         }
268     }
269 
callbackIfNeeded()270     private void callbackIfNeeded() {
271         if (mOneTimeFrameDrawnListener != null) {
272             mOneTimeFrameDrawnListener.onFrameDrawn(this);
273             mOneTimeFrameDrawnListener = null;
274         }
275     }
276 
277     @Override
updateTransformMatrix(float[] matrix)278     protected void updateTransformMatrix(float[] matrix) {
279         super.updateTransformMatrix(matrix);
280         Matrix.translateM(matrix, 0, .5f, .5f, 0);
281         Matrix.scaleM(matrix, 0, mScaleX, mScaleY, 1f);
282         Matrix.translateM(matrix, 0, -.5f, -.5f, 0);
283     }
284 
directDraw(GLCanvas canvas, int x, int y, int width, int height)285     public void directDraw(GLCanvas canvas, int x, int y, int width, int height) {
286         super.draw(canvas, x, y, width, height);
287     }
288 
289     @Override
draw(GLCanvas canvas, int x, int y, int width, int height)290     public void draw(GLCanvas canvas, int x, int y, int width, int height) {
291         synchronized (mLock) {
292             if (!mVisible) mVisible = true;
293             SurfaceTexture surfaceTexture = getSurfaceTexture();
294             if (surfaceTexture == null || !mFirstFrameArrived) return;
295             if (mOnFrameDrawnListener != null) {
296                 mOnFrameDrawnListener.run();
297                 mOnFrameDrawnListener = null;
298             }
299             float oldAlpha = canvas.getAlpha();
300             canvas.setAlpha(mAlpha);
301 
302             switch (mAnimState) {
303                 case ANIM_NONE:
304                     super.draw(canvas, x, y, width, height);
305                     break;
306                 case ANIM_SWITCH_COPY_TEXTURE:
307                     copyPreviewTexture(canvas);
308                     mSwitchAnimManager.setReviewDrawingSize(width, height);
309                     mListener.onPreviewTextureCopied();
310                     mAnimState = ANIM_SWITCH_DARK_PREVIEW;
311                     // The texture is ready. Fall through to draw darkened
312                     // preview.
313                 case ANIM_SWITCH_DARK_PREVIEW:
314                 case ANIM_SWITCH_WAITING_FIRST_FRAME:
315                     // Consume the frame. If the buffers are full,
316                     // onFrameAvailable will not be called. Animation state
317                     // relies on onFrameAvailable.
318                     surfaceTexture.updateTexImage();
319                     mSwitchAnimManager.drawDarkPreview(canvas, x, y, width,
320                             height, mAnimTexture);
321                     break;
322                 case ANIM_SWITCH_START:
323                     mSwitchAnimManager.startAnimation();
324                     mAnimState = ANIM_SWITCH_RUNNING;
325                     break;
326                 case ANIM_CAPTURE_START:
327                     copyPreviewTexture(canvas);
328                     mListener.onCaptureTextureCopied();
329                     mCaptureAnimManager.startAnimation(x, y, width, height);
330                     mAnimState = ANIM_CAPTURE_RUNNING;
331                     break;
332             }
333 
334             if (mAnimState == ANIM_CAPTURE_RUNNING || mAnimState == ANIM_SWITCH_RUNNING) {
335                 boolean drawn;
336                 if (mAnimState == ANIM_CAPTURE_RUNNING) {
337                     if (!mFullScreen) {
338                         // Skip the animation if no longer in full screen mode
339                         drawn = false;
340                     } else {
341                         drawn = mCaptureAnimManager.drawAnimation(canvas, this, mAnimTexture);
342                     }
343                 } else {
344                     drawn = mSwitchAnimManager.drawAnimation(canvas, x, y,
345                             width, height, this, mAnimTexture);
346                 }
347                 if (drawn) {
348                     mListener.requestRender();
349                 } else {
350                     // Continue to the normal draw procedure if the animation is
351                     // not drawn.
352                     mAnimState = ANIM_NONE;
353                     super.draw(canvas, x, y, width, height);
354                 }
355             }
356             canvas.setAlpha(oldAlpha);
357             callbackIfNeeded();
358         } // mLock
359     }
360 
copyPreviewTexture(GLCanvas canvas)361     private void copyPreviewTexture(GLCanvas canvas) {
362         int width = mAnimTexture.getWidth();
363         int height = mAnimTexture.getHeight();
364         canvas.beginRenderTarget(mAnimTexture);
365         // Flip preview texture vertically. OpenGL uses bottom left point
366         // as the origin (0, 0).
367         canvas.translate(0, height);
368         canvas.scale(1, -1, 1);
369         getSurfaceTexture().getTransformMatrix(mTextureTransformMatrix);
370         updateTransformMatrix(mTextureTransformMatrix);
371         canvas.drawTexture(mExtTexture,
372                 mTextureTransformMatrix, 0, 0, width, height);
373         canvas.endRenderTarget();
374     }
375 
376     @Override
noDraw()377     public void noDraw() {
378         synchronized (mLock) {
379             mVisible = false;
380         }
381     }
382 
383     @Override
recycle()384     public void recycle() {
385         synchronized (mLock) {
386             mVisible = false;
387         }
388     }
389 
390     @Override
onFrameAvailable(SurfaceTexture surfaceTexture)391     public void onFrameAvailable(SurfaceTexture surfaceTexture) {
392         synchronized (mLock) {
393             if (getSurfaceTexture() != surfaceTexture) {
394                 return;
395             }
396             mFirstFrameArrived = true;
397             if (mVisible) {
398                 if (mAnimState == ANIM_SWITCH_WAITING_FIRST_FRAME) {
399                     mAnimState = ANIM_SWITCH_START;
400                 }
401                 // We need to ask for re-render if the SurfaceTexture receives a new
402                 // frame.
403                 mListener.requestRender();
404             }
405         }
406     }
407 
408     // We need to keep track of the size of preview frame on the screen because
409     // it's needed when we do switch-camera animation. See comments in
410     // SwitchAnimManager.java. This is based on the natural orientation, not the
411     // view system orientation.
setPreviewFrameLayoutSize(int width, int height)412     public void setPreviewFrameLayoutSize(int width, int height) {
413         synchronized (mLock) {
414             mSwitchAnimManager.setPreviewFrameLayoutSize(width, height);
415             setPreviewLayoutSize(width, height);
416         }
417     }
418 
setOneTimeOnFrameDrawnListener(OnFrameDrawnListener l)419     public void setOneTimeOnFrameDrawnListener(OnFrameDrawnListener l) {
420         synchronized (mLock) {
421             mFirstFrameArrived = false;
422             mOneTimeFrameDrawnListener = l;
423         }
424     }
425 
setOnFrameDrawnOneShot(Runnable run)426     public void setOnFrameDrawnOneShot(Runnable run) {
427         synchronized (mLock) {
428             mOnFrameDrawnListener = run;
429         }
430     }
431 
getAlpha()432     public float getAlpha() {
433         synchronized (mLock) {
434             return mAlpha;
435         }
436     }
437 
setAlpha(float alpha)438     public void setAlpha(float alpha) {
439         synchronized (mLock) {
440             mAlpha = alpha;
441             mListener.requestRender();
442         }
443     }
444 }
445