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