JavaFx generates QR code tool class encapsulation

In the past, Star Music Downloader needed to generate QR codes. At that time, an open source library was used to implement it. But after a while, I found that the library had too many dependencies, and there was a dependency on http-client, which made the software bigger. Double, and sometimes downloading dependencies and reporting errors during development, I want to change the solution

So I found a solution on the Internet, and finally only need to rely on two dependencies of two zxing to realize the function

This article is written based on the TornadoFx framework, the package tool code is the kotlin version, and the tool class has been packaged in the common-controls library

The tool supports QR code generation with logo icon and bottom text

Code encapsulation

1. Introduce dependencies

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.5.0</version>
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.5.0</version>
</dependency>

2. Use

Since there are too many tool codes that are inconvenient to read, let’s talk about the use first, and the tool codes will be placed below

There are two core methods, as shown in the following code, the other method is with the Swing keyword, which is to generate the Image object in the Swing package

The getQRcodeFxImg() method is to directly generate the Image object of Fx, which can be used directly in JavaFx

/**
 * Initialization settings
 *
 * @param qrcodeSize QR code size, the default is 320 (ie 320*320)
 * @param logoSize logo icon size, the default is 80 (ie 80*80)
 * @param bottomTextSize bottom text size, default 20px
 * @param qrcodeType QR code image format, the default is png
 */
fun initConfig(qrcodeSize: Int = 320, logoSize: Int = 80, bottomTextSize: Int = 20, qrcodeType: String = "PNG")

/**
 * Generate a QR code image
 *
 * @param data QR code text content
 * @param logoPath the path of the icon image
 * @param bottomText bottom text
 * @return img object of fx
 */
fun getQRcodeFxImg(data: String?, logoPath: String?=null, bottomText: String?=null): WritableImage

It is also relatively simple to use:

//Get the image object of swing
val buImg = QRCodeUtil.getQRcodeFxImg("This is the test text")
val buImg1 = QRCodeUtil.getQRcodeFxImg("This is the test text", null, "bottom text")
val buImg2 = QRCodeUtil.getQRcodeFxImg("This is the test text", "/x5.jpg", "Bottom text")

val list = listOf(buImg, buImg1, buImg2)

hbox(20.0) {
    list. forEach {
        imageview(it) {
            fitWidth = 200.0
            fitHeight = 200.0
        }
    }
}

3. Tool library code

/**
 * QR code generation tools
 * Created by stars-one
 */
object QRCodeUtil {
    private var QRCODE_SIZE = 320 // QR code size, both width and height are 320
    private var LOGO_SIZE = 80 // The size of the logo in the QR code, with the same width and height 80*80
    private var BOTTOM_TEXT_SIZE = 20 // text size of bottom text
    private var FORMAT_TYPE = "PNG" // QR code image type

    /**
     * Initialization settings
     *
     * @param qrcodeSize QR code size, the default is 320 (ie 320*320)
     * @param logoSize logo icon size, the default is 80 (ie 80*80)
     * @param bottomTextSize bottom text size, default 20px
     * @param qrcodeType QR code image format, the default is png
     */
    fun initConfig(qrcodeSize: Int = 320, logoSize: Int = 80, bottomTextSize: Int = 20, qrcodeType: String = "PNG") {
        QRCODE_SIZE = qrcodeSize
        LOGO_SIZE = logoSize
        BOTTOM_TEXT_SIZE = bottomTextSize
        FORMAT_TYPE = qrcodeType
    }

    /**
     * Generate a QR code image
     *
     * @param data QR code text content
     * @param logoPath the path of the icon image
     * @param bottomText bottom text
     * @return
     */
    fun getQRcodeFxImg(data: String?, logoPath: String?=null, bottomText: String?=null): WritableImage {
        val resources = ResourceLookup(this)
        val url = if (logoPath == null) {
            null
        } else {
            resources.url(logoPath)
        }
        val swingImg = getQRCodeSwingImg(data, url, bottomText)
        return SwingFXUtils.toFXImage(swingImg, null)
    }

    /**
     * Logo is required by default, no bottom text
     * Return BufferedImage can use ImageIO.write(BufferedImage, "png", outputStream); output
     *
     * @param dataStr
     * @return return BufferedImage can use ImageIO.write(BufferedImage, "png", outputStream); output
     */
    @Throws(Exception::class)
    fun getQRCodeSwingImg(dataStr: String?): BufferedImage {
        return getQRCodeSwingImg(dataStr, null, null)
    }

    /**
     * Logo is required by default, no bottom text
     *
     * @param dataStr
     * @return return byte array
     */
    @Throws(Exception::class)
    fun getQRCodeByte(dataStr: String?): ByteArray {
        val bufferedImage = getQRCodeSwingImg(dataStr, null, null)
        val outputStream = ByteArrayOutputStream()
        ImageIO.write(bufferedImage, FORMAT_TYPE, outputStream)
        return outputStream.toByteArray()
    }

