Skip to content

Commit

Permalink
Promote renderResult method to public
Browse files Browse the repository at this point in the history
    This small commit makes public the "renderResult" method of the
"QRCodeWriter" class.

    The reason to promote this method as public is that I'm facing
Business Case where, besides of generating the QR code as a PNG image,
I also need to print information about the QR code itself (specifically,
the QR version). Navigating through the code of zxing library, I noticed
that the com.google.zxing.qrcode.encoder.Encoder allows me to have that
(the QR metadata); however, by invoking that class I only have part of
the equation (the QRCode DTO) ... I'm still needing to generate the
BitMatrix in order to produce the QR image.

    Here, I have two alternatives: either I invoke the Encoder.encode
class by myself and repeat the same logic when invoking the
QRCodeWriter.encode method; or I make the renderResult Public, call the
Encoder.encode and then I proceed to call the QRCodeWriter.renderResult
method with the previous result.
  • Loading branch information
cdcarloschacon committed May 1, 2024
1 parent 2326dd4 commit 27e3059
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 37 deletions.
91 changes: 54 additions & 37 deletions core/src/main/java/com/google/zxing/qrcode/QRCodeWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,60 @@ public final class QRCodeWriter implements Writer {

private static final int QUIET_ZONE_SIZE = 4;

/**
* Renders the given {@link QRCode} as a {@link BitMatrix}, scaling the
* same to be compliant with the provided dimensions.
*
* <p>If no scaling is required, both {@code width} and {@code height}
* arguments should be non-positive numbers.
*
* @param code {@code QRCode} to be adapted as a {@code BitMatrix}
* @param width desired width for the {@code QRCode} (in pixel units)
* @param height desired height for the {@code QRCode} (in pixel units)
* @param quietZone the size of the QR quiet zone (in pixel units)
* @return {@code BitMatrix} instance
*
* @throws IllegalStateException if {@code code} does not have
* a {@link QRCode#getMatrix() Matrix}
*
* @throws NullPointerException if {@code code} is {@code null}
*/
public static BitMatrix renderResult(QRCode code, int width, int height, int quietZone) {
// Note that the input matrix uses 0 == white, 1 == black, while the output matrix uses
// 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap).
ByteMatrix input = code.getMatrix();
if (input == null) {
throw new IllegalStateException();
}
int inputWidth = input.getWidth();
int inputHeight = input.getHeight();
int qrWidth = inputWidth + (quietZone * 2);
int qrHeight = inputHeight + (quietZone * 2);
int outputWidth = Math.max(width, qrWidth);
int outputHeight = Math.max(height, qrHeight);

int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);
// Padding includes both the quiet zone and the extra white pixels to accommodate the requested
// dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone.
// If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will
// handle all the padding from 100x100 (the actual QR) up to 200x160.
int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
int topPadding = (outputHeight - (inputHeight * multiple)) / 2;

BitMatrix output = new BitMatrix(outputWidth, outputHeight);

for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
// Write the contents of this row of the barcode
for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
if (input.get(inputX, inputY) == 1) {
output.setRegion(outputX, outputY, multiple, multiple);
}
}
}

return output;
}

@Override
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
throws WriterException {
Expand Down Expand Up @@ -78,41 +132,4 @@ public BitMatrix encode(String contents,
QRCode code = Encoder.encode(contents, errorCorrectionLevel, hints);
return renderResult(code, width, height, quietZone);
}

// Note that the input matrix uses 0 == white, 1 == black, while the output matrix uses
// 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap).
private static BitMatrix renderResult(QRCode code, int width, int height, int quietZone) {
ByteMatrix input = code.getMatrix();
if (input == null) {
throw new IllegalStateException();
}
int inputWidth = input.getWidth();
int inputHeight = input.getHeight();
int qrWidth = inputWidth + (quietZone * 2);
int qrHeight = inputHeight + (quietZone * 2);
int outputWidth = Math.max(width, qrWidth);
int outputHeight = Math.max(height, qrHeight);

int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);
// Padding includes both the quiet zone and the extra white pixels to accommodate the requested
// dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone.
// If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will
// handle all the padding from 100x100 (the actual QR) up to 200x160.
int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
int topPadding = (outputHeight - (inputHeight * multiple)) / 2;

BitMatrix output = new BitMatrix(outputWidth, outputHeight);

for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
// Write the contents of this row of the barcode
for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
if (input.get(inputX, inputY) == 1) {
output.setRegion(outputX, outputY, multiple, multiple);
}
}
}

return output;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import com.google.zxing.qrcode.encoder.QRCode;

import org.junit.Assert;
import org.junit.Test;

Expand Down Expand Up @@ -133,4 +136,55 @@ public void testRegressionTest() throws Exception {
"renderer-test-01.png");
}

@Test
public void renderResultScalesNothing() {
final int expected_size = 33; // Original Size (25) + quietZone
BitMatrix result;
ByteMatrix matrix;
QRCode code;

matrix = new ByteMatrix(25, 25); // QR Version 2! It's all white
// but it doesn't matter here

code = new QRCode();
code.setMatrix(matrix);

// Test:
result = QRCodeWriter.renderResult(code, -1, -1, 4);

assertNotNull(result);
assertEquals(result.getHeight(), expected_size);
assertEquals(result.getWidth(), expected_size);
}

@Test
public void renderResultScalesWhenRequired() {
final int expected_size = 66;
BitMatrix result;
ByteMatrix matrix;
QRCode code;

matrix = new ByteMatrix(25, 25); // QR Version 2! It's all white
// but it doesn't matter here

code = new QRCode();
code.setMatrix(matrix);

// Test:
result = QRCodeWriter.renderResult(code, 66, 66, 4);

assertNotNull(result);
assertEquals(result.getHeight(), expected_size);
assertEquals(result.getWidth(), expected_size);
}

@Test(expected = NullPointerException.class)
public void renderResultThrowsExIfCcodeIsNull() {
QRCodeWriter.renderResult(null, 0, 0, 0);
}

@Test(expected = IllegalStateException.class)
public void renderResultThrowsExIfCodeIsIncomplete() {
QRCodeWriter.renderResult(new QRCode(), 0, 0, 0);
}
}

0 comments on commit 27e3059

Please sign in to comment.