Cấu trúc và Nguyên lý Hoạt động của Mã QR

Cấu trúc mã QR

Thành phần chính

+--+--+--+--+--+--+--+--+--+--+--+
|██|  |██|  |  |  |  |██|  |██|  |  Mẫu định vị
+--+--+--+--+--+--+--+--+--+--+--+  (góc trái trên, phải trên, trái dưới)
|  |██|  |██|  |  |  |  |██|  |██|
+--+--+--+--+--+--+--+--+--+--+--+
|██|  |██|  |  |  |  |██|  |██|  |  Mẫu căn chỉnh
+--+--+--+--+--+--+--+--+--+--+--+  (phiên bản lớn)
|  |  |  |  |  |  |  |  |  |  |  |
+--+--+--+--+--+--+--+--+--+--+--+
|  |  |  |  |Vùng dữ liệu|  |  |  |  Dữ liệu chính
+--+--+--+--+--+--+--+--+--+--+--+  
|  |  |  |  |  |  |  |  |  |  |  |  Mã sửa lỗi
+--+--+--+--+--+--+--+--+--+--+--+  (Reed-Solomon)
  • Mẫu định vị: 3 góc, xác định hướng và vị trí
  • Vùng dữ liệu: Chứa thông tin được mã hóa
  • Mã sửa lỗi: Reed-Solomon, khôi phục dữ liệu hư hỏng
  • Thông tin định dạng: Xác định cấp độ chịu lỗi và kiểu mặt nạ

Phiên bản và dung lượng

Phiên bảnKích thướcDung lượng (ký tự số)
V121×2141 (L), 34 (M), 27 (Q), 17 (H)
V1057×57652 (L), 513 (M), 364 (Q), 288 (H)
V40177×1777089 (L), 5596 (M), 3993 (Q), 3057 (H)

Cấp độ chịu lỗi

  • L: ~7% - Môi trường sạch
  • M: ~15% - Sử dụng phổ biến
  • Q: ~25% - Môi trường khắc nghiệt
  • H: ~30% - Ứng dụng quan trọng, có logo

Mã hóa dữ liệu

Phương thức mã hóa

public class MaHoaQR {
  public enum CheDoMaHoa {
    SO(1, "0001"),         // Chỉ số
    CHU_SO(2, "0010"),     // Chữ và số
    BYTE(4, "0100"),       // Dữ liệu nhị phân
    KANJI(8, "1000");      // Chữ Hán
    
    private int bit;
    private String chiSo;
    
    CheDoMaHoa(int bit, String chiSo) {
      this.bit = bit;
      this.chiSo = chiSo;
    }
  }
  
  public String maHoaSo(String duLieu) {
    StringBuilder nhiPhan = new StringBuilder();
    for (int i = 0; i < duLieu.length(); i += 3) {
      String nhom = duLieu.substring(i, Math.min(i + 3, duLieu.length()));
      int giaTri = Integer.parseInt(nhom);
      int bit = nhom.length() == 3 ? 10 : nhom.length() == 2 ? 7 : 4;
      nhiPhan.append(String.format("%" + bit + "s", 
                 Integer.toBinaryString(giaTri)).replace(' ', '0'));
    }
    return nhiPhan.toString();
  }
  
  public String maHoaChuSo(String duLieu) {
    String kyTu = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
    StringBuilder nhiPhan = new StringBuilder();
    for (int i = 0; i < duLieu.length(); i += 2) {
      if (i + 1 < duLieu.length()) {
        int gt1 = kyTu.indexOf(duLieu.charAt(i));
        int gt2 = kyTu.indexOf(duLieu.charAt(i + 1));
        nhiPhan.append(String.format("%11s", 
                   Integer.toBinaryString(gt1 * 45 + gt2)).replace(' ', '0'));
      } else {
        nhiPhan.append(String.format("%6s", 
                   Integer.toBinaryString(kyTu.indexOf(duLieu.charAt(i))).replace(' ', '0'));
      }
    }
    return nhiPhan.toString();
  }
}

Thuật toán sửa lỗi Reed-Solomon

public class MaHoaReedSolomon {
  private static final int DA_THUC_GOC = 0x11D;
  private int[] bangLog = new int[256];
  private int[] bangAntiLog = new int[256];
  
