zxing二維碼生成服務之深度定製

二維碼生成服務之深度定製

以前寫了一篇二維碼服務定製的博文,如今則在以前的基礎上,再進一步,花樣的實現深度定製的需求,咱們的目標是二維碼上的一切都是能夠由用戶來隨意指定java

設計

1. 技術相關

  • zxing 開源包用於生成二維碼
  • springboot 搭建基本web服務,提供http接口
  • awt 用於圖片的編輯
  • httpclient 用於從網絡下載圖片
  • lombok 簡化編碼

2. 目的

既然是對二維碼服務的深度定製,那咱們的目的基本上就是二維碼上面出現的東西,均可以按照咱們的需求進行改造git

這裏,咱們設計兩個目的,一個基礎版,一個進階版github

  • 基礎版web

    • 二維碼大小
    • 邊距留白指定
    • 添加logo
    • 加背景
  • 進階版spring

    • 二維碼中前置色和背景色可自由指定顏色
    • 二維碼中前置色(黑白二維碼中的黑色區域)可換成圓點,三角形等其餘圖形
    • 前置色可用圖片替換
    • 探測點(三個矩形框就是探測點,也叫作定位點)顏色可配置
    • 探測點可用圖片替換
    • 二維碼樣式(圓角矩形,添加邊框,邊框顏色可指定)
    • 背景支持填充(填充在背景圖片的某個區域)和覆蓋方式(全覆蓋背景圖,二維碼設置透明度)

上面是咱們但願達到的目的,下面給幾個實際生成的二維碼瞅瞅最終的效果數組

輸入圖片說明

<font color="red">(小灰灰blog公衆號,實際測試時,請用微信掃一掃)</font>springboot

3. 前提準備

1.相關博文

在直接進入上面花樣的二維碼生成以前,有必要安利一把zxing的基本使用方式,本篇將不會對如何使用zxing進行說明,有需求瞭解的能夠參考下面幾篇相關博文,此篇博文是 《spring-boot & zxing 搭建二維碼服務》 的衍生微信

2. 源碼介紹

此外下面直接貼代碼,可能有些地方不太容易理解,下面將簡單對一些輔助類進行必要的功能說明網絡

源碼直通車:quick-mediaapp

涉及到的工具類:

  • QrCodeUtil : 二維碼生成工具類
    • 生成二維碼矩陣
    • 根據二維碼矩陣渲染二維碼圖片
  • ImageUtil : 圖片處理工具類
    • 加載圖片(支持從本地,網絡獲取圖片)
    • 繪製二維碼logo
    • 圖片圓角化
    • 圖片添加純色邊框
    • 背景繪製
    • 二維碼繪製
  • QrCodeOptions: 二維碼配置類
  • BitMatrixEx: 二維碼矩陣信息擴展類
  • QrCodeGenWrapper: 二維碼生成服務包裝類,與用戶進行交互的主要接口,設置配置信息,生成二維碼,選擇輸出方式,都是經過它來設定

4. 實現說明

第一步,生成矩陣

咱們直接利用zxing來生成二維碼矩陣信息,並用來實例咱們的矩陣拓展類 BitMatrixEx

在咱們的工程中,相關的代碼爲

com.hust.hui.quickmedia.common.util.QrCodeUtil#encode

在這裏,只關心下面幾個參數的生成,其餘的基本上就是zxing庫的調用了

/**
 * 實際生成二維碼的寬
 */
private int width;


/**
 * 實際生成二維碼的高
 */
private int height;


/**
 * 左白邊大小
 */
private int leftPadding;

/**
 * 上白邊大小
 */
private int topPadding;

/**
 * 矩陣信息縮放比例
 */
private int multiple;

private ByteMatrix byteMatrix;

在理解爲何有上面的幾個參數以前,有必要看一下byteMatrix究竟是個什麼東西?(自問自答:二維碼矩陣)

