JavaFx 生成二維碼工具類封裝

one發表於2023-04-28

原文地址: JavaFx 生成二維碼工具類封裝 - Stars-One的雜貨小窩

之前星之音樂下載器有需要生成二維碼功能,當時用的是一個開源庫來實現的,但是沒過多久,發現那個庫依賴太多,有個http-client的依賴,把軟體都搞大了一倍,而且有時候開發的時候下載依賴還報錯,就想換個方案

於是在網上找了下解決方案,最終只需要依賴兩個zxing的兩個依賴即可實現功能

本文基於TornadoFx框架進行編寫,封裝工具程式碼是kotlin版本,工具類已經封裝在common-controls庫中

工具支援帶logo圖示,帶底部文字的二維碼生成

程式碼封裝

1.引入依賴

<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.使用

由於工具程式碼過多不便閱讀,就先講些使用,工具程式碼就放下面了

比較核心的就兩個方法,如下面程式碼所示,其他的方法是帶Swing關鍵字,就是生成Swing包中的Image物件

getQRcodeFxImg()方法就是直接生成Fx的Image物件,可以JavaFx中直接使用

/**
 * 初始化設定
 *
 * @param qrcodeSize 二維碼尺寸,預設為320(即320*320)
 * @param logoSize logo圖示尺寸,預設為80(即80*80)
 * @param bottomTextSize 底部文字大小,預設20px
 * @param qrcodeType 二維碼圖片格式,預設為png
 */
fun initConfig(qrcodeSize: Int = 320, logoSize: Int = 80, bottomTextSize: Int = 20, qrcodeType: String = "PNG")

/**
 * 生成二維碼圖片
 *
 * @param data 二維碼文字內容
 * @param logoPath 圖示圖片的路徑
 * @param bottomText 底部文字
 * @return fx的img物件
 */
fun getQRcodeFxImg(data: String?, logoPath: String?=null, bottomText: String?=null): WritableImage

使用的話也比較簡單:

//得到的swing的image物件
val buImg = QRCodeUtil.getQRcodeFxImg("這是測試文字")
val buImg1 = QRCodeUtil.getQRcodeFxImg("這是測試文字", null, "底部文字")
val buImg2 = QRCodeUtil.getQRcodeFxImg("這是測試文字", "/x5.jpg", "底部文字")

val list = listOf(buImg, buImg1, buImg2)

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

3.工具庫程式碼

/**
 * 二維碼生成工具類
 * Created by stars-one
 */
object QRCodeUtil {
    private var QRCODE_SIZE = 320 // 二維碼尺寸,寬度和高度均是320
    private var LOGO_SIZE = 80 // 二維碼裡logo的尺寸,寬高一致 80*80
    private var BOTTOM_TEXT_SIZE = 20 // 底部文字的文字大小
    private var FORMAT_TYPE = "PNG" // 二維碼圖片型別

    /**
     * 初始化設定
     *
     * @param qrcodeSize 二維碼尺寸,預設為320(即320*320)
     * @param logoSize logo圖示尺寸,預設為80(即80*80)
     * @param bottomTextSize 底部文字大小,預設20px
     * @param qrcodeType 二維碼圖片格式,預設為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
    }

    /**
     * 生成二維碼圖片
     *
     * @param data 二維碼文字內容
     * @param logoPath 圖示圖片的路徑
     * @param bottomText 底部文字
     * @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,無底部文字
     * 返回 BufferedImage 可以使用ImageIO.write(BufferedImage, "png", outputStream);輸出
     *
     * @param dataStr
     * @return 返回 BufferedImage 可以使用ImageIO.write(BufferedImage, "png", outputStream);輸出
     */
    @Throws(Exception::class)
    fun getQRCodeSwingImg(dataStr: String?): BufferedImage {
        return getQRCodeSwingImg(dataStr, null, null)
    }

    /**
     * 預設需要logo,無底部文字
     *
     * @param dataStr
     * @return 返回位元組陣列
     */
    @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()
    }

    /**
     * 預設需要logo,包含底部文字 文字為空則不顯示文字
     * 返回 BufferedImage 可以使用ImageIO.write(BufferedImage, "png", outputStream);輸出
     *
     * @param dataStr
     * @return
     */
    @Throws(Exception::class)
    fun getQRCodeSwingImg(dataStr: String?, bottomText: String?): BufferedImage {
        return getQRCodeSwingImg(dataStr, null, bottomText)
    }

    /**
     * 預設需要logo,包含底部文字 文字為空則不顯示文字
     *
     * @param dataStr
     * @return 返回位元組陣列
     */
    @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()
    }

    /**
     * 獲取二維碼圖片
     *
     * @param dataStr    二維碼內容
     * @param needLogo   是否需要新增logo
     * @param bottomText 底部文字       為空則不顯示
     * @return
     */
    @Throws(Exception::class)
    fun getQRCodeSwingImg(dataStr: String?, url: URL?, bottomText: String?): BufferedImage {
        if (dataStr == null) {
            throw RuntimeException("未包含任何資訊")
        }
        val hints = HashMap<EncodeHintType, Any?>()
        hints[EncodeHintType.CHARACTER_SET] = "utf-8" //定義內容字符集的編碼
        hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.L //定義糾錯等級
        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)
            }
        }
        // 判斷是否新增logo
        if (url != null) {
            insertLogoImage(image, url)
        }
        // 判斷是否新增底部文字
        if (StringUtils.isNotBlank(bottomText)) {
            addFontImage(image, bottomText)
        }
        return image
    }

    /**
     * 插入logo圖片
     *
     * @param source 二維碼圖片
     * @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) // 繪製縮小後的圖
        g.dispose()
        src = image

        // 插入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?) {
        //生成image
        val defineWidth = QRCODE_SIZE
        val defineHeight = 20
        val textImage = BufferedImage(defineWidth, defineHeight, BufferedImage.TYPE_INT_RGB)
        val g2 = textImage.graphics as Graphics2D
        //開啟文字抗鋸齒
        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
        //部署linux需要注意 linux無此字型會顯示方塊
        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()
        //開啟文字抗鋸齒
        graph.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
        //新增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()
    }
}

相關文章