move qrcode

This commit is contained in:
Henry Schimke
2022-08-14 18:17:31 -05:00
parent 7ade09dc7b
commit 20c7ec32f1
29 changed files with 118 additions and 0 deletions

View File

@@ -0,0 +1,221 @@
/*
* Copyright 2007 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.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.DecodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.NotFoundException;
import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DecoderResult;
import com.google.zxing.common.DetectorResult;
import com.google.zxing.qrcode.decoder.Decoder;
import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData;
import com.google.zxing.qrcode.detector.Detector;
import java.util.List;
import java.util.Map;
/**
* This implementation can detect and decode QR Codes in an image.
*
* @author Sean Owen
*/
public class QRCodeReader implements Reader {
private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
private final Decoder decoder = new Decoder();
protected final Decoder getDecoder() {
return decoder;
}
/**
* Locates and decodes a QR code in an image.
*
* @return a String representing the content encoded by the QR code
* @throws NotFoundException if a QR code cannot be found
* @throws FormatException if a QR code cannot be decoded
* @throws ChecksumException if error correction fails
*/
@Override
public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException {
return decode(image, null);
}
@Override
public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
throws NotFoundException, ChecksumException, FormatException {
DecoderResult decoderResult;
ResultPoint[] points;
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
BitMatrix bits = extractPureBits(image.getBlackMatrix());
decoderResult = decoder.decode(bits, hints);
points = NO_POINTS;
} else {
DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
decoderResult = decoder.decode(detectorResult.getBits(), hints);
points = detectorResult.getPoints();
}
// If the code was mirrored: swap the bottom-left and the top-right points.
if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
}
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
List<byte[]> byteSegments = decoderResult.getByteSegments();
if (byteSegments != null) {
result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
}
String ecLevel = decoderResult.getECLevel();
if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
if (decoderResult.hasStructuredAppend()) {
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
decoderResult.getStructuredAppendSequenceNumber());
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderResult.getStructuredAppendParity());
}
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]Q" + decoderResult.getSymbologyModifier());
return result;
}
@Override
public void reset() {
// do nothing
}
/**
* This method detects a code in a "pure" image -- that is, pure monochrome image
* which contains only an unrotated, unskewed, image of a code, with some white border
* around it. This is a specialized method that works exceptionally fast in this special
* case.
*/
private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException {
int[] leftTopBlack = image.getTopLeftOnBit();
int[] rightBottomBlack = image.getBottomRightOnBit();
if (leftTopBlack == null || rightBottomBlack == null) {
throw NotFoundException.getNotFoundInstance();
}
float moduleSize = moduleSize(leftTopBlack, image);
int top = leftTopBlack[1];
int bottom = rightBottomBlack[1];
int left = leftTopBlack[0];
int right = rightBottomBlack[0];
// Sanity check!
if (left >= right || top >= bottom) {
throw NotFoundException.getNotFoundInstance();
}
if (bottom - top != right - left) {
// Special case, where bottom-right module wasn't black so we found something else in the last row
// Assume it's a square, so use height as the width
right = left + (bottom - top);
if (right >= image.getWidth()) {
// Abort if that would not make sense -- off image
throw NotFoundException.getNotFoundInstance();
}
}
int matrixWidth = Math.round((right - left + 1) / moduleSize);
int matrixHeight = Math.round((bottom - top + 1) / moduleSize);
if (matrixWidth <= 0 || matrixHeight <= 0) {
throw NotFoundException.getNotFoundInstance();
}
if (matrixHeight != matrixWidth) {
// Only possibly decode square regions
throw NotFoundException.getNotFoundInstance();
}
// Push in the "border" by half the module width so that we start
// sampling in the middle of the module. Just in case the image is a
// little off, this will help recover.
int nudge = (int) (moduleSize / 2.0f);
top += nudge;
left += nudge;
// But careful that this does not sample off the edge
// "right" is the farthest-right valid pixel location -- right+1 is not necessarily
// This is positive by how much the inner x loop below would be too large
int nudgedTooFarRight = left + (int) ((matrixWidth - 1) * moduleSize) - right;
if (nudgedTooFarRight > 0) {
if (nudgedTooFarRight > nudge) {
// Neither way fits; abort
throw NotFoundException.getNotFoundInstance();
}
left -= nudgedTooFarRight;
}
// See logic above
int nudgedTooFarDown = top + (int) ((matrixHeight - 1) * moduleSize) - bottom;
if (nudgedTooFarDown > 0) {
if (nudgedTooFarDown > nudge) {
// Neither way fits; abort
throw NotFoundException.getNotFoundInstance();
}
top -= nudgedTooFarDown;
}
// Now just read off the bits
BitMatrix bits = new BitMatrix(matrixWidth, matrixHeight);
for (int y = 0; y < matrixHeight; y++) {
int iOffset = top + (int) (y * moduleSize);
for (int x = 0; x < matrixWidth; x++) {
if (image.get(left + (int) (x * moduleSize), iOffset)) {
bits.set(x, y);
}
}
}
return bits;
}
private static float moduleSize(int[] leftTopBlack, BitMatrix image) throws NotFoundException {
int height = image.getHeight();
int width = image.getWidth();
int x = leftTopBlack[0];
int y = leftTopBlack[1];
boolean inBlack = true;
int transitions = 0;
while (x < width && y < height) {
if (inBlack != image.get(x, y)) {
if (++transitions == 5) {
break;
}
inBlack = !inBlack;
}
x++;
y++;
}
if (x == width || y == height) {
throw NotFoundException.getNotFoundInstance();
}
return (x - leftTopBlack[0]) / 7.0f;
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.EncodeHintType;
import com.google.zxing.Writer;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.encoder.Encoder;
import com.google.zxing.qrcode.encoder.QRCode;
import java.util.Map;
/**
* This object renders a QR Code as a BitMatrix 2D array of greyscale values.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class QRCodeWriter implements Writer {
private static final int QUIET_ZONE_SIZE = 4;
@Override
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
throws WriterException {
return encode(contents, format, width, height, null);
}
@Override
public BitMatrix encode(String contents,
BarcodeFormat format,
int width,
int height,
Map<EncodeHintType,?> hints) throws WriterException {
if (contents.isEmpty()) {
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);
}
ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
int quietZone = QUIET_ZONE_SIZE;
if (hints != null) {
if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) {
errorCorrectionLevel = ErrorCorrectionLevel.valueOf(hints.get(EncodeHintType.ERROR_CORRECTION).toString());
}
if (hints.containsKey(EncodeHintType.MARGIN)) {
quietZone = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString());
}
}
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;
}
}

View File

@@ -0,0 +1,245 @@
/*
* Copyright 2007 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.decoder;
import com.google.zxing.FormatException;
import com.google.zxing.common.BitMatrix;
/**
* @author Sean Owen
*/
final class BitMatrixParser {
private final BitMatrix bitMatrix;
private Version parsedVersion;
private FormatInformation parsedFormatInfo;
private boolean mirror;
/**
* @param bitMatrix {@link BitMatrix} to parse
* @throws FormatException if dimension is not >= 21 and 1 mod 4
*/
BitMatrixParser(BitMatrix bitMatrix) throws FormatException {
int dimension = bitMatrix.getHeight();
if (dimension < 21 || (dimension & 0x03) != 1) {
throw FormatException.getFormatInstance();
}
this.bitMatrix = bitMatrix;
}
/**
* <p>Reads format information from one of its two locations within the QR Code.</p>
*
* @return {@link FormatInformation} encapsulating the QR Code's format info
* @throws FormatException if both format information locations cannot be parsed as
* the valid encoding of format information
*/
FormatInformation readFormatInformation() throws FormatException {
if (parsedFormatInfo != null) {
return parsedFormatInfo;
}
// Read top-left format info bits
int formatInfoBits1 = 0;
for (int i = 0; i < 6; i++) {
formatInfoBits1 = copyBit(i, 8, formatInfoBits1);
}
// .. and skip a bit in the timing pattern ...
formatInfoBits1 = copyBit(7, 8, formatInfoBits1);
formatInfoBits1 = copyBit(8, 8, formatInfoBits1);
formatInfoBits1 = copyBit(8, 7, formatInfoBits1);
// .. and skip a bit in the timing pattern ...
for (int j = 5; j >= 0; j--) {
formatInfoBits1 = copyBit(8, j, formatInfoBits1);
}
// Read the top-right/bottom-left pattern too
int dimension = bitMatrix.getHeight();
int formatInfoBits2 = 0;
int jMin = dimension - 7;
for (int j = dimension - 1; j >= jMin; j--) {
formatInfoBits2 = copyBit(8, j, formatInfoBits2);
}
for (int i = dimension - 8; i < dimension; i++) {
formatInfoBits2 = copyBit(i, 8, formatInfoBits2);
}
parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits1, formatInfoBits2);
if (parsedFormatInfo != null) {
return parsedFormatInfo;
}
throw FormatException.getFormatInstance();
}
/**
* <p>Reads version information from one of its two locations within the QR Code.</p>
*
* @return {@link Version} encapsulating the QR Code's version
* @throws FormatException if both version information locations cannot be parsed as
* the valid encoding of version information
*/
Version readVersion() throws FormatException {
if (parsedVersion != null) {
return parsedVersion;
}
int dimension = bitMatrix.getHeight();
int provisionalVersion = (dimension - 17) / 4;
if (provisionalVersion <= 6) {
return Version.getVersionForNumber(provisionalVersion);
}
// Read top-right version info: 3 wide by 6 tall
int versionBits = 0;
int ijMin = dimension - 11;
for (int j = 5; j >= 0; j--) {
for (int i = dimension - 9; i >= ijMin; i--) {
versionBits = copyBit(i, j, versionBits);
}
}
Version theParsedVersion = Version.decodeVersionInformation(versionBits);
if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) {
parsedVersion = theParsedVersion;
return theParsedVersion;
}
// Hmm, failed. Try bottom left: 6 wide by 3 tall
versionBits = 0;
for (int i = 5; i >= 0; i--) {
for (int j = dimension - 9; j >= ijMin; j--) {
versionBits = copyBit(i, j, versionBits);
}
}
theParsedVersion = Version.decodeVersionInformation(versionBits);
if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) {
parsedVersion = theParsedVersion;
return theParsedVersion;
}
throw FormatException.getFormatInstance();
}
private int copyBit(int i, int j, int versionBits) {
boolean bit = mirror ? bitMatrix.get(j, i) : bitMatrix.get(i, j);
return bit ? (versionBits << 1) | 0x1 : versionBits << 1;
}
/**
* <p>Reads the bits in the {@link BitMatrix} representing the finder pattern in the
* correct order in order to reconstruct the codewords bytes contained within the
* QR Code.</p>
*
* @return bytes encoded within the QR Code
* @throws FormatException if the exact number of bytes expected is not read
*/
byte[] readCodewords() throws FormatException {
FormatInformation formatInfo = readFormatInformation();
Version version = readVersion();
// Get the data mask for the format used in this QR Code. This will exclude
// some bits from reading as we wind through the bit matrix.
DataMask dataMask = DataMask.values()[formatInfo.getDataMask()];
int dimension = bitMatrix.getHeight();
dataMask.unmaskBitMatrix(bitMatrix, dimension);
BitMatrix functionPattern = version.buildFunctionPattern();
boolean readingUp = true;
byte[] result = new byte[version.getTotalCodewords()];
int resultOffset = 0;
int currentByte = 0;
int bitsRead = 0;
// Read columns in pairs, from right to left
for (int j = dimension - 1; j > 0; j -= 2) {
if (j == 6) {
// Skip whole column with vertical alignment pattern;
// saves time and makes the other code proceed more cleanly
j--;
}
// Read alternatingly from bottom to top then top to bottom
for (int count = 0; count < dimension; count++) {
int i = readingUp ? dimension - 1 - count : count;
for (int col = 0; col < 2; col++) {
// Ignore bits covered by the function pattern
if (!functionPattern.get(j - col, i)) {
// Read a bit
bitsRead++;
currentByte <<= 1;
if (bitMatrix.get(j - col, i)) {
currentByte |= 1;
}
// If we've made a whole byte, save it off
if (bitsRead == 8) {
result[resultOffset++] = (byte) currentByte;
bitsRead = 0;
currentByte = 0;
}
}
}
}
readingUp ^= true; // readingUp = !readingUp; // switch directions
}
if (resultOffset != version.getTotalCodewords()) {
throw FormatException.getFormatInstance();
}
return result;
}
/**
* Revert the mask removal done while reading the code words. The bit matrix should revert to its original state.
*/
void remask() {
if (parsedFormatInfo == null) {
return; // We have no format information, and have no data mask
}
DataMask dataMask = DataMask.values()[parsedFormatInfo.getDataMask()];
int dimension = bitMatrix.getHeight();
dataMask.unmaskBitMatrix(bitMatrix, dimension);
}
/**
* Prepare the parser for a mirrored operation.
* This flag has effect only on the {@link #readFormatInformation()} and the
* {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the
* {@link #mirror()} method should be called.
*
* @param mirror Whether to read version and format information mirrored.
*/
void setMirror(boolean mirror) {
parsedVersion = null;
parsedFormatInfo = null;
this.mirror = mirror;
}
/** Mirror the bit matrix in order to attempt a second reading. */
void mirror() {
for (int x = 0; x < bitMatrix.getWidth(); x++) {
for (int y = x + 1; y < bitMatrix.getHeight(); y++) {
if (bitMatrix.get(x, y) != bitMatrix.get(y, x)) {
bitMatrix.flip(y, x);
bitMatrix.flip(x, y);
}
}
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2007 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.decoder;
/**
* <p>Encapsulates a block of data within a QR Code. QR Codes may split their data into
* multiple blocks, each of which is a unit of data and error-correction codewords. Each
* is represented by an instance of this class.</p>
*
* @author Sean Owen
*/
final class DataBlock {
private final int numDataCodewords;
private final byte[] codewords;
private DataBlock(int numDataCodewords, byte[] codewords) {
this.numDataCodewords = numDataCodewords;
this.codewords = codewords;
}
/**
* <p>When QR Codes use multiple data blocks, they are actually interleaved.
* That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This
* method will separate the data into original blocks.</p>
*
* @param rawCodewords bytes as read directly from the QR Code
* @param version version of the QR Code
* @param ecLevel error-correction level of the QR Code
* @return DataBlocks containing original bytes, "de-interleaved" from representation in the
* QR Code
*/
static DataBlock[] getDataBlocks(byte[] rawCodewords,
Version version,
ErrorCorrectionLevel ecLevel) {
if (rawCodewords.length != version.getTotalCodewords()) {
throw new IllegalArgumentException();
}
// Figure out the number and size of data blocks used by this version and
// error correction level
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
// First count the total number of data blocks
int totalBlocks = 0;
Version.ECB[] ecBlockArray = ecBlocks.getECBlocks();
for (Version.ECB ecBlock : ecBlockArray) {
totalBlocks += ecBlock.getCount();
}
// Now establish DataBlocks of the appropriate size and number of data codewords
DataBlock[] result = new DataBlock[totalBlocks];
int numResultBlocks = 0;
for (Version.ECB ecBlock : ecBlockArray) {
for (int i = 0; i < ecBlock.getCount(); i++) {
int numDataCodewords = ecBlock.getDataCodewords();
int numBlockCodewords = ecBlocks.getECCodewordsPerBlock() + numDataCodewords;
result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]);
}
}
// All blocks have the same amount of data, except that the last n
// (where n may be 0) have 1 more byte. Figure out where these start.
int shorterBlocksTotalCodewords = result[0].codewords.length;
int longerBlocksStartAt = result.length - 1;
while (longerBlocksStartAt >= 0) {
int numCodewords = result[longerBlocksStartAt].codewords.length;
if (numCodewords == shorterBlocksTotalCodewords) {
break;
}
longerBlocksStartAt--;
}
longerBlocksStartAt++;
int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewordsPerBlock();
// The last elements of result may be 1 element longer;
// first fill out as many elements as all of them have
int rawCodewordsOffset = 0;
for (int i = 0; i < shorterBlocksNumDataCodewords; i++) {
for (int j = 0; j < numResultBlocks; j++) {
result[j].codewords[i] = rawCodewords[rawCodewordsOffset++];
}
}
// Fill out the last data block in the longer ones
for (int j = longerBlocksStartAt; j < numResultBlocks; j++) {
result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++];
}
// Now add in error correction blocks
int max = result[0].codewords.length;
for (int i = shorterBlocksNumDataCodewords; i < max; i++) {
for (int j = 0; j < numResultBlocks; j++) {
int iOffset = j < longerBlocksStartAt ? i : i + 1;
result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++];
}
}
return result;
}
int getNumDataCodewords() {
return numDataCodewords;
}
byte[] getCodewords() {
return codewords;
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright 2007 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.decoder;
import com.google.zxing.common.BitMatrix;
/**
* <p>Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations
* of this class can un-mask a raw BitMatrix. For simplicity, they will unmask the entire BitMatrix,
* including areas used for finder patterns, timing patterns, etc. These areas should be unused
* after the point they are unmasked anyway.</p>
*
* <p>Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position
* and j is row position. In fact, as the text says, i is row position and j is column position.</p>
*
* @author Sean Owen
*/
enum DataMask {
// See ISO 18004:2006 6.8.1
/**
* 000: mask bits for which (x + y) mod 2 == 0
*/
DATA_MASK_000() {
@Override
boolean isMasked(int i, int j) {
return ((i + j) & 0x01) == 0;
}
},
/**
* 001: mask bits for which x mod 2 == 0
*/
DATA_MASK_001() {
@Override
boolean isMasked(int i, int j) {
return (i & 0x01) == 0;
}
},
/**
* 010: mask bits for which y mod 3 == 0
*/
DATA_MASK_010() {
@Override
boolean isMasked(int i, int j) {
return j % 3 == 0;
}
},
/**
* 011: mask bits for which (x + y) mod 3 == 0
*/
DATA_MASK_011() {
@Override
boolean isMasked(int i, int j) {
return (i + j) % 3 == 0;
}
},
/**
* 100: mask bits for which (x/2 + y/3) mod 2 == 0
*/
DATA_MASK_100() {
@Override
boolean isMasked(int i, int j) {
return (((i / 2) + (j / 3)) & 0x01) == 0;
}
},
/**
* 101: mask bits for which xy mod 2 + xy mod 3 == 0
* equivalently, such that xy mod 6 == 0
*/
DATA_MASK_101() {
@Override
boolean isMasked(int i, int j) {
return (i * j) % 6 == 0;
}
},
/**
* 110: mask bits for which (xy mod 2 + xy mod 3) mod 2 == 0
* equivalently, such that xy mod 6 < 3
*/
DATA_MASK_110() {
@Override
boolean isMasked(int i, int j) {
return ((i * j) % 6) < 3;
}
},
/**
* 111: mask bits for which ((x+y)mod 2 + xy mod 3) mod 2 == 0
* equivalently, such that (x + y + xy mod 3) mod 2 == 0
*/
DATA_MASK_111() {
@Override
boolean isMasked(int i, int j) {
return ((i + j + ((i * j) % 3)) & 0x01) == 0;
}
};
// End of enum constants.
/**
* <p>Implementations of this method reverse the data masking process applied to a QR Code and
* make its bits ready to read.</p>
*
* @param bits representation of QR Code bits
* @param dimension dimension of QR Code, represented by bits, being unmasked
*/
final void unmaskBitMatrix(BitMatrix bits, int dimension) {
for (int i = 0; i < dimension; i++) {
for (int j = 0; j < dimension; j++) {
if (isMasked(i, j)) {
bits.flip(j, i);
}
}
}
}
abstract boolean isMasked(int i, int j);
}

View File

@@ -0,0 +1,375 @@
/*
* Copyright 2007 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.decoder;
import com.google.zxing.DecodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.common.BitSource;
import com.google.zxing.common.CharacterSetECI;
import com.google.zxing.common.DecoderResult;
import com.google.zxing.common.StringUtils;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* <p>QR Codes can encode text as bits in one of several modes, and can use multiple modes
* in one QR Code. This class decodes the bits back into text.</p>
*
* <p>See ISO 18004:2006, 6.4.3 - 6.4.7</p>
*
* @author Sean Owen
*/
final class DecodedBitStreamParser {
/**
* See ISO 18004:2006, 6.4.4 Table 5
*/
private static final char[] ALPHANUMERIC_CHARS =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".toCharArray();
private static final int GB2312_SUBSET = 1;
private DecodedBitStreamParser() {
}
static DecoderResult decode(byte[] bytes,
Version version,
ErrorCorrectionLevel ecLevel,
Map<DecodeHintType,?> hints) throws FormatException {
BitSource bits = new BitSource(bytes);
StringBuilder result = new StringBuilder(50);
List<byte[]> byteSegments = new ArrayList<>(1);
int symbolSequence = -1;
int parityData = -1;
int symbologyModifier;
try {
CharacterSetECI currentCharacterSetECI = null;
boolean fc1InEffect = false;
boolean hasFNC1first = false;
boolean hasFNC1second = false;
Mode mode;
do {
// While still another segment to read...
if (bits.available() < 4) {
// OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
mode = Mode.TERMINATOR;
} else {
mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
}
switch (mode) {
case TERMINATOR:
break;
case FNC1_FIRST_POSITION:
hasFNC1first = true; // symbology detection
// We do little with FNC1 except alter the parsed result a bit according to the spec
fc1InEffect = true;
break;
case FNC1_SECOND_POSITION:
hasFNC1second = true; // symbology detection
// We do little with FNC1 except alter the parsed result a bit according to the spec
fc1InEffect = true;
break;
case STRUCTURED_APPEND:
if (bits.available() < 16) {
throw FormatException.getFormatInstance();
}
// sequence number and parity is added later to the result metadata
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
symbolSequence = bits.readBits(8);
parityData = bits.readBits(8);
break;
case ECI:
// Count doesn't apply to ECI
int value = parseECIValue(bits);
currentCharacterSetECI = CharacterSetECI.getCharacterSetECIByValue(value);
if (currentCharacterSetECI == null) {
throw FormatException.getFormatInstance();
}
break;
case HANZI:
// First handle Hanzi mode which does not start with character count
// Chinese mode contains a sub set indicator right after mode indicator
int subset = bits.readBits(4);
int countHanzi = bits.readBits(mode.getCharacterCountBits(version));
if (subset == GB2312_SUBSET) {
decodeHanziSegment(bits, result, countHanzi);
}
break;
default:
// "Normal" QR code modes:
// How many characters will follow, encoded in this mode?
int count = bits.readBits(mode.getCharacterCountBits(version));
switch (mode) {
case NUMERIC:
decodeNumericSegment(bits, result, count);
break;
case ALPHANUMERIC:
decodeAlphanumericSegment(bits, result, count, fc1InEffect);
break;
case BYTE:
decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints);
break;
case KANJI:
decodeKanjiSegment(bits, result, count);
break;
default:
throw FormatException.getFormatInstance();
}
break;
}
} while (mode != Mode.TERMINATOR);
if (currentCharacterSetECI != null) {
if (hasFNC1first) {
symbologyModifier = 4;
} else if (hasFNC1second) {
symbologyModifier = 6;
} else {
symbologyModifier = 2;
}
} else {
if (hasFNC1first) {
symbologyModifier = 3;
} else if (hasFNC1second) {
symbologyModifier = 5;
} else {
symbologyModifier = 1;
}
}
} catch (IllegalArgumentException iae) {
// from readBits() calls
throw FormatException.getFormatInstance();
}
return new DecoderResult(bytes,
result.toString(),
byteSegments.isEmpty() ? null : byteSegments,
ecLevel == null ? null : ecLevel.toString(),
symbolSequence,
parityData,
symbologyModifier);
}
/**
* See specification GBT 18284-2000
*/
private static void decodeHanziSegment(BitSource bits,
StringBuilder result,
int count) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (count * 13 > bits.available()) {
throw FormatException.getFormatInstance();
}
// Each character will require 2 bytes. Read the characters as 2-byte pairs
// and decode as GB2312 afterwards
byte[] buffer = new byte[2 * count];
int offset = 0;
while (count > 0) {
// Each 13 bits encodes a 2-byte character
int twoBytes = bits.readBits(13);
int assembledTwoBytes = ((twoBytes / 0x060) << 8) | (twoBytes % 0x060);
if (assembledTwoBytes < 0x00A00) {
// In the 0xA1A1 to 0xAAFE range
assembledTwoBytes += 0x0A1A1;
} else {
// In the 0xB0A1 to 0xFAFE range
assembledTwoBytes += 0x0A6A1;
}
buffer[offset] = (byte) ((assembledTwoBytes >> 8) & 0xFF);
buffer[offset + 1] = (byte) (assembledTwoBytes & 0xFF);
offset += 2;
count--;
}
result.append(new String(buffer, StringUtils.GB2312_CHARSET));
}
private static void decodeKanjiSegment(BitSource bits,
StringBuilder result,
int count) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (count * 13 > bits.available()) {
throw FormatException.getFormatInstance();
}
// Each character will require 2 bytes. Read the characters as 2-byte pairs
// and decode as Shift_JIS afterwards
byte[] buffer = new byte[2 * count];
int offset = 0;
while (count > 0) {
// Each 13 bits encodes a 2-byte character
int twoBytes = bits.readBits(13);
int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0);
if (assembledTwoBytes < 0x01F00) {
// In the 0x8140 to 0x9FFC range
assembledTwoBytes += 0x08140;
} else {
// In the 0xE040 to 0xEBBF range
assembledTwoBytes += 0x0C140;
}
buffer[offset] = (byte) (assembledTwoBytes >> 8);
buffer[offset + 1] = (byte) assembledTwoBytes;
offset += 2;
count--;
}
result.append(new String(buffer, StringUtils.SHIFT_JIS_CHARSET));
}
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
Charset encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessCharset(readBytes, hints);
} else {
encoding = currentCharacterSetECI.getCharset();
}
result.append(new String(readBytes, encoding));
byteSegments.add(readBytes);
}
private static char toAlphaNumericChar(int value) throws FormatException {
if (value >= ALPHANUMERIC_CHARS.length) {
throw FormatException.getFormatInstance();
}
return ALPHANUMERIC_CHARS[value];
}
private static void decodeAlphanumericSegment(BitSource bits,
StringBuilder result,
int count,
boolean fc1InEffect) throws FormatException {
// Read two characters at a time
int start = result.length();
while (count > 1) {
if (bits.available() < 11) {
throw FormatException.getFormatInstance();
}
int nextTwoCharsBits = bits.readBits(11);
result.append(toAlphaNumericChar(nextTwoCharsBits / 45));
result.append(toAlphaNumericChar(nextTwoCharsBits % 45));
count -= 2;
}
if (count == 1) {
// special case: one character left
if (bits.available() < 6) {
throw FormatException.getFormatInstance();
}
result.append(toAlphaNumericChar(bits.readBits(6)));
}
// See section 6.4.8.1, 6.4.8.2
if (fc1InEffect) {
// We need to massage the result a bit if in an FNC1 mode:
for (int i = start; i < result.length(); i++) {
if (result.charAt(i) == '%') {
if (i < result.length() - 1 && result.charAt(i + 1) == '%') {
// %% is rendered as %
result.deleteCharAt(i + 1);
} else {
// In alpha mode, % should be converted to FNC1 separator 0x1D
result.setCharAt(i, (char) 0x1D);
}
}
}
}
}
private static void decodeNumericSegment(BitSource bits,
StringBuilder result,
int count) throws FormatException {
// Read three digits at a time
while (count >= 3) {
// Each 10 bits encodes three digits
if (bits.available() < 10) {
throw FormatException.getFormatInstance();
}
int threeDigitsBits = bits.readBits(10);
if (threeDigitsBits >= 1000) {
throw FormatException.getFormatInstance();
}
result.append(toAlphaNumericChar(threeDigitsBits / 100));
result.append(toAlphaNumericChar((threeDigitsBits / 10) % 10));
result.append(toAlphaNumericChar(threeDigitsBits % 10));
count -= 3;
}
if (count == 2) {
// Two digits left over to read, encoded in 7 bits
if (bits.available() < 7) {
throw FormatException.getFormatInstance();
}
int twoDigitsBits = bits.readBits(7);
if (twoDigitsBits >= 100) {
throw FormatException.getFormatInstance();
}
result.append(toAlphaNumericChar(twoDigitsBits / 10));
result.append(toAlphaNumericChar(twoDigitsBits % 10));
} else if (count == 1) {
// One digit left over to read
if (bits.available() < 4) {
throw FormatException.getFormatInstance();
}
int digitBits = bits.readBits(4);
if (digitBits >= 10) {
throw FormatException.getFormatInstance();
}
result.append(toAlphaNumericChar(digitBits));
}
}
private static int parseECIValue(BitSource bits) throws FormatException {
int firstByte = bits.readBits(8);
if ((firstByte & 0x80) == 0) {
// just one byte
return firstByte & 0x7F;
}
if ((firstByte & 0xC0) == 0x80) {
// two bytes
int secondByte = bits.readBits(8);
return ((firstByte & 0x3F) << 8) | secondByte;
}
if ((firstByte & 0xE0) == 0xC0) {
// three bytes
int secondThirdBytes = bits.readBits(16);
return ((firstByte & 0x1F) << 16) | secondThirdBytes;
}
throw FormatException.getFormatInstance();
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright 2007 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.decoder;
import com.google.zxing.ChecksumException;
import com.google.zxing.DecodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DecoderResult;
import com.google.zxing.common.reedsolomon.GenericGF;
import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
import com.google.zxing.common.reedsolomon.ReedSolomonException;
import java.util.Map;
/**
* <p>The main class which implements QR Code decoding -- as opposed to locating and extracting
* the QR Code from an image.</p>
*
* @author Sean Owen
*/
public final class Decoder {
private final ReedSolomonDecoder rsDecoder;
public Decoder() {
rsDecoder = new ReedSolomonDecoder(GenericGF.QR_CODE_FIELD_256);
}
public DecoderResult decode(boolean[][] image) throws ChecksumException, FormatException {
return decode(image, null);
}
/**
* <p>Convenience method that can decode a QR Code represented as a 2D array of booleans.
* "true" is taken to mean a black module.</p>
*
* @param image booleans representing white/black QR Code modules
* @param hints decoding hints that should be used to influence decoding
* @return text and bytes encoded within the QR Code
* @throws FormatException if the QR Code cannot be decoded
* @throws ChecksumException if error correction fails
*/
public DecoderResult decode(boolean[][] image, Map<DecodeHintType,?> hints)
throws ChecksumException, FormatException {
return decode(BitMatrix.parse(image), hints);
}
public DecoderResult decode(BitMatrix bits) throws ChecksumException, FormatException {
return decode(bits, null);
}
/**
* <p>Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.</p>
*
* @param bits booleans representing white/black QR Code modules
* @param hints decoding hints that should be used to influence decoding
* @return text and bytes encoded within the QR Code
* @throws FormatException if the QR Code cannot be decoded
* @throws ChecksumException if error correction fails
*/
public DecoderResult decode(BitMatrix bits, Map<DecodeHintType,?> hints)
throws FormatException, ChecksumException {
// Construct a parser and read version, error-correction level
BitMatrixParser parser = new BitMatrixParser(bits);
FormatException fe = null;
ChecksumException ce = null;
try {
return decode(parser, hints);
} catch (FormatException e) {
fe = e;
} catch (ChecksumException e) {
ce = e;
}
try {
// Revert the bit matrix
parser.remask();
// Will be attempting a mirrored reading of the version and format info.
parser.setMirror(true);
// Preemptively read the version.
parser.readVersion();
// Preemptively read the format information.
parser.readFormatInformation();
/*
* Since we're here, this means we have successfully detected some kind
* of version and format information when mirrored. This is a good sign,
* that the QR code may be mirrored, and we should try once more with a
* mirrored content.
*/
// Prepare for a mirrored reading.
parser.mirror();
DecoderResult result = decode(parser, hints);
// Success! Notify the caller that the code was mirrored.
result.setOther(new QRCodeDecoderMetaData(true));
return result;
} catch (FormatException | ChecksumException e) {
// Throw the exception from the original reading
if (fe != null) {
throw fe;
}
throw ce; // If fe is null, this can't be
}
}
private DecoderResult decode(BitMatrixParser parser, Map<DecodeHintType,?> hints)
throws FormatException, ChecksumException {
Version version = parser.readVersion();
ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel();
// Read codewords
byte[] codewords = parser.readCodewords();
// Separate into data blocks
DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel);
// Count total number of data bytes
int totalBytes = 0;
for (DataBlock dataBlock : dataBlocks) {
totalBytes += dataBlock.getNumDataCodewords();
}
byte[] resultBytes = new byte[totalBytes];
int resultOffset = 0;
// Error-correct and copy data blocks together into a stream of bytes
for (DataBlock dataBlock : dataBlocks) {
byte[] codewordBytes = dataBlock.getCodewords();
int numDataCodewords = dataBlock.getNumDataCodewords();
correctErrors(codewordBytes, numDataCodewords);
for (int i = 0; i < numDataCodewords; i++) {
resultBytes[resultOffset++] = codewordBytes[i];
}
}
// Decode the contents of that stream of bytes
return DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints);
}
/**
* <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
* correct the errors in-place using Reed-Solomon error correction.</p>
*
* @param codewordBytes data and error correction codewords
* @param numDataCodewords number of codewords that are data bytes
* @throws ChecksumException if error correction fails
*/
private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException {
int numCodewords = codewordBytes.length;
// First read into an array of ints
int[] codewordsInts = new int[numCodewords];
for (int i = 0; i < numCodewords; i++) {
codewordsInts[i] = codewordBytes[i] & 0xFF;
}
try {
rsDecoder.decode(codewordsInts, codewordBytes.length - numDataCodewords);
} catch (ReedSolomonException ignored) {
throw ChecksumException.getChecksumInstance();
}
// Copy back into array of bytes -- only need to worry about the bytes that were data
// We don't care about errors in the error-correction codewords
for (int i = 0; i < numDataCodewords; i++) {
codewordBytes[i] = (byte) codewordsInts[i];
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2007 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.decoder;
/**
* <p>See ISO 18004:2006, 6.5.1. This enum encapsulates the four error correction levels
* defined by the QR code standard.</p>
*
* @author Sean Owen
*/
public enum ErrorCorrectionLevel {
/** L = ~7% correction */
L(0x01),
/** M = ~15% correction */
M(0x00),
/** Q = ~25% correction */
Q(0x03),
/** H = ~30% correction */
H(0x02);
private static final ErrorCorrectionLevel[] FOR_BITS = {M, L, H, Q};
private final int bits;
ErrorCorrectionLevel(int bits) {
this.bits = bits;
}
public int getBits() {
return bits;
}
/**
* @param bits int containing the two bits encoding a QR Code's error correction level
* @return ErrorCorrectionLevel representing the encoded error correction level
*/
public static ErrorCorrectionLevel forBits(int bits) {
if (bits < 0 || bits >= FOR_BITS.length) {
throw new IllegalArgumentException();
}
return FOR_BITS[bits];
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright 2007 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.decoder;
/**
* <p>Encapsulates a QR Code's format information, including the data mask used and
* error correction level.</p>
*
* @author Sean Owen
* @see DataMask
* @see ErrorCorrectionLevel
*/
final class FormatInformation {
private static final int FORMAT_INFO_MASK_QR = 0x5412;
/**
* See ISO 18004:2006, Annex C, Table C.1
*/
private static final int[][] FORMAT_INFO_DECODE_LOOKUP = {
{0x5412, 0x00},
{0x5125, 0x01},
{0x5E7C, 0x02},
{0x5B4B, 0x03},
{0x45F9, 0x04},
{0x40CE, 0x05},
{0x4F97, 0x06},
{0x4AA0, 0x07},
{0x77C4, 0x08},
{0x72F3, 0x09},
{0x7DAA, 0x0A},
{0x789D, 0x0B},
{0x662F, 0x0C},
{0x6318, 0x0D},
{0x6C41, 0x0E},
{0x6976, 0x0F},
{0x1689, 0x10},
{0x13BE, 0x11},
{0x1CE7, 0x12},
{0x19D0, 0x13},
{0x0762, 0x14},
{0x0255, 0x15},
{0x0D0C, 0x16},
{0x083B, 0x17},
{0x355F, 0x18},
{0x3068, 0x19},
{0x3F31, 0x1A},
{0x3A06, 0x1B},
{0x24B4, 0x1C},
{0x2183, 0x1D},
{0x2EDA, 0x1E},
{0x2BED, 0x1F},
};
private final ErrorCorrectionLevel errorCorrectionLevel;
private final byte dataMask;
private FormatInformation(int formatInfo) {
// Bits 3,4
errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03);
// Bottom 3 bits
dataMask = (byte) (formatInfo & 0x07);
}
static int numBitsDiffering(int a, int b) {
return Integer.bitCount(a ^ b);
}
/**
* @param maskedFormatInfo1 format info indicator, with mask still applied
* @param maskedFormatInfo2 second copy of same info; both are checked at the same time
* to establish best match
* @return information about the format it specifies, or {@code null}
* if doesn't seem to match any known pattern
*/
static FormatInformation decodeFormatInformation(int maskedFormatInfo1, int maskedFormatInfo2) {
FormatInformation formatInfo = doDecodeFormatInformation(maskedFormatInfo1, maskedFormatInfo2);
if (formatInfo != null) {
return formatInfo;
}
// Should return null, but, some QR codes apparently
// do not mask this info. Try again by actually masking the pattern
// first
return doDecodeFormatInformation(maskedFormatInfo1 ^ FORMAT_INFO_MASK_QR,
maskedFormatInfo2 ^ FORMAT_INFO_MASK_QR);
}
private static FormatInformation doDecodeFormatInformation(int maskedFormatInfo1, int maskedFormatInfo2) {
// Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing
int bestDifference = Integer.MAX_VALUE;
int bestFormatInfo = 0;
for (int[] decodeInfo : FORMAT_INFO_DECODE_LOOKUP) {
int targetInfo = decodeInfo[0];
if (targetInfo == maskedFormatInfo1 || targetInfo == maskedFormatInfo2) {
// Found an exact match
return new FormatInformation(decodeInfo[1]);
}
int bitsDifference = numBitsDiffering(maskedFormatInfo1, targetInfo);
if (bitsDifference < bestDifference) {
bestFormatInfo = decodeInfo[1];
bestDifference = bitsDifference;
}
if (maskedFormatInfo1 != maskedFormatInfo2) {
// also try the other option
bitsDifference = numBitsDiffering(maskedFormatInfo2, targetInfo);
if (bitsDifference < bestDifference) {
bestFormatInfo = decodeInfo[1];
bestDifference = bitsDifference;
}
}
}
// Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits
// differing means we found a match
if (bestDifference <= 3) {
return new FormatInformation(bestFormatInfo);
}
return null;
}
ErrorCorrectionLevel getErrorCorrectionLevel() {
return errorCorrectionLevel;
}
byte getDataMask() {
return dataMask;
}
@Override
public int hashCode() {
return (errorCorrectionLevel.ordinal() << 3) | dataMask;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof FormatInformation)) {
return false;
}
FormatInformation other = (FormatInformation) o;
return this.errorCorrectionLevel == other.errorCorrectionLevel &&
this.dataMask == other.dataMask;
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2007 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.decoder;
/**
* <p>See ISO 18004:2006, 6.4.1, Tables 2 and 3. This enum encapsulates the various modes in which
* data can be encoded to bits in the QR code standard.</p>
*
* @author Sean Owen
*/
public enum Mode {
TERMINATOR(new int[]{0, 0, 0}, 0x00), // Not really a mode...
NUMERIC(new int[]{10, 12, 14}, 0x01),
ALPHANUMERIC(new int[]{9, 11, 13}, 0x02),
STRUCTURED_APPEND(new int[]{0, 0, 0}, 0x03), // Not supported
BYTE(new int[]{8, 16, 16}, 0x04),
ECI(new int[]{0, 0, 0}, 0x07), // character counts don't apply
KANJI(new int[]{8, 10, 12}, 0x08),
FNC1_FIRST_POSITION(new int[]{0, 0, 0}, 0x05),
FNC1_SECOND_POSITION(new int[]{0, 0, 0}, 0x09),
/** See GBT 18284-2000; "Hanzi" is a transliteration of this mode name. */
HANZI(new int[]{8, 10, 12}, 0x0D);
private final int[] characterCountBitsForVersions;
private final int bits;
Mode(int[] characterCountBitsForVersions, int bits) {
this.characterCountBitsForVersions = characterCountBitsForVersions;
this.bits = bits;
}
/**
* @param bits four bits encoding a QR Code data mode
* @return Mode encoded by these bits
* @throws IllegalArgumentException if bits do not correspond to a known mode
*/
public static Mode forBits(int bits) {
switch (bits) {
case 0x0:
return TERMINATOR;
case 0x1:
return NUMERIC;
case 0x2:
return ALPHANUMERIC;
case 0x3:
return STRUCTURED_APPEND;
case 0x4:
return BYTE;
case 0x5:
return FNC1_FIRST_POSITION;
case 0x7:
return ECI;
case 0x8:
return KANJI;
case 0x9:
return FNC1_SECOND_POSITION;
case 0xD:
// 0xD is defined in GBT 18284-2000, may not be supported in foreign country
return HANZI;
default:
throw new IllegalArgumentException();
}
}
/**
* @param version version in question
* @return number of bits used, in this QR Code symbol {@link Version}, to encode the
* count of characters that will follow encoded in this Mode
*/
public int getCharacterCountBits(Version version) {
int number = version.getVersionNumber();
int offset;
if (number <= 9) {
offset = 0;
} else if (number <= 26) {
offset = 1;
} else {
offset = 2;
}
return characterCountBitsForVersions[offset];
}
public int getBits() {
return bits;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2013 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.decoder;
import com.google.zxing.ResultPoint;
/**
* Meta-data container for QR Code decoding. Instances of this class may be used to convey information back to the
* decoding caller. Callers are expected to process this.
*
* @see com.google.zxing.common.DecoderResult#getOther()
*/
public final class QRCodeDecoderMetaData {
private final boolean mirrored;
QRCodeDecoderMetaData(boolean mirrored) {
this.mirrored = mirrored;
}
/**
* @return true if the QR Code was mirrored.
*/
public boolean isMirrored() {
return mirrored;
}
/**
* Apply the result points' order correction due to mirroring.
*
* @param points Array of points to apply mirror correction to.
*/
public void applyMirroredCorrection(ResultPoint[] points) {
if (!mirrored || points == null || points.length < 3) {
return;
}
ResultPoint bottomLeft = points[0];
points[0] = points[2];
points[2] = bottomLeft;
// No need to 'fix' top-left and alignment pattern.
}
}

View File

@@ -0,0 +1,577 @@
/*
* Copyright 2007 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.decoder;
import com.google.zxing.FormatException;
import com.google.zxing.common.BitMatrix;
/**
* See ISO 18004:2006 Annex D
*
* @author Sean Owen
*/
public final class Version {
/**
* See ISO 18004:2006 Annex D.
* Element i represents the raw version bits that specify version i + 7
*/
private static final int[] VERSION_DECODE_INFO = {
0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6,
0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78,
0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683,
0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB,
0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250,
0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B,
0x2542E, 0x26A64, 0x27541, 0x28C69
};
private static final Version[] VERSIONS = buildVersions();
private final int versionNumber;
private final int[] alignmentPatternCenters;
private final ECBlocks[] ecBlocks;
private final int totalCodewords;
private Version(int versionNumber,
int[] alignmentPatternCenters,
ECBlocks... ecBlocks) {
this.versionNumber = versionNumber;
this.alignmentPatternCenters = alignmentPatternCenters;
this.ecBlocks = ecBlocks;
int total = 0;
int ecCodewords = ecBlocks[0].getECCodewordsPerBlock();
ECB[] ecbArray = ecBlocks[0].getECBlocks();
for (ECB ecBlock : ecbArray) {
total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords);
}
this.totalCodewords = total;
}
public int getVersionNumber() {
return versionNumber;
}
public int[] getAlignmentPatternCenters() {
return alignmentPatternCenters;
}
public int getTotalCodewords() {
return totalCodewords;
}
public int getDimensionForVersion() {
return 17 + 4 * versionNumber;
}
public ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) {
return ecBlocks[ecLevel.ordinal()];
}
/**
* <p>Deduces version information purely from QR Code dimensions.</p>
*
* @param dimension dimension in modules
* @return Version for a QR Code of that dimension
* @throws FormatException if dimension is not 1 mod 4
*/
public static Version getProvisionalVersionForDimension(int dimension) throws FormatException {
if (dimension % 4 != 1) {
throw FormatException.getFormatInstance();
}
try {
return getVersionForNumber((dimension - 17) / 4);
} catch (IllegalArgumentException ignored) {
throw FormatException.getFormatInstance();
}
}
public static Version getVersionForNumber(int versionNumber) {
if (versionNumber < 1 || versionNumber > 40) {
throw new IllegalArgumentException();
}
return VERSIONS[versionNumber - 1];
}
static Version decodeVersionInformation(int versionBits) {
int bestDifference = Integer.MAX_VALUE;
int bestVersion = 0;
for (int i = 0; i < VERSION_DECODE_INFO.length; i++) {
int targetVersion = VERSION_DECODE_INFO[i];
// Do the version info bits match exactly? done.
if (targetVersion == versionBits) {
return getVersionForNumber(i + 7);
}
// Otherwise see if this is the closest to a real version info bit string
// we have seen so far
int bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion);
if (bitsDifference < bestDifference) {
bestVersion = i + 7;
bestDifference = bitsDifference;
}
}
// We can tolerate up to 3 bits of error since no two version info codewords will
// differ in less than 8 bits.
if (bestDifference <= 3) {
return getVersionForNumber(bestVersion);
}
// If we didn't find a close enough match, fail
return null;
}
/**
* See ISO 18004:2006 Annex E
*/
BitMatrix buildFunctionPattern() {
int dimension = getDimensionForVersion();
BitMatrix bitMatrix = new BitMatrix(dimension);
// Top left finder pattern + separator + format
bitMatrix.setRegion(0, 0, 9, 9);
// Top right finder pattern + separator + format
bitMatrix.setRegion(dimension - 8, 0, 8, 9);
// Bottom left finder pattern + separator + format
bitMatrix.setRegion(0, dimension - 8, 9, 8);
// Alignment patterns
int max = alignmentPatternCenters.length;
for (int x = 0; x < max; x++) {
int i = alignmentPatternCenters[x] - 2;
for (int y = 0; y < max; y++) {
if ((x != 0 || (y != 0 && y != max - 1)) && (x != max - 1 || y != 0)) {
bitMatrix.setRegion(alignmentPatternCenters[y] - 2, i, 5, 5);
}
// else no o alignment patterns near the three finder patterns
}
}
// Vertical timing pattern
bitMatrix.setRegion(6, 9, 1, dimension - 17);
// Horizontal timing pattern
bitMatrix.setRegion(9, 6, dimension - 17, 1);
if (versionNumber > 6) {
// Version info, top right
bitMatrix.setRegion(dimension - 11, 0, 3, 6);
// Version info, bottom left
bitMatrix.setRegion(0, dimension - 11, 6, 3);
}
return bitMatrix;
}
/**
* <p>Encapsulates a set of error-correction blocks in one symbol version. Most versions will
* use blocks of differing sizes within one version, so, this encapsulates the parameters for
* each set of blocks. It also holds the number of error-correction codewords per block since it
* will be the same across all blocks within one version.</p>
*/
public static final class ECBlocks {
private final int ecCodewordsPerBlock;
private final ECB[] ecBlocks;
ECBlocks(int ecCodewordsPerBlock, ECB... ecBlocks) {
this.ecCodewordsPerBlock = ecCodewordsPerBlock;
this.ecBlocks = ecBlocks;
}
public int getECCodewordsPerBlock() {
return ecCodewordsPerBlock;
}
public int getNumBlocks() {
int total = 0;
for (ECB ecBlock : ecBlocks) {
total += ecBlock.getCount();
}
return total;
}
public int getTotalECCodewords() {
return ecCodewordsPerBlock * getNumBlocks();
}
public ECB[] getECBlocks() {
return ecBlocks;
}
}
/**
* <p>Encapsulates the parameters for one error-correction block in one symbol version.
* This includes the number of data codewords, and the number of times a block with these
* parameters is used consecutively in the QR code version's format.</p>
*/
public static final class ECB {
private final int count;
private final int dataCodewords;
ECB(int count, int dataCodewords) {
this.count = count;
this.dataCodewords = dataCodewords;
}
public int getCount() {
return count;
}
public int getDataCodewords() {
return dataCodewords;
}
}
@Override
public String toString() {
return String.valueOf(versionNumber);
}
/**
* See ISO 18004:2006 6.5.1 Table 9
*/
private static Version[] buildVersions() {
return new Version[]{
new Version(1, new int[]{},
new ECBlocks(7, new ECB(1, 19)),
new ECBlocks(10, new ECB(1, 16)),
new ECBlocks(13, new ECB(1, 13)),
new ECBlocks(17, new ECB(1, 9))),
new Version(2, new int[]{6, 18},
new ECBlocks(10, new ECB(1, 34)),
new ECBlocks(16, new ECB(1, 28)),
new ECBlocks(22, new ECB(1, 22)),
new ECBlocks(28, new ECB(1, 16))),
new Version(3, new int[]{6, 22},
new ECBlocks(15, new ECB(1, 55)),
new ECBlocks(26, new ECB(1, 44)),
new ECBlocks(18, new ECB(2, 17)),
new ECBlocks(22, new ECB(2, 13))),
new Version(4, new int[]{6, 26},
new ECBlocks(20, new ECB(1, 80)),
new ECBlocks(18, new ECB(2, 32)),
new ECBlocks(26, new ECB(2, 24)),
new ECBlocks(16, new ECB(4, 9))),
new Version(5, new int[]{6, 30},
new ECBlocks(26, new ECB(1, 108)),
new ECBlocks(24, new ECB(2, 43)),
new ECBlocks(18, new ECB(2, 15),
new ECB(2, 16)),
new ECBlocks(22, new ECB(2, 11),
new ECB(2, 12))),
new Version(6, new int[]{6, 34},
new ECBlocks(18, new ECB(2, 68)),
new ECBlocks(16, new ECB(4, 27)),
new ECBlocks(24, new ECB(4, 19)),
new ECBlocks(28, new ECB(4, 15))),
new Version(7, new int[]{6, 22, 38},
new ECBlocks(20, new ECB(2, 78)),
new ECBlocks(18, new ECB(4, 31)),
new ECBlocks(18, new ECB(2, 14),
new ECB(4, 15)),
new ECBlocks(26, new ECB(4, 13),
new ECB(1, 14))),
new Version(8, new int[]{6, 24, 42},
new ECBlocks(24, new ECB(2, 97)),
new ECBlocks(22, new ECB(2, 38),
new ECB(2, 39)),
new ECBlocks(22, new ECB(4, 18),
new ECB(2, 19)),
new ECBlocks(26, new ECB(4, 14),
new ECB(2, 15))),
new Version(9, new int[]{6, 26, 46},
new ECBlocks(30, new ECB(2, 116)),
new ECBlocks(22, new ECB(3, 36),
new ECB(2, 37)),
new ECBlocks(20, new ECB(4, 16),
new ECB(4, 17)),
new ECBlocks(24, new ECB(4, 12),
new ECB(4, 13))),
new Version(10, new int[]{6, 28, 50},
new ECBlocks(18, new ECB(2, 68),
new ECB(2, 69)),
new ECBlocks(26, new ECB(4, 43),
new ECB(1, 44)),
new ECBlocks(24, new ECB(6, 19),
new ECB(2, 20)),
new ECBlocks(28, new ECB(6, 15),
new ECB(2, 16))),
new Version(11, new int[]{6, 30, 54},
new ECBlocks(20, new ECB(4, 81)),
new ECBlocks(30, new ECB(1, 50),
new ECB(4, 51)),
new ECBlocks(28, new ECB(4, 22),
new ECB(4, 23)),
new ECBlocks(24, new ECB(3, 12),
new ECB(8, 13))),
new Version(12, new int[]{6, 32, 58},
new ECBlocks(24, new ECB(2, 92),
new ECB(2, 93)),
new ECBlocks(22, new ECB(6, 36),
new ECB(2, 37)),
new ECBlocks(26, new ECB(4, 20),
new ECB(6, 21)),
new ECBlocks(28, new ECB(7, 14),
new ECB(4, 15))),
new Version(13, new int[]{6, 34, 62},
new ECBlocks(26, new ECB(4, 107)),
new ECBlocks(22, new ECB(8, 37),
new ECB(1, 38)),
new ECBlocks(24, new ECB(8, 20),
new ECB(4, 21)),
new ECBlocks(22, new ECB(12, 11),
new ECB(4, 12))),
new Version(14, new int[]{6, 26, 46, 66},
new ECBlocks(30, new ECB(3, 115),
new ECB(1, 116)),
new ECBlocks(24, new ECB(4, 40),
new ECB(5, 41)),
new ECBlocks(20, new ECB(11, 16),
new ECB(5, 17)),
new ECBlocks(24, new ECB(11, 12),
new ECB(5, 13))),
new Version(15, new int[]{6, 26, 48, 70},
new ECBlocks(22, new ECB(5, 87),
new ECB(1, 88)),
new ECBlocks(24, new ECB(5, 41),
new ECB(5, 42)),
new ECBlocks(30, new ECB(5, 24),
new ECB(7, 25)),
new ECBlocks(24, new ECB(11, 12),
new ECB(7, 13))),
new Version(16, new int[]{6, 26, 50, 74},
new ECBlocks(24, new ECB(5, 98),
new ECB(1, 99)),
new ECBlocks(28, new ECB(7, 45),
new ECB(3, 46)),
new ECBlocks(24, new ECB(15, 19),
new ECB(2, 20)),
new ECBlocks(30, new ECB(3, 15),
new ECB(13, 16))),
new Version(17, new int[]{6, 30, 54, 78},
new ECBlocks(28, new ECB(1, 107),
new ECB(5, 108)),
new ECBlocks(28, new ECB(10, 46),
new ECB(1, 47)),
new ECBlocks(28, new ECB(1, 22),
new ECB(15, 23)),
new ECBlocks(28, new ECB(2, 14),
new ECB(17, 15))),
new Version(18, new int[]{6, 30, 56, 82},
new ECBlocks(30, new ECB(5, 120),
new ECB(1, 121)),
new ECBlocks(26, new ECB(9, 43),
new ECB(4, 44)),
new ECBlocks(28, new ECB(17, 22),
new ECB(1, 23)),
new ECBlocks(28, new ECB(2, 14),
new ECB(19, 15))),
new Version(19, new int[]{6, 30, 58, 86},
new ECBlocks(28, new ECB(3, 113),
new ECB(4, 114)),
new ECBlocks(26, new ECB(3, 44),
new ECB(11, 45)),
new ECBlocks(26, new ECB(17, 21),
new ECB(4, 22)),
new ECBlocks(26, new ECB(9, 13),
new ECB(16, 14))),
new Version(20, new int[]{6, 34, 62, 90},
new ECBlocks(28, new ECB(3, 107),
new ECB(5, 108)),
new ECBlocks(26, new ECB(3, 41),
new ECB(13, 42)),
new ECBlocks(30, new ECB(15, 24),
new ECB(5, 25)),
new ECBlocks(28, new ECB(15, 15),
new ECB(10, 16))),
new Version(21, new int[]{6, 28, 50, 72, 94},
new ECBlocks(28, new ECB(4, 116),
new ECB(4, 117)),
new ECBlocks(26, new ECB(17, 42)),
new ECBlocks(28, new ECB(17, 22),
new ECB(6, 23)),
new ECBlocks(30, new ECB(19, 16),
new ECB(6, 17))),
new Version(22, new int[]{6, 26, 50, 74, 98},
new ECBlocks(28, new ECB(2, 111),
new ECB(7, 112)),
new ECBlocks(28, new ECB(17, 46)),
new ECBlocks(30, new ECB(7, 24),
new ECB(16, 25)),
new ECBlocks(24, new ECB(34, 13))),
new Version(23, new int[]{6, 30, 54, 78, 102},
new ECBlocks(30, new ECB(4, 121),
new ECB(5, 122)),
new ECBlocks(28, new ECB(4, 47),
new ECB(14, 48)),
new ECBlocks(30, new ECB(11, 24),
new ECB(14, 25)),
new ECBlocks(30, new ECB(16, 15),
new ECB(14, 16))),
new Version(24, new int[]{6, 28, 54, 80, 106},
new ECBlocks(30, new ECB(6, 117),
new ECB(4, 118)),
new ECBlocks(28, new ECB(6, 45),
new ECB(14, 46)),
new ECBlocks(30, new ECB(11, 24),
new ECB(16, 25)),
new ECBlocks(30, new ECB(30, 16),
new ECB(2, 17))),
new Version(25, new int[]{6, 32, 58, 84, 110},
new ECBlocks(26, new ECB(8, 106),
new ECB(4, 107)),
new ECBlocks(28, new ECB(8, 47),
new ECB(13, 48)),
new ECBlocks(30, new ECB(7, 24),
new ECB(22, 25)),
new ECBlocks(30, new ECB(22, 15),
new ECB(13, 16))),
new Version(26, new int[]{6, 30, 58, 86, 114},
new ECBlocks(28, new ECB(10, 114),
new ECB(2, 115)),
new ECBlocks(28, new ECB(19, 46),
new ECB(4, 47)),
new ECBlocks(28, new ECB(28, 22),
new ECB(6, 23)),
new ECBlocks(30, new ECB(33, 16),
new ECB(4, 17))),
new Version(27, new int[]{6, 34, 62, 90, 118},
new ECBlocks(30, new ECB(8, 122),
new ECB(4, 123)),
new ECBlocks(28, new ECB(22, 45),
new ECB(3, 46)),
new ECBlocks(30, new ECB(8, 23),
new ECB(26, 24)),
new ECBlocks(30, new ECB(12, 15),
new ECB(28, 16))),
new Version(28, new int[]{6, 26, 50, 74, 98, 122},
new ECBlocks(30, new ECB(3, 117),
new ECB(10, 118)),
new ECBlocks(28, new ECB(3, 45),
new ECB(23, 46)),
new ECBlocks(30, new ECB(4, 24),
new ECB(31, 25)),
new ECBlocks(30, new ECB(11, 15),
new ECB(31, 16))),
new Version(29, new int[]{6, 30, 54, 78, 102, 126},
new ECBlocks(30, new ECB(7, 116),
new ECB(7, 117)),
new ECBlocks(28, new ECB(21, 45),
new ECB(7, 46)),
new ECBlocks(30, new ECB(1, 23),
new ECB(37, 24)),
new ECBlocks(30, new ECB(19, 15),
new ECB(26, 16))),
new Version(30, new int[]{6, 26, 52, 78, 104, 130},
new ECBlocks(30, new ECB(5, 115),
new ECB(10, 116)),
new ECBlocks(28, new ECB(19, 47),
new ECB(10, 48)),
new ECBlocks(30, new ECB(15, 24),
new ECB(25, 25)),
new ECBlocks(30, new ECB(23, 15),
new ECB(25, 16))),
new Version(31, new int[]{6, 30, 56, 82, 108, 134},
new ECBlocks(30, new ECB(13, 115),
new ECB(3, 116)),
new ECBlocks(28, new ECB(2, 46),
new ECB(29, 47)),
new ECBlocks(30, new ECB(42, 24),
new ECB(1, 25)),
new ECBlocks(30, new ECB(23, 15),
new ECB(28, 16))),
new Version(32, new int[]{6, 34, 60, 86, 112, 138},
new ECBlocks(30, new ECB(17, 115)),
new ECBlocks(28, new ECB(10, 46),
new ECB(23, 47)),
new ECBlocks(30, new ECB(10, 24),
new ECB(35, 25)),
new ECBlocks(30, new ECB(19, 15),
new ECB(35, 16))),
new Version(33, new int[]{6, 30, 58, 86, 114, 142},
new ECBlocks(30, new ECB(17, 115),
new ECB(1, 116)),
new ECBlocks(28, new ECB(14, 46),
new ECB(21, 47)),
new ECBlocks(30, new ECB(29, 24),
new ECB(19, 25)),
new ECBlocks(30, new ECB(11, 15),
new ECB(46, 16))),
new Version(34, new int[]{6, 34, 62, 90, 118, 146},
new ECBlocks(30, new ECB(13, 115),
new ECB(6, 116)),
new ECBlocks(28, new ECB(14, 46),
new ECB(23, 47)),
new ECBlocks(30, new ECB(44, 24),
new ECB(7, 25)),
new ECBlocks(30, new ECB(59, 16),
new ECB(1, 17))),
new Version(35, new int[]{6, 30, 54, 78, 102, 126, 150},
new ECBlocks(30, new ECB(12, 121),
new ECB(7, 122)),
new ECBlocks(28, new ECB(12, 47),
new ECB(26, 48)),
new ECBlocks(30, new ECB(39, 24),
new ECB(14, 25)),
new ECBlocks(30, new ECB(22, 15),
new ECB(41, 16))),
new Version(36, new int[]{6, 24, 50, 76, 102, 128, 154},
new ECBlocks(30, new ECB(6, 121),
new ECB(14, 122)),
new ECBlocks(28, new ECB(6, 47),
new ECB(34, 48)),
new ECBlocks(30, new ECB(46, 24),
new ECB(10, 25)),
new ECBlocks(30, new ECB(2, 15),
new ECB(64, 16))),
new Version(37, new int[]{6, 28, 54, 80, 106, 132, 158},
new ECBlocks(30, new ECB(17, 122),
new ECB(4, 123)),
new ECBlocks(28, new ECB(29, 46),
new ECB(14, 47)),
new ECBlocks(30, new ECB(49, 24),
new ECB(10, 25)),
new ECBlocks(30, new ECB(24, 15),
new ECB(46, 16))),
new Version(38, new int[]{6, 32, 58, 84, 110, 136, 162},
new ECBlocks(30, new ECB(4, 122),
new ECB(18, 123)),
new ECBlocks(28, new ECB(13, 46),
new ECB(32, 47)),
new ECBlocks(30, new ECB(48, 24),
new ECB(14, 25)),
new ECBlocks(30, new ECB(42, 15),
new ECB(32, 16))),
new Version(39, new int[]{6, 26, 54, 82, 110, 138, 166},
new ECBlocks(30, new ECB(20, 117),
new ECB(4, 118)),
new ECBlocks(28, new ECB(40, 47),
new ECB(7, 48)),
new ECBlocks(30, new ECB(43, 24),
new ECB(22, 25)),
new ECBlocks(30, new ECB(10, 15),
new ECB(67, 16))),
new Version(40, new int[]{6, 30, 58, 86, 114, 142, 170},
new ECBlocks(30, new ECB(19, 118),
new ECB(6, 119)),
new ECBlocks(28, new ECB(18, 47),
new ECB(31, 48)),
new ECBlocks(30, new ECB(34, 24),
new ECB(34, 25)),
new ECBlocks(30, new ECB(20, 15),
new ECB(61, 16)))
};
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2007 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.detector;
import com.google.zxing.ResultPoint;
/**
* <p>Encapsulates an alignment pattern, which are the smaller square patterns found in
* all but the simplest QR Codes.</p>
*
* @author Sean Owen
*/
public final class AlignmentPattern extends ResultPoint {
private final float estimatedModuleSize;
AlignmentPattern(float posX, float posY, float estimatedModuleSize) {
super(posX, posY);
this.estimatedModuleSize = estimatedModuleSize;
}
/**
* <p>Determines if this alignment pattern "about equals" an alignment pattern at the stated
* position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
*/
boolean aboutEquals(float moduleSize, float i, float j) {
if (Math.abs(i - getY()) <= moduleSize && Math.abs(j - getX()) <= moduleSize) {
float moduleSizeDiff = Math.abs(moduleSize - estimatedModuleSize);
return moduleSizeDiff <= 1.0f || moduleSizeDiff <= estimatedModuleSize;
}
return false;
}
/**
* Combines this object's current estimate of a finder pattern position and module size
* with a new estimate. It returns a new {@code FinderPattern} containing an average of the two.
*/
AlignmentPattern combineEstimate(float i, float j, float newModuleSize) {
float combinedX = (getX() + j) / 2.0f;
float combinedY = (getY() + i) / 2.0f;
float combinedModuleSize = (estimatedModuleSize + newModuleSize) / 2.0f;
return new AlignmentPattern(combinedX, combinedY, combinedModuleSize);
}
}

View File

@@ -0,0 +1,277 @@
/*
* Copyright 2007 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.detector;
import com.google.zxing.NotFoundException;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.BitMatrix;
import java.util.ArrayList;
import java.util.List;
/**
* <p>This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder
* patterns but are smaller and appear at regular intervals throughout the image.</p>
*
* <p>At the moment this only looks for the bottom-right alignment pattern.</p>
*
* <p>This is mostly a simplified copy of {@link FinderPatternFinder}. It is copied,
* pasted and stripped down here for maximum performance but does unfortunately duplicate
* some code.</p>
*
* <p>This class is thread-safe but not reentrant. Each thread must allocate its own object.</p>
*
* @author Sean Owen
*/
final class AlignmentPatternFinder {
private final BitMatrix image;
private final List<AlignmentPattern> possibleCenters;
private final int startX;
private final int startY;
private final int width;
private final int height;
private final float moduleSize;
private final int[] crossCheckStateCount;
private final ResultPointCallback resultPointCallback;
/**
* <p>Creates a finder that will look in a portion of the whole image.</p>
*
* @param image image to search
* @param startX left column from which to start searching
* @param startY top row from which to start searching
* @param width width of region to search
* @param height height of region to search
* @param moduleSize estimated module size so far
*/
AlignmentPatternFinder(BitMatrix image,
int startX,
int startY,
int width,
int height,
float moduleSize,
ResultPointCallback resultPointCallback) {
this.image = image;
this.possibleCenters = new ArrayList<>(5);
this.startX = startX;
this.startY = startY;
this.width = width;
this.height = height;
this.moduleSize = moduleSize;
this.crossCheckStateCount = new int[3];
this.resultPointCallback = resultPointCallback;
}
/**
* <p>This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since
* it's pretty performance-critical and so is written to be fast foremost.</p>
*
* @return {@link AlignmentPattern} if found
* @throws NotFoundException if not found
*/
AlignmentPattern find() throws NotFoundException {
int startX = this.startX;
int height = this.height;
int maxJ = startX + width;
int middleI = startY + (height / 2);
// We are looking for black/white/black modules in 1:1:1 ratio;
// this tracks the number of black/white/black modules seen so far
int[] stateCount = new int[3];
for (int iGen = 0; iGen < height; iGen++) {
// Search from middle outwards
int i = middleI + ((iGen & 0x01) == 0 ? (iGen + 1) / 2 : -((iGen + 1) / 2));
stateCount[0] = 0;
stateCount[1] = 0;
stateCount[2] = 0;
int j = startX;
// Burn off leading white pixels before anything else; if we start in the middle of
// a white run, it doesn't make sense to count its length, since we don't know if the
// white run continued to the left of the start point
while (j < maxJ && !image.get(j, i)) {
j++;
}
int currentState = 0;
while (j < maxJ) {
if (image.get(j, i)) {
// Black pixel
if (currentState == 1) { // Counting black pixels
stateCount[1]++;
} else { // Counting white pixels
if (currentState == 2) { // A winner?
if (foundPatternCross(stateCount)) { // Yes
AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, j);
if (confirmed != null) {
return confirmed;
}
}
stateCount[0] = stateCount[2];
stateCount[1] = 1;
stateCount[2] = 0;
currentState = 1;
} else {
stateCount[++currentState]++;
}
}
} else { // White pixel
if (currentState == 1) { // Counting black pixels
currentState++;
}
stateCount[currentState]++;
}
j++;
}
if (foundPatternCross(stateCount)) {
AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, maxJ);
if (confirmed != null) {
return confirmed;
}
}
}
// Hmm, nothing we saw was observed and confirmed twice. If we had
// any guess at all, return it.
if (!possibleCenters.isEmpty()) {
return possibleCenters.get(0);
}
throw NotFoundException.getNotFoundInstance();
}
/**
* Given a count of black/white/black pixels just seen and an end position,
* figures the location of the center of this black/white/black run.
*/
private static float centerFromEnd(int[] stateCount, int end) {
return (end - stateCount[2]) - stateCount[1] / 2.0f;
}
/**
* @param stateCount count of black/white/black pixels just read
* @return true iff the proportions of the counts is close enough to the 1/1/1 ratios
* used by alignment patterns to be considered a match
*/
private boolean foundPatternCross(int[] stateCount) {
float moduleSize = this.moduleSize;
float maxVariance = moduleSize / 2.0f;
for (int i = 0; i < 3; i++) {
if (Math.abs(moduleSize - stateCount[i]) >= maxVariance) {
return false;
}
}
return true;
}
/**
* <p>After a horizontal scan finds a potential alignment pattern, this method
* "cross-checks" by scanning down vertically through the center of the possible
* alignment pattern to see if the same proportion is detected.</p>
*
* @param startI row where an alignment pattern was detected
* @param centerJ center of the section that appears to cross an alignment pattern
* @param maxCount maximum reasonable number of modules that should be
* observed in any reading state, based on the results of the horizontal scan
* @return vertical center of alignment pattern, or {@link Float#NaN} if not found
*/
private float crossCheckVertical(int startI, int centerJ, int maxCount,
int originalStateCountTotal) {
BitMatrix image = this.image;
int maxI = image.getHeight();
int[] stateCount = crossCheckStateCount;
stateCount[0] = 0;
stateCount[1] = 0;
stateCount[2] = 0;
// Start counting up from center
int i = startI;
while (i >= 0 && image.get(centerJ, i) && stateCount[1] <= maxCount) {
stateCount[1]++;
i--;
}
// If already too many modules in this state or ran off the edge:
if (i < 0 || stateCount[1] > maxCount) {
return Float.NaN;
}
while (i >= 0 && !image.get(centerJ, i) && stateCount[0] <= maxCount) {
stateCount[0]++;
i--;
}
if (stateCount[0] > maxCount) {
return Float.NaN;
}
// Now also count down from center
i = startI + 1;
while (i < maxI && image.get(centerJ, i) && stateCount[1] <= maxCount) {
stateCount[1]++;
i++;
}
if (i == maxI || stateCount[1] > maxCount) {
return Float.NaN;
}
while (i < maxI && !image.get(centerJ, i) && stateCount[2] <= maxCount) {
stateCount[2]++;
i++;
}
if (stateCount[2] > maxCount) {
return Float.NaN;
}
int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
return Float.NaN;
}
return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN;
}
/**
* <p>This is called when a horizontal scan finds a possible alignment pattern. It will
* cross check with a vertical scan, and if successful, will see if this pattern had been
* found on a previous horizontal scan. If so, we consider it confirmed and conclude we have
* found the alignment pattern.</p>
*
* @param stateCount reading state module counts from horizontal scan
* @param i row where alignment pattern may be found
* @param j end of possible alignment pattern in row
* @return {@link AlignmentPattern} if we have found the same pattern twice, or null if not
*/
private AlignmentPattern handlePossibleCenter(int[] stateCount, int i, int j) {
int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
float centerJ = centerFromEnd(stateCount, j);
float centerI = crossCheckVertical(i, (int) centerJ, 2 * stateCount[1], stateCountTotal);
if (!Float.isNaN(centerI)) {
float estimatedModuleSize = (stateCount[0] + stateCount[1] + stateCount[2]) / 3.0f;
for (AlignmentPattern center : possibleCenters) {
// Look for about the same center and module size:
if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
return center.combineEstimate(centerI, centerJ, estimatedModuleSize);
}
}
// Hadn't found this before; save it
AlignmentPattern point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
possibleCenters.add(point);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(point);
}
}
return null;
}
}

