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