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.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 }
    525