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