View File

@@ -0,0 +1,405 @@
/*
* Copyright 2007 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.detector;
import com.google.zxing.DecodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.NotFoundException;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DetectorResult;
import com.google.zxing.common.GridSampler;
import com.google.zxing.common.PerspectiveTransform;
import com.google.zxing.common.detector.MathUtils;
import com.google.zxing.qrcode.decoder.Version;
import java.util.Map;
/**
* <p>Encapsulates logic that can detect a QR Code in an image, even if the QR Code
* is rotated or skewed, or partially obscured.</p>
*
* @author Sean Owen
*/
public class Detector {
private final BitMatrix image;
private ResultPointCallback resultPointCallback;
public Detector(BitMatrix image) {
this.image = image;
}
protected final BitMatrix getImage() {
return image;
}
protected final ResultPointCallback getResultPointCallback() {
return resultPointCallback;
}
/**
* <p>Detects a QR Code in an image.</p>
*
* @return {@link DetectorResult} encapsulating results of detecting a QR Code
* @throws NotFoundException if QR Code cannot be found
* @throws FormatException if a QR Code cannot be decoded
*/
public DetectorResult detect() throws NotFoundException, FormatException {
return detect(null);
}
/**
* <p>Detects a QR Code in an image.</p>
*
* @param hints optional hints to detector
* @return {@link DetectorResult} encapsulating results of detecting a QR Code
* @throws NotFoundException if QR Code cannot be found
* @throws FormatException if a QR Code cannot be decoded
*/
public final DetectorResult detect(Map<DecodeHintType,?> hints) throws NotFoundException, FormatException {
resultPointCallback = hints == null ? null :
(ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
FinderPatternFinder finder = new FinderPatternFinder(image, resultPointCallback);
FinderPatternInfo info = finder.find(hints);
return processFinderPatternInfo(info);
}
protected final DetectorResult processFinderPatternInfo(FinderPatternInfo info)
throws NotFoundException, FormatException {
FinderPattern topLeft = info.getTopLeft();
FinderPattern topRight = info.getTopRight();
FinderPattern bottomLeft = info.getBottomLeft();
float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft);
if (moduleSize < 1.0f) {
throw NotFoundException.getNotFoundInstance();
}
int dimension = computeDimension(topLeft, topRight, bottomLeft, moduleSize);
Version provisionalVersion = Version.getProvisionalVersionForDimension(dimension);
int modulesBetweenFPCenters = provisionalVersion.getDimensionForVersion() - 7;
AlignmentPattern alignmentPattern = null;
// Anything above version 1 has an alignment pattern
if (provisionalVersion.getAlignmentPatternCenters().length > 0) {
// Guess where a "bottom right" finder pattern would have been
float bottomRightX = topRight.getX() - topLeft.getX() + bottomLeft.getX();
float bottomRightY = topRight.getY() - topLeft.getY() + bottomLeft.getY();
// Estimate that alignment pattern is closer by 3 modules
// from "bottom right" to known top left location
float correctionToTopLeft = 1.0f - 3.0f / modulesBetweenFPCenters;
int estAlignmentX = (int) (topLeft.getX() + correctionToTopLeft * (bottomRightX - topLeft.getX()));
int estAlignmentY = (int) (topLeft.getY() + correctionToTopLeft * (bottomRightY - topLeft.getY()));
// Kind of arbitrary -- expand search radius before giving up
for (int i = 4; i <= 16; i <<= 1) {
try {
alignmentPattern = findAlignmentInRegion(moduleSize,
estAlignmentX,
estAlignmentY,
i);
break;
} catch (NotFoundException re) {
// try next round
}
}
// If we didn't find alignment pattern... well try anyway without it
}
PerspectiveTransform transform =
createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension);
BitMatrix bits = sampleGrid(image, transform, dimension);
ResultPoint[] points;
if (alignmentPattern == null) {
points = new ResultPoint[]{bottomLeft, topLeft, topRight};
} else {
points = new ResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern};
}
return new DetectorResult(bits, points);
}
private static PerspectiveTransform createTransform(ResultPoint topLeft,
ResultPoint topRight,
ResultPoint bottomLeft,
ResultPoint alignmentPattern,
int dimension) {
float dimMinusThree = dimension - 3.5f;
float bottomRightX;
float bottomRightY;
float sourceBottomRightX;
float sourceBottomRightY;
if (alignmentPattern != null) {
bottomRightX = alignmentPattern.getX();
bottomRightY = alignmentPattern.getY();
sourceBottomRightX = dimMinusThree - 3.0f;
sourceBottomRightY = sourceBottomRightX;
} else {
// Don't have an alignment pattern, just make up the bottom-right point
bottomRightX = (topRight.getX() - topLeft.getX()) + bottomLeft.getX();
bottomRightY = (topRight.getY() - topLeft.getY()) + bottomLeft.getY();
sourceBottomRightX = dimMinusThree;
sourceBottomRightY = dimMinusThree;
}
return PerspectiveTransform.quadrilateralToQuadrilateral(
3.5f,
3.5f,
dimMinusThree,
3.5f,
sourceBottomRightX,
sourceBottomRightY,
3.5f,
dimMinusThree,
topLeft.getX(),
topLeft.getY(),
topRight.getX(),
topRight.getY(),
bottomRightX,
bottomRightY,
bottomLeft.getX(),
bottomLeft.getY());
}
private static BitMatrix sampleGrid(BitMatrix image,
PerspectiveTransform transform,
int dimension) throws NotFoundException {
GridSampler sampler = GridSampler.getInstance();
return sampler.sampleGrid(image, dimension, dimension, transform);
}
/**
* <p>Computes the dimension (number of modules on a size) of the QR Code based on the position
* of the finder patterns and estimated module size.</p>
*/
private static int computeDimension(ResultPoint topLeft,
ResultPoint topRight,
ResultPoint bottomLeft,
float moduleSize) throws NotFoundException {
int tltrCentersDimension = MathUtils.round(ResultPoint.distance(topLeft, topRight) / moduleSize);
int tlblCentersDimension = MathUtils.round(ResultPoint.distance(topLeft, bottomLeft) / moduleSize);
int dimension = ((tltrCentersDimension + tlblCentersDimension) / 2) + 7;
switch (dimension & 0x03) { // mod 4
case 0:
dimension++;
break;
// 1? do nothing
case 2:
dimension--;
break;
case 3:
throw NotFoundException.getNotFoundInstance();
}
return dimension;
}
/**
* <p>Computes an average estimated module size based on estimated derived from the positions
* of the three finder patterns.</p>
*
* @param topLeft detected top-left finder pattern center
* @param topRight detected top-right finder pattern center
* @param bottomLeft detected bottom-left finder pattern center
* @return estimated module size
*/
protected final float calculateModuleSize(ResultPoint topLeft,
ResultPoint topRight,
ResultPoint bottomLeft) {
// Take the average
return (calculateModuleSizeOneWay(topLeft, topRight) +
calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f;
}
/**
* <p>Estimates module size based on two finder patterns -- it uses
* {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the
* width of each, measuring along the axis between their centers.</p>
*/
private float calculateModuleSizeOneWay(ResultPoint pattern, ResultPoint otherPattern) {
float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays((int) pattern.getX(),
(int) pattern.getY(),
(int) otherPattern.getX(),
(int) otherPattern.getY());
float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays((int) otherPattern.getX(),
(int) otherPattern.getY(),
(int) pattern.getX(),
(int) pattern.getY());
if (Float.isNaN(moduleSizeEst1)) {
return moduleSizeEst2 / 7.0f;
}
if (Float.isNaN(moduleSizeEst2)) {
return moduleSizeEst1 / 7.0f;
}
// Average them, and divide by 7 since we've counted the width of 3 black modules,
// and 1 white and 1 black module on either side. Ergo, divide sum by 14.
return (moduleSizeEst1 + moduleSizeEst2) / 14.0f;
}
/**
* See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of
* a finder pattern by looking for a black-white-black run from the center in the direction
* of another point (another finder pattern center), and in the opposite direction too.
*/
private float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY) {
float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);
// Now count other way -- don't run off image though of course
float scale = 1.0f;
int otherToX = fromX - (toX - fromX);
if (otherToX < 0) {
scale = fromX / (float) (fromX - otherToX);
otherToX = 0;
} else if (otherToX >= image.getWidth()) {
scale = (image.getWidth() - 1 - fromX) / (float) (otherToX - fromX);
otherToX = image.getWidth() - 1;
}
int otherToY = (int) (fromY - (toY - fromY) * scale);
scale = 1.0f;
if (otherToY < 0) {
scale = fromY / (float) (fromY - otherToY);
otherToY = 0;
} else if (otherToY >= image.getHeight()) {
scale = (image.getHeight() - 1 - fromY) / (float) (otherToY - fromY);
otherToY = image.getHeight() - 1;
}
otherToX = (int) (fromX + (otherToX - fromX) * scale);
result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY);
// Middle pixel is double-counted this way; subtract 1
return result - 1.0f;
}
/**
* <p>This method traces a line from a point in the image, in the direction towards another point.
* It begins in a black region, and keeps going until it finds white, then black, then white again.
* It reports the distance from the start to this point.</p>
*
* <p>This is used when figuring out how wide a finder pattern is, when the finder pattern
* may be skewed or rotated.</p>
*/
private float sizeOfBlackWhiteBlackRun(int fromX, int fromY, int toX, int toY) {
// Mild variant of Bresenham's algorithm;
// see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
boolean steep = Math.abs(toY - fromY) > Math.abs(toX - fromX);
if (steep) {
int temp = fromX;
fromX = fromY;
fromY = temp;
temp = toX;
toX = toY;
toY = temp;
}
int dx = Math.abs(toX - fromX);
int dy = Math.abs(toY - fromY);
int error = -dx / 2;
int xstep = fromX < toX ? 1 : -1;
int ystep = fromY < toY ? 1 : -1;
// In black pixels, looking for white, first or second time.
int state = 0;
// Loop up until x == toX, but not beyond
int xLimit = toX + xstep;
for (int x = fromX, y = fromY; x != xLimit; x += xstep) {
int realX = steep ? y : x;
int realY = steep ? x : y;
// Does current pixel mean we have moved white to black or vice versa?
// Scanning black in state 0,2 and white in state 1, so if we find the wrong
// color, advance to next state or end if we are in state 2 already
if ((state == 1) == image.get(realX, realY)) {
if (state == 2) {
return MathUtils.distance(x, y, fromX, fromY);
}
state++;
}
error += dy;
if (error > 0) {
if (y == toY) {
break;
}
y += ystep;
error -= dx;
}
}
// Found black-white-black; give the benefit of the doubt that the next pixel outside the image
// is "white" so this last point at (toX+xStep,toY) is the right ending. This is really a
// small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this.
if (state == 2) {
return MathUtils.distance(toX + xstep, toY, fromX, fromY);
}
// else we didn't find even black-white-black; no estimate is really possible
return Float.NaN;
}
/**
* <p>Attempts to locate an alignment pattern in a limited region of the image, which is
* guessed to contain it. This method uses {@link AlignmentPattern}.</p>
*
* @param overallEstModuleSize estimated module size so far
* @param estAlignmentX x coordinate of center of area probably containing alignment pattern
* @param estAlignmentY y coordinate of above
* @param allowanceFactor number of pixels in all directions to search from the center
* @return {@link AlignmentPattern} if found, or null otherwise
* @throws NotFoundException if an unexpected error occurs during detection
*/
protected final AlignmentPattern findAlignmentInRegion(float overallEstModuleSize,
int estAlignmentX,
int estAlignmentY,
float allowanceFactor)
throws NotFoundException {
// Look for an alignment pattern (3 modules in size) around where it
// should be
int allowance = (int) (allowanceFactor * overallEstModuleSize);
int alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance);
int alignmentAreaRightX = Math.min(image.getWidth() - 1, estAlignmentX + allowance);
if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) {
throw NotFoundException.getNotFoundInstance();
}
int alignmentAreaTopY = Math.max(0, estAlignmentY - allowance);
int alignmentAreaBottomY = Math.min(image.getHeight() - 1, estAlignmentY + allowance);
if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) {
throw NotFoundException.getNotFoundInstance();
}
AlignmentPatternFinder alignmentFinder =
new AlignmentPatternFinder(
image,
alignmentAreaLeftX,
alignmentAreaTopY,
alignmentAreaRightX - alignmentAreaLeftX,
alignmentAreaBottomY - alignmentAreaTopY,
overallEstModuleSize,
resultPointCallback);
return alignmentFinder.find();
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2007 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.detector;
import com.google.zxing.ResultPoint;
/**
* <p>Encapsulates a finder pattern, which are the three square patterns found in
* the corners of QR Codes. It also encapsulates a count of similar finder patterns,
* as a convenience to the finder's bookkeeping.</p>
*
* @author Sean Owen
*/
public final class FinderPattern extends ResultPoint {
private final float estimatedModuleSize;
private final int count;
FinderPattern(float posX, float posY, float estimatedModuleSize) {
this(posX, posY, estimatedModuleSize, 1);
}
private FinderPattern(float posX, float posY, float estimatedModuleSize, int count) {
super(posX, posY);
this.estimatedModuleSize = estimatedModuleSize;
this.count = count;
}
public float getEstimatedModuleSize() {
return estimatedModuleSize;
}
public int getCount() {
return count;
}
/**
* <p>Determines if this finder pattern "about equals" a finder pattern at the stated
* position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
*/
boolean aboutEquals(float moduleSize, float i, float j) {
if (Math.abs(i - getY()) <= moduleSize && Math.abs(j - getX()) <= moduleSize) {
float moduleSizeDiff = Math.abs(moduleSize - estimatedModuleSize);
return moduleSizeDiff <= 1.0f || moduleSizeDiff <= estimatedModuleSize;
}
return false;
}
/**
* Combines this object's current estimate of a finder pattern position and module size
* with a new estimate. It returns a new {@code FinderPattern} containing a weighted average
* based on count.
*/
FinderPattern combineEstimate(float i, float j, float newModuleSize) {
int combinedCount = count + 1;
float combinedX = (count * getX() + j) / combinedCount;
float combinedY = (count * getY() + i) / combinedCount;
float combinedModuleSize = (count * estimatedModuleSize + newModuleSize) / combinedCount;
return new FinderPattern(combinedX, combinedY, combinedModuleSize, combinedCount);
}
}

