diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/YuvConvertor.java b/app/src/main/java/com/astatin3/scoutingapp2025/YuvConvertor.java deleted file mode 100644 index 03a7ad9..0000000 --- a/app/src/main/java/com/astatin3/scoutingapp2025/YuvConvertor.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.astatin3.scoutingapp2025; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.ImageFormat; -import android.graphics.YuvImage; -import android.media.Image; -import android.renderscript.Allocation; -import android.renderscript.Element; -import android.renderscript.RenderScript; -import android.renderscript.ScriptIntrinsicYuvToRGB; -import android.renderscript.Type; - -import androidx.annotation.Nullable; - -import java.nio.ByteBuffer; - - - -public class YuvConvertor { - private final Allocation in, out; - private final ScriptIntrinsicYuvToRGB script; - - public YuvConvertor(Context context, int width, int height) { - RenderScript rs = RenderScript.create(context); - this.script = ScriptIntrinsicYuvToRGB.create( - rs, Element.U8_4(rs)); - - // NV21 YUV image of dimension 4 X 4 has following packing: - // YYYYYYYYYYYYYYYYVUVUVUVU - // With each pixel (of any channel) taking 8 bits. - int yuvByteArrayLength = (int) (width * height * 1.5f); - Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)) - .setX(yuvByteArrayLength); - this.in = Allocation.createTyped( - rs, yuvType.create(), Allocation.USAGE_SCRIPT); - - Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)) - .setX(width) - .setY(height); - this.out = Allocation.createTyped( - rs, rgbaType.create(), Allocation.USAGE_SCRIPT); - } - - - public Bitmap toBitmap(Image image) { - if (image.getFormat() != ImageFormat.YUV_420_888) { - throw new IllegalArgumentException("Only supports YUV_420_888."); - } - - byte[] yuvByteArray = toNv21(image); - in.copyFrom(yuvByteArray); - script.setInput(in); - script.forEach(out); - - // Allocate memory for the bitmap to return. If you have a reusable Bitmap - // I recommending using that. - Bitmap bitmap = Bitmap.createBitmap( - image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888); - out.copyTo(bitmap); - - return bitmap; - } - - private byte[] toNv21(Image image) { - int width = image.getWidth(); - int height = image.getHeight(); - - // Order of U/V channel guaranteed, read more: - // https://developer.android.com/reference/android/graphics/ImageFormat#YUV_420_888 - Image.Plane yPlane = image.getPlanes()[0]; - Image.Plane uPlane = image.getPlanes()[1]; - Image.Plane vPlane = image.getPlanes()[2]; - - ByteBuffer yBuffer = yPlane.getBuffer(); - ByteBuffer uBuffer = uPlane.getBuffer(); - ByteBuffer vBuffer = vPlane.getBuffer(); - - // Full size Y channel and quarter size U+V channels. - int numPixels = (int) (width * height * 1.5f); - byte[] nv21 = new byte[numPixels]; - int idY = 0; - int idUV = width * height; - int uvWidth = width / 2; - int uvHeight = height / 2; - - // Copy Y & UV channel. - // NV21 format is expected to have YYYYVU packaging. - // The U/V planes are guaranteed to have the same row stride and pixel stride. - int uvRowStride = uPlane.getRowStride(); - int uvPixelStride = uPlane.getPixelStride(); - int yRowStride = yPlane.getRowStride(); - int yPixelStride = yPlane.getPixelStride(); - for(int y = 0; y < height; ++y) { - int yOffset = y * yRowStride; - int uvOffset = y * uvRowStride; - - for (int x = 0; x < width; ++x) { - nv21[idY++] = yBuffer.get(yOffset + x * yPixelStride); - - if (y < uvHeight && x < uvWidth) { - int bufferIndex = uvOffset + (x * uvPixelStride); - // V channel. - nv21[idUV++] = vBuffer.get(bufferIndex); - // U channel. - nv21[idUV++] = uBuffer.get(bufferIndex); - } - } - } - - return nv21; - } - - YuvImage toYuvImage(Image image) { - if (image.getFormat() != ImageFormat.YUV_420_888) { - throw new IllegalArgumentException("Invalid image format"); - } - - int width = image.getWidth(); - int height = image.getHeight(); - - // Order of U/V channel guaranteed, read more: - // https://developer.android.com/reference/android/graphics/ImageFormat#YUV_420_888 - Image.Plane yPlane = image.getPlanes()[0]; - Image.Plane uPlane = image.getPlanes()[1]; - Image.Plane vPlane = image.getPlanes()[2]; - - ByteBuffer yBuffer = yPlane.getBuffer(); - ByteBuffer uBuffer = uPlane.getBuffer(); - ByteBuffer vBuffer = vPlane.getBuffer(); - - // Full size Y channel and quarter size U+V channels. - int numPixels = (int) (width * height * 1.5f); - byte[] nv21 = new byte[numPixels]; - int index = 0; - - // Copy Y channel. - int yRowStride = yPlane.getRowStride(); - int yPixelStride = yPlane.getPixelStride(); - for(int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - nv21[index++] = yBuffer.get(y * yRowStride + x * yPixelStride); - } - } - - // Copy VU data; NV21 format is expected to have YYYYVU packaging. - // The U/V planes are guaranteed to have the same row stride and pixel stride. - int uvRowStride = uPlane.getRowStride(); - int uvPixelStride = uPlane.getPixelStride(); - int uvWidth = width / 2; - int uvHeight = height / 2; - - for(int y = 0; y < uvHeight; ++y) { - for (int x = 0; x < uvWidth; ++x) { - int bufferIndex = (y * uvRowStride) + (x * uvPixelStride); - // V channel. - nv21[index++] = vBuffer.get(bufferIndex); - // U channel. - nv21[index++] = uBuffer.get(bufferIndex); - } - } - return new YuvImage( - nv21, ImageFormat.NV21, width, height, /* strides= */ null); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/fileEditor.java b/app/src/main/java/com/astatin3/scoutingapp2025/fileEditor.java index 4e9a740..aa85512 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/fileEditor.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/fileEditor.java @@ -37,12 +37,7 @@ public final class fileEditor { public static char byteToChar(int num){ - if(num < 0 || num > 255){ - throw new BufferOverflowException(); - } - byte[] bytes = new byte[1]; - bytes[0] = (byte) num; - return new String(bytes, Charset.defaultCharset()).charAt(0); + return new String(toBytes(num, 1), StandardCharsets.UTF_8).charAt(0); } diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/qrScanTask.java b/app/src/main/java/com/astatin3/scoutingapp2025/qrScanTask.java new file mode 100644 index 0000000..442175d --- /dev/null +++ b/app/src/main/java/com/astatin3/scoutingapp2025/qrScanTask.java @@ -0,0 +1,67 @@ +package com.astatin3.scoutingapp2025; + +import android.graphics.Bitmap; +import android.os.AsyncTask; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.ChecksumException; +import com.google.zxing.DecodeHintType; +import com.google.zxing.FormatException; +import com.google.zxing.NotFoundException; +import com.google.zxing.RGBLuminanceSource; +import com.google.zxing.Reader; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; +import com.google.zxing.qrcode.QRCodeReader; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public class qrScanTask extends AsyncTask{ + private Function resultFunction = null; + private Bitmap image; + + @Override + protected String doInBackground(String... str) { + if(image == null){return null;} + + int width = image.getWidth(); + int height = image.getHeight(); + int[] pixels = new int[width * height]; + image.getPixels(pixels, 0, width, 0, 0, width, height); + + RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels); + BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); + + Map hints = new HashMap<>(); + hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); +// hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE); + hints.put(DecodeHintType.POSSIBLE_FORMATS, EnumSet.of(BarcodeFormat.QR_CODE)); + + Reader reader = new QRCodeReader(); + try { + Result result = reader.decode(binaryBitmap, hints); + return result.getText(); + } catch (NotFoundException | ChecksumException | FormatException e) { + e.printStackTrace(); + } + + return null; + } + public void setImage(Bitmap image){this.image = image;} + public void onResult(Function func) { + this.resultFunction = func; + } + + @Override + protected void onPostExecute(String result) { + super.onPostExecute(result); + if(resultFunction != null){ + resultFunction.apply(result); + } + } +} + 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 6e6df2d..6c0aed5 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 @@ -78,7 +78,7 @@ public class generatorView extends ConstraintLayout { // 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.L); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); hints.put(EncodeHintType.MARGIN, 0); /* default = 4 */ MultiFormatWriter writer = new MultiFormatWriter(); diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/scannerView.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/scannerView.java index 3f3843c..b324aeb 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/scannerView.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/transfer/scannerView.java @@ -1,35 +1,17 @@ package com.astatin3.scoutingapp2025.ui.transfer; -import static android.view.Surface.ROTATION_0; import static androidx.core.math.MathUtils.clamp; -import android.app.ActionBar; import android.app.AlertDialog; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.ImageFormat; -import android.graphics.Paint; -import android.graphics.PointF; -import android.graphics.YuvImage; import android.media.Image; import android.os.Handler; -import android.os.SystemClock; -import android.renderscript.Element; -import android.renderscript.RenderScript; import android.renderscript.ScriptIntrinsicYuvToRGB; import android.util.AttributeSet; import android.util.Log; -import android.util.Size; import android.view.Surface; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; +import android.widget.SeekBar; import androidx.annotation.NonNull; import androidx.annotation.OptIn; @@ -40,30 +22,25 @@ import androidx.camera.core.ExperimentalGetImage; import androidx.camera.core.ImageAnalysis; import androidx.camera.core.ImageProxy; import androidx.camera.core.Preview; -import androidx.camera.core.impl.CameraFilters; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; -import com.astatin3.scoutingapp2025.YuvConvertor; import com.astatin3.scoutingapp2025.databinding.FragmentTransferBinding; import com.astatin3.scoutingapp2025.fileEditor; +import com.astatin3.scoutingapp2025.qrScanTask; import com.dlazaro66.qrcodereaderview.QRCodeReaderView; import com.google.common.util.concurrent.ListenableFuture; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.function.Function; import java.util.zip.DataFormatException; //public class scannerView extends androidx.appcompat.widget.AppCompatImageView { @@ -78,7 +55,6 @@ public class scannerView extends ConstraintLayout { private qrOverlayView qrOverlayView; private Handler uiHandler; private ScriptIntrinsicYuvToRGB script; - private YuvConvertor yuvConvertor; // private class codeReadListener implements QRCodeReaderView.OnQRCodeReadListener { // @Override @@ -115,114 +91,153 @@ public class scannerView extends ConstraintLayout { } private float scale = 0; - private double threshhold = 0.5; private FragmentTransferBinding binding; private LifecycleOwner lifecycle; private void setImage(Bitmap bmp){ if(scale == 0) { - scale = ((float) ((View) getParent()).getWidth() / bmp.getWidth()) * ((float) 16 / 9); - setScaleX(scale); - setScaleY(scale); + scale = ((float) getWidth() / bmp.getWidth()) * ((float) 16 / 9); + binding.scannerImage.setTranslationX(0); + binding.scannerImage.setTranslationY(0); +// binding.scannerImage.setScaleX(scale); +// binding.scannerImage.setScaleY(scale); } + scanQRCode(bmp); binding.scannerImage.setImageBitmap(bmp); binding.scannerThreshold.bringToFront(); +// alert("test", getChildCount()+""); } - private Bitmap toGreyscale(Bitmap originalBitmap){ - int width = originalBitmap.getWidth(); - int height = originalBitmap.getHeight(); +// private Bitmap img - Bitmap oneBitBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(oneBitBitmap); - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(new float[] { - 0.299f, 0.587f, 0.114f, 0, 0, - 0.299f, 0.587f, 0.114f, 0, 0, - 0.299f, 0.587f, 0.114f, 0, 0, - 0, 0, 0, 1, 0 - }))); - - canvas.drawBitmap(originalBitmap, 0, 0, paint); + int[] levelMap = new int[256]; + private void recalcMap(){ + for (int i = 0; i < 256; i++) { + levelMap[i] = clamp( + (clamp( + i-thresholdOffset, 0, 255) / (256 / numColors)) * (256 / numColors + )+brightness, 0, 255 + ); + } + } + private Bitmap toGreyscale(Image image){ + // Turns out the "Y" In YUV is the Luminance of the pixel. + // Makes converting to greyscale 1000x easier + ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); + final int width = image.getWidth(); + final int height = image.getHeight(); int[] pixels = new int[width * height]; - oneBitBitmap.getPixels(pixels, 0, width, 0, 0, width, height); - - int[] oneBitPixels = new int[width * height]; - int threshold = 128; // Adjust this value to change the threshold - - for (int i = 0; i < pixels.length; i++) { - int pixel = pixels[i]; - int red = Color.red(pixel); - int green = Color.green(pixel); - int blue = Color.blue(pixel); - int average = (red + green + blue) / 3; - oneBitPixels[i] = (average > threshold) ? Color.WHITE : Color.BLACK; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { +// int L = levelMap[clamp((yBuffer.get() & 0xff) - thresholdOffset, 0, 255)]; +// int L = clamp(levelMap[yBuffer.get() & 0xff]-thresholdOffset, 0, 255); + int L = levelMap[yBuffer.get() & 0xff]; + pixels[y * width + x] = 0xff000000 | (L << 16) | (L << 8) | L; +// if(L > threshold) { +// pixels[y * width + x] = 0xffffffff; +// }else{ +// pixels[y * width + x] = 0xff000000; +// } +// pixels[y * width + x] = levelMap[L]; + } } - oneBitBitmap.setPixels(oneBitPixels, 0, width, 0, 0, width, height); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return oneBitBitmap; + return bitmap; } + public void scanQRCode(Bitmap bitmap) { + qrScanTask async = new qrScanTask(); + async.setImage(bitmap); + async.onResult(new Function() { + @Override + public String apply(String data) { + if(data != null){ +// alert("test", ""+fileEditor.byteFromChar(data.charAt(0))); + compileData( + fileEditor.byteFromChar(data.charAt(0)), + fileEditor.byteFromChar(data.charAt(1)), + fileEditor.byteFromChar(data.charAt(2)), + (fileEditor.byteFromChar(data.charAt(3))+1), + data.substring(4) + ); + } + return null; + } + }); + async.execute(); + + + +// return contents; + } + private int numColors = 3; + private int thresholdOffset = 128; + private int brightness = 128; 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(); + + binding.scannerThreshold.setProgress(thresholdOffset); + binding.scannerThreshold.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + thresholdOffset = 127-progress; + recalcMap(); + } + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} + }); + binding.scannerThreshold.setMax(255); + + binding.scannerColors.setProgress(numColors); + binding.scannerColors.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + numColors = 18-(progress-2); + recalcMap(); + } + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} + }); + binding.scannerColors.setMax(18); + binding.scannerBrightness.setProgress(brightness); + binding.scannerBrightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + brightness = progress-128; + recalcMap(); + } + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} + }); + binding.scannerBrightness.setMax(256); -// 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); + recalcMap(); -// 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(); + qrOverlayView = new qrOverlayView(getContext()); + qrOverlayView.bringToFront(); + ConstraintLayout.LayoutParams pointsOverlayViewParams = new ConstraintLayout.LayoutParams( + LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT + ); + + qrOverlayView.setLayoutParams(pointsOverlayViewParams); + this.addView(qrOverlayView); ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(this.getContext()); @@ -267,14 +282,18 @@ public class scannerView extends ConstraintLayout { @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)); - } - }); +// Log.i("test", img.getWidth() + ", " + img.getHeight()); +// final Bitmap bmp = yuvConvertor.toBitmap(img); + if(img != null) { + uiHandler.post(new Runnable() { + Bitmap bmp = toGreyscale(img); + @Override + public void run() { +// setImage(toGreyscale(bmp)); + setImage(bmp); + } + }); + } image.close(); } }); @@ -295,7 +314,7 @@ public class scannerView extends ConstraintLayout { 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"); + alert("Error", "Incorrect data version ("+dataVersion+" != "+fileEditor.internalDataVersion+")"); return; } @@ -324,14 +343,13 @@ public class scannerView extends ConstraintLayout { if(updated && qrScannedCount >= qrCount){ - // I guess String.join does not like non-ascii text - String compiledData = ""; + String compiledString = ""; for(int i=0;i + + + + + + + + + + + - - - - - - -