二维码纯手工制作(一步一步带你走进二维码的世界,逐渐让它变得越来越清晰,直至你完全了解)

时间:2024-05-19 11:17:28

二维码由于不喜欢去查看zxing里面的api。所以就自己研究了下,不过由于时间问题,就只支持版本1.。(其实其他版本类似)

二维码纯手工制作(一步一步带你走进二维码的世界,逐渐让它变得越来越清晰,直至你完全了解)

制作前需要了解:

一、版本公式是:(V-1)*4 + 21(V是版本号)最高版本为40

二、数字采用Numeric mode 数字编码

三、0-9,大写的A到Z(没有小写),以及符号$ % * + – . / : 包括空格可以采用Alphanumeric mode 字符编码

四、大写、小写、数字以及一些符号采用Byte mode, 字节编码

五、中文采用Kanji mode 这是日文编码,也是双字节编码

我这里只做了 数字编码( 其实其他编码也一样简单,就不BB了)

从0到9。如果需要编码的数字的个数不是3的倍数,那么,最后剩下的1或2位数会被转成4或7bits(bits数=1+3*数字位数,针对版本1),则其它的每3位数字会被编成 10(v1-v9),12(v10-v26),14(27-40),(版本1为10)。

示例:数字编码(官方例子)

在Version 1的尺寸下,纠错级别为H的情况下,编码: 01234567

1. 把上述数字分成三组: 012 345 67

2. 把他们转成二进制:  012 转成 0000001100;  345 转成 0101011001;  67 转成 1000011。

3. 把这三个二进制串起来: 0000001100 0101011001 1000011

4. 把数字的个数转成二进制 (version 1-H是10 bits ): 8个数字的二进制是 0000001000

5. 把数字编码的标志0001和第4步的编码加到前面:  0001 0000001000 0000001100 0101011001 1000011

2.补位

按8bits重排

如果所有的编码加起来不是8个倍数我们还要在后面加上足够的0,比如上面一共有78个bits,所以,我们还要加上2个0,然后按8个bits分好组:

00100000   01011011   00001011   01111000   11010001   01110010   11011100   01001101   01000011   01000000

也就是按8的倍数重排如果不是8的倍数就在后面补0直至8的倍数

然后

"11101100","00010001" 重复添加也就是236 17

剩余位的计算方法等于下图黄色区域的块数除以8 的余数

数据码与纠错码排序方法总结:数据码排完后 在排纠错码。

思路分析(自己理解方便记忆):

        假如数据码区有4块 就好像上学的时候分为四个班级 1班成绩(a)>2班(b)>3班(c)>4班(d)(>表示较好于)但是为了平衡分化因此采用成绩打乱的方式,a1>b1>c1>d1>a2>b2>c2>d2>''''''''(当然这只是理想状态),纠错码也一样。这样就好记它们到底怎么排序的了。

可能这里由很多朋友想问很多版本即使纠错码与数据码混乱排序后还要加上Remainder Bits

这个是我总结的(剩余位 见qrcode.doc表1 v2-6 :7 v1,7-13,35-40:0 v14-20,28-34:3 v21-27:4 )

剩余位=需要填充的快数%8 

二维码纯手工制作(一步一步带你走进二维码的世界,逐渐让它变得越来越清晰,直至你完全了解)

黑白的区域在QR码规范中被指定为固定的位置,称为寻像图形(finder pattern) 和 定位图形

黄色的区域用来保存被编码的数据内容以及纠错信息码。

蓝色的区域,用来标识纠错的级别(也就是Level L到Level H)和所谓的”Mask pattern”,这个区域被称为“格式化信息”(format information)。

数据整体填充方式

二维码纯手工制作(一步一步带你走进二维码的世界,逐渐让它变得越来越清晰,直至你完全了解)

数据局部填充方式

二维码纯手工制作(一步一步带你走进二维码的世界,逐渐让它变得越来越清晰,直至你完全了解)

