52

I'm using com.google.zxing.qrcode.QRCodeWriter to encode data and com.google.zxing.client.j2se.MatrixToImageWriter to generate the QR Code image. On a 400x400 image, there is about a 52 pixel wide border around the code. I'd like this border to be narrower, maybe 15 pixels, but I don't see anything in the API for doing that. Am I missing something in the documenation? Or would I need to process the image myself?

For reference, here is an example 400x400 QR Code produced with the ZXing library:

An example QR Code

James Sumners
  • 14,485
  • 10
  • 59
  • 77

5 Answers5

98

The QR spec requires a four module quiet zone and that's what zxing creates. (See QUIET_ZONE_SIZE in QRCodeWriter.renderResult.)

More recent versions of ZXing allow you to set the size of the quiet zone (basically the intrinsic padding of the QR code) by supplying an int value with the EncodeHintType.MARGIN key. Simply include it in the hints Map you supply to the Writer's encode(...) method, e.g.:

Map<EncodeHintType, Object> hints = new EnumMap<EncodeHintType, Object>(EncodeHintType.class);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.MARGIN, 2); /* default = 4 */

If you change this, you risk lowering the decode success rate.

Mudlabs
  • 551
  • 5
  • 16
smparkes
  • 13,807
  • 4
  • 36
  • 61
  • I suppose I'll just deal with it then. Thank you. – James Sumners Apr 13 '12 at 15:00
  • I can confirm from experience that a code with no quiet zone at all will most often not scan. – Nathan Oct 17 '12 at 12:31
  • Right. I can't speak for other decoders, but zxing uses heuristics to detect qr codes that assume there is a quiet zone. The heuristics don't assume the quiet zone is four modules wide which is what the spec says, but if it's not there or too narrow, it's likely that the zxing decode success rate will decrease, very possibly dramatically. – smparkes Oct 17 '12 at 19:30
  • could you update the dead link, have it point to a working link on "quiet zones"? – Don Cheadle Feb 23 '15 at 04:09
  • Fixed the link but as the follow up edits say, you can now set this with an option. (The docs on the option look a bit imprecise, though. The quiet zone size is in units of modules. The comments on the MARGIN option say it's in pixels, but looking at the code, it looks to be setting the quiet zone size for QR codes. I haven't looked further to validate but I suspect MARGIN is in modules for QR codes. – smparkes Feb 23 '15 at 13:31
  • @smparkes Why change the size of quite zone, lower the decode success rate, the function of the quite zone is to distinguish the real content with border ? – twlkyao Mar 14 '17 at 08:01
  • Yes, the quiet zone is so the detection algorithms can distinguish code from other features surrounding the code. ZXing assumes a certain mount of white around the code. It doesn't assume exactly four modules but the fewer there are, the more likely it will get confused, particularly when decoding noisier/more distorted images. – smparkes Mar 14 '17 at 16:04
13

Even by setting EncodeHintType.MARGIN to 0, the algorithm that convert the QRCode "dot" matrix to pixels data can generate a small margin (the algorithm enforce a constant number of pixels per dots, so the margin pixel size is the remainder of the integer division of pixels size by QR-Code dot size).

However you can completely bypass this "dot to pixel" generation: you compute the QRCode dot matrix directly by calling the public com.google.zxing.qrcode.encoder.Encoder class, and generate the pixel image yourself. Code below:

// Step 1 - generate the QRCode dot array
Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>(1);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
QRCode qrCode = Encoder.encode(what, ErrorCorrectionLevel.L, hints);

// Step 2 - create a BufferedImage out of this array
int width = qrCode.getMatrix().getWidth();
int height = qrCode.getMatrix().getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
int[] rgbArray = new int[width * height];
int i = 0;
for (int y = 0; y < height; y++) {
  for (int x = 0; x < width; x++) {
    rgbArray[i] = qrCode.getMatrix().get(x, y) > 0 ? 0xFFFFFF : 0x000000;
    i++;
} }
image.setRGB(0, 0, width, height, rgbArray, 0, width);

The conversion of the BufferedImage to PNG data is left as an exercise to the reader. You can also scale the image by setting a fixed number of pixels per dots.

It's usually more optimized that way, the generated image size is the smallest possible. If you rely on client to scale the image (w/o blur) you do not need more than 1 pixel per dot.

Laurent Grégoire
  • 4,006
  • 29
  • 52
  • 2
    Note that colors seem reversed, I had to use `if (matrix.get(x, y) == 0) { rgbArray[i] = 0xFFFFFF; } i++;` to get an output similar to the Zxing one. –  Dec 28 '16 at 14:51
  • @RC. Thanks for the hint, I did not noticed this. But are you sure? The QRCode I get with this code seems to match. – Laurent Grégoire Dec 30 '16 at 17:28
  • Another way to do this would be to retrieve the size of margin (e.g. total size modulo the number of pixel per dot) and scale the image using ImageIO transform. – Matthias Beaupère Jan 24 '18 at 14:00
6
    HashMap hintMap = new HashMap();
    hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.Q);
    hintMap.put(EncodeHintType.MARGIN, -1);