View File

@@ -0,0 +1,715 @@
/*
* Copyright 2007 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.detector;
import com.google.zxing.DecodeHintType;
import com.google.zxing.NotFoundException;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.BitMatrix;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* <p>This class attempts to find finder patterns in a QR Code. Finder patterns are the square
* markers at three corners of a QR Code.</p>
*
* <p>This class is thread-safe but not reentrant. Each thread must allocate its own object.
*
* @author Sean Owen
*/
public class FinderPatternFinder {
private static final int CENTER_QUORUM = 2;
private static final EstimatedModuleComparator moduleComparator = new EstimatedModuleComparator();
protected static final int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center
protected static final int MAX_MODULES = 97; // support up to version 20 for mobile clients
private final BitMatrix image;
private final List<FinderPattern> possibleCenters;
private boolean hasSkipped;
private final int[] crossCheckStateCount;
private final ResultPointCallback resultPointCallback;
/**
* <p>Creates a finder that will search the image for three finder patterns.</p>
*
* @param image image to search
*/
public FinderPatternFinder(BitMatrix image) {
this(image, null);
}
public FinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback) {
this.image = image;
this.possibleCenters = new ArrayList<>();
this.crossCheckStateCount = new int[5];
this.resultPointCallback = resultPointCallback;
}
protected final BitMatrix getImage() {
return image;
}
protected final List<FinderPattern> getPossibleCenters() {
return possibleCenters;
}
final FinderPatternInfo find(Map<DecodeHintType,?> hints) throws NotFoundException {
boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
int maxI = image.getHeight();
int maxJ = image.getWidth();
// We are looking for black/white/black/white/black modules in
// 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
// Let's assume that the maximum version QR Code we support takes up 1/4 the height of the
// image, and then account for the center being 3 modules in size. This gives the smallest
// number of pixels the center could be, so skip this often. When trying harder, look for all
// QR versions regardless of how dense they are.
int iSkip = (3 * maxI) / (4 * MAX_MODULES);
if (iSkip < MIN_SKIP || tryHarder) {
iSkip = MIN_SKIP;
}
boolean done = false;
int[] stateCount = new int[5];
for (int i = iSkip - 1; i < maxI && !done; i += iSkip) {
// Get a row of black/white values
doClearCounts(stateCount);
int currentState = 0;
for (int j = 0; j < maxJ; j++) {
if (image.get(j, i)) {
// Black pixel
if ((currentState & 1) == 1) { // Counting white pixels
currentState++;
}
stateCount[currentState]++;
} else { // White pixel
if ((currentState & 1) == 0) { // Counting black pixels
if (currentState == 4) { // A winner?
if (foundPatternCross(stateCount)) { // Yes
boolean confirmed = handlePossibleCenter(stateCount, i, j);
if (confirmed) {
// Start examining every other line. Checking each line turned out to be too
// expensive and didn't improve performance.
iSkip = 2;
if (hasSkipped) {
done = haveMultiplyConfirmedCenters();
} else {
int rowSkip = findRowSkip();
if (rowSkip > stateCount[2]) {
// Skip rows between row of lower confirmed center
// and top of presumed third confirmed center
// but back up a bit to get a full chance of detecting
// it, entire width of center of finder pattern
// Skip by rowSkip, but back off by stateCount[2] (size of last center
// of pattern we saw) to be conservative, and also back off by iSkip which
// is about to be re-added
i += rowSkip - stateCount[2] - iSkip;
j = maxJ - 1;
}
}
} else {
doShiftCounts2(stateCount);
currentState = 3;
continue;
}
// Clear state to start looking again
currentState = 0;
doClearCounts(stateCount);
} else { // No, shift counts back by two
doShiftCounts2(stateCount);
currentState = 3;
}
} else {
stateCount[++currentState]++;
}
} else { // Counting white pixels
stateCount[currentState]++;
}
}
}
if (foundPatternCross(stateCount)) {
boolean confirmed = handlePossibleCenter(stateCount, i, maxJ);
if (confirmed) {
iSkip = stateCount[0];
if (hasSkipped) {
// Found a third one
done = haveMultiplyConfirmedCenters();
}
}
}
}
FinderPattern[] patternInfo = selectBestPatterns();
ResultPoint.orderBestPatterns(patternInfo);
return new FinderPatternInfo(patternInfo);
}
/**
* Given a count of black/white/black/white/black pixels just seen and an end position,
* figures the location of the center of this run.
*/
private static float centerFromEnd(int[] stateCount, int end) {
return (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0f;
}
/**
* @param stateCount count of black/white/black/white/black pixels just read
* @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios
* used by finder patterns to be considered a match
*/
protected static boolean foundPatternCross(int[] stateCount) {
int totalModuleSize = 0;
for (int i = 0; i < 5; i++) {
int count = stateCount[i];
if (count == 0) {
return false;
}
totalModuleSize += count;
}
if (totalModuleSize < 7) {
return false;
}
float moduleSize = totalModuleSize / 7.0f;
float maxVariance = moduleSize / 2.0f;
// Allow less than 50% variance from 1-1-3-1-1 proportions
return
Math.abs(moduleSize - stateCount[0]) < maxVariance &&
Math.abs(moduleSize - stateCount[1]) < maxVariance &&
Math.abs(3.0f * moduleSize - stateCount[2]) < 3 * maxVariance &&
Math.abs(moduleSize - stateCount[3]) < maxVariance &&
Math.abs(moduleSize - stateCount[4]) < maxVariance;
}
/**
* @param stateCount count of black/white/black/white/black pixels just read
* @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios
* used by finder patterns to be considered a match
*/
protected static boolean foundPatternDiagonal(int[] stateCount) {
int totalModuleSize = 0;
for (int i = 0; i < 5; i++) {
int count = stateCount[i];
if (count == 0) {
return false;
}
totalModuleSize += count;
}
if (totalModuleSize < 7) {
return false;
}
float moduleSize = totalModuleSize / 7.0f;
float maxVariance = moduleSize / 1.333f;
// Allow less than 75% variance from 1-1-3-1-1 proportions
return
Math.abs(moduleSize - stateCount[0]) < maxVariance &&
Math.abs(moduleSize - stateCount[1]) < maxVariance &&
Math.abs(3.0f * moduleSize - stateCount[2]) < 3 * maxVariance &&
Math.abs(moduleSize - stateCount[3]) < maxVariance &&
Math.abs(moduleSize - stateCount[4]) < maxVariance;
}
private int[] getCrossCheckStateCount() {
doClearCounts(crossCheckStateCount);
return crossCheckStateCount;
}
@Deprecated
protected final void clearCounts(int[] counts) {
doClearCounts(counts);
}
@Deprecated
protected final void shiftCounts2(int[] stateCount) {
doShiftCounts2(stateCount);
}
protected static void doClearCounts(int[] counts) {
Arrays.fill(counts, 0);
}
protected static void doShiftCounts2(int[] stateCount) {
stateCount[0] = stateCount[2];
stateCount[1] = stateCount[3];
stateCount[2] = stateCount[4];
stateCount[3] = 1;
stateCount[4] = 0;
}
/**
* After a vertical and horizontal scan finds a potential finder pattern, this method
* "cross-cross-cross-checks" by scanning down diagonally through the center of the possible
* finder pattern to see if the same proportion is detected.
*
* @param centerI row where a finder pattern was detected
* @param centerJ center of the section that appears to cross a finder pattern
* @return true if proportions are withing expected limits
*/
private boolean crossCheckDiagonal(int centerI, int centerJ) {
int[] stateCount = getCrossCheckStateCount();
// Start counting up, left from center finding black center mass
int i = 0;
while (centerI >= i && centerJ >= i && image.get(centerJ - i, centerI - i)) {
stateCount[2]++;
i++;
}
if (stateCount[2] == 0) {
return false;
}
// Continue up, left finding white space
while (centerI >= i && centerJ >= i && !image.get(centerJ - i, centerI - i)) {
stateCount[1]++;
i++;
}
if (stateCount[1] == 0) {
return false;
}
// Continue up, left finding black border
while (centerI >= i && centerJ >= i && image.get(centerJ - i, centerI - i)) {
stateCount[0]++;
i++;
}
if (stateCount[0] == 0) {
return false;
}
int maxI = image.getHeight();
int maxJ = image.getWidth();
// Now also count down, right from center
i = 1;
while (centerI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, centerI + i)) {
stateCount[2]++;
i++;
}
while (centerI + i < maxI && centerJ + i < maxJ && !image.get(centerJ + i, centerI + i)) {
stateCount[3]++;
i++;
}
if (stateCount[3] == 0) {
return false;
}
while (centerI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, centerI + i)) {
stateCount[4]++;
i++;
}
if (stateCount[4] == 0) {
return false;
}
return foundPatternDiagonal(stateCount);
}
/**
* <p>After a horizontal scan finds a potential finder pattern, this method
* "cross-checks" by scanning down vertically through the center of the possible
* finder pattern to see if the same proportion is detected.</p>
*
* @param startI row where a finder pattern was detected
* @param centerJ center of the section that appears to cross a finder pattern
* @param maxCount maximum reasonable number of modules that should be
* observed in any reading state, based on the results of the horizontal scan
* @return vertical center of finder pattern, or {@link Float#NaN} if not found
*/
private float crossCheckVertical(int startI, int centerJ, int maxCount,
int originalStateCountTotal) {
BitMatrix image = this.image;
int maxI = image.getHeight();
int[] stateCount = getCrossCheckStateCount();
// Start counting up from center
int i = startI;
while (i >= 0 && image.get(centerJ, i)) {
stateCount[2]++;
i--;
}
if (i < 0) {
return Float.NaN;
}
while (i >= 0 && !image.get(centerJ, i) && stateCount[1] <= maxCount) {
stateCount[1]++;
i--;
}
// If already too many modules in this state or ran off the edge:
if (i < 0 || stateCount[1] > maxCount) {
return Float.NaN;
}
while (i >= 0 && image.get(centerJ, i) && stateCount[0] <= maxCount) {
stateCount[0]++;
i--;
}
if (stateCount[0] > maxCount) {
return Float.NaN;
}
// Now also count down from center
i = startI + 1;
while (i < maxI && image.get(centerJ, i)) {
stateCount[2]++;
i++;
}
if (i == maxI) {
return Float.NaN;
}
while (i < maxI && !image.get(centerJ, i) && stateCount[3] < maxCount) {
stateCount[3]++;
i++;
}
if (i == maxI || stateCount[3] >= maxCount) {
return Float.NaN;
}
while (i < maxI && image.get(centerJ, i) && stateCount[4] < maxCount) {
stateCount[4]++;
i++;
}
if (stateCount[4] >= maxCount) {
return Float.NaN;
}
// If we found a finder-pattern-like section, but its size is more than 40% different than
// the original, assume it's a false positive
int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
stateCount[4];
if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
return Float.NaN;
}
return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN;
}
/**
* <p>Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical,
* except it reads horizontally instead of vertically. This is used to cross-cross
* check a vertical cross check and locate the real center of the alignment pattern.</p>
*/
private float crossCheckHorizontal(int startJ, int centerI, int maxCount,
int originalStateCountTotal) {
BitMatrix image = this.image;
int maxJ = image.getWidth();
int[] stateCount = getCrossCheckStateCount();
int j = startJ;
while (j >= 0 && image.get(j, centerI)) {
stateCount[2]++;
j--;
}
if (j < 0) {
return Float.NaN;
}
while (j >= 0 && !image.get(j, centerI) && stateCount[1] <= maxCount) {
stateCount[1]++;
j--;
}
if (j < 0 || stateCount[1] > maxCount) {
return Float.NaN;
}
while (j >= 0 && image.get(j, centerI) && stateCount[0] <= maxCount) {
stateCount[0]++;
j--;
}
if (stateCount[0] > maxCount) {
return Float.NaN;
}
j = startJ + 1;
while (j < maxJ && image.get(j, centerI)) {
stateCount[2]++;
j++;
}
if (j == maxJ) {
return Float.NaN;
}
while (j < maxJ && !image.get(j, centerI) && stateCount[3] < maxCount) {
stateCount[3]++;
j++;
}
if (j == maxJ || stateCount[3] >= maxCount) {
return Float.NaN;
}
while (j < maxJ && image.get(j, centerI) && stateCount[4] < maxCount) {
stateCount[4]++;
j++;
}
if (stateCount[4] >= maxCount) {
return Float.NaN;
}
// If we found a finder-pattern-like section, but its size is significantly different than
// the original, assume it's a false positive
int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
stateCount[4];
if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) {
return Float.NaN;
}
return foundPatternCross(stateCount) ? centerFromEnd(stateCount, j) : Float.NaN;
}
/**
* @param stateCount reading state module counts from horizontal scan
* @param i row where finder pattern may be found
* @param j end of possible finder pattern in row
* @param pureBarcode ignored
* @return true if a finder pattern candidate was found this time
* @deprecated only exists for backwards compatibility
* @see #handlePossibleCenter(int[], int, int)
*/
@Deprecated
protected final boolean handlePossibleCenter(int[] stateCount, int i, int j, boolean pureBarcode) {
return handlePossibleCenter(stateCount, i, j);
}
/**
* <p>This is called when a horizontal scan finds a possible alignment pattern. It will
* cross check with a vertical scan, and if successful, will, ah, cross-cross-check
* with another horizontal scan. This is needed primarily to locate the real horizontal
* center of the pattern in cases of extreme skew.
* And then we cross-cross-cross check with another diagonal scan.</p>
*
* <p>If that succeeds the finder pattern location is added to a list that tracks
* the number of times each location has been nearly-matched as a finder pattern.
* Each additional find is more evidence that the location is in fact a finder
* pattern center
*
* @param stateCount reading state module counts from horizontal scan
* @param i row where finder pattern may be found
* @param j end of possible finder pattern in row
* @return true if a finder pattern candidate was found this time
*/
protected final boolean handlePossibleCenter(int[] stateCount, int i, int j) {
int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
stateCount[4];
float centerJ = centerFromEnd(stateCount, j);
float centerI = crossCheckVertical(i, (int) centerJ, stateCount[2], stateCountTotal);
if (!Float.isNaN(centerI)) {
// Re-cross check
centerJ = crossCheckHorizontal((int) centerJ, (int) centerI, stateCount[2], stateCountTotal);
if (!Float.isNaN(centerJ) && crossCheckDiagonal((int) centerI, (int) centerJ)) {
float estimatedModuleSize = stateCountTotal / 7.0f;
boolean found = false;
for (int index = 0; index < possibleCenters.size(); index++) {
FinderPattern center = possibleCenters.get(index);
// Look for about the same center and module size:
if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
possibleCenters.set(index, center.combineEstimate(centerI, centerJ, estimatedModuleSize));
found = true;
break;
}
}
if (!found) {
FinderPattern point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
possibleCenters.add(point);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(point);
}
}
return true;
}
}
return false;
}
/**
* @return number of rows we could safely skip during scanning, based on the first
* two finder patterns that have been located. In some cases their position will
* allow us to infer that the third pattern must lie below a certain point farther
* down in the image.
*/
private int findRowSkip() {
int max = possibleCenters.size();
if (max <= 1) {
return 0;
}
ResultPoint firstConfirmedCenter = null;
for (FinderPattern center : possibleCenters) {
if (center.getCount() >= CENTER_QUORUM) {
if (firstConfirmedCenter == null) {
firstConfirmedCenter = center;
} else {
// We have two confirmed centers
// How far down can we skip before resuming looking for the next
// pattern? In the worst case, only the difference between the
// difference in the x / y coordinates of the two centers.
// This is the case where you find top left last.
hasSkipped = true;
return (int) (Math.abs(firstConfirmedCenter.getX() - center.getX()) -
Math.abs(firstConfirmedCenter.getY() - center.getY())) / 2;
}
}
}
return 0;
}
/**
* @return true iff we have found at least 3 finder patterns that have been detected
* at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the
* candidates is "pretty similar"
*/
private boolean haveMultiplyConfirmedCenters() {
int confirmedCount = 0;
float totalModuleSize = 0.0f;
int max = possibleCenters.size();
for (FinderPattern pattern : possibleCenters) {
if (pattern.getCount() >= CENTER_QUORUM) {
confirmedCount++;
totalModuleSize += pattern.getEstimatedModuleSize();
}
}
if (confirmedCount < 3) {
return false;
}
// OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"
// and that we need to keep looking. We detect this by asking if the estimated module sizes
// vary too much. We arbitrarily say that when the total deviation from average exceeds
// 5% of the total module size estimates, it's too much.
float average = totalModuleSize / max;
float totalDeviation = 0.0f;
for (FinderPattern pattern : possibleCenters) {
totalDeviation += Math.abs(pattern.getEstimatedModuleSize() - average);
}
return totalDeviation <= 0.05f * totalModuleSize;
}
/**
* Get square of distance between a and b.
*/
private static double squaredDistance(FinderPattern a, FinderPattern b) {
double x = a.getX() - b.getX();
double y = a.getY() - b.getY();
return x * x + y * y;
}
/**
* @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are
* those have similar module size and form a shape closer to a isosceles right triangle.
* @throws NotFoundException if 3 such finder patterns do not exist
*/
private FinderPattern[] selectBestPatterns() throws NotFoundException {
int startSize = possibleCenters.size();
if (startSize < 3) {
// Couldn't find enough finder patterns
throw NotFoundException.getNotFoundInstance();
}
possibleCenters.sort(moduleComparator);
double distortion = Double.MAX_VALUE;
FinderPattern[] bestPatterns = new FinderPattern[3];
for (int i = 0; i < possibleCenters.size() - 2; i++) {
FinderPattern fpi = possibleCenters.get(i);
float minModuleSize = fpi.getEstimatedModuleSize();
for (int j = i + 1; j < possibleCenters.size() - 1; j++) {
FinderPattern fpj = possibleCenters.get(j);
double squares0 = squaredDistance(fpi, fpj);
for (int k = j + 1; k < possibleCenters.size(); k++) {
FinderPattern fpk = possibleCenters.get(k);
float maxModuleSize = fpk.getEstimatedModuleSize();
if (maxModuleSize > minModuleSize * 1.4f) {
// module size is not similar
continue;
}
double a = squares0;
double b = squaredDistance(fpj, fpk);
double c = squaredDistance(fpi, fpk);
// sorts ascending - inlined
if (a < b) {
if (b > c) {
if (a < c) {
double temp = b;
b = c;
c = temp;
} else {
double temp = a;
a = c;
c = b;
b = temp;
}
}
} else {
if (b < c) {
if (a < c) {
double temp = a;
a = b;
b = temp;
} else {
double temp = a;
a = b;
b = c;
c = temp;
}
} else {
double temp = a;
a = c;
c = temp;
}
}
// a^2 + b^2 = c^2 (Pythagorean theorem), and a = b (isosceles triangle).
// Since any right triangle satisfies the formula c^2 - b^2 - a^2 = 0,
// we need to check both two equal sides separately.
// The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity
// from isosceles right triangle.
double d = Math.abs(c - 2 * b) + Math.abs(c - 2 * a);
if (d < distortion) {
distortion = d;
bestPatterns[0] = fpi;
bestPatterns[1] = fpj;
bestPatterns[2] = fpk;
}
}
}
}
if (distortion == Double.MAX_VALUE) {
throw NotFoundException.getNotFoundInstance();
}
return bestPatterns;
}
/**
* <p>Orders by {@link FinderPattern#getEstimatedModuleSize()}</p>
*/
private static final class EstimatedModuleComparator implements Comparator<FinderPattern>, Serializable {
@Override
public int compare(FinderPattern center1, FinderPattern center2) {
return Float.compare(center1.getEstimatedModuleSize(), center2.getEstimatedModuleSize());
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2007 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.detector;
/**
* <p>Encapsulates information about finder patterns in an image, including the location of
* the three finder patterns, and their estimated module size.</p>
*
* @author Sean Owen
*/
public final class FinderPatternInfo {
private final FinderPattern bottomLeft;
private final FinderPattern topLeft;
private final FinderPattern topRight;
public FinderPatternInfo(FinderPattern[] patternCenters) {
this.bottomLeft = patternCenters[0];
this.topLeft = patternCenters[1];
this.topRight = patternCenters[2];
}
public FinderPattern getBottomLeft() {
return bottomLeft;
}
public FinderPattern getTopLeft() {
return topLeft;
}
public FinderPattern getTopRight() {
return topRight;
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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;
final class BlockPair {
private final byte[] dataBytes;
private final byte[] errorCorrectionBytes;
BlockPair(byte[] data, byte[] errorCorrection) {
dataBytes = data;
errorCorrectionBytes = errorCorrection;
}
public byte[] getDataBytes() {
return dataBytes;
}
public byte[] getErrorCorrectionBytes() {
return errorCorrectionBytes;
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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;
import java.util.Arrays;
/**
* JAVAPORT: The original code was a 2D array of ints, but since it only ever gets assigned
* -1, 0, and 1, I'm going to use less memory and go with bytes.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ByteMatrix {
private final byte[][] bytes;
private final int width;
private final int height;
public ByteMatrix(int width, int height) {
bytes = new byte[height][width];
this.width = width;
this.height = height;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public byte get(int x, int y) {
return bytes[y][x];
}
/**
* @return an internal representation as bytes, in row-major order. array[y][x] represents point (x,y)
*/
public byte[][] getArray() {
return bytes;
}
public void set(int x, int y, byte value) {
bytes[y][x] = value;
}
public void set(int x, int y, int value) {
bytes[y][x] = (byte) value;
}
public void set(int x, int y, boolean value) {
bytes[y][x] = (byte) (value ? 1 : 0);
}
public void clear(byte value) {
for (byte[] aByte : bytes) {
Arrays.fill(aByte, value);
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder(2 * width * height + 2);
for (int y = 0; y < height; ++y) {
byte[] bytesY = bytes[y];
for (int x = 0; x < width; ++x) {
switch (bytesY[x]) {
case 0:
result.append(" 0");
break;
case 1:
result.append(" 1");
break;
default:
result.append(" ");
break;
}
}
result.append('\n');
}
return result.toString();
}
}

View File

@@ -0,0 +1,637 @@
/*
* 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;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.StringUtils;
import com.google.zxing.common.CharacterSetECI;
import com.google.zxing.common.reedsolomon.GenericGF;
import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.decoder.Mode;
import com.google.zxing.qrcode.decoder.Version;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
/**
* @author satorux@google.com (Satoru Takabayashi) - creator
* @author dswitkin@google.com (Daniel Switkin) - ported from C++
*/
public final class Encoder {
// The original table is defined in the table 5 of JISX0510:2004 (p.19).
private static final int[] ALPHANUMERIC_TABLE = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f
36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f
-1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f
};
static final Charset DEFAULT_BYTE_MODE_ENCODING = StandardCharsets.ISO_8859_1;
private Encoder() {
}
// 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.
private static int calculateMaskPenalty(ByteMatrix matrix) {
return MaskUtil.applyMaskPenaltyRule1(matrix)
+ MaskUtil.applyMaskPenaltyRule2(matrix)
+ MaskUtil.applyMaskPenaltyRule3(matrix)
+ MaskUtil.applyMaskPenaltyRule4(matrix);
}
/**
* @param content text to encode
* @param ecLevel error correction level to use
* @return {@link QRCode} representing the encoded QR code
* @throws WriterException if encoding can't succeed, because of for example invalid content
* or configuration
*/
public static QRCode encode(String content, ErrorCorrectionLevel ecLevel) throws WriterException {
return encode(content, ecLevel, null);
}
public static QRCode encode(String content,
ErrorCorrectionLevel ecLevel,
Map<EncodeHintType,?> hints) throws WriterException {
Version version;
BitArray headerAndDataBits;
Mode mode;
boolean hasGS1FormatHint = hints != null && hints.containsKey(EncodeHintType.GS1_FORMAT) &&
Boolean.parseBoolean(hints.get(EncodeHintType.GS1_FORMAT).toString());
boolean hasCompactionHint = hints != null && hints.containsKey(EncodeHintType.QR_COMPACT) &&
Boolean.parseBoolean(hints.get(EncodeHintType.QR_COMPACT).toString());
// Determine what character encoding has been specified by the caller, if any
Charset encoding = DEFAULT_BYTE_MODE_ENCODING;
boolean hasEncodingHint = hints != null && hints.containsKey(EncodeHintType.CHARACTER_SET);
if (hasEncodingHint) {
encoding = Charset.forName(hints.get(EncodeHintType.CHARACTER_SET).toString());
}
if (hasCompactionHint) {
mode = Mode.BYTE;
Charset priorityEncoding = encoding.equals(DEFAULT_BYTE_MODE_ENCODING) ? null : encoding;
MinimalEncoder.ResultList rn = MinimalEncoder.encode(content, null, priorityEncoding, hasGS1FormatHint, ecLevel);
headerAndDataBits = new BitArray();
rn.getBits(headerAndDataBits);
version = rn.getVersion();
} else {
// Pick an encoding mode appropriate for the content. Note that this will not attempt to use
// multiple modes / segments even if that were more efficient.
mode = chooseMode(content, encoding);
// This will store the header information, like mode and
// length, as well as "header" segments like an ECI segment.
BitArray headerBits = new BitArray();
// Append ECI segment if applicable
if (mode == Mode.BYTE && hasEncodingHint) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECI(encoding);
if (eci != null) {
appendECI(eci, headerBits);
}
}
// Append the FNC1 mode header for GS1 formatted data if applicable
if (hasGS1FormatHint) {
// GS1 formatted codes are prefixed with a FNC1 in first position mode header
appendModeInfo(Mode.FNC1_FIRST_POSITION, headerBits);
}
// (With ECI in place,) Write the mode marker
appendModeInfo(mode, headerBits);
// Collect data within the main segment, separately, to count its size if needed. Don't add it to
// main payload yet.
BitArray dataBits = new BitArray();
appendBytes(content, mode, dataBits, encoding);
if (hints != null && hints.containsKey(EncodeHintType.QR_VERSION)) {
int versionNumber = Integer.parseInt(hints.get(EncodeHintType.QR_VERSION).toString());
version = Version.getVersionForNumber(versionNumber);
int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, version);
if (!willFit(bitsNeeded, version, ecLevel)) {
throw new WriterException("Data too big for requested version");
}
} else {
version = recommendVersion(ecLevel, mode, headerBits, dataBits);
}
headerAndDataBits = new BitArray();
headerAndDataBits.appendBitArray(headerBits);
// Find "length" of main segment and write it
int numLetters = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length();
appendLengthInfo(numLetters, version, mode, headerAndDataBits);
// Put data together into the overall payload
headerAndDataBits.appendBitArray(dataBits);
}
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
int numDataBytes = version.getTotalCodewords() - ecBlocks.getTotalECCodewords();
// Terminate the bits properly.
terminateBits(numDataBytes, headerAndDataBits);
// Interleave data bits with error correction code.
BitArray finalBits = interleaveWithECBytes(headerAndDataBits,
version.getTotalCodewords(),
numDataBytes,
ecBlocks.getNumBlocks());
QRCode qrCode = new QRCode();
qrCode.setECLevel(ecLevel);
qrCode.setMode(mode);
qrCode.setVersion(version);
// Choose the mask pattern and set to "qrCode".
int dimension = version.getDimensionForVersion();
ByteMatrix matrix = new ByteMatrix(dimension, dimension);
// Enable manual selection of the pattern to be used via hint
int maskPattern = -1;
if (hints != null && hints.containsKey(EncodeHintType.QR_MASK_PATTERN)) {
int hintMaskPattern = Integer.parseInt(hints.get(EncodeHintType.QR_MASK_PATTERN).toString());
maskPattern = QRCode.isValidMaskPattern(hintMaskPattern) ? hintMaskPattern : -1;
}
if (maskPattern == -1) {
maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix);
}
qrCode.setMaskPattern(maskPattern);
// Build the matrix and set it to "qrCode".
MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix);
qrCode.setMatrix(matrix);
return qrCode;
}
/**
* Decides the smallest version of QR code that will contain all of the provided data.
*
* @throws WriterException if the data cannot fit in any version
*/
private static Version recommendVersion(ErrorCorrectionLevel ecLevel,
Mode mode,
BitArray headerBits,
BitArray dataBits) throws WriterException {
// Hard part: need to know version to know how many bits length takes. But need to know how many
// bits it takes to know version. First we take a guess at version by assuming version will be
// the minimum, 1:
int provisionalBitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, Version.getVersionForNumber(1));
Version provisionalVersion = chooseVersion(provisionalBitsNeeded, ecLevel);
// Use that guess to calculate the right version. I am still not sure this works in 100% of cases.
int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, provisionalVersion);
return chooseVersion(bitsNeeded, ecLevel);
}
private static int calculateBitsNeeded(Mode mode,
BitArray headerBits,
BitArray dataBits,
Version version) {
return headerBits.getSize() + mode.getCharacterCountBits(version) + dataBits.getSize();
}
/**
* @return the code point of the table used in alphanumeric mode or
* -1 if there is no corresponding code in the table.
*/
static int getAlphanumericCode(int code) {
if (code < ALPHANUMERIC_TABLE.length) {
return ALPHANUMERIC_TABLE[code];
}
return -1;
}
public static Mode chooseMode(String content) {
return chooseMode(content, null);
}
/**
* Choose the best mode by examining the content. Note that 'encoding' is used as a hint;
* if it is Shift_JIS, and the input is only double-byte Kanji, then we return {@link Mode#KANJI}.
*/
private static Mode chooseMode(String content, Charset encoding) {
if (StringUtils.SHIFT_JIS_CHARSET.equals(encoding) && isOnlyDoubleByteKanji(content)) {
// Choose Kanji mode if all input are double-byte characters
return Mode.KANJI;
}
boolean hasNumeric = false;
boolean hasAlphanumeric = false;
for (int i = 0; i < content.length(); ++i) {
char c = content.charAt(i);
if (c >= '0' && c <= '9') {
hasNumeric = true;
} else if (getAlphanumericCode(c) != -1) {
hasAlphanumeric = true;
} else {
return Mode.BYTE;
}
}
if (hasAlphanumeric) {
return Mode.ALPHANUMERIC;
}
if (hasNumeric) {
return Mode.NUMERIC;
}
return Mode.BYTE;
}
static boolean isOnlyDoubleByteKanji(String content) {
byte[] bytes = content.getBytes(StringUtils.SHIFT_JIS_CHARSET);
int length = bytes.length;
if (length % 2 != 0) {
return false;
}
for (int i = 0; i < length; i += 2) {
int byte1 = bytes[i] & 0xFF;
if ((byte1 < 0x81 || byte1 > 0x9F) && (byte1 < 0xE0 || byte1 > 0xEB)) {
return false;
}
}
return true;
}
private static int chooseMaskPattern(BitArray bits,
ErrorCorrectionLevel ecLevel,
Version version,
ByteMatrix matrix) throws WriterException {
int minPenalty = Integer.MAX_VALUE; // Lower penalty is better.
int bestMaskPattern = -1;
// We try all mask patterns to choose the best one.
for (int maskPattern = 0; maskPattern < QRCode.NUM_MASK_PATTERNS; maskPattern++) {
MatrixUtil.buildMatrix(bits, ecLevel, version, maskPattern, matrix);
int penalty = calculateMaskPenalty(matrix);
if (penalty < minPenalty) {
minPenalty = penalty;
bestMaskPattern = maskPattern;
}
}
return bestMaskPattern;
}
private static Version chooseVersion(int numInputBits, ErrorCorrectionLevel ecLevel) throws WriterException {
for (int versionNum = 1; versionNum <= 40; versionNum++) {
Version version = Version.getVersionForNumber(versionNum);
if (willFit(numInputBits, version, ecLevel)) {
return version;
}
}
throw new WriterException("Data too big");
}
/**
* @return true if the number of input bits will fit in a code with the specified version and
* error correction level.
*/
static boolean willFit(int numInputBits, Version version, ErrorCorrectionLevel ecLevel) {
// In the following comments, we use numbers of Version 7-H.
// numBytes = 196
int numBytes = version.getTotalCodewords();
// getNumECBytes = 130
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
int numEcBytes = ecBlocks.getTotalECCodewords();
// getNumDataBytes = 196 - 130 = 66
int numDataBytes = numBytes - numEcBytes;
int totalInputBytes = (numInputBits + 7) / 8;
return numDataBytes >= totalInputBytes;
}
/**
* Terminate bits as described in 8.4.8 and 8.4.9 of JISX0510:2004 (p.24).
*/
static void terminateBits(int numDataBytes, BitArray bits) throws WriterException {
int capacity = numDataBytes * 8;
if (bits.getSize() > capacity) {
throw new WriterException("data bits cannot fit in the QR Code" + bits.getSize() + " > " +
capacity);
}
// Append Mode.TERMINATE if there is enough space (value is 0000)
for (int i = 0; i < 4 && bits.getSize() < capacity; ++i) {
bits.appendBit(false);
}
// Append termination bits. See 8.4.8 of JISX0510:2004 (p.24) for details.
// If the last byte isn't 8-bit aligned, we'll add padding bits.
int numBitsInLastByte = bits.getSize() & 0x07;
if (numBitsInLastByte > 0) {
for (int i = numBitsInLastByte; i < 8; i++) {
bits.appendBit(false);
}
}
// If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24).
int numPaddingBytes = numDataBytes - bits.getSizeInBytes();
for (int i = 0; i < numPaddingBytes; ++i) {
bits.appendBits((i & 0x01) == 0 ? 0xEC : 0x11, 8);
}
if (bits.getSize() != capacity) {
throw new WriterException("Bits size does not equal capacity");
}
}
/**
* Get number of data bytes and number of error correction bytes for block id "blockID". Store
* the result in "numDataBytesInBlock", and "numECBytesInBlock". See table 12 in 8.5.1 of
* JISX0510:2004 (p.30)
*/
static void getNumDataBytesAndNumECBytesForBlockID(int numTotalBytes,
int numDataBytes,
int numRSBlocks,
int blockID,
int[] numDataBytesInBlock,
int[] numECBytesInBlock) throws WriterException {
if (blockID >= numRSBlocks) {
throw new WriterException("Block ID too large");
}
// numRsBlocksInGroup2 = 196 % 5 = 1
int numRsBlocksInGroup2 = numTotalBytes % numRSBlocks;
// numRsBlocksInGroup1 = 5 - 1 = 4
int numRsBlocksInGroup1 = numRSBlocks - numRsBlocksInGroup2;
// numTotalBytesInGroup1 = 196 / 5 = 39
int numTotalBytesInGroup1 = numTotalBytes / numRSBlocks;
// numTotalBytesInGroup2 = 39 + 1 = 40
int numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1;
// numDataBytesInGroup1 = 66 / 5 = 13
int numDataBytesInGroup1 = numDataBytes / numRSBlocks;
// numDataBytesInGroup2 = 13 + 1 = 14
int numDataBytesInGroup2 = numDataBytesInGroup1 + 1;
// numEcBytesInGroup1 = 39 - 13 = 26
int numEcBytesInGroup1 = numTotalBytesInGroup1 - numDataBytesInGroup1;
// numEcBytesInGroup2 = 40 - 14 = 26
int numEcBytesInGroup2 = numTotalBytesInGroup2 - numDataBytesInGroup2;
// Sanity checks.
// 26 = 26
if (numEcBytesInGroup1 != numEcBytesInGroup2) {
throw new WriterException("EC bytes mismatch");
}
// 5 = 4 + 1.
if (numRSBlocks != numRsBlocksInGroup1 + numRsBlocksInGroup2) {
throw new WriterException("RS blocks mismatch");
}
// 196 = (13 + 26) * 4 + (14 + 26) * 1
if (numTotalBytes !=
((numDataBytesInGroup1 + numEcBytesInGroup1) *
numRsBlocksInGroup1) +
((numDataBytesInGroup2 + numEcBytesInGroup2) *
numRsBlocksInGroup2)) {
throw new WriterException("Total bytes mismatch");
}
if (blockID < numRsBlocksInGroup1) {
numDataBytesInBlock[0] = numDataBytesInGroup1;
numECBytesInBlock[0] = numEcBytesInGroup1;
} else {
numDataBytesInBlock[0] = numDataBytesInGroup2;
numECBytesInBlock[0] = numEcBytesInGroup2;
}
}
/**
* Interleave "bits" with corresponding error correction bytes. On success, store the result in
* "result". The interleave rule is complicated. See 8.6 of JISX0510:2004 (p.37) for details.
*/
static BitArray interleaveWithECBytes(BitArray bits,
int numTotalBytes,
int numDataBytes,
int numRSBlocks) throws WriterException {
// "bits" must have "getNumDataBytes" bytes of data.
if (bits.getSizeInBytes() != numDataBytes) {
throw new WriterException("Number of bits and data bytes does not match");
}
// Step 1. Divide data bytes into blocks and generate error correction bytes for them. We'll
// store the divided data bytes blocks and error correction bytes blocks into "blocks".
int dataBytesOffset = 0;
int maxNumDataBytes = 0;
int maxNumEcBytes = 0;
// Since, we know the number of reedsolmon blocks, we can initialize the vector with the number.
Collection<BlockPair> blocks = new ArrayList<>(numRSBlocks);
for (int i = 0; i < numRSBlocks; ++i) {
int[] numDataBytesInBlock = new int[1];
int[] numEcBytesInBlock = new int[1];
getNumDataBytesAndNumECBytesForBlockID(
numTotalBytes, numDataBytes, numRSBlocks, i,
numDataBytesInBlock, numEcBytesInBlock);
int size = numDataBytesInBlock[0];
byte[] dataBytes = new byte[size];
bits.toBytes(8 * dataBytesOffset, dataBytes, 0, size);
byte[] ecBytes = generateECBytes(dataBytes, numEcBytesInBlock[0]);
blocks.add(new BlockPair(dataBytes, ecBytes));
maxNumDataBytes = Math.max(maxNumDataBytes, size);
maxNumEcBytes = Math.max(maxNumEcBytes, ecBytes.length);
dataBytesOffset += numDataBytesInBlock[0];
}
if (numDataBytes != dataBytesOffset) {
throw new WriterException("Data bytes does not match offset");
}
BitArray result = new BitArray();
// First, place data blocks.
for (int i = 0; i < maxNumDataBytes; ++i) {
for (BlockPair block : blocks) {
byte[] dataBytes = block.getDataBytes();
if (i < dataBytes.length) {
result.appendBits(dataBytes[i], 8);
}
}
}
// Then, place error correction blocks.
for (int i = 0; i < maxNumEcBytes; ++i) {
for (BlockPair block : blocks) {
byte[] ecBytes = block.getErrorCorrectionBytes();
if (i < ecBytes.length) {
result.appendBits(ecBytes[i], 8);
}
}
}
if (numTotalBytes != result.getSizeInBytes()) { // Should be same.
throw new WriterException("Interleaving error: " + numTotalBytes + " and " +
result.getSizeInBytes() + " differ.");
}
return result;
}
static byte[] generateECBytes(byte[] dataBytes, int numEcBytesInBlock) {
int numDataBytes = dataBytes.length;
int[] toEncode = new int[numDataBytes + numEcBytesInBlock];
for (int i = 0; i < numDataBytes; i++) {
toEncode[i] = dataBytes[i] & 0xFF;
}
new ReedSolomonEncoder(GenericGF.QR_CODE_FIELD_256).encode(toEncode, numEcBytesInBlock);
byte[] ecBytes = new byte[numEcBytesInBlock];
for (int i = 0; i < numEcBytesInBlock; i++) {
ecBytes[i] = (byte) toEncode[numDataBytes + i];
}
return ecBytes;
}
/**
* Append mode info. On success, store the result in "bits".
*/
static void appendModeInfo(Mode mode, BitArray bits) {
bits.appendBits(mode.getBits(), 4);
}
/**
* Append length info. On success, store the result in "bits".
*/
static void appendLengthInfo(int numLetters, Version version, Mode mode, BitArray bits) throws WriterException {
int numBits = mode.getCharacterCountBits(version);
if (numLetters >= (1 << numBits)) {
throw new WriterException(numLetters + " is bigger than " + ((1 << numBits) - 1));
}
bits.appendBits(numLetters, numBits);
}
/**
* Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits".
*/
static void appendBytes(String content,
Mode mode,
BitArray bits,
Charset encoding) throws WriterException {
switch (mode) {
case NUMERIC:
appendNumericBytes(content, bits);
break;
case ALPHANUMERIC:
appendAlphanumericBytes(content, bits);
break;
case BYTE:
append8BitBytes(content, bits, encoding);
break;
case KANJI:
appendKanjiBytes(content, bits);
break;
default:
throw new WriterException("Invalid mode: " + mode);
}
}
static void appendNumericBytes(CharSequence content, BitArray bits) {
int length = content.length();
int i = 0;
while (i < length) {
int num1 = content.charAt(i) - '0';
if (i + 2 < length) {
// Encode three numeric letters in ten bits.
int num2 = content.charAt(i + 1) - '0';
int num3 = content.charAt(i + 2) - '0';
bits.appendBits(num1 * 100 + num2 * 10 + num3, 10);
i += 3;
} else if (i + 1 < length) {
// Encode two numeric letters in seven bits.
int num2 = content.charAt(i + 1) - '0';
bits.appendBits(num1 * 10 + num2, 7);
i += 2;
} else {
// Encode one numeric letter in four bits.
bits.appendBits(num1, 4);
i++;
}
}
}
static void appendAlphanumericBytes(CharSequence content, BitArray bits) throws WriterException {
int length = content.length();
int i = 0;
while (i < length) {
int code1 = getAlphanumericCode(content.charAt(i));
if (code1 == -1) {
throw new WriterException();
}
if (i + 1 < length) {
int code2 = getAlphanumericCode(content.charAt(i + 1));
if (code2 == -1) {
throw new WriterException();
}
// Encode two alphanumeric letters in 11 bits.
bits.appendBits(code1 * 45 + code2, 11);
i += 2;
} else {
// Encode one alphanumeric letter in six bits.
bits.appendBits(code1, 6);
i++;
}
}
}
static void append8BitBytes(String content, BitArray bits, Charset encoding) {
byte[] bytes = content.getBytes(encoding);
for (byte b : bytes) {
bits.appendBits(b, 8);
}
}
static void appendKanjiBytes(String content, BitArray bits) throws WriterException {
byte[] bytes = content.getBytes(StringUtils.SHIFT_JIS_CHARSET);
if (bytes.length % 2 != 0) {
throw new WriterException("Kanji byte size not even");
}
int maxI = bytes.length - 1; // bytes.length must be even
for (int i = 0; i < maxI; i += 2) {
int byte1 = bytes[i] & 0xFF;
int byte2 = bytes[i + 1] & 0xFF;
int code = (byte1 << 8) | byte2;
int subtracted = -1;
if (code >= 0x8140 && code <= 0x9ffc) {
subtracted = code - 0x8140;
} else if (code >= 0xe040 && code <= 0xebbf) {
subtracted = code - 0xc140;
}
if (subtracted == -1) {
throw new WriterException("Invalid byte sequence");
}
int encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff);
bits.appendBits(encoded, 13);
}
}
private static void appendECI(CharacterSetECI eci, BitArray bits) {
bits.appendBits(Mode.ECI.getBits(), 4);
// This is correct for values up to 127, which is all we need now.
bits.appendBits(eci.getValue(), 8);
}
}

