Home | History | Annotate | Download | only in camera
      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.glrenderer.GLCanvas;
     26 import com.android.gallery3d.glrenderer.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 boolean mAcquireTexture = false;
     81     private final DrawClient mDefaultDraw = new DrawClient() {
     82         @Override
     83         public void onDraw(GLCanvas canvas, int x, int y, int width, int height) {
     84             CameraScreenNail.super.draw(canvas, x, y, width, height);
     85         }
     86 
     87         @Override
     88         public boolean requiresSurfaceTexture() {
     89             return true;
     90         }
     91     };
     92     private DrawClient mDraw = mDefaultDraw;
     93 
     94     public interface Listener {
     95         void requestRender();
     96         // Preview has been copied to a texture.
     97         void onPreviewTextureCopied();
     98 
     99         void onCaptureTextureCopied();
    100     }
    101 
    102     public interface OnFrameDrawnListener {
    103         void onFrameDrawn(CameraScreenNail c);
    104     }
    105 
    106     public interface DrawClient {
    107         void onDraw(GLCanvas canvas, int x, int y, int width, int height);
    108 
    109         boolean requiresSurfaceTexture();
    110     }
    111 
    112     public CameraScreenNail(Listener listener) {
    113         mListener = listener;
    114     }
    115 
    116     public void setFullScreen(boolean full) {
    117         synchronized (mLock) {
    118             mFullScreen = full;
    119         }
    120     }
    121 
    122     /**
    123      * returns the uncropped, but scaled, width of the rendered texture
    124      */
    125     public int getUncroppedRenderWidth() {
    126         return mUncroppedRenderWidth;
    127     }
    128 
    129     /**
    130      * returns the uncropped, but scaled, width of the rendered texture
    131      */
    132     public int getUncroppedRenderHeight() {
    133         return mUncroppedRenderHeight;
    134     }
    135 
    136     @Override
    137     public int getWidth() {
    138         return mEnableAspectRatioClamping ? mRenderWidth : getTextureWidth();
    139     }
    140 
    141     @Override
    142     public int getHeight() {
    143         return mEnableAspectRatioClamping ? mRenderHeight : getTextureHeight();
    144     }
    145 
    146     private int getTextureWidth() {
    147         return super.getWidth();
    148     }
    149 
    150     private int getTextureHeight() {
    151         return super.getHeight();
    152     }
    153 
    154     @Override
    155     public void setSize(int w, int h) {
    156         super.setSize(w,  h);
    157         mEnableAspectRatioClamping = false;
    158         if (mRenderWidth == 0) {
    159             mRenderWidth = w;
    160             mRenderHeight = h;
    161         }
    162         updateRenderSize();
    163     }
    164 
    165     /**
    166      * Tells the ScreenNail to override the default aspect ratio scaling
    167      * and instead perform custom scaling to basically do a centerCrop instead
    168      * of the default centerInside
    169      *
    170      * Note that calls to setSize will disable this
    171      */
    172     public void enableAspectRatioClamping() {
    173         mEnableAspectRatioClamping = true;
    174         updateRenderSize();
    175     }
    176 
    177     private void setPreviewLayoutSize(int w, int h) {
    178         Log.i(TAG, "preview layout size: "+w+"/"+h);
    179         mRenderWidth = w;
    180         mRenderHeight = h;
    181         updateRenderSize();
    182     }
    183 
    184     private void updateRenderSize() {
    185         if (!mEnableAspectRatioClamping) {
    186             mScaleX = mScaleY = 1f;
    187             mUncroppedRenderWidth = getTextureWidth();
    188             mUncroppedRenderHeight = getTextureHeight();
    189             Log.i(TAG, "aspect ratio clamping disabled");
    190             return;
    191         }
    192 
    193         float aspectRatio;
    194         if (getTextureWidth() > getTextureHeight()) {
    195             aspectRatio = (float) getTextureWidth() / (float) getTextureHeight();
    196         } else {
    197             aspectRatio = (float) getTextureHeight() / (float) getTextureWidth();
    198         }
    199         float scaledTextureWidth, scaledTextureHeight;
    200         if (mRenderWidth > mRenderHeight) {
    201             scaledTextureWidth = Math.max(mRenderWidth,
    202                     (int) (mRenderHeight * aspectRatio));
    203             scaledTextureHeight = Math.max(mRenderHeight,
    204                     (int)(mRenderWidth / aspectRatio));
    205         } else {
    206             scaledTextureWidth = Math.max(mRenderWidth,
    207                     (int) (mRenderHeight / aspectRatio));
    208             scaledTextureHeight = Math.max(mRenderHeight,
    209                     (int) (mRenderWidth * aspectRatio));
    210         }
    211         mScaleX = mRenderWidth / scaledTextureWidth;
    212         mScaleY = mRenderHeight / scaledTextureHeight;
    213         mUncroppedRenderWidth = Math.round(scaledTextureWidth);
    214         mUncroppedRenderHeight = Math.round(scaledTextureHeight);
    215         Log.i(TAG, "aspect ratio clamping enabled, surfaceTexture scale: " + mScaleX + ", " + mScaleY);
    216     }
    217 
    218     public void acquireSurfaceTexture() {
    219         synchronized (mLock) {
    220             mFirstFrameArrived = false;
    221             mAnimTexture = new RawTexture(getTextureWidth(), getTextureHeight(), true);
    222             mAcquireTexture = true;
    223         }
    224         mListener.requestRender();
    225     }
    226 
    227     @Override
    228     public void releaseSurfaceTexture() {
    229         synchronized (mLock) {
    230             if (mAcquireTexture) {
    231                 mAcquireTexture = false;
    232                 mLock.notifyAll();
    233             } else {
    234                 if (super.getSurfaceTexture() != null) {
    235                     super.releaseSurfaceTexture();
    236                 }
    237                 mAnimState = ANIM_NONE; // stop the animation
    238             }
    239         }
    240     }
    241 
    242     public void copyTexture() {
    243         synchronized (mLock) {
    244             mListener.requestRender();
    245             mAnimState = ANIM_SWITCH_COPY_TEXTURE;
    246         }
    247     }
    248 
    249     public void animateSwitchCamera() {
    250         Log.v(TAG, "animateSwitchCamera");
    251         synchronized (mLock) {
    252             if (mAnimState == ANIM_SWITCH_DARK_PREVIEW) {
    253                 // Do not request render here because camera has been just
    254                 // started. We do not want to draw black frames.
    255                 mAnimState = ANIM_SWITCH_WAITING_FIRST_FRAME;
    256             }
    257         }
    258     }
    259 
    260     public void animateCapture(int displayRotation) {
    261         synchronized (mLock) {
    262             mCaptureAnimManager.setOrientation(displayRotation);
    263             mCaptureAnimManager.animateFlashAndSlide();
    264             mListener.requestRender();
    265             mAnimState = ANIM_CAPTURE_START;
    266         }
    267     }
    268 
    269     public RawTexture getAnimationTexture() {
    270         return mAnimTexture;
    271     }
    272 
    273     public void animateFlash(int displayRotation) {
    274         synchronized (mLock) {
    275             mCaptureAnimManager.setOrientation(displayRotation);
    276             mCaptureAnimManager.animateFlash();
    277             mListener.requestRender();
    278             mAnimState = ANIM_CAPTURE_START;
    279         }
    280     }
    281 
    282     public void animateSlide() {
    283         synchronized (mLock) {
    284             // Ignore the case where animateFlash is skipped but animateSlide is called
    285             // e.g. Double tap shutter and immediately swipe to gallery, and quickly swipe back
    286             // to camera. This case only happens in monkey tests, not applicable to normal
    287             // human beings.
    288             if (mAnimState != ANIM_CAPTURE_RUNNING) {
    289                 Log.v(TAG, "Cannot animateSlide outside of animateCapture!"
    290                         + " Animation state = " + mAnimState);
    291                 return;
    292             }
    293             mCaptureAnimManager.animateSlide();
    294             mListener.requestRender();
    295         }
    296     }
    297 
    298     private void callbackIfNeeded() {
    299         if (mOneTimeFrameDrawnListener != null) {
    300             mOneTimeFrameDrawnListener.onFrameDrawn(this);
    301             mOneTimeFrameDrawnListener = null;
    302         }
    303     }
    304 
    305     @Override
    306     protected void updateTransformMatrix(float[] matrix) {
    307         super.updateTransformMatrix(matrix);
    308         Matrix.translateM(matrix, 0, .5f, .5f, 0);
    309         Matrix.scaleM(matrix, 0, mScaleX, mScaleY, 1f);
    310         Matrix.translateM(matrix, 0, -.5f, -.5f, 0);
    311     }
    312 
    313     public void directDraw(GLCanvas canvas, int x, int y, int width, int height) {
    314         DrawClient draw;
    315         synchronized (mLock) {
    316             draw = mDraw;
    317         }
    318         draw.onDraw(canvas, x, y, width, height);
    319     }
    320 
    321     public void setDraw(DrawClient draw) {
    322         synchronized (mLock) {
    323             if (draw == null) {
    324                 mDraw = mDefaultDraw;
    325             } else {
    326                 mDraw = draw;
    327             }
    328         }
    329         mListener.requestRender();
    330     }
    331 
    332     @Override
    333     public void draw(GLCanvas canvas, int x, int y, int width, int height) {
    334         synchronized (mLock) {
    335             allocateTextureIfRequested(canvas);
    336             if (!mVisible) mVisible = true;
    337             SurfaceTexture surfaceTexture = getSurfaceTexture();
    338             if (mDraw.requiresSurfaceTexture() && (surfaceTexture == null || !mFirstFrameArrived)) {
    339                 return;
    340             }
    341 
    342             switch (mAnimState) {
    343                 case ANIM_NONE:
    344                     directDraw(canvas, x, y, width, height);
    345                     break;
    346                 case ANIM_SWITCH_COPY_TEXTURE:
    347                     copyPreviewTexture(canvas);
    348                     mSwitchAnimManager.setReviewDrawingSize(width, height);
    349                     mListener.onPreviewTextureCopied();
    350                     mAnimState = ANIM_SWITCH_DARK_PREVIEW;
    351                     // The texture is ready. Fall through to draw darkened
    352                     // preview.
    353                 case ANIM_SWITCH_DARK_PREVIEW:
    354                 case ANIM_SWITCH_WAITING_FIRST_FRAME:
    355                     // Consume the frame. If the buffers are full,
    356                     // onFrameAvailable will not be called. Animation state
    357                     // relies on onFrameAvailable.
    358                     surfaceTexture.updateTexImage();
    359                     mSwitchAnimManager.drawDarkPreview(canvas, x, y, width,
    360                             height, mAnimTexture);
    361                     break;
    362                 case ANIM_SWITCH_START:
    363                     mSwitchAnimManager.startAnimation();
    364                     mAnimState = ANIM_SWITCH_RUNNING;
    365                     break;
    366                 case ANIM_CAPTURE_START:
    367                     copyPreviewTexture(canvas);
    368                     mListener.onCaptureTextureCopied();
    369                     mCaptureAnimManager.startAnimation(x, y, width, height);
    370                     mAnimState = ANIM_CAPTURE_RUNNING;
    371                     break;
    372             }
    373 
    374             if (mAnimState == ANIM_CAPTURE_RUNNING || mAnimState == ANIM_SWITCH_RUNNING) {
    375                 boolean drawn;
    376                 if (mAnimState == ANIM_CAPTURE_RUNNING) {
    377                     if (!mFullScreen) {
    378                         // Skip the animation if no longer in full screen mode
    379                         drawn = false;
    380                     } else {
    381                         drawn = mCaptureAnimManager.drawAnimation(canvas, this, mAnimTexture);
    382                     }
    383                 } else {
    384                     drawn = mSwitchAnimManager.drawAnimation(canvas, x, y,
    385                             width, height, this, mAnimTexture);
    386                 }
    387                 if (drawn) {
    388                     mListener.requestRender();
    389                 } else {
    390                     // Continue to the normal draw procedure if the animation is
    391                     // not drawn.
    392                     mAnimState = ANIM_NONE;
    393                     directDraw(canvas, x, y, width, height);
    394                 }
    395             }
    396             callbackIfNeeded();
    397         } // mLock
    398     }
    399 
    400     private void copyPreviewTexture(GLCanvas canvas) {
    401         if (!mDraw.requiresSurfaceTexture() && mAnimTexture == null) {
    402             mAnimTexture = new RawTexture(getTextureWidth(), getTextureHeight(), true);
    403             mAnimTexture.setIsFlippedVertically(true);
    404         }
    405         int width = mAnimTexture.getWidth();
    406         int height = mAnimTexture.getHeight();
    407         canvas.beginRenderTarget(mAnimTexture);
    408         if (!mDraw.requiresSurfaceTexture()) {
    409             mDraw.onDraw(canvas, 0, 0, width, height);
    410         } else {
    411             // Flip preview texture vertically. OpenGL uses bottom left point
    412             // as the origin (0, 0).
    413             canvas.translate(0, height);
    414             canvas.scale(1, -1, 1);
    415             getSurfaceTexture().getTransformMatrix(mTextureTransformMatrix);
    416             updateTransformMatrix(mTextureTransformMatrix);
    417             canvas.drawTexture(mExtTexture, mTextureTransformMatrix, 0, 0, width, height);
    418         }
    419         canvas.endRenderTarget();
    420     }
    421 
    422     @Override
    423     public void noDraw() {
    424         synchronized (mLock) {
    425             mVisible = false;
    426         }
    427     }
    428 
    429     @Override
    430     public void recycle() {
    431         synchronized (mLock) {
    432             mVisible = false;
    433         }
    434     }
    435 
    436     @Override
    437     public void onFrameAvailable(SurfaceTexture surfaceTexture) {
    438         synchronized (mLock) {
    439             if (getSurfaceTexture() != surfaceTexture) {
    440                 return;
    441             }
    442             mFirstFrameArrived = true;
    443             if (mVisible) {
    444                 if (mAnimState == ANIM_SWITCH_WAITING_FIRST_FRAME) {
    445                     mAnimState = ANIM_SWITCH_START;
    446                 }
    447                 // We need to ask for re-render if the SurfaceTexture receives a new
    448                 // frame.
    449                 mListener.requestRender();
    450             }
    451         }
    452     }
    453 
    454     // We need to keep track of the size of preview frame on the screen because
    455     // it's needed when we do switch-camera animation. See comments in
    456     // SwitchAnimManager.java. This is based on the natural orientation, not the
    457     // view system orientation.
    458     public void setPreviewFrameLayoutSize(int width, int height) {
    459         synchronized (mLock) {
    460             mSwitchAnimManager.setPreviewFrameLayoutSize(width, height);
    461             setPreviewLayoutSize(width, height);
    462         }
    463     }
    464 
    465     public void setOneTimeOnFrameDrawnListener(OnFrameDrawnListener l) {
    466         synchronized (mLock) {
    467             mFirstFrameArrived = false;
    468             mOneTimeFrameDrawnListener = l;
    469         }
    470     }
    471 
    472     @Override
    473     public SurfaceTexture getSurfaceTexture() {
    474         synchronized (mLock) {
    475             SurfaceTexture surfaceTexture = super.getSurfaceTexture();
    476             if (surfaceTexture == null && mAcquireTexture) {
    477                 try {
    478                     mLock.wait();
    479                     surfaceTexture = super.getSurfaceTexture();
    480                 } catch (InterruptedException e) {
    481                     Log.w(TAG, "unexpected interruption");
    482                 }
    483             }
    484             return surfaceTexture;
    485         }
    486     }
    487 
    488     private void allocateTextureIfRequested(GLCanvas canvas) {
    489         synchronized (mLock) {
    490             if (mAcquireTexture) {
    491                 super.acquireSurfaceTexture(canvas);
    492                 mAcquireTexture = false;
    493                 mLock.notifyAll();
    494             }
    495         }
    496     }
    497 }
    498