From 3365a2ce99d1465d1b26052f1ef4c364d6028ad9 Mon Sep 17 00:00:00 2001 From: Astatin3 <77305074+Astatin3@users.noreply.github.com> Date: Sat, 6 Apr 2024 18:52:25 -0600 Subject: [PATCH] Work on speedier qr code scanning. --- app/build.gradle.kts | 23 +- app/src/main/AndroidManifest.xml | 1 + .../scoutingapp2025/YuvConvertor.java | 165 +++++++++ .../astatin3/scoutingapp2025/fileEditor.java | 11 +- .../ui/transfer/TransferFragment.java | 3 +- .../ui/transfer/generatorView.java | 42 ++- .../ui/transfer/qrOverlayView.java | 7 +- .../ui/transfer/scannerView.java | 340 ++++++++++++++---- app/src/main/res/layout/fragment_transfer.xml | 43 ++- 9 files changed, 531 insertions(+), 104 deletions(-) create mode 100644 app/src/main/java/com/astatin3/scoutingapp2025/YuvConvertor.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5ab980f..e151582 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,8 +51,29 @@ dependencies { androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) + // CameraX\ +// implementation(libs.camera.core) +// implementation(libs.androidx.camera.camera2) +// implementation(libs.camera.lifecycle) + + // CameraX View class + + var camerax_version = "1.3.2" +// implementation(libs.camera.core) +// implementation(libs.androidx.camera.camera2) +// implementation(libs.camera.lifecycle) + implementation("androidx.camera:camera-core:1.3.2") + implementation("androidx.camera:camera-camera2:1.3.2") + implementation("androidx.camera:camera-lifecycle:1.3.2") + implementation("androidx.camera:camera-view:${camerax_version}") +// implementation("com.quickbirdstudios:yuvtomat:1.1.0") + + // implementation("com.github.yuriy-budiyev:code-scanner:2.3.0") // implementation("com.github.kenglxn.QRGen:android:3.0.1") -// implementation("com.journeyapps:zxing-android-embedded:2.3.0") +// implementation("com.github.nisrulz:qreader:2.1.1") +// implementation("com.journeyapps:zxing-android-embedded:4.3.0") +// implementation("androidx.camera:1.0.0-alpha09") +// api("com.otaliastudios:cameraview:2.7.2") implementation("com.dlazaro66.qrcodereaderview:qrcodereaderview:2.0.3") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4024ee3..0a47e62 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.ScoutingApp2025" + android:hardwareAccelerated="true" tools:targetApi="31"> 0) { int decompressedSize = inflater.inflate(buffer); + if (decompressedSize == 0) { + break; + } outputStream.write(buffer, 0, decompressedSize); } diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/TransferFragment.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/TransferFragment.java index 94c5e72..31ba86d 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/TransferFragment.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/TransferFragment.java @@ -27,6 +27,7 @@ public class TransferFragment extends Fragment { alert.setCancelable(true); alert.create().show(); } + @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @@ -53,7 +54,7 @@ public class TransferFragment extends Fragment { public void onClick(View v) { binding.selectLayout.setVisibility(View.GONE); binding.scannerLayout.setVisibility(View.VISIBLE); - binding.scannerLayout.start(binding); + binding.scannerLayout.start(binding, getViewLifecycleOwner()); } }); binding.TBAButton.setOnClickListener(new View.OnClickListener() { diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/generatorView.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/generatorView.java index 585bf7b..6e6df2d 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/generatorView.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/generatorView.java @@ -15,8 +15,10 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.astatin3.scoutingapp2025.databinding.FragmentTransferBinding; import com.astatin3.scoutingapp2025.fileEditor; + import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; @@ -38,7 +40,7 @@ public class generatorView extends ConstraintLayout { private final int maxQrCount = 256; //The max number that can be stored in a byte - private final int maxQrSpeed = 20; + private final int maxQrSpeed = 5; private final int minQrSpeed = 300 + maxQrSpeed - 1; private int minQrSize = 0; @@ -64,32 +66,42 @@ public class generatorView extends ConstraintLayout { super(context, attributeSet); } - public static Bitmap generateQrCode(String myCodeText) throws WriterException { + private Bitmap generateQrCode(String contents) throws WriterException { - QRCodeWriter qrCodeWriter = new QRCodeWriter(); + final int size = 512; + + if (contents == null) { + return null; + } Map hints = new EnumMap(EncodeHintType.class); + + // The Charset must be UTF-8, Or data will not be transferred properly. IDK why. hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); - hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // H = 30% damage -// hints.put(EncodeHintType.QR_COMPACT, true); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); hints.put(EncodeHintType.MARGIN, 0); /* default = 4 */ + MultiFormatWriter writer = new MultiFormatWriter(); - int size = 512; - - BitMatrix bitMatrix = qrCodeWriter.encode(myCodeText, BarcodeFormat.QR_CODE, size, size, hints); - - int width = bitMatrix.getWidth(); - int height = bitMatrix.getHeight(); + BitMatrix result; + try { + result = writer.encode(contents, BarcodeFormat.QR_CODE, size, size, hints); + } catch (IllegalArgumentException iae) { + // Unsupported format + return null; + } + int width = result.getWidth(); + int height = result.getHeight(); int[] pixels = new int[width * height]; for (int y = 0; y < height; y++) { int offset = y * width; for (int x = 0; x < width; x++) { - pixels[offset + x] = bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE; + pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE; } } - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Bitmap bitmap = Bitmap.createBitmap(width, height, + Bitmap.Config.ARGB_8888); bitmap.setPixels(pixels, 0, width, 0, 0, width, height); return bitmap; @@ -132,6 +144,8 @@ public class generatorView extends ConstraintLayout { StandardCharsets.ISO_8859_1) + compressedBlock; + + } // byte[] tempData = fileEditor.compress(inputData); @@ -209,6 +223,7 @@ public class generatorView extends ConstraintLayout { String.valueOf(fileEditor.byteToChar(qrCount-1)) + data.substring(start, end) )); +// alert("title", ""+(qrCount-1)); }catch (WriterException e){ e.printStackTrace(); } @@ -222,7 +237,6 @@ public class generatorView extends ConstraintLayout { private void updateQr(){ qrImage.setImageBitmap(qrBitmaps.get(qrIndex)); - Log.i("test", qrIndex+", "+qrCount); if(qrDelay > 0) { this.qrIndex += 1; if (this.qrIndex >= this.qrCount) { diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/qrOverlayView.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/qrOverlayView.java index c934fdb..8cf2a91 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/qrOverlayView.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/qrOverlayView.java @@ -17,7 +17,6 @@ public class qrOverlayView extends View { int[] barColors; private Paint paint; private final int barHeight = 50; - private final int barMargin = 5; public qrOverlayView(Context context) { super(context); @@ -63,12 +62,14 @@ public class qrOverlayView extends View { final int top = 0; final int bottom = barHeight; + final int margin = 5*(int)((double)width/getWidth()); for(int i=0;i threshold) ? Color.WHITE : Color.BLACK; + } + + oneBitBitmap.setPixels(oneBitPixels, 0, width, 0, 0, width, height); + + return oneBitBitmap; + } + + public void start(FragmentTransferBinding binding, LifecycleOwner lifecycle){ + this.binding = binding; + this.lifecycle = lifecycle; + yuvConvertor = new YuvConvertor(getContext(), 1280, 720); + + uiHandler = new Handler(); + +// IntentIntegrator integrator = IntentIntegrator.forSupportFragment(TransferFragment); +// integrator.setPrompt("Scan a QR code"); +// integrator.setBeepEnabled(true); +// integrator.setOrientationLocked(true); +// integrator.setCaptureActivity(CaptureActivity.class); +// integrator.initiateScan(); + + +// ScanOptions options = new ScanOptions(); +// options.setDesiredBarcodeFormats(ScanOptions.QR_CODE); +// options.setPrompt("Scan a barcode"); +// options.setCameraId(0); // Use a specific camera of the device +// options.setBeepEnabled(false); +// options.setBarcodeImageEnabled(true); +// barcodeLauncher.launch(options); + +// qrCodeReaderView = new QRCodeReaderView(getContext()); +// this.addView(qrCodeReaderView); +// ConstraintLayout.LayoutParams qrCodeReaderViewParams = (ConstraintLayout.LayoutParams) qrCodeReaderView.getLayoutParams(); +// qrCodeReaderViewParams.width = ActionBar.LayoutParams.MATCH_PARENT; +// qrCodeReaderViewParams.height = ActionBar.LayoutParams.MATCH_PARENT; +// qrCodeReaderView.setLayoutParams(qrCodeReaderViewParams); +// +// qrOverlayView = new qrOverlayView(getContext()); +// qrOverlayView.bringToFront(); +// this.addView(qrOverlayView); +// ConstraintLayout.LayoutParams pointsOverlayViewParams = (ConstraintLayout.LayoutParams) qrCodeReaderView.getLayoutParams(); +// pointsOverlayViewParams.width = ActionBar.LayoutParams.MATCH_PARENT; +// pointsOverlayViewParams.height = ActionBar.LayoutParams.MATCH_PARENT; +// qrOverlayView.setLayoutParams(pointsOverlayViewParams); +// +// Map hints = new EnumMap(DecodeHintType.class); +// hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE); +// hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE); +//// hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); +// +// qrCodeReaderView.setDecodeHints(hints); +// +//// qrCodeReaderView = (QRCodeReaderView) binding.qrdecoderview; +// qrCodeReaderView.setOnQRCodeReadListener(new codeReadListener()); +//// qrCodeReaderView.setQRDecodingEnabled(true); +// qrCodeReaderView.setAutofocusInterval(2000L); +//// qrCodeReaderView.setFrontCamera(); +// qrCodeReaderView.setBackCamera(); +// qrCodeReaderView.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// qrCodeReaderView.forceAutoFocus(); +// } +// }); +// qrCodeReaderView.startCamera(); + + ListenableFuture cameraProviderFuture + = ProcessCameraProvider.getInstance(this.getContext()); + + cameraProviderFuture.addListener(new Runnable() { + @Override + public void run() { + try { + ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); + bindPreview(cameraProvider); + } catch (ExecutionException | InterruptedException e) { + // No errors need to be handled for this Future. + // This should never be reached. + } + } + }, ContextCompat.getMainExecutor(this.getContext())); + } + + void bindPreview(@NonNull ProcessCameraProvider cameraProvider) { + + Preview preview = new Preview.Builder() + .setTargetAspectRatio(AspectRatio.RATIO_16_9) + .setTargetRotation(Surface.ROTATION_180) + .build(); + + CameraSelector cameraSelector = new CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) +// .addCameraFilter(CameraFilters.NON) + .build(); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + + ImageAnalysis imageAnalysis = new ImageAnalysis.Builder() +// .setTargetResolution(new Size(224, 224)) + .setOutputImageRotationEnabled(false) + .setTargetAspectRatio(AspectRatio.RATIO_16_9) + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build(); + + + imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() { + @OptIn(markerClass = ExperimentalGetImage.class) @Override + public void analyze(@NonNull ImageProxy image) { + Image img = Objects.requireNonNull(image.getImage()); + Log.i("test", img.getWidth() + ", " + img.getHeight()); + final Bitmap bmp = yuvConvertor.toBitmap(img); + uiHandler.post(new Runnable() { + @Override + public void run() { + setImage(toGreyscale(bmp)); + } + }); + image.close(); + } + }); + + cameraProvider.unbindAll(); + + Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)lifecycle, + cameraSelector, imageAnalysis, preview); + +// preview.setSurfaceProvider(binding.previewView.getSurfaceProvider()); + + } + + private String[] qrDataArr; + private int qrScannedCount; + private int[] barColors; + private int randID; + private int prevQrIndex; private void compileData(int dataVersion, int randID, int qrIndex, int qrCount, String qrData){ if(dataVersion != fileEditor.internalDataVersion){ alert("Error", "Incorrect data version"); @@ -105,6 +303,9 @@ public class scannerView extends ConstraintLayout { if(randID != this.randID){ this.randID = randID; qrDataArr = new String[qrCount]; + Log.i("title", ""+qrCount); + barColors = new int[qrCount]; + prevQrIndex = qrIndex; } final boolean updated; @@ -112,29 +313,16 @@ public class scannerView extends ConstraintLayout { if(qrDataArr[qrIndex] == null) { qrDataArr[qrIndex] = qrData; updated = true; + qrScannedCount += 1; }else{ updated = false; } - int count = 0; - int[] barColors = new int[qrCount]; - - for(int i =0;i= qrCount){ + if(updated && qrScannedCount >= qrCount){ // I guess String.join does not like non-ascii text String compiledData = ""; @@ -142,31 +330,29 @@ public class scannerView extends ConstraintLayout { compiledData += qrDataArr[i]; } - try { byte[] compiledBytes = compiledData.getBytes(StandardCharsets.ISO_8859_1); +// alert(count+", "+compiledData.length()+", "+compiledBytes.length, ""+fileEditor.fromBytes(fileEditor.getByteBlock(compiledBytes, 0,2),2)); // alert("completed", new String(fileEditor.decompress(compiledBytes), StandardCharsets.ISO_8859_1)); alert("completed", blockUncompress(compiledBytes)); }catch (Exception e){ e.printStackTrace(); } } + prevQrIndex = qrIndex; } private static String blockUncompress(byte[] data) throws DataFormatException { String uncompressedData = ""; int curIndex = 0; while(curIndex < data.length){ - final int blockLength = fileEditor.fromBytes(fileEditor.getByteBlock(data, curIndex, curIndex+2), 2); - Log.i("test", ""+blockLength); + final int blockLength = fileEditor.fromBytes(fileEditor.getByteBlock(data, curIndex, curIndex+2), 2); uncompressedData += new String( fileEditor.decompress( - fileEditor.getByteBlock( - data, curIndex+2, curIndex+blockLength+2) - ), StandardCharsets.ISO_8859_1 - ); + fileEditor.getByteBlock(data, curIndex+2, curIndex+blockLength+2) + ), StandardCharsets.ISO_8859_1); curIndex += blockLength+2; } diff --git a/app/src/main/res/layout/fragment_transfer.xml b/app/src/main/res/layout/fragment_transfer.xml index 7fccc2a..de4014a 100644 --- a/app/src/main/res/layout/fragment_transfer.xml +++ b/app/src/main/res/layout/fragment_transfer.xml @@ -129,21 +129,58 @@ + + app:layout_constraintTop_toBottomOf="parent" + tools:layout_editor_absoluteX="-62dp" + tools:visibility="visible"> + + + + + + + + + + + + + + +