Java 中生成二维码(带 logo)

项目中涉及到生成二维码(像微信那种的),就用 ZXing 并混合 Java 的图形库搞了一个工具类。

奉上效果图一张
qrcode-1

废话不多说,直接上代码

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.io.*;
import java.net.URL;
import java.util.*;

import javax.imageio.ImageIO;

import org.apache.commons.io.IOUtils;
import org.slf4j.*;

import com.google.zxing.*;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

/**
 * 二维码生成工具类
 *
 * @author [email protected]
 */
public class QRCodeUtils {

    // logo 宽度
    private static final int LOGO_WIDTH = 110;

    private static final int LOGO_HEIGHT = LOGO_WIDTH;

    // logo 边框宽度
    private static final int LOGO_FRAME_WIDTH = 5;

    // logo 边框颜色
    private static final int LOGO_FRAME_COLOR = 0xffffffff;

    // logo 边缘颜色
    private static final int LOGO_BORDER_COLOR = 0x00000000;

    // logo 圆角半径
    private static final int LOGO_RADIUS = 10;

    // 二维码前景色
    private static final int QRCODE_FOREGROUND_COLOR = 0x00000000;

    // 二维码背景色
    private static final int QRCODE_BACKGROUND_COLOR = 0xffffffff;

    // 二维码四周留白宽度
    private static final int QRCODE_SPECIFIES_MARGIN = 1;

    // 二维码写码器
    private static final MultiFormatWriter mutiWriter = new MultiFormatWriter();

    private static final Logger logger = LoggerFactory.getLogger(QRCodeUtils.class);

    /**
     * 将生成的二维码输出到流中
     *
     * @param content
     *        二维码存储的信息
     * @param width
     *        二维码的宽度
     * @param height
     *        二维码的高度
     * @param logo
     *        输入流: Logo 源
     * @param out
     *        输出流
     */
    public static void generateToStream(String content, int width, int height, InputStream logo,
            OutputStream out) {
        try {
            ImageIO.write(genQRCode(content, width, height, logo), "jpg", out);
        } catch (IOException e) {
            e.printStackTrace();
            logger.warn("Oops.", e);
        } catch (WriterException e) {
            e.printStackTrace();
            logger.warn("Oops.", e);
        }
    }

    /**
     * 生成二维码
     *
     * @param content
     *        二维码存储的信息
     * @param width
     *        二维码的宽度
     * @param height
     *        二维码的高度
     * @param logo
     *        InputSteam of the logo
     * @return {@link BufferedImage}
     * @throws WriterException
     * @throws IOException
     */
    private static BufferedImage genQRCode(String content, int width, int height, InputStream logo)
            throws WriterException, IOException {
        int r = LOGO_RADIUS;

        // 读取 logo 源图像
        int logoPixels[][] = null;
        boolean hasLogo = false;
        if (logo != null) {
            try {
                logoPixels = getLogoPixels(scale(logo, LOGO_WIDTH, LOGO_HEIGHT, true), r);
                hasLogo = true;
            } catch (IOException e) {
                e.printStackTrace();
                logger.warn("Oops. Read the logo failed.", e);
            }
        }

        Map<EncodeHintType, Object> hint = new HashMap<>();
        hint.put(EncodeHintType.CHARACTER_SET, "utf-8");
        hint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hint.put(EncodeHintType.MARGIN, QRCODE_SPECIFIES_MARGIN);

        // 生成二维码
        BitMatrix matrix = mutiWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hint);

        int foregroundColor = QRCODE_FOREGROUND_COLOR;
        int backgroundColor = QRCODE_BACKGROUND_COLOR;

        // 二维矩阵转为一维像素数组
        int halfW = matrix.getWidth() / 2;
        int halfH = matrix.getHeight() / 2;
        int[] pixels = new int[width * height];
        int logoHalfWidth = LOGO_WIDTH / 2;
        int near = halfW - logoHalfWidth - LOGO_FRAME_WIDTH + r;
        int far = halfW + logoHalfWidth + LOGO_FRAME_WIDTH - r;

