diff --git a/README.md b/README.md index e9b41ad..cfd22e6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Acuant Android SDK v11.4.8 +# Acuant Android SDK v11.4.9 **December 2020** See [https://github.com/Acuant/AndroidSDKV11/releases](https://github.com/Acuant/AndroidSDKV11/releases) for release notes. @@ -189,18 +189,18 @@ The SDK includes the following modules: - Add the following dependencies - implementation 'com.acuant:acuantcommon:11.4.8' - implementation 'com.acuant:acuantcamera:11.4.8' - implementation 'com.acuant:acuantimagepreparation:11.4.8' - implementation 'com.acuant:acuantdocumentprocessing:11.4.8' - implementation 'com.acuant:acuantechipreader:11.4.8' - implementation 'com.acuant:acuantfacematch:11.4.8' - implementation 'com.acuant:acuanthgliveness:11.4.8' - implementation ('com.acuant:acuantipliveness:11.4.8'){ + implementation 'com.acuant:acuantcommon:11.4.9' + implementation 'com.acuant:acuantcamera:11.4.9' + implementation 'com.acuant:acuantimagepreparation:11.4.9' + implementation 'com.acuant:acuantdocumentprocessing:11.4.9' + implementation 'com.acuant:acuantechipreader:11.4.9' + implementation 'com.acuant:acuantfacematch:11.4.9' + implementation 'com.acuant:acuanthgliveness:11.4.9' + implementation ('com.acuant:acuantipliveness:11.4.9'){ transitive = true } - implementation 'com.acuant:acuantfacecapture:11.4.8' - implementation 'com.acuant:acuantpassiveliveness:11.4.8' + implementation 'com.acuant:acuantfacecapture:11.4.9' + implementation 'com.acuant:acuantpassiveliveness:11.4.9' - Acuant also relies on Google Play services dependencies, which are pre-installed on almost all Android devices. diff --git a/acuantcamera/src/main/AndroidManifest.xml b/acuantcamera/src/main/AndroidManifest.xml index f7591c6..a2ba64a 100644 --- a/acuantcamera/src/main/AndroidManifest.xml +++ b/acuantcamera/src/main/AndroidManifest.xml @@ -23,7 +23,7 @@ android:screenOrientation="portrait" /> diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantBaseCameraFragment.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantBaseCameraFragment.kt index 69e1383..5abec71 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantBaseCameraFragment.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantBaseCameraFragment.kt @@ -40,7 +40,9 @@ import java.util.* import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit import kotlin.collections.ArrayList +import kotlin.math.abs import kotlin.math.max +import kotlin.math.min abstract class AcuantBaseCameraFragment : Fragment() { @@ -58,11 +60,12 @@ abstract class AcuantBaseCameraFragment : Fragment() { protected lateinit var textView: TextView protected lateinit var imageView: ImageView protected lateinit var detectors: List + private val previewBoundThreshold = 10 + protected var pointXOffset = 0 + protected var pointYOffset = 0 private lateinit var orientationListener: AcuantOrientationListener protected var oldPoints : Array? = null private lateinit var displaySize: Point - internal var targetSmallDocDpi: Int = 0 - internal var targetLargeDocDpi: Int = 0 internal var barCodeString: String? = null internal var isCapturing = false /** @@ -141,8 +144,6 @@ abstract class AcuantBaseCameraFragment : Fragment() { */ private var sensorOrientation = 0 - private val previewBoundThreshold = 25 - protected abstract fun setTextFromState(state: CameraState) override fun onCreate(savedInstanceState: Bundle?) { @@ -177,17 +178,23 @@ abstract class AcuantBaseCameraFragment : Fragment() { internal fun isDocumentInFrame(points: Array?) : Boolean{ - if(points != null){ - val startY = 0 //textureView.width.toFloat() / 2 - previewSize.height.toFloat() / 2 - val startX = 0 //textureView.height.toFloat() / 2 - previewSize.width.toFloat() / 2 - val endY = startY + displaySize.x //textureView.width - val endX = startX + displaySize.y //textureView.height + if (points != null) { + val minOffset = 0.025f + val startY = displaySize.x * minOffset//textureView.width + val startX = displaySize.y * minOffset //textureView.height.toFloat() / 2 - previewSize.width.toFloat() / 2 + val endY = displaySize.x * (1 - minOffset)//textureView.width + val endX = displaySize.y * (1 - minOffset)//textureView.height + +// Log.d("WTF", "start: $startX,$startY\tend: $endX,$endY") +// if (previewSize.width.toFloat()/displaySize.y < previewSize.height.toFloat()/displaySize.x) { +// endX = (previewSize.width * displaySize.x/previewSize.height.toFloat()).toInt() +// } else { +// endY = (previewSize.height * displaySize.y/previewSize.width.toFloat()).toInt() +// } +// Log.d("WTF", "start: $startX,$startY\tend: $endX,$endY") for (point in points) { - if( point.x < -previewBoundThreshold || - point.x > endX + previewBoundThreshold || - (textureView.width - point.y) < -previewBoundThreshold || - (textureView.width - point.y) > endY + previewBoundThreshold) { + if (point.x < startX || point.y < startY || point.x > endX || point.y > endY) { return false } } @@ -284,6 +291,7 @@ abstract class AcuantBaseCameraFragment : Fragment() { override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) { configureTransform(width, height) + textureView.requestLayout() } override fun onSurfaceTextureDestroyed(texture: SurfaceTexture) = true @@ -470,36 +478,49 @@ abstract class AcuantBaseCameraFragment : Fragment() { val maxPreviewWidth = if (swappedDimensions) displaySize.y else displaySize.x val maxPreviewHeight = if (swappedDimensions) displaySize.x else displaySize.y - val largestJpeg = Collections.max( + val bestJpeg = Collections.max( listOf(*map.getOutputSizes(ImageFormat.JPEG)), CompareSizesByArea()) +// val bestJpeg = chooseBestCaptureSize(listOf(*map.getOutputSizes(ImageFormat.JPEG)), maxPreviewWidth, maxPreviewHeight) + // Danger, W.R.! Attempting to use too large a preview size could exceed the camera // bus' bandwidth limitation, resulting in gorgeous previews but the storage of // garbage capture data. previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture::class.java), rotatedPreviewWidth, rotatedPreviewHeight, - maxPreviewWidth, maxPreviewHeight) - - targetSmallDocDpi = (previewSize.width * SMALL_DOC_DPI_SCALE_VALUE).toInt() - targetLargeDocDpi = (previewSize.width * LARGE_DOC_DPI_SCALE_VALUE).toInt() + maxPreviewWidth, maxPreviewHeight, bestJpeg) imageReader = ImageReader.newInstance(previewSize.width , previewSize.height, ImageFormat.YUV_420_888, /*maxImages*/ 3).apply { setOnImageAvailableListener(onFrameImageAvailableListener, backgroundHandler) } - captureImageReader = ImageReader.newInstance(largestJpeg.width, largestJpeg.height, + captureImageReader = ImageReader.newInstance(bestJpeg.width, bestJpeg.height, ImageFormat.JPEG, /*maxImages*/ 1).apply { setOnImageAvailableListener(onCaptureImageAvailableListener, backgroundHandler) } + val scaledWidth: Int + val scaledHeight: Int + + if (rotatedPreviewWidth/previewSize.width.toFloat() < rotatedPreviewHeight/previewSize.height.toFloat()) { + scaledWidth = rotatedPreviewWidth + scaledHeight = (previewSize.height * rotatedPreviewWidth/previewSize.width.toFloat()).toInt() + } else { + scaledWidth = (previewSize.width * rotatedPreviewHeight/previewSize.height.toFloat()).toInt() + scaledHeight = rotatedPreviewHeight + } + + pointXOffset = (rotatedPreviewWidth - scaledWidth)/2 + pointYOffset = (rotatedPreviewHeight - scaledHeight)/2 + // We fit the aspect ratio of TextureView to the size of preview we picked. if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { - textureView.setMax(maxPreviewWidth, maxPreviewHeight) + //textureView.setMax(maxPreviewWidth, maxPreviewHeight) textureView.setAspectRatio(previewSize.width, previewSize.height) } else { - textureView.setMax(maxPreviewHeight, maxPreviewWidth) + //textureView.setMax(maxPreviewHeight, maxPreviewWidth) textureView.setAspectRatio(previewSize.height, previewSize.width) } @@ -573,6 +594,7 @@ abstract class AcuantBaseCameraFragment : Fragment() { throw RuntimeException("Time out waiting to lock camera opening.") } manager.openCamera(cameraId, stateCallback, backgroundHandler) + textureView.requestLayout() } catch (e: CameraAccessException) { Log.e(TAG, e.toString()) } catch (e: InterruptedException) { @@ -606,7 +628,7 @@ abstract class AcuantBaseCameraFragment : Fragment() { */ private fun startBackgroundThread() { backgroundThread = HandlerThread("CameraBackground").also { it.start() } - backgroundHandler = Handler(backgroundThread?.looper) + backgroundHandler = Handler(backgroundThread?.looper ?: throw IllegalStateException("Background thread was null in a place where it can not/should not be null.")) } /** @@ -888,17 +910,30 @@ abstract class AcuantBaseCameraFragment : Fragment() { */ private const val STATE_PICTURE_TAKEN = 4 - /** - * Target DPI for preview size 1920x1080 = 350 - * SMALL_DOC_DPI_SCALE_VALUE = target_dpi/preview_size_width - */ - private const val SMALL_DOC_DPI_SCALE_VALUE = .18229 + private const val RATIO_TOLERANCE = 0.1f - /** - * Target DPI for preview size 1920x1080 = 225 - * LARGE_DOC_DPI_SCALE_VALUE = target_dpi/preview_size_width - */ - private const val LARGE_DOC_DPI_SCALE_VALUE = .11719 + @Suppress("unused") + @JvmStatic private fun chooseBestCaptureSize(sizes: List, screenWidth: Int, screenHeight: Int) : Size { + val targetSmallSide = min(screenHeight, screenWidth) + val targetLargeSide = max(screenHeight, screenWidth) + + val sortedSizes = sizes.sortedWith(CompareSizesByArea()) + + val minSize = sortedSizes[0].width * sortedSizes[0].height * 0.75f + + for (option in sortedSizes) { + + val currentSmallSide = min(option.height, option.width) + val currentLargeSide = max(option.height, option.width) + + if (abs(currentLargeSide.toFloat()/currentSmallSide - targetLargeSide.toFloat()/targetSmallSide) < RATIO_TOLERANCE && + currentLargeSide * currentSmallSide > minSize) { + return option + } + } + + return sortedSizes[0] + } /** * Given `choices` of `Size`s supported by a camera, choose the smallest one that @@ -920,19 +955,30 @@ abstract class AcuantBaseCameraFragment : Fragment() { textureViewWidth: Int, textureViewHeight: Int, maxWidth: Int, - maxHeight: Int + maxHeight: Int, + captureSize: Size ): Size { - + val targetRatio = captureSize.width.toFloat()/captureSize.height // Collect the supported resolutions that are at least as big as the preview Surface - val bigEnough = ArrayList() + val bigEnoughGood = ArrayList() + val bigEnoughBad = ArrayList() // Collect the supported resolutions that are smaller than the preview Surface - val notBigEnough = ArrayList() + val notBigEnoughGood = ArrayList() + val notBigEnoughBad = ArrayList() for (option in choices) { if (option.width <= maxWidth && option.height <= maxHeight ) { if (option.width >= textureViewWidth && option.height >= textureViewHeight) { - bigEnough.add(option) + if (abs(option.width.toFloat()/option.height - targetRatio) < RATIO_TOLERANCE) { + bigEnoughGood.add(option) + } else { + bigEnoughBad.add(option) + } } else { - notBigEnough.add(option) + if (abs(option.width.toFloat()/option.height - targetRatio) < RATIO_TOLERANCE) { + notBigEnoughGood.add(option) + } else { + notBigEnoughBad.add(option) + } } } } @@ -940,8 +986,10 @@ abstract class AcuantBaseCameraFragment : Fragment() { // Pick the smallest of those big enough. If there is no one big enough, pick the // largest of those not big enough. return when { - bigEnough.size > 0 -> Collections.min(bigEnough, CompareSizesByArea()) - notBigEnough.size > 0 -> Collections.max(notBigEnough, CompareSizesByArea()) + bigEnoughGood.size > 0 -> Collections.min(bigEnoughGood, CompareSizesByArea()) + notBigEnoughGood.size > 0 -> Collections.max(notBigEnoughGood, CompareSizesByArea()) + bigEnoughBad.size > 0 -> Collections.min(bigEnoughBad, CompareSizesByArea()) + notBigEnoughBad.size > 0 -> Collections.max(notBigEnoughBad, CompareSizesByArea()) else -> { Log.e(TAG, "Couldn't find any suitable preview size") choices[0] diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt index 53aeb6e..a192fea 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt @@ -128,7 +128,7 @@ class AcuantCameraActivity : AppCompatActivity(), ICameraActivityFinish { } else { val cameraIntent = Intent( this@AcuantCameraActivity, - com.acuant.acuantcamera.camera.mrz.cameraone.DocumentCaptureActivity::class.java + com.acuant.acuantcamera.camera.mrz.cameraone.MrzCaptureActivity::class.java ) cameraIntent.putExtra(ACUANT_EXTRA_CAMERA_OPTIONS, options) diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/AcuantDocCameraFragment.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/AcuantDocCameraFragment.kt index 000ad92..587fbdd 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/AcuantDocCameraFragment.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/AcuantDocCameraFragment.kt @@ -4,6 +4,7 @@ import android.graphics.* import android.graphics.drawable.Drawable import android.os.* import android.support.v4.app.ActivityCompat +import android.util.Size import android.util.SparseIntArray import android.view.* import com.acuant.acuantcamera.R @@ -15,9 +16,7 @@ import com.acuant.acuantcamera.detector.barcode.AcuantBarcodeDetectorHandler import com.acuant.acuantcamera.detector.document.AcuantDocumentDetectorHandler import com.acuant.acuantcamera.detector.document.AcuantDocumentDetector import com.acuant.acuantcamera.overlay.DocRectangleView -import kotlin.math.pow -import kotlin.math.roundToInt -import kotlin.math.sqrt +import kotlin.math.* class AcuantDocCameraFragment : AcuantBaseCameraFragment(), ActivityCompat.OnRequestPermissionsResultCallback, AcuantDocumentDetectorHandler, AcuantBarcodeDetectorHandler { @@ -52,31 +51,26 @@ class AcuantDocCameraFragment : AcuantBaseCameraFragment(), holdTextDrawable = activity!!.getDrawable(R.drawable.camera_text_config_hold) } - private fun getTargetDpi(isPassport : Boolean): Int{ - return if(isPassport){ - targetLargeDocDpi - } - else{ - targetSmallDocDpi - } - } - private fun scalePoints(points: Array) : Array { val scaledPoints = points.copyOf() val scaledPointY = textureView.height.toFloat() / previewSize.width.toFloat() val scaledPointX = textureView.width.toFloat() / previewSize.height.toFloat() rectangleView.setWidth(textureView.width.toFloat()) - scaledPoints.forEach { - it.x = (it.x * scaledPointY).toInt() - it.y = (it.y * scaledPointX).toInt() + points.apply { + this.forEach { + it.x = (it.x * scaledPointY).toInt() + it.y = (it.y * scaledPointX).toInt() + it.y -= pointYOffset + it.x += pointXOffset + } } return scaledPoints } private fun drawBorder(points: Array?){ - if(points != null){ + if(points != null) { rectangleView.setAndDrawPoints(points) } else{ @@ -132,7 +126,7 @@ class AcuantDocCameraFragment : AcuantBaseCameraFragment(), setTextFromState(CameraState.NotInFrame) resetTimer() } - croppedImage.dpi < getTargetDpi(croppedImage.isPassport) -> { + !isAcceptableDistance(detectedPoints, Size(textureView.width, textureView.height)) -> { unlockFocus() rectangleView.setViewFromState(CameraState.MoveCloser) setTextFromState(CameraState.MoveCloser) @@ -284,6 +278,24 @@ class AcuantDocCameraFragment : AcuantBaseCameraFragment(), internal const val TOO_SLOW_FOR_AUTO_THRESHOLD: Long = 130 + fun isAcceptableDistance(points: Array?, screenSize: Size): Boolean { + if (points != null) { + val shortSide = min(distance(points[0], points[1]), distance(points[0], points[3])) + val largeSide = max(distance(points[0], points[1]), distance(points[0], points[3])) + val screenShortSide = min(screenSize.width, screenSize.height).toFloat() + val screenLargeSide = max(screenSize.width, screenSize.height).toFloat() + + if (shortSide > 0.75 * screenShortSide || largeSide > 0.75 * screenLargeSide) { + return true + } + } + return false + } + + private fun distance(pointA: Point, pointB: Point): Float { + return sqrt( (pointA.x - pointB.x).toFloat().pow(2) + (pointA.y - pointB.y).toFloat().pow(2)) + } + /** * Conversion from screen rotation to JPEG orientation. */ diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSource.java b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSource.java index 5edf376..14e4786 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSource.java +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSource.java @@ -18,7 +18,6 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.WindowManager; - import com.google.android.gms.common.images.Size; import com.google.android.gms.vision.Detector; import com.google.android.gms.vision.Frame; @@ -330,15 +329,8 @@ public DocumentCameraSource start() throws IOException { mCamera = createCamera(); - // SurfaceTexture was introduced in Honeycomb (11), so if we are running and - // old version of Android. fall back to use SurfaceView. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mDummySurfaceTexture = new SurfaceTexture(DUMMY_TEXTURE_NAME); - mCamera.setPreviewTexture(mDummySurfaceTexture); - } else { - mDummySurfaceView = new SurfaceView(mContext); - mCamera.setPreviewDisplay(mDummySurfaceView.getHolder()); - } + mDummySurfaceTexture = new SurfaceTexture(DUMMY_TEXTURE_NAME); + mCamera.setPreviewTexture(mDummySurfaceTexture); mCamera.startPreview(); mProcessingThread = new Thread(mFrameProcessor); @@ -348,15 +340,8 @@ public DocumentCameraSource start() throws IOException { return this; } - /** - * Opens the camera and starts sending preview frames to the underlying detector. The supplied - * surface holder is used for the preview so frames can be displayed to the user. - * - * @param surfaceHolder the surface holder to use for the preview frames - * @throws IOException if the supplied surface holder could not be used as the preview display - */ @RequiresPermission(Manifest.permission.CAMERA) - public DocumentCameraSource start(SurfaceHolder surfaceHolder) throws IOException { + public DocumentCameraSource start(SurfaceHolder surfaceHolder, DocumentCameraSourcePreview preview) throws IOException { synchronized (mCameraLock) { if (mCamera != null) { return this; @@ -371,9 +356,15 @@ public DocumentCameraSource start(SurfaceHolder surfaceHolder) throws IOExceptio mCamera.setPreviewDisplay(surfaceHolder); mCamera.startPreview(); + if (preview != null) { + preview.requestLayout(); + } + mProcessingThread = new Thread(mFrameProcessor); mFrameProcessor.setActive(true); mProcessingThread.start(); + + } return this; } @@ -382,7 +373,7 @@ public DocumentCameraSource start(SurfaceHolder surfaceHolder) throws IOExceptio * Closes the camera and stops sending frames to the underlying frame detector. *

