JavaFx 生成二维码工具类封装

之前星之音乐下载器有需要生成二维码功能,当时用的是一个开源库来实现的,但是没过多久,发现那个库依赖太多,有个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()
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我可以为您提供一个实现Prim算法生成随机迷宫的JavaFX程序。以下是程序代码: ```java import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.util.Duration; public class RandomMaze extends Application { private static final int WIDTH = 600; private static final int HEIGHT = 600; private static final int CELL_SIZE = 20; private static final int NUM_ROWS = HEIGHT / CELL_SIZE; private static final int NUM_COLS = WIDTH / CELL_SIZE; private static final int FRAME_RATE = 30; private static final Random random = new Random(); private static final int WALL = 1; private static final int EMPTY = 0; private int[][] maze = new int[NUM_ROWS][NUM_COLS]; private Timeline timeline; private boolean generatingMaze = false; @Override public void start(Stage primaryStage) throws Exception { Canvas canvas = new Canvas(WIDTH, HEIGHT); GraphicsContext gc = canvas.getGraphicsContext2D(); gc.setFill(Color.WHITE); gc.fillRect(0, 0, WIDTH, HEIGHT); primaryStage.setScene(new Scene(canvas)); primaryStage.show(); canvas.setOnMouseClicked(event -> { if (!generatingMaze) { generateMaze(); } }); } private void generateMaze() { generatingMaze = true; for (int i = 0; i < NUM_ROWS; i++) { for (int j = 0; j < NUM_COLS; j++) { maze[i][j] = WALL; } } int startRow = random.nextInt(NUM_ROWS); int startCol = random.nextInt(NUM_COLS); List<int[]> walls = getWalls(startRow, startCol); Collections.shuffle(walls); maze[startRow][startCol] = EMPTY; timeline = new Timeline(new KeyFrame(Duration.millis(1000 / FRAME_RATE), event -> { if (!walls.isEmpty()) { int[] wall = walls.remove(walls.size() - 1); int row = wall[0]; int col = wall[1]; if (isWall(row, col) && hasEmptyNeighbor(row, col)) { maze[row][col] = EMPTY; drawCell(row, col, Color.WHITE); List<int[]> newWalls = getWalls(row, col); walls.addAll(newWalls); Collections.shuffle(walls); } } else { timeline.stop(); generatingMaze = false; } })); timeline.setCycleCount(Animation.INDEFINITE); timeline.play(); } private boolean isWall(int row, int col) { return maze[row][col] == WALL; } private boolean hasEmptyNeighbor(int row, int col) { for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (i == 0 && j == 0) { continue; } int neighborRow = row + i; int neighborCol = col + j; if (neighborRow < 0 || neighborRow >= NUM_ROWS || neighborCol < 0 || neighborCol >= NUM_COLS) { continue; } if (maze[neighborRow][neighborCol] == EMPTY) { return true; } } } return false; } private List<int[]> getWalls(int row, int col) { List<int[]> walls = new ArrayList<>(); if (row > 0) { walls.add(new int[] {row - 1, col}); } if (row < NUM_ROWS - 1) { walls.add(new int[] {row + 1, col}); } if (col > 0) { walls.add(new int[] {row, col - 1}); } if (col < NUM_COLS - 1) { walls.add(new int[] {row, col + 1}); } return walls; } private void drawCell(int row, int col, Color color) { GraphicsContext gc = ((Canvas) timeline.getNode()).getGraphicsContext2D(); gc.setFill(color); gc.fillRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE); } public static void main(String[] args) { launch(args); } } ``` 该程序使用JavaFX绘制随机迷宫,通过Prim算法生成迷宫。程序会在单击窗口时随机生成一个起点,并从该点开始生成迷宫。 程序使用一个二维数组`maze`表示迷宫,数组中的元素为0表示空白,为1表示墙。在`generateMaze()`方法中,程序先将整个迷宫初始化为墙,然后随机选择一个起点,并将其设为0(空白)。 接着,程序从起点开始,获取所有与之相邻的墙,并将这些墙随机打乱顺序。然后程序从墙的列表中取出一个墙,如果该墙旁边有空白格子,则将该墙设为0(空白),并将新生成的墙加入墙的列表中。程序不断重复上述过程,直到墙的列表为空。 程序使用JavaFX的动画来演示迷宫生成的过程,每秒钟刷新30次。在每一次刷新时,程序从墙的列表中取出一个墙,如果该墙旁边有空白格子,则将该墙设为0(空白),并将新生成的墙加入墙的列表中。程序在完成所有操作后停止动画演示,并将`generatingMaze`变量设为false,表示迷宫已经生成完毕。 如果您希望了解更多关于JavaFX的内容,可以查看以下链接: - JavaFX官方文档:https://openjfx.io/javadoc/16/ - JavaFX教程:https://docs.oracle.com/javafx/2/get_started/jfxpub-get_started.htm
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wsnbb_2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值