Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generator for dot qr codes #241

Merged
merged 1 commit into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
Loading