View File

@@ -0,0 +1,224 @@
/*
* 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;
/**
* @author Satoru Takabayashi
* @author Daniel Switkin
* @author Sean Owen
*/
final class MaskUtil {
// Penalty weights from section 6.8.2.1
private static final int N1 = 3;
private static final int N2 = 3;
private static final int N3 = 40;
private static final int N4 = 10;
private MaskUtil() {
// do nothing
}
/**
* 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.
*/
static int applyMaskPenaltyRule1(ByteMatrix matrix) {
return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false);
}
/**
* Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
* penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a
* penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block.
*/
static int applyMaskPenaltyRule2(ByteMatrix matrix) {
int penalty = 0;
byte[][] array = matrix.getArray();
int width = matrix.getWidth();
int height = matrix.getHeight();
for (int y = 0; y < height - 1; y++) {
byte[] arrayY = array[y];
for (int x = 0; x < width - 1; x++) {
int value = arrayY[x];
if (value == arrayY[x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1]) {
penalty++;
}
}
}
return N2 * penalty;
}
/**
* Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4
* starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we
* find patterns like 000010111010000, we give penalty once.
*/
static int applyMaskPenaltyRule3(ByteMatrix matrix) {
int numPenalties = 0;
byte[][] array = matrix.getArray();
int width = matrix.getWidth();
int height = matrix.getHeight();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
byte[] arrayY = array[y]; // We can at least optimize this access
if (x + 6 < width &&
arrayY[x] == 1 &&
arrayY[x + 1] == 0 &&
arrayY[x + 2] == 1 &&
arrayY[x + 3] == 1 &&
arrayY[x + 4] == 1 &&
arrayY[x + 5] == 0 &&
arrayY[x + 6] == 1 &&
(isWhiteHorizontal(arrayY, x - 4, x) || isWhiteHorizontal(arrayY, x + 7, x + 11))) {
numPenalties++;
}
if (y + 6 < height &&
array[y][x] == 1 &&
array[y + 1][x] == 0 &&
array[y + 2][x] == 1 &&
array[y + 3][x] == 1 &&
array[y + 4][x] == 1 &&
array[y + 5][x] == 0 &&
array[y + 6][x] == 1 &&
(isWhiteVertical(array, x, y - 4, y) || isWhiteVertical(array, x, y + 7, y + 11))) {
numPenalties++;
}
}
}
return numPenalties * N3;
}
private static boolean isWhiteHorizontal(byte[] rowArray, int from, int to) {
if (from < 0 || rowArray.length < to) {
return false;
}
for (int i = from; i < to; i++) {
if (rowArray[i] == 1) {
return false;
}
}
return true;
}
private static boolean isWhiteVertical(byte[][] array, int col, int from, int to) {
if (from < 0 || array.length < to) {
return false;
}
for (int i = from; i < to; i++) {
if (array[i][col] == 1) {
return false;
}
}
return true;
}
/**
* Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give
* penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance.
*/
static int applyMaskPenaltyRule4(ByteMatrix matrix) {
int numDarkCells = 0;
byte[][] array = matrix.getArray();
int width = matrix.getWidth();
int height = matrix.getHeight();
for (int y = 0; y < height; y++) {
byte[] arrayY = array[y];
for (int x = 0; x < width; x++) {
if (arrayY[x] == 1) {
numDarkCells++;
}
}
}
int numTotalCells = matrix.getHeight() * matrix.getWidth();
int fivePercentVariances = Math.abs(numDarkCells * 2 - numTotalCells) * 10 / numTotalCells;
return fivePercentVariances * N4;
}
/**
* Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask
* pattern conditions.
*/
static boolean getDataMaskBit(int maskPattern, int x, int y) {
int intermediate;
int temp;
switch (maskPattern) {
case 0:
intermediate = (y + x) & 0x1;
break;
case 1:
intermediate = y & 0x1;
break;
case 2:
intermediate = x % 3;
break;
case 3:
intermediate = (y + x) % 3;
break;
case 4:
intermediate = ((y / 2) + (x / 3)) & 0x1;
break;
case 5:
temp = y * x;
intermediate = (temp & 0x1) + (temp % 3);
break;
case 6:
temp = y * x;
intermediate = ((temp & 0x1) + (temp % 3)) & 0x1;
break;
case 7:
temp = y * x;
intermediate = ((temp % 3) + ((y + x) & 0x1)) & 0x1;
break;
default:
throw new IllegalArgumentException("Invalid mask pattern: " + maskPattern);
}
return intermediate == 0;
}
/**
* Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both
* vertical and horizontal orders respectively.
*/
private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, boolean isHorizontal) {
int penalty = 0;
int iLimit = isHorizontal ? matrix.getHeight() : matrix.getWidth();
int jLimit = isHorizontal ? matrix.getWidth() : matrix.getHeight();
byte[][] array = matrix.getArray();
for (int i = 0; i < iLimit; i++) {
int numSameBitCells = 0;
int prevBit = -1;
for (int j = 0; j < jLimit; j++) {
int bit = isHorizontal ? array[i][j] : array[j][i];
if (bit == prevBit) {
numSameBitCells++;
} else {
if (numSameBitCells >= 5) {
penalty += N1 + (numSameBitCells - 5);
}
numSameBitCells = 1; // Include the cell itself.
prevBit = bit;
}
}
if (numSameBitCells >= 5) {
penalty += N1 + (numSameBitCells - 5);
}
}
return penalty;
}
}

