Skip to content

Commit

Permalink
Add generator for dot qr codes
Browse files Browse the repository at this point in the history
  • Loading branch information
udenr committed Jul 26, 2024
1 parent 734efc5 commit 1cb1a2c
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.secuso.privacyfriendlycodescanner.qrscanner.generator;

import static android.content.Context.WINDOW_SERVICE;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.media.MediaScannerConnection;
import android.net.Uri;
Expand All @@ -15,14 +20,19 @@
import android.view.Display;
import android.view.WindowManager;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import com.google.zxing.qrcode.encoder.Encoder;
import com.google.zxing.qrcode.encoder.QRCode;

import java.io.File;
import java.io.FileOutputStream;
Expand All @@ -31,9 +41,9 @@
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;

import static android.content.Context.WINDOW_SERVICE;
import java.util.Map;

public class QRGeneratorUtils {

Expand Down Expand Up @@ -85,36 +95,115 @@ public static Uri getCachedUri() {
return cache;
}

public static Uri createImage(Context context, String qrInputText, Contents.Type qrType, BarcodeFormat barcodeFormat, String errorCorrectionLevel) {

private static int getDimension(Context context) {
//Find screen size
WindowManager manager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
Point point = new Point();
display.getSize(point);
int width = point.x;
int height = point.y;
int smallerDimension = width < height ? width : height;
smallerDimension = smallerDimension * 3 / 4;

//Encode with a QR Code image
QRCodeEncoder qrCodeEncoder = new QRCodeEncoder(qrInputText,
null,
qrType,
barcodeFormat.toString(),
smallerDimension);
Bitmap bitmap_ = null;
try {
bitmap_ = qrCodeEncoder.encodeAsBitmap(errorCorrectionLevel);
// return bitmap_;
int smallerDimension = Math.min(width, height);
return smallerDimension * 3 / 4;
}

} catch (WriterException e) {
e.printStackTrace();
public static Uri createImage(Context context, String qrInputText, Contents.Type qrType, BarcodeFormat barcodeFormat, String errorCorrectionLevel, boolean dots) {
int smallerDimension = getDimension(context);

Bitmap bitmap_ = null;
if (!dots) {
//Encode with a QR Code image
QRCodeEncoder qrCodeEncoder = new QRCodeEncoder(qrInputText,
null,
qrType,
barcodeFormat.toString(),
smallerDimension);
try {
bitmap_ = qrCodeEncoder.encodeAsBitmap(errorCorrectionLevel);
} catch (WriterException e) {
e.printStackTrace();
}
} else {
final Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.MARGIN, 1);
QRCode code;
try {
code = Encoder.encode(qrInputText, ErrorCorrectionLevel.valueOf(errorCorrectionLevel), hints);
} catch (WriterException e) {
throw new RuntimeException(e);
}
bitmap_ = createDotQRCode(code, smallerDimension, smallerDimension, Color.BLACK, Color.WHITE, 1);
}

return cacheImage(context, bitmap_);
}

private static Bitmap createDotQRCode(QRCode code, int width, int height, @ColorInt int color, @ColorInt int backgroundColor, int quietZone) {
Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(image);
canvas.drawColor(backgroundColor);
Paint paint = new Paint();
paint.setColor(color);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);


ByteMatrix input = code.getMatrix();
if (input == null) {
throw new IllegalArgumentException();
}

final int QR_WIDTH = input.getWidth() + (quietZone * 2);
final int QR_HEIGHT = input.getHeight() + (quietZone * 2);
final int OUTPUT_WIDTH = Math.max(width, QR_WIDTH);
final int OUTPUT_HEIGHT = Math.max(height, QR_HEIGHT);

final float SCALE = Math.min((float) OUTPUT_WIDTH / (float) QR_WIDTH, (float) OUTPUT_HEIGHT / (float) QR_HEIGHT); //scale from ByteMatrix to Canvas
final int POSITION_PATTERN_SIZE = 7; //size of the position pattern inside the ByteMatrix
final float POSITION_PATTERN_RADIUS = (SCALE * POSITION_PATTERN_SIZE) / 2f;
final float CIRCLE_RADIUS = (SCALE * 0.35f);
final float PADDING_LEFT = (OUTPUT_WIDTH - (input.getWidth() * SCALE)) / 2.0f + CIRCLE_RADIUS / 2.0f;
final float PADDING_TOP = (OUTPUT_HEIGHT - (input.getHeight() * SCALE)) / 2.0f + CIRCLE_RADIUS / 2.0f;

for (int y = 0; y < input.getHeight(); y++) {
for (int x = 0; x < input.getWidth(); x++) {
if (input.get(x, y) == 1) {
boolean isInPositionPatternArea = //do not draw anything inside the position pattern regions
x <= POSITION_PATTERN_SIZE && y <= POSITION_PATTERN_SIZE || //top left position pattern
x >= input.getWidth() - POSITION_PATTERN_SIZE && y <= POSITION_PATTERN_SIZE || //top right position pattern
x <= POSITION_PATTERN_SIZE && y >= input.getHeight() - POSITION_PATTERN_SIZE; //bottom left position pattern
if (!isInPositionPatternArea) {
float outputX = PADDING_LEFT + x * SCALE;
float outputY = PADDING_TOP + y * SCALE;
canvas.drawCircle(outputX + CIRCLE_RADIUS, outputY + CIRCLE_RADIUS, CIRCLE_RADIUS, paint);
}
}
}
}

//draw position patterns
drawPositionPattern(canvas, color, backgroundColor, PADDING_LEFT, PADDING_TOP, POSITION_PATTERN_RADIUS);
drawPositionPattern(canvas, color, backgroundColor, PADDING_LEFT + (input.getWidth() - POSITION_PATTERN_SIZE) * SCALE, PADDING_TOP, POSITION_PATTERN_RADIUS);
drawPositionPattern(canvas, color, backgroundColor, PADDING_LEFT, PADDING_TOP + (input.getHeight() - POSITION_PATTERN_SIZE) * SCALE, POSITION_PATTERN_RADIUS);

return image;
}

private static void drawPositionPattern(Canvas canvas, @ColorInt int color, @ColorInt int backgroundColor, float x, float y, float patternRadius) {
final float BACKGROUND_CIRCLE_RADIUS = patternRadius * 5f / 7f;
final float MIDDLE_DOT_RADIUS = patternRadius * 3f / 7f;

Paint paint = new Paint();
paint.setColor(color);
Paint bgPaint = new Paint();
bgPaint.setColor(backgroundColor);

canvas.drawCircle(x + patternRadius, y + patternRadius, patternRadius, paint);
canvas.drawCircle(x + patternRadius, y + patternRadius, BACKGROUND_CIRCLE_RADIUS, bgPaint);
canvas.drawCircle(x + patternRadius, y + patternRadius, MIDDLE_DOT_RADIUS, paint);
}

public static void saveCachedImageToExternalStorage(Context context) {
Bitmap bitmap = null;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,17 @@ public class QrGeneratorDisplayActivity extends AppCompatActivity {
String qrInputText = "";
Contents.Type qrInputType = Contents.Type.UNDEFINED;

private static final String BARCODE_FORMAT_QR_CODE_DOTS = BarcodeFormat.QR_CODE.name() + "_DOTS";
private String[] barcodeFormats = new String[]{
BarcodeFormat.QR_CODE.name(),
BARCODE_FORMAT_QR_CODE_DOTS,
BarcodeFormat.AZTEC.name(),
BarcodeFormat.DATA_MATRIX.name(),
BarcodeFormat.PDF_417.name(),
BarcodeFormat.CODE_128.name()};
private Integer[] barcodeFormatIcons = new Integer[]{
R.drawable.ic_baseline_qr_code_24dp,
R.drawable.ic_baseline_qr_code_dots_24dp,
R.drawable.ic_aztec_code_24dp,
R.drawable.ic_data_matrix_code_24dp,
R.drawable.ic_pdf_417_code_24dp,
Expand Down Expand Up @@ -137,7 +140,7 @@ private void initDropDownMenus() {
}

private void updateDropDownMenus() {
barcodeFormat = BarcodeFormat.valueOf(barcodeFormatMenu.getText().toString());
UpdateBarcodeFormatFromMenuValue();

if (barcodeFormat.equals(BarcodeFormat.QR_CODE)) {
currentErrorCorrections = errorCorrectionsQR;
Expand All @@ -151,7 +154,7 @@ private void updateDropDownMenus() {
updateErrorCorrectionMenu();
//Update icon
ImageView barcodeFormatIcon = findViewById(R.id.iconImageView);
Glide.with(this).load(AppCompatResources.getDrawable(this, barcodeFormatIcons[Arrays.asList(barcodeFormats).indexOf(barcodeFormat.name())])).into(barcodeFormatIcon);
Glide.with(this).load(AppCompatResources.getDrawable(this, barcodeFormatIcons[Arrays.asList(barcodeFormats).indexOf(barcodeFormatMenu.getText().toString())])).into(barcodeFormatIcon);

}

Expand All @@ -176,11 +179,15 @@ private void updateErrorCorrectionMenu() {
private void generateAndUpdateImage() {
ImageView myImage = findViewById(R.id.resultQRCodeImage);

barcodeFormat = BarcodeFormat.valueOf(barcodeFormatMenu.getText().toString());
UpdateBarcodeFormatFromMenuValue();
String errorCorrectionLevel = errorCorrectionMenu.getText().toString();
try {
Log.d(getClass().getSimpleName(), "Creating image...");
Glide.with(this).asBitmap().load(QRGeneratorUtils.createImage(this, qrInputText, qrInputType, barcodeFormat, errorCorrectionLevel)).into(new BitmapImageViewTarget(myImage));
if (barcodeFormatMenu.getText().toString().equals(BARCODE_FORMAT_QR_CODE_DOTS)) {
Glide.with(this).asBitmap().load(QRGeneratorUtils.createImage(this, qrInputText, qrInputType, barcodeFormat, errorCorrectionLevel, true)).into(new BitmapImageViewTarget(myImage));
} else {
Glide.with(this).asBitmap().load(QRGeneratorUtils.createImage(this, qrInputText, qrInputType, barcodeFormat, errorCorrectionLevel, false)).into(new BitmapImageViewTarget(myImage));
}
} catch (IllegalArgumentException e) {
Toast.makeText(this, R.string.code_generation_error, Toast.LENGTH_SHORT).show();
Log.d(getClass().getSimpleName(), "Error during code generation.", e);
Expand Down Expand Up @@ -263,6 +270,14 @@ protected void onDestroy() {
QRGeneratorUtils.purgeCacheFolder(this);
}

private void UpdateBarcodeFormatFromMenuValue() {
if (barcodeFormatMenu.getText().toString().equals(BARCODE_FORMAT_QR_CODE_DOTS)) {
barcodeFormat = BarcodeFormat.QR_CODE;
} else {
barcodeFormat = BarcodeFormat.valueOf(barcodeFormatMenu.getText().toString());
}
}

@Override
protected void onResume() {
super.onResume();
Expand Down
55 changes: 55 additions & 0 deletions app/src/main/res/drawable/ic_baseline_qr_code_dots_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorIcons"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/white"
android:pathData="M5,7 a2,2 0 1,1 4,0
a2,2 0 1,1 -4,0
h-2
a4,4 0 1,0 8,0
a4,4 0 1,0 -8,0 Z" />

<path
android:fillColor="@color/white"
android:pathData="M5,17 a2,2 0 1,1 4,0
a2,2 0 1,1 -4,0
h-2
a4,4 0 1,0 8,0
a4,4 0 1,0 -8,0 Z" />

<path
android:fillColor="@color/white"
android:pathData="M15,7 a2,2 0 1,1 4,0
a2,2 0 1,1 -4,0
h-2
a4,4 0 1,0 8,0
a4,4 0 1,0 -8,0 Z" />

<path
android:fillColor="@android:color/white"
android:pathData="M19,20a1,1 0 1,1 2,0a1,1 0 1,1 -2,0z" />
<path
android:fillColor="@android:color/white"
android:pathData="M13,14a1,1 0 1,1 2,0a1,1 0 1,1 -2,0z" />
<path
android:fillColor="@android:color/white"
android:pathData="M15,16a1,1 0 1,1 2,0a1,1 0 1,1 -2,0z" />
<path
android:fillColor="@android:color/white"
android:pathData="M13,18a1,1 0 1,1 2,0a1,1 0 1,1 -2,0z" />
<path
android:fillColor="@android:color/white"
android:pathData="M15,20a1,1 0 1,1 2,0a1,1 0 1,1 -2,0z" />
<path
android:fillColor="@android:color/white"
android:pathData="M17,18a1,1 0 1,1 2,0a1,1 0 1,1 -2,0z" />
<path
android:fillColor="@android:color/white"
android:pathData="M17,14a1,1 0 1,1 2,0a1,1 0 1,1 -2,0z" />
<path
android:fillColor="@android:color/white"
android:pathData="M19,16a1,1 0 1,1 2,0a1,1 0 1,1 -2,0z" />
</vector>

0 comments on commit 1cb1a2c

Please sign in to comment.