Search code examples
androidkotlinbarcode-scannergoogle-mlkitandroid-rect

How can detect barcode cornerPoints inside a View?


My barcode reader runs well but I want user can only scan barcode what is inside in view area to detect. I wrote a code for it. I can get cornerPoints of barcode that scanned but I can't realize if it's inside my detection view. I can't see rect value of view that passed from fragment to BarcodeAnalyzer class in debug mode. I don't know wether it is problem to achieve this or not.

enter image description here

BarcodeReaderFragment.kt

val imageAnalyzer = ImageAnalysis.Builder()
                                                 .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                                                 .build()

                                                 .also {
                                                     val rect = Rect()
                                                     binding.viewFocusArea.getGlobalVisibleRect(rect)
                                                     it.setAnalyzer(
                                                         cameraExecutor, BarcodeAnalyzer(
                                                             rect,
                                                             ::barcodeListener
                                                         )
                                                     )
                                                 }

BarcodeAnalyzer.kt

class BarcodeAnalyzer(
    private val rectArea: Rect,
    private val barcodeListener: (barcode: String) -> Unit
) : ImageAnalysis.Analyzer {
   
.addOnCompleteListener {
                image.close()
                if (it.isSuccessful) {
                    val barcodes = it.result as List<Barcode>
                    // Task completed successfully
                    for (barcode in barcodes) {

                            val cornerTL = barcode.cornerPoints?.get(0)
                            val cornerTR = barcode.cornerPoints?.get(1)
                            val cornerBL = barcode.cornerPoints?.get(2)
                            val cornerBR = barcode.cornerPoints?.get(3)

                            cornerTL?.let { topLeft ->
                                cornerTR?.let { topRight ->
                                    cornerBR?.let { bottomRight ->
                                        cornerBL?.let { bottomLeft ->
                                            if (rectArea.contains(topLeft) && rectArea.contains(topRight) && rectArea.contains(bottomRight) && rectArea.contains(
                                                    bottomLeft
                                                )
                                            ) {
                                                barcodeListener(barcode.rawValue ?: "")
                                            }
                                        }

}

fragment_barcode_reader.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.camera.view.PreviewView
        android:id="@+id/preview_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0" />

    <View
        android:id="@+id/viewRightTopVCorner"
        android:layout_width="12dp"
        android:layout_height="1dp"
        android:layout_marginEnd="-4dp"
        android:layout_marginBottom="5dp"
        android:background="@color/white"
        app:layout_constraintBottom_toTopOf="@+id/viewFocusArea"
        app:layout_constraintEnd_toEndOf="@+id/viewFocusArea" />

    <View
        android:id="@+id/viewRightTopHCorner"
        android:layout_width="1dp"
        android:layout_height="12dp"
        android:background="@color/white"
        app:layout_constraintEnd_toEndOf="@+id/viewRightTopVCorner"
        app:layout_constraintTop_toBottomOf="@+id/viewRightTopVCorner" />

    <View
        android:id="@+id/viewRightBottomVCorner"
        android:layout_width="12dp"
        android:layout_height="1dp"
        android:layout_marginTop="5dp"
        android:layout_marginEnd="-4dp"
        android:background="@color/white"
        app:layout_constraintEnd_toEndOf="@+id/viewFocusArea"
        app:layout_constraintTop_toBottomOf="@+id/viewFocusArea" />

    <View
        android:id="@+id/viewRightBottomHCorner"
        android:layout_width="1dp"
        android:layout_height="12dp"
        android:background="@color/white"
        app:layout_constraintEnd_toEndOf="@+id/viewRightBottomVCorner"
        app:layout_constraintBottom_toTopOf="@+id/viewRightBottomVCorner" />

    <View
        android:id="@+id/viewLeftBottomVCorner"
        android:layout_width="12dp"
        android:layout_height="1dp"
        android:layout_marginStart="-4dp"
        android:layout_marginTop="5dp"
        android:background="@color/white"
        app:layout_constraintStart_toStartOf="@+id/viewFocusArea"
        app:layout_constraintTop_toBottomOf="@+id/viewFocusArea" />

    <View
        android:id="@+id/viewLeftBottomHCorner"
        android:layout_width="1dp"
        android:layout_height="12dp"
        android:background="@color/white"
        app:layout_constraintBottom_toTopOf="@+id/viewLeftBottomVCorner"
        app:layout_constraintStart_toStartOf="@+id/viewLeftBottomVCorner" />

    <View
        android:id="@+id/viewLeftTopVCorner"
        android:layout_width="12dp"
        android:layout_height="1dp"
        android:layout_marginStart="-4dp"
        android:layout_marginBottom="5dp"
        android:background="@color/white"
        app:layout_constraintBottom_toTopOf="@+id/viewFocusArea"
        app:layout_constraintStart_toStartOf="@+id/viewFocusArea" />

    <View
        android:id="@+id/viewLeftTopHCorner"
        android:layout_width="1dp"
        android:layout_height="12dp"
        android:background="@color/white"
        app:layout_constraintTop_toBottomOf="@+id/viewLeftTopVCorner"
        app:layout_constraintStart_toStartOf="@+id/viewLeftTopVCorner" />

    <View
        android:id="@+id/viewFocusArea"
        android:layout_width="0dp"
        android:layout_height="101dp"
        android:layout_marginStart="48dp"
        android:layout_marginTop="35dp"
        android:layout_marginEnd="47dp"
        android:background="#00FFFFFF"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/preview_view"
        app:layout_constraintTop_toBottomOf="@+id/txtBarcodeScan" />

    <View
        android:id="@+id/shadow_top"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="-4dp"
        android:background="#80000000"
        app:layout_constraintBottom_toTopOf="@+id/viewFocusArea"
        app:layout_constraintEnd_toStartOf="@+id/shadow_right"
        app:layout_constraintStart_toEndOf="@+id/shadow_left"
        app:layout_constraintTop_toTopOf="parent"
        tools:visibility="invisible" />

    <View
        android:id="@+id/shadow_bottom"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="-4dp"
        android:background="#80000000"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/shadow_right"
        app:layout_constraintStart_toEndOf="@+id/shadow_left"
        app:layout_constraintTop_toBottomOf="@+id/viewFocusArea"
        tools:visibility="invisible" />

    <View
        android:id="@+id/shadow_left"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginEnd="-4dp"
        android:background="#80000000"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/viewFocusArea"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:visibility="invisible" />

    <View
        android:id="@+id/shadow_right"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="-4dp"
        android:background="#80000000"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/preview_view"
        app:layout_constraintStart_toEndOf="@+id/viewFocusArea"
        app:layout_constraintTop_toTopOf="parent"
        tools:visibility="invisible" />

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        app:layout_constraintEnd_toEndOf="@+id/preview_view"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_identity_hexagon" />

    <TextView
        android:id="@+id/txtBarcodeScan"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Lütfen fiyatını öğrenmek istediğin \nürünün barkodunu okut"
        android:textColor="@color/white"
        android:gravity="center"
        android:fontFamily="@font/poppins_regular"
        android:textSize="16sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/image" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btnChangeShop"
        style="@style/Widget.MaterialComponents.Button.TextButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="55dp"
        android:backgroundTint="@color/dull_orange"
        android:fontFamily="@font/poppins_semibold"
        android:insetTop="0dp"
        android:insetBottom="0dp"
        android:letterSpacing="0"
        app:cornerRadius="4dp"
        android:paddingVertical="15dp"
        android:text="@string/click_to_change_shop"
        android:textAllCaps="false"
        android:textColor="@color/white"
        android:textSize="14sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Solution

  • Just in case you still need it.

    I've coded a snippet to test the transformation of the imageProxy coordinates to the preview coordinates using the Java version of the snippet found at :https://developer.android.com/training/camerax/transform-output.

    The barcode is detected when its view enters the rectangle view located at height = previewView.getHeight()/5 and width = preview.getWidth()/2.

    Conclusion : the detection works very well.

     @SuppressLint("SetTextI18n")
        @Override
        public void analyze(@NonNull ImageProxy imageProxy) {
    
            Image mediaImage = imageProxy.getImage();
            int rotation = imageProxy.getImageInfo().getRotationDegrees();
    
            if (mediaImage != null) {
                InputImage image =
                        InputImage.fromMediaImage(mediaImage, rotation);
    
                scanner.process(image)
                        .addOnSuccessListener(barcodes -> {
                            for (Barcode barcode : barcodes) {
                                processBarcodeResult(barcode, imageProxy);
                            }
                        })
                        .addOnCompleteListener(results -> imageProxy.close());
            }
        }
    
        static void processBarcodeResult(Barcode barcode, ImageProxy imageProxy) {
    // xp and yp are the coordinates of the center of the preview rectangle
            int xp = previewView.getWidth() >> 1;
            int yp = previewView.getHeight() / 5;
    // xOffset and yOffset are threshold for detecting
    // that the barcode view enters the preview rectangle        
            int xOffset = (int) (barcode.getBoundingBox().width() / 2.5f);
            int yOffset = (int) (barcode.getBoundingBox().height() / 2.5f);
    // xb and yb are the coordinates of the center 
    // of the barcode boundingbox.
            int xb = barcode.getBoundingBox().centerX();
            int yb = barcode.getBoundingBox().centerY();
    // matrix transforms the imageProxy coordinates in the
    // preview coordinates        
            Matrix matrix = getMappingMatrix(imageProxy);
            float[] xyb = new float[]{xb, yb};
            
            matrix.mapPoints(xyb);// transforms the bounding box center 
                                  // coordinates
            xb = (int) xyb[0];
            yb = (int) xyb[1];
    
            if (xb > xp - xOffset && xb < xp + xOffset && yb > yp - yOffset && yb < yp + yOffset) {
                format.setText(barcode.getFormat() + "");
                code.setText(barcode.getRawValue());
    // code and format are 2 TextView in the mainactivity layout.
            }
    
        }
    
        static Matrix getMappingMatrix(ImageProxy imageProxy) {
            Rect cropRect = imageProxy.getCropRect();
            Matrix matrix = new Matrix();
    
            // A float array of the source vertices (crop rect) in clockwise order.
            float[] source = {cropRect.left, cropRect.top, cropRect.right,
                    cropRect.top, cropRect.right, cropRect.bottom, cropRect.left,
                    cropRect.bottom
            };
    
            // A float array of the destination vertices in clockwise order.
            float[] destination = {0f, 0f, previewView.getWidth(), 0f,
                    previewView.getWidth(), previewView.getHeight(), 0f,
                    previewView.getHeight()
            };
    
            // The destination vertexes need to be shifted based on rotation degrees.
            // The rotation degree represents the clockwise rotation needed to correct
            // the image.
    
            // Each vertex is represented by 2 float numbers in the vertices array.
            int vertexSize = 2;
            // The destination needs to be shifted 1 vertex for every 90° rotation.
            // imageProxy is already rotated => rotationDegrees = 0!
            int rotationDegrees = 0;
            int shiftOffset = rotationDegrees / 90 * vertexSize;
    
            float[] tempArray = destination.clone();
            for (int toIndex = 0; toIndex < source.length; toIndex++) {
                int fromIndex = (toIndex + shiftOffset) % source.length;
                destination[toIndex] = tempArray[fromIndex];
            }
            matrix.setPolyToPoly(source, 0, destination, 0, 4);
            return matrix;
        }
    

    I assume that you want to detect the barcode when it enters this ImageView!

    You must determine the coordinates of the center of this image. These coordinates are the xp and yp coordinates I use in my snippet above.

    <ImageView
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            app:layout_constraintEnd_toEndOf="@+id/preview_view"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@drawable/ic_identity_hexagon" />