下面截出前面二維碼中對應的矩陣信息,在生成一張二維碼時,下面的1表示一個小黑塊,0表示一個小白塊;

1 1 1 1 1 1 1 0 0 0 0 1 0 0 0 1 1 1 0 0 0 0 0 1 0 1 1 0 0 0 1 1 1 1 1 1 1
 1 0 0 0 0 0 1 0 1 0 1 1 1 1 0 1 1 0 0 1 0 0 1 0 1 0 0 0 1 0 1 0 0 0 0 0 1
 1 0 1 1 1 0 1 0 1 1 0 0 0 0 0 1 1 0 1 0 1 0 1 0 0 1 0 0 1 0 1 0 1 1 1 0 1
 1 0 1 1 1 0 1 0 1 1 0 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1 0 1 0 1 0 1 1 1 0 1
 1 0 1 1 1 0 1 0 1 0 0 1 0 1 1 1 0 0 0 0 1 1 0 0 1 1 0 0 0 0 1 0 1 1 1 0 1
 1 0 0 0 0 0 1 0 1 0 0 1 0 1 0 0 0 1 1 0 1 1 1 0 0 1 0 0 1 0 1 0 0 0 0 0 1
 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1
 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 1 1 1 0 0 0 0 0 0 0 0
 0 0 1 0 0 1 1 1 1 1 0 1 1 0 0 1 1 1 1 0 1 1 0 0 1 0 0 0 0 1 0 1 1 1 1 1 0
 0 1 0 0 0 1 0 1 0 1 1 1 0 1 1 0 1 1 0 0 1 0 1 0 1 0 0 1 0 0 1 1 0 1 0 0 1
 1 1 1 1 0 0 1 0 1 0 1 1 1 0 0 1 1 1 1 0 1 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 1
 1 0 1 1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 1 1 1 1 1 1 1 0 1 0 0 0 0 1 0 0 0 0 1
 1 0 1 1 0 0 1 0 1 0 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 1 1 1 0 0 0 0 1 1
 1 0 1 0 0 0 0 0 0 1 1 0 0 1 1 1 1 0 0 1 0 1 1 1 0 1 0 0 1 1 1 0 0 0 1 0 1
 1 1 1 1 0 0 1 0 0 0 0 0 1 1 1 0 1 1 1 0 0 0 1 1 1 0 0 1 0 1 1 0 0 1 1 0 1
 1 1 1 0 0 0 0 1 0 1 0 1 1 1 1 1 1 0 0 0 1 0 1 0 1 0 1 0 0 1 0 0 0 1 0 0 0
 0 1 1 0 0 0 1 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 1 0 1 0 0 1 1 0 1 0 0 0 0 1 0
 0 0 1 1 1 0 0 0 0 1 0 1 1 1 0 0 1 0 1 1 1 0 0 0 1 0 0 1 1 1 1 1 0 0 1 0 1
 0 0 1 1 1 1 1 1 0 0 1 0 1 0 0 1 0 0 0 1 1 1 1 1 0 1 1 1 1 1 0 1 0 0 1 0 1
 0 0 1 0 1 1 0 1 1 0 1 0 0 0 0 0 1 0 0 0 1 1 1 0 1 0 0 1 1 1 0 1 1 1 0 1 1
 0 0 1 0 1 0 1 0 0 1 0 1 1 0 0 1 1 0 0 0 1 1 1 0 0 1 0 0 1 0 1 0 0 1 0 1 0
 1 1 0 1 0 0 0 0 1 0 0 0 0 1 1 0 0 1 1 1 0 0 0 1 1 1 0 1 0 1 1 1 0 1 1 1 1
 1 0 0 0 1 0 1 1 0 1 1 0 1 1 0 0 0 0 1 1 0 0 1 1 1 0 0 1 0 1 1 1 1 0 0 1 1
 0 1 1 0 1 1 0 0 1 1 0 1 1 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 1 1 0 1 0 1 0
 1 0 0 1 0 1 1 0 1 1 0 0 0 1 1 0 1 0 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 1 1 1
 0 0 1 0 1 1 0 1 0 0 1 0 1 1 1 0 0 1 1 0 0 0 0 0 1 0 0 1 0 0 0 1 0 0 1 1 1
 1 1 0 1 1 0 1 1 0 1 0 0 1 0 0 1 1 0 0 0 0 1 1 0 1 1 0 1 1 1 0 1 0 1 1 0 1
 0 0 1 0 1 1 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 1 0 1 1 0 1 0 1 0 1 0 0 1 0 1 1
 1 1 0 1 0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0 1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0
 0 0 0 0 0 0 0 0 1 1 1 0 1 0 1 1 1 0 1 1 1 0 1 1 0 1 0 0 1 0 0 0 1 1 0 1 1
 1 1 1 1 1 1 1 0 1 0 0 1 0 0 0 0 0 0 1 0 0 1 0 1 0 1 0 0 1 0 1 0 1 1 1 0 1
 1 0 0 0 0 0 1 0 1 0 0 0 1 0 0 1 0 1 0 0 0 0 0 1 1 0 1 1 1 0 0 0 1 1 0 1 0
 1 0 1 1 1 0 1 0 0 0 0 1 0 0 1 1 1 0 1 0 0 1 0 1 1 0 1 1 1 1 1 1 1 0 0 1 0
 1 0 1 1 1 0 1 0 0 1 0 0 1 1 0 1 0 0 0 1 1 1 0 0 0 0 0 1 1 0 0 1 0 1 1 0 1
 1 0 1 1 1 0 1 0 1 1 0 0 1 1 1 1 0 1 0 0 0 1 1 1 1 1 0 0 1 1 0 0 1 1 0 1 1
 1 0 0 0 0 0 1 0 0 0 1 1 1 0 1 1 1 1 1 0 0 1 1 0 0 0 1 0 1 0 0 0 1 0 0 0 0
 1 1 1 1 1 1 1 0 0 0 1 1 1 0 1 0 0 0 0 0 0 1 1 0 1 0 0 1 1 0 1 0 1 1 0 0 1

