OSDN Git Service

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