|
@@ -0,0 +1,308 @@
|
|
|
|
|
+package me.dm7.barcodescanner.core;
|
|
|
|
|
+
|
|
|
|
|
+import android.content.Context;
|
|
|
|
|
+import android.content.res.Configuration;
|
|
|
|
|
+import android.graphics.Point;
|
|
|
|
|
+import android.hardware.Camera;
|
|
|
|
|
+import android.os.Handler;
|
|
|
|
|
+import android.util.AttributeSet;
|
|
|
|
|
+import android.util.Log;
|
|
|
|
|
+import android.view.Display;
|
|
|
|
|
+import android.view.Surface;
|
|
|
|
|
+import android.view.SurfaceHolder;
|
|
|
|
|
+import android.view.SurfaceView;
|
|
|
|
|
+import android.view.View;
|
|
|
|
|
+import android.view.ViewGroup;
|
|
|
|
|
+import android.view.WindowManager;
|
|
|
|
|
+
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
|
|
|
|
|
+ private static final String TAG = "CameraPreview";
|
|
|
|
|
+
|
|
|
|
|
+ private CameraWrapper mCameraWrapper;
|
|
|
|
|
+ private Handler mAutoFocusHandler;
|
|
|
|
|
+ private boolean mPreviewing = true;
|
|
|
|
|
+ private boolean mAutoFocus = true;
|
|
|
|
|
+ private boolean mSurfaceCreated = false;
|
|
|
|
|
+ private boolean mShouldScaleToFill = true;
|
|
|
|
|
+ private Camera.PreviewCallback mPreviewCallback;
|
|
|
|
|
+
|
|
|
|
|
+ public CameraPreview(Context context, CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
|
|
|
|
|
+ super(context);
|
|
|
|
|
+ init(cameraWrapper, previewCallback);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public CameraPreview(Context context, AttributeSet attrs, CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
|
|
|
|
|
+ super(context, attrs);
|
|
|
|
|
+ init(cameraWrapper, previewCallback);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void init(CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
|
|
|
|
|
+ setCamera(cameraWrapper, previewCallback);
|
|
|
|
|
+ mAutoFocusHandler = new Handler();
|
|
|
|
|
+ getHolder().addCallback(this);
|
|
|
|
|
+ getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setCamera(CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
|
|
|
|
|
+ mCameraWrapper = cameraWrapper;
|
|
|
|
|
+ mPreviewCallback = previewCallback;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setShouldScaleToFill(boolean scaleToFill) {
|
|
|
|
|
+ mShouldScaleToFill = scaleToFill;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void surfaceCreated(SurfaceHolder surfaceHolder) {
|
|
|
|
|
+ mSurfaceCreated = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) {
|
|
|
|
|
+ if(surfaceHolder.getSurface() == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ stopCameraPreview();
|
|
|
|
|
+ showCameraPreview();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
|
|
|
|
|
+ mSurfaceCreated = false;
|
|
|
|
|
+ stopCameraPreview();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void showCameraPreview() {
|
|
|
|
|
+ if(mCameraWrapper != null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ getHolder().addCallback(this);
|
|
|
|
|
+ mPreviewing = true;
|
|
|
|
|
+ setupCameraParameters();
|
|
|
|
|
+ mCameraWrapper.mCamera.setPreviewDisplay(getHolder());
|
|
|
|
|
+ mCameraWrapper.mCamera.setDisplayOrientation(getDisplayOrientation());
|
|
|
|
|
+ mCameraWrapper.mCamera.setOneShotPreviewCallback(mPreviewCallback);
|
|
|
|
|
+ mCameraWrapper.mCamera.startPreview();
|
|
|
|
|
+ if(mAutoFocus) {
|
|
|
|
|
+ if (mSurfaceCreated) { // check if surface created before using autofocus
|
|
|
|
|
+ safeAutoFocus();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ scheduleAutoFocus(); // wait 1 sec and then do check again
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ Log.e(TAG, e.toString(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void safeAutoFocus() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ mCameraWrapper.mCamera.autoFocus(autoFocusCB);
|
|
|
|
|
+ } catch (RuntimeException re) {
|
|
|
|
|
+ // Horrible hack to deal with autofocus errors on Sony devices
|
|
|
|
|
+ // See https://github.com/dm77/barcodescanner/issues/7 for example
|
|
|
|
|
+ scheduleAutoFocus(); // wait 1 sec and then do check again
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void stopCameraPreview() {
|
|
|
|
|
+ if(mCameraWrapper != null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ mPreviewing = false;
|
|
|
|
|
+ getHolder().removeCallback(this);
|
|
|
|
|
+ mCameraWrapper.mCamera.cancelAutoFocus();
|
|
|
|
|
+ mCameraWrapper.mCamera.setOneShotPreviewCallback(null);
|
|
|
|
|
+ mCameraWrapper.mCamera.stopPreview();
|
|
|
|
|
+ } catch(Exception e) {
|
|
|
|
|
+ Log.e(TAG, e.toString(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setupCameraParameters() {
|
|
|
|
|
+ Camera.Size optimalSize = getOptimalPreviewSize();
|
|
|
|
|
+ Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
|
|
|
|
|
+ parameters.setPreviewSize(optimalSize.width, optimalSize.height);
|
|
|
|
|
+ mCameraWrapper.mCamera.setParameters(parameters);
|
|
|
|
|
+ adjustViewSize(optimalSize);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void adjustViewSize(Camera.Size cameraSize) {
|
|
|
|
|
+ Point previewSize = convertSizeToLandscapeOrientation(new Point(getWidth(), getHeight()));
|
|
|
|
|
+ float cameraRatio = ((float) cameraSize.width) / cameraSize.height;
|
|
|
|
|
+ float screenRatio = ((float) previewSize.x) / previewSize.y;
|
|
|
|
|
+
|
|
|
|
|
+ if (screenRatio > cameraRatio) {
|
|
|
|
|
+ setViewSize((int) (previewSize.y * cameraRatio), previewSize.y);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setViewSize(previewSize.x, (int) (previewSize.x / cameraRatio));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @SuppressWarnings("SuspiciousNameCombination")
|
|
|
|
|
+ private Point convertSizeToLandscapeOrientation(Point size) {
|
|
|
|
|
+ if (getDisplayOrientation() % 180 == 0) {
|
|
|
|
|
+ return size;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return new Point(size.y, size.x);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @SuppressWarnings("SuspiciousNameCombination")
|
|
|
|
|
+ private void setViewSize(int width, int height) {
|
|
|
|
|
+ ViewGroup.LayoutParams layoutParams = getLayoutParams();
|
|
|
|
|
+ int tmpWidth;
|
|
|
|
|
+ int tmpHeight;
|
|
|
|
|
+ if (getDisplayOrientation() % 180 == 0) {
|
|
|
|
|
+ tmpWidth = width;
|
|
|
|
|
+ tmpHeight = height;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ tmpWidth = height;
|
|
|
|
|
+ tmpHeight = width;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (mShouldScaleToFill) {
|
|
|
|
|
+ int parentWidth = ((View) getParent()).getWidth();
|
|
|
|
|
+ int parentHeight = ((View) getParent()).getHeight();
|
|
|
|
|
+ float ratioWidth = (float) parentWidth / (float) tmpWidth;
|
|
|
|
|
+ float ratioHeight = (float) parentHeight / (float) tmpHeight;
|
|
|
|
|
+
|
|
|
|
|
+ float compensation;
|
|
|
|
|
+
|
|
|
|
|
+ if (ratioWidth > ratioHeight) {
|
|
|
|
|
+ compensation = ratioWidth;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ compensation = ratioHeight;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ tmpWidth = Math.round(tmpWidth * compensation);
|
|
|
|
|
+ tmpHeight = Math.round(tmpHeight * compensation);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ layoutParams.width = tmpWidth;
|
|
|
|
|
+ layoutParams.height = tmpHeight;
|
|
|
|
|
+ setLayoutParams(layoutParams);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public int getDisplayOrientation() {
|
|
|
|
|
+ if (mCameraWrapper == null) {
|
|
|
|
|
+ //If we don't have a camera set there is no orientation so return dummy value
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Camera.CameraInfo info = new Camera.CameraInfo();
|
|
|
|
|
+ if(mCameraWrapper.mCameraId == -1) {
|
|
|
|
|
+ Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Camera.getCameraInfo(mCameraWrapper.mCameraId, info);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
|
|
|
|
|
+ Display display = wm.getDefaultDisplay();
|
|
|
|
|
+
|
|
|
|
|
+ int rotation = display.getRotation();
|
|
|
|
|
+ int degrees = 0;
|
|
|
|
|
+ switch (rotation) {
|
|
|
|
|
+ case Surface.ROTATION_0: degrees = 0; break;
|
|
|
|
|
+ case Surface.ROTATION_90: degrees = 90; break;
|
|
|
|
|
+ case Surface.ROTATION_180: degrees = 180; break;
|
|
|
|
|
+ case Surface.ROTATION_270: degrees = 270; break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int result;
|
|
|
|
|
+ if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
|
|
|
|
+ result = (info.orientation + degrees) % 360;
|
|
|
|
|
+ result = (360 - result) % 360; // compensate the mirror
|
|
|
|
|
+ } else { // back-facing
|
|
|
|
|
+ result = (info.orientation - degrees + 360) % 360;
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Camera.Size getOptimalPreviewSize() {
|
|
|
|
|
+ if(mCameraWrapper == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<Camera.Size> sizes = mCameraWrapper.mCamera.getParameters().getSupportedPreviewSizes();
|
|
|
|
|
+ int w = getWidth();
|
|
|
|
|
+ int h = getHeight();
|
|
|
|
|
+ if (DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_PORTRAIT) {
|
|
|
|
|
+ int portraitWidth = h;
|
|
|
|
|
+ h = w;
|
|
|
|
|
+ w = portraitWidth;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ final double ASPECT_TOLERANCE = 0.1;
|
|
|
|
|
+ double targetRatio = (double) w / h;
|
|
|
|
|
+ if (sizes == null) return null;
|
|
|
|
|
+
|
|
|
|
|
+ Camera.Size optimalSize = null;
|
|
|
|
|
+ double minDiff = Double.MAX_VALUE;
|
|
|
|
|
+
|
|
|
|
|
+ int targetHeight = h;
|
|
|
|
|
+
|
|
|
|
|
+ // Try to find an size match aspect ratio and size
|
|
|
|
|
+ for (Camera.Size size : sizes) {
|
|
|
|
|
+ double ratio = (double) size.width / size.height;
|
|
|
|
|
+ if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
|
|
|
|
|
+ if (Math.abs(size.height - targetHeight) < minDiff) {
|
|
|
|
|
+ optimalSize = size;
|
|
|
|
|
+ minDiff = Math.abs(size.height - targetHeight);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Cannot find the one match the aspect ratio, ignore the requirement
|
|
|
|
|
+ if (optimalSize == null) {
|
|
|
|
|
+ minDiff = Double.MAX_VALUE;
|
|
|
|
|
+ for (Camera.Size size : sizes) {
|
|
|
|
|
+ if (Math.abs(size.height - targetHeight) < minDiff) {
|
|
|
|
|
+ optimalSize = size;
|
|
|
|
|
+ minDiff = Math.abs(size.height - targetHeight);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return optimalSize;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setAutoFocus(boolean state) {
|
|
|
|
|
+ if(mCameraWrapper != null && mPreviewing) {
|
|
|
|
|
+ if(state == mAutoFocus) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ mAutoFocus = state;
|
|
|
|
|
+ if(mAutoFocus) {
|
|
|
|
|
+ if (mSurfaceCreated) { // check if surface created before using autofocus
|
|
|
|
|
+ Log.v(TAG, "Starting autofocus");
|
|
|
|
|
+ safeAutoFocus();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ scheduleAutoFocus(); // wait 1 sec and then do check again
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Log.v(TAG, "Cancelling autofocus");
|
|
|
|
|
+ mCameraWrapper.mCamera.cancelAutoFocus();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Runnable doAutoFocus = new Runnable() {
|
|
|
|
|
+ public void run() {
|
|
|
|
|
+ if(mCameraWrapper != null && mPreviewing && mAutoFocus && mSurfaceCreated) {
|
|
|
|
|
+ safeAutoFocus();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Mimic continuous auto-focusing
|
|
|
|
|
+ Camera.AutoFocusCallback autoFocusCB = new Camera.AutoFocusCallback() {
|
|
|
|
|
+ public void onAutoFocus(boolean success, Camera camera) {
|
|
|
|
|
+ scheduleAutoFocus();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ private void scheduleAutoFocus() {
|
|
|
|
|
+ mAutoFocusHandler.postDelayed(doAutoFocus, 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|