Created the base Writer object for all barcode encoding, then wrote a QR Code version. A Writer encodes a series of raw bytes into a 2D greyscale bitmap, represented by a ByteArray. This seems like a better container than MonochromeBitmapSource, because we may want shades of grey when encoding future formats. The next step is to hook this up to the Android client.

git-svn-id: https://zxing.googlecode.com/svn/trunk@716 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
dswitkin 2008-11-17 22:10:45 +00:00
parent 8d79ad7085
commit 8175e2cc25
10 changed files with 274 additions and 222 deletions

View file

@ -41,10 +41,7 @@
optimize="true" optimize="true"
debug="${generate-debug}" debug="${generate-debug}"
deprecation="true" deprecation="true"
fork="true"> fork="true"/>
<!-- TODO re-add this package -->
<exclude name="com/google/zxing/qrcode/encoder/**"/>
</javac>
<jar jarfile="core.jar" basedir="build"> <jar jarfile="core.jar" basedir="build">
<!-- These entries allow core.jar to function as an OSGi bundle, and also specifices <!-- These entries allow core.jar to function as an OSGi bundle, and also specifices
additional attributes for compatibility with BugLabs's BUG platform. additional attributes for compatibility with BugLabs's BUG platform.

View file

@ -0,0 +1,42 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import com.google.zxing.common.ByteMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import java.util.Hashtable;
public final class MultiFormatWriter implements Writer {
public ByteMatrix encode(byte[] contents, BarcodeFormat format, int width,
int height) throws Exception {
return encode(contents, format, width, height, null);
}
public ByteMatrix encode(byte[] contents, BarcodeFormat format, int width, int height,
Hashtable hints) throws Exception {
if (format == BarcodeFormat.QR_CODE) {
return new QRCodeWriter().encode(contents, format, width, height, hints);
} else {
throw new IllegalArgumentException("No encoder available for format " + format);
}
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing;
import com.google.zxing.common.ByteMatrix;
import java.util.Hashtable;
public interface Writer {
/**
* Encode a barcode using the default settings.
*
* @param contents The raw contents to encode in the barcode
* @param format The barcode format to generate
* @param width The preferred width in pixels
* @param height The preferred height in pixels
* @return The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white)
*/
ByteMatrix encode(byte[] contents, BarcodeFormat format, int width, int height) throws Exception;
/**
*
* @param contents The raw contents to encode in the barcode
* @param format The barcode format to generate
* @param width The preferred width in pixels
* @param height The preferred height in pixels
* @param hints Additional parameters to supply to the encoder
* @return The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white)
*/
ByteMatrix encode(byte[] contents, BarcodeFormat format, int width, int height, Hashtable hints)
throws Exception;
}

View file

@ -14,10 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.zxing.qrcode.encoder; package com.google.zxing.common;
/** /**
* A class which wraps a 2D array. * A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a
* unsigned container, it's up to you to do byteValue & 0xff at each location.
* *
* JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have * JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have
* the horizontal component first, but this is for compatibility with the C++ code. The original * the horizontal component first, but this is for compatibility with the C++ code. The original
@ -26,13 +27,13 @@ package com.google.zxing.qrcode.encoder;
* *
* @author dswitkin@google.com (Daniel Switkin) * @author dswitkin@google.com (Daniel Switkin)
*/ */
public final class Matrix { public final class ByteMatrix {
private final byte[][] bytes; private final byte[][] bytes;
private final int height; private final int height;
private final int width; private final int width;
public Matrix(int height, int width) { public ByteMatrix(int height, int width) {
bytes = new byte[height][width]; bytes = new byte[height][width];
this.height = height; this.height = height;
this.width = width; this.width = width;
@ -50,6 +51,10 @@ public final class Matrix {
return bytes[y][x]; return bytes[y][x];
} }
public final byte[][] getArray() {
return bytes;
}
public final void set(int y, int x, byte value) { public final void set(int y, int x, byte value) {
bytes[y][x] = value; bytes[y][x] = value;
} }

View file

@ -0,0 +1,133 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.qrcode;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Writer;
import com.google.zxing.common.ByteMatrix;
import com.google.zxing.qrcode.encoder.ByteArray;
import com.google.zxing.qrcode.encoder.Encoder;
import com.google.zxing.qrcode.encoder.QRCode;
import java.util.Hashtable;
public final class QRCodeWriter implements Writer {
public ByteMatrix encode(byte[] contents, BarcodeFormat format, int width,
int height) throws Exception {
return encode(contents, format, width, height, null);
}
public ByteMatrix encode(byte[] contents, BarcodeFormat format, int width, int height,
Hashtable hints) throws Exception {
if (contents == null || contents.length == 0) {
throw new IllegalArgumentException("Found empty contents");
}
if (format != BarcodeFormat.QR_CODE) {
throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format);
}
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Requested dimensions are too small: " + width + "x" +
height);
}
// TODO: Check hints for error correction level instead of hardcoding
int errorCorrectionLevel = QRCode.EC_LEVEL_L;
QRCode code = new QRCode();
if (Encoder.Encode(new ByteArray(contents), errorCorrectionLevel, code)) {
return renderResult(code, width, height);
} else {
throw new IllegalStateException("Could not generate a QR Code");
}
}
// 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 ByteMatrix renderResult(QRCode code, final int width, final int height) {
ByteMatrix input = code.matrix();
int inputWidth = input.width();
int inputHeight = input.height();
int outputWidth = Math.max(width, inputWidth);
int outputHeight = Math.max(height, inputHeight);
int multiple = Math.min(outputWidth / inputWidth, outputHeight / inputHeight);
int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
int topPadding = (outputHeight - (inputHeight * multiple)) / 2;
ByteMatrix output = new ByteMatrix(outputHeight, outputWidth);
byte[][] outputArray = output.getArray();
// We could be tricky and use the first row in each set of multiple as the temporary storage,
// instead of allocating this separate array.
byte[] row = new byte[outputWidth];
// 1. Write the white lines at the top
for (int y = 0; y < topPadding; y++) {
setRowColor(outputArray[y], (byte) 255);
}
// 2. Expand the QR image to the multiple
final byte[][] inputArray = input.getArray();
for (int y = 0; y < inputHeight; y++) {
// a. Write the white pixels at the left of each row
for (int x = 0; x < leftPadding; x++) {
row[x] = (byte) 255;
}
// b. Write the contents of this row of the barcode
int offset = leftPadding;
for (int x = 0; x < inputWidth; x++) {
byte value = (inputArray[y][x] == 1) ? 0 : (byte) 255;
for (int z = 0; z < multiple; z++) {
row[offset + z] = value;
}
offset += multiple;
}
// c. Write the white pixels at the right of each row
offset = leftPadding + (inputWidth * multiple);
for (int x = offset; x < outputWidth; x++) {
row[x] = (byte) 255;
}
// d. Write the completed row multiple times
offset = topPadding + (y * multiple);
for (int z = 0; z < multiple; z++) {
System.arraycopy(row, 0, outputArray[offset + z], 0, outputWidth);
}
}
// 3. Write the white lines at the bottom
int offset = topPadding + (inputHeight * multiple);
for (int y = offset; y < outputHeight; y++) {
setRowColor(outputArray[y], (byte) 255);
}
return output;
}
private static void setRowColor(byte[] row, byte value) {
for (int x = 0; x < row.length; x++) {
row[x] = value;
}
}
}

View file

@ -18,6 +18,7 @@ package com.google.zxing.qrcode.encoder;
import com.google.zxing.common.reedsolomon.ReedSolomonEncoder; import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
import com.google.zxing.common.reedsolomon.GF256; import com.google.zxing.common.reedsolomon.GF256;
import com.google.zxing.common.ByteMatrix;
import java.util.Vector; import java.util.Vector;
@ -341,7 +342,7 @@ private static final ECPolyInfo kECPolynomials[] = {
qr_code.num_rs_blocks(), final_bits); qr_code.num_rs_blocks(), final_bits);
// Step 7: Choose the mask pattern and set to "qr_code". // Step 7: Choose the mask pattern and set to "qr_code".
Matrix matrix = new Matrix(qr_code.matrix_width(), qr_code.matrix_width()); ByteMatrix matrix = new ByteMatrix(qr_code.matrix_width(), qr_code.matrix_width());
qr_code.set_mask_pattern(ChooseMaskPattern(final_bits, qr_code.ec_level(), qr_code.version(), qr_code.set_mask_pattern(ChooseMaskPattern(final_bits, qr_code.ec_level(), qr_code.version(),
matrix)); matrix));
if (qr_code.mask_pattern() == -1) { if (qr_code.mask_pattern() == -1) {
@ -405,7 +406,7 @@ private static final ECPolyInfo kECPolynomials[] = {
} }
private static int ChooseMaskPattern(final BitVector bits, int ec_level, int version, private static int ChooseMaskPattern(final BitVector bits, int ec_level, int version,
Matrix matrix) { ByteMatrix matrix) {
if (!QRCode.IsValidMatrixWidth(matrix.width())) { if (!QRCode.IsValidMatrixWidth(matrix.width())) {
Debug.LOG_ERROR("Invalid matrix width: " + matrix.width()); Debug.LOG_ERROR("Invalid matrix width: " + matrix.width());
return -1; return -1;

View file

@ -16,6 +16,8 @@
package com.google.zxing.qrcode.encoder; package com.google.zxing.qrcode.encoder;
import com.google.zxing.common.ByteMatrix;
/** /**
* @author satorux@google.com (Satoru Takabayashi) - creator * @author satorux@google.com (Satoru Takabayashi) - creator
* @author dswitkin@google.com (Daniel Switkin) - ported from C++ * @author dswitkin@google.com (Daniel Switkin) - ported from C++
@ -24,7 +26,7 @@ public final class MaskUtil {
// The mask penalty calculation is complicated. See Table 21 of JISX0510:2004 (p.45) for details. // The mask penalty calculation is complicated. See Table 21 of JISX0510:2004 (p.45) for details.
// Basically it applies four rules and summate all penalties. // Basically it applies four rules and summate all penalties.
public static int CalculateMaskPenalty(final Matrix matrix) { public static int CalculateMaskPenalty(final ByteMatrix matrix) {
int penalty = 0; int penalty = 0;
penalty += ApplyMaskPenaltyRule1(matrix); penalty += ApplyMaskPenaltyRule1(matrix);
penalty += ApplyMaskPenaltyRule2(matrix); penalty += ApplyMaskPenaltyRule2(matrix);
@ -35,7 +37,7 @@ public final class MaskUtil {
// Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and // Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
// give penalty to them. Example: 00000 or 11111. // give penalty to them. Example: 00000 or 11111.
public static int ApplyMaskPenaltyRule1(final Matrix matrix) { public static int ApplyMaskPenaltyRule1(final ByteMatrix matrix) {
final int penalty = (ApplyMaskPenaltyRule1Internal(matrix, true) + final int penalty = (ApplyMaskPenaltyRule1Internal(matrix, true) +
ApplyMaskPenaltyRule1Internal(matrix, false)); ApplyMaskPenaltyRule1Internal(matrix, false));
Debug.LOG_INFO("\tApplyMaskPenaltyRule1: " + penalty); Debug.LOG_INFO("\tApplyMaskPenaltyRule1: " + penalty);
@ -45,8 +47,8 @@ public final class MaskUtil {
// Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give // Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
// penalty to them. // penalty to them.
// //
// JAVAPORT: Consider using Matrix.getArray() instead. // JAVAPORT: Consider using ByteMatrix.getArray() instead.
public static int ApplyMaskPenaltyRule2(final Matrix matrix) { public static int ApplyMaskPenaltyRule2(final ByteMatrix matrix) {
int penalty = 0; int penalty = 0;
for (int y = 0; y < matrix.height() - 1; ++y) { for (int y = 0; y < matrix.height() - 1; ++y) {
for (int x = 0; x < matrix.width() - 1; ++x) { for (int x = 0; x < matrix.width() - 1; ++x) {
@ -66,9 +68,9 @@ public final class MaskUtil {
// 10111010000, and give penalty to them. If we find patterns like 000010111010000, we give // 10111010000, and give penalty to them. If we find patterns like 000010111010000, we give
// penalties twice (i.e. 40 * 2). // penalties twice (i.e. 40 * 2).
// //
// JAVAPORT: This many calls to Matrix.get() looks expensive. We should profile and consider // JAVAPORT: This many calls to ByteMatrix.get() looks expensive. We should profile and consider
// adding a byte[][] Matrix.getArray() method, then using that array locally. // adding a byte[][] ByteMatrix.getArray() method, then using that array locally.
public static int ApplyMaskPenaltyRule3(final Matrix matrix) { public static int ApplyMaskPenaltyRule3(final ByteMatrix matrix) {
int penalty = 0; int penalty = 0;
for (int y = 0; y < matrix.height(); ++y) { for (int y = 0; y < matrix.height(); ++y) {
for (int x = 0; x < matrix.width(); ++x) { for (int x = 0; x < matrix.width(); ++x) {
@ -128,7 +130,7 @@ public final class MaskUtil {
// - 55% => 10 // - 55% => 10
// - 55% => 20 // - 55% => 20
// - 100% => 100 // - 100% => 100
public static int ApplyMaskPenaltyRule4(final Matrix matrix) { public static int ApplyMaskPenaltyRule4(final ByteMatrix matrix) {
int num_dark_cells = 0; int num_dark_cells = 0;
for (int y = 0; y < matrix.height(); ++y) { for (int y = 0; y < matrix.height(); ++y) {
for (int x = 0; x < matrix.width(); ++x) { for (int x = 0; x < matrix.width(); ++x) {
@ -174,7 +176,7 @@ public final class MaskUtil {
// Helper function for ApplyMaskPenaltyRule1. We need this for doing this calculation in both // Helper function for ApplyMaskPenaltyRule1. We need this for doing this calculation in both
// vertical and horizontal orders respectively. // vertical and horizontal orders respectively.
private static int ApplyMaskPenaltyRule1Internal(final Matrix matrix, boolean is_horizontal) { private static int ApplyMaskPenaltyRule1Internal(final ByteMatrix matrix, boolean is_horizontal) {
int penalty = 0; int penalty = 0;
int num_same_bit_cells = 0; int num_same_bit_cells = 0;
int prev_bit = -1; int prev_bit = -1;

View file

@ -16,6 +16,8 @@
package com.google.zxing.qrcode.encoder; package com.google.zxing.qrcode.encoder;
import com.google.zxing.common.ByteMatrix;
/** /**
* @author satorux@google.com (Satoru Takabayashi) - creator * @author satorux@google.com (Satoru Takabayashi) - creator
* @author dswitkin@google.com (Daniel Switkin) - ported from C++ * @author dswitkin@google.com (Daniel Switkin) - ported from C++
@ -121,15 +123,15 @@ public final class MatrixUtil {
// Set all cells to -1. -1 means that the cell is empty (not set yet). // Set all cells to -1. -1 means that the cell is empty (not set yet).
// //
// JAVAPORT: We shouldn't need to do this at all. The code should be rewritten to begin encoding // JAVAPORT: We shouldn't need to do this at all. The code should be rewritten to begin encoding
// with the Matrix initialized all to zero. // with the ByteMatrix initialized all to zero.
public static void ClearMatrix(Matrix matrix) { public static void ClearMatrix(ByteMatrix matrix) {
matrix.clear((byte) -1); matrix.clear((byte) -1);
} }
// Build 2D matrix of QR Code from "data_bits" with "ec_level", "version" and "mask_pattern". On // Build 2D matrix of QR Code from "data_bits" with "ec_level", "version" and "mask_pattern". On
// success, store the result in "matrix" and return true. On error, return false. // success, store the result in "matrix" and return true. On error, return false.
public static boolean BuildMatrix(final BitVector data_bits, int ec_level, int version, public static boolean BuildMatrix(final BitVector data_bits, int ec_level, int version,
int mask_pattern, Matrix matrix) { int mask_pattern, ByteMatrix matrix) {
MatrixUtil.ClearMatrix(matrix); MatrixUtil.ClearMatrix(matrix);
if (!EmbedBasicPatterns(version, matrix)) { if (!EmbedBasicPatterns(version, matrix)) {
return false; return false;
@ -152,7 +154,7 @@ public final class MatrixUtil {
// - Timing patterns // - Timing patterns
// - Dark dot at the left bottom corner // - Dark dot at the left bottom corner
// - Position adjustment patterns, if need be // - Position adjustment patterns, if need be
public static boolean EmbedBasicPatterns(int version, Matrix matrix) { public static boolean EmbedBasicPatterns(int version, ByteMatrix matrix) {
// Let's get started with embedding big squares at corners. // Let's get started with embedding big squares at corners.
EmbedPositionDetectionPatternsAndSeparators(matrix); EmbedPositionDetectionPatternsAndSeparators(matrix);
// Then, embed the dark dot at the left bottom corner. // Then, embed the dark dot at the left bottom corner.
@ -166,7 +168,7 @@ public final class MatrixUtil {
} }
// Embed type information. On success, modify the matrix and return true. On error, return false. // Embed type information. On success, modify the matrix and return true. On error, return false.
public static boolean EmbedTypeInfo(int ec_level, int mask_pattern, Matrix matrix) { public static boolean EmbedTypeInfo(int ec_level, int mask_pattern, ByteMatrix matrix) {
BitVector type_info_bits = new BitVector(); BitVector type_info_bits = new BitVector();
if (!MakeTypeInfoBits(ec_level, mask_pattern, type_info_bits)) { if (!MakeTypeInfoBits(ec_level, mask_pattern, type_info_bits)) {
return false; return false;
@ -201,7 +203,7 @@ public final class MatrixUtil {
// Embed version information if need be. On success, modify the matrix and return true. On error, // Embed version information if need be. On success, modify the matrix and return true. On error,
// return false. See 8.10 of JISX0510:2004 (p.47) for how to embed version information. Return // return false. See 8.10 of JISX0510:2004 (p.47) for how to embed version information. Return
// true on success, otherwise return false. // true on success, otherwise return false.
public static boolean MaybeEmbedVersionInfo(int version, Matrix matrix) { public static boolean MaybeEmbedVersionInfo(int version, ByteMatrix matrix) {
if (version < 7) { // Version info is necessary if version >= 7. if (version < 7) { // Version info is necessary if version >= 7.
return true; // Don't need version info. return true; // Don't need version info.
} }
@ -229,7 +231,7 @@ public final class MatrixUtil {
// Embed "data_bits" using "mask_pattern". On success, modify the matrix and return true. On // Embed "data_bits" using "mask_pattern". On success, modify the matrix and return true. On
// error, return false. For debugging purposes, it skips masking process if "mask_pattern" is -1. // error, return false. For debugging purposes, it skips masking process if "mask_pattern" is -1.
// See 8.7 of JISX0510:2004 (p.38) for how to embed data bits. // See 8.7 of JISX0510:2004 (p.38) for how to embed data bits.
public static boolean EmbedDataBits(final BitVector data_bits, int mask_pattern, Matrix matrix) { public static boolean EmbedDataBits(final BitVector data_bits, int mask_pattern, ByteMatrix matrix) {
int bit_index = 0; int bit_index = 0;
int direction = -1; int direction = -1;
// Start from the right bottom cell. // Start from the right bottom cell.
@ -387,7 +389,7 @@ public final class MatrixUtil {
value == 1); // Dark (black). value == 1); // Dark (black).
} }
private static void EmbedTimingPatterns(Matrix matrix) { private static void EmbedTimingPatterns(ByteMatrix matrix) {
// -8 is for skipping position detection patterns (size 7), and two horizontal/vertical // -8 is for skipping position detection patterns (size 7), and two horizontal/vertical
// separation patterns (size 1). Thus, 8 = 7 + 1. // separation patterns (size 1). Thus, 8 = 7 + 1.
for (int i = 8; i < matrix.width() - 8; ++i) { for (int i = 8; i < matrix.width() - 8; ++i) {
@ -406,13 +408,13 @@ public final class MatrixUtil {
} }
// Embed the lonely dark dot at left bottom corner. JISX0510:2004 (p.46) // Embed the lonely dark dot at left bottom corner. JISX0510:2004 (p.46)
private static void EmbedDarkDotAtLeftBottomCorner(Matrix matrix) { private static void EmbedDarkDotAtLeftBottomCorner(ByteMatrix matrix) {
Debug.DCHECK(matrix.get(matrix.height() - 8, 8) != 0); Debug.DCHECK(matrix.get(matrix.height() - 8, 8) != 0);
matrix.set(matrix.height() - 8, 8, 1); matrix.set(matrix.height() - 8, 8, 1);
} }
private static void EmbedHorizontalSeparationPattern(final int x_start, final int y_start, private static void EmbedHorizontalSeparationPattern(final int x_start, final int y_start,
Matrix matrix) { ByteMatrix matrix) {
// We know the width and height. // We know the width and height.
Debug.DCHECK_EQ(8, kHorizontalSeparationPattern[0].length); Debug.DCHECK_EQ(8, kHorizontalSeparationPattern[0].length);
Debug.DCHECK_EQ(1, kHorizontalSeparationPattern.length); Debug.DCHECK_EQ(1, kHorizontalSeparationPattern.length);
@ -423,7 +425,7 @@ public final class MatrixUtil {
} }
private static void EmbedVerticalSeparationPattern(final int x_start, final int y_start, private static void EmbedVerticalSeparationPattern(final int x_start, final int y_start,
Matrix matrix) { ByteMatrix matrix) {
// We know the width and height. // We know the width and height.
Debug.DCHECK_EQ(1, kVerticalSeparationPattern[0].length); Debug.DCHECK_EQ(1, kVerticalSeparationPattern[0].length);
Debug.DCHECK_EQ(7, kVerticalSeparationPattern.length); Debug.DCHECK_EQ(7, kVerticalSeparationPattern.length);
@ -437,7 +439,7 @@ public final class MatrixUtil {
// almost identical, since we cannot write a function that takes 2D arrays in different sizes in // almost identical, since we cannot write a function that takes 2D arrays in different sizes in
// C/C++. We should live with the fact. // C/C++. We should live with the fact.
private static void EmbedPositionAdjustmentPattern(final int x_start, final int y_start, private static void EmbedPositionAdjustmentPattern(final int x_start, final int y_start,
Matrix matrix) { ByteMatrix matrix) {
// We know the width and height. // We know the width and height.
Debug.DCHECK_EQ(5, kPositionAdjustmentPattern[0].length); Debug.DCHECK_EQ(5, kPositionAdjustmentPattern[0].length);
Debug.DCHECK_EQ(5, kPositionAdjustmentPattern.length); Debug.DCHECK_EQ(5, kPositionAdjustmentPattern.length);
@ -450,7 +452,7 @@ public final class MatrixUtil {
} }
private static void EmbedPositionDetectionPattern(final int x_start, final int y_start, private static void EmbedPositionDetectionPattern(final int x_start, final int y_start,
Matrix matrix) { ByteMatrix matrix) {
// We know the width and height. // We know the width and height.
Debug.DCHECK_EQ(7, kPositionDetectionPattern[0].length); Debug.DCHECK_EQ(7, kPositionDetectionPattern[0].length);
Debug.DCHECK_EQ(7, kPositionDetectionPattern.length); Debug.DCHECK_EQ(7, kPositionDetectionPattern.length);
@ -463,7 +465,7 @@ public final class MatrixUtil {
} }
// Embed position detection patterns and surrounding vertical/horizontal separators. // Embed position detection patterns and surrounding vertical/horizontal separators.
private static void EmbedPositionDetectionPatternsAndSeparators(Matrix matrix) { private static void EmbedPositionDetectionPatternsAndSeparators(ByteMatrix matrix) {
// Embed three big squares at corners. // Embed three big squares at corners.
final int pdp_width = kPositionDetectionPattern[0].length; final int pdp_width = kPositionDetectionPattern[0].length;
// Left top corner. // Left top corner.
@ -495,7 +497,7 @@ public final class MatrixUtil {
} }
// Embed position adjustment patterns if need be. // Embed position adjustment patterns if need be.
private static void MaybeEmbedPositionAdjustmentPatterns(final int version, Matrix matrix) { private static void MaybeEmbedPositionAdjustmentPatterns(final int version, ByteMatrix matrix) {
if (version < 2) { // The patterns appear if version >= 2 if (version < 2) { // The patterns appear if version >= 2
return; return;
} }

View file

@ -16,6 +16,8 @@
package com.google.zxing.qrcode.encoder; package com.google.zxing.qrcode.encoder;
import com.google.zxing.common.ByteMatrix;
/** /**
* @author satorux@google.com (Satoru Takabayashi) - creator * @author satorux@google.com (Satoru Takabayashi) - creator
* @author dswitkin@google.com (Daniel Switkin) - ported from C++ * @author dswitkin@google.com (Daniel Switkin) - ported from C++
@ -49,7 +51,7 @@ public final class QRCode {
private int num_data_bytes_; private int num_data_bytes_;
private int num_ec_bytes_; private int num_ec_bytes_;
private int num_rs_blocks_; private int num_rs_blocks_;
private Matrix matrix_; private ByteMatrix matrix_;
// They call encoding "mode". The modes are defined in 8.3 of JISX0510:2004 (p.14). It's unlikely // They call encoding "mode". The modes are defined in 8.3 of JISX0510:2004 (p.14). It's unlikely
@ -106,7 +108,7 @@ public final class QRCode {
public int ec_level() { return ec_level_; } public int ec_level() { return ec_level_; }
// Version of the QR Code. The bigger size, the bigger version. // Version of the QR Code. The bigger size, the bigger version.
public int version() { return version_; } public int version() { return version_; }
// Matrix width of the QR Code. // ByteMatrix width of the QR Code.
public int matrix_width() { return matrix_width_; } public int matrix_width() { return matrix_width_; }
// Mask pattern of the QR Code. // Mask pattern of the QR Code.
public int mask_pattern() { return mask_pattern_; } public int mask_pattern() { return mask_pattern_; }
@ -118,8 +120,8 @@ public final class QRCode {
public int num_ec_bytes() { return num_ec_bytes_; } public int num_ec_bytes() { return num_ec_bytes_; }
// Number of Reedsolomon blocks in the QR Code. // Number of Reedsolomon blocks in the QR Code.
public int num_rs_blocks() { return num_rs_blocks_; } public int num_rs_blocks() { return num_rs_blocks_; }
// Matrix data of the QR Code. // ByteMatrix data of the QR Code.
public final Matrix matrix() { return matrix_; } public final ByteMatrix matrix() { return matrix_; }
// Return the value of the module (cell) pointed by "x" and "y" in the matrix of the QR Code. They // Return the value of the module (cell) pointed by "x" and "y" in the matrix of the QR Code. They
// call cells in the matrix "modules". 1 represents a black cell, and 0 represents a white cell. // call cells in the matrix "modules". 1 represents a black cell, and 0 represents a white cell.
@ -152,7 +154,7 @@ public final class QRCode {
IsValidMatrixWidth(matrix_width_) && IsValidMatrixWidth(matrix_width_) &&
IsValidMaskPattern(mask_pattern_) && IsValidMaskPattern(mask_pattern_) &&
num_total_bytes_ == num_data_bytes_ + num_ec_bytes_ && num_total_bytes_ == num_data_bytes_ + num_ec_bytes_ &&
// Matrix stuff. // ByteMatrix stuff.
matrix_ != null && matrix_ != null &&
matrix_width_ == matrix_.width() && matrix_width_ == matrix_.width() &&
// See 7.3.1 of JISX0510:2004 (p.5). // See 7.3.1 of JISX0510:2004 (p.5).
@ -230,7 +232,7 @@ public final class QRCode {
// This takes ownership of the 2D array. The 2D array will be // This takes ownership of the 2D array. The 2D array will be
// deleted in the destructor of the class. // deleted in the destructor of the class.
public void set_matrix(Matrix value) { public void set_matrix(ByteMatrix value) {
matrix_ = value; matrix_ = value;
} }
@ -360,8 +362,8 @@ public final class QRCode {
// //
// JAVAPORT: This is going to be super expensive and unnecessary, we should not call this in // JAVAPORT: This is going to be super expensive and unnecessary, we should not call this in
// production. I'm leaving it because it may be useful for testing. It should be removed entirely // production. I'm leaving it because it may be useful for testing. It should be removed entirely
// if Matrix is changed never to contain a -1. // if ByteMatrix is changed never to contain a -1.
private static boolean EverythingIsBinary(final Matrix matrix) { private static boolean EverythingIsBinary(final ByteMatrix matrix) {
for (int y = 0; y < matrix.height(); ++y) { for (int y = 0; y < matrix.height(); ++y) {
for (int x = 0; x < matrix.width(); ++x) { for (int x = 0; x < matrix.width(); ++x) {
int value = matrix.get(y, x); int value = matrix.get(y, x);

View file

@ -1,180 +0,0 @@
/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.qrcode.encoder;
// #include "third_party/png/png.h"
/**
* JAVAPORT: This class may get thrown out in the future, or it may turn into the object which
* returns a MonochromeBitmapSource.
*
* @author satorux@google.com (Satoru Takabayashi) - creator
* @author dswitkin@google.com (Daniel Switkin) - ported from C++
*/
public final class Renderer {
// See 7.3.7 of JISX0510:2004 (p. 11).
private static final int kQuietZoneSize = 4;
// Render QR Code as PNG image with "cell_size". On success, store
// the result in "result" and return true. On error, return false.
// The recommended cell size for desktop screens is 3. This
// setting generates 87x87 pixels PNG image for version 1 QR Code
// (21x21). 87 = (21 + 4 + 4) * 3. 4 is for surrounding white
// space (they call it quiet zone).
// Sorry for the long function but libpng's API is a bit complecated.
// See http://www.libpng.org/pub/png/libpng-1.2.5-manual.html for
// details.
public static boolean RenderAsPNG(final QRCode &qr_code, int cell_size,
String *result) {
// First, clear the result String.
result.clear();
// Create PNG class.
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
null, null, null);
if (png_ptr == null) {
Debug.LOG_ERROR("Unable to create png_strupctp");
return false;
}
// Create PNG info.
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == null) {
Debug.LOG_ERROR("Unable to create png_infop");
png_destroy_write_struct(&png_ptr, (png_infopp)null);
return false;
}
// Calculate the width of the resulting image. Note that the height
// is equal to the width (i.e. the resulting image is square).
final int image_width = (qr_code.matrix_width() +
kQuietZoneSize * 2) * cell_size;
// Since we use 1-bit color depth, we only need 1 bit per pixel.
final int num_bytes_in_row = image_width / 8 +
(image_width % 8 == 0 ? 0 : 1);
// We'll use this storage later but we should prepare this before
// setjmp() so that this will be deleted on error. Today's lesson
// is that RAII isn't reliable with setjmp/longjmp!
scoped_array<char> row(new char[num_bytes_in_row]);
// Erorr handling of libpng is a bit tricky. If something bad
// happens in libpng, they call longjmp() to get to here.
if (setjmp(png_ptr.jmpbuf)) {
Debug.LOG_ERROR("Something bad happened in libpng");
png_destroy_write_struct(&png_ptr, &info_ptr);
return false;
}
// Attach the pointer to the result String and the pointer to the
// writer function.
png_set_write_fn(png_ptr, static_cast<void*>(result), PNGWriter, null);
// Set the image information.
png_set_IHDR(png_ptr, info_ptr, image_width, image_width,
1, // The color depth is 1 (black and white).
PNG_COLOR_TYPE_GRAY,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
// Write the file header information.
png_write_info(png_ptr, info_ptr);
// Quiet zone at the top.
FillRowWithWhite(num_bytes_in_row, row.get());
WriteRowNumTimes(png_ptr, row.get(), kQuietZoneSize * cell_size);
// Fill data.
for (int y = 0; y < qr_code.matrix_width(); ++y) {
FillRowWithData(num_bytes_in_row, qr_code, y, cell_size, row.get());
WriteRowNumTimes(png_ptr, row.get(), cell_size);
}
// Quiet zone at the bottom.
FillRowWithWhite(num_bytes_in_row, row.get());
WriteRowNumTimes(png_ptr, row.get(), kQuietZoneSize * cell_size);
// Cleanups for libpng stuff.
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
// Finally, it's all done!
return true;
}
// Similar to RenderAsPNG but it renders QR code from data in
// "bytes" with error correction level "ec_level". This is the
// friendliest function in the QR code library.
public static boolean RenderAsPNGFromData(final ByteArray& bytes, int ec_level, int cell_size,
String *result) {
QRCode qr_code;
if (!Encoder.Encode(bytes, ec_level, &qr_code)) {
return false;
}
return RenderAsPNG(qr_code, cell_size, result);
}
// Callback function which gets called by png_write_row().
private static void PNGWriter(png_structp png_ptr, png_bytep data, png_size_t length) {
String* out = static_cast<String*>(png_get_io_ptr(png_ptr));
out.append(reinterpret_cast<char*>(data), length);
}
// Fill all pixels in "row" with white.
private static void FillRowWithWhite(final int num_bytes_in_row, char *row) {
memset(row, 0xff, num_bytes_in_row);
}
// Set the bit in "row" pointed by "index" to 1 (1 is for white).
private static void SetBit(final int index, char *row) {
final int byte_index = index / 8;
final int bit_index = index % 8;
row[byte_index] |= 0x80 >> bit_index;
}
// Fill pixels in "row" with data in "qr_code".
private static void FillRowWithData(final int num_bytes_in_row,
final QRCode &qr_code,
final int y,
final int cell_size,
char *row) {
memset(row, 0, num_bytes_in_row); // Fill all pixels with black.
int index = 0;
for (int i = 0; i < kQuietZoneSize * cell_size; ++i) {
SetBit(index++, row); // Cells in the quite zone should be white.
}
for (int x = 0; x < qr_code.matrix_width(); ++x) {
for (int i = 0; i < cell_size; ++i) {
if (qr_code.at(x, y) == 0) { // White cell.
SetBit(index, row);
}
++index;
}
}
for (int i = 0; i < kQuietZoneSize * cell_size; ++i) {
SetBit(index++, row);
}
}
// Write pixels in "row" to "png_ptr" "num" times.
private static void WriteRowNumTimes(png_structp png_ptr, char *row, final int num) {
for (int i = 0; i < num; ++i) {
png_write_row(png_ptr, reinterpret_cast<png_bytep>(row));
}
}
}