矩形二维码生成,解析(彩色、多个)
说明
- java生成普通二维码、带logo二维码、彩色二维码
- java解析彩色、多个二维码(一个图片上的多个二维码)
使用到的第三方jar包如下:
com.google.zxing:core:3.4.0
com.google.zxing:javase:3.4.0
生成二维码
package com.utils;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.multi.qrcode.QRCodeMultiReader;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.imageio.ImageIO;
import java.awt.*;
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;
@Slf4j
public class QRUtil {
private static final String CHARSET = "UTF-8";
private static final String FORMAT = "PNG";
private static final int QRCODE_SIZE = 150;
private static final int LOGO_SIZE = 50;
private static final HashMap<EncodeHintType, Object> ENCODE_HINTS = new HashMap<>();
private static final HashMap<DecodeHintType, Object> DECODE_HINTS = new HashMap<>();
static {
ENCODE_HINTS.put(EncodeHintType.CHARACTER_SET, CHARSET);
ENCODE_HINTS.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
ENCODE_HINTS.put(EncodeHintType.MARGIN, 1);
DECODE_HINTS.put(DecodeHintType.CHARACTER_SET, CHARSET);
}
public static void encode(String content, String destPath) {
encode(content, null, destPath);
}
public static void encode(String content, String logoPath, String destPath) {
try {
BufferedImage img = bufferedImage(content, logoPath);
if (img != null) {
ImageIO.write(img, FORMAT, new File(destPath));
}
} catch (IOException ignored) {
}
}
public static BufferedImage encodeBuffer(String content, String logoPath) {
return bufferedImage(content, logoPath);
}
private static BufferedImage bufferedImage(String content, String logoPath) {
BitMatrix bitMatrix = null;
try {
bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, ENCODE_HINTS);
} catch (WriterException ignored) {
}
if (bitMatrix == null) {
return null;
}
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, bitMatrix.get(x, y) ? Color.BLACK.getRGB() : Color.WHITE.getRGB());
}
}
if (!StringUtils.isEmpty(logoPath)) {
insertLogo(image, logoPath);
}
return image;
}
private static void insertLogo(BufferedImage source, String logoPath) {
File file = new File(logoPath);
if (!file.exists()) {
return;
}
try {
BufferedImage srcImage = ImageIO.read(file);
int width = srcImage.getWidth(null);
int height = srcImage.getHeight(null);
Image destImage = srcImage.getScaledInstance(LOGO_SIZE, LOGO_SIZE, Image.SCALE_SMOOTH);
if ((height > LOGO_SIZE) || (width > LOGO_SIZE)) {
double ratio;
if (height > width) {
ratio = Integer.valueOf(LOGO_SIZE).doubleValue() / height;
} else {
ratio = Integer.valueOf(LOGO_SIZE).doubleValue() / width;
}
AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(ratio, ratio), null);
destImage = op.filter(srcImage, null);
}
width = destImage.getWidth(null);
height = destImage.getHeight(null);
BufferedImage tag = new BufferedImage(width - 5, height - 5, BufferedImage.TYPE_INT_RGB);
Graphics g = tag.getGraphics();
g.drawImage(destImage, 0, 0, null);
g.dispose();
destImage = tag;
Graphics2D graph = source.createGraphics();
int x = (QRCODE_SIZE - width) / 2;
int y = (QRCODE_SIZE - height) / 2;
graph.drawImage(destImage, x, y, width, height, null);
Shape shape = new RoundRectangle2D.Float(x, y, width, width, 5, 5);
graph.setStroke(new BasicStroke(1f));
graph.draw(shape);
graph.dispose();
} catch (IOException e) {
log.error("read img error", e);
}
}
}
解析二维码
zxing自带的二值化(HybridBinarizer与GlobalHistogramBinarizer)并不能解决问题
因此要手动实现一下,解析二维码的主要流程:
1.将图片灰度化,使用加权灰度法(效果与opencv基本一致),尝试一次解析,失败则继续
2.对图片二值化(与opencv有差异,毕竟算法比不过它,暂时够用)
3.更换二值化阈值多次解析
public static Result decode(String url, boolean handle) {
try {
BufferedImage image = ImageIO.read(new File(url));
if (image != null) {
int[][] pointGray = new int[image.getWidth()][image.getHeight()];
if (handle) {
image = gray(image, pointGray);
Result result = decode(image);
String content = result == null ? null : result.getText();
if (!StringUtils.isEmpty(content)) {
log.debug("The img decode success by only gray,[url:{}]", url);
return result;
}
int threshold = 170;
for (int i = 0; i < 80; i += 5) {
image = binary(image, pointGray, threshold + i);
result = decode(image);
content = result == null ? null : result.getText();
if (!StringUtils.isEmpty(content)) {
log.debug("The img decode success,[url:{}],[threshold:{}]", url, threshold + i);
break;
}
}
return result;
}
return decode(image);
}
} catch (IOException e) {
log.error("read img error", e);
}
return null;
}
public static Result[] decodeMulti(String url, boolean handle) {
try {
BufferedImage image = ImageIO.read(new File(url));
if (image != null) {
int[][] pointGray = new int[image.getWidth()][image.getHeight()];
if (handle) {
image = gray(image, pointGray);
Result[] results = decodeMulti(image);
if (results.length > 0) {
log.debug("The img decode success by only gray,[url:{}]", url);
return results;
}
int threshold = 170;
for (int i = 0; i < 80; i += 5) {
image = binary(image, pointGray, threshold + i);
results = decodeMulti(image);
if (results.length > 0) {
log.debug("The img decode success,[url:{}],[threshold:{}]", url, threshold + i);
break;
}
}
return results;
}
return decodeMulti(image);
}
} catch (IOException e) {
log.error("read img error", e);
}
return new Result[0];
}
private static Result decode(BufferedImage image) {
try {
BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
return new MultiFormatReader().decode(bitmap, DECODE_HINTS);
} catch (NotFoundException ignored) {
}
return null;
}
private static Result[] decodeMulti(BufferedImage image) {
try {
BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
return new QRCodeMultiReader().decodeMultiple(bitmap, DECODE_HINTS);
} catch (NotFoundException ignored) {
}
return new Result[0];
}
public static BufferedImage gray(BufferedImage image, int[][] pointGray) {
int width = image.getWidth();
int height = image.getHeight();
BufferedImage grayImage = new BufferedImage(width, height, image.getType());
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
final int color = image.getRGB(i, j);
final int r = (color >> 16) & 0xff;
final int g = (color >> 8) & 0xff;
final int b = color & 0xff;
int gray = (int) (0.3 * r + 0.59 * g + 0.11 * b);
pointGray[i][j] = gray;
int newPixel = colorToRgb(gray, gray, gray);
grayImage.setRGB(i, j, newPixel);
}
}
return grayImage;
}
private static int colorToRgb(int red, int green, int blue) {
int newPixel = 0;
newPixel += 255;
newPixel = newPixel << 8;
newPixel += red;
newPixel = newPixel << 8;
newPixel += green;
newPixel = newPixel << 8;
newPixel += blue;
return newPixel;
}
public static BufferedImage binary(BufferedImage image, int[][] pointGray, int threshold) {
int width = image.getWidth();
int height = image.getHeight();
BufferedImage target = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int i = avgColor(pointGray, x, y, width, height);
if (i > threshold) {
target.setRGB(x, y, Color.WHITE.getRGB());
} else {
target.setRGB(x, y, Color.BLACK.getRGB());
}
}
}
return target;
}
public static int avgColor(int[][] gray, int x, int y, int w, int h) {
int rs = gray[x][y]
+ (x == 0 ? 255 : gray[x - 1][y])
+ (x == 0 || y == 0 ? 255 : gray[x - 1][y - 1])
+ (x == 0 || y == h - 1 ? 255 : gray[x - 1][y + 1])
+ (y == 0 ? 255 : gray[x][y - 1])
+ (y == h - 1 ? 255 : gray[x][y + 1])
+ (x == w - 1 ? 255 : gray[x + 1][y])
+ (x == w - 1 || y == 0 ? 255 : gray[x + 1][y - 1])
+ (x == w - 1 || y == h - 1 ? 255 : gray[x + 1][y + 1]);
return rs / 9;
}