package edu.mit.mobile.android.livingpostcards; /* * Copyright (C) 2012-2013 MIT Mobile Experience Lab * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation version 2 * of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import java.io.IOException; import android.content.Context; import android.hardware.Camera; import android.hardware.Camera.Parameters; import android.hardware.Camera.Size; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import edu.mit.mobile.android.locast.Constants; /** * Camera preview, based on the Android example code. * * If the width isn't specified, will resize to keep the aspect ratio of the preview image. Sets the * preview size to the optimal size based on this view. * */ public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private static final String TAG = CameraPreview.class.getSimpleName(); private final SurfaceHolder mHolder; private final Camera mCamera; private float mForceAspectRatio = 0f; private OnPreviewStartedListener mOnPreviewStartedListener; private static final float ASPECT_RATIO_TOLERENCE = 0.001f; @SuppressWarnings("deprecation") public CameraPreview(Context context, final Camera camera) { super(context); mCamera = camera; if (mCamera == null) { throw new NullPointerException("camera cannot be null"); } // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. mHolder = getHolder(); mHolder.addCallback(this); // deprecated setting, but required on Android versions prior to 3.0 mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } /** * Instead of letting the preview pick the best size based on * * @param widthByHeight */ public void setForceAspectRatio(float widthByHeight) { mForceAspectRatio = widthByHeight; } public void surfaceCreated(SurfaceHolder holder) { // The Surface has been created, now tell the camera where to draw the preview. try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (final IOException e) { Log.e(TAG, "Error setting camera preview: " + e.getMessage()); } catch (final RuntimeException e) { Log.e(TAG, "Error setting camera preview: " + e.getMessage()); } } public void surfaceDestroyed(SurfaceHolder holder) { // empty. Take care of releasing the Camera preview in your activity. } public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { if (mHolder.getSurface() == null) { // preview surface does not exist return; } // stop preview before making changes try { mCamera.stopPreview(); } catch (final Exception e) { // ignore: tried to stop a non-existent preview } final Parameters p = mCamera.getParameters(); Camera.Size size = getBestPreviewSize(w, h, p, mForceAspectRatio); // This means our screen is probably small and there are no pixel-for-pixel preview sizes // that have the same aspect ratio. Try searching from the original picture size and letting // the UI scale. if (size == null) { Log.w(TAG, "Couldn't find the best size that maps pixel-for-pixel on the screen. Trying larger ones..."); final Size picSize = p.getPictureSize(); size = getBestPreviewSize(picSize.width, picSize.height, p, (float) picSize.width / picSize.height); } // ok, this isn't good. But it's better than failing. if (size == null) { Log.e(TAG, "Trying to find the best size, but couldn't find one with the request aspect ratio (" + mForceAspectRatio + "). Ignoring ratio request..."); size = getBestPreviewSize(w, h, p, 0f); } p.setPreviewSize(size.width, size.height); mCamera.setParameters(p); // start preview with new settings try { mCamera.setPreviewDisplay(mHolder); mCamera.startPreview(); if (mOnPreviewStartedListener != null) { mOnPreviewStartedListener.onPreviewStarted(); } } catch (final Exception e) { Log.e(TAG, "Error starting camera preview: " + e.getMessage()); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int wMode = View.MeasureSpec.getMode(widthMeasureSpec); final int hMode = View.MeasureSpec.getMode(heightMeasureSpec); // if the size is specified exactly, we won't attempt to resize ourselves if (wMode == View.MeasureSpec.EXACTLY && hMode == View.MeasureSpec.EXACTLY) { if (Constants.DEBUG) { Log.d(TAG, "not correcting aspect ratio for preview"); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } int w = View.MeasureSpec.getSize(widthMeasureSpec); int h = View.MeasureSpec.getSize(heightMeasureSpec); final Parameters p = mCamera.getParameters(); // the aspect ratio of the preview is often wrong. We need to use the aspect ratio of the // picture instead. final Camera.Size picSize = p.getPictureSize(); if (wMode == View.MeasureSpec.AT_MOST) { // preserve aspect ratio w = (int) (((float) picSize.width / picSize.height) * h); } else if (hMode == View.MeasureSpec.AT_MOST) { // preserve aspect ratio h = (int) (((float) picSize.height / picSize.width) * w); } else { if (Constants.DEBUG) { Log.d(TAG, "not correcting aspect ratio for preview"); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } if (Constants.DEBUG) { Log.d(TAG, "Adjusting view to match aspect ratio of preview " + picSize.width + "x" + picSize.height + "; set to " + w + "x" + h); } setMeasuredDimension(w, h); } /*** * Copyright (c) 2008-2012 CommonsWare, LLC Licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required by * applicable law or agreed to in writing, software distributed under the License is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the * License. From _The Busy Coder's Guide to Advanced Android Development_ * http://commonsware.com/AdvAndroid */ /** * Finds the highest resolution preview size that fits within the given width and height. * * @param width * @param height * @param parameters * @return */ public static Camera.Size getBestPreviewSize(int width, int height, Camera.Parameters parameters, float widthByHeight) { Camera.Size result = null; if (Constants.DEBUG) { Log.d(TAG, "Looking for best preview size that fits within " + width + "x" + height + (widthByHeight != 0f ? " and has aspect ratio of " + widthByHeight : "")); final StringBuilder sb = new StringBuilder(); for (final Camera.Size size : parameters.getSupportedPreviewSizes()) { sb.append(size.width); sb.append('x'); sb.append(size.height); sb.append(" "); } Log.d(TAG, "available sizes: " + sb.toString()); } for (final Camera.Size size : parameters.getSupportedPreviewSizes()) { if (widthByHeight != 0f && Math.abs((float) size.width / size.height - widthByHeight) >= ASPECT_RATIO_TOLERENCE) { continue; } if (Constants.DEBUG && widthByHeight != 0f) { Log.d(TAG, size.width + "x" + size.height + " has the correct aspect ratio"); } if (size.width <= width && size.height <= height) { if (result == null) { result = size; } else { final int resultArea = result.width * result.height; final int newArea = size.width * size.height; if (newArea > resultArea) { result = size; } } } } if (Constants.DEBUG) { if (result == null) { Log.e(TAG, "Couldn't find a size that matches the requirements."); } else { Log.d(TAG, "choose " + result.width + "x" + result.height); } } return result; } public void setOnPreviewStartedListener(OnPreviewStartedListener l) { mOnPreviewStartedListener = l; } public interface OnPreviewStartedListener { public void onPreviewStarted(); } }