View File

@@ -0,0 +1,477 @@
/*
* 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;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitArray;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.decoder.Version;
/**
* @author satorux@google.com (Satoru Takabayashi) - creator
* @author dswitkin@google.com (Daniel Switkin) - ported from C++
*/
final class MatrixUtil {
private static final int[][] POSITION_DETECTION_PATTERN = {
{1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 0, 1},
{1, 0, 1, 1, 1, 0, 1},
{1, 0, 1, 1, 1, 0, 1},
{1, 0, 1, 1, 1, 0, 1},
{1, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1},
};
private static final int[][] POSITION_ADJUSTMENT_PATTERN = {
{1, 1, 1, 1, 1},
{1, 0, 0, 0, 1},
{1, 0, 1, 0, 1},
{1, 0, 0, 0, 1},
{1, 1, 1, 1, 1},
};
// From Appendix E. Table 1, JIS0510X:2004 (p 71). The table was double-checked by komatsu.
private static final int[][] POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = {
{-1, -1, -1, -1, -1, -1, -1}, // Version 1
{ 6, 18, -1, -1, -1, -1, -1}, // Version 2
{ 6, 22, -1, -1, -1, -1, -1}, // Version 3
{ 6, 26, -1, -1, -1, -1, -1}, // Version 4
{ 6, 30, -1, -1, -1, -1, -1}, // Version 5
{ 6, 34, -1, -1, -1, -1, -1}, // Version 6
{ 6, 22, 38, -1, -1, -1, -1}, // Version 7
{ 6, 24, 42, -1, -1, -1, -1}, // Version 8
{ 6, 26, 46, -1, -1, -1, -1}, // Version 9
{ 6, 28, 50, -1, -1, -1, -1}, // Version 10
{ 6, 30, 54, -1, -1, -1, -1}, // Version 11
{ 6, 32, 58, -1, -1, -1, -1}, // Version 12
{ 6, 34, 62, -1, -1, -1, -1}, // Version 13
{ 6, 26, 46, 66, -1, -1, -1}, // Version 14
{ 6, 26, 48, 70, -1, -1, -1}, // Version 15
{ 6, 26, 50, 74, -1, -1, -1}, // Version 16
{ 6, 30, 54, 78, -1, -1, -1}, // Version 17
{ 6, 30, 56, 82, -1, -1, -1}, // Version 18
{ 6, 30, 58, 86, -1, -1, -1}, // Version 19
{ 6, 34, 62, 90, -1, -1, -1}, // Version 20
{ 6, 28, 50, 72, 94, -1, -1}, // Version 21
{ 6, 26, 50, 74, 98, -1, -1}, // Version 22
{ 6, 30, 54, 78, 102, -1, -1}, // Version 23
{ 6, 28, 54, 80, 106, -1, -1}, // Version 24
{ 6, 32, 58, 84, 110, -1, -1}, // Version 25
{ 6, 30, 58, 86, 114, -1, -1}, // Version 26
{ 6, 34, 62, 90, 118, -1, -1}, // Version 27
{ 6, 26, 50, 74, 98, 122, -1}, // Version 28
{ 6, 30, 54, 78, 102, 126, -1}, // Version 29
{ 6, 26, 52, 78, 104, 130, -1}, // Version 30
{ 6, 30, 56, 82, 108, 134, -1}, // Version 31
{ 6, 34, 60, 86, 112, 138, -1}, // Version 32
{ 6, 30, 58, 86, 114, 142, -1}, // Version 33
{ 6, 34, 62, 90, 118, 146, -1}, // Version 34
{ 6, 30, 54, 78, 102, 126, 150}, // Version 35
{ 6, 24, 50, 76, 102, 128, 154}, // Version 36
{ 6, 28, 54, 80, 106, 132, 158}, // Version 37
{ 6, 32, 58, 84, 110, 136, 162}, // Version 38
{ 6, 26, 54, 82, 110, 138, 166}, // Version 39
{ 6, 30, 58, 86, 114, 142, 170}, // Version 40
};
// Type info cells at the left top corner.
private static final int[][] TYPE_INFO_COORDINATES = {
{8, 0},
{8, 1},
{8, 2},
{8, 3},
{8, 4},
{8, 5},
{8, 7},
{8, 8},
{7, 8},
{5, 8},
{4, 8},
{3, 8},
{2, 8},
{1, 8},
{0, 8},
};
// From Appendix D in JISX0510:2004 (p. 67)
private static final int VERSION_INFO_POLY = 0x1f25; // 1 1111 0010 0101
// From Appendix C in JISX0510:2004 (p.65).
private static final int TYPE_INFO_POLY = 0x537;
private static final int TYPE_INFO_MASK_PATTERN = 0x5412;
private MatrixUtil() {
// do nothing
}
// 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
// with the ByteMatrix initialized all to zero.
static void clearMatrix(ByteMatrix matrix) {
matrix.clear((byte) -1);
}
// Build 2D matrix of QR Code from "dataBits" with "ecLevel", "version" and "getMaskPattern". On
// success, store the result in "matrix" and return true.
static void buildMatrix(BitArray dataBits,
ErrorCorrectionLevel ecLevel,
Version version,
int maskPattern,
ByteMatrix matrix) throws WriterException {
clearMatrix(matrix);
embedBasicPatterns(version, matrix);
// Type information appear with any version.
embedTypeInfo(ecLevel, maskPattern, matrix);
// Version info appear if version >= 7.
maybeEmbedVersionInfo(version, matrix);
// Data should be embedded at end.
embedDataBits(dataBits, maskPattern, matrix);
}
// Embed basic patterns. On success, modify the matrix and return true.
// The basic patterns are:
// - Position detection patterns
// - Timing patterns
// - Dark dot at the left bottom corner
// - Position adjustment patterns, if need be
static void embedBasicPatterns(Version version, ByteMatrix matrix) throws WriterException {
// Let's get started with embedding big squares at corners.
embedPositionDetectionPatternsAndSeparators(matrix);
// Then, embed the dark dot at the left bottom corner.
embedDarkDotAtLeftBottomCorner(matrix);
// Position adjustment patterns appear if version >= 2.
maybeEmbedPositionAdjustmentPatterns(version, matrix);
// Timing patterns should be embedded after position adj. patterns.
embedTimingPatterns(matrix);
}
// Embed type information. On success, modify the matrix.
static void embedTypeInfo(ErrorCorrectionLevel ecLevel, int maskPattern, ByteMatrix matrix)
throws WriterException {
BitArray typeInfoBits = new BitArray();
makeTypeInfoBits(ecLevel, maskPattern, typeInfoBits);
for (int i = 0; i < typeInfoBits.getSize(); ++i) {
// Place bits in LSB to MSB order. LSB (least significant bit) is the last value in
// "typeInfoBits".
boolean bit = typeInfoBits.get(typeInfoBits.getSize() - 1 - i);
// Type info bits at the left top corner. See 8.9 of JISX0510:2004 (p.46).
int[] coordinates = TYPE_INFO_COORDINATES[i];
int x1 = coordinates[0];
int y1 = coordinates[1];
matrix.set(x1, y1, bit);
int x2;
int y2;
if (i < 8) {
// Right top corner.
x2 = matrix.getWidth() - i - 1;
y2 = 8;
} else {
// Left bottom corner.
x2 = 8;
y2 = matrix.getHeight() - 7 + (i - 8);
}
matrix.set(x2, y2, bit);
}
}
// Embed version information if need be. On success, modify the matrix and return true.
// See 8.10 of JISX0510:2004 (p.47) for how to embed version information.
static void maybeEmbedVersionInfo(Version version, ByteMatrix matrix) throws WriterException {
if (version.getVersionNumber() < 7) { // Version info is necessary if version >= 7.
return; // Don't need version info.
}
BitArray versionInfoBits = new BitArray();
makeVersionInfoBits(version, versionInfoBits);
int bitIndex = 6 * 3 - 1; // It will decrease from 17 to 0.
for (int i = 0; i < 6; ++i) {
for (int j = 0; j < 3; ++j) {
// Place bits in LSB (least significant bit) to MSB order.
boolean bit = versionInfoBits.get(bitIndex);
bitIndex--;
// Left bottom corner.
matrix.set(i, matrix.getHeight() - 11 + j, bit);
// Right bottom corner.
matrix.set(matrix.getHeight() - 11 + j, i, bit);
}
}
}
// Embed "dataBits" using "getMaskPattern". On success, modify the matrix and return true.
// For debugging purposes, it skips masking process if "getMaskPattern" is -1.
// See 8.7 of JISX0510:2004 (p.38) for how to embed data bits.
static void embedDataBits(BitArray dataBits, int maskPattern, ByteMatrix matrix)
throws WriterException {
int bitIndex = 0;
int direction = -1;
// Start from the right bottom cell.
int x = matrix.getWidth() - 1;
int y = matrix.getHeight() - 1;
while (x > 0) {
// Skip the vertical timing pattern.
if (x == 6) {
x -= 1;
}
while (y >= 0 && y < matrix.getHeight()) {
for (int i = 0; i < 2; ++i) {
int xx = x - i;
// Skip the cell if it's not empty.
if (!isEmpty(matrix.get(xx, y))) {
continue;
}
boolean bit;
if (bitIndex < dataBits.getSize()) {
bit = dataBits.get(bitIndex);
++bitIndex;
} else {
// Padding bit. If there is no bit left, we'll fill the left cells with 0, as described
// in 8.4.9 of JISX0510:2004 (p. 24).
bit = false;
}
// Skip masking if mask_pattern is -1.
if (maskPattern != -1 && MaskUtil.getDataMaskBit(maskPattern, xx, y)) {
bit = !bit;
}
matrix.set(xx, y, bit);
}
y += direction;
}
direction = -direction; // Reverse the direction.
y += direction;
x -= 2; // Move to the left.
}
// All bits should be consumed.
if (bitIndex != dataBits.getSize()) {
throw new WriterException("Not all bits consumed: " + bitIndex + '/' + dataBits.getSize());
}
}
// Return the position of the most significant bit set (to one) in the "value". The most
// significant bit is position 32. If there is no bit set, return 0. Examples:
// - findMSBSet(0) => 0
// - findMSBSet(1) => 1
// - findMSBSet(255) => 8
static int findMSBSet(int value) {
return 32 - Integer.numberOfLeadingZeros(value);
}
// Calculate BCH (Bose-Chaudhuri-Hocquenghem) code for "value" using polynomial "poly". The BCH
// code is used for encoding type information and version information.
// Example: Calculation of version information of 7.
// f(x) is created from 7.
// - 7 = 000111 in 6 bits
// - f(x) = x^2 + x^1 + x^0
// g(x) is given by the standard (p. 67)
// - g(x) = x^12 + x^11 + x^10 + x^9 + x^8 + x^5 + x^2 + 1
// Multiply f(x) by x^(18 - 6)
// - f'(x) = f(x) * x^(18 - 6)
// - f'(x) = x^14 + x^13 + x^12
// Calculate the remainder of f'(x) / g(x)
// x^2
// __________________________________________________
// g(x) )x^14 + x^13 + x^12
// x^14 + x^13 + x^12 + x^11 + x^10 + x^7 + x^4 + x^2
// --------------------------------------------------
// x^11 + x^10 + x^7 + x^4 + x^2
//
// The remainder is x^11 + x^10 + x^7 + x^4 + x^2
// Encode it in binary: 110010010100
// The return value is 0xc94 (1100 1001 0100)
//
// Since all coefficients in the polynomials are 1 or 0, we can do the calculation by bit
// operations. We don't care if coefficients are positive or negative.
static int calculateBCHCode(int value, int poly) {
if (poly == 0) {
throw new IllegalArgumentException("0 polynomial");
}
// If poly is "1 1111 0010 0101" (version info poly), msbSetInPoly is 13. We'll subtract 1
// from 13 to make it 12.
int msbSetInPoly = findMSBSet(poly);
value <<= msbSetInPoly - 1;
// Do the division business using exclusive-or operations.
while (findMSBSet(value) >= msbSetInPoly) {
value ^= poly << (findMSBSet(value) - msbSetInPoly);
}
// Now the "value" is the remainder (i.e. the BCH code)
return value;
}
// Make bit vector of type information. On success, store the result in "bits" and return true.
// Encode error correction level and mask pattern. See 8.9 of
// JISX0510:2004 (p.45) for details.
static void makeTypeInfoBits(ErrorCorrectionLevel ecLevel, int maskPattern, BitArray bits)
throws WriterException {
if (!QRCode.isValidMaskPattern(maskPattern)) {
throw new WriterException("Invalid mask pattern");
}
int typeInfo = (ecLevel.getBits() << 3) | maskPattern;
bits.appendBits(typeInfo, 5);
int bchCode = calculateBCHCode(typeInfo, TYPE_INFO_POLY);
bits.appendBits(bchCode, 10);
BitArray maskBits = new BitArray();
maskBits.appendBits(TYPE_INFO_MASK_PATTERN, 15);
bits.xor(maskBits);
if (bits.getSize() != 15) { // Just in case.
throw new WriterException("should not happen but we got: " + bits.getSize());
}
}
// Make bit vector of version information. On success, store the result in "bits" and return true.
// See 8.10 of JISX0510:2004 (p.45) for details.
static void makeVersionInfoBits(Version version, BitArray bits) throws WriterException {
bits.appendBits(version.getVersionNumber(), 6);
int bchCode = calculateBCHCode(version.getVersionNumber(), VERSION_INFO_POLY);
bits.appendBits(bchCode, 12);
if (bits.getSize() != 18) { // Just in case.
throw new WriterException("should not happen but we got: " + bits.getSize());
}
}
// Check if "value" is empty.
private static boolean isEmpty(int value) {
return value == -1;
}
private static void embedTimingPatterns(ByteMatrix matrix) {
// -8 is for skipping position detection patterns (size 7), and two horizontal/vertical
// separation patterns (size 1). Thus, 8 = 7 + 1.
for (int i = 8; i < matrix.getWidth() - 8; ++i) {
int bit = (i + 1) % 2;
// Horizontal line.
if (isEmpty(matrix.get(i, 6))) {
matrix.set(i, 6, bit);
}
// Vertical line.
if (isEmpty(matrix.get(6, i))) {
matrix.set(6, i, bit);
}
}
}
// Embed the lonely dark dot at left bottom corner. JISX0510:2004 (p.46)
private static void embedDarkDotAtLeftBottomCorner(ByteMatrix matrix) throws WriterException {
if (matrix.get(8, matrix.getHeight() - 8) == 0) {
throw new WriterException();
}
matrix.set(8, matrix.getHeight() - 8, 1);
}
private static void embedHorizontalSeparationPattern(int xStart,
int yStart,
ByteMatrix matrix) throws WriterException {
for (int x = 0; x < 8; ++x) {
if (!isEmpty(matrix.get(xStart + x, yStart))) {
throw new WriterException();
}
matrix.set(xStart + x, yStart, 0);
}
}
private static void embedVerticalSeparationPattern(int xStart,
int yStart,
ByteMatrix matrix) throws WriterException {
for (int y = 0; y < 7; ++y) {
if (!isEmpty(matrix.get(xStart, yStart + y))) {
throw new WriterException();
}
matrix.set(xStart, yStart + y, 0);
}
}
private static void embedPositionAdjustmentPattern(int xStart, int yStart, ByteMatrix matrix) {
for (int y = 0; y < 5; ++y) {
int[] patternY = POSITION_ADJUSTMENT_PATTERN[y];
for (int x = 0; x < 5; ++x) {
matrix.set(xStart + x, yStart + y, patternY[x]);
}
}
}
private static void embedPositionDetectionPattern(int xStart, int yStart, ByteMatrix matrix) {
for (int y = 0; y < 7; ++y) {
int[] patternY = POSITION_DETECTION_PATTERN[y];
for (int x = 0; x < 7; ++x) {
matrix.set(xStart + x, yStart + y, patternY[x]);
}
}
}
// Embed position detection patterns and surrounding vertical/horizontal separators.
private static void embedPositionDetectionPatternsAndSeparators(ByteMatrix matrix) throws WriterException {
// Embed three big squares at corners.
int pdpWidth = POSITION_DETECTION_PATTERN[0].length;
// Left top corner.
embedPositionDetectionPattern(0, 0, matrix);
// Right top corner.
embedPositionDetectionPattern(matrix.getWidth() - pdpWidth, 0, matrix);
// Left bottom corner.
embedPositionDetectionPattern(0, matrix.getWidth() - pdpWidth, matrix);
// Embed horizontal separation patterns around the squares.
int hspWidth = 8;
// Left top corner.
embedHorizontalSeparationPattern(0, hspWidth - 1, matrix);
// Right top corner.
embedHorizontalSeparationPattern(matrix.getWidth() - hspWidth,
hspWidth - 1, matrix);
// Left bottom corner.
embedHorizontalSeparationPattern(0, matrix.getWidth() - hspWidth, matrix);
// Embed vertical separation patterns around the squares.
int vspSize = 7;
// Left top corner.
embedVerticalSeparationPattern(vspSize, 0, matrix);
// Right top corner.
embedVerticalSeparationPattern(matrix.getHeight() - vspSize - 1, 0, matrix);
// Left bottom corner.
embedVerticalSeparationPattern(vspSize, matrix.getHeight() - vspSize,
matrix);
}
// Embed position adjustment patterns if need be.
private static void maybeEmbedPositionAdjustmentPatterns(Version version, ByteMatrix matrix) {
if (version.getVersionNumber() < 2) { // The patterns appear if version >= 2
return;
}
int index = version.getVersionNumber() - 1;
int[] coordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index];
for (int y : coordinates) {
if (y >= 0) {
for (int x : coordinates) {
if (x >= 0 && isEmpty(matrix.get(x, y))) {
// If the cell is unset, we embed the position adjustment pattern here.
// -2 is necessary since the x/y coordinates point to the center of the pattern, not the
// left top corner.
embedPositionAdjustmentPattern(x - 2, y - 2, matrix);
}
}
}
}
}
}

