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