信息区填充方式

二维码纯手工制作(一步一步带你走进二维码的世界,逐渐让它变得越来越清晰,直至你完全了解)

纠错码等级图

二维码纯手工制作(一步一步带你走进二维码的世界,逐渐让它变得越来越清晰,直至你完全了解)

mask等级图

二维码纯手工制作(一步一步带你走进二维码的世界,逐渐让它变得越来越清晰,直至你完全了解)

mask算法

1.逐一检查每一行,在一行中连续5颜色相同的模块,加3,连续相同颜色模块超过5个以上,每增加一个,就加1。然后将每行中计算的数值加在一起,即为评判标准1的值。

2.在二维码图像上寻找模块颜色相同的区域(最小为2×2的区域),然后代入公式3×(m-1)×(n-1)(这是一个m×n区域),将所有满足的区域代入公式所得的值全部相加起来。,即为评判标准2的值。

3.寻找图案中dark-light-dark-dark-dark-light-dark-light-light-light-light图案(行/列都要计数),或者反过来,加40。即为评判标准3的值。

4.  计算 黑色/总和 *100  然后取其前一个五的倍数以及后一个五的倍数 然后取其中较小值 a 值=|a-50|*2即为评判标准4的值。

特别注意:mask进行标准计算时 时针对整个二维码,然而掩码只针对数据区

代码:

package com;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

/**
 * Created by Administrator on 2020/5/25.
 */
public class qrcodeEwUtil {
    public String data;
    public String dj="Q";
    public Map<String,String> mapCodeDj;//二维码纠错等级数组
    public String[] mapXorDj;//二维码mask等级数组
    private int WIDTH;
    private int HEIGHT;
    private int pd=30;
    public int[][] v1Mode;
    public String ewmDada;//二维码最终编码
    private int bs=6;//画二维码的时候对应的倍数 如 21*21 width=bs*21
    public String mask;
    public Map<String,int[][]> maskEnd =new HashMap<String, int[][]>();

  详情了解我的博客(二维码纠错码生成)
    makeJcm mkjcm;//二维码纠错码生成类

     详情了解我的博客(二维码数据生成)
    makeDataCode mkdata;//二维码数据码生成类

     详情了解我的博客(二维码信息码的生成)
    makeBchCode mkinfo;//二维码信息区生成类