        for (int y = 0; y < matrix.getHeight(); y++) {
            for (int x = 0; x < matrix.getWidth(); x++) {
                if (!hasLogo) {
                    // 没有 logo 的二维码
                    pixels[y * width + x] = matrix.get(x, y) ? foregroundColor : backgroundColor;
                } else {
                    // logo
                    if (x > halfW - logoHalfWidth && x < halfW + logoHalfWidth
                            && y > halfH - logoHalfWidth && y < halfH + logoHalfWidth) {
                        pixels[y * width + x] = logoPixels[x - halfW + logoHalfWidth][y - halfH
                                + logoHalfWidth];
                    }
                    // 在图片四周形成边框
                    else if ((x > halfW - logoHalfWidth - LOGO_FRAME_WIDTH
                            && x < halfW - logoHalfWidth + LOGO_FRAME_WIDTH
                            && y > halfH - logoHalfWidth - LOGO_FRAME_WIDTH
                            && y < halfH + logoHalfWidth + LOGO_FRAME_WIDTH)
                            || (x > halfW + logoHalfWidth - LOGO_FRAME_WIDTH
                                    && x < halfW + logoHalfWidth + LOGO_FRAME_WIDTH
                                    && y > halfH - logoHalfWidth - LOGO_FRAME_WIDTH
                                    && y < halfH + logoHalfWidth + LOGO_FRAME_WIDTH)
                            || (x > halfW - logoHalfWidth - LOGO_FRAME_WIDTH
                                    && x < halfW + logoHalfWidth + LOGO_FRAME_WIDTH
                                    && y > halfH - logoHalfWidth - LOGO_FRAME_WIDTH
                                    && y < halfH - logoHalfWidth + LOGO_FRAME_WIDTH)
                            || (x > halfW - logoHalfWidth - LOGO_FRAME_WIDTH
                                    && x < halfW + logoHalfWidth + LOGO_FRAME_WIDTH
                                    && y > halfH + logoHalfWidth - LOGO_FRAME_WIDTH
                                    && y < halfH + logoHalfWidth + LOGO_FRAME_WIDTH)) {

                        // 圆角处理
                        if (x < near && y < near
                                && (near - x) * (near - x) + (near - y) * (near - y) > r * r) {
                            // 左上圆角
                            pixels[y * width + x] = matrix.get(x, y) ? foregroundColor
                                    : backgroundColor;
                        } else if (x > far && y < near
                                && (x - far) * (x - far) + (near - y) * (near - y) > r * r) {
                            // 右上圆角
                            pixels[y * width + x] = matrix.get(x, y) ? foregroundColor
                                    : backgroundColor;
                        } else if (x < near && y > far
                                && (near - x) * (near - x) + (y - far) * (y - far) > r * r) {
                            // 左下圆角
                            pixels[y * width + x] = matrix.get(x, y) ? foregroundColor
                                    : backgroundColor;
                        } else if (x > far && y > far
                                && (x - far) * (x - far) + (y - far) * (y - far) > r * r) {
                            // 右下圆角
                            pixels[y * width + x] = matrix.get(x, y) ? foregroundColor
                                    : backgroundColor;
                        } else {
                            // 边框填充颜色
                            pixels[y * width + x] = backgroundColor;
                        }
                    } else {
                        pixels[y * width + x] = matrix.get(x, y) ? foregroundColor
                                : backgroundColor;
                    }
                }

            }
        }

        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        image.getRaster().setDataElements(0, 0, width, height, pixels);

        return image;
    }

    /**
     * 对图片进行圆角处理, 并生成数组
     *
     * @param logo
     *        BufferedImage of the log
     * @param radius
     *        圆角半径
     * @return logo 像素数组
     */
    public static int[][] getLogoPixels(BufferedImage logo, int radius) {

        int borderColor = LOGO_BORDER_COLOR;
        int whiteColor = LOGO_FRAME_COLOR;

        int srcPixels[][] = new int[LOGO_WIDTH][LOGO_HEIGHT];

        int r = radius;// 圆角处理半径
        int max = LOGO_WIDTH;

        for (int x = 0; x < LOGO_WIDTH; x++) {
            for (int y = 0; y < LOGO_HEIGHT; y++) {
                if (x < r && y < r && ((r - x) * (r - x) + (r - y) * (r - y) > (r - 1) * (r - 1))) {
                    // 左上圆角
                    if ((r - x) * (r - x) + (r - y) * (r - y) > r * r) {
                        srcPixels[x][y] = whiteColor;
                    } else {
                        srcPixels[x][y] = borderColor;
                    }
                } else if (x > (max - r) && y < r
                        && (x + r - max) * (x + r - max) + (r - y) * (r - y) > (r - 1) * (r - 1)) {
                    // 右上圆角
                    if ((x + r - max) * (x + r - max) + (r - y) * (r - y) > r * r) {
                        srcPixels[x][y] = whiteColor;
                    } else {
                        srcPixels[x][y] = borderColor;
                    }
                } else if (x < r && y > (max - r)
                        && (r - x) * (r - x) + (y + r - max) * (y + r - max) > (r - 1) * (r - 1)) {
                    // 左下圆角
                    if ((r - x) * (r - x) + (y + r - max) * (y + r - max) > r * r) {
                        srcPixels[x][y] = whiteColor;
                    } else {
                        srcPixels[x][y] = borderColor;
                    }
                } else if (x > (max - r) && y > (max - r) && (x + r - max) * (x + r - max)
                        + (y + r - max) * (y + r - max) > (r - 1) * (r - 1)) {
                    // 右下圆角
                    if ((x + r - max) * (x + r - max) + (y + r - max) * (y + r - max) > r * r) {
                        srcPixels[x][y] = whiteColor;
                    } else {
                        srcPixels[x][y] = borderColor;
                    }
                } else {
                    if (((x >= r && x <= max - r) && (y == 0 || y == 1 || y == max - 1 || y == max))
                            || ((y >= r && y <= max - r)
                                    && (x == 0 || x == 1 || x == max - 1 || x == max))) {
                        // 四周除圆角的边框
                        srcPixels[x][y] = borderColor;
                    } else {
                        // 图片值
                        srcPixels[x][y] = logo.getRGB(x, y);
                    }
                }
            }
        }
        return srcPixels;
    }

    /**
     * 把传入的原始图像按高度和宽度进行缩放,生成符合要求的图标
     *
     * @param src
     *        源
     * @param height
     *        目标高度
     * @param width
     *        目标宽度
     * @param hasFiller
     *        比例不对时是否需要补白:true 为补白; false 为不补白;
     * @return {@link BufferedImage}
     * @throws IOException
     */
    private static BufferedImage scale(InputStream src, int height, int width, boolean hasFiller)
            throws IOException {
        double ratio = 0.0D; // 缩放比例
        BufferedImage srcImage = ImageIO.read(src);
        Image destImage = srcImage.getScaledInstance(width, height, BufferedImage.SCALE_SMOOTH);
        // 计算比例
        if ((srcImage.getHeight() > height) || (srcImage.getWidth() > width)) {
            if (srcImage.getHeight() > srcImage.getWidth()) {
                ratio = (new Integer(height)).doubleValue() / srcImage.getHeight();
            } else {
                ratio = (new Integer(width)).doubleValue() / srcImage.getWidth();
            }
            AffineTransformOp op = new AffineTransformOp(
                    AffineTransform.getScaleInstance(ratio, ratio), null);
            destImage = op.filter(srcImage, null);
        }
        // 补白
        if (hasFiller) {
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics2D graphic = image.createGraphics();
            graphic.setColor(Color.white);
            graphic.fillRect(0, 0, width, height);
            if (width == destImage.getWidth(null))
                graphic.drawImage(destImage, 0, (height - destImage.getHeight(null)) / 2,
                        destImage.getWidth(null), destImage.getHeight(null), Color.white, null);
            else graphic.drawImage(destImage, (width - destImage.getWidth(null)) / 2, 0,
                    destImage.getWidth(null), destImage.getHeight(null), Color.white, null);
            graphic.dispose();
            destImage = image;
        }
        return (BufferedImage) destImage;
    }

    public static void main(String[] args) {
        InputStream in = null;
        try {
            in = new URL("https://dn-codingon.qbox.me/img/avatar/me.jpeg").openStream();
            generateToStream("https://www.haoyizebo.com", 500, 500, in,
                    new FileOutputStream("/Users/yibo/Desktop/qrcode.jpg"));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(in);
        }
    }
}

依赖:

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.2.1</version>
    <type>jar</type>
</dependency>

相关阅读:
二维码的生成细节和原理


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!