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