    /**
     * The logo is required by default, including the bottom text. If the text is empty, the text will not be displayed
     * Return BufferedImage can use ImageIO.write(BufferedImage, "png", outputStream); output
     *
     * @param dataStr
     * @return
     */
    @Throws(Exception::class)
    fun getQRCodeSwingImg(dataStr: String?, bottomText: String?): BufferedImage {
        return getQRCodeSwingImg(dataStr, null, bottomText)
    }

    /**
     * The logo is required by default, including the bottom text. If the text is empty, the text will not be displayed
     *
     * @param dataStr
     * @return return byte array
     */
    @Throws(Exception::class)
    fun getQRCodeByte(dataStr: String?, bottomText: String?): ByteArray {
        val bufferedImage = getQRCodeSwingImg(dataStr, null, bottomText)
        val outputStream = ByteArrayOutputStream()
        ImageIO.write(bufferedImage, FORMAT_TYPE, outputStream)
        return outputStream.toByteArray()
    }

    /**
     * Get QR code image
     *
     * @param dataStr QR code content
     * @param needLogo whether to add logo
     * @param bottomText Bottom text is not displayed if it is empty
     * @return
     */
    @Throws(Exception::class)
    fun getQRCodeSwingImg(dataStr: String?, url: URL?, bottomText: String?): BufferedImage {
        if (dataStr == null) {
            throw RuntimeException("Does not contain any information")
        }
        val hints = HashMap<EncodeHintType, Any?>()
        hints[EncodeHintType.CHARACTER_SET] = "utf-8" //Define the encoding of the content character set
        hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.L //Define error correction level
        hints[EncodeHintType.MARGIN] = 1
        val qrCodeWriter = QRCodeWriter()
        val bitMatrix = qrCodeWriter.encode(dataStr, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints)
        val width = bitMatrix. width
        val height = bitMatrix. height
        var tempHeight = height
        if (StringUtils. isNotBlank(bottomText)) {
            tempHeight = tempHeight + 12
        }
        val image = BufferedImage(width, tempHeight, BufferedImage.TYPE_INT_RGB)
        for (x in 0 until width) {
            for (y in 0 until height) {
                image.setRGB(x, y, if (bitMatrix[x, y]) -0x1000000 else -0x1)
            }
        }
        // Determine whether to add a logo
        if (url != null) {
            insertLogoImage(image, url)
        }
        // Determine whether to add bottom text
        if (StringUtils. isNotBlank(bottomText)) {
            addFontImage(image, bottomText)
        }
        return image
    }

    /**
     * Insert logo picture
     *
     * @param source QR code image
     * @throws Exception
     */
    @Throws(Exception::class)
    private fun insertLogoImage(source: BufferedImage, url: URL) {
        var src: Image = ImageIO. read(url)
        val width = LOGO_SIZE
        val height = LOGO_SIZE
        val image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH)
        val tag = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
        val g = tag. graphics
        g.drawImage(image, 0, 0, null) // Draw the reduced image
        g. dispose()
        src = image

        // insert LOGO
        val graph = source. createGraphics()
        val x = (QRCODE_SIZE - width) / 2
        val y = (QRCODE_SIZE - height) / 2
        graph. drawImage(src, x, y, width, height, null)
        val shape: Shape = RoundRectangle2D.Float(x.toFloat(), y.toFloat(), width.toFloat(), width.toFloat(), 6f, 6f)
        graph.stroke = BasicStroke(3f)
        graph. draw(shape)
        graph. dispose()
    }

    private fun addFontImage(source: BufferedImage, declareText: String?) {
        //Generate image
        val defineWidth = QRCODE_SIZE
        val defineHeight = 20
        val textImage = BufferedImage(defineWidth, defineHeight, BufferedImage.TYPE_INT_RGB)
        val g2 = textImage. graphics as Graphics2D
        //Enable text anti-aliasing
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
        g2.background = Color.WHITE
        g2. clearRect(0, 0, defineWidth, defineHeight)
        g2.paint = Color.BLACK
        val context = g2.fontRenderContext
        //When deploying linux, you need to pay attention. If linux does not have this font, it will display a square
        val font = Font("宋体", Font.BOLD, BOTTOM_TEXT_SIZE)
        g2.font = font
        val lineMetrics = font. getLineMetrics(declareText, context)
        val fontMetrics: FontMetrics = FontDesignMetrics. getMetrics(font)
        val offset = ((defineWidth - fontMetrics. stringWidth(declareText)) / 2). toFloat()
        val y = (defineHeight + lineMetrics.ascent - lineMetrics.descent - lineMetrics.leading) / 2
        g2.drawString(declareText, offset.toInt(), y.toInt())
        val graph = source. createGraphics()
        //Enable text anti-aliasing
        graph.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
        //add image
        val width = textImage. getWidth(null)
        val height = textImage. getHeight(null)
        val src: Image = textImage
        graph.drawImage(src, 0, QRCODE_SIZE - 8, width, height, Color.WHITE, null)
        graph. dispose()
    }
}