在矢量图像中创建QR码

时间:2012-05-28 18:38:03

标签: qr-code zxing vector-graphics

我可以使用ZXing成功创建QR码PNG图像,但没有简单的方法可以将输出作为SVG或EPS。

如何从QRCodeWriter创建的BitMatrix对象创建矢量图像?

5 个答案:

答案 0 :(得分:10)

我知道这个老问题,但对于那些想要如何做到这一点的人来说......将ZXing连接到JFreeSVGhttp://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());
    }

}

答案 1 :(得分:3)

您甚至只能使用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");

请注意,上述解决方案在内存消耗方面比使用蜡染DOM的解决方案更为有效。

答案 2 :(得分:1)

我找到的最简单方法是使用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个字符。这可能取决于手机或屏幕上任一相机的精度。

我希望这有助于某人

答案 3 :(得分:0)

如果要获得更自由的许可证,可以使用Apache Batik代替JFreeSVG。当您使用包含Apache FOP作为暂时依赖项的Apache Batik时,这尤其有意义。

这里是经过调整的版本。这个想法和原始代码归功于David Gilbert。

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);
        }
    }
}

答案 4 :(得分:0)

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);
    }
  }
}