2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera;
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;
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;
31 * This is a ScreenNail which can display camera's preview.
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
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;
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];
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
69 private Object mLock = new Object();
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() {
84 public void onDraw(GLCanvas canvas, int x, int y, int width, int height) {
85 CameraScreenNail.super.draw(canvas, x, y, width, height);
89 public boolean requiresSurfaceTexture() {
94 public RawTexture copyToTexture(GLCanvas c, RawTexture texture, int w, int h) {
95 // We shouldn't be here since requireSurfaceTexture() returns true.
99 private DrawClient mDraw = mDefaultDraw;
100 private float mAlpha = 1f;
101 private Runnable mOnFrameDrawnListener;
103 public interface Listener {
104 void requestRender();
105 // Preview has been copied to a texture.
106 void onPreviewTextureCopied();
108 void onCaptureTextureCopied();
111 public interface OnFrameDrawnListener {
112 void onFrameDrawn(CameraScreenNail c);
115 public interface DrawClient {
116 void onDraw(GLCanvas canvas, int x, int y, int width, int height);
118 boolean requiresSurfaceTexture();
119 // The client should implement this if requiresSurfaceTexture() is false;
120 RawTexture copyToTexture(GLCanvas c, RawTexture texture, int width, int height);
123 public CameraScreenNail(Listener listener, Context ctx) {
124 mListener = listener;
125 mCaptureAnimManager = new CaptureAnimManager(ctx);
128 public void setFullScreen(boolean full) {
129 synchronized (mLock) {
135 * returns the uncropped, but scaled, width of the rendered texture
137 public int getUncroppedRenderWidth() {
138 return mUncroppedRenderWidth;
142 * returns the uncropped, but scaled, width of the rendered texture
144 public int getUncroppedRenderHeight() {
145 return mUncroppedRenderHeight;
149 public int getWidth() {
150 return mEnableAspectRatioClamping ? mRenderWidth : getTextureWidth();
154 public int getHeight() {
155 return mEnableAspectRatioClamping ? mRenderHeight : getTextureHeight();
158 private int getTextureWidth() {
159 return super.getWidth();
162 private int getTextureHeight() {
163 return super.getHeight();
167 public void setSize(int w, int h) {
169 mEnableAspectRatioClamping = false;
170 if (mRenderWidth == 0) {
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
182 * Note that calls to setSize will disable this
184 public void enableAspectRatioClamping() {
185 mEnableAspectRatioClamping = true;
189 private void setPreviewLayoutSize(int w, int h) {
190 Log.i(TAG, "preview layout size: "+w+"/"+h);
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");
206 if (getTextureWidth() > getTextureHeight()) {
207 aspectRatio = (float) getTextureWidth() / (float) getTextureHeight();
209 aspectRatio = (float) getTextureHeight() / (float) getTextureWidth();
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));
218 scaledTextureWidth = Math.max(mRenderWidth,
219 (int) (mRenderHeight / aspectRatio));
220 scaledTextureHeight = Math.max(mRenderHeight,
221 (int) (mRenderWidth * aspectRatio));
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);
230 public void acquireSurfaceTexture() {
231 synchronized (mLock) {
232 mFirstFrameArrived = false;
233 mAnimTexture = new RawTexture(getTextureWidth(), getTextureHeight(), true);
234 mAcquireTexture = true;
236 mListener.requestRender();
240 public void releaseSurfaceTexture() {
241 synchronized (mLock) {
242 if (mAcquireTexture) {
243 mAcquireTexture = false;
246 if (super.getSurfaceTexture() != null) {
247 super.releaseSurfaceTexture();
249 mAnimState = ANIM_NONE; // stop the animation
254 public void copyTexture() {
255 synchronized (mLock) {
256 mListener.requestRender();
257 mAnimState = ANIM_SWITCH_COPY_TEXTURE;
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;
272 public void animateCapture(int displayRotation) {
273 synchronized (mLock) {
274 mCaptureAnimManager.setOrientation(displayRotation);
275 mCaptureAnimManager.animateFlashAndSlide();
276 mListener.requestRender();
277 mAnimState = ANIM_CAPTURE_START;
281 public RawTexture getAnimationTexture() {
285 public void animateFlash(int displayRotation) {
286 synchronized (mLock) {
287 mCaptureAnimManager.setOrientation(displayRotation);
288 mCaptureAnimManager.animateFlash();
289 mListener.requestRender();
290 mAnimState = ANIM_CAPTURE_START;
294 public void animateSlide() {
295 synchronized (mLock) {
296 mCaptureAnimManager.animateSlide();
297 mListener.requestRender();
301 private void callbackIfNeeded() {
302 if (mOneTimeFrameDrawnListener != null) {
303 mOneTimeFrameDrawnListener.onFrameDrawn(this);
304 mOneTimeFrameDrawnListener = null;
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);
316 public void directDraw(GLCanvas canvas, int x, int y, int width, int height) {
318 synchronized (mLock) {
321 draw.onDraw(canvas, x, y, width, height);
324 public void setDraw(DrawClient draw) {
325 synchronized (mLock) {
327 mDraw = mDefaultDraw;
332 mListener.requestRender();
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)) {
344 if (mOnFrameDrawnListener != null) {
345 mOnFrameDrawnListener.run();
346 mOnFrameDrawnListener = null;
348 float oldAlpha = canvas.getAlpha();
349 canvas.setAlpha(mAlpha);
351 switch (mAnimState) {
353 directDraw(canvas, x, y, width, height);
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
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);
371 case ANIM_SWITCH_START:
372 mSwitchAnimManager.startAnimation();
373 mAnimState = ANIM_SWITCH_RUNNING;
375 case ANIM_CAPTURE_START:
376 copyPreviewTexture(canvas);
377 mListener.onCaptureTextureCopied();
378 mCaptureAnimManager.startAnimation();
379 mAnimState = ANIM_CAPTURE_RUNNING;
383 if (mAnimState == ANIM_CAPTURE_RUNNING || mAnimState == ANIM_SWITCH_RUNNING) {
385 if (mAnimState == ANIM_CAPTURE_RUNNING) {
387 // Skip the animation if no longer in full screen mode
390 drawn = mCaptureAnimManager.drawAnimation(canvas, this, mAnimTexture,
391 x, y, width, height);
394 drawn = mSwitchAnimManager.drawAnimation(canvas, x, y,
395 width, height, this, mAnimTexture);
398 mListener.requestRender();
400 // Continue to the normal draw procedure if the animation is
402 mAnimState = ANIM_NONE;
403 directDraw(canvas, x, y, width, height);
406 canvas.setAlpha(oldAlpha);
411 private void copyPreviewTexture(GLCanvas canvas) {
412 if (!mDraw.requiresSurfaceTexture()) {
413 mAnimTexture = mDraw.copyToTexture(
414 canvas, mAnimTexture, getTextureWidth(), getTextureHeight());
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();
431 public void noDraw() {
432 synchronized (mLock) {
438 public void recycle() {
439 synchronized (mLock) {
445 public void onFrameAvailable(SurfaceTexture surfaceTexture) {
446 synchronized (mLock) {
447 if (getSurfaceTexture() != surfaceTexture) {
450 mFirstFrameArrived = true;
452 if (mAnimState == ANIM_SWITCH_WAITING_FIRST_FRAME) {
453 mAnimState = ANIM_SWITCH_START;
455 // We need to ask for re-render if the SurfaceTexture receives a new
457 mListener.requestRender();
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);
473 public void setOneTimeOnFrameDrawnListener(OnFrameDrawnListener l) {
474 synchronized (mLock) {
475 mFirstFrameArrived = false;
476 mOneTimeFrameDrawnListener = l;
481 public SurfaceTexture getSurfaceTexture() {
482 synchronized (mLock) {
483 SurfaceTexture surfaceTexture = super.getSurfaceTexture();
484 if (surfaceTexture == null && mAcquireTexture) {
487 surfaceTexture = super.getSurfaceTexture();
488 } catch (InterruptedException e) {
489 Log.w(TAG, "unexpected interruption");
492 return surfaceTexture;
496 private void allocateTextureIfRequested(GLCanvas canvas) {
497 synchronized (mLock) {
498 if (mAcquireTexture) {
499 super.acquireSurfaceTexture(canvas);
500 mAcquireTexture = false;
506 public void setOnFrameDrawnOneShot(Runnable run) {
507 synchronized (mLock) {
508 mOnFrameDrawnListener = run;
512 public float getAlpha() {
513 synchronized (mLock) {
518 public void setAlpha(float alpha) {
519 synchronized (mLock) {
521 mListener.requestRender();