當生成了上面的人矩陣以後,最終的二維碼繪製都是根據上面的矩陣來的,將1的地方用咱們但願繪製的樣式(如圓點,三角形,圖形等)來替換;

上面的矩陣表示的基本的二維碼信息,最終渲染二維碼圖片時,咱們還須要知道最終的圖片大小,四周的留白空間,每一個二維碼信息在放射到最終二維碼圖片時放大的倍數,有這些參數以後才能惟一指定最終的輸出結果,因此就有了上面的幾個參數

第二步, 二維碼信息的繪製

根據上面的二維碼矩陣來渲染二維碼圖片,先考慮最簡單的,沒有任何配置時,能夠怎麼玩?

下面用到的參數來自BitMatirxEx

  1. 繪製整個背景(直接根據給定的寬高繪製矩形背景便可)
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, qrCodeWidth, qrCodeHeight);
  1. 二維碼矩陣中(x,y) == 1的地方繪製小方塊
g2.setColor(Color.BLACK);
g2.fillRect(x+leftPadding, y+topPadding, multiple, multiple);
  1. 根據2可知,整個渲染就是矩陣(二維數組)的遍歷而已

根據上面的生成邏輯,咱們能夠很清晰的發現,有幾個目標是能夠很簡單實現的

  • 二維碼背景色&前置色的指定(就是在1,2步驟中的setColor用指定的顏色替換便可)
  • 替換二維碼黑色小方塊爲其餘圖形

這裏是一個小關鍵點了,在具體的實現中,我提供了:

  • 三角形,
  • 矩形(即二維碼默認格式),
  • 五邊形(鑽石),
  • 六邊形,
  • 八邊形,
  • 圓形,
  • 圖片

