如何在 Gluon 中生成带有图像叠加的二维码?
How to generate qr code with Image overlay in Gluon?
我想生成QR CODE,必须把logo放在中间。我检查了 zxing 库,并通过阅读此代码 () 使用 Java 应用程序完成了它。但据我所知,我们不能在 android/ios 中使用 swing。那么如何处理呢?
基于 you are referring, the accepted answer pointed to this post, where you can see that the trick to generate a QR that allows hiding its center part without affecting its readability by a QR scanner, can be done by increasing the error level:
30% (H) of error correction were a error correction of level H should result in a QRCode that is still valid even when it’s 30% obscured
作为此 的后续内容,您可以在对文本进行编码时仅包含一个提示:
public Image generateQR(int width, int height) {
File root = Services.get(StorageService.class)
.flatMap(StorageService::getPrivateStorage)
.orElseThrow(() -> new RuntimeException("Storage Service not found"));
String uuid = Services.get(DeviceService.class)
.map(DeviceService::getUuid)
.orElse("123456789");
MultiFormatWriter codeWriter = new MultiFormatWriter();
// Add maximum correction
Map<EncodeHintType, ErrorCorrectionLevel> hints = new HashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
try {
BarcodeFormat format = BarcodeFormat.QR_CODE;
BitMatrix bitMatrix = codeWriter.encode(uuid, format,
width, height, hints); // <--- add hints
...
return writableImage;
}
}
您将获得一个 QR 码图像,没有叠加层。
下一步是在不使用 Swing 的情况下将此图像与您的徽标图像结合起来。但实际上我们不需要这样做:我们可以只使用两个 ImageView
个节点!
ImageView imageView = new ImageView();
imageView.setFitWidth(256);
imageView.setFitHeight(256);
imageView.setImage(service.generateQR(256, 256));
ImageView overlay = new ImageView(
new Image(getClass().getResourceAsStream("/icon.png")));
overlay.setFitWidth(80);
overlay.setFitHeight(80);
StackPane combinedQR = new StackPane(imageView, overlay);
...
生成的代码仍然可读。
您还可以使用结果,为图像添加混合效果,例如:
overlay.setBlendMode(BlendMode.ADD);
但这取决于您的徽标图像与二维码的融合方式。
最后,如果您仍然需要单个组合图像,您可以创建堆栈窗格的快照:
WritableImage snapshot = combinedQR.snapshot(new SnapshotParameters(), null);
受何塞回答的启发,我用 Kotlin 为 Android 设备编写了自己的解决方案。
首先,将 ZXing 添加到您的项目中:
implementation "com.google.zxing:core:3.4.0"
创建一个虚拟可绘制对象并将其放入您的 res/drawable
文件夹中:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/colorAccent" />
<size
android:width="48dp"
android:height="48dp" />
</shape>
然后将以下扩展添加到您的项目中:
String.encodeAsQrCodeBitmap
将字符串转换为二维码位图。它还接受一个 overlayBitmap
,如果为 null,则仅将字符串转换为 QR 码,您可以自定义 QR 码的颜色。
Bitmap.addOverlayToCenter
将两个位图合并为一个并将叠加位图放在中心。
Int.dpToPx()
将 DP 转换为像素。
@Throws(WriterException::class)
fun String.encodeAsQrCodeBitmap(
dimension: Int,
overlayBitmap: Bitmap? = null,
@ColorInt color1: Int = Color.BLACK,
@ColorInt color2: Int = Color.WHITE
): Bitmap? {
val result: BitMatrix
try {
result = MultiFormatWriter().encode(
this,
BarcodeFormat.QR_CODE,
dimension,
dimension,
hashMapOf(EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.H)
)
} catch (e: IllegalArgumentException) {
// Unsupported format
return null
}
val w = result.width
val h = result.height
val pixels = IntArray(w * h)
for (y in 0 until h) {
val offset = y * w
for (x in 0 until w) {
pixels[offset + x] = if (result.get(x, y)) color1 else color2
}
}
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, dimension, 0, 0, w, h)
return if (overlayBitmap != null) {
bitmap.addOverlayToCenter(overlayBitmap)
} else {
bitmap
}
}
fun Bitmap.addOverlayToCenter(overlayBitmap: Bitmap): Bitmap {
val bitmap2Width = overlayBitmap.width
val bitmap2Height = overlayBitmap.height
val marginLeft = (this.width * 0.5 - bitmap2Width * 0.5).toFloat()
val marginTop = (this.height * 0.5 - bitmap2Height * 0.5).toFloat()
val canvas = Canvas(this)
canvas.drawBitmap(this, Matrix(), null)
canvas.drawBitmap(overlayBitmap, marginLeft, marginTop, null)
return this
}
fun Int.dpToPx(): Int {
return (this * Resources.getSystem().displayMetrics.density).toInt()
}
然后在您的 Fragment/Activity 中执行以下操作:从资源中获取叠加层,将字符串转换为 QR 码并将其显示在 ImageView 中:
try {
val displayMetrics = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getMetrics(displayMetrics)
val size = displayMetrics.widthPixels.coerceAtMost(displayMetrics.heightPixels)
val overlay = ContextCompat.getDrawable(requireContext(), R.drawable.dummy_round)
?.toBitmap(72.dpToPx(), 72.dpToPx())
val bitmap = "https://www.example.com".encodeAsQrCodeBitmap(size, overlay)
imageView.setImageBitmap(bitmap)
} catch (e: Exception) {
// handle Errors here
}
结果:
我想生成QR CODE,必须把logo放在中间。我检查了 zxing 库,并通过阅读此代码 (
基于
30% (H) of error correction were a error correction of level H should result in a QRCode that is still valid even when it’s 30% obscured
作为此
public Image generateQR(int width, int height) {
File root = Services.get(StorageService.class)
.flatMap(StorageService::getPrivateStorage)
.orElseThrow(() -> new RuntimeException("Storage Service not found"));
String uuid = Services.get(DeviceService.class)
.map(DeviceService::getUuid)
.orElse("123456789");
MultiFormatWriter codeWriter = new MultiFormatWriter();
// Add maximum correction
Map<EncodeHintType, ErrorCorrectionLevel> hints = new HashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
try {
BarcodeFormat format = BarcodeFormat.QR_CODE;
BitMatrix bitMatrix = codeWriter.encode(uuid, format,
width, height, hints); // <--- add hints
...
return writableImage;
}
}
您将获得一个 QR 码图像,没有叠加层。
下一步是在不使用 Swing 的情况下将此图像与您的徽标图像结合起来。但实际上我们不需要这样做:我们可以只使用两个 ImageView
个节点!
ImageView imageView = new ImageView();
imageView.setFitWidth(256);
imageView.setFitHeight(256);
imageView.setImage(service.generateQR(256, 256));
ImageView overlay = new ImageView(
new Image(getClass().getResourceAsStream("/icon.png")));
overlay.setFitWidth(80);
overlay.setFitHeight(80);
StackPane combinedQR = new StackPane(imageView, overlay);
...
生成的代码仍然可读。
您还可以使用结果,为图像添加混合效果,例如:
overlay.setBlendMode(BlendMode.ADD);
但这取决于您的徽标图像与二维码的融合方式。
最后,如果您仍然需要单个组合图像,您可以创建堆栈窗格的快照:
WritableImage snapshot = combinedQR.snapshot(new SnapshotParameters(), null);
受何塞回答的启发,我用 Kotlin 为 Android 设备编写了自己的解决方案。 首先,将 ZXing 添加到您的项目中:
implementation "com.google.zxing:core:3.4.0"
创建一个虚拟可绘制对象并将其放入您的 res/drawable
文件夹中:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/colorAccent" />
<size
android:width="48dp"
android:height="48dp" />
</shape>
然后将以下扩展添加到您的项目中:
String.encodeAsQrCodeBitmap
将字符串转换为二维码位图。它还接受一个 overlayBitmap
,如果为 null,则仅将字符串转换为 QR 码,您可以自定义 QR 码的颜色。
Bitmap.addOverlayToCenter
将两个位图合并为一个并将叠加位图放在中心。
Int.dpToPx()
将 DP 转换为像素。
@Throws(WriterException::class)
fun String.encodeAsQrCodeBitmap(
dimension: Int,
overlayBitmap: Bitmap? = null,
@ColorInt color1: Int = Color.BLACK,
@ColorInt color2: Int = Color.WHITE
): Bitmap? {
val result: BitMatrix
try {
result = MultiFormatWriter().encode(
this,
BarcodeFormat.QR_CODE,
dimension,
dimension,
hashMapOf(EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.H)
)
} catch (e: IllegalArgumentException) {
// Unsupported format
return null
}
val w = result.width
val h = result.height
val pixels = IntArray(w * h)
for (y in 0 until h) {
val offset = y * w
for (x in 0 until w) {
pixels[offset + x] = if (result.get(x, y)) color1 else color2
}
}
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, dimension, 0, 0, w, h)
return if (overlayBitmap != null) {
bitmap.addOverlayToCenter(overlayBitmap)
} else {
bitmap
}
}
fun Bitmap.addOverlayToCenter(overlayBitmap: Bitmap): Bitmap {
val bitmap2Width = overlayBitmap.width
val bitmap2Height = overlayBitmap.height
val marginLeft = (this.width * 0.5 - bitmap2Width * 0.5).toFloat()
val marginTop = (this.height * 0.5 - bitmap2Height * 0.5).toFloat()
val canvas = Canvas(this)
canvas.drawBitmap(this, Matrix(), null)
canvas.drawBitmap(overlayBitmap, marginLeft, marginTop, null)
return this
}
fun Int.dpToPx(): Int {
return (this * Resources.getSystem().displayMetrics.density).toInt()
}
然后在您的 Fragment/Activity 中执行以下操作:从资源中获取叠加层,将字符串转换为 QR 码并将其显示在 ImageView 中:
try {
val displayMetrics = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getMetrics(displayMetrics)
val size = displayMetrics.widthPixels.coerceAtMost(displayMetrics.heightPixels)
val overlay = ContextCompat.getDrawable(requireContext(), R.drawable.dummy_round)
?.toBitmap(72.dpToPx(), 72.dpToPx())
val bitmap = "https://www.example.com".encodeAsQrCodeBitmap(size, overlay)
imageView.setImageBitmap(bitmap)
} catch (e: Exception) {
// handle Errors here
}
结果: