Generate a QR code with text at the bottom of the image inserted in the center

package com.scamel.mom.common.util;

import cn.hutool.core.io.resource.ClassPathResource;
import com.alibaba.nacos.common.utils.StringUtils;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import sun.font.FontDesignMetrics;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.geom.AffineTransform;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * QR code, barcode generation tool
 */
public class CodeUtils {<!-- -->

    /**
     * the width of the center image
     */
    private static final int IMAGE_WIDTH = 80;
    private static final int IMAGE_HEIGHT = 80;
    private static final int IMAGE_HALF_WIDTH = IMAGE_WIDTH / 2;
    private static final int FRAME_WIDTH = 2;
    /**
     * Times New Roman
     */
    private static Font fontSun;

    /*
      Statically load fonts (linux does not have Chinese fonts, it will display 口口口)
     */
    static {<!-- -->
        try {<!-- -->
            ClassPathResource fontResource = new ClassPathResource("font/simsun.ttc");
            Font font = Font.createFont(Font.TRUETYPE_FONT, fontResource.getStream());
            fontSun = font. deriveFont(Font. BOLD, 15);
        } catch (Exception e) {<!-- -->
            e.printStackTrace();
        }
    }

    /**
     * Generate QR code
     *
     * @param content QR code content
     * @return image
     * @throws WriterException exception
     */
    public static BufferedImage createQRImage(String content) throws WriterException {<!-- -->
        // QR code parameter setting
        Map<EncodeHintType, Object> hints = new HashMap<>();
        // security level, the highest h
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        // encoding settings
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
        // set margin=0-10
        hints. put(EncodeHintType. MARGIN, 1);
        // create matrix container
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, 300, 300,
                hints);
        int width = bitMatrix. getWidth();
        int height = bitMatrix. getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // matrix transform image
        for (int x = 0; x < width; x ++ ) {<!-- -->
            for (int y = 0; y < height; y ++ ) {<!-- -->
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        return image;
    }

    /**
     * Generate a QR code with text at the bottom of the picture inserted in the center
     *
     * @param content QR code content
     * @param description Bottom description text
     * @return the picture to be inserted
     * @throws WriterException exception
     */
    public static BufferedImage createQRImageWithPic(String content, String description, String srcImagePath) throws WriterException, IOException {<!-- -->
        BufferedImage bufferedImage = genBarcode(content, description, 300, 300, srcImagePath);
        // Determine whether to add bottom text
        if (StringUtils. hasText(description)) {<!-- -->
            addText(bufferedImage, description, 300);
        }
        return bufferedImage;
    }


    /**
     * Add text at the bottom of the QR code
     *
     * @param source source QR code image
     * @param description Bottom description text
     */
    private static void addText(BufferedImage source, String description, int qrWidth) {<!-- -->
        //Generate image
        int defineHeight = 20;
        BufferedImage textImage = new BufferedImage(qrWidth, defineHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = (Graphics2D) textImage. getGraphics();
        //Enable text anti-aliasing
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2.setBackground(Color.WHITE);
        g2. clearRect(0, 0, qrWidth, defineHeight);
        g2.setPaint(Color.BLACK);
        FontRenderContext context = g2. getFontRenderContext();
        g2.setFont(fontSun);
        LineMetrics lineMetrics = fontSun. getLineMetrics(description, context);
        FontMetrics fontMetrics = FontDesignMetrics. getMetrics(fontSun);
        float offset = (qrWidth - fontMetrics. stringWidth(description)) / 2;
        float y = (defineHeight + lineMetrics.getAscent() - lineMetrics.getDescent() - lineMetrics.getLeading()) / 2;
        g2.drawString(description, (int) offset, (int) y);

        Graphics2D graph = source. createGraphics();
        //Enable text anti-aliasing
        graph.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        //add image
        int width = textImage. getWidth(null);
        int height = textImage. getHeight(null);
        graph.drawImage(textImage, 0, qrWidth - 8, width, height, Color.WHITE, null);
        graph.dispose();
    }

    /**
     * Generate a QR code and insert a picture in the center
     *
     * @param content QR code content
     * @param description Bottom description text
     * @param width QR code width
     * @param height QR code height
     * @param srcImagePath original image
     * @return
     * @throws WriterException
     * @throws IOException
     */
    private static BufferedImage genBarcode(String content, String description, int width, int height, String srcImagePath) throws WriterException, IOException {<!-- -->
        // There is text, height + 12, otherwise the text at the bottom will not be fully displayed
        if (StringUtils. hasText(description)) {<!-- -->
            height = height + 12;
        }

        // read source image and scale
        BufferedImage scaleImage = scale(srcImagePath, IMAGE_WIDTH, IMAGE_HEIGHT, false);
        int[][] srcPixels = new int[IMAGE_WIDTH][IMAGE_HEIGHT];
        for (int i = 0; i < scaleImage.getWidth(); i ++ ) {<!-- -->
            for (int j = 0; j < scaleImage.getHeight(); j ++ ) {<!-- -->
                srcPixels[i][j] = scaleImage.getRGB(i, j);
            }
        }
        // QR code parameter setting
        Map<EncodeHintType, Object> hints = new HashMap<>();
        // security level, the highest h
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        // encoding settings
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
        // set margin=0-10
        hints. put(EncodeHintType. MARGIN, 1);
        // create matrix container
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height,
                hints);
        // Convert two-dimensional matrix to one-dimensional pixel array
        int halfW = bitMatrix. getWidth() / 2;
        int halfH = bitMatrix. getHeight() / 2;
        int[] pixels = new int[width * height];
        for (int y = 0; y < bitMatrix. getHeight(); y ++ ) {<!-- -->
            for (int x = 0; x < bitMatrix.getWidth(); x ++ ) {<!-- -->
                // read the image
                if (x > halfW - IMAGE_HALF_WIDTH
                         & amp; & amp; x < halfW + IMAGE_HALF_WIDTH
                         & amp; & amp; y > halfH - IMAGE_HALF_WIDTH
                         & amp; & amp; y < halfH + IMAGE_HALF_WIDTH) {<!-- -->
                    pixels[y * width + x] = srcPixels[x - halfW
                             + IMAGE_HALF_WIDTH][y - halfH + IMAGE_HALF_WIDTH];
                } else if (
                    // Form a border around the image
                        (x > halfW - IMAGE_HALF_WIDTH - FRAME_WIDTH
                                 & amp; & amp; x < halfW - IMAGE_HALF_WIDTH + FRAME_WIDTH
                                 & amp; & amp; y > halfH - IMAGE_HALF_WIDTH - FRAME_WIDTH & amp; & amp; y < halfH
                                 + IMAGE_HALF_WIDTH + FRAME_WIDTH)
                                || (x > halfW + IMAGE_HALF_WIDTH - FRAME_WIDTH
                                 & amp; & amp; x < halfW + IMAGE_HALF_WIDTH + FRAME_WIDTH
                                 & amp; & amp; y > halfH - IMAGE_HALF_WIDTH - FRAME_WIDTH & amp; & amp; y < halfH
                                 + IMAGE_HALF_WIDTH + FRAME_WIDTH)
                                || (x > halfW - IMAGE_HALF_WIDTH - FRAME_WIDTH
                                 & amp; & amp; x < halfW + IMAGE_HALF_WIDTH + FRAME_WIDTH
                                 & amp; & amp; y > halfH - IMAGE_HALF_WIDTH - FRAME_WIDTH & amp; & amp; y < halfH
                                - IMAGE_HALF_WIDTH + FRAME_WIDTH)
                                || (x > halfW - IMAGE_HALF_WIDTH - FRAME_WIDTH
                                 & amp; & amp; x < halfW + IMAGE_HALF_WIDTH + FRAME_WIDTH
                                 & amp; & amp; y > halfH + IMAGE_HALF_WIDTH - FRAME_WIDTH & amp; & amp; y < halfH
                                 + IMAGE_HALF_WIDTH + FRAME_WIDTH)) {<!-- -->
                    pixels[y * width + x] = 0xfffffff;
                } else {<!-- -->
                    // The color of the QR code can be modified here, and the color of the QR code and the background can be specified separately;
                    pixels[y * width + x] = bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF;
                }
            }
        }
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        image.getRaster().setDataElements(0, 0, width, height, pixels);
        return image;
    }


    /**
     * Scale the incoming original image according to height and width to generate an icon that meets the requirements
     *
     * @param srcImageFile source file address
     * @param height target height
     * @param width target width
     * @param hasFiller Whether filling is required when the ratio is wrong: true is filling; false is not filling;
     * @throws IOException
     */
    private static BufferedImage scale(String srcImageFile, int height, int width, boolean hasFiller) throws IOException {<!-- -->
        // scaling ratio
        double ratio;
        File file = new File(srcImageFile);
        BufferedImage srcImage = ImageIO. read(file);
        Image destImage = srcImage.getScaledInstance(width, height,
                BufferedImage.SCALE_SMOOTH);
        // calculate ratio
        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.PINK);
            graphic. fillRect(10, 10, width, height);
            graphic. drawRect(100, 360, 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);
                Shape shape = new RoundRectangle2D. Float(0, (height - destImage. getHeight(null)) / 2, width, width, 20, 20);
                graphic.setStroke(new BasicStroke(5f));
                graphic. draw(shape);
            } else {<!-- -->
                graphic.drawImage(destImage,
                        (width - destImage.getWidth(null)) / 2, 0,
                        destImage.getWidth(null), destImage.getHeight(null),
                        Color. white, null);
                Shape shape = new RoundRectangle2D. Float((width - destImage. getWidth(null)) / 2, 0, width, width, 20, 20);
                graphic.setStroke(new BasicStroke(5f));
                graphic. draw(shape);
            }
            graphic.dispose();
            destImage = image;
        }
        return (BufferedImage) destImage;
    }


    public static void main(String[] args) {<!-- -->
        try {<!-- -->
            String content = "Nintendo is the pig of the world";
            BufferedImage image = genBarcode(content, content, 300, 300, "D://QR code//Nintendo is the world's piglet-s.jpg");
            // Determine whether to add bottom text
            if (StringUtils.hasText(content)) {<!-- -->
                addText(image, content, 300);
            }
            if (!ImageIO.write(image, "jpg", new File("D://QR code//test.jpg"))) {<!-- -->
                throw new IOException("Could not write an image of format ");
            }
        } catch (WriterException | IOException e) {<!-- -->
            e.printStackTrace();
        }
    }
}