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ản | Kích thước | Dung lượng (ký tự số) |
| V1 | 21×21 | 41 (L), 34 (M), 27 (Q), 17 (H) |
| V10 | 57×57 | 652 (L), 513 (M), 364 (Q), 288 (H) |
| V40 | 177×177 | 7089 (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;
}
}