View File

@@ -0,0 +1,667 @@
/*
* Copyright 2021 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;
import com.google.zxing.qrcode.decoder.Mode;
import com.google.zxing.qrcode.decoder.Version;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.ECIEncoderSet;
import com.google.zxing.WriterException;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/**
* Encoder that encodes minimally
*
* Algorithm:
*
* The eleventh commandment was "Thou Shalt Compute" or "Thou Shalt Not Compute" - I forget which (Alan Perilis).
*
* This implementation computes. As an alternative, the QR-Code specification suggests heuristics like this one:
*
* If initial input data is in the exclusive subset of the Alphanumeric character set AND if there are less than
* [6,7,8] characters followed by data from the remainder of the 8-bit byte character set, THEN select the 8-
* bit byte mode ELSE select Alphanumeric mode;
*
* This is probably right for 99.99% of cases but there is at least this one counter example: The string "AAAAAAa"
* encodes 2 bits smaller as ALPHANUMERIC(AAAAAA), BYTE(a) than by encoding it as BYTE(AAAAAAa).
* Perhaps that is the only counter example but without having proof, it remains unclear.
*
* ECI switching:
*
* In multi language content the algorithm selects the most compact representation using ECI modes.
* For example the most compact representation of the string "\u0150\u015C" (O-double-acute, S-circumflex) is
* ECI(UTF-8), BYTE(\u0150\u015C) while prepending one or more times the same leading character as in
* "\u0150\u0150\u015C", the most compact representation uses two ECIs so that the string is encoded as
* ECI(ISO-8859-2), BYTE(\u0150\u0150), ECI(ISO-8859-3), BYTE(\u015C).
*
* @author Alex Geller
*/
final class MinimalEncoder {
private enum VersionSize {
SMALL("version 1-9"),
MEDIUM("version 10-26"),
LARGE("version 27-40");
private final String description;
VersionSize(String description) {
this.description = description;
}
public String toString() {
return description;
}
}
private final String stringToEncode;
private final boolean isGS1;
private final ECIEncoderSet encoders;
private final ErrorCorrectionLevel ecLevel;
/**
* Creates a MinimalEncoder
*
* @param stringToEncode The string to encode
* @param priorityCharset The preferred {@link Charset}. When the value of the argument is null, the algorithm
* chooses charsets that leads to a minimal representation. Otherwise the algorithm will use the priority
* charset to encode any character in the input that can be encoded by it if the charset is among the
* supported charsets.
* @param isGS1 {@code true} if a FNC1 is to be prepended; {@code false} otherwise
* @param ecLevel The error correction level.
* @see ResultList#getVersion
*/
MinimalEncoder(String stringToEncode, Charset priorityCharset, boolean isGS1, ErrorCorrectionLevel ecLevel) {
this.stringToEncode = stringToEncode;
this.isGS1 = isGS1;
this.encoders = new ECIEncoderSet(stringToEncode, priorityCharset, -1);
this.ecLevel = ecLevel;
}
/**
* Encodes the string minimally
*
* @param stringToEncode The string to encode
* @param version The preferred {@link Version}. A minimal version is computed (see
* {@link ResultList#getVersion method} when the value of the argument is null
* @param priorityCharset The preferred {@link Charset}. When the value of the argument is null, the algorithm
* chooses charsets that leads to a minimal representation. Otherwise the algorithm will use the priority
* charset to encode any character in the input that can be encoded by it if the charset is among the
* supported charsets.
* @param isGS1 {@code true} if a FNC1 is to be prepended; {@code false} otherwise
* @param ecLevel The error correction level.
* @return An instance of {@code ResultList} representing the minimal solution.
* @see ResultList#getBits
* @see ResultList#getVersion
* @see ResultList#getSize
*/
static ResultList encode(String stringToEncode, Version version, Charset priorityCharset, boolean isGS1,
ErrorCorrectionLevel ecLevel) throws WriterException {
return new MinimalEncoder(stringToEncode, priorityCharset, isGS1, ecLevel).encode(version);
}
ResultList encode(Version version) throws WriterException {
if (version == null) { // compute minimal encoding trying the three version sizes.
Version[] versions = { getVersion(VersionSize.SMALL),
getVersion(VersionSize.MEDIUM),
getVersion(VersionSize.LARGE) };
ResultList[] results = { encodeSpecificVersion(versions[0]),
encodeSpecificVersion(versions[1]),
encodeSpecificVersion(versions[2]) };
int smallestSize = Integer.MAX_VALUE;
int smallestResult = -1;
for (int i = 0; i < 3; i++) {
int size = results[i].getSize();
if (Encoder.willFit(size, versions[i], ecLevel) && size < smallestSize) {
smallestSize = size;
smallestResult = i;
}
}
if (smallestResult < 0) {
throw new WriterException("Data too big for any version");
}
return results[smallestResult];
} else { // compute minimal encoding for a given version
ResultList result = encodeSpecificVersion(version);
if (!Encoder.willFit(result.getSize(), getVersion(getVersionSize(result.getVersion())), ecLevel)) {
throw new WriterException("Data too big for version" + version);
}
return result;
}
}
static VersionSize getVersionSize(Version version) {
return version.getVersionNumber() <= 9 ? VersionSize.SMALL : version.getVersionNumber() <= 26 ?
VersionSize.MEDIUM : VersionSize.LARGE;
}
static Version getVersion(VersionSize versionSize) {
switch (versionSize) {
case SMALL:
return Version.getVersionForNumber(9);
case MEDIUM:
return Version.getVersionForNumber(26);
case LARGE:
default:
return Version.getVersionForNumber(40);
}
}
static boolean isNumeric(char c) {
return c >= '0' && c <= '9';
}
static boolean isDoubleByteKanji(char c) {
return Encoder.isOnlyDoubleByteKanji(String.valueOf(c));
}
static boolean isAlphanumeric(char c) {
return Encoder.getAlphanumericCode(c) != -1;
}
boolean canEncode(Mode mode, char c) {
switch (mode) {
case KANJI: return isDoubleByteKanji(c);
case ALPHANUMERIC: return isAlphanumeric(c);
case NUMERIC: return isNumeric(c);
case BYTE: return true; // any character can be encoded as byte(s). Up to the caller to manage splitting into
// multiple bytes when String.getBytes(Charset) return more than one byte.
default:
return false;
}
}
static int getCompactedOrdinal(Mode mode) {
if (mode == null) {
return 0;
}
switch (mode) {
case KANJI:
return 0;
case ALPHANUMERIC:
return 1;
case NUMERIC:
return 2;
case BYTE:
return 3;
default:
throw new IllegalStateException("Illegal mode " + mode);
}
}
void addEdge(Edge[][][] edges, int position, Edge edge) {
int vertexIndex = position + edge.characterLength;
Edge[] modeEdges = edges[vertexIndex][edge.charsetEncoderIndex];
int modeOrdinal = getCompactedOrdinal(edge.mode);
if (modeEdges[modeOrdinal] == null || modeEdges[modeOrdinal].cachedTotalSize > edge.cachedTotalSize) {
modeEdges[modeOrdinal] = edge;
}
}
void addEdges(Version version, Edge[][][] edges, int from, Edge previous) {
int start = 0;
int end = encoders.length();
int priorityEncoderIndex = encoders.getPriorityEncoderIndex();
if (priorityEncoderIndex >= 0 && encoders.canEncode(stringToEncode.charAt(from),priorityEncoderIndex)) {
start = priorityEncoderIndex;
end = priorityEncoderIndex + 1;
}
for (int i = start; i < end; i++) {
if (encoders.canEncode(stringToEncode.charAt(from), i)) {
addEdge(edges, from, new Edge(Mode.BYTE, from, i, 1, previous, version));
}
}
if (canEncode(Mode.KANJI, stringToEncode.charAt(from))) {
addEdge(edges, from, new Edge(Mode.KANJI, from, 0, 1, previous, version));
}
int inputLength = stringToEncode.length();
if (canEncode(Mode.ALPHANUMERIC, stringToEncode.charAt(from))) {
addEdge(edges, from, new Edge(Mode.ALPHANUMERIC, from, 0, from + 1 >= inputLength ||
!canEncode(Mode.ALPHANUMERIC, stringToEncode.charAt(from + 1)) ? 1 : 2, previous, version));
}
if (canEncode(Mode.NUMERIC, stringToEncode.charAt(from))) {
addEdge(edges, from, new Edge(Mode.NUMERIC, from, 0, from + 1 >= inputLength ||
!canEncode(Mode.NUMERIC, stringToEncode.charAt(from + 1)) ? 1 : from + 2 >= inputLength ||
!canEncode(Mode.NUMERIC, stringToEncode.charAt(from + 2)) ? 2 : 3, previous, version));
}
}
ResultList encodeSpecificVersion(Version version) throws WriterException {
@SuppressWarnings("checkstyle:lineLength")
/* A vertex represents a tuple of a position in the input, a mode and a character encoding where position 0
* denotes the position left of the first character, 1 the position left of the second character and so on.
* Likewise the end vertices are located after the last character at position stringToEncode.length().
*
* An edge leading to such a vertex encodes one or more of the characters left of the position that the vertex
* represents and encodes it in the same encoding and mode as the vertex on which the edge ends. In other words,
* all edges leading to a particular vertex encode the same characters in the same mode with the same character
* encoding. They differ only by their source vertices who are all located at i+1 minus the number of encoded
* characters.
*
* The edges leading to a vertex are stored in such a way that there is a fast way to enumerate the edges ending
* on a particular vertex.
*
* The algorithm processes the vertices in order of their position thereby performing the following:
*
* For every vertex at position i the algorithm enumerates the edges ending on the vertex and removes all but the
* shortest from that list.
* Then it processes the vertices for the position i+1. If i+1 == stringToEncode.length() then the algorithm ends
* and chooses the the edge with the smallest size from any of the edges leading to vertices at this position.
* Otherwise the algorithm computes all possible outgoing edges for the vertices at the position i+1
*
* Examples:
* The process is illustrated by showing the graph (edges) after each iteration from left to right over the input:
* An edge is drawn as follows "(" + fromVertex + ") -- " + encodingMode + "(" + encodedInput + ") (" +
* accumulatedSize + ") --> (" + toVertex + ")"
*
* Example 1 encoding the string "ABCDE":
* Note: This example assumes that alphanumeric encoding is only possible in multiples of two characters so that
* the example is both short and showing the principle. In reality this restriction does not exist.
*
* Initial situation
* (initial) -- BYTE(A) (20) --> (1_BYTE)
* (initial) -- ALPHANUMERIC(AB) (24) --> (2_ALPHANUMERIC)
*
* Situation after adding edges to vertices at position 1
* (initial) -- BYTE(A) (20) --> (1_BYTE) -- BYTE(B) (28) --> (2_BYTE)
* (1_BYTE) -- ALPHANUMERIC(BC) (44) --> (3_ALPHANUMERIC)
* (initial) -- ALPHANUMERIC(AB) (24) --> (2_ALPHANUMERIC)
*
* Situation after adding edges to vertices at position 2
* (initial) -- BYTE(A) (20) --> (1_BYTE)
* (initial) -- ALPHANUMERIC(AB) (24) --> (2_ALPHANUMERIC)
* (initial) -- BYTE(A) (20) --> (1_BYTE) -- BYTE(B) (28) --> (2_BYTE)
* (1_BYTE) -- ALPHANUMERIC(BC) (44) --> (3_ALPHANUMERIC)
* (initial) -- ALPHANUMERIC(AB) (24) --> (2_ALPHANUMERIC) -- BYTE(C) (44) --> (3_BYTE)
* (2_ALPHANUMERIC) -- ALPHANUMERIC(CD) (35) --> (4_ALPHANUMERIC)
*
* Situation after adding edges to vertices at position 3
* (initial) -- BYTE(A) (20) --> (1_BYTE) -- BYTE(B) (28) --> (2_BYTE) -- BYTE(C) (36) --> (3_BYTE)
* (1_BYTE) -- ALPHANUMERIC(BC) (44) --> (3_ALPHANUMERIC) -- BYTE(D) (64) --> (4_BYTE)
* (3_ALPHANUMERIC) -- ALPHANUMERIC(DE) (55) --> (5_ALPHANUMERIC)
* (initial) -- ALPHANUMERIC(AB) (24) --> (2_ALPHANUMERIC) -- ALPHANUMERIC(CD) (35) --> (4_ALPHANUMERIC)
* (2_ALPHANUMERIC) -- ALPHANUMERIC(CD) (35) --> (4_ALPHANUMERIC)
*
* Situation after adding edges to vertices at position 4
* (initial) -- BYTE(A) (20) --> (1_BYTE) -- BYTE(B) (28) --> (2_BYTE) -- BYTE(C) (36) --> (3_BYTE) -- BYTE(D) (44) --> (4_BYTE)
* (1_BYTE) -- ALPHANUMERIC(BC) (44) --> (3_ALPHANUMERIC) -- ALPHANUMERIC(DE) (55) --> (5_ALPHANUMERIC)
* (initial) -- ALPHANUMERIC(AB) (24) --> (2_ALPHANUMERIC) -- ALPHANUMERIC(CD) (35) --> (4_ALPHANUMERIC) -- BYTE(E) (55) --> (5_BYTE)
*
* Situation after adding edges to vertices at position 5
* (initial) -- BYTE(A) (20) --> (1_BYTE) -- BYTE(B) (28) --> (2_BYTE) -- BYTE(C) (36) --> (3_BYTE) -- BYTE(D) (44) --> (4_BYTE) -- BYTE(E) (52) --> (5_BYTE)
* (1_BYTE) -- ALPHANUMERIC(BC) (44) --> (3_ALPHANUMERIC) -- ALPHANUMERIC(DE) (55) --> (5_ALPHANUMERIC)
* (initial) -- ALPHANUMERIC(AB) (24) --> (2_ALPHANUMERIC) -- ALPHANUMERIC(CD) (35) --> (4_ALPHANUMERIC)
*
* Encoding as BYTE(ABCDE) has the smallest size of 52 and is hence chosen. The encodation ALPHANUMERIC(ABCD),
* BYTE(E) is longer with a size of 55.
*
* Example 2 encoding the string "XXYY" where X denotes a character unique to character set ISO-8859-2 and Y a
* character unique to ISO-8859-3. Both characters encode as double byte in UTF-8:
*
* Initial situation
* (initial) -- BYTE(X) (32) --> (1_BYTE_ISO-8859-2)
* (initial) -- BYTE(X) (40) --> (1_BYTE_UTF-8)
* (initial) -- BYTE(X) (40) --> (1_BYTE_UTF-16BE)
*
* Situation after adding edges to vertices at position 1
* (initial) -- BYTE(X) (32) --> (1_BYTE_ISO-8859-2) -- BYTE(X) (40) --> (2_BYTE_ISO-8859-2)
* (1_BYTE_ISO-8859-2) -- BYTE(X) (72) --> (2_BYTE_UTF-8)
* (1_BYTE_ISO-8859-2) -- BYTE(X) (72) --> (2_BYTE_UTF-16BE)
* (initial) -- BYTE(X) (40) --> (1_BYTE_UTF-8)
* (initial) -- BYTE(X) (40) --> (1_BYTE_UTF-16BE)
*
* Situation after adding edges to vertices at position 2
* (initial) -- BYTE(X) (32) --> (1_BYTE_ISO-8859-2) -- BYTE(X) (40) --> (2_BYTE_ISO-8859-2)
* (2_BYTE_ISO-8859-2) -- BYTE(Y) (72) --> (3_BYTE_ISO-8859-3)
* (2_BYTE_ISO-8859-2) -- BYTE(Y) (80) --> (3_BYTE_UTF-8)
* (2_BYTE_ISO-8859-2) -- BYTE(Y) (80) --> (3_BYTE_UTF-16BE)
* (initial) -- BYTE(X) (40) --> (1_BYTE_UTF-8) -- BYTE(X) (56) --> (2_BYTE_UTF-8)
* (initial) -- BYTE(X) (40) --> (1_BYTE_UTF-16BE) -- BYTE(X) (56) --> (2_BYTE_UTF-16BE)
*
* Situation after adding edges to vertices at position 3
* (initial) -- BYTE(X) (32) --> (1_BYTE_ISO-8859-2) -- BYTE(X) (40) --> (2_BYTE_ISO-8859-2) -- BYTE(Y) (72) --> (3_BYTE_ISO-8859-3)
* (3_BYTE_ISO-8859-3) -- BYTE(Y) (80) --> (4_BYTE_ISO-8859-3)
* (3_BYTE_ISO-8859-3) -- BYTE(Y) (112) --> (4_BYTE_UTF-8)
* (3_BYTE_ISO-8859-3) -- BYTE(Y) (112) --> (4_BYTE_UTF-16BE)
* (initial) -- BYTE(X) (40) --> (1_BYTE_UTF-8) -- BYTE(X) (56) --> (2_BYTE_UTF-8) -- BYTE(Y) (72) --> (3_BYTE_UTF-8)
* (initial) -- BYTE(X) (40) --> (1_BYTE_UTF-16BE) -- BYTE(X) (56) --> (2_BYTE_UTF-16BE) -- BYTE(Y) (72) --> (3_BYTE_UTF-16BE)
*
* Situation after adding edges to vertices at position 4
* (initial) -- BYTE(X) (32) --> (1_BYTE_ISO-8859-2) -- BYTE(X) (40) --> (2_BYTE_ISO-8859-2) -- BYTE(Y) (72) --> (3_BYTE_ISO-8859-3) -- BYTE(Y) (80) --> (4_BYTE_ISO-8859-3)
* (3_BYTE_UTF-8) -- BYTE(Y) (88) --> (4_BYTE_UTF-8)
* (3_BYTE_UTF-16BE) -- BYTE(Y) (88) --> (4_BYTE_UTF-16BE)
* (initial) -- BYTE(X) (40) --> (1_BYTE_UTF-8) -- BYTE(X) (56) --> (2_BYTE_UTF-8) -- BYTE(Y) (72) --> (3_BYTE_UTF-8)
* (initial) -- BYTE(X) (40) --> (1_BYTE_UTF-16BE) -- BYTE(X) (56) --> (2_BYTE_UTF-16BE) -- BYTE(Y) (72) --> (3_BYTE_UTF-16BE)
*
* Encoding as ECI(ISO-8859-2),BYTE(XX),ECI(ISO-8859-3),BYTE(YY) has the smallest size of 80 and is hence chosen.
* The encodation ECI(UTF-8),BYTE(XXYY) is longer with a size of 88.
*/
int inputLength = stringToEncode.length();
// Array that represents vertices. There is a vertex for every character, encoding and mode. The vertex contains
// a list of all edges that lead to it that have the same encoding and mode.
// The lists are created lazily
// The last dimension in the array below encodes the 4 modes KANJI, ALPHANUMERIC, NUMERIC and BYTE via the
// function getCompactedOrdinal(Mode)
Edge[][][] edges = new Edge[inputLength + 1][encoders.length()][4];
addEdges(version, edges, 0, null);
for (int i = 1; i <= inputLength; i++) {
for (int j = 0; j < encoders.length(); j++) {
for (int k = 0; k < 4; k++) {
if (edges[i][j][k] != null && i < inputLength) {
addEdges(version, edges, i, edges[i][j][k]);
}
}
}
}
int minimalJ = -1;
int minimalK = -1;
int minimalSize = Integer.MAX_VALUE;
for (int j = 0; j < encoders.length(); j++) {
for (int k = 0; k < 4; k++) {
if (edges[inputLength][j][k] != null) {
Edge edge = edges[inputLength][j][k];
if (edge.cachedTotalSize < minimalSize) {
minimalSize = edge.cachedTotalSize;
minimalJ = j;
minimalK = k;
}
}
}
}
if (minimalJ < 0) {
throw new WriterException("Internal error: failed to encode \"" + stringToEncode + "\"");
}
return new ResultList(version, edges[inputLength][minimalJ][minimalK]);
}
private final class Edge {
private final Mode mode;
private final int fromPosition;
private final int charsetEncoderIndex;
private final int characterLength;
private final Edge previous;
private final int cachedTotalSize;
private Edge(Mode mode, int fromPosition, int charsetEncoderIndex, int characterLength, Edge previous,
Version version) {
this.mode = mode;
this.fromPosition = fromPosition;
this.charsetEncoderIndex = mode == Mode.BYTE || previous == null ? charsetEncoderIndex :
previous.charsetEncoderIndex; // inherit the encoding if not of type BYTE
this.characterLength = characterLength;
this.previous = previous;
int size = previous != null ? previous.cachedTotalSize : 0;
boolean needECI = mode == Mode.BYTE &&
(previous == null && this.charsetEncoderIndex != 0) || // at the beginning and charset is not ISO-8859-1
(previous != null && this.charsetEncoderIndex != previous.charsetEncoderIndex);
if (previous == null || mode != previous.mode || needECI) {
size += 4 + mode.getCharacterCountBits(version);
}
switch (mode) {
case KANJI:
size += 13;
break;
case ALPHANUMERIC:
size += characterLength == 1 ? 6 : 11;
break;
case NUMERIC:
size += characterLength == 1 ? 4 : characterLength == 2 ? 7 : 10;
break;
case BYTE:
size += 8 * encoders.encode(stringToEncode.substring(fromPosition, fromPosition + characterLength),
charsetEncoderIndex).length;
if (needECI) {
size += 4 + 8; // the ECI assignment numbers for ISO-8859-x, UTF-8 and UTF-16 are all 8 bit long
}
break;
}
cachedTotalSize = size;
}
}
final class ResultList {
private final List<ResultList.ResultNode> list = new ArrayList<>();
private final Version version;
ResultList(Version version, Edge solution) {
int length = 0;
Edge current = solution;
boolean containsECI = false;
while (current != null) {
length += current.characterLength;
Edge previous = current.previous;
boolean needECI = current.mode == Mode.BYTE &&
(previous == null && current.charsetEncoderIndex != 0) || // at the beginning and charset is not ISO-8859-1
(previous != null && current.charsetEncoderIndex != previous.charsetEncoderIndex);
if (needECI) {
containsECI = true;
}
if (previous == null || previous.mode != current.mode || needECI) {
list.add(0, new ResultNode(current.mode, current.fromPosition, current.charsetEncoderIndex, length));
length = 0;
}
if (needECI) {
list.add(0, new ResultNode(Mode.ECI, current.fromPosition, current.charsetEncoderIndex, 0));
}
current = previous;
}
// prepend FNC1 if needed. If the bits contain an ECI then the FNC1 must be preceeded by an ECI.
// If there is no ECI at the beginning then we put an ECI to the default charset (ISO-8859-1)
if (isGS1) {
ResultNode first = list.get(0);
if (first != null && first.mode != Mode.ECI && containsECI) {
// prepend a default character set ECI
list.add(0, new ResultNode(Mode.ECI, 0, 0, 0));
}
first = list.get(0);
// prepend or insert a FNC1_FIRST_POSITION after the ECI (if any)
list.add(first.mode != Mode.ECI ? 0 : 1, new ResultNode(Mode.FNC1_FIRST_POSITION, 0, 0, 0));
}
// set version to smallest version into which the bits fit.
int versionNumber = version.getVersionNumber();
int lowerLimit;
int upperLimit;
switch (getVersionSize(version)) {
case SMALL:
lowerLimit = 1;
upperLimit = 9;
break;
case MEDIUM:
lowerLimit = 10;
upperLimit = 26;
break;
case LARGE:
default:
lowerLimit = 27;
upperLimit = 40;
break;
}
int size = getSize(version);
// increase version if needed
while (versionNumber < upperLimit && !Encoder.willFit(size, Version.getVersionForNumber(versionNumber),
ecLevel)) {
versionNumber++;
}
// shrink version if possible
while (versionNumber > lowerLimit && Encoder.willFit(size, Version.getVersionForNumber(versionNumber - 1),
ecLevel)) {
versionNumber--;
}
this.version = Version.getVersionForNumber(versionNumber);
}
/**
* returns the size in bits
*/
int getSize() {
return getSize(version);
}
private int getSize(Version version) {
int result = 0;
for (ResultNode resultNode : list) {
result += resultNode.getSize(version);
}
return result;
}
/**
* appends the bits
*/
void getBits(BitArray bits) throws WriterException {
for (ResultNode resultNode : list) {
resultNode.getBits(bits);
}
}
Version getVersion() {
return version;
}
public String toString() {
StringBuilder result = new StringBuilder();
ResultNode previous = null;
for (ResultNode current : list) {
if (previous != null) {
result.append(",");
}
result.append(current.toString());
previous = current;
}
return result.toString();
}
final class ResultNode {
private final Mode mode;
private final int fromPosition;
private final int charsetEncoderIndex;
private final int characterLength;
ResultNode(Mode mode, int fromPosition, int charsetEncoderIndex, int characterLength) {
this.mode = mode;
this.fromPosition = fromPosition;
this.charsetEncoderIndex = charsetEncoderIndex;
this.characterLength = characterLength;
}
/**
* returns the size in bits
*/
private int getSize(Version version) {
int size = 4 + mode.getCharacterCountBits(version);
switch (mode) {
case KANJI:
size += 13 * characterLength;
break;
case ALPHANUMERIC:
size += (characterLength / 2) * 11;
size += (characterLength % 2) == 1 ? 6 : 0;
break;
case NUMERIC:
size += (characterLength / 3) * 10;
int rest = characterLength % 3;
size += rest == 1 ? 4 : rest == 2 ? 7 : 0;
break;
case BYTE:
size += 8 * getCharacterCountIndicator();
break;
case ECI:
size += 8; // the ECI assignment numbers for ISO-8859-x, UTF-8 and UTF-16 are all 8 bit long
}
return size;
}
/**
* returns the length in characters according to the specification (differs from getCharacterLength() in BYTE mode
* for multi byte encoded characters)
*/
private int getCharacterCountIndicator() {
return mode == Mode.BYTE ?
encoders.encode(stringToEncode.substring(fromPosition, fromPosition + characterLength),
charsetEncoderIndex).length : characterLength;
}
/**
* appends the bits
*/
private void getBits(BitArray bits) throws WriterException {
bits.appendBits(mode.getBits(), 4);
if (characterLength > 0) {
int length = getCharacterCountIndicator();
bits.appendBits(length, mode.getCharacterCountBits(version));
}
if (mode == Mode.ECI) {
bits.appendBits(encoders.getECIValue(charsetEncoderIndex), 8);
} else if (characterLength > 0) {
// append data
Encoder.appendBytes(stringToEncode.substring(fromPosition, fromPosition + characterLength), mode, bits,
encoders.getCharset(charsetEncoderIndex));
}
}
public String toString() {
StringBuilder result = new StringBuilder();
result.append(mode).append('(');
if (mode == Mode.ECI) {
result.append(encoders.getCharset(charsetEncoderIndex).displayName());
} else {
result.append(makePrintable(stringToEncode.substring(fromPosition, fromPosition + characterLength)));
}
result.append(')');
return result.toString();
}
private String makePrintable(String s) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) < 32 || s.charAt(i) > 126) {
result.append('.');
} else {
result.append(s.charAt(i));
}
}
return result.toString();
}
}
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.decoder.Mode;
import com.google.zxing.qrcode.decoder.Version;
/**
* @author satorux@google.com (Satoru Takabayashi) - creator
* @author dswitkin@google.com (Daniel Switkin) - ported from C++
*/
public final class QRCode {
public static final int NUM_MASK_PATTERNS = 8;
private Mode mode;
private ErrorCorrectionLevel ecLevel;
private Version version;
private int maskPattern;
private ByteMatrix matrix;
public QRCode() {
maskPattern = -1;
}
/**
* @return the mode. Not relevant if {@link com.google.zxing.EncodeHintType#QR_COMPACT} is selected.
*/
public Mode getMode() {
return mode;
}
public ErrorCorrectionLevel getECLevel() {
return ecLevel;
}
public Version getVersion() {
return version;
}
public int getMaskPattern() {
return maskPattern;
}
public ByteMatrix getMatrix() {
return matrix;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder(200);
result.append("<<\n");
result.append(" mode: ");
result.append(mode);
result.append("\n ecLevel: ");
result.append(ecLevel);
result.append("\n version: ");
result.append(version);
result.append("\n maskPattern: ");
result.append(maskPattern);
if (matrix == null) {
result.append("\n matrix: null\n");
} else {
result.append("\n matrix:\n");
result.append(matrix);
}
result.append(">>\n");
return result.toString();
}
public void setMode(Mode value) {
mode = value;
}
public void setECLevel(ErrorCorrectionLevel value) {
ecLevel = value;
}
public void setVersion(Version version) {
this.version = version;
}
public void setMaskPattern(int value) {
maskPattern = value;
}
public void setMatrix(ByteMatrix value) {
matrix = value;
}
// Check if "mask_pattern" is valid.
public static boolean isValidMaskPattern(int maskPattern) {
return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS;
}
}