I can successfully create QR Code PNG images with ZXing but there is no easy way to get the output as SVG or EPS.
How can I create a vector image from the BitMatrix object that is created by the QRCodeWriter?
I can successfully create QR Code PNG images with ZXing but there is no easy way to get the output as SVG or EPS.
How can I create a vector image from the BitMatrix object that is created by the QRCodeWriter?
The easiest way I found was to create a PDF with iText and then convert the resulting PDF to EPS or SVG. Here is the code to create the 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();
}
I then use image magic for the conversion. Like so:
convert qr-code.pdf qr-code.eps
the same can NOT be done for svg
convert qr-code.pdf qr-code.svg
this does not work
I tested this code with some long content and it worked with up to 600 characters. This is probably down to the precision of either camera on the phone or screen.
I hope this helps someone
Old question I know, but for anyone that comes along looking for how to do this...it's pretty easy to connect ZXing
to JFreeSVG
(http://www.jfree.org/jfreesvg), for example:
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());
}
}
You can even do it only with zxing withou additional libraries:
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");
Please note that the solution above is much more efficient in terms of memory consumption than the one using Batik's DOM.
style
element is redundant here (fill is black by default for path: see developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill#path). It's unclear why two string builders are used here: it's simpler just to add first part of the XML to a single builder, then loop through array adding pixels, then add the rest of XML. The space before M
is completely optional, and only helps making the end result readable. –
Callisto Svg format is meant to be set of rectangles which constructs BitMatrix. The idea behind the format is the resolution of the image not to be affected by any increase in the size. For instance, @siom's solution creates 1x1 rectangles for all pixels(That's why it creates a huge file). I classify that solution as brute force approach to this problem.
I developed a better solution running in O(n^2). It scans whole bitMatrix and first detects the maximum length square in each point, then it tries to extend the square to rectange in each dimension. Finally it draws all possible rectangles and squares in various sizes.
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);
}
}
}
The easiest way I found was to create a PDF with iText and then convert the resulting PDF to EPS or SVG. Here is the code to create the 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();
}
I then use image magic for the conversion. Like so:
convert qr-code.pdf qr-code.eps
the same can NOT be done for svg
convert qr-code.pdf qr-code.svg
this does not work
I tested this code with some long content and it worked with up to 600 characters. This is probably down to the precision of either camera on the phone or screen.
I hope this helps someone
You can use Apache Batik
instead of JFreeSVG
if you want a more liberate license. That makes especially sense when you use Apache FOP
that includes Apache Batik
as a transient dependency.
Here a tweaked version. Credits for the idea and the original code goes to 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);
}
}
}
© 2022 - 2024 — McMap. All rights reserved.