<font color="red">比較遺憾的是五角星沒有支持,沒想到合適的繪製方式</font>

不一樣的樣式,對應的繪製不一樣,咱們定義了一個枚舉,來定義不一樣的樣式對應的繪製規則,優點就是擴展自定義樣式方便,下面給出具體的繪製代碼

/**
 * 繪製二維碼信息的樣式
 */
public enum DrawStyle {
    RECT { // 矩形

        @Override
        public void draw(Graphics2D g2d, int x, int y, int w, int h, BufferedImage img) {
            g2d.fillRect(x, y, w, h);
        }

        @Override
        public boolean expand(ExpandType expandType) {
            return true;
        }
    },
    CIRCLE {
        // 圓點
        @Override
        public void draw(Graphics2D g2d, int x, int y, int w, int h, BufferedImage img) {
            g2d.fill(new Ellipse2D.Float(x, y, w, h));
        }

        @Override
        public boolean expand(ExpandType expandType) {
            return expandType == ExpandType.SIZE4;
        }
    },
    TRIANGLE {
        // 三角形
        @Override
        public void draw(Graphics2D g2d, int x, int y, int w, int h, BufferedImage img) {
            int px[] = {x, x + (w >> 1), x + w};
            int py[] = {y + w, y, y + w};
            g2d.fillPolygon(px, py, 3);
        }

        @Override
        public boolean expand(ExpandType expandType) {
            return false;
        }
    },
    DIAMOND {
        // 五邊形-鑽石
        @Override
        public void draw(Graphics2D g2d, int x, int y, int size, int h, BufferedImage img) {
            int cell4 = size >> 2;
            int cell2 = size >> 1;
            int px[] = {x + cell4, x + size - cell4, x + size, x + cell2, x};
            int py[] = {y, y, y + cell2, y + size, y + cell2};
            g2d.fillPolygon(px, py, 5);
        }

        @Override
        public boolean expand(ExpandType expandType) {
            return expandType == ExpandType.SIZE4;
        }
    },
    SEXANGLE {
        // 六邊形
        @Override
        public void draw(Graphics2D g2d, int x, int y, int size, int h, BufferedImage img) {
            int add = size >> 2;
            int px[] = {x + add, x + size - add, x + size, x + size - add, x + add, x};
            int py[] = {y, y, y + add + add, y + size, y + size, y + add + add};
            g2d.fillPolygon(px, py, 6);
        }

        @Override
        public boolean expand(ExpandType expandType) {
            return expandType == ExpandType.SIZE4;
        }
    },
    OCTAGON {
        // 八邊形
        @Override
        public void draw(Graphics2D g2d, int x, int y, int size, int h, BufferedImage img) {
            int add = size / 3;
            int px[] = {x + add, x + size - add, x + size, x + size, x + size - add, x + add, x, x};
            int py[] = {y, y, y + add, y + size - add, y + size, y + size, y + size - add, y + add};
            g2d.fillPolygon(px, py, 8);
        }

        @Override
        public boolean expand(ExpandType expandType) {
            return expandType == ExpandType.SIZE4;
        }
    },
    IMAGE {
        // 自定義圖片
        @Override
        public void draw(Graphics2D g2d, int x, int y, int w, int h, BufferedImage img) {
            g2d.drawImage(img, x, y, w, h, null);
        }

        @Override
        public boolean expand(ExpandType expandType) {
            return true;
        }
    },;

    private static Map<String, DrawStyle> map;

    static {
        map = new HashMap<>(7);
        for (DrawStyle style : DrawStyle.values()) {
            map.put(style.name(), style);
        }
    }

    public static DrawStyle getDrawStyle(String name) {
        if (StringUtils.isBlank(name)) { // 默認返回矩形
            return RECT;
        }


        DrawStyle style = map.get(name.toUpperCase());
        return style == null ? RECT : style;
    }


