diff --git a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraXState.kt b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraXState.kt index efd01954..216f825d 100644 --- a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraXState.kt +++ b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraXState.kt @@ -2,6 +2,7 @@ package com.apparence.camerawesome.cameraX import android.annotation.SuppressLint import android.app.Activity +import android.graphics.ImageFormat import android.hardware.camera2.CameraCharacteristics import android.util.Log import android.util.Rational @@ -11,6 +12,8 @@ import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat import androidx.camera.camera2.internal.compat.quirk.CamcorderProfileResolutionQuirk import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.core.* +import androidx.camera.core.resolutionselector.AspectRatioStrategy +import androidx.camera.core.resolutionselector.ResolutionSelector import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.video.* import androidx.core.content.ContextCompat @@ -125,13 +128,20 @@ data class CameraXState( // }) // .build() - - val preview = if (aspectRatio != null) { - Preview.Builder().setTargetAspectRatio(aspectRatio!!) - .setCameraSelector(cameraSelector).build() + val resolutionSelector = if (aspectRatio != null){ + ResolutionSelector.Builder() + .setAspectRatioStrategy( + AspectRatioStrategy(aspectRatio!!, AspectRatioStrategy.FALLBACK_RULE_AUTO) + ) + .build() } else { - Preview.Builder().setCameraSelector(cameraSelector).build() + ResolutionSelector.Builder() + .setAllowedResolutionMode(ResolutionSelector.PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION) + .build() } + val preview = Preview.Builder() + .setResolutionSelector(resolutionSelector) + .setCameraSelector(cameraSelector).build() preview.setSurfaceProvider( surfaceProvider(executor(activity), sensor.deviceId ?: "$index") ) @@ -144,7 +154,12 @@ data class CameraXState( .apply { //photoSize?.let { setTargetResolution(it) } if (rational.denominator != rational.numerator) { - setTargetAspectRatio(aspectRatio ?: AspectRatio.RATIO_4_3) + val resolutionSelector = ResolutionSelector.Builder(). + setAspectRatioStrategy( + AspectRatioStrategy(aspectRatio ?: AspectRatio.RATIO_4_3, AspectRatioStrategy.FALLBACK_RULE_AUTO) + ) + .build() + setResolutionSelector(resolutionSelector) } setFlashMode( @@ -196,13 +211,20 @@ data class CameraXState( if (sensors.first().position == PigeonSensorPosition.FRONT) CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA // Preview if (currentCaptureMode != CaptureModes.ANALYSIS_ONLY) { + val resolutionSelector = if (aspectRatio != null){ + ResolutionSelector.Builder() + .setAspectRatioStrategy( + AspectRatioStrategy(aspectRatio!!, AspectRatioStrategy.FALLBACK_RULE_AUTO) + ) + .build() + } else { + ResolutionSelector.Builder() + .setAllowedResolutionMode(ResolutionSelector.PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION) + .build() + } previews!!.add( - if (aspectRatio != null) { - Preview.Builder().setTargetAspectRatio(aspectRatio!!) - .setCameraSelector(cameraSelector).build() - } else { - Preview.Builder().setCameraSelector(cameraSelector).build() - } + Preview.Builder().setResolutionSelector(resolutionSelector) + .setCameraSelector(cameraSelector).build() ) previews!!.first().setSurfaceProvider( @@ -217,7 +239,12 @@ data class CameraXState( .apply { //photoSize?.let { setTargetResolution(it) } if (rational.denominator != rational.numerator) { - setTargetAspectRatio(aspectRatio ?: AspectRatio.RATIO_4_3) + val resolutionSelector = ResolutionSelector.Builder(). + setAspectRatioStrategy( + AspectRatioStrategy(aspectRatio ?: AspectRatio.RATIO_4_3, AspectRatioStrategy.FALLBACK_RULE_AUTO) + ) + .build() + setResolutionSelector(resolutionSelector) } setFlashMode( when (flashMode) { @@ -260,12 +287,14 @@ data class CameraXState( .build() concurrentCamera = null + val useCaseGroup : UseCaseGroup = useCaseGroupBuilder.build() previewCamera = cameraProvider.bindToLifecycle( activity as LifecycleOwner, cameraSelector, - useCaseGroupBuilder.build(), + useCaseGroup, ) previewCamera!!.cameraControl.enableTorch(flashMode == FlashMode.ALWAYS) + } } @@ -442,5 +471,8 @@ data class CameraXState( "RATIO_1_1" -> Rational(1, 1) else -> Rational(3, 4) } + if (imageAnalysisBuilder != null){ + imageAnalysisBuilder!!.updateAspectRatio(newAspectRatio) + } } } \ No newline at end of file diff --git a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/ImageAnalysisBuilder.kt b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/ImageAnalysisBuilder.kt index 15f012b1..48ad069c 100644 --- a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/ImageAnalysisBuilder.kt +++ b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/ImageAnalysisBuilder.kt @@ -3,10 +3,15 @@ package com.apparence.camerawesome.cameraX import android.annotation.SuppressLint import android.graphics.Rect import android.util.Size +import android.util.Log import androidx.camera.core.AspectRatio import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy +import androidx.camera.core.resolutionselector.AspectRatioStrategy +import androidx.camera.core.resolutionselector.ResolutionSelector +import androidx.camera.core.resolutionselector.ResolutionStrategy import androidx.camera.core.internal.utils.ImageUtil +import android.hardware.camera2.params.StreamConfigurationMap import com.apparence.camerawesome.utils.ResettableCountDownLatch import io.flutter.plugin.common.EventChannel import kotlinx.coroutines.* @@ -21,7 +26,8 @@ enum class OutputImageFormat { class ImageAnalysisBuilder private constructor( private val format: OutputImageFormat, private val width: Int, - private val height: Int, + private var height: Int, + private var aspectRatio: Int, private val executor: Executor, var previewStreamSink: EventChannel.EventSink? = null, private val maxFramesPerSecond: Double?, @@ -54,6 +60,7 @@ class ImageAnalysisBuilder private constructor( format, widthOrDefault, height.toInt(), + aspectRatio, executor, maxFramesPerSecond = maxFps, ) @@ -63,7 +70,15 @@ class ImageAnalysisBuilder private constructor( @SuppressLint("RestrictedApi") fun build(): ImageAnalysis { countDownLatch.reset() - val imageAnalysis = ImageAnalysis.Builder().setTargetResolution(Size(width, height)) + val imageAnalysisResolutionSelector = ResolutionSelector.Builder() + .setAspectRatioStrategy( + AspectRatioStrategy(aspectRatio, AspectRatioStrategy.FALLBACK_RULE_AUTO) + ) + .setResolutionStrategy( + ResolutionStrategy(Size(width, height), ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER) + ) + .build() + val imageAnalysis = ImageAnalysis.Builder().setResolutionSelector(imageAnalysisResolutionSelector) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888).build() imageAnalysis.setAnalyzer(Dispatchers.IO.asExecutor()) { imageProxy -> @@ -120,6 +135,15 @@ class ImageAnalysisBuilder private constructor( return imageAnalysis } + fun updateAspectRatio(newAspectRatio: String){ + aspectRatio = if (newAspectRatio == "RATIO_16_9") 1 else 0 + val analysisAspectRatio = when (aspectRatio) { + AspectRatio.RATIO_4_3 -> 4f / 3 + else -> 16f / 9 + } + height = (width * (1 / analysisAspectRatio)).toInt() + } + private fun cropRect(imageProxy: ImageProxy): Map { return mapOf( "left" to imageProxy.cropRect.left, diff --git a/example/lib/preview_overlay_example.dart b/example/lib/preview_overlay_example.dart index c9e23d93..2e2e4e80 100644 --- a/example/lib/preview_overlay_example.dart +++ b/example/lib/preview_overlay_example.dart @@ -48,6 +48,7 @@ class _CameraPageState extends State { aspectRatio: CameraAspectRatios.ratio_16_9, ), previewFit: CameraPreviewFit.fitWidth, + previewAlignment: Alignment.center, onMediaTap: (mediaCapture) { mediaCapture.captureRequest .when(single: (single) => single.file?.open()); diff --git a/example/lib/widgets/barcode_preview_overlay.dart b/example/lib/widgets/barcode_preview_overlay.dart index a54bd90e..8c55ffac 100644 --- a/example/lib/widgets/barcode_preview_overlay.dart +++ b/example/lib/widgets/barcode_preview_overlay.dart @@ -23,7 +23,6 @@ class BarcodePreviewOverlay extends StatefulWidget { } class _BarcodePreviewOverlayState extends State { - late Size _screenSize; late Rect _scanArea; // The barcode that is currently in the scan area (one at a time) @@ -64,7 +63,7 @@ class _BarcodePreviewOverlayState extends State { // including the clipping that may be needed to respect the current // aspectRatio. _scanArea = Rect.fromCenter( - center: widget.preview.rect.center, + center: widget.preview.rect.center + widget.preview.offset, // In this example, we want the barcode scan area to be a fraction // of the preview that is seen by the user, so we use previewRect width: widget.preview.rect.width * 0.7, @@ -74,8 +73,6 @@ class _BarcodePreviewOverlayState extends State { @override Widget build(BuildContext context) { - _screenSize = MediaQuery.of(context).size; - return IgnorePointer( ignoring: true, child: Stack(children: [ @@ -163,10 +160,7 @@ class _BarcodePreviewOverlayState extends State { // Approximately detect if the barcode is in the scan area by checking // if the center of the barcode is in the scan area. if (_scanArea.contains( - _barcodeRect!.center.translate( - (_screenSize.width - widget.preview.previewSize.width) / 2, - (_screenSize.height - widget.preview.previewSize.height) / 2, - ), + _barcodeRect!.center, )) { // Note: for a better detection, you should calculate the area of the // intersection between the barcode and the scan area and compare it diff --git a/lib/src/orchestrator/analysis/analysis_to_image.dart b/lib/src/orchestrator/analysis/analysis_to_image.dart index c7d76dce..941d6606 100644 --- a/lib/src/orchestrator/analysis/analysis_to_image.dart +++ b/lib/src/orchestrator/analysis/analysis_to_image.dart @@ -39,29 +39,29 @@ class Preview { AnalysisImage img, { bool? flipXY, }) { - num imageDiffX; - num imageDiffY; + num imageStretchX; + num imageStretchY; + num imgToNativeScaleX; + num imgToNativeScaleY; final shouldflipXY = flipXY ?? img.flipXY(); if (Platform.isIOS) { - imageDiffX = img.size.width - img.croppedSize.width; - imageDiffY = img.size.height - img.croppedSize.height; + imageStretchX = img.size.width / img.croppedSize.width; + imageStretchY = img.size.height / img.croppedSize.height; + imgToNativeScaleX = nativePreviewSize.width / img.croppedSize.width; + imgToNativeScaleY = nativePreviewSize.height / img.croppedSize.height; } else { // Width and height are inverted on Android - imageDiffX = img.size.height - img.croppedSize.width; - imageDiffY = img.size.width - img.croppedSize.height; + imageStretchX = img.size.height / img.croppedSize.width; + imageStretchY = img.size.width / img.croppedSize.height; + imgToNativeScaleX = nativePreviewSize.width / img.croppedSize.width; + imgToNativeScaleY = nativePreviewSize.height / img.croppedSize.height; } - var offset = (Offset( - (shouldflipXY ? point.dy : point.dx).toDouble() - - (imageDiffX / 2), - (shouldflipXY ? point.dx : point.dy).toDouble() - - (imageDiffY / 2), - ) * - scale) - .translate( - // If screenSize is bigger than croppedSize, move the element to half the difference - (previewSize.width - (img.croppedSize.width * scale)) / 2, - (previewSize.height - (img.croppedSize.height * scale)) / 2, - ); + var offset = Offset( + (shouldflipXY ? point.dy : point.dx).toDouble() / imageStretchX, + (shouldflipXY ? point.dx : point.dy).toDouble() / imageStretchY, + ) + .scale(imgToNativeScaleX * scale, imgToNativeScaleY * scale) + .translate(this.offset.dx, this.offset.dy); return offset; } diff --git a/lib/src/widgets/preview/awesome_preview_fit.dart b/lib/src/widgets/preview/awesome_preview_fit.dart index a82cddbd..888f4608 100644 --- a/lib/src/widgets/preview/awesome_preview_fit.dart +++ b/lib/src/widgets/preview/awesome_preview_fit.dart @@ -45,6 +45,7 @@ class _AnimatedPreviewFitState extends State { previewFit: widget.previewFit, previewSize: widget.previewSize, constraints: widget.constraints, + previewAlignment: widget.alignment, ); sizeCalculator!.compute(); maxSize = sizeCalculator!.maxSize; @@ -66,11 +67,13 @@ class _AnimatedPreviewFitState extends State { previewFit: oldWidget.previewFit, previewSize: oldWidget.previewSize, constraints: oldWidget.constraints, + previewAlignment: oldWidget.alignment, ); sizeCalculator = PreviewSizeCalculator( previewFit: widget.previewFit, previewSize: widget.previewSize, constraints: widget.constraints, + previewAlignment: widget.alignment, ); oldsizeCalculator.compute(); sizeCalculator!.compute(); @@ -116,7 +119,7 @@ class _AnimatedPreviewFitState extends State { previewFit: widget.previewFit, previewSize: widget.previewSize, scale: ratio, - maxSize: maxSize!, + maxSize: currentSize, child: child!, ); }, @@ -152,6 +155,12 @@ class PreviewFitWidget extends StatelessWidget { Widget build(BuildContext context) { final transformController = TransformationController() ..value = (Matrix4.identity()..scale(scale)); + final wDiff = + max(previewSize.width * scale - constraints.maxWidth, 0) / scale; + final hDiff = + max(previewSize.height * scale - constraints.maxHeight, 0) / scale; + transformController.value.translate( + wDiff * -((alignment.x + 1) / 2), hDiff * -((alignment.y + 1) / 2)); return Align( alignment: alignment, child: SizedBox( @@ -163,7 +172,7 @@ class PreviewFitWidget extends StatelessWidget { scaleEnabled: false, constrained: false, panEnabled: false, - alignment: FractionalOffset.topLeft, + alignment: Alignment.topLeft, clipBehavior: Clip.antiAlias, child: Align( alignment: Alignment.topLeft, @@ -185,6 +194,7 @@ class PreviewSizeCalculator { final CameraPreviewFit previewFit; final PreviewSize previewSize; final BoxConstraints constraints; + final Alignment previewAlignment; Size? _maxSize; double? _zoom; @@ -194,6 +204,7 @@ class PreviewSizeCalculator { required this.previewFit, required this.previewSize, required this.constraints, + required this.previewAlignment, }); void compute() { @@ -222,46 +233,46 @@ class PreviewSizeCalculator { return _offset!; } + Offset _computeOffset(num wDiff, num hDiff) { + final wMult = (previewAlignment.x + 1) / 2; + final hMult = (previewAlignment.y + 1) / 2; + return Offset(wDiff * wMult, hDiff * hMult); + } + Size _computeMaxSize() { var nativePreviewSize = previewSize.toSize(); Size maxSize; - final nativeWidthProjection = constraints.maxWidth * 1 / zoom; - final wDiff = nativePreviewSize.width - nativeWidthProjection; - - final nativeHeightProjection = constraints.maxHeight * 1 / zoom; - final hDiff = nativePreviewSize.height - nativeHeightProjection; switch (previewFit) { case CameraPreviewFit.fitWidth: maxSize = Size(constraints.maxWidth, nativePreviewSize.height * zoom); - _offset = Offset(0, constraints.maxHeight - maxSize.height); + _offset = _computeOffset(0, constraints.maxHeight - maxSize.height); break; case CameraPreviewFit.fitHeight: maxSize = Size(nativePreviewSize.width * zoom, constraints.maxHeight); - _offset = Offset(constraints.maxWidth - maxSize.width, 0); + _offset = _computeOffset(constraints.maxWidth - maxSize.width, 0); break; case CameraPreviewFit.cover: - maxSize = Size(constraints.maxWidth, constraints.maxHeight); - if (constraints.maxWidth / constraints.maxHeight > previewSize.width / previewSize.height) { - _offset = Offset((hDiff * zoom) * 2, 0); + maxSize = Size(constraints.maxWidth, nativePreviewSize.height * zoom); + _offset = _computeOffset(0, constraints.maxHeight - maxSize.height); // _offset = Offset(0, constraints.maxHeight - maxSize.height); } else { - _offset = Offset(0, (wDiff * zoom)); + maxSize = Size(nativePreviewSize.width * zoom, constraints.maxHeight); + _offset = _computeOffset(constraints.maxWidth - maxSize.width, 0); // _offset = Offset(constraints.maxWidth - maxSize.width, 0); } break; case CameraPreviewFit.contain: maxSize = Size( nativePreviewSize.width * zoom, nativePreviewSize.height * zoom); - _offset = Offset( + _offset = _computeOffset( constraints.maxWidth - maxSize.width, constraints.maxHeight - maxSize.height, ); break; } - return maxSize; } @@ -308,7 +319,8 @@ class PreviewSizeCalculator { runtimeType == other.runtimeType && previewFit == other.previewFit && constraints == other.constraints && - previewSize == other.previewSize; + previewSize == other.previewSize && + previewAlignment == other.previewAlignment; @override int get hashCode => previewSize.hashCode ^ previewSize.hashCode;