    public qrcodeEwUtil(){}
    public qrcodeEwUtil(String data){
        this.data=data;
        init();
    }
    public qrcodeEwUtil(String data,String dj){
        this.data=data;
        if(dj!=null&&(dj.equals("L")||dj.equals("M")||dj.equals("H"))){
            this.dj=dj;
        }
        init();
    }
    public void init(){
        //jcm: mkjcm.jcmArr  data:mkdata.sjmArr
        getCodeBaseMap();
        v1ModeArr();
        for(int j=0;j<mapXorDj.length;j++){
        mask=mapXorDj[j];
        mkdata=new  makeDataCode(data,dj);//生成数据码

     详情了解我的博客(二维码数据生成)
        mkjcm=new makeJcm();
        mkjcm.makeQrJcm(mkdata.sjmArr,mkjcm.makeDxsDeep(mkdata.jcCodeNumTotal[0][mkdata.jcmDj]));//生成纠错码

     详情了解我的博客(二维码纠错码生成)
        int[] arr=new int[]{15,5};//版本一默认
        mkinfo=new makeBchCode(arr,mapCodeDj.get(dj)+mask);//生成信息码

     详情了解我的博客(二维码信息码的生成)
        mkinfo.infoCode=new StringBuffer(mkinfo.infoCode).reverse().toString();//为了方便后面运算 因此将信息码反转
        StringBuffer sbf=new StringBuffer("");

      //由于信息码以及纠错码是采用十进制 因此需要转换为二进制并连接起来 数据码在前,纠错码在后
        for(int i=0;i<mkdata.sjmArr.length;i++){
            sbf.append(sjzZh8byte(mkdata.sjmArr[i]));
        }
        for(int i=0;i<mkjcm.jcmArr.length;i++){
            sbf.append(sjzZh8byte(mkjcm.jcmArr[i]));
        }
        ewmDada=sbf.toString();
           if(maskEnd.isEmpty()){
               maskEnd.put(mask,getImageMode());
           }else{

             //二维码数组
               int[][] arr2=getImageMode();

            //mask值
               int a=pt1(arr2)+pt2(arr2)+pt3(arr2)+pt4(arr2);
               boolean flag=false;

           //mask值最小如果有两个表示计算错误
               if(maskEnd.size()!=1){
                   try {
                       throw new Exception("bad maskPartern!");
                   } catch (Exception e) {
                       e.printStackTrace();
                   }
               }

            //将mask较小值的mask等级以及二维码数组存入集合中
               for(String key:maskEnd.keySet()){
                    int b=pt1(maskEnd.get(key))+pt2(maskEnd.get(key))+pt3(maskEnd.get(key))+pt4(maskEnd.get(key));
                    flag=a<b?true:false;
               }
               if(flag){
                   maskEnd.clear();
                   maskEnd.put(mask,arr2);
               }
           }
    }
    //drawImage("ewmModeDemoXorRgbch-"+dj+mask+"_" + data);

    //画二维码
        drawEwmImage("ewmModeDemo" + data);
    }
    public void getCodeBaseMap(){
        mapCodeDj=new HashMap<String, String>();
        mapXorDj=new String[]{"000","001","010","011","100","101","110","111"};
        mapCodeDj.put("L","01");
        mapCodeDj.put("M","00");
        mapCodeDj.put("Q","11");
        mapCodeDj.put("H","10");
        //div 表示整除运算符  mod求模运算符
        /*
        * 掩模图形参考    条件
000    (i + j) mod 2 = 0
001    i mod 2 = 0
010    j mod 3 = 0
011    (i + j) mod 3 = 0
100    ((i div 2) + (j div 3)) mod 2 = 0
101    (i j) mod 2 + (i j) mod 3 = 0
110    ((i j) mod 2 + (i j) mod 3) mod 2 = 0
111    ((i j) mod 3 + (i+j) mod 2) mod 2 = 0
        * */
    }

   //画二维码
    public BufferedImage createEwmImage(){
        WIDTH=bs*21+2*pd;
        HEIGHT=bs*21+2*pd;
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics graphic = image.getGraphics();
        Graphics2D g2=(Graphics2D)graphic;
        g2.setColor(Color.WHITE);
        g2.fillRect(0, 0, WIDTH, HEIGHT);
        for(String key:maskEnd.keySet()){
            int[][]  arr=maskEnd.get(key);
            for(int i=0;i<arr.length;i++){
             for(int j=0;j<arr[i].length;j++){
                 if(arr[i][j]==1){
                     g2.setColor(Color.BLACK);
                 }else{
                     g2.setColor(Color.WHITE);
                 }
                 for (int l = 0; l < bs; l++) {
                     g2.drawLine( i * bs + pd, j * bs + pd + l,  i * bs + pd + bs - 1, j * bs + pd + l);
                 }
             }
            }
        }
        return image;
    }

  //得到二维码数组模型
    public int[][] getImageMode(){
        int a=20; int b=0;
        int[][] arr=new int[21][21];
        while(true){
            int lx=2;
            if(a==6){
                a=a-1;
                lx=1;
            }else{
                a=a-2;
            }
            int fxNum=a>=6?a-1:a;
            if((fxNum+4)%4==3){
                for(int j=0;j<21;j++) {
                    for (int i = lx; i >= 1; i--) {
                        if (v1Mode[a + i][j] == 3) {
                            b++;
                            String ewmdata = ewmDada.substring(b - 1, b);
                            arr[a + i][j]=maskEwm(a + i, j, ewmdata)==Color.BLACK?1:0;
                            //g2.setColor(maskEwm(a + i, j, ewmdata));
                        } else {
                            arr[a + i][j]=getColor(a + i, j)==Color.BLACK?1:0;
                            //g2.setColor(getColor(a + i, j));
                        }
                    }
                }
            }else{
                for(int j=20;j>=0;j--){
                    for(int i=lx;i>=1;i--){
                        if(v1Mode[a+i][j]==3){
                            b++;
                            String ewmdata=ewmDada.substring(b-1,b);
                            arr[a + i][j]=maskEwm(a + i, j, ewmdata)==Color.BLACK?1:0;
                            //g2.setColor(maskEwm(a + i, j, ewmdata));
                        }else{
                            arr[a + i][j]=getColor(a + i, j)==Color.BLACK?1:0;
                            //g2.setColor(getColor(a+i,j));
                        }
                    }
                }
            }
            if(a<0){
                break;
            }
        }
        return arr;
    }
    /*非数据区
    * 返回颜色 1:黑色 0:白色
    * */
    public Color getColor(int i,int j) {
        int a = 0;
        switch (v1Mode[i][j]) {
            case 0:
                a = 0;
                break;
            case 1:
                a = 1;
                break;
            case 2:
                String strInfo = "";
                if (i == 8) {
                    if (j <= 5) {
                        strInfo = mkinfo.infoCode.substring(j, j + 1);
                    } else if (j > 5 && j <= 13) {
                        strInfo = mkinfo.infoCode.substring(j - 1, j);
                    } else {
                        strInfo = mkinfo.infoCode.substring(j - 6, j - 5);
                    }
                } else {
                    if (j == 8) {
                        if (i <= 5) {
                            strInfo = mkinfo.infoCode.substring(mkinfo.infoCode.length() - i - 1, mkinfo.infoCode.length() - i);
                        } else if (i > 5 && i <= 13) {
                            strInfo = mkinfo.infoCode.substring(mkinfo.infoCode.length() - i, mkinfo.infoCode.length() - i + 1);
                        } else {
                            strInfo = mkinfo.infoCode.substring(mkinfo.infoCode.length() - i + 5, mkinfo.infoCode.length() - i + 6);
                        }
                    }
                }
                if (strInfo.equals("1")) {
                    a = 1;
                } else {
                    a = 0;
                }
                break;
        }
        if(a==1){
            return Color.BLACK;
        }
        return Color.WHITE;
    }

    /*数据区
    * 返回颜色 1:黑色 0:白色
    * */
    public Color maskEwm(int i,int j,String str){
        int modYs=getBackMask(j,i);
        int a=0;
        if(Integer.valueOf(str)==1){
        a=modYs;
        }else{
        a=(modYs+1)%2;
        }
        if(a==1){
            return Color.BLACK;
        }
        return Color.WHITE;
    }

   /*
    * mask运算规则,row表示行,col表示列
    * */
    public int getBackMask(int row,int col){
        int a=0;
        if(mask.equals("000")){
         a=(row+col)%2;
        }
        else if(mask.equals("001")){
            a=row%2;
        }
        else if(mask.equals("010")){
            a=col%3;
        }
        else if(mask.equals("011")){
            a=(row+col)%3;
        }
        else if(mask.equals("100")){
            a=((int)Math.floor(row/2)+(int)Math.floor(col/3))%2;
        }
        else if(mask.equals("101")){
            a=(row*col)%2+(row*col)%3;
        }
        else if(mask.equals("110")){
            a=((row*col)%2+(row*col)%3)%2;
        }
        else if(mask.equals("111")){
            a=((row+col)%2+(row*col)%3)%2;
        }
        return a==0?0:1;
    }
    public void drawEwmImage(String code){
        try {
            ImageIO.write(createEwmImage(), "jpg", new File("D://code/" + code + ".jpg"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /*
    * 将十进制转换成8bite的二进制数据
    * */
    public String sjzZh8byte(int a){
      if(a==0){
          return "00000000";
      }
        String str=Integer.toBinaryString(a);
        if(str.length()<8){
            int len=8-str.length();
            for(int i=0;i<len;i++){
                str="0"+str;
            }
        }
        return str;
    }
    /*mask pattern1 算法
    * 1:mask pattern1 算法连续相同>=5块开始计算 等于相同数-2;
    * 2:横、纵都要计算
    * */
    public int pt1(int[][] arr){
        int a=0;
        for(int i=0;i<arr.length;i++){
            int b=-1;int count=0;
            for(int j=0;j<arr.length;j++){
                if(b!=arr[i][j]){
                    b=arr[i][j];
                    if(count>=5){
                        a+=count-2;
                    }
                    count=1;
                }else{
                    count++;
                }
            }
        }
        for(int j=0;j<arr.length;j++){
            int b=-1;int count=0;
            for(int i=0;i<arr.length;i++){
                if(b!=arr[i][j]){
                    b=arr[i][j];
                    if(count>=5){
                        a+=count-2;
                    }
                    count=1;
                }else{
                    count++;
                }
            }
        }
        return a;
    }
    /*mask pattern2 算法
    * 计算小方块 要求长、宽必须均>=2 算法:3(m-1)(n-1)
    * */
    public int pt2(int[][] arr){
        int a=0;
        int[][] arr2=new int[arr.length][arr.length];
        ArrayList<Integer> list;
        for(int i=0;i<arr.length-1;i++){
            for(int j=0;j<arr.length-1;j++){
                if(arr2[i][j]==1){
                    continue;
                }
                int b=arr[i][j];
                list =new ArrayList<Integer>();
                for(int m=i;m<arr.length;m++){
                    if(arr[m][j]!=b){
                        break;
                    }
                   for(int n=j;n<arr.length;n++){
                       if(arr[m][n]==b){
                           //arr2[m][n]=1;
                           list.add(m-i,n-j);
                       }else{
                           break;
                       }
                   }
                }
                if(!list.isEmpty()&&list.size()>1){
                    int x=0;int y=0;int d=0;
                        for(int l=0;l<list.size();l++){
                            if(list.get(l)==0){
                                if((l-x)>2&&y>0){
                                    a+=3*(l-x-2)*y;
                                    for(int m=x+1;m<=l-1;m++){
                                        for(int n=j;n<=j+y;n++){
                                            arr2[m][n]=1;
                                        }
                                    }
                                }
                                x=l;
                                continue;
                            }else{
                                y=Math.min(y,list.get(l));
                            }
                            if((l==list.size()-1)&&(l-x)>1&&y>0){
                                a+=3*(l-x-1)*y;
                                for(int m=x+1;m<=l;m++){
                                    for(int n=j;n<=j+y;n++){
                                        arr2[m][n]=1;
                                    }
                                }
                            }
                        }
                    }
            }
        }
        return a;
    }
/*mask pattern3 算法
 * 出现10111010000 或者00001011101 加40
 * */
    public int pt3(int[][] arr) {
        String str1="10111010000";
        String str2="00001011101";
        String str="";
        int a=0;
        for(int i=0;i<arr.length;i++){
            for(int j=0;j<=arr.length-11;j++){
                boolean flag=true;
                if(arr[i][j]==0){
                    str=str2;
                }else{
                    str=str1;
                }
                for(int m=j+1;m<=arr.length-11;m++){
                    if(arr[i][m]!=Integer.valueOf(str.substring(m-j,m-j+1))){
                        flag=false;
                    }
                }
                if(flag){
                    a+=40;
                }
            }
        }
        for(int j=0;j<arr.length;j++){
            for(int i=0;i<=arr.length-11;i++){
                boolean flag=true;
                if(arr[i][j]==0){
                    str=str2;
                }else{
                    str=str1;
                }
                for(int m=i+1;m<=arr.length-11;m++){
                    if(arr[m][j]!=Integer.valueOf(str.substring(m-i,m-i+1))){
                        flag=false;
                    }
                }
                if(flag){
                    a+=40;
                }
            }
        }
        return a;
    }
    /*  mask pattern4 算法
    * 计算 黑色/总和 *100  然后取其前一个五的倍数以及后一个五的倍数 然后取其中较小值 a
    * 值=|a-50|*2
    * */
    public int pt4(int[][] arr) {
        int a=0;
        int block_num=0;
        int total_num=arr.length*arr.length;
        for(int i=0;i<arr.length;i++){
            for(int j=0;j<arr.length;j++){
                if(arr[i][j]==1){
                    block_num++;
                }
            }
        }
        double block_bl=20*block_num/total_num;
        int back_num=(int)Math.floor(block_bl);
        int last_num=back_num+1;
        return Math.min(Math.abs(back_num-10),Math.abs(last_num-10))*10;
    }
    /*
    *
    *
    * 1表示黑色 0 表示白色 并且不能填充
    * 2 表示信息区  3 表示数据区  v1版本模型数组
    * */
    public void v1ModeArr(){
        int[][] v1ModeFfx=new int[21][21];
         v1Mode=new int[21][21];
        v1ModeFfx[0]=new int[]{1,1,1,1,1,1,1,0,2,3,3,3,3,0,1,1,1,1,1,1,1};
        v1ModeFfx[1]=new int[]{1,0,0,0,0,0,1,0,2,3,3,3,3,0,1,0,0,0,0,0,1};
        v1ModeFfx[2]=new int[]{1,0,1,1,1,0,1,0,2,3,3,3,3,0,1,0,1,1,1,0,1};
        v1ModeFfx[3]=v1ModeFfx[2];
        v1ModeFfx[4]=v1ModeFfx[2];
        v1ModeFfx[5]=v1ModeFfx[1];
        v1ModeFfx[6]=new int[]{1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1};
        v1ModeFfx[7]=new int[]{0,0,0,0,0,0,0,0,2,3,3,3,3,0,0,0,0,0,0,0,0};
        v1ModeFfx[8]=new int[]{2,2,2,2,2,2,1,2,2,3,3,3,3,2,2,2,2,2,2,2,2};
        v1ModeFfx[9]=new int[]{3,3,3,3,3,3,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3};
        v1ModeFfx[10]=new int[]{3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3};
        v1ModeFfx[11]=v1ModeFfx[9];
        v1ModeFfx[12]=v1ModeFfx[10];
        v1ModeFfx[13]=new int[]{0,0,0,0,0,0,0,0,1,3,3,3,3,3,3,3,3,3,3,3,3};
        v1ModeFfx[14]=new int[]{1,1,1,1,1,1,1,0,2,3,3,3,3,3,3,3,3,3,3,3,3};
        v1ModeFfx[15]=new int[]{1,0,0,0,0,0,1,0,2,3,3,3,3,3,3,3,3,3,3,3,3};
        v1ModeFfx[16]=new int[]{1,0,1,1,1,0,1,0,2,3,3,3,3,3,3,3,3,3,3,3,3};
        v1ModeFfx[17]=v1ModeFfx[16];
        v1ModeFfx[18]=v1ModeFfx[16];
        v1ModeFfx[19]=v1ModeFfx[15];
        v1ModeFfx[20]=v1ModeFfx[14];
        for(int i=0;i<21;i++){
            for(int j=0;j<21;j++){
                v1Mode[j][i]=v1ModeFfx[i][j];
            }
        }
    }
    public static void main(String[] args) {

//可以自定义等级 默认为Q 由于只有版本1,因此数字不要太长,否则有可能测试失败
        qrcodeEwUtil qr=new qrcodeEwUtil("01234567","M");
    }
}