mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 04:12:34 +00:00
moved java files for pre-convert
This commit is contained in:
58
src/aztec/AztecDetectorResult.java
Normal file
58
src/aztec/AztecDetectorResult.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2010 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.aztec;
|
||||
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.DetectorResult;
|
||||
|
||||
/**
|
||||
* <p>Extends {@link DetectorResult} with more information specific to the Aztec format,
|
||||
* like the number of layers and whether it's compact.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class AztecDetectorResult extends DetectorResult {
|
||||
|
||||
private final boolean compact;
|
||||
private final int nbDatablocks;
|
||||
private final int nbLayers;
|
||||
|
||||
public AztecDetectorResult(BitMatrix bits,
|
||||
ResultPoint[] points,
|
||||
boolean compact,
|
||||
int nbDatablocks,
|
||||
int nbLayers) {
|
||||
super(bits, points);
|
||||
this.compact = compact;
|
||||
this.nbDatablocks = nbDatablocks;
|
||||
this.nbLayers = nbLayers;
|
||||
}
|
||||
|
||||
public int getNbLayers() {
|
||||
return nbLayers;
|
||||
}
|
||||
|
||||
public int getNbDatablocks() {
|
||||
return nbDatablocks;
|
||||
}
|
||||
|
||||
public boolean isCompact() {
|
||||
return compact;
|
||||
}
|
||||
|
||||
}
|
||||
123
src/aztec/AztecReader.java
Normal file
123
src/aztec/AztecReader.java
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2010 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.aztec;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
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.ResultPointCallback;
|
||||
import com.google.zxing.aztec.decoder.Decoder;
|
||||
import com.google.zxing.aztec.detector.Detector;
|
||||
import com.google.zxing.common.DecoderResult;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This implementation can detect and decode Aztec codes in an image.
|
||||
*
|
||||
* @author David Olivier
|
||||
*/
|
||||
public final class AztecReader implements Reader {
|
||||
|
||||
/**
|
||||
* Locates and decodes a Data Matrix code in an image.
|
||||
*
|
||||
* @return a String representing the content encoded by the Data Matrix code
|
||||
* @throws NotFoundException if a Data Matrix code cannot be found
|
||||
* @throws FormatException if a Data Matrix code cannot be decoded
|
||||
*/
|
||||
@Override
|
||||
public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
|
||||
return decode(image, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
|
||||
throws NotFoundException, FormatException {
|
||||
|
||||
NotFoundException notFoundException = null;
|
||||
FormatException formatException = null;
|
||||
Detector detector = new Detector(image.getBlackMatrix());
|
||||
ResultPoint[] points = null;
|
||||
DecoderResult decoderResult = null;
|
||||
try {
|
||||
AztecDetectorResult detectorResult = detector.detect(false);
|
||||
points = detectorResult.getPoints();
|
||||
decoderResult = new Decoder().decode(detectorResult);
|
||||
} catch (NotFoundException e) {
|
||||
notFoundException = e;
|
||||
} catch (FormatException e) {
|
||||
formatException = e;
|
||||
}
|
||||
if (decoderResult == null) {
|
||||
try {
|
||||
AztecDetectorResult detectorResult = detector.detect(true);
|
||||
points = detectorResult.getPoints();
|
||||
decoderResult = new Decoder().decode(detectorResult);
|
||||
} catch (NotFoundException | FormatException e) {
|
||||
if (notFoundException != null) {
|
||||
throw notFoundException;
|
||||
}
|
||||
if (formatException != null) {
|
||||
throw formatException;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (hints != null) {
|
||||
ResultPointCallback rpcb = (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
|
||||
if (rpcb != null) {
|
||||
for (ResultPoint point : points) {
|
||||
rpcb.foundPossibleResultPoint(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result result = new Result(decoderResult.getText(),
|
||||
decoderResult.getRawBytes(),
|
||||
decoderResult.getNumBits(),
|
||||
points,
|
||||
BarcodeFormat.AZTEC,
|
||||
System.currentTimeMillis());
|
||||
|
||||
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);
|
||||
}
|
||||
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]z" + decoderResult.getSymbologyModifier());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
}
|
||||
94
src/aztec/AztecWriter.java
Normal file
94
src/aztec/AztecWriter.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.aztec;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.Writer;
|
||||
import com.google.zxing.aztec.encoder.AztecCode;
|
||||
import com.google.zxing.aztec.encoder.Encoder;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Renders an Aztec code as a {@link BitMatrix}.
|
||||
*/
|
||||
public final class AztecWriter implements Writer {
|
||||
|
||||
@Override
|
||||
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) {
|
||||
return encode(contents, format, width, height, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Map<EncodeHintType,?> hints) {
|
||||
Charset charset = null; // Do not add any ECI code by default
|
||||
int eccPercent = Encoder.DEFAULT_EC_PERCENT;
|
||||
int layers = Encoder.DEFAULT_AZTEC_LAYERS;
|
||||
if (hints != null) {
|
||||
if (hints.containsKey(EncodeHintType.CHARACTER_SET)) {
|
||||
charset = Charset.forName(hints.get(EncodeHintType.CHARACTER_SET).toString());
|
||||
}
|
||||
if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) {
|
||||
eccPercent = Integer.parseInt(hints.get(EncodeHintType.ERROR_CORRECTION).toString());
|
||||
}
|
||||
if (hints.containsKey(EncodeHintType.AZTEC_LAYERS)) {
|
||||
layers = Integer.parseInt(hints.get(EncodeHintType.AZTEC_LAYERS).toString());
|
||||
}
|
||||
}
|
||||
return encode(contents, format, width, height, charset, eccPercent, layers);
|
||||
}
|
||||
|
||||
private static BitMatrix encode(String contents, BarcodeFormat format,
|
||||
int width, int height,
|
||||
Charset charset, int eccPercent, int layers) {
|
||||
if (format != BarcodeFormat.AZTEC) {
|
||||
throw new IllegalArgumentException("Can only encode AZTEC, but got " + format);
|
||||
}
|
||||
AztecCode aztec = Encoder.encode(contents, eccPercent, layers, charset);
|
||||
return renderResult(aztec, width, height);
|
||||
}
|
||||
|
||||
private static BitMatrix renderResult(AztecCode code, int width, int height) {
|
||||
BitMatrix input = code.getMatrix();
|
||||
if (input == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
int inputWidth = input.getWidth();
|
||||
int inputHeight = input.getHeight();
|
||||
int outputWidth = Math.max(width, inputWidth);
|
||||
int outputHeight = Math.max(height, inputHeight);
|
||||
|
||||
int multiple = Math.min(outputWidth / inputWidth, outputHeight / inputHeight);
|
||||
int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
|
||||
int topPadding = (outputHeight - (inputHeight * multiple)) / 2;
|
||||
|
||||
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)) {
|
||||
output.setRegion(outputX, outputY, multiple, multiple);
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
443
src/aztec/decoder/Decoder.java
Normal file
443
src/aztec/decoder/Decoder.java
Normal file
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
* Copyright 2010 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.aztec.decoder;
|
||||
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.aztec.AztecDetectorResult;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.CharacterSetECI;
|
||||
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.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* <p>The main class which implements Aztec Code decoding -- as opposed to locating and extracting
|
||||
* the Aztec Code from an image.</p>
|
||||
*
|
||||
* @author David Olivier
|
||||
*/
|
||||
public final class Decoder {
|
||||
|
||||
private enum Table {
|
||||
UPPER,
|
||||
LOWER,
|
||||
MIXED,
|
||||
DIGIT,
|
||||
PUNCT,
|
||||
BINARY
|
||||
}
|
||||
|
||||
private static final String[] UPPER_TABLE = {
|
||||
"CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
|
||||
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS"
|
||||
};
|
||||
|
||||
private static final String[] LOWER_TABLE = {
|
||||
"CTRL_PS", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
|
||||
"q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "CTRL_US", "CTRL_ML", "CTRL_DL", "CTRL_BS"
|
||||
};
|
||||
|
||||
private static final String[] MIXED_TABLE = {
|
||||
"CTRL_PS", " ", "\1", "\2", "\3", "\4", "\5", "\6", "\7", "\b", "\t", "\n",
|
||||
"\13", "\f", "\r", "\33", "\34", "\35", "\36", "\37", "@", "\\", "^", "_",
|
||||
"`", "|", "~", "\177", "CTRL_LL", "CTRL_UL", "CTRL_PL", "CTRL_BS"
|
||||
};
|
||||
|
||||
private static final String[] PUNCT_TABLE = {
|
||||
"FLG(n)", "\r", "\r\n", ". ", ", ", ": ", "!", "\"", "#", "$", "%", "&", "'", "(", ")",
|
||||
"*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL"
|
||||
};
|
||||
|
||||
private static final String[] DIGIT_TABLE = {
|
||||
"CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US"
|
||||
};
|
||||
|
||||
private static final Charset DEFAULT_ENCODING = StandardCharsets.ISO_8859_1;
|
||||
|
||||
private AztecDetectorResult ddata;
|
||||
|
||||
public DecoderResult decode(AztecDetectorResult detectorResult) throws FormatException {
|
||||
ddata = detectorResult;
|
||||
BitMatrix matrix = detectorResult.getBits();
|
||||
boolean[] rawbits = extractBits(matrix);
|
||||
CorrectedBitsResult correctedBits = correctBits(rawbits);
|
||||
byte[] rawBytes = convertBoolArrayToByteArray(correctedBits.correctBits);
|
||||
String result = getEncodedData(correctedBits.correctBits);
|
||||
DecoderResult decoderResult =
|
||||
new DecoderResult(rawBytes, result, null, String.format("%d%%", correctedBits.ecLevel));
|
||||
decoderResult.setNumBits(correctedBits.correctBits.length);
|
||||
return decoderResult;
|
||||
}
|
||||
|
||||
// This method is used for testing the high-level encoder
|
||||
public static String highLevelDecode(boolean[] correctedBits) throws FormatException {
|
||||
return getEncodedData(correctedBits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string encoded in the aztec code bits
|
||||
*
|
||||
* @return the decoded string
|
||||
*/
|
||||
private static String getEncodedData(boolean[] correctedBits) throws FormatException {
|
||||
int endIndex = correctedBits.length;
|
||||
Table latchTable = Table.UPPER; // table most recently latched to
|
||||
Table shiftTable = Table.UPPER; // table to use for the next read
|
||||
|
||||
// Final decoded string result
|
||||
// (correctedBits-5) / 4 is an upper bound on the size (all-digit result)
|
||||
StringBuilder result = new StringBuilder((correctedBits.length - 5) / 4);
|
||||
|
||||
// Intermediary buffer of decoded bytes, which is decoded into a string and flushed
|
||||
// when character encoding changes (ECI) or input ends.
|
||||
ByteArrayOutputStream decodedBytes = new ByteArrayOutputStream();
|
||||
Charset encoding = DEFAULT_ENCODING;
|
||||
|
||||
int index = 0;
|
||||
while (index < endIndex) {
|
||||
if (shiftTable == Table.BINARY) {
|
||||
if (endIndex - index < 5) {
|
||||
break;
|
||||
}
|
||||
int length = readCode(correctedBits, index, 5);
|
||||
index += 5;
|
||||
if (length == 0) {
|
||||
if (endIndex - index < 11) {
|
||||
break;
|
||||
}
|
||||
length = readCode(correctedBits, index, 11) + 31;
|
||||
index += 11;
|
||||
}
|
||||
for (int charCount = 0; charCount < length; charCount++) {
|
||||
if (endIndex - index < 8) {
|
||||
index = endIndex; // Force outer loop to exit
|
||||
break;
|
||||
}
|
||||
int code = readCode(correctedBits, index, 8);
|
||||
decodedBytes.write((byte) code);
|
||||
index += 8;
|
||||
}
|
||||
// Go back to whatever mode we had been in
|
||||
shiftTable = latchTable;
|
||||
} else {
|
||||
int size = shiftTable == Table.DIGIT ? 4 : 5;
|
||||
if (endIndex - index < size) {
|
||||
break;
|
||||
}
|
||||
int code = readCode(correctedBits, index, size);
|
||||
index += size;
|
||||
String str = getCharacter(shiftTable, code);
|
||||
if ("FLG(n)".equals(str)) {
|
||||
if (endIndex - index < 3) {
|
||||
break;
|
||||
}
|
||||
int n = readCode(correctedBits, index, 3);
|
||||
index += 3;
|
||||
// flush bytes, FLG changes state
|
||||
try {
|
||||
result.append(decodedBytes.toString(encoding.name()));
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new IllegalStateException(uee);
|
||||
}
|
||||
decodedBytes.reset();
|
||||
switch (n) {
|
||||
case 0:
|
||||
result.append((char) 29); // translate FNC1 as ASCII 29
|
||||
break;
|
||||
case 7:
|
||||
throw FormatException.getFormatInstance(); // FLG(7) is reserved and illegal
|
||||
default:
|
||||
// ECI is decimal integer encoded as 1-6 codes in DIGIT mode
|
||||
int eci = 0;
|
||||
if (endIndex - index < 4 * n) {
|
||||
break;
|
||||
}
|
||||
while (n-- > 0) {
|
||||
int nextDigit = readCode(correctedBits, index, 4);
|
||||
index += 4;
|
||||
if (nextDigit < 2 || nextDigit > 11) {
|
||||
throw FormatException.getFormatInstance(); // Not a decimal digit
|
||||
}
|
||||
eci = eci * 10 + (nextDigit - 2);
|
||||
}
|
||||
CharacterSetECI charsetECI = CharacterSetECI.getCharacterSetECIByValue(eci);
|
||||
if (charsetECI == null) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
encoding = charsetECI.getCharset();
|
||||
}
|
||||
// Go back to whatever mode we had been in
|
||||
shiftTable = latchTable;
|
||||
} else if (str.startsWith("CTRL_")) {
|
||||
// Table changes
|
||||
// ISO/IEC 24778:2008 prescribes ending a shift sequence in the mode from which it was invoked.
|
||||
// That's including when that mode is a shift.
|
||||
// Our test case dlusbs.png for issue #642 exercises that.
|
||||
latchTable = shiftTable; // Latch the current mode, so as to return to Upper after U/S B/S
|
||||
shiftTable = getTable(str.charAt(5));
|
||||
if (str.charAt(6) == 'L') {
|
||||
latchTable = shiftTable;
|
||||
}
|
||||
} else {
|
||||
// Though stored as a table of strings for convenience, codes actually represent 1 or 2 *bytes*.
|
||||
byte[] b = str.getBytes(StandardCharsets.US_ASCII);
|
||||
decodedBytes.write(b, 0, b.length);
|
||||
// Go back to whatever mode we had been in
|
||||
shiftTable = latchTable;
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
result.append(decodedBytes.toString(encoding.name()));
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
// can't happen
|
||||
throw new IllegalStateException(uee);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the table corresponding to the char passed
|
||||
*/
|
||||
private static Table getTable(char t) {
|
||||
switch (t) {
|
||||
case 'L':
|
||||
return Table.LOWER;
|
||||
case 'P':
|
||||
return Table.PUNCT;
|
||||
case 'M':
|
||||
return Table.MIXED;
|
||||
case 'D':
|
||||
return Table.DIGIT;
|
||||
case 'B':
|
||||
return Table.BINARY;
|
||||
case 'U':
|
||||
default:
|
||||
return Table.UPPER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the character (or string) corresponding to the passed code in the given table
|
||||
*
|
||||
* @param table the table used
|
||||
* @param code the code of the character
|
||||
*/
|
||||
private static String getCharacter(Table table, int code) {
|
||||
switch (table) {
|
||||
case UPPER:
|
||||
return UPPER_TABLE[code];
|
||||
case LOWER:
|
||||
return LOWER_TABLE[code];
|
||||
case MIXED:
|
||||
return MIXED_TABLE[code];
|
||||
case PUNCT:
|
||||
return PUNCT_TABLE[code];
|
||||
case DIGIT:
|
||||
return DIGIT_TABLE[code];
|
||||
default:
|
||||
// Should not reach here.
|
||||
throw new IllegalStateException("Bad table");
|
||||
}
|
||||
}
|
||||
|
||||
static final class CorrectedBitsResult {
|
||||
private final boolean[] correctBits;
|
||||
private final int ecLevel;
|
||||
|
||||
CorrectedBitsResult(boolean[] correctBits, int ecLevel) {
|
||||
this.correctBits = correctBits;
|
||||
this.ecLevel = ecLevel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Performs RS error correction on an array of bits.</p>
|
||||
*
|
||||
* @return the corrected array
|
||||
* @throws FormatException if the input contains too many errors
|
||||
*/
|
||||
private CorrectedBitsResult correctBits(boolean[] rawbits) throws FormatException {
|
||||
GenericGF gf;
|
||||
int codewordSize;
|
||||
|
||||
if (ddata.getNbLayers() <= 2) {
|
||||
codewordSize = 6;
|
||||
gf = GenericGF.AZTEC_DATA_6;
|
||||
} else if (ddata.getNbLayers() <= 8) {
|
||||
codewordSize = 8;
|
||||
gf = GenericGF.AZTEC_DATA_8;
|
||||
} else if (ddata.getNbLayers() <= 22) {
|
||||
codewordSize = 10;
|
||||
gf = GenericGF.AZTEC_DATA_10;
|
||||
} else {
|
||||
codewordSize = 12;
|
||||
gf = GenericGF.AZTEC_DATA_12;
|
||||
}
|
||||
|
||||
int numDataCodewords = ddata.getNbDatablocks();
|
||||
int numCodewords = rawbits.length / codewordSize;
|
||||
if (numCodewords < numDataCodewords) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
int offset = rawbits.length % codewordSize;
|
||||
|
||||
int[] dataWords = new int[numCodewords];
|
||||
for (int i = 0; i < numCodewords; i++, offset += codewordSize) {
|
||||
dataWords[i] = readCode(rawbits, offset, codewordSize);
|
||||
}
|
||||
|
||||
try {
|
||||
ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(gf);
|
||||
rsDecoder.decode(dataWords, numCodewords - numDataCodewords);
|
||||
} catch (ReedSolomonException ex) {
|
||||
throw FormatException.getFormatInstance(ex);
|
||||
}
|
||||
|
||||
// Now perform the unstuffing operation.
|
||||
// First, count how many bits are going to be thrown out as stuffing
|
||||
int mask = (1 << codewordSize) - 1;
|
||||
int stuffedBits = 0;
|
||||
for (int i = 0; i < numDataCodewords; i++) {
|
||||
int dataWord = dataWords[i];
|
||||
if (dataWord == 0 || dataWord == mask) {
|
||||
throw FormatException.getFormatInstance();
|
||||
} else if (dataWord == 1 || dataWord == mask - 1) {
|
||||
stuffedBits++;
|
||||
}
|
||||
}
|
||||
// Now, actually unpack the bits and remove the stuffing
|
||||
boolean[] correctedBits = new boolean[numDataCodewords * codewordSize - stuffedBits];
|
||||
int index = 0;
|
||||
for (int i = 0; i < numDataCodewords; i++) {
|
||||
int dataWord = dataWords[i];
|
||||
if (dataWord == 1 || dataWord == mask - 1) {
|
||||
// next codewordSize-1 bits are all zeros or all ones
|
||||
Arrays.fill(correctedBits, index, index + codewordSize - 1, dataWord > 1);
|
||||
index += codewordSize - 1;
|
||||
} else {
|
||||
for (int bit = codewordSize - 1; bit >= 0; --bit) {
|
||||
correctedBits[index++] = (dataWord & (1 << bit)) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new CorrectedBitsResult(correctedBits, 100 * (numCodewords - numDataCodewords) / numCodewords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the array of bits from an Aztec Code matrix
|
||||
*
|
||||
* @return the array of bits
|
||||
*/
|
||||
private boolean[] extractBits(BitMatrix matrix) {
|
||||
boolean compact = ddata.isCompact();
|
||||
int layers = ddata.getNbLayers();
|
||||
int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines
|
||||
int[] alignmentMap = new int[baseMatrixSize];
|
||||
boolean[] rawbits = new boolean[totalBitsInLayer(layers, compact)];
|
||||
|
||||
if (compact) {
|
||||
for (int i = 0; i < alignmentMap.length; i++) {
|
||||
alignmentMap[i] = i;
|
||||
}
|
||||
} else {
|
||||
int matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
|
||||
int origCenter = baseMatrixSize / 2;
|
||||
int center = matrixSize / 2;
|
||||
for (int i = 0; i < origCenter; i++) {
|
||||
int newOffset = i + i / 15;
|
||||
alignmentMap[origCenter - i - 1] = center - newOffset - 1;
|
||||
alignmentMap[origCenter + i] = center + newOffset + 1;
|
||||
}
|
||||
}
|
||||
for (int i = 0, rowOffset = 0; i < layers; i++) {
|
||||
int rowSize = (layers - i) * 4 + (compact ? 9 : 12);
|
||||
// The top-left most point of this layer is <low, low> (not including alignment lines)
|
||||
int low = i * 2;
|
||||
// The bottom-right most point of this layer is <high, high> (not including alignment lines)
|
||||
int high = baseMatrixSize - 1 - low;
|
||||
// We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows
|
||||
for (int j = 0; j < rowSize; j++) {
|
||||
int columnOffset = j * 2;
|
||||
for (int k = 0; k < 2; k++) {
|
||||
// left column
|
||||
rawbits[rowOffset + columnOffset + k] =
|
||||
matrix.get(alignmentMap[low + k], alignmentMap[low + j]);
|
||||
// bottom row
|
||||
rawbits[rowOffset + 2 * rowSize + columnOffset + k] =
|
||||
matrix.get(alignmentMap[low + j], alignmentMap[high - k]);
|
||||
// right column
|
||||
rawbits[rowOffset + 4 * rowSize + columnOffset + k] =
|
||||
matrix.get(alignmentMap[high - k], alignmentMap[high - j]);
|
||||
// top row
|
||||
rawbits[rowOffset + 6 * rowSize + columnOffset + k] =
|
||||
matrix.get(alignmentMap[high - j], alignmentMap[low + k]);
|
||||
}
|
||||
}
|
||||
rowOffset += rowSize * 8;
|
||||
}
|
||||
return rawbits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a code of given length and at given index in an array of bits
|
||||
*/
|
||||
private static int readCode(boolean[] rawbits, int startIndex, int length) {
|
||||
int res = 0;
|
||||
for (int i = startIndex; i < startIndex + length; i++) {
|
||||
res <<= 1;
|
||||
if (rawbits[i]) {
|
||||
res |= 0x01;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a code of length 8 in an array of bits, padding with zeros
|
||||
*/
|
||||
private static byte readByte(boolean[] rawbits, int startIndex) {
|
||||
int n = rawbits.length - startIndex;
|
||||
if (n >= 8) {
|
||||
return (byte) readCode(rawbits, startIndex, 8);
|
||||
}
|
||||
return (byte) (readCode(rawbits, startIndex, n) << (8 - n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Packs a bit array into bytes, most significant bit first
|
||||
*/
|
||||
static byte[] convertBoolArrayToByteArray(boolean[] boolArr) {
|
||||
byte[] byteArr = new byte[(boolArr.length + 7) / 8];
|
||||
for (int i = 0; i < byteArr.length; i++) {
|
||||
byteArr[i] = readByte(boolArr, 8 * i);
|
||||
}
|
||||
return byteArr;
|
||||
}
|
||||
|
||||
private static int totalBitsInLayer(int layers, boolean compact) {
|
||||
return ((compact ? 88 : 112) + 16 * layers) * layers;
|
||||
}
|
||||
}
|
||||
603
src/aztec/detector/Detector.java
Normal file
603
src/aztec/detector/Detector.java
Normal file
@@ -0,0 +1,603 @@
|
||||
/*
|
||||
* Copyright 2010 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.aztec.detector;
|
||||
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.aztec.AztecDetectorResult;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.GridSampler;
|
||||
import com.google.zxing.common.detector.MathUtils;
|
||||
import com.google.zxing.common.detector.WhiteRectangleDetector;
|
||||
import com.google.zxing.common.reedsolomon.GenericGF;
|
||||
import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
|
||||
import com.google.zxing.common.reedsolomon.ReedSolomonException;
|
||||
|
||||
/**
|
||||
* Encapsulates logic that can detect an Aztec Code in an image, even if the Aztec Code
|
||||
* is rotated or skewed, or partially obscured.
|
||||
*
|
||||
* @author David Olivier
|
||||
* @author Frank Yellin
|
||||
*/
|
||||
public final class Detector {
|
||||
|
||||
private static final int[] EXPECTED_CORNER_BITS = {
|
||||
0xee0, // 07340 XXX .XX X.. ...
|
||||
0x1dc, // 00734 ... XXX .XX X..
|
||||
0x83b, // 04073 X.. ... XXX .XX
|
||||
0x707, // 03407 .XX X.. ... XXX
|
||||
};
|
||||
|
||||
private final BitMatrix image;
|
||||
|
||||
private boolean compact;
|
||||
private int nbLayers;
|
||||
private int nbDataBlocks;
|
||||
private int nbCenterLayers;
|
||||
private int shift;
|
||||
|
||||
public Detector(BitMatrix image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public AztecDetectorResult detect() throws NotFoundException {
|
||||
return detect(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects an Aztec Code in an image.
|
||||
*
|
||||
* @param isMirror if true, image is a mirror-image of original
|
||||
* @return {@link AztecDetectorResult} encapsulating results of detecting an Aztec Code
|
||||
* @throws NotFoundException if no Aztec Code can be found
|
||||
*/
|
||||
public AztecDetectorResult detect(boolean isMirror) throws NotFoundException {
|
||||
|
||||
// 1. Get the center of the aztec matrix
|
||||
Point pCenter = getMatrixCenter();
|
||||
|
||||
// 2. Get the center points of the four diagonal points just outside the bull's eye
|
||||
// [topRight, bottomRight, bottomLeft, topLeft]
|
||||
ResultPoint[] bullsEyeCorners = getBullsEyeCorners(pCenter);
|
||||
|
||||
if (isMirror) {
|
||||
ResultPoint temp = bullsEyeCorners[0];
|
||||
bullsEyeCorners[0] = bullsEyeCorners[2];
|
||||
bullsEyeCorners[2] = temp;
|
||||
}
|
||||
|
||||
// 3. Get the size of the matrix and other parameters from the bull's eye
|
||||
extractParameters(bullsEyeCorners);
|
||||
|
||||
// 4. Sample the grid
|
||||
BitMatrix bits = sampleGrid(image,
|
||||
bullsEyeCorners[shift % 4],
|
||||
bullsEyeCorners[(shift + 1) % 4],
|
||||
bullsEyeCorners[(shift + 2) % 4],
|
||||
bullsEyeCorners[(shift + 3) % 4]);
|
||||
|
||||
// 5. Get the corners of the matrix.
|
||||
ResultPoint[] corners = getMatrixCornerPoints(bullsEyeCorners);
|
||||
|
||||
return new AztecDetectorResult(bits, corners, compact, nbDataBlocks, nbLayers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the number of data layers and data blocks from the layer around the bull's eye.
|
||||
*
|
||||
* @param bullsEyeCorners the array of bull's eye corners
|
||||
* @throws NotFoundException in case of too many errors or invalid parameters
|
||||
*/
|
||||
private void extractParameters(ResultPoint[] bullsEyeCorners) throws NotFoundException {
|
||||
if (!isValid(bullsEyeCorners[0]) || !isValid(bullsEyeCorners[1]) ||
|
||||
!isValid(bullsEyeCorners[2]) || !isValid(bullsEyeCorners[3])) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
int length = 2 * nbCenterLayers;
|
||||
// Get the bits around the bull's eye
|
||||
int[] sides = {
|
||||
sampleLine(bullsEyeCorners[0], bullsEyeCorners[1], length), // Right side
|
||||
sampleLine(bullsEyeCorners[1], bullsEyeCorners[2], length), // Bottom
|
||||
sampleLine(bullsEyeCorners[2], bullsEyeCorners[3], length), // Left side
|
||||
sampleLine(bullsEyeCorners[3], bullsEyeCorners[0], length) // Top
|
||||
};
|
||||
|
||||
// bullsEyeCorners[shift] is the corner of the bulls'eye that has three
|
||||
// orientation marks.
|
||||
// sides[shift] is the row/column that goes from the corner with three
|
||||
// orientation marks to the corner with two.
|
||||
shift = getRotation(sides, length);
|
||||
|
||||
// Flatten the parameter bits into a single 28- or 40-bit long
|
||||
long parameterData = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int side = sides[(shift + i) % 4];
|
||||
if (compact) {
|
||||
// Each side of the form ..XXXXXXX. where Xs are parameter data
|
||||
parameterData <<= 7;
|
||||
parameterData += (side >> 1) & 0x7F;
|
||||
} else {
|
||||
// Each side of the form ..XXXXX.XXXXX. where Xs are parameter data
|
||||
parameterData <<= 10;
|
||||
parameterData += ((side >> 2) & (0x1f << 5)) + ((side >> 1) & 0x1F);
|
||||
}
|
||||
}
|
||||
|
||||
// Corrects parameter data using RS. Returns just the data portion
|
||||
// without the error correction.
|
||||
int correctedData = getCorrectedParameterData(parameterData, compact);
|
||||
|
||||
if (compact) {
|
||||
// 8 bits: 2 bits layers and 6 bits data blocks
|
||||
nbLayers = (correctedData >> 6) + 1;
|
||||
nbDataBlocks = (correctedData & 0x3F) + 1;
|
||||
} else {
|
||||
// 16 bits: 5 bits layers and 11 bits data blocks
|
||||
nbLayers = (correctedData >> 11) + 1;
|
||||
nbDataBlocks = (correctedData & 0x7FF) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getRotation(int[] sides, int length) throws NotFoundException {
|
||||
// In a normal pattern, we expect to See
|
||||
// ** .* D A
|
||||
// * *
|
||||
//
|
||||
// . *
|
||||
// .. .. C B
|
||||
//
|
||||
// Grab the 3 bits from each of the sides the form the locator pattern and concatenate
|
||||
// into a 12-bit integer. Start with the bit at A
|
||||
int cornerBits = 0;
|
||||
for (int side : sides) {
|
||||
// XX......X where X's are orientation marks
|
||||
int t = ((side >> (length - 2)) << 1) + (side & 1);
|
||||
cornerBits = (cornerBits << 3) + t;
|
||||
}
|
||||
// Mov the bottom bit to the top, so that the three bits of the locator pattern at A are
|
||||
// together. cornerBits is now:
|
||||
// 3 orientation bits at A || 3 orientation bits at B || ... || 3 orientation bits at D
|
||||
cornerBits = ((cornerBits & 1) << 11) + (cornerBits >> 1);
|
||||
// The result shift indicates which element of BullsEyeCorners[] goes into the top-left
|
||||
// corner. Since the four rotation values have a Hamming distance of 8, we
|
||||
// can easily tolerate two errors.
|
||||
for (int shift = 0; shift < 4; shift++) {
|
||||
if (Integer.bitCount(cornerBits ^ EXPECTED_CORNER_BITS[shift]) <= 2) {
|
||||
return shift;
|
||||
}
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Corrects the parameter bits using Reed-Solomon algorithm.
|
||||
*
|
||||
* @param parameterData parameter bits
|
||||
* @param compact true if this is a compact Aztec code
|
||||
* @throws NotFoundException if the array contains too many errors
|
||||
*/
|
||||
private static int getCorrectedParameterData(long parameterData, boolean compact) throws NotFoundException {
|
||||
int numCodewords;
|
||||
int numDataCodewords;
|
||||
|
||||
if (compact) {
|
||||
numCodewords = 7;
|
||||
numDataCodewords = 2;
|
||||
} else {
|
||||
numCodewords = 10;
|
||||
numDataCodewords = 4;
|
||||
}
|
||||
|
||||
int numECCodewords = numCodewords - numDataCodewords;
|
||||
int[] parameterWords = new int[numCodewords];
|
||||
for (int i = numCodewords - 1; i >= 0; --i) {
|
||||
parameterWords[i] = (int) parameterData & 0xF;
|
||||
parameterData >>= 4;
|
||||
}
|
||||
try {
|
||||
ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(GenericGF.AZTEC_PARAM);
|
||||
rsDecoder.decode(parameterWords, numECCodewords);
|
||||
} catch (ReedSolomonException ignored) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
// Toss the error correction. Just return the data as an integer
|
||||
int result = 0;
|
||||
for (int i = 0; i < numDataCodewords; i++) {
|
||||
result = (result << 4) + parameterWords[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the corners of a bull-eye centered on the passed point.
|
||||
* This returns the centers of the diagonal points just outside the bull's eye
|
||||
* Returns [topRight, bottomRight, bottomLeft, topLeft]
|
||||
*
|
||||
* @param pCenter Center point
|
||||
* @return The corners of the bull-eye
|
||||
* @throws NotFoundException If no valid bull-eye can be found
|
||||
*/
|
||||
private ResultPoint[] getBullsEyeCorners(Point pCenter) throws NotFoundException {
|
||||
|
||||
Point pina = pCenter;
|
||||
Point pinb = pCenter;
|
||||
Point pinc = pCenter;
|
||||
Point pind = pCenter;
|
||||
|
||||
boolean color = true;
|
||||
|
||||
for (nbCenterLayers = 1; nbCenterLayers < 9; nbCenterLayers++) {
|
||||
Point pouta = getFirstDifferent(pina, color, 1, -1);
|
||||
Point poutb = getFirstDifferent(pinb, color, 1, 1);
|
||||
Point poutc = getFirstDifferent(pinc, color, -1, 1);
|
||||
Point poutd = getFirstDifferent(pind, color, -1, -1);
|
||||
|
||||
//d a
|
||||
//
|
||||
//c b
|
||||
|
||||
if (nbCenterLayers > 2) {
|
||||
float q = distance(poutd, pouta) * nbCenterLayers / (distance(pind, pina) * (nbCenterLayers + 2));
|
||||
if (q < 0.75 || q > 1.25 || !isWhiteOrBlackRectangle(pouta, poutb, poutc, poutd)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pina = pouta;
|
||||
pinb = poutb;
|
||||
pinc = poutc;
|
||||
pind = poutd;
|
||||
|
||||
color = !color;
|
||||
}
|
||||
|
||||
if (nbCenterLayers != 5 && nbCenterLayers != 7) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
compact = nbCenterLayers == 5;
|
||||
|
||||
// Expand the square by .5 pixel in each direction so that we're on the border
|
||||
// between the white square and the black square
|
||||
ResultPoint pinax = new ResultPoint(pina.getX() + 0.5f, pina.getY() - 0.5f);
|
||||
ResultPoint pinbx = new ResultPoint(pinb.getX() + 0.5f, pinb.getY() + 0.5f);
|
||||
ResultPoint pincx = new ResultPoint(pinc.getX() - 0.5f, pinc.getY() + 0.5f);
|
||||
ResultPoint pindx = new ResultPoint(pind.getX() - 0.5f, pind.getY() - 0.5f);
|
||||
|
||||
// Expand the square so that its corners are the centers of the points
|
||||
// just outside the bull's eye.
|
||||
return expandSquare(new ResultPoint[]{pinax, pinbx, pincx, pindx},
|
||||
2 * nbCenterLayers - 3,
|
||||
2 * nbCenterLayers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a candidate center point of an Aztec code from an image
|
||||
*
|
||||
* @return the center point
|
||||
*/
|
||||
private Point getMatrixCenter() {
|
||||
|
||||
ResultPoint pointA;
|
||||
ResultPoint pointB;
|
||||
ResultPoint pointC;
|
||||
ResultPoint pointD;
|
||||
|
||||
//Get a white rectangle that can be the border of the matrix in center bull's eye or
|
||||
try {
|
||||
|
||||
ResultPoint[] cornerPoints = new WhiteRectangleDetector(image).detect();
|
||||
pointA = cornerPoints[0];
|
||||
pointB = cornerPoints[1];
|
||||
pointC = cornerPoints[2];
|
||||
pointD = cornerPoints[3];
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
|
||||
// This exception can be in case the initial rectangle is white
|
||||
// In that case, surely in the bull's eye, we try to expand the rectangle.
|
||||
int cx = image.getWidth() / 2;
|
||||
int cy = image.getHeight() / 2;
|
||||
pointA = getFirstDifferent(new Point(cx + 7, cy - 7), false, 1, -1).toResultPoint();
|
||||
pointB = getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint();
|
||||
pointC = getFirstDifferent(new Point(cx - 7, cy + 7), false, -1, 1).toResultPoint();
|
||||
pointD = getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint();
|
||||
|
||||
}
|
||||
|
||||
//Compute the center of the rectangle
|
||||
int cx = MathUtils.round((pointA.getX() + pointD.getX() + pointB.getX() + pointC.getX()) / 4.0f);
|
||||
int cy = MathUtils.round((pointA.getY() + pointD.getY() + pointB.getY() + pointC.getY()) / 4.0f);
|
||||
|
||||
// Redetermine the white rectangle starting from previously computed center.
|
||||
// This will ensure that we end up with a white rectangle in center bull's eye
|
||||
// in order to compute a more accurate center.
|
||||
try {
|
||||
ResultPoint[] cornerPoints = new WhiteRectangleDetector(image, 15, cx, cy).detect();
|
||||
pointA = cornerPoints[0];
|
||||
pointB = cornerPoints[1];
|
||||
pointC = cornerPoints[2];
|
||||
pointD = cornerPoints[3];
|
||||
} catch (NotFoundException e) {
|
||||
// This exception can be in case the initial rectangle is white
|
||||
// In that case we try to expand the rectangle.
|
||||
pointA = getFirstDifferent(new Point(cx + 7, cy - 7), false, 1, -1).toResultPoint();
|
||||
pointB = getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint();
|
||||
pointC = getFirstDifferent(new Point(cx - 7, cy + 7), false, -1, 1).toResultPoint();
|
||||
pointD = getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint();
|
||||
}
|
||||
|
||||
// Recompute the center of the rectangle
|
||||
cx = MathUtils.round((pointA.getX() + pointD.getX() + pointB.getX() + pointC.getX()) / 4.0f);
|
||||
cy = MathUtils.round((pointA.getY() + pointD.getY() + pointB.getY() + pointC.getY()) / 4.0f);
|
||||
|
||||
return new Point(cx, cy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Aztec code corners from the bull's eye corners and the parameters.
|
||||
*
|
||||
* @param bullsEyeCorners the array of bull's eye corners
|
||||
* @return the array of aztec code corners
|
||||
*/
|
||||
private ResultPoint[] getMatrixCornerPoints(ResultPoint[] bullsEyeCorners) {
|
||||
return expandSquare(bullsEyeCorners, 2 * nbCenterLayers, getDimension());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BitMatrix by sampling the provided image.
|
||||
* topLeft, topRight, bottomRight, and bottomLeft are the centers of the squares on the
|
||||
* diagonal just outside the bull's eye.
|
||||
*/
|
||||
private BitMatrix sampleGrid(BitMatrix image,
|
||||
ResultPoint topLeft,
|
||||
ResultPoint topRight,
|
||||
ResultPoint bottomRight,
|
||||
ResultPoint bottomLeft) throws NotFoundException {
|
||||
|
||||
GridSampler sampler = GridSampler.getInstance();
|
||||
int dimension = getDimension();
|
||||
|
||||
float low = dimension / 2.0f - nbCenterLayers;
|
||||
float high = dimension / 2.0f + nbCenterLayers;
|
||||
|
||||
return sampler.sampleGrid(image,
|
||||
dimension,
|
||||
dimension,
|
||||
low, low, // topleft
|
||||
high, low, // topright
|
||||
high, high, // bottomright
|
||||
low, high, // bottomleft
|
||||
topLeft.getX(), topLeft.getY(),
|
||||
topRight.getX(), topRight.getY(),
|
||||
bottomRight.getX(), bottomRight.getY(),
|
||||
bottomLeft.getX(), bottomLeft.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Samples a line.
|
||||
*
|
||||
* @param p1 start point (inclusive)
|
||||
* @param p2 end point (exclusive)
|
||||
* @param size number of bits
|
||||
* @return the array of bits as an int (first bit is high-order bit of result)
|
||||
*/
|
||||
private int sampleLine(ResultPoint p1, ResultPoint p2, int size) {
|
||||
int result = 0;
|
||||
|
||||
float d = distance(p1, p2);
|
||||
float moduleSize = d / size;
|
||||
float px = p1.getX();
|
||||
float py = p1.getY();
|
||||
float dx = moduleSize * (p2.getX() - p1.getX()) / d;
|
||||
float dy = moduleSize * (p2.getY() - p1.getY()) / d;
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (image.get(MathUtils.round(px + i * dx), MathUtils.round(py + i * dy))) {
|
||||
result |= 1 << (size - i - 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the border of the rectangle passed in parameter is compound of white points only
|
||||
* or black points only
|
||||
*/
|
||||
private boolean isWhiteOrBlackRectangle(Point p1,
|
||||
Point p2,
|
||||
Point p3,
|
||||
Point p4) {
|
||||
|
||||
int corr = 3;
|
||||
|
||||
p1 = new Point(Math.max(0, p1.getX() - corr), Math.min(image.getHeight() - 1, p1.getY() + corr));
|
||||
p2 = new Point(Math.max(0, p2.getX() - corr), Math.max(0, p2.getY() - corr));
|
||||
p3 = new Point(Math.min(image.getWidth() - 1, p3.getX() + corr),
|
||||
Math.max(0, Math.min(image.getHeight() - 1, p3.getY() - corr)));
|
||||
p4 = new Point(Math.min(image.getWidth() - 1, p4.getX() + corr),
|
||||
Math.min(image.getHeight() - 1, p4.getY() + corr));
|
||||
|
||||
int cInit = getColor(p4, p1);
|
||||
|
||||
if (cInit == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int c = getColor(p1, p2);
|
||||
|
||||
if (c != cInit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
c = getColor(p2, p3);
|
||||
|
||||
if (c != cInit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
c = getColor(p3, p4);
|
||||
|
||||
return c == cInit;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color of a segment
|
||||
*
|
||||
* @return 1 if segment more than 90% black, -1 if segment is more than 90% white, 0 else
|
||||
*/
|
||||
private int getColor(Point p1, Point p2) {
|
||||
float d = distance(p1, p2);
|
||||
if (d == 0.0f) {
|
||||
return 0;
|
||||
}
|
||||
float dx = (p2.getX() - p1.getX()) / d;
|
||||
float dy = (p2.getY() - p1.getY()) / d;
|
||||
int error = 0;
|
||||
|
||||
float px = p1.getX();
|
||||
float py = p1.getY();
|
||||
|
||||
boolean colorModel = image.get(p1.getX(), p1.getY());
|
||||
|
||||
int iMax = (int) Math.floor(d);
|
||||
for (int i = 0; i < iMax; i++) {
|
||||
if (image.get(MathUtils.round(px), MathUtils.round(py)) != colorModel) {
|
||||
error++;
|
||||
}
|
||||
px += dx;
|
||||
py += dy;
|
||||
}
|
||||
|
||||
float errRatio = error / d;
|
||||
|
||||
if (errRatio > 0.1f && errRatio < 0.9f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (errRatio <= 0.1f) == colorModel ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the coordinate of the first point with a different color in the given direction
|
||||
*/
|
||||
private Point getFirstDifferent(Point init, boolean color, int dx, int dy) {
|
||||
int x = init.getX() + dx;
|
||||
int y = init.getY() + dy;
|
||||
|
||||
while (isValid(x, y) && image.get(x, y) == color) {
|
||||
x += dx;
|
||||
y += dy;
|
||||
}
|
||||
|
||||
x -= dx;
|
||||
y -= dy;
|
||||
|
||||
while (isValid(x, y) && image.get(x, y) == color) {
|
||||
x += dx;
|
||||
}
|
||||
x -= dx;
|
||||
|
||||
while (isValid(x, y) && image.get(x, y) == color) {
|
||||
y += dy;
|
||||
}
|
||||
y -= dy;
|
||||
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the square represented by the corner points by pushing out equally in all directions
|
||||
*
|
||||
* @param cornerPoints the corners of the square, which has the bull's eye at its center
|
||||
* @param oldSide the original length of the side of the square in the target bit matrix
|
||||
* @param newSide the new length of the size of the square in the target bit matrix
|
||||
* @return the corners of the expanded square
|
||||
*/
|
||||
private static ResultPoint[] expandSquare(ResultPoint[] cornerPoints, int oldSide, int newSide) {
|
||||
float ratio = newSide / (2.0f * oldSide);
|
||||
float dx = cornerPoints[0].getX() - cornerPoints[2].getX();
|
||||
float dy = cornerPoints[0].getY() - cornerPoints[2].getY();
|
||||
float centerx = (cornerPoints[0].getX() + cornerPoints[2].getX()) / 2.0f;
|
||||
float centery = (cornerPoints[0].getY() + cornerPoints[2].getY()) / 2.0f;
|
||||
|
||||
ResultPoint result0 = new ResultPoint(centerx + ratio * dx, centery + ratio * dy);
|
||||
ResultPoint result2 = new ResultPoint(centerx - ratio * dx, centery - ratio * dy);
|
||||
|
||||
dx = cornerPoints[1].getX() - cornerPoints[3].getX();
|
||||
dy = cornerPoints[1].getY() - cornerPoints[3].getY();
|
||||
centerx = (cornerPoints[1].getX() + cornerPoints[3].getX()) / 2.0f;
|
||||
centery = (cornerPoints[1].getY() + cornerPoints[3].getY()) / 2.0f;
|
||||
ResultPoint result1 = new ResultPoint(centerx + ratio * dx, centery + ratio * dy);
|
||||
ResultPoint result3 = new ResultPoint(centerx - ratio * dx, centery - ratio * dy);
|
||||
|
||||
return new ResultPoint[]{result0, result1, result2, result3};
|
||||
}
|
||||
|
||||
private boolean isValid(int x, int y) {
|
||||
return x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight();
|
||||
}
|
||||
|
||||
private boolean isValid(ResultPoint point) {
|
||||
int x = MathUtils.round(point.getX());
|
||||
int y = MathUtils.round(point.getY());
|
||||
return isValid(x, y);
|
||||
}
|
||||
|
||||
private static float distance(Point a, Point b) {
|
||||
return MathUtils.distance(a.getX(), a.getY(), b.getX(), b.getY());
|
||||
}
|
||||
|
||||
private static float distance(ResultPoint a, ResultPoint b) {
|
||||
return MathUtils.distance(a.getX(), a.getY(), b.getX(), b.getY());
|
||||
}
|
||||
|
||||
private int getDimension() {
|
||||
if (compact) {
|
||||
return 4 * nbLayers + 11;
|
||||
}
|
||||
return 4 * nbLayers + 2 * ((2 * nbLayers + 6) / 15) + 15;
|
||||
}
|
||||
|
||||
static final class Point {
|
||||
private final int x;
|
||||
private final int y;
|
||||
|
||||
ResultPoint toResultPoint() {
|
||||
return new ResultPoint(x, y);
|
||||
}
|
||||
|
||||
Point(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<" + x + ' ' + y + '>';
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/aztec/encoder/AztecCode.java
Normal file
89
src/aztec/encoder/AztecCode.java
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.aztec.encoder;
|
||||
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
/**
|
||||
* Aztec 2D code representation
|
||||
*
|
||||
* @author Rustam Abdullaev
|
||||
*/
|
||||
public final class AztecCode {
|
||||
|
||||
private boolean compact;
|
||||
private int size;
|
||||
private int layers;
|
||||
private int codeWords;
|
||||
private BitMatrix matrix;
|
||||
|
||||
/**
|
||||
* @return {@code true} if compact instead of full mode
|
||||
*/
|
||||
public boolean isCompact() {
|
||||
return compact;
|
||||
}
|
||||
|
||||
public void setCompact(boolean compact) {
|
||||
this.compact = compact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return size in pixels (width and height)
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of levels
|
||||
*/
|
||||
public int getLayers() {
|
||||
return layers;
|
||||
}
|
||||
|
||||
public void setLayers(int layers) {
|
||||
this.layers = layers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of data codewords
|
||||
*/
|
||||
public int getCodeWords() {
|
||||
return codeWords;
|
||||
}
|
||||
|
||||
public void setCodeWords(int codeWords) {
|
||||
this.codeWords = codeWords;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the symbol image
|
||||
*/
|
||||
public BitMatrix getMatrix() {
|
||||
return matrix;
|
||||
}
|
||||
|
||||
public void setMatrix(BitMatrix matrix) {
|
||||
this.matrix = matrix;
|
||||
}
|
||||
|
||||
}
|
||||
61
src/aztec/encoder/BinaryShiftToken.java
Normal file
61
src/aztec/encoder/BinaryShiftToken.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.aztec.encoder;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
final class BinaryShiftToken extends Token {
|
||||
|
||||
private final int binaryShiftStart;
|
||||
private final int binaryShiftByteCount;
|
||||
|
||||
BinaryShiftToken(Token previous,
|
||||
int binaryShiftStart,
|
||||
int binaryShiftByteCount) {
|
||||
super(previous);
|
||||
this.binaryShiftStart = binaryShiftStart;
|
||||
this.binaryShiftByteCount = binaryShiftByteCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendTo(BitArray bitArray, byte[] text) {
|
||||
int bsbc = binaryShiftByteCount;
|
||||
for (int i = 0; i < bsbc; i++) {
|
||||
if (i == 0 || (i == 31 && bsbc <= 62)) {
|
||||
// We need a header before the first character, and before
|
||||
// character 31 when the total byte code is <= 62
|
||||
bitArray.appendBits(31, 5); // BINARY_SHIFT
|
||||
if (bsbc > 62) {
|
||||
bitArray.appendBits(bsbc - 31, 16);
|
||||
} else if (i == 0) {
|
||||
// 1 <= binaryShiftByteCode <= 62
|
||||
bitArray.appendBits(Math.min(bsbc, 31), 5);
|
||||
} else {
|
||||
// 32 <= binaryShiftCount <= 62 and i == 31
|
||||
bitArray.appendBits(bsbc - 31, 5);
|
||||
}
|
||||
}
|
||||
bitArray.appendBits(text[binaryShiftStart + i], 8);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<" + binaryShiftStart + "::" + (binaryShiftStart + binaryShiftByteCount - 1) + '>';
|
||||
}
|
||||
|
||||
}
|
||||
404
src/aztec/encoder/Encoder.java
Normal file
404
src/aztec/encoder/Encoder.java
Normal file
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* 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.aztec.encoder;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.reedsolomon.GenericGF;
|
||||
import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Generates Aztec 2D barcodes.
|
||||
*
|
||||
* @author Rustam Abdullaev
|
||||
*/
|
||||
public final class Encoder {
|
||||
|
||||
public static final int DEFAULT_EC_PERCENT = 33; // default minimal percentage of error check words
|
||||
public static final int DEFAULT_AZTEC_LAYERS = 0;
|
||||
private static final int MAX_NB_BITS = 32;
|
||||
private static final int MAX_NB_BITS_COMPACT = 4;
|
||||
|
||||
private static final int[] WORD_SIZE = {
|
||||
4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
|
||||
12, 12, 12, 12, 12, 12, 12, 12, 12, 12
|
||||
};
|
||||
|
||||
private Encoder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given string content as an Aztec symbol (without ECI code)
|
||||
*
|
||||
* @param data input data string; must be encodable as ISO/IEC 8859-1 (Latin-1)
|
||||
* @return Aztec symbol matrix with metadata
|
||||
*/
|
||||
public static AztecCode encode(String data) {
|
||||
return encode(data.getBytes(StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given string content as an Aztec symbol (without ECI code)
|
||||
*
|
||||
* @param data input data string; must be encodable as ISO/IEC 8859-1 (Latin-1)
|
||||
* @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
|
||||
* a minimum of 23% + 3 words is recommended)
|
||||
* @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
|
||||
* @return Aztec symbol matrix with metadata
|
||||
*/
|
||||
public static AztecCode encode(String data, int minECCPercent, int userSpecifiedLayers) {
|
||||
return encode(data.getBytes(StandardCharsets.ISO_8859_1), minECCPercent, userSpecifiedLayers, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given string content as an Aztec symbol
|
||||
*
|
||||
* @param data input data string
|
||||
* @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
|
||||
* a minimum of 23% + 3 words is recommended)
|
||||
* @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
|
||||
* @param charset character set in which to encode string using ECI; if null, no ECI code
|
||||
* will be inserted, and the string must be encodable as ISO/IEC 8859-1
|
||||
* (Latin-1), the default encoding of the symbol.
|
||||
* @return Aztec symbol matrix with metadata
|
||||
*/
|
||||
public static AztecCode encode(String data, int minECCPercent, int userSpecifiedLayers, Charset charset) {
|
||||
byte[] bytes = data.getBytes(null != charset ? charset : StandardCharsets.ISO_8859_1);
|
||||
return encode(bytes, minECCPercent, userSpecifiedLayers, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given binary content as an Aztec symbol (without ECI code)
|
||||
*
|
||||
* @param data input data string
|
||||
* @return Aztec symbol matrix with metadata
|
||||
*/
|
||||
public static AztecCode encode(byte[] data) {
|
||||
return encode(data, DEFAULT_EC_PERCENT, DEFAULT_AZTEC_LAYERS, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given binary content as an Aztec symbol (without ECI code)
|
||||
*
|
||||
* @param data input data string
|
||||
* @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
|
||||
* a minimum of 23% + 3 words is recommended)
|
||||
* @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
|
||||
* @return Aztec symbol matrix with metadata
|
||||
*/
|
||||
public static AztecCode encode(byte[] data, int minECCPercent, int userSpecifiedLayers) {
|
||||
return encode(data, minECCPercent, userSpecifiedLayers, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given binary content as an Aztec symbol
|
||||
*
|
||||
* @param data input data string
|
||||
* @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
|
||||
* a minimum of 23% + 3 words is recommended)
|
||||
* @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
|
||||
* @param charset character set to mark using ECI; if null, no ECI code will be inserted, and the
|
||||
* default encoding of ISO/IEC 8859-1 will be assuming by readers.
|
||||
* @return Aztec symbol matrix with metadata
|
||||
*/
|
||||
public static AztecCode encode(byte[] data, int minECCPercent, int userSpecifiedLayers, Charset charset) {
|
||||
// High-level encode
|
||||
BitArray bits = new HighLevelEncoder(data, charset).encode();
|
||||
|
||||
// stuff bits and choose symbol size
|
||||
int eccBits = bits.getSize() * minECCPercent / 100 + 11;
|
||||
int totalSizeBits = bits.getSize() + eccBits;
|
||||
boolean compact;
|
||||
int layers;
|
||||
int totalBitsInLayer;
|
||||
int wordSize;
|
||||
BitArray stuffedBits;
|
||||
if (userSpecifiedLayers != DEFAULT_AZTEC_LAYERS) {
|
||||
compact = userSpecifiedLayers < 0;
|
||||
layers = Math.abs(userSpecifiedLayers);
|
||||
if (layers > (compact ? MAX_NB_BITS_COMPACT : MAX_NB_BITS)) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Illegal value %s for layers", userSpecifiedLayers));
|
||||
}
|
||||
totalBitsInLayer = totalBitsInLayer(layers, compact);
|
||||
wordSize = WORD_SIZE[layers];
|
||||
int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
|
||||
stuffedBits = stuffBits(bits, wordSize);
|
||||
if (stuffedBits.getSize() + eccBits > usableBitsInLayers) {
|
||||
throw new IllegalArgumentException("Data to large for user specified layer");
|
||||
}
|
||||
if (compact && stuffedBits.getSize() > wordSize * 64) {
|
||||
// Compact format only allows 64 data words, though C4 can hold more words than that
|
||||
throw new IllegalArgumentException("Data to large for user specified layer");
|
||||
}
|
||||
} else {
|
||||
wordSize = 0;
|
||||
stuffedBits = null;
|
||||
// We look at the possible table sizes in the order Compact1, Compact2, Compact3,
|
||||
// Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1)
|
||||
// is the same size, but has more data.
|
||||
for (int i = 0; ; i++) {
|
||||
if (i > MAX_NB_BITS) {
|
||||
throw new IllegalArgumentException("Data too large for an Aztec code");
|
||||
}
|
||||
compact = i <= 3;
|
||||
layers = compact ? i + 1 : i;
|
||||
totalBitsInLayer = totalBitsInLayer(layers, compact);
|
||||
if (totalSizeBits > totalBitsInLayer) {
|
||||
continue;
|
||||
}
|
||||
// [Re]stuff the bits if this is the first opportunity, or if the
|
||||
// wordSize has changed
|
||||
if (stuffedBits == null || wordSize != WORD_SIZE[layers]) {
|
||||
wordSize = WORD_SIZE[layers];
|
||||
stuffedBits = stuffBits(bits, wordSize);
|
||||
}
|
||||
int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
|
||||
if (compact && stuffedBits.getSize() > wordSize * 64) {
|
||||
// Compact format only allows 64 data words, though C4 can hold more words than that
|
||||
continue;
|
||||
}
|
||||
if (stuffedBits.getSize() + eccBits <= usableBitsInLayers) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
BitArray messageBits = generateCheckWords(stuffedBits, totalBitsInLayer, wordSize);
|
||||
|
||||
// generate mode message
|
||||
int messageSizeInWords = stuffedBits.getSize() / wordSize;
|
||||
BitArray modeMessage = generateModeMessage(compact, layers, messageSizeInWords);
|
||||
|
||||
// allocate symbol
|
||||
int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines
|
||||
int[] alignmentMap = new int[baseMatrixSize];
|
||||
int matrixSize;
|
||||
if (compact) {
|
||||
// no alignment marks in compact mode, alignmentMap is a no-op
|
||||
matrixSize = baseMatrixSize;
|
||||
for (int i = 0; i < alignmentMap.length; i++) {
|
||||
alignmentMap[i] = i;
|
||||
}
|
||||
} else {
|
||||
matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
|
||||
int origCenter = baseMatrixSize / 2;
|
||||
int center = matrixSize / 2;
|
||||
for (int i = 0; i < origCenter; i++) {
|
||||
int newOffset = i + i / 15;
|
||||
alignmentMap[origCenter - i - 1] = center - newOffset - 1;
|
||||
alignmentMap[origCenter + i] = center + newOffset + 1;
|
||||
}
|
||||
}
|
||||
BitMatrix matrix = new BitMatrix(matrixSize);
|
||||
|
||||
// draw data bits
|
||||
for (int i = 0, rowOffset = 0; i < layers; i++) {
|
||||
int rowSize = (layers - i) * 4 + (compact ? 9 : 12);
|
||||
for (int j = 0; j < rowSize; j++) {
|
||||
int columnOffset = j * 2;
|
||||
for (int k = 0; k < 2; k++) {
|
||||
if (messageBits.get(rowOffset + columnOffset + k)) {
|
||||
matrix.set(alignmentMap[i * 2 + k], alignmentMap[i * 2 + j]);
|
||||
}
|
||||
if (messageBits.get(rowOffset + rowSize * 2 + columnOffset + k)) {
|
||||
matrix.set(alignmentMap[i * 2 + j], alignmentMap[baseMatrixSize - 1 - i * 2 - k]);
|
||||
}
|
||||
if (messageBits.get(rowOffset + rowSize * 4 + columnOffset + k)) {
|
||||
matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - k], alignmentMap[baseMatrixSize - 1 - i * 2 - j]);
|
||||
}
|
||||
if (messageBits.get(rowOffset + rowSize * 6 + columnOffset + k)) {
|
||||
matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - j], alignmentMap[i * 2 + k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
rowOffset += rowSize * 8;
|
||||
}
|
||||
|
||||
// draw mode message
|
||||
drawModeMessage(matrix, compact, matrixSize, modeMessage);
|
||||
|
||||
// draw alignment marks
|
||||
if (compact) {
|
||||
drawBullsEye(matrix, matrixSize / 2, 5);
|
||||
} else {
|
||||
drawBullsEye(matrix, matrixSize / 2, 7);
|
||||
for (int i = 0, j = 0; i < baseMatrixSize / 2 - 1; i += 15, j += 16) {
|
||||
for (int k = (matrixSize / 2) & 1; k < matrixSize; k += 2) {
|
||||
matrix.set(matrixSize / 2 - j, k);
|
||||
matrix.set(matrixSize / 2 + j, k);
|
||||
matrix.set(k, matrixSize / 2 - j);
|
||||
matrix.set(k, matrixSize / 2 + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AztecCode aztec = new AztecCode();
|
||||
aztec.setCompact(compact);
|
||||
aztec.setSize(matrixSize);
|
||||
aztec.setLayers(layers);
|
||||
aztec.setCodeWords(messageSizeInWords);
|
||||
aztec.setMatrix(matrix);
|
||||
return aztec;
|
||||
}
|
||||
|
||||
private static void drawBullsEye(BitMatrix matrix, int center, int size) {
|
||||
for (int i = 0; i < size; i += 2) {
|
||||
for (int j = center - i; j <= center + i; j++) {
|
||||
matrix.set(j, center - i);
|
||||
matrix.set(j, center + i);
|
||||
matrix.set(center - i, j);
|
||||
matrix.set(center + i, j);
|
||||
}
|
||||
}
|
||||
matrix.set(center - size, center - size);
|
||||
matrix.set(center - size + 1, center - size);
|
||||
matrix.set(center - size, center - size + 1);
|
||||
matrix.set(center + size, center - size);
|
||||
matrix.set(center + size, center - size + 1);
|
||||
matrix.set(center + size, center + size - 1);
|
||||
}
|
||||
|
||||
static BitArray generateModeMessage(boolean compact, int layers, int messageSizeInWords) {
|
||||
BitArray modeMessage = new BitArray();
|
||||
if (compact) {
|
||||
modeMessage.appendBits(layers - 1, 2);
|
||||
modeMessage.appendBits(messageSizeInWords - 1, 6);
|
||||
modeMessage = generateCheckWords(modeMessage, 28, 4);
|
||||
} else {
|
||||
modeMessage.appendBits(layers - 1, 5);
|
||||
modeMessage.appendBits(messageSizeInWords - 1, 11);
|
||||
modeMessage = generateCheckWords(modeMessage, 40, 4);
|
||||
}
|
||||
return modeMessage;
|
||||
}
|
||||
|
||||
private static void drawModeMessage(BitMatrix matrix, boolean compact, int matrixSize, BitArray modeMessage) {
|
||||
int center = matrixSize / 2;
|
||||
if (compact) {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
int offset = center - 3 + i;
|
||||
if (modeMessage.get(i)) {
|
||||
matrix.set(offset, center - 5);
|
||||
}
|
||||
if (modeMessage.get(i + 7)) {
|
||||
matrix.set(center + 5, offset);
|
||||
}
|
||||
if (modeMessage.get(20 - i)) {
|
||||
matrix.set(offset, center + 5);
|
||||
}
|
||||
if (modeMessage.get(27 - i)) {
|
||||
matrix.set(center - 5, offset);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int offset = center - 5 + i + i / 5;
|
||||
if (modeMessage.get(i)) {
|
||||
matrix.set(offset, center - 7);
|
||||
}
|
||||
if (modeMessage.get(i + 10)) {
|
||||
matrix.set(center + 7, offset);
|
||||
}
|
||||
if (modeMessage.get(29 - i)) {
|
||||
matrix.set(offset, center + 7);
|
||||
}
|
||||
if (modeMessage.get(39 - i)) {
|
||||
matrix.set(center - 7, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BitArray generateCheckWords(BitArray bitArray, int totalBits, int wordSize) {
|
||||
// bitArray is guaranteed to be a multiple of the wordSize, so no padding needed
|
||||
int messageSizeInWords = bitArray.getSize() / wordSize;
|
||||
ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize));
|
||||
int totalWords = totalBits / wordSize;
|
||||
int[] messageWords = bitsToWords(bitArray, wordSize, totalWords);
|
||||
rs.encode(messageWords, totalWords - messageSizeInWords);
|
||||
int startPad = totalBits % wordSize;
|
||||
BitArray messageBits = new BitArray();
|
||||
messageBits.appendBits(0, startPad);
|
||||
for (int messageWord : messageWords) {
|
||||
messageBits.appendBits(messageWord, wordSize);
|
||||
}
|
||||
return messageBits;
|
||||
}
|
||||
|
||||
private static int[] bitsToWords(BitArray stuffedBits, int wordSize, int totalWords) {
|
||||
int[] message = new int[totalWords];
|
||||
int i;
|
||||
int n;
|
||||
for (i = 0, n = stuffedBits.getSize() / wordSize; i < n; i++) {
|
||||
int value = 0;
|
||||
for (int j = 0; j < wordSize; j++) {
|
||||
value |= stuffedBits.get(i * wordSize + j) ? (1 << wordSize - j - 1) : 0;
|
||||
}
|
||||
message[i] = value;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
private static GenericGF getGF(int wordSize) {
|
||||
switch (wordSize) {
|
||||
case 4:
|
||||
return GenericGF.AZTEC_PARAM;
|
||||
case 6:
|
||||
return GenericGF.AZTEC_DATA_6;
|
||||
case 8:
|
||||
return GenericGF.AZTEC_DATA_8;
|
||||
case 10:
|
||||
return GenericGF.AZTEC_DATA_10;
|
||||
case 12:
|
||||
return GenericGF.AZTEC_DATA_12;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported word size " + wordSize);
|
||||
}
|
||||
}
|
||||
|
||||
static BitArray stuffBits(BitArray bits, int wordSize) {
|
||||
BitArray out = new BitArray();
|
||||
|
||||
int n = bits.getSize();
|
||||
int mask = (1 << wordSize) - 2;
|
||||
for (int i = 0; i < n; i += wordSize) {
|
||||
int word = 0;
|
||||
for (int j = 0; j < wordSize; j++) {
|
||||
if (i + j >= n || bits.get(i + j)) {
|
||||
word |= 1 << (wordSize - 1 - j);
|
||||
}
|
||||
}
|
||||
if ((word & mask) == mask) {
|
||||
out.appendBits(word & mask, wordSize);
|
||||
i--;
|
||||
} else if ((word & mask) == 0) {
|
||||
out.appendBits(word | 1, wordSize);
|
||||
i--;
|
||||
} else {
|
||||
out.appendBits(word, wordSize);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static int totalBitsInLayer(int layers, boolean compact) {
|
||||
return ((compact ? 88 : 112) + 16 * layers) * layers;
|
||||
}
|
||||
}
|
||||
325
src/aztec/encoder/HighLevelEncoder.java
Normal file
325
src/aztec/encoder/HighLevelEncoder.java
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* 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.aztec.encoder;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
import com.google.zxing.common.CharacterSetECI;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* This produces nearly optimal encodings of text into the first-level of
|
||||
* encoding used by Aztec code.
|
||||
*
|
||||
* It uses a dynamic algorithm. For each prefix of the string, it determines
|
||||
* a set of encodings that could lead to this prefix. We repeatedly add a
|
||||
* character and generate a new set of optimal encodings until we have read
|
||||
* through the entire input.
|
||||
*
|
||||
* @author Frank Yellin
|
||||
* @author Rustam Abdullaev
|
||||
*/
|
||||
public final class HighLevelEncoder {
|
||||
|
||||
static final String[] MODE_NAMES = {"UPPER", "LOWER", "DIGIT", "MIXED", "PUNCT"};
|
||||
|
||||
static final int MODE_UPPER = 0; // 5 bits
|
||||
static final int MODE_LOWER = 1; // 5 bits
|
||||
static final int MODE_DIGIT = 2; // 4 bits
|
||||
static final int MODE_MIXED = 3; // 5 bits
|
||||
static final int MODE_PUNCT = 4; // 5 bits
|
||||
|
||||
// The Latch Table shows, for each pair of Modes, the optimal method for
|
||||
// getting from one mode to another. In the worst possible case, this can
|
||||
// be up to 14 bits. In the best possible case, we are already there!
|
||||
// The high half-word of each entry gives the number of bits.
|
||||
// The low half-word of each entry are the actual bits necessary to change
|
||||
static final int[][] LATCH_TABLE = {
|
||||
{
|
||||
0,
|
||||
(5 << 16) + 28, // UPPER -> LOWER
|
||||
(5 << 16) + 30, // UPPER -> DIGIT
|
||||
(5 << 16) + 29, // UPPER -> MIXED
|
||||
(10 << 16) + (29 << 5) + 30, // UPPER -> MIXED -> PUNCT
|
||||
},
|
||||
{
|
||||
(9 << 16) + (30 << 4) + 14, // LOWER -> DIGIT -> UPPER
|
||||
0,
|
||||
(5 << 16) + 30, // LOWER -> DIGIT
|
||||
(5 << 16) + 29, // LOWER -> MIXED
|
||||
(10 << 16) + (29 << 5) + 30, // LOWER -> MIXED -> PUNCT
|
||||
},
|
||||
{
|
||||
(4 << 16) + 14, // DIGIT -> UPPER
|
||||
(9 << 16) + (14 << 5) + 28, // DIGIT -> UPPER -> LOWER
|
||||
0,
|
||||
(9 << 16) + (14 << 5) + 29, // DIGIT -> UPPER -> MIXED
|
||||
(14 << 16) + (14 << 10) + (29 << 5) + 30,
|
||||
// DIGIT -> UPPER -> MIXED -> PUNCT
|
||||
},
|
||||
{
|
||||
(5 << 16) + 29, // MIXED -> UPPER
|
||||
(5 << 16) + 28, // MIXED -> LOWER
|
||||
(10 << 16) + (29 << 5) + 30, // MIXED -> UPPER -> DIGIT
|
||||
0,
|
||||
(5 << 16) + 30, // MIXED -> PUNCT
|
||||
},
|
||||
{
|
||||
(5 << 16) + 31, // PUNCT -> UPPER
|
||||
(10 << 16) + (31 << 5) + 28, // PUNCT -> UPPER -> LOWER
|
||||
(10 << 16) + (31 << 5) + 30, // PUNCT -> UPPER -> DIGIT
|
||||
(10 << 16) + (31 << 5) + 29, // PUNCT -> UPPER -> MIXED
|
||||
0,
|
||||
},
|
||||
};
|
||||
|
||||
// A reverse mapping from [mode][char] to the encoding for that character
|
||||
// in that mode. An entry of 0 indicates no mapping exists.
|
||||
private static final int[][] CHAR_MAP = new int[5][256];
|
||||
static {
|
||||
CHAR_MAP[MODE_UPPER][' '] = 1;
|
||||
for (int c = 'A'; c <= 'Z'; c++) {
|
||||
CHAR_MAP[MODE_UPPER][c] = c - 'A' + 2;
|
||||
}
|
||||
CHAR_MAP[MODE_LOWER][' '] = 1;
|
||||
for (int c = 'a'; c <= 'z'; c++) {
|
||||
CHAR_MAP[MODE_LOWER][c] = c - 'a' + 2;
|
||||
}
|
||||
CHAR_MAP[MODE_DIGIT][' '] = 1;
|
||||
for (int c = '0'; c <= '9'; c++) {
|
||||
CHAR_MAP[MODE_DIGIT][c] = c - '0' + 2;
|
||||
}
|
||||
CHAR_MAP[MODE_DIGIT][','] = 12;
|
||||
CHAR_MAP[MODE_DIGIT]['.'] = 13;
|
||||
int[] mixedTable = {
|
||||
'\0', ' ', '\1', '\2', '\3', '\4', '\5', '\6', '\7', '\b', '\t', '\n',
|
||||
'\13', '\f', '\r', '\33', '\34', '\35', '\36', '\37', '@', '\\', '^',
|
||||
'_', '`', '|', '~', '\177'
|
||||
};
|
||||
for (int i = 0; i < mixedTable.length; i++) {
|
||||
CHAR_MAP[MODE_MIXED][mixedTable[i]] = i;
|
||||
}
|
||||
int[] punctTable = {
|
||||
'\0', '\r', '\0', '\0', '\0', '\0', '!', '\'', '#', '$', '%', '&', '\'',
|
||||
'(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?',
|
||||
'[', ']', '{', '}'
|
||||
};
|
||||
for (int i = 0; i < punctTable.length; i++) {
|
||||
if (punctTable[i] > 0) {
|
||||
CHAR_MAP[MODE_PUNCT][punctTable[i]] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A map showing the available shift codes. (The shifts to BINARY are not
|
||||
// shown
|
||||
static final int[][] SHIFT_TABLE = new int[6][6]; // mode shift codes, per table
|
||||
static {
|
||||
for (int[] table : SHIFT_TABLE) {
|
||||
Arrays.fill(table, -1);
|
||||
}
|
||||
SHIFT_TABLE[MODE_UPPER][MODE_PUNCT] = 0;
|
||||
|
||||
SHIFT_TABLE[MODE_LOWER][MODE_PUNCT] = 0;
|
||||
SHIFT_TABLE[MODE_LOWER][MODE_UPPER] = 28;
|
||||
|
||||
SHIFT_TABLE[MODE_MIXED][MODE_PUNCT] = 0;
|
||||
|
||||
SHIFT_TABLE[MODE_DIGIT][MODE_PUNCT] = 0;
|
||||
SHIFT_TABLE[MODE_DIGIT][MODE_UPPER] = 15;
|
||||
}
|
||||
|
||||
private final byte[] text;
|
||||
private final Charset charset;
|
||||
|
||||
public HighLevelEncoder(byte[] text) {
|
||||
this.text = text;
|
||||
this.charset = null;
|
||||
}
|
||||
|
||||
public HighLevelEncoder(byte[] text, Charset charset) {
|
||||
this.text = text;
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return text represented by this encoder encoded as a {@link BitArray}
|
||||
*/
|
||||
public BitArray encode() {
|
||||
State initialState = State.INITIAL_STATE;
|
||||
if (charset != null) {
|
||||
CharacterSetECI eci = CharacterSetECI.getCharacterSetECI(charset);
|
||||
if (null == eci) {
|
||||
throw new IllegalArgumentException("No ECI code for character set " + charset);
|
||||
}
|
||||
initialState = initialState.appendFLGn(eci.getValue());
|
||||
}
|
||||
Collection<State> states = Collections.singletonList(initialState);
|
||||
for (int index = 0; index < text.length; index++) {
|
||||
int pairCode;
|
||||
int nextChar = index + 1 < text.length ? text[index + 1] : 0;
|
||||
switch (text[index]) {
|
||||
case '\r':
|
||||
pairCode = nextChar == '\n' ? 2 : 0;
|
||||
break;
|
||||
case '.' :
|
||||
pairCode = nextChar == ' ' ? 3 : 0;
|
||||
break;
|
||||
case ',' :
|
||||
pairCode = nextChar == ' ' ? 4 : 0;
|
||||
break;
|
||||
case ':' :
|
||||
pairCode = nextChar == ' ' ? 5 : 0;
|
||||
break;
|
||||
default:
|
||||
pairCode = 0;
|
||||
}
|
||||
if (pairCode > 0) {
|
||||
// We have one of the four special PUNCT pairs. Treat them specially.
|
||||
// Get a new set of states for the two new characters.
|
||||
states = updateStateListForPair(states, index, pairCode);
|
||||
index++;
|
||||
} else {
|
||||
// Get a new set of states for the new character.
|
||||
states = updateStateListForChar(states, index);
|
||||
}
|
||||
}
|
||||
// We are left with a set of states. Find the shortest one.
|
||||
State minState = Collections.min(states, new Comparator<State>() {
|
||||
@Override
|
||||
public int compare(State a, State b) {
|
||||
return a.getBitCount() - b.getBitCount();
|
||||
}
|
||||
});
|
||||
// Convert it to a bit array, and return.
|
||||
return minState.toBitArray(text);
|
||||
}
|
||||
|
||||
// We update a set of states for a new character by updating each state
|
||||
// for the new character, merging the results, and then removing the
|
||||
// non-optimal states.
|
||||
private Collection<State> updateStateListForChar(Iterable<State> states, int index) {
|
||||
Collection<State> result = new LinkedList<>();
|
||||
for (State state : states) {
|
||||
updateStateForChar(state, index, result);
|
||||
}
|
||||
return simplifyStates(result);
|
||||
}
|
||||
|
||||
// Return a set of states that represent the possible ways of updating this
|
||||
// state for the next character. The resulting set of states are added to
|
||||
// the "result" list.
|
||||
private void updateStateForChar(State state, int index, Collection<State> result) {
|
||||
char ch = (char) (text[index] & 0xFF);
|
||||
boolean charInCurrentTable = CHAR_MAP[state.getMode()][ch] > 0;
|
||||
State stateNoBinary = null;
|
||||
for (int mode = 0; mode <= MODE_PUNCT; mode++) {
|
||||
int charInMode = CHAR_MAP[mode][ch];
|
||||
if (charInMode > 0) {
|
||||
if (stateNoBinary == null) {
|
||||
// Only create stateNoBinary the first time it's required.
|
||||
stateNoBinary = state.endBinaryShift(index);
|
||||
}
|
||||
// Try generating the character by latching to its mode
|
||||
if (!charInCurrentTable || mode == state.getMode() || mode == MODE_DIGIT) {
|
||||
// If the character is in the current table, we don't want to latch to
|
||||
// any other mode except possibly digit (which uses only 4 bits). Any
|
||||
// other latch would be equally successful *after* this character, and
|
||||
// so wouldn't save any bits.
|
||||
State latchState = stateNoBinary.latchAndAppend(mode, charInMode);
|
||||
result.add(latchState);
|
||||
}
|
||||
// Try generating the character by switching to its mode.
|
||||
if (!charInCurrentTable && SHIFT_TABLE[state.getMode()][mode] >= 0) {
|
||||
// It never makes sense to temporarily shift to another mode if the
|
||||
// character exists in the current mode. That can never save bits.
|
||||
State shiftState = stateNoBinary.shiftAndAppend(mode, charInMode);
|
||||
result.add(shiftState);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.getBinaryShiftByteCount() > 0 || CHAR_MAP[state.getMode()][ch] == 0) {
|
||||
// It's never worthwhile to go into binary shift mode if you're not already
|
||||
// in binary shift mode, and the character exists in your current mode.
|
||||
// That can never save bits over just outputting the char in the current mode.
|
||||
State binaryState = state.addBinaryShiftChar(index);
|
||||
result.add(binaryState);
|
||||
}
|
||||
}
|
||||
|
||||
private static Collection<State> updateStateListForPair(Iterable<State> states, int index, int pairCode) {
|
||||
Collection<State> result = new LinkedList<>();
|
||||
for (State state : states) {
|
||||
updateStateForPair(state, index, pairCode, result);
|
||||
}
|
||||
return simplifyStates(result);
|
||||
}
|
||||
|
||||
private static void updateStateForPair(State state, int index, int pairCode, Collection<State> result) {
|
||||
State stateNoBinary = state.endBinaryShift(index);
|
||||
// Possibility 1. Latch to MODE_PUNCT, and then append this code
|
||||
result.add(stateNoBinary.latchAndAppend(MODE_PUNCT, pairCode));
|
||||
if (state.getMode() != MODE_PUNCT) {
|
||||
// Possibility 2. Shift to MODE_PUNCT, and then append this code.
|
||||
// Every state except MODE_PUNCT (handled above) can shift
|
||||
result.add(stateNoBinary.shiftAndAppend(MODE_PUNCT, pairCode));
|
||||
}
|
||||
if (pairCode == 3 || pairCode == 4) {
|
||||
// both characters are in DIGITS. Sometimes better to just add two digits
|
||||
State digitState = stateNoBinary
|
||||
.latchAndAppend(MODE_DIGIT, 16 - pairCode) // period or comma in DIGIT
|
||||
.latchAndAppend(MODE_DIGIT, 1); // space in DIGIT
|
||||
result.add(digitState);
|
||||
}
|
||||
if (state.getBinaryShiftByteCount() > 0) {
|
||||
// It only makes sense to do the characters as binary if we're already
|
||||
// in binary mode.
|
||||
State binaryState = state.addBinaryShiftChar(index).addBinaryShiftChar(index + 1);
|
||||
result.add(binaryState);
|
||||
}
|
||||
}
|
||||
|
||||
private static Collection<State> simplifyStates(Iterable<State> states) {
|
||||
Deque<State> result = new LinkedList<>();
|
||||
for (State newState : states) {
|
||||
boolean add = true;
|
||||
for (Iterator<State> iterator = result.iterator(); iterator.hasNext();) {
|
||||
State oldState = iterator.next();
|
||||
if (oldState.isBetterThanOrEqualTo(newState)) {
|
||||
add = false;
|
||||
break;
|
||||
}
|
||||
if (newState.isBetterThanOrEqualTo(oldState)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
if (add) {
|
||||
result.addFirst(newState);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
45
src/aztec/encoder/SimpleToken.java
Normal file
45
src/aztec/encoder/SimpleToken.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.aztec.encoder;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
final class SimpleToken extends Token {
|
||||
|
||||
// For normal words, indicates value and bitCount
|
||||
private final short value;
|
||||
private final short bitCount;
|
||||
|
||||
SimpleToken(Token previous, int value, int bitCount) {
|
||||
super(previous);
|
||||
this.value = (short) value;
|
||||
this.bitCount = (short) bitCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
void appendTo(BitArray bitArray, byte[] text) {
|
||||
bitArray.appendBits(value, bitCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
int value = this.value & ((1 << bitCount) - 1);
|
||||
value |= 1 << bitCount;
|
||||
return '<' + Integer.toBinaryString(value | (1 << bitCount)).substring(1) + '>';
|
||||
}
|
||||
|
||||
}
|
||||
195
src/aztec/encoder/State.java
Normal file
195
src/aztec/encoder/State.java
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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.aztec.encoder;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* State represents all information about a sequence necessary to generate the current output.
|
||||
* Note that a state is immutable.
|
||||
*/
|
||||
final class State {
|
||||
|
||||
static final State INITIAL_STATE = new State(Token.EMPTY, HighLevelEncoder.MODE_UPPER, 0, 0);
|
||||
|
||||
// The current mode of the encoding (or the mode to which we'll return if
|
||||
// we're in Binary Shift mode.
|
||||
private final int mode;
|
||||
// The list of tokens that we output. If we are in Binary Shift mode, this
|
||||
// token list does *not* yet included the token for those bytes
|
||||
private final Token token;
|
||||
// If non-zero, the number of most recent bytes that should be output
|
||||
// in Binary Shift mode.
|
||||
private final int binaryShiftByteCount;
|
||||
// The total number of bits generated (including Binary Shift).
|
||||
private final int bitCount;
|
||||
private final int binaryShiftCost;
|
||||
|
||||
private State(Token token, int mode, int binaryBytes, int bitCount) {
|
||||
this.token = token;
|
||||
this.mode = mode;
|
||||
this.binaryShiftByteCount = binaryBytes;
|
||||
this.bitCount = bitCount;
|
||||
this.binaryShiftCost = calculateBinaryShiftCost(binaryBytes);
|
||||
}
|
||||
|
||||
int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
Token getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
int getBinaryShiftByteCount() {
|
||||
return binaryShiftByteCount;
|
||||
}
|
||||
|
||||
int getBitCount() {
|
||||
return bitCount;
|
||||
}
|
||||
|
||||
State appendFLGn(int eci) {
|
||||
State result = shiftAndAppend(HighLevelEncoder.MODE_PUNCT, 0); // 0: FLG(n)
|
||||
Token token = result.token;
|
||||
int bitsAdded = 3;
|
||||
if (eci < 0) {
|
||||
token = token.add(0, 3); // 0: FNC1
|
||||
} else if (eci > 999999) {
|
||||
throw new IllegalArgumentException("ECI code must be between 0 and 999999");
|
||||
} else {
|
||||
byte[] eciDigits = Integer.toString(eci).getBytes(StandardCharsets.ISO_8859_1);
|
||||
token = token.add(eciDigits.length, 3); // 1-6: number of ECI digits
|
||||
for (byte eciDigit : eciDigits) {
|
||||
token = token.add(eciDigit - '0' + 2, 4);
|
||||
}
|
||||
bitsAdded += eciDigits.length * 4;
|
||||
}
|
||||
return new State(token, mode, 0, bitCount + bitsAdded);
|
||||
}
|
||||
|
||||
// Create a new state representing this state with a latch to a (not
|
||||
// necessary different) mode, and then a code.
|
||||
State latchAndAppend(int mode, int value) {
|
||||
int bitCount = this.bitCount;
|
||||
Token token = this.token;
|
||||
if (mode != this.mode) {
|
||||
int latch = HighLevelEncoder.LATCH_TABLE[this.mode][mode];
|
||||
token = token.add(latch & 0xFFFF, latch >> 16);
|
||||
bitCount += latch >> 16;
|
||||
}
|
||||
int latchModeBitCount = mode == HighLevelEncoder.MODE_DIGIT ? 4 : 5;
|
||||
token = token.add(value, latchModeBitCount);
|
||||
return new State(token, mode, 0, bitCount + latchModeBitCount);
|
||||
}
|
||||
|
||||
// Create a new state representing this state, with a temporary shift
|
||||
// to a different mode to output a single value.
|
||||
State shiftAndAppend(int mode, int value) {
|
||||
Token token = this.token;
|
||||
int thisModeBitCount = this.mode == HighLevelEncoder.MODE_DIGIT ? 4 : 5;
|
||||
// Shifts exist only to UPPER and PUNCT, both with tokens size 5.
|
||||
token = token.add(HighLevelEncoder.SHIFT_TABLE[this.mode][mode], thisModeBitCount);
|
||||
token = token.add(value, 5);
|
||||
return new State(token, this.mode, 0, this.bitCount + thisModeBitCount + 5);
|
||||
}
|
||||
|
||||
// Create a new state representing this state, but an additional character
|
||||
// output in Binary Shift mode.
|
||||
State addBinaryShiftChar(int index) {
|
||||
Token token = this.token;
|
||||
int mode = this.mode;
|
||||
int bitCount = this.bitCount;
|
||||
if (this.mode == HighLevelEncoder.MODE_PUNCT || this.mode == HighLevelEncoder.MODE_DIGIT) {
|
||||
int latch = HighLevelEncoder.LATCH_TABLE[mode][HighLevelEncoder.MODE_UPPER];
|
||||
token = token.add(latch & 0xFFFF, latch >> 16);
|
||||
bitCount += latch >> 16;
|
||||
mode = HighLevelEncoder.MODE_UPPER;
|
||||
}
|
||||
int deltaBitCount =
|
||||
(binaryShiftByteCount == 0 || binaryShiftByteCount == 31) ? 18 :
|
||||
(binaryShiftByteCount == 62) ? 9 : 8;
|
||||
State result = new State(token, mode, binaryShiftByteCount + 1, bitCount + deltaBitCount);
|
||||
if (result.binaryShiftByteCount == 2047 + 31) {
|
||||
// The string is as long as it's allowed to be. We should end it.
|
||||
result = result.endBinaryShift(index + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create the state identical to this one, but we are no longer in
|
||||
// Binary Shift mode.
|
||||
State endBinaryShift(int index) {
|
||||
if (binaryShiftByteCount == 0) {
|
||||
return this;
|
||||
}
|
||||
Token token = this.token;
|
||||
token = token.addBinaryShift(index - binaryShiftByteCount, binaryShiftByteCount);
|
||||
return new State(token, mode, 0, this.bitCount);
|
||||
}
|
||||
|
||||
// Returns true if "this" state is better (or equal) to be in than "that"
|
||||
// state under all possible circumstances.
|
||||
boolean isBetterThanOrEqualTo(State other) {
|
||||
int newModeBitCount = this.bitCount + (HighLevelEncoder.LATCH_TABLE[this.mode][other.mode] >> 16);
|
||||
if (this.binaryShiftByteCount < other.binaryShiftByteCount) {
|
||||
// add additional B/S encoding cost of other, if any
|
||||
newModeBitCount += other.binaryShiftCost - this.binaryShiftCost;
|
||||
} else if (this.binaryShiftByteCount > other.binaryShiftByteCount && other.binaryShiftByteCount > 0) {
|
||||
// maximum possible additional cost (we end up exceeding the 31 byte boundary and other state can stay beneath it)
|
||||
newModeBitCount += 10;
|
||||
}
|
||||
return newModeBitCount <= other.bitCount;
|
||||
}
|
||||
|
||||
BitArray toBitArray(byte[] text) {
|
||||
List<Token> symbols = new ArrayList<>();
|
||||
for (Token token = endBinaryShift(text.length).token; token != null; token = token.getPrevious()) {
|
||||
symbols.add(token);
|
||||
}
|
||||
BitArray bitArray = new BitArray();
|
||||
// Add each token to the result in forward order
|
||||
for (int i = symbols.size() - 1; i >= 0; i--) {
|
||||
symbols.get(i).appendTo(bitArray, text);
|
||||
}
|
||||
return bitArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s bits=%d bytes=%d", HighLevelEncoder.MODE_NAMES[mode], bitCount, binaryShiftByteCount);
|
||||
}
|
||||
|
||||
private static int calculateBinaryShiftCost(int binaryShiftByteCount) {
|
||||
if (binaryShiftByteCount > 62) {
|
||||
return 21; // B/S with extended length
|
||||
}
|
||||
if (binaryShiftByteCount > 31) {
|
||||
return 20; // two B/S
|
||||
}
|
||||
if (binaryShiftByteCount > 0) {
|
||||
return 10; // one B/S
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
46
src/aztec/encoder/Token.java
Normal file
46
src/aztec/encoder/Token.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.aztec.encoder;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
abstract class Token {
|
||||
|
||||
static final Token EMPTY = new SimpleToken(null, 0, 0);
|
||||
|
||||
private final Token previous;
|
||||
|
||||
Token(Token previous) {
|
||||
this.previous = previous;
|
||||
}
|
||||
|
||||
final Token getPrevious() {
|
||||
return previous;
|
||||
}
|
||||
|
||||
final Token add(int value, int bitCount) {
|
||||
return new SimpleToken(this, value, bitCount);
|
||||
}
|
||||
|
||||
final Token addBinaryShift(int start, int byteCount) {
|
||||
//int bitCount = (byteCount * 8) + (byteCount <= 31 ? 10 : byteCount <= 62 ? 20 : 21);
|
||||
return new BinaryShiftToken(this, start, byteCount);
|
||||
}
|
||||
|
||||
abstract void appendTo(BitArray bitArray, byte[] text);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user