parent
b10504aed5
commit
fa83300dea
12 changed files with 542 additions and 20 deletions
@ -0,0 +1,113 @@ |
||||
package org.springblade.lims.utils.qrcode; |
||||
|
||||
import com.google.zxing.LuminanceSource; |
||||
|
||||
import java.awt.*; |
||||
import java.awt.geom.AffineTransform; |
||||
import java.awt.image.BufferedImage; |
||||
|
||||
/** |
||||
* <pre> |
||||
* Created by Binary Wang on 2017-01-05. |
||||
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> |
||||
* </pre> |
||||
*/ |
||||
public final class BufferedImageLuminanceSource extends LuminanceSource { |
||||
|
||||
private final BufferedImage image; |
||||
private final int left; |
||||
private final int top; |
||||
|
||||
public BufferedImageLuminanceSource(BufferedImage image) { |
||||
this(image, 0, 0, image.getWidth(), image.getHeight()); |
||||
} |
||||
|
||||
public BufferedImageLuminanceSource(BufferedImage image, int left, int top, |
||||
int width, int height) { |
||||
super(width, height); |
||||
|
||||
int sourceWidth = image.getWidth(); |
||||
int sourceHeight = image.getHeight(); |
||||
if (left + width > sourceWidth || top + height > sourceHeight) { |
||||
throw new IllegalArgumentException("Crop rectangle does not fit within image data."); |
||||
} |
||||
|
||||
for (int y = top; y < top + height; y++) { |
||||
for (int x = left; x < left + width; x++) { |
||||
if ((image.getRGB(x, y) & 0xFF000000) == 0) { |
||||
image.setRGB(x, y, 0xFFFFFFFF);// = white
|
||||
} |
||||
} |
||||
} |
||||
|
||||
this.image = new BufferedImage(sourceWidth, sourceHeight, |
||||
BufferedImage.TYPE_BYTE_GRAY); |
||||
this.image.getGraphics().drawImage(image, 0, 0, null); |
||||
this.left = left; |
||||
this.top = top; |
||||
} |
||||
|
||||
@Override |
||||
public byte[] getRow(int y, byte[] row) { |
||||
if (y < 0 || y >= getHeight()) { |
||||
throw new IllegalArgumentException( |
||||
"Requested row is outside the image: " + y); |
||||
} |
||||
int width = getWidth(); |
||||
if (row == null || row.length < width) { |
||||
row = new byte[width]; |
||||
} |
||||
this.image.getRaster().getDataElements(this.left, this.top + y, width, |
||||
1, row); |
||||
return row; |
||||
} |
||||
|
||||
@Override |
||||
public byte[] getMatrix() { |
||||
int width = getWidth(); |
||||
int height = getHeight(); |
||||
int area = width * height; |
||||
byte[] matrix = new byte[area]; |
||||
this.image.getRaster().getDataElements(this.left, this.top, width, |
||||
height, matrix); |
||||
return matrix; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isCropSupported() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public LuminanceSource crop(int left, int top, int width, int height) { |
||||
return new com.github.binarywang.utils.qrcode.BufferedImageLuminanceSource(this.image, this.left + left, |
||||
this.top + top, width, height); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isRotateSupported() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public LuminanceSource rotateCounterClockwise() { |
||||
|
||||
int sourceWidth = this.image.getWidth(); |
||||
int sourceHeight = this.image.getHeight(); |
||||
|
||||
AffineTransform transform = new AffineTransform(0.0, -1.0, 1.0, 0.0, |
||||
0.0, sourceWidth); |
||||
|
||||
BufferedImage rotatedImage = new BufferedImage(sourceHeight, |
||||
sourceWidth, BufferedImage.TYPE_BYTE_GRAY); |
||||
|
||||
Graphics2D g = rotatedImage.createGraphics(); |
||||
g.drawImage(this.image, transform, null); |
||||
g.dispose(); |
||||
|
||||
int width = getWidth(); |
||||
return new com.github.binarywang.utils.qrcode.BufferedImageLuminanceSource(rotatedImage, this.top, |
||||
sourceWidth - (this.left + width), getHeight(), width); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,55 @@ |
||||
package org.springblade.lims.utils.qrcode; |
||||
|
||||
import com.google.zxing.common.BitMatrix; |
||||
|
||||
import javax.imageio.ImageIO; |
||||
import java.awt.image.BufferedImage; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
|
||||
/** |
||||
* <pre> |
||||
* Created by Binary Wang on 2017-01-05. |
||||
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> |
||||
* </pre> |
||||
*/ |
||||
public final class MatrixToImageWriter { |
||||
|
||||
private static final int BLACK = 0xFF000000; |
||||
private static final int WHITE = 0xFFFFFFFF; |
||||
|
||||
private MatrixToImageWriter() { |
||||
} |
||||
|
||||
public static BufferedImage toBufferedImage(BitMatrix matrix) { |
||||
int width = matrix.getWidth(); |
||||
int height = matrix.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, matrix.get(x, y) ? BLACK : WHITE); |
||||
} |
||||
} |
||||
|
||||
return image; |
||||
} |
||||
|
||||
public static void writeToFile(BitMatrix matrix, String format, File file) |
||||
throws IOException { |
||||
BufferedImage image = toBufferedImage(matrix); |
||||
if (!ImageIO.write(image, format, file)) { |
||||
throw new IOException("Could not write an image of format " + format + " to " + file); |
||||
} |
||||
} |
||||
|
||||
public static void writeToStream(BitMatrix matrix, String format, OutputStream stream) |
||||
throws IOException { |
||||
BufferedImage image = toBufferedImage(matrix); |
||||
if (!ImageIO.write(image, format, stream)) { |
||||
throw new IOException("Could not write an image of format " + format); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,56 @@ |
||||
package org.springblade.lims.utils.qrcode; |
||||
|
||||
import java.awt.*; |
||||
|
||||
/** |
||||
* <pre> |
||||
* Created by Binary Wang on 2017-01-05. |
||||
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> |
||||
* </pre> |
||||
*/ |
||||
public class MatrixToLogoImageConfig { |
||||
/** |
||||
* logo默认边框颜色 |
||||
*/ |
||||
public static final Color DEFAULT_BORDERCOLOR = Color.WHITE; |
||||
/** |
||||
* logo默认边框宽度 |
||||
*/ |
||||
public static final int DEFAULT_BORDER = 5; |
||||
/** |
||||
* logo大小默认为照片的1/5 |
||||
*/ |
||||
public static final int DEFAULT_LOGOPART = 5; |
||||
|
||||
private final int border; |
||||
private final Color borderColor; |
||||
private final int logoPart; |
||||
|
||||
public MatrixToLogoImageConfig() { |
||||
this(DEFAULT_BORDERCOLOR, DEFAULT_LOGOPART); |
||||
} |
||||
|
||||
public MatrixToLogoImageConfig(Color borderColor, int logoPart) { |
||||
this(borderColor, logoPart, DEFAULT_BORDER); |
||||
} |
||||
|
||||
public MatrixToLogoImageConfig(Color borderColor, int logoPart, int border) { |
||||
this.borderColor = borderColor; |
||||
this.logoPart = logoPart; |
||||
this.border = border; |
||||
} |
||||
|
||||
|
||||
public Color getBorderColor() { |
||||
return this.borderColor; |
||||
} |
||||
|
||||
public int getBorder() { |
||||
return this.border; |
||||
} |
||||
|
||||
public int getLogoPart() { |
||||
return this.logoPart; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,243 @@ |
||||
package org.springblade.lims.utils.qrcode; |
||||
|
||||
import com.github.binarywang.utils.qrcode.BufferedImageLuminanceSource; |
||||
import com.github.binarywang.utils.qrcode.MatrixToImageWriter; |
||||
import com.github.binarywang.utils.qrcode.MatrixToLogoImageConfig; |
||||
import com.google.common.collect.Maps; |
||||
import com.google.zxing.*; |
||||
import com.google.zxing.common.BitMatrix; |
||||
import com.google.zxing.common.HybridBinarizer; |
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; |
||||
import lombok.experimental.UtilityClass; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
|
||||
import javax.imageio.ImageIO; |
||||
import java.awt.*; |
||||
import java.awt.geom.RoundRectangle2D; |
||||
import java.awt.image.BufferedImage; |
||||
import java.io.*; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.nio.file.Files; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* <pre> |
||||
* Created by Binary Wang on 2017-01-05. |
||||
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> |
||||
* </pre> |
||||
*/ |
||||
@Slf4j |
||||
@UtilityClass |
||||
public class QrcodeUtils { |
||||
/** |
||||
* 生成二维码的默认边长,因为是正方形的,所以高度和宽度一致 |
||||
*/ |
||||
private static final int DEFAULT_LENGTH = 400; |
||||
/** |
||||
* 生成二维码的格式 |
||||
*/ |
||||
private static final String FORMAT = "jpg"; |
||||
|
||||
/** |
||||
* 根据内容生成二维码数据 |
||||
* |
||||
* @param content 二维码文字内容[为了信息安全性,一般都要先进行数据加密] |
||||
* @param length 二维码图片宽度和高度 |
||||
*/ |
||||
public static BitMatrix createQrcodeMatrix(String content, int length) { |
||||
return createQrcodeMatrix(content, length, ErrorCorrectionLevel.H); |
||||
} |
||||
|
||||
public static BitMatrix createQrcodeMatrix(String content, int length, ErrorCorrectionLevel level) { |
||||
Map<EncodeHintType, Object> hints = Maps.newEnumMap(EncodeHintType.class); |
||||
// 设置字符编码
|
||||
hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name()); |
||||
// 指定纠错等级
|
||||
hints.put(EncodeHintType.ERROR_CORRECTION, level); |
||||
|
||||
try { |
||||
return new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, length, length, hints); |
||||
} catch (WriterException e) { |
||||
throw new RuntimeException("内容为:【" + content + "】的二维码生成失败!", e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 根据指定边长创建生成的二维码,允许配置logo属性 |
||||
* |
||||
* @param content 二维码内容 |
||||
* @param length 二维码的高度和宽度 |
||||
* @param logoFile logo 文件对象,可以为空 |
||||
* @param logoConfig logo配置,可设置logo展示长宽,边框颜色 |
||||
* @return 二维码图片的字节数组 |
||||
*/ |
||||
public static byte[] createQrcode(String content, int length, File logoFile, MatrixToLogoImageConfig logoConfig) |
||||
throws Exception { |
||||
if (logoFile != null && !logoFile.exists()) { |
||||
throw new IllegalArgumentException("请提供正确的logo文件!"); |
||||
} |
||||
|
||||
try (InputStream logo = logoFile == null ? null : new FileInputStream(logoFile)) { |
||||
return createQrcode(content, length, logo, logoConfig); |
||||
} |
||||
} |
||||
|
||||
public static byte[] createQrcode(String content, int length, InputStream logo, MatrixToLogoImageConfig logoConfig) throws Exception { |
||||
BufferedImage img = generateQRCodeImage(content, length, logo, logoConfig); |
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
ImageIO.write(img, FORMAT, baos); |
||||
return baos.toByteArray(); |
||||
} |
||||
|
||||
/** |
||||
* 根据指定边长创建生成的二维码 |
||||
* |
||||
* @param content 二维码内容 |
||||
* @param length 二维码的高度和宽度 |
||||
* @param logoFile logo 文件对象,可以为空 |
||||
* @return 二维码图片的字节数组 |
||||
*/ |
||||
public static byte[] createQrcode(String content, int length, File logoFile) throws Exception { |
||||
return createQrcode(content, length, logoFile, new MatrixToLogoImageConfig()); |
||||
} |
||||
|
||||
/** |
||||
* 创建生成默认高度(400)的二维码图片 |
||||
* 可以指定是否贷logo |
||||
* |
||||
* @param content 二维码内容 |
||||
* @param logoFile logo 文件对象,可以为空 |
||||
* @return 二维码图片的字节数组 |
||||
*/ |
||||
public static byte[] createQrcode(String content, File logoFile) throws Exception { |
||||
return createQrcode(content, DEFAULT_LENGTH, logoFile); |
||||
} |
||||
|
||||
public static BufferedImage generateQRCodeImage(String content, int length, InputStream logo, MatrixToLogoImageConfig logoConfig) throws Exception { |
||||
// 生成二维码图像
|
||||
BitMatrix qrCodeMatrix = createQrcodeMatrix(content, length); |
||||
BufferedImage img = MatrixToImageWriter.toBufferedImage(qrCodeMatrix); |
||||
try { |
||||
if (logo != null) { |
||||
overlapImage(img, FORMAT, logo, logoConfig); |
||||
} |
||||
} catch (Exception e) { |
||||
throw new RuntimeException("为二维码添加LOGO时失败!", e); |
||||
} |
||||
return img; |
||||
} |
||||
|
||||
public static BufferedImage generateQRCodeImage(String content, int length, File logoFile, MatrixToLogoImageConfig logoConfig) throws Exception { |
||||
if (logoFile != null && !logoFile.exists()) { |
||||
throw new IllegalArgumentException("请提供正确的logo文件!"); |
||||
} |
||||
try (InputStream logo = Files.newInputStream(logoFile.toPath())) { |
||||
return generateQRCodeImage(content, length, logo, logoConfig); |
||||
} |
||||
} |
||||
|
||||
public static BufferedImage generateQRCodeImage(String content, int length, InputStream logo) throws Exception { |
||||
return generateQRCodeImage(content, length, logo, new MatrixToLogoImageConfig()); |
||||
} |
||||
|
||||
public static BufferedImage generateQRCodeImage(String content, int length, File logoFile) throws Exception { |
||||
return generateQRCodeImage(content, length, logoFile, new MatrixToLogoImageConfig()); |
||||
} |
||||
|
||||
public static BufferedImage generateQRCodeImage(String content, InputStream logo) throws Exception { |
||||
return generateQRCodeImage(content, DEFAULT_LENGTH, logo); |
||||
} |
||||
|
||||
public static BufferedImage generateQRCodeImage(String content, File logoFile) throws Exception { |
||||
return generateQRCodeImage(content, DEFAULT_LENGTH, logoFile); |
||||
} |
||||
|
||||
/** |
||||
* 将logo添加到二维码中间 |
||||
* |
||||
* @param image 生成的二维码图片对象 |
||||
* @param logo logo文件对象 |
||||
* @param ignoredFormat 图片格式 |
||||
*/ |
||||
private static void overlapImage(final BufferedImage image, String ignoredFormat, final InputStream logo, |
||||
MatrixToLogoImageConfig logoConfig) throws IOException { |
||||
BufferedImage logoImg = ImageIO.read(logo); |
||||
logoImg = clipRound(logoImg); |
||||
Graphics2D g = logoImg.createGraphics(); |
||||
// 考虑到logo图片贴到二维码中,建议大小不要超过二维码的1/5;
|
||||
int width = image.getWidth() / logoConfig.getLogoPart(); |
||||
int height = image.getHeight() / logoConfig.getLogoPart(); |
||||
int radius = width / 10; |
||||
// logo起始位置,此目的是为logo居中显示
|
||||
int x = (image.getWidth() - width) / 2; |
||||
int y = (image.getHeight() - height) / 2; |
||||
|
||||
// 创建一个支持有透明度的图像缓冲区
|
||||
BufferedImage buffer = g.getDeviceConfiguration().createCompatibleImage(image.getWidth(), image.getHeight(), Transparency.TRANSLUCENT); |
||||
g.dispose(); |
||||
|
||||
// 绘制阴影
|
||||
g = buffer.createGraphics(); |
||||
g.setComposite(AlphaComposite.getInstance(AlphaComposite.XOR, 0.1f)); |
||||
g.setColor(Color.BLACK); |
||||
g.fillRoundRect(x + 10, y + 10, width - 20, height, radius, radius); |
||||
g.dispose(); |
||||
|
||||
// 绘制LOGO到缓冲区
|
||||
g = buffer.createGraphics(); |
||||
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1)); |
||||
g.drawImage(logoImg, x, y, width, height, null); |
||||
|
||||
// 给logo画边框
|
||||
// 构造一个具有指定线条宽度以及 cap 和 join 风格的默认值的实心 BasicStroke
|
||||
g.setStroke(new BasicStroke(logoConfig.getBorder())); |
||||
g.setColor(logoConfig.getBorderColor()); |
||||
g.drawRoundRect(x, y, width, height, radius, radius); |
||||
g.setStroke(new BasicStroke(1)); |
||||
g.setColor(Color.GRAY); |
||||
g.drawRoundRect(x + logoConfig.getBorder() / 2, y + logoConfig.getBorder() / 2, width - logoConfig.getBorder(), |
||||
height - logoConfig.getBorder(), radius, radius); |
||||
g.dispose(); |
||||
|
||||
// 将带阴影的图像绘制到二维码上
|
||||
g = image.createGraphics(); |
||||
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1.0f)); |
||||
g.drawImage(buffer, 0, 0, image.getWidth(), image.getHeight(), null); |
||||
g.dispose(); |
||||
} |
||||
|
||||
/** |
||||
* 为LOGO剪出圆角 |
||||
* |
||||
* @param srcImage LOGO图像 |
||||
*/ |
||||
private static BufferedImage clipRound(BufferedImage srcImage) { |
||||
int width = srcImage.getWidth(); |
||||
int height = srcImage.getHeight(); |
||||
int radius = width / 10; |
||||
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); |
||||
Graphics2D g = image.createGraphics(); |
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
||||
g.setClip(new RoundRectangle2D.Double(0, 0, width, height, radius, radius)); |
||||
g.drawImage(srcImage, 0, 0, null); |
||||
g.dispose(); |
||||
return image; |
||||
} |
||||
|
||||
/** |
||||
* 解析二维码 |
||||
* |
||||
* @param file 二维码文件内容 |
||||
* @return 二维码的内容 |
||||
*/ |
||||
public static String decodeQrcode(File file) throws IOException, NotFoundException { |
||||
BufferedImage image = ImageIO.read(file); |
||||
LuminanceSource source = new BufferedImageLuminanceSource(image); |
||||
Binarizer binarizer = new HybridBinarizer(source); |
||||
BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer); |
||||
Map<DecodeHintType, Object> hints = Maps.newEnumMap(DecodeHintType.class); |
||||
hints.put(DecodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name()); |
||||
return new MultiFormatReader().decode(binaryBitmap, hints).getText(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue