Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 50798fee authored by Mohammed Althaf T's avatar Mohammed Althaf T 😊
Browse files

Camera: Update QR implementation

parent ccdde0bf
Loading
Loading
Loading
Loading
+12 −2
Original line number Original line Diff line number Diff line
@@ -17,7 +17,7 @@ android {


    defaultConfig {
    defaultConfig {
        applicationId "foundation.e.camera"
        applicationId "foundation.e.camera"
        minSdkVersion 21
        minSdkVersion 29
        targetSdkVersion 35
        targetSdkVersion 35
        //compileSdkVersion 31 // needed to support appcompat:1.4.0 (which we need for emoji policy support, and not yet ready to target SDK 30)
        //compileSdkVersion 31 // needed to support appcompat:1.4.0 (which we need for emoji policy support, and not yet ready to target SDK 30)


@@ -37,6 +37,15 @@ android {
        }
        }
    }
    }


    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }

    kotlinOptions {
        jvmTarget = '11'
    }

    // needed to use android.test package (ActivityInstrumentationTestCase2 etc) when targetting sdk 28 (Android 9) -
    // needed to use android.test package (ActivityInstrumentationTestCase2 etc) when targetting sdk 28 (Android 9) -
    // see https://developer.android.com/training/testing/set-up-project
    // see https://developer.android.com/training/testing/set-up-project
    useLibrary 'android.test.runner'
    useLibrary 'android.test.runner'