    public abstract void draw(Graphics2D g2d, int x, int y, int w, int h, BufferedImage img);


    /**
     * 返回是否支持繪製圖形的擴展
     *
     * @param expandType
     * @return
     */
    public abstract boolean expand(ExpandType expandType);
}

上面完成了二維碼樣式的定製,還有一個探測點(或者叫作定位點)的定製,也得在這一步中進行;

普通的二維碼結構以下

二維碼結構

探測點就是二維碼中的三個方塊,再看上面的二維碼矩陣,下圖中的兩個紅框內的其實就是上面的兩個探測圖形,外面的那層全0是分割符

輸入圖片說明

二者一結合,很容易就能夠搞定探測圖形的位置,第一行有多少個連續的1就表示探測圖形的size是多大

因此探測圖形的私人定製就比較簡單了,下面是具體的繪製代碼(下面實現圖片繪製,內外框採用不一樣顏色的實現)

// 設置三個位置探測圖形
if (x < detectCornerSize && y < detectCornerSize // 左上角
      || (x < detectCornerSize && y >= byteH - detectCornerSize) // 左下腳
      || (x >= byteW - detectCornerSize && y < detectCornerSize)) { // 右上角

  if (qrCodeConfig.getDetectOptions().getDetectImg() != null) {
     // 繪製圖片
      g2.drawImage(qrCodeConfig.getDetectOptions().getDetectImg(),
              leftPadding + x * infoSize, topPadding + y * infoSize,
              infoSize * detectCornerSize, infoSize * detectCornerSize, null);

      for (int addX = 0; addX < detectCornerSize; addX++) {
          for (int addY = 0; addY < detectCornerSize; addY++) {
              bitMatrix.getByteMatrix().set(x + addX, y + addY, 0);
          }
      }
      continue;
  }


  if (x == 0 || x == detectCornerSize - 1 || x == byteW - 1 || x == byteW - detectCornerSize
          || y == 0 || y == detectCornerSize - 1 || y == byteH - 1 || y == byteH - detectCornerSize) {
      // 外層的框
      g2.setColor(detectOutColor);
  } else {
      // 內層的框
      g2.setColor(detectInnerColor);
  }

  g2.fillRect(leftPadding + x * infoSize, topPadding + y * infoSize, infoSize, infoSize);
}

到此,二維碼主體的定製基本上over了,就最終的實現來看,咱們的目標中除了logo和背景外,其餘的基本上都是ok的,這裏稍稍拓展了一點,若是連續兩個爲1,或一個小矩形全是1,則將這相同的幾個串在一塊兒,所以纔有了上面的部分圖形較大的狀況(固然這個是可選的配置)

下面貼出整個繪製代碼