* This camera source may be restarted again by calling {@link #start()} or - * {@link #start(SurfaceHolder)}. + * {@link #start(SurfaceHolder, DocumentCameraSourcePreview)}. *

* Call {@link #release()} instead to completely shut down this camera source and release the * resources of the underlying detector. @@ -409,17 +400,7 @@ public void stop() { mCamera.stopPreview(); mCamera.setPreviewCallbackWithBuffer(null); try { - // We want to be compatible back to Gingerbread, but SurfaceTexture - // wasn't introduced until Honeycomb. Since the interface cannot use a SurfaceTexture, if the - // developer wants to display a preview we must use a SurfaceHolder. If the developer doesn't - // want to display a preview we use a SurfaceTexture if we are running at least Honeycomb. - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mCamera.setPreviewTexture(null); - - } else { - mCamera.setPreviewDisplay(null); - } + mCamera.setPreviewTexture(null); } catch (Exception e) { Log.e(TAG, "Failed to clear camera preview: " + e); } @@ -479,7 +460,7 @@ public int doZoom(float scale) { /** * Initiates taking a picture, which happens asynchronously. The camera source should have been - * activated previously with {@link #start()} or {@link #start(SurfaceHolder)}. The camera + * activated previously with {@link #start()} or {@link #start(SurfaceHolder, DocumentCameraSourcePreview)}. The camera * preview is suspended while the picture is being taken, but will resume once picture taking is * done. * @@ -584,7 +565,7 @@ public boolean setFlashMode(@FlashMode String mode) { /** * Starts camera auto-focus and registers a callback function to run when * the camera is focused. This method is only valid when preview is active - * (between {@link #start()} or {@link #start(SurfaceHolder)} and before {@link #stop()} or {@link #release()}). + * (between {@link #start()} or {@link #start(SurfaceHolder, DocumentCameraSourcePreview)} and before {@link #stop()} or {@link #release()}). *

*

Callers should check * {@link #getFocusMode()} to determine if @@ -794,6 +775,8 @@ private Camera createCamera() { camera.setParameters(parameters); + + // Four frame buffers are needed for working with the camera: // // one for the frame that is currently being executed upon in doing detection @@ -825,6 +808,41 @@ private static int getIdForRequestedCamera(int facing) { return -1; } +// private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { +// List validPreviewSizes = generateValidPreviewSizeList(camera); +// // The method for selecting the best size is to minimize the sum of the differences between +// // the desired values and the actual values for width and height. This is certainly not the +// // only way to select the best size, but it provides a decent tradeoff between using the +// // closest aspect ratio vs. using the closest pixel area. +// SizePair selectedPair = null; +// int maxArea = Integer.MIN_VALUE; +// int maxPict = Integer.MIN_VALUE; +// int area = Integer.MIN_VALUE; +// for (SizePair sizePair : validPreviewSizes) { +// Size size_pict = sizePair.pictureSize(); +// int newArea = size_pict.getWidth()*size_pict.getHeight(); +// if (newArea > maxPict) { +// maxPict = newArea; +// maxArea = Integer.MIN_VALUE; +// Size size = sizePair.previewSize(); +// area = size.getWidth()*size.getHeight(); +// if (maxArea < area) { +// selectedPair = sizePair; +// maxArea = area; +// } +// } +// else if (newArea == maxPict) { +// Size size = sizePair.previewSize(); +// area = size.getWidth()*size.getHeight(); +// if (maxArea < area) { +// selectedPair = sizePair; +// maxArea = area; +// } +// } +// } +// return selectedPair; +// } + /** * Selects the most suitable preview and picture size, given the desired width and height. *

@@ -838,9 +856,11 @@ private static int getIdForRequestedCamera(int facing) { * @param desiredHeight the desired height of the camera preview frames * @return the selected preview and picture size pair */ - /*private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { + private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { List validPreviewSizes = generateValidPreviewSizeList(camera); - + long expectedSize = desiredWidth*desiredHeight; + int desiredShortSide = Math.min(desiredHeight, desiredWidth); + int desiredLongSide = Math.max(desiredHeight, desiredWidth); // The method for selecting the best size is to minimize the sum of the differences between // the desired values and the actual values for width and height. This is certainly not the // only way to select the best size, but it provides a decent tradeoff between using the @@ -849,52 +869,57 @@ private static int getIdForRequestedCamera(int facing) { int minDiff = Integer.MAX_VALUE; for (SizePair sizePair : validPreviewSizes) { Size size = sizePair.previewSize(); - int diff = Math.abs(size.getWidth() - desiredWidth) + - Math.abs(size.getHeight() - desiredHeight); - if (diff < minDiff) { + + + int currentShortSide = Math.min(size.getWidth(), size.getHeight()); + int currentLongSide = Math.max(size.getWidth(), size.getHeight()); + float change = Math.min(Math.min((float) desiredShortSide / currentShortSide, (float) desiredLongSide / currentLongSide), 2f); + currentLongSide *= change; + currentShortSide *= change; + + + int diff = Math.abs(currentShortSide - desiredShortSide) + + Math.abs(currentLongSide - desiredLongSide); + if ((!(currentLongSide > desiredLongSide) && !(currentShortSide > desiredShortSide)) && diff < minDiff && (sizePair.pictureSize().getWidth()*sizePair.pictureSize().getHeight() >= expectedSize * 0.6f)) { selectedPair = sizePair; minDiff = diff; } } - return selectedPair; - }*/ - - private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { - List validPreviewSizes = generateValidPreviewSizeList(camera); - // The method for selecting the best size is to minimize the sum of the differences between - // the desired values and the actual values for width and height. This is certainly not the - // only way to select the best size, but it provides a decent tradeoff between using the - // closest aspect ratio vs. using the closest pixel area. - SizePair selectedPair = null; - int maxArea = Integer.MIN_VALUE; - int maxPict = Integer.MIN_VALUE; - int area = Integer.MIN_VALUE; - for (SizePair sizePair : validPreviewSizes) { - Size size_pict = sizePair.pictureSize(); - int newArea = size_pict.getWidth()*size_pict.getHeight(); - if (newArea > maxPict) { - maxPict = newArea; - maxArea = Integer.MIN_VALUE; - Size size = sizePair.previewSize(); - area = size.getWidth()*size.getHeight(); - if (maxArea < area) { - selectedPair = sizePair; - maxArea = area; - } - } - else if (newArea == maxPict) { - Size size = sizePair.previewSize(); - area = size.getWidth()*size.getHeight(); - if (maxArea < area) { - selectedPair = sizePair; - maxArea = area; - } - } + if (selectedPair != null) { + return selectedPair; + } else if (validPreviewSizes.size() > 0){ + return validPreviewSizes.get(0); + } else { + return null; } - return selectedPair; } +// private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { +// List validPreviewSizes = generateValidPreviewSizeList(camera); +// +// int desiredSize = desiredHeight * desiredWidth; +// float sizeRatio = 0.6f; +// +// SizePair selectedPair = null; +// +// do { +// float desiredRatio = (float) Math.max(desiredWidth, desiredHeight) / Math.min(desiredWidth, desiredHeight); +// float minDiff = Integer.MAX_VALUE; +// for (SizePair sizePair : validPreviewSizes) { +// Size size = sizePair.previewSize(); +// float diff = Math.abs((float) Math.max(size.getWidth(), size.getHeight()) / Math.min(size.getWidth(), size.getHeight()) - desiredRatio); +// if (diff < minDiff && size.getWidth()*size.getHeight() > desiredSize*sizeRatio) { +// selectedPair = sizePair; +// minDiff = diff; +// } +// } +// sizeRatio -= 0.1f; +// } while (selectedPair == null); +// +// return selectedPair; +// } + /** * Stores a preview size and a corresponding same-aspect-ratio picture size. To avoid distorted * preview images on some devices, the picture size must be set to a size that is the same diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSourcePreview.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSourcePreview.kt index 054208c..493c97a 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSourcePreview.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSourcePreview.kt @@ -15,6 +15,7 @@ */ package com.acuant.acuantcamera.camera.document.cameraone + import android.Manifest import android.content.Context import android.content.res.Configuration @@ -25,14 +26,14 @@ import android.util.Log import android.view.SurfaceHolder import android.view.SurfaceView import android.view.ViewGroup +import android.widget.RelativeLayout import com.acuant.acuantcamera.R -import com.acuant.acuantcamera.camera.mrz.cameraone.DocumentCameraSourcePreview - - import java.io.IOException class DocumentCameraSourcePreview(private val mContext: Context, attrs: AttributeSet?) : ViewGroup(mContext, attrs) { var mSurfaceView: SurfaceView + var pointXOffset: Int = 0 + var pointYOffset: Int = 0 private var mStartRequested: Boolean = false private var mSurfaceAvailable: Boolean = false private var documentCameraSource: DocumentCameraSource? = null @@ -55,7 +56,11 @@ class DocumentCameraSourcePreview(private val mContext: Context, attrs: Attribut mStartRequested = false mSurfaceAvailable = false + val previewParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.MATCH_PARENT) + previewParams.addRule(RelativeLayout.CENTER_IN_PARENT) mSurfaceView = SurfaceView(mContext) + mSurfaceView.layoutParams = previewParams mSurfaceView.holder.addCallback(SurfaceCallback()) addView(mSurfaceView) } @@ -92,7 +97,7 @@ class DocumentCameraSourcePreview(private val mContext: Context, attrs: Attribut @Throws(IOException::class, SecurityException::class) private fun startIfReady() { if (mStartRequested && mSurfaceAvailable) { - documentCameraSource!!.start(mSurfaceView.holder) + documentCameraSource!!.start(mSurfaceView.holder, this) mStartRequested = false } } @@ -124,48 +129,64 @@ class DocumentCameraSourcePreview(private val mContext: Context, attrs: Attribut } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {} + } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - var width = 320 - var height = 240 + + val viewWidth = right - left + val viewHeight = bottom - top + + var previewWidth = viewWidth + var previewHeight = viewHeight if (documentCameraSource != null) { - val size = documentCameraSource!!.previewSize + val size = documentCameraSource?.previewSize if (size != null) { - width = size.width - height = size.height + previewWidth = size.width + previewHeight = size.height } } // Swap width and height sizes when in portrait, since it will be rotated 90 degrees if (isPortraitMode) { - val tmp = width - - width = height - height = tmp + val tmp = previewWidth + previewWidth = previewHeight + previewHeight = tmp + } + val childWidth: Int + val childHeight: Int + val childXOffset: Int + val childYOffset: Int + val widthRatio = viewWidth.toFloat() / previewWidth.toFloat() + val heightRatio = viewHeight.toFloat() / previewHeight.toFloat() + + if (widthRatio < heightRatio) { + childWidth = viewWidth + childHeight = (previewHeight.toFloat() * widthRatio).toInt() + } else { + childWidth = (previewWidth.toFloat() * heightRatio).toInt() + childHeight = viewHeight } - val layoutWidth = right - left - val layoutHeight = bottom - top + childXOffset = (childWidth - viewWidth) / 2 + childYOffset = (childHeight - viewHeight) / 2 - // Computes height and width for potentially doing fit width. - var childWidth = layoutWidth - var childHeight = (layoutWidth.toFloat() / width.toFloat() * height).toInt() - - if (childHeight < layoutHeight) { - childHeight = layoutHeight - childWidth = (layoutHeight.toFloat() / height.toFloat() * width).toInt() - } + pointXOffset = childXOffset + pointYOffset = childYOffset for (i in 0 until childCount) { - getChildAt(i).layout(0, 0, childWidth, layoutHeight) + getChildAt(i).layout( + -1 * childXOffset, -1 * childYOffset, + childWidth - childXOffset, childHeight - childYOffset) + getChildAt(i).requestLayout() } try { startIfReady() - } catch (e: Exception) { + } catch (e: IOException) { e.printStackTrace() + } catch (se: SecurityException) { + se.printStackTrace() } - } companion object { diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCaptureActivity.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCaptureActivity.kt index 9354674..a8c6af5 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCaptureActivity.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCaptureActivity.kt @@ -8,38 +8,38 @@ import android.content.DialogInterface import android.content.Intent import android.content.pm.ActivityInfo import android.content.pm.PackageManager -import android.graphics.Color -import android.graphics.Point -import android.hardware.Camera -import android.os.Bundle -import android.util.Log -import android.util.Size -import android.widget.LinearLayout -import android.widget.RelativeLayout -import android.widget.TextView - -import java.io.IOException - import android.content.res.Configuration.ORIENTATION_LANDSCAPE import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Color +import android.graphics.Point import android.graphics.drawable.Drawable +import android.hardware.Camera +import android.os.Bundle import android.support.v4.app.ActivityCompat import android.support.v7.app.AlertDialog import android.support.v7.app.AppCompatActivity +import android.util.DisplayMetrics +import android.util.Log +import android.util.Size import android.view.* +import android.widget.RelativeLayout +import android.widget.TextView import com.acuant.acuantcamera.R +import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment.CameraState import com.acuant.acuantcamera.camera.AcuantCameraActivity import com.acuant.acuantcamera.camera.AcuantCameraOptions -import com.acuant.acuantcamera.constant.* -import java.io.ByteArrayOutputStream -import com.acuant.acuantcamera.overlay.DocRectangleView -import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment.CameraState import com.acuant.acuantcamera.camera.document.AcuantDocCameraFragment +import com.acuant.acuantcamera.constant.ACUANT_EXTRA_CAMERA_OPTIONS +import com.acuant.acuantcamera.constant.ACUANT_EXTRA_IMAGE_URL +import com.acuant.acuantcamera.constant.ACUANT_EXTRA_PDF417_BARCODE import com.acuant.acuantcamera.detector.ImageSaver +import com.acuant.acuantcamera.overlay.DocRectangleView +import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream +import java.io.IOException import java.util.* import kotlin.math.pow import kotlin.math.roundToInt @@ -90,35 +90,19 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT supportActionBar?.title = "" supportActionBar?.hide() - parent = RelativeLayout(this) - parent.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT) - parent.keepScreenOn = true - - setContentView(parent) - mPreview = DocumentCameraSourcePreview(this, null) - parent.addView(mPreview) capturingTextDrawable = this.getDrawable(R.drawable.camera_text_config_capturing) defaultTextDrawable = this.getDrawable(R.drawable.camera_text_config_default) holdTextDrawable = this.getDrawable(R.drawable.camera_text_config_hold) - val vfvp = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, - RelativeLayout.LayoutParams.WRAP_CONTENT) - rectangleView = DocRectangleView(this, null) - rectangleView.layoutParams = vfvp - parent.addView(rectangleView) - - // UI Customization - val tvlp = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT) - tvlp.addRule(RelativeLayout.CENTER_IN_PARENT) - instructionView = TextView(this) - - instructionView.setPadding(60, 15, 60, 15) - instructionView.gravity = Gravity.CENTER - instructionView.rotation = 90.0f - instructionView.layoutParams = tvlp + setContentView(R.layout.activity_acu_document_camera) + + parent = findViewById(R.id.cam1_doc_parent) + mPreview = findViewById(R.id.cam1_doc_preview) + rectangleView = findViewById(R.id.cam1_doc_rect) + instructionView = findViewById(R.id.cam1_doc_text) + setTextFromState(CameraState.Align) - parent.addView(instructionView, tvlp) // Check for the camera permission before accessing the camera. If the // permission is not granted yet, request permission. @@ -244,10 +228,15 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur // Creates and starts the camera. Note that this uses a higher resolution in comparison // to other detection examples to enable the barcode detector to detect small barcodes // at long distances. + val displayMetrics = DisplayMetrics() + windowManager.defaultDisplay.getMetrics(displayMetrics) + val height: Int = displayMetrics.heightPixels + val width: Int = displayMetrics.widthPixels + documentDetector = createDocumentDetector() var builder: DocumentCameraSource.Builder = DocumentCameraSource.Builder(applicationContext, documentDetector) .setFacing(DocumentCameraSource.CAMERA_FACING_BACK) - .setRequestedPreviewSize(1600, 1200) + .setRequestedPreviewSize(width, height) .setRequestedFps(60.0f) builder = builder.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) @@ -257,28 +246,49 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur .build() } - - - private fun isDocumentInPreviewFrame(points: Array): Boolean { - points.apply { - this.forEach { p -> - if (p.x < 0 || (mPreview!!.mSurfaceView.width - p.y) < 0 || p.x > displaySize.y || (mPreview!!.mSurfaceView.width - p.y) > displaySize.x) { + private fun isDocumentInPreviewFrame(points: Array, frameSize: Size): Boolean { + val minOffset = 0.01f + if (mPreview != null) { + val scaleX = mPreview!!.mSurfaceView.width / frameSize.height.toFloat() + val scaleY = mPreview!!.mSurfaceView.height / frameSize.width.toFloat() + + val startY = frameSize.height * minOffset * scaleY//textureView.width + val startX = frameSize.width * minOffset * scaleX //textureView.height.toFloat() / 2 - previewSize.width.toFloat() / 2 + val endY = frameSize.height * (1 - minOffset) * scaleY//textureView.width + val endX = frameSize.width * (1 - minOffset) * scaleX//textureView.height + +// Log.d("WTF", "start: $startX,$startY\tend: $endX,$endY") +// if (previewSize.width.toFloat()/displaySize.y < previewSize.height.toFloat()/displaySize.x) { +// endX = (previewSize.width * displaySize.x/previewSize.height.toFloat()).toInt() +// } else { +// endY = (previewSize.height * displaySize.y/previewSize.width.toFloat()).toInt() +// } +// Log.d("WTF", "start: $startX,$startY\tend: $endX,$endY") + + for (point in points) { + if (point.x < startX || point.y < startY || point.x > endX || point.y > endY) { return false } } + + return true } - return true + + return false } private fun scalePoints(points: Array, frameSize: Size): Array { val scaledPoints = points.copyOf() - val scaleX = mPreview!!.mSurfaceView.width / frameSize.height.toFloat() - val scaleY = mPreview!!.mSurfaceView.height / frameSize.width.toFloat() - - scaledPoints.apply { - this.forEach { p -> - p.x = (p.x * scaleY).toInt() - p.y = (p.y * scaleX).toInt() + if (mPreview != null) { + val scaleX = mPreview!!.mSurfaceView.width / frameSize.height.toFloat() + val scaleY = mPreview!!.mSurfaceView.height / frameSize.width.toFloat() + rectangleView.setWidth(mPreview!!.mSurfaceView.width.toFloat()) + + scaledPoints.forEach { + it.x = (it.x * scaleY).toInt() + it.y = (it.y * scaleX).toInt() + it.y += mPreview?.pointXOffset ?: 0 + it.x -= mPreview?.pointYOffset ?: 0 } } @@ -313,13 +323,12 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur var points = it.point val frameSize = it.frameSize!! var feedback = it.feedback - rectangleView.setWidth(mPreview!!.mSurfaceView.width.toFloat()) if (points != null && points.size == 4) { points = scalePoints(points, frameSize) points = fixPoints(points) - if (!isDocumentInPreviewFrame(points)) { + if (!isDocumentInPreviewFrame(points, frameSize)) { feedback = DocumentFeedback.NotInFrame } } @@ -498,7 +507,7 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur */ @Throws(SecurityException::class) private fun startCameraSource() { - if (documentCameraSource != null) { + if (documentCameraSource != null && mPreview != null) { try { mPreview!!.start(documentCameraSource) } catch (e: IOException) { diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/LiveDocumentProcessor.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/LiveDocumentProcessor.kt index da69f61..f1a5d16 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/LiveDocumentProcessor.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/LiveDocumentProcessor.kt @@ -4,7 +4,9 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.graphics.Bitmap +import android.util.Log import android.util.Size +import com.acuant.acuantcamera.camera.document.AcuantDocCameraFragment import com.acuant.acuantimagepreparation.AcuantImagePreparation import com.acuant.acuantimagepreparation.model.CroppingData @@ -43,7 +45,7 @@ class LiveDocumentProcessor : DocumentGraphicTracker.BarcodeUpdateListener { val hasLowStorage = context.registerReceiver(null, lowstorageFilter) != null if (hasLowStorage) { - + Log.d("Live Doc Processor", "Received low storage warning") } } provideFeedback() @@ -81,11 +83,11 @@ class LiveDocumentProcessor : DocumentGraphicTracker.BarcodeUpdateListener { val startTime = System.currentTimeMillis() val acuantImage = AcuantImagePreparation.detect(data) val elapsed = System.currentTimeMillis() - startTime - var feedback: AcuantDocumentFeedback? = null + var feedback: AcuantDocumentFeedback? - feedback = if (acuantImage?.points == null || acuantImage.dpi < 20) { + feedback = if (acuantImage.points == null || acuantImage.dpi < 20) { AcuantDocumentFeedback(DocumentFeedback.NoDocument, null, frameSize, null, elapsed) - } else if ((!acuantImage.isPassport && acuantImage.dpi < (SMALL_DOC_DPI_SCALE_VALUE * frame!!.width)) || (acuantImage.isPassport && acuantImage.dpi < (LARGE_DOC_DPI_SCALE_VALUE * frame!!.width))) { + } else if (!AcuantDocCameraFragment.isAcceptableDistance(acuantImage.points, frameSize)) { AcuantDocumentFeedback(DocumentFeedback.SmallDocument, acuantImage.points, frameSize, null, elapsed) } else if (!acuantImage.isCorrectAspectRatio) { AcuantDocumentFeedback(DocumentFeedback.BadDocument, acuantImage.points, frameSize, null, elapsed) @@ -109,17 +111,7 @@ class LiveDocumentProcessor : DocumentGraphicTracker.BarcodeUpdateListener { thread!!.start() } - /** - * Target DPI for preview size 1920x1080 = 350 - * SMALL_DOC_DPI_SCALE_VALUE = target_dpi/preview_size_width - */ - private val SMALL_DOC_DPI_SCALE_VALUE = .18229 - - /** - * Target DPI for preview size 1920x1080 = 225 - * LARGE_DOC_DPI_SCALE_VALUE = target_dpi/preview_size_width - */ - private val LARGE_DOC_DPI_SCALE_VALUE = .11719 + override fun onBarcodeDetected(barcode: Barcode?) { if(barcode?.rawValue != null){ feedbackListener?.let { it(AcuantDocumentFeedback(DocumentFeedback.Barcode, null, null, barcode.rawValue)) } diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/AcuantMrzCameraFragment.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/AcuantMrzCameraFragment.kt index 25c3473..470574b 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/AcuantMrzCameraFragment.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/AcuantMrzCameraFragment.kt @@ -46,10 +46,13 @@ class AcuantMrzCameraFragment : AcuantBaseCameraFragment(), ActivityCompat.OnReq val scaledPointY = textureView.height.toFloat() / previewSize.width.toFloat() val scaledPointX = textureView.width.toFloat() / previewSize.height.toFloat() rectangleView.setWidth(textureView.width.toFloat()) + points.apply { this.forEach { it.x = (it.x * scaledPointY).toInt() it.y = (it.y * scaledPointX).toInt() + it.y -= pointYOffset + it.x += pointXOffset } } diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/AcuantDocumentFeedback.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/AcuantMrzFeedback.kt similarity index 83% rename from acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/AcuantDocumentFeedback.kt rename to acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/AcuantMrzFeedback.kt index b8b1be4..18a2c44 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/AcuantDocumentFeedback.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/AcuantMrzFeedback.kt @@ -3,12 +3,12 @@ package com.acuant.acuantcamera.camera.mrz.cameraone import android.graphics.Point import android.util.Size -data class AcuantDocumentFeedback(val feedback: DocumentFeedback, val point: Array?, val frameSize: Size?, val barcode: String? = null) { +data class AcuantMrzFeedback(val feedback: MrzFeedback, val point: Array?, val frameSize: Size?, val barcode: String? = null) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - other as AcuantDocumentFeedback + other as AcuantMrzFeedback if (feedback != other.feedback) return false if (point != null) { diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/LiveDocumentProcessor.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/LiveMrzProcessor.kt similarity index 74% rename from acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/LiveDocumentProcessor.kt rename to acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/LiveMrzProcessor.kt index b16e848..88eb9d6 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/LiveDocumentProcessor.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/LiveMrzProcessor.kt @@ -2,15 +2,16 @@ package com.acuant.acuantcamera.camera.mrz.cameraone import android.content.Context import android.graphics.Bitmap +import android.util.Log import com.acuant.acuantcamera.detector.ocr.AcuantOcrDetector import com.google.android.gms.vision.MultiProcessor import com.google.android.gms.vision.barcode.Barcode import com.google.android.gms.vision.barcode.BarcodeDetector -class LiveDocumentProcessor : DocumentGraphicTracker.BarcodeUpdateListener { +class LiveMrzProcessor : MrzGraphicTracker.BarcodeUpdateListener { private var finishedCapturing = false - private var documentDetector: DocumentDetector? = null + private var mrzDetector: MrzDetector? = null private var ocrDetector: AcuantOcrDetector? = null var frame: Bitmap? = null @@ -18,31 +19,32 @@ class LiveDocumentProcessor : DocumentGraphicTracker.BarcodeUpdateListener { ocrDetector = detector } - fun getDetector(context: Context): DocumentDetector { + fun getDetector(context: Context): MrzDetector { val barcodeDetectorDelagte = BarcodeDetector.Builder(context).setBarcodeFormats(Barcode.PDF417).build() - val barcodeFactory = DocumentTrackerFactory(this) + val barcodeFactory = MrzTrackerFactory(this) val processor = MultiProcessor.Builder(barcodeFactory).build() - documentDetector = DocumentDetector(barcodeDetectorDelagte) - documentDetector!!.setProcessor(processor) + mrzDetector = MrzDetector(barcodeDetectorDelagte) + mrzDetector!!.setProcessor(processor) provideFeedback() - return documentDetector as DocumentDetector + return mrzDetector as MrzDetector } fun stop(){ finishedCapturing = true thread?.join() - documentDetector?.release() + mrzDetector?.release() thread = null - documentDetector = null + mrzDetector = null // thread = null } private var thread : Thread? = null private fun provideFeedback() { - if(ocrDetector == null) { + if (ocrDetector == null) { + Log.e("ocrDetector", "OCR detector can not be null") //TODO: return error } thread = Thread(object : Runnable { @@ -52,7 +54,7 @@ class LiveDocumentProcessor : DocumentGraphicTracker.BarcodeUpdateListener { while (flag) { if (!processing) { processing = true - frame = documentDetector?.frame + frame = mrzDetector?.frame if (frame != null) { ocrDetector?.detect(frame) diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCameraSource.java b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSource.java similarity index 92% rename from acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCameraSource.java rename to acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSource.java index 0bb9bf4..2ff2cf8 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCameraSource.java +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSource.java @@ -56,7 +56,7 @@ * */ @SuppressWarnings("deprecation") -public class DocumentCameraSource { +public class MrzCameraSource { @SuppressLint("InlinedApi") static final int CAMERA_FACING_BACK = CameraInfo.CAMERA_FACING_BACK; @SuppressLint("InlinedApi") @@ -157,7 +157,7 @@ public class DocumentCameraSource { */ public static class Builder { private final Detector mDetector; - private DocumentCameraSource documentCameraSource = new DocumentCameraSource(); + private MrzCameraSource mrzCameraSource = new MrzCameraSource(); /** * Creates a camera source builder with the supplied context and detector. Camera preview @@ -172,7 +172,7 @@ public Builder(Context context, Detector detector) { } mDetector = detector; - documentCameraSource.mContext = context; + mrzCameraSource.mContext = context; } /** @@ -183,19 +183,19 @@ public Builder setRequestedFps(float fps) { if (fps <= 0) { throw new IllegalArgumentException("Invalid fps: " + fps); } - documentCameraSource.mRequestedFps = fps; + mrzCameraSource.mRequestedFps = fps; return this; } public Builder setFocusMode(@FocusMode String mode) { - documentCameraSource.mFocusMode = mode; + mrzCameraSource.mFocusMode = mode; return this; } public Builder setFlashMode(@FlashMode String mode) { - documentCameraSource.mFlashMode = mode; + mrzCameraSource.mFlashMode = mode; return this; } @@ -213,8 +213,8 @@ public Builder setRequestedPreviewSize(int width, int height) { if ((width <= 0) || (width > MAX) || (height <= 0) || (height > MAX)) { throw new IllegalArgumentException("Invalid preview size: " + width + "x" + height); } - documentCameraSource.mRequestedPreviewWidth = width; - documentCameraSource.mRequestedPreviewHeight = height; + mrzCameraSource.mRequestedPreviewWidth = width; + mrzCameraSource.mRequestedPreviewHeight = height; return this; } @@ -226,16 +226,16 @@ public Builder setFacing(int facing) { if ((facing != CAMERA_FACING_BACK) && (facing != CAMERA_FACING_FRONT)) { throw new IllegalArgumentException("Invalid camera: " + facing); } - documentCameraSource.mFacing = facing; + mrzCameraSource.mFacing = facing; return this; } /** * Creates an instance of the camera source. */ - public DocumentCameraSource build() { - documentCameraSource.mFrameProcessor = documentCameraSource.new FrameProcessingRunnable(mDetector); - return documentCameraSource; + public MrzCameraSource build() { + mrzCameraSource.mFrameProcessor = mrzCameraSource.new FrameProcessingRunnable(mDetector); + return mrzCameraSource; } } @@ -324,7 +324,7 @@ public void release() { * @throws IOException if the camera's preview texture or display could not be initialized */ @RequiresPermission(Manifest.permission.CAMERA) - public DocumentCameraSource start() throws IOException { + public MrzCameraSource start() throws IOException { synchronized (mCameraLock) { if (mCamera != null) { return this; @@ -358,7 +358,7 @@ public DocumentCameraSource start() throws IOException { * @throws IOException if the supplied surface holder could not be used as the preview display */ @RequiresPermission(Manifest.permission.CAMERA) - public DocumentCameraSource start(SurfaceHolder surfaceHolder) throws IOException { + public MrzCameraSource start(SurfaceHolder surfaceHolder, MrzCameraSourcePreview preview) throws IOException { synchronized (mCameraLock) { if (mCamera != null) { return this; @@ -373,6 +373,10 @@ public DocumentCameraSource start(SurfaceHolder surfaceHolder) throws IOExceptio mCamera.setPreviewDisplay(surfaceHolder); mCamera.startPreview(); + if (preview != null) { + preview.requestLayout(); + } + mProcessingThread = new Thread(mFrameProcessor); mFrameProcessor.setActive(true); mProcessingThread.start(); @@ -384,7 +388,7 @@ public DocumentCameraSource start(SurfaceHolder surfaceHolder) throws IOExceptio * Closes the camera and stops sending frames to the underlying frame detector. *

* This camera source may be restarted again by calling {@link #start()} or - * {@link #start(SurfaceHolder)}. + * {@link #start(SurfaceHolder, MrzCameraSourcePreview)}. *

* Call {@link #release()} instead to completely shut down this camera source and release the * resources of the underlying detector. @@ -481,7 +485,7 @@ public int doZoom(float scale) { /** * Initiates taking a picture, which happens asynchronously. The camera source should have been - * activated previously with {@link #start()} or {@link #start(SurfaceHolder)}. The camera + * activated previously with {@link #start()} or {@link #start(SurfaceHolder, MrzCameraSourcePreview)}. The camera * preview is suspended while the picture is being taken, but will resume once picture taking is * done. * @@ -586,7 +590,7 @@ public boolean setFlashMode(@FlashMode String mode) { /** * Starts camera auto-focus and registers a callback function to run when * the camera is focused. This method is only valid when preview is active - * (between {@link #start()} or {@link #start(SurfaceHolder)} and before {@link #stop()} or {@link #release()}). + * (between {@link #start()} or {@link #start(SurfaceHolder, MrzCameraSourcePreview)} and before {@link #stop()} or {@link #release()}). *

*

Callers should check * {@link #getFocusMode()} to determine if @@ -663,7 +667,7 @@ public boolean setAutoFocusMoveCallback(@Nullable AutoFocusMoveCallback cb) { /** * Only allow creation via the builder class. */ - private DocumentCameraSource() { + private MrzCameraSource() { } /** @@ -827,6 +831,41 @@ private static int getIdForRequestedCamera(int facing) { return -1; } +// private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { +// List validPreviewSizes = generateValidPreviewSizeList(camera); +// // The method for selecting the best size is to minimize the sum of the differences between +// // the desired values and the actual values for width and height. This is certainly not the +// // only way to select the best size, but it provides a decent tradeoff between using the +// // closest aspect ratio vs. using the closest pixel area. +// SizePair selectedPair = null; +// int maxArea = Integer.MIN_VALUE; +// int maxPict = Integer.MIN_VALUE; +// int area = Integer.MIN_VALUE; +// for (SizePair sizePair : validPreviewSizes) { +// Size size_pict = sizePair.pictureSize(); +// int newArea = size_pict.getWidth()*size_pict.getHeight(); +// if (newArea > maxPict) { +// maxPict = newArea; +// maxArea = Integer.MIN_VALUE; +// Size size = sizePair.previewSize(); +// area = size.getWidth()*size.getHeight(); +// if (maxArea < area) { +// selectedPair = sizePair; +// maxArea = area; +// } +// } +// else if (newArea == maxPict) { +// Size size = sizePair.previewSize(); +// area = size.getWidth()*size.getHeight(); +// if (maxArea < area) { +// selectedPair = sizePair; +// maxArea = area; +// } +// } +// } +// return selectedPair; +// } + /** * Selects the most suitable preview and picture size, given the desired width and height. *

@@ -840,9 +879,11 @@ private static int getIdForRequestedCamera(int facing) { * @param desiredHeight the desired height of the camera preview frames * @return the selected preview and picture size pair */ - /*private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { + private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { List validPreviewSizes = generateValidPreviewSizeList(camera); - + long expectedSize = desiredWidth*desiredHeight; + int desiredShortSide = Math.min(desiredHeight, desiredWidth); + int desiredLongSide = Math.max(desiredHeight, desiredWidth); // The method for selecting the best size is to minimize the sum of the differences between // the desired values and the actual values for width and height. This is certainly not the // only way to select the best size, but it provides a decent tradeoff between using the @@ -851,52 +892,33 @@ private static int getIdForRequestedCamera(int facing) { int minDiff = Integer.MAX_VALUE; for (SizePair sizePair : validPreviewSizes) { Size size = sizePair.previewSize(); - int diff = Math.abs(size.getWidth() - desiredWidth) + - Math.abs(size.getHeight() - desiredHeight); - if (diff < minDiff) { + + + int currentShortSide = Math.min(size.getWidth(), size.getHeight()); + int currentLongSide = Math.max(size.getWidth(), size.getHeight()); + float change = Math.min(Math.min((float) desiredShortSide / currentShortSide, (float) desiredLongSide / currentLongSide), 2f); + currentLongSide *= change; + currentShortSide *= change; + + + int diff = Math.abs(currentShortSide - desiredShortSide) + + Math.abs(currentLongSide - desiredLongSide); + if ((!(currentLongSide > desiredLongSide) && !(currentShortSide > desiredShortSide)) && diff < minDiff && (sizePair.pictureSize().getWidth()*sizePair.pictureSize().getHeight() >= expectedSize * 0.6f)) { selectedPair = sizePair; minDiff = diff; } } - return selectedPair; - }*/ - - private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { - List validPreviewSizes = generateValidPreviewSizeList(camera); - // The method for selecting the best size is to minimize the sum of the differences between - // the desired values and the actual values for width and height. This is certainly not the - // only way to select the best size, but it provides a decent tradeoff between using the - // closest aspect ratio vs. using the closest pixel area. - SizePair selectedPair = null; - int maxArea = Integer.MIN_VALUE; - int maxPict = Integer.MIN_VALUE; - int area = Integer.MIN_VALUE; - for (SizePair sizePair : validPreviewSizes) { - Size size_pict = sizePair.pictureSize(); - int newArea = size_pict.getWidth()*size_pict.getHeight(); - if (newArea > maxPict) { - maxPict = newArea; - maxArea = Integer.MIN_VALUE; - Size size = sizePair.previewSize(); - area = size.getWidth()*size.getHeight(); - if (maxArea < area) { - selectedPair = sizePair; - maxArea = area; - } - } - else if (newArea == maxPict) { - Size size = sizePair.previewSize(); - area = size.getWidth()*size.getHeight(); - if (maxArea < area) { - selectedPair = sizePair; - maxArea = area; - } - } + if (selectedPair != null) { + return selectedPair; + } else if (validPreviewSizes.size() > 0){ + return validPreviewSizes.get(0); + } else { + return null; } - return selectedPair; } + /** * Stores a preview size and a corresponding same-aspect-ratio picture size. To avoid distorted * preview images on some devices, the picture size must be set to a size that is the same diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCameraSourcePreview.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSourcePreview.kt similarity index 65% rename from acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCameraSourcePreview.kt rename to acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSourcePreview.kt index 56bd376..2402c16 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCameraSourcePreview.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSourcePreview.kt @@ -30,11 +30,13 @@ import com.acuant.acuantcamera.R import java.io.IOException -class DocumentCameraSourcePreview(private val mContext: Context, attrs: AttributeSet?) : ViewGroup(mContext, attrs) { +class MrzCameraSourcePreview(private val mContext: Context, attrs: AttributeSet?) : ViewGroup(mContext, attrs) { var mSurfaceView: SurfaceView + var pointXOffset: Int = 0 + var pointYOffset: Int = 0 private var mStartRequested: Boolean = false private var mSurfaceAvailable: Boolean = false - private var documentCameraSource: DocumentCameraSource? = null + private var mrzCameraSource: MrzCameraSource? = null private val isPortraitMode: Boolean get() { @@ -61,29 +63,29 @@ class DocumentCameraSourcePreview(private val mContext: Context, attrs: Attribut @RequiresPermission(Manifest.permission.CAMERA) @Throws(IOException::class, SecurityException::class) - fun start(documentCameraSource: DocumentCameraSource?) { - if (documentCameraSource == null) { + fun start(mrzCameraSource: MrzCameraSource?) { + if (mrzCameraSource == null) { stop() } - this.documentCameraSource = documentCameraSource + this.mrzCameraSource = mrzCameraSource - if (this.documentCameraSource != null) { + if (this.mrzCameraSource != null) { mStartRequested = true startIfReady() } } fun stop() { - if (documentCameraSource != null) { - documentCameraSource!!.stop() + if (mrzCameraSource != null) { + mrzCameraSource!!.stop() } } fun release() { - if (documentCameraSource != null) { - documentCameraSource!!.release() - documentCameraSource = null + if (mrzCameraSource != null) { + mrzCameraSource!!.release() + mrzCameraSource = null } } @@ -91,7 +93,7 @@ class DocumentCameraSourcePreview(private val mContext: Context, attrs: Attribut @Throws(IOException::class, SecurityException::class) private fun startIfReady() { if (mStartRequested && mSurfaceAvailable) { - documentCameraSource!!.start(mSurfaceView.holder) + mrzCameraSource!!.start(mSurfaceView.holder, this) mStartRequested = false } } @@ -124,46 +126,60 @@ class DocumentCameraSourcePreview(private val mContext: Context, attrs: Attribut } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - var width = 320 - var height = 240 - if (documentCameraSource != null) { - val size = documentCameraSource!!.previewSize + + val viewWidth = right - left + val viewHeight = bottom - top + + var previewWidth = viewWidth + var previewHeight = viewHeight + if (mrzCameraSource != null) { + val size = mrzCameraSource?.previewSize if (size != null) { - width = size.width - height = size.height + previewWidth = size.width + previewHeight = size.height } } // Swap width and height sizes when in portrait, since it will be rotated 90 degrees if (isPortraitMode) { - val tmp = width - - width = height - height = tmp + val tmp = previewWidth + previewWidth = previewHeight + previewHeight = tmp } - val layoutWidth = right - left - val layoutHeight = bottom - top - - // Computes height and width for potentially doing fit width. - var childWidth = layoutWidth - var childHeight = (layoutWidth.toFloat() / width.toFloat() * height).toInt() - - if (childHeight < layoutHeight) { - childHeight = layoutHeight - childWidth = (layoutHeight.toFloat() / height.toFloat() * width).toInt() + val childWidth: Int + val childHeight: Int + val childXOffset: Int + val childYOffset: Int + val widthRatio = viewWidth.toFloat() / previewWidth.toFloat() + val heightRatio = viewHeight.toFloat() / previewHeight.toFloat() + + if (widthRatio < heightRatio) { + childWidth = viewWidth + childHeight = (previewHeight.toFloat() * widthRatio).toInt() + } else { + childWidth = (previewWidth.toFloat() * heightRatio).toInt() + childHeight = viewHeight } + childXOffset = (childWidth - viewWidth) / 2 + childYOffset = (childHeight - viewHeight) / 2 + + pointXOffset = childXOffset + pointYOffset = childYOffset for (i in 0 until childCount) { - getChildAt(i).layout(0, 0, childWidth, layoutHeight) + getChildAt(i).layout( + -1 * childXOffset, -1 * childYOffset, + childWidth - childXOffset, childHeight - childYOffset) + getChildAt(i).requestLayout() } - try { startIfReady() - } catch (e: Exception) { + } catch (e: IOException) { e.printStackTrace() + } catch (se: SecurityException) { + se.printStackTrace() } - } companion object { diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCaptureActivity.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCaptureActivity.kt similarity index 85% rename from acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCaptureActivity.kt rename to acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCaptureActivity.kt index 6d90c3e..3215453 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCaptureActivity.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCaptureActivity.kt @@ -14,13 +14,10 @@ import android.hardware.Camera import android.media.AudioManager import android.os.Bundle import android.util.Log -import android.view.Gravity import android.view.OrientationEventListener import android.view.View import android.view.Window import android.view.WindowManager -import android.widget.LinearLayout -import android.widget.RelativeLayout import android.widget.TextView import com.google.android.gms.common.ConnectionResult @@ -30,12 +27,12 @@ import java.io.IOException import android.content.res.Configuration.ORIENTATION_LANDSCAPE import android.content.res.Configuration.ORIENTATION_PORTRAIT -import android.graphics.Typeface import android.graphics.drawable.Drawable import android.os.Handler import android.support.v4.app.ActivityCompat import android.support.v7.app.AlertDialog import android.support.v7.app.AppCompatActivity +import android.util.DisplayMetrics import android.widget.ImageView import com.acuant.acuantcamera.R import com.acuant.acuantcamera.camera.* @@ -60,15 +57,15 @@ import kotlin.math.sqrt * rear facing camera. During detection overlay graphics are drawn to indicate the position, * size, and ID of each barcode. */ -class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.PictureCallback, DocumentCameraSource.ShutterCallback, AcuantOcrDetectorHandler { +class MrzCaptureActivity : AppCompatActivity(), MrzCameraSource.PictureCallback, MrzCameraSource.ShutterCallback, AcuantOcrDetectorHandler { - private var documentCameraSource: DocumentCameraSource? = null - private var mPreview: DocumentCameraSourcePreview? = null + private var mrzCameraSource: MrzCameraSource? = null + private var mPreview: MrzCameraSourcePreview? = null private var waitTime = 2 private var autoCapture = false - private lateinit var documentProcessor: LiveDocumentProcessor + private lateinit var mrzProcessor: LiveMrzProcessor private var permissionNotGranted = false - private var documentDetector: DocumentDetector? = null + private var mrzDetector: MrzDetector? = null private lateinit var instructionView: TextView private lateinit var imageView: ImageView @@ -76,7 +73,6 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur private var capturedbarcodeString: String? = null private lateinit var rectangleView: MrzRectangleView - private lateinit var tvlp: RelativeLayout.LayoutParams private var capturingTextDrawable: Drawable? = null private var defaultTextDrawable: Drawable? = null @@ -92,13 +88,16 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur runOnUiThread { if (points != null) { if (points.size == 4) { - val scaledPointY = mPreview!!.mSurfaceView.height.toFloat() / (documentDetector?.frame?.width?.toFloat() ?: mPreview!!.mSurfaceView.height.toFloat()) - val scaledPointX = mPreview!!.mSurfaceView.width.toFloat() / (documentDetector?.frame?.height?.toFloat() ?: mPreview!!.mSurfaceView.width.toFloat()) + val scaledPointY = mPreview!!.mSurfaceView.height.toFloat() / (mrzDetector?.frame?.width?.toFloat() ?: mPreview!!.mSurfaceView.height.toFloat()) + val scaledPointX = mPreview!!.mSurfaceView.width.toFloat() / (mrzDetector?.frame?.height?.toFloat() ?: mPreview!!.mSurfaceView.width.toFloat()) rectangleView.setWidth(mPreview!!.mSurfaceView.width.toFloat()) + points.apply { this.forEach { it.x = (it.x * scaledPointY).toInt() it.y = (it.y * scaledPointX).toInt() + it.y += mPreview?.pointXOffset ?: 0 + it.x -= mPreview?.pointYOffset ?: 0 } } @@ -222,49 +221,20 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT supportActionBar?.title = "" supportActionBar?.hide() - val parent = RelativeLayout(this) - parent.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT) - parent.keepScreenOn = true - - setContentView(parent) - mPreview = DocumentCameraSourcePreview(this, null) - parent.addView(mPreview) capturingTextDrawable = this.getDrawable(R.drawable.camera_text_config_capturing) defaultTextDrawable = this.getDrawable(R.drawable.camera_text_config_default) - val vfvp = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, - RelativeLayout.LayoutParams.WRAP_CONTENT) - rectangleView = MrzRectangleView(this, null) - rectangleView.layoutParams = vfvp - parent.addView(rectangleView) + setContentView(R.layout.activity_acu_mrz_camera) - setOptions(options) + mPreview = findViewById(R.id.cam1_mrz_preview) + rectangleView = findViewById(R.id.cam1_mrz_rect) + instructionView = findViewById(R.id.cam1_mrz_text) + imageView = findViewById(R.id.cam1_mrz_image) - // UI Customization - tvlp = RelativeLayout.LayoutParams(resources.getDimension(R.dimen.cam_error_width).toInt(), resources.getDimension(R.dimen.cam_mrz_height).toInt()) - tvlp.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE) - instructionView = TextView(this) - - instructionView.setPadding(60, 15, 60, 15) - instructionView.gravity = Gravity.CENTER - instructionView.rotation = 90.0f - instructionView.layoutParams = tvlp - instructionView.typeface = Typeface.MONOSPACE - instructionView.id = R.id.acu_display_text - - val tvlp2 = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, - RelativeLayout.LayoutParams.WRAP_CONTENT) - tvlp2.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE) - imageView = ImageView(this) - imageView.rotation = 90.0f - imageView.setImageResource(R.drawable.camera_overlay) - imageView.layoutParams = tvlp2 - imageView.id = R.id.acu_help_image + setOptions(options) setTextFromState(this, AcuantBaseCameraFragment.CameraState.Align, instructionView, imageView) - parent.addView(instructionView, tvlp) - parent.addView(imageView, tvlp2) // Check for the camera permission before accessing the camera. If the // permission is not granted yet, request permission. @@ -345,24 +315,29 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur // Creates and starts the camera. Note that this uses a higher resolution in comparison // to other detection examples to enable the barcode detector to detect small barcodes // at long distances. - documentDetector = createDocumentDetector() - var builder: DocumentCameraSource.Builder = DocumentCameraSource.Builder(applicationContext, documentDetector) - .setFacing(DocumentCameraSource.CAMERA_FACING_BACK) - .setRequestedPreviewSize(1600, 1200) + val displayMetrics = DisplayMetrics() + windowManager.defaultDisplay.getMetrics(displayMetrics) + val height: Int = displayMetrics.heightPixels + val width: Int = displayMetrics.widthPixels + + mrzDetector = createDocumentDetector() + var builder: MrzCameraSource.Builder = MrzCameraSource.Builder(applicationContext, mrzDetector) + .setFacing(MrzCameraSource.CAMERA_FACING_BACK) + .setRequestedPreviewSize(width, height) .setRequestedFps(60.0f) // make sure that auto focus is an available option builder = builder.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) - documentCameraSource = builder + mrzCameraSource = builder .setFlashMode(if (useFlash) Camera.Parameters.FLASH_MODE_TORCH else Camera.Parameters.FLASH_MODE_OFF) .build() } - private fun createDocumentDetector(): DocumentDetector { - documentProcessor = LiveDocumentProcessor() - documentProcessor.setOcrDetector(AcuantOcrDetector(this, this)) - return documentProcessor.getDetector(applicationContext) + private fun createDocumentDetector(): MrzDetector { + mrzProcessor = LiveMrzProcessor() + mrzProcessor.setOcrDetector(AcuantOcrDetector(this, this)) + return mrzProcessor.getDetector(applicationContext) } @@ -398,7 +373,7 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur override fun onDestroy() { super.onDestroy() if (!permissionNotGranted) { - documentProcessor.stop() + mrzProcessor.stop() } if (mPreview != null) { mPreview!!.release() @@ -466,19 +441,19 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur dlg.show() } - if (documentCameraSource != null) { + if (mrzCameraSource != null) { try { - mPreview!!.start(documentCameraSource) + mPreview!!.start(mrzCameraSource) } catch (e: IOException) { Log.e(TAG, "Unable to start camera source.", e) - documentCameraSource!!.release() - documentCameraSource = null + mrzCameraSource!!.release() + mrzCameraSource = null } } } override fun onBackPressed() { - this@DocumentCaptureActivity.finish() + this@MrzCaptureActivity.finish() } private fun onChanged(@Suppress("UNUSED_PARAMETER") lastOrientation: Int, curOrientation: Int) { @@ -507,7 +482,7 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur Thread(Runnable { val mgr = getSystemService(Context.AUDIO_SERVICE) as AudioManager mgr.setStreamMute(AudioManager.STREAM_SYSTEM, false) - this@DocumentCaptureActivity.runOnUiThread { + this@MrzCaptureActivity.runOnUiThread { val result = Intent() val file = File(this.getExternalFilesDir(null), "${UUID.randomUUID()}.jpg") saveFile(file, data) diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentDetector.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzDetector.kt similarity index 94% rename from acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentDetector.kt rename to acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzDetector.kt index c298baa..35fad70 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentDetector.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzDetector.kt @@ -14,7 +14,7 @@ import com.google.android.gms.vision.barcode.Barcode import java.io.ByteArrayOutputStream -class DocumentDetector(private val mDelegate: Detector) : Detector() { +class MrzDetector(private val mDelegate: Detector) : Detector() { var frame: Bitmap? = null private set diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentFeedback.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzFeedback.kt similarity index 82% rename from acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentFeedback.kt rename to acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzFeedback.kt index 0a87fcd..495f1b2 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentFeedback.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzFeedback.kt @@ -1,6 +1,6 @@ package com.acuant.acuantcamera.camera.mrz.cameraone -enum class DocumentFeedback { +enum class MrzFeedback { NoDocument, SmallDocument, BadDocument, diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentGraphicTracker.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzGraphicTracker.kt similarity index 95% rename from acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentGraphicTracker.kt rename to acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzGraphicTracker.kt index f21134a..8e2f871 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentGraphicTracker.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzGraphicTracker.kt @@ -26,7 +26,7 @@ import com.google.android.gms.vision.barcode.Barcode * to an overlay, update the graphics as the item changes, and remove the graphics when the item * goes away. */ -internal class DocumentGraphicTracker(private val mBarcodeUpdateListener: BarcodeUpdateListener) : Tracker() { +internal class MrzGraphicTracker(private val mBarcodeUpdateListener: BarcodeUpdateListener) : Tracker() { /** * Consume the item instance detected from an Activity or Fragment level by implementing the diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentTrackerFactory.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzTrackerFactory.kt similarity index 85% rename from acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentTrackerFactory.kt rename to acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzTrackerFactory.kt index b66750d..01cce28 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentTrackerFactory.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzTrackerFactory.kt @@ -24,10 +24,10 @@ import com.google.android.gms.vision.barcode.Barcode * Factory for creating a tracker and associated graphic to be associated with a new barcode. The * multi-processor uses this factory to create barcode trackers as needed -- one for each barcode. */ -internal class DocumentTrackerFactory(private val listener: DocumentGraphicTracker.BarcodeUpdateListener) : MultiProcessor.Factory { +internal class MrzTrackerFactory(private val listener: MrzGraphicTracker.BarcodeUpdateListener) : MultiProcessor.Factory { override fun create(barcode: Barcode): Tracker { - return DocumentGraphicTracker(listener) + return MrzGraphicTracker(listener) } } diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/AutoFitTextureView.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/AutoFitTextureView.kt index 9dbc678..ece4676 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/AutoFitTextureView.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/AutoFitTextureView.kt @@ -3,7 +3,6 @@ package com.acuant.acuantcamera.helper import android.content.Context import android.util.AttributeSet import android.view.TextureView -import android.view.View /** * A [TextureView] that can be adjusted to a specified aspect ratio. @@ -14,10 +13,9 @@ class AutoFitTextureView @JvmOverloads constructor( defStyle: Int = 0 ) : TextureView(context, attrs, defStyle) { - private var ratioWidth = 0 - private var ratioHeight = 0 - private var maxWidth = 0 - private var maxHeight = 0 + private var mRatioWidth = 0 + private var mRatioHeight = 0 + /** * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio * calculated from the parameters. Note that the actual sizes of parameters don't matter, that @@ -27,38 +25,24 @@ class AutoFitTextureView @JvmOverloads constructor( * @param height Relative vertical size */ fun setAspectRatio(width: Int, height: Int) { - if (width < 0 || height < 0) { - throw IllegalArgumentException("Size cannot be negative.") - } - ratioWidth = width - ratioHeight = height + require(!(width < 0 || height < 0)) { "Size cannot be negative." } + mRatioWidth = width + mRatioHeight = height requestLayout() } - fun setMax(width: Int, height: Int) { - if (width < 0 || height < 0) { - throw IllegalArgumentException("Size cannot be negative.") - } - maxWidth = width - maxHeight = height - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) - val width = View.MeasureSpec.getSize(widthMeasureSpec) - val height = View.MeasureSpec.getSize(heightMeasureSpec) - if (ratioWidth == 0 || ratioHeight == 0) { + val width = MeasureSpec.getSize(widthMeasureSpec) + val height = MeasureSpec.getSize(heightMeasureSpec) + if (0 == mRatioWidth || 0 == mRatioHeight) { setMeasuredDimension(width, height) - } - else { - if((height * ratioWidth / ratioHeight) <= maxWidth ){ - setMeasuredDimension(width, (width * ratioHeight/ratioWidth.toFloat()).toInt()) - } - else{ - setMeasuredDimension((height * ratioWidth / ratioHeight.toFloat()).toInt(), height) + } else { + if (width/mRatioWidth.toFloat() < height/mRatioHeight.toFloat()) { + setMeasuredDimension(width, (mRatioHeight * width/mRatioWidth.toFloat()).toInt()) + } else { + setMeasuredDimension((mRatioWidth * height/mRatioHeight.toFloat()).toInt(), height) } } } - } diff --git a/acuantcamera/src/main/res/layout/activity_acu_document_camera.xml b/acuantcamera/src/main/res/layout/activity_acu_document_camera.xml new file mode 100644 index 0000000..5db4937 --- /dev/null +++ b/acuantcamera/src/main/res/layout/activity_acu_document_camera.xml @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/acuantcamera/src/main/res/layout/activity_acu_mrz_camera.xml b/acuantcamera/src/main/res/layout/activity_acu_mrz_camera.xml new file mode 100644 index 0000000..c0668b4 --- /dev/null +++ b/acuantcamera/src/main/res/layout/activity_acu_mrz_camera.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + diff --git a/acuantcamera/src/main/res/layout/fragment_camera2_basic.xml b/acuantcamera/src/main/res/layout/fragment_camera2_basic.xml index 3e06a4c..97a60a7 100644 --- a/acuantcamera/src/main/res/layout/fragment_camera2_basic.xml +++ b/acuantcamera/src/main/res/layout/fragment_camera2_basic.xml @@ -1,4 +1,5 @@ -