我可以使用 ZXing 成功创建 QR 码 PNG 图像,但没有简单的方法可以将输出作为 SVG 或 EPS。
如何从 QRCodeWriter 创建的 BitMatrix 对象创建矢量图像?
我可以使用 ZXing 成功创建 QR 码 PNG 图像,但没有简单的方法可以将输出作为 SVG 或 EPS。
如何从 QRCodeWriter 创建的 BitMatrix 对象创建矢量图像?
我知道的老问题,但对于任何来寻找如何做到这一点的人......连接ZXing
到JFreeSVG
(http://www.jfree.org/jfreesvg)非常容易,例如:
package org.jfree.demo;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import org.jfree.graphics2d.svg.SVGGraphics2D;
import org.jfree.graphics2d.svg.SVGUtils;
public class QRCodes {
public static void main(String[] args) throws WriterException, IOException {
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode("http://www.jfree.org/jfreesvg",
BarcodeFormat.QR_CODE, 160, 160);
int w = bitMatrix.getWidth();
SVGGraphics2D g2 = new SVGGraphics2D(w, w);
g2.setColor(Color.BLACK);
for (int xIndex = 0; xIndex < w; xIndex = xIndex + bitMatrix.getRowSize()) {
for (int yIndex = 0; yIndex < w; yIndex = yIndex + bitMatrix.getRowSize()) {
if (bitMatrix.get(xIndex, yIndex)) {
g2.fillRect(xIndex, yIndex, bitMatrix.getRowSize(), bitMatrix.getRowSize());
}
}
}
SVGUtils.writeToSVG(new File("qrtest.svg"), g2.getSVGElement());
}
}
你甚至可以只使用 zxing 来完成,而无需额外的库:
QRCodeWriter qrCodeWriter = new QRCodeWriter();
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());
BitMatrix bitMatrix = qrCodeWriter.encode(payload, BarcodeFormat.QR_CODE, 543, 543, hints);
StringBuilder sbPath = new StringBuilder();
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
int rowSize = bitMatrix.getRowSize();
BitArray row = new BitArray(width);
for(int y = 0; y < height; ++y) {
row = bitMatrix.getRow(y, row);
for(int x = 0; x < width; ++x) {
if (row.get(x)) {
sbPath.append(" M"+x+","+y+"h1v1h-1z");
}
}
}
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 ").append(width).append(" ").append(height).append("\" stroke=\"none\">\n");
sb.append("<style type=\"text/css\">\n");
sb.append(".black {fill:#000000;}\n");
sb.append("</style>\n");
sb.append("<path class=\"black\" d=\"").append(sbPath.toString()).append("\"/>\n");
sb.append("</svg>\n");
请注意,上述解决方案在内存消耗方面比使用 Batik DOM 的解决方案要高效得多。
我发现最简单的方法是使用 iText 创建 PDF,然后将生成的 PDF 转换为 EPS 或 SVG。以下是创建 PDF 的代码:
@Test
public void testQRtoPDF() throws WriterException, FileNotFoundException, DocumentException, UnsupportedEncodingException {
final int s = 600;
int r = 1;
Charset charset = Charset.forName( "UTF-8" );
CharsetEncoder encoder = charset.newEncoder();
byte[] b = null;
try {
// Convert a string to UTF-8 bytes in a ByteBuffer
ByteBuffer bbuf = encoder.encode( CharBuffer.wrap(
"1éöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùò1" +
"2éöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùò2" +
"3éöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùò3" +
"4éöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùò4" +
"5éöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùò5" +
"6éöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùòïëéöàäèüùò6" ) );
b = bbuf.array();
} catch ( CharacterCodingException e ) {
System.out.println( e.getMessage() );
}
String content = new String( b, "UTF-8" );
QRCodeWriter qrCodeWriter = new QRCodeWriter();
Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>( 2 );
hints.put( EncodeHintType.CHARACTER_SET, "UTF-8" );
BitMatrix qrCode = qrCodeWriter.encode( content, BarcodeFormat.QR_CODE, s, s, hints );
Document doc = new Document( new Rectangle( s, s ) );
PdfWriter pdfWriter = PdfWriter.getInstance( doc, new FileOutputStream( "qr-code.pdf" ) );
doc.open();
PdfContentByte contentByte = pdfWriter.getDirectContent();
contentByte.setColorFill( BaseColor.BLACK );
boolean d = false;
for ( int x = 0; x < qrCode.getWidth(); x += r ) {
for ( int y = 0; y < qrCode.getHeight(); y += r ) {
if ( qrCode.get( x, y ) ) {
contentByte.rectangle( x, s - y, r, r );
contentByte.fill();
contentByte.stroke();
}
}
}
doc.close();
}
然后我使用图像魔法进行转换。像这样:
convert qr-code.pdf qr-code.eps
不能对 svg 做同样的事情
convert qr-code.pdf qr-code.svg
这不起作用
我用一些长内容测试了这段代码,它最多可以处理 600 个字符。这可能取决于手机或屏幕上摄像头的精度。
我希望这可以帮助别人
如果您想要一个更自由的许可证,您可以使用Apache Batik
而不是。当您使用包含作为瞬态依赖JFreeSVG
项时,这尤其有意义。Apache FOP
Apache Batik
这是一个调整后的版本。这个想法和原始代码的功劳归大卫吉尔伯特所有。
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.svg.SVGDocument;
import java.awt.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class QRCodesBatik {
public static void main(String[] args) throws WriterException, IOException {
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode("https://xmlgraphics.apache.org/batik/",
BarcodeFormat.QR_CODE, 800, 800);
// Create a document with the appropriate namespace
DOMImplementation domImpl = SVGDOMImplementation.getDOMImplementation();
SVGDocument document = (SVGDocument) domImpl.createDocument(SVGDOMImplementation.SVG_NAMESPACE_URI, "svg", null);
// Create an instance of the SVG Generator
org.apache.batik.svggen.SVGGraphics2D g2 = new org.apache.batik.svggen.SVGGraphics2D(document);
// draw onto the SVG Graphics object
g2.setColor(Color.BLACK);
for (int xIndex = 0; xIndex < bitMatrix.getWidth(); xIndex = xIndex + bitMatrix.getRowSize()) {
for (int yIndex = 0; yIndex < bitMatrix.getWidth(); yIndex = yIndex + bitMatrix.getRowSize()) {
if (bitMatrix.get(xIndex, yIndex)) {
g2.fillRect(xIndex, yIndex, bitMatrix.getRowSize(), bitMatrix.getRowSize());
}
}
}
try (Writer out = new OutputStreamWriter(new FileOutputStream(new File("qrtest.svg")), "UTF-8")) {
g2.stream(out, true);
}
}
}
Svg 格式是一组构成 BitMatrix 的矩形。格式背后的想法是图像的分辨率不受尺寸增加的影响。例如,@siom 的解决方案为所有像素创建 1x1 矩形(这就是它创建一个巨大文件的原因)。我将该解决方案归类为解决此问题的蛮力方法。
我开发了一个在 O(n^2) 中运行的更好的解决方案。它扫描整个 bitMatrix 并首先检测每个点的最大长度正方形,然后尝试将正方形扩展到每个维度的矩形。最后,它绘制了所有可能的各种尺寸的矩形和正方形。
import com.google.zxing.common.BitMatrix;
import java.awt.Color;
import java.awt.Point;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import org.jfree.graphics2d.svg.SVGGraphics2D;
public class SvgUtils {
public static byte[] createSvgImage(BitMatrix bitMatrix){
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
Set<Point> visitedPoints = new HashSet<>();
SVGGraphics2D g2 = new SVGGraphics2D(width, height);
g2.setColor(Color.BLACK);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Point maxRectangleLength = getMaxRectangleLength(new Point(x, y), bitMatrix, visitedPoints);
if(maxRectangleLength != null) {
g2.fillRect(x, y, maxRectangleLength.x, maxRectangleLength.y);
y += maxRectangleLength.y-1;
}
}
}
return g2.getSVGDocument().getBytes(StandardCharsets.UTF_8);
}
private static int getMaxSquareLength(Point startPoint, BitMatrix bitMatrix, Set<Point> visitedPoints){
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
int maxLength = 0;
while(startPoint.x + maxLength < width && startPoint.y + maxLength < height) {
for (int xOffSett = 0; xOffSett < maxLength; xOffSett++) {
if (!bitMatrix.get(startPoint.x + xOffSett, startPoint.y + maxLength) || visitedPoints.contains(new Point(startPoint.x + xOffSett, startPoint.y + maxLength))) {
return maxLength;
}
}
for (int yOffset = 0; yOffset <= maxLength; yOffset++) {
if (!bitMatrix.get(startPoint.x + maxLength, startPoint.y + yOffset) || visitedPoints.contains(new Point(startPoint.x + maxLength, startPoint.y + yOffset))) {
return maxLength;
}
}
for (int xOffSett = 0; xOffSett < maxLength; xOffSett++) {
visitedPoints.add(new Point(startPoint.x + xOffSett, startPoint.y + maxLength));
}
for (int yOffset = 0; yOffset <= maxLength; yOffset++) {
visitedPoints.add(new Point(startPoint.x + maxLength, startPoint.y + yOffset));
}
maxLength++;
}
return maxLength;
}
private static Point getMaxRectangleLength(Point startPoint, BitMatrix bitMatrix, Set<Point> visitedPoints){
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
int maxSquareLength = getMaxSquareLength(startPoint, bitMatrix, visitedPoints);
if(maxSquareLength == 0)
return null;
int maxWidth = maxSquareLength-1;
int maxHeight = maxSquareLength-1;
boolean searchFinished = false;
while(!searchFinished && startPoint.y + ++maxHeight < height) {
for (int xOffSett = 0; xOffSett < maxSquareLength; xOffSett++) {
if (!bitMatrix.get(startPoint.x + xOffSett, startPoint.y + maxHeight) ||
visitedPoints.contains(new Point(startPoint.x + xOffSett, startPoint.y + maxHeight))) {
searchFinished = true;
break;
}
}
}
searchFinished = false;
while(!searchFinished && startPoint.x + ++maxWidth < width) {
for (int yOffSett = 0; yOffSett < maxSquareLength; yOffSett++) {
if (!bitMatrix.get(startPoint.x + maxWidth, startPoint.y + yOffSett) ||
visitedPoints.contains(new Point(startPoint.x + maxWidth, startPoint.y + yOffSett))) {
searchFinished = true;
break;
}
}
}
if(maxHeight >= maxWidth){
for(int yOffSet = maxSquareLength; yOffSet < maxHeight; yOffSet++){
for (int xOffSett = 0; xOffSett < maxSquareLength; xOffSett++) {
visitedPoints.add(new Point(startPoint.x + xOffSett, startPoint.y + yOffSet));
}
}
return new Point(maxSquareLength, maxHeight);
} else {
for(int xOffSett = maxSquareLength; xOffSett < maxWidth; xOffSett++){
for (int yOffSet = 0; yOffSet < maxSquareLength; yOffSet++) {
visitedPoints.add(new Point(startPoint.x + xOffSett, startPoint.y + yOffSet));
}
}
return new Point(maxWidth, maxSquareLength);
}
}
}