/*
* Copyright 2012-2013 the original author or authors.
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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/>.
*/
package de.schildbach.wallet.digitalcoin.camera;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import android.annotation.SuppressLint;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.view.SurfaceHolder;
import com.google.zxing.PlanarYUVLuminanceSource;
/**
* @author Andreas Schildbach
*/
public final class CameraManager
{
private static final boolean CONTINUOUS_FOCUS = true;
private static final int MIN_FRAME_WIDTH = 240;
private static final int MIN_FRAME_HEIGHT = 240;
private static final int MAX_FRAME_WIDTH = 600;
private static final int MAX_FRAME_HEIGHT = 400;
private static final int MIN_PREVIEW_PIXELS = 470 * 320; // normal screen
private static final int MAX_PREVIEW_PIXELS = 1280 * 720;
private Camera camera;
private Camera.Size cameraResolution;
private Rect frame;
private Rect framePreview;
public Camera getCamera()
{
return camera;
}
public Rect getFrame()
{
return frame;
}
public Rect getFramePreview()
{
return framePreview;
}
public void open(final SurfaceHolder holder) throws IOException
{
camera = new CameraSupportManager().build().open();
camera.setPreviewDisplay(holder);
final Camera.Parameters parameters = camera.getParameters();
final Rect surfaceFrame = holder.getSurfaceFrame();
final int surfaceWidth = surfaceFrame.width();
final int surfaceHeight = surfaceFrame.height();
cameraResolution = findBestPreviewSizeValue(parameters, new Point(surfaceWidth, surfaceHeight));
int width = surfaceWidth * 3 / 4;
if (width < MIN_FRAME_WIDTH)
width = MIN_FRAME_WIDTH;
else if (width > MAX_FRAME_WIDTH)
width = MAX_FRAME_WIDTH;
int height = surfaceHeight * 3 / 4;
if (height < MIN_FRAME_HEIGHT)
height = MIN_FRAME_HEIGHT;
else if (height > MAX_FRAME_HEIGHT)
height = MAX_FRAME_HEIGHT;
final int finalWidth = Math.min(width, height);
final int leftOffset = (surfaceWidth - finalWidth) / 2;
final int topOffset = (surfaceHeight - finalWidth) / 2;
frame = new Rect(leftOffset, topOffset, leftOffset + finalWidth, topOffset + finalWidth);
framePreview = new Rect(frame.left * cameraResolution.width / surfaceWidth, frame.top * cameraResolution.height / surfaceHeight, frame.right
* cameraResolution.width / surfaceWidth, frame.bottom * cameraResolution.height / surfaceHeight);
final String savedParameters = parameters == null ? null : parameters.flatten();
try
{
setDesiredCameraParameters(camera, cameraResolution, false);
}
catch (final RuntimeException x)
{
if (savedParameters != null)
{
final Camera.Parameters parameters2 = camera.getParameters();
parameters2.unflatten(savedParameters);
try
{
camera.setParameters(parameters2);
setDesiredCameraParameters(camera, cameraResolution, true);
}
catch (final RuntimeException x2)
{
x2.printStackTrace();
}
}
}
camera.startPreview();
}
public void close()
{
if (camera != null)
{
camera.stopPreview();
camera.release();
}
}
private static final Comparator<Camera.Size> numPixelComparator = new Comparator<Camera.Size>()
{
public int compare(final Camera.Size size1, final Camera.Size size2)
{
final int pixels1 = size1.height * size1.width;
final int pixels2 = size2.height * size2.width;
if (pixels1 < pixels2)
return 1;
else if (pixels1 > pixels2)
return -1;
else
return 0;
}
};
private static Camera.Size findBestPreviewSizeValue(final Camera.Parameters parameters, final Point screenResolution)
{
final List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
if (rawSupportedSizes == null)
return parameters.getPreviewSize();
// sort by size, descending
final List<Camera.Size> supportedPreviewSizes = new ArrayList<Camera.Size>(rawSupportedSizes);
Collections.sort(supportedPreviewSizes, numPixelComparator);
final float screenAspectRatio = (float) screenResolution.x / (float) screenResolution.y;
Camera.Size bestSize = null;
float diff = Float.POSITIVE_INFINITY;
for (final Camera.Size supportedPreviewSize : supportedPreviewSizes)
{
final int realWidth = supportedPreviewSize.width;
final int realHeight = supportedPreviewSize.height;
final int realPixels = realWidth * realHeight;
if (realPixels < MIN_PREVIEW_PIXELS || realPixels > MAX_PREVIEW_PIXELS)
continue;
final boolean isCandidatePortrait = realWidth < realHeight;
final int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
final int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y)
return supportedPreviewSize;
final float aspectRatio = (float) maybeFlippedWidth / (float) maybeFlippedHeight;
final float newDiff = Math.abs(aspectRatio - screenAspectRatio);
if (newDiff < diff)
{
bestSize = supportedPreviewSize;
diff = newDiff;
}
}
if (bestSize != null)
return bestSize;
else
return parameters.getPreviewSize();
}
@SuppressLint("InlinedApi")
private static void setDesiredCameraParameters(final Camera camera, final Camera.Size cameraResolution, final boolean safeMode)
{
final Camera.Parameters parameters = camera.getParameters();
if (parameters == null)
return;
String focusMode;
if (safeMode || !CONTINUOUS_FOCUS)
focusMode = findValue(parameters.getSupportedFocusModes(), Camera.Parameters.FOCUS_MODE_AUTO);
else
focusMode = findValue(parameters.getSupportedFocusModes(), Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, Camera.Parameters.FOCUS_MODE_AUTO);
if (!safeMode && focusMode == null)
focusMode = findValue(parameters.getSupportedFocusModes(), Camera.Parameters.FOCUS_MODE_MACRO, Camera.Parameters.FOCUS_MODE_EDOF);
if (focusMode != null)
parameters.setFocusMode(focusMode);
parameters.setPreviewSize(cameraResolution.width, cameraResolution.height);
camera.setParameters(parameters);
}
public void requestPreviewFrame(final PreviewCallback callback)
{
camera.setOneShotPreviewCallback(callback);
}
public PlanarYUVLuminanceSource buildLuminanceSource(final byte[] data)
{
return new PlanarYUVLuminanceSource(data, cameraResolution.width, cameraResolution.height, framePreview.left, framePreview.top,
framePreview.width(), framePreview.height(), false);
}
public void setTorch(final boolean enabled)
{
if (enabled != getTorchEnabled(camera))
setTorchEnabled(camera, enabled);
}
private static boolean getTorchEnabled(final Camera camera)
{
final Camera.Parameters parameters = camera.getParameters();
if (parameters != null)
{
final String flashMode = camera.getParameters().getFlashMode();
return flashMode != null && (Camera.Parameters.FLASH_MODE_ON.equals(flashMode) || Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode));
}
return false;
}
private static void setTorchEnabled(final Camera camera, final boolean enabled)
{
final Camera.Parameters parameters = camera.getParameters();
final List<String> supportedFlashModes = parameters.getSupportedFlashModes();
if (supportedFlashModes != null)
{
final String flashMode;
if (enabled)
flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_TORCH, Camera.Parameters.FLASH_MODE_ON);
else
flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF);
if (flashMode != null)
{
camera.cancelAutoFocus(); // autofocus can cause conflict
parameters.setFlashMode(flashMode);
camera.setParameters(parameters);
}
}
}
private static String findValue(final Collection<String> values, final String... valuesToFind)
{
for (final String valueToFind : valuesToFind)
if (values.contains(valueToFind))
return valueToFind;
return null;
}
}