public static BufferedImage drawQrInfo(QrCodeOptions qrCodeConfig, BitMatrixEx bitMatrix) {
    int qrCodeWidth = bitMatrix.getWidth();
    int qrCodeHeight = bitMatrix.getHeight();
    int infoSize = bitMatrix.getMultiple();
    BufferedImage qrCode = new BufferedImage(qrCodeWidth, qrCodeHeight, BufferedImage.TYPE_INT_RGB);


    // 繪製的背景色
    Color bgColor = qrCodeConfig.getDrawOptions().getBgColor();
    // 繪製前置色
    Color preColor = qrCodeConfig.getDrawOptions().getPreColor();

    // 探測圖形外圈的顏色
    Color detectOutColor = qrCodeConfig.getDetectOptions().getOutColor();
    // 探測圖形內圈的顏色
    Color detectInnerColor = qrCodeConfig.getDetectOptions().getInColor();


    int leftPadding = bitMatrix.getLeftPadding();
    int topPadding = bitMatrix.getTopPadding();

    Graphics2D g2 = qrCode.createGraphics();
    g2.setComposite(AlphaComposite.Src);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);


    // 直接背景鋪滿整個圖
    g2.setColor(bgColor);
    g2.fillRect(0, 0, qrCodeWidth, qrCodeHeight);

    // 探測圖形的大小
    int detectCornerSize = bitMatrix.getByteMatrix().get(0, 5) == 1 ? 7 : 5;

    int byteW = bitMatrix.getByteMatrix().getWidth();
    int byteH = bitMatrix.getByteMatrix().getHeight();

    boolean row2 = false;
    boolean col2 = false;
    QrCodeOptions.DrawStyle drawStyle = qrCodeConfig.getDrawOptions().getDrawStyle();
    for (int x = 0; x < byteW; x++) {
        for (int y = 0; y < byteH; y++) {
            if (bitMatrix.getByteMatrix().get(x, y) == 0) {
                continue;
            }

            // 設置三個位置探測圖形
            if (x < detectCornerSize && y < detectCornerSize // 左上角
                    || (x < detectCornerSize && y >= byteH - detectCornerSize) // 左下腳
                    || (x >= byteW - detectCornerSize && y < detectCornerSize)) { // 右上角

                if (qrCodeConfig.getDetectOptions().getDetectImg() != null) {
                    g2.drawImage(qrCodeConfig.getDetectOptions().getDetectImg(),
                            leftPadding + x * infoSize, topPadding + y * infoSize,
                            infoSize * detectCornerSize, infoSize * detectCornerSize, null);

                    for (int addX = 0; addX < detectCornerSize; addX++) {
                        for (int addY = 0; addY < detectCornerSize; addY++) {
                            bitMatrix.getByteMatrix().set(x + addX, y + addY, 0);
                        }
                    }
                    continue;
                }


                if (x == 0 || x == detectCornerSize - 1 || x == byteW - 1 || x == byteW - detectCornerSize
                        || y == 0 || y == detectCornerSize - 1 || y == byteH - 1 || y == byteH - detectCornerSize) {
                    // 外層的框
                    g2.setColor(detectOutColor);
                } else {
                    // 內層的框
                    g2.setColor(detectInnerColor);
                }

                g2.fillRect(leftPadding + x * infoSize, topPadding + y * infoSize, infoSize, infoSize);
            } else { // 着色二維碼主題
                g2.setColor(preColor);

                if (!qrCodeConfig.getDrawOptions().isEnableScale()) {
                    drawStyle.draw(g2,
                            leftPadding + x * infoSize,
                            topPadding + y * infoSize,
                            infoSize,
                            infoSize,
                            qrCodeConfig.getDrawOptions().getImg());
                    continue;
                }


                // 支持拓展時
                row2 = rightTrue(bitMatrix.getByteMatrix(), x, y);
                col2 = belowTrue(bitMatrix.getByteMatrix(), x, y);

                if (row2 && col2 && diagonalTrue(bitMatrix.getByteMatrix(), x, y) &&
                        qrCodeConfig.getDrawOptions().enableScale(QrCodeOptions.ExpandType.SIZE4)) {
                    // 四個相等
                    bitMatrix.getByteMatrix().set(x + 1, y, 0);
                    bitMatrix.getByteMatrix().set(x + 1, y + 1, 0);
                    bitMatrix.getByteMatrix().set(x, y + 1, 0);
                    drawStyle.draw(g2,
                            leftPadding + x * infoSize,
                            topPadding + y * infoSize,
                            infoSize << 1,
                            infoSize << 1,
                            qrCodeConfig.getDrawOptions().getSize4Img());
                } else if (row2 && qrCodeConfig.getDrawOptions().enableScale(QrCodeOptions.ExpandType.ROW2)) { // 橫向相同
                    bitMatrix.getByteMatrix().set(x + 1, y, 0);
                    drawStyle.draw(g2,
                            leftPadding + x * infoSize,
                            topPadding + y * infoSize,
                            infoSize << 1,
                            infoSize,
                            qrCodeConfig.getDrawOptions().getRow2Img());
                } else if (col2 && qrCodeConfig.getDrawOptions().enableScale(QrCodeOptions.ExpandType.COL2)) { // 列的兩個
                    bitMatrix.getByteMatrix().set(x, y + 1, 0);
                    drawStyle.draw(g2,
                            leftPadding + x * infoSize,
                            topPadding + y * infoSize,
                            infoSize,
                            infoSize << 1,
                            qrCodeConfig.getDrawOptions().getCol2img());
                } else {
                    drawStyle.draw(g2,
                            leftPadding + x * infoSize,
                            topPadding + y * infoSize,
                            infoSize,
                            infoSize,
                            qrCodeConfig.getDrawOptions().getImg());
                }
            }
        }
    }
    g2.dispose();
    return qrCode;
}


