From 82793363fc60c9bd1fd81d19dc4ec15b9b15bd44 Mon Sep 17 00:00:00 2001 From: Pasha Shkaran Date: Fri, 10 Nov 2017 18:05:27 +0200 Subject: [PATCH] Adjust camera focus --- .../utils/BarcodeScannerView.java | 5 +- .../communicator/utils/CameraPreview.java | 380 ++++++++++++++++++ .../communicator/utils/CameraWrapper.java | 22 + 3 files changed, 405 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/nynja/mobile/communicator/utils/CameraPreview.java create mode 100644 app/src/main/java/com/nynja/mobile/communicator/utils/CameraWrapper.java diff --git a/app/src/main/java/com/nynja/mobile/communicator/utils/BarcodeScannerView.java b/app/src/main/java/com/nynja/mobile/communicator/utils/BarcodeScannerView.java index 77387bfbe0..2278ca7f8a 100644 --- a/app/src/main/java/com/nynja/mobile/communicator/utils/BarcodeScannerView.java +++ b/app/src/main/java/com/nynja/mobile/communicator/utils/BarcodeScannerView.java @@ -1,5 +1,6 @@ package com.nynja.mobile.communicator.utils; +import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; @@ -14,9 +15,7 @@ import android.widget.RelativeLayout; import com.nynja.mobile.communicator.R; -import me.dm7.barcodescanner.core.CameraPreview; import me.dm7.barcodescanner.core.CameraUtils; -import me.dm7.barcodescanner.core.CameraWrapper; import me.dm7.barcodescanner.core.IViewFinder; import me.dm7.barcodescanner.core.R.integer; import me.dm7.barcodescanner.core.R.styleable; @@ -36,6 +35,7 @@ public abstract class BarcodeScannerView extends FrameLayout implements PreviewC private float mBorderAlpha; private int mViewFinderOffset; private float mAspectTolerance; + private static final int FOCUS_AREA_SIZE = 300; public BarcodeScannerView(Context context) { super(context); @@ -76,6 +76,7 @@ public abstract class BarcodeScannerView extends FrameLayout implements PreviewC this.mViewFinderView = this.createViewFinderView(this.getContext()); } + @SuppressLint("ClickableViewAccessibility") public final void setupLayout(CameraWrapper cameraWrapper) { this.removeAllViews(); this.mPreview = new CameraPreview(this.getContext(), cameraWrapper, this); diff --git a/app/src/main/java/com/nynja/mobile/communicator/utils/CameraPreview.java b/app/src/main/java/com/nynja/mobile/communicator/utils/CameraPreview.java new file mode 100644 index 0000000000..6ebcaee81b --- /dev/null +++ b/app/src/main/java/com/nynja/mobile/communicator/utils/CameraPreview.java @@ -0,0 +1,380 @@ +package com.nynja.mobile.communicator.utils; + +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.Camera; +import android.hardware.Camera.AutoFocusCallback; +import android.hardware.Camera.CameraInfo; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.PreviewCallback; +import android.hardware.Camera.Size; +import android.os.Handler; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Display; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceHolder.Callback; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import me.dm7.barcodescanner.core.DisplayUtils; + +public class CameraPreview extends SurfaceView implements Callback, View.OnTouchListener { + private static final String TAG = "CameraPreview"; + private CameraWrapper mCameraWrapper; + private Handler mAutoFocusHandler; + private boolean mPreviewing = true; + private boolean mAutoFocus = false; + private boolean mSurfaceCreated = false; + private boolean mShouldScaleToFill = true; + private PreviewCallback mPreviewCallback; + private float mAspectTolerance = 0.1F; + private static final int FOCUS_AREA_SIZE = 300; + private Runnable doAutoFocus = new Runnable() { + public void run() { + if (CameraPreview.this.mCameraWrapper != null && CameraPreview.this.mPreviewing && CameraPreview.this.mAutoFocus && CameraPreview.this.mSurfaceCreated) { + CameraPreview.this.safeAutoFocus(); + } + + } + }; + AutoFocusCallback autoFocusCB = new AutoFocusCallback() { + public void onAutoFocus(boolean success, Camera camera) { + CameraPreview.this.scheduleAutoFocus(); + } + }; + + public CameraPreview(Context context, CameraWrapper cameraWrapper, PreviewCallback previewCallback) { + super(context); + this.init(cameraWrapper, previewCallback); + } + + public CameraPreview(Context context, AttributeSet attrs, CameraWrapper cameraWrapper, PreviewCallback previewCallback) { + super(context, attrs); + this.init(cameraWrapper, previewCallback); + } + + public void init(CameraWrapper cameraWrapper, PreviewCallback previewCallback) { + this.setCamera(cameraWrapper, previewCallback); + this.mAutoFocusHandler = new Handler(); + this.getHolder().addCallback(this); + this.getHolder().setType(3); + } + + public void setCamera(CameraWrapper cameraWrapper, PreviewCallback previewCallback) { + this.mCameraWrapper = cameraWrapper; + this.mPreviewCallback = previewCallback; + } + + public void setShouldScaleToFill(boolean scaleToFill) { + this.mShouldScaleToFill = scaleToFill; + } + + public void setAspectTolerance(float aspectTolerance) { + this.mAspectTolerance = aspectTolerance; + } + + public void surfaceCreated(SurfaceHolder surfaceHolder) { + this.mSurfaceCreated = true; + } + + public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) { + if (surfaceHolder.getSurface() != null) { + this.stopCameraPreview(); + this.showCameraPreview(); + } + } + + public void surfaceDestroyed(SurfaceHolder surfaceHolder) { + this.mSurfaceCreated = false; + this.stopCameraPreview(); + } + + public void showCameraPreview() { + if (this.mCameraWrapper != null) { + try { + this.getHolder().addCallback(this); + this.mPreviewing = true; + this.setupCameraParameters(); + this.mCameraWrapper.mCamera.setPreviewDisplay(this.getHolder()); + this.mCameraWrapper.mCamera.setDisplayOrientation(this.getDisplayOrientation()); + this.mCameraWrapper.mCamera.setOneShotPreviewCallback(this.mPreviewCallback); + this.mCameraWrapper.mCamera.startPreview(); + if (this.mAutoFocus) { + if (this.mSurfaceCreated) { + this.safeAutoFocus(); + } else { + this.scheduleAutoFocus(); + } + } + } catch (Exception var2) { + Log.e("CameraPreview", var2.toString(), var2); + } + } + + } + + public void safeAutoFocus() { + try { + this.mCameraWrapper.mCamera.autoFocus(this.autoFocusCB); + } catch (RuntimeException var2) { + this.scheduleAutoFocus(); + } + + } + + public void stopCameraPreview() { + if (this.mCameraWrapper != null) { + try { + this.mPreviewing = false; + this.getHolder().removeCallback(this); + this.mCameraWrapper.mCamera.cancelAutoFocus(); + this.mCameraWrapper.mCamera.setOneShotPreviewCallback((PreviewCallback) null); + this.mCameraWrapper.mCamera.stopPreview(); + } catch (Exception var2) { + Log.e("CameraPreview", var2.toString(), var2); + } + } + + } + + public void setupCameraParameters() { + Size optimalSize = this.getOptimalPreviewSize(); + Parameters parameters = this.mCameraWrapper.mCamera.getParameters(); + parameters.setPreviewSize(optimalSize.width, optimalSize.height); + this.mCameraWrapper.mCamera.setParameters(parameters); + this.adjustViewSize(optimalSize); + } + + private void adjustViewSize(Size cameraSize) { + Point previewSize = this.convertSizeToLandscapeOrientation(new Point(this.getWidth(), this.getHeight())); + float cameraRatio = (float) cameraSize.width / (float) cameraSize.height; + float screenRatio = (float) previewSize.x / (float) previewSize.y; + if (screenRatio > cameraRatio) { + this.setViewSize((int) ((float) previewSize.y * cameraRatio), previewSize.y); + } else { + this.setViewSize(previewSize.x, (int) ((float) previewSize.x / cameraRatio)); + } + + } + + private Point convertSizeToLandscapeOrientation(Point size) { + return this.getDisplayOrientation() % 180 == 0 ? size : new Point(size.y, size.x); + } + + private void setViewSize(int width, int height) { + LayoutParams layoutParams = this.getLayoutParams(); + int tmpWidth; + int tmpHeight; + if (this.getDisplayOrientation() % 180 == 0) { + tmpWidth = width; + tmpHeight = height; + } else { + tmpWidth = height; + tmpHeight = width; + } + + if (this.mShouldScaleToFill) { + int parentWidth = ((View) this.getParent()).getWidth(); + int parentHeight = ((View) this.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((float) tmpWidth * compensation); + tmpHeight = Math.round((float) tmpHeight * compensation); + } + + layoutParams.width = tmpWidth; + layoutParams.height = tmpHeight; + this.setLayoutParams(layoutParams); + } + + public int getDisplayOrientation() { + if (this.mCameraWrapper == null) { + return 0; + } else { + CameraInfo info = new CameraInfo(); + if (this.mCameraWrapper.mCameraId == -1) { + Camera.getCameraInfo(0, info); + } else { + Camera.getCameraInfo(this.mCameraWrapper.mCameraId, info); + } + + WindowManager wm = (WindowManager) this.getContext().getSystemService("window"); + Display display = wm.getDefaultDisplay(); + int rotation = display.getRotation(); + int degrees = 0; + switch (rotation) { + case 0: + degrees = 0; + break; + case 1: + degrees = 90; + break; + case 2: + degrees = 180; + break; + case 3: + degrees = 270; + } + + int result; + if (info.facing == 1) { + result = (info.orientation + degrees) % 360; + result = (360 - result) % 360; + } else { + result = (info.orientation - degrees + 360) % 360; + } + + return result; + } + } + + private Size getOptimalPreviewSize() { + if (this.mCameraWrapper == null) { + return null; + } else { + List sizes = this.mCameraWrapper.mCamera.getParameters().getSupportedPreviewSizes(); + int w = this.getWidth(); + int h = this.getHeight(); + if (DisplayUtils.getScreenOrientation(this.getContext()) == 1) { + int portraitWidth = h; + h = w; + w = portraitWidth; + } + + double targetRatio = (double) w / (double) h; + if (sizes == null) { + return null; + } else { + Size optimalSize = null; + double minDiff = 1.7976931348623157E308D; + int targetHeight = h; + Iterator var10 = sizes.iterator(); + + Size size; + while (var10.hasNext()) { + size = (Size) var10.next(); + double ratio = (double) size.width / (double) size.height; + if (Math.abs(ratio - targetRatio) <= (double) this.mAspectTolerance && (double) Math.abs(size.height - targetHeight) < minDiff) { + optimalSize = size; + minDiff = (double) Math.abs(size.height - targetHeight); + } + } + + if (optimalSize == null) { + minDiff = 1.7976931348623157E308D; + var10 = sizes.iterator(); + + while (var10.hasNext()) { + size = (Size) var10.next(); + if ((double) Math.abs(size.height - targetHeight) < minDiff) { + optimalSize = size; + minDiff = (double) Math.abs(size.height - targetHeight); + } + } + } + + return optimalSize; + } + } + } + + public void setAutoFocus(boolean state) { + if (this.mCameraWrapper != null && this.mPreviewing) { + if (state == this.mAutoFocus) { + return; + } + + this.mAutoFocus = state; + if (this.mAutoFocus) { + if (this.mSurfaceCreated) { + Log.v("CameraPreview", "Starting autofocus"); + this.safeAutoFocus(); + } else { + this.scheduleAutoFocus(); + } + } else { + Log.v("CameraPreview", "Cancelling autofocus"); + this.mCameraWrapper.mCamera.cancelAutoFocus(); + } + } + + } + + private void scheduleAutoFocus() { + this.mAutoFocusHandler.postDelayed(this.doAutoFocus, 1000L); + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { + focusOnTouch(motionEvent); + } + return true; + } + + private void focusOnTouch(MotionEvent event) { + if (mCameraWrapper.mCamera != null) { + Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters(); + if (parameters.getMaxNumMeteringAreas() > 0) { + Rect rect = calculateFocusArea(event.getX(), event.getY()); + + parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); + List meteringAreas = new ArrayList<>(); + meteringAreas.add(new Camera.Area(rect, 800)); + parameters.setFocusAreas(meteringAreas); + + mCameraWrapper.mCamera.setParameters(parameters); + mCameraWrapper.mCamera.autoFocus(mAutoFocusTakePictureCallback); + } else { + mCameraWrapper.mCamera.autoFocus(mAutoFocusTakePictureCallback); + } + } + } + + private Rect calculateFocusArea(float x, float y) { + int left = clamp(Float.valueOf((x / this.getWidth()) * 2000 - 1000).intValue(), FOCUS_AREA_SIZE); + int top = clamp(Float.valueOf((y / this.getHeight()) * 2000 - 1000).intValue(), FOCUS_AREA_SIZE); + + return new Rect(left, top, left + FOCUS_AREA_SIZE, top + FOCUS_AREA_SIZE); + } + + private int clamp(int touchCoordinateInCameraReper, int focusAreaSize) { + int result; + if (Math.abs(touchCoordinateInCameraReper) + focusAreaSize / 2 > 1000) { + if (touchCoordinateInCameraReper > 0) { + result = 1000 - focusAreaSize / 2; + } else { + result = -1000 + focusAreaSize / 2; + } + } else { + result = touchCoordinateInCameraReper - focusAreaSize / 2; + } + return result; + } + + private Camera.AutoFocusCallback mAutoFocusTakePictureCallback = (success, camera) -> { + if (success) { + // do something... + Log.i("tap_to_focus", "success!"); + } else { + // do something... + Log.i("tap_to_focus", "fail!"); + } + }; +} diff --git a/app/src/main/java/com/nynja/mobile/communicator/utils/CameraWrapper.java b/app/src/main/java/com/nynja/mobile/communicator/utils/CameraWrapper.java new file mode 100644 index 0000000000..1f9273ff35 --- /dev/null +++ b/app/src/main/java/com/nynja/mobile/communicator/utils/CameraWrapper.java @@ -0,0 +1,22 @@ +package com.nynja.mobile.communicator.utils; + +import android.hardware.Camera; +import android.support.annotation.NonNull; + +public class CameraWrapper { + public final Camera mCamera; + public final int mCameraId; + + private CameraWrapper(@NonNull Camera camera, int cameraId) { + if(camera == null) { + throw new NullPointerException("Camera cannot be null"); + } else { + this.mCamera = camera; + this.mCameraId = cameraId; + } + } + + public static CameraWrapper getWrapper(Camera camera, int cameraId) { + return camera == null?null:new CameraWrapper(camera, cameraId); + } +} \ No newline at end of file -- GitLab