1

I want to generate QR CODE which have to put logo in the center. I have check the zxing library and I did it with a Java application by reading this code (How to generate QR code with logo inside it?). But as I know we can't use swing in android/ios. So how to deal with this ?

piet.t
  • 11,718
  • 21
  • 43
  • 52
Sovandara LENG
  • 129
  • 1
  • 12
  • Check https://blog.idrsolutions.com/2012/11/convert-bufferedimage-to-javafx-image/ . Don't use `SwingFXUtils#toFXImage` as Gluon Mobile doesn't support Swing modules. – ItachiUchiha Feb 11 '19 at 15:16
  • What do you mean? I don't understand. – Sovandara LENG Feb 12 '19 at 01:24
  • Once you have a `BufferedImage`, you can convert it to a JavaFX image using the link I posted in my comment above. – ItachiUchiha Feb 12 '19 at 03:59
  • So when I generate barcode by using WritableImage and PixelWriter how can I convert to bufferedImage? Because I need bufferImage to combined overlay with the barcode – Sovandara LENG Feb 12 '19 at 04:08
  • Don't you have a BufferedImage after overlay according to your "Generate QR" link in the question? – ItachiUchiha Feb 12 '19 at 04:45
  • When I generate QR, I used another approach so what I got is WritableImage ( you change check this link https://stackoverflow.com/questions/54445369/how-to-generate-qr-code-or-bar-code-by-using-gluon-mobile-in-order-to-support-mu). So when I need to add overlay I need a bufferedImage type. – Sovandara LENG Feb 12 '19 at 06:25

2 Answers2

3

Based on the link 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

As a follow-up of this question, you can just include a hint when encoding the text:

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;
    }
}

You will be getting a QR code image, without overlay.

The next step is to combine this image with your logo image, without using Swing. But we actually don't need to do that: we can just use two ImageView nodes!

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);
...

The resulting code is still readable.

You can also play with the result, adding a blend effect to the images, like:

overlay.setBlendMode(BlendMode.ADD);

but this will depend on how your logo image blends with the QR.

Finally, if you still need a single combined image, you can create a snapshot of the stack pane:

WritableImage snapshot = combinedQR.snapshot(new SnapshotParameters(), null);
José Pereda
  • 44,311
  • 7
  • 104
  • 132
  • It is working perfect. Please explain me how did you find the solution. It might help me in the future. Thx in Advance – Sovandara LENG Feb 13 '19 at 10:23
  • I've edited my answer, linking to the post that explained the use of hints – José Pereda Feb 13 '19 at 10:33
  • For the hints, I understand. but I want to know why you know the way of using two ImageView. I try to solved this problem by try to find the way to drawing image without depending on Swing (think of WritableImage and PixelWriter, etc) I also try of using BufferedImage. But not luck – Sovandara LENG Feb 13 '19 at 10:45
  • 1
    With JavaFX you can do an overlay with any number of nodes and a `StackPane`, as it piles them up while keeps them centered, so if you have to compose two images, the natural solution is to use two `ImageView` nodes in a `StackPane`. No need for Swing at all. Does it make sense? – José Pereda Feb 13 '19 at 10:48
  • Yeh, Thank you very much for your explanation – Sovandara LENG Feb 13 '19 at 10:52
1

Inspired by José answer, I wrote my own solution in Kotlin for Android devices. First, add ZXing to your project:

implementation "com.google.zxing:core:3.4.0"

Create a dummy drawable and put it in your res/drawable folder:

<?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>

Then add the following Extensions to your project:

String.encodeAsQrCodeBitmap converts a String into a QR Code Bitmap. It also accepts an overlayBitmap, if null then only the String is converted into a QR-Code and you can customize the colours of the QR Code.

Bitmap.addOverlayToCenter merges two Bitmaps into one and places the overlay bitmap in the centre.

Int.dpToPx() converts DPs to pixels.

@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()
}

Then do in your Fragment/Activity the following: get the overlay from the resources, convert a String into a QR-Code and display it in an 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
}

The result:

enter image description here

Paul Spiesberger
  • 5,630
  • 1
  • 43
  • 53