@@ -74,7 +83,8 @@ dependencies {
    implementation 'androidx.exifinterface:exifinterface:1.4.1'
    implementation 'androidx.exifinterface:exifinterface:1.4.1'


    implementation 'androidx.camera:camera-core:1.2.3'
    implementation 'androidx.camera:camera-core:1.2.3'
    implementation 'com.google.zxing:core:3.5.2'
    implementation 'com.google.zxing:core:3.5.3'
    implementation 'io.github.zxing-cpp:android:2.2.0'


    testImplementation 'junit:junit:4.13.2'
    testImplementation 'junit:junit:4.13.2'


+1 −6
Original line number Original line Diff line number Diff line
@@ -317,12 +317,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen


        //QRCode
        //QRCode
        job = JobKt.Job(null);
        job = JobKt.Job(null);
        coroutineScope = new CoroutineScope() {
        coroutineScope = () -> Dispatchers.getMain().plus(job);
            @Override
            public CoroutineContext getCoroutineContext() {
                return Dispatchers.getMain().plus(job);
            }
        };
        qrImageAnalyzer = new QrImageAnalyzer(this, coroutineScope);
        qrImageAnalyzer = new QrImageAnalyzer(this, coroutineScope);


        setContentView(R.layout.activity_main);
        setContentView(R.layout.activity_main);
+0 −42
Original line number Original line Diff line number Diff line
/*
 * SPDX-FileCopyrightText: 2022 The LineageOS Project
 * SPDX-License-Identifier: Apache-2.0
 */

package net.sourceforge.opencamera.ext

import androidx.camera.core.ImageProxy
import com.google.zxing.PlanarYUVLuminanceSource

private fun rotateYUVLuminancePlane(data: ByteArray, width: Int, height: Int): ByteArray {
    val yuv = ByteArray(width * height)
    // Rotate the Y luma
    var i = 0
    for (x in 0 until width) {
        for (y in height - 1 downTo 0) {
            yuv[i] = data[y * width + x]
            i++
        }
    }
    return yuv
}

internal val ImageProxy.planarYUVLuminanceSource: PlanarYUVLuminanceSource
    get() {
        val plane = planes[0]
        val buffer = plane.buffer
        var bytes = ByteArray(buffer.remaining())
        buffer.get(bytes)

        var width = width
        var height = height

        if (imageInfo.rotationDegrees == 90 || imageInfo.rotationDegrees == 270) {
            bytes = rotateYUVLuminancePlane(bytes, width, height)
            width = height.also { height = width }
        }

        return PlanarYUVLuminanceSource(
            bytes, width, height, 0, 0, width, height, true
        )
    }
+5 −34
Original line number Original line Diff line number Diff line
@@ -95,14 +95,6 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Toast;


import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.RGBLuminanceSource;


/** This class was originally named due to encapsulating the camera preview,
/** This class was originally named due to encapsulating the camera preview,
 *  but in practice it's grown to more than this, and includes most of the
 *  but in practice it's grown to more than this, and includes most of the
@@ -1168,31 +1160,10 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu
            }
            }
            TextureView textureView = (TextureView) this.cameraSurface;
            TextureView textureView = (TextureView) this.cameraSurface;
            Bitmap bitmap = textureView.getBitmap(preview_bitmap);
            Bitmap bitmap = textureView.getBitmap(preview_bitmap);

            if (bitmap!=null) {
                int width = bitmap.getWidth();
                int height = bitmap.getHeight();
                int[] pixels = new int[width * height];
                bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

                LuminanceSource source = new RGBLuminanceSource(width, height, pixels);
                BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
                Reader reader = new MultiFormatReader();

                try {
                    Result result = reader.decode(binaryBitmap);
                    String qrcodeContent = result.getText();
            MainActivity mActivity = (MainActivity) this.getContext();
            MainActivity mActivity = (MainActivity) this.getContext();
                    if (MyDebug.LOG)
            int rotation = getDisplayRotationDegrees(false);
                        Log.d(TAG, "Find QRCode qrcodeContent="+qrcodeContent );
            if (bitmap != null) {
                    if (result.getBarcodeFormat() == BarcodeFormat.QR_CODE) {
                mActivity.qrImageAnalyzer.readImage(bitmap, rotation);
                        mActivity.qrImageAnalyzer.showQrDialog(result);
                    }
                } catch (Exception e) {
                    // K1ZFP TODO Error 2
                }
            } else {
                // K1ZFP TODO Error 1
            }
            }
        } else if (!previewBitmapWasEnabled) {
        } else if (!previewBitmapWasEnabled) {
            disablePreviewBitmap();
            disablePreviewBitmap();
+84 −16
Original line number Original line Diff line number Diff line
/*
/*
 * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project
 * SPDX-FileCopyrightText: 2022-2024 The LineageOS Project
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-License-Identifier: Apache-2.0
 */
 */


@@ -13,8 +13,11 @@ import android.content.ClipDescription
import android.content.ClipboardManager
import android.content.ClipboardManager
import android.content.Intent
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo
import android.graphics.Bitmap
import android.graphics.Rect
import android.os.Build
import android.os.Build
import android.text.method.LinkMovementMethod
import android.text.method.LinkMovementMethod
import android.util.Log
import android.view.textclassifier.TextClassificationManager
import android.view.textclassifier.TextClassificationManager
import android.widget.ImageButton
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.ImageView
@@ -23,30 +26,33 @@ import android.widget.TextView
import android.widget.Toast
import android.widget.Toast
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.LinearLayoutCompat.LayoutParams
import androidx.appcompat.widget.LinearLayoutCompat.LayoutParams
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.cardview.widget.CardView
import androidx.cardview.widget.CardView
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialButton
import com.google.zxing.MultiFormatReader
import com.google.zxing.BarcodeFormat
import com.google.zxing.Result
import com.google.zxing.Result
import net.sourceforge.opencamera.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withContext
import net.sourceforge.opencamera.MyDebug
import net.sourceforge.opencamera.R
import net.sourceforge.opencamera.ext.getThemeColor
import net.sourceforge.opencamera.ext.getThemeColor
import net.sourceforge.opencamera.ext.px
import net.sourceforge.opencamera.ext.px
import zxingcpp.BarcodeReader
import kotlin.reflect.cast
import kotlin.reflect.cast


class QrImageAnalyzer(private val activity: Activity, private val scope: CoroutineScope) {
class QrImageAnalyzer(private val activity: Activity, private val scope: CoroutineScope) :

    ImageAnalysis.Analyzer {
    // Views
    // Views
    private val bottomSheetDialog by lazy {
    private val bottomSheetDialog by lazy {
        BottomSheetDialog(activity).apply {
        BottomSheetDialog(activity).apply {
            setContentView(R.layout.qr_bottom_sheet_dialog)
            setContentView(R.layout.qr_bottom_sheet_dialog)
        }
        }
    }
    }

    private val bottomSheetDialogCardView by lazy {
    private val bottomSheetDialogCardView by lazy {
        bottomSheetDialog.findViewById<CardView>(R.id.cardView)!!
        bottomSheetDialog.findViewById<CardView>(R.id.cardView)!!
    }
    }
@@ -69,6 +75,8 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti
        bottomSheetDialog.findViewById<LinearLayout>(R.id.actionsLayout)!!
        bottomSheetDialog.findViewById<LinearLayout>(R.id.actionsLayout)!!
    }
    }


    private val tag = "QrImageAnalyzer"

    // System services
    // System services
    private val clipboardManager by lazy { activity.getSystemService(ClipboardManager::class.java) }
    private val clipboardManager by lazy { activity.getSystemService(ClipboardManager::class.java) }
    private val keyguardManager by lazy { activity.getSystemService(KeyguardManager::class.java) }
    private val keyguardManager by lazy { activity.getSystemService(KeyguardManager::class.java) }
@@ -77,13 +85,53 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti
    }
    }


    // QR
    // QR
    private val reader by lazy { MultiFormatReader() }
    private val reader by lazy {
        BarcodeReader().apply {
            options.tryInvert = true
            options.tryRotate = true
        }
    }


    private val qrTextClassifier by lazy {
    private val qrTextClassifier by lazy {
        QrTextClassifier(activity, textClassificationManager.textClassifier)
        QrTextClassifier(activity, textClassificationManager.textClassifier)
    }
    }


    public fun showQrDialog(result: Result) {
    override fun analyze(image: ImageProxy) {
        image.use {
            reader.read(image).firstOrNull()?.let {
                showQrDialog(it)
            }
        }
    }

    fun readImage(bitmap: Bitmap, rotation: Int) {
        // We want to scan the full bitmap
        val cropRect = Rect(0, 0, bitmap.width, bitmap.height)

        // If we want to scan a specific area, e.g., middle portion, we can adjust the coordinates like this:
        // For example, scan the central 50% of the bitmap:
        //val left = (width * 0.25).toInt() // 25% from the left
        //val top = (height * 0.25).toInt() // 25% from the top
        //val right = (width * 0.75).toInt() // 75% from the left (50% width in total)
        //val bottom = (height * 0.75).toInt() // 75% from the top (50% height in total)

        // Define the customized Rect
        //cropRect = Rect(left, top, right, bottom)

        try {
            val results: List<BarcodeReader.Result> = reader.read(bitmap, cropRect, rotation)
            val qrcodeContent = results.toString()
            if (MyDebug.LOG) {
                Log.d(tag, "Find QRCode qrcodeContent = $qrcodeContent")
            }

            results.firstOrNull()?.let {
                showQrDialog(it)
            }
        } catch (ignored: Exception) { }
    }

    private fun showQrDialog(result: BarcodeReader.Result) {
        scope.launch(Dispatchers.Main) {
        scope.launch(Dispatchers.Main) {
            if (bottomSheetDialog.isShowing) {
            if (bottomSheetDialog.isShowing) {
                return@launch
                return@launch
@@ -94,7 +142,31 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti


            // Classify message
            // Classify message
            val textClassification = withContext(Dispatchers.IO) {
            val textClassification = withContext(Dispatchers.IO) {
                qrTextClassifier.classifyText(result)
                qrTextClassifier.classifyText(
                    Result(
                        text, result.bytes, null, when (result.format) {
                            BarcodeReader.Format.NONE -> null
                            BarcodeReader.Format.AZTEC -> BarcodeFormat.AZTEC
                            BarcodeReader.Format.CODABAR -> BarcodeFormat.CODABAR
                            BarcodeReader.Format.CODE_39 -> BarcodeFormat.CODE_39
                            BarcodeReader.Format.CODE_93 -> BarcodeFormat.CODE_93
                            BarcodeReader.Format.CODE_128 -> BarcodeFormat.CODE_128
                            BarcodeReader.Format.DATA_BAR -> null
                            BarcodeReader.Format.DATA_BAR_EXPANDED -> null
                            BarcodeReader.Format.DATA_MATRIX -> BarcodeFormat.DATA_MATRIX
                            BarcodeReader.Format.EAN_8 -> BarcodeFormat.EAN_8
                            BarcodeReader.Format.EAN_13 -> BarcodeFormat.EAN_13
                            BarcodeReader.Format.ITF -> BarcodeFormat.ITF
                            BarcodeReader.Format.MAXICODE -> BarcodeFormat.MAXICODE
                            BarcodeReader.Format.PDF_417 -> BarcodeFormat.PDF_417
                            BarcodeReader.Format.QR_CODE -> BarcodeFormat.QR_CODE
                            BarcodeReader.Format.MICRO_QR_CODE -> BarcodeFormat.QR_CODE
                            BarcodeReader.Format.RMQR_CODE -> BarcodeFormat.QR_CODE
                            BarcodeReader.Format.UPC_A -> BarcodeFormat.UPC_A
                            BarcodeReader.Format.UPC_E -> BarcodeFormat.UPC_E
                        }
                    )
                )
            }
            }


            bottomSheetDialogData.text = textClassification.text
            bottomSheetDialogData.text = textClassification.text
@@ -182,7 +254,7 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti
                            action = Intent.ACTION_SEND
                            action = Intent.ACTION_SEND
                            type = ClipDescription.MIMETYPE_TEXT_PLAIN
                            type = ClipDescription.MIMETYPE_TEXT_PLAIN
                            putExtra(
                            putExtra(
                                Intent.EXTRA_TEXT, result.text
                                Intent.EXTRA_TEXT, text
                            )
                            )
                        },
                        },
                        activity.getString(R.string.abc_shareactionprovider_share_with)
                        activity.getString(R.string.abc_shareactionprovider_share_with)
@@ -191,14 +263,11 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti
            }
            }


            // Show dialog
            // Show dialog
            activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
            activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT

            bottomSheetDialog.show()
            bottomSheetDialog.show()

            bottomSheetDialog.setOnDismissListener {
            bottomSheetDialog.setOnDismissListener {
                activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
                activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
            }
            }

        }
        }
    }
    }


@@ -211,5 +280,4 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti
    ).apply {
    ).apply {
        layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
        layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
    }
    }

}
}
Loading