no margin

UPDATE

Add dependencies (from comments)

<dependency>
    <groupId>com.google.zxing</groupId> 
    <artifactId>core</artifactId> 
    <version>3.2.0</version>
    <type>jar</type> 
</dependency> 
<dependency> 
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId> 
    <version>3.2.0</version> 
</dependency>
Ernesto Campohermoso
  • 7,213
  • 1
  • 40
  • 51
ougalejo
  • 69
  • 1
  • 4
0

In swift you can:

let hints = ZXEncodeHints()
hints!.margin = NSNumber(int: 0)

let result = try writer.encode(code, format: format, width: Int32(size.width), height: Int32(size.height), hints: hints)
let cgImage = ZXImage(matrix: result, onColor: UIColor.blackColor().CGColor, offColor: UIColor.clearColor().CGColor).cgimage
let QRImage = UIImage(CGImage: cgImage)
Luca Davanzo
  • 21,000
  • 15
  • 120
  • 146
0

My problem is that I need to generate a PNG image with a transparent background fixed to x * x pixels.
I find that whatever I do with EncodeHintType.MARGIN, these is always some unexpected margin.

After reading its source code, I find a way to fix my problem, this is my code. There is no margin in the output BufferedImage.

BufferedImage oriQrImg = getQrImg(CONTENT_PREFIX+userInfo, ErrorCorrectionLevel.L,BLACK);
BufferedImage scaledImg = getScaledImg(oriQrImg,REQUIRED_QR_WIDTH,REQUIRED_QR_HEIGHT);
private static BufferedImage getQrImg(String content, ErrorCorrectionLevel level, int qrColor) throws WriterException {
    QRCode qrCode = Encoder.encode(content, level, QR_HINTS);
    ByteMatrix input = qrCode.getMatrix();

    int w=input.getWidth(),h=input.getHeight();

    BufferedImage qrImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2d = qrImg.createGraphics();
    qrImg = g2d.getDeviceConfiguration().createCompatibleImage(w,h, Transparency.BITMASK);
    g2d.dispose();

    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            if (input.get(x,y) == 1) {
                qrImg.setRGB(x, y, qrColor);
            }else{
                qrImg.setRGB(x, y, Transparency.BITMASK);
            }
        }
    }
    return qrImg;
}
static BufferedImage getScaledImg(BufferedImage oriImg,int aimWidth,int aimHeight){
    Image scaled = oriImg.getScaledInstance(aimWidth,aimHeight,SCALE_DEFAULT);
    Graphics2D g2d = new BufferedImage(aimWidth,aimHeight, BufferedImage.TYPE_INT_RGB).createGraphics();
    BufferedImage scaledImg = g2d.getDeviceConfiguration().createCompatibleImage(aimWidth,aimHeight, Transparency.BITMASK);
    g2d.dispose();

    scaledImg.createGraphics().drawImage(scaled, 0, 0,null);
    return scaledImg;
}
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
  • Code looks good but can you tell us what change is affecting the margin in output? More explanation would make this a better answer. – navicore Dec 28 '22 at 03:44