private static boolean rightTrue(ByteMatrix byteMatrix, int x, int y) {
    return x + 1 < byteMatrix.getWidth() && byteMatrix.get(x + 1, y) == 1;
}

private static boolean belowTrue(ByteMatrix byteMatrix, int x, int y) {
    return y + 1 < byteMatrix.getHeight() && byteMatrix.get(x, y + 1) == 1;
}

// 對角是否相等
private static boolean diagonalTrue(ByteMatrix byteMatrix, int x, int y) {
    return byteMatrix.get(x + 1, y + 1) == 1;
}

第三步. logo&背景的繪製

到第二步,其實二維碼就已經繪製完成了,二維碼和背景都是在二維碼這種圖片上作文章,一個是往二維碼上加圖片,一個是將二維碼繪製在另外一張圖片上

一個圖片在另外一個圖片上繪製沒啥技術含量,稍微特別點的就是logo的圓角和邊框了

《二維碼服務拓展(支持logo,圓角logo,背景圖,顏色配置)》 較清晰的說了如何繪製圓角圖片,圓角邊框

不想看上面博文的沒啥關係,下面直接貼出代碼,算是比較通用的方法了,與二維碼項目自己沒什麼黏合

/**
 * 生成邊框
 *
 * @param image        原圖
 * @param cornerRadius 角度 0表示直角
 * @param color        邊框顏色
 * @return
 */
public static BufferedImage makeRoundBorder(BufferedImage image,
                                                int cornerRadius,
                                                Color color) {
    int size = image.getWidth() / 15;
    int w = image.getWidth() + size;
    int h = image.getHeight() + size;
    BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

    Graphics2D g2 = output.createGraphics();
    g2.setComposite(AlphaComposite.Src);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(color == null ? Color.WHITE : color);
    g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));

    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1.0f));
    g2.drawImage(image, size >> 1, size >> 1, null);
    g2.dispose();

    return output;
}


/**
 * 生成圓角圖片
 *
 * @param image        原始圖片
 * @param cornerRadius 圓角的弧度大小(根據實測效果,通常建議爲圖片寬度的1/4), 0表示直角
 * @return 返回圓角圖
 */
public static BufferedImage makeRoundedCorner(BufferedImage image,
                                              int cornerRadius) {
    int w = image.getWidth();
    int h = image.getHeight();
    BufferedImage output = new BufferedImage(w, h,
            BufferedImage.TYPE_INT_ARGB);

    Graphics2D g2 = output.createGraphics();
    g2.setComposite(AlphaComposite.Src);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(Color.WHITE);
    g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius,
            cornerRadius));


    g2.setComposite(AlphaComposite.SrcAtop);
    g2.drawImage(image, 0, 0, null);

    g2.dispose();

    return output;
}

與上一篇定製博文有一點區別的是,對背景圖的支持進行了擴展,除了支持以前的設置二維碼透明度,全覆蓋背景圖以外,又支持了在背景圖的指定位置處進行繪製二維碼,由於這一塊確實沒什麼好講的,乾脆貼下代碼好了

/**
 * 繪製背景圖
 *
 * @param source       二維碼圖
 * @param bgImgOptions 背景圖信息
 * @return
 */
public static BufferedImage drawBackground(BufferedImage source, QrCodeOptions.BgImgOptions bgImgOptions) {
    int sW = source.getWidth();
    int sH = source.getHeight();

    // 背景的圖寬高不該該小於原圖
    int bgW = bgImgOptions.getBgW() < sW ? sW : bgImgOptions.getBgW();
    int bgH = bgImgOptions.getBgH() < sH ? sH : bgImgOptions.getBgH();


    // 背景圖縮放
    BufferedImage bg = bgImgOptions.getBgImg();
    if (bg.getWidth() != bgW || bg.getHeight() != bgH) {
        BufferedImage temp = new BufferedImage(bgW, bgH, BufferedImage.TYPE_INT_ARGB);
        temp.getGraphics().drawImage(bg.getScaledInstance(bgW, bgH, Image.SCALE_SMOOTH)
                , 0, 0, null);
        bg = temp;
    }

    Graphics2D g2d = bg.createGraphics();
    if (bgImgOptions.getBgImgStyle() == QrCodeOptions.BgImgStyle.FILL) {
        // 選擇一塊區域進行填充
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1.0f));
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.drawImage(source, bgImgOptions.getStartX(), bgImgOptions.getStartY(), sW, sH, null);
    } else {
        // 覆蓋方式
        int x = (bgW - sW) >> 1;
        int y = (bgH - sH) >> 1;
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, bgImgOptions.getOpacity())); // 透明度, 避免看不到背景
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.drawImage(source, x, y, sW, sH, null);
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1.0f));
    }
    g2d.dispose();
    bg.flush();
    return bg;
}

測試

開發完了以後,就要開始愉快的進行測試了,測試一個全乎的

@Test
public void testGenStyleCodeV2() {
    String msg = "http://weixin.qq.com/r/FS9waAPEg178rUcL93oH";

    try {
        String logo = "logo.jpg";
        String bg = "qrbg.jpg";
        BufferedImage img = QrCodeGenWrapper.of(msg)
                .setW(550)
                .setDrawPreColor(0xff002fa7) // 寶石藍
                .setDetectOutColor(0xff0000ff)
                .setDetectInColor(Color.RED)
                .setDetectImg("detect.png")
                .setPadding(1)
                .setErrorCorrection(ErrorCorrectionLevel.H)
                .setLogo(logo)
                .setLogoStyle(QrCodeOptions.LogoStyle.ROUND)
                .setLogoBgColor(0xff00cc00)
                .setLogoRate(15)
                .setDrawStyle(QrCodeOptions.DrawStyle.IMAGE.name())
                .setDrawEnableScale(true)
                .setDrawImg("xhrBase.jpg")
                .setDrawRow2Img("xhrr2.jpeg")
                .setDrawCol2Img("xhrc2.jpeg")
                .setDrawSize4Img("xhrSize4.jpg")
                .setBgStyle(QrCodeOptions.BgImgStyle.FILL)
                .setBgImg(bg)
                .setBgStartX(230)
                .setBgStartY(330)
                .asBufferedImage();


        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(img, "png", outputStream);
        String img64 = Base64Util.encode(outputStream);
        System.out.println("<img src=\"data:image/png;base64," + img64 + "\" />");
    } catch (Exception e) {
        System.out.println("create qrcode error! e: " + e);
        Assert.assertTrue(false);
    }
}

演示case:

測試示例

一個最終定格的二維碼

定格圖

說明

上面的改造,在實際使用時,建議多測試測試是否能夠掃描出來,騰訊系列產品的二維碼掃描特別給力,通常都能很迅速的識別,其餘的就很差說了

其餘

相關博文

項目地址: https://github.com/liuyueyi/quick-media

我的博客:一灰的我的博客

公衆號獲取更多:

我的信息

參考

相關文章
相關標籤/搜索