Make QR Codes Better

This commit is contained in:
Astatin3
2024-04-04 14:48:29 -06:00
parent af2ea86a03
commit eac22e1c91
7 changed files with 247 additions and 117 deletions
@@ -1,19 +1,18 @@
package com.astatin3.scoutingapp2025;
import android.content.Context;
import android.util.Log;
import com.astatin3.scoutingapp2025.Utils.frcMatch;
import com.astatin3.scoutingapp2025.Utils.frcTeam;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.BufferOverflowException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
@@ -21,6 +20,9 @@ import java.util.zip.Inflater;
public final class fileEditor {
// private final static String baseDir = android.os.Environment.getExternalStorageDirectory().getAbsolutePath();
public static final byte internalDataVersion = 0x01;
public static final int maxCompressedBlockSize = 4096;
public static String binaryVisualize(byte[] bytes){
String returnStr = "";
@@ -33,7 +35,9 @@ public final class fileEditor {
return returnStr;
}
public static char toChar(int num){
public static char byteToChar(int num){
if(num < 0 || num > 255){
throw new BufferOverflowException();
}
@@ -42,18 +46,55 @@ public final class fileEditor {
return new String(bytes, Charset.defaultCharset()).charAt(0);
}
public static int fromChar(char c){
public static byte[] toBytes(int num, int byteCount){
if(num < 0 || num > (Math.pow(2,byteCount*8)-1)){
throw new BufferOverflowException();
}
byte[] bytes = new byte[byteCount];
for(int i=0;i<byteCount;i++){
bytes[i] = (byte)(num >> (i*8));
}
return bytes;
}
public static int fromBytes(byte[] bytes, int byteCount){
int returnInt = 0;
for(int i=0;i<byteCount;i++){
returnInt |= (bytes[i] & 0xFF) << (i*8);
}
return returnInt;
}
public static int byteFromChar(char c){
byte[] bytes = (String.valueOf(c)).getBytes(Charset.defaultCharset());
return Byte.toUnsignedInt(bytes[0]);
}
public static byte[] getByteBlock(byte[] bytes, int start, int end){
byte[] dataBlock = new byte[end-start] ;
for(int a=start;a<end;a++){
Log.i("test", start+", "+a+", "+end);
dataBlock[a-start] = bytes[a];
}
return dataBlock;
}
public static byte[] compress(byte[] input) {
Deflater deflater = new Deflater();
deflater.setInput(input);
deflater.finish();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
byte[] buffer = new byte[maxCompressedBlockSize];
while (!deflater.finished()) {
int compressedSize = deflater.deflate(buffer);
@@ -69,7 +110,9 @@ public final class fileEditor {
inflater.setInput(input);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
byte[] buffer = new byte[maxCompressedBlockSize];
while (!inflater.finished()) {
int decompressedSize = inflater.inflate(buffer);
@@ -1,53 +0,0 @@
package com.astatin3.scoutingapp2025;
// From https://github.com/dlazaro66/QRCodeReaderView/blob/master/samples/src/main/java/com/example/qr_readerexample/PointsOverlayView.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
public class qrPointsOverlayView extends View {
PointF[] points;
private Paint paint;
public qrPointsOverlayView(Context context) {
super(context);
init();
}
public qrPointsOverlayView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public qrPointsOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.YELLOW);
paint.setStyle(Paint.Style.FILL);
}
public void setPoints(PointF[] points) {
this.points = points;
invalidate();
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (points != null) {
for (PointF pointF : points) {
canvas.drawCircle(pointF.x, pointF.y, 10, paint);
}
}
}
}
File diff suppressed because one or more lines are too long
@@ -13,7 +13,6 @@ import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.astatin3.scoutingapp2025.R;
import com.astatin3.scoutingapp2025.databinding.FragmentTransferBinding;
import com.astatin3.scoutingapp2025.fileEditor;
import com.google.zxing.BarcodeFormat;
@@ -23,13 +22,11 @@ import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.Map;
import java.util.Random;
public class generatorView extends ConstraintLayout {
private FragmentTransferBinding binding;
@@ -45,7 +42,7 @@ public class generatorView extends ConstraintLayout {
private final int minQrSpeed = 300 + maxQrSpeed - 1;
private int minQrSize = 0;
private final int maxQrSize = 600;
private final int maxQrSize = 500;
private int qrSize = 200;
@@ -73,11 +70,11 @@ public class generatorView extends ConstraintLayout {
Map<EncodeHintType, Object> hints = new EnumMap<EncodeHintType, Object>(EncodeHintType.class);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); // H = 30% damage
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // H = 30% damage
// hints.put(EncodeHintType.QR_COMPACT, true);
hints.put(EncodeHintType.MARGIN, 0); /* default = 4 */
int size = 200;
int size = 512;
BitMatrix bitMatrix = qrCodeWriter.encode(myCodeText, BarcodeFormat.QR_CODE, size, size, hints);
@@ -99,31 +96,48 @@ public class generatorView extends ConstraintLayout {
}
public void start(FragmentTransferBinding binding, String inputData){
start(binding, inputData.getBytes(StandardCharsets.ISO_8859_1));
}
public void start(FragmentTransferBinding binding, byte[] inputData){
qrImage = binding.qrImage;
qrSpeedSlider = binding.qrSpeedSlider;
qrSizeSlider = binding.qrSizeSlider;
qrIndexN = binding.qrIndexN;
qrIndexD = binding.qrIndexD;
String compiledData = null;
// try {
byte[] tempData = fileEditor.compress(inputData.getBytes(StandardCharsets.ISO_8859_1));
compiledData = new String(tempData, StandardCharsets.ISO_8859_1);
// alert(""+tempData.length, fileEditor.binaryVisualize(tempData));
// Log.i("Info", fileEditor.binaryVisualize(tempData));
// byte[] randomData = new byte[4596];
// new Random().nextBytes(randomData);
// inputData = randomData;
// }catch (UnsupportedEncodingException e){
// e.printStackTrace();
// }
alert(""+compiledData.length(), compiledData);
String compiledData = "";
if(compiledData == null || inputData.length() < compiledData.length()){
sendData(inputData);
}else{
sendData(compiledData);
for(int i=0;i<Math.ceil((double) inputData.length / fileEditor.maxCompressedBlockSize);i++){
final int start = i*fileEditor.maxCompressedBlockSize;
int end = ((i+1)*fileEditor.maxCompressedBlockSize);
if(end > inputData.length) {
end = inputData.length;
}
byte[] dataBlock = fileEditor.getByteBlock(inputData, start, end);
final String compressedBlock =
new String(
fileEditor.compress(dataBlock),
StandardCharsets.ISO_8859_1);
compiledData +=
new String(
fileEditor.toBytes(compressedBlock.length(), 2),
StandardCharsets.ISO_8859_1) +
compressedBlock;
}
// byte[] tempData = fileEditor.compress(inputData);
// String compiledData = new String(tempData, StandardCharsets.ISO_8859_1);
sendData(compiledData);
}
private void alert(String title, String content) {
@@ -137,7 +151,8 @@ public class generatorView extends ConstraintLayout {
private void sendData(String data){
minQrSize = Math.round(data.length()/maxQrCount);
// minQrSize = 0;
minQrSize = Math.round(data.length()/maxQrCount)+1;
qrSizeSlider.setMax(maxQrSize-minQrSize);
qrSpeedSlider.setMax((minQrSpeed-maxQrSpeed)*2);
@@ -165,8 +180,9 @@ public class generatorView extends ConstraintLayout {
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
qrSize = seekBar.getProgress() + minQrSpeed;
qrCount = (data.length()/qrSize)+1;
qrSize = seekBar.getProgress() + minQrSize;
// qrCount = (int)Math.ceil((double) (data.length()+1)/qrSize);
qrCount = (int)((data.length()+1)/qrSize)+1;
qrIndexD.setText(String.valueOf(qrCount));
sendData(data);
}
@@ -175,6 +191,9 @@ public class generatorView extends ConstraintLayout {
qrSpeedSlider.setProgress(defaultQrDelay+5);
qrBitmaps = new ArrayList<Bitmap>();
int randID = new Random().nextInt(255);
for(int i=0;i<=((data.length()+1)/qrSize);i++){
final int start = i*qrSize;
int end = (i+1)*qrSize;
@@ -184,9 +203,10 @@ public class generatorView extends ConstraintLayout {
try {
// alert("test", ""+Math.ceil((double)data.length()/(double)qrSize));
qrBitmaps.add(generateQrCode(
String.valueOf(fileEditor.toChar(fileEditor.internalDataVersion)) +
String.valueOf(fileEditor.toChar(i)) +
String.valueOf(fileEditor.toChar(qrCount-1)) +
String.valueOf(fileEditor.byteToChar(fileEditor.internalDataVersion)) +
String.valueOf(fileEditor.byteToChar(randID)) +
String.valueOf(fileEditor.byteToChar(i)) +
String.valueOf(fileEditor.byteToChar(qrCount-1)) +
data.substring(start, end)
));
}catch (WriterException e){
@@ -202,6 +222,7 @@ 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) {
@@ -0,0 +1,88 @@
package com.astatin3.scoutingapp2025.ui.transfer;
// From https://github.com/dlazaro66/QRCodeReaderView/blob/master/samples/src/main/java/com/example/qr_readerexample/PointsOverlayView.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
public class qrOverlayView extends View {
PointF[] points;
int[] barColors;
private Paint paint;
private final int barHeight = 50;
private final int barMargin = 5;
public qrOverlayView(Context context) {
super(context);
init();
}
public qrOverlayView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public qrOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.YELLOW);
paint.setStyle(Paint.Style.FILL);
}
public void setPoints(PointF[] points) {
this.points = points;
invalidate();
}
public void setBar(int[] barColors){
this.barColors = barColors;
invalidate();
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (points != null) {
for (PointF pointF : points) {
canvas.drawCircle(pointF.x, pointF.y, 10, paint);
}
}
if(barColors != null){
final int width = (int)(getWidth()/barColors.length);
final int top = 0;
final int bottom = barHeight;
for(int i=0;i<barColors.length;i++){
final int num = barColors[i];
int c = Color.RED;
if(num == 2){
c = Color.GREEN;
}else if(num == 1){
c = Color.YELLOW;
}
final Paint p = new Paint();
p.setColor(c);
canvas.drawRect(new Rect(
(i*width)+barMargin, top+barMargin,
((i+1)*width)-barMargin, bottom-barMargin
), p);
}
}
}
}
@@ -6,19 +6,15 @@ import android.content.Context;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.astatin3.scoutingapp2025.databinding.FragmentTransferBinding;
import com.astatin3.scoutingapp2025.fileEditor;
import com.astatin3.scoutingapp2025.qrPointsOverlayView;
import com.dlazaro66.qrcodereaderview.QRCodeReaderView;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.zip.DataFormatException;
public class scannerView extends ConstraintLayout {
@@ -29,19 +25,22 @@ public class scannerView extends ConstraintLayout {
}
private QRCodeReaderView qrCodeReaderView;
private qrPointsOverlayView pointsOverlayView;
private qrOverlayView qrOverlayView;
private String[] qrDataArr;
private int randID;
private class codeReadListener implements QRCodeReaderView.OnQRCodeReadListener {
@Override
public void onQRCodeRead(String text, PointF[] points) {
pointsOverlayView.setPoints(points);
qrOverlayView.setPoints(points);
compileData(
fileEditor.fromChar(text.charAt(0)),
fileEditor.fromChar(text.charAt(1)),
(fileEditor.fromChar(text.charAt(2))+1),
text.substring(3)
fileEditor.byteFromChar(text.charAt(0)),
fileEditor.byteFromChar(text.charAt(1)),
fileEditor.byteFromChar(text.charAt(2)),
(fileEditor.byteFromChar(text.charAt(3))+1),
text.substring(4)
);
}
}
@@ -71,13 +70,13 @@ public class scannerView extends ConstraintLayout {
qrCodeReaderViewParams.height = ActionBar.LayoutParams.MATCH_PARENT;
qrCodeReaderView.setLayoutParams(qrCodeReaderViewParams);
pointsOverlayView = new qrPointsOverlayView(getContext());
pointsOverlayView.bringToFront();
this.addView(pointsOverlayView);
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;
pointsOverlayView.setLayoutParams(pointsOverlayViewParams);
qrOverlayView.setLayoutParams(pointsOverlayViewParams);
qrCodeReaderView.startCamera();
@@ -96,28 +95,46 @@ public class scannerView extends ConstraintLayout {
});
}
private void compileData(int dataVersion, int qrIndex, int qrCount, String qrData){
private void compileData(int dataVersion, int randID, int qrIndex, int qrCount, String qrData){
if(dataVersion != fileEditor.internalDataVersion){
alert("Error", "Incorrect data version");
return;
}
if(qrDataArr == null || qrDataArr.length != qrCount){
// Reset code array if ID Changes
if(randID != this.randID){
this.randID = randID;
qrDataArr = new String[qrCount];
}
final boolean updated;
if(qrDataArr[qrIndex] == null) {
qrDataArr[qrIndex] = qrData;
alert((qrIndex+1)+"/"+qrCount, qrData);
updated = true;
}else{
updated = false;
}
int count = 0;
int[] barColors = new int[qrCount];
for(int i =0;i<qrCount;i++){
if(qrDataArr[i] != null){
barColors[i] = 2;
count++;
}
if(i == qrIndex){
barColors[i] = 1;
}else if(qrDataArr[i] == null) {
barColors[i] = 0;
}
}
if(count >= qrCount){
qrOverlayView.setBar(barColors);
if(updated && count >= qrCount){
// I guess String.join does not like non-ascii text
String compiledData = "";
@@ -128,13 +145,31 @@ public class scannerView extends ConstraintLayout {
try {
byte[] compiledBytes = compiledData.getBytes(StandardCharsets.ISO_8859_1);
alert("completed", new String(fileEditor.decompress(compiledBytes), StandardCharsets.ISO_8859_1));
// alert(""+compiledBytes.length, fileEditor.binaryVisualize(compiledBytes));
// Log.i("Info", fileEditor.binaryVisualize(compiledBytes));
// alert("completed", new String(fileEditor.decompress(compiledBytes), StandardCharsets.ISO_8859_1));
alert("completed", blockUncompress(compiledBytes));
}catch (Exception e){
// alert("completed", compiledData);
e.printStackTrace();
}
}
}
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);
uncompressedData += new String(
fileEditor.decompress(
fileEditor.getByteBlock(
data, curIndex+2, curIndex+blockLength+2)
), StandardCharsets.ISO_8859_1
);
curIndex += blockLength+2;
}
return uncompressedData;
}
}