  private void khoiTaoTruongGalois() {
    int x = 1;
    for (int i = 0; i < 255; i++) {
      bangAntiLog[i] = x;
      bangLog[x] = i;
      x = (x << 1) ^ (x >= 128 ? DA_THUC_GOC : 0);
    }
  }
  
  public byte[] taoMaSuaLoi(byte[] duLieu, int soByteSuaLoi) {
    int[] daThucSinh = taoDaThucSinh(soByteSuaLoi);
    int[] ketQua = new int[duLieu.length + soByteSuaLoi];
    for (int i = 0; i < duLieu.length; i++) {
      int heSo = duLieu[i] & 0xFF;
      if (heSo != 0) {
        for (int j = 0; j < daThucSinh.length; j++) {
          ketQua[i + j] ^= nhanGalois(daThucSinh[j], heSo);
        }
      }
    }
    byte[] maSuaLoi = new byte[soByteSuaLoi];
    for (int i = 0; i < soByteSuaLoi; i++) {
      maSuaLoi[i] = (byte) ketQua[duLieu.length + i];
    }
    return maSuaLoi;
  }
  
  private int nhanGalois(int a, int b) {
    return (a == 0 || b == 0) ? 0 : 
           bangAntiLog[(bangLog[a] + bangLog[b]) % 255];
  }
}

Tạo và đọc mã QR

Sinh mã QR với ZXing

public class SinhMaQR {
  public BufferedImage taoMaQR(String noiDung, int kichThuoc, ErrorCorrectionLevel capDo) {
    Map<EncodeHintType, Object> thamSo = new HashMap<>();
    thamSo.put(EncodeHintType.CHARACTER_SET, "UTF-8");
    thamSo.put(EncodeHintType.ERROR_CORRECTION, capDo);
    
    QRCodeWriter boGhi = new QRCodeWriter();
    BitMatrix maTrix = boGhi.encode(noiDung, BarcodeFormat.QR_CODE, kichThuoc, kichThuoc, thamSo);
    
    BufferedImage hinhAnh = new BufferedImage(kichThuoc, kichThuoc, BufferedImage.TYPE_INT_RGB);
    for (int x = 0; x < kichThuoc; x++) {
      for (int y = 0; y < kichThuoc; y++) {
        hinhAnh.setRGB(x, y, maTrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
      }
    }
    return hinhAnh;
  }
}

Giải mã mã QR

public class DocMaQR {
  public String giaiMa(BufferedImage hinhAnh) {
    LuminanceSource nguon = new BufferedImageLuminanceSource(hinhAnh);
    BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(nguon));
    
    Map<DecodeHintType, Object> thamSo = new HashMap<>();
    thamSo.put(DecodeHintType.CHARACTER_SET, "UTF-8");
    
    MultiFormatReader boDoc = new MultiFormatReader();
    Result ketQua = boDoc.decode(bitmap, thamSo);
    return ketQua.getText();
  }
}

Ứng dụng thực tế

public class DichVuQRThanhToan {
  public BufferedImage taoQRThanhToan(DonHang don) {
    String url = String.format("wxp://f2f0%s?amount=%s&orderId=%s", 
                  don.getMaGianHang(), don.getSoTien(), don.getMaDon());
    BufferedImage qr = new SinhMaQR().taoMaQR(url, 300, ErrorCorrectionLevel.H);
    
    // Thêm thông tin số tiền
    BufferedImage qrMoi = new BufferedImage(qr.getWidth(), qr.getHeight() + 50, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = qrMoi.createGraphics();
    g.drawImage(qr, 0, 0, null);
    g.setFont(new Font("Arial", Font.BOLD, 24));
    g.drawString("VNĐ " + don.getSoTien(), (qr.getWidth() - g.getFontMetrics().stringWidth("VNĐ " + don.getSoTien())) / 2, qr.getHeight() + 35);
    g.dispose();
    
    return qrMoi;
  }
}

Thẻ: QRCode ReedSolomon ZXing MãHóa SửaLỗi

Đăng vào ngày 